diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/ffmpeg/FFmpegVideoConverter.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/ffmpeg/FFmpegVideoConverter.java index 0953eab..3550529 100644 --- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/ffmpeg/FFmpegVideoConverter.java +++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/ffmpeg/FFmpegVideoConverter.java @@ -8,7 +8,6 @@ import com.github.gtache.autosubtitle.VideoInfo; import com.github.gtache.autosubtitle.impl.AudioInfoImpl; import com.github.gtache.autosubtitle.impl.FileAudioImpl; 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.FFmpegSystemPath; import com.github.gtache.autosubtitle.process.ProcessRunner; @@ -60,7 +59,7 @@ public class FFmpegVideoConverter implements VideoConverter { public Video addSoftSubtitles(final Video video, final Collection> subtitles) throws IOException { final var out = getTempFile(video.info().format()); 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 @@ -105,7 +104,7 @@ public class FFmpegVideoConverter implements VideoConverter { args.add("language=" + c.language().iso3()); }); args.add(path.toString()); - runListen(args, Duration.ofHours(1)); + runLog(args, Duration.ofHours(1)); } @Override @@ -123,13 +122,14 @@ public class FFmpegVideoConverter implements VideoConverter { final var subtitleArg = getSubtitleConverter().formatName().equalsIgnoreCase("ass") ? "ass='" + escapedPath + "'" : "subtitles='" + escapedPath + "'"; final var args = List.of( getFFmpegPath(), + "-y", "-i", videoPath.toString(), "-vf", subtitleArg, path.toString() ); - runListen(args, Duration.ofHours(1)); + runLog(args, Duration.ofHours(1)); } private SubtitleConverter getSubtitleConverter() { @@ -158,7 +158,7 @@ public class FFmpegVideoConverter implements VideoConverter { "0:v", dumpVideoPath.toString() ); - runListen(args, Duration.ofHours(1)); + runLog(args, Duration.ofHours(1)); Files.deleteIfExists(dumpVideoPath); 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(); } - private void runListen(final List args, final Duration duration) throws IOException { + private void runLog(final List args, final Duration duration) throws IOException { final var listener = processRunner.startListen(args); var line = listener.readLine(); final var processName = args.getFirst(); @@ -211,6 +211,9 @@ public class FFmpegVideoConverter implements VideoConverter { logger.info("[{}]: {}", processName, line); 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()); + } } } diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/ffmpeg/FFprobeVideoLoader.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/ffmpeg/FFprobeVideoLoader.java index 0b0bb40..05a029d 100644 --- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/ffmpeg/FFprobeVideoLoader.java +++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/ffmpeg/FFprobeVideoLoader.java @@ -37,15 +37,19 @@ public class FFprobeVideoLoader implements VideoLoader { @Override 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 resolution = result.output().getLast(); - final var split = resolution.split(","); - final var width = Integer.parseInt(split[0]); - final var height = Integer.parseInt(split[1]); - final var filename = path.getFileName().toString(); - final var extension = filename.substring(filename.lastIndexOf('.') + 1); - final var duration = (long) (Double.parseDouble(split[2]) * 1000L); - final var info = new VideoInfoImpl(extension, width, height, duration); - return new FileVideoImpl(path, info); + if (result.exitCode() == 0) { + final var resolution = result.output().getLast(); + final var split = resolution.split(","); + final var width = Integer.parseInt(split[0]); + final var height = Integer.parseInt(split[1]); + final var filename = path.getFileName().toString(); + final var extension = filename.substring(filename.lastIndexOf('.') + 1); + final var duration = (long) (Double.parseDouble(split[2]) * 1000L); + final var info = new VideoInfoImpl(extension, width, height, duration); + return new FileVideoImpl(path, info); + } else { + throw new IOException("FFprobe error : " + result.exitCode() + " - " + result.output()); + } } private String getFFprobePath() { diff --git a/ffmpeg/src/test/java/com/github/gtache/autosubtitle/ffmpeg/TestFFmpegVideoConverter.java b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/ffmpeg/TestFFmpegVideoConverter.java index 62d4074..d84ceb3 100644 --- a/ffmpeg/src/test/java/com/github/gtache/autosubtitle/ffmpeg/TestFFmpegVideoConverter.java +++ b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/ffmpeg/TestFFmpegVideoConverter.java @@ -1,66 +1,311 @@ package com.github.gtache.autosubtitle.ffmpeg; +import com.github.gtache.autosubtitle.Language; import com.github.gtache.autosubtitle.Video; 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.subtitle.Subtitle; import com.github.gtache.autosubtitle.subtitle.SubtitleCollection; import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter; 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.io.TempDir; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import java.io.IOException; +import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.util.Objects; +import java.nio.file.Paths; +import java.time.Duration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; 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) class TestFFmpegVideoConverter { - private final FFmpegVideoConverter converter; private final ProcessRunner runner; - private final SubtitleConverter subtitleConverter; + private final ProcessListener listener; + private final ProcessResult result; + private final SubtitleConverter subtitleConverter; private final SubtitleConverterProvider subtitleConverterProvider; private final Video video; + private final InputStream in; private final VideoInfo videoInfo; - private final Path tmpFile; - private final Path outputPath; - private final SubtitleCollection collection; + private final SubtitleCollection frCollection; + private final SubtitleCollection deCollection; private final Preferences preferences; + private final Path systemPath; - TestFFmpegVideoConverter(@Mock final SubtitleConverter subtitleConverter, @Mock final ProcessRunner runner, @Mock final SubtitleConverterProvider subtitleConverterProvider, @Mock final Video video, - @Mock final VideoInfo videoInfo, @Mock final SubtitleCollection collection, @Mock final Preferences preferences) throws IOException { - final var output = (OS.getOS() == OS.WINDOWS ? System.getProperty("java.io.tmpdir") : "/tmp"); - final var resource = OS.getOS() == OS.WINDOWS ? "fake-ffmpeg.exe" : "fake-ffmpeg.sh"; - this.video = Objects.requireNonNull(video); - this.runner = Objects.requireNonNull(runner); - this.videoInfo = Objects.requireNonNull(videoInfo); + TestFFmpegVideoConverter(@Mock final SubtitleConverter subtitleConverter, @Mock final ProcessRunner runner, + @Mock final SubtitleConverterProvider subtitleConverterProvider, @Mock final Video video, + @Mock final VideoInfo videoInfo, @Mock final SubtitleCollection frCollection, @Mock final SubtitleCollection deCollection, + @Mock final Preferences preferences, @Mock final ProcessListener listener, @Mock final InputStream in, + @Mock final ProcessResult result) { + this.video = requireNonNull(video); + 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); - this.tmpFile = Files.createTempFile("fake-ffmpeg", resource.substring(resource.lastIndexOf('.'))); - try (final var in = getClass().getResourceAsStream(resource)) { - Files.copy(in, tmpFile, StandardCopyOption.REPLACE_EXISTING); - } - this.outputPath = Path.of(output, "test-ffmpeg-output.txt"); - this.subtitleConverter = Objects.requireNonNull(subtitleConverter); - this.subtitleConverterProvider = Objects.requireNonNull(subtitleConverterProvider); - this.preferences = Objects.requireNonNull(preferences); - this.converter = new FFmpegVideoConverter(tmpFile, tmpFile, subtitleConverterProvider, preferences, runner); - this.collection = Objects.requireNonNull(collection); + when(subtitleConverterProvider.getConverter("srt")).thenReturn((SubtitleConverter) subtitleConverter); + when(subtitleConverter.formatName()).thenReturn("srt"); + when(subtitleConverter.format(any(), any())).thenReturn(""); + when(preferences.get("outputFormat", "srt")).thenReturn("srt"); + when(video.getInputStream()).thenReturn(in); + when(frCollection.language()).thenReturn(Language.FR); + when(deCollection.language()).thenReturn(Language.DE); + when(runner.startListen(anyList())).thenReturn(listener); + when(listener.join(Duration.ofHours(1))).thenReturn(result); + when(result.exitCode()).thenReturn(0); } - @AfterEach - void afterEach() throws IOException { - Files.deleteIfExists(tmpFile); - Files.deleteIfExists(outputPath); + @Test + void testAddSoftSubtitlesMp4() throws IOException { + when(videoInfo.format()).thenReturn("mp4"); + 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(); + 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 argMap, final List 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(); + 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(); + 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(); + 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(); + 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(); + 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 getArgs() throws IOException { + final var captor = ArgumentCaptor.forClass(List.class); + verify(runner).startListen(captor.capture()); + return captor.getValue(); + } } diff --git a/ffmpeg/src/test/java/com/github/gtache/autosubtitle/ffmpeg/TestFFmpegVideoLoader.java b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/ffmpeg/TestFFmpegVideoLoader.java index 9143f38..a6111ec 100644 --- a/ffmpeg/src/test/java/com/github/gtache/autosubtitle/ffmpeg/TestFFmpegVideoLoader.java +++ b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/ffmpeg/TestFFmpegVideoLoader.java @@ -1,13 +1,13 @@ package com.github.gtache.autosubtitle.ffmpeg; 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.process.ProcessResult; import com.github.gtache.autosubtitle.process.ProcessRunner; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -15,48 +15,70 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; +import java.time.Duration; +import java.util.List; 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.verify; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class TestFFmpegVideoLoader { - private final FFprobeVideoLoader loader; private final ProcessRunner runner; - private final Path tmpFile; - private final Path outputPath; + private final ProcessResult result; + private final Path systemPath; + private final Path in; - TestFFmpegVideoLoader(@Mock final ProcessRunner runner) throws IOException { - 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"); + TestFFmpegVideoLoader(@Mock final ProcessRunner runner, @Mock final ProcessResult result) { 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 - void afterEach() throws IOException { - Files.deleteIfExists(tmpFile); - Files.deleteIfExists(outputPath); + @BeforeEach + void beforeEach() { + when(result.exitCode()).thenReturn(0); + when(result.output()).thenReturn(List.of("1920,1080,25")); } @Test - @Disabled("Doesn't work") - void testLoadVideo() throws IOException { - final var in = Paths.get("in.mp4"); + void testLoadVideoBundled(@TempDir final Path tempDir) throws IOException { + final var bundledPath = tempDir.resolve("ffprobe"); + 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 expected = new FileVideoImpl(in, expectedInfo); - final var result = loader.loadVideo(in); - assertEquals(expected, result); - assertEquals("-v error -select_streams v -show_entries stream=width,height,duration -of csv=p=0 in.mp4", Files.readString(outputPath)); + final var actual = loader.loadVideo(in); + assertEquals(expected, actual); + 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)); } } diff --git a/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffmpeg.exe b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffmpeg.exe deleted file mode 100644 index 2bc1a21..0000000 Binary files a/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffmpeg.exe and /dev/null differ diff --git a/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffmpeg.ps1 b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffmpeg.ps1 deleted file mode 100644 index cfd8317..0000000 --- a/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffmpeg.ps1 +++ /dev/null @@ -1,5 +0,0 @@ -$TempDir = [System.IO.Path]::GetTempPath() -$Output = "$TempDir\test-ffmpeg-output.txt" - -Write-Output "$args" > $Output -exit \ No newline at end of file diff --git a/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffmpeg.sh b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffmpeg.sh deleted file mode 100644 index b6cbfe0..0000000 --- a/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffmpeg.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -set -o errexit -o noclobber -o pipefail -o nounset - -output=/tmp/test-ffmpeg-output.txt - -echo "$@" >| $output \ No newline at end of file diff --git a/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffprobe.exe b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffprobe.exe deleted file mode 100644 index 60c6ac7..0000000 Binary files a/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffprobe.exe and /dev/null differ diff --git a/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffprobe.ps1 b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffprobe.ps1 deleted file mode 100644 index e11cb1a..0000000 --- a/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffprobe.ps1 +++ /dev/null @@ -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 \ No newline at end of file diff --git a/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffprobe.sh b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffprobe.sh deleted file mode 100644 index 438d99f..0000000 --- a/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffprobe.sh +++ /dev/null @@ -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" \ No newline at end of file diff --git a/pom.xml b/pom.xml index a80a1a5..c12177c 100644 --- a/pom.xml +++ b/pom.xml @@ -192,6 +192,25 @@ + + org.jacoco + jacoco-maven-plugin + 0.8.12 + + + + prepare-agent + + + + report + prepare-package + + report + + + + org.apache.maven.plugins maven-compiler-plugin @@ -221,6 +240,12 @@ + + + org.jacoco + jacoco-maven-plugin + + \ No newline at end of file diff --git a/whisper/common/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/AudioOrVideo.java b/whisper/common/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/AudioOrVideo.java index 6292431..d92be5c 100644 --- a/whisper/common/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/AudioOrVideo.java +++ b/whisper/common/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/AudioOrVideo.java @@ -33,6 +33,10 @@ public record AudioOrVideo(Audio audio, Video video) implements Audio { this(null, video); } + /** + * @param The type of the inner object + * @return The inner object (audio or video) + */ public T inner() { return (T) (audio == null ? video : audio); } diff --git a/whisper/common/src/test/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/TestAudioOrVideo.java b/whisper/common/src/test/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/TestAudioOrVideo.java new file mode 100644 index 0000000..e5bcabf --- /dev/null +++ b/whisper/common/src/test/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/TestAudioOrVideo.java @@ -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)); + } + +}