Extraction works
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -37,3 +37,5 @@ build/
|
|||||||
|
|
||||||
### Mac OS ###
|
### Mac OS ###
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
tools
|
||||||
2
.idea/encodings.xml
generated
2
.idea/encodings.xml
generated
@@ -3,6 +3,8 @@
|
|||||||
<component name="Encoding" native2AsciiForPropertiesFiles="true" defaultCharsetForPropertiesFiles="ISO-8859-1">
|
<component name="Encoding" native2AsciiForPropertiesFiles="true" defaultCharsetForPropertiesFiles="ISO-8859-1">
|
||||||
<file url="file://$PROJECT_DIR$/api/src/main/java" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/api/src/main/java" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/api/src/main/resources" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/api/src/main/resources" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/cli/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/cli/src/main/resources" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/core/src/main/java" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/core/src/main/java" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/core/src/main/resources" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/core/src/main/resources" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/deepl/src/main/java" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/deepl/src/main/java" charset="UTF-8" />
|
||||||
|
|||||||
@@ -7,5 +7,10 @@ public interface AudioInfo {
|
|||||||
/**
|
/**
|
||||||
* @return The audio extension (mp3, etc.)
|
* @return The audio extension (mp3, etc.)
|
||||||
*/
|
*/
|
||||||
String videoFormat();
|
String audioFormat();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The audio duration in milliseconds
|
||||||
|
*/
|
||||||
|
long duration();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package com.github.gtache.autosubtitle;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of languages //TODO add more or use Locale if possible?
|
||||||
|
*/
|
||||||
|
public enum Language {
|
||||||
|
AR("arabic", "ar", "ara"),
|
||||||
|
BE("belarusian", "be", "bel"),
|
||||||
|
BG("bulgarian", "bg", "bul"),
|
||||||
|
CS("czech", "cs", "cze"),
|
||||||
|
DA("danish", "da", "dan"),
|
||||||
|
DE("german", "de", "deu", "ger"),
|
||||||
|
EL("greek", "el", "gre"),
|
||||||
|
EN("english", "en", "eng"),
|
||||||
|
ES("spanish", "es", "spa"),
|
||||||
|
FA("persian", "fa", "per"),
|
||||||
|
FI("finnish", "fi", "fin"),
|
||||||
|
FR("french", "fr", "fra", "fre"),
|
||||||
|
HE("hebrew", "he", "heb"),
|
||||||
|
HR("croatian", "hr", "hrv"),
|
||||||
|
ID("indonesian", "id", "ind"),
|
||||||
|
IT("italian", "it", "ita", "gre"),
|
||||||
|
JA("japanese", "ja", "jpn"),
|
||||||
|
KO("korean", "ko", "kor"),
|
||||||
|
LA("latin", "la", "lat"),
|
||||||
|
LB("luxembourgish", "lb", "ltz"),
|
||||||
|
LO("lao", "lo", "lao"),
|
||||||
|
LT("lithuanian", "lt", "lit"),
|
||||||
|
MT("maltese", "mt", "mlt"),
|
||||||
|
MY("myanmar", "my", "mya"),
|
||||||
|
NL("dutch", "nl", "nld"),
|
||||||
|
NO("norwegian", "no", "nor"),
|
||||||
|
PL("polish", "pl", "pol"),
|
||||||
|
PT("portuguese", "pt", "por"),
|
||||||
|
RO("romanian", "ro", "ron"),
|
||||||
|
RU("russian", "ru", "rus"),
|
||||||
|
SK("slovak", "sk", "slo"),
|
||||||
|
SL("slovenian", "sl", "slv"),
|
||||||
|
SV("swedish", "sv", "swe"),
|
||||||
|
TH("thai", "th", "tha"),
|
||||||
|
TR("turkish", "tr", "tur"),
|
||||||
|
UK("ukrainian", "uk", "ukr"),
|
||||||
|
VI("vietnamese", "vi", "vie"),
|
||||||
|
ZH("chinese", "zh", "zho", "chi"),
|
||||||
|
AUTO("auto", "auto", "auto");
|
||||||
|
|
||||||
|
private static final Map<String, Language> STRING_LANGUAGE_MAP;
|
||||||
|
|
||||||
|
static {
|
||||||
|
final Map<String, Language> map = new java.util.HashMap<>();
|
||||||
|
for (final var language : Language.values()) {
|
||||||
|
map.put(language.name().toLowerCase(), language);
|
||||||
|
map.put(language.iso2, language);
|
||||||
|
map.put(language.iso3, language);
|
||||||
|
language.aliases.forEach(s -> map.put(s, language));
|
||||||
|
}
|
||||||
|
STRING_LANGUAGE_MAP = map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final String englishName;
|
||||||
|
private final String iso2;
|
||||||
|
private final String iso3;
|
||||||
|
private final Set<String> aliases;
|
||||||
|
|
||||||
|
Language(final String englishName, final String iso2, final String iso3, final String... aliases) {
|
||||||
|
this.englishName = requireNonNull(englishName);
|
||||||
|
this.iso2 = requireNonNull(iso2);
|
||||||
|
this.iso3 = requireNonNull(iso3);
|
||||||
|
this.aliases = Set.of(aliases);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String englishName() {
|
||||||
|
return englishName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String iso2() {
|
||||||
|
return iso2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String iso3() {
|
||||||
|
return iso3;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Language getLanguage(final String name) {
|
||||||
|
return STRING_LANGUAGE_MAP.get(name.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Language getDefault() {
|
||||||
|
return STRING_LANGUAGE_MAP.getOrDefault(Locale.getDefault().getLanguage(), EN);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,88 +3,86 @@ package com.github.gtache.autosubtitle;
|
|||||||
import com.github.gtache.autosubtitle.subtitle.Subtitle;
|
import com.github.gtache.autosubtitle.subtitle.Subtitle;
|
||||||
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
|
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Translates texts and subtitles
|
* Translates texts and subtitles
|
||||||
*/
|
*/
|
||||||
public interface Translator {
|
public interface Translator {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Guesses the locale of the given text
|
* Guesses the language of the given text
|
||||||
*
|
*
|
||||||
* @param text The text
|
* @param text The text
|
||||||
* @return The guessed locale
|
* @return The guessed language
|
||||||
*/
|
*/
|
||||||
Locale getLocale(final String text);
|
Language getLanguage(final String text);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Guesses the locale of the given subtitle
|
* Guesses the language of the given subtitle
|
||||||
*
|
*
|
||||||
* @param subtitle The subtitle
|
* @param subtitle The subtitle
|
||||||
* @return The guessed locale
|
* @return The guessed language
|
||||||
*/
|
*/
|
||||||
default Locale getLocale(final Subtitle subtitle) {
|
default Language getLanguage(final Subtitle subtitle) {
|
||||||
return getLocale(subtitle.content());
|
return getLanguage(subtitle.content());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Translates the given text to the given locale
|
* Translates the given text to the given language
|
||||||
*
|
*
|
||||||
* @param text The text to translate
|
* @param text The text to translate
|
||||||
* @param to The target locale
|
* @param to The target language
|
||||||
* @return The translated text
|
* @return The translated text
|
||||||
*/
|
*/
|
||||||
String translate(String text, Locale to);
|
String translate(String text, Language to);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Translates the given text to the given locale
|
* Translates the given text to the given language
|
||||||
*
|
*
|
||||||
* @param text The text to translate
|
* @param text The text to translate
|
||||||
* @param to The target locale
|
* @param to The target language
|
||||||
* @return The translated text
|
* @return The translated text
|
||||||
*/
|
*/
|
||||||
default String translate(final String text, final String to) {
|
default String translate(final String text, final String to) {
|
||||||
return translate(text, Locale.forLanguageTag(to));
|
return translate(text, Language.getLanguage(to));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Translates the given subtitle to the given locale
|
* Translates the given subtitle to the given language
|
||||||
*
|
*
|
||||||
* @param subtitle The subtitle to translate
|
* @param subtitle The subtitle to translate
|
||||||
* @param to The target locale
|
* @param to The target language
|
||||||
* @return The translated subtitle
|
* @return The translated subtitle
|
||||||
*/
|
*/
|
||||||
Subtitle translate(Subtitle subtitle, Locale to);
|
Subtitle translate(Subtitle subtitle, Language to);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Translates the given subtitle to the given locale
|
* Translates the given subtitle to the given language
|
||||||
*
|
*
|
||||||
* @param subtitle The subtitle to translate
|
* @param subtitle The subtitle to translate
|
||||||
* @param to The target locale
|
* @param to The target language
|
||||||
* @return The translated subtitle
|
* @return The translated subtitle
|
||||||
*/
|
*/
|
||||||
default Subtitle translate(final Subtitle subtitle, final String to) {
|
default Subtitle translate(final Subtitle subtitle, final String to) {
|
||||||
return translate(subtitle, Locale.forLanguageTag(to));
|
return translate(subtitle, Language.getLanguage(to));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Translates the given subtitles collection to the given locale
|
* Translates the given subtitles collection to the given language
|
||||||
*
|
*
|
||||||
* @param collection The subtitles collection to translate
|
* @param collection The subtitles collection to translate
|
||||||
* @param to The target locale
|
* @param to The target language
|
||||||
* @return The translated subtitles collection
|
* @return The translated subtitles collection
|
||||||
*/
|
*/
|
||||||
default SubtitleCollection translate(final SubtitleCollection collection, final String to) {
|
default SubtitleCollection translate(final SubtitleCollection collection, final String to) {
|
||||||
return translate(collection, Locale.forLanguageTag(to));
|
return translate(collection, Language.getLanguage(to));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Translates the given subtitles collection to the given locale
|
* Translates the given subtitles collection to the given language
|
||||||
*
|
*
|
||||||
* @param collection The subtitles collection to translate
|
* @param collection The subtitles collection to translate
|
||||||
* @param to The target locale
|
* @param to The target language
|
||||||
* @return The translated subtitles collection
|
* @return The translated subtitles collection
|
||||||
*/
|
*/
|
||||||
SubtitleCollection translate(SubtitleCollection collection, Locale to);
|
SubtitleCollection translate(SubtitleCollection collection, Language to);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.github.gtache.autosubtitle.process;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listens to a process
|
||||||
|
*/
|
||||||
|
public interface ProcessListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The process
|
||||||
|
*/
|
||||||
|
Process process();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits for the next output of the process (or its end). Note that the process may become stuck if the output is not read.
|
||||||
|
*
|
||||||
|
* @return The next line of the process output, or null if the process has ended
|
||||||
|
* @throws IOException if an error occurs
|
||||||
|
*/
|
||||||
|
String readLine() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits for the process to finish
|
||||||
|
*
|
||||||
|
* @param duration The maximum time to wait
|
||||||
|
* @return The process result
|
||||||
|
*/
|
||||||
|
ProcessResult join(final Duration duration) throws IOException;
|
||||||
|
}
|
||||||
@@ -28,4 +28,44 @@ public interface ProcessRunner {
|
|||||||
* @throws IOException if something goes wrong
|
* @throws IOException if something goes wrong
|
||||||
*/
|
*/
|
||||||
ProcessResult run(final List<String> args) throws IOException;
|
ProcessResult run(final List<String> args) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a process
|
||||||
|
*
|
||||||
|
* @param args the command
|
||||||
|
* @return The process
|
||||||
|
* @throws IOException if something goes wrong
|
||||||
|
*/
|
||||||
|
default Process start(final String... args) throws IOException {
|
||||||
|
return start(Arrays.asList(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a process
|
||||||
|
*
|
||||||
|
* @param args the command
|
||||||
|
* @return The process
|
||||||
|
* @throws IOException if something goes wrong
|
||||||
|
*/
|
||||||
|
Process start(final List<String> args) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a process
|
||||||
|
*
|
||||||
|
* @param args the command
|
||||||
|
* @return An object allowing to listen to the process
|
||||||
|
* @throws IOException if something goes wrong
|
||||||
|
*/
|
||||||
|
default ProcessListener startListen(final String... args) throws IOException {
|
||||||
|
return startListen(Arrays.asList(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a process
|
||||||
|
*
|
||||||
|
* @param args the command
|
||||||
|
* @return An object allowing to listen to the process
|
||||||
|
* @throws IOException if something goes wrong
|
||||||
|
*/
|
||||||
|
ProcessListener startListen(final List<String> args) throws IOException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.github.gtache.autosubtitle.setup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a setup action
|
||||||
|
*/
|
||||||
|
public enum SetupAction {
|
||||||
|
CHECK, DOWNLOAD, INSTALL, UNINSTALL, UPDATE, DELETE
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.github.gtache.autosubtitle.setup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Events that can be triggered by {@link SetupManager}
|
||||||
|
*/
|
||||||
|
public interface SetupEvent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the action that triggered the event
|
||||||
|
*/
|
||||||
|
SetupAction action();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the target of the action
|
||||||
|
*/
|
||||||
|
String target();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the progress of the setup
|
||||||
|
*/
|
||||||
|
double progress();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the setup manager that triggered the event
|
||||||
|
*/
|
||||||
|
SetupManager setupManager();
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.github.gtache.autosubtitle.setup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listens on {@link SetupManager}'s {@link SetupEvent}s
|
||||||
|
*/
|
||||||
|
public interface SetupListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggered when an action starts
|
||||||
|
*
|
||||||
|
* @param event the event
|
||||||
|
*/
|
||||||
|
void onActionStart(SetupEvent event);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggered when an action ends
|
||||||
|
*
|
||||||
|
* @param event the event
|
||||||
|
*/
|
||||||
|
void onActionEnd(SetupEvent event);
|
||||||
|
}
|
||||||
@@ -17,11 +17,9 @@ public interface SetupManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @return whether the component is installed
|
* @return whether the component is installed
|
||||||
* @throws SetupException if an error occurred during the check
|
* @throws SetupException if an error occurred
|
||||||
*/
|
*/
|
||||||
default boolean isInstalled() throws SetupException {
|
boolean isInstalled() throws SetupException;
|
||||||
return status().isInstalled();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Installs the component
|
* Installs the component
|
||||||
@@ -42,10 +40,7 @@ public interface SetupManager {
|
|||||||
*
|
*
|
||||||
* @throws SetupException if an error occurred during the reinstallation
|
* @throws SetupException if an error occurred during the reinstallation
|
||||||
*/
|
*/
|
||||||
default void reinstall() throws SetupException {
|
void reinstall() throws SetupException;
|
||||||
uninstall();
|
|
||||||
install();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if an update is available for the component
|
* Checks if an update is available for the component
|
||||||
@@ -53,9 +48,7 @@ public interface SetupManager {
|
|||||||
* @return whether an update is available
|
* @return whether an update is available
|
||||||
* @throws SetupException if an error occurred during the check
|
* @throws SetupException if an error occurred during the check
|
||||||
*/
|
*/
|
||||||
default boolean isUpdateAvailable() throws SetupException {
|
boolean isUpdateAvailable() throws SetupException;
|
||||||
return status() == SetupStatus.UPDATE_AVAILABLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the component
|
* Updates the component
|
||||||
@@ -63,4 +56,23 @@ public interface SetupManager {
|
|||||||
* @throws SetupException if an error occurred during the update
|
* @throws SetupException if an error occurred during the update
|
||||||
*/
|
*/
|
||||||
void update() throws SetupException;
|
void update() throws SetupException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a listener
|
||||||
|
*
|
||||||
|
* @param listener the listener
|
||||||
|
*/
|
||||||
|
void addListener(SetupListener listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a listener
|
||||||
|
*
|
||||||
|
* @param listener the listener
|
||||||
|
*/
|
||||||
|
void removeListener(SetupListener listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all listeners
|
||||||
|
*/
|
||||||
|
void removeListeners();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ package com.github.gtache.autosubtitle.setup;
|
|||||||
* The status of a setup
|
* The status of a setup
|
||||||
*/
|
*/
|
||||||
public enum SetupStatus {
|
public enum SetupStatus {
|
||||||
ERRORED, NOT_INSTALLED, INSTALLED, UPDATE_AVAILABLE;
|
ERRORED, NOT_INSTALLED, SYSTEM_INSTALLED, BUNDLE_INSTALLED, UPDATE_AVAILABLE;
|
||||||
|
|
||||||
public boolean isInstalled() {
|
public boolean isInstalled() {
|
||||||
return this == INSTALLED || this == UPDATE_AVAILABLE;
|
return this == SYSTEM_INSTALLED || this == BUNDLE_INSTALLED || this == UPDATE_AVAILABLE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.github.gtache.autosubtitle.subtitle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Events that can be triggered by {@link SubtitleExtractor}
|
||||||
|
*/
|
||||||
|
public interface ExtractEvent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the message
|
||||||
|
*/
|
||||||
|
String message();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the progress of the setup
|
||||||
|
*/
|
||||||
|
double progress();
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.github.gtache.autosubtitle.subtitle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when an error occurs during subtitle extraction
|
||||||
|
*/
|
||||||
|
public class ExtractException extends Exception {
|
||||||
|
|
||||||
|
public ExtractException(final String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExtractException(final String message, final Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExtractException(final Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.github.gtache.autosubtitle.subtitle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An extraction model
|
||||||
|
*/
|
||||||
|
public interface ExtractionModel {
|
||||||
|
/**
|
||||||
|
* @return the name of the model
|
||||||
|
*/
|
||||||
|
String name();
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.github.gtache.autosubtitle.subtitle;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provider of {@link ExtractionModel}
|
||||||
|
*/
|
||||||
|
public interface ExtractionModelProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the list of all available models
|
||||||
|
*/
|
||||||
|
List<ExtractionModel> getAvailableExtractionModels();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the default model
|
||||||
|
*/
|
||||||
|
ExtractionModel getDefaultExtractionModel();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param name the name of the model
|
||||||
|
* @return the model with the specified name
|
||||||
|
*/
|
||||||
|
ExtractionModel getExtractionModel(final String name);
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.github.gtache.autosubtitle.subtitle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The possible subtitles output formats
|
||||||
|
*/
|
||||||
|
public enum OutputFormat {
|
||||||
|
SRT, ASS
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
package com.github.gtache.autosubtitle.subtitle;
|
package com.github.gtache.autosubtitle.subtitle;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.Language;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a collection of {@link Subtitle}
|
* Represents a collection of {@link Subtitle}
|
||||||
@@ -14,7 +15,7 @@ public interface SubtitleCollection {
|
|||||||
Collection<? extends Subtitle> subtitles();
|
Collection<? extends Subtitle> subtitles();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The locale of the subtitles
|
* @return The language of the subtitles
|
||||||
*/
|
*/
|
||||||
Locale locale();
|
Language language();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,76 @@
|
|||||||
package com.github.gtache.autosubtitle.subtitle;
|
package com.github.gtache.autosubtitle.subtitle;
|
||||||
|
|
||||||
import com.github.gtache.autosubtitle.Audio;
|
import com.github.gtache.autosubtitle.Audio;
|
||||||
|
import com.github.gtache.autosubtitle.Language;
|
||||||
import com.github.gtache.autosubtitle.Video;
|
import com.github.gtache.autosubtitle.Video;
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts subtitles from a video or audio
|
* Extracts subtitles from a video or audio
|
||||||
*/
|
*/
|
||||||
public interface SubtitleExtractor {
|
public interface SubtitleExtractor {
|
||||||
|
|
||||||
Collection<? extends EditableSubtitle> extract(final Video in);
|
/**
|
||||||
|
* Adds a listener
|
||||||
|
*
|
||||||
|
* @param listener The listener
|
||||||
|
*/
|
||||||
|
void addListener(SubtitleExtractorListener listener);
|
||||||
|
|
||||||
Collection<? extends EditableSubtitle> extract(final Audio in);
|
/**
|
||||||
|
* Removes a listener
|
||||||
|
*
|
||||||
|
* @param listener The listener
|
||||||
|
*/
|
||||||
|
void removeListener(SubtitleExtractorListener listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all listeners
|
||||||
|
*/
|
||||||
|
void removeListeners();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the subtitles from a video
|
||||||
|
*
|
||||||
|
* @param video The video
|
||||||
|
* @param model The model to use
|
||||||
|
* @return The extracted subtitle collection
|
||||||
|
* @throws ExtractException If an error occurs
|
||||||
|
*/
|
||||||
|
default SubtitleCollection extract(final Video video, final ExtractionModel model) throws ExtractException {
|
||||||
|
return extract(video, Language.AUTO, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the subtitles from a video
|
||||||
|
*
|
||||||
|
* @param video The video
|
||||||
|
* @param language The language of the video
|
||||||
|
* @param model The model to use
|
||||||
|
* @return The extracted subtitle collection
|
||||||
|
* @throws ExtractException If an error occurs
|
||||||
|
*/
|
||||||
|
SubtitleCollection extract(final Video video, final Language language, final ExtractionModel model) throws ExtractException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the subtitles from an audio
|
||||||
|
*
|
||||||
|
* @param audio The audio
|
||||||
|
* @param model The model to use
|
||||||
|
* @return The extracted subtitle collection
|
||||||
|
* @throws ExtractException If an error occurs
|
||||||
|
*/
|
||||||
|
default SubtitleCollection extract(final Audio audio, final ExtractionModel model) throws ExtractException {
|
||||||
|
return extract(audio, Language.AUTO, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the subtitles from an audio
|
||||||
|
*
|
||||||
|
* @param audio The audio
|
||||||
|
* @param language The language of the audio
|
||||||
|
* @param model The model to use
|
||||||
|
* @return The extracted subtitle collection
|
||||||
|
* @throws ExtractException If an error occurs
|
||||||
|
*/
|
||||||
|
SubtitleCollection extract(final Audio audio, final Language language, final ExtractionModel model) throws ExtractException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.github.gtache.autosubtitle.subtitle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener for {@link SubtitleExtractor}
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface SubtitleExtractorListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an event is triggered
|
||||||
|
*
|
||||||
|
* @param event The event
|
||||||
|
*/
|
||||||
|
void listen(final ExtractEvent event);
|
||||||
|
}
|
||||||
41
cli/pom.xml
Normal file
41
cli/pom.xml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>com.github.gtache.autosubtitle</groupId>
|
||||||
|
<artifactId>autosubtitle</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>autosubtitle-cli</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>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.gtache.autosubtitle</groupId>
|
||||||
|
<artifactId>autosubtitle-whisper</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
|
<artifactId>log4j-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.dagger</groupId>
|
||||||
|
<artifactId>dagger</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>info.picocli</groupId>
|
||||||
|
<artifactId>picocli</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package com.github.gtache.autosubtitle.cli;
|
||||||
|
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.modules.cli.DaggerCliComponent;
|
||||||
|
import picocli.CommandLine;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@CommandLine.Command(name = "autosubtitle", mixinStandardHelpOptions = true, version = "autosubtitle 1.0-SNAPSHOT", description = "CLI for auto-subtitle")
|
||||||
|
public final class Cli implements Runnable {
|
||||||
|
|
||||||
|
@CommandLine.Option(names = {"-b", "--burn"}, description = "Burn the subtitles. Otherwise, adds them to the video", defaultValue = "false")
|
||||||
|
private boolean burn;
|
||||||
|
@CommandLine.Option(names = {"-e", "--extractor"}, description = "The subtitle extractor to use [whisper]", defaultValue = "whisper")
|
||||||
|
private String extractor;
|
||||||
|
@CommandLine.Option(names = {"-i", "--input"}, description = "The input file", required = true)
|
||||||
|
private String input;
|
||||||
|
@CommandLine.Option(names = {"-l", "--loader"}, description = "The video loader to use [ffprobe]", defaultValue = "ffprobe")
|
||||||
|
private String loader;
|
||||||
|
@CommandLine.Option(names = {"-o", "--output"}, description = "The output file", required = true)
|
||||||
|
private String output;
|
||||||
|
@CommandLine.Option(names = {"-s", "--subtitle-converter"}, description = "The subtitle converter to use [srt|ass]", defaultValue = "srt")
|
||||||
|
private String subtitleConverter;
|
||||||
|
@CommandLine.Option(names = {"-c", "--video-converter"}, description = "The video converter to use [ffmpeg]", defaultValue = "ffmpeg")
|
||||||
|
private String videoConverter;
|
||||||
|
@CommandLine.Option(names = {"--translations"}, description = "The list of translations to create. Ignored if burn is specified", split = ",", arity = "0..*")
|
||||||
|
private Set<String> translations;
|
||||||
|
@CommandLine.Option(names = {"-t", "--translator"}, description = "The translator to use [deepl]. Ignored if burn is specified", defaultValue = "deepl")
|
||||||
|
private String translator;
|
||||||
|
@CommandLine.Option(names = {"-w", "--wait"}, description = "Allow modifying subtitle files before creating output", defaultValue = "false")
|
||||||
|
private boolean wait;
|
||||||
|
|
||||||
|
private Cli() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (!extractor.equals("whisper")) {
|
||||||
|
throw new IllegalArgumentException("Unknown extractor : " + extractor);
|
||||||
|
}
|
||||||
|
if (!loader.equals("ffprobe")) {
|
||||||
|
throw new IllegalArgumentException("Unknown loader : " + loader);
|
||||||
|
}
|
||||||
|
if (!subtitleConverter.equals("srt")) {
|
||||||
|
throw new IllegalArgumentException("Unknown subtitle converter : " + subtitleConverter);
|
||||||
|
}
|
||||||
|
if (!videoConverter.equals("ffmpeg")) {
|
||||||
|
throw new IllegalArgumentException("Unknown video converter : " + videoConverter);
|
||||||
|
}
|
||||||
|
if (!translator.equals("deepl")) {
|
||||||
|
throw new IllegalArgumentException("Unknown translator : " + translator);
|
||||||
|
}
|
||||||
|
|
||||||
|
final var component = DaggerCliComponent.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(final String[] args) {
|
||||||
|
System.exit(new CommandLine(new Cli()).execute(args));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.github.gtache.autosubtitle.modules.cli;
|
||||||
|
|
||||||
|
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.subtitles.impl.ConverterModule;
|
||||||
|
import com.github.gtache.autosubtitle.modules.whisper.WhisperModule;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.SubtitleConverter;
|
||||||
|
import dagger.Component;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
@Component(modules = {ConverterModule.class, CoreModule.class, DeepLModule.class, FFmpegModule.class, WhisperModule.class})
|
||||||
|
@Singleton
|
||||||
|
public interface CliComponent {
|
||||||
|
|
||||||
|
SubtitleConverter getSubtitleConverter();
|
||||||
|
}
|
||||||
9
cli/src/main/java/module-info.java
Normal file
9
cli/src/main/java/module-info.java
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* CLI module for autosubtitle
|
||||||
|
*/
|
||||||
|
module com.github.gtache.autosubtitle.cli {
|
||||||
|
requires com.github.gtache.autosubtitle.deepl;
|
||||||
|
requires com.github.gtache.autosubtitle.ffmpeg;
|
||||||
|
requires com.github.gtache.autosubtitle.whisper;
|
||||||
|
requires info.picocli;
|
||||||
|
}
|
||||||
@@ -4,7 +4,17 @@ package com.github.gtache.autosubtitle.impl;
|
|||||||
* The list of possible operating systems
|
* The list of possible operating systems
|
||||||
*/
|
*/
|
||||||
public enum Architecture {
|
public enum Architecture {
|
||||||
I386, I486, I586, I686, PPC, POWERPC, X86, X86_32, X86_64, AMD64, ARM, ARM32, ARM64, AARCH64, UNKNOWN;
|
//32 bit
|
||||||
|
I386, I486, I586, I686, X86_32,
|
||||||
|
//PowerPC
|
||||||
|
PPC, POWERPC,
|
||||||
|
//64 bit
|
||||||
|
X86, X86_64, AMD64,
|
||||||
|
//ARM 32 bit
|
||||||
|
ARM32, ARM, ARMV1, ARMV2, ARMV3, ARMV4, ARMV5, ARMV6, ARMV7, AARCH32,
|
||||||
|
//ARM 64 bit
|
||||||
|
ARM64, ARMV8, ARMV9, AARCH64,
|
||||||
|
UNKNOWN;
|
||||||
|
|
||||||
public static Architecture getArchitecture(final String name) {
|
public static Architecture getArchitecture(final String name) {
|
||||||
try {
|
try {
|
||||||
@@ -13,4 +23,12 @@ public enum Architecture {
|
|||||||
return UNKNOWN;
|
return UNKNOWN;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isAMD64() {
|
||||||
|
return this == X86 || this == X86_64 || this == AMD64;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isARM64() {
|
||||||
|
return this == ARM64 || this == ARMV8 || this == ARMV9 || this == AARCH64;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,14 +7,12 @@ import java.util.Objects;
|
|||||||
/**
|
/**
|
||||||
* Implementation of {@link AudioInfo}
|
* Implementation of {@link AudioInfo}
|
||||||
*/
|
*/
|
||||||
public record AudioInfoImpl(String audioFormat) implements AudioInfo {
|
public record AudioInfoImpl(String audioFormat, long duration) implements AudioInfo {
|
||||||
|
|
||||||
public AudioInfoImpl {
|
public AudioInfoImpl {
|
||||||
Objects.requireNonNull(audioFormat);
|
Objects.requireNonNull(audioFormat);
|
||||||
}
|
if (duration < 0) {
|
||||||
|
throw new IllegalArgumentException("Duration must be positive");
|
||||||
@Override
|
}
|
||||||
public String videoFormat() {
|
|
||||||
return audioFormat;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.github.gtache.autosubtitle.setup.modules.impl;
|
package com.github.gtache.autosubtitle.modules.setup.impl;
|
||||||
|
|
||||||
import javax.inject.Qualifier;
|
import javax.inject.Qualifier;
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.Documented;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.github.gtache.autosubtitle.setup.modules.impl;
|
package com.github.gtache.autosubtitle.modules.setup.impl;
|
||||||
|
|
||||||
import javax.inject.Qualifier;
|
import javax.inject.Qualifier;
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.Documented;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.github.gtache.autosubtitle.setup.modules.impl;
|
package com.github.gtache.autosubtitle.modules.setup.impl;
|
||||||
|
|
||||||
import javax.inject.Qualifier;
|
import javax.inject.Qualifier;
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.Documented;
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
package com.github.gtache.autosubtitle.subtitle.modules.impl;
|
package com.github.gtache.autosubtitle.modules.subtitles.impl;
|
||||||
|
|
||||||
import com.github.gtache.autosubtitle.subtitle.SubtitleConverter;
|
import com.github.gtache.autosubtitle.subtitle.SubtitleConverter;
|
||||||
import com.github.gtache.autosubtitle.subtitle.impl.SRTSubtitleConverter;
|
import com.github.gtache.autosubtitle.subtitle.impl.SRTSubtitleConverter;
|
||||||
import dagger.Binds;
|
import dagger.Binds;
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dagger module for subtitle converter
|
* Dagger module for subtitle converter
|
||||||
*/
|
*/
|
||||||
@@ -14,6 +12,5 @@ import javax.inject.Singleton;
|
|||||||
public interface ConverterModule {
|
public interface ConverterModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@Singleton
|
|
||||||
SubtitleConverter bindsSubtitleConverter(final SRTSubtitleConverter converter);
|
SubtitleConverter bindsSubtitleConverter(final SRTSubtitleConverter converter);
|
||||||
}
|
}
|
||||||
@@ -1,19 +1,15 @@
|
|||||||
package com.github.gtache.autosubtitle.process.impl;
|
package com.github.gtache.autosubtitle.process.impl;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.process.ProcessListener;
|
||||||
import com.github.gtache.autosubtitle.process.ProcessResult;
|
import com.github.gtache.autosubtitle.process.ProcessResult;
|
||||||
import com.github.gtache.autosubtitle.process.ProcessRunner;
|
import com.github.gtache.autosubtitle.process.ProcessRunner;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.time.Duration;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base implementation of {@link ProcessRunner}
|
* Base implementation of {@link ProcessRunner}
|
||||||
@@ -24,29 +20,30 @@ public abstract class AbstractProcessRunner implements ProcessRunner {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ProcessResult run(final List<String> args) throws IOException {
|
public ProcessResult run(final List<String> args) throws IOException {
|
||||||
final var builder = new ProcessBuilder(args);
|
final var listener = startListen(args);
|
||||||
builder.redirectErrorStream(true);
|
CompletableFuture.runAsync(() -> {
|
||||||
final var process = builder.start();
|
try {
|
||||||
final var readFuture = CompletableFuture.supplyAsync(() -> {
|
var line = listener.readLine();
|
||||||
final var output = new ArrayList<String>();
|
|
||||||
try (final var in = new BufferedReader(new InputStreamReader(new BufferedInputStream(process.getInputStream()), StandardCharsets.UTF_8))) {
|
|
||||||
var line = in.readLine();
|
|
||||||
while (line != null) {
|
while (line != null) {
|
||||||
output.add(line);
|
line = listener.readLine();
|
||||||
line = in.readLine();
|
|
||||||
}
|
}
|
||||||
} catch (final IOException e) {
|
} catch (final IOException e) {
|
||||||
logger.error("Error listening to process output of {}", args, e);
|
logger.error("Error listening to process output of {}", args, e);
|
||||||
}
|
}
|
||||||
return output;
|
|
||||||
});
|
});
|
||||||
try {
|
return listener.join(Duration.ofHours(1));
|
||||||
process.waitFor(1, TimeUnit.HOURS);
|
}
|
||||||
} catch (final InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
@Override
|
||||||
process.destroy();
|
public Process start(final List<String> args) throws IOException {
|
||||||
}
|
final var builder = new ProcessBuilder(args);
|
||||||
final var output = readFuture.join();
|
builder.redirectErrorStream(true);
|
||||||
return new ProcessResultImpl(process.exitValue(), output);
|
return builder.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProcessListener startListen(final List<String> args) throws IOException {
|
||||||
|
final var process = start(args);
|
||||||
|
return new ProcessListenerImpl(process);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package com.github.gtache.autosubtitle.process.impl;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.process.ProcessListener;
|
||||||
|
import com.github.gtache.autosubtitle.process.ProcessResult;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of {@link ProcessListener}
|
||||||
|
*/
|
||||||
|
public class ProcessListenerImpl implements ProcessListener {
|
||||||
|
|
||||||
|
private final Process process;
|
||||||
|
private final BufferedReader reader;
|
||||||
|
private final List<String> output;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates the listener
|
||||||
|
*
|
||||||
|
* @param process The process to listen to
|
||||||
|
*/
|
||||||
|
public ProcessListenerImpl(final Process process) {
|
||||||
|
this.process = Objects.requireNonNull(process);
|
||||||
|
this.reader = new BufferedReader(new InputStreamReader(new BufferedInputStream(process.getInputStream()), StandardCharsets.UTF_8));
|
||||||
|
this.output = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Process process() {
|
||||||
|
return process;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String readLine() throws IOException {
|
||||||
|
final var line = reader.readLine();
|
||||||
|
if (line != null) {
|
||||||
|
output.add(line);
|
||||||
|
}
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProcessResult join(final Duration duration) throws IOException {
|
||||||
|
try {
|
||||||
|
process.waitFor(duration.getSeconds(), TimeUnit.SECONDS);
|
||||||
|
} catch (final InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
process.destroy();
|
||||||
|
}
|
||||||
|
if (process.isAlive()) {
|
||||||
|
process.destroyForcibly();
|
||||||
|
}
|
||||||
|
reader.close();
|
||||||
|
return new ProcessResultImpl(process.exitValue(), output);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
package com.github.gtache.autosubtitle.setup.impl;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.process.impl.AbstractProcessRunner;
|
||||||
|
import com.github.gtache.autosubtitle.setup.SetupAction;
|
||||||
|
import com.github.gtache.autosubtitle.setup.SetupEvent;
|
||||||
|
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;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for all {@link SetupManager} implementations
|
||||||
|
*/
|
||||||
|
public abstract class AbstractSetupManager extends AbstractProcessRunner implements SetupManager {
|
||||||
|
|
||||||
|
private static final Logger logger = LogManager.getLogger(AbstractSetupManager.class);
|
||||||
|
private final Set<SetupListener> listeners;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates the manager
|
||||||
|
*/
|
||||||
|
protected AbstractSetupManager() {
|
||||||
|
this.listeners = new HashSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addListener(final SetupListener listener) {
|
||||||
|
listeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeListener(final SetupListener listener) {
|
||||||
|
listeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeListeners() {
|
||||||
|
listeners.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reinstall() throws SetupException {
|
||||||
|
sendStartEvent(SetupAction.UNINSTALL, name(), 0);
|
||||||
|
uninstall();
|
||||||
|
sendEndEvent(SetupAction.UNINSTALL, name(), -1);
|
||||||
|
sendStartEvent(SetupAction.INSTALL, name(), -1);
|
||||||
|
install();
|
||||||
|
sendEndEvent(SetupAction.INSTALL, name(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInstalled() {
|
||||||
|
return status() == SetupStatus.SYSTEM_INSTALLED || status() == SetupStatus.BUNDLE_INSTALLED || status() == SetupStatus.UPDATE_AVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUpdateAvailable() {
|
||||||
|
return status() == SetupStatus.UPDATE_AVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SetupStatus status() {
|
||||||
|
sendStartEvent(SetupAction.CHECK, name(), 0);
|
||||||
|
try {
|
||||||
|
final var status = getStatus();
|
||||||
|
sendEndEvent(SetupAction.CHECK, name(), 1);
|
||||||
|
return status;
|
||||||
|
} catch (final SetupException e) {
|
||||||
|
logger.error("Error getting status of {}", name(), e);
|
||||||
|
sendEndEvent(SetupAction.CHECK, name(), 1);
|
||||||
|
return SetupStatus.ERRORED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Retrieves the setup status
|
||||||
|
* @throws SetupException if an error occurred
|
||||||
|
*/
|
||||||
|
protected abstract SetupStatus getStatus() throws SetupException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a start event
|
||||||
|
*
|
||||||
|
* @param action the action
|
||||||
|
* @param target the target
|
||||||
|
* @param progress the progress
|
||||||
|
*/
|
||||||
|
protected void sendStartEvent(final SetupAction action, final String target, final double progress) {
|
||||||
|
sendStartEvent(new SetupEventImpl(action, target, progress, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends an end event
|
||||||
|
*
|
||||||
|
* @param action the action
|
||||||
|
* @param target the target
|
||||||
|
* @param progress the progress
|
||||||
|
*/
|
||||||
|
protected void sendEndEvent(final SetupAction action, final String target, final double progress) {
|
||||||
|
sendEndEvent(new SetupEventImpl(action, target, progress, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a start event
|
||||||
|
*
|
||||||
|
* @param event the event
|
||||||
|
*/
|
||||||
|
protected void sendStartEvent(final SetupEvent event) {
|
||||||
|
listeners.forEach(listener -> listener.onActionStart(event));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends an end event
|
||||||
|
*
|
||||||
|
* @param event the event
|
||||||
|
*/
|
||||||
|
protected void sendEndEvent(final SetupEvent event) {
|
||||||
|
listeners.forEach(listener -> listener.onActionEnd(event));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a folder
|
||||||
|
*
|
||||||
|
* @param path the path
|
||||||
|
* @throws SetupException if an error occurred
|
||||||
|
*/
|
||||||
|
protected void deleteFolder(final Path path) throws SetupException {
|
||||||
|
logger.info("Deleting {}", path);
|
||||||
|
final var index = new AtomicInteger(0);
|
||||||
|
try (final var files = Files.walk(path)) {
|
||||||
|
final var total = getFilesCount(path);
|
||||||
|
files.sorted(Comparator.reverseOrder())
|
||||||
|
.forEach(f -> {
|
||||||
|
try {
|
||||||
|
final var progress = index.get() / (double) total;
|
||||||
|
sendStartEvent(SetupAction.DELETE, f.toString(), progress);
|
||||||
|
Files.deleteIfExists(f);
|
||||||
|
final var newProgress = index.incrementAndGet() / (double) total;
|
||||||
|
sendEndEvent(SetupAction.DELETE, f.toString(), newProgress);
|
||||||
|
} catch (final IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (final IOException | UncheckedIOException e) {
|
||||||
|
throw new SetupException(e);
|
||||||
|
}
|
||||||
|
logger.info("{} deleted", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long getFilesCount(final Path path) throws IOException {
|
||||||
|
try (final var stream = Files.walk(path)) {
|
||||||
|
return stream.count();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.github.gtache.autosubtitle.setup.impl;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.setup.SetupAction;
|
||||||
|
import com.github.gtache.autosubtitle.setup.SetupEvent;
|
||||||
|
import com.github.gtache.autosubtitle.setup.SetupManager;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of {@link SetupEvent}
|
||||||
|
*/
|
||||||
|
public record SetupEventImpl(SetupAction action, String target, double progress,
|
||||||
|
SetupManager setupManager) implements SetupEvent {
|
||||||
|
|
||||||
|
public SetupEventImpl {
|
||||||
|
Objects.requireNonNull(action);
|
||||||
|
Objects.requireNonNull(target);
|
||||||
|
Objects.requireNonNull(setupManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.github.gtache.autosubtitle.subtitle.impl;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.ExtractEvent;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.SubtitleExtractor;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.SubtitleExtractorListener;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base implementation of {@link SubtitleExtractor}
|
||||||
|
*/
|
||||||
|
public abstract class AbstractSubtitleExtractor implements SubtitleExtractor {
|
||||||
|
|
||||||
|
private final Set<SubtitleExtractorListener> listeners;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates the extractor
|
||||||
|
*/
|
||||||
|
protected AbstractSubtitleExtractor() {
|
||||||
|
this.listeners = new HashSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addListener(final SubtitleExtractorListener listener) {
|
||||||
|
listeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeListener(final SubtitleExtractorListener listener) {
|
||||||
|
listeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeListeners() {
|
||||||
|
listeners.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies all listeners
|
||||||
|
*
|
||||||
|
* @param event The event
|
||||||
|
*/
|
||||||
|
protected void notifyListeners(final ExtractEvent event) {
|
||||||
|
listeners.forEach(listener -> listener.listen(event));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.github.gtache.autosubtitle.subtitle.impl;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.ExtractEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of {@link ExtractEvent}
|
||||||
|
*/
|
||||||
|
public record ExtractEventImpl(String message, double progress) implements ExtractEvent {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
package com.github.gtache.autosubtitle.subtitle.impl;
|
package com.github.gtache.autosubtitle.subtitle.impl;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.Subtitle;
|
||||||
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
|
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
|
||||||
import com.github.gtache.autosubtitle.subtitle.SubtitleConverter;
|
import com.github.gtache.autosubtitle.subtitle.SubtitleConverter;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts subtitles to SRT format
|
* Converts subtitles to SRT format
|
||||||
@@ -17,7 +21,13 @@ public class SRTSubtitleConverter implements SubtitleConverter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String convert(final SubtitleCollection collection) {
|
public String convert(final SubtitleCollection collection) {
|
||||||
throw new UnsupportedOperationException("TODO");
|
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);
|
||||||
|
return (i + 1) + "\n" +
|
||||||
|
subtitle.start() + " --> " + subtitle.end() + "\n" +
|
||||||
|
subtitle.content();
|
||||||
|
}).collect(Collectors.joining("\n\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,22 +1,24 @@
|
|||||||
package com.github.gtache.autosubtitle.subtitle.impl;
|
package com.github.gtache.autosubtitle.subtitle.impl;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.Language;
|
||||||
import com.github.gtache.autosubtitle.subtitle.Subtitle;
|
import com.github.gtache.autosubtitle.subtitle.Subtitle;
|
||||||
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
|
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Objects;
|
||||||
|
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of {@link SubtitleCollection}
|
* Implementation of {@link SubtitleCollection}
|
||||||
*/
|
*/
|
||||||
public record SubtitleCollectionImpl(Collection<? extends Subtitle> subtitles,
|
public record SubtitleCollectionImpl(String text, Collection<? extends Subtitle> subtitles,
|
||||||
Locale locale) implements SubtitleCollection {
|
Language language) implements SubtitleCollection {
|
||||||
|
|
||||||
public SubtitleCollectionImpl {
|
public SubtitleCollectionImpl {
|
||||||
|
Objects.requireNonNull(text);
|
||||||
subtitles = List.copyOf(subtitles);
|
subtitles = List.copyOf(subtitles);
|
||||||
requireNonNull(locale);
|
requireNonNull(language);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.github.gtache.autosubtitle.subtitle.impl;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.Bounds;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.Font;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.Subtitle;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of {@link Subtitle}
|
||||||
|
*/
|
||||||
|
public record SubtitleImpl(String content, long start, long end, Font font, Bounds bounds) implements Subtitle {
|
||||||
|
public SubtitleImpl {
|
||||||
|
Objects.requireNonNull(content);
|
||||||
|
if (start < 0) {
|
||||||
|
throw new IllegalArgumentException("start must be >= 0 : " + start);
|
||||||
|
}
|
||||||
|
if (end < 0) {
|
||||||
|
throw new IllegalArgumentException("end must be >= 0 : " + end);
|
||||||
|
}
|
||||||
|
if (start > end) {
|
||||||
|
throw new IllegalArgumentException("start must be <= end : " + start + " > " + end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,9 +8,11 @@ module com.github.gtache.autosubtitle.core {
|
|||||||
requires org.apache.logging.log4j;
|
requires org.apache.logging.log4j;
|
||||||
|
|
||||||
exports com.github.gtache.autosubtitle.impl;
|
exports com.github.gtache.autosubtitle.impl;
|
||||||
exports com.github.gtache.autosubtitle.modules.impl;
|
|
||||||
exports com.github.gtache.autosubtitle.process.impl;
|
exports com.github.gtache.autosubtitle.process.impl;
|
||||||
|
exports com.github.gtache.autosubtitle.setup.impl;
|
||||||
exports com.github.gtache.autosubtitle.subtitle.impl;
|
exports com.github.gtache.autosubtitle.subtitle.impl;
|
||||||
exports com.github.gtache.autosubtitle.setup.modules.impl;
|
|
||||||
exports com.github.gtache.autosubtitle.subtitle.modules.impl;
|
exports com.github.gtache.autosubtitle.modules.impl;
|
||||||
|
exports com.github.gtache.autosubtitle.modules.setup.impl;
|
||||||
|
exports com.github.gtache.autosubtitle.modules.subtitles.impl;
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
package com.github.gtache.autosubtitle.deepl;
|
package com.github.gtache.autosubtitle.deepl;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.Language;
|
||||||
import com.github.gtache.autosubtitle.Translator;
|
import com.github.gtache.autosubtitle.Translator;
|
||||||
import com.github.gtache.autosubtitle.subtitle.Subtitle;
|
import com.github.gtache.autosubtitle.subtitle.Subtitle;
|
||||||
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
|
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DeepL implementation of {@link Translator}
|
* DeepL implementation of {@link Translator}
|
||||||
@@ -17,22 +17,22 @@ public class DeepLTranslator implements Translator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Locale getLocale(final String text) {
|
public Language getLanguage(final String text) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String translate(final String text, final Locale to) {
|
public String translate(final String text, final Language to) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Subtitle translate(final Subtitle subtitle, final Locale to) {
|
public Subtitle translate(final Subtitle subtitle, final Language to) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SubtitleCollection translate(final SubtitleCollection collection, final Locale to) {
|
public SubtitleCollection translate(final SubtitleCollection collection, final Language to) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import com.github.gtache.autosubtitle.deepl.DeepLTranslator;
|
|||||||
import dagger.Binds;
|
import dagger.Binds;
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dagger module for DeepL
|
* Dagger module for DeepL
|
||||||
*/
|
*/
|
||||||
@@ -14,6 +12,5 @@ import javax.inject.Singleton;
|
|||||||
public interface DeepLModule {
|
public interface DeepLModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@Singleton
|
|
||||||
Translator bindsTranslator(final DeepLTranslator translator);
|
Translator bindsTranslator(final DeepLTranslator translator);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ public class FFmpegVideoConverter extends AbstractProcessRunner implements Video
|
|||||||
args.add("-map");
|
args.add("-map");
|
||||||
args.add(String.valueOf(n));
|
args.add(String.valueOf(n));
|
||||||
args.add("-metadata:s:s:" + n);
|
args.add("-metadata:s:s:" + n);
|
||||||
args.add("language=" + c.locale().getISO3Language());
|
args.add("language=" + c.language().iso3());
|
||||||
});
|
});
|
||||||
args.add(out.toString());
|
args.add(out.toString());
|
||||||
run(args);
|
run(args);
|
||||||
@@ -114,7 +114,7 @@ public class FFmpegVideoConverter extends AbstractProcessRunner implements Video
|
|||||||
);
|
);
|
||||||
run(args);
|
run(args);
|
||||||
Files.deleteIfExists(dumpVideoPath);
|
Files.deleteIfExists(dumpVideoPath);
|
||||||
return new FileAudioImpl(audioPath, new AudioInfoImpl("wav"));
|
return new FileAudioImpl(audioPath, new AudioInfoImpl("wav", video.info().duration()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Path getPath(final Video video) throws IOException {
|
private static Path getPath(final Video video) throws IOException {
|
||||||
|
|||||||
@@ -7,17 +7,12 @@ import com.github.gtache.autosubtitle.ffmpeg.FFprobeVideoLoader;
|
|||||||
import dagger.Binds;
|
import dagger.Binds;
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
public interface FFmpegModule {
|
public interface FFmpegModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@Singleton
|
|
||||||
VideoConverter bindsVideoConverter(final FFmpegVideoConverter converter);
|
VideoConverter bindsVideoConverter(final FFmpegVideoConverter converter);
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@Singleton
|
|
||||||
VideoLoader bindsVideoLoader(final FFprobeVideoLoader loader);
|
VideoLoader bindsVideoLoader(final FFprobeVideoLoader loader);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.github.gtache.autosubtitle.setup.modules.ffmpeg;
|
package com.github.gtache.autosubtitle.modules.setup.ffmpeg;
|
||||||
|
|
||||||
import com.github.gtache.autosubtitle.modules.ffmpeg.FFBundledRoot;
|
import com.github.gtache.autosubtitle.modules.ffmpeg.FFBundledRoot;
|
||||||
import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegBundledPath;
|
import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegBundledPath;
|
||||||
@@ -7,9 +7,9 @@ import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegVersion;
|
|||||||
import com.github.gtache.autosubtitle.modules.ffmpeg.FFprobeBundledPath;
|
import com.github.gtache.autosubtitle.modules.ffmpeg.FFprobeBundledPath;
|
||||||
import com.github.gtache.autosubtitle.modules.ffmpeg.FFprobeSystemPath;
|
import com.github.gtache.autosubtitle.modules.ffmpeg.FFprobeSystemPath;
|
||||||
import com.github.gtache.autosubtitle.modules.impl.ExecutableExtension;
|
import com.github.gtache.autosubtitle.modules.impl.ExecutableExtension;
|
||||||
|
import com.github.gtache.autosubtitle.modules.setup.impl.VideoConverterSetup;
|
||||||
import com.github.gtache.autosubtitle.setup.SetupManager;
|
import com.github.gtache.autosubtitle.setup.SetupManager;
|
||||||
import com.github.gtache.autosubtitle.setup.ffmpeg.FFmpegSetupManager;
|
import com.github.gtache.autosubtitle.setup.ffmpeg.FFmpegSetupManager;
|
||||||
import com.github.gtache.autosubtitle.setup.modules.impl.VideoConverterSetup;
|
|
||||||
import dagger.Binds;
|
import dagger.Binds;
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
@@ -5,10 +5,9 @@ import com.github.gtache.autosubtitle.impl.OS;
|
|||||||
import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegBundledPath;
|
import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegBundledPath;
|
||||||
import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegSystemPath;
|
import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegSystemPath;
|
||||||
import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegVersion;
|
import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegVersion;
|
||||||
import com.github.gtache.autosubtitle.process.impl.AbstractProcessRunner;
|
|
||||||
import com.github.gtache.autosubtitle.setup.SetupException;
|
import com.github.gtache.autosubtitle.setup.SetupException;
|
||||||
import com.github.gtache.autosubtitle.setup.SetupManager;
|
|
||||||
import com.github.gtache.autosubtitle.setup.SetupStatus;
|
import com.github.gtache.autosubtitle.setup.SetupStatus;
|
||||||
|
import com.github.gtache.autosubtitle.setup.impl.AbstractSetupManager;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
@@ -21,7 +20,7 @@ import java.util.Objects;
|
|||||||
/**
|
/**
|
||||||
* Manager managing the FFmpeg installation
|
* Manager managing the FFmpeg installation
|
||||||
*/
|
*/
|
||||||
public class FFmpegSetupManager extends AbstractProcessRunner implements SetupManager {
|
public class FFmpegSetupManager extends AbstractSetupManager {
|
||||||
private static final Logger logger = LogManager.getLogger(FFmpegSetupManager.class);
|
private static final Logger logger = LogManager.getLogger(FFmpegSetupManager.class);
|
||||||
private final Path bundledPath;
|
private final Path bundledPath;
|
||||||
private final Path systemPath;
|
private final Path systemPath;
|
||||||
@@ -46,16 +45,17 @@ public class FFmpegSetupManager extends AbstractProcessRunner implements SetupMa
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SetupStatus status() {
|
public SetupStatus getStatus() throws SetupException {
|
||||||
try {
|
try {
|
||||||
if (checkSystemFFmpeg() || checkBundledFFmpeg()) {
|
if (checkSystemFFmpeg()) {
|
||||||
return SetupStatus.INSTALLED;
|
return SetupStatus.SYSTEM_INSTALLED;
|
||||||
|
} else if (checkBundledFFmpeg()) {
|
||||||
|
return SetupStatus.BUNDLE_INSTALLED;
|
||||||
} else {
|
} else {
|
||||||
return SetupStatus.NOT_INSTALLED;
|
return SetupStatus.NOT_INSTALLED;
|
||||||
}
|
}
|
||||||
} catch (final IOException e) {
|
} catch (final IOException e) {
|
||||||
logger.error("Error checking status of {}", name(), e);
|
throw new SetupException(e);
|
||||||
return SetupStatus.ERRORED;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ public class FFmpegSetupManager extends AbstractProcessRunner implements SetupMa
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkSystemFFmpeg() throws IOException {
|
private boolean checkSystemFFmpeg() throws IOException {
|
||||||
final var result = run(systemPath.toString(), "-h");
|
final var result = run(systemPath.toString(), "-version");
|
||||||
return result.exitCode() == 0;
|
return result.exitCode() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ module com.github.gtache.autosubtitle.ffmpeg {
|
|||||||
requires org.apache.logging.log4j;
|
requires org.apache.logging.log4j;
|
||||||
|
|
||||||
exports com.github.gtache.autosubtitle.ffmpeg;
|
exports com.github.gtache.autosubtitle.ffmpeg;
|
||||||
exports com.github.gtache.autosubtitle.modules.ffmpeg;
|
|
||||||
exports com.github.gtache.autosubtitle.setup.ffmpeg;
|
exports com.github.gtache.autosubtitle.setup.ffmpeg;
|
||||||
exports com.github.gtache.autosubtitle.setup.modules.ffmpeg;
|
|
||||||
|
exports com.github.gtache.autosubtitle.modules.ffmpeg;
|
||||||
|
exports com.github.gtache.autosubtitle.modules.setup.ffmpeg;
|
||||||
}
|
}
|
||||||
@@ -42,6 +42,11 @@
|
|||||||
<artifactId>javafx-fxml</artifactId>
|
<artifactId>javafx-fxml</artifactId>
|
||||||
<version>${javafx.version}</version>
|
<version>${javafx.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.controlsfx</groupId>
|
||||||
|
<artifactId>controlsfx</artifactId>
|
||||||
|
<version>11.2.1</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.github.gtache.autosubtitle.gui.fx;
|
||||||
|
|
||||||
|
import javafx.scene.control.Alert;
|
||||||
|
import javafx.scene.control.ButtonType;
|
||||||
|
import javafx.stage.Window;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for FX controllers
|
||||||
|
*/
|
||||||
|
public abstract class AbstractFXController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the current window
|
||||||
|
*/
|
||||||
|
protected abstract Window window();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show an error dialog
|
||||||
|
*
|
||||||
|
* @param title the dialog title
|
||||||
|
* @param message the error message
|
||||||
|
*/
|
||||||
|
protected void showErrorDialog(final String title, final String message) {
|
||||||
|
final var alert = new Alert(Alert.AlertType.ERROR, message, ButtonType.OK);
|
||||||
|
alert.initOwner(window());
|
||||||
|
alert.setHeaderText(null);
|
||||||
|
alert.setTitle(title);
|
||||||
|
alert.showAndWait();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package com.github.gtache.autosubtitle.gui.fx;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.gui.TimeFormatter;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link TimeFormatter} separating values using a colon
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
public class ColonTimeFormatter implements TimeFormatter {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ColonTimeFormatter() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String format(final long elapsed, final long total) {
|
||||||
|
final var elapsedString = format(elapsed);
|
||||||
|
final var totalString = format(total);
|
||||||
|
return elapsedString + "/" + totalString;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String format(final long millis) {
|
||||||
|
final var secondsInMinute = 60;
|
||||||
|
final var secondsInHour = secondsInMinute * 60;
|
||||||
|
var intDuration = (int) millis / 1000;
|
||||||
|
final var durationHours = intDuration / secondsInHour;
|
||||||
|
if (durationHours > 0) {
|
||||||
|
intDuration -= durationHours * secondsInHour;
|
||||||
|
}
|
||||||
|
final var durationMinutes = intDuration / secondsInMinute;
|
||||||
|
final var durationSeconds = intDuration - durationHours * secondsInHour
|
||||||
|
- durationMinutes * secondsInMinute;
|
||||||
|
if (durationHours > 0) {
|
||||||
|
return String.format("%d:%02d:%02d", durationHours, durationMinutes, durationSeconds);
|
||||||
|
} else {
|
||||||
|
return String.format("%02d:%02d", durationMinutes, durationSeconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long parse(final String time) {
|
||||||
|
final var split = time.split(":");
|
||||||
|
final var secondsInMinute = 60;
|
||||||
|
final var secondsInHour = secondsInMinute * 60;
|
||||||
|
return switch (split.length) {
|
||||||
|
case 1 -> toLong(split[0]) * 1000;
|
||||||
|
case 2 -> (toLong(split[0]) * secondsInMinute + toLong(split[1])) * 1000;
|
||||||
|
case 3 -> (toLong(split[0]) * secondsInHour + toLong(split[1]) * secondsInMinute + toLong(split[2])) * 1000;
|
||||||
|
default -> 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private long toLong(final String time) {
|
||||||
|
if (time.startsWith("0")) {
|
||||||
|
return Long.parseLong(time.substring(1));
|
||||||
|
} else {
|
||||||
|
return Long.parseLong(time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,6 @@ public class FXMediaBinder {
|
|||||||
|
|
||||||
public void createBindings() {
|
public void createBindings() {
|
||||||
mediaModel.videoProperty().bindBidirectional(workModel.videoProperty());
|
mediaModel.videoProperty().bindBidirectional(workModel.videoProperty());
|
||||||
Bindings.bindContent(workModel.subtitles(), mediaModel.subtitles());
|
Bindings.bindContent(mediaModel.subtitles(), workModel.subtitles());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.github.gtache.autosubtitle.gui.fx;
|
package com.github.gtache.autosubtitle.gui.fx;
|
||||||
|
|
||||||
import com.github.gtache.autosubtitle.gui.MediaController;
|
import com.github.gtache.autosubtitle.gui.MediaController;
|
||||||
|
import com.github.gtache.autosubtitle.gui.TimeFormatter;
|
||||||
import com.github.gtache.autosubtitle.impl.FileVideoImpl;
|
import com.github.gtache.autosubtitle.impl.FileVideoImpl;
|
||||||
import com.github.gtache.autosubtitle.modules.gui.Pause;
|
import com.github.gtache.autosubtitle.modules.gui.Pause;
|
||||||
import com.github.gtache.autosubtitle.modules.gui.Play;
|
import com.github.gtache.autosubtitle.modules.gui.Play;
|
||||||
@@ -51,14 +52,19 @@ public class FXMediaController implements MediaController {
|
|||||||
private Label volumeValueLabel;
|
private Label volumeValueLabel;
|
||||||
|
|
||||||
private final FXMediaModel model;
|
private final FXMediaModel model;
|
||||||
|
private final FXMediaBinder binder;
|
||||||
|
private final TimeFormatter timeFormatter;
|
||||||
private final Image playImage;
|
private final Image playImage;
|
||||||
private final Image pauseImage;
|
private final Image pauseImage;
|
||||||
|
|
||||||
private boolean wasPlaying;
|
private boolean wasPlaying;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
FXMediaController(final FXMediaModel model, @Play final Image playImage, @Pause final Image pauseImage) {
|
FXMediaController(final FXMediaModel model, final FXMediaBinder binder, final TimeFormatter timeFormatter,
|
||||||
|
@Play final Image playImage, @Pause final Image pauseImage) {
|
||||||
this.model = requireNonNull(model);
|
this.model = requireNonNull(model);
|
||||||
|
this.binder = requireNonNull(binder);
|
||||||
|
this.timeFormatter = requireNonNull(timeFormatter);
|
||||||
this.playImage = requireNonNull(playImage);
|
this.playImage = requireNonNull(playImage);
|
||||||
this.pauseImage = requireNonNull(pauseImage);
|
this.pauseImage = requireNonNull(pauseImage);
|
||||||
}
|
}
|
||||||
@@ -66,7 +72,7 @@ public class FXMediaController implements MediaController {
|
|||||||
@FXML
|
@FXML
|
||||||
private void initialize() {
|
private void initialize() {
|
||||||
volumeValueLabel.textProperty().bind(Bindings.createStringBinding(() -> String.valueOf((int) (model.volume() * 100)), model.volumeProperty()));
|
volumeValueLabel.textProperty().bind(Bindings.createStringBinding(() -> String.valueOf((int) (model.volume() * 100)), model.volumeProperty()));
|
||||||
playLabel.textProperty().bind(Bindings.createStringBinding(() -> formatTime(model.position(), model.duration()), model.positionProperty(), model.durationProperty()));
|
playLabel.textProperty().bind(Bindings.createStringBinding(() -> timeFormatter.format(model.position(), model.duration()), model.positionProperty(), model.durationProperty()));
|
||||||
model.positionProperty().bindBidirectional(playSlider.valueProperty());
|
model.positionProperty().bindBidirectional(playSlider.valueProperty());
|
||||||
|
|
||||||
model.volumeProperty().addListener((observable, oldValue, newValue) -> volumeSlider.setValue(newValue.doubleValue() * 100));
|
model.volumeProperty().addListener((observable, oldValue, newValue) -> volumeSlider.setValue(newValue.doubleValue() * 100));
|
||||||
@@ -97,6 +103,7 @@ public class FXMediaController implements MediaController {
|
|||||||
final var millis = newTime.toMillis();
|
final var millis = newTime.toMillis();
|
||||||
playSlider.setValue(millis);
|
playSlider.setValue(millis);
|
||||||
model.subtitles().forEach(s -> {
|
model.subtitles().forEach(s -> {
|
||||||
|
//TODO optimize
|
||||||
if (s.start() <= millis && s.end() >= millis) {
|
if (s.start() <= millis && s.end() >= millis) {
|
||||||
final var label = createDraggableLabel(s);
|
final var label = createDraggableLabel(s);
|
||||||
stackPane.getChildren().add(label);
|
stackPane.getChildren().add(label);
|
||||||
@@ -144,6 +151,7 @@ public class FXMediaController implements MediaController {
|
|||||||
view.setFitHeight(24);
|
view.setFitHeight(24);
|
||||||
return view;
|
return view;
|
||||||
}, model.isPlayingProperty()));
|
}, model.isPlayingProperty()));
|
||||||
|
binder.createBindings();
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@@ -190,26 +198,4 @@ public class FXMediaController implements MediaController {
|
|||||||
label.setOnMouseEntered(mouseEvent -> label.setCursor(Cursor.HAND));
|
label.setOnMouseEntered(mouseEvent -> label.setCursor(Cursor.HAND));
|
||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String formatTime(final long position, final long duration) {
|
|
||||||
final var positionString = formatTime(position);
|
|
||||||
final var durationString = formatTime(duration);
|
|
||||||
return positionString + "/" + durationString;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String formatTime(final long time) {
|
|
||||||
var intDuration = (int) time / 1000;
|
|
||||||
final var durationHours = intDuration / (60 * 60);
|
|
||||||
if (durationHours > 0) {
|
|
||||||
intDuration -= durationHours * 60 * 60;
|
|
||||||
}
|
|
||||||
final var durationMinutes = intDuration / 60;
|
|
||||||
final var durationSeconds = intDuration - durationHours * 60 * 60
|
|
||||||
- durationMinutes * 60;
|
|
||||||
if (durationHours > 0) {
|
|
||||||
return String.format("%d:%02d:%02d", durationHours, durationMinutes, durationSeconds);
|
|
||||||
} else {
|
|
||||||
return String.format("%02d:%02d", durationMinutes, durationSeconds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import com.github.gtache.autosubtitle.subtitle.EditableSubtitle;
|
|||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.*;
|
import javafx.beans.property.*;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FX implementation of {@link com.github.gtache.autosubtitle.gui.MediaModel}
|
* FX implementation of {@link com.github.gtache.autosubtitle.gui.MediaModel}
|
||||||
@@ -22,7 +22,7 @@ public class FXMediaModel implements MediaModel {
|
|||||||
private final BooleanProperty isPlaying;
|
private final BooleanProperty isPlaying;
|
||||||
private final ReadOnlyLongWrapper duration;
|
private final ReadOnlyLongWrapper duration;
|
||||||
private final LongProperty position;
|
private final LongProperty position;
|
||||||
private final ObservableList<EditableSubtitle> subtitles;
|
private final List<EditableSubtitle> subtitles;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
FXMediaModel() {
|
FXMediaModel() {
|
||||||
@@ -103,7 +103,7 @@ public class FXMediaModel implements MediaModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ObservableList<EditableSubtitle> subtitles() {
|
public List<EditableSubtitle> subtitles() {
|
||||||
return subtitles;
|
return subtitles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package com.github.gtache.autosubtitle.gui.fx;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.gui.ParametersController;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.ExtractionModel;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.ExtractionModelProvider;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.OutputFormat;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.scene.control.TextFormatter;
|
||||||
|
import javafx.stage.Window;
|
||||||
|
import javafx.util.converter.IntegerStringConverter;
|
||||||
|
import javafx.util.converter.NumberStringConverter;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.controlsfx.control.PrefixSelectionComboBox;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
|
import java.util.prefs.BackingStoreException;
|
||||||
|
import java.util.prefs.Preferences;
|
||||||
|
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FX implementation of {@link ParametersController}
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
public class FXParametersController extends AbstractFXController implements ParametersController {
|
||||||
|
|
||||||
|
private static final Logger logger = LogManager.getLogger(FXParametersController.class);
|
||||||
|
@FXML
|
||||||
|
private PrefixSelectionComboBox<ExtractionModel> extractionModelCombobox;
|
||||||
|
@FXML
|
||||||
|
private PrefixSelectionComboBox<OutputFormat> extractionOutputFormat;
|
||||||
|
@FXML
|
||||||
|
private PrefixSelectionComboBox<String> fontFamilyCombobox;
|
||||||
|
@FXML
|
||||||
|
private TextField fontSizeField;
|
||||||
|
|
||||||
|
private final FXParametersModel model;
|
||||||
|
private final Preferences preferences;
|
||||||
|
private final ExtractionModelProvider extractionModelProvider;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
FXParametersController(final FXParametersModel model, final Preferences preferences, final ExtractionModelProvider extractionModelProvider) {
|
||||||
|
this.model = requireNonNull(model);
|
||||||
|
this.preferences = requireNonNull(preferences);
|
||||||
|
this.extractionModelProvider = requireNonNull(extractionModelProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void initialize() {
|
||||||
|
extractionModelCombobox.setItems(model.availableExtractionModels());
|
||||||
|
extractionModelCombobox.valueProperty().bindBidirectional(model.extractionModelProperty());
|
||||||
|
|
||||||
|
extractionOutputFormat.setItems(model.availableOutputFormats());
|
||||||
|
extractionOutputFormat.valueProperty().bindBidirectional(model.outputFormatProperty());
|
||||||
|
|
||||||
|
fontFamilyCombobox.setItems(model.availableFontFamilies());
|
||||||
|
fontFamilyCombobox.valueProperty().bindBidirectional(model.fontFamilyProperty());
|
||||||
|
|
||||||
|
final UnaryOperator<TextFormatter.Change> integerFilter = change -> {
|
||||||
|
final var newText = change.getControlNewText();
|
||||||
|
if (newText.matches("[1-9]\\d*")) {
|
||||||
|
return change;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
fontSizeField.setTextFormatter(new TextFormatter<>(new IntegerStringConverter(), 0, integerFilter));
|
||||||
|
|
||||||
|
fontSizeField.textProperty().bindBidirectional(model.fontSizeProperty(), new NumberStringConverter());
|
||||||
|
|
||||||
|
loadPreferences();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 fontSize = preferences.getInt("fontSize", model.fontSize());
|
||||||
|
|
||||||
|
model.setExtractionModel(extractionModelProvider.getExtractionModel(extractionModel));
|
||||||
|
model.setOutputFormat(OutputFormat.valueOf(outputFormat));
|
||||||
|
model.setFontFamily(fontFamily);
|
||||||
|
model.setFontSize(fontSize);
|
||||||
|
logger.info("Loaded preferences");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save() {
|
||||||
|
logger.info("Saving preferences");
|
||||||
|
preferences.put("extractionModel", model.extractionModel().name());
|
||||||
|
preferences.put("outputFormat", model.outputFormat().name());
|
||||||
|
preferences.put("fontFamily", model.fontFamily());
|
||||||
|
preferences.putInt("fontSize", model.fontSize());
|
||||||
|
try {
|
||||||
|
preferences.flush();
|
||||||
|
logger.info("Preferences saved");
|
||||||
|
} catch (final BackingStoreException e) {
|
||||||
|
logger.error("Error saving preferences", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
loadPreferences();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FXParametersModel model() {
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void savePressed() {
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void resetPressed() {
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Window window() {
|
||||||
|
return extractionModelCombobox.getScene().getWindow();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
package com.github.gtache.autosubtitle.gui.fx;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.gui.ParametersModel;
|
||||||
|
import com.github.gtache.autosubtitle.modules.gui.FontFamily;
|
||||||
|
import com.github.gtache.autosubtitle.modules.gui.FontSize;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.ExtractionModel;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.ExtractionModelProvider;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.OutputFormat;
|
||||||
|
import javafx.beans.property.IntegerProperty;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.property.StringProperty;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FX implementation of {@link ParametersModel}
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
public class FXParametersModel implements ParametersModel {
|
||||||
|
|
||||||
|
private final ObservableList<ExtractionModel> availableExtractionModels;
|
||||||
|
private final ObjectProperty<ExtractionModel> extractionModel;
|
||||||
|
private final ObservableList<OutputFormat> availableOutputFormats;
|
||||||
|
private final ObjectProperty<OutputFormat> outputFormat;
|
||||||
|
private final ObservableList<String> availableFontFamilies;
|
||||||
|
private final StringProperty fontFamily;
|
||||||
|
private final IntegerProperty fontSize;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
FXParametersModel(final ExtractionModelProvider extractionModelProvider, @FontFamily final String defaultFontFamily, @FontSize final int defaultFontSize) {
|
||||||
|
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.availableFontFamilies = FXCollections.unmodifiableObservableList(FXCollections.observableArrayList("Arial"));
|
||||||
|
this.fontFamily = new SimpleStringProperty(defaultFontFamily);
|
||||||
|
this.fontSize = new SimpleIntegerProperty(defaultFontSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ObservableList<ExtractionModel> availableExtractionModels() {
|
||||||
|
return availableExtractionModels;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExtractionModel extractionModel() {
|
||||||
|
return extractionModel.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setExtractionModel(final ExtractionModel model) {
|
||||||
|
extractionModel.set(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectProperty<ExtractionModel> extractionModelProperty() {
|
||||||
|
return extractionModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ObservableList<OutputFormat> availableOutputFormats() {
|
||||||
|
return availableOutputFormats;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OutputFormat outputFormat() {
|
||||||
|
return outputFormat.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOutputFormat(final OutputFormat format) {
|
||||||
|
outputFormat.set(format);
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectProperty<OutputFormat> outputFormatProperty() {
|
||||||
|
return outputFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ObservableList<String> availableFontFamilies() {
|
||||||
|
return availableFontFamilies;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String fontFamily() {
|
||||||
|
return fontFamily.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFontFamily(final String fontFamily) {
|
||||||
|
this.fontFamily.set(fontFamily);
|
||||||
|
}
|
||||||
|
|
||||||
|
StringProperty fontFamilyProperty() {
|
||||||
|
return fontFamily;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int fontSize() {
|
||||||
|
return fontSize.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFontSize(final int fontSize) {
|
||||||
|
this.fontSize.set(fontSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
IntegerProperty fontSizeProperty() {
|
||||||
|
return fontSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,20 @@
|
|||||||
package com.github.gtache.autosubtitle.gui.fx;
|
package com.github.gtache.autosubtitle.gui.fx;
|
||||||
|
|
||||||
import com.github.gtache.autosubtitle.gui.SetupController;
|
import com.github.gtache.autosubtitle.gui.SetupController;
|
||||||
|
import com.github.gtache.autosubtitle.modules.setup.impl.SubtitleExtractorSetup;
|
||||||
|
import com.github.gtache.autosubtitle.modules.setup.impl.TranslatorSetup;
|
||||||
|
import com.github.gtache.autosubtitle.modules.setup.impl.VideoConverterSetup;
|
||||||
|
import com.github.gtache.autosubtitle.setup.SetupEvent;
|
||||||
import com.github.gtache.autosubtitle.setup.SetupException;
|
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.SetupManager;
|
||||||
import com.github.gtache.autosubtitle.setup.SetupStatus;
|
import com.github.gtache.autosubtitle.setup.SetupStatus;
|
||||||
import com.github.gtache.autosubtitle.setup.modules.impl.SubtitleExtractorSetup;
|
import javafx.application.Platform;
|
||||||
import com.github.gtache.autosubtitle.setup.modules.impl.TranslatorSetup;
|
|
||||||
import com.github.gtache.autosubtitle.setup.modules.impl.VideoConverterSetup;
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.property.DoubleProperty;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.StringProperty;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.Alert;
|
|
||||||
import javafx.scene.control.ButtonType;
|
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.MenuButton;
|
import javafx.scene.control.MenuButton;
|
||||||
import javafx.scene.control.MenuItem;
|
import javafx.scene.control.MenuItem;
|
||||||
@@ -27,12 +30,13 @@ import java.util.HashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FX implementation of {@link SetupController}
|
* FX implementation of {@link SetupController}
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
public class FXSetupController implements SetupController {
|
public class FXSetupController extends AbstractFXController implements SetupController, SetupListener {
|
||||||
|
|
||||||
private static final Logger logger = LogManager.getLogger(FXSetupController.class);
|
private static final Logger logger = LogManager.getLogger(FXSetupController.class);
|
||||||
|
|
||||||
@@ -75,6 +79,8 @@ public class FXSetupController implements SetupController {
|
|||||||
private final SetupManager translatorManager;
|
private final SetupManager translatorManager;
|
||||||
|
|
||||||
private final Map<SetupManager, ObjectProperty<SetupStatus>> statusMap;
|
private final Map<SetupManager, ObjectProperty<SetupStatus>> statusMap;
|
||||||
|
private final Map<SetupManager, StringProperty> setupProgressMessageMap;
|
||||||
|
private final Map<SetupManager, DoubleProperty> setupProgressMap;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
FXSetupController(final FXSetupModel model,
|
FXSetupController(final FXSetupModel model,
|
||||||
@@ -86,6 +92,8 @@ public class FXSetupController implements SetupController {
|
|||||||
this.extractorManager = Objects.requireNonNull(extractorManager);
|
this.extractorManager = Objects.requireNonNull(extractorManager);
|
||||||
this.translatorManager = Objects.requireNonNull(translatorManager);
|
this.translatorManager = Objects.requireNonNull(translatorManager);
|
||||||
statusMap = HashMap.newHashMap(3);
|
statusMap = HashMap.newHashMap(3);
|
||||||
|
setupProgressMessageMap = HashMap.newHashMap(3);
|
||||||
|
setupProgressMap = HashMap.newHashMap(3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@@ -93,6 +101,16 @@ public class FXSetupController implements SetupController {
|
|||||||
statusMap.put(converterManager, model.videoConverterStatusProperty());
|
statusMap.put(converterManager, model.videoConverterStatusProperty());
|
||||||
statusMap.put(extractorManager, model.subtitleExtractorStatusProperty());
|
statusMap.put(extractorManager, model.subtitleExtractorStatusProperty());
|
||||||
statusMap.put(translatorManager, model.translatorStatusProperty());
|
statusMap.put(translatorManager, model.translatorStatusProperty());
|
||||||
|
setupProgressMessageMap.put(converterManager, model.videoConverterSetupProgressLabelProperty());
|
||||||
|
setupProgressMessageMap.put(extractorManager, model.subtitleExtractorSetupProgressLabelProperty());
|
||||||
|
setupProgressMessageMap.put(translatorManager, model.translatorSetupProgressLabelProperty());
|
||||||
|
setupProgressMap.put(converterManager, model.videoConverterSetupProgressProperty());
|
||||||
|
setupProgressMap.put(extractorManager, model.subtitleExtractorSetupProgressProperty());
|
||||||
|
setupProgressMap.put(translatorManager, model.translatorSetupProgressProperty());
|
||||||
|
|
||||||
|
bindMenu(converterButton, converterManager);
|
||||||
|
bindMenu(extractorButton, extractorManager);
|
||||||
|
bindMenu(translatorButton, translatorManager);
|
||||||
|
|
||||||
model.setSubtitleExtractorStatus(extractorManager.status());
|
model.setSubtitleExtractorStatus(extractorManager.status());
|
||||||
model.setVideoConverterStatus(converterManager.status());
|
model.setVideoConverterStatus(converterManager.status());
|
||||||
@@ -109,21 +127,17 @@ public class FXSetupController implements SetupController {
|
|||||||
extractorProgress.progressProperty().bindBidirectional(model.subtitleExtractorSetupProgressProperty());
|
extractorProgress.progressProperty().bindBidirectional(model.subtitleExtractorSetupProgressProperty());
|
||||||
translatorProgress.progressProperty().bindBidirectional(model.translatorSetupProgressProperty());
|
translatorProgress.progressProperty().bindBidirectional(model.translatorSetupProgressProperty());
|
||||||
|
|
||||||
converterProgress.visibleProperty().bind(model.videoConverterSetupProgressProperty().greaterThan(0));
|
converterProgress.visibleProperty().bind(model.videoConverterSetupProgressProperty().greaterThan(-2));
|
||||||
extractorProgress.visibleProperty().bind(model.subtitleExtractorSetupProgressProperty().greaterThan(0));
|
extractorProgress.visibleProperty().bind(model.subtitleExtractorSetupProgressProperty().greaterThan(-2));
|
||||||
translatorProgress.visibleProperty().bind(model.translatorSetupProgressProperty().greaterThan(0));
|
translatorProgress.visibleProperty().bind(model.translatorSetupProgressProperty().greaterThan(-2));
|
||||||
|
|
||||||
converterProgressLabel.textProperty().bind(model.videoConverterSetupProgressLabelProperty());
|
converterProgressLabel.textProperty().bind(model.videoConverterSetupProgressLabelProperty());
|
||||||
extractorProgressLabel.textProperty().bind(model.subtitleExtractorSetupProgressLabelProperty());
|
extractorProgressLabel.textProperty().bind(model.subtitleExtractorSetupProgressLabelProperty());
|
||||||
translatorProgressLabel.textProperty().bind(model.translatorSetupProgressLabelProperty());
|
translatorProgressLabel.textProperty().bind(model.translatorSetupProgressLabelProperty());
|
||||||
|
|
||||||
converterProgressLabel.visibleProperty().bind(model.videoConverterSetupProgressProperty().greaterThan(0));
|
converterProgressLabel.visibleProperty().bind(converterProgress.visibleProperty());
|
||||||
extractorProgressLabel.visibleProperty().bind(model.subtitleExtractorSetupProgressProperty().greaterThan(0));
|
extractorProgressLabel.visibleProperty().bind(extractorProgress.visibleProperty());
|
||||||
translatorProgressLabel.visibleProperty().bind(model.translatorSetupProgressProperty().greaterThan(0));
|
translatorProgressLabel.visibleProperty().bind(translatorProgress.visibleProperty());
|
||||||
|
|
||||||
bindMenu(converterButton, converterManager);
|
|
||||||
bindMenu(extractorButton, extractorManager);
|
|
||||||
bindMenu(translatorButton, translatorManager);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void bindMenu(final MenuButton button, final SetupManager setupManager) {
|
private void bindMenu(final MenuButton button, final SetupManager setupManager) {
|
||||||
@@ -132,23 +146,23 @@ public class FXSetupController implements SetupController {
|
|||||||
button.getItems().clear();
|
button.getItems().clear();
|
||||||
switch (newValue) {
|
switch (newValue) {
|
||||||
case NOT_INSTALLED -> {
|
case NOT_INSTALLED -> {
|
||||||
final var installItem = new MenuItem(resources.getString("setup.menu.install"));
|
final var installItem = new MenuItem(resources.getString("setup.menu.install.label"));
|
||||||
installItem.setOnAction(e -> tryInstall(setupManager));
|
installItem.setOnAction(e -> tryInstall(setupManager));
|
||||||
button.getItems().add(installItem);
|
button.getItems().add(installItem);
|
||||||
}
|
}
|
||||||
case INSTALLED -> {
|
case BUNDLE_INSTALLED -> {
|
||||||
final var reinstallItem = new MenuItem(resources.getString("setup.menu.reinstall"));
|
final var reinstallItem = new MenuItem(resources.getString("setup.menu.reinstall.label"));
|
||||||
reinstallItem.setOnAction(e -> tryReinstall(setupManager));
|
reinstallItem.setOnAction(e -> tryReinstall(setupManager));
|
||||||
final var uninstallItem = new MenuItem(resources.getString("setup.menu.uninstall"));
|
final var uninstallItem = new MenuItem(resources.getString("setup.menu.uninstall.label"));
|
||||||
uninstallItem.setOnAction(e -> tryUninstall(setupManager));
|
uninstallItem.setOnAction(e -> tryUninstall(setupManager));
|
||||||
button.getItems().addAll(reinstallItem, uninstallItem);
|
button.getItems().addAll(reinstallItem, uninstallItem);
|
||||||
}
|
}
|
||||||
case UPDATE_AVAILABLE -> {
|
case UPDATE_AVAILABLE -> {
|
||||||
final var updateItem = new MenuItem(resources.getString("setup.menu.update"));
|
final var updateItem = new MenuItem(resources.getString("setup.menu.update.label"));
|
||||||
updateItem.setOnAction(e -> tryUpdate(setupManager));
|
updateItem.setOnAction(e -> tryUpdate(setupManager));
|
||||||
final var reinstallItem = new MenuItem(resources.getString("setup.menu.reinstall"));
|
final var reinstallItem = new MenuItem(resources.getString("setup.menu.reinstall.label"));
|
||||||
reinstallItem.setOnAction(e -> tryReinstall(setupManager));
|
reinstallItem.setOnAction(e -> tryReinstall(setupManager));
|
||||||
final var uninstallItem = new MenuItem(resources.getString("setup.menu.uninstall"));
|
final var uninstallItem = new MenuItem(resources.getString("setup.menu.uninstall.label"));
|
||||||
uninstallItem.setOnAction(e -> tryUninstall(setupManager));
|
uninstallItem.setOnAction(e -> tryUninstall(setupManager));
|
||||||
button.getItems().addAll(updateItem, reinstallItem, uninstallItem);
|
button.getItems().addAll(updateItem, reinstallItem, uninstallItem);
|
||||||
}
|
}
|
||||||
@@ -240,33 +254,67 @@ public class FXSetupController implements SetupController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void trySetup(final SetupManager manager, final SetupConsumer consumer, final String operation) {
|
private void trySetup(final SetupManager manager, final SetupConsumer consumer, final String operation) {
|
||||||
try {
|
manager.addListener(this);
|
||||||
consumer.accept(manager);
|
CompletableFuture.runAsync(() -> {
|
||||||
statusMap.get(manager).set(manager.status());
|
try {
|
||||||
} catch (final SetupException e) {
|
consumer.accept(manager);
|
||||||
logger.error("Error {}ing {}", operation, manager.name(), e);
|
Platform.runLater(() -> {
|
||||||
showErrorDialog(resources.getString("setup." + operation + ".error.title"), MessageFormat.format(resources.getString("setup." + operation + ".error.message"), e.getMessage()));
|
statusMap.get(manager).set(manager.status());
|
||||||
}
|
setupProgressMap.get(manager).set(-2);
|
||||||
|
});
|
||||||
|
} catch (final SetupException e) {
|
||||||
|
logger.error("Error {}ing {}", operation, manager.name(), e);
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
statusMap.get(manager).set(SetupStatus.ERRORED);
|
||||||
|
setupProgressMap.get(manager).set(-2);
|
||||||
|
showErrorDialog(resources.getString("setup." + operation + ".error.title"),
|
||||||
|
MessageFormat.format(resources.getString("setup." + operation + ".error.label"), e.getMessage()));
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
manager.removeListener(this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActionStart(final SetupEvent event) {
|
||||||
|
final var action = event.action();
|
||||||
|
final var target = event.target();
|
||||||
|
final var display = MessageFormat.format(resources.getString("setup.event." + action.name().toLowerCase() + ".start.label"), target);
|
||||||
|
onAction(event, display);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActionEnd(final SetupEvent event) {
|
||||||
|
final var action = event.action();
|
||||||
|
final var target = event.target();
|
||||||
|
final var display = MessageFormat.format(resources.getString("setup.event." + action.name().toLowerCase() + ".end.label"), target);
|
||||||
|
onAction(event, display);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onAction(final SetupEvent event, final String display) {
|
||||||
|
final var manager = event.setupManager();
|
||||||
|
final var property = setupProgressMessageMap.get(manager);
|
||||||
|
final var progress = event.progress();
|
||||||
|
final var progressProperty = setupProgressMap.get(manager);
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
property.set(display);
|
||||||
|
progressProperty.set(progress);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
private interface SetupConsumer {
|
private interface SetupConsumer {
|
||||||
|
|
||||||
void accept(SetupManager manager) throws SetupException;
|
void accept(SetupManager manager) throws SetupException;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void showErrorDialog(final String title, final String message) {
|
|
||||||
final var alert = new Alert(Alert.AlertType.ERROR, message, ButtonType.OK);
|
|
||||||
alert.setTitle(title);
|
|
||||||
alert.showAndWait();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FXSetupModel model() {
|
public FXSetupModel model() {
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Window window() {
|
@Override
|
||||||
|
protected Window window() {
|
||||||
return converterNameLabel.getScene().getWindow();
|
return converterNameLabel.getScene().getWindow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,20 +39,20 @@ public class FXSetupModel implements SetupModel {
|
|||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
FXSetupModel() {
|
FXSetupModel() {
|
||||||
this.subtitleExtractorStatus = new SimpleObjectProperty<>(SetupStatus.NOT_INSTALLED);
|
this.subtitleExtractorStatus = new SimpleObjectProperty<>(SetupStatus.ERRORED);
|
||||||
this.subtitleExtractorInstalled = new ReadOnlyBooleanWrapper(false);
|
this.subtitleExtractorInstalled = new ReadOnlyBooleanWrapper(false);
|
||||||
this.subtitleExtractorUpdateAvailable = new ReadOnlyBooleanWrapper(false);
|
this.subtitleExtractorUpdateAvailable = new ReadOnlyBooleanWrapper(false);
|
||||||
this.subtitleExtractorSetupProgress = new SimpleDoubleProperty(0);
|
this.subtitleExtractorSetupProgress = new SimpleDoubleProperty(-2);
|
||||||
this.subtitleExtractorSetupProgressLabel = new SimpleStringProperty("");
|
this.subtitleExtractorSetupProgressLabel = new SimpleStringProperty("");
|
||||||
this.videoConverterStatus = new SimpleObjectProperty<>(SetupStatus.NOT_INSTALLED);
|
this.videoConverterStatus = new SimpleObjectProperty<>(SetupStatus.ERRORED);
|
||||||
this.videoConverterInstalled = new ReadOnlyBooleanWrapper(false);
|
this.videoConverterInstalled = new ReadOnlyBooleanWrapper(false);
|
||||||
this.videoConverterUpdateAvailable = new ReadOnlyBooleanWrapper(false);
|
this.videoConverterUpdateAvailable = new ReadOnlyBooleanWrapper(false);
|
||||||
this.videoConverterSetupProgress = new SimpleDoubleProperty(0);
|
this.videoConverterSetupProgress = new SimpleDoubleProperty(-2);
|
||||||
this.videoConverterSetupProgressLabel = new SimpleStringProperty("");
|
this.videoConverterSetupProgressLabel = new SimpleStringProperty("");
|
||||||
this.translatorStatus = new SimpleObjectProperty<>(SetupStatus.NOT_INSTALLED);
|
this.translatorStatus = new SimpleObjectProperty<>(SetupStatus.ERRORED);
|
||||||
this.translatorInstalled = new ReadOnlyBooleanWrapper(false);
|
this.translatorInstalled = new ReadOnlyBooleanWrapper(false);
|
||||||
this.translatorUpdateAvailable = new ReadOnlyBooleanWrapper(false);
|
this.translatorUpdateAvailable = new ReadOnlyBooleanWrapper(false);
|
||||||
this.translatorSetupProgress = new SimpleDoubleProperty(0);
|
this.translatorSetupProgress = new SimpleDoubleProperty(-2);
|
||||||
this.translatorSetupProgressLabel = new SimpleStringProperty("");
|
this.translatorSetupProgressLabel = new SimpleStringProperty("");
|
||||||
|
|
||||||
subtitleExtractorInstalled.bind(Bindings.createBooleanBinding(() -> subtitleExtractorStatus.get().isInstalled(), subtitleExtractorStatus));
|
subtitleExtractorInstalled.bind(Bindings.createBooleanBinding(() -> subtitleExtractorStatus.get().isInstalled(), subtitleExtractorStatus));
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.github.gtache.autosubtitle.gui.fx;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class FXWorkBinder {
|
||||||
|
|
||||||
|
private final FXWorkModel workModel;
|
||||||
|
private final FXParametersModel parametersModel;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
FXWorkBinder(final FXWorkModel workModel, final FXParametersModel parametersModel) {
|
||||||
|
this.workModel = Objects.requireNonNull(workModel);
|
||||||
|
this.parametersModel = Objects.requireNonNull(parametersModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
void createBindings() {
|
||||||
|
workModel.extractionModelProperty().bind(parametersModel.extractionModelProperty());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,38 +1,48 @@
|
|||||||
package com.github.gtache.autosubtitle.gui.fx;
|
package com.github.gtache.autosubtitle.gui.fx;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.Language;
|
||||||
import com.github.gtache.autosubtitle.Translator;
|
import com.github.gtache.autosubtitle.Translator;
|
||||||
import com.github.gtache.autosubtitle.VideoConverter;
|
import com.github.gtache.autosubtitle.VideoConverter;
|
||||||
import com.github.gtache.autosubtitle.VideoLoader;
|
import com.github.gtache.autosubtitle.VideoLoader;
|
||||||
|
import com.github.gtache.autosubtitle.gui.TimeFormatter;
|
||||||
import com.github.gtache.autosubtitle.gui.WorkController;
|
import com.github.gtache.autosubtitle.gui.WorkController;
|
||||||
|
import com.github.gtache.autosubtitle.gui.WorkStatus;
|
||||||
import com.github.gtache.autosubtitle.subtitle.EditableSubtitle;
|
import com.github.gtache.autosubtitle.subtitle.EditableSubtitle;
|
||||||
import com.github.gtache.autosubtitle.subtitle.Subtitle;
|
import com.github.gtache.autosubtitle.subtitle.ExtractEvent;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.ExtractException;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
|
||||||
import com.github.gtache.autosubtitle.subtitle.SubtitleExtractor;
|
import com.github.gtache.autosubtitle.subtitle.SubtitleExtractor;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.SubtitleExtractorListener;
|
||||||
import com.github.gtache.autosubtitle.subtitle.fx.ObservableSubtitleImpl;
|
import com.github.gtache.autosubtitle.subtitle.fx.ObservableSubtitleImpl;
|
||||||
import com.github.gtache.autosubtitle.subtitle.impl.SubtitleCollectionImpl;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.Alert;
|
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.ButtonType;
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.ProgressBar;
|
||||||
import javafx.scene.control.TableColumn;
|
import javafx.scene.control.TableColumn;
|
||||||
import javafx.scene.control.TableView;
|
import javafx.scene.control.TableView;
|
||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.scene.control.cell.TextFieldTableCell;
|
||||||
|
import javafx.scene.input.KeyCode;
|
||||||
import javafx.stage.FileChooser;
|
import javafx.stage.FileChooser;
|
||||||
import javafx.stage.Window;
|
import javafx.stage.Window;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.controlsfx.control.CheckComboBox;
|
||||||
|
import org.controlsfx.control.PrefixSelectionComboBox;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
import java.util.stream.Collectors;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.CompletionException;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
@@ -41,14 +51,14 @@ import static java.util.Objects.requireNonNull;
|
|||||||
* FX implementation of {@link WorkController}
|
* FX implementation of {@link WorkController}
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
public class FXWorkController implements WorkController {
|
public class FXWorkController extends AbstractFXController implements WorkController, SubtitleExtractorListener {
|
||||||
|
|
||||||
private static final Logger logger = LogManager.getLogger(FXWorkController.class);
|
private static final Logger logger = LogManager.getLogger(FXWorkController.class);
|
||||||
|
|
||||||
private static final List<String> VIDEO_EXTENSIONS = List.of(".webm", ".mkv", ".flv", ".vob", ".ogv", ".ogg",
|
private static final List<String> VIDEO_EXTENSIONS = Stream.of("webm", "mkv", "flv", "vob", "ogv", "ogg",
|
||||||
".drc", ".gif", ".gifv", ".mng", ".avi", ".mts", ".m2ts", ".ts", ".mov", ".qt", ".wmv", ".yuv", ".rm", ".rmvb",
|
"drc", "gif", "gifv", "mng", "avi", "mts", "m2ts", "ts", "mov", "qt", "wmv", "yuv", "rm", "rmvb",
|
||||||
".viv", ".asf", ".amv", ".mp4", ".m4p", ".m4v", ".mpg", ".mp2", ".mpeg", ".mpe", ".mpv", ".m2v", ".m4v", ".svi",
|
"viv", "asf", "amv", "mp4", "m4p", "m4v", "mpg", "mp2", "mpeg", "mpe", "mpv", "m2v", "m4v", "svi",
|
||||||
".3gp", ".3g2", ".mxf", ".roq", ".nsv", ".flv", ".f4v", ".f4p", ".f4a", ".f4b");
|
"3gp", "3g2", "mxf", "roq", "nsv", "flv", "f4v", "f4p", "f4a", "f4b").map(s -> "*." + s).toList();
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TextField fileField;
|
private TextField fileField;
|
||||||
@@ -69,56 +79,125 @@ public class FXWorkController implements WorkController {
|
|||||||
@FXML
|
@FXML
|
||||||
private TableColumn<EditableSubtitle, String> textColumn;
|
private TableColumn<EditableSubtitle, String> textColumn;
|
||||||
@FXML
|
@FXML
|
||||||
private TextField translationField;
|
|
||||||
@FXML
|
|
||||||
private FXMediaController mediaController;
|
private FXMediaController mediaController;
|
||||||
|
@FXML
|
||||||
|
private Button addSubtitleButton;
|
||||||
|
@FXML
|
||||||
|
private PrefixSelectionComboBox<Language> languageCombobox;
|
||||||
|
@FXML
|
||||||
|
private CheckComboBox<Language> translationsCombobox;
|
||||||
|
@FXML
|
||||||
|
private Label progressLabel;
|
||||||
|
@FXML
|
||||||
|
private ProgressBar progressBar;
|
||||||
|
@FXML
|
||||||
|
private Label progressDetailLabel;
|
||||||
@FXML
|
@FXML
|
||||||
private ResourceBundle resources;
|
private ResourceBundle resources;
|
||||||
|
|
||||||
private final FXWorkModel model;
|
private final FXWorkModel model;
|
||||||
|
private final FXWorkBinder binder;
|
||||||
private final SubtitleExtractor subtitleExtractor;
|
private final SubtitleExtractor subtitleExtractor;
|
||||||
private final VideoConverter videoConverter;
|
private final VideoConverter videoConverter;
|
||||||
private final VideoLoader videoLoader;
|
private final VideoLoader videoLoader;
|
||||||
private final Translator translator;
|
private final Translator translator;
|
||||||
private final FXMediaBinder binder;
|
private final TimeFormatter timeFormatter;
|
||||||
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
FXWorkController(final FXWorkModel model, final SubtitleExtractor subtitleExtractor, final VideoLoader videoLoader,
|
FXWorkController(final FXWorkModel model, final FXWorkBinder binder, final SubtitleExtractor subtitleExtractor, final VideoLoader videoLoader,
|
||||||
final VideoConverter videoConverter, final Translator translator, final FXMediaBinder binder) {
|
final VideoConverter videoConverter, final Translator translator, final TimeFormatter timeFormatter) {
|
||||||
this.model = requireNonNull(model);
|
this.model = requireNonNull(model);
|
||||||
|
this.binder = requireNonNull(binder);
|
||||||
this.subtitleExtractor = requireNonNull(subtitleExtractor);
|
this.subtitleExtractor = requireNonNull(subtitleExtractor);
|
||||||
this.videoConverter = requireNonNull(videoConverter);
|
this.videoConverter = requireNonNull(videoConverter);
|
||||||
this.videoLoader = requireNonNull(videoLoader);
|
this.videoLoader = requireNonNull(videoLoader);
|
||||||
this.translator = requireNonNull(translator);
|
this.translator = requireNonNull(translator);
|
||||||
this.binder = requireNonNull(binder);
|
this.timeFormatter = requireNonNull(timeFormatter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private void initialize() {
|
private void initialize() {
|
||||||
extractButton.disableProperty().bind(model.videoProperty().isNull());
|
languageCombobox.valueProperty().bindBidirectional(model.videoLanguageProperty());
|
||||||
|
languageCombobox.setItems(model.availableVideoLanguages());
|
||||||
|
languageCombobox.setConverter(new LanguageStringConverter());
|
||||||
|
translationsCombobox.setConverter(new LanguageStringConverter());
|
||||||
|
Bindings.bindContent(translationsCombobox.getItems(), model.availableTranslationsLanguage());
|
||||||
|
Bindings.bindContent(model.translations(), translationsCombobox.getCheckModel().getCheckedItems());
|
||||||
|
|
||||||
|
extractButton.disableProperty().bind(model.videoProperty().isNull().or(model.statusProperty().isNotEqualTo(WorkStatus.IDLE)));
|
||||||
resetButton.disableProperty().bind(Bindings.isEmpty(model.subtitles()));
|
resetButton.disableProperty().bind(Bindings.isEmpty(model.subtitles()));
|
||||||
exportSoftButton.disableProperty().bind(Bindings.isEmpty(model.subtitles()));
|
exportSoftButton.disableProperty().bind(Bindings.isEmpty(model.subtitles()).or(model.statusProperty().isNotEqualTo(WorkStatus.IDLE)));
|
||||||
exportHardButton.disableProperty().bind(Bindings.isEmpty(model.subtitles()));
|
exportHardButton.disableProperty().bind(Bindings.isEmpty(model.subtitles()).or(model.statusProperty().isNotEqualTo(WorkStatus.IDLE)));
|
||||||
|
addSubtitleButton.disableProperty().bind(model.videoProperty().isNull());
|
||||||
|
|
||||||
subtitlesTable.setItems(model.subtitles());
|
subtitlesTable.setItems(model.subtitles());
|
||||||
|
subtitlesTable.setOnKeyPressed(e -> {
|
||||||
|
if (e.getCode().isLetterKey() || e.getCode().isDigitKey()) {
|
||||||
|
editFocusedCell();
|
||||||
|
} else if (e.getCode() == KeyCode.RIGHT ||
|
||||||
|
e.getCode() == KeyCode.TAB) {
|
||||||
|
subtitlesTable.getSelectionModel().selectNext();
|
||||||
|
e.consume();
|
||||||
|
} else if (e.getCode() == KeyCode.LEFT) {
|
||||||
|
subtitlesTable.getSelectionModel().selectPrevious();
|
||||||
|
e.consume();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
startColumn.setCellFactory(TextFieldTableCell.forTableColumn(new TimeStringConverter(timeFormatter)));
|
||||||
startColumn.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue() == null ? null : param.getValue().start()));
|
startColumn.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue() == null ? null : param.getValue().start()));
|
||||||
|
startColumn.setOnEditCommit(e -> {
|
||||||
|
final var subtitle = e.getRowValue();
|
||||||
|
subtitle.setStart(e.getNewValue());
|
||||||
|
subtitlesTable.refresh();
|
||||||
|
});
|
||||||
|
endColumn.setCellFactory(TextFieldTableCell.forTableColumn(new TimeStringConverter(timeFormatter)));
|
||||||
endColumn.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue() == null ? null : param.getValue().end()));
|
endColumn.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue() == null ? null : param.getValue().end()));
|
||||||
textColumn.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue() == null ? "" : param.getValue().content()));
|
endColumn.setOnEditCommit(e -> {
|
||||||
|
final var subtitle = e.getRowValue();
|
||||||
|
subtitle.setEnd(e.getNewValue());
|
||||||
|
subtitlesTable.refresh();
|
||||||
|
});
|
||||||
|
textColumn.setCellFactory(TextFieldTableCell.forTableColumn());
|
||||||
|
textColumn.setCellValueFactory(param -> new SimpleStringProperty(param.getValue() == null ? null : param.getValue().content()));
|
||||||
|
textColumn.setOnEditCommit(e -> {
|
||||||
|
final var subtitle = e.getRowValue();
|
||||||
|
subtitle.setContent(e.getNewValue());
|
||||||
|
subtitlesTable.refresh();
|
||||||
|
});
|
||||||
|
|
||||||
|
subtitlesTable.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> model.selectedSubtitleProperty().set(newValue));
|
||||||
model.selectedSubtitleProperty().addListener((observable, oldValue, newValue) -> {
|
model.selectedSubtitleProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
if (newValue != null) {
|
if (newValue != null) {
|
||||||
mediaController.seek(newValue.start());
|
mediaController.seek(newValue.start());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
progressLabel.textProperty().bind(Bindings.createStringBinding(() -> resources.getString("work.status." + model.status().name().toLowerCase() + ".label"), model.statusProperty()));
|
||||||
|
progressLabel.visibleProperty().bind(model.statusProperty().isNotEqualTo(WorkStatus.IDLE));
|
||||||
|
progressBar.visibleProperty().bind(model.statusProperty().isNotEqualTo(WorkStatus.IDLE));
|
||||||
|
progressDetailLabel.visibleProperty().bind(model.statusProperty().isNotEqualTo(WorkStatus.IDLE));
|
||||||
|
progressLabel.managedProperty().bind(model.statusProperty().isNotEqualTo(WorkStatus.IDLE));
|
||||||
|
progressBar.managedProperty().bind(model.statusProperty().isNotEqualTo(WorkStatus.IDLE));
|
||||||
|
progressDetailLabel.managedProperty().bind(model.statusProperty().isNotEqualTo(WorkStatus.IDLE));
|
||||||
|
progressBar.progressProperty().bindBidirectional(model.progressProperty());
|
||||||
binder.createBindings();
|
binder.createBindings();
|
||||||
|
|
||||||
|
subtitleExtractor.addListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void editFocusedCell() {
|
||||||
|
final var focusedCell = subtitlesTable.getFocusModel().getFocusedCell();
|
||||||
|
if (focusedCell != null) {
|
||||||
|
subtitlesTable.edit(focusedCell.getRow(), focusedCell.getTableColumn());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private void fileButtonPressed() {
|
private void fileButtonPressed() {
|
||||||
final var filePicker = new FileChooser();
|
final var filePicker = new FileChooser();
|
||||||
filePicker.setSelectedExtensionFilter(new FileChooser.ExtensionFilter("Video", VIDEO_EXTENSIONS));
|
final var extensionFilter = new FileChooser.ExtensionFilter("All supported", VIDEO_EXTENSIONS);
|
||||||
|
filePicker.getExtensionFilters().add(extensionFilter);
|
||||||
|
filePicker.setSelectedExtensionFilter(extensionFilter);
|
||||||
final var file = filePicker.showOpenDialog(window());
|
final var file = filePicker.showOpenDialog(window());
|
||||||
if (file != null) {
|
if (file != null) {
|
||||||
loadVideo(file.toPath());
|
loadVideo(file.toPath());
|
||||||
@@ -128,12 +207,32 @@ public class FXWorkController implements WorkController {
|
|||||||
@Override
|
@Override
|
||||||
public void extractSubtitles() {
|
public void extractSubtitles() {
|
||||||
if (model.video() != null) {
|
if (model.video() != null) {
|
||||||
final var subtitles = subtitleExtractor.extract(model.video()).stream().sorted(Comparator.comparing(Subtitle::start).thenComparing(Subtitle::end)).toList();
|
model.setStatus(WorkStatus.EXTRACTING);
|
||||||
final var subtitlesCopy = subtitles.stream().map(ObservableSubtitleImpl::new).toList();
|
CompletableFuture.supplyAsync(this::extractAsync).whenCompleteAsync(this::manageExtractResult, Platform::runLater);
|
||||||
model.subtitles().setAll(subtitles);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SubtitleCollection extractAsync() {
|
||||||
|
try {
|
||||||
|
return subtitleExtractor.extract(model.video(), model.videoLanguage(), model.extractionModel());
|
||||||
|
} catch (final ExtractException e) {
|
||||||
|
throw new CompletionException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void manageExtractResult(final SubtitleCollection newCollection, final Throwable t) {
|
||||||
|
if (t == null) {
|
||||||
|
final var newSubtitles = newCollection.subtitles().stream().map(ObservableSubtitleImpl::new).toList();
|
||||||
|
final var subtitlesCopy = newSubtitles.stream().map(ObservableSubtitleImpl::new).toList();
|
||||||
|
model.subtitles().setAll(newSubtitles);
|
||||||
model.originalSubtitles().clear();
|
model.originalSubtitles().clear();
|
||||||
model.originalSubtitles().addAll(subtitlesCopy);
|
model.originalSubtitles().addAll(subtitlesCopy);
|
||||||
|
model.videoLanguageProperty().set(newCollection.language());
|
||||||
|
} else {
|
||||||
|
logger.error("Error extracting subtitles", t);
|
||||||
|
showErrorDialog(resources.getString("work.extract.error.title"), MessageFormat.format(resources.getString("work.extract.error.label"), t.getMessage()));
|
||||||
}
|
}
|
||||||
|
model.setStatus(WorkStatus.IDLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -144,6 +243,7 @@ public class FXWorkController implements WorkController {
|
|||||||
model.videoProperty().set(loadedVideo);
|
model.videoProperty().set(loadedVideo);
|
||||||
} catch (final IOException e) {
|
} catch (final IOException e) {
|
||||||
logger.error("Error loading video {}", file, e);
|
logger.error("Error loading video {}", file, e);
|
||||||
|
showErrorDialog(resources.getString("work.load.error.title"), MessageFormat.format(resources.getString("work.load.error.label"), e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,17 +252,27 @@ public class FXWorkController implements WorkController {
|
|||||||
final var filePicker = new FileChooser();
|
final var filePicker = new FileChooser();
|
||||||
final var file = filePicker.showSaveDialog(window());
|
final var file = filePicker.showSaveDialog(window());
|
||||||
if (file != null) {
|
if (file != null) {
|
||||||
final var text = model.subtitles().stream().map(Subtitle::content).collect(Collectors.joining(" "));
|
final var baseCollection = model.subtitleCollection();
|
||||||
final var baseCollection = new SubtitleCollectionImpl(model.subtitles(), translator.getLocale(text));
|
final var translations = model.translations();
|
||||||
final var collections = Stream.concat(Stream.of(baseCollection), model.translations().stream().map(l -> translator.translate(baseCollection, l))).toList();
|
model.setStatus(WorkStatus.TRANSLATING);
|
||||||
try {
|
CompletableFuture.supplyAsync(() -> Stream.concat(Stream.of(baseCollection), translations.stream().map(l -> translator.translate(baseCollection, l))).toList())
|
||||||
videoConverter.addSoftSubtitles(model.video(), collections);
|
.thenApplyAsync(c -> {
|
||||||
} catch (final IOException e) {
|
model.setStatus(WorkStatus.EXPORTING);
|
||||||
logger.error("Error exporting subtitles", e);
|
return c;
|
||||||
final var alert = new Alert(Alert.AlertType.ERROR, MessageFormat.format(resources.getString("work.error.export.label"), e.getMessage()), ButtonType.OK);
|
}, Platform::runLater)
|
||||||
alert.setTitle(resources.getString("work.error.export.title"));
|
.thenAcceptAsync(collections -> {
|
||||||
alert.showAndWait();
|
try {
|
||||||
}
|
videoConverter.addSoftSubtitles(model.video(), collections);
|
||||||
|
} catch (final IOException e) {
|
||||||
|
throw new CompletionException(e);
|
||||||
|
}
|
||||||
|
}).whenCompleteAsync((v, t) -> {
|
||||||
|
if (t != null) {
|
||||||
|
logger.error("Error exporting subtitles", t);
|
||||||
|
showErrorDialog(resources.getString("work.export.error.title"), MessageFormat.format(resources.getString("work.export.error.label"), t.getMessage()));
|
||||||
|
}
|
||||||
|
model.setStatus(WorkStatus.IDLE);
|
||||||
|
}, Platform::runLater);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,14 +281,19 @@ public class FXWorkController implements WorkController {
|
|||||||
final var filePicker = new FileChooser();
|
final var filePicker = new FileChooser();
|
||||||
final var file = filePicker.showSaveDialog(window());
|
final var file = filePicker.showSaveDialog(window());
|
||||||
if (file != null) {
|
if (file != null) {
|
||||||
try {
|
CompletableFuture.runAsync(() -> {
|
||||||
videoConverter.addHardSubtitles(model.video(), new SubtitleCollectionImpl(model.subtitles(), Locale.getDefault()));
|
try {
|
||||||
} catch (final IOException e) {
|
videoConverter.addHardSubtitles(model.video(), model.subtitleCollection());
|
||||||
logger.error("Error exporting subtitles", e);
|
} catch (final IOException e) {
|
||||||
final var alert = new Alert(Alert.AlertType.ERROR, MessageFormat.format(resources.getString("work.error.export.label"), e.getMessage()), ButtonType.OK);
|
throw new CompletionException(e);
|
||||||
alert.setTitle(resources.getString("work.error.export.title"));
|
}
|
||||||
alert.showAndWait();
|
}).whenCompleteAsync((v, t) -> {
|
||||||
}
|
if (t != null) {
|
||||||
|
logger.error("Error exporting subtitles", t);
|
||||||
|
showErrorDialog(resources.getString("work.export.error.title"), MessageFormat.format(resources.getString("work.export.error.label"), t.getMessage()));
|
||||||
|
}
|
||||||
|
model.setStatus(WorkStatus.IDLE);
|
||||||
|
}, Platform::runLater);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,4 +316,17 @@ public class FXWorkController implements WorkController {
|
|||||||
private void resetButtonPressed() {
|
private void resetButtonPressed() {
|
||||||
model.subtitles().setAll(model.originalSubtitles());
|
model.subtitles().setAll(model.originalSubtitles());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void addSubtitlePressed() {
|
||||||
|
model.subtitles().add(new ObservableSubtitleImpl("Enter text here..."));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void listen(final ExtractEvent event) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
model.setProgress(event.progress());
|
||||||
|
progressDetailLabel.setText(event.message());
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,21 @@
|
|||||||
package com.github.gtache.autosubtitle.gui.fx;
|
package com.github.gtache.autosubtitle.gui.fx;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.Language;
|
||||||
import com.github.gtache.autosubtitle.Video;
|
import com.github.gtache.autosubtitle.Video;
|
||||||
import com.github.gtache.autosubtitle.gui.WorkModel;
|
import com.github.gtache.autosubtitle.gui.WorkModel;
|
||||||
|
import com.github.gtache.autosubtitle.gui.WorkStatus;
|
||||||
import com.github.gtache.autosubtitle.subtitle.EditableSubtitle;
|
import com.github.gtache.autosubtitle.subtitle.EditableSubtitle;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.ExtractionModel;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.impl.SubtitleCollectionImpl;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.property.DoubleProperty;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
|
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||||
|
import javafx.beans.property.ReadOnlyStringProperty;
|
||||||
|
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||||
|
import javafx.beans.property.SimpleDoubleProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
@@ -11,8 +23,10 @@ import javafx.collections.ObservableList;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FX implementation of {@link WorkModel}
|
* FX implementation of {@link WorkModel}
|
||||||
@@ -21,18 +35,52 @@ import java.util.Locale;
|
|||||||
public class FXWorkModel implements WorkModel {
|
public class FXWorkModel implements WorkModel {
|
||||||
|
|
||||||
private final ObjectProperty<Video> video;
|
private final ObjectProperty<Video> video;
|
||||||
|
private final ReadOnlyObjectWrapper<SubtitleCollection> subtitleCollection;
|
||||||
private final ObservableList<EditableSubtitle> subtitles;
|
private final ObservableList<EditableSubtitle> subtitles;
|
||||||
private final List<EditableSubtitle> originalSubtitles;
|
private final List<EditableSubtitle> originalSubtitles;
|
||||||
private final ObjectProperty<EditableSubtitle> subtitle;
|
private final ObjectProperty<EditableSubtitle> subtitle;
|
||||||
private final ObservableList<Locale> translations;
|
private final ObservableList<Language> availableVideoLanguages;
|
||||||
|
private final ObservableList<Language> availableTranslationLanguages;
|
||||||
|
private final ObjectProperty<ExtractionModel> extractionModel;
|
||||||
|
private final ObjectProperty<Language> videoLanguage;
|
||||||
|
private final ObservableList<Language> translations;
|
||||||
|
private final ReadOnlyStringWrapper text;
|
||||||
|
private final ObjectProperty<WorkStatus> workStatus;
|
||||||
|
private final DoubleProperty progress;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
FXWorkModel() {
|
FXWorkModel() {
|
||||||
this.video = new SimpleObjectProperty<>();
|
this.video = new SimpleObjectProperty<>();
|
||||||
|
this.subtitleCollection = new ReadOnlyObjectWrapper<>();
|
||||||
this.subtitles = FXCollections.observableArrayList();
|
this.subtitles = FXCollections.observableArrayList();
|
||||||
this.originalSubtitles = new ArrayList<>();
|
this.originalSubtitles = new ArrayList<>();
|
||||||
this.subtitle = new SimpleObjectProperty<>();
|
this.subtitle = new SimpleObjectProperty<>();
|
||||||
|
this.availableVideoLanguages =
|
||||||
|
FXCollections.unmodifiableObservableList(FXCollections.observableArrayList(Arrays.stream(Language.values())
|
||||||
|
.sorted((o1, o2) -> {
|
||||||
|
if (o1 == Language.AUTO) {
|
||||||
|
return -1;
|
||||||
|
} else if (o2 == Language.AUTO) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return o1.compareTo(o2);
|
||||||
|
}
|
||||||
|
}).toList()));
|
||||||
|
this.availableTranslationLanguages =
|
||||||
|
FXCollections.observableArrayList(Arrays.stream(Language.values()).filter(l -> l != Language.AUTO).toList());
|
||||||
|
this.videoLanguage = new SimpleObjectProperty<>(Language.AUTO);
|
||||||
this.translations = FXCollections.observableArrayList();
|
this.translations = FXCollections.observableArrayList();
|
||||||
|
this.text = new ReadOnlyStringWrapper("");
|
||||||
|
this.workStatus = new SimpleObjectProperty<>(WorkStatus.IDLE);
|
||||||
|
this.extractionModel = new SimpleObjectProperty<>();
|
||||||
|
this.progress = new SimpleDoubleProperty(-1);
|
||||||
|
text.bind(Bindings.createStringBinding(() ->
|
||||||
|
subtitles.stream().map(EditableSubtitle::content).collect(Collectors.joining(" ")),
|
||||||
|
subtitles));
|
||||||
|
subtitleCollection.bind(Bindings.createObjectBinding(() -> new SubtitleCollectionImpl(text(), subtitles, videoLanguage())));
|
||||||
|
videoLanguage.addListener((observable, oldValue, newValue) -> {
|
||||||
|
FXCollections.observableArrayList(Arrays.stream(Language.values()).filter(l -> l != Language.AUTO && l != newValue).sorted(Comparator.naturalOrder()).toList());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -40,7 +88,30 @@ public class FXWorkModel implements WorkModel {
|
|||||||
return video.get();
|
return video.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObjectProperty<Video> videoProperty() {
|
@Override
|
||||||
|
public ExtractionModel extractionModel() {
|
||||||
|
return extractionModel.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setExtractionModel(final ExtractionModel model) {
|
||||||
|
extractionModel.set(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectProperty<ExtractionModel> extractionModelProperty() {
|
||||||
|
return extractionModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SubtitleCollection subtitleCollection() {
|
||||||
|
return subtitleCollection.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadOnlyObjectProperty<SubtitleCollection> subtitleCollectionProperty() {
|
||||||
|
return subtitleCollection.getReadOnlyProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectProperty<Video> videoProperty() {
|
||||||
return video;
|
return video;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,17 +125,78 @@ public class FXWorkModel implements WorkModel {
|
|||||||
return originalSubtitles;
|
return originalSubtitles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String text() {
|
||||||
|
return text.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadOnlyStringProperty textProperty() {
|
||||||
|
return text.getReadOnlyProperty();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EditableSubtitle selectedSubtitle() {
|
public EditableSubtitle selectedSubtitle() {
|
||||||
return subtitle.get();
|
return subtitle.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ObjectProperty<EditableSubtitle> selectedSubtitleProperty() {
|
||||||
|
return subtitle;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ObservableList<Locale> translations() {
|
public ObservableList<Language> availableVideoLanguages() {
|
||||||
|
return availableVideoLanguages;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ObservableList<Language> availableTranslationsLanguage() {
|
||||||
|
return availableTranslationLanguages;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Language videoLanguage() {
|
||||||
|
return videoLanguage.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setVideoLanguage(final Language language) {
|
||||||
|
videoLanguage.set(language);
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectProperty<Language> videoLanguageProperty() {
|
||||||
|
return videoLanguage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ObservableList<Language> translations() {
|
||||||
return translations;
|
return translations;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObjectProperty<EditableSubtitle> selectedSubtitleProperty() {
|
@Override
|
||||||
return subtitle;
|
public WorkStatus status() {
|
||||||
|
return workStatus.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStatus(final WorkStatus status) {
|
||||||
|
workStatus.set(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectProperty<WorkStatus> statusProperty() {
|
||||||
|
return workStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double progress() {
|
||||||
|
return progress.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setProgress(final double progress) {
|
||||||
|
this.progress.set(progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
DoubleProperty progressProperty() {
|
||||||
|
return progress;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.github.gtache.autosubtitle.gui.fx;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.Language;
|
||||||
|
import javafx.util.StringConverter;
|
||||||
|
|
||||||
|
class LanguageStringConverter extends StringConverter<Language> {
|
||||||
|
@Override
|
||||||
|
public String toString(final Language object) {
|
||||||
|
return object == null ? "" : object.englishName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Language fromString(final String string) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.github.gtache.autosubtitle.gui.fx;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.gui.TimeFormatter;
|
||||||
|
import javafx.util.StringConverter;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
class TimeStringConverter extends StringConverter<Long> {
|
||||||
|
|
||||||
|
private final TimeFormatter timeFormatter;
|
||||||
|
|
||||||
|
TimeStringConverter(final TimeFormatter timeFormatter) {
|
||||||
|
this.timeFormatter = Objects.requireNonNull(timeFormatter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString(final Long object) {
|
||||||
|
return object == null ? "" : timeFormatter.format(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long fromString(final String string) {
|
||||||
|
return string == null ? 0L : timeFormatter.parse(string);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
package com.github.gtache.autosubtitle.gui.modules.fx;
|
package com.github.gtache.autosubtitle.modules.gui.fx;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.gui.TimeFormatter;
|
||||||
|
import com.github.gtache.autosubtitle.gui.fx.ColonTimeFormatter;
|
||||||
import com.github.gtache.autosubtitle.gui.fx.FXMainController;
|
import com.github.gtache.autosubtitle.gui.fx.FXMainController;
|
||||||
import com.github.gtache.autosubtitle.gui.fx.FXMediaController;
|
import com.github.gtache.autosubtitle.gui.fx.FXMediaController;
|
||||||
|
import com.github.gtache.autosubtitle.gui.fx.FXParametersController;
|
||||||
import com.github.gtache.autosubtitle.gui.fx.FXSetupController;
|
import com.github.gtache.autosubtitle.gui.fx.FXSetupController;
|
||||||
import com.github.gtache.autosubtitle.gui.fx.FXWorkController;
|
import com.github.gtache.autosubtitle.gui.fx.FXWorkController;
|
||||||
import com.github.gtache.autosubtitle.modules.gui.Pause;
|
import com.github.gtache.autosubtitle.modules.gui.Pause;
|
||||||
import com.github.gtache.autosubtitle.modules.gui.Play;
|
import com.github.gtache.autosubtitle.modules.gui.Play;
|
||||||
|
import dagger.Binds;
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
@@ -14,6 +18,7 @@ import javafx.scene.image.Image;
|
|||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
import java.util.prefs.Preferences;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dagger module for FX
|
* Dagger module for FX
|
||||||
@@ -21,9 +26,12 @@ import java.util.ResourceBundle;
|
|||||||
@Module
|
@Module
|
||||||
public abstract class FXModule {
|
public abstract class FXModule {
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract TimeFormatter bindsTimeFormatter(final ColonTimeFormatter formatter);
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
static FXMLLoader providesFXMLLoader(final FXMainController mainController, final FXSetupController setupController,
|
static FXMLLoader providesFXMLLoader(final FXMainController mainController, final FXSetupController setupController, final FXParametersController parametersController,
|
||||||
final FXWorkController workController, final FXMediaController mediaController, final ResourceBundle bundle) {
|
final FXWorkController workController, final FXMediaController mediaController, final ResourceBundle bundle) {
|
||||||
final var loader = new FXMLLoader(FXModule.class.getResource("/com/github/gtache/autosubtitle/gui/fx/mainView.fxml"));
|
final var loader = new FXMLLoader(FXModule.class.getResource("/com/github/gtache/autosubtitle/gui/fx/mainView.fxml"));
|
||||||
loader.setResources(bundle);
|
loader.setResources(bundle);
|
||||||
@@ -36,6 +44,8 @@ public abstract class FXModule {
|
|||||||
return workController;
|
return workController;
|
||||||
} else if (c == FXMediaController.class) {
|
} else if (c == FXMediaController.class) {
|
||||||
return mediaController;
|
return mediaController;
|
||||||
|
} else if (c == FXParametersController.class) {
|
||||||
|
return parametersController;
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("Unknown controller " + c);
|
throw new IllegalArgumentException("Unknown controller " + c);
|
||||||
}
|
}
|
||||||
@@ -56,4 +66,10 @@ public abstract class FXModule {
|
|||||||
static Image providesPauseImage(@Pause final byte[] pauseImage) {
|
static Image providesPauseImage(@Pause final byte[] pauseImage) {
|
||||||
return new Image(new ByteArrayInputStream(pauseImage));
|
return new Image(new ByteArrayInputStream(pauseImage));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
static Preferences providesPreferences() {
|
||||||
|
return Preferences.userNodeForPackage(FXParametersController.class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -24,7 +24,11 @@ public class ObservableSubtitleImpl implements EditableSubtitle {
|
|||||||
private final ObjectProperty<Bounds> location;
|
private final ObjectProperty<Bounds> location;
|
||||||
|
|
||||||
public ObservableSubtitleImpl() {
|
public ObservableSubtitleImpl() {
|
||||||
this.content = new SimpleStringProperty("");
|
this("");
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableSubtitleImpl(final String content) {
|
||||||
|
this.content = new SimpleStringProperty(content);
|
||||||
this.start = new SimpleLongProperty(0);
|
this.start = new SimpleLongProperty(0);
|
||||||
this.end = new SimpleLongProperty(0);
|
this.end = new SimpleLongProperty(0);
|
||||||
this.font = new SimpleObjectProperty<>();
|
this.font = new SimpleObjectProperty<>();
|
||||||
|
|||||||
@@ -11,12 +11,16 @@ module com.github.gtache.autosubtitle.fx {
|
|||||||
requires transitive javafx.controls;
|
requires transitive javafx.controls;
|
||||||
requires transitive javafx.media;
|
requires transitive javafx.media;
|
||||||
requires transitive javafx.fxml;
|
requires transitive javafx.fxml;
|
||||||
|
requires org.controlsfx.controls;
|
||||||
requires org.apache.logging.log4j;
|
requires org.apache.logging.log4j;
|
||||||
exports com.github.gtache.autosubtitle.gui.fx;
|
requires java.desktop;
|
||||||
exports com.github.gtache.autosubtitle.gui.modules.fx;
|
requires transitive java.prefs;
|
||||||
|
|
||||||
|
exports com.github.gtache.autosubtitle.gui.fx;
|
||||||
opens com.github.gtache.autosubtitle.gui.fx to javafx.fxml;
|
opens com.github.gtache.autosubtitle.gui.fx to javafx.fxml;
|
||||||
|
|
||||||
|
exports com.github.gtache.autosubtitle.modules.gui.fx;
|
||||||
|
|
||||||
uses MainBundleProvider;
|
uses MainBundleProvider;
|
||||||
uses SetupBundleProvider;
|
uses SetupBundleProvider;
|
||||||
uses WorkBundleProvider;
|
uses WorkBundleProvider;
|
||||||
|
|||||||
@@ -2,17 +2,22 @@
|
|||||||
|
|
||||||
<?import javafx.scene.control.Tab?>
|
<?import javafx.scene.control.Tab?>
|
||||||
<?import javafx.scene.control.TabPane?>
|
<?import javafx.scene.control.TabPane?>
|
||||||
|
<TabPane fx:id="tabPane" xmlns="http://javafx.com/javafx/22" xmlns:fx="http://javafx.com/fxml/1"
|
||||||
<TabPane fx:id="tabPane" xmlns="http://javafx.com/javafx/22" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.github.gtache.autosubtitle.gui.fx.FXMainController">
|
fx:controller="com.github.gtache.autosubtitle.gui.fx.FXMainController">
|
||||||
<tabs>
|
<tabs>
|
||||||
<Tab closable="false" text="%main.tab.work.label">
|
<Tab closable="false" text="%main.tab.work.label">
|
||||||
<content>
|
<content>
|
||||||
<fx:include source="workView.fxml" />
|
<fx:include source="workView.fxml"/>
|
||||||
</content>
|
</content>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab closable="false" text="%main.tab.setup.label">
|
<Tab closable="false" text="%main.tab.setup.label">
|
||||||
<content>
|
<content>
|
||||||
<fx:include source="setupView.fxml" />
|
<fx:include source="setupView.fxml"/>
|
||||||
|
</content>
|
||||||
|
</Tab>
|
||||||
|
<Tab closable="false" text="%main.tab.parameters.label">
|
||||||
|
<content>
|
||||||
|
<fx:include source="parametersView.fxml"/>
|
||||||
</content>
|
</content>
|
||||||
</Tab>
|
</Tab>
|
||||||
</tabs>
|
</tabs>
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.Label?>
|
||||||
|
<?import javafx.scene.control.TextField?>
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
<?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">
|
||||||
|
<columnConstraints>
|
||||||
|
<ColumnConstraints hgrow="SOMETIMES"/>
|
||||||
|
<ColumnConstraints hgrow="SOMETIMES"/>
|
||||||
|
</columnConstraints>
|
||||||
|
<rowConstraints>
|
||||||
|
<RowConstraints vgrow="SOMETIMES"/>
|
||||||
|
<RowConstraints vgrow="SOMETIMES"/>
|
||||||
|
<RowConstraints vgrow="SOMETIMES"/>
|
||||||
|
<RowConstraints 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"/>
|
||||||
|
</children>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
|
||||||
|
</padding>
|
||||||
|
</GridPane>
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
<?import javafx.geometry.Insets?>
|
<?import javafx.geometry.Insets?>
|
||||||
<?import javafx.scene.control.Label?>
|
<?import javafx.scene.control.Label?>
|
||||||
<?import javafx.scene.control.MenuButton?>
|
<?import javafx.scene.control.MenuButton?>
|
||||||
<?import javafx.scene.control.MenuItem?>
|
|
||||||
<?import javafx.scene.control.ProgressBar?>
|
<?import javafx.scene.control.ProgressBar?>
|
||||||
<?import javafx.scene.layout.ColumnConstraints?>
|
<?import javafx.scene.layout.ColumnConstraints?>
|
||||||
<?import javafx.scene.layout.GridPane?>
|
<?import javafx.scene.layout.GridPane?>
|
||||||
@@ -31,24 +30,9 @@
|
|||||||
<Label fx:id="extractorStatusLabel" text="Label" GridPane.columnIndex="1" GridPane.rowIndex="2" />
|
<Label fx:id="extractorStatusLabel" text="Label" GridPane.columnIndex="1" GridPane.rowIndex="2" />
|
||||||
<Label fx:id="translatorNameLabel" text="Label" GridPane.rowIndex="3" />
|
<Label fx:id="translatorNameLabel" text="Label" GridPane.rowIndex="3" />
|
||||||
<Label fx:id="translatorStatusLabel" text="Label" GridPane.columnIndex="1" GridPane.rowIndex="3" />
|
<Label fx:id="translatorStatusLabel" text="Label" GridPane.columnIndex="1" GridPane.rowIndex="3" />
|
||||||
<MenuButton fx:id="converterButton" mnemonicParsing="false" text="%setup.menu.label" GridPane.columnIndex="2" GridPane.rowIndex="1">
|
<MenuButton fx:id="converterButton" mnemonicParsing="false" text="%setup.menu.label" GridPane.columnIndex="2" GridPane.rowIndex="1" />
|
||||||
<items>
|
<MenuButton fx:id="extractorButton" mnemonicParsing="false" text="%setup.menu.label" GridPane.columnIndex="2" GridPane.rowIndex="2" />
|
||||||
<MenuItem mnemonicParsing="false" text="Action 1" />
|
<MenuButton fx:id="translatorButton" mnemonicParsing="false" text="%setup.menu.label" GridPane.columnIndex="2" GridPane.rowIndex="3" />
|
||||||
<MenuItem mnemonicParsing="false" text="Action 2" />
|
|
||||||
</items>
|
|
||||||
</MenuButton>
|
|
||||||
<MenuButton fx:id="extractorButton" mnemonicParsing="false" text="%setup.menu.label" GridPane.columnIndex="2" GridPane.rowIndex="2">
|
|
||||||
<items>
|
|
||||||
<MenuItem mnemonicParsing="false" text="Action 1" />
|
|
||||||
<MenuItem mnemonicParsing="false" text="Action 2" />
|
|
||||||
</items>
|
|
||||||
</MenuButton>
|
|
||||||
<MenuButton fx:id="translatorButton" mnemonicParsing="false" text="%setup.menu.label" GridPane.columnIndex="2" GridPane.rowIndex="3">
|
|
||||||
<items>
|
|
||||||
<MenuItem mnemonicParsing="false" text="Action 1" />
|
|
||||||
<MenuItem mnemonicParsing="false" text="Action 2" />
|
|
||||||
</items>
|
|
||||||
</MenuButton>
|
|
||||||
<Label text="%setup.description.label" GridPane.columnSpan="2147483647" />
|
<Label text="%setup.description.label" GridPane.columnSpan="2147483647" />
|
||||||
<ProgressBar fx:id="converterProgress" prefWidth="200.0" progress="0.0" GridPane.columnIndex="3" GridPane.rowIndex="1" />
|
<ProgressBar fx:id="converterProgress" prefWidth="200.0" progress="0.0" GridPane.columnIndex="3" GridPane.rowIndex="1" />
|
||||||
<ProgressBar fx:id="extractorProgress" prefWidth="200.0" progress="0.0" GridPane.columnIndex="3" GridPane.rowIndex="2" />
|
<ProgressBar fx:id="extractorProgress" prefWidth="200.0" progress="0.0" GridPane.columnIndex="3" GridPane.rowIndex="2" />
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
|
||||||
<?import javafx.geometry.Insets?>
|
<?import javafx.geometry.Insets?>
|
||||||
<?import javafx.scene.control.*?>
|
<?import javafx.scene.control.*?>
|
||||||
<?import javafx.scene.layout.*?>
|
<?import javafx.scene.layout.*?>
|
||||||
|
<?import org.controlsfx.control.CheckComboBox?>
|
||||||
|
<?import org.controlsfx.control.PrefixSelectionComboBox?>
|
||||||
<GridPane hgap="10.0" vgap="10.0" xmlns="http://javafx.com/javafx/22" xmlns:fx="http://javafx.com/fxml/1"
|
<GridPane hgap="10.0" vgap="10.0" xmlns="http://javafx.com/javafx/22" xmlns:fx="http://javafx.com/fxml/1"
|
||||||
fx:controller="com.github.gtache.autosubtitle.gui.fx.FXWorkController">
|
fx:controller="com.github.gtache.autosubtitle.gui.fx.FXWorkController">
|
||||||
<columnConstraints>
|
<columnConstraints>
|
||||||
@@ -15,6 +16,7 @@
|
|||||||
<RowConstraints vgrow="SOMETIMES"/>
|
<RowConstraints vgrow="SOMETIMES"/>
|
||||||
<RowConstraints vgrow="ALWAYS"/>
|
<RowConstraints vgrow="ALWAYS"/>
|
||||||
<RowConstraints vgrow="SOMETIMES"/>
|
<RowConstraints vgrow="SOMETIMES"/>
|
||||||
|
<RowConstraints vgrow="NEVER"/>
|
||||||
</rowConstraints>
|
</rowConstraints>
|
||||||
<children>
|
<children>
|
||||||
<TextField fx:id="fileField" editable="false" GridPane.columnIndex="1"/>
|
<TextField fx:id="fileField" editable="false" GridPane.columnIndex="1"/>
|
||||||
@@ -38,11 +40,12 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</children>
|
</children>
|
||||||
</HBox>
|
</HBox>
|
||||||
<TableView fx:id="subtitlesTable" GridPane.rowIndex="1">
|
<TableView fx:id="subtitlesTable" editable="true" GridPane.rowIndex="1">
|
||||||
<columns>
|
<columns>
|
||||||
<TableColumn fx:id="startColumn" prefWidth="50.0" text="%work.table.column.from.label"/>
|
<TableColumn fx:id="startColumn" prefWidth="50.0" sortable="false"
|
||||||
<TableColumn fx:id="endColumn" prefWidth="50.0" text="%work.table.column.to.label"/>
|
text="%work.table.column.from.label"/>
|
||||||
<TableColumn fx:id="textColumn" prefWidth="75.0" text="%work.table.column.text.label"/>
|
<TableColumn fx:id="endColumn" prefWidth="50.0" sortable="false" text="%work.table.column.to.label"/>
|
||||||
|
<TableColumn fx:id="textColumn" prefWidth="75.0" sortable="false" text="%work.table.column.text.label"/>
|
||||||
</columns>
|
</columns>
|
||||||
<columnResizePolicy>
|
<columnResizePolicy>
|
||||||
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
|
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
|
||||||
@@ -52,15 +55,21 @@
|
|||||||
text="%work.button.reset.label" GridPane.rowIndex="2"/>
|
text="%work.button.reset.label" GridPane.rowIndex="2"/>
|
||||||
<fx:include fx:id="media" source="mediaView.fxml" GridPane.columnIndex="1" GridPane.columnSpan="2147483647"
|
<fx:include fx:id="media" source="mediaView.fxml" GridPane.columnIndex="1" GridPane.columnSpan="2147483647"
|
||||||
GridPane.rowIndex="1"/>
|
GridPane.rowIndex="1"/>
|
||||||
<Button mnemonicParsing="false" text="+" GridPane.halignment="RIGHT" GridPane.rowIndex="2"/>
|
<Button fx:id="addSubtitleButton" mnemonicParsing="false" onAction="#addSubtitlePressed" text="+"
|
||||||
|
GridPane.halignment="RIGHT" GridPane.rowIndex="2"/>
|
||||||
<HBox alignment="CENTER_LEFT" spacing="10.0">
|
<HBox alignment="CENTER_LEFT" spacing="10.0">
|
||||||
<children>
|
<children>
|
||||||
|
<Label text="%work.language.label"/>
|
||||||
|
<PrefixSelectionComboBox fx:id="languageCombobox"/>
|
||||||
<Label text="%work.translate.label"/>
|
<Label text="%work.translate.label"/>
|
||||||
<TextField fx:id="translationField">
|
<CheckComboBox fx:id="translationsCombobox"/>
|
||||||
<tooltip>
|
</children>
|
||||||
<Tooltip text="%work.translate.tooltip"/>
|
</HBox>
|
||||||
</tooltip>
|
<Label fx:id="progressLabel" GridPane.columnIndex="1" GridPane.rowIndex="3"/>
|
||||||
</TextField>
|
<HBox spacing="10.0" GridPane.columnIndex="2" GridPane.rowIndex="3">
|
||||||
|
<children>
|
||||||
|
<Label fx:id="progressDetailLabel"/>
|
||||||
|
<ProgressBar fx:id="progressBar" maxWidth="1.7976931348623157E308" HBox.hgrow="ALWAYS"/>
|
||||||
</children>
|
</children>
|
||||||
</HBox>
|
</HBox>
|
||||||
</children>
|
</children>
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ public interface MediaModel {
|
|||||||
void setPosition(long position);
|
void setPosition(long position);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The current list of subtitles
|
* @return The current subtitles
|
||||||
*/
|
*/
|
||||||
List<EditableSubtitle> subtitles();
|
List<EditableSubtitle> subtitles();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.github.gtache.autosubtitle.gui;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller for the parameters view
|
||||||
|
*/
|
||||||
|
public interface ParametersController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the parameters
|
||||||
|
*/
|
||||||
|
void save();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the parameters
|
||||||
|
*/
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The model
|
||||||
|
*/
|
||||||
|
ParametersModel model();
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package com.github.gtache.autosubtitle.gui;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.ExtractionModel;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.OutputFormat;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model for the media view
|
||||||
|
*/
|
||||||
|
public interface ParametersModel {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The available extraction models
|
||||||
|
*/
|
||||||
|
List<ExtractionModel> availableExtractionModels();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The current extraction model
|
||||||
|
*/
|
||||||
|
ExtractionModel extractionModel();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param model The new extraction model
|
||||||
|
*/
|
||||||
|
void setExtractionModel(ExtractionModel model);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The available output formats
|
||||||
|
*/
|
||||||
|
List<OutputFormat> availableOutputFormats();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The current output format
|
||||||
|
*/
|
||||||
|
OutputFormat outputFormat();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param format The new output format
|
||||||
|
*/
|
||||||
|
void setOutputFormat(OutputFormat format);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The available font families
|
||||||
|
*/
|
||||||
|
List<String> availableFontFamilies();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The current font family
|
||||||
|
*/
|
||||||
|
String fontFamily();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param fontFamily The new font family
|
||||||
|
*/
|
||||||
|
void setFontFamily(String fontFamily);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The current font size
|
||||||
|
*/
|
||||||
|
int fontSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param fontSize The new font size
|
||||||
|
*/
|
||||||
|
void setFontSize(int fontSize);
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.github.gtache.autosubtitle.gui;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formatter for times
|
||||||
|
*/
|
||||||
|
public interface TimeFormatter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats the given elapsed time and total time into a string
|
||||||
|
*
|
||||||
|
* @param elapsed The elapsed time in milliseconds
|
||||||
|
* @param total The total time in milliseconds
|
||||||
|
* @return The formatted string
|
||||||
|
*/
|
||||||
|
String format(long elapsed, long total);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats the given time into a string
|
||||||
|
*
|
||||||
|
* @param millis The time in milliseconds
|
||||||
|
* @return The formatted string
|
||||||
|
*/
|
||||||
|
String format(long millis);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the given string
|
||||||
|
*
|
||||||
|
* @param time The time as a string
|
||||||
|
* @return The time in milliseconds
|
||||||
|
*/
|
||||||
|
long parse(final String time);
|
||||||
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
package com.github.gtache.autosubtitle.gui;
|
package com.github.gtache.autosubtitle.gui;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.Language;
|
||||||
import com.github.gtache.autosubtitle.Video;
|
import com.github.gtache.autosubtitle.Video;
|
||||||
import com.github.gtache.autosubtitle.subtitle.EditableSubtitle;
|
import com.github.gtache.autosubtitle.subtitle.EditableSubtitle;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.ExtractionModel;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model for the main view
|
* Model for the main view
|
||||||
@@ -16,11 +18,31 @@ public interface WorkModel {
|
|||||||
*/
|
*/
|
||||||
Video video();
|
Video video();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The current extraction model
|
||||||
|
*/
|
||||||
|
ExtractionModel extractionModel();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param model The new extraction model
|
||||||
|
*/
|
||||||
|
void setExtractionModel(ExtractionModel model);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The current subtitle collection
|
||||||
|
*/
|
||||||
|
SubtitleCollection subtitleCollection();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The current list of subtitles
|
* @return The current list of subtitles
|
||||||
*/
|
*/
|
||||||
List<EditableSubtitle> subtitles();
|
List<EditableSubtitle> subtitles();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The current text
|
||||||
|
*/
|
||||||
|
String text();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The original extracted subtitles (used to reset)
|
* @return The original extracted subtitles (used to reset)
|
||||||
*/
|
*/
|
||||||
@@ -31,8 +53,48 @@ public interface WorkModel {
|
|||||||
*/
|
*/
|
||||||
EditableSubtitle selectedSubtitle();
|
EditableSubtitle selectedSubtitle();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The list of available video languages
|
||||||
|
*/
|
||||||
|
List<Language> availableVideoLanguages();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The list of available translations languages
|
||||||
|
*/
|
||||||
|
List<Language> availableTranslationsLanguage();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The video language
|
||||||
|
*/
|
||||||
|
Language videoLanguage();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param language The video language
|
||||||
|
*/
|
||||||
|
void setVideoLanguage(Language language);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The list of selected translations
|
* @return The list of selected translations
|
||||||
*/
|
*/
|
||||||
List<Locale> translations();
|
List<Language> translations();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The current status
|
||||||
|
*/
|
||||||
|
WorkStatus status();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param status The new status
|
||||||
|
*/
|
||||||
|
void setStatus(WorkStatus status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The current progress
|
||||||
|
*/
|
||||||
|
double progress();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param progress The new progress
|
||||||
|
*/
|
||||||
|
void setProgress(double progress);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.github.gtache.autosubtitle.gui;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Possible statuses for the work controller
|
||||||
|
*/
|
||||||
|
public enum WorkStatus {
|
||||||
|
IDLE, EXTRACTING, TRANSLATING, EXPORTING
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.github.gtache.autosubtitle.gui.spi;
|
||||||
|
|
||||||
|
import java.util.spi.ResourceBundleProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provider for ParametersBundle
|
||||||
|
*/
|
||||||
|
public interface ParametersBundleProvider extends ResourceBundleProvider {
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.github.gtache.autosubtitle.gui.spi;
|
||||||
|
|
||||||
|
import java.util.spi.AbstractResourceBundleProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of {@link ParametersBundleProvider}
|
||||||
|
*/
|
||||||
|
public class ParametersBundleProviderImpl extends AbstractResourceBundleProvider implements ParametersBundleProvider {
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.github.gtache.autosubtitle.modules.gui;
|
||||||
|
|
||||||
|
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 FontFamily {
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.github.gtache.autosubtitle.modules.gui;
|
||||||
|
|
||||||
|
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 FontSize {
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import dagger.Provides;
|
|||||||
|
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,6 +20,7 @@ public class GuiModule {
|
|||||||
return new CombinedResourceBundle(ResourceBundle.getBundle("com.github.gtache.autosubtitle.gui.MainBundle"),
|
return new CombinedResourceBundle(ResourceBundle.getBundle("com.github.gtache.autosubtitle.gui.MainBundle"),
|
||||||
ResourceBundle.getBundle("com.github.gtache.autosubtitle.gui.SetupBundle"),
|
ResourceBundle.getBundle("com.github.gtache.autosubtitle.gui.SetupBundle"),
|
||||||
ResourceBundle.getBundle("com.github.gtache.autosubtitle.gui.WorkBundle"),
|
ResourceBundle.getBundle("com.github.gtache.autosubtitle.gui.WorkBundle"),
|
||||||
|
ResourceBundle.getBundle("com.github.gtache.autosubtitle.gui.ParametersBundle"),
|
||||||
ResourceBundle.getBundle("com.github.gtache.autosubtitle.gui.MediaBundle"));
|
ResourceBundle.getBundle("com.github.gtache.autosubtitle.gui.MediaBundle"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,10 +28,10 @@ public class GuiModule {
|
|||||||
@Singleton
|
@Singleton
|
||||||
@Play
|
@Play
|
||||||
static byte[] providesPlayImage() {
|
static byte[] providesPlayImage() {
|
||||||
try {
|
try (final var in = GuiModule.class.getResourceAsStream("/com/github/gtache/autosubtitle/gui/play_64.png")) {
|
||||||
return GuiModule.class.getResourceAsStream("/com/github/gtache/autosubtitle/gui/play_64.png").readAllBytes();
|
return in.readAllBytes();
|
||||||
} catch (final IOException e) {
|
} catch (final IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new UncheckedIOException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,10 +39,24 @@ public class GuiModule {
|
|||||||
@Singleton
|
@Singleton
|
||||||
@Pause
|
@Pause
|
||||||
static byte[] providesPauseImage() {
|
static byte[] providesPauseImage() {
|
||||||
try {
|
try (final var in = GuiModule.class.getResourceAsStream("/com/github/gtache/autosubtitle/gui/pause_64.png")) {
|
||||||
return GuiModule.class.getResourceAsStream("/com/github/gtache/autosubtitle/gui/pause_64.png").readAllBytes();
|
return in.readAllBytes();
|
||||||
} catch (final IOException e) {
|
} catch (final IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new UncheckedIOException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@FontFamily
|
||||||
|
static String providesFontFamily() {
|
||||||
|
return "Arial";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@FontSize
|
||||||
|
static int providesFontSize() {
|
||||||
|
return 12;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import com.github.gtache.autosubtitle.gui.spi.MainBundleProvider;
|
import com.github.gtache.autosubtitle.gui.spi.MainBundleProvider;
|
||||||
import com.github.gtache.autosubtitle.gui.spi.MainBundleProviderImpl;
|
import com.github.gtache.autosubtitle.gui.spi.MainBundleProviderImpl;
|
||||||
|
import com.github.gtache.autosubtitle.gui.spi.ParametersBundleProvider;
|
||||||
|
import com.github.gtache.autosubtitle.gui.spi.ParametersBundleProviderImpl;
|
||||||
import com.github.gtache.autosubtitle.gui.spi.SetupBundleProvider;
|
import com.github.gtache.autosubtitle.gui.spi.SetupBundleProvider;
|
||||||
import com.github.gtache.autosubtitle.gui.spi.SetupBundleProviderImpl;
|
import com.github.gtache.autosubtitle.gui.spi.SetupBundleProviderImpl;
|
||||||
import com.github.gtache.autosubtitle.gui.spi.WorkBundleProvider;
|
import com.github.gtache.autosubtitle.gui.spi.WorkBundleProvider;
|
||||||
@@ -18,6 +20,7 @@ module com.github.gtache.autosubtitle.gui {
|
|||||||
exports com.github.gtache.autosubtitle.modules.gui;
|
exports com.github.gtache.autosubtitle.modules.gui;
|
||||||
|
|
||||||
provides MainBundleProvider with MainBundleProviderImpl;
|
provides MainBundleProvider with MainBundleProviderImpl;
|
||||||
|
provides ParametersBundleProvider with ParametersBundleProviderImpl;
|
||||||
provides SetupBundleProvider with SetupBundleProviderImpl;
|
provides SetupBundleProvider with SetupBundleProviderImpl;
|
||||||
provides WorkBundleProvider with WorkBundleProviderImpl;
|
provides WorkBundleProvider with WorkBundleProviderImpl;
|
||||||
}
|
}
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
|
main.tab.parameters.label=Parameters
|
||||||
main.tab.setup.label=Setup
|
main.tab.setup.label=Setup
|
||||||
main.tab.work.label=Work
|
main.tab.work.label=Work
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
|
main.tab.parameters.label=Param\u00E8tres
|
||||||
main.tab.setup.label=Installation
|
main.tab.setup.label=Installation
|
||||||
main.tab.work.label=Travail
|
main.tab.work.label=Travail
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
parameters.button.reset.label=Reset
|
||||||
|
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.output.format=Output format for the subtitles
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
parameters.button.reset.label=R\u00E9initialiser
|
||||||
|
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.output.format=Format de sortie pour les sous-titres
|
||||||
@@ -1,4 +1,16 @@
|
|||||||
setup.description.label=Status of the various tools used by the app
|
setup.description.label=Status of the various tools used by the app
|
||||||
|
setup.event.check.end.label=Checked {0}
|
||||||
|
setup.event.check.start.label=Checking {0}
|
||||||
|
setup.event.delete.end.label=Deleted {0}
|
||||||
|
setup.event.delete.start.label=Deleting {0}
|
||||||
|
setup.event.download.end.label=Downloaded {0}
|
||||||
|
setup.event.download.start.label=Downloading {0}
|
||||||
|
setup.event.install.end.label=Installed {0}
|
||||||
|
setup.event.install.start.label=Installing {0}
|
||||||
|
setup.event.uninstall.end.label=Uninstalled {0}
|
||||||
|
setup.event.uninstall.start.label=Uninstalling {0}
|
||||||
|
setup.event.update.end.label=Updated {0}
|
||||||
|
setup.event.update.start.label=Updating {0}
|
||||||
setup.install.error.label=An error occurred while installing : {0}
|
setup.install.error.label=An error occurred while installing : {0}
|
||||||
setup.install.error.title=Error installing
|
setup.install.error.title=Error installing
|
||||||
setup.menu.install.label=Install
|
setup.menu.install.label=Install
|
||||||
@@ -8,9 +20,11 @@ setup.menu.uninstall.label=Uninstall
|
|||||||
setup.menu.update.label=Update
|
setup.menu.update.label=Update
|
||||||
setup.reinstall.error.label=An error occurred while reinstalling : {0}
|
setup.reinstall.error.label=An error occurred while reinstalling : {0}
|
||||||
setup.reinstall.error.title=Error reinstalling
|
setup.reinstall.error.title=Error reinstalling
|
||||||
|
setup.status.bundle_installed.label=is installed.
|
||||||
setup.status.errored.label=threw an error.
|
setup.status.errored.label=threw an error.
|
||||||
setup.status.installed.label=is installed.
|
setup.status.installed.label=is installed.
|
||||||
setup.status.not_installed.label=is not installed.
|
setup.status.not_installed.label=is not installed.
|
||||||
|
setup.status.system_installed.label=is installed (system).
|
||||||
setup.status.update_available.label=has an available update.
|
setup.status.update_available.label=has an available update.
|
||||||
setup.uninstall.error.label=An error occurred while uninstalling : {0}
|
setup.uninstall.error.label=An error occurred while uninstalling : {0}
|
||||||
setup.uninstall.error.title=Error uninstalling
|
setup.uninstall.error.title=Error uninstalling
|
||||||
|
|||||||
@@ -1,4 +1,16 @@
|
|||||||
setup.description.label=Statut des outils utilis\u00E9s par l'application
|
setup.description.label=Statut des outils utilis\u00E9s par l'application
|
||||||
|
setup.event.check.end.label=Contr\u00F4le de {0} termin\u00E9
|
||||||
|
setup.event.check.start.label=Contr\u00F4le de {0} en cours
|
||||||
|
setup.event.delete.end.label=Suppression de {0} termin\u00E9e
|
||||||
|
setup.event.delete.start.label=Suppression de {0} en cours
|
||||||
|
setup.event.download.end.label=T\u00E9l\u00E9chargement de {0} termin\u00E9
|
||||||
|
setup.event.download.start.label=T\u00E9l\u00E9chargement de {0} en cours
|
||||||
|
setup.event.install.end.label=Installation de {0} termin\u00E9e
|
||||||
|
setup.event.install.start.label=Installation de {0} en cours
|
||||||
|
setup.event.uninstall.end.label=D\u00E9sinstallation de {0} termin\u00E9e
|
||||||
|
setup.event.uninstall.start.label=D\u00E9sinstallation de {0} en cours
|
||||||
|
setup.event.update.end.label=Mise \u00E0 jour de {0} termin\u00E9e
|
||||||
|
setup.event.update.start.label=Mise \u00E0 jour de {0} en cours
|
||||||
setup.install.error.label=Une erreur s''est produite lors de l''installation: {0}
|
setup.install.error.label=Une erreur s''est produite lors de l''installation: {0}
|
||||||
setup.install.error.title=Erreur d'installation
|
setup.install.error.title=Erreur d'installation
|
||||||
setup.menu.install.label=Installer
|
setup.menu.install.label=Installer
|
||||||
@@ -8,9 +20,11 @@ setup.menu.uninstall.label=D\u00E9sinstaller
|
|||||||
setup.menu.update.label=Mettre \u00E0 jour
|
setup.menu.update.label=Mettre \u00E0 jour
|
||||||
setup.reinstall.error.label=Une erreur s''est produite lors de la r\u00E9installation: {0}
|
setup.reinstall.error.label=Une erreur s''est produite lors de la r\u00E9installation: {0}
|
||||||
setup.reinstall.error.title=Erreur de r\u00E9installation
|
setup.reinstall.error.title=Erreur de r\u00E9installation
|
||||||
|
setup.status.bundle_installed.label=est install\u00E9.
|
||||||
setup.status.errored.label=a caus\u00E9 une erreur.
|
setup.status.errored.label=a caus\u00E9 une erreur.
|
||||||
setup.status.installed.label=est install\u00E9.
|
setup.status.installed.label=est install\u00E9.
|
||||||
setup.status.not_installed.label=n'est pas install\u00E9.
|
setup.status.not_installed.label=n'est pas install\u00E9.
|
||||||
|
setup.status.system_installed.label=est install\u00E9 (syst\u00E8me).
|
||||||
setup.status.update_available.label=a une mise \u00E0 jour disponible.
|
setup.status.update_available.label=a une mise \u00E0 jour disponible.
|
||||||
setup.uninstall.error.label=Une erreur s''est produite lors de la d\u00E9sinstallation : {0}
|
setup.uninstall.error.label=Une erreur s''est produite lors de la d\u00E9sinstallation : {0}
|
||||||
setup.uninstall.error.title=Erreur de d\u00E9sinstallation
|
setup.uninstall.error.title=Erreur de d\u00E9sinstallation
|
||||||
|
|||||||
@@ -5,10 +5,18 @@ work.button.export.soft.tooltip=Adds the subtitles to the video. This allows a v
|
|||||||
work.button.extract.label=Extract subtitles
|
work.button.extract.label=Extract subtitles
|
||||||
work.button.file.label=Open video...
|
work.button.file.label=Open video...
|
||||||
work.button.reset.label=Reset subtitles
|
work.button.reset.label=Reset subtitles
|
||||||
work.error.export.label=Error during the export : {0}
|
work.export.error.label=Error during the export : {0}
|
||||||
work.error.export.title=Error exporting
|
work.export.error.title=Error exporting
|
||||||
|
work.extract.error.label=Error extracting subtitles : {0}
|
||||||
|
work.extract.error.title=Error extracting
|
||||||
|
work.language.label=Video language
|
||||||
|
work.load.error.label=Error loading video : {0}
|
||||||
|
work.load.error.title=Error loading
|
||||||
|
work.status.exporting.label=Exporting...
|
||||||
|
work.status.extracting.label=Extracting...
|
||||||
|
work.status.idle.label=Idle
|
||||||
|
work.status.translating.label=Translating...
|
||||||
work.table.column.from.label=From
|
work.table.column.from.label=From
|
||||||
work.table.column.text.label=Text
|
work.table.column.text.label=Text
|
||||||
work.table.column.to.label=To
|
work.table.column.to.label=To
|
||||||
work.translate.label=Automatic translation (for adding)
|
work.translate.label=Automatic translations
|
||||||
work.translate.tooltip=A comma-separated list of ISO 639-3 codes
|
|
||||||
@@ -1,3 +1,22 @@
|
|||||||
|
work.button.export.hard.label=Graver la vid\u00E9o...
|
||||||
|
work.button.export.hard.tooltip=Grave les sous-titres dans la vid\u00E9o. Cela veut dire que les sous-titres sont \u00E9crits directement dans l'image et ne peuvent pas \u00EAtre d\u00E9sactiv\u00E9s.
|
||||||
|
work.button.export.soft.label=Exporter la vid\u00E9o...
|
||||||
|
work.button.export.soft.tooltip=Ajoute les sous-titres \u00E0 la vid\u00E9o. Cela permet d'avoir plusieurs pistes de sous-titres dans une m\u00EAme vid\u00E9o et de les activer comme d\u00E9sir\u00E9.
|
||||||
|
work.button.extract.label=Extraire les sous-titres
|
||||||
|
work.button.file.label=Ouvrir une vid\u00E9o...
|
||||||
|
work.button.reset.label=R\u00E9initialiser les sous-titres
|
||||||
|
work.export.error.label=Erreur durant l''export : {0}
|
||||||
|
work.export.error.title=Erreur d'export
|
||||||
|
work.extract.error.label=Erreur durant l''extraction des sous-titres : {0}
|
||||||
|
work.extract.error.title=Erreur d'extraction
|
||||||
|
work.language.label=Language de la vid\u00E9o
|
||||||
|
work.load.error.label=Erreur lors du chargement de la vid\u00E9o : {0}
|
||||||
|
work.load.error.title=Erreur de chargement
|
||||||
|
work.status.exporting.label=Exportation en cours...
|
||||||
|
work.status.extracting.label=Extraction en cours...
|
||||||
|
work.status.idle.label=Idle
|
||||||
|
work.status.translating.label=Traduction en cours...
|
||||||
work.table.column.from.label=De
|
work.table.column.from.label=De
|
||||||
work.table.column.text.label=Texte
|
work.table.column.text.label=Texte
|
||||||
work.table.column.to.label=\u00C0
|
work.table.column.to.label=\u00C0
|
||||||
|
work.translate.label=Traductions automatiques
|
||||||
|
|||||||
27
pom.xml
27
pom.xml
@@ -17,6 +17,7 @@
|
|||||||
<module>gui</module>
|
<module>gui</module>
|
||||||
<module>run</module>
|
<module>run</module>
|
||||||
<module>whisper</module>
|
<module>whisper</module>
|
||||||
|
<module>cli</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
@@ -26,6 +27,7 @@
|
|||||||
|
|
||||||
<dagger.version>2.51.1</dagger.version>
|
<dagger.version>2.51.1</dagger.version>
|
||||||
<log4j.version>2.23.1</log4j.version>
|
<log4j.version>2.23.1</log4j.version>
|
||||||
|
<picocli.version>4.7.6</picocli.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
@@ -35,6 +37,11 @@
|
|||||||
<artifactId>autosubtitle-api</artifactId>
|
<artifactId>autosubtitle-api</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.gtache.autosubtitle</groupId>
|
||||||
|
<artifactId>autosubtitle-cli</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.gtache.autosubtitle</groupId>
|
<groupId>com.github.gtache.autosubtitle</groupId>
|
||||||
<artifactId>autosubtitle-core</artifactId>
|
<artifactId>autosubtitle-core</artifactId>
|
||||||
@@ -80,6 +87,16 @@
|
|||||||
<artifactId>log4j-api</artifactId>
|
<artifactId>log4j-api</artifactId>
|
||||||
<version>${log4j.version}</version>
|
<version>${log4j.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
|
<artifactId>log4j-core</artifactId>
|
||||||
|
<version>${log4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>info.picocli</groupId>
|
||||||
|
<artifactId>picocli</artifactId>
|
||||||
|
<version>${picocli.version}</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
@@ -91,16 +108,22 @@
|
|||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<version>3.13.0</version>
|
<version>3.13.0</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<annotationProcessorPaths>
|
<annotationProcessorPaths combine.children="append">
|
||||||
<path>
|
<path>
|
||||||
<groupId>com.google.dagger</groupId>
|
<groupId>com.google.dagger</groupId>
|
||||||
<artifactId>dagger-compiler</artifactId>
|
<artifactId>dagger-compiler</artifactId>
|
||||||
<version>${dagger.version}</version>
|
<version>${dagger.version}</version>
|
||||||
</path>
|
</path>
|
||||||
|
<path>
|
||||||
|
<groupId>info.picocli</groupId>
|
||||||
|
<artifactId>picocli-codegen</artifactId>
|
||||||
|
<version>${picocli.version}</version>
|
||||||
|
</path>
|
||||||
</annotationProcessorPaths>
|
</annotationProcessorPaths>
|
||||||
<compilerArgs>
|
<compilerArgs combine.children="append">
|
||||||
<arg>-Adagger.fastInit=enabled</arg>
|
<arg>-Adagger.fastInit=enabled</arg>
|
||||||
<arg>-Adagger.ignoreProvisionKeyWildcards=ENABLED</arg>
|
<arg>-Adagger.ignoreProvisionKeyWildcards=ENABLED</arg>
|
||||||
|
<arg>-Aproject=${project.groupId}/${project.artifactId}</arg>
|
||||||
<arg>-Xlint:all</arg>
|
<arg>-Xlint:all</arg>
|
||||||
</compilerArgs>
|
</compilerArgs>
|
||||||
<showDeprecation>true</showDeprecation>
|
<showDeprecation>true</showDeprecation>
|
||||||
|
|||||||
@@ -27,7 +27,10 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.logging.log4j</groupId>
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
<artifactId>log4j-core</artifactId>
|
<artifactId>log4j-core</artifactId>
|
||||||
<version>${log4j.version}</version>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>info.picocli</groupId>
|
||||||
|
<artifactId>picocli</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,15 @@
|
|||||||
package com.github.gtache.autosubtitle.modules.run;
|
package com.github.gtache.autosubtitle.modules.run;
|
||||||
|
|
||||||
import com.github.gtache.autosubtitle.Audio;
|
import com.github.gtache.autosubtitle.Language;
|
||||||
import com.github.gtache.autosubtitle.Translator;
|
import com.github.gtache.autosubtitle.Translator;
|
||||||
import com.github.gtache.autosubtitle.Video;
|
import com.github.gtache.autosubtitle.modules.setup.impl.TranslatorSetup;
|
||||||
import com.github.gtache.autosubtitle.setup.SetupManager;
|
import com.github.gtache.autosubtitle.setup.SetupManager;
|
||||||
import com.github.gtache.autosubtitle.setup.modules.impl.SubtitleExtractorSetup;
|
|
||||||
import com.github.gtache.autosubtitle.setup.modules.impl.TranslatorSetup;
|
|
||||||
import com.github.gtache.autosubtitle.subtitle.EditableSubtitle;
|
|
||||||
import com.github.gtache.autosubtitle.subtitle.Subtitle;
|
import com.github.gtache.autosubtitle.subtitle.Subtitle;
|
||||||
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
|
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
|
||||||
import com.github.gtache.autosubtitle.subtitle.SubtitleConverter;
|
|
||||||
import com.github.gtache.autosubtitle.subtitle.SubtitleExtractor;
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
|
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Module for missing components
|
* Module for missing components
|
||||||
@@ -30,66 +22,27 @@ public abstract class MissingComponentsModule {
|
|||||||
static Translator providesTranslator() {
|
static Translator providesTranslator() {
|
||||||
return new Translator() {
|
return new Translator() {
|
||||||
@Override
|
@Override
|
||||||
public Locale getLocale(final String text) {
|
public Language getLanguage(final String text) {
|
||||||
return Locale.getDefault();
|
return Language.getDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String translate(final String text, final Locale to) {
|
public String translate(final String text, final Language to) {
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Subtitle translate(final Subtitle subtitle, final Locale to) {
|
public Subtitle translate(final Subtitle subtitle, final Language to) {
|
||||||
return subtitle;
|
return subtitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SubtitleCollection translate(final SubtitleCollection collection, final Locale to) {
|
public SubtitleCollection translate(final SubtitleCollection collection, final Language to) {
|
||||||
return collection;
|
return collection;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
static SubtitleExtractor providesSubtitleExtractor() {
|
|
||||||
return new SubtitleExtractor() {
|
|
||||||
@Override
|
|
||||||
public Collection<? extends EditableSubtitle> extract(final Video in) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<? extends EditableSubtitle> extract(final Audio in) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
static SubtitleConverter providesSubtitleConverter() {
|
|
||||||
return new SubtitleConverter() {
|
|
||||||
@Override
|
|
||||||
public String convert(final SubtitleCollection collection) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String formatName() {
|
|
||||||
return "none";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
@SubtitleExtractorSetup
|
|
||||||
static SetupManager providesSubtitleExtractorSetupManager() {
|
|
||||||
return new NoOpSetupManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
@TranslatorSetup
|
@TranslatorSetup
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.github.gtache.autosubtitle.modules.run;
|
package com.github.gtache.autosubtitle.modules.run;
|
||||||
|
|
||||||
import com.github.gtache.autosubtitle.setup.SetupException;
|
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.SetupManager;
|
||||||
import com.github.gtache.autosubtitle.setup.SetupStatus;
|
import com.github.gtache.autosubtitle.setup.SetupStatus;
|
||||||
|
|
||||||
@@ -16,6 +17,11 @@ class NoOpSetupManager implements SetupManager {
|
|||||||
return SetupStatus.NOT_INSTALLED;
|
return SetupStatus.NOT_INSTALLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInstalled() throws SetupException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void install() throws SetupException {
|
public void install() throws SetupException {
|
||||||
|
|
||||||
@@ -26,8 +32,33 @@ class NoOpSetupManager implements SetupManager {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reinstall() throws SetupException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUpdateAvailable() throws SetupException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void update() throws SetupException {
|
public void update() throws SetupException {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addListener(final SetupListener listener) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeListener(final SetupListener listener) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeListeners() {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
package com.github.gtache.autosubtitle.modules.run;
|
package com.github.gtache.autosubtitle.modules.run;
|
||||||
|
|
||||||
import com.github.gtache.autosubtitle.gui.modules.fx.FXModule;
|
|
||||||
import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegModule;
|
import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegModule;
|
||||||
import com.github.gtache.autosubtitle.modules.gui.GuiModule;
|
import com.github.gtache.autosubtitle.modules.gui.GuiModule;
|
||||||
|
import com.github.gtache.autosubtitle.modules.gui.fx.FXModule;
|
||||||
import com.github.gtache.autosubtitle.modules.impl.CoreModule;
|
import com.github.gtache.autosubtitle.modules.impl.CoreModule;
|
||||||
import com.github.gtache.autosubtitle.setup.modules.ffmpeg.FFmpegSetupModule;
|
import com.github.gtache.autosubtitle.modules.setup.ffmpeg.FFmpegSetupModule;
|
||||||
|
import com.github.gtache.autosubtitle.modules.setup.whisper.WhisperSetupModule;
|
||||||
|
import com.github.gtache.autosubtitle.modules.subtitles.impl.ConverterModule;
|
||||||
|
import com.github.gtache.autosubtitle.modules.whisper.WhisperModule;
|
||||||
import dagger.Component;
|
import dagger.Component;
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
|
|
||||||
@@ -14,7 +17,8 @@ import javax.inject.Singleton;
|
|||||||
* Main component
|
* Main component
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
@Component(modules = {CoreModule.class, GuiModule.class, FXModule.class, FFmpegModule.class, FFmpegSetupModule.class, MissingComponentsModule.class})
|
@Component(modules = {CoreModule.class, GuiModule.class, FXModule.class, FFmpegModule.class, FFmpegSetupModule.class,
|
||||||
|
ConverterModule.class, WhisperModule.class, WhisperSetupModule.class, MissingComponentsModule.class})
|
||||||
public interface RunComponent {
|
public interface RunComponent {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
13
run/src/main/resources/log4j2.xml
Normal file
13
run/src/main/resources/log4j2.xml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Configuration status="INFO">
|
||||||
|
<Appenders>
|
||||||
|
<Console name="Console" target="SYSTEM_OUT">
|
||||||
|
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
|
||||||
|
</Console>
|
||||||
|
</Appenders>
|
||||||
|
<Loggers>
|
||||||
|
<Root level="info">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</Root>
|
||||||
|
</Loggers>
|
||||||
|
</Configuration>
|
||||||
@@ -16,6 +16,11 @@
|
|||||||
<groupId>com.github.gtache.autosubtitle</groupId>
|
<groupId>com.github.gtache.autosubtitle</groupId>
|
||||||
<artifactId>autosubtitle-core</artifactId>
|
<artifactId>autosubtitle-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.code.gson</groupId>
|
||||||
|
<artifactId>gson</artifactId>
|
||||||
|
<version>2.11.0</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.github.gtache.autosubtitle.modules.setup.whisper;
|
||||||
|
|
||||||
|
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 CondaBundledPath {
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.github.gtache.autosubtitle.modules.setup.whisper;
|
||||||
|
|
||||||
|
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 CondaInstallerPath {
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.github.gtache.autosubtitle.modules.setup.whisper;
|
||||||
|
|
||||||
|
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 CondaMinimumMajorVersion {
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.github.gtache.autosubtitle.modules.setup.whisper;
|
||||||
|
|
||||||
|
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 CondaMinimumMinorVersion {
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user