diff --git a/api/src/main/java/com/github/gtache/autosubtitle/archive/ArchiverProvider.java b/api/src/main/java/com/github/gtache/autosubtitle/archive/ArchiverProvider.java new file mode 100644 index 0000000..3caf970 --- /dev/null +++ b/api/src/main/java/com/github/gtache/autosubtitle/archive/ArchiverProvider.java @@ -0,0 +1,22 @@ +package com.github.gtache.autosubtitle.archive; + +import java.util.Collection; + +/** + * Provider of {@link Archiver}s + */ +public interface ArchiverProvider { + + /** + * @return the list of all available archivers + */ + Collection allArchivers(); + + /** + * Returns the archiver for the given extension + * + * @param extension The extension + * @return The archiver (or null if not found) + */ + Archiver getArchiver(String extension); +} diff --git a/api/src/main/java/com/github/gtache/autosubtitle/subtitle/SubtitleImporterExporter.java b/api/src/main/java/com/github/gtache/autosubtitle/subtitle/SubtitleImporterExporter.java index 8fa222f..e883b04 100644 --- a/api/src/main/java/com/github/gtache/autosubtitle/subtitle/SubtitleImporterExporter.java +++ b/api/src/main/java/com/github/gtache/autosubtitle/subtitle/SubtitleImporterExporter.java @@ -1,6 +1,7 @@ package com.github.gtache.autosubtitle.subtitle; import com.github.gtache.autosubtitle.Language; +import com.github.gtache.autosubtitle.VideoInfo; import com.github.gtache.autosubtitle.subtitle.converter.ParseException; import java.io.IOException; @@ -28,20 +29,22 @@ public interface SubtitleImporterExporter { * Exports multiple collections to a file * * @param collections The subtitle collections + * @param videoInfo The video info (e.g. ASS format uses it) * @param file The path to the file * @throws IOException If an error occurred */ - void exportSubtitles(final Collection> collections, final Path file) throws IOException; + void exportSubtitles(final Collection> collections, final VideoInfo videoInfo, final Path file) throws IOException; /** * Exports a single collection to a file * * @param collection The subtitle collection + * @param videoInfo The video info (e.g. ASS format uses it) * @param file The path to the file * @throws IOException If an error occurred */ - default void exportSubtitles(final SubtitleCollection collection, final Path file) throws IOException { - exportSubtitles(List.of(collection), file); + default void exportSubtitles(final SubtitleCollection collection, final VideoInfo videoInfo, final Path file) throws IOException { + exportSubtitles(List.of(collection), videoInfo, file); } /** diff --git a/api/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/SubtitleConverter.java b/api/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/SubtitleConverter.java index 2b70487..0864fc9 100644 --- a/api/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/SubtitleConverter.java +++ b/api/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/SubtitleConverter.java @@ -1,5 +1,6 @@ package com.github.gtache.autosubtitle.subtitle.converter; +import com.github.gtache.autosubtitle.VideoInfo; import com.github.gtache.autosubtitle.subtitle.Subtitle; import com.github.gtache.autosubtitle.subtitle.SubtitleCollection; @@ -16,9 +17,10 @@ public interface SubtitleConverter { * Converts the subtitle collection * * @param collection The collection + * @param videoInfo The video info (e.g. ASS format uses it) * @return The converted subtitles as the content of a file */ - String format(final SubtitleCollection collection); + String format(final SubtitleCollection collection, final VideoInfo videoInfo); /** * Parses a subtitle collection diff --git a/api/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/SubtitleConverterProvider.java b/api/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/SubtitleConverterProvider.java new file mode 100644 index 0000000..96d40c1 --- /dev/null +++ b/api/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/SubtitleConverterProvider.java @@ -0,0 +1,22 @@ +package com.github.gtache.autosubtitle.subtitle.converter; + +import java.util.Collection; + +/** + * Provider of {@link SubtitleConverter}s + */ +public interface SubtitleConverterProvider { + + /** + * @return the list of all available converters + */ + Collection> allConverters(); + + /** + * Returns the converter for the given format + * + * @param format The format + * @return The converter (or null if not found) + */ + SubtitleConverter getConverter(final String format); +} diff --git a/api/src/main/java/com/github/gtache/autosubtitle/translation/Translator.java b/api/src/main/java/com/github/gtache/autosubtitle/translation/Translator.java index 7c73b04..cfce867 100644 --- a/api/src/main/java/com/github/gtache/autosubtitle/translation/Translator.java +++ b/api/src/main/java/com/github/gtache/autosubtitle/translation/Translator.java @@ -8,7 +8,7 @@ import com.github.gtache.autosubtitle.subtitle.SubtitleCollection; * Translates texts and subtitles */ public interface Translator { - + /** * Guesses the language of the given text * diff --git a/cli/src/main/java/com/github/gtache/autosubtitle/modules/cli/CliComponent.java b/cli/src/main/java/com/github/gtache/autosubtitle/modules/cli/CliComponent.java index 797a2b7..4a22495 100644 --- a/cli/src/main/java/com/github/gtache/autosubtitle/modules/cli/CliComponent.java +++ b/cli/src/main/java/com/github/gtache/autosubtitle/modules/cli/CliComponent.java @@ -4,15 +4,14 @@ import com.github.gtache.autosubtitle.modules.deepl.DeepLModule; import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegModule; import com.github.gtache.autosubtitle.modules.impl.CoreModule; import com.github.gtache.autosubtitle.modules.whisperx.WhisperXModule; -import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter; +import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverterProvider; import dagger.Component; import javax.inject.Singleton; -import java.util.Map; @Component(modules = {CoreModule.class, DeepLModule.class, FFmpegModule.class, WhisperXModule.class}) @Singleton public interface CliComponent { - Map getSubtitleConverters(); + SubtitleConverterProvider getSubtitleConverterProvider(); } diff --git a/core/src/main/java/com/github/gtache/autosubtitle/archive/impl/ArchiverProviderImpl.java b/core/src/main/java/com/github/gtache/autosubtitle/archive/impl/ArchiverProviderImpl.java new file mode 100644 index 0000000..a076228 --- /dev/null +++ b/core/src/main/java/com/github/gtache/autosubtitle/archive/impl/ArchiverProviderImpl.java @@ -0,0 +1,33 @@ +package com.github.gtache.autosubtitle.archive.impl; + + +import com.github.gtache.autosubtitle.archive.Archiver; +import com.github.gtache.autosubtitle.archive.ArchiverProvider; + +import javax.inject.Inject; +import java.util.Collection; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Implementation of {@link ArchiverProvider} + */ +public class ArchiverProviderImpl implements ArchiverProvider { + + private final Map archiverMap; + + @Inject + ArchiverProviderImpl(final Map archiverMap) { + this.archiverMap = archiverMap.entrySet().stream().collect(Collectors.toMap(e -> e.getKey().toUpperCase(), Map.Entry::getValue)); + } + + @Override + public Collection allArchivers() { + return archiverMap.values(); + } + + @Override + public Archiver getArchiver(final String extension) { + return archiverMap.get(extension.toUpperCase()); + } +} diff --git a/core/src/main/java/com/github/gtache/autosubtitle/modules/archive/impl/ArchiveModule.java b/core/src/main/java/com/github/gtache/autosubtitle/modules/archive/impl/ArchiveModule.java new file mode 100644 index 0000000..1832d88 --- /dev/null +++ b/core/src/main/java/com/github/gtache/autosubtitle/modules/archive/impl/ArchiveModule.java @@ -0,0 +1,29 @@ +package com.github.gtache.autosubtitle.modules.archive.impl; + +import com.github.gtache.autosubtitle.archive.Archiver; +import com.github.gtache.autosubtitle.archive.ArchiverProvider; +import com.github.gtache.autosubtitle.archive.impl.ArchiverProviderImpl; +import com.github.gtache.autosubtitle.archive.impl.ZipDecompresser; +import dagger.Binds; +import dagger.Module; +import dagger.multibindings.IntoMap; +import dagger.multibindings.StringKey; + +/** + * Dagger module for Archivers + */ +@Module +public abstract class ArchiveModule { + + private ArchiveModule() { + + } + + @Binds + abstract ArchiverProvider bindsArchiverProvider(ArchiverProviderImpl provider); + + @Binds + @StringKey("zip") + @IntoMap + abstract Archiver bindsZipDecompresser(final ZipDecompresser decompresser); +} 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 index 4752fe1..95390ce 100644 --- 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 @@ -1,17 +1,13 @@ package com.github.gtache.autosubtitle.modules.impl; -import com.github.gtache.autosubtitle.archive.Archiver; -import com.github.gtache.autosubtitle.archive.impl.ZipDecompresser; import com.github.gtache.autosubtitle.impl.Architecture; import com.github.gtache.autosubtitle.impl.DaggerException; import com.github.gtache.autosubtitle.impl.OS; +import com.github.gtache.autosubtitle.modules.archive.impl.ArchiveModule; import com.github.gtache.autosubtitle.modules.setup.impl.SetupModule; import com.github.gtache.autosubtitle.modules.subtitle.impl.SubtitleModule; -import dagger.Binds; import dagger.Module; import dagger.Provides; -import dagger.multibindings.IntoMap; -import dagger.multibindings.StringKey; import javax.inject.Singleton; import java.util.prefs.Preferences; @@ -19,18 +15,13 @@ import java.util.prefs.Preferences; /** * Dagger module for Core */ -@Module(includes = {SetupModule.class, SubtitleModule.class}) +@Module(includes = {ArchiveModule.class, SetupModule.class, SubtitleModule.class}) public abstract class CoreModule { private CoreModule() { } - @Binds - @StringKey("zip") - @IntoMap - abstract Archiver bindsZipDecompresser(final ZipDecompresser decompresser); - @Provides static OS providesOS() { final var os = OS.getOS(); @@ -74,4 +65,16 @@ public abstract class CoreModule { static int providesMaxLines() { return 1; } + + @Provides + @FontName + static String providesFontName() { + return "Arial"; + } + + @Provides + @FontSize + static int providesFontSize() { + return 12; + } } diff --git a/gui/core/src/main/java/com/github/gtache/autosubtitle/modules/gui/impl/FontFamily.java b/core/src/main/java/com/github/gtache/autosubtitle/modules/impl/FontName.java similarity index 80% rename from gui/core/src/main/java/com/github/gtache/autosubtitle/modules/gui/impl/FontFamily.java rename to core/src/main/java/com/github/gtache/autosubtitle/modules/impl/FontName.java index 04a55a5..484b3b6 100644 --- a/gui/core/src/main/java/com/github/gtache/autosubtitle/modules/gui/impl/FontFamily.java +++ b/core/src/main/java/com/github/gtache/autosubtitle/modules/impl/FontName.java @@ -1,4 +1,4 @@ -package com.github.gtache.autosubtitle.modules.gui.impl; +package com.github.gtache.autosubtitle.modules.impl; import javax.inject.Qualifier; import java.lang.annotation.Documented; @@ -12,5 +12,5 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; @Documented @Retention(RUNTIME) @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD}) -public @interface FontFamily { +public @interface FontName { } diff --git a/gui/core/src/main/java/com/github/gtache/autosubtitle/modules/gui/impl/FontSize.java b/core/src/main/java/com/github/gtache/autosubtitle/modules/impl/FontSize.java similarity index 87% rename from gui/core/src/main/java/com/github/gtache/autosubtitle/modules/gui/impl/FontSize.java rename to core/src/main/java/com/github/gtache/autosubtitle/modules/impl/FontSize.java index 3caf742..89898a4 100644 --- a/gui/core/src/main/java/com/github/gtache/autosubtitle/modules/gui/impl/FontSize.java +++ b/core/src/main/java/com/github/gtache/autosubtitle/modules/impl/FontSize.java @@ -1,4 +1,4 @@ -package com.github.gtache.autosubtitle.modules.gui.impl; +package com.github.gtache.autosubtitle.modules.impl; import javax.inject.Qualifier; import java.lang.annotation.Documented; diff --git a/core/src/main/java/com/github/gtache/autosubtitle/modules/subtitle/converter/impl/SubtitleConverterModule.java b/core/src/main/java/com/github/gtache/autosubtitle/modules/subtitle/converter/impl/SubtitleConverterModule.java new file mode 100644 index 0000000..58a9c3a --- /dev/null +++ b/core/src/main/java/com/github/gtache/autosubtitle/modules/subtitle/converter/impl/SubtitleConverterModule.java @@ -0,0 +1,34 @@ +package com.github.gtache.autosubtitle.modules.subtitle.converter.impl; + +import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter; +import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverterProvider; +import com.github.gtache.autosubtitle.subtitle.converter.impl.ASSSubtitleConverter; +import com.github.gtache.autosubtitle.subtitle.converter.impl.SRTSubtitleConverter; +import com.github.gtache.autosubtitle.subtitle.converter.impl.SubtitleConverterProviderImpl; +import dagger.Binds; +import dagger.Module; +import dagger.multibindings.IntoMap; +import dagger.multibindings.StringKey; + +/** + * Dagger module for the subtitle converters + */ +@Module +public abstract class SubtitleConverterModule { + + private SubtitleConverterModule() { + } + + @Binds + abstract SubtitleConverterProvider bindsSubtitleConverterProvider(final SubtitleConverterProviderImpl subtitleConverterProvider); + + @Binds + @IntoMap + @StringKey("srt") + abstract SubtitleConverter bindsSrtSubtitleConverter(final SRTSubtitleConverter converter); + + @Binds + @IntoMap + @StringKey("ass") + abstract SubtitleConverter bindsAssSubtitleConverter(final ASSSubtitleConverter converter); +} diff --git a/core/src/main/java/com/github/gtache/autosubtitle/modules/subtitle/impl/SubtitleModule.java b/core/src/main/java/com/github/gtache/autosubtitle/modules/subtitle/impl/SubtitleModule.java index 2a4e256..acef780 100644 --- a/core/src/main/java/com/github/gtache/autosubtitle/modules/subtitle/impl/SubtitleModule.java +++ b/core/src/main/java/com/github/gtache/autosubtitle/modules/subtitle/impl/SubtitleModule.java @@ -1,28 +1,20 @@ package com.github.gtache.autosubtitle.modules.subtitle.impl; +import com.github.gtache.autosubtitle.modules.subtitle.converter.impl.SubtitleConverterModule; import com.github.gtache.autosubtitle.subtitle.SubtitleImporterExporter; -import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter; -import com.github.gtache.autosubtitle.subtitle.converter.impl.SRTSubtitleConverter; import com.github.gtache.autosubtitle.subtitle.impl.SubtitleImporterExporterImpl; import dagger.Binds; import dagger.Module; -import dagger.multibindings.IntoMap; -import dagger.multibindings.StringKey; /** * Dagger module for subtitles */ -@Module +@Module(includes = {SubtitleConverterModule.class}) public abstract class SubtitleModule { private SubtitleModule() { } - @Binds - @IntoMap - @StringKey("srt") - abstract SubtitleConverter bindsSubtitleConverter(final SRTSubtitleConverter converter); - @Binds abstract SubtitleImporterExporter bindsSubtitleImporterExporter(final SubtitleImporterExporterImpl impl); } diff --git a/core/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/impl/ASSSubtitleConverter.java b/core/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/impl/ASSSubtitleConverter.java new file mode 100644 index 0000000..d91d13a --- /dev/null +++ b/core/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/impl/ASSSubtitleConverter.java @@ -0,0 +1,200 @@ +package com.github.gtache.autosubtitle.subtitle.converter.impl; + +import com.github.gtache.autosubtitle.VideoInfo; +import com.github.gtache.autosubtitle.modules.impl.FontName; +import com.github.gtache.autosubtitle.modules.impl.FontSize; +import com.github.gtache.autosubtitle.subtitle.Font; +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.impl.FontImpl; +import com.github.gtache.autosubtitle.subtitle.impl.SubtitleCollectionImpl; +import com.github.gtache.autosubtitle.subtitle.impl.SubtitleImpl; +import com.github.gtache.autosubtitle.translation.Translator; + +import javax.inject.Inject; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.prefs.Preferences; +import java.util.stream.Collectors; + +import static java.util.Objects.requireNonNull; + +/** + * Converts subtitles to SRT format + */ +public class ASSSubtitleConverter implements SubtitleConverter { + + private static final String STYLE = "Style:"; + private static final String FORMAT = "Format:"; + private static final String DIALOGUE = "Dialogue:"; + private static final String EVENTS_SECTION = "[Events]"; + private static final String STYLES_SECTION = "[V4+ Styles]"; + private final Translator translator; + private final Preferences preferences; + private final String defaultFontName; + private final int defaultFontSize; + + @Inject + ASSSubtitleConverter(final Translator translator, final Preferences preferences, + @FontName final String defaultFontName, @FontSize final int defaultFontSize) { + this.translator = requireNonNull(translator); + this.preferences = requireNonNull(preferences); + this.defaultFontName = Objects.requireNonNull(defaultFontName); + this.defaultFontSize = defaultFontSize; + } + + @Override + public String format(final SubtitleCollection collection, final VideoInfo videoInfo) { + final var subtitles = collection.subtitles().stream().sorted(Comparator.comparing(Subtitle::start).thenComparing(Subtitle::end)).toList(); + final var scriptInfo = getScriptInfo(videoInfo); + final var styles = getStyles(subtitles); + final var events = getEvents(subtitles); + return scriptInfo + "\n\n" + styles + "\n\n" + events; + } + + private String getEvents(final Collection subtitles) { + return EVENTS_SECTION + "\n" + "Format: Start, End, Style, Text\n" + + subtitles.stream().map(this::getEvent).collect(Collectors.joining("\n")); + } + + private String getEvent(final Subtitle subtitle) { + return DIALOGUE + " " + formatTime(subtitle.start()) + "," + formatTime(subtitle.end()) + "," + getName(subtitle.font()) + "," + subtitle.content(); + } + + private String getName(final Font font) { + final String fontName; + final int fontSize; + if (font == null) { + fontName = preferences.get("fontName", defaultFontName); + fontSize = preferences.getInt("fontSize", defaultFontSize); + } else { + fontName = font.name(); + fontSize = font.size(); + } + return fontName + fontSize; + } + + private String getStyles(final Collection subtitles) { + return STYLES_SECTION + "\n" + "Format: Name, Fontname, Fontsize\n" + listStyles(subtitles); + } + + private String listStyles(final Collection subtitles) { + final var uniqueStyles = subtitles.stream().map(Subtitle::font).filter(Objects::nonNull).collect(Collectors.toSet()); + if (subtitles.stream().anyMatch(s -> s.font() == null)) { + uniqueStyles.add(new FontImpl(preferences.get("fontName", defaultFontName), preferences.getInt("fontSize", defaultFontSize))); + } + return uniqueStyles.stream().map(f -> STYLE + " " + getName(f) + ", " + f.name() + ", " + f.size()).collect(Collectors.joining("\n")); + } + + private static String getScriptInfo(final VideoInfo videoInfo) { + return """ + [Script Info] + PlayResX: %d + PlayResY: %d + WrapStyle: 1""".formatted(videoInfo.width(), videoInfo.height()); + } + + private static String formatTime(final long time) { + final var millisPerHour = 3600000; + final var millisPerMinute = 60000; + final var hours = time / millisPerHour; + final var minutes = (time - hours * millisPerHour) / millisPerMinute; + final var seconds = (time - hours * millisPerHour - minutes * millisPerMinute) / 1000; + final var millis = time - hours * millisPerHour - minutes * millisPerMinute - seconds * 1000; + final var hundredths = millis / 10; + return String.format("%d:%02d:%02d.%02d", hours, minutes, seconds, hundredths); + } + + @Override + public SubtitleCollectionImpl parse(final String content) throws ParseException { + final var fonts = parseFonts(content); + final var subtitles = parseSubtitles(content, fonts); + final var text = subtitles.stream().map(Subtitle::content).collect(Collectors.joining()); + final var language = translator.getLanguage(text); + return new SubtitleCollectionImpl<>(text, subtitles, language); + } + + private static List parseSubtitles(final String content, final Map fonts) throws ParseException { + final var fontIndex = content.indexOf(EVENTS_SECTION); + if (fontIndex == -1) { + throw new ParseException("Events section not found in " + content); + } else { + final var split = content.substring(fontIndex).split("\\n"); + final List fields; + if (split[0].startsWith(EVENTS_SECTION)) { + fields = getFields(split[1]); + } else { + throw new ParseException("Couldn't parse events : " + content); + } + final var startIndex = fields.indexOf("Start"); + final var endIndex = fields.indexOf("End"); + final var styleIndex = fields.indexOf("Style"); + final var textIndex = fields.indexOf("Text"); + if (startIndex == -1 || endIndex == -1 || styleIndex == -1 || textIndex == -1) { + throw new ParseException("Couldn't parse events : " + content); + } + return Arrays.stream(split).filter(s -> s.startsWith(DIALOGUE)) + .map(s -> { + final var values = Arrays.stream(s.replace(DIALOGUE, "").split(",")).map(String::trim).filter(w -> !w.isBlank()).toList(); + final var start = parseTime(values.get(startIndex)); + final var end = parseTime(values.get(endIndex)); + final var style = values.get(styleIndex); + final var font = fonts.get(style); + final var text = values.stream().skip(textIndex).collect(Collectors.joining(", ")); + return new SubtitleImpl(text, start, end, font, null); //TODO pos style overrides + }).toList(); + } + } + + private Map parseFonts(final String content) throws ParseException { + final var fontIndex = content.indexOf(STYLES_SECTION); + if (fontIndex == -1) { + throw new ParseException("Styles section not found in " + content); + } else { + final var split = content.substring(fontIndex).split("\\n"); + final List fields; + if (split[0].startsWith(STYLES_SECTION)) { + fields = getFields(split[1]); + } else { + throw new ParseException("Couldn't parse styles : " + content); + } + final var fontNameIndex = fields.indexOf("Fontname"); + final var fontSizeIndex = fields.indexOf("Fontsize"); + if (fontNameIndex == -1 || fontSizeIndex == -1) { + throw new ParseException("Couldn't parse styles : " + content); + } + return Arrays.stream(split).filter(s -> s.startsWith(STYLE)) + .map(s -> { + final var values = Arrays.stream(s.replace(STYLE, "").split(",")).map(String::trim).filter(w -> !w.isBlank()).toList(); + final var name = values.get(fontNameIndex); + final var size = Integer.parseInt(values.get(fontSizeIndex)); + return new FontImpl(name, size); + }).collect(Collectors.toMap(this::getName, f -> f)); + } + } + + private static List getFields(final String string) { + return Arrays.stream(string.replace(FORMAT, "").split(",")).map(String::trim).filter(s -> !s.isBlank()).toList(); + } + + private static long parseTime(final String timeStr) { + final var split = timeStr.split(":"); + final var hours = Integer.parseInt(split[0]); + final var minutes = Integer.parseInt(split[1]); + final var secondsSplit = split[2].split("\\."); + final var seconds = Integer.parseInt(secondsSplit[0]); + final var hundredths = Integer.parseInt(secondsSplit[1]); + return (hours * 3600L + minutes * 60L + seconds) * 1000L + hundredths * 10L; + } + + @Override + public String formatName() { + return "ass"; + } +} diff --git a/core/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/impl/SRTSubtitleConverter.java b/core/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/impl/SRTSubtitleConverter.java index c6dd87f..4b394e6 100644 --- a/core/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/impl/SRTSubtitleConverter.java +++ b/core/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/impl/SRTSubtitleConverter.java @@ -1,5 +1,6 @@ package com.github.gtache.autosubtitle.subtitle.converter.impl; +import com.github.gtache.autosubtitle.VideoInfo; import com.github.gtache.autosubtitle.subtitle.Subtitle; import com.github.gtache.autosubtitle.subtitle.SubtitleCollection; import com.github.gtache.autosubtitle.subtitle.converter.ParseException; @@ -29,7 +30,7 @@ public class SRTSubtitleConverter implements SubtitleConverter { } @Override - public String format(final SubtitleCollection collection) { + public String format(final SubtitleCollection collection, final VideoInfo videoInfo) { final var subtitles = collection.subtitles().stream().sorted(Comparator.comparing(Subtitle::start).thenComparing(Subtitle::end)).toList(); return IntStream.range(0, subtitles.size()).mapToObj(i -> { final var subtitle = subtitles.get(i); diff --git a/core/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/impl/SubtitleConverterProviderImpl.java b/core/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/impl/SubtitleConverterProviderImpl.java new file mode 100644 index 0000000..39660fc --- /dev/null +++ b/core/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/impl/SubtitleConverterProviderImpl.java @@ -0,0 +1,32 @@ +package com.github.gtache.autosubtitle.subtitle.converter.impl; + +import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter; +import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverterProvider; + +import javax.inject.Inject; +import java.util.Collection; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Implementation of {@link SubtitleConverterProvider} + */ +public class SubtitleConverterProviderImpl implements SubtitleConverterProvider { + + private final Map> converters; + + @Inject + SubtitleConverterProviderImpl(final Map converters) { + this.converters = converters.entrySet().stream().collect(Collectors.toMap(e -> e.getKey().toUpperCase(), Map.Entry::getValue)); + } + + @Override + public Collection> allConverters() { + return converters.values(); + } + + @Override + public SubtitleConverter getConverter(final String format) { + return converters.get(format.toUpperCase()); + } +} diff --git a/core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SubtitleImporterExporterImpl.java b/core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SubtitleImporterExporterImpl.java index b930f60..d95cbde 100644 --- a/core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SubtitleImporterExporterImpl.java +++ b/core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SubtitleImporterExporterImpl.java @@ -1,11 +1,14 @@ package com.github.gtache.autosubtitle.subtitle.impl; import com.github.gtache.autosubtitle.Language; +import com.github.gtache.autosubtitle.VideoInfo; import com.github.gtache.autosubtitle.archive.Archiver; +import com.github.gtache.autosubtitle.archive.ArchiverProvider; import com.github.gtache.autosubtitle.subtitle.SubtitleCollection; import com.github.gtache.autosubtitle.subtitle.SubtitleImporterExporter; 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 org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -17,7 +20,8 @@ import java.util.ArrayList; import java.util.Collection; import java.util.EnumMap; import java.util.Map; -import java.util.stream.Collectors; + +import static java.util.Objects.requireNonNull; /** * Implementation of {@link SubtitleImporterExporter} @@ -25,32 +29,32 @@ import java.util.stream.Collectors; public class SubtitleImporterExporterImpl implements SubtitleImporterExporter { private static final Logger logger = LogManager.getLogger(SubtitleImporterExporterImpl.class); - private final Map archiverMap; - private final Map> converterMap; + private final ArchiverProvider archiverProvider; + private final SubtitleConverterProvider converterProvider; @Inject - SubtitleImporterExporterImpl(final Map archiverMap, final Map converterMap) { - this.archiverMap = Map.copyOf(archiverMap); - this.converterMap = converterMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + SubtitleImporterExporterImpl(final ArchiverProvider archiverProvider, final SubtitleConverterProvider converterProvider) { + this.archiverProvider = requireNonNull(archiverProvider); + this.converterProvider = requireNonNull(converterProvider); } @Override public Map> importSubtitles(final Path file) throws IOException, ParseException { final var fileName = file.getFileName().toString(); final var extension = fileName.substring(fileName.lastIndexOf('.') + 1); - if (archiverMap.containsKey(extension)) { - return loadArchive(file); - } else { + if (archiverProvider.getArchiver(extension) == null) { final var loaded = loadSingleFile(file); logger.info("Loaded {}", file); return Map.of(loaded.language(), loaded); + } else { + return loadArchive(file); } } private SubtitleCollection loadSingleFile(final Path file) throws ParseException { final var fileName = file.getFileName().toString(); final var extension = fileName.substring(fileName.lastIndexOf('.') + 1); - final var parser = converterMap.get(extension); + final var parser = converterProvider.getConverter(extension); if (parser == null) { throw new ParseException("No converter found for " + file); } else { @@ -62,7 +66,7 @@ public class SubtitleImporterExporterImpl implements SubtitleImporterExporter> loadArchive(final Path file) throws IOException, ParseException { final var fileName = file.getFileName().toString(); final var extension = fileName.substring(fileName.lastIndexOf('.') + 1); - final var archiver = archiverMap.get(extension); + final var archiver = archiverProvider.getArchiver(extension); final var tempDirectory = Files.createTempDirectory("autosubtitle"); archiver.decompress(file, tempDirectory); final var files = new ArrayList(); @@ -81,27 +85,27 @@ public class SubtitleImporterExporterImpl implements SubtitleImporterExporter> collections, final Path file) throws IOException { + public void exportSubtitles(final Collection> collections, final VideoInfo videoInfo, final Path file) throws IOException { final var fileName = file.getFileName().toString(); final var extension = fileName.substring(fileName.lastIndexOf('.') + 1); - if (archiverMap.containsKey(extension)) { - saveArchive(file, collections); + if (archiverProvider.getArchiver(extension) != null) { + saveArchive(file, collections, videoInfo); } else if (collections.size() == 1) { - saveSingleFile(file, collections.iterator().next()); + saveSingleFile(file, collections.iterator().next(), videoInfo); } else { throw new IllegalArgumentException("Cannot export multiple collections to a non-archive file : " + file); } } - private void saveArchive(final Path file, final Iterable> collections) throws IOException { + private void saveArchive(final Path file, final Iterable> collections, final VideoInfo videoInfo) throws IOException { final var fileName = file.getFileName().toString(); final var extension = fileName.substring(fileName.lastIndexOf('.') + 1); - final var archiver = archiverMap.get(extension); - final var singleExporter = converterMap.getOrDefault("json", converterMap.values().iterator().next()); + final var archiver = archiverProvider.getArchiver(extension); + final var singleExporter = converterProvider.getConverter("json"); final var tempDir = Files.createTempDirectory("autosubtitle"); for (final var collection : collections) { final var subtitleFile = tempDir.resolve(collection.language() + "." + singleExporter.formatName()); - saveSingleFile(subtitleFile, collection); + saveSingleFile(subtitleFile, collection, videoInfo); } final var files = new ArrayList(); try (final var stream = Files.list(tempDir)) { @@ -115,14 +119,14 @@ public class SubtitleImporterExporterImpl implements SubtitleImporterExporter collection) throws IOException { + private void saveSingleFile(final Path file, final SubtitleCollection collection, final VideoInfo videoInfo) throws IOException { final var fileName = file.getFileName().toString(); final var extension = fileName.substring(fileName.lastIndexOf('.') + 1); - final var converter = converterMap.get(extension); + final var converter = converterProvider.getConverter(extension); if (converter == null) { throw new IOException("No converter found for " + file); } else { - final var string = converter.format(collection); + final var string = converter.format(collection, videoInfo); Files.writeString(file, string); logger.info("Saved {}", file); } @@ -131,11 +135,11 @@ public class SubtitleImporterExporterImpl implements SubtitleImporterExporter supportedArchiveExtensions() { - return archiverMap.keySet(); + return archiverProvider.allArchivers().stream().map(Archiver::archiveExtension).toList(); } @Override public Collection supportedSingleFileExtensions() { - return converterMap.keySet(); + return converterProvider.allConverters().stream().map(SubtitleConverter::formatName).toList(); } } diff --git a/core/src/test/java/com/github/gtache/autosubtitle/subtitle/converter/impl/TestSRTSubtitleConverter.java b/core/src/test/java/com/github/gtache/autosubtitle/subtitle/converter/impl/TestSRTSubtitleConverter.java index aa87394..d93671e 100644 --- a/core/src/test/java/com/github/gtache/autosubtitle/subtitle/converter/impl/TestSRTSubtitleConverter.java +++ b/core/src/test/java/com/github/gtache/autosubtitle/subtitle/converter/impl/TestSRTSubtitleConverter.java @@ -1,6 +1,7 @@ package com.github.gtache.autosubtitle.subtitle.converter.impl; import com.github.gtache.autosubtitle.Language; +import com.github.gtache.autosubtitle.VideoInfo; import com.github.gtache.autosubtitle.subtitle.converter.ParseException; import com.github.gtache.autosubtitle.subtitle.impl.SubtitleCollectionImpl; import com.github.gtache.autosubtitle.subtitle.impl.SubtitleImpl; @@ -23,10 +24,12 @@ class TestSRTSubtitleConverter { private final Translator translator; private final Language language; + private final VideoInfo videoInfo; - TestSRTSubtitleConverter(@Mock final Translator translator, @Mock final Language language) { + TestSRTSubtitleConverter(@Mock final Translator translator, @Mock final Language language, @Mock final VideoInfo videoInfo) { this.translator = Objects.requireNonNull(translator); this.language = Objects.requireNonNull(language); + this.videoInfo = Objects.requireNonNull(videoInfo); when(translator.getLanguage(anyString())).thenReturn(language); } @@ -53,7 +56,7 @@ class TestSRTSubtitleConverter { final var subtitles = new SubtitleCollectionImpl<>(subtitle1.content() + " " + subtitle2.content(), Arrays.asList(subtitle1, subtitle2), language); final var converter = new SRTSubtitleConverter(translator); assertEquals(subtitles, converter.parse(in)); - assertEquals(in, converter.format(subtitles)); + assertEquals(in, converter.format(subtitles, videoInfo)); } @Test 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 4180ac7..79ee40b 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 @@ -4,6 +4,7 @@ import com.github.gtache.autosubtitle.Audio; import com.github.gtache.autosubtitle.File; import com.github.gtache.autosubtitle.Video; import com.github.gtache.autosubtitle.VideoConverter; +import com.github.gtache.autosubtitle.VideoInfo; import com.github.gtache.autosubtitle.impl.AudioInfoImpl; import com.github.gtache.autosubtitle.impl.FileAudioImpl; import com.github.gtache.autosubtitle.impl.FileVideoImpl; @@ -13,6 +14,7 @@ import com.github.gtache.autosubtitle.modules.setup.ffmpeg.FFmpegSystemPath; import com.github.gtache.autosubtitle.process.impl.AbstractProcessRunner; import com.github.gtache.autosubtitle.subtitle.SubtitleCollection; import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter; +import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverterProvider; import javax.inject.Inject; import java.io.IOException; @@ -23,9 +25,9 @@ import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import java.util.SequencedMap; import java.util.concurrent.atomic.AtomicInteger; +import java.util.prefs.Preferences; import static java.util.Objects.requireNonNull; @@ -37,13 +39,16 @@ public class FFmpegVideoConverter extends AbstractProcessRunner implements Video private static final String TEMP_FILE_PREFIX = "autosubtitle"; private final Path bundledPath; private final Path systemPath; - private final SubtitleConverter subtitleConverter; + private final SubtitleConverterProvider converterProvider; + private final Preferences preferences; @Inject - FFmpegVideoConverter(@FFmpegBundledPath final Path bundledPath, @FFmpegSystemPath final Path systemPath, final Map subtitleConverters) { + FFmpegVideoConverter(@FFmpegBundledPath final Path bundledPath, @FFmpegSystemPath final Path systemPath, + final SubtitleConverterProvider converterProvider, final Preferences preferences) { this.bundledPath = requireNonNull(bundledPath); this.systemPath = requireNonNull(systemPath); - this.subtitleConverter = subtitleConverters.get("srt"); + this.converterProvider = requireNonNull(converterProvider); + this.preferences = requireNonNull(preferences); } @Override @@ -56,7 +61,7 @@ public class FFmpegVideoConverter extends AbstractProcessRunner implements Video @Override public void addSoftSubtitles(final Video video, final Collection> subtitles, final Path path) throws IOException { final var videoPath = getPath(video); - final var collectionMap = dumpCollections(subtitles); + final var collectionMap = dumpCollections(subtitles, video.info()); final var args = new ArrayList(); args.add(getFFmpegPath()); args.add("-y"); @@ -86,7 +91,7 @@ public class FFmpegVideoConverter extends AbstractProcessRunner implements Video args.add("mov_text"); } else { args.add("-c:s"); - args.add(subtitleConverter.formatName()); + args.add("copy"); } final var j = new AtomicInteger(0); collectionMap.forEach((c, p) -> { @@ -108,9 +113,9 @@ public class FFmpegVideoConverter extends AbstractProcessRunner implements Video @Override public void addHardSubtitles(final Video video, final SubtitleCollection subtitles, final Path path) throws IOException { final var videoPath = getPath(video); - final var subtitlesPath = dumpSubtitles(subtitles); + final var subtitlesPath = dumpSubtitles(subtitles, video.info()); final var escapedPath = escapeVF(subtitlesPath.toString()); - final var subtitleArg = subtitleConverter.formatName().equalsIgnoreCase("ass") ? "ass='" + escapedPath + "'" : "subtitles='" + escapedPath + "'"; + final var subtitleArg = getSubtitleConverter().formatName().equalsIgnoreCase("ass") ? "ass='" + escapedPath + "'" : "subtitles='" + escapedPath + "'"; final var args = List.of( getFFmpegPath(), "-i", @@ -122,6 +127,10 @@ public class FFmpegVideoConverter extends AbstractProcessRunner implements Video runListen(args, Duration.ofHours(1)); } + private SubtitleConverter getSubtitleConverter() { + return converterProvider.getConverter(preferences.get("outputFormat", "ass")); + } + private static String escapeVF(final String path) { return path.replace("\\", "\\\\").replace(":", "\\:").replace("'", "'\\''") .replace("%", "\\%"); @@ -165,17 +174,17 @@ public class FFmpegVideoConverter extends AbstractProcessRunner implements Video return path; } - private > SequencedMap dumpCollections(final Collection collections) throws IOException { + private > SequencedMap dumpCollections(final Collection collections, final VideoInfo videoInfo) throws IOException { final var ret = LinkedHashMap.newLinkedHashMap(collections.size()); for (final var subtitles : collections) { - ret.put(subtitles, dumpSubtitles(subtitles)); + ret.put(subtitles, dumpSubtitles(subtitles, videoInfo)); } return ret; } - private Path dumpSubtitles(final SubtitleCollection subtitles) throws IOException { - final var path = getTempFile("srt"); - Files.writeString(path, subtitleConverter.format(subtitles)); + private Path dumpSubtitles(final SubtitleCollection subtitles, final VideoInfo videoInfo) throws IOException { + final var path = getTempFile(getSubtitleConverter().formatName().toLowerCase()); + Files.writeString(path, getSubtitleConverter().format(subtitles, videoInfo)); return path; } 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 9ec7533..ecc0ad3 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,6 +1,6 @@ package com.github.gtache.autosubtitle.setup.ffmpeg; -import com.github.gtache.autosubtitle.archive.Archiver; +import com.github.gtache.autosubtitle.archive.ArchiverProvider; import com.github.gtache.autosubtitle.impl.Architecture; import com.github.gtache.autosubtitle.setup.SetupException; import com.github.gtache.autosubtitle.setup.SetupManager; @@ -16,7 +16,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; import java.util.List; -import java.util.Map; import static com.github.gtache.autosubtitle.impl.Architecture.ARMEL; import static com.github.gtache.autosubtitle.impl.Architecture.ARMHF; @@ -29,14 +28,14 @@ import static java.util.Objects.requireNonNull; public class FFmpegSetupManager extends AbstractSetupManager { private static final Logger logger = LogManager.getLogger(FFmpegSetupManager.class); private final FFmpegSetupConfiguration configuration; - private final Map archivers; + private final ArchiverProvider archiverProvider; @Inject - FFmpegSetupManager(final FFmpegSetupConfiguration configuration, final Map archivers, + FFmpegSetupManager(final FFmpegSetupConfiguration configuration, final ArchiverProvider archiverProvider, final HttpClient httpClient) { super(httpClient); this.configuration = requireNonNull(configuration); - this.archivers = Map.copyOf(archivers); + this.archiverProvider = requireNonNull(archiverProvider); } @Override @@ -135,7 +134,7 @@ public class FFmpegSetupManager extends AbstractSetupManager { try { final var filename = from.getFileName().toString(); final var extension = filename.substring(filename.lastIndexOf('.') + 1); - archivers.get(extension).decompress(from, to); + archiverProvider.getArchiver(extension).decompress(from, to); } catch (final IOException e) { throw new SetupException(e); } diff --git a/ffmpeg/src/main/java/module-info.java b/ffmpeg/src/main/java/module-info.java index 4605994..8c87be0 100644 --- a/ffmpeg/src/main/java/module-info.java +++ b/ffmpeg/src/main/java/module-info.java @@ -9,6 +9,7 @@ module com.github.gtache.autosubtitle.ffmpeg { requires org.apache.logging.log4j; requires org.tukaani.xz; requires org.apache.commons.compress; + requires java.prefs; exports com.github.gtache.autosubtitle.ffmpeg; exports com.github.gtache.autosubtitle.setup.ffmpeg; diff --git a/ffmpeg/src/test/java/com/github/gtache/autosubtitle/ffmpeg/TestFFmpegVideoConverter.java b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/ffmpeg/TestFFmpegVideoConverter.java index 7ce2845..16f9db3 100644 --- a/ffmpeg/src/test/java/com/github/gtache/autosubtitle/ffmpeg/TestFFmpegVideoConverter.java +++ b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/ffmpeg/TestFFmpegVideoConverter.java @@ -5,6 +5,7 @@ import com.github.gtache.autosubtitle.VideoInfo; import com.github.gtache.autosubtitle.impl.OS; import com.github.gtache.autosubtitle.subtitle.SubtitleCollection; import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter; +import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverterProvider; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -14,8 +15,8 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; -import java.util.Map; import java.util.Objects; +import java.util.prefs.Preferences; import static org.mockito.Mockito.when; @@ -24,14 +25,16 @@ class TestFFmpegVideoConverter { private final FFmpegVideoConverter converter; private final SubtitleConverter subtitleConverter; + private final SubtitleConverterProvider subtitleConverterProvider; private final Video video; private final VideoInfo videoInfo; private final Path tmpFile; private final Path outputPath; private final SubtitleCollection collection; + private final Preferences preferences; - TestFFmpegVideoConverter(@Mock final SubtitleConverter subtitleConverter, @Mock final Video video, - @Mock final VideoInfo videoInfo, @Mock final SubtitleCollection collection) throws IOException { + TestFFmpegVideoConverter(@Mock final SubtitleConverter subtitleConverter, @Mock final SubtitleConverterProvider subtitleConverterProvider, @Mock final Video video, + @Mock final VideoInfo videoInfo, @Mock final SubtitleCollection collection, @Mock final Preferences preferences) throws IOException { final var output = (OS.getOS() == OS.WINDOWS ? System.getProperty("java.io.tmpdir") : "/tmp"); final var resource = OS.getOS() == OS.WINDOWS ? "fake-ffmpeg.exe" : "fake-ffmpeg.sh"; this.video = Objects.requireNonNull(video); @@ -43,7 +46,9 @@ class TestFFmpegVideoConverter { } this.outputPath = Path.of(output, "test-ffmpeg-output.txt"); this.subtitleConverter = Objects.requireNonNull(subtitleConverter); - this.converter = new FFmpegVideoConverter(tmpFile, tmpFile, Map.of("srt", subtitleConverter)); + this.subtitleConverterProvider = Objects.requireNonNull(subtitleConverterProvider); + this.preferences = Objects.requireNonNull(preferences); + this.converter = new FFmpegVideoConverter(tmpFile, tmpFile, subtitleConverterProvider, preferences); this.collection = Objects.requireNonNull(collection); } 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 e1b67dc..e64e75c 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 @@ -48,12 +48,12 @@ public interface ParametersModel { /** * @return The current font family */ - String fontFamily(); + String fontName(); /** * @param fontFamily The new font family */ - void setFontFamily(String fontFamily); + void setFontName(String fontFamily); /** * @return The current font size diff --git a/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/SubtitlesModel.java b/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/SubtitlesModel.java index 1dec1d9..ede2812 100644 --- a/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/SubtitlesModel.java +++ b/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/SubtitlesModel.java @@ -1,6 +1,7 @@ package com.github.gtache.autosubtitle.gui; import com.github.gtache.autosubtitle.Language; +import com.github.gtache.autosubtitle.VideoInfo; import com.github.gtache.autosubtitle.subtitle.Subtitle; import com.github.gtache.autosubtitle.subtitle.SubtitleCollection; @@ -32,6 +33,18 @@ public interface SubtitlesModel 0); } - - @Test - void testFontFamily() { - assertEquals("Arial", GuiCoreModule.providesFontFamily()); - } - - @Test - void testFontSize() { - assertEquals(12, GuiCoreModule.providesFontSize()); - } } diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXParametersController.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXParametersController.java index c6ccb8b..73e464a 100644 --- a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXParametersController.java +++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXParametersController.java @@ -62,7 +62,7 @@ public class FXParametersController extends AbstractFXController implements Para extractionOutputFormat.valueProperty().bindBidirectional(model.outputFormatProperty()); fontFamilyCombobox.setItems(model.availableFontFamilies()); - fontFamilyCombobox.valueProperty().bindBidirectional(model.fontFamilyProperty()); + fontFamilyCombobox.valueProperty().bindBidirectional(model.fontNameProperty()); final UnaryOperator integerFilter = change -> { final var newText = change.getControlNewText(); @@ -85,14 +85,14 @@ public class FXParametersController extends AbstractFXController implements Para private void loadPreferences() { final var extractionModel = preferences.get("extractionModel", model.extractionModel().name()); final var outputFormat = preferences.get("outputFormat", model.outputFormat().name()); - final var fontFamily = preferences.get("fontFamily", model.fontFamily()); + final var fontFamily = preferences.get("fontName", model.fontName()); final var fontSize = preferences.getInt("fontSize", model.fontSize()); final var maxLineLength = preferences.getInt("maxLineLength", model.maxLineLength()); final var maxLines = preferences.getInt("maxLines", model.maxLines()); model.setExtractionModel(extractionModelProvider.getExtractionModel(extractionModel)); model.setOutputFormat(OutputFormat.valueOf(outputFormat)); - model.setFontFamily(fontFamily); + model.setFontName(fontFamily); model.setFontSize(fontSize); model.setMaxLineLength(maxLineLength); model.setMaxLines(maxLines); @@ -105,7 +105,7 @@ public class FXParametersController extends AbstractFXController implements Para logger.info("Saving preferences"); preferences.put("extractionModel", model.extractionModel().name()); preferences.put("outputFormat", model.outputFormat().name()); - preferences.put("fontFamily", model.fontFamily()); + preferences.put("fontName", model.fontName()); preferences.putInt("fontSize", model.fontSize()); preferences.putInt("maxLineLength", model.maxLineLength()); preferences.putInt("maxLines", model.maxLines()); diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXParametersModel.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXParametersModel.java index 055076d..c58c629 100644 --- a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXParametersModel.java +++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXParametersModel.java @@ -1,8 +1,8 @@ package com.github.gtache.autosubtitle.gui.fx; import com.github.gtache.autosubtitle.gui.ParametersModel; -import com.github.gtache.autosubtitle.modules.gui.impl.FontFamily; -import com.github.gtache.autosubtitle.modules.gui.impl.FontSize; +import com.github.gtache.autosubtitle.modules.impl.FontName; +import com.github.gtache.autosubtitle.modules.impl.FontSize; import com.github.gtache.autosubtitle.modules.impl.MaxLineLength; import com.github.gtache.autosubtitle.modules.impl.MaxLines; import com.github.gtache.autosubtitle.subtitle.OutputFormat; @@ -31,20 +31,20 @@ public class FXParametersModel implements ParametersModel { private final ObservableList availableOutputFormats; private final ObjectProperty outputFormat; private final ObservableList availableFontFamilies; - private final StringProperty fontFamily; + private final StringProperty fontName; private final IntegerProperty fontSize; private final IntegerProperty maxLineLength; private final IntegerProperty maxLines; @Inject - FXParametersModel(final ExtractionModelProvider extractionModelProvider, @FontFamily final String defaultFontFamily, + FXParametersModel(final ExtractionModelProvider extractionModelProvider, @FontName final String defaultFontFamily, @FontSize final int defaultFontSize, @MaxLineLength final int defaultMaxLineLength, @MaxLines final int defaultMaxLines) { this.availableExtractionModels = FXCollections.unmodifiableObservableList(FXCollections.observableArrayList(extractionModelProvider.getAvailableExtractionModels())); this.extractionModel = new SimpleObjectProperty<>(extractionModelProvider.getDefaultExtractionModel()); - this.availableOutputFormats = FXCollections.unmodifiableObservableList(FXCollections.observableArrayList(OutputFormat.SRT)); - this.outputFormat = new SimpleObjectProperty<>(OutputFormat.SRT); + this.availableOutputFormats = FXCollections.unmodifiableObservableList(FXCollections.observableArrayList(OutputFormat.values())); + this.outputFormat = new SimpleObjectProperty<>(OutputFormat.ASS); this.availableFontFamilies = FXCollections.unmodifiableObservableList(FXCollections.observableArrayList("Arial")); - this.fontFamily = new SimpleStringProperty(defaultFontFamily); + this.fontName = new SimpleStringProperty(defaultFontFamily); this.fontSize = new SimpleIntegerProperty(defaultFontSize); this.maxLineLength = new SimpleIntegerProperty(defaultMaxLineLength); this.maxLines = new SimpleIntegerProperty(defaultMaxLines); @@ -94,17 +94,17 @@ public class FXParametersModel implements ParametersModel { } @Override - public String fontFamily() { - return fontFamily.get(); + public String fontName() { + return fontName.get(); } @Override - public void setFontFamily(final String fontFamily) { - this.fontFamily.set(fontFamily); + public void setFontName(final String fontFamily) { + this.fontName.set(fontFamily); } - StringProperty fontFamilyProperty() { - return fontFamily; + StringProperty fontNameProperty() { + return fontName; } @Override diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesBinder.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesBinder.java index bda34c2..ddbf450 100644 --- a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesBinder.java +++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesBinder.java @@ -1,6 +1,7 @@ package com.github.gtache.autosubtitle.gui.fx; import com.github.gtache.autosubtitle.Language; +import com.github.gtache.autosubtitle.Video; import com.github.gtache.autosubtitle.gui.WorkStatus; import com.github.gtache.autosubtitle.subtitle.gui.fx.ObservableSubtitleCollectionImpl; import javafx.beans.binding.Bindings; @@ -34,6 +35,7 @@ public class FXSubtitlesBinder implements FXBinder { workModel.selectedSubtitleProperty().bind(subtitlesModel.selectedSubtitleProperty()); workModel.canExportProperty().bind(Bindings.isNotEmpty(subtitlesModel.collections()).and(workModel.statusProperty().isEqualTo(WorkStatus.IDLE))); workModel.videoLanguageProperty().bind(subtitlesModel.videoLanguageProperty()); + subtitlesModel.videoInfoProperty().bind(workModel.videoProperty().map(Video::info)); subtitlesModel.translatingProperty().addListener((observable, oldValue, newValue) -> { if (newValue) { diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesController.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesController.java index b9dcea0..df72230 100644 --- a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesController.java +++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesController.java @@ -152,6 +152,9 @@ public class FXSubtitlesController extends AbstractFXController implements Subti model.setSelectedCollection(model.collections().get(value)); } else { logger.error("Error while translating to {}", value, t); + final var newCollection = new ObservableSubtitleCollectionImpl(); + loadCollection(newCollection); + model.setSelectedCollection(newCollection); } model.setTranslating(false); }, Platform::runLater); @@ -284,9 +287,9 @@ public class FXSubtitlesController extends AbstractFXController implements Subti final var filename = file.getFileName().toString(); final var extension = filename.substring(filename.lastIndexOf('.') + 1); if (subtitleExtensions.contains(extension)) { - importerExporter.exportSubtitles(model.selectedCollection(), file); + importerExporter.exportSubtitles(model.selectedCollection(), model.videoInfo(), file); } else { - importerExporter.exportSubtitles(model.collections().values(), file); + importerExporter.exportSubtitles(model.collections().values(), model.videoInfo(), file); } } catch (final IOException e) { logger.error("Error saving subtitles {}", file, e); diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesModel.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesModel.java index 328442b..3091eaf 100644 --- a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesModel.java +++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesModel.java @@ -1,6 +1,7 @@ package com.github.gtache.autosubtitle.gui.fx; import com.github.gtache.autosubtitle.Language; +import com.github.gtache.autosubtitle.VideoInfo; import com.github.gtache.autosubtitle.gui.SubtitlesModel; import com.github.gtache.autosubtitle.subtitle.gui.fx.ObservableSubtitleCollectionImpl; import com.github.gtache.autosubtitle.subtitle.gui.fx.ObservableSubtitleImpl; @@ -29,6 +30,7 @@ public class FXSubtitlesModel implements SubtitlesModel availableVideoLanguages; private final ObjectProperty videoLanguage; + private final ObjectProperty videoInfo; private final ObservableList availableTranslationLanguages; private final ObservableList selectedTranslationsLanguages; private final ObjectProperty selectedLanguage; @@ -59,6 +61,7 @@ public class FXSubtitlesModel implements SubtitlesModel l != Language.AUTO).toList()); this.videoLanguage = new SimpleObjectProperty<>(Language.AUTO); + this.videoInfo = new SimpleObjectProperty<>(); this.selectedTranslationsLanguages = FXCollections.observableArrayList(); this.selectedLanguage = new SimpleObjectProperty<>(Language.AUTO); this.collections = FXCollections.observableHashMap(); @@ -108,6 +111,20 @@ public class FXSubtitlesModel implements SubtitlesModel videoInfoProperty() { + return videoInfo; + } + @Override public ObservableList availableTranslationsLanguage() { return FXCollections.unmodifiableObservableList(availableTranslationLanguages); diff --git a/gui/fx/src/test/java/com/github/gtache/autosubtitle/gui/fx/TestFXParametersModel.java b/gui/fx/src/test/java/com/github/gtache/autosubtitle/gui/fx/TestFXParametersModel.java index b0b7cb8..1ee5d9a 100644 --- a/gui/fx/src/test/java/com/github/gtache/autosubtitle/gui/fx/TestFXParametersModel.java +++ b/gui/fx/src/test/java/com/github/gtache/autosubtitle/gui/fx/TestFXParametersModel.java @@ -77,13 +77,13 @@ class TestFXParametersModel { } @Test - void testFontFamily() { - assertEquals(DEFAULT_FONT_FAMILY, model.fontFamily()); - assertEquals(DEFAULT_FONT_FAMILY, model.fontFamilyProperty().get()); + void testFontName() { + assertEquals(DEFAULT_FONT_FAMILY, model.fontName()); + assertEquals(DEFAULT_FONT_FAMILY, model.fontNameProperty().get()); final var fontFamily = DEFAULT_FONT_FAMILY + " A"; - model.setFontFamily(fontFamily); - assertEquals(fontFamily, model.fontFamily()); - assertEquals(fontFamily, model.fontFamilyProperty().get()); + model.setFontName(fontFamily); + assertEquals(fontFamily, model.fontName()); + assertEquals(fontFamily, model.fontNameProperty().get()); } @Test diff --git a/whisper/base/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/base/WhisperSubtitleExtractor.java b/whisper/base/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/base/WhisperSubtitleExtractor.java index 5366a27..81df659 100644 --- a/whisper/base/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/base/WhisperSubtitleExtractor.java +++ b/whisper/base/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/base/WhisperSubtitleExtractor.java @@ -3,7 +3,7 @@ package com.github.gtache.autosubtitle.subtitle.extractor.whisper.base; import com.github.gtache.autosubtitle.Language; import com.github.gtache.autosubtitle.impl.OS; import com.github.gtache.autosubtitle.modules.setup.whisper.WhisperVenvPath; -import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter; +import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverterProvider; import com.github.gtache.autosubtitle.subtitle.extractor.ExtractionModel; import com.github.gtache.autosubtitle.subtitle.extractor.SubtitleExtractor; import com.github.gtache.autosubtitle.subtitle.extractor.whisper.AbstractWhisperSubtitleExtractor; @@ -14,7 +14,6 @@ import javax.inject.Singleton; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; -import java.util.Map; /** * Whisper implementation of {@link SubtitleExtractor} @@ -24,8 +23,8 @@ public class WhisperSubtitleExtractor extends AbstractWhisperSubtitleExtractor { @Inject - WhisperSubtitleExtractor(@WhisperVenvPath final Path venvPath, final Map converters, final OS os) { - super(venvPath, converters, os); + WhisperSubtitleExtractor(@WhisperVenvPath final Path venvPath, final SubtitleConverterProvider converterProvider, final OS os) { + super(venvPath, converterProvider, os); } @Override diff --git a/whisper/base/src/main/java/com/github/gtache/autosubtitle/subtitle/parser/json/whisper/base/JSONSubtitleConverter.java b/whisper/base/src/main/java/com/github/gtache/autosubtitle/subtitle/parser/json/whisper/base/JSONSubtitleConverter.java index ea294f5..810b0ff 100644 --- a/whisper/base/src/main/java/com/github/gtache/autosubtitle/subtitle/parser/json/whisper/base/JSONSubtitleConverter.java +++ b/whisper/base/src/main/java/com/github/gtache/autosubtitle/subtitle/parser/json/whisper/base/JSONSubtitleConverter.java @@ -1,6 +1,7 @@ package com.github.gtache.autosubtitle.subtitle.parser.json.whisper.base; import com.github.gtache.autosubtitle.Language; +import com.github.gtache.autosubtitle.VideoInfo; import com.github.gtache.autosubtitle.subtitle.Subtitle; import com.github.gtache.autosubtitle.subtitle.SubtitleCollection; import com.github.gtache.autosubtitle.subtitle.converter.ParseException; @@ -35,7 +36,7 @@ public class JSONSubtitleConverter implements SubtitleConverter { } @Override - public String format(final SubtitleCollection collection) { + public String format(final SubtitleCollection collection, final VideoInfo videoInfo) { final var id = new AtomicInteger(0); final var segments = collection.subtitles().stream().map(s -> new JSONSubtitleSegment(id.incrementAndGet(), 0, s.start() / (double) 1000, s.end() / (double) 1000, s.content(), List.of(), 0, 0, 0, 0)).toList(); 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 d0843b2..bdcbe59 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 @@ -10,6 +10,7 @@ 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; @@ -25,7 +26,6 @@ import java.nio.file.Path; import java.time.Duration; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.regex.Pattern; @@ -47,9 +47,9 @@ public abstract class AbstractWhisperSubtitleExtractor extends AbstractProcessRu private final OS os; private final Set listeners; - protected AbstractWhisperSubtitleExtractor(final Path venvPath, final Map converters, final OS os) { + protected AbstractWhisperSubtitleExtractor(final Path venvPath, final SubtitleConverterProvider converterProvider, final OS os) { this.venvPath = requireNonNull(venvPath); - this.converter = requireNonNull(converters.get("json")); + this.converter = requireNonNull(converterProvider.getConverter("json")); this.os = requireNonNull(os); this.listeners = new HashSet<>(); } diff --git a/whisper/whisperx/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisperx/WhisperXSubtitleExtractor.java b/whisper/whisperx/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisperx/WhisperXSubtitleExtractor.java index 32d21ca..2c588df 100644 --- a/whisper/whisperx/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisperx/WhisperXSubtitleExtractor.java +++ b/whisper/whisperx/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisperx/WhisperXSubtitleExtractor.java @@ -3,7 +3,7 @@ package com.github.gtache.autosubtitle.subtitle.extractor.whisperx; import com.github.gtache.autosubtitle.Language; import com.github.gtache.autosubtitle.impl.OS; import com.github.gtache.autosubtitle.modules.setup.whisper.WhisperVenvPath; -import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter; +import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverterProvider; import com.github.gtache.autosubtitle.subtitle.extractor.ExtractionModel; import com.github.gtache.autosubtitle.subtitle.extractor.SubtitleExtractor; import com.github.gtache.autosubtitle.subtitle.extractor.whisper.AbstractWhisperSubtitleExtractor; @@ -14,7 +14,6 @@ import javax.inject.Singleton; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; -import java.util.Map; /** * WhisperX implementation of {@link SubtitleExtractor} @@ -23,8 +22,8 @@ import java.util.Map; public class WhisperXSubtitleExtractor extends AbstractWhisperSubtitleExtractor { @Inject - WhisperXSubtitleExtractor(@WhisperVenvPath final Path venvPath, final Map converters, final OS os) { - super(venvPath, converters, os); + WhisperXSubtitleExtractor(@WhisperVenvPath final Path venvPath, final SubtitleConverterProvider converterProvider, final OS os) { + super(venvPath, converterProvider, os); } @Override diff --git a/whisper/whisperx/src/main/java/com/github/gtache/autosubtitle/subtitle/parser/json/whisperx/JSONSubtitleConverter.java b/whisper/whisperx/src/main/java/com/github/gtache/autosubtitle/subtitle/parser/json/whisperx/JSONSubtitleConverter.java index e5c41ba..1d2d01c 100644 --- a/whisper/whisperx/src/main/java/com/github/gtache/autosubtitle/subtitle/parser/json/whisperx/JSONSubtitleConverter.java +++ b/whisper/whisperx/src/main/java/com/github/gtache/autosubtitle/subtitle/parser/json/whisperx/JSONSubtitleConverter.java @@ -1,6 +1,7 @@ package com.github.gtache.autosubtitle.subtitle.parser.json.whisperx; import com.github.gtache.autosubtitle.Language; +import com.github.gtache.autosubtitle.VideoInfo; import com.github.gtache.autosubtitle.modules.impl.MaxLineLength; import com.github.gtache.autosubtitle.modules.impl.MaxLines; import com.github.gtache.autosubtitle.subtitle.Subtitle; @@ -47,7 +48,7 @@ public class JSONSubtitleConverter implements SubtitleConverter { } @Override - public String format(final SubtitleCollection collection) { + public String format(final SubtitleCollection collection, final VideoInfo videoInfo) { final var segments = collection.subtitles().stream().map(s -> new JSONSubtitleSegment(s.start() / (double) 1000, s.end() / (double) 1000, s.content(), List.of())).toList(); final var subtitles = new JSONSubtitles(segments, collection.language().iso2());