이번에 내가 맡게 된 업무는 배너 이미지(소재) 만 등록 되던 기능에
운영중인 서비스 환경에 동영상 업로드 및 압축 기능을 추가하는거였다.
개발을 잘 모르는 사람들은 "아니 이미 파일 업로드 기능이 있는데? 그냥 기존에 업로드 하던 부분 조금만
손보면 되는거 아니야?" 이렇게 말을 할 수도 있지만
개발은 운영중인 서비스에 어떤 기능을 도입하는게
해당 기능을 새로 만드는 것보다 어렵다.. 🤨
그러니 운영 중인 서비스에 어느 기능을 넣을 땐 최대한
많이 검토하는 습관을 길러야한다!!
제일 힘들었던건 ffmpeg가 국내에 정보가 많이 없는 것 같다. 🤬
그래서 따라하기 좋은 예제를 github 이슈에 모아놓았다..
github 링크는 아래 레퍼런스에 남김 :)
FFmpeg
FFmpeg는 오디오와 비디오를 다루기 위한 오픈 소스 멀티미디어 프레임워크😁.
이 도구는 다양한 형식의 미디어 파일을 변환, 편집, 인코딩, 디코딩하는 데 사용되고 FFmpeg는 커맨드 라인 인터페이스를 제공하며, 다양한 옵션을 통해 다양한 작업을 수행할 수 있다😎
사용 예시 커맨드
1. 비디오 변환:
ffmpeg -i input.mp4 -c:v libx264 -c:a aac output.mp4
이 커맨드는 input.mp4` 파일을 H.264 비디오 코덱과 AAC 오디오 코덱을 사용하여 `output.mp4`로 변환하는 커맨드
2. 오디오 추출:
ffmpeg -i input.mp4 -vn output.mp3
이 커맨드는 input.mp4` 파일에서 오디오를 추출하여 `output.mp3` 파일로 저장하는 커맨드
3. 비디오 자르기:
ffmpeg -i input.mp4 -ss 00:00:10 -t 00:00:20 -c:v copy -c:a copy output.mp4
이 커맨드는 input.mp4` 파일에서 10초부터 30초까지의 구간을 자르아 `output.mp4`로 저장하는 커맨드
FFprobe
FFprobe는 FFmpeg 프로젝트의 일부로서, 미디어 파일의 정보를 분석하는 도구인데
이를 통해 오디오와 비디오 파일의 코덱, 해상도, 비트레이트 등의 정보를 확인할 수 있다! 😆
사용 예시 커맨드
ffprobe input.mp4
이 커맨드는 `input.mp4` 파일에 대한 정보를 출력하는 커맨드 옵션으로는 json이나 xml 형식 문자열로 추출 가능
ffprobe -show_streams -select_streams v:0 -print_format json input.mp4
이 커맨드는 `input.mp4` 파일의 비디오 스트림에 대한 정보를 JSON 형식으로 출력합니다.
이 외에도 FFmpeg와 FFprobe에는 수많은 옵션과 기능이 있으며, 위의 예시는 일부에 불과하니 커맨드를 실행하기 전에 해당 도구의 공식문서를 꼭 참고하도록 해 그리고 위키도!
진행상황
dropzone.js를 커스터마이징 하여
동영상 업로드 및 썸네일 추출 이후 서버에 파일 및 데이터 처리 까지 완료
용량 5MB 초과 시 압축 로직
미디어 메타 인포 긁어오는 기능 추가 ( 해당 동영상에 비율로 업로드 가능 여부를 체크)
업무 중 이슈/고민 또는 해결내용
1. 로컬(개발) 환경에서 이상 없던 코드가 운영 환경에 반영하니 ffmpeg ,ffprobe 권한이 없어
접근 거부 급하게 개발하다보니 권한도 신경안쓰다가 운영환경에서 로그보고 뒤늦게 깨달았다..
(이런 바보같은 실수를 한게 너무 창피하다 😭)
-> 리눅스 서버 war 구동 중인 계정에 권한 변경으로 해결
2. 미디어 메타 인포 읽어 map으로 변환하여 클라이언트에서 비교하는 로직
-> 아 이거 하면서 어질어질 했다 일단 추후에 나랑 비슷한걸 개발하게 되는 사람들에게 말해주면
미디어 인포를 읽을 때는 ffmpeg가 아니라 ffprobe를 사용하자 아래 예제 코드에
내가 적용한 코드를 첨부해놓았다 🤪 아래 코드에 ffprobe를 이용한 커맨드도 있음 😂
2-1. 무해화 로직 거친 파일을 검증해야되었기 때문에 서버에 파일 보낼 때는 바이러스 검사 후 이상 없는 파일을 보내 temp 파일로 메타 인포만 읽고 삭제하였다.. (추후 용량 고려하여)
참고 레퍼런스
ffmpeg 공식 문서
https://www.ffmpeg.org/ffmpeg.html
https://github.com/DongJu-Na/sbwvss-mvc
ffmpeg 이용하면서 여러 기능 추가해본 git
https://github.com/DongJu-Na/sbwvss-mvc/issues/1
효과나 사용할만한 예제 모아놓은 이슈 리스트
Example Code
미디어 인포 읽어오는 기능 (BackEnd)
@PostMapping(value = "/getVideoMetaInfo" , produces = "application/json")
@CheckAuth(value = true)
public @ResponseBody Map<String,Object> getVideoInfo(@RequestParam(value = "file" , required = true) MultipartFile file
){
Map<String,Object> result = new HashMap<String,Object>();
try {
File tempFile = File.createTempFile("temp", ".mp4");
file.transferTo(tempFile);
String tempPath = tempFile.getAbsolutePath().replaceAll("\\\\", "/");
ProcessBuilder processBuilder = new ProcessBuilder(ffprobe ,"-v","error","-show_format" ,"-show_streams" , "-of" ,"json" ,tempPath);
processBuilder.redirectErrorStream(true);
Process process = processBuilder.start();
InputStream inputStream = process.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String line;
StringBuilder jsonOutput = new StringBuilder();
Map<String,Object> jsonMap = null;
while ((line = bufferedReader.readLine()) != null) {
jsonOutput.append(line);
}
int exitCode = process.waitFor();
if(jsonOutput.length() > 0 && exitCode == 0){
ObjectMapper om = new ObjectMapper();
jsonMap = om.readValue(jsonOutput.toString(), new TypeReference<Map<String,Object>>() {});
}
result.put("metaData",jsonMap);
result.put("resultCode",exitCode);
tempFile.delete();
} catch (Exception e) { //IOException | InterruptedException
e.printStackTrace();
result.put("metaData",null);
result.put("resultCode",1);
}
return result;
}
미디어 메타 인포 읽어오기 FrontEnd
function getVideoMetaInfo(_file){
var _result = null;
var saveData = new FormData();
saveData.append("file", _file);
$.ajax({
async: false,
crossDomain: true,
url: '/item/getVideoMetaInfo',
method: "POST",
type: "POST",
data: saveData,
mimeType: "multipart/form-data",
xhrFields: {
withCredentials: false
},
dataType: "json",
processData: false,
contentType: false,
success: function(data) {
_result = data;
},
error: function (request, status, error) {
console.error(status,error);
},
cache: false
});
return _result;
}
동영상 압축 코드 예제
ProcessBuilder processBuilder = new ProcessBuilder(
ffmpeg,
"-y",
"-v",
"error",
"-i",
filePath + uploadPath,
"-strict",
"experimental",
"-f",
"mp4",
"-vcodec",
"libx264",
"-s",
"720x480",
"-r",
"24/1",
"-acodec",
"aac",
"-ac",
"1",
"-ar",
"48000",
"-b:a",
"32768",
"-sn",
filePath + "/" + formatFileName
);
Process process = processBuilder.start();
int exitCode = process.waitFor();
if (exitCode == 0) {
if (!"local".equals(profileActive)) {
Runtime.getRuntime().exec("chmod -R 777 " + filePath + "/" + formatFileName);
}
File compressedFile = new File(filePath + "/" + formatFileName);
long compressedFileSizeInBytes = compressedFile.length();
if(compressedFileSizeInBytes > fiveMegaBytes){
uploadPath = "formatErrer-01";
return uploadPath;
}
uploadPath = uploadPath.replace(newFileName,formatFileName);
}else{
uploadPath = "formatErrer-01";
}
'Develop > Back-End' 카테고리의 다른 글
Selenium 을 이용하여 특정 URL 요청과 응답 값 모니터링 하는 기능 만들기 (77) | 2023.09.07 |
---|---|
타임리프(Thymeleaf) 본격적으로 사용하기 ( +@ 타임리프 벤치마크 성능 비교) (59) | 2023.08.31 |
타임리프(Thymeleaf) 사용 방법 및 문법 정리 (35) | 2023.08.08 |
[Java] 스트림(Stream) API 사용 방법 / 외부반복 내부반복 차이점 (4) | 2023.08.05 |
[Spring Cloud] 스프링 클라우드 란 무엇인가요? (4) | 2023.04.27 |