From 3cc2f7a0c914463fd439eca13f48abf6671bb6db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillaume=20T=C3=A2che?= Date: Mon, 16 Sep 2024 14:01:57 +0200 Subject: [PATCH] Adds tests for common whisper --- .../com/github/gtache/autosubtitle/Audio.java | 2 +- .../github/gtache/autosubtitle/AudioInfo.java | 11 +- .../com/github/gtache/autosubtitle/Video.java | 4 +- .../github/gtache/autosubtitle/VideoInfo.java | 11 +- .../subtitle/extractor/SubtitleExtractor.java | 2 +- .../converter/TestSubtitleConverter.java | 4 +- .../extractor/TestSubtitleExtractor.java | 9 +- cli/src/main/java/module-info.java | 1 + .../archive/client/RemoteArchiver.java | 27 --- .../autosubtitle/impl/AudioInfoImpl.java | 4 +- .../autosubtitle/impl/VideoInfoImpl.java | 4 +- .../autosubtitle/impl/TestAudioInfoImpl.java | 4 +- .../autosubtitle/impl/TestVideoInfoImpl.java | 4 +- .../impl/TestAbstractSubtitleExtractor.java | 2 +- deepl/pom.xml | 13 +- .../modules/deepl/DeepLModule.java | 11 +- .../setup/deepl/DeepLSetupManager.java | 4 +- .../translation/deepl/DeepLTranslator.java | 4 +- deepl/src/main/java/module-info.java | 3 +- .../modules/deepl/TestDeepLModule.java | 15 ++ .../setup/deepl/TestDeepLSetupManager.java | 99 ++++++++ .../deepl/TestDeepLTranslator.java | 5 + .../ffmpeg/FFmpegVideoConverter.java | 10 +- .../autosubtitle/gui/ParametersModel.java | 24 +- .../gui/fx/TestColonTimeFormatter.java | 34 +-- gui/run/src/main/java/module-info.java | 1 + .../modules/whisper/base/WhisperModule.java | 2 +- .../base}/WhisperExtractionModelProvider.java | 3 +- whisper/base/src/main/java/module-info.java | 5 +- .../TestWhisperExtractionModelProvider.java | 32 +++ .../whisper/WhisperCommonSetupModule.java | 1 - .../AbstractWhisperSubtitleExtractor.java | 48 ++-- .../extractor/whisper/AudioOrVideo.java | 57 +++++ .../whisper/TestWhisperCommonSetupModule.java | 23 ++ .../TestAbstractWhisperSetupManager.java | 171 ++++++++++++++ .../TestWhisperSetupConfiguration.java | 46 ++++ .../TestAbstractWhisperSubtitleExtractor.java | 211 ++++++++++++++++++ .../whisper/TestWhisperModels.java | 15 ++ 38 files changed, 796 insertions(+), 130 deletions(-) delete mode 100644 client/src/main/java/com/github/gtache/autosubtitle/archive/client/RemoteArchiver.java create mode 100644 deepl/src/test/java/com/github/gtache/autosubtitle/modules/deepl/TestDeepLModule.java create mode 100644 deepl/src/test/java/com/github/gtache/autosubtitle/setup/deepl/TestDeepLSetupManager.java create mode 100644 deepl/src/test/java/com/github/gtache/autosubtitle/translation/deepl/TestDeepLTranslator.java rename whisper/{common/src/main/java/com/github/gtache/autosubtitle/whisper => base/src/main/java/com/github/gtache/autosubtitle/whisper/base}/WhisperExtractionModelProvider.java (88%) create mode 100644 whisper/base/src/test/java/com/github/gtache/autosubtitle/whisper/base/TestWhisperExtractionModelProvider.java create mode 100644 whisper/common/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/AudioOrVideo.java create mode 100644 whisper/common/src/test/java/com/github/gtache/autosubtitle/modules/setup/whisper/TestWhisperCommonSetupModule.java create mode 100644 whisper/common/src/test/java/com/github/gtache/autosubtitle/setup/whisper/TestAbstractWhisperSetupManager.java create mode 100644 whisper/common/src/test/java/com/github/gtache/autosubtitle/setup/whisper/TestWhisperSetupConfiguration.java create mode 100644 whisper/common/src/test/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/TestAbstractWhisperSubtitleExtractor.java create mode 100644 whisper/common/src/test/java/com/github/gtache/autosubtitle/whisper/TestWhisperModels.java diff --git a/api/src/main/java/com/github/gtache/autosubtitle/Audio.java b/api/src/main/java/com/github/gtache/autosubtitle/Audio.java index 81a0abd..6a8a066 100644 --- a/api/src/main/java/com/github/gtache/autosubtitle/Audio.java +++ b/api/src/main/java/com/github/gtache/autosubtitle/Audio.java @@ -9,7 +9,7 @@ import java.io.InputStream; public interface Audio { /** - * @return The audio input stream + * @return The input stream * @throws IOException If an I/O error occurs */ InputStream getInputStream() throws IOException; diff --git a/api/src/main/java/com/github/gtache/autosubtitle/AudioInfo.java b/api/src/main/java/com/github/gtache/autosubtitle/AudioInfo.java index 701cdf4..3cb612b 100644 --- a/api/src/main/java/com/github/gtache/autosubtitle/AudioInfo.java +++ b/api/src/main/java/com/github/gtache/autosubtitle/AudioInfo.java @@ -4,13 +4,14 @@ package com.github.gtache.autosubtitle; * Represents info about an audio */ public interface AudioInfo { - /** - * @return The audio extension (mp3, etc.) - */ - String audioFormat(); /** - * @return The audio duration in milliseconds + * @return The extension (mp3, etc.) + */ + String format(); + + /** + * @return The duration in milliseconds */ long duration(); } diff --git a/api/src/main/java/com/github/gtache/autosubtitle/Video.java b/api/src/main/java/com/github/gtache/autosubtitle/Video.java index e929912..b2f6078 100644 --- a/api/src/main/java/com/github/gtache/autosubtitle/Video.java +++ b/api/src/main/java/com/github/gtache/autosubtitle/Video.java @@ -7,9 +7,8 @@ import java.io.InputStream; * Represents a video */ public interface Video { - /** - * @return The video input stream + * @return The input stream * @throws IOException If an I/O error occurs */ InputStream getInputStream() throws IOException; @@ -18,5 +17,4 @@ public interface Video { * @return The video info */ VideoInfo info(); - } diff --git a/api/src/main/java/com/github/gtache/autosubtitle/VideoInfo.java b/api/src/main/java/com/github/gtache/autosubtitle/VideoInfo.java index 9788ea4..b2ddd7f 100644 --- a/api/src/main/java/com/github/gtache/autosubtitle/VideoInfo.java +++ b/api/src/main/java/com/github/gtache/autosubtitle/VideoInfo.java @@ -3,11 +3,7 @@ package com.github.gtache.autosubtitle; /** * Info about a video */ -public interface VideoInfo { - /** - * @return The video extension (mp4, etc.) - */ - String videoFormat(); +public interface VideoInfo extends AudioInfo { /** * @return The video width in pixels @@ -19,11 +15,6 @@ public interface VideoInfo { */ int height(); - /** - * @return The video duration in milliseconds - */ - long duration(); - /** * @return The aspect ratio of the video */ diff --git a/api/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/SubtitleExtractor.java b/api/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/SubtitleExtractor.java index f5e77bf..be85b18 100644 --- a/api/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/SubtitleExtractor.java +++ b/api/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/SubtitleExtractor.java @@ -46,7 +46,7 @@ public interface SubtitleExtractor { * Extracts the subtitles from a video * * @param video The video - * @param language The language of the video + * @param language The language of the audio * @param model The model to use * @return The extracted subtitle collection * @throws ExtractException If an error occurs diff --git a/api/src/test/java/com/github/gtache/autosubtitle/subtitle/converter/TestSubtitleConverter.java b/api/src/test/java/com/github/gtache/autosubtitle/subtitle/converter/TestSubtitleConverter.java index ca310a4..f5fbb7f 100644 --- a/api/src/test/java/com/github/gtache/autosubtitle/subtitle/converter/TestSubtitleConverter.java +++ b/api/src/test/java/com/github/gtache/autosubtitle/subtitle/converter/TestSubtitleConverter.java @@ -21,10 +21,10 @@ import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class TestSubtitleConverter { - private final SubtitleConverter subtitleConverter; + private final SubtitleConverter subtitleConverter; private final SubtitleCollection subtitleCollection; - TestSubtitleConverter(@Mock final SubtitleConverter subtitleConverter, + TestSubtitleConverter(@Mock final SubtitleConverter subtitleConverter, @Mock final SubtitleCollection subtitleCollection) { this.subtitleConverter = Objects.requireNonNull(subtitleConverter); this.subtitleCollection = Objects.requireNonNull(subtitleCollection); diff --git a/api/src/test/java/com/github/gtache/autosubtitle/subtitle/extractor/TestSubtitleExtractor.java b/api/src/test/java/com/github/gtache/autosubtitle/subtitle/extractor/TestSubtitleExtractor.java index 76d73ad..614cda5 100644 --- a/api/src/test/java/com/github/gtache/autosubtitle/subtitle/extractor/TestSubtitleExtractor.java +++ b/api/src/test/java/com/github/gtache/autosubtitle/subtitle/extractor/TestSubtitleExtractor.java @@ -3,6 +3,7 @@ package com.github.gtache.autosubtitle.subtitle.extractor; import com.github.gtache.autosubtitle.Audio; import com.github.gtache.autosubtitle.Language; import com.github.gtache.autosubtitle.Video; +import com.github.gtache.autosubtitle.subtitle.Subtitle; import com.github.gtache.autosubtitle.subtitle.SubtitleCollection; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -18,14 +19,14 @@ import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class TestSubtitleExtractor { - private final SubtitleExtractor subtitleExtractor; - private final SubtitleCollection subtitleCollection; + private final SubtitleExtractor subtitleExtractor; + private final SubtitleCollection subtitleCollection; private final Audio audio; private final Video video; private final ExtractionModel extractionModel; - TestSubtitleExtractor(@Mock final SubtitleExtractor subtitleExtractor, - @Mock final SubtitleCollection subtitleCollection, + TestSubtitleExtractor(@Mock final SubtitleExtractor subtitleExtractor, + @Mock final SubtitleCollection subtitleCollection, @Mock final Audio audio, @Mock final Video video, @Mock final ExtractionModel extractionModel) { diff --git a/cli/src/main/java/module-info.java b/cli/src/main/java/module-info.java index ec5b9fa..3e86d6e 100644 --- a/cli/src/main/java/module-info.java +++ b/cli/src/main/java/module-info.java @@ -6,4 +6,5 @@ module com.github.gtache.autosubtitle.cli { requires com.github.gtache.autosubtitle.ffmpeg; requires com.github.gtache.autosubtitle.whisperx; requires info.picocli; + requires guava; } \ No newline at end of file diff --git a/client/src/main/java/com/github/gtache/autosubtitle/archive/client/RemoteArchiver.java b/client/src/main/java/com/github/gtache/autosubtitle/archive/client/RemoteArchiver.java deleted file mode 100644 index d7ab213..0000000 --- a/client/src/main/java/com/github/gtache/autosubtitle/archive/client/RemoteArchiver.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.github.gtache.autosubtitle.archive.client; - -import com.github.gtache.autosubtitle.archive.Archiver; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.List; - -/** - * {@link Archiver} using a remote API - */ -public class RemoteArchiver implements Archiver { - @Override - public void compress(final List files, final Path destination) throws IOException { - throw new UnsupportedOperationException(); - } - - @Override - public void decompress(final Path archive, final Path destination) throws IOException { - throw new UnsupportedOperationException(); - } - - @Override - public String archiveExtension() { - throw new UnsupportedOperationException(); - } -} diff --git a/core/src/main/java/com/github/gtache/autosubtitle/impl/AudioInfoImpl.java b/core/src/main/java/com/github/gtache/autosubtitle/impl/AudioInfoImpl.java index 61bd0e4..97894c3 100644 --- a/core/src/main/java/com/github/gtache/autosubtitle/impl/AudioInfoImpl.java +++ b/core/src/main/java/com/github/gtache/autosubtitle/impl/AudioInfoImpl.java @@ -7,10 +7,10 @@ import java.util.Objects; /** * Implementation of {@link AudioInfo} */ -public record AudioInfoImpl(String audioFormat, long duration) implements AudioInfo { +public record AudioInfoImpl(String format, long duration) implements AudioInfo { public AudioInfoImpl { - Objects.requireNonNull(audioFormat); + Objects.requireNonNull(format); if (duration < 0) { throw new IllegalArgumentException("Duration must be positive"); } diff --git a/core/src/main/java/com/github/gtache/autosubtitle/impl/VideoInfoImpl.java b/core/src/main/java/com/github/gtache/autosubtitle/impl/VideoInfoImpl.java index 911faca..28f5b72 100644 --- a/core/src/main/java/com/github/gtache/autosubtitle/impl/VideoInfoImpl.java +++ b/core/src/main/java/com/github/gtache/autosubtitle/impl/VideoInfoImpl.java @@ -7,10 +7,10 @@ import java.util.Objects; /** * Implementation of {@link VideoInfo} */ -public record VideoInfoImpl(String videoFormat, int width, int height, long duration) implements VideoInfo { +public record VideoInfoImpl(String format, int width, int height, long duration) implements VideoInfo { public VideoInfoImpl { - Objects.requireNonNull(videoFormat); + Objects.requireNonNull(format); if (width <= 0) { throw new IllegalArgumentException("Width must be greater than 0 : " + width); } diff --git a/core/src/test/java/com/github/gtache/autosubtitle/impl/TestAudioInfoImpl.java b/core/src/test/java/com/github/gtache/autosubtitle/impl/TestAudioInfoImpl.java index cbee9a3..10f6ea2 100644 --- a/core/src/test/java/com/github/gtache/autosubtitle/impl/TestAudioInfoImpl.java +++ b/core/src/test/java/com/github/gtache/autosubtitle/impl/TestAudioInfoImpl.java @@ -11,14 +11,14 @@ class TestAudioInfoImpl { private final long duration; TestAudioInfoImpl() { - this.audioFormat = "audioFormat"; + this.audioFormat = "format"; this.duration = 1000L; } @Test void testGetters() { final var audioInfo = new AudioInfoImpl(audioFormat, duration); - assertEquals(audioFormat, audioInfo.audioFormat()); + assertEquals(audioFormat, audioInfo.format()); assertEquals(duration, audioInfo.duration()); } diff --git a/core/src/test/java/com/github/gtache/autosubtitle/impl/TestVideoInfoImpl.java b/core/src/test/java/com/github/gtache/autosubtitle/impl/TestVideoInfoImpl.java index 7b0ab6d..9605f06 100644 --- a/core/src/test/java/com/github/gtache/autosubtitle/impl/TestVideoInfoImpl.java +++ b/core/src/test/java/com/github/gtache/autosubtitle/impl/TestVideoInfoImpl.java @@ -13,7 +13,7 @@ class TestVideoInfoImpl { private final long duration; TestVideoInfoImpl() { - this.videoFormat = "videoFormat"; + this.videoFormat = "format"; this.width = 1; this.height = 2; this.duration = 3; @@ -22,7 +22,7 @@ class TestVideoInfoImpl { @Test void testGetters() { final var videoInfo = new VideoInfoImpl(videoFormat, width, height, duration); - assertEquals(videoFormat, videoInfo.videoFormat()); + assertEquals(videoFormat, videoInfo.format()); assertEquals(width, videoInfo.width()); assertEquals(height, videoInfo.height()); assertEquals(duration, videoInfo.duration()); diff --git a/core/src/test/java/com/github/gtache/autosubtitle/subtitle/extractor/impl/TestAbstractSubtitleExtractor.java b/core/src/test/java/com/github/gtache/autosubtitle/subtitle/extractor/impl/TestAbstractSubtitleExtractor.java index 2e80774..d05fca8 100644 --- a/core/src/test/java/com/github/gtache/autosubtitle/subtitle/extractor/impl/TestAbstractSubtitleExtractor.java +++ b/core/src/test/java/com/github/gtache/autosubtitle/subtitle/extractor/impl/TestAbstractSubtitleExtractor.java @@ -57,7 +57,7 @@ class TestAbstractSubtitleExtractor { private static final class DummySubtitleExtractor extends AbstractSubtitleExtractor { @Override - public SubtitleCollection extract(final Video video, final Language language, final ExtractionModel model) throws ExtractException { + public SubtitleCollection extract(final Video video, final Language language, final ExtractionModel model) throws ExtractException { throw new UnsupportedOperationException(); } diff --git a/deepl/pom.xml b/deepl/pom.xml index d270ce4..374922d 100644 --- a/deepl/pom.xml +++ b/deepl/pom.xml @@ -14,6 +14,7 @@ 1.5.0 1.2.2 + 2.9.2 @@ -28,9 +29,15 @@ ${deepl.version} - com.github.pemistahl - lingua - ${lingua.version} + org.apache.tika + tika-core + ${tika.version} + + org.apache.tika + tika-langdetect-optimaize + ${tika.version} + + \ No newline at end of file diff --git a/deepl/src/main/java/com/github/gtache/autosubtitle/modules/deepl/DeepLModule.java b/deepl/src/main/java/com/github/gtache/autosubtitle/modules/deepl/DeepLModule.java index 2af5871..88f364a 100644 --- a/deepl/src/main/java/com/github/gtache/autosubtitle/modules/deepl/DeepLModule.java +++ b/deepl/src/main/java/com/github/gtache/autosubtitle/modules/deepl/DeepLModule.java @@ -3,13 +3,14 @@ package com.github.gtache.autosubtitle.modules.deepl; import com.github.gtache.autosubtitle.modules.setup.deepl.DeepLSetupModule; import com.github.gtache.autosubtitle.translation.Translator; import com.github.gtache.autosubtitle.translation.deepl.DeepLTranslator; -import com.github.pemistahl.lingua.api.LanguageDetector; -import com.github.pemistahl.lingua.api.LanguageDetectorBuilder; import dagger.Binds; import dagger.Module; import dagger.Provides; +import org.apache.tika.language.detect.LanguageDetector; import javax.inject.Singleton; +import java.io.IOException; +import java.io.UncheckedIOException; /** * Dagger module for DeepL @@ -23,6 +24,10 @@ public abstract class DeepLModule { @Provides @Singleton static LanguageDetector providesLanguageDetector() { - return LanguageDetectorBuilder.fromAllSpokenLanguages().build(); + try { + return LanguageDetector.getDefaultLanguageDetector().loadModels(); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } } } diff --git a/deepl/src/main/java/com/github/gtache/autosubtitle/setup/deepl/DeepLSetupManager.java b/deepl/src/main/java/com/github/gtache/autosubtitle/setup/deepl/DeepLSetupManager.java index f2129c3..af8e88b 100644 --- a/deepl/src/main/java/com/github/gtache/autosubtitle/setup/deepl/DeepLSetupManager.java +++ b/deepl/src/main/java/com/github/gtache/autosubtitle/setup/deepl/DeepLSetupManager.java @@ -32,7 +32,7 @@ public class DeepLSetupManager extends AbstractSetupManager { } @Override - protected SetupStatus getStatus() throws SetupException { + protected SetupStatus getStatus() { final var key = preferences.get(DEEPL_API_KEY, null); return key == null ? SetupStatus.NOT_INSTALLED : SetupStatus.BUNDLE_INSTALLED; } @@ -68,7 +68,7 @@ public class DeepLSetupManager extends AbstractSetupManager { } @Override - public void update() throws SetupException { + public void update() { //No need to update } } diff --git a/deepl/src/main/java/com/github/gtache/autosubtitle/translation/deepl/DeepLTranslator.java b/deepl/src/main/java/com/github/gtache/autosubtitle/translation/deepl/DeepLTranslator.java index e491b13..87c5b5e 100644 --- a/deepl/src/main/java/com/github/gtache/autosubtitle/translation/deepl/DeepLTranslator.java +++ b/deepl/src/main/java/com/github/gtache/autosubtitle/translation/deepl/DeepLTranslator.java @@ -9,7 +9,7 @@ import com.github.gtache.autosubtitle.subtitle.impl.SubtitleCollectionImpl; import com.github.gtache.autosubtitle.subtitle.impl.SubtitleImpl; import com.github.gtache.autosubtitle.translation.TranslationException; import com.github.gtache.autosubtitle.translation.Translator; -import com.github.pemistahl.lingua.api.LanguageDetector; +import org.apache.tika.language.detect.LanguageDetector; import javax.inject.Inject; import java.util.ArrayList; @@ -37,7 +37,7 @@ public class DeepLTranslator implements Translator { @Override public Language getLanguage(final String text) { - return Language.getLanguage(languageDetector.detectLanguageOf(text).getIsoCode639_1().toString()); + return Language.getLanguage(languageDetector.detect(text).getLanguage()); } @Override diff --git a/deepl/src/main/java/module-info.java b/deepl/src/main/java/module-info.java index 67c0c6c..679367f 100644 --- a/deepl/src/main/java/module-info.java +++ b/deepl/src/main/java/module-info.java @@ -4,8 +4,9 @@ module com.github.gtache.autosubtitle.deepl { requires transitive com.github.gtache.autosubtitle.core; requires transitive deepl.java; - requires transitive com.github.pemistahl.lingua; requires transitive java.prefs; + requires transitive org.apache.tika.core; + requires transitive language.detector; exports com.github.gtache.autosubtitle.modules.deepl; exports com.github.gtache.autosubtitle.translation.deepl; exports com.github.gtache.autosubtitle.setup.deepl; diff --git a/deepl/src/test/java/com/github/gtache/autosubtitle/modules/deepl/TestDeepLModule.java b/deepl/src/test/java/com/github/gtache/autosubtitle/modules/deepl/TestDeepLModule.java new file mode 100644 index 0000000..88cab23 --- /dev/null +++ b/deepl/src/test/java/com/github/gtache/autosubtitle/modules/deepl/TestDeepLModule.java @@ -0,0 +1,15 @@ +package com.github.gtache.autosubtitle.modules.deepl; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class TestDeepLModule { + + @Test + void testLanguageDetector() { + final var languageDetector = DeepLModule.providesLanguageDetector(); + assertEquals("fr", languageDetector.detect("bonjour tout le monde").getLanguage()); + assertEquals("en", languageDetector.detect("hello everyone this is a text").getLanguage()); + } +} diff --git a/deepl/src/test/java/com/github/gtache/autosubtitle/setup/deepl/TestDeepLSetupManager.java b/deepl/src/test/java/com/github/gtache/autosubtitle/setup/deepl/TestDeepLSetupManager.java new file mode 100644 index 0000000..0f9fc09 --- /dev/null +++ b/deepl/src/test/java/com/github/gtache/autosubtitle/setup/deepl/TestDeepLSetupManager.java @@ -0,0 +1,99 @@ +package com.github.gtache.autosubtitle.setup.deepl; + +import com.github.gtache.autosubtitle.process.ProcessRunner; +import com.github.gtache.autosubtitle.setup.SetupException; +import com.github.gtache.autosubtitle.setup.SetupStatus; +import com.github.gtache.autosubtitle.setup.SetupUserBridge; +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.net.http.HttpClient; +import java.util.Objects; +import java.util.prefs.BackingStoreException; +import java.util.prefs.Preferences; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class TestDeepLSetupManager { + + private final SetupUserBridge userBridge; + private final Preferences preferences; + private final ProcessRunner processRunner; + private final HttpClient httpClient; + + private final DeepLSetupManager setupManager; + + TestDeepLSetupManager(@Mock final SetupUserBridge userBridge, @Mock final Preferences preferences, + @Mock final ProcessRunner processRunner, @Mock final HttpClient httpClient) { + this.userBridge = Objects.requireNonNull(userBridge); + this.preferences = Objects.requireNonNull(preferences); + this.processRunner = Objects.requireNonNull(processRunner); + this.httpClient = Objects.requireNonNull(httpClient); + this.setupManager = new DeepLSetupManager(userBridge, preferences, processRunner, httpClient); + } + + @Test + void testGetStatus() { + assertEquals(SetupStatus.NOT_INSTALLED, setupManager.getStatus()); + when(preferences.get("deepl.api.key", null)).thenReturn("key"); + assertEquals(SetupStatus.BUNDLE_INSTALLED, setupManager.getStatus()); + } + + @Test + void testInstall() throws BackingStoreException, SetupException { + final var key = "key"; + when(userBridge.askForUserInput(any())).thenReturn(key); + setupManager.install(); + verify(preferences).put("deepl.api.key", key); + verify(preferences).flush(); + } + + @Test + void testInstallException() throws BackingStoreException { + final var key = "key"; + when(userBridge.askForUserInput(any())).thenReturn(key); + doThrow(BackingStoreException.class).when(preferences).flush(); + assertThrows(SetupException.class, setupManager::install); + verify(preferences).put("deepl.api.key", key); + verify(preferences).flush(); + } + + @Test + void testInstallNull() { + assertThrows(SetupException.class, setupManager::install); + verifyNoInteractions(preferences); + } + + @Test + void testUninstall() throws BackingStoreException, SetupException { + setupManager.uninstall(); + verify(preferences).remove("deepl.api.key"); + verify(preferences).flush(); + } + + @Test + void testUninstallException() throws BackingStoreException { + doThrow(BackingStoreException.class).when(preferences).flush(); + assertThrows(SetupException.class, setupManager::uninstall); + verify(preferences).remove("deepl.api.key"); + verify(preferences).flush(); + } + + @Test + void testName() { + assertEquals("DeepL", setupManager.name()); + } + + @Test + void testIllegal() { + assertThrows(NullPointerException.class, () -> new DeepLSetupManager(null, preferences, processRunner, httpClient)); + assertThrows(NullPointerException.class, () -> new DeepLSetupManager(userBridge, null, processRunner, httpClient)); + assertThrows(NullPointerException.class, () -> new DeepLSetupManager(userBridge, preferences, null, httpClient)); + assertThrows(NullPointerException.class, () -> new DeepLSetupManager(userBridge, preferences, processRunner, null)); + } +} diff --git a/deepl/src/test/java/com/github/gtache/autosubtitle/translation/deepl/TestDeepLTranslator.java b/deepl/src/test/java/com/github/gtache/autosubtitle/translation/deepl/TestDeepLTranslator.java new file mode 100644 index 0000000..3201c0e --- /dev/null +++ b/deepl/src/test/java/com/github/gtache/autosubtitle/translation/deepl/TestDeepLTranslator.java @@ -0,0 +1,5 @@ +package com.github.gtache.autosubtitle.translation.deepl; + +//TODO mock, postman, ... +class TestDeepLTranslator { +} 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 bcea11f..0953eab 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 @@ -58,9 +58,9 @@ public class FFmpegVideoConverter implements VideoConverter { @Override public Video addSoftSubtitles(final Video video, final Collection> subtitles) throws IOException { - final var out = getTempFile(video.info().videoFormat()); + final var out = getTempFile(video.info().format()); addSoftSubtitles(video, subtitles, out); - return new FileVideoImpl(out, new VideoInfoImpl(video.info().videoFormat(), video.info().width(), video.info().height(), video.info().duration())); + return new FileVideoImpl(out, new VideoInfoImpl(video.info().format(), video.info().width(), video.info().height(), video.info().duration())); } @Override @@ -110,7 +110,7 @@ public class FFmpegVideoConverter implements VideoConverter { @Override public Video addHardSubtitles(final Video video, final SubtitleCollection subtitles) throws IOException { - final var out = getTempFile(video.info().videoFormat()); + final var out = getTempFile(video.info().format()); addHardSubtitles(video, subtitles, out); return new FileVideoImpl(out, video.info()); } @@ -145,7 +145,7 @@ public class FFmpegVideoConverter implements VideoConverter { public Audio getAudio(final Video video) throws IOException { final var videoPath = getPath(video); final var audioPath = getTempFile(".wav"); - final var dumpVideoPath = getTempFile("." + video.info().videoFormat()); + final var dumpVideoPath = getTempFile("." + video.info().format()); final var args = List.of( getFFmpegPath(), "-y", @@ -172,7 +172,7 @@ public class FFmpegVideoConverter implements VideoConverter { } private static Path dumpVideo(final Video video) throws IOException { - final var path = getTempFile(video.info().videoFormat()); + final var path = getTempFile(video.info().format()); try (final var out = Files.newOutputStream(path)) { video.getInputStream().transferTo(out); } diff --git a/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/ParametersModel.java b/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/ParametersModel.java index e64e75c..5c9d87d 100644 --- a/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/ParametersModel.java +++ b/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/ParametersModel.java @@ -16,7 +16,7 @@ public interface ParametersModel { List availableExtractionModels(); /** - * @return The current extraction model + * @return The current extraction model setting */ ExtractionModel extractionModel(); @@ -31,7 +31,7 @@ public interface ParametersModel { List availableOutputFormats(); /** - * @return The current output format + * @return The current output format setting */ OutputFormat outputFormat(); @@ -46,7 +46,7 @@ public interface ParametersModel { List availableFontFamilies(); /** - * @return The current font family + * @return The current font family setting */ String fontName(); @@ -56,7 +56,7 @@ public interface ParametersModel { void setFontName(String fontFamily); /** - * @return The current font size + * @return The current font size setting */ int fontSize(); @@ -65,11 +65,27 @@ public interface ParametersModel { */ void setFontSize(int fontSize); + /** + * @return The current max line length setting + */ int maxLineLength(); + /** + * Sets the max line length + * + * @param maxLineLength The new max line length + */ void setMaxLineLength(int maxLineLength); + /** + * @return The current max lines setting + */ int maxLines(); + /** + * Sets the max lines + * + * @param maxLines The new max lines + */ void setMaxLines(int maxLines); } diff --git a/gui/fx/src/test/java/com/github/gtache/autosubtitle/gui/fx/TestColonTimeFormatter.java b/gui/fx/src/test/java/com/github/gtache/autosubtitle/gui/fx/TestColonTimeFormatter.java index 5b78dbf..0273468 100644 --- a/gui/fx/src/test/java/com/github/gtache/autosubtitle/gui/fx/TestColonTimeFormatter.java +++ b/gui/fx/src/test/java/com/github/gtache/autosubtitle/gui/fx/TestColonTimeFormatter.java @@ -12,15 +12,15 @@ class TestColonTimeFormatter { @ParameterizedTest @CsvSource({ - "12:34:56,45296000", - "12:34,754000", - "01:02,62000", - "1:2,62000", - "01:02:03,3723000", - "1:2:3,3723000", - "00:00:03,3000", - "00:03,3000", - "1234:00:01,4442401000" + "12:34:56.000,45296000", + "12:34.321,754321", + "01:02.000,62000", + "1:2.555,62555", + "01:02:03.000,3723000", + "1:2:3.000,3723000", + "00:00:03.000,3000", + "00:03.000,3000", + "1234:00:01.000,4442401000" }) void testParse(final String time, final long millis) { assertEquals(millis, timeFormatter.parse(time)); @@ -28,14 +28,14 @@ class TestColonTimeFormatter { @ParameterizedTest @CsvSource({ - "45296000,12:34:56", - "45296521,12:34:56", - "754000,12:34", - "754620,12:34", - "62000,01:02", - "3723000,1:02:03", - "3000,00:03", - "4442401000,1234:00:01" + "45296000,12:34:56.000", + "45296521,12:34:56.521", + "754000,12:34.000", + "754620,12:34.620", + "62000,01:02.000", + "3723000,1:02:03.000", + "3000,00:03.000", + "4442401000,1234:00:01.000" }) void testFormat(final long millis, final String time) { assertEquals(time, timeFormatter.format(millis)); diff --git a/gui/run/src/main/java/module-info.java b/gui/run/src/main/java/module-info.java index dfaf10a..c6c0877 100644 --- a/gui/run/src/main/java/module-info.java +++ b/gui/run/src/main/java/module-info.java @@ -8,6 +8,7 @@ module com.github.gtache.autosubtitle.gui.run { requires com.github.gtache.autosubtitle.whisperx; requires javafx.fxml; requires javafx.graphics; + requires guava; opens com.github.gtache.autosubtitle.gui.run to javafx.graphics; } \ No newline at end of file diff --git a/whisper/base/src/main/java/com/github/gtache/autosubtitle/modules/whisper/base/WhisperModule.java b/whisper/base/src/main/java/com/github/gtache/autosubtitle/modules/whisper/base/WhisperModule.java index d5fa3d9..1921f89 100644 --- a/whisper/base/src/main/java/com/github/gtache/autosubtitle/modules/whisper/base/WhisperModule.java +++ b/whisper/base/src/main/java/com/github/gtache/autosubtitle/modules/whisper/base/WhisperModule.java @@ -4,7 +4,7 @@ import com.github.gtache.autosubtitle.modules.setup.whisper.base.WhisperSetupMod import com.github.gtache.autosubtitle.modules.subtitle.extractor.whisper.base.WhisperExtractorModule; import com.github.gtache.autosubtitle.modules.subtitle.parser.json.whisper.base.WhisperJsonModule; import com.github.gtache.autosubtitle.subtitle.extractor.ExtractionModelProvider; -import com.github.gtache.autosubtitle.whisper.WhisperExtractionModelProvider; +import com.github.gtache.autosubtitle.whisper.base.WhisperExtractionModelProvider; import dagger.Binds; import dagger.Module; diff --git a/whisper/common/src/main/java/com/github/gtache/autosubtitle/whisper/WhisperExtractionModelProvider.java b/whisper/base/src/main/java/com/github/gtache/autosubtitle/whisper/base/WhisperExtractionModelProvider.java similarity index 88% rename from whisper/common/src/main/java/com/github/gtache/autosubtitle/whisper/WhisperExtractionModelProvider.java rename to whisper/base/src/main/java/com/github/gtache/autosubtitle/whisper/base/WhisperExtractionModelProvider.java index 5957f53..d39ee77 100644 --- a/whisper/common/src/main/java/com/github/gtache/autosubtitle/whisper/WhisperExtractionModelProvider.java +++ b/whisper/base/src/main/java/com/github/gtache/autosubtitle/whisper/base/WhisperExtractionModelProvider.java @@ -1,7 +1,8 @@ -package com.github.gtache.autosubtitle.whisper; +package com.github.gtache.autosubtitle.whisper.base; import com.github.gtache.autosubtitle.subtitle.extractor.ExtractionModel; import com.github.gtache.autosubtitle.subtitle.extractor.ExtractionModelProvider; +import com.github.gtache.autosubtitle.whisper.WhisperModels; import javax.inject.Inject; import javax.inject.Singleton; diff --git a/whisper/base/src/main/java/module-info.java b/whisper/base/src/main/java/module-info.java index 508f7c6..120f24e 100644 --- a/whisper/base/src/main/java/module-info.java +++ b/whisper/base/src/main/java/module-info.java @@ -8,8 +8,9 @@ module com.github.gtache.autosubtitle.whisper.base { requires com.google.gson; exports com.github.gtache.autosubtitle.setup.whisper.base; - exports com.github.gtache.autosubtitle.modules.whisper.base; - exports com.github.gtache.autosubtitle.modules.setup.whisper.base; exports com.github.gtache.autosubtitle.subtitle.extractor.whisper.base; + exports com.github.gtache.autosubtitle.whisper.base; + exports com.github.gtache.autosubtitle.modules.setup.whisper.base; exports com.github.gtache.autosubtitle.modules.subtitle.extractor.whisper.base; + exports com.github.gtache.autosubtitle.modules.whisper.base; } \ No newline at end of file diff --git a/whisper/base/src/test/java/com/github/gtache/autosubtitle/whisper/base/TestWhisperExtractionModelProvider.java b/whisper/base/src/test/java/com/github/gtache/autosubtitle/whisper/base/TestWhisperExtractionModelProvider.java new file mode 100644 index 0000000..484114f --- /dev/null +++ b/whisper/base/src/test/java/com/github/gtache/autosubtitle/whisper/base/TestWhisperExtractionModelProvider.java @@ -0,0 +1,32 @@ +package com.github.gtache.autosubtitle.whisper.base; + +import com.github.gtache.autosubtitle.subtitle.extractor.ExtractionModelProvider; +import com.github.gtache.autosubtitle.whisper.WhisperModels; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class TestWhisperExtractionModelProvider { + + private final ExtractionModelProvider provider = new WhisperExtractionModelProvider(); + + @Test + void testGetAvailableExtractionModels() { + assertEquals(Arrays.asList(WhisperModels.values()), provider.getAvailableExtractionModels()); + } + + @Test + void testGetDefaultExtractionModel() { + assertEquals(WhisperModels.MEDIUM, provider.getDefaultExtractionModel()); + } + + @Test + void testGetExtractionModel() { + for (final var value : WhisperModels.values()) { + assertEquals(value, provider.getExtractionModel(value.name())); + assertEquals(value, provider.getExtractionModel(value.name().toLowerCase())); + } + } +} diff --git a/whisper/common/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/WhisperCommonSetupModule.java b/whisper/common/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/WhisperCommonSetupModule.java index 26e875f..f4498e0 100644 --- a/whisper/common/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/WhisperCommonSetupModule.java +++ b/whisper/common/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/WhisperCommonSetupModule.java @@ -22,5 +22,4 @@ public final class WhisperCommonSetupModule { final OS os) { return new WhisperSetupConfiguration(root, venvPath, pythonVersion, os); } - } diff --git a/whisper/common/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/AbstractWhisperSubtitleExtractor.java b/whisper/common/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/AbstractWhisperSubtitleExtractor.java index afd2956..4723396 100644 --- a/whisper/common/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/AbstractWhisperSubtitleExtractor.java +++ b/whisper/common/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/AbstractWhisperSubtitleExtractor.java @@ -23,6 +23,7 @@ import org.apache.logging.log4j.Logger; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.time.Duration; import java.util.HashSet; import java.util.List; @@ -72,45 +73,40 @@ public abstract class AbstractWhisperSubtitleExtractor implements SubtitleExtrac listeners.clear(); } - private void notifyListeners(final ExtractEvent event) { + void notifyListeners(final ExtractEvent event) { listeners.forEach(listener -> listener.listen(event)); } + @Override public SubtitleCollection extract(final Video video, final Language language, final ExtractionModel model) throws ExtractException { - if (video instanceof final File f) { - return extract(f.path(), language, model, video.info().duration()); + return extract(new AudioOrVideo(video), language, model); + } + + @Override + public SubtitleCollection extract(final Audio audio, final Language language, final ExtractionModel model) throws ExtractException { + return extract(new AudioOrVideo(audio), language, model); + } + + private SubtitleCollection extract(final AudioOrVideo av, final Language language, final ExtractionModel model) throws ExtractException { + if (av.inner() instanceof final File f) { + return extract(f.path(), language, model, av.info().duration()); } else { try { - final var path = Files.createTempFile(AUTOSUBTITLE, video.info().videoFormat()); - try (final var in = video.getInputStream()) { - Files.copy(in, path); - final var ret = extract(path, language, model, video.info().duration()); - Files.deleteIfExists(path); - return ret; - } + return dumpExtract(av, language, model); } catch (final IOException e) { throw new ExtractException(e); } } } - @Override - public SubtitleCollection extract(final Audio audio, final Language language, final ExtractionModel model) throws ExtractException { - if (audio instanceof final File f) { - return extract(f.path(), language, model, audio.info().duration()); - } else { - try { - final var path = Files.createTempFile(AUTOSUBTITLE, audio.info().audioFormat()); - try (final var in = audio.getInputStream()) { - Files.copy(in, path); - final var ret = extract(path, language, model, audio.info().duration()); - Files.deleteIfExists(path); - return ret; - } - } catch (final IOException e) { - throw new ExtractException(e); - } + private SubtitleCollection dumpExtract(final AudioOrVideo av, final Language language, final ExtractionModel model) throws ExtractException, IOException { + final var path = Files.createTempFile(AUTOSUBTITLE, "." + av.info().format()); + try (final var in = av.getInputStream()) { + Files.copy(in, path, StandardCopyOption.REPLACE_EXISTING); + return extract(path, language, model, av.info().duration()); + } finally { + Files.deleteIfExists(path); } } 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 new file mode 100644 index 0000000..6292431 --- /dev/null +++ b/whisper/common/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/AudioOrVideo.java @@ -0,0 +1,57 @@ +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 java.io.IOException; +import java.io.InputStream; + +import static java.util.Objects.requireNonNull; + +/** + * Used internally for the Whisper subtitle extractor. Either audio or video must be not null + * + * @param audio The possible audio + * @param video The possible video + */ +public record AudioOrVideo(Audio audio, Video video) implements Audio { + + public AudioOrVideo { + if (audio == null) { + requireNonNull(video); + } else if (video != null) { + throw new IllegalArgumentException("Either audio or video must be null"); + } + } + + public AudioOrVideo(final Audio audio) { + this(audio, null); + } + + public AudioOrVideo(final Video video) { + this(null, video); + } + + public T inner() { + return (T) (audio == null ? video : audio); + } + + @Override + public InputStream getInputStream() throws IOException { + if (audio == null) { + return video.getInputStream(); + } else { + return audio.getInputStream(); + } + } + + @Override + public AudioInfo info() { + if (audio == null) { + return video.info(); + } else { + return audio.info(); + } + } +} diff --git a/whisper/common/src/test/java/com/github/gtache/autosubtitle/modules/setup/whisper/TestWhisperCommonSetupModule.java b/whisper/common/src/test/java/com/github/gtache/autosubtitle/modules/setup/whisper/TestWhisperCommonSetupModule.java new file mode 100644 index 0000000..07069b4 --- /dev/null +++ b/whisper/common/src/test/java/com/github/gtache/autosubtitle/modules/setup/whisper/TestWhisperCommonSetupModule.java @@ -0,0 +1,23 @@ +package com.github.gtache.autosubtitle.modules.setup.whisper; + +import com.github.gtache.autosubtitle.impl.OS; +import com.github.gtache.autosubtitle.setup.whisper.WhisperSetupConfiguration; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; + +class TestWhisperCommonSetupModule { + + @Test + void testWhisperSetupConfiguration() { + final var root = mock(Path.class); + final var venvPath = mock(Path.class); + final var pythonVersion = "3.10"; + final var os = OS.LINUX; + final var expected = new WhisperSetupConfiguration(root, venvPath, pythonVersion, os); + assertEquals(expected, WhisperCommonSetupModule.providesWhisperSetupConfiguration(root, venvPath, pythonVersion, os)); + } +} diff --git a/whisper/common/src/test/java/com/github/gtache/autosubtitle/setup/whisper/TestAbstractWhisperSetupManager.java b/whisper/common/src/test/java/com/github/gtache/autosubtitle/setup/whisper/TestAbstractWhisperSetupManager.java new file mode 100644 index 0000000..c0c8e6a --- /dev/null +++ b/whisper/common/src/test/java/com/github/gtache/autosubtitle/setup/whisper/TestAbstractWhisperSetupManager.java @@ -0,0 +1,171 @@ +package com.github.gtache.autosubtitle.setup.whisper; + +import com.github.gtache.autosubtitle.impl.OS; +import com.github.gtache.autosubtitle.process.ProcessRunner; +import com.github.gtache.autosubtitle.setup.SetupException; +import com.github.gtache.autosubtitle.setup.conda.CondaSetupManager; +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; + +import java.io.IOException; +import java.net.http.HttpClient; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Objects; + +import static com.github.gtache.autosubtitle.setup.SetupStatus.BUNDLE_INSTALLED; +import static com.github.gtache.autosubtitle.setup.SetupStatus.NOT_INSTALLED; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class TestAbstractWhisperSetupManager { + + private final CondaSetupManager condaSetupManager; + private final WhisperSetupConfiguration configuration; + private final ProcessRunner processRunner; + private final HttpClient httpClient; + private final AbstractWhisperSetupManager setupManager; + + TestAbstractWhisperSetupManager(@Mock final CondaSetupManager condaSetupManager, @Mock final WhisperSetupConfiguration configuration, + @Mock final ProcessRunner processRunner, @Mock final HttpClient httpClient) { + this.condaSetupManager = Objects.requireNonNull(condaSetupManager); + when(condaSetupManager.name()).thenReturn("conda"); + this.configuration = Objects.requireNonNull(configuration); + this.processRunner = Objects.requireNonNull(processRunner); + this.httpClient = Objects.requireNonNull(httpClient); + this.setupManager = spy(new DummyWhisperSetupManager(condaSetupManager, configuration, processRunner, httpClient)); + } + + @Test + void testGetStatus() throws SetupException { + assertEquals(NOT_INSTALLED, setupManager.getStatus()); + doReturn(true).when(setupManager).isWhisperInstalled(); + assertEquals(BUNDLE_INSTALLED, setupManager.getStatus()); + } + + @Test + void testInstallAlreadyInstalled() throws SetupException { + final var venvPath = Paths.get("path"); + when(configuration.venvPath()).thenReturn(venvPath); + when(condaSetupManager.isInstalled()).thenReturn(true); + when(condaSetupManager.venvExists(venvPath)).thenReturn(true); + doReturn(true).when(setupManager).isWhisperInstalled(); + setupManager.install(); + verify(condaSetupManager, never()).install(); + verify(condaSetupManager, never()).createVenv(any(), any()); + verify(setupManager, never()).installWhisper(); + } + + @Test + void testInstallWhisper() throws SetupException { + final var venvPath = Paths.get("path"); + when(configuration.venvPath()).thenReturn(venvPath); + when(condaSetupManager.isInstalled()).thenReturn(true); + when(condaSetupManager.venvExists(venvPath)).thenReturn(true); + setupManager.install(); + verify(condaSetupManager, never()).install(); + verify(condaSetupManager, never()).createVenv(any(), any()); + verify(setupManager).installWhisper(); + } + + @Test + void testInstallVenv() throws SetupException { + final var venvPath = Paths.get("path"); + final var pythonVersion = "3.10"; + when(configuration.venvPath()).thenReturn(venvPath); + when(configuration.pythonVersion()).thenReturn(pythonVersion); + when(condaSetupManager.isInstalled()).thenReturn(true); + doReturn(true).when(setupManager).isWhisperInstalled(); + setupManager.install(); + verify(condaSetupManager, never()).install(); + verify(condaSetupManager).createVenv(venvPath, pythonVersion); + verify(setupManager, never()).installWhisper(); + } + + @Test + void testInstallConda() throws SetupException { + final var venvPath = Paths.get("path"); + final var pythonVersion = "3.10"; + when(configuration.venvPath()).thenReturn(venvPath); + when(configuration.pythonVersion()).thenReturn(pythonVersion); + when(condaSetupManager.isInstalled()).thenReturn(false); + when(condaSetupManager.venvExists(venvPath)).thenReturn(true); + doReturn(true).when(setupManager).isWhisperInstalled(); + setupManager.install(); + verify(condaSetupManager).install(); + verify(condaSetupManager, never()).createVenv(any(), any()); + verify(setupManager, never()).installWhisper(); + } + + @Test + void testUninstall(@TempDir final Path tempDir) throws SetupException, IOException { + final var subfolder = tempDir.resolve("subfolder"); + Files.createDirectory(subfolder); + final var subfile = subfolder.resolve("subfile"); + Files.createFile(subfile); + final var rootFile = tempDir.resolve("rootfile"); + Files.createFile(rootFile); + when(configuration.root()).thenReturn(tempDir); + setupManager.uninstall(); + assertFalse(Files.exists(tempDir)); + } + + @Test + void testUpdateNotAvailable() throws SetupException { + setupManager.update(); + verify(condaSetupManager).isUpdateAvailable(); + verify(condaSetupManager, never()).update(); + } + + @Test + void testUpdateAvailable() throws SetupException { + when(condaSetupManager.isUpdateAvailable()).thenReturn(true); + setupManager.update(); + verify(condaSetupManager).isUpdateAvailable(); + verify(condaSetupManager).update(); + } + + @Test + void testGetPythonPath() { + final var path = Paths.get("path"); + when(configuration.venvPath()).thenReturn(path); + when(configuration.os()).thenReturn(OS.WINDOWS); + assertEquals(path.resolve("python.exe"), setupManager.getPythonPath()); + when(configuration.os()).thenReturn(OS.LINUX); + assertEquals(path.resolve("python"), setupManager.getPythonPath()); + } + + @Test + void testIllegal() { + assertThrows(NullPointerException.class, () -> new DummyWhisperSetupManager(null, configuration, processRunner, httpClient)); + assertThrows(NullPointerException.class, () -> new DummyWhisperSetupManager(condaSetupManager, null, processRunner, httpClient)); + assertThrows(NullPointerException.class, () -> new DummyWhisperSetupManager(condaSetupManager, configuration, null, httpClient)); + assertThrows(NullPointerException.class, () -> new DummyWhisperSetupManager(condaSetupManager, configuration, processRunner, null)); + } + + private static final class DummyWhisperSetupManager extends AbstractWhisperSetupManager { + + private DummyWhisperSetupManager(final CondaSetupManager condaSetupManager, final WhisperSetupConfiguration configuration, final ProcessRunner processRunner, final HttpClient httpClient) { + super(condaSetupManager, configuration, processRunner, httpClient); + } + + @Override + protected void installWhisper() throws SetupException { + } + + @Override + protected boolean isWhisperInstalled() throws SetupException { + return false; + } + + @Override + public String name() { + return "Dummy"; + } + } +} diff --git a/whisper/common/src/test/java/com/github/gtache/autosubtitle/setup/whisper/TestWhisperSetupConfiguration.java b/whisper/common/src/test/java/com/github/gtache/autosubtitle/setup/whisper/TestWhisperSetupConfiguration.java new file mode 100644 index 0000000..939b04b --- /dev/null +++ b/whisper/common/src/test/java/com/github/gtache/autosubtitle/setup/whisper/TestWhisperSetupConfiguration.java @@ -0,0 +1,46 @@ +package com.github.gtache.autosubtitle.setup.whisper; + +import com.github.gtache.autosubtitle.impl.OS; +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.nio.file.Path; +import java.util.Objects; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@ExtendWith(MockitoExtension.class) +class TestWhisperSetupConfiguration { + + private final Path root; + private final Path venvPath; + private final String pythonVersion; + private final OS os; + + TestWhisperSetupConfiguration(@Mock final Path root, @Mock final Path venvPath) { + this.root = Objects.requireNonNull(root); + this.venvPath = Objects.requireNonNull(venvPath); + this.pythonVersion = "3.10"; + this.os = OS.LINUX; + } + + @Test + void testGetters() { + final var configuration = new WhisperSetupConfiguration(root, venvPath, pythonVersion, os); + assertEquals(root, configuration.root()); + assertEquals(venvPath, configuration.venvPath()); + assertEquals(pythonVersion, configuration.pythonVersion()); + assertEquals(os, configuration.os()); + } + + @Test + void testIllegal() { + assertThrows(NullPointerException.class, () -> new WhisperSetupConfiguration(null, venvPath, pythonVersion, os)); + assertThrows(NullPointerException.class, () -> new WhisperSetupConfiguration(root, null, pythonVersion, os)); + assertThrows(NullPointerException.class, () -> new WhisperSetupConfiguration(root, venvPath, null, os)); + assertThrows(NullPointerException.class, () -> new WhisperSetupConfiguration(root, venvPath, pythonVersion, null)); + } +} diff --git a/whisper/common/src/test/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/TestAbstractWhisperSubtitleExtractor.java b/whisper/common/src/test/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/TestAbstractWhisperSubtitleExtractor.java new file mode 100644 index 0000000..f7f8827 --- /dev/null +++ b/whisper/common/src/test/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/TestAbstractWhisperSubtitleExtractor.java @@ -0,0 +1,211 @@ +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.Language; +import com.github.gtache.autosubtitle.Video; +import com.github.gtache.autosubtitle.VideoInfo; +import com.github.gtache.autosubtitle.impl.FileAudioImpl; +import com.github.gtache.autosubtitle.impl.OS; +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.ParseException; +import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter; +import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverterProvider; +import com.github.gtache.autosubtitle.subtitle.extractor.ExtractEvent; +import com.github.gtache.autosubtitle.subtitle.extractor.ExtractException; +import com.github.gtache.autosubtitle.subtitle.extractor.ExtractionModel; +import com.github.gtache.autosubtitle.subtitle.extractor.SubtitleExtractorListener; +import com.github.gtache.autosubtitle.subtitle.extractor.impl.ExtractEventImpl; +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.ByteArrayInputStream; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +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.*; + +@ExtendWith(MockitoExtension.class) +class TestAbstractWhisperSubtitleExtractor { + + private final Path venvPath; + private final SubtitleConverter converter; + private final SubtitleConverterProvider converterProvider; + private final ProcessRunner processRunner; + private final ProcessListener processListener; + private final ProcessResult processResult; + private final OS os; + private final DummyWhisperSubtitleExtractor extractor; + + private final AudioInfo audioInfo; + private final VideoInfo videoInfo; + private final ExtractionModel extractionModel; + private final SubtitleCollection collection; + + + TestAbstractWhisperSubtitleExtractor(@Mock final SubtitleConverterProvider converterProvider, @Mock final SubtitleConverter converter, + @Mock final ProcessRunner processRunner, @Mock final ProcessListener processListener, + @Mock final ProcessResult processResult, @Mock final VideoInfo videoInfo, + @Mock final AudioInfo audioInfo, @Mock final ExtractionModel extractionModel, + @Mock final SubtitleCollection collection) throws IOException { + this.venvPath = Path.of("venv"); + this.os = OS.LINUX; + this.converterProvider = Objects.requireNonNull(converterProvider); + this.converter = Objects.requireNonNull(converter); + doReturn(converter).when(converterProvider).getConverter("json"); + this.processRunner = Objects.requireNonNull(processRunner); + this.processListener = Objects.requireNonNull(processListener); + this.processResult = Objects.requireNonNull(processResult); + when(processRunner.startListen(anyList())).thenReturn(processListener); + when(processListener.join(Duration.ofHours(1))).thenReturn(processResult); + this.extractor = new DummyWhisperSubtitleExtractor(venvPath, converterProvider, processRunner, os); + + this.audioInfo = Objects.requireNonNull(audioInfo); + when(audioInfo.format()).thenReturn("mp3"); + this.videoInfo = Objects.requireNonNull(videoInfo); + when(videoInfo.format()).thenReturn("mp4"); + this.extractionModel = Objects.requireNonNull(extractionModel); + this.collection = Objects.requireNonNull(collection); + } + + @Test + void testNotifyListeners() { + final var listener1 = mock(SubtitleExtractorListener.class); + final var listener2 = mock(SubtitleExtractorListener.class); + final var event = mock(ExtractEvent.class); + extractor.addListener(listener1); + extractor.addListener(listener2); + + extractor.notifyListeners(event); + + verify(listener1).listen(event); + verify(listener2).listen(event); + } + + @Test + void testRemoveListener() { + final var listener1 = mock(SubtitleExtractorListener.class); + final var listener2 = mock(SubtitleExtractorListener.class); + extractor.addListener(listener1); + extractor.addListener(listener2); + extractor.removeListener(listener2); + extractor.notifyListeners(mock(ExtractEvent.class)); + verify(listener1).listen(any()); + verifyNoInteractions(listener2); + } + + @Test + void testRemoveListeners() { + final var listener1 = mock(SubtitleExtractorListener.class); + final var listener2 = mock(SubtitleExtractorListener.class); + extractor.addListener(listener1); + extractor.addListener(listener2); + extractor.removeListeners(); + extractor.notifyListeners(mock(ExtractEvent.class)); + verifyNoInteractions(listener1, listener2); + } + + @Test + void testExtractAudioNotFileException() throws IOException { + final var audio = mock(Audio.class); + when(audio.info()).thenReturn(audioInfo); + when(audio.getInputStream()).thenThrow(IOException.class); + assertThrows(ExtractException.class, () -> extractor.extract(audio, Language.EN, extractionModel)); + } + + @Test + void testExtractAudioFileException() throws IOException { + final var path = Paths.get("path"); + final var audio = new FileAudioImpl(path, audioInfo); + doThrow(IOException.class).when(processListener).readLine(); + assertThrows(ExtractException.class, () -> extractor.extract(audio, Language.EN, extractionModel)); + } + + @Test + void testExtractAudioFileParseException() throws ParseException { + final var path = Paths.get("path.path"); + final var audio = new FileAudioImpl(path, audioInfo); + doThrow(ParseException.class).when(converter).parse(any(Path.class)); + assertThrows(ExtractException.class, () -> extractor.extract(audio, Language.EN, extractionModel)); + verify(converter).parse(any(Path.class)); + } + + @Test + void testExtractAudioFileBadResultCode() { + final var path = Paths.get("path"); + final var audio = new FileAudioImpl(path, audioInfo); + when(processResult.exitCode()).thenReturn(1); + assertThrows(ExtractException.class, () -> extractor.extract(audio, Language.EN, extractionModel)); + } + + @Test + void testExtractVideoNotFileException() throws IOException { + final var video = mock(Video.class); + when(video.info()).thenReturn(videoInfo); + when(video.getInputStream()).thenThrow(IOException.class); + assertThrows(ExtractException.class, () -> extractor.extract(video, Language.EN, extractionModel)); + } + + @Test + void testExtractVideoFile() throws IOException, ParseException, ExtractException { + final var video = mock(Video.class); + when(video.info()).thenReturn(videoInfo); + final var in = new ByteArrayInputStream("test".getBytes()); + when(video.getInputStream()).thenReturn(in); + when(converter.parse(any(Path.class))).thenReturn(collection); + assertEquals(collection, extractor.extract(video, Language.EN, extractionModel)); + } + + @Test + void testReadLines() throws IOException, ExtractException, ParseException { + final var video = mock(Video.class); + when(video.info()).thenReturn(videoInfo); + when(videoInfo.duration()).thenReturn(100000L); + final var in = new ByteArrayInputStream("test".getBytes()); + when(video.getInputStream()).thenReturn(in); + when(converter.parse(any(Path.class))).thenReturn(collection); + when(processListener.readLine()).thenReturn("Progress: 1.7abcd", "[00:12.234 --> 00:13.234] Hello", "98%|bbb", "abcd", null); + final var listener = mock(SubtitleExtractorListener.class); + extractor.addListener(listener); + assertEquals(collection, extractor.extract(video, Language.EN, extractionModel)); + verify(listener).listen(new ExtractEventImpl("Progress: 1.7abcd", 0.017)); + verify(listener).listen(new ExtractEventImpl("[00:12.234 --> 00:13.234] Hello", 0.13234)); + verify(listener).listen(new ExtractEventImpl("98%|bbb", 0.98)); + verify(listener).listen(new ExtractEventImpl("abcd", 0.98)); + } + + @Test + void testGetPythonPath() { + assertEquals(venvPath.resolve("python"), extractor.getPythonPath()); + } + + @Test + void testGetPythonPathWindows() { + final var windowsExtractor = new DummyWhisperSubtitleExtractor(venvPath, converterProvider, processRunner, OS.WINDOWS); + assertEquals(venvPath.resolve("python.exe"), windowsExtractor.getPythonPath()); + } + + private static final class DummyWhisperSubtitleExtractor extends AbstractWhisperSubtitleExtractor { + + private DummyWhisperSubtitleExtractor(final Path venvPath, final SubtitleConverterProvider converterProvider, final ProcessRunner processRunner, final OS os) { + super(venvPath, converterProvider, processRunner, os); + } + + @Override + protected List createArgs(final Path path, final Language language, final ExtractionModel model, final Path outputDir) { + return List.of(path.toString(), language.toString(), model.toString(), outputDir.toString()); + } + } +} diff --git a/whisper/common/src/test/java/com/github/gtache/autosubtitle/whisper/TestWhisperModels.java b/whisper/common/src/test/java/com/github/gtache/autosubtitle/whisper/TestWhisperModels.java new file mode 100644 index 0000000..10000b8 --- /dev/null +++ b/whisper/common/src/test/java/com/github/gtache/autosubtitle/whisper/TestWhisperModels.java @@ -0,0 +1,15 @@ +package com.github.gtache.autosubtitle.whisper; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class TestWhisperModels { + + @Test + void testHasEnglishSpecific() { + for (final var model : WhisperModels.values()) { + assertEquals(model != WhisperModels.LARGE, model.hasEnglishSpecific()); + } + } +}