Allows setting max lines and max line length, enables DeepL, adds user setup bridge
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -38,4 +38,5 @@ build/
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
|
||||
tools
|
||||
tools
|
||||
.secrets
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
package com.github.gtache.autosubtitle;
|
||||
|
||||
import com.github.gtache.autosubtitle.subtitle.Subtitle;
|
||||
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
|
||||
|
||||
/**
|
||||
* Translates texts and subtitles
|
||||
*/
|
||||
public interface Translator<T extends Subtitle> {
|
||||
|
||||
/**
|
||||
* Guesses the language of the given text
|
||||
*
|
||||
* @param text The text
|
||||
* @return The guessed language
|
||||
*/
|
||||
Language getLanguage(final String text);
|
||||
|
||||
/**
|
||||
* Guesses the language of the given subtitle
|
||||
*
|
||||
* @param subtitle The subtitle
|
||||
* @return The guessed language
|
||||
*/
|
||||
default Language getLanguage(final Subtitle subtitle) {
|
||||
return getLanguage(subtitle.content());
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates the given text to the given language
|
||||
*
|
||||
* @param text The text to translate
|
||||
* @param to The target language
|
||||
* @return The translated text
|
||||
*/
|
||||
String translate(String text, Language to);
|
||||
|
||||
/**
|
||||
* Translates the given subtitle to the given language
|
||||
*
|
||||
* @param subtitle The subtitle to translate
|
||||
* @param to The target language
|
||||
* @return The translated subtitle
|
||||
*/
|
||||
T translate(Subtitle subtitle, Language to);
|
||||
|
||||
/**
|
||||
* Translates the given subtitles collection to the given language
|
||||
*
|
||||
* @param collection The subtitles collection to translate
|
||||
* @param to The target language
|
||||
* @return The translated subtitles collection
|
||||
*/
|
||||
SubtitleCollection<T> translate(SubtitleCollection<?> collection, Language to);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.github.gtache.autosubtitle.setup;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Bridge between setup manager and the user
|
||||
*/
|
||||
public interface SetupUserBridge {
|
||||
|
||||
/**
|
||||
* Asks for user input
|
||||
*
|
||||
* @param question the question to display to the user
|
||||
* @return the user input
|
||||
*/
|
||||
String askForUserInput(String question);
|
||||
|
||||
/**
|
||||
* Asks for user choice
|
||||
*
|
||||
* @param question the question to display to the user
|
||||
* @param choices the possible choices
|
||||
* @param <T> the type of the choices
|
||||
* @return the user choice
|
||||
*/
|
||||
<T> T askForUserChoice(String question, List<T> choices);
|
||||
|
||||
/**
|
||||
* Asks for user confirmation
|
||||
*
|
||||
* @param question the question to display to the user
|
||||
* @return whether the user confirmed
|
||||
*/
|
||||
boolean askForUserConfirmation(String question);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.github.gtache.autosubtitle.translation;
|
||||
|
||||
/**
|
||||
* Exception thrown when an error occurs during translation
|
||||
*/
|
||||
public class TranslationException extends Exception {
|
||||
|
||||
public TranslationException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public TranslationException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public TranslationException(final Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.github.gtache.autosubtitle.translation;
|
||||
|
||||
import com.github.gtache.autosubtitle.Language;
|
||||
import com.github.gtache.autosubtitle.subtitle.Subtitle;
|
||||
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
|
||||
|
||||
/**
|
||||
* Translates texts and subtitles
|
||||
*/
|
||||
public interface Translator<T extends Subtitle> {
|
||||
|
||||
/**
|
||||
* Guesses the language of the given text
|
||||
*
|
||||
* @param text The text
|
||||
* @return The guessed language
|
||||
*/
|
||||
Language getLanguage(final String text);
|
||||
|
||||
/**
|
||||
* Guesses the language of the given subtitle
|
||||
*
|
||||
* @param subtitle The subtitle
|
||||
* @return The guessed language
|
||||
*/
|
||||
default Language getLanguage(final Subtitle subtitle) {
|
||||
return getLanguage(subtitle.content());
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates the given text to the given language
|
||||
*
|
||||
* @param text The text to translate
|
||||
* @param to The target language
|
||||
* @return The translated text
|
||||
* @throws TranslationException if an error occurred during the translation
|
||||
*/
|
||||
default String translate(final String text, final Language to) throws TranslationException {
|
||||
return translate(text, getLanguage(text), to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates the given text to the given language
|
||||
*
|
||||
* @param text The text to translate
|
||||
* @param from The source language
|
||||
* @param to The target language
|
||||
* @return The translated text
|
||||
* @throws TranslationException if an error occurred during the translation
|
||||
*/
|
||||
String translate(String text, Language from, Language to) throws TranslationException;
|
||||
|
||||
/**
|
||||
* Translates the given subtitle to the given language
|
||||
*
|
||||
* @param subtitle The subtitle to translate
|
||||
* @param to The target language
|
||||
* @return The translated subtitle
|
||||
* @throws TranslationException if an error occurred during the translation
|
||||
*/
|
||||
default T translate(final Subtitle subtitle, final Language to) throws TranslationException {
|
||||
return translate(subtitle, getLanguage(subtitle), to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates the given subtitle to the given language
|
||||
*
|
||||
* @param subtitle The subtitle to translate
|
||||
* @param from The source language
|
||||
* @param to The target language
|
||||
* @return The translated subtitle
|
||||
* @throws TranslationException if an error occurred during the translation
|
||||
*/
|
||||
T translate(Subtitle subtitle, Language from, Language to) throws TranslationException;
|
||||
|
||||
/**
|
||||
* Translates the given subtitles collection to the given language
|
||||
*
|
||||
* @param collection The subtitles collection to translate
|
||||
* @param to The target language
|
||||
* @return The translated subtitles collection
|
||||
* @throws TranslationException if an error occurred during the translation
|
||||
*/
|
||||
default SubtitleCollection<T> translate(final SubtitleCollection<?> collection, final Language to) throws TranslationException {
|
||||
return translate(collection, getLanguage(collection.text()), to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates the given subtitles collection to the given language
|
||||
*
|
||||
* @param collection The subtitles collection to translate
|
||||
* @param from The source language
|
||||
* @param to The target language
|
||||
* @return The translated subtitles collection
|
||||
* @throws TranslationException if an error occurred during the translation
|
||||
*/
|
||||
SubtitleCollection<T> translate(SubtitleCollection<?> collection, Language from, Language to) throws TranslationException;
|
||||
}
|
||||
@@ -9,4 +9,5 @@ module com.github.gtache.autosubtitle.api {
|
||||
exports com.github.gtache.autosubtitle.subtitle;
|
||||
exports com.github.gtache.autosubtitle.subtitle.extractor;
|
||||
exports com.github.gtache.autosubtitle.subtitle.converter;
|
||||
exports com.github.gtache.autosubtitle.translation;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.github.gtache.autosubtitle;
|
||||
|
||||
import com.github.gtache.autosubtitle.subtitle.Subtitle;
|
||||
import com.github.gtache.autosubtitle.translation.Translator;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
|
||||
@@ -13,6 +13,9 @@ import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import dagger.multibindings.StringKey;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
/**
|
||||
* Dagger module for Core
|
||||
*/
|
||||
@@ -53,4 +56,22 @@ public abstract class CoreModule {
|
||||
static String providesExecutableExtension(final OS os) {
|
||||
return os == OS.WINDOWS ? ".exe" : "";
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static Preferences providesPreferences() {
|
||||
return Preferences.userRoot().node("/com/github/gtache/autosubtitle");
|
||||
}
|
||||
|
||||
@Provides
|
||||
@MaxLineLength
|
||||
static int providesMaxLineLength() {
|
||||
return 40;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@MaxLines
|
||||
static int providesMaxLines() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 MaxLineLength {
|
||||
}
|
||||
@@ -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 MaxLines {
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
package com.github.gtache.autosubtitle.subtitle.converter.impl;
|
||||
|
||||
import com.github.gtache.autosubtitle.Translator;
|
||||
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.SubtitleCollectionImpl;
|
||||
import com.github.gtache.autosubtitle.subtitle.impl.SubtitleImpl;
|
||||
import com.github.gtache.autosubtitle.translation.Translator;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Arrays;
|
||||
|
||||
@@ -7,6 +7,7 @@ module com.github.gtache.autosubtitle.core {
|
||||
requires transitive java.net.http;
|
||||
requires transitive javax.inject;
|
||||
requires org.apache.logging.log4j;
|
||||
requires java.prefs;
|
||||
|
||||
exports com.github.gtache.autosubtitle.impl;
|
||||
exports com.github.gtache.autosubtitle.archive.impl;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package com.github.gtache.autosubtitle.subtitle.converter.impl;
|
||||
|
||||
import com.github.gtache.autosubtitle.Language;
|
||||
import com.github.gtache.autosubtitle.Translator;
|
||||
import com.github.gtache.autosubtitle.subtitle.converter.ParseException;
|
||||
import com.github.gtache.autosubtitle.subtitle.impl.SubtitleCollectionImpl;
|
||||
import com.github.gtache.autosubtitle.subtitle.impl.SubtitleImpl;
|
||||
import com.github.gtache.autosubtitle.translation.Translator;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
|
||||
@@ -11,11 +11,26 @@
|
||||
|
||||
<artifactId>autosubtitle-deepl</artifactId>
|
||||
|
||||
<properties>
|
||||
<deepl.version>1.5.0</deepl.version>
|
||||
<lingua.version>1.2.2</lingua.version>
|
||||
</properties>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.github.gtache.autosubtitle</groupId>
|
||||
<artifactId>autosubtitle-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.deepl.api</groupId>
|
||||
<artifactId>deepl-java</artifactId>
|
||||
<version>${deepl.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.pemistahl</groupId>
|
||||
<artifactId>lingua</artifactId>
|
||||
<version>${lingua.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,38 +0,0 @@
|
||||
package com.github.gtache.autosubtitle.deepl;
|
||||
|
||||
import com.github.gtache.autosubtitle.Language;
|
||||
import com.github.gtache.autosubtitle.Translator;
|
||||
import com.github.gtache.autosubtitle.subtitle.Subtitle;
|
||||
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* DeepL implementation of {@link Translator}
|
||||
*/
|
||||
public class DeepLTranslator implements Translator<Subtitle> {
|
||||
|
||||
@Inject
|
||||
DeepLTranslator() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Language getLanguage(final String text) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String translate(final String text, final Language to) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Subtitle translate(final Subtitle subtitle, final Language to) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubtitleCollection<Subtitle> translate(final SubtitleCollection<?> collection, final Language to) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,28 @@
|
||||
package com.github.gtache.autosubtitle.modules.deepl;
|
||||
|
||||
import com.github.gtache.autosubtitle.Translator;
|
||||
import com.github.gtache.autosubtitle.deepl.DeepLTranslator;
|
||||
import com.github.gtache.autosubtitle.modules.setup.deepl.DeepLSetupModule;
|
||||
import com.github.gtache.autosubtitle.translation.Translator;
|
||||
import com.github.gtache.autosubtitle.translation.deepl.DeepLTranslator;
|
||||
import com.github.pemistahl.lingua.api.LanguageDetector;
|
||||
import com.github.pemistahl.lingua.api.LanguageDetectorBuilder;
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/**
|
||||
* Dagger module for DeepL
|
||||
*/
|
||||
@Module
|
||||
public interface DeepLModule {
|
||||
@Module(includes = DeepLSetupModule.class)
|
||||
public abstract class DeepLModule {
|
||||
|
||||
@Binds
|
||||
Translator bindsTranslator(final DeepLTranslator translator);
|
||||
abstract Translator bindsTranslator(final DeepLTranslator translator);
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static LanguageDetector providesLanguageDetector() {
|
||||
return LanguageDetectorBuilder.fromAllSpokenLanguages().build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.github.gtache.autosubtitle.modules.setup.deepl;
|
||||
|
||||
import com.github.gtache.autosubtitle.modules.setup.impl.TranslatorSetup;
|
||||
import com.github.gtache.autosubtitle.setup.SetupManager;
|
||||
import com.github.gtache.autosubtitle.setup.deepl.DeepLSetupManager;
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
|
||||
/**
|
||||
* Dagger module for DeepL setup
|
||||
*/
|
||||
@Module
|
||||
public abstract class DeepLSetupModule {
|
||||
private DeepLSetupModule() {
|
||||
}
|
||||
|
||||
@Binds
|
||||
@TranslatorSetup
|
||||
abstract SetupManager bindsSetupManager(final DeepLSetupManager setupManager);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.github.gtache.autosubtitle.setup.deepl;
|
||||
|
||||
import com.github.gtache.autosubtitle.setup.SetupException;
|
||||
import com.github.gtache.autosubtitle.setup.SetupManager;
|
||||
import com.github.gtache.autosubtitle.setup.SetupStatus;
|
||||
import com.github.gtache.autosubtitle.setup.SetupUserBridge;
|
||||
import com.github.gtache.autosubtitle.setup.impl.AbstractSetupManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Objects;
|
||||
import java.util.prefs.BackingStoreException;
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
/**
|
||||
* {@link SetupManager} for DeepL
|
||||
*/
|
||||
public class DeepLSetupManager extends AbstractSetupManager {
|
||||
|
||||
private static final String DEEPL_API_KEY = "deepl.api.key";
|
||||
|
||||
private final SetupUserBridge userBridge;
|
||||
private final Preferences preferences;
|
||||
|
||||
@Inject
|
||||
DeepLSetupManager(final SetupUserBridge userBridge, final Preferences preferences) {
|
||||
this.userBridge = Objects.requireNonNull(userBridge);
|
||||
this.preferences = Objects.requireNonNull(preferences);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SetupStatus getStatus() throws SetupException {
|
||||
final var key = preferences.get(DEEPL_API_KEY, null);
|
||||
return key == null ? SetupStatus.NOT_INSTALLED : SetupStatus.BUNDLE_INSTALLED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "DeepL";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void install() throws SetupException {
|
||||
final var key = userBridge.askForUserInput("Please enter your DeepL API key - https://www.deepl.com/pro-api? (It will be stored unencrypted)");
|
||||
if (key == null) {
|
||||
throw new SetupException("API key cannot be null");
|
||||
} else {
|
||||
preferences.put(DEEPL_API_KEY, key);
|
||||
try {
|
||||
preferences.flush();
|
||||
} catch (final BackingStoreException e) {
|
||||
throw new SetupException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uninstall() throws SetupException {
|
||||
preferences.remove(DEEPL_API_KEY);
|
||||
try {
|
||||
preferences.flush();
|
||||
} catch (final BackingStoreException e) {
|
||||
throw new SetupException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() throws SetupException {
|
||||
//No need to update
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.github.gtache.autosubtitle.translation.deepl;
|
||||
|
||||
import com.deepl.api.DeepLException;
|
||||
import com.github.gtache.autosubtitle.Language;
|
||||
import com.github.gtache.autosubtitle.subtitle.Subtitle;
|
||||
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
|
||||
import com.github.gtache.autosubtitle.subtitle.impl.SubtitleCollectionImpl;
|
||||
import com.github.gtache.autosubtitle.subtitle.impl.SubtitleImpl;
|
||||
import com.github.gtache.autosubtitle.translation.TranslationException;
|
||||
import com.github.gtache.autosubtitle.translation.Translator;
|
||||
import com.github.pemistahl.lingua.api.LanguageDetector;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Objects;
|
||||
import java.util.prefs.Preferences;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* DeepL implementation of {@link Translator}
|
||||
*/
|
||||
public class DeepLTranslator implements Translator<Subtitle> {
|
||||
|
||||
private final Preferences preferences;
|
||||
private final LanguageDetector languageDetector;
|
||||
private com.deepl.api.Translator translator;
|
||||
|
||||
@Inject
|
||||
DeepLTranslator(final Preferences preferences, final LanguageDetector languageDetector) {
|
||||
this.preferences = Objects.requireNonNull(preferences);
|
||||
this.languageDetector = Objects.requireNonNull(languageDetector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Language getLanguage(final String text) {
|
||||
return Language.getLanguage(languageDetector.detectLanguageOf(text).getIsoCode639_1().toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String translate(final String text, final Language from, final Language to) throws TranslationException {
|
||||
final var currentTranslator = getTranslator();
|
||||
if (currentTranslator == null) {
|
||||
throw new TranslationException("DeepL API key is not set");
|
||||
}
|
||||
try {
|
||||
final var translated = currentTranslator.translateText(text, from.iso2(), to.iso2());
|
||||
return translated.getText();
|
||||
} catch (final DeepLException e) {
|
||||
throw new TranslationException(e);
|
||||
} catch (final InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new TranslationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Subtitle translate(final Subtitle subtitle, final Language from, final Language to) throws TranslationException {
|
||||
final var translated = translate(subtitle.content(), from, to);
|
||||
return new SubtitleImpl(translated, subtitle.start(), subtitle.end(), subtitle.font(), subtitle.bounds());
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubtitleCollection<Subtitle> translate(final SubtitleCollection<?> collection, final Language from, final Language to) throws TranslationException {
|
||||
final var subtitles = new ArrayList<Subtitle>(collection.subtitles().size());
|
||||
for (final var subtitle : collection.subtitles()) {
|
||||
subtitles.add(translate(subtitle, from, to));
|
||||
}
|
||||
final var text = subtitles.stream().map(Subtitle::content).collect(Collectors.joining(""));
|
||||
return new SubtitleCollectionImpl<>(text, subtitles, to);
|
||||
}
|
||||
|
||||
private com.deepl.api.Translator getTranslator() {
|
||||
if (translator == null && preferences.get("deepl.api.key", null) != null) {
|
||||
translator = new com.deepl.api.Translator(preferences.get("deepl.api.key", null));
|
||||
}
|
||||
return translator;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,10 @@
|
||||
*/
|
||||
module com.github.gtache.autosubtitle.deepl {
|
||||
requires transitive com.github.gtache.autosubtitle.core;
|
||||
exports com.github.gtache.autosubtitle.deepl;
|
||||
requires transitive deepl.java;
|
||||
requires transitive com.github.pemistahl.lingua;
|
||||
requires transitive java.prefs;
|
||||
exports com.github.gtache.autosubtitle.modules.deepl;
|
||||
exports com.github.gtache.autosubtitle.translation.deepl;
|
||||
exports com.github.gtache.autosubtitle.setup.deepl;
|
||||
}
|
||||
@@ -64,4 +64,12 @@ public interface ParametersModel {
|
||||
* @param fontSize The new font size
|
||||
*/
|
||||
void setFontSize(int fontSize);
|
||||
|
||||
int maxLineLength();
|
||||
|
||||
void setMaxLineLength(int maxLineLength);
|
||||
|
||||
int maxLines();
|
||||
|
||||
void setMaxLines(int maxLines);
|
||||
}
|
||||
|
||||
@@ -3,4 +3,6 @@ parameters.button.save.label=Save
|
||||
parameters.extraction.model.label=Model to use for subtitle extraction
|
||||
parameters.subtitles.font.family=Default subtitle font name
|
||||
parameters.subtitles.font.size=Default subtitle font size
|
||||
parameters.subtitles.max.length.label=Max length of a subtitles line (characters)
|
||||
parameters.subtitles.max.lines.label=Max subtitles lines
|
||||
parameters.subtitles.output.format=Output format for the subtitles
|
||||
@@ -3,4 +3,5 @@ parameters.button.save.label=Sauvegarder
|
||||
parameters.extraction.model.label=Mod\u00E8le utilis\u00E9 pour l'extraction des sous-titres
|
||||
parameters.subtitles.font.family=Police par d\u00E9faut pour les sous-titres
|
||||
parameters.subtitles.font.size=Taille de la police par d\u00E9faut pour les sous-titres
|
||||
parameters.subtitles.max.length.label=Taille maximale d'une ligne de sous-titres (caract\u00E8res)
|
||||
parameters.subtitles.output.format=Format de sortie pour les sous-titres
|
||||
@@ -12,7 +12,7 @@ public abstract class AbstractFXController {
|
||||
/**
|
||||
* @return the current window
|
||||
*/
|
||||
protected abstract Window window();
|
||||
public abstract Window window();
|
||||
|
||||
/**
|
||||
* Show an error dialog
|
||||
|
||||
@@ -42,7 +42,7 @@ public class FXMainController extends AbstractFXController implements MainContro
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Window window() {
|
||||
public Window window() {
|
||||
return tabPane.getScene().getWindow();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,10 @@ public class FXParametersController extends AbstractFXController implements Para
|
||||
private PrefixSelectionComboBox<String> fontFamilyCombobox;
|
||||
@FXML
|
||||
private TextField fontSizeField;
|
||||
@FXML
|
||||
private TextField maxLengthField;
|
||||
@FXML
|
||||
private TextField maxLinesField;
|
||||
|
||||
private final FXParametersModel model;
|
||||
private final Preferences preferences;
|
||||
@@ -68,8 +72,12 @@ public class FXParametersController extends AbstractFXController implements Para
|
||||
return null;
|
||||
};
|
||||
fontSizeField.setTextFormatter(new TextFormatter<>(new IntegerStringConverter(), 0, integerFilter));
|
||||
maxLengthField.setTextFormatter(new TextFormatter<>(new IntegerStringConverter(), 0, integerFilter));
|
||||
maxLinesField.setTextFormatter(new TextFormatter<>(new IntegerStringConverter(), 0, integerFilter));
|
||||
|
||||
fontSizeField.textProperty().bindBidirectional(model.fontSizeProperty(), new NumberStringConverter());
|
||||
maxLengthField.textProperty().bindBidirectional(model.maxLineLengthProperty(), new NumberStringConverter());
|
||||
maxLinesField.textProperty().bindBidirectional(model.maxLinesProperty(), new NumberStringConverter());
|
||||
|
||||
loadPreferences();
|
||||
}
|
||||
@@ -79,11 +87,15 @@ public class FXParametersController extends AbstractFXController implements Para
|
||||
final var outputFormat = preferences.get("outputFormat", model.outputFormat().name());
|
||||
final var fontFamily = preferences.get("fontFamily", model.fontFamily());
|
||||
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.setFontSize(fontSize);
|
||||
model.setMaxLineLength(maxLineLength);
|
||||
model.setMaxLines(maxLines);
|
||||
logger.info("Loaded preferences");
|
||||
}
|
||||
|
||||
@@ -95,6 +107,8 @@ public class FXParametersController extends AbstractFXController implements Para
|
||||
preferences.put("outputFormat", model.outputFormat().name());
|
||||
preferences.put("fontFamily", model.fontFamily());
|
||||
preferences.putInt("fontSize", model.fontSize());
|
||||
preferences.putInt("maxLineLength", model.maxLineLength());
|
||||
preferences.putInt("maxLines", model.maxLines());
|
||||
try {
|
||||
preferences.flush();
|
||||
logger.info("Preferences saved");
|
||||
@@ -124,7 +138,7 @@ public class FXParametersController extends AbstractFXController implements Para
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Window window() {
|
||||
public Window window() {
|
||||
return extractionModelCombobox.getScene().getWindow();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,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.MaxLineLength;
|
||||
import com.github.gtache.autosubtitle.modules.impl.MaxLines;
|
||||
import com.github.gtache.autosubtitle.subtitle.OutputFormat;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.ExtractionModel;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.ExtractionModelProvider;
|
||||
@@ -31,9 +33,12 @@ public class FXParametersModel implements ParametersModel {
|
||||
private final ObservableList<String> availableFontFamilies;
|
||||
private final StringProperty fontFamily;
|
||||
private final IntegerProperty fontSize;
|
||||
private final IntegerProperty maxLineLength;
|
||||
private final IntegerProperty maxLines;
|
||||
|
||||
@Inject
|
||||
FXParametersModel(final ExtractionModelProvider extractionModelProvider, @FontFamily final String defaultFontFamily, @FontSize final int defaultFontSize) {
|
||||
FXParametersModel(final ExtractionModelProvider extractionModelProvider, @FontFamily 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));
|
||||
@@ -41,6 +46,8 @@ public class FXParametersModel implements ParametersModel {
|
||||
this.availableFontFamilies = FXCollections.unmodifiableObservableList(FXCollections.observableArrayList("Arial"));
|
||||
this.fontFamily = new SimpleStringProperty(defaultFontFamily);
|
||||
this.fontSize = new SimpleIntegerProperty(defaultFontSize);
|
||||
this.maxLineLength = new SimpleIntegerProperty(defaultMaxLineLength);
|
||||
this.maxLines = new SimpleIntegerProperty(defaultMaxLines);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -113,4 +120,32 @@ public class FXParametersModel implements ParametersModel {
|
||||
IntegerProperty fontSizeProperty() {
|
||||
return fontSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int maxLineLength() {
|
||||
return maxLineLength.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxLineLength(final int maxLineLength) {
|
||||
this.maxLineLength.set(maxLineLength);
|
||||
}
|
||||
|
||||
IntegerProperty maxLineLengthProperty() {
|
||||
return maxLineLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int maxLines() {
|
||||
return maxLines.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxLines(final int maxLines) {
|
||||
this.maxLines.set(maxLines);
|
||||
}
|
||||
|
||||
IntegerProperty maxLinesProperty() {
|
||||
return maxLines;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,7 +314,7 @@ public class FXSetupController extends AbstractFXController implements SetupCont
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Window window() {
|
||||
public Window window() {
|
||||
return converterNameLabel.getScene().getWindow();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.github.gtache.autosubtitle.gui.fx;
|
||||
|
||||
import com.github.gtache.autosubtitle.Language;
|
||||
import com.github.gtache.autosubtitle.Translator;
|
||||
import com.github.gtache.autosubtitle.gui.SubtitlesController;
|
||||
import com.github.gtache.autosubtitle.gui.TimeFormatter;
|
||||
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
|
||||
@@ -9,6 +8,8 @@ import com.github.gtache.autosubtitle.subtitle.SubtitleImporterExporter;
|
||||
import com.github.gtache.autosubtitle.subtitle.converter.ParseException;
|
||||
import com.github.gtache.autosubtitle.subtitle.gui.fx.ObservableSubtitleCollectionImpl;
|
||||
import com.github.gtache.autosubtitle.subtitle.gui.fx.ObservableSubtitleImpl;
|
||||
import com.github.gtache.autosubtitle.translation.TranslationException;
|
||||
import com.github.gtache.autosubtitle.translation.Translator;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
@@ -39,6 +40,7 @@ import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
@@ -129,19 +131,30 @@ public class FXSubtitlesController extends AbstractFXController implements Subti
|
||||
}
|
||||
});
|
||||
|
||||
translationsCombobox.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue != null && !model.collections().containsKey(newValue)) {
|
||||
translationsCombobox.setOnAction(e -> {
|
||||
final var value = translationsCombobox.getValue();
|
||||
if (value != null && !model.collections().containsKey(value)) {
|
||||
model.setTranslating(true);
|
||||
CompletableFuture.supplyAsync(() -> translator.translate(model.collections().get(model.videoLanguage()), newValue))
|
||||
.whenCompleteAsync((r, t) -> {
|
||||
if (t == null) {
|
||||
loadCollection(r);
|
||||
model.setSelectedCollection(model.collections().get(newValue));
|
||||
} else {
|
||||
logger.error("Error while translating to {}", newValue, t);
|
||||
}
|
||||
model.setTranslating(false);
|
||||
}, Platform::runLater);
|
||||
CompletableFuture.supplyAsync(() -> {
|
||||
final var mainCollection = model.collections().get(model.videoLanguage());
|
||||
try {
|
||||
if (mainCollection == null) {
|
||||
return translator.translate(model.selectedCollection(), value);
|
||||
} else {
|
||||
return translator.translate(mainCollection, value);
|
||||
}
|
||||
} catch (final TranslationException ex) {
|
||||
throw new CompletionException(ex);
|
||||
}
|
||||
}).whenCompleteAsync((r, t) -> {
|
||||
if (t == null) {
|
||||
loadCollection(r);
|
||||
model.setSelectedCollection(model.collections().get(value));
|
||||
} else {
|
||||
logger.error("Error while translating to {}", value, t);
|
||||
}
|
||||
model.setTranslating(false);
|
||||
}, Platform::runLater);
|
||||
}
|
||||
});
|
||||
binder.createBindings();
|
||||
@@ -197,7 +210,7 @@ public class FXSubtitlesController extends AbstractFXController implements Subti
|
||||
final var toRemove = new ArrayList<Tab>();
|
||||
final var toAdd = new ArrayList<Tab>();
|
||||
tabPane.getTabs().forEach(tab -> {
|
||||
if (!model.collections().containsKey(Language.getLanguage(tab.getText()))) {
|
||||
if (tab != mainSubtitlesTab && !model.collections().containsKey(Language.getLanguage(tab.getText()))) {
|
||||
toRemove.add(tab);
|
||||
}
|
||||
});
|
||||
@@ -237,7 +250,9 @@ public class FXSubtitlesController extends AbstractFXController implements Subti
|
||||
filePicker.getExtensionFilters().addAll(archiveFilter, allSupportedFilter);
|
||||
filePicker.setSelectedExtensionFilter(allSupportedFilter);
|
||||
final var file = filePicker.showOpenDialog(window());
|
||||
loadSubtitles(file.toPath());
|
||||
if (file != null) {
|
||||
loadSubtitles(file.toPath());
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -283,10 +298,11 @@ public class FXSubtitlesController extends AbstractFXController implements Subti
|
||||
public void loadSubtitles(final Path file) {
|
||||
try {
|
||||
final var map = importerExporter.importSubtitles(file);
|
||||
map.values().forEach(this::loadCollection);
|
||||
if (model.videoLanguage() == Language.AUTO) {
|
||||
model.setVideoLanguage(map.keySet().stream().findFirst().orElse(Language.AUTO));
|
||||
}
|
||||
map.values().forEach(this::loadCollection);
|
||||
model.setSelectedCollection(model.collections().get(map.keySet().iterator().next()));
|
||||
} catch (final IOException | ParseException e) {
|
||||
logger.error("Error loading subtitles {}", file, e);
|
||||
showErrorDialog(resources.getString("subtitles.load.error.title"), MessageFormat.format(resources.getString("subtitles.load.error.label"), file));
|
||||
@@ -316,7 +332,7 @@ public class FXSubtitlesController extends AbstractFXController implements Subti
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Window window() {
|
||||
public Window window() {
|
||||
return saveButton.getScene().getWindow();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.github.gtache.autosubtitle.gui.fx.FXSubtitlesController;
|
||||
import com.github.gtache.autosubtitle.gui.fx.FXWorkController;
|
||||
import com.github.gtache.autosubtitle.modules.gui.impl.Pause;
|
||||
import com.github.gtache.autosubtitle.modules.gui.impl.Play;
|
||||
import com.github.gtache.autosubtitle.modules.setup.gui.fx.FXSetupModule;
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
@@ -19,12 +20,11 @@ import javafx.scene.image.Image;
|
||||
import javax.inject.Singleton;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
/**
|
||||
* Dagger module for FX
|
||||
*/
|
||||
@Module
|
||||
@Module(includes = FXSetupModule.class)
|
||||
public abstract class FXModule {
|
||||
|
||||
@Binds
|
||||
@@ -69,10 +69,4 @@ public abstract class FXModule {
|
||||
static Image providesPauseImage(@Pause final byte[] pauseImage) {
|
||||
return new Image(new ByteArrayInputStream(pauseImage));
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static Preferences providesPreferences() {
|
||||
return Preferences.userNodeForPackage(FXParametersController.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.github.gtache.autosubtitle.modules.setup.gui.fx;
|
||||
|
||||
import com.github.gtache.autosubtitle.setup.SetupUserBridge;
|
||||
import com.github.gtache.autosubtitle.setup.gui.fx.FXSetupUserBridge;
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
|
||||
/**
|
||||
* Dagger module for FX setup
|
||||
*/
|
||||
@Module
|
||||
public abstract class FXSetupModule {
|
||||
|
||||
private FXSetupModule() {
|
||||
}
|
||||
|
||||
@Binds
|
||||
abstract SetupUserBridge bindsSetupUserBridge(final FXSetupUserBridge setupUserBridge);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.github.gtache.autosubtitle.setup.gui.fx;
|
||||
|
||||
import com.github.gtache.autosubtitle.gui.fx.FXMainController;
|
||||
import com.github.gtache.autosubtitle.setup.SetupUserBridge;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.ChoiceDialog;
|
||||
import javafx.scene.control.TextInputDialog;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* FX implementation of {@link SetupUserBridge}
|
||||
*/
|
||||
public class FXSetupUserBridge implements SetupUserBridge {
|
||||
|
||||
private final FXMainController controller;
|
||||
|
||||
@Inject
|
||||
FXSetupUserBridge(final FXMainController mainController) {
|
||||
this.controller = Objects.requireNonNull(mainController);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean askForUserConfirmation(final String question) {
|
||||
return showConfirmationDialog("Confirmation", question);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T askForUserChoice(final String question, final List<T> choices) {
|
||||
return showChoiceDialog("Choice", question, choices);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String askForUserInput(final String question) {
|
||||
return showInputDialog("Input", question);
|
||||
}
|
||||
|
||||
private <T> T showChoiceDialog(final String title, final String message, final List<T> choices) {
|
||||
final var dialog = new ChoiceDialog<>(choices.getFirst(), choices);
|
||||
dialog.initOwner(controller.window());
|
||||
dialog.setHeaderText(message);
|
||||
dialog.setTitle(title);
|
||||
return dialog.showAndWait().orElse(null);
|
||||
}
|
||||
|
||||
private String showInputDialog(final String title, final String message) {
|
||||
final var dialog = new TextInputDialog();
|
||||
dialog.initOwner(controller.window());
|
||||
dialog.setHeaderText(message);
|
||||
dialog.setTitle(title);
|
||||
return dialog.showAndWait().orElse(null);
|
||||
}
|
||||
|
||||
private boolean showConfirmationDialog(final String title, final String message) {
|
||||
final var alert = new Alert(Alert.AlertType.CONFIRMATION, message, ButtonType.YES, ButtonType.NO);
|
||||
alert.initOwner(controller.window());
|
||||
alert.setHeaderText(null);
|
||||
alert.setTitle(title);
|
||||
return alert.showAndWait().map(bt -> bt == ButtonType.YES).orElse(false);
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,8 @@ module com.github.gtache.autosubtitle.gui.fx {
|
||||
requires transitive java.prefs;
|
||||
|
||||
exports com.github.gtache.autosubtitle.gui.fx;
|
||||
exports com.github.gtache.autosubtitle.modules.gui.fx;
|
||||
exports com.github.gtache.autosubtitle.setup.gui.fx;
|
||||
exports com.github.gtache.autosubtitle.subtitle.gui.fx;
|
||||
exports com.github.gtache.autosubtitle.modules.gui.fx;
|
||||
opens com.github.gtache.autosubtitle.gui.fx to javafx.fxml;
|
||||
}
|
||||
@@ -4,37 +4,42 @@
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.TextField?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import javafx.scene.layout.ColumnConstraints?>
|
||||
<?import javafx.scene.layout.GridPane?>
|
||||
<?import javafx.scene.layout.RowConstraints?>
|
||||
<?import org.controlsfx.control.PrefixSelectionComboBox?>
|
||||
<GridPane hgap="10.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" vgap="10.0"
|
||||
xmlns="http://javafx.com/javafx/22" xmlns:fx="http://javafx.com/fxml/1"
|
||||
fx:controller="com.github.gtache.autosubtitle.gui.fx.FXParametersController">
|
||||
|
||||
<GridPane hgap="10.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" vgap="10.0" xmlns="http://javafx.com/javafx/22" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.github.gtache.autosubtitle.gui.fx.FXParametersController">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="SOMETIMES"/>
|
||||
<ColumnConstraints hgrow="SOMETIMES"/>
|
||||
<ColumnConstraints hgrow="SOMETIMES" />
|
||||
<ColumnConstraints hgrow="SOMETIMES" />
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
<RowConstraints vgrow="SOMETIMES"/>
|
||||
<RowConstraints vgrow="SOMETIMES"/>
|
||||
<RowConstraints vgrow="SOMETIMES"/>
|
||||
<RowConstraints vgrow="SOMETIMES"/>
|
||||
<RowConstraints vgrow="SOMETIMES"/>
|
||||
<RowConstraints vgrow="SOMETIMES" />
|
||||
<RowConstraints vgrow="SOMETIMES" />
|
||||
<RowConstraints vgrow="SOMETIMES" />
|
||||
<RowConstraints vgrow="SOMETIMES" />
|
||||
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints vgrow="SOMETIMES" />
|
||||
</rowConstraints>
|
||||
<children>
|
||||
<Label text="%parameters.extraction.model.label"/>
|
||||
<PrefixSelectionComboBox fx:id="extractionModelCombobox" GridPane.columnIndex="1"/>
|
||||
<PrefixSelectionComboBox fx:id="extractionOutputFormat" GridPane.columnIndex="1" GridPane.rowIndex="1"/>
|
||||
<Label text="%parameters.subtitles.output.format" GridPane.rowIndex="1"/>
|
||||
<Label text="%parameters.subtitles.font.size" GridPane.rowIndex="3"/>
|
||||
<Label text="%parameters.subtitles.font.family" GridPane.rowIndex="2"/>
|
||||
<PrefixSelectionComboBox fx:id="fontFamilyCombobox" GridPane.columnIndex="1" GridPane.rowIndex="2"/>
|
||||
<TextField fx:id="fontSizeField" GridPane.columnIndex="1" GridPane.rowIndex="3"/>
|
||||
<Button mnemonicParsing="false" onAction="#savePressed" text="%parameters.button.save.label"
|
||||
GridPane.columnIndex="1" GridPane.rowIndex="4"/>
|
||||
<Button mnemonicParsing="false" onAction="#resetPressed" text="%parameters.button.reset.label"
|
||||
GridPane.rowIndex="4"/>
|
||||
<Label text="%parameters.extraction.model.label" />
|
||||
<PrefixSelectionComboBox fx:id="extractionModelCombobox" GridPane.columnIndex="1" />
|
||||
<PrefixSelectionComboBox fx:id="extractionOutputFormat" GridPane.columnIndex="1" GridPane.rowIndex="1" />
|
||||
<Label text="%parameters.subtitles.output.format" GridPane.rowIndex="1" />
|
||||
<Label text="%parameters.subtitles.font.size" GridPane.rowIndex="3" />
|
||||
<Label text="%parameters.subtitles.font.family" GridPane.rowIndex="2" />
|
||||
<PrefixSelectionComboBox fx:id="fontFamilyCombobox" GridPane.columnIndex="1" GridPane.rowIndex="2" />
|
||||
<TextField fx:id="fontSizeField" GridPane.columnIndex="1" GridPane.rowIndex="3" />
|
||||
<Button mnemonicParsing="false" onAction="#savePressed" text="%parameters.button.save.label" GridPane.columnIndex="1" GridPane.rowIndex="6" />
|
||||
<Button mnemonicParsing="false" onAction="#resetPressed" text="%parameters.button.reset.label" GridPane.rowIndex="6" />
|
||||
<Label text="%parameters.subtitles.max.length.label" GridPane.rowIndex="4" />
|
||||
<Label text="%parameters.subtitles.max.lines.label" GridPane.rowIndex="5" />
|
||||
<TextField fx:id="maxLengthField" GridPane.columnIndex="1" GridPane.rowIndex="4" />
|
||||
<TextField fx:id="maxLinesField" GridPane.columnIndex="1" GridPane.rowIndex="5" />
|
||||
</children>
|
||||
<padding>
|
||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
|
||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||
</padding>
|
||||
</GridPane>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.github.gtache.autosubtitle.gui.fx;
|
||||
|
||||
import com.github.gtache.autosubtitle.Translator;
|
||||
import com.github.gtache.autosubtitle.VideoConverter;
|
||||
import com.github.gtache.autosubtitle.VideoLoader;
|
||||
import com.github.gtache.autosubtitle.gui.TimeFormatter;
|
||||
@@ -8,6 +7,7 @@ import com.github.gtache.autosubtitle.modules.gui.fx.DaggerResourceComponent;
|
||||
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.ExtractionModelProvider;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.SubtitleExtractor;
|
||||
import com.github.gtache.autosubtitle.translation.Translator;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
@@ -51,7 +51,7 @@ class TestFXParametersController extends FxRobot {
|
||||
this.videoConverter = requireNonNull(videoConverter);
|
||||
this.translator = requireNonNull(translator);
|
||||
this.timeFormatter = requireNonNull(timeFormatter);
|
||||
this.model = spy(new FXParametersModel(mock(ExtractionModelProvider.class), "Arial", 12));
|
||||
this.model = spy(new FXParametersModel(mock(ExtractionModelProvider.class), "Arial", 12, 40, 1));
|
||||
this.binder = requireNonNull(binder);
|
||||
this.window = FxToolkit.registerPrimaryStage();
|
||||
this.controller = spy(FXParametersController.class);
|
||||
|
||||
@@ -21,6 +21,8 @@ class TestFXParametersModel {
|
||||
|
||||
private static final String DEFAULT_FONT_FAMILY = "Arial";
|
||||
private static final int DEFAULT_FONT_SIZE = 12;
|
||||
private static final int DEFAULT_MAX_LINE_LENGTH = 40;
|
||||
private static final int DEFAULT_MAX_LINES = 2;
|
||||
private final List<ExtractionModel> availableExtractionModels;
|
||||
private final ExtractionModel defaultExtractionModel;
|
||||
private final ExtractionModelProvider provider;
|
||||
@@ -34,7 +36,7 @@ class TestFXParametersModel {
|
||||
when(provider.getDefaultExtractionModel()).thenReturn(defaultExtractionModel);
|
||||
this.availableExtractionModels = List.of(defaultExtractionModel, extractionModel);
|
||||
when(provider.getAvailableExtractionModels()).thenReturn(availableExtractionModels);
|
||||
model = new FXParametersModel(extractionModelProvider, DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE);
|
||||
model = new FXParametersModel(extractionModelProvider, DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, DEFAULT_MAX_LINE_LENGTH, DEFAULT_MAX_LINES);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package com.github.gtache.autosubtitle.gui.fx;
|
||||
|
||||
import com.github.gtache.autosubtitle.Translator;
|
||||
import com.github.gtache.autosubtitle.VideoConverter;
|
||||
import com.github.gtache.autosubtitle.VideoLoader;
|
||||
import com.github.gtache.autosubtitle.gui.TimeFormatter;
|
||||
import com.github.gtache.autosubtitle.modules.gui.fx.DaggerResourceComponent;
|
||||
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.SubtitleExtractor;
|
||||
import com.github.gtache.autosubtitle.translation.Translator;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package com.github.gtache.autosubtitle.gui.fx;
|
||||
|
||||
import com.github.gtache.autosubtitle.Translator;
|
||||
import com.github.gtache.autosubtitle.VideoConverter;
|
||||
import com.github.gtache.autosubtitle.VideoLoader;
|
||||
import com.github.gtache.autosubtitle.gui.TimeFormatter;
|
||||
import com.github.gtache.autosubtitle.modules.gui.fx.DaggerResourceComponent;
|
||||
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.SubtitleExtractor;
|
||||
import com.github.gtache.autosubtitle.translation.Translator;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
|
||||
@@ -12,7 +12,7 @@ class TestFXWorkBinder {
|
||||
@Test
|
||||
void testBindings() {
|
||||
final var workModel = new FXWorkModel();
|
||||
final var parametersModel = new FXParametersModel(mock(ExtractionModelProvider.class), "Arial", 12);
|
||||
final var parametersModel = new FXParametersModel(mock(ExtractionModelProvider.class), "Arial", 12, 40, 1);
|
||||
final var binder = new FXWorkBinder(workModel, parametersModel);
|
||||
binder.createBindings();
|
||||
assertNull(workModel.extractionModel());
|
||||
|
||||
@@ -11,7 +11,6 @@ import javafx.scene.image.Image;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -36,8 +35,4 @@ class TestFXModule {
|
||||
assertInstanceOf(Image.class, FXModule.providesPauseImage(IMAGE));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPreferences() {
|
||||
assertInstanceOf(Preferences.class, FXModule.providesPreferences());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
<artifactId>autosubtitle-gui-run</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.github.gtache.autosubtitle</groupId>
|
||||
<artifactId>autosubtitle-deepl</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.gtache.autosubtitle</groupId>
|
||||
<artifactId>autosubtitle-ffmpeg</artifactId>
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
package com.github.gtache.autosubtitle.modules.gui.run;
|
||||
|
||||
import com.github.gtache.autosubtitle.Language;
|
||||
import com.github.gtache.autosubtitle.Translator;
|
||||
import com.github.gtache.autosubtitle.modules.setup.impl.TranslatorSetup;
|
||||
import com.github.gtache.autosubtitle.setup.SetupManager;
|
||||
import com.github.gtache.autosubtitle.subtitle.Subtitle;
|
||||
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
/**
|
||||
* Module for missing components
|
||||
*/
|
||||
@Module
|
||||
public abstract class MissingComponentsModule {
|
||||
|
||||
private MissingComponentsModule() {
|
||||
|
||||
}
|
||||
|
||||
@Provides
|
||||
static Translator providesTranslator() {
|
||||
return new Translator<>() {
|
||||
@Override
|
||||
public Language getLanguage(final String text) {
|
||||
return Language.getDefault();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String translate(final String text, final Language to) {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Subtitle translate(final Subtitle subtitle, final Language to) {
|
||||
return subtitle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubtitleCollection<Subtitle> translate(final SubtitleCollection<?> collection, final Language to) {
|
||||
return (SubtitleCollection<Subtitle>) collection;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Provides
|
||||
@TranslatorSetup
|
||||
static SetupManager providesTranslatorSetupManager() {
|
||||
return new NoOpSetupManager();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package com.github.gtache.autosubtitle.modules.gui.run;
|
||||
|
||||
import com.github.gtache.autosubtitle.setup.SetupException;
|
||||
import com.github.gtache.autosubtitle.setup.SetupListener;
|
||||
import com.github.gtache.autosubtitle.setup.SetupManager;
|
||||
import com.github.gtache.autosubtitle.setup.SetupStatus;
|
||||
|
||||
class NoOpSetupManager implements SetupManager {
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "NoOpSetupManager";
|
||||
}
|
||||
|
||||
@Override
|
||||
public SetupStatus status() {
|
||||
return SetupStatus.NOT_INSTALLED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInstalled() throws SetupException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void install() throws SetupException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uninstall() throws SetupException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reinstall() throws SetupException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUpdateAvailable() throws SetupException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() throws SetupException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(final SetupListener listener) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(final SetupListener listener) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListeners() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.github.gtache.autosubtitle.modules.gui.run;
|
||||
|
||||
import com.github.gtache.autosubtitle.modules.deepl.DeepLModule;
|
||||
import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegModule;
|
||||
import com.github.gtache.autosubtitle.modules.gui.fx.FXModule;
|
||||
import com.github.gtache.autosubtitle.modules.gui.impl.GuiCoreModule;
|
||||
@@ -15,7 +16,7 @@ import javax.inject.Singleton;
|
||||
*/
|
||||
@Singleton
|
||||
@Component(modules = {CoreModule.class, GuiCoreModule.class, FXModule.class, FFmpegModule.class,
|
||||
WhisperXModule.class, MissingComponentsModule.class})
|
||||
WhisperXModule.class, DeepLModule.class})
|
||||
public interface RunComponent {
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Main module for auto-subtitle
|
||||
*/
|
||||
module com.github.gtache.autosubtitle.run {
|
||||
module com.github.gtache.autosubtitle.gui.run {
|
||||
requires com.github.gtache.autosubtitle.deepl;
|
||||
requires com.github.gtache.autosubtitle.ffmpeg;
|
||||
requires com.github.gtache.autosubtitle.gui.fx;
|
||||
requires com.github.gtache.autosubtitle.whisperx;
|
||||
|
||||
@@ -48,8 +48,8 @@ public class JSONSubtitleConverter implements SubtitleConverter<SubtitleImpl> {
|
||||
try {
|
||||
final var json = gson.fromJson(content, JSONSubtitles.class);
|
||||
final var subtitles = json.segments().stream().map(s -> {
|
||||
final var start = (long) s.start() * 1000L;
|
||||
final var end = (long) s.end() * 1000L;
|
||||
final var start = (long) (s.start() * 1000L);
|
||||
final var end = (long) (s.end() * 1000L);
|
||||
return new SubtitleImpl(s.text(), start, end, null, null);
|
||||
}).sorted(Comparator.comparing(Subtitle::start).thenComparing(Subtitle::end)).toList();
|
||||
final var language = Language.getLanguage(json.language());
|
||||
|
||||
@@ -55,10 +55,8 @@ public class WhisperXSubtitleExtractor extends AbstractWhisperSubtitleExtractor
|
||||
args.add("True");
|
||||
args.add("--compute_type");
|
||||
args.add("int8");
|
||||
args.add("--max_line_count");
|
||||
args.add("2");
|
||||
args.add("--max_line_width");
|
||||
args.add("30");
|
||||
args.add("--threads");
|
||||
args.add(String.valueOf(Runtime.getRuntime().availableProcessors()));
|
||||
if (language != Language.AUTO) {
|
||||
args.add("--language");
|
||||
args.add(language.iso2());
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.github.gtache.autosubtitle.subtitle.parser.json.whisperx;
|
||||
|
||||
import com.github.gtache.autosubtitle.Language;
|
||||
import com.github.gtache.autosubtitle.modules.impl.MaxLineLength;
|
||||
import com.github.gtache.autosubtitle.modules.impl.MaxLines;
|
||||
import com.github.gtache.autosubtitle.subtitle.Subtitle;
|
||||
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
|
||||
import com.github.gtache.autosubtitle.subtitle.converter.ParseException;
|
||||
@@ -12,10 +14,14 @@ import com.google.gson.Gson;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.prefs.Preferences;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* {@link SubtitleConverter} implementation for JSON files
|
||||
@@ -23,11 +29,21 @@ import java.util.stream.Collectors;
|
||||
@Singleton
|
||||
public class JSONSubtitleConverter implements SubtitleConverter<SubtitleImpl> {
|
||||
|
||||
private static final String MAX_LINE_LENGTH = "maxLineLength";
|
||||
private static final String MAX_LINES = "maxLines";
|
||||
private static final Pattern SPLIT_PATTERN = Pattern.compile("[ \n]+");
|
||||
|
||||
private final Gson gson;
|
||||
private final Preferences preferences;
|
||||
private final int defaultMaxLineLength;
|
||||
private final int defaultMaxLines;
|
||||
|
||||
@Inject
|
||||
JSONSubtitleConverter(final Gson gson) {
|
||||
JSONSubtitleConverter(final Gson gson, final Preferences preferences, @MaxLineLength final int defaultMaxLineLength, @MaxLines final int defaultMaxLines) {
|
||||
this.gson = Objects.requireNonNull(gson);
|
||||
this.preferences = Objects.requireNonNull(preferences);
|
||||
this.defaultMaxLineLength = defaultMaxLineLength;
|
||||
this.defaultMaxLines = defaultMaxLines;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -42,10 +58,15 @@ public class JSONSubtitleConverter implements SubtitleConverter<SubtitleImpl> {
|
||||
public SubtitleCollectionImpl<SubtitleImpl> parse(final String content) throws ParseException {
|
||||
try {
|
||||
final var json = gson.fromJson(content, JSONSubtitles.class);
|
||||
final var subtitles = json.segments().stream().map(s -> {
|
||||
final var start = (long) s.start() * 1000L;
|
||||
final var end = (long) s.end() * 1000L;
|
||||
return new SubtitleImpl(s.text(), start, end, null, null);
|
||||
final var subtitles = json.segments().stream().flatMap(s -> {
|
||||
final var start = (long) (s.start() * 1000L);
|
||||
final var end = (long) (s.end() * 1000L);
|
||||
if (s.words().isEmpty()) {
|
||||
return Stream.of(new SubtitleImpl(s.text(), start, end, null, null));
|
||||
} else {
|
||||
return splitSubtitle(s);
|
||||
|
||||
}
|
||||
}).sorted(Comparator.comparing(Subtitle::start).thenComparing(Subtitle::end)).toList();
|
||||
final var language = Language.getLanguage(json.language());
|
||||
final var subtitlesText = subtitles.stream().map(Subtitle::content).collect(Collectors.joining(""));
|
||||
@@ -55,6 +76,79 @@ public class JSONSubtitleConverter implements SubtitleConverter<SubtitleImpl> {
|
||||
}
|
||||
}
|
||||
|
||||
private Stream<SubtitleImpl> splitSubtitle(final JSONSubtitleSegment segment) {
|
||||
final var maxLineLength = preferences.getInt(MAX_LINE_LENGTH, defaultMaxLineLength);
|
||||
final var maxLines = preferences.getInt(MAX_LINES, defaultMaxLines);
|
||||
final var text = segment.text();
|
||||
if (text.length() <= maxLineLength) {
|
||||
final var start = (long) (segment.start() * 1000L);
|
||||
final var end = (long) (segment.end() * 1000L);
|
||||
return Stream.of(new SubtitleImpl(text.replace("\n", " "), start, end, null, null));
|
||||
} else if (text.length() <= maxLines * maxLineLength) {
|
||||
return splitSubtitleLines(segment);
|
||||
} else {
|
||||
return splitSubtitleWords(segment);
|
||||
}
|
||||
}
|
||||
|
||||
private Stream<SubtitleImpl> splitSubtitleLines(final JSONSubtitleSegment segment) {
|
||||
final var maxLineLength = preferences.getInt(MAX_LINE_LENGTH, defaultMaxLineLength);
|
||||
final var text = segment.text();
|
||||
final var split = SPLIT_PATTERN.split(text);
|
||||
final var builder = new StringBuilder(text.length());
|
||||
for (final var s : split) {
|
||||
final var newLength = builder.length() + s.length();
|
||||
if (areDifferentLines(builder.length(), newLength, maxLineLength)) {
|
||||
builder.append("\n").append(s);
|
||||
} else {
|
||||
builder.append(" ").append(s);
|
||||
}
|
||||
}
|
||||
final var start = (long) (segment.start() * 1000L);
|
||||
final var end = (long) (segment.end() * 1000L);
|
||||
return Stream.of(new SubtitleImpl(builder.toString(), start, end, null, null));
|
||||
}
|
||||
|
||||
private static boolean areDifferentLines(final int currentLength, final int newLength, final int maxLength) {
|
||||
return currentLength / (maxLength + 1) < newLength / (maxLength + 1);
|
||||
}
|
||||
|
||||
private Stream<SubtitleImpl> splitSubtitleWords(final JSONSubtitleSegment segment) {
|
||||
final var maxLineLength = preferences.getInt(MAX_LINE_LENGTH, defaultMaxLineLength);
|
||||
final var maxLines = preferences.getInt(MAX_LINES, defaultMaxLines);
|
||||
final var ret = new ArrayList<SubtitleImpl>(segment.text().length() / (maxLines * maxLineLength));
|
||||
final var builder = new StringBuilder(maxLines * maxLineLength);
|
||||
final var words = segment.words();
|
||||
var currentStart = -1L;
|
||||
var currentEnd = -1L;
|
||||
for (final var word : words) {
|
||||
final var text = word.word();
|
||||
final var start = (long) (word.start() * 1000L);
|
||||
final var end = (long) (word.end() * 1000L);
|
||||
if (currentStart < 0) {
|
||||
currentStart = start;
|
||||
}
|
||||
final var newLength = builder.length() + text.length();
|
||||
if (newLength > maxLineLength * maxLines) {
|
||||
final var newSubtitle = new SubtitleImpl(builder.toString(), currentStart, currentEnd, null, null);
|
||||
ret.add(newSubtitle);
|
||||
builder.delete(0, builder.length());
|
||||
builder.append(text);
|
||||
currentStart = start == 0 ? currentEnd : start;
|
||||
} else if (areDifferentLines(builder.length(), newLength, maxLineLength)) {
|
||||
builder.append("\n").append(text);
|
||||
} else {
|
||||
builder.append(" ").append(text);
|
||||
}
|
||||
currentEnd = end == 0 ? currentEnd : end;
|
||||
}
|
||||
if (!builder.isEmpty()) {
|
||||
final var newSubtitle = new SubtitleImpl(builder.toString(), currentStart, currentEnd, null, null);
|
||||
ret.add(newSubtitle);
|
||||
}
|
||||
return ret.stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canParse(final Path file) {
|
||||
return file.getFileName().toString().endsWith(".json");
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
module com.github.gtache.autosubtitle.whisperx {
|
||||
requires transitive com.github.gtache.autosubtitle.whisper.common;
|
||||
requires org.apache.logging.log4j;
|
||||
requires java.prefs;
|
||||
|
||||
exports com.github.gtache.autosubtitle.whisperx;
|
||||
exports com.github.gtache.autosubtitle.setup.whisperx;
|
||||
|
||||
Reference in New Issue
Block a user