티스토리 뷰

Java

FFMPEG Wrapper Library 사용법

Jodu 2018. 5. 4. 15:06
반응형

[서론]

이번에는 두 개 영상의 시간 정보를 가지고 두 영상을 하나의 영상으로 합쳐주는 프로그램을 만들어보고 싶었다. 하나의 단일 프로그램이 아니라 서버에서 클라이언트로 부터 전달 받은 영상의 시간 정보를 가지고 새로운 영상을 생성하고 사용자는 그것을 클라이언트가 다운로드 받는 형태로 만들어보고자 했다. 영상을 직접 자르고 붙여서 하나의 파일로 만들어주는 것을 직접 구현하기 전에 사용할 수 있는 라이브러리 및 프로그램을 찾아 보았다. 그렇게 해서 찾은 프로그램이 FFMPEG이라는 영상 관련 프로그램이였는데 FFMPEG은 상당히 유명한 프로그램 이였다. FFMPEG에 대한 설명은 링크를 타고 가서 볼 수 있다. FFMPEG은 서버에 별도로 설치를 하고 터미널을 통해서 명령어를 받아서 동작을 하는데 서버에서 콘솔 명령어를 보낼 수 있어야 한다. 이것을 쉽게 해주는 라이브러리가 FFMPEG JAVA라는 라이브러리이다. 해당 라이브러리를 사용하면 프로그래밍을 통해 손쉽게 ffmpeg 명령어을 실행 할 수 있다. 이번 포스팅에서는 해당 라이브러리를 사용하는 법에 대해서 설명한다. 

[FFMPEG 기본 명령]

  1. 영상 구간 자르는 명령어
        ffmpeg -i '영상경로.mp4' -ss 5000 -t 10 '출력물경로'
    영상의 오디오를 제거 하고 싶다면 
        ffmpeg -i '영상경로.mp4' -ss 5000 -t 10 -an '출력물경로'
    과 같이 출력물경로 바로 앞에 -an 옵션을 적용하면 된다. 
  2. 여러개의 영상 합치는 명령어
    mp4를 사용하는 경우는 concat명령어가 작동하지 않는다. 그래서 text파일에 여러 영상들의 경로를 적어 놓고 해당 text파일을 읽어서 영상을 합쳐야 한다. 
    여기서는 mp4를 기준으로 설명을 할 것이므로 text파일을 이용해서 영상을 합치는 명령어를 설명한다.
        ffmpeg -f concat -safe 0 -i `text file input path` `out path`
    -safe 옵션은 출력파일명에 대한 안정성을 보장 하기 위한 옵션이다. 
  3. 두개의 영상을 Side by Side로 위치 시키기
        ffmpeg -i `input file path 1` -i `input file path 2` -preset ultrafast -filter_complex "[0:v]setpts=PTS-STARTPTS, pad=iw*2:ih[bg]; [1:v]setpts=PTS-STARTPTS[fg]; [bg][fg]overlay=w" `out put file path'

    위 명령어를 보게 되면 두개의 입력 영상 경로를 받아서 0번째 2번째 파일로 지정한뒤 이것을 하나로 합치는 명령을 볼 수 있다. pad라는 것에 iw는 입력받은 영상의 크기를 말하며 여기서 두개의 영상을 하나로 합칠 것이기 때문에 iw*2라는 것을 준것을 알  수 있다. 
 
[FFMPEG Wrapper만들기]
첫번째는 영상 구간을 자르는 명령어를 구현한 부분이다. 소스를 확인 해보면 정말 간단한 것을 확인 할 수 있다. 

 
package ffmpeg.timecut;

import java.io.IOException;

import net.bramp.ffmpeg.FFmpeg;
import net.bramp.ffmpeg.FFmpegExecutor;
import net.bramp.ffmpeg.FFprobe;
import net.bramp.ffmpeg.builder.FFmpegBuilder;

public class VideoTimeCut {
	/**
	 * 입력 영상의 구간을 잘라서 새로운 영상을 만드는 FFMPEG 명령어를 자바로 
	 * 구현 한 소스
	 * @param args
	 */
	private static final String inputPath = "/Users/birdhead/Desktop/videoTest/pitching.mp4"; 
	private static final String outputPath = "/Users/birdhead/Desktop/videoTest/";
	
	public static void main(String[] args) throws IOException {
		FFmpeg ffmpeg = new FFmpeg("/usr/local/bin/ffmpeg");
		FFprobe ffprobe = new FFprobe("/usr/local/bin/ffprobe");
		
		//다음번 영상 merge를 위해서 3개의 영상 생성
		for(int i=0; i<3; i++) {
			FFmpegBuilder builder = new FFmpegBuilder()
					.overrideOutputFiles(true)
					.addInput(inputPath)	 //입력 영상 경로의
					.addExtraArgs("-ss", String.valueOf(i)) //영상의 i초 위치 부
					.addExtraArgs("-t", "3") //3초 동안 재생한 영상
					.addOutput(outputPath + "pitching_out" + i + ".mp4") //outputpath 위치에
					.addExtraArgs("-an") //영상의 소리를 제거하고
					.done();	//저장
			
			FFmpegExecutor excutor = new FFmpegExecutor(ffmpeg, ffprobe);
			excutor.createJob(builder).run();
		}
	}
}

 
FFmpegBuilder 클래스를 이용해서 사용하고자 하는 명령어를 만들어 갈 수 있다. 위 소스의 영상 소리를 제거하는 부분을 보게 되면 addOutput 메소드 뒤에 선언된 것을 볼 수 있는데 이렇게 addOutput 메소드 뒤에 addExtraArgs를 사용하면 output으로 나오는 영상에 적용되는 옵션으로 동작하게 된다.

다음으로는 곧바로 연이어 영상을 합치는 부분과 Side by Side하는 소스를 연속해서 보자.

* Video Merge

package ffmpeg.merge;

import java.io.IOException;

import net.bramp.ffmpeg.FFmpeg;
import net.bramp.ffmpeg.FFmpegExecutor;
import net.bramp.ffmpeg.FFprobe;
import net.bramp.ffmpeg.builder.FFmpegBuilder;

public class VideoMerge {
	private static final String inputPath = "/Users/birdhead/Desktop/videoTest/mergeInfo.txt"; 
	private static final String outputPath = "/Users/birdhead/Desktop/videoTest/";
	
	public static void main(String[] args) throws IOException {
		FFmpeg ffmpeg = new FFmpeg("/usr/local/bin/ffmpeg");
		FFprobe ffprobe = new FFprobe("/usr/local/bin/ffprobe");
		
		FFmpegBuilder builder = new FFmpegBuilder()
				.overrideOutputFiles(true)
				.addInput(inputPath)
				.addExtraArgs("-f", "concat")
				.addExtraArgs("-safe", "0")
				.addOutput(outputPath + "mergeVideo.mp4")
				.done();
		
		FFmpegExecutor executor = new FFmpegExecutor(ffmpeg, ffprobe);
		executor.createJob(builder).run();
	}
}

 

* Video Side By Side

package ffmpeg.sidebyside;

import java.io.IOException;

import net.bramp.ffmpeg.FFmpeg;
import net.bramp.ffmpeg.FFmpegExecutor;
import net.bramp.ffmpeg.FFprobe;
import net.bramp.ffmpeg.builder.FFmpegBuilder;

public class VideoSideBySide {
	private static final String inputPath = "/Users/birdhead/Desktop/videoTest/mergeVideo.mp4";
	private static final String outputPath = "/Users/birdhead/Desktop/videoTest/sideBySide.mp4";;
	public static void main(String[] args) throws IOException {
		FFmpeg ffmpeg = new FFmpeg("/usr/local/bin/ffmpeg");
		FFprobe ffprobe = new FFprobe("/usr/local/bin/ffprobe");
		
		FFmpegBuilder builder = new FFmpegBuilder()
				.overrideOutputFiles(true)
				.addInput(inputPath)
				.addInput(inputPath)
				.addOutput(outputPath)
				.addExtraArgs("-preset", "ultrafast")
				.addExtraArgs("-filter_complex", "[0:v]setpts=PTS-STARTPTS, pad=iw*2+5:ih[bg]; [1:v]setpts=PTS-STARTPTS[fg]; [bg][fg]overlay=w+10")
				.done();
		
		FFmpegExecutor executor = new FFmpegExecutor(ffmpeg, ffprobe);
		executor.createJob(builder).run();
	}
}

 
Side By Side소스를 보면 기본명령어 설명 부분에서는 없는 w+10이라는 부분을 볼 수 있다. 이것은 영상을 바로 합치면 구분선이 없어 보기 힘들 수 있기 때문에 구분선을 넣기 위해서 추가한 옵션이다. 
이렇게 하면 

그럼 여기서 하나 추가적인 의문이 들 수 있다. 콘솔 명령일 경우 main 메소드에서 완료 되었다는 결과 를 받을 수가 없다. 그 이유는 별도의 프로그램이므로 하나의 스레드에서 동작 하는 것이 아니기 때문인데 이 경우 파일이 생성 되었다는 것을 어떻게 알 수 있을까?

그것 또한 아주 손쉽게 처리 할 수 있도록 지원 해주고 있다. ProgressListener를 사용하여 동작에 대한 완료 상태를 체크 할 수 있다. 확인 하기 위해서 VideoTimeCut 클래스에 리스너를 등록 해 보도록 한다. 

 

package ffmpeg.timecut;

import java.io.IOException;

import net.bramp.ffmpeg.FFmpeg;
import net.bramp.ffmpeg.FFmpegExecutor;
import net.bramp.ffmpeg.FFprobe;
import net.bramp.ffmpeg.builder.FFmpegBuilder;

public class VideoTimeCutWithListener {
	private static final String inputPath = "/Users/birdhead/Desktop/videoTest/pitching.mp4"; 
	private static final String outputPath = "/Users/birdhead/Desktop/videoTest/";
	
	public static void main(String[] args) throws IOException {
		FFmpeg ffmpeg = new FFmpeg("/usr/local/bin/ffmpeg");
		FFprobe ffprobe = new FFprobe("/usr/local/bin/ffprobe");
		
		//다음번 영상 merge를 위해서 3개의 영상 생성
		for(int i=0; i<3; i++) {
			final String fileName = "pitching_out" + i + ".mp4";
			FFmpegBuilder builder = new FFmpegBuilder()
					.overrideOutputFiles(true)
					.addInput(inputPath)	 //입력 영상 경로의
					.addExtraArgs("-ss", String.valueOf(i)) //영상의 i초 위치 부
					.addExtraArgs("-t", "3") //3초 동안 재생한 영상
					.addOutput(outputPath + "pitching_out" + i + ".mp4") //outputpath 위치에
					.addExtraArgs("-an") //영상의 소리를 제거하고
					.done();	//저장
			
			FFmpegExecutor excutor = new FFmpegExecutor(ffmpeg, ffprobe);
			excutor.createJob(builder, p -> {
				if(p.isEnd()) {
					System.out.println(fileName + "make success");
				}
			}).run();
		}
		
	}
}

 
람다를 이용해서 ProgressListener를 구현하여 등록 해주었다. 람다에서는 변하지 않는 값만을 참조 할 수 있다는 것을 알아두자! 여기까지가 간단하게 영상을 자르고 합치고 옆으로 배치하는 것에 대한 모든 것이다. 
 
[결론]
이번 포스팅에서는 FFmpeg에 대해서 알아보았지만 아주 일부분에 대해서만 알아 보았다. 조금 더 고급지게 사용하고 한다면 좀 더 깊게 공부를 하여야 하지만 내가 요구 받은 사항을 구현하는데는 해당 내용이면 충분 하였다. 내가 만들어 낸것은 해당 내용을 바탕으로 영상 편집 서버를 만드는 것이 었다. 사용자가 보낸 시간 정보, 그것을 바탕으로 영상을 합쳐내고 사용자는 그것의 진행 상태를 확인 할 수 있어야 했다. 그래서 사용했던것이 마지막에 사용한 ProgeressListener였다. 위의 총 3단계의 플로우가 흘러 가는 동안 각 플로우에서 구현된 ProgressListener는 작업 상태의 진행 상태에 대해서 관리 하였고 그것을 사용자가 볼 수 있도록 하였다. 마지막으로는 다운 URL을 사용자에게 전달하고 해당 URL을 통해 결과 영상을 내려 받도록 구현 하였다.
오랜만에 재밌는 내용을 구현 할 수 있는 기회 였던거 같다. 

소스 https://github.com/aq3aq4/FFmpegPosting

반응형

'Java' 카테고리의 다른 글

MapStruct란?  (0) 2021.08.01
HttpServletRequest의 getInputStream 사용시 주의 사항  (0) 2019.03.28
구글 주스와 MyBatis의 연동  (0) 2017.09.23
구글 주스 사용기[1]  (0) 2017.08.27
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함