Adds tests for FFmpeg
This commit is contained in:
@@ -8,7 +8,6 @@ import com.github.gtache.autosubtitle.VideoInfo;
|
|||||||
import com.github.gtache.autosubtitle.impl.AudioInfoImpl;
|
import com.github.gtache.autosubtitle.impl.AudioInfoImpl;
|
||||||
import com.github.gtache.autosubtitle.impl.FileAudioImpl;
|
import com.github.gtache.autosubtitle.impl.FileAudioImpl;
|
||||||
import com.github.gtache.autosubtitle.impl.FileVideoImpl;
|
import com.github.gtache.autosubtitle.impl.FileVideoImpl;
|
||||||
import com.github.gtache.autosubtitle.impl.VideoInfoImpl;
|
|
||||||
import com.github.gtache.autosubtitle.modules.setup.ffmpeg.FFmpegBundledPath;
|
import com.github.gtache.autosubtitle.modules.setup.ffmpeg.FFmpegBundledPath;
|
||||||
import com.github.gtache.autosubtitle.modules.setup.ffmpeg.FFmpegSystemPath;
|
import com.github.gtache.autosubtitle.modules.setup.ffmpeg.FFmpegSystemPath;
|
||||||
import com.github.gtache.autosubtitle.process.ProcessRunner;
|
import com.github.gtache.autosubtitle.process.ProcessRunner;
|
||||||
@@ -60,7 +59,7 @@ public class FFmpegVideoConverter implements VideoConverter {
|
|||||||
public Video addSoftSubtitles(final Video video, final Collection<? extends SubtitleCollection<?>> subtitles) throws IOException {
|
public Video addSoftSubtitles(final Video video, final Collection<? extends SubtitleCollection<?>> subtitles) throws IOException {
|
||||||
final var out = getTempFile(video.info().format());
|
final var out = getTempFile(video.info().format());
|
||||||
addSoftSubtitles(video, subtitles, out);
|
addSoftSubtitles(video, subtitles, out);
|
||||||
return new FileVideoImpl(out, new VideoInfoImpl(video.info().format(), video.info().width(), video.info().height(), video.info().duration()));
|
return new FileVideoImpl(out, video.info());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -105,7 +104,7 @@ public class FFmpegVideoConverter implements VideoConverter {
|
|||||||
args.add("language=" + c.language().iso3());
|
args.add("language=" + c.language().iso3());
|
||||||
});
|
});
|
||||||
args.add(path.toString());
|
args.add(path.toString());
|
||||||
runListen(args, Duration.ofHours(1));
|
runLog(args, Duration.ofHours(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -123,13 +122,14 @@ public class FFmpegVideoConverter implements VideoConverter {
|
|||||||
final var subtitleArg = getSubtitleConverter().formatName().equalsIgnoreCase("ass") ? "ass='" + escapedPath + "'" : "subtitles='" + escapedPath + "'";
|
final var subtitleArg = getSubtitleConverter().formatName().equalsIgnoreCase("ass") ? "ass='" + escapedPath + "'" : "subtitles='" + escapedPath + "'";
|
||||||
final var args = List.of(
|
final var args = List.of(
|
||||||
getFFmpegPath(),
|
getFFmpegPath(),
|
||||||
|
"-y",
|
||||||
"-i",
|
"-i",
|
||||||
videoPath.toString(),
|
videoPath.toString(),
|
||||||
"-vf",
|
"-vf",
|
||||||
subtitleArg,
|
subtitleArg,
|
||||||
path.toString()
|
path.toString()
|
||||||
);
|
);
|
||||||
runListen(args, Duration.ofHours(1));
|
runLog(args, Duration.ofHours(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
private SubtitleConverter<?> getSubtitleConverter() {
|
private SubtitleConverter<?> getSubtitleConverter() {
|
||||||
@@ -158,7 +158,7 @@ public class FFmpegVideoConverter implements VideoConverter {
|
|||||||
"0:v",
|
"0:v",
|
||||||
dumpVideoPath.toString()
|
dumpVideoPath.toString()
|
||||||
);
|
);
|
||||||
runListen(args, Duration.ofHours(1));
|
runLog(args, Duration.ofHours(1));
|
||||||
Files.deleteIfExists(dumpVideoPath);
|
Files.deleteIfExists(dumpVideoPath);
|
||||||
return new FileAudioImpl(audioPath, new AudioInfoImpl("wav", video.info().duration()));
|
return new FileAudioImpl(audioPath, new AudioInfoImpl("wav", video.info().duration()));
|
||||||
}
|
}
|
||||||
@@ -203,7 +203,7 @@ public class FFmpegVideoConverter implements VideoConverter {
|
|||||||
return Files.isRegularFile(bundledPath) ? bundledPath.toString() : systemPath.toString();
|
return Files.isRegularFile(bundledPath) ? bundledPath.toString() : systemPath.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void runListen(final List<String> args, final Duration duration) throws IOException {
|
private void runLog(final List<String> args, final Duration duration) throws IOException {
|
||||||
final var listener = processRunner.startListen(args);
|
final var listener = processRunner.startListen(args);
|
||||||
var line = listener.readLine();
|
var line = listener.readLine();
|
||||||
final var processName = args.getFirst();
|
final var processName = args.getFirst();
|
||||||
@@ -211,6 +211,9 @@ public class FFmpegVideoConverter implements VideoConverter {
|
|||||||
logger.info("[{}]: {}", processName, line);
|
logger.info("[{}]: {}", processName, line);
|
||||||
line = listener.readLine();
|
line = listener.readLine();
|
||||||
}
|
}
|
||||||
listener.join(duration);
|
final var result = listener.join(duration);
|
||||||
|
if (result.exitCode() != 0) {
|
||||||
|
throw new IOException("FFmpeg error : " + result.exitCode() + " - " + result.output());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ public class FFprobeVideoLoader implements VideoLoader {
|
|||||||
@Override
|
@Override
|
||||||
public Video loadVideo(final Path path) throws IOException {
|
public Video loadVideo(final Path path) throws IOException {
|
||||||
final var result = processRunner.run(List.of(getFFprobePath(), "-v", "error", "-select_streams", "v", "-show_entries", "stream=width,height,duration", "-of", "csv=p=0", path.toString()), Duration.ofSeconds(5));
|
final var result = processRunner.run(List.of(getFFprobePath(), "-v", "error", "-select_streams", "v", "-show_entries", "stream=width,height,duration", "-of", "csv=p=0", path.toString()), Duration.ofSeconds(5));
|
||||||
|
if (result.exitCode() == 0) {
|
||||||
final var resolution = result.output().getLast();
|
final var resolution = result.output().getLast();
|
||||||
final var split = resolution.split(",");
|
final var split = resolution.split(",");
|
||||||
final var width = Integer.parseInt(split[0]);
|
final var width = Integer.parseInt(split[0]);
|
||||||
@@ -46,6 +47,9 @@ public class FFprobeVideoLoader implements VideoLoader {
|
|||||||
final var duration = (long) (Double.parseDouble(split[2]) * 1000L);
|
final var duration = (long) (Double.parseDouble(split[2]) * 1000L);
|
||||||
final var info = new VideoInfoImpl(extension, width, height, duration);
|
final var info = new VideoInfoImpl(extension, width, height, duration);
|
||||||
return new FileVideoImpl(path, info);
|
return new FileVideoImpl(path, info);
|
||||||
|
} else {
|
||||||
|
throw new IOException("FFprobe error : " + result.exitCode() + " - " + result.output());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getFFprobePath() {
|
private String getFFprobePath() {
|
||||||
|
|||||||
@@ -1,66 +1,311 @@
|
|||||||
package com.github.gtache.autosubtitle.ffmpeg;
|
package com.github.gtache.autosubtitle.ffmpeg;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.Language;
|
||||||
import com.github.gtache.autosubtitle.Video;
|
import com.github.gtache.autosubtitle.Video;
|
||||||
import com.github.gtache.autosubtitle.VideoInfo;
|
import com.github.gtache.autosubtitle.VideoInfo;
|
||||||
import com.github.gtache.autosubtitle.impl.OS;
|
import com.github.gtache.autosubtitle.impl.AudioInfoImpl;
|
||||||
|
import com.github.gtache.autosubtitle.impl.FileVideoImpl;
|
||||||
|
import com.github.gtache.autosubtitle.process.ProcessListener;
|
||||||
|
import com.github.gtache.autosubtitle.process.ProcessResult;
|
||||||
import com.github.gtache.autosubtitle.process.ProcessRunner;
|
import com.github.gtache.autosubtitle.process.ProcessRunner;
|
||||||
import com.github.gtache.autosubtitle.subtitle.Subtitle;
|
|
||||||
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
|
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
|
||||||
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter;
|
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter;
|
||||||
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverterProvider;
|
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverterProvider;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.StandardCopyOption;
|
import java.nio.file.Paths;
|
||||||
import java.util.Objects;
|
import java.time.Duration;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.prefs.Preferences;
|
import java.util.prefs.Preferences;
|
||||||
|
|
||||||
import static org.mockito.Mockito.when;
|
import static java.util.Objects.requireNonNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyList;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
class TestFFmpegVideoConverter {
|
class TestFFmpegVideoConverter {
|
||||||
|
|
||||||
private final FFmpegVideoConverter converter;
|
|
||||||
private final ProcessRunner runner;
|
private final ProcessRunner runner;
|
||||||
private final SubtitleConverter<Subtitle> subtitleConverter;
|
private final ProcessListener listener;
|
||||||
|
private final ProcessResult result;
|
||||||
|
private final SubtitleConverter<?> subtitleConverter;
|
||||||
private final SubtitleConverterProvider subtitleConverterProvider;
|
private final SubtitleConverterProvider subtitleConverterProvider;
|
||||||
private final Video video;
|
private final Video video;
|
||||||
|
private final InputStream in;
|
||||||
private final VideoInfo videoInfo;
|
private final VideoInfo videoInfo;
|
||||||
private final Path tmpFile;
|
private final SubtitleCollection<?> frCollection;
|
||||||
private final Path outputPath;
|
private final SubtitleCollection<?> deCollection;
|
||||||
private final SubtitleCollection<?> collection;
|
|
||||||
private final Preferences preferences;
|
private final Preferences preferences;
|
||||||
|
private final Path systemPath;
|
||||||
|
|
||||||
TestFFmpegVideoConverter(@Mock final SubtitleConverter<Subtitle> subtitleConverter, @Mock final ProcessRunner runner, @Mock final SubtitleConverterProvider subtitleConverterProvider, @Mock final Video video,
|
TestFFmpegVideoConverter(@Mock final SubtitleConverter<?> subtitleConverter, @Mock final ProcessRunner runner,
|
||||||
@Mock final VideoInfo videoInfo, @Mock final SubtitleCollection<?> collection, @Mock final Preferences preferences) throws IOException {
|
@Mock final SubtitleConverterProvider subtitleConverterProvider, @Mock final Video video,
|
||||||
final var output = (OS.getOS() == OS.WINDOWS ? System.getProperty("java.io.tmpdir") : "/tmp");
|
@Mock final VideoInfo videoInfo, @Mock final SubtitleCollection<?> frCollection, @Mock final SubtitleCollection<?> deCollection,
|
||||||
final var resource = OS.getOS() == OS.WINDOWS ? "fake-ffmpeg.exe" : "fake-ffmpeg.sh";
|
@Mock final Preferences preferences, @Mock final ProcessListener listener, @Mock final InputStream in,
|
||||||
this.video = Objects.requireNonNull(video);
|
@Mock final ProcessResult result) {
|
||||||
this.runner = Objects.requireNonNull(runner);
|
this.video = requireNonNull(video);
|
||||||
this.videoInfo = Objects.requireNonNull(videoInfo);
|
this.runner = requireNonNull(runner);
|
||||||
|
this.result = requireNonNull(result);
|
||||||
|
this.videoInfo = requireNonNull(videoInfo);
|
||||||
|
this.subtitleConverter = requireNonNull(subtitleConverter);
|
||||||
|
this.subtitleConverterProvider = requireNonNull(subtitleConverterProvider);
|
||||||
|
this.preferences = requireNonNull(preferences);
|
||||||
|
this.listener = requireNonNull(listener);
|
||||||
|
this.in = requireNonNull(in);
|
||||||
|
this.systemPath = Paths.get("system");
|
||||||
|
this.frCollection = requireNonNull(frCollection);
|
||||||
|
this.deCollection = requireNonNull(deCollection);
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void beforeEach() throws IOException {
|
||||||
when(video.info()).thenReturn(videoInfo);
|
when(video.info()).thenReturn(videoInfo);
|
||||||
this.tmpFile = Files.createTempFile("fake-ffmpeg", resource.substring(resource.lastIndexOf('.')));
|
when(subtitleConverterProvider.getConverter("srt")).thenReturn((SubtitleConverter) subtitleConverter);
|
||||||
try (final var in = getClass().getResourceAsStream(resource)) {
|
when(subtitleConverter.formatName()).thenReturn("srt");
|
||||||
Files.copy(in, tmpFile, StandardCopyOption.REPLACE_EXISTING);
|
when(subtitleConverter.format(any(), any())).thenReturn("");
|
||||||
}
|
when(preferences.get("outputFormat", "srt")).thenReturn("srt");
|
||||||
this.outputPath = Path.of(output, "test-ffmpeg-output.txt");
|
when(video.getInputStream()).thenReturn(in);
|
||||||
this.subtitleConverter = Objects.requireNonNull(subtitleConverter);
|
when(frCollection.language()).thenReturn(Language.FR);
|
||||||
this.subtitleConverterProvider = Objects.requireNonNull(subtitleConverterProvider);
|
when(deCollection.language()).thenReturn(Language.DE);
|
||||||
this.preferences = Objects.requireNonNull(preferences);
|
when(runner.startListen(anyList())).thenReturn(listener);
|
||||||
this.converter = new FFmpegVideoConverter(tmpFile, tmpFile, subtitleConverterProvider, preferences, runner);
|
when(listener.join(Duration.ofHours(1))).thenReturn(result);
|
||||||
this.collection = Objects.requireNonNull(collection);
|
when(result.exitCode()).thenReturn(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterEach
|
@Test
|
||||||
void afterEach() throws IOException {
|
void testAddSoftSubtitlesMp4() throws IOException {
|
||||||
Files.deleteIfExists(tmpFile);
|
when(videoInfo.format()).thenReturn("mp4");
|
||||||
Files.deleteIfExists(outputPath);
|
final var converter = new FFmpegVideoConverter(Paths.get("bundled"), systemPath, subtitleConverterProvider, preferences, runner);
|
||||||
|
final var result = converter.addSoftSubtitles(video, List.of(frCollection, deCollection));
|
||||||
|
|
||||||
|
assertEquals(videoInfo, result.info());
|
||||||
|
|
||||||
|
final var args = getArgs();
|
||||||
|
assertEquals(27, args.size());
|
||||||
|
final var argMap = new HashMap<Integer, String>();
|
||||||
|
argMap.put(0, systemPath.toString());
|
||||||
|
argMap.put(1, "-y");
|
||||||
|
argMap.put(2, "-i");
|
||||||
|
argMap.put(4, "-i");
|
||||||
|
argMap.put(6, "-i");
|
||||||
|
argMap.put(8, "-map");
|
||||||
|
argMap.put(9, "0:v");
|
||||||
|
argMap.put(10, "-map");
|
||||||
|
argMap.put(11, "0:a");
|
||||||
|
argMap.put(12, "-map");
|
||||||
|
argMap.put(13, "1");
|
||||||
|
argMap.put(14, "-map");
|
||||||
|
argMap.put(15, "2");
|
||||||
|
argMap.put(16, "-c:v");
|
||||||
|
argMap.put(17, "copy");
|
||||||
|
argMap.put(18, "-c:a");
|
||||||
|
argMap.put(19, "copy");
|
||||||
|
argMap.put(20, "-c:s");
|
||||||
|
argMap.put(21, "mov_text");
|
||||||
|
argMap.put(22, "-metadata:s:s:0");
|
||||||
|
argMap.put(23, "language=fra");
|
||||||
|
argMap.put(24, "-metadata:s:s:1");
|
||||||
|
argMap.put(25, "language=deu");
|
||||||
|
|
||||||
|
checkEquals(argMap, args);
|
||||||
|
assertTrue(args.get(3).endsWith(".mp4"));
|
||||||
|
assertTrue(args.get(5).endsWith(".srt"));
|
||||||
|
assertTrue(args.get(7).endsWith(".srt"));
|
||||||
|
assertTrue(args.get(26).endsWith(".mp4"));
|
||||||
|
verify(subtitleConverter).format(frCollection, videoInfo);
|
||||||
|
verify(subtitleConverter).format(deCollection, videoInfo);
|
||||||
|
verify(in).transferTo(any());
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO tests
|
private static void checkEquals(final Map<Integer, String> argMap, final List<String> args) {
|
||||||
|
argMap.forEach((key, value) -> assertEquals(value, args.get(key)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAddSoftSubtitlesMkvPathFile() throws IOException {
|
||||||
|
when(videoInfo.format()).thenReturn("mkv");
|
||||||
|
doReturn(null).when(subtitleConverterProvider).getConverter("srt");
|
||||||
|
when(subtitleConverterProvider.getConverter("ass")).thenReturn((SubtitleConverter) subtitleConverter);
|
||||||
|
when(subtitleConverter.formatName()).thenReturn("ass");
|
||||||
|
when(preferences.get("outputFormat", "srt")).thenReturn("ass");
|
||||||
|
final var fileVideo = mock(FileVideoImpl.class);
|
||||||
|
when(fileVideo.info()).thenReturn(videoInfo);
|
||||||
|
final var path = Paths.get("path.mkv");
|
||||||
|
when(fileVideo.path()).thenReturn(path);
|
||||||
|
final var out = Paths.get("out.mkv");
|
||||||
|
final var converter = new FFmpegVideoConverter(Paths.get("bundled"), systemPath, subtitleConverterProvider, preferences, runner);
|
||||||
|
converter.addSoftSubtitles(fileVideo, List.of(frCollection, deCollection), out);
|
||||||
|
|
||||||
|
final var args = getArgs();
|
||||||
|
assertEquals(27, args.size());
|
||||||
|
final var argMap = new HashMap<Integer, String>();
|
||||||
|
argMap.put(0, systemPath.toString());
|
||||||
|
argMap.put(1, "-y");
|
||||||
|
argMap.put(2, "-i");
|
||||||
|
argMap.put(3, path.toString());
|
||||||
|
argMap.put(4, "-i");
|
||||||
|
argMap.put(6, "-i");
|
||||||
|
argMap.put(8, "-map");
|
||||||
|
argMap.put(9, "0:v");
|
||||||
|
argMap.put(10, "-map");
|
||||||
|
argMap.put(11, "0:a");
|
||||||
|
argMap.put(12, "-map");
|
||||||
|
argMap.put(13, "1");
|
||||||
|
argMap.put(14, "-map");
|
||||||
|
argMap.put(15, "2");
|
||||||
|
argMap.put(16, "-c:v");
|
||||||
|
argMap.put(17, "copy");
|
||||||
|
argMap.put(18, "-c:a");
|
||||||
|
argMap.put(19, "copy");
|
||||||
|
argMap.put(20, "-c:s");
|
||||||
|
argMap.put(21, "copy");
|
||||||
|
argMap.put(22, "-metadata:s:s:0");
|
||||||
|
argMap.put(23, "language=fra");
|
||||||
|
argMap.put(24, "-metadata:s:s:1");
|
||||||
|
argMap.put(25, "language=deu");
|
||||||
|
argMap.put(26, out.toString());
|
||||||
|
|
||||||
|
checkEquals(argMap, args);
|
||||||
|
assertTrue(args.get(5).endsWith(".ass"));
|
||||||
|
assertTrue(args.get(7).endsWith(".ass"));
|
||||||
|
verify(subtitleConverter).format(frCollection, videoInfo);
|
||||||
|
verify(subtitleConverter).format(deCollection, videoInfo);
|
||||||
|
verifyNoInteractions(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAddHardSubtitles() throws IOException {
|
||||||
|
when(videoInfo.format()).thenReturn("mp4");
|
||||||
|
final var converter = new FFmpegVideoConverter(Paths.get("bundled"), systemPath, subtitleConverterProvider, preferences, runner);
|
||||||
|
final var result = converter.addHardSubtitles(video, frCollection);
|
||||||
|
|
||||||
|
assertEquals(videoInfo, result.info());
|
||||||
|
|
||||||
|
final var args = getArgs();
|
||||||
|
assertEquals(7, args.size());
|
||||||
|
final var argMap = new HashMap<Integer, String>();
|
||||||
|
argMap.put(0, systemPath.toString());
|
||||||
|
argMap.put(1, "-y");
|
||||||
|
argMap.put(2, "-i");
|
||||||
|
argMap.put(4, "-vf");
|
||||||
|
|
||||||
|
checkEquals(argMap, args);
|
||||||
|
assertTrue(args.get(3).endsWith(".mp4"));
|
||||||
|
assertTrue(args.get(5).startsWith("subtitles="));
|
||||||
|
assertTrue(args.get(6).endsWith(".mp4"));
|
||||||
|
verify(subtitleConverter).format(frCollection, videoInfo);
|
||||||
|
verify(in).transferTo(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAddHardAssSubtitles() throws IOException {
|
||||||
|
doReturn(null).when(subtitleConverterProvider).getConverter("srt");
|
||||||
|
when(subtitleConverterProvider.getConverter("ass")).thenReturn((SubtitleConverter) subtitleConverter);
|
||||||
|
when(subtitleConverter.formatName()).thenReturn("ass");
|
||||||
|
when(preferences.get("outputFormat", "srt")).thenReturn("ass");
|
||||||
|
when(videoInfo.format()).thenReturn("mp4");
|
||||||
|
final var converter = new FFmpegVideoConverter(Paths.get("bundled"), systemPath, subtitleConverterProvider, preferences, runner);
|
||||||
|
final var result = converter.addHardSubtitles(video, frCollection);
|
||||||
|
|
||||||
|
assertEquals(videoInfo, result.info());
|
||||||
|
|
||||||
|
final var args = getArgs();
|
||||||
|
assertEquals(7, args.size());
|
||||||
|
final var argMap = new HashMap<Integer, String>();
|
||||||
|
argMap.put(0, systemPath.toString());
|
||||||
|
argMap.put(1, "-y");
|
||||||
|
argMap.put(2, "-i");
|
||||||
|
argMap.put(4, "-vf");
|
||||||
|
|
||||||
|
checkEquals(argMap, args);
|
||||||
|
assertTrue(args.get(3).endsWith(".mp4"));
|
||||||
|
assertTrue(args.get(5).startsWith("ass="));
|
||||||
|
assertTrue(args.get(6).endsWith(".mp4"));
|
||||||
|
verify(subtitleConverter).format(frCollection, videoInfo);
|
||||||
|
verify(in).transferTo(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetAudio() throws IOException {
|
||||||
|
when(videoInfo.format()).thenReturn("mp4");
|
||||||
|
when(videoInfo.duration()).thenReturn(25000L);
|
||||||
|
final var converter = new FFmpegVideoConverter(Paths.get("bundled"), systemPath, subtitleConverterProvider, preferences, runner);
|
||||||
|
final var result = converter.getAudio(video);
|
||||||
|
|
||||||
|
assertEquals(new AudioInfoImpl("wav", 25000L), result.info());
|
||||||
|
|
||||||
|
final var args = getArgs();
|
||||||
|
assertEquals(10, args.size());
|
||||||
|
final var argMap = new HashMap<Integer, String>();
|
||||||
|
argMap.put(0, systemPath.toString());
|
||||||
|
argMap.put(1, "-y");
|
||||||
|
argMap.put(2, "-i");
|
||||||
|
argMap.put(4, "-map");
|
||||||
|
argMap.put(5, "0:a");
|
||||||
|
argMap.put(7, "-map");
|
||||||
|
argMap.put(8, "0:v");
|
||||||
|
|
||||||
|
checkEquals(argMap, args);
|
||||||
|
assertTrue(args.get(3).endsWith(".mp4"));
|
||||||
|
assertTrue(args.get(6).endsWith(".wav"));
|
||||||
|
assertTrue(args.get(9).endsWith(".mp4"));
|
||||||
|
verify(in).transferTo(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetAudioException() {
|
||||||
|
when(result.exitCode()).thenReturn(1);
|
||||||
|
when(videoInfo.format()).thenReturn("mp4");
|
||||||
|
when(videoInfo.duration()).thenReturn(25000L);
|
||||||
|
final var converter = new FFmpegVideoConverter(Paths.get("bundled"), systemPath, subtitleConverterProvider, preferences, runner);
|
||||||
|
assertThrows(IOException.class, () -> converter.getAudio(video));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetAudioBundled(@TempDir final Path tempDir) throws IOException {
|
||||||
|
final var bundled = tempDir.resolve("ffmpeg");
|
||||||
|
Files.createFile(bundled);
|
||||||
|
when(videoInfo.format()).thenReturn("mp4");
|
||||||
|
when(videoInfo.duration()).thenReturn(25000L);
|
||||||
|
final var converter = new FFmpegVideoConverter(bundled, systemPath, subtitleConverterProvider, preferences, runner);
|
||||||
|
final var result = converter.getAudio(video);
|
||||||
|
|
||||||
|
assertEquals(new AudioInfoImpl("wav", 25000L), result.info());
|
||||||
|
|
||||||
|
final var args = getArgs();
|
||||||
|
assertEquals(10, args.size());
|
||||||
|
final var argMap = new HashMap<Integer, String>();
|
||||||
|
argMap.put(0, bundled.toString());
|
||||||
|
argMap.put(1, "-y");
|
||||||
|
argMap.put(2, "-i");
|
||||||
|
argMap.put(4, "-map");
|
||||||
|
argMap.put(5, "0:a");
|
||||||
|
argMap.put(7, "-map");
|
||||||
|
argMap.put(8, "0:v");
|
||||||
|
|
||||||
|
checkEquals(argMap, args);
|
||||||
|
assertTrue(args.get(3).endsWith(".mp4"));
|
||||||
|
assertTrue(args.get(6).endsWith(".wav"));
|
||||||
|
assertTrue(args.get(9).endsWith(".mp4"));
|
||||||
|
verify(in).transferTo(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getArgs() throws IOException {
|
||||||
|
final var captor = ArgumentCaptor.forClass(List.class);
|
||||||
|
verify(runner).startListen(captor.capture());
|
||||||
|
return captor.getValue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
package com.github.gtache.autosubtitle.ffmpeg;
|
package com.github.gtache.autosubtitle.ffmpeg;
|
||||||
|
|
||||||
import com.github.gtache.autosubtitle.impl.FileVideoImpl;
|
import com.github.gtache.autosubtitle.impl.FileVideoImpl;
|
||||||
import com.github.gtache.autosubtitle.impl.OS;
|
|
||||||
import com.github.gtache.autosubtitle.impl.VideoInfoImpl;
|
import com.github.gtache.autosubtitle.impl.VideoInfoImpl;
|
||||||
|
import com.github.gtache.autosubtitle.process.ProcessResult;
|
||||||
import com.github.gtache.autosubtitle.process.ProcessRunner;
|
import com.github.gtache.autosubtitle.process.ProcessRunner;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Disabled;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
@@ -15,48 +15,70 @@ import java.io.IOException;
|
|||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.nio.file.StandardCopyOption;
|
import java.time.Duration;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
class TestFFmpegVideoLoader {
|
class TestFFmpegVideoLoader {
|
||||||
|
|
||||||
private final FFprobeVideoLoader loader;
|
|
||||||
private final ProcessRunner runner;
|
private final ProcessRunner runner;
|
||||||
private final Path tmpFile;
|
private final ProcessResult result;
|
||||||
private final Path outputPath;
|
private final Path systemPath;
|
||||||
|
private final Path in;
|
||||||
|
|
||||||
TestFFmpegVideoLoader(@Mock final ProcessRunner runner) throws IOException {
|
TestFFmpegVideoLoader(@Mock final ProcessRunner runner, @Mock final ProcessResult result) {
|
||||||
final var output = (OS.getOS() == OS.WINDOWS ? System.getProperty("java.io.tmpdir") : "/tmp");
|
|
||||||
final var resource = OS.getOS() == OS.WINDOWS ? "fake-ffprobe.exe" : "fake-ffprobe.sh";
|
|
||||||
this.tmpFile = Files.createTempFile("fake-ffprobe", resource.substring(resource.lastIndexOf('.')));
|
|
||||||
try (final var in = getClass().getResourceAsStream(resource)) {
|
|
||||||
if (in == null) {
|
|
||||||
throw new IOException("Unable to find " + resource);
|
|
||||||
}
|
|
||||||
Files.copy(in, tmpFile, StandardCopyOption.REPLACE_EXISTING);
|
|
||||||
}
|
|
||||||
this.outputPath = Path.of(output, "test-ffprobe-output.txt");
|
|
||||||
this.runner = Objects.requireNonNull(runner);
|
this.runner = Objects.requireNonNull(runner);
|
||||||
this.loader = new FFprobeVideoLoader(tmpFile, tmpFile, runner);
|
this.result = Objects.requireNonNull(result);
|
||||||
|
this.systemPath = Paths.get("system");
|
||||||
|
this.in = Paths.get("in.mp4");
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterEach
|
@BeforeEach
|
||||||
void afterEach() throws IOException {
|
void beforeEach() {
|
||||||
Files.deleteIfExists(tmpFile);
|
when(result.exitCode()).thenReturn(0);
|
||||||
Files.deleteIfExists(outputPath);
|
when(result.output()).thenReturn(List.of("1920,1080,25"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Disabled("Doesn't work")
|
void testLoadVideoBundled(@TempDir final Path tempDir) throws IOException {
|
||||||
void testLoadVideo() throws IOException {
|
final var bundledPath = tempDir.resolve("ffprobe");
|
||||||
final var in = Paths.get("in.mp4");
|
Files.createFile(bundledPath);
|
||||||
|
final var args = List.of(bundledPath.toString(), "-v", "error", "-select_streams", "v", "-show_entries", "stream=width,height,duration", "-of", "csv=p=0", in.toString());
|
||||||
|
when(runner.run(args, Duration.ofSeconds(5)))
|
||||||
|
.thenReturn(result);
|
||||||
|
final var loader = new FFprobeVideoLoader(bundledPath, systemPath, runner);
|
||||||
final var expectedInfo = new VideoInfoImpl("mp4", 1920, 1080, 25000L);
|
final var expectedInfo = new VideoInfoImpl("mp4", 1920, 1080, 25000L);
|
||||||
final var expected = new FileVideoImpl(in, expectedInfo);
|
final var expected = new FileVideoImpl(in, expectedInfo);
|
||||||
final var result = loader.loadVideo(in);
|
final var actual = loader.loadVideo(in);
|
||||||
assertEquals(expected, result);
|
assertEquals(expected, actual);
|
||||||
assertEquals("-v error -select_streams v -show_entries stream=width,height,duration -of csv=p=0 in.mp4", Files.readString(outputPath));
|
verify(runner).run(args, Duration.ofSeconds(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testLoadVideoSystem() throws IOException {
|
||||||
|
final var args = List.of(systemPath.toString(), "-v", "error", "-select_streams", "v", "-show_entries", "stream=width,height,duration", "-of", "csv=p=0", in.toString());
|
||||||
|
when(runner.run(args, Duration.ofSeconds(5))).thenReturn(result);
|
||||||
|
final var loader = new FFprobeVideoLoader(Paths.get("bundled"), systemPath, runner);
|
||||||
|
final var expectedInfo = new VideoInfoImpl("mp4", 1920, 1080, 25000L);
|
||||||
|
final var expected = new FileVideoImpl(in, expectedInfo);
|
||||||
|
final var actual = loader.loadVideo(in);
|
||||||
|
assertEquals(expected, actual);
|
||||||
|
verify(runner).run(args, Duration.ofSeconds(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testLoadVideoSystemError() throws IOException {
|
||||||
|
final var args = List.of(systemPath.toString(), "-v", "error", "-select_streams", "v", "-show_entries", "stream=width,height,duration", "-of", "csv=p=0", in.toString());
|
||||||
|
when(runner.run(args, Duration.ofSeconds(5))).thenReturn(result);
|
||||||
|
final var loader = new FFprobeVideoLoader(Paths.get("bundled"), systemPath, runner);
|
||||||
|
when(result.exitCode()).thenReturn(1);
|
||||||
|
assertThrows(IOException.class, () -> loader.loadVideo(in));
|
||||||
|
verify(runner).run(args, Duration.ofSeconds(5));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@@ -1,5 +0,0 @@
|
|||||||
$TempDir = [System.IO.Path]::GetTempPath()
|
|
||||||
$Output = "$TempDir\test-ffmpeg-output.txt"
|
|
||||||
|
|
||||||
Write-Output "$args" > $Output
|
|
||||||
exit
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -o errexit -o noclobber -o pipefail -o nounset
|
|
||||||
|
|
||||||
output=/tmp/test-ffmpeg-output.txt
|
|
||||||
|
|
||||||
echo "$@" >| $output
|
|
||||||
Binary file not shown.
@@ -1,6 +0,0 @@
|
|||||||
$TempDir = [System.IO.Path]::GetTempPath()
|
|
||||||
$Output = "$TempDir\test-ffprobe-output.txt"
|
|
||||||
|
|
||||||
Write-Output "$args" > $Output
|
|
||||||
Write-Host "1920,1080,25"
|
|
||||||
exit
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -o errexit -o noclobber -o pipefail -o nounset
|
|
||||||
|
|
||||||
output=/tmp/test-ffprobe-output.txt
|
|
||||||
|
|
||||||
echo "$@" >| $output
|
|
||||||
echo "1920,1080,25"
|
|
||||||
25
pom.xml
25
pom.xml
@@ -192,6 +192,25 @@
|
|||||||
<build>
|
<build>
|
||||||
<pluginManagement>
|
<pluginManagement>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.jacoco</groupId>
|
||||||
|
<artifactId>jacoco-maven-plugin</artifactId>
|
||||||
|
<version>0.8.12</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>prepare-agent</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>report</id>
|
||||||
|
<phase>prepare-package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>report</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
@@ -221,6 +240,12 @@
|
|||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</pluginManagement>
|
</pluginManagement>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.jacoco</groupId>
|
||||||
|
<artifactId>jacoco-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
@@ -33,6 +33,10 @@ public record AudioOrVideo(Audio audio, Video video) implements Audio {
|
|||||||
this(null, video);
|
this(null, video);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param <T> The type of the inner object
|
||||||
|
* @return The inner object (audio or video)
|
||||||
|
*/
|
||||||
public <T> T inner() {
|
public <T> T inner() {
|
||||||
return (T) (audio == null ? video : audio);
|
return (T) (audio == null ? video : audio);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package com.github.gtache.autosubtitle.subtitle.extractor.whisper;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.Audio;
|
||||||
|
import com.github.gtache.autosubtitle.AudioInfo;
|
||||||
|
import com.github.gtache.autosubtitle.Video;
|
||||||
|
import com.github.gtache.autosubtitle.VideoInfo;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class TestAudioOrVideo {
|
||||||
|
|
||||||
|
private final Audio audio;
|
||||||
|
private final Video video;
|
||||||
|
private final InputStream in;
|
||||||
|
|
||||||
|
|
||||||
|
TestAudioOrVideo(@Mock final Audio audio, @Mock final Video video, @Mock final InputStream in) {
|
||||||
|
this.audio = Objects.requireNonNull(audio);
|
||||||
|
this.video = Objects.requireNonNull(video);
|
||||||
|
this.in = Objects.requireNonNull(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAudio() throws IOException {
|
||||||
|
final var audioOrVideo = new AudioOrVideo(audio);
|
||||||
|
when(audio.getInputStream()).thenReturn(in);
|
||||||
|
assertEquals(in, audioOrVideo.getInputStream());
|
||||||
|
final var info = mock(AudioInfo.class);
|
||||||
|
when(audio.info()).thenReturn(info);
|
||||||
|
|
||||||
|
assertEquals(info, audioOrVideo.info());
|
||||||
|
assertEquals(audio, audioOrVideo.inner());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testVideo() throws IOException {
|
||||||
|
final var audioOrVideo = new AudioOrVideo(video);
|
||||||
|
when(video.getInputStream()).thenReturn(in);
|
||||||
|
assertEquals(in, audioOrVideo.getInputStream());
|
||||||
|
final var info = mock(VideoInfo.class);
|
||||||
|
when(video.info()).thenReturn(info);
|
||||||
|
|
||||||
|
assertEquals(info, audioOrVideo.info());
|
||||||
|
assertEquals(video, audioOrVideo.inner());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testIllegal() {
|
||||||
|
assertThrows(NullPointerException.class, () -> new AudioOrVideo((Audio) null));
|
||||||
|
assertThrows(NullPointerException.class, () -> new AudioOrVideo((Video) null));
|
||||||
|
assertThrows(NullPointerException.class, () -> new AudioOrVideo(null, null));
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> new AudioOrVideo(audio, video));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user