앞서 작업까지의 결과물인 FfmpegTranscoder 클래스의 코드를 보자.
public class FfmpegTranscoder implements Transcoder {
@Override
public List<File> transcode(File multimediaFile,
List<OutputFormat> outputFormats) {
List<File> results = new ArrayList<File>();
for (OutputFormat format : outputFormats) {
results.add(transcode(multimediaFile, format));
}
return results;
}
private File transcode(File sourceFile, OutputFormat format) {
IMediaReader reader = ToolFactory.makeReader(sourceFile
.getAbsolutePath());
String outputFile = "outputFile.mp4"; // 항상 확장자, 파일 명이 같음
VideoConverter converter = new VideoConverter(outputFile, reader,
format);
...
}
}
원하는 파일이 MP4인지 AVI인지에 상관없이 항상 파일명은 outputFile.mp4 이다. 이제 이 부분을 건드려볼 차례이다.
우선, 확장자는 멀티미디어 컨테이너에 따라 다르다. 예를 들어, AVI, MP4, MOV, FLV 등이 컨테이너이며, 보통 이 이름을 파일의 확장자로 사용한다. 또한, 각 컨테이너의 특징에 따라 사용가능한 코덱에 제한이 있다고도 한다. 하튼, 필자는 이 부분의 전문가는 아니기 때문에 컨테이너 포맷이나 코덱 등의 정보가 궁금한 사람은 따로 정보를 검색해보시라.
컨테이너 모델 추가
확장자를 검사하도록 테스트를 먼저 수정하자. 확장자를 검사하기 적당한 위치는 VideoFormatVerifier 클래스이다. 이 클래스에 확장자를 확인하는 기능을 추가해 넣도록 하자.
public class VideoFormatVerifier {
...
public VideoFormatVerifier(OutputFormat expectedFormat, File videoFile) {
this.expectedFormat = expectedFormat;
this.videoFile = videoFile;
}
public void verify() {
try {
assertExtension();
makeContainer();
extractMetaInfoOfVideo();
assertVideoFile();
} finally {
closeContainer();
}
}
private void assertExtension() {
assertEquals(expectedFormat.getFileExtenstion(), fileExtenstion());
}
private String fileExtenstion() {
String filePath = videoFile.getAbsolutePath();
int lastDotIdx = filePath.lastIndexOf(".");
String extension = filePath.substring(lastDotIdx + 1);
return extension;
}
OutputFormat의 getFileExtension() 메서드가 파일 확장자를 제공한다고 했다. 이 메서드가 없으니 아래와 같이 추가해서 기존의 테스트가 통과되도록 하자.
public class OutputFormat {
private int width;
private int height;
private int bitrate;
...
public String getFileExtension() {
return "mp4"; //일단 테스트가 통과하도록
}
public VideoCodec getVideoCodec() {
return videoCodec;
}
public AudioCodec getAudioCodec() {
return audioCodec;
}
}
FfmpegTranscoderTest를 실행해 보자. 녹색! 통과다.
이제 본격적으로 컨테이너 정보를 추가해 보자. 파일의 확장자를 결졍짓는 것은 컨테이너이므로, 코덱과 별개로 컨테이너 정보를 추가하자.
public enum Container {
MP4(VideoCodec.H264, AudioCodec.AAC, "mp4"),
AVI(VideoCodec.MPEG4,AudioCodec.MP3, "avi");
private VideoCodec defaultVideoCodec;
private AudioCodec defaultAudioCodec;
private String fileExtension;
private Container(VideoCodec defaultVideoCodec,
AudioCodec defaultAudioCodec, String fileExtenstion) {
this.defaultVideoCodec = defaultVideoCodec;
this.defaultAudioCodec = defaultAudioCodec;
this.fileExtension = fileExtenstion;
}
public VideoCodec getDefaultVideoCodec() {
return defaultVideoCodec;
}
public AudioCodec getDefaultAudioCodec() {
return defaultAudioCodec;
}
public String getFileExtension() {
return fileExtension;
}
}
Container는 enum 타입으로 각 값은 해당 컨테이너의 기본 코덱 정보와 확장자 정보를 갖도록 했다.
Container를 추가했으므로, 변환 결과물 정보를 담는 OutputFormat에 추가할 차례이다. OutputFormat이 Container를 갖도록 필드를 추가혹, 생성자를 통해서 Container를 전달받도록 수정하자.
public class OutputFormat {
private int width;
private int height;
private int bitrate;
private Container container;
private VideoCodec videoCodec;
private AudioCodec audioCodec;
public OutputFormat(int width, int height, int bitrate,
Container container, VideoCodec videoCodec, AudioCodec audioCodec) {
this.width = width;
this.height = height;
this.bitrate = bitrate;
this.container = container;
this.videoCodec = videoCodec;
this.audioCodec = audioCodec;
}
...
public String getFileExtension() {
return container.getFileExtension();
}
...
}
OutputFormat에 getFileExtension() 메서드가 추가되었다. 이제 VideoFormatVerifier 테스트에 추가했던 확장자 확인 코드가 정상적으로 컴파일된다.
public class VideoFormatVerifier {
...
...
private void assertExtension() {
assertEquals(expectedFormat.getFileExtenstion(), fileExtenstion()); // 컴파일 됨
}
위 코드는 정상적으로 컴파일되지만, OutputFormat 생성자에 새로운 파라미터가 추가되었기 때문에, OutputFormat 객체를 생성하는 테스트 코드에서는 컴파일 에러가 발생한다.
public class VideoConverterTest {
...
@Test
public void transcode() {
IMediaReader reader = ToolFactory.makeReader(SOURCE_FILE);
OutputFormat outputFormat = new OutputFormat(WIDTH, HEIGHT, BITRATE,
VideoCodec.H264, AudioCodec.AAC);
VideoConverter writer = new VideoConverter(TRANSCODED_FILE, reader,
outputFormat);
...
}
}
public class FfmpegTranscoderTest {
...
@Test
public void transcodeWithOneOutputFormat() {
File multimediaFile = new File("src/test/resources/sample.avi");
List<OutputFormat> outputFormats = new ArrayList<OutputFormat>();
outputFormats.add(new OutputFormat(160, 120, 150, VideoCodec.H264,
AudioCodec.AAC));
List<File> transcodedFiles = transcoder.transcode(multimediaFile,
outputFormats);
...
}
}
컴파일 에러가 나지 않도록 OutputFormat 생성자에 Container를 값으로 전달해 준다.
public class VideoConverterTest {
@Test
public void transcode() {
IMediaReader reader = ToolFactory.makeReader(SOURCE_FILE);
OutputFormat outputFormat = new OutputFormat(WIDTH, HEIGHT, BITRATE,
Container.MP4, VideoCodec.H264, AudioCodec.AAC);
VideoConverter writer = new VideoConverter(TRANSCODED_FILE, reader,
outputFormat);
...
}
}
public class FfmpegTranscoderTest {
...
@Test
public void transcodeWithOneOutputFormat() {
File multimediaFile = new File("src/test/resources/sample.avi");
List<OutputFormat> outputFormats = new ArrayList<OutputFormat>();
outputFormats.add(new OutputFormat(160, 120, 150, Container.MP4,
VideoCodec.H264, AudioCodec.AAC));
...
}
}
위와 같이 변경했으면, 두 테스트가 정상적으로 동작하는지 다시 테스트 해 본다. 녹색! 통과다.
AVI 변환 추가
이제 AVI 파일로 변환해주는 기능을 추가해 넣고 테스트를 해 보자. 이를 위해 FfmpegTranscoderTest를 다음과 같이 작성하였다. (음,, 바로 이전 코드하고 좀 많이 변경되었는데, 하나 하나 설명하지 않고 한 번에 넘어간 점 양해 바란다. 중복된 부분을 별도 메서드로 분리하고 setup에서 초기화를 진행하도록 수정했다.)
public class FfmpegTranscoderTest {
private Transcoder transcoder;
private File multimediaFile;
private List<OutputFormat> outputFormats;
private OutputFormat mp4Format;
private OutputFormat aviFormat;
@Before
public void setup() {
outputFormats = new ArrayList<OutputFormat>();
mp4Format = new OutputFormat(160, 120, 150, Container.MP4,
VideoCodec.H264, AudioCodec.AAC);
aviFormat = new OutputFormat(160, 120, 150, Container.AVI,
VideoCodec.MPEG4, AudioCodec.MP3);
multimediaFile = new File("src/test/resources/sample.avi");
transcoder = new FfmpegTranscoder(namingRule);
}
@Test
public void transcodeWithOneMp4OutputFormat() {
outputFormats.add(mp4Format);
executeTranscoderAndAssert();
}
private void executeTranscoderAndAssert() {
List<File> transcodedFiles = transcoder.transcode(multimediaFile,
outputFormats);
assertEquals(1, transcodedFiles.size());
assertTrue(transcodedFiles.get(0).exists());
VideoFormatVerifier.verifyVideoFormat(outputFormats.get(0),
transcodedFiles.get(0));
}
@Test
public void transcodeWithOneAviOutputFormat() {
outputFormats.add(aviFormat);
executeTranscoderAndAssert();
}
}
AVI 변환에 대한 테스트 코드를 넣었다. 테스트 실행! 실패다. 실패 이유는 다음과 같다.
실패 메시지:
org.junit.ComparisonFailure: expected:<[avi]> but was:<[mp4]>
at org.junit.Assert.assertEquals(Assert.java:123)
at org.junit.Assert.assertEquals(Assert.java:145)
at org.chimi.s4t.infra.ffmpeg.VideoFormatVerifier.assertExtension(VideoFormatVerifier.java:47)
at org.chimi.s4t.infra.ffmpeg.VideoFormatVerifier.verify(VideoFormatVerifier.java:37)
...
실재 난 테스트 메서드:
@Test
public void transcodeWithOneAviOutputFormat() {
outputFormats.add(aviFormat);
executeTranscoderAndAssert();
}
}
파일명이 AVI이길 기대했는데 실제 파일 이름은 MP4 여서 테스트를 통과하지 못했다. 이제 테스트를 통과시켜 보자. FfmpegTranscoder가 OutputFormat을 이용해서 파일의 확장자를 결정하도록 수정했다.
public class FfmpegTranscoder implements Transcoder {
@Override
public List<File> transcode(File multimediaFile,
List<OutputFormat> outputFormats) {
List<File> results = new ArrayList<File>();
for (OutputFormat format : outputFormats) {
results.add(transcode(multimediaFile, format));
}
return results;
}
private File transcode(File sourceFile, OutputFormat format) {
IMediaReader reader = ToolFactory.makeReader(sourceFile
.getAbsolutePath());
String outputFile = getFileName(format);
VideoConverter converter = new VideoConverter(outputFile, reader,
format);
reader.addListener(converter);
while (reader.readPacket() == null)
do {
} while (false);
return new File(outputFile);
}
private String getFileName(OutputFormat format) {
return "outputFile." + format.getFileExtension(); // 기존 "outputFile.mp4"
}
}
테스트를 실행해보자. 녹색! 통과다!
지금까지 한 번에 1개의 변환을 처리하는 부분에 집중했는데, 다음 연습 8에서는 여러 형식으로 변환하는 기능을 추가해 보도록 하자.