diff --git a/.idea/encodings.xml b/.idea/encodings.xml index 6b5c9e1..412d5ee 100644 --- a/.idea/encodings.xml +++ b/.idea/encodings.xml @@ -5,12 +5,16 @@ + + + + diff --git a/api/src/main/java/com/github/gtache/autosubtitle/Translator.java b/api/src/main/java/com/github/gtache/autosubtitle/Translator.java index b80965a..0cee55c 100644 --- a/api/src/main/java/com/github/gtache/autosubtitle/Translator.java +++ b/api/src/main/java/com/github/gtache/autosubtitle/Translator.java @@ -1,20 +1,90 @@ package com.github.gtache.autosubtitle; import com.github.gtache.autosubtitle.subtitle.Subtitle; +import com.github.gtache.autosubtitle.subtitle.SubtitleCollection; import java.util.Locale; +/** + * Translates texts and subtitles + */ public interface Translator { + /** + * Guesses the locale of the given text + * + * @param text The text + * @return The guessed locale + */ + Locale getLocale(final String text); + + /** + * Guesses the locale of the given subtitle + * + * @param subtitle The subtitle + * @return The guessed locale + */ + default Locale getLocale(final Subtitle subtitle) { + return getLocale(subtitle.content()); + } + + /** + * Translates the given text to the given locale + * + * @param text The text to translate + * @param to The target locale + * @return The translated text + */ String translate(String text, Locale to); + /** + * Translates the given text to the given locale + * + * @param text The text to translate + * @param to The target locale + * @return The translated text + */ default String translate(final String text, final String to) { return translate(text, Locale.forLanguageTag(to)); } + /** + * Translates the given subtitle to the given locale + * + * @param subtitle The subtitle to translate + * @param to The target locale + * @return The translated subtitle + */ Subtitle translate(Subtitle subtitle, Locale to); + /** + * Translates the given subtitle to the given locale + * + * @param subtitle The subtitle to translate + * @param to The target locale + * @return The translated subtitle + */ default Subtitle translate(final Subtitle subtitle, final String to) { return translate(subtitle, Locale.forLanguageTag(to)); } + + /** + * Translates the given subtitles collection to the given locale + * + * @param collection The subtitles collection to translate + * @param to The target locale + * @return The translated subtitles collection + */ + default SubtitleCollection translate(final SubtitleCollection collection, final String to) { + return translate(collection, Locale.forLanguageTag(to)); + } + + /** + * Translates the given subtitles collection to the given locale + * + * @param collection The subtitles collection to translate + * @param to The target locale + * @return The translated subtitles collection + */ + SubtitleCollection translate(SubtitleCollection collection, Locale to); } 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 d3a56e9..e929912 100644 --- a/api/src/main/java/com/github/gtache/autosubtitle/Video.java +++ b/api/src/main/java/com/github/gtache/autosubtitle/Video.java @@ -18,4 +18,5 @@ 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 b2e940b..d557606 100644 --- a/api/src/main/java/com/github/gtache/autosubtitle/VideoInfo.java +++ b/api/src/main/java/com/github/gtache/autosubtitle/VideoInfo.java @@ -8,4 +8,27 @@ public interface VideoInfo { * @return The video extension (mp4, etc.) */ String videoFormat(); + + /** + * @return The video width in pixels + */ + int width(); + + /** + * @return The video height in pixels + */ + int height(); + + + /** + * @return The video duration in milliseconds + */ + long duration(); + + /** + * @return The aspect ratio of the video + */ + default double aspectRatio() { + return (double) width() / height(); + } } diff --git a/api/src/main/java/com/github/gtache/autosubtitle/VideoLoader.java b/api/src/main/java/com/github/gtache/autosubtitle/VideoLoader.java new file mode 100644 index 0000000..6fd0bc7 --- /dev/null +++ b/api/src/main/java/com/github/gtache/autosubtitle/VideoLoader.java @@ -0,0 +1,20 @@ +package com.github.gtache.autosubtitle; + +import java.io.IOException; +import java.nio.file.Path; + +/** + * Loads videos + */ +@FunctionalInterface +public interface VideoLoader { + + /** + * Loads a video + * + * @param path The path to the video + * @return The loaded video + * @throws IOException If an error occurred + */ + Video loadVideo(final Path path) throws IOException; +} diff --git a/api/src/main/java/com/github/gtache/autosubtitle/setup/SetupException.java b/api/src/main/java/com/github/gtache/autosubtitle/setup/SetupException.java index f6e7236..b3b9e3f 100644 --- a/api/src/main/java/com/github/gtache/autosubtitle/setup/SetupException.java +++ b/api/src/main/java/com/github/gtache/autosubtitle/setup/SetupException.java @@ -1,5 +1,8 @@ package com.github.gtache.autosubtitle.setup; +/** + * Exception thrown when an error occurs during setup + */ public class SetupException extends Exception { public SetupException(final String message) { diff --git a/api/src/main/java/com/github/gtache/autosubtitle/setup/SetupManager.java b/api/src/main/java/com/github/gtache/autosubtitle/setup/SetupManager.java index da4e4de..b95c0c0 100644 --- a/api/src/main/java/com/github/gtache/autosubtitle/setup/SetupManager.java +++ b/api/src/main/java/com/github/gtache/autosubtitle/setup/SetupManager.java @@ -20,7 +20,7 @@ public interface SetupManager { * @throws SetupException if an error occurred during the check */ default boolean isInstalled() throws SetupException { - return status() != SetupStatus.NOT_INSTALLED; + return status().isInstalled(); } /** diff --git a/api/src/main/java/com/github/gtache/autosubtitle/setup/SetupStatus.java b/api/src/main/java/com/github/gtache/autosubtitle/setup/SetupStatus.java index 2be94e7..15fbbcb 100644 --- a/api/src/main/java/com/github/gtache/autosubtitle/setup/SetupStatus.java +++ b/api/src/main/java/com/github/gtache/autosubtitle/setup/SetupStatus.java @@ -4,5 +4,9 @@ package com.github.gtache.autosubtitle.setup; * The status of a setup */ public enum SetupStatus { - NOT_INSTALLED, INSTALLED, UPDATE_AVAILABLE + ERRORED, NOT_INSTALLED, INSTALLED, UPDATE_AVAILABLE; + + public boolean isInstalled() { + return this == INSTALLED || this == UPDATE_AVAILABLE; + } } diff --git a/api/src/main/java/com/github/gtache/autosubtitle/subtitle/SubtitleCollection.java b/api/src/main/java/com/github/gtache/autosubtitle/subtitle/SubtitleCollection.java index cc6232b..0b0a053 100644 --- a/api/src/main/java/com/github/gtache/autosubtitle/subtitle/SubtitleCollection.java +++ b/api/src/main/java/com/github/gtache/autosubtitle/subtitle/SubtitleCollection.java @@ -1,6 +1,7 @@ package com.github.gtache.autosubtitle.subtitle; import java.util.Collection; +import java.util.Locale; /** * Represents a collection of {@link Subtitle} @@ -13,7 +14,7 @@ public interface SubtitleCollection { Collection subtitles(); /** - * @return The language of the subtitles + * @return The locale of the subtitles */ - String language(); + Locale locale(); } diff --git a/api/src/main/java/com/github/gtache/autosubtitle/subtitle/SubtitleExtractor.java b/api/src/main/java/com/github/gtache/autosubtitle/subtitle/SubtitleExtractor.java index b64a532..08c006b 100644 --- a/api/src/main/java/com/github/gtache/autosubtitle/subtitle/SubtitleExtractor.java +++ b/api/src/main/java/com/github/gtache/autosubtitle/subtitle/SubtitleExtractor.java @@ -10,7 +10,7 @@ import java.util.Collection; */ public interface SubtitleExtractor { - Collection extract(final Video in); + Collection extract(final Video in); - Collection extract(final Audio in); + Collection extract(final Audio in); } diff --git a/api/src/main/java/module-info.java b/api/src/main/java/module-info.java new file mode 100644 index 0000000..6a7fe34 --- /dev/null +++ b/api/src/main/java/module-info.java @@ -0,0 +1,9 @@ +/** + * API module for auto-subtitle + */ +module com.github.gtache.autosubtitle.api { + exports com.github.gtache.autosubtitle; + exports com.github.gtache.autosubtitle.process; + exports com.github.gtache.autosubtitle.setup; + exports com.github.gtache.autosubtitle.subtitle; +} \ No newline at end of file diff --git a/core/pom.xml b/core/pom.xml index 87987b4..ee2e5d0 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -23,7 +23,6 @@ org.apache.logging.log4j log4j-api - 2.23.1 \ No newline at end of file diff --git a/core/src/main/java/com/github/gtache/autosubtitle/impl/Architecture.java b/core/src/main/java/com/github/gtache/autosubtitle/impl/Architecture.java new file mode 100644 index 0000000..c41accb --- /dev/null +++ b/core/src/main/java/com/github/gtache/autosubtitle/impl/Architecture.java @@ -0,0 +1,16 @@ +package com.github.gtache.autosubtitle.impl; + +/** + * The list of possible operating systems + */ +public enum Architecture { + I386, I486, I586, I686, PPC, POWERPC, X86, X86_32, X86_64, AMD64, ARM, ARM32, ARM64, AARCH64, UNKNOWN; + + public static Architecture getArchitecture(final String name) { + try { + return valueOf(name.toUpperCase()); + } catch (final IllegalArgumentException e) { + return UNKNOWN; + } + } +} diff --git a/core/src/main/java/com/github/gtache/autosubtitle/impl/OS.java b/core/src/main/java/com/github/gtache/autosubtitle/impl/OS.java new file mode 100644 index 0000000..6a8d6b7 --- /dev/null +++ b/core/src/main/java/com/github/gtache/autosubtitle/impl/OS.java @@ -0,0 +1,8 @@ +package com.github.gtache.autosubtitle.impl; + +/** + * The list of possible operating systems + */ +public enum OS { + WINDOWS, LINUX, MAC +} 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 99deb0b..051ef21 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,19 @@ import java.util.Objects; /** * Implementation of {@link VideoInfo} */ -public record VideoInfoImpl(String videoFormat) implements VideoInfo { +public record VideoInfoImpl(String videoFormat, int width, int height, long duration) implements VideoInfo { public VideoInfoImpl { Objects.requireNonNull(videoFormat); + if (width <= 0) { + throw new IllegalArgumentException("Width must be greater than 0 : " + width); + } + if (height <= 0) { + throw new IllegalArgumentException("Height must be greater than 0 : " + height); + } + if (duration <= 0) { + throw new IllegalArgumentException("Duration must be greater than 0 : " + duration); + } } @Override diff --git a/core/src/main/java/com/github/gtache/autosubtitle/modules/impl/CoreModule.java b/core/src/main/java/com/github/gtache/autosubtitle/modules/impl/CoreModule.java new file mode 100644 index 0000000..96b6728 --- /dev/null +++ b/core/src/main/java/com/github/gtache/autosubtitle/modules/impl/CoreModule.java @@ -0,0 +1,39 @@ +package com.github.gtache.autosubtitle.modules.impl; + +import com.github.gtache.autosubtitle.impl.Architecture; +import com.github.gtache.autosubtitle.impl.OS; +import dagger.Module; +import dagger.Provides; + +import javax.inject.Singleton; + +@Module +public abstract class CoreModule { + + @Provides + @Singleton + static OS providesOS() { + final var name = System.getProperty("os.name"); + if (name.contains("Windows")) { + return OS.WINDOWS; + } else if (name.contains("Mac")) { + return OS.MAC; + } else { + return OS.LINUX; + } + } + + @Provides + @Singleton + static Architecture providesArchitecture() { + final var arch = System.getProperty("os.arch"); + return Architecture.getArchitecture(arch); + } + + @Provides + @Singleton + @ExecutableExtension + static String providesExecutableExtension(final OS os) { + return os == OS.WINDOWS ? ".exe" : ""; + } +} diff --git a/core/src/main/java/com/github/gtache/autosubtitle/modules/impl/ExecutableExtension.java b/core/src/main/java/com/github/gtache/autosubtitle/modules/impl/ExecutableExtension.java new file mode 100644 index 0000000..0a29dfd --- /dev/null +++ b/core/src/main/java/com/github/gtache/autosubtitle/modules/impl/ExecutableExtension.java @@ -0,0 +1,16 @@ +package com.github.gtache.autosubtitle.modules.impl; + +import javax.inject.Qualifier; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Qualifier +@Documented +@Retention(RUNTIME) +@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD}) +public @interface ExecutableExtension { +} diff --git a/core/src/main/java/com/github/gtache/autosubtitle/process/impl/AbstractProcessRunner.java b/core/src/main/java/com/github/gtache/autosubtitle/process/impl/AbstractProcessRunner.java index a0e4a1e..dd87c23 100644 --- a/core/src/main/java/com/github/gtache/autosubtitle/process/impl/AbstractProcessRunner.java +++ b/core/src/main/java/com/github/gtache/autosubtitle/process/impl/AbstractProcessRunner.java @@ -12,6 +12,7 @@ import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; /** @@ -24,25 +25,28 @@ public abstract class AbstractProcessRunner implements ProcessRunner { @Override public ProcessResult run(final List args) throws IOException { final var builder = new ProcessBuilder(args); - builder.inheritIO(); builder.redirectErrorStream(true); final var process = builder.start(); - final var output = new ArrayList(); - new Thread(() -> { + final var readFuture = CompletableFuture.supplyAsync(() -> { + final var output = new ArrayList(); try (final var in = new BufferedReader(new InputStreamReader(new BufferedInputStream(process.getInputStream()), StandardCharsets.UTF_8))) { - while (in.ready()) { - output.add(in.readLine()); + var line = in.readLine(); + while (line != null) { + output.add(line); + line = in.readLine(); } } catch (final IOException e) { logger.error("Error listening to process output of {}", args, e); } - }).start(); + return output; + }); try { process.waitFor(1, TimeUnit.HOURS); } catch (final InterruptedException e) { Thread.currentThread().interrupt(); process.destroy(); } + final var output = readFuture.join(); return new ProcessResultImpl(process.exitValue(), output); } } diff --git a/core/src/main/java/com/github/gtache/autosubtitle/setup/modules/impl/VideoConverter.java b/core/src/main/java/com/github/gtache/autosubtitle/setup/modules/impl/SubtitleExtractorSetup.java similarity index 75% rename from core/src/main/java/com/github/gtache/autosubtitle/setup/modules/impl/VideoConverter.java rename to core/src/main/java/com/github/gtache/autosubtitle/setup/modules/impl/SubtitleExtractorSetup.java index d23a279..bff9369 100644 --- a/core/src/main/java/com/github/gtache/autosubtitle/setup/modules/impl/VideoConverter.java +++ b/core/src/main/java/com/github/gtache/autosubtitle/setup/modules/impl/SubtitleExtractorSetup.java @@ -11,6 +11,6 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; @Qualifier @Documented @Retention(RUNTIME) -@Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.FIELD}) -public @interface VideoConverter { +@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD}) +public @interface SubtitleExtractorSetup { } diff --git a/core/src/main/java/com/github/gtache/autosubtitle/setup/modules/impl/Translator.java b/core/src/main/java/com/github/gtache/autosubtitle/setup/modules/impl/TranslatorSetup.java similarity index 76% rename from core/src/main/java/com/github/gtache/autosubtitle/setup/modules/impl/Translator.java rename to core/src/main/java/com/github/gtache/autosubtitle/setup/modules/impl/TranslatorSetup.java index 32b4c64..8a45dc6 100644 --- a/core/src/main/java/com/github/gtache/autosubtitle/setup/modules/impl/Translator.java +++ b/core/src/main/java/com/github/gtache/autosubtitle/setup/modules/impl/TranslatorSetup.java @@ -11,6 +11,6 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; @Qualifier @Documented @Retention(RUNTIME) -@Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.FIELD}) -public @interface Translator { +@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD}) +public @interface TranslatorSetup { } diff --git a/core/src/main/java/com/github/gtache/autosubtitle/setup/modules/impl/SubtitleExtractor.java b/core/src/main/java/com/github/gtache/autosubtitle/setup/modules/impl/VideoConverterSetup.java similarity index 75% rename from core/src/main/java/com/github/gtache/autosubtitle/setup/modules/impl/SubtitleExtractor.java rename to core/src/main/java/com/github/gtache/autosubtitle/setup/modules/impl/VideoConverterSetup.java index 831400e..58f65e3 100644 --- a/core/src/main/java/com/github/gtache/autosubtitle/setup/modules/impl/SubtitleExtractor.java +++ b/core/src/main/java/com/github/gtache/autosubtitle/setup/modules/impl/VideoConverterSetup.java @@ -11,6 +11,6 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; @Qualifier @Documented @Retention(RUNTIME) -@Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.FIELD}) -public @interface SubtitleExtractor { +@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD}) +public @interface VideoConverterSetup { } diff --git a/core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SRTSubtitleConverter.java b/core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SRTSubtitleConverter.java index a599d69..d370ebd 100644 --- a/core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SRTSubtitleConverter.java +++ b/core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SRTSubtitleConverter.java @@ -3,13 +3,21 @@ package com.github.gtache.autosubtitle.subtitle.impl; import com.github.gtache.autosubtitle.subtitle.SubtitleCollection; import com.github.gtache.autosubtitle.subtitle.SubtitleConverter; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * Converts subtitles to SRT format */ +@Singleton public class SRTSubtitleConverter implements SubtitleConverter { - public String convert(final SubtitleCollection collection) { + @Inject + SRTSubtitleConverter() { + } + public String convert(final SubtitleCollection collection) { + throw new UnsupportedOperationException("TODO"); } @Override diff --git a/core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SubtitleCollectionImpl.java b/core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SubtitleCollectionImpl.java index 7295046..f021551 100644 --- a/core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SubtitleCollectionImpl.java +++ b/core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SubtitleCollectionImpl.java @@ -5,6 +5,7 @@ import com.github.gtache.autosubtitle.subtitle.SubtitleCollection; import java.util.Collection; import java.util.List; +import java.util.Locale; import static java.util.Objects.requireNonNull; @@ -12,10 +13,10 @@ import static java.util.Objects.requireNonNull; * Implementation of {@link SubtitleCollection} */ public record SubtitleCollectionImpl(Collection subtitles, - String language) implements SubtitleCollection { + Locale locale) implements SubtitleCollection { public SubtitleCollectionImpl { subtitles = List.copyOf(subtitles); - requireNonNull(language); + requireNonNull(locale); } } diff --git a/core/src/main/java/com/github/gtache/autosubtitle/subtitle/modules/impl/ConverterModule.java b/core/src/main/java/com/github/gtache/autosubtitle/subtitle/modules/impl/ConverterModule.java new file mode 100644 index 0000000..4b913c0 --- /dev/null +++ b/core/src/main/java/com/github/gtache/autosubtitle/subtitle/modules/impl/ConverterModule.java @@ -0,0 +1,19 @@ +package com.github.gtache.autosubtitle.subtitle.modules.impl; + +import com.github.gtache.autosubtitle.subtitle.SubtitleConverter; +import com.github.gtache.autosubtitle.subtitle.impl.SRTSubtitleConverter; +import dagger.Binds; +import dagger.Module; + +import javax.inject.Singleton; + +/** + * Dagger module for subtitle converter + */ +@Module +public interface ConverterModule { + + @Binds + @Singleton + SubtitleConverter bindsSubtitleConverter(final SRTSubtitleConverter converter); +} diff --git a/core/src/main/java/module-info.java b/core/src/main/java/module-info.java new file mode 100644 index 0000000..d42c585 --- /dev/null +++ b/core/src/main/java/module-info.java @@ -0,0 +1,16 @@ +/** + * Core module for auto-subtitle + */ +module com.github.gtache.autosubtitle.core { + requires transitive com.github.gtache.autosubtitle.api; + requires transitive dagger; + requires transitive javax.inject; + requires org.apache.logging.log4j; + + exports com.github.gtache.autosubtitle.impl; + exports com.github.gtache.autosubtitle.modules.impl; + exports com.github.gtache.autosubtitle.process.impl; + exports com.github.gtache.autosubtitle.subtitle.impl; + exports com.github.gtache.autosubtitle.setup.modules.impl; + exports com.github.gtache.autosubtitle.subtitle.modules.impl; +} \ No newline at end of file diff --git a/deepl/pom.xml b/deepl/pom.xml new file mode 100644 index 0000000..0afc18f --- /dev/null +++ b/deepl/pom.xml @@ -0,0 +1,21 @@ + + + 4.0.0 + + com.github.gtache.autosubtitle + autosubtitle + 1.0-SNAPSHOT + + + autosubtitle-deepl + + + + + com.github.gtache.autosubtitle + autosubtitle-core + + + \ No newline at end of file diff --git a/deepl/src/main/java/com/github/gtache/autosubtitle/deepl/DeepLTranslator.java b/deepl/src/main/java/com/github/gtache/autosubtitle/deepl/DeepLTranslator.java new file mode 100644 index 0000000..17cdaaa --- /dev/null +++ b/deepl/src/main/java/com/github/gtache/autosubtitle/deepl/DeepLTranslator.java @@ -0,0 +1,38 @@ +package com.github.gtache.autosubtitle.deepl; + +import com.github.gtache.autosubtitle.Translator; +import com.github.gtache.autosubtitle.subtitle.Subtitle; +import com.github.gtache.autosubtitle.subtitle.SubtitleCollection; + +import javax.inject.Inject; +import java.util.Locale; + +/** + * DeepL implementation of {@link Translator} + */ +public class DeepLTranslator implements Translator { + + @Inject + DeepLTranslator() { + } + + @Override + public Locale getLocale(final String text) { + return null; + } + + @Override + public String translate(final String text, final Locale to) { + return ""; + } + + @Override + public Subtitle translate(final Subtitle subtitle, final Locale to) { + return null; + } + + @Override + public SubtitleCollection translate(final SubtitleCollection collection, final Locale to) { + return null; + } +} 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 new file mode 100644 index 0000000..801ed67 --- /dev/null +++ b/deepl/src/main/java/com/github/gtache/autosubtitle/modules/deepl/DeepLModule.java @@ -0,0 +1,19 @@ +package com.github.gtache.autosubtitle.modules.deepl; + +import com.github.gtache.autosubtitle.Translator; +import com.github.gtache.autosubtitle.deepl.DeepLTranslator; +import dagger.Binds; +import dagger.Module; + +import javax.inject.Singleton; + +/** + * Dagger module for DeepL + */ +@Module +public interface DeepLModule { + + @Binds + @Singleton + Translator bindsTranslator(final DeepLTranslator translator); +} diff --git a/deepl/src/main/java/module-info.java b/deepl/src/main/java/module-info.java new file mode 100644 index 0000000..0ea7396 --- /dev/null +++ b/deepl/src/main/java/module-info.java @@ -0,0 +1,8 @@ +/** + * DeepL module for auto-subtitle + */ +module com.github.gtache.autosubtitle.deepl { + requires transitive com.github.gtache.autosubtitle.core; + exports com.github.gtache.autosubtitle.deepl; + exports com.github.gtache.autosubtitle.modules.deepl; +} \ No newline at end of file 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 baf8a9d..abd2ec9 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,20 +8,22 @@ 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.ffmpeg.FFmpegBundledPath; +import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegSystemPath; +import com.github.gtache.autosubtitle.process.impl.AbstractProcessRunner; import com.github.gtache.autosubtitle.subtitle.SubtitleCollection; import com.github.gtache.autosubtitle.subtitle.SubtitleConverter; import javax.inject.Inject; +import javax.inject.Singleton; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.SequencedMap; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import static java.util.Objects.requireNonNull; @@ -29,15 +31,18 @@ import static java.util.Objects.requireNonNull; /** * FFmpeg implementation of {@link VideoConverter} */ -public class FFmpegVideoConverter implements VideoConverter { +@Singleton +public class FFmpegVideoConverter extends AbstractProcessRunner implements VideoConverter { private static final String TEMP_FILE_PREFIX = "autosubtitle"; - private final Path ffmpegPath; + private final Path bundledPath; + private final Path systemPath; private final SubtitleConverter subtitleConverter; @Inject - FFmpegVideoConverter(final Path ffmpegPath, final SubtitleConverter subtitleConverter) { - this.ffmpegPath = requireNonNull(ffmpegPath); + FFmpegVideoConverter(@FFmpegBundledPath final Path bundledPath, @FFmpegSystemPath final Path systemPath, final SubtitleConverter subtitleConverter) { + this.bundledPath = requireNonNull(bundledPath); + this.systemPath = requireNonNull(systemPath); this.subtitleConverter = requireNonNull(subtitleConverter); } @@ -47,7 +52,7 @@ public class FFmpegVideoConverter implements VideoConverter { final var collectionMap = dumpCollections(subtitles); final var out = getTempFile("mkv"); //Soft subtitles are only supported by mkv apparently final var args = new ArrayList(); - args.add(ffmpegPath.toString()); + args.add(getFFmpegPath()); args.add("-i"); args.add(videoPath.toString()); collectionMap.forEach((c, p) -> { @@ -66,11 +71,11 @@ public class FFmpegVideoConverter implements VideoConverter { args.add("-map"); args.add(String.valueOf(n)); args.add("-metadata:s:s:" + n); - args.add("language=" + c.language()); + args.add("language=" + c.locale().getISO3Language()); }); args.add(out.toString()); run(args); - return new FileVideoImpl(out, new VideoInfoImpl("mkv")); + return new FileVideoImpl(out, new VideoInfoImpl("mkv", video.info().width(), video.info().height(), video.info().duration())); } @Override @@ -80,7 +85,7 @@ public class FFmpegVideoConverter implements VideoConverter { final var out = getTempFile(video.info().videoFormat()); final var subtitleArg = subtitleConverter.formatName().equalsIgnoreCase("ass") ? "ass=" + subtitlesPath : "subtitles=" + subtitlesPath; final var args = List.of( - ffmpegPath.toString(), + getFFmpegPath(), "-i", videoPath.toString(), "-vf", @@ -97,7 +102,7 @@ public class FFmpegVideoConverter implements VideoConverter { final var audioPath = getTempFile(".wav"); final var dumpVideoPath = getTempFile("." + video.info().videoFormat()); final var args = List.of( - ffmpegPath.toString(), + getFFmpegPath(), "-i", videoPath.toString(), "-map", @@ -129,7 +134,7 @@ public class FFmpegVideoConverter implements VideoConverter { } private SequencedMap dumpCollections(final Collection collections) throws IOException { - final var ret = new LinkedHashMap(collections.size()); + final var ret = LinkedHashMap.newLinkedHashMap(collections.size()); for (final var subtitles : collections) { ret.put(subtitles, dumpSubtitles(subtitles)); } @@ -148,23 +153,7 @@ public class FFmpegVideoConverter implements VideoConverter { return path; } - private void run(final String... args) throws IOException { - run(Arrays.asList(args)); - } - - private void run(final List args) throws IOException { - final var builder = new ProcessBuilder(args); - builder.inheritIO(); - builder.redirectErrorStream(true); - final var process = builder.start(); - try { - process.waitFor(1, TimeUnit.HOURS); - } catch (final InterruptedException e) { - Thread.currentThread().interrupt(); - process.destroy(); - } - if (process.exitValue() != 0) { - throw new IOException("FFmpeg exited with code " + process.exitValue()); - } + private String getFFmpegPath() { + return Files.isRegularFile(bundledPath) ? bundledPath.toString() : systemPath.toString(); } } 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 new file mode 100644 index 0000000..272d55b --- /dev/null +++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/ffmpeg/FFprobeVideoLoader.java @@ -0,0 +1,51 @@ +package com.github.gtache.autosubtitle.ffmpeg; + +import com.github.gtache.autosubtitle.Video; +import com.github.gtache.autosubtitle.VideoLoader; +import com.github.gtache.autosubtitle.impl.FileVideoImpl; +import com.github.gtache.autosubtitle.impl.VideoInfoImpl; +import com.github.gtache.autosubtitle.modules.ffmpeg.FFprobeBundledPath; +import com.github.gtache.autosubtitle.modules.ffmpeg.FFprobeSystemPath; +import com.github.gtache.autosubtitle.process.impl.AbstractProcessRunner; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static java.util.Objects.requireNonNull; + +/** + * FFprobe implementation of {@link VideoLoader} + */ +@Singleton +public class FFprobeVideoLoader extends AbstractProcessRunner implements VideoLoader { + + private final Path bundledPath; + private final Path systemPath; + + @Inject + FFprobeVideoLoader(@FFprobeBundledPath final Path bundledPath, @FFprobeSystemPath final Path systemPath) { + this.bundledPath = requireNonNull(bundledPath); + this.systemPath = requireNonNull(systemPath); + } + + @Override + public Video loadVideo(final Path path) throws IOException { + final var result = run(getFFprobePath(), "-v", "error", "-select_streams", "v", "-show_entries", "stream=width,height,duration", "-of", "csv=p=0", path.toString()); + 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); + } + + private String getFFprobePath() { + return Files.isRegularFile(bundledPath) ? bundledPath.toString() : systemPath.toString(); + } +} diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFBundledRoot.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFBundledRoot.java new file mode 100644 index 0000000..b96beb8 --- /dev/null +++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFBundledRoot.java @@ -0,0 +1,16 @@ +package com.github.gtache.autosubtitle.modules.ffmpeg; + +import javax.inject.Qualifier; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Qualifier +@Documented +@Retention(RUNTIME) +@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD}) +public @interface FFBundledRoot { +} diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegBundledPath.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegBundledPath.java new file mode 100644 index 0000000..3d16cf7 --- /dev/null +++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegBundledPath.java @@ -0,0 +1,16 @@ +package com.github.gtache.autosubtitle.modules.ffmpeg; + +import javax.inject.Qualifier; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Qualifier +@Documented +@Retention(RUNTIME) +@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD}) +public @interface FFmpegBundledPath { +} diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegModule.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegModule.java new file mode 100644 index 0000000..b005dbb --- /dev/null +++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegModule.java @@ -0,0 +1,23 @@ +package com.github.gtache.autosubtitle.modules.ffmpeg; + +import com.github.gtache.autosubtitle.VideoConverter; +import com.github.gtache.autosubtitle.VideoLoader; +import com.github.gtache.autosubtitle.ffmpeg.FFmpegVideoConverter; +import com.github.gtache.autosubtitle.ffmpeg.FFprobeVideoLoader; +import dagger.Binds; +import dagger.Module; + +import javax.inject.Singleton; + +@Module +public interface FFmpegModule { + + @Binds + @Singleton + VideoConverter bindsVideoConverter(final FFmpegVideoConverter converter); + + @Binds + @Singleton + VideoLoader bindsVideoLoader(final FFprobeVideoLoader loader); + +} diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegSystemPath.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegSystemPath.java new file mode 100644 index 0000000..6b58fa3 --- /dev/null +++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegSystemPath.java @@ -0,0 +1,16 @@ +package com.github.gtache.autosubtitle.modules.ffmpeg; + +import javax.inject.Qualifier; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Qualifier +@Documented +@Retention(RUNTIME) +@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD}) +public @interface FFmpegSystemPath { +} diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegVersion.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegVersion.java new file mode 100644 index 0000000..a883339 --- /dev/null +++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegVersion.java @@ -0,0 +1,16 @@ +package com.github.gtache.autosubtitle.modules.ffmpeg; + +import javax.inject.Qualifier; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Qualifier +@Documented +@Retention(RUNTIME) +@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD}) +public @interface FFmpegVersion { +} diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFprobeBundledPath.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFprobeBundledPath.java new file mode 100644 index 0000000..9869e9e --- /dev/null +++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFprobeBundledPath.java @@ -0,0 +1,16 @@ +package com.github.gtache.autosubtitle.modules.ffmpeg; + +import javax.inject.Qualifier; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Qualifier +@Documented +@Retention(RUNTIME) +@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD}) +public @interface FFprobeBundledPath { +} diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFprobeSystemPath.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFprobeSystemPath.java new file mode 100644 index 0000000..b1ec1a6 --- /dev/null +++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFprobeSystemPath.java @@ -0,0 +1,16 @@ +package com.github.gtache.autosubtitle.modules.ffmpeg; + +import javax.inject.Qualifier; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Qualifier +@Documented +@Retention(RUNTIME) +@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD}) +public @interface FFprobeSystemPath { +} diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/FFmpegSetupManager.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/FFmpegSetupManager.java index b115e42..eec7f82 100644 --- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/FFmpegSetupManager.java +++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/FFmpegSetupManager.java @@ -1,15 +1,62 @@ package com.github.gtache.autosubtitle.setup.ffmpeg; +import com.github.gtache.autosubtitle.impl.Architecture; +import com.github.gtache.autosubtitle.impl.OS; +import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegBundledPath; +import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegSystemPath; +import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegVersion; +import com.github.gtache.autosubtitle.process.impl.AbstractProcessRunner; import com.github.gtache.autosubtitle.setup.SetupException; import com.github.gtache.autosubtitle.setup.SetupManager; +import com.github.gtache.autosubtitle.setup.SetupStatus; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import javax.inject.Inject; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Objects; /** * Manager managing the FFmpeg installation */ -public class FFmpegSetupManager implements SetupManager { +public class FFmpegSetupManager extends AbstractProcessRunner implements SetupManager { + private static final Logger logger = LogManager.getLogger(FFmpegSetupManager.class); + private final Path bundledPath; + private final Path systemPath; + private final String version; + private final OS os; + private final Architecture architecture; + private final String executableExtension; + + @Inject + FFmpegSetupManager(@FFmpegBundledPath final Path bundledPath, @FFmpegSystemPath final Path systemPath, @FFmpegVersion final String version, final OS os, final Architecture architecture) { + this.bundledPath = Objects.requireNonNull(bundledPath); + this.systemPath = Objects.requireNonNull(systemPath); + this.version = Objects.requireNonNull(version); + this.os = Objects.requireNonNull(os); + this.architecture = Objects.requireNonNull(architecture); + this.executableExtension = os == OS.WINDOWS ? ".exe" : ""; + } + @Override - public boolean isInstalled() throws SetupException { - return checkSystemFFmpeg() || checkBundledFFmpeg(); + public String name() { + return "FFmpeg"; + } + + @Override + public SetupStatus status() { + try { + if (checkSystemFFmpeg() || checkBundledFFmpeg()) { + return SetupStatus.INSTALLED; + } else { + return SetupStatus.NOT_INSTALLED; + } + } catch (final IOException e) { + logger.error("Error checking status of {}", name(), e); + return SetupStatus.ERRORED; + } } @Override @@ -22,21 +69,17 @@ public class FFmpegSetupManager implements SetupManager { } - @Override - public boolean isUpdateAvailable() throws SetupException { - return false; - } - @Override public void update() throws SetupException { } - private boolean checkSystemFFmpeg() { - return false; + private boolean checkSystemFFmpeg() throws IOException { + final var result = run(systemPath.toString(), "-h"); + return result.exitCode() == 0; } - private boolean checkBundledFFmpeg() { - return false; + private boolean checkBundledFFmpeg() throws IOException { + return Files.isRegularFile(bundledPath); } } diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/modules/ffmpeg/FFmpegSetupModule.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/modules/ffmpeg/FFmpegSetupModule.java new file mode 100644 index 0000000..3ef7b2c --- /dev/null +++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/modules/ffmpeg/FFmpegSetupModule.java @@ -0,0 +1,75 @@ +package com.github.gtache.autosubtitle.setup.modules.ffmpeg; + +import com.github.gtache.autosubtitle.modules.ffmpeg.FFBundledRoot; +import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegBundledPath; +import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegSystemPath; +import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegVersion; +import com.github.gtache.autosubtitle.modules.ffmpeg.FFprobeBundledPath; +import com.github.gtache.autosubtitle.modules.ffmpeg.FFprobeSystemPath; +import com.github.gtache.autosubtitle.modules.impl.ExecutableExtension; +import com.github.gtache.autosubtitle.setup.SetupManager; +import com.github.gtache.autosubtitle.setup.ffmpeg.FFmpegSetupManager; +import com.github.gtache.autosubtitle.setup.modules.impl.VideoConverterSetup; +import dagger.Binds; +import dagger.Module; +import dagger.Provides; + +import javax.inject.Singleton; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * Dagger module for FFmpeg setup + */ +@Module +public abstract class FFmpegSetupModule { + + private static final String FFMPEG = "ffmpeg"; + private static final String FFPROBE = "ffprobe"; + + @Binds + @VideoConverterSetup + abstract SetupManager bindsFFmpegSetupManager(final FFmpegSetupManager manager); + + @Provides + @Singleton + @FFBundledRoot + static Path providesFFBundledRoot() { + return Paths.get("tools", FFMPEG); + } + + @Provides + @Singleton + @FFprobeBundledPath + static Path providesFFProbeBundledPath(@FFBundledRoot final Path root, @ExecutableExtension final String extension) { + return root.resolve(FFPROBE + extension); + } + + @Provides + @Singleton + @FFprobeSystemPath + static Path providesFFProbeSystemPath(@ExecutableExtension final String extension) { + return Paths.get(FFPROBE + extension); + } + + @Provides + @Singleton + @FFmpegBundledPath + static Path providesFFmpegBundledPath(@FFBundledRoot final Path root, @ExecutableExtension final String extension) { + return root.resolve(FFMPEG + extension); + } + + @Provides + @Singleton + @FFmpegSystemPath + static Path providesFFmpegSystemPath(@ExecutableExtension final String extension) { + return Paths.get(FFMPEG + extension); + } + + @Provides + @Singleton + @FFmpegVersion + static String providesFFmpegVersion() { + return "7.0.1"; + } +} diff --git a/ffmpeg/src/main/java/module-info.java b/ffmpeg/src/main/java/module-info.java new file mode 100644 index 0000000..0faf6e0 --- /dev/null +++ b/ffmpeg/src/main/java/module-info.java @@ -0,0 +1,14 @@ +/** + * FFmpeg module for auto-subtitle + */ +module com.github.gtache.autosubtitle.ffmpeg { + requires transitive com.github.gtache.autosubtitle.core; + requires transitive dagger; + requires transitive javax.inject; + requires org.apache.logging.log4j; + + exports com.github.gtache.autosubtitle.ffmpeg; + exports com.github.gtache.autosubtitle.modules.ffmpeg; + exports com.github.gtache.autosubtitle.setup.ffmpeg; + exports com.github.gtache.autosubtitle.setup.modules.ffmpeg; +} \ No newline at end of file diff --git a/fx/pom.xml b/fx/pom.xml index 6b07449..48b3664 100644 --- a/fx/pom.xml +++ b/fx/pom.xml @@ -11,6 +11,10 @@ autosubtitle-fx + + 22.0.1 + + com.github.gtache.autosubtitle @@ -24,15 +28,19 @@ com.google.dagger dagger + + org.apache.logging.log4j + log4j-api + org.openjfx javafx-media - 22.0.1 + ${javafx.version} org.openjfx javafx-fxml - 22.0.1 + ${javafx.version} diff --git a/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMainController.java b/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMainController.java index 9c1a469..cf72ea2 100644 --- a/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMainController.java +++ b/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMainController.java @@ -1,146 +1,42 @@ package com.github.gtache.autosubtitle.gui.fx; -import com.github.gtache.autosubtitle.subtitle.EditableSubtitle; -import com.github.gtache.autosubtitle.subtitle.Subtitle; -import com.github.gtache.autosubtitle.subtitle.SubtitleExtractor; -import com.github.gtache.autosubtitle.VideoConverter; import com.github.gtache.autosubtitle.gui.MainController; -import com.github.gtache.autosubtitle.impl.FileVideoImpl; -import javafx.beans.binding.Bindings; -import javafx.beans.property.SimpleObjectProperty; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; -import javafx.event.ActionEvent; import javafx.fxml.FXML; -import javafx.scene.control.Button; -import javafx.scene.control.TableColumn; -import javafx.scene.control.TableView; -import javafx.scene.control.TextField; -import javafx.scene.layout.StackPane; -import javafx.scene.media.Media; -import javafx.scene.media.MediaPlayer; -import javafx.scene.media.MediaView; -import javafx.stage.FileChooser; -import javafx.stage.Window; +import javafx.scene.control.TabPane; import javax.inject.Inject; -import java.nio.file.Path; -import java.time.Duration; -import java.time.LocalTime; -import java.util.Comparator; - -import static java.util.Objects.requireNonNull; +import javax.inject.Singleton; +import java.util.Objects; /** * FX implementation of {@link MainController} */ +@Singleton public class FXMainController implements MainController { @FXML - private MediaView videoView; - @FXML - private TextField fileField; - @FXML - private Button extractButton; - @FXML - private Button resetButton; - @FXML - private Button exportButton; - @FXML - private TableView subtitlesTable; - @FXML - private TableColumn startColumn; - @FXML - private TableColumn endColumn; - @FXML - private TableColumn textColumn; - @FXML - private StackPane stackPane; + private TabPane tabPane; private final FXMainModel model; - private final SubtitleExtractor subtitleExtractor; - private final VideoConverter videoConverter; @Inject - FXMainController(final FXMainModel model, final SubtitleExtractor subtitleExtractor, final VideoConverter videoConverter) { - this.model = requireNonNull(model); - this.subtitleExtractor = requireNonNull(subtitleExtractor); - this.videoConverter = requireNonNull(videoConverter); + FXMainController(final FXMainModel model) { + this.model = Objects.requireNonNull(model); } @FXML private void initialize() { - extractButton.disableProperty().bind(model.videoProperty().isNull()); - resetButton.disableProperty().bind(Bindings.isEmpty(model.subtitles())); - exportButton.disableProperty().bind(Bindings.isEmpty(model.subtitles())); - - subtitlesTable.setItems(model.subtitles()); - startColumn.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue() == null ? null : param.getValue().start())); - endColumn.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue() == null ? null : param.getValue().end())); - textColumn.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue() == null ? "" : param.getValue().content())); - - model.selectedSubtitleProperty().addListener(new ChangeListener() { - @Override - public void changed(final ObservableValue observable, final EditableSubtitle oldValue, final EditableSubtitle newValue) { - if (newValue != null) { - videoView.getMediaPlayer().seek(Duration.of(newValue.start().to)); - } - } - }); - } - - @FXML - private void fileButtonPressed() { - final var filePicker = new FileChooser(); - final var file = filePicker.showOpenDialog(window()); - if (file != null) { - loadVideo(file.toPath()); - } + tabPane.getSelectionModel().selectedIndexProperty().addListener((observable, oldValue, newValue) -> model.selectTab(newValue.intValue())); + model.selectedTabProperty().addListener((observable, oldValue, newValue) -> tabPane.getSelectionModel().select(newValue.intValue())); } @Override - public void extractSubtitles() { - if (model.video() != null) { - final var subtitles = subtitleExtractor.extract(model.video()); - model.subtitles().setAll(subtitles.stream().sorted(Comparator.comparing(Subtitle::start)).toList()); - } - } - - @Override - public void loadVideo(final Path file) { - fileField.setText(file.toAbsolutePath().toString()); - model.videoProperty().set(new FileVideoImpl(file)); - final var media = new Media(file.toUri().toString()); - final var player = new MediaPlayer(media); - videoView.getMediaPlayer().dispose(); - videoView.setMediaPlayer(player); + public void selectTab(final int index) { + model.selectTab(index); } @Override public FXMainModel model() { return model; } - - public Window window() { - return videoView.getScene().getWindow(); - } - - @FXML - private void extractPressed(final ActionEvent actionEvent) { - extractSubtitles(); - } - - @FXML - private void exportPressed(final ActionEvent actionEvent) { - final var filePicker = new FileChooser(); - final var file = filePicker.showSaveDialog(window()); - if (file != null) { - videoConverter.addSoftSubtitles(model.video(), model.subtitles()); - } - } - - @FXML - private void resetButtonPressed(final ActionEvent actionEvent) { - - } } diff --git a/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMainModel.java b/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMainModel.java index 0da134b..04a38a6 100644 --- a/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMainModel.java +++ b/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMainModel.java @@ -1,51 +1,36 @@ package com.github.gtache.autosubtitle.gui.fx; -import com.github.gtache.autosubtitle.subtitle.EditableSubtitle; -import com.github.gtache.autosubtitle.Video; import com.github.gtache.autosubtitle.gui.MainModel; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.SimpleIntegerProperty; import javax.inject.Inject; +import javax.inject.Singleton; /** * FX implementation of {@link MainModel} */ +@Singleton public class FXMainModel implements MainModel { - private final ObjectProperty