Can playback video with controls, need to fix performance and reducing window size
This commit is contained in:
4
.idea/encodings.xml
generated
4
.idea/encodings.xml
generated
@@ -5,12 +5,16 @@
|
|||||||
<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$/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/resources" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/ffmpeg/src/main/java" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/ffmpeg/src/main/java" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/ffmpeg/src/main/resources" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/ffmpeg/src/main/resources" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/fx/src/main/java" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/fx/src/main/java" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/fx/src/main/resources" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/fx/src/main/resources" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/gui/src/main/java" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/gui/src/main/java" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/gui/src/main/resources" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/gui/src/main/resources" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/run/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/run/src/main/resources" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/whisper/src/main/java" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/whisper/src/main/java" charset="UTF-8" />
|
||||||
|
|||||||
@@ -1,20 +1,90 @@
|
|||||||
package com.github.gtache.autosubtitle;
|
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 java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translates texts and subtitles
|
||||||
|
*/
|
||||||
public interface Translator {
|
public interface Translator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guesses the locale of the given text
|
||||||
|
*
|
||||||
|
* @param text The text
|
||||||
|
* @return The guessed locale
|
||||||
|
*/
|
||||||
|
Locale getLocale(final String text);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guesses the locale of the given subtitle
|
||||||
|
*
|
||||||
|
* @param subtitle The subtitle
|
||||||
|
* @return The guessed locale
|
||||||
|
*/
|
||||||
|
default Locale getLocale(final Subtitle subtitle) {
|
||||||
|
return getLocale(subtitle.content());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translates the given text to the given locale
|
||||||
|
*
|
||||||
|
* @param text The text to translate
|
||||||
|
* @param to The target locale
|
||||||
|
* @return The translated text
|
||||||
|
*/
|
||||||
String translate(String text, Locale to);
|
String translate(String text, Locale to);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translates the given text to the given locale
|
||||||
|
*
|
||||||
|
* @param text The text to translate
|
||||||
|
* @param to The target locale
|
||||||
|
* @return The translated text
|
||||||
|
*/
|
||||||
default String translate(final String text, final String to) {
|
default String translate(final String text, final String to) {
|
||||||
return translate(text, Locale.forLanguageTag(to));
|
return translate(text, Locale.forLanguageTag(to));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translates the given subtitle to the given locale
|
||||||
|
*
|
||||||
|
* @param subtitle The subtitle to translate
|
||||||
|
* @param to The target locale
|
||||||
|
* @return The translated subtitle
|
||||||
|
*/
|
||||||
Subtitle translate(Subtitle subtitle, Locale to);
|
Subtitle translate(Subtitle subtitle, Locale to);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translates the given subtitle to the given locale
|
||||||
|
*
|
||||||
|
* @param subtitle The subtitle to translate
|
||||||
|
* @param to The target locale
|
||||||
|
* @return The translated subtitle
|
||||||
|
*/
|
||||||
default Subtitle translate(final Subtitle subtitle, final String to) {
|
default Subtitle translate(final Subtitle subtitle, final String to) {
|
||||||
return translate(subtitle, Locale.forLanguageTag(to));
|
return translate(subtitle, Locale.forLanguageTag(to));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translates the given subtitles collection to the given locale
|
||||||
|
*
|
||||||
|
* @param collection The subtitles collection to translate
|
||||||
|
* @param to The target locale
|
||||||
|
* @return The translated subtitles collection
|
||||||
|
*/
|
||||||
|
default SubtitleCollection translate(final SubtitleCollection collection, final String to) {
|
||||||
|
return translate(collection, Locale.forLanguageTag(to));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translates the given subtitles collection to the given locale
|
||||||
|
*
|
||||||
|
* @param collection The subtitles collection to translate
|
||||||
|
* @param to The target locale
|
||||||
|
* @return The translated subtitles collection
|
||||||
|
*/
|
||||||
|
SubtitleCollection translate(SubtitleCollection collection, Locale to);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,4 +18,5 @@ public interface Video {
|
|||||||
* @return The video info
|
* @return The video info
|
||||||
*/
|
*/
|
||||||
VideoInfo info();
|
VideoInfo info();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,4 +8,27 @@ public interface VideoInfo {
|
|||||||
* @return The video extension (mp4, etc.)
|
* @return The video extension (mp4, etc.)
|
||||||
*/
|
*/
|
||||||
String videoFormat();
|
String videoFormat();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The video width in pixels
|
||||||
|
*/
|
||||||
|
int width();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The video height in pixels
|
||||||
|
*/
|
||||||
|
int height();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The video duration in milliseconds
|
||||||
|
*/
|
||||||
|
long duration();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The aspect ratio of the video
|
||||||
|
*/
|
||||||
|
default double aspectRatio() {
|
||||||
|
return (double) width() / height();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.github.gtache.autosubtitle;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads videos
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface VideoLoader {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a video
|
||||||
|
*
|
||||||
|
* @param path The path to the video
|
||||||
|
* @return The loaded video
|
||||||
|
* @throws IOException If an error occurred
|
||||||
|
*/
|
||||||
|
Video loadVideo(final Path path) throws IOException;
|
||||||
|
}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
package com.github.gtache.autosubtitle.setup;
|
package com.github.gtache.autosubtitle.setup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when an error occurs during setup
|
||||||
|
*/
|
||||||
public class SetupException extends Exception {
|
public class SetupException extends Exception {
|
||||||
|
|
||||||
public SetupException(final String message) {
|
public SetupException(final String message) {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ public interface SetupManager {
|
|||||||
* @throws SetupException if an error occurred during the check
|
* @throws SetupException if an error occurred during the check
|
||||||
*/
|
*/
|
||||||
default boolean isInstalled() throws SetupException {
|
default boolean isInstalled() throws SetupException {
|
||||||
return status() != SetupStatus.NOT_INSTALLED;
|
return status().isInstalled();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,5 +4,9 @@ package com.github.gtache.autosubtitle.setup;
|
|||||||
* The status of a setup
|
* The status of a setup
|
||||||
*/
|
*/
|
||||||
public enum SetupStatus {
|
public enum SetupStatus {
|
||||||
NOT_INSTALLED, INSTALLED, UPDATE_AVAILABLE
|
ERRORED, NOT_INSTALLED, INSTALLED, UPDATE_AVAILABLE;
|
||||||
|
|
||||||
|
public boolean isInstalled() {
|
||||||
|
return this == INSTALLED || this == UPDATE_AVAILABLE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.github.gtache.autosubtitle.subtitle;
|
package com.github.gtache.autosubtitle.subtitle;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a collection of {@link Subtitle}
|
* Represents a collection of {@link Subtitle}
|
||||||
@@ -13,7 +14,7 @@ public interface SubtitleCollection {
|
|||||||
Collection<? extends Subtitle> subtitles();
|
Collection<? extends Subtitle> subtitles();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The language of the subtitles
|
* @return The locale of the subtitles
|
||||||
*/
|
*/
|
||||||
String language();
|
Locale locale();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import java.util.Collection;
|
|||||||
*/
|
*/
|
||||||
public interface SubtitleExtractor {
|
public interface SubtitleExtractor {
|
||||||
|
|
||||||
Collection<EditableSubtitle> extract(final Video in);
|
Collection<? extends EditableSubtitle> extract(final Video in);
|
||||||
|
|
||||||
Collection<EditableSubtitle> extract(final Audio in);
|
Collection<? extends EditableSubtitle> extract(final Audio in);
|
||||||
}
|
}
|
||||||
|
|||||||
9
api/src/main/java/module-info.java
Normal file
9
api/src/main/java/module-info.java
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* API module for auto-subtitle
|
||||||
|
*/
|
||||||
|
module com.github.gtache.autosubtitle.api {
|
||||||
|
exports com.github.gtache.autosubtitle;
|
||||||
|
exports com.github.gtache.autosubtitle.process;
|
||||||
|
exports com.github.gtache.autosubtitle.setup;
|
||||||
|
exports com.github.gtache.autosubtitle.subtitle;
|
||||||
|
}
|
||||||
@@ -23,7 +23,6 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.logging.log4j</groupId>
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
<artifactId>log4j-api</artifactId>
|
<artifactId>log4j-api</artifactId>
|
||||||
<version>2.23.1</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.github.gtache.autosubtitle.impl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of possible operating systems
|
||||||
|
*/
|
||||||
|
public enum Architecture {
|
||||||
|
I386, I486, I586, I686, PPC, POWERPC, X86, X86_32, X86_64, AMD64, ARM, ARM32, ARM64, AARCH64, UNKNOWN;
|
||||||
|
|
||||||
|
public static Architecture getArchitecture(final String name) {
|
||||||
|
try {
|
||||||
|
return valueOf(name.toUpperCase());
|
||||||
|
} catch (final IllegalArgumentException e) {
|
||||||
|
return UNKNOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.github.gtache.autosubtitle.impl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of possible operating systems
|
||||||
|
*/
|
||||||
|
public enum OS {
|
||||||
|
WINDOWS, LINUX, MAC
|
||||||
|
}
|
||||||
@@ -7,10 +7,19 @@ import java.util.Objects;
|
|||||||
/**
|
/**
|
||||||
* Implementation of {@link VideoInfo}
|
* Implementation of {@link VideoInfo}
|
||||||
*/
|
*/
|
||||||
public record VideoInfoImpl(String videoFormat) implements VideoInfo {
|
public record VideoInfoImpl(String videoFormat, int width, int height, long duration) implements VideoInfo {
|
||||||
|
|
||||||
public VideoInfoImpl {
|
public VideoInfoImpl {
|
||||||
Objects.requireNonNull(videoFormat);
|
Objects.requireNonNull(videoFormat);
|
||||||
|
if (width <= 0) {
|
||||||
|
throw new IllegalArgumentException("Width must be greater than 0 : " + width);
|
||||||
|
}
|
||||||
|
if (height <= 0) {
|
||||||
|
throw new IllegalArgumentException("Height must be greater than 0 : " + height);
|
||||||
|
}
|
||||||
|
if (duration <= 0) {
|
||||||
|
throw new IllegalArgumentException("Duration must be greater than 0 : " + duration);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package com.github.gtache.autosubtitle.modules.impl;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.impl.Architecture;
|
||||||
|
import com.github.gtache.autosubtitle.impl.OS;
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
public abstract class CoreModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
static OS providesOS() {
|
||||||
|
final var name = System.getProperty("os.name");
|
||||||
|
if (name.contains("Windows")) {
|
||||||
|
return OS.WINDOWS;
|
||||||
|
} else if (name.contains("Mac")) {
|
||||||
|
return OS.MAC;
|
||||||
|
} else {
|
||||||
|
return OS.LINUX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
static Architecture providesArchitecture() {
|
||||||
|
final var arch = System.getProperty("os.arch");
|
||||||
|
return Architecture.getArchitecture(arch);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@ExecutableExtension
|
||||||
|
static String providesExecutableExtension(final OS os) {
|
||||||
|
return os == OS.WINDOWS ? ".exe" : "";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.github.gtache.autosubtitle.modules.impl;
|
||||||
|
|
||||||
|
import javax.inject.Qualifier;
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
|
@Qualifier
|
||||||
|
@Documented
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
|
||||||
|
public @interface ExecutableExtension {
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import java.io.InputStreamReader;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -24,25 +25,28 @@ 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 builder = new ProcessBuilder(args);
|
||||||
builder.inheritIO();
|
|
||||||
builder.redirectErrorStream(true);
|
builder.redirectErrorStream(true);
|
||||||
final var process = builder.start();
|
final var process = builder.start();
|
||||||
final var output = new ArrayList<String>();
|
final var readFuture = CompletableFuture.supplyAsync(() -> {
|
||||||
new Thread(() -> {
|
final var output = new ArrayList<String>();
|
||||||
try (final var in = new BufferedReader(new InputStreamReader(new BufferedInputStream(process.getInputStream()), StandardCharsets.UTF_8))) {
|
try (final var in = new BufferedReader(new InputStreamReader(new BufferedInputStream(process.getInputStream()), StandardCharsets.UTF_8))) {
|
||||||
while (in.ready()) {
|
var line = in.readLine();
|
||||||
output.add(in.readLine());
|
while (line != null) {
|
||||||
|
output.add(line);
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}).start();
|
return output;
|
||||||
|
});
|
||||||
try {
|
try {
|
||||||
process.waitFor(1, TimeUnit.HOURS);
|
process.waitFor(1, TimeUnit.HOURS);
|
||||||
} catch (final InterruptedException e) {
|
} catch (final InterruptedException e) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
process.destroy();
|
process.destroy();
|
||||||
}
|
}
|
||||||
|
final var output = readFuture.join();
|
||||||
return new ProcessResultImpl(process.exitValue(), output);
|
return new ProcessResultImpl(process.exitValue(), output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,6 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
|||||||
@Qualifier
|
@Qualifier
|
||||||
@Documented
|
@Documented
|
||||||
@Retention(RUNTIME)
|
@Retention(RUNTIME)
|
||||||
@Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.FIELD})
|
@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
|
||||||
public @interface VideoConverter {
|
public @interface SubtitleExtractorSetup {
|
||||||
}
|
}
|
||||||
@@ -11,6 +11,6 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
|||||||
@Qualifier
|
@Qualifier
|
||||||
@Documented
|
@Documented
|
||||||
@Retention(RUNTIME)
|
@Retention(RUNTIME)
|
||||||
@Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.FIELD})
|
@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
|
||||||
public @interface Translator {
|
public @interface TranslatorSetup {
|
||||||
}
|
}
|
||||||
@@ -11,6 +11,6 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
|||||||
@Qualifier
|
@Qualifier
|
||||||
@Documented
|
@Documented
|
||||||
@Retention(RUNTIME)
|
@Retention(RUNTIME)
|
||||||
@Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.FIELD})
|
@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
|
||||||
public @interface SubtitleExtractor {
|
public @interface VideoConverterSetup {
|
||||||
}
|
}
|
||||||
@@ -3,13 +3,21 @@ package com.github.gtache.autosubtitle.subtitle.impl;
|
|||||||
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.Singleton;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts subtitles to SRT format
|
* Converts subtitles to SRT format
|
||||||
*/
|
*/
|
||||||
|
@Singleton
|
||||||
public class SRTSubtitleConverter implements SubtitleConverter {
|
public class SRTSubtitleConverter implements SubtitleConverter {
|
||||||
|
|
||||||
public String convert(final SubtitleCollection collection) {
|
@Inject
|
||||||
|
SRTSubtitleConverter() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public String convert(final SubtitleCollection collection) {
|
||||||
|
throw new UnsupportedOperationException("TODO");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ 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 static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
@@ -12,10 +13,10 @@ import static java.util.Objects.requireNonNull;
|
|||||||
* Implementation of {@link SubtitleCollection}
|
* Implementation of {@link SubtitleCollection}
|
||||||
*/
|
*/
|
||||||
public record SubtitleCollectionImpl(Collection<? extends Subtitle> subtitles,
|
public record SubtitleCollectionImpl(Collection<? extends Subtitle> subtitles,
|
||||||
String language) implements SubtitleCollection {
|
Locale locale) implements SubtitleCollection {
|
||||||
|
|
||||||
public SubtitleCollectionImpl {
|
public SubtitleCollectionImpl {
|
||||||
subtitles = List.copyOf(subtitles);
|
subtitles = List.copyOf(subtitles);
|
||||||
requireNonNull(language);
|
requireNonNull(locale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.github.gtache.autosubtitle.subtitle.modules.impl;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.SubtitleConverter;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.impl.SRTSubtitleConverter;
|
||||||
|
import dagger.Binds;
|
||||||
|
import dagger.Module;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dagger module for subtitle converter
|
||||||
|
*/
|
||||||
|
@Module
|
||||||
|
public interface ConverterModule {
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
SubtitleConverter bindsSubtitleConverter(final SRTSubtitleConverter converter);
|
||||||
|
}
|
||||||
16
core/src/main/java/module-info.java
Normal file
16
core/src/main/java/module-info.java
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Core module for auto-subtitle
|
||||||
|
*/
|
||||||
|
module com.github.gtache.autosubtitle.core {
|
||||||
|
requires transitive com.github.gtache.autosubtitle.api;
|
||||||
|
requires transitive dagger;
|
||||||
|
requires transitive javax.inject;
|
||||||
|
requires org.apache.logging.log4j;
|
||||||
|
|
||||||
|
exports com.github.gtache.autosubtitle.impl;
|
||||||
|
exports com.github.gtache.autosubtitle.modules.impl;
|
||||||
|
exports com.github.gtache.autosubtitle.process.impl;
|
||||||
|
exports com.github.gtache.autosubtitle.subtitle.impl;
|
||||||
|
exports com.github.gtache.autosubtitle.setup.modules.impl;
|
||||||
|
exports com.github.gtache.autosubtitle.subtitle.modules.impl;
|
||||||
|
}
|
||||||
21
deepl/pom.xml
Normal file
21
deepl/pom.xml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?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-deepl</artifactId>
|
||||||
|
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.gtache.autosubtitle</groupId>
|
||||||
|
<artifactId>autosubtitle-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package com.github.gtache.autosubtitle.deepl;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.Translator;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.Subtitle;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DeepL implementation of {@link Translator}
|
||||||
|
*/
|
||||||
|
public class DeepLTranslator implements Translator {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
DeepLTranslator() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Locale getLocale(final String text) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String translate(final String text, final Locale to) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Subtitle translate(final Subtitle subtitle, final Locale to) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SubtitleCollection translate(final SubtitleCollection collection, final Locale to) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.github.gtache.autosubtitle.modules.deepl;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.Translator;
|
||||||
|
import com.github.gtache.autosubtitle.deepl.DeepLTranslator;
|
||||||
|
import dagger.Binds;
|
||||||
|
import dagger.Module;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dagger module for DeepL
|
||||||
|
*/
|
||||||
|
@Module
|
||||||
|
public interface DeepLModule {
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
Translator bindsTranslator(final DeepLTranslator translator);
|
||||||
|
}
|
||||||
8
deepl/src/main/java/module-info.java
Normal file
8
deepl/src/main/java/module-info.java
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* DeepL module for auto-subtitle
|
||||||
|
*/
|
||||||
|
module com.github.gtache.autosubtitle.deepl {
|
||||||
|
requires transitive com.github.gtache.autosubtitle.core;
|
||||||
|
exports com.github.gtache.autosubtitle.deepl;
|
||||||
|
exports com.github.gtache.autosubtitle.modules.deepl;
|
||||||
|
}
|
||||||
@@ -8,20 +8,22 @@ import com.github.gtache.autosubtitle.impl.AudioInfoImpl;
|
|||||||
import com.github.gtache.autosubtitle.impl.FileAudioImpl;
|
import com.github.gtache.autosubtitle.impl.FileAudioImpl;
|
||||||
import com.github.gtache.autosubtitle.impl.FileVideoImpl;
|
import com.github.gtache.autosubtitle.impl.FileVideoImpl;
|
||||||
import com.github.gtache.autosubtitle.impl.VideoInfoImpl;
|
import com.github.gtache.autosubtitle.impl.VideoInfoImpl;
|
||||||
|
import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegBundledPath;
|
||||||
|
import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegSystemPath;
|
||||||
|
import com.github.gtache.autosubtitle.process.impl.AbstractProcessRunner;
|
||||||
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
|
import com.github.gtache.autosubtitle.subtitle.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 java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.SequencedMap;
|
import java.util.SequencedMap;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
@@ -29,15 +31,18 @@ import static java.util.Objects.requireNonNull;
|
|||||||
/**
|
/**
|
||||||
* FFmpeg implementation of {@link VideoConverter}
|
* FFmpeg implementation of {@link VideoConverter}
|
||||||
*/
|
*/
|
||||||
public class FFmpegVideoConverter implements VideoConverter {
|
@Singleton
|
||||||
|
public class FFmpegVideoConverter extends AbstractProcessRunner implements VideoConverter {
|
||||||
|
|
||||||
private static final String TEMP_FILE_PREFIX = "autosubtitle";
|
private static final String TEMP_FILE_PREFIX = "autosubtitle";
|
||||||
private final Path ffmpegPath;
|
private final Path bundledPath;
|
||||||
|
private final Path systemPath;
|
||||||
private final SubtitleConverter subtitleConverter;
|
private final SubtitleConverter subtitleConverter;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
FFmpegVideoConverter(final Path ffmpegPath, final SubtitleConverter subtitleConverter) {
|
FFmpegVideoConverter(@FFmpegBundledPath final Path bundledPath, @FFmpegSystemPath final Path systemPath, final SubtitleConverter subtitleConverter) {
|
||||||
this.ffmpegPath = requireNonNull(ffmpegPath);
|
this.bundledPath = requireNonNull(bundledPath);
|
||||||
|
this.systemPath = requireNonNull(systemPath);
|
||||||
this.subtitleConverter = requireNonNull(subtitleConverter);
|
this.subtitleConverter = requireNonNull(subtitleConverter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +52,7 @@ public class FFmpegVideoConverter implements VideoConverter {
|
|||||||
final var collectionMap = dumpCollections(subtitles);
|
final var collectionMap = dumpCollections(subtitles);
|
||||||
final var out = getTempFile("mkv"); //Soft subtitles are only supported by mkv apparently
|
final var out = getTempFile("mkv"); //Soft subtitles are only supported by mkv apparently
|
||||||
final var args = new ArrayList<String>();
|
final var args = new ArrayList<String>();
|
||||||
args.add(ffmpegPath.toString());
|
args.add(getFFmpegPath());
|
||||||
args.add("-i");
|
args.add("-i");
|
||||||
args.add(videoPath.toString());
|
args.add(videoPath.toString());
|
||||||
collectionMap.forEach((c, p) -> {
|
collectionMap.forEach((c, p) -> {
|
||||||
@@ -66,11 +71,11 @@ public class FFmpegVideoConverter implements VideoConverter {
|
|||||||
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.language());
|
args.add("language=" + c.locale().getISO3Language());
|
||||||
});
|
});
|
||||||
args.add(out.toString());
|
args.add(out.toString());
|
||||||
run(args);
|
run(args);
|
||||||
return new FileVideoImpl(out, new VideoInfoImpl("mkv"));
|
return new FileVideoImpl(out, new VideoInfoImpl("mkv", video.info().width(), video.info().height(), video.info().duration()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -80,7 +85,7 @@ public class FFmpegVideoConverter implements VideoConverter {
|
|||||||
final var out = getTempFile(video.info().videoFormat());
|
final var out = getTempFile(video.info().videoFormat());
|
||||||
final var subtitleArg = subtitleConverter.formatName().equalsIgnoreCase("ass") ? "ass=" + subtitlesPath : "subtitles=" + subtitlesPath;
|
final var subtitleArg = subtitleConverter.formatName().equalsIgnoreCase("ass") ? "ass=" + subtitlesPath : "subtitles=" + subtitlesPath;
|
||||||
final var args = List.of(
|
final var args = List.of(
|
||||||
ffmpegPath.toString(),
|
getFFmpegPath(),
|
||||||
"-i",
|
"-i",
|
||||||
videoPath.toString(),
|
videoPath.toString(),
|
||||||
"-vf",
|
"-vf",
|
||||||
@@ -97,7 +102,7 @@ public class FFmpegVideoConverter implements VideoConverter {
|
|||||||
final var audioPath = getTempFile(".wav");
|
final var audioPath = getTempFile(".wav");
|
||||||
final var dumpVideoPath = getTempFile("." + video.info().videoFormat());
|
final var dumpVideoPath = getTempFile("." + video.info().videoFormat());
|
||||||
final var args = List.of(
|
final var args = List.of(
|
||||||
ffmpegPath.toString(),
|
getFFmpegPath(),
|
||||||
"-i",
|
"-i",
|
||||||
videoPath.toString(),
|
videoPath.toString(),
|
||||||
"-map",
|
"-map",
|
||||||
@@ -129,7 +134,7 @@ public class FFmpegVideoConverter implements VideoConverter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private SequencedMap<SubtitleCollection, Path> dumpCollections(final Collection<SubtitleCollection> collections) throws IOException {
|
private SequencedMap<SubtitleCollection, Path> dumpCollections(final Collection<SubtitleCollection> collections) throws IOException {
|
||||||
final var ret = new LinkedHashMap<SubtitleCollection, Path>(collections.size());
|
final var ret = LinkedHashMap.<SubtitleCollection, Path>newLinkedHashMap(collections.size());
|
||||||
for (final var subtitles : collections) {
|
for (final var subtitles : collections) {
|
||||||
ret.put(subtitles, dumpSubtitles(subtitles));
|
ret.put(subtitles, dumpSubtitles(subtitles));
|
||||||
}
|
}
|
||||||
@@ -148,23 +153,7 @@ public class FFmpegVideoConverter implements VideoConverter {
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void run(final String... args) throws IOException {
|
private String getFFmpegPath() {
|
||||||
run(Arrays.asList(args));
|
return Files.isRegularFile(bundledPath) ? bundledPath.toString() : systemPath.toString();
|
||||||
}
|
|
||||||
|
|
||||||
private void run(final List<String> args) throws IOException {
|
|
||||||
final var builder = new ProcessBuilder(args);
|
|
||||||
builder.inheritIO();
|
|
||||||
builder.redirectErrorStream(true);
|
|
||||||
final var process = builder.start();
|
|
||||||
try {
|
|
||||||
process.waitFor(1, TimeUnit.HOURS);
|
|
||||||
} catch (final InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
process.destroy();
|
|
||||||
}
|
|
||||||
if (process.exitValue() != 0) {
|
|
||||||
throw new IOException("FFmpeg exited with code " + process.exitValue());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package com.github.gtache.autosubtitle.ffmpeg;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.Video;
|
||||||
|
import com.github.gtache.autosubtitle.VideoLoader;
|
||||||
|
import com.github.gtache.autosubtitle.impl.FileVideoImpl;
|
||||||
|
import com.github.gtache.autosubtitle.impl.VideoInfoImpl;
|
||||||
|
import com.github.gtache.autosubtitle.modules.ffmpeg.FFprobeBundledPath;
|
||||||
|
import com.github.gtache.autosubtitle.modules.ffmpeg.FFprobeSystemPath;
|
||||||
|
import com.github.gtache.autosubtitle.process.impl.AbstractProcessRunner;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FFprobe implementation of {@link VideoLoader}
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
public class FFprobeVideoLoader extends AbstractProcessRunner implements VideoLoader {
|
||||||
|
|
||||||
|
private final Path bundledPath;
|
||||||
|
private final Path systemPath;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
FFprobeVideoLoader(@FFprobeBundledPath final Path bundledPath, @FFprobeSystemPath final Path systemPath) {
|
||||||
|
this.bundledPath = requireNonNull(bundledPath);
|
||||||
|
this.systemPath = requireNonNull(systemPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Video loadVideo(final Path path) throws IOException {
|
||||||
|
final var result = run(getFFprobePath(), "-v", "error", "-select_streams", "v", "-show_entries", "stream=width,height,duration", "-of", "csv=p=0", path.toString());
|
||||||
|
final var resolution = result.output().getLast();
|
||||||
|
final var split = resolution.split(",");
|
||||||
|
final var width = Integer.parseInt(split[0]);
|
||||||
|
final var height = Integer.parseInt(split[1]);
|
||||||
|
final var filename = path.getFileName().toString();
|
||||||
|
final var extension = filename.substring(filename.lastIndexOf('.') + 1);
|
||||||
|
final var duration = (long) (Double.parseDouble(split[2]) * 1000L);
|
||||||
|
final var info = new VideoInfoImpl(extension, width, height, duration);
|
||||||
|
return new FileVideoImpl(path, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getFFprobePath() {
|
||||||
|
return Files.isRegularFile(bundledPath) ? bundledPath.toString() : systemPath.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.github.gtache.autosubtitle.modules.ffmpeg;
|
||||||
|
|
||||||
|
import javax.inject.Qualifier;
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
|
@Qualifier
|
||||||
|
@Documented
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
|
||||||
|
public @interface FFBundledRoot {
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.github.gtache.autosubtitle.modules.ffmpeg;
|
||||||
|
|
||||||
|
import javax.inject.Qualifier;
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
|
@Qualifier
|
||||||
|
@Documented
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
|
||||||
|
public @interface FFmpegBundledPath {
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.github.gtache.autosubtitle.modules.ffmpeg;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.VideoConverter;
|
||||||
|
import com.github.gtache.autosubtitle.VideoLoader;
|
||||||
|
import com.github.gtache.autosubtitle.ffmpeg.FFmpegVideoConverter;
|
||||||
|
import com.github.gtache.autosubtitle.ffmpeg.FFprobeVideoLoader;
|
||||||
|
import dagger.Binds;
|
||||||
|
import dagger.Module;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
public interface FFmpegModule {
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
VideoConverter bindsVideoConverter(final FFmpegVideoConverter converter);
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
VideoLoader bindsVideoLoader(final FFprobeVideoLoader loader);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.github.gtache.autosubtitle.modules.ffmpeg;
|
||||||
|
|
||||||
|
import javax.inject.Qualifier;
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
|
@Qualifier
|
||||||
|
@Documented
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
|
||||||
|
public @interface FFmpegSystemPath {
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.github.gtache.autosubtitle.modules.ffmpeg;
|
||||||
|
|
||||||
|
import javax.inject.Qualifier;
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
|
@Qualifier
|
||||||
|
@Documented
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
|
||||||
|
public @interface FFmpegVersion {
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.github.gtache.autosubtitle.modules.ffmpeg;
|
||||||
|
|
||||||
|
import javax.inject.Qualifier;
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
|
@Qualifier
|
||||||
|
@Documented
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
|
||||||
|
public @interface FFprobeBundledPath {
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.github.gtache.autosubtitle.modules.ffmpeg;
|
||||||
|
|
||||||
|
import javax.inject.Qualifier;
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
|
@Qualifier
|
||||||
|
@Documented
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
|
||||||
|
public @interface FFprobeSystemPath {
|
||||||
|
}
|
||||||
@@ -1,15 +1,62 @@
|
|||||||
package com.github.gtache.autosubtitle.setup.ffmpeg;
|
package com.github.gtache.autosubtitle.setup.ffmpeg;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.impl.Architecture;
|
||||||
|
import com.github.gtache.autosubtitle.impl.OS;
|
||||||
|
import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegBundledPath;
|
||||||
|
import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegSystemPath;
|
||||||
|
import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegVersion;
|
||||||
|
import com.github.gtache.autosubtitle.process.impl.AbstractProcessRunner;
|
||||||
import com.github.gtache.autosubtitle.setup.SetupException;
|
import com.github.gtache.autosubtitle.setup.SetupException;
|
||||||
import com.github.gtache.autosubtitle.setup.SetupManager;
|
import com.github.gtache.autosubtitle.setup.SetupManager;
|
||||||
|
import com.github.gtache.autosubtitle.setup.SetupStatus;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manager managing the FFmpeg installation
|
* Manager managing the FFmpeg installation
|
||||||
*/
|
*/
|
||||||
public class FFmpegSetupManager implements SetupManager {
|
public class FFmpegSetupManager extends AbstractProcessRunner implements SetupManager {
|
||||||
|
private static final Logger logger = LogManager.getLogger(FFmpegSetupManager.class);
|
||||||
|
private final Path bundledPath;
|
||||||
|
private final Path systemPath;
|
||||||
|
private final String version;
|
||||||
|
private final OS os;
|
||||||
|
private final Architecture architecture;
|
||||||
|
private final String executableExtension;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
FFmpegSetupManager(@FFmpegBundledPath final Path bundledPath, @FFmpegSystemPath final Path systemPath, @FFmpegVersion final String version, final OS os, final Architecture architecture) {
|
||||||
|
this.bundledPath = Objects.requireNonNull(bundledPath);
|
||||||
|
this.systemPath = Objects.requireNonNull(systemPath);
|
||||||
|
this.version = Objects.requireNonNull(version);
|
||||||
|
this.os = Objects.requireNonNull(os);
|
||||||
|
this.architecture = Objects.requireNonNull(architecture);
|
||||||
|
this.executableExtension = os == OS.WINDOWS ? ".exe" : "";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isInstalled() throws SetupException {
|
public String name() {
|
||||||
return checkSystemFFmpeg() || checkBundledFFmpeg();
|
return "FFmpeg";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SetupStatus status() {
|
||||||
|
try {
|
||||||
|
if (checkSystemFFmpeg() || checkBundledFFmpeg()) {
|
||||||
|
return SetupStatus.INSTALLED;
|
||||||
|
} else {
|
||||||
|
return SetupStatus.NOT_INSTALLED;
|
||||||
|
}
|
||||||
|
} catch (final IOException e) {
|
||||||
|
logger.error("Error checking status of {}", name(), e);
|
||||||
|
return SetupStatus.ERRORED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -22,21 +69,17 @@ public class FFmpegSetupManager implements SetupManager {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isUpdateAvailable() throws SetupException {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void update() throws SetupException {
|
public void update() throws SetupException {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkSystemFFmpeg() {
|
private boolean checkSystemFFmpeg() throws IOException {
|
||||||
return false;
|
final var result = run(systemPath.toString(), "-h");
|
||||||
|
return result.exitCode() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkBundledFFmpeg() {
|
private boolean checkBundledFFmpeg() throws IOException {
|
||||||
return false;
|
return Files.isRegularFile(bundledPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package com.github.gtache.autosubtitle.setup.modules.ffmpeg;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.modules.ffmpeg.FFBundledRoot;
|
||||||
|
import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegBundledPath;
|
||||||
|
import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegSystemPath;
|
||||||
|
import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegVersion;
|
||||||
|
import com.github.gtache.autosubtitle.modules.ffmpeg.FFprobeBundledPath;
|
||||||
|
import com.github.gtache.autosubtitle.modules.ffmpeg.FFprobeSystemPath;
|
||||||
|
import com.github.gtache.autosubtitle.modules.impl.ExecutableExtension;
|
||||||
|
import com.github.gtache.autosubtitle.setup.SetupManager;
|
||||||
|
import com.github.gtache.autosubtitle.setup.ffmpeg.FFmpegSetupManager;
|
||||||
|
import com.github.gtache.autosubtitle.setup.modules.impl.VideoConverterSetup;
|
||||||
|
import dagger.Binds;
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dagger module for FFmpeg setup
|
||||||
|
*/
|
||||||
|
@Module
|
||||||
|
public abstract class FFmpegSetupModule {
|
||||||
|
|
||||||
|
private static final String FFMPEG = "ffmpeg";
|
||||||
|
private static final String FFPROBE = "ffprobe";
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@VideoConverterSetup
|
||||||
|
abstract SetupManager bindsFFmpegSetupManager(final FFmpegSetupManager manager);
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@FFBundledRoot
|
||||||
|
static Path providesFFBundledRoot() {
|
||||||
|
return Paths.get("tools", FFMPEG);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@FFprobeBundledPath
|
||||||
|
static Path providesFFProbeBundledPath(@FFBundledRoot final Path root, @ExecutableExtension final String extension) {
|
||||||
|
return root.resolve(FFPROBE + extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@FFprobeSystemPath
|
||||||
|
static Path providesFFProbeSystemPath(@ExecutableExtension final String extension) {
|
||||||
|
return Paths.get(FFPROBE + extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@FFmpegBundledPath
|
||||||
|
static Path providesFFmpegBundledPath(@FFBundledRoot final Path root, @ExecutableExtension final String extension) {
|
||||||
|
return root.resolve(FFMPEG + extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@FFmpegSystemPath
|
||||||
|
static Path providesFFmpegSystemPath(@ExecutableExtension final String extension) {
|
||||||
|
return Paths.get(FFMPEG + extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@FFmpegVersion
|
||||||
|
static String providesFFmpegVersion() {
|
||||||
|
return "7.0.1";
|
||||||
|
}
|
||||||
|
}
|
||||||
14
ffmpeg/src/main/java/module-info.java
Normal file
14
ffmpeg/src/main/java/module-info.java
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* FFmpeg module for auto-subtitle
|
||||||
|
*/
|
||||||
|
module com.github.gtache.autosubtitle.ffmpeg {
|
||||||
|
requires transitive com.github.gtache.autosubtitle.core;
|
||||||
|
requires transitive dagger;
|
||||||
|
requires transitive javax.inject;
|
||||||
|
requires org.apache.logging.log4j;
|
||||||
|
|
||||||
|
exports com.github.gtache.autosubtitle.ffmpeg;
|
||||||
|
exports com.github.gtache.autosubtitle.modules.ffmpeg;
|
||||||
|
exports com.github.gtache.autosubtitle.setup.ffmpeg;
|
||||||
|
exports com.github.gtache.autosubtitle.setup.modules.ffmpeg;
|
||||||
|
}
|
||||||
12
fx/pom.xml
12
fx/pom.xml
@@ -11,6 +11,10 @@
|
|||||||
|
|
||||||
<artifactId>autosubtitle-fx</artifactId>
|
<artifactId>autosubtitle-fx</artifactId>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<javafx.version>22.0.1</javafx.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.gtache.autosubtitle</groupId>
|
<groupId>com.github.gtache.autosubtitle</groupId>
|
||||||
@@ -24,15 +28,19 @@
|
|||||||
<groupId>com.google.dagger</groupId>
|
<groupId>com.google.dagger</groupId>
|
||||||
<artifactId>dagger</artifactId>
|
<artifactId>dagger</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
|
<artifactId>log4j-api</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openjfx</groupId>
|
<groupId>org.openjfx</groupId>
|
||||||
<artifactId>javafx-media</artifactId>
|
<artifactId>javafx-media</artifactId>
|
||||||
<version>22.0.1</version>
|
<version>${javafx.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openjfx</groupId>
|
<groupId>org.openjfx</groupId>
|
||||||
<artifactId>javafx-fxml</artifactId>
|
<artifactId>javafx-fxml</artifactId>
|
||||||
<version>22.0.1</version>
|
<version>${javafx.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|||||||
@@ -1,146 +1,42 @@
|
|||||||
package com.github.gtache.autosubtitle.gui.fx;
|
package com.github.gtache.autosubtitle.gui.fx;
|
||||||
|
|
||||||
import com.github.gtache.autosubtitle.subtitle.EditableSubtitle;
|
|
||||||
import com.github.gtache.autosubtitle.subtitle.Subtitle;
|
|
||||||
import com.github.gtache.autosubtitle.subtitle.SubtitleExtractor;
|
|
||||||
import com.github.gtache.autosubtitle.VideoConverter;
|
|
||||||
import com.github.gtache.autosubtitle.gui.MainController;
|
import com.github.gtache.autosubtitle.gui.MainController;
|
||||||
import com.github.gtache.autosubtitle.impl.FileVideoImpl;
|
|
||||||
import javafx.beans.binding.Bindings;
|
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
|
||||||
import javafx.beans.value.ChangeListener;
|
|
||||||
import javafx.beans.value.ObservableValue;
|
|
||||||
import javafx.event.ActionEvent;
|
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.TabPane;
|
||||||
import javafx.scene.control.TableColumn;
|
|
||||||
import javafx.scene.control.TableView;
|
|
||||||
import javafx.scene.control.TextField;
|
|
||||||
import javafx.scene.layout.StackPane;
|
|
||||||
import javafx.scene.media.Media;
|
|
||||||
import javafx.scene.media.MediaPlayer;
|
|
||||||
import javafx.scene.media.MediaView;
|
|
||||||
import javafx.stage.FileChooser;
|
|
||||||
import javafx.stage.Window;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import java.nio.file.Path;
|
import javax.inject.Singleton;
|
||||||
import java.time.Duration;
|
import java.util.Objects;
|
||||||
import java.time.LocalTime;
|
|
||||||
import java.util.Comparator;
|
|
||||||
|
|
||||||
import static java.util.Objects.requireNonNull;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FX implementation of {@link MainController}
|
* FX implementation of {@link MainController}
|
||||||
*/
|
*/
|
||||||
|
@Singleton
|
||||||
public class FXMainController implements MainController {
|
public class FXMainController implements MainController {
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private MediaView videoView;
|
private TabPane tabPane;
|
||||||
@FXML
|
|
||||||
private TextField fileField;
|
|
||||||
@FXML
|
|
||||||
private Button extractButton;
|
|
||||||
@FXML
|
|
||||||
private Button resetButton;
|
|
||||||
@FXML
|
|
||||||
private Button exportButton;
|
|
||||||
@FXML
|
|
||||||
private TableView<EditableSubtitle> subtitlesTable;
|
|
||||||
@FXML
|
|
||||||
private TableColumn<EditableSubtitle, LocalTime> startColumn;
|
|
||||||
@FXML
|
|
||||||
private TableColumn<EditableSubtitle, LocalTime> endColumn;
|
|
||||||
@FXML
|
|
||||||
private TableColumn<EditableSubtitle, String> textColumn;
|
|
||||||
@FXML
|
|
||||||
private StackPane stackPane;
|
|
||||||
|
|
||||||
private final FXMainModel model;
|
private final FXMainModel model;
|
||||||
private final SubtitleExtractor subtitleExtractor;
|
|
||||||
private final VideoConverter videoConverter;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
FXMainController(final FXMainModel model, final SubtitleExtractor subtitleExtractor, final VideoConverter videoConverter) {
|
FXMainController(final FXMainModel model) {
|
||||||
this.model = requireNonNull(model);
|
this.model = Objects.requireNonNull(model);
|
||||||
this.subtitleExtractor = requireNonNull(subtitleExtractor);
|
|
||||||
this.videoConverter = requireNonNull(videoConverter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private void initialize() {
|
private void initialize() {
|
||||||
extractButton.disableProperty().bind(model.videoProperty().isNull());
|
tabPane.getSelectionModel().selectedIndexProperty().addListener((observable, oldValue, newValue) -> model.selectTab(newValue.intValue()));
|
||||||
resetButton.disableProperty().bind(Bindings.isEmpty(model.subtitles()));
|
model.selectedTabProperty().addListener((observable, oldValue, newValue) -> tabPane.getSelectionModel().select(newValue.intValue()));
|
||||||
exportButton.disableProperty().bind(Bindings.isEmpty(model.subtitles()));
|
|
||||||
|
|
||||||
subtitlesTable.setItems(model.subtitles());
|
|
||||||
startColumn.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue() == null ? null : param.getValue().start()));
|
|
||||||
endColumn.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue() == null ? null : param.getValue().end()));
|
|
||||||
textColumn.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue() == null ? "" : param.getValue().content()));
|
|
||||||
|
|
||||||
model.selectedSubtitleProperty().addListener(new ChangeListener<EditableSubtitle>() {
|
|
||||||
@Override
|
|
||||||
public void changed(final ObservableValue<? extends EditableSubtitle> observable, final EditableSubtitle oldValue, final EditableSubtitle newValue) {
|
|
||||||
if (newValue != null) {
|
|
||||||
videoView.getMediaPlayer().seek(Duration.of(newValue.start().to));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private void fileButtonPressed() {
|
|
||||||
final var filePicker = new FileChooser();
|
|
||||||
final var file = filePicker.showOpenDialog(window());
|
|
||||||
if (file != null) {
|
|
||||||
loadVideo(file.toPath());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void extractSubtitles() {
|
public void selectTab(final int index) {
|
||||||
if (model.video() != null) {
|
model.selectTab(index);
|
||||||
final var subtitles = subtitleExtractor.extract(model.video());
|
|
||||||
model.subtitles().setAll(subtitles.stream().sorted(Comparator.comparing(Subtitle::start)).toList());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loadVideo(final Path file) {
|
|
||||||
fileField.setText(file.toAbsolutePath().toString());
|
|
||||||
model.videoProperty().set(new FileVideoImpl(file));
|
|
||||||
final var media = new Media(file.toUri().toString());
|
|
||||||
final var player = new MediaPlayer(media);
|
|
||||||
videoView.getMediaPlayer().dispose();
|
|
||||||
videoView.setMediaPlayer(player);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FXMainModel model() {
|
public FXMainModel model() {
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Window window() {
|
|
||||||
return videoView.getScene().getWindow();
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private void extractPressed(final ActionEvent actionEvent) {
|
|
||||||
extractSubtitles();
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private void exportPressed(final ActionEvent actionEvent) {
|
|
||||||
final var filePicker = new FileChooser();
|
|
||||||
final var file = filePicker.showSaveDialog(window());
|
|
||||||
if (file != null) {
|
|
||||||
videoConverter.addSoftSubtitles(model.video(), model.subtitles());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private void resetButtonPressed(final ActionEvent actionEvent) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,51 +1,36 @@
|
|||||||
package com.github.gtache.autosubtitle.gui.fx;
|
package com.github.gtache.autosubtitle.gui.fx;
|
||||||
|
|
||||||
import com.github.gtache.autosubtitle.subtitle.EditableSubtitle;
|
|
||||||
import com.github.gtache.autosubtitle.Video;
|
|
||||||
import com.github.gtache.autosubtitle.gui.MainModel;
|
import com.github.gtache.autosubtitle.gui.MainModel;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.IntegerProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FX implementation of {@link MainModel}
|
* FX implementation of {@link MainModel}
|
||||||
*/
|
*/
|
||||||
|
@Singleton
|
||||||
public class FXMainModel implements MainModel {
|
public class FXMainModel implements MainModel {
|
||||||
|
|
||||||
private final ObjectProperty<Video> video;
|
private final IntegerProperty selectedTab;
|
||||||
private final ObservableList<EditableSubtitle> subtitles;
|
|
||||||
private final ObjectProperty<EditableSubtitle> subtitle;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
FXMainModel() {
|
FXMainModel() {
|
||||||
video = new SimpleObjectProperty<>();
|
selectedTab = new SimpleIntegerProperty(0);
|
||||||
subtitles = FXCollections.observableArrayList();
|
|
||||||
subtitle = new SimpleObjectProperty<>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Video video() {
|
public int selectedTab() {
|
||||||
return video.get();
|
return selectedTab.get();
|
||||||
}
|
|
||||||
|
|
||||||
public ObjectProperty<Video> videoProperty() {
|
|
||||||
return video;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ObservableList<EditableSubtitle> subtitles() {
|
public void selectTab(final int index) {
|
||||||
return subtitles;
|
selectedTab.set(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
IntegerProperty selectedTabProperty() {
|
||||||
public EditableSubtitle selectedSubtitle() {
|
return selectedTab;
|
||||||
return subtitle.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObjectProperty<EditableSubtitle> selectedSubtitleProperty() {
|
|
||||||
return subtitle;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.github.gtache.autosubtitle.gui.fx;
|
||||||
|
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds the media model
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
public class FXMediaBinder {
|
||||||
|
|
||||||
|
private final FXWorkModel workModel;
|
||||||
|
private final FXMediaModel mediaModel;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
FXMediaBinder(final FXWorkModel workModel, final FXMediaModel mediaModel) {
|
||||||
|
this.workModel = Objects.requireNonNull(workModel);
|
||||||
|
this.mediaModel = Objects.requireNonNull(mediaModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createBindings() {
|
||||||
|
mediaModel.videoProperty().bindBidirectional(workModel.videoProperty());
|
||||||
|
Bindings.bindContent(workModel.subtitles(), mediaModel.subtitles());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
package com.github.gtache.autosubtitle.gui.fx;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.gui.MediaController;
|
||||||
|
import com.github.gtache.autosubtitle.impl.FileVideoImpl;
|
||||||
|
import com.github.gtache.autosubtitle.modules.gui.Pause;
|
||||||
|
import com.github.gtache.autosubtitle.modules.gui.Play;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.Subtitle;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.fx.SubtitleLabel;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.Cursor;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.Slider;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
import javafx.scene.media.Media;
|
||||||
|
import javafx.scene.media.MediaPlayer;
|
||||||
|
import javafx.scene.media.MediaView;
|
||||||
|
import javafx.util.Duration;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FX implementation of {@link MediaController}
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
public class FXMediaController implements MediaController {
|
||||||
|
|
||||||
|
private static final Logger logger = LogManager.getLogger(FXMediaController.class);
|
||||||
|
@FXML
|
||||||
|
private StackPane stackPane;
|
||||||
|
@FXML
|
||||||
|
private MediaView videoView;
|
||||||
|
@FXML
|
||||||
|
private Slider playSlider;
|
||||||
|
@FXML
|
||||||
|
private Label playLabel;
|
||||||
|
@FXML
|
||||||
|
private Button playButton;
|
||||||
|
@FXML
|
||||||
|
private Slider volumeSlider;
|
||||||
|
@FXML
|
||||||
|
private Label volumeValueLabel;
|
||||||
|
|
||||||
|
private final FXMediaModel model;
|
||||||
|
private final Image playImage;
|
||||||
|
private final Image pauseImage;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
FXMediaController(final FXMediaModel model, @Play final Image playImage, @Pause final Image pauseImage) {
|
||||||
|
this.model = requireNonNull(model);
|
||||||
|
this.playImage = requireNonNull(playImage);
|
||||||
|
this.pauseImage = requireNonNull(pauseImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void initialize() {
|
||||||
|
volumeValueLabel.textProperty().bind(Bindings.createStringBinding(() -> String.valueOf((int) model.volume()), model.volumeProperty()));
|
||||||
|
playLabel.textProperty().bind(Bindings.createStringBinding(() -> model.position() + "/" + model.duration(), model.positionProperty(), model.durationProperty()));
|
||||||
|
model.positionProperty().bindBidirectional(playSlider.valueProperty());
|
||||||
|
|
||||||
|
model.volumeProperty().addListener((observable, oldValue, newValue) -> volumeSlider.setValue(newValue.doubleValue() * 100));
|
||||||
|
volumeSlider.valueProperty().addListener((observable, oldValue, newValue) -> model.setVolume(newValue.doubleValue() / 100));
|
||||||
|
|
||||||
|
model.isPlayingProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
final var player = videoView.getMediaPlayer();
|
||||||
|
if (player != null) {
|
||||||
|
if (Boolean.TRUE.equals(newValue)) {
|
||||||
|
player.play();
|
||||||
|
} else {
|
||||||
|
player.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
model.videoProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
if (videoView.getMediaPlayer() != null) {
|
||||||
|
videoView.getMediaPlayer().dispose();
|
||||||
|
}
|
||||||
|
if (newValue instanceof final FileVideoImpl fileVideo) {
|
||||||
|
final var media = new Media(fileVideo.path().toUri().toString());
|
||||||
|
final var player = new MediaPlayer(media);
|
||||||
|
player.currentTimeProperty().addListener((ignored, oldTime, newTime) -> {
|
||||||
|
final var millis = newTime.toMillis();
|
||||||
|
playSlider.setValue(millis);
|
||||||
|
model.subtitles().forEach(s -> {
|
||||||
|
if (s.start() <= millis && s.end() >= millis) {
|
||||||
|
final var label = createDraggableLabel(s);
|
||||||
|
stackPane.getChildren().add(label);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
playSlider.valueProperty().addListener((observable1, oldValue1, newValue1) -> seek(newValue1.longValue()));
|
||||||
|
player.volumeProperty().bindBidirectional(model.volumeProperty());
|
||||||
|
player.setOnPlaying(() -> model.setIsPlaying(true));
|
||||||
|
player.setOnPaused(() -> model.setIsPlaying(false));
|
||||||
|
playSlider.setMax(model.duration());
|
||||||
|
playSlider.setValue(0L);
|
||||||
|
videoView.setMediaPlayer(player);
|
||||||
|
} else {
|
||||||
|
logger.error("Unsupported video type : {}", newValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
playButton.disableProperty().bind(model.videoProperty().isNull());
|
||||||
|
playButton.graphicProperty().bind(Bindings.createObjectBinding(() -> {
|
||||||
|
final ImageView view;
|
||||||
|
if (model.isPlaying()) {
|
||||||
|
view = new ImageView(pauseImage);
|
||||||
|
} else {
|
||||||
|
view = new ImageView(playImage);
|
||||||
|
}
|
||||||
|
view.setPreserveRatio(true);
|
||||||
|
view.setFitWidth(24);
|
||||||
|
view.setFitHeight(24);
|
||||||
|
return view;
|
||||||
|
}, model.isPlayingProperty()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void playPressed() {
|
||||||
|
model.setIsPlaying(!model.isPlaying());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void play() {
|
||||||
|
model.setIsPlaying(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pause() {
|
||||||
|
model.setIsPlaying(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seek(final long position) {
|
||||||
|
if (videoView.getMediaPlayer() != null) {
|
||||||
|
videoView.getMediaPlayer().seek(Duration.millis(position));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FXMediaModel model() {
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SubtitleLabel createDraggableLabel(final Subtitle subtitle) {
|
||||||
|
final var label = new SubtitleLabel(subtitle);
|
||||||
|
label.setOpacity(0.8);
|
||||||
|
label.setOnMousePressed(mouseEvent -> {
|
||||||
|
final var x = label.getLayoutX() - mouseEvent.getSceneX();
|
||||||
|
final var y = label.getLayoutY() - mouseEvent.getSceneY();
|
||||||
|
label.setDragged(x, y);
|
||||||
|
label.setCursor(Cursor.MOVE);
|
||||||
|
});
|
||||||
|
label.setOnMouseReleased(mouseEvent -> label.setCursor(Cursor.HAND));
|
||||||
|
label.setOnMouseDragged(mouseEvent -> {
|
||||||
|
label.setLayoutX(mouseEvent.getSceneX() + label.getDraggedX());
|
||||||
|
label.setLayoutY(mouseEvent.getSceneY() + label.getDraggedY());
|
||||||
|
});
|
||||||
|
label.setOnMouseEntered(mouseEvent -> label.setCursor(Cursor.HAND));
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package com.github.gtache.autosubtitle.gui.fx;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.Video;
|
||||||
|
import com.github.gtache.autosubtitle.gui.MediaModel;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.EditableSubtitle;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.property.*;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FX implementation of {@link com.github.gtache.autosubtitle.gui.MediaModel}
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
public class FXMediaModel implements MediaModel {
|
||||||
|
|
||||||
|
private final ObjectProperty<Video> video;
|
||||||
|
private final DoubleProperty volume;
|
||||||
|
private final BooleanProperty isPlaying;
|
||||||
|
private final ReadOnlyLongWrapper duration;
|
||||||
|
private final LongProperty position;
|
||||||
|
private final ObservableList<EditableSubtitle> subtitles;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
FXMediaModel() {
|
||||||
|
this.video = new SimpleObjectProperty<>();
|
||||||
|
this.volume = new SimpleDoubleProperty(0.0);
|
||||||
|
this.isPlaying = new SimpleBooleanProperty(false);
|
||||||
|
this.duration = new ReadOnlyLongWrapper(0);
|
||||||
|
this.position = new SimpleLongProperty(0);
|
||||||
|
this.subtitles = FXCollections.observableArrayList();
|
||||||
|
duration.bind(Bindings.createLongBinding(() -> {
|
||||||
|
if (video() == null) {
|
||||||
|
return 0L;
|
||||||
|
} else {
|
||||||
|
return video().info().duration();
|
||||||
|
}
|
||||||
|
}, video));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Video video() {
|
||||||
|
return video.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setVideo(final Video video) {
|
||||||
|
this.video.set(video);
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectProperty<Video> videoProperty() {
|
||||||
|
return video;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double volume() {
|
||||||
|
return volume.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setVolume(final double volume) {
|
||||||
|
this.volume.set(volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
DoubleProperty volumeProperty() {
|
||||||
|
return volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPlaying() {
|
||||||
|
return isPlaying.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setIsPlaying(final boolean playing) {
|
||||||
|
this.isPlaying.set(playing);
|
||||||
|
}
|
||||||
|
|
||||||
|
BooleanProperty isPlayingProperty() {
|
||||||
|
return isPlaying;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long duration() {
|
||||||
|
return duration.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadOnlyLongProperty durationProperty() {
|
||||||
|
return duration.getReadOnlyProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long position() {
|
||||||
|
return position.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPosition(final long position) {
|
||||||
|
this.position.set(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ObservableList<EditableSubtitle> subtitles() {
|
||||||
|
return subtitles;
|
||||||
|
}
|
||||||
|
|
||||||
|
LongProperty positionProperty() {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,24 +1,41 @@
|
|||||||
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.setup.SetupException;
|
||||||
import com.github.gtache.autosubtitle.setup.SetupManager;
|
import com.github.gtache.autosubtitle.setup.SetupManager;
|
||||||
import com.github.gtache.autosubtitle.setup.modules.impl.SubtitleExtractor;
|
import com.github.gtache.autosubtitle.setup.SetupStatus;
|
||||||
import com.github.gtache.autosubtitle.setup.modules.impl.Translator;
|
import com.github.gtache.autosubtitle.setup.modules.impl.SubtitleExtractorSetup;
|
||||||
import com.github.gtache.autosubtitle.setup.modules.impl.VideoConverter;
|
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.property.ObjectProperty;
|
||||||
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.ProgressBar;
|
||||||
import javafx.stage.Window;
|
import javafx.stage.Window;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FX implementation of {@link SetupController}
|
* FX implementation of {@link SetupController}
|
||||||
*/
|
*/
|
||||||
|
@Singleton
|
||||||
public class FXSetupController implements SetupController {
|
public class FXSetupController implements SetupController {
|
||||||
|
|
||||||
|
private static final Logger logger = LogManager.getLogger(FXSetupController.class);
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private ResourceBundle resources;
|
private ResourceBundle resources;
|
||||||
@FXML
|
@FXML
|
||||||
@@ -39,89 +56,209 @@ public class FXSetupController implements SetupController {
|
|||||||
private MenuButton extractorButton;
|
private MenuButton extractorButton;
|
||||||
@FXML
|
@FXML
|
||||||
private MenuButton translatorButton;
|
private MenuButton translatorButton;
|
||||||
|
@FXML
|
||||||
|
private ProgressBar converterProgress;
|
||||||
|
@FXML
|
||||||
|
private ProgressBar extractorProgress;
|
||||||
|
@FXML
|
||||||
|
private ProgressBar translatorProgress;
|
||||||
|
@FXML
|
||||||
|
private Label converterProgressLabel;
|
||||||
|
@FXML
|
||||||
|
private Label extractorProgressLabel;
|
||||||
|
@FXML
|
||||||
|
private Label translatorProgressLabel;
|
||||||
|
|
||||||
private final FXSetupModel model;
|
private final FXSetupModel model;
|
||||||
private final SetupManager converterManager;
|
private final SetupManager converterManager;
|
||||||
private final SetupManager extractorManager;
|
private final SetupManager extractorManager;
|
||||||
private final SetupManager translatorManager;
|
private final SetupManager translatorManager;
|
||||||
|
|
||||||
|
private final Map<SetupManager, ObjectProperty<SetupStatus>> statusMap;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
FXSetupController(final FXSetupModel model, @VideoConverter final SetupManager converterManager,
|
FXSetupController(final FXSetupModel model,
|
||||||
@SubtitleExtractor final SetupManager extractorManager, @Translator final SetupManager translatorManager) {
|
@VideoConverterSetup final SetupManager converterManager,
|
||||||
|
@SubtitleExtractorSetup final SetupManager extractorManager,
|
||||||
|
@TranslatorSetup final SetupManager translatorManager) {
|
||||||
this.model = Objects.requireNonNull(model);
|
this.model = Objects.requireNonNull(model);
|
||||||
this.converterManager = Objects.requireNonNull(converterManager);
|
this.converterManager = Objects.requireNonNull(converterManager);
|
||||||
this.extractorManager = Objects.requireNonNull(extractorManager);
|
this.extractorManager = Objects.requireNonNull(extractorManager);
|
||||||
this.translatorManager = Objects.requireNonNull(translatorManager);
|
this.translatorManager = Objects.requireNonNull(translatorManager);
|
||||||
|
statusMap = HashMap.newHashMap(3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private void initialize() {
|
private void initialize() {
|
||||||
|
statusMap.put(converterManager, model.videoConverterStatusProperty());
|
||||||
|
statusMap.put(extractorManager, model.subtitleExtractorStatusProperty());
|
||||||
|
statusMap.put(translatorManager, model.translatorStatusProperty());
|
||||||
|
|
||||||
|
model.setSubtitleExtractorStatus(extractorManager.status());
|
||||||
|
model.setVideoConverterStatus(converterManager.status());
|
||||||
|
model.setTranslatorStatus(translatorManager.status());
|
||||||
|
|
||||||
converterNameLabel.setText(converterManager.name());
|
converterNameLabel.setText(converterManager.name());
|
||||||
converterStatusLabel.setText(converterManager.status().toString());
|
|
||||||
extractorNameLabel.setText(extractorManager.name());
|
extractorNameLabel.setText(extractorManager.name());
|
||||||
extractorStatusLabel.setText(extractorManager.status().toString());
|
|
||||||
translatorNameLabel.setText(translatorManager.name());
|
translatorNameLabel.setText(translatorManager.name());
|
||||||
translatorStatusLabel.setText(translatorManager.status().toString());
|
bindLabelStatus(converterStatusLabel, model.videoConverterStatusProperty());
|
||||||
|
bindLabelStatus(extractorStatusLabel, model.subtitleExtractorStatusProperty());
|
||||||
|
bindLabelStatus(translatorStatusLabel, model.translatorStatusProperty());
|
||||||
|
|
||||||
|
converterProgress.progressProperty().bindBidirectional(model.videoConverterSetupProgressProperty());
|
||||||
|
extractorProgress.progressProperty().bindBidirectional(model.subtitleExtractorSetupProgressProperty());
|
||||||
|
translatorProgress.progressProperty().bindBidirectional(model.translatorSetupProgressProperty());
|
||||||
|
|
||||||
|
converterProgress.visibleProperty().bind(model.videoConverterSetupProgressProperty().greaterThan(0));
|
||||||
|
extractorProgress.visibleProperty().bind(model.subtitleExtractorSetupProgressProperty().greaterThan(0));
|
||||||
|
translatorProgress.visibleProperty().bind(model.translatorSetupProgressProperty().greaterThan(0));
|
||||||
|
|
||||||
|
converterProgressLabel.textProperty().bind(model.videoConverterSetupProgressLabelProperty());
|
||||||
|
extractorProgressLabel.textProperty().bind(model.subtitleExtractorSetupProgressLabelProperty());
|
||||||
|
translatorProgressLabel.textProperty().bind(model.translatorSetupProgressLabelProperty());
|
||||||
|
|
||||||
|
converterProgressLabel.visibleProperty().bind(model.videoConverterSetupProgressProperty().greaterThan(0));
|
||||||
|
extractorProgressLabel.visibleProperty().bind(model.subtitleExtractorSetupProgressProperty().greaterThan(0));
|
||||||
|
translatorProgressLabel.visibleProperty().bind(model.translatorSetupProgressProperty().greaterThan(0));
|
||||||
|
|
||||||
|
bindMenu(converterButton, converterManager);
|
||||||
|
bindMenu(extractorButton, extractorManager);
|
||||||
|
bindMenu(translatorButton, translatorManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindMenu(final MenuButton button, final SetupManager setupManager) {
|
||||||
|
button.disableProperty().bind(Bindings.isEmpty(button.getItems()));
|
||||||
|
statusMap.get(setupManager).addListener((observable, oldValue, newValue) -> {
|
||||||
|
button.getItems().clear();
|
||||||
|
switch (newValue) {
|
||||||
|
case NOT_INSTALLED -> {
|
||||||
|
final var installItem = new MenuItem(resources.getString("setup.menu.install"));
|
||||||
|
installItem.setOnAction(e -> tryInstall(setupManager));
|
||||||
|
button.getItems().add(installItem);
|
||||||
|
}
|
||||||
|
case INSTALLED -> {
|
||||||
|
final var reinstallItem = new MenuItem(resources.getString("setup.menu.reinstall"));
|
||||||
|
reinstallItem.setOnAction(e -> tryReinstall(setupManager));
|
||||||
|
final var uninstallItem = new MenuItem(resources.getString("setup.menu.uninstall"));
|
||||||
|
uninstallItem.setOnAction(e -> tryUninstall(setupManager));
|
||||||
|
button.getItems().addAll(reinstallItem, uninstallItem);
|
||||||
|
}
|
||||||
|
case UPDATE_AVAILABLE -> {
|
||||||
|
final var updateItem = new MenuItem(resources.getString("setup.menu.update"));
|
||||||
|
updateItem.setOnAction(e -> tryUpdate(setupManager));
|
||||||
|
final var reinstallItem = new MenuItem(resources.getString("setup.menu.reinstall"));
|
||||||
|
reinstallItem.setOnAction(e -> tryReinstall(setupManager));
|
||||||
|
final var uninstallItem = new MenuItem(resources.getString("setup.menu.uninstall"));
|
||||||
|
uninstallItem.setOnAction(e -> tryUninstall(setupManager));
|
||||||
|
button.getItems().addAll(updateItem, reinstallItem, uninstallItem);
|
||||||
|
}
|
||||||
|
case null, default -> {
|
||||||
|
// Do nothing, buttons are cleared
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindLabelStatus(final Label label, final ObjectProperty<SetupStatus> status) {
|
||||||
|
label.textProperty().bind(Bindings.createStringBinding(() -> resources.getString("setup.status." + status.get().name().toLowerCase() + ".label"), status));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void installVideoConverter() {
|
public void installVideoConverter() {
|
||||||
converterManager.install();
|
tryInstall(converterManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void uninstallVideoConverter() {
|
public void uninstallVideoConverter() {
|
||||||
converterManager.uninstall();
|
tryUninstall(converterManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateVideoConverter() {
|
public void updateVideoConverter() {
|
||||||
converterManager.update();
|
tryUpdate(converterManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reinstallVideoConverter() {
|
public void reinstallVideoConverter() {
|
||||||
converterManager.reinstall();
|
tryReinstall(converterManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void installSubtitleExtractor() {
|
public void installSubtitleExtractor() {
|
||||||
extractorManager.install();
|
tryInstall(extractorManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void uninstallSubtitleExtractor() {
|
public void uninstallSubtitleExtractor() {
|
||||||
extractorManager.uninstall();
|
tryUninstall(extractorManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateSubtitleExtractor() {
|
public void updateSubtitleExtractor() {
|
||||||
extractorManager.update();
|
tryUpdate(extractorManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reinstallSubtitleExtractor() {
|
public void reinstallSubtitleExtractor() {
|
||||||
extractorManager.reinstall();
|
tryReinstall(extractorManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void installTranslator() {
|
public void installTranslator() {
|
||||||
translatorManager.install();
|
tryInstall(translatorManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void uninstallTranslator() {
|
public void uninstallTranslator() {
|
||||||
translatorManager.uninstall();
|
tryUninstall(translatorManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateTranslator() {
|
public void updateTranslator() {
|
||||||
translatorManager.update();
|
tryUpdate(translatorManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reinstallTranslator() {
|
public void reinstallTranslator() {
|
||||||
translatorManager.reinstall();
|
tryReinstall(translatorManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryInstall(final SetupManager manager) {
|
||||||
|
trySetup(manager, SetupManager::install, "install");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryUninstall(final SetupManager manager) {
|
||||||
|
trySetup(manager, SetupManager::uninstall, "uninstall");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryReinstall(final SetupManager manager) {
|
||||||
|
trySetup(manager, SetupManager::reinstall, "reinstall");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryUpdate(final SetupManager manager) {
|
||||||
|
trySetup(manager, SetupManager::update, "update");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void trySetup(final SetupManager manager, final SetupConsumer consumer, final String operation) {
|
||||||
|
try {
|
||||||
|
consumer.accept(manager);
|
||||||
|
statusMap.get(manager).set(manager.status());
|
||||||
|
} catch (final SetupException e) {
|
||||||
|
logger.error("Error {}ing {}", operation, manager.name(), e);
|
||||||
|
showErrorDialog(resources.getString("setup." + operation + ".error.title"), MessageFormat.format(resources.getString("setup." + operation + ".error.message"), e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
private interface SetupConsumer {
|
||||||
|
|
||||||
|
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
|
||||||
@@ -130,6 +267,6 @@ public class FXSetupController implements SetupController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Window window() {
|
public Window window() {
|
||||||
return extractorButton.getScene().getWindow();
|
return converterNameLabel.getScene().getWindow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,80 @@
|
|||||||
package com.github.gtache.autosubtitle.gui.fx;
|
package com.github.gtache.autosubtitle.gui.fx;
|
||||||
|
|
||||||
import com.github.gtache.autosubtitle.gui.SetupModel;
|
import com.github.gtache.autosubtitle.gui.SetupModel;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import com.github.gtache.autosubtitle.setup.SetupStatus;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.property.DoubleProperty;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||||
|
import javafx.beans.property.ReadOnlyBooleanWrapper;
|
||||||
|
import javafx.beans.property.SimpleDoubleProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.property.StringProperty;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FX implementation of {@link SetupModel}
|
* FX implementation of {@link SetupModel}
|
||||||
*/
|
*/
|
||||||
|
@Singleton
|
||||||
public class FXSetupModel implements SetupModel {
|
public class FXSetupModel implements SetupModel {
|
||||||
|
|
||||||
private final BooleanProperty subtitleExtractorInstalled;
|
private final ObjectProperty<SetupStatus> subtitleExtractorStatus;
|
||||||
private final BooleanProperty subtitleExtractorUpdateAvailable;
|
private final ReadOnlyBooleanWrapper subtitleExtractorInstalled;
|
||||||
private final BooleanProperty videoConverterInstalled;
|
private final ReadOnlyBooleanWrapper subtitleExtractorUpdateAvailable;
|
||||||
private final BooleanProperty videoConverterUpdateAvailable;
|
private final DoubleProperty subtitleExtractorSetupProgress;
|
||||||
private final BooleanProperty translatorInstalled;
|
private final StringProperty subtitleExtractorSetupProgressLabel;
|
||||||
private final BooleanProperty translatorUpdateAvailable;
|
private final ObjectProperty<SetupStatus> videoConverterStatus;
|
||||||
|
private final ReadOnlyBooleanWrapper videoConverterInstalled;
|
||||||
|
private final ReadOnlyBooleanWrapper videoConverterUpdateAvailable;
|
||||||
|
private final DoubleProperty videoConverterSetupProgress;
|
||||||
|
private final StringProperty videoConverterSetupProgressLabel;
|
||||||
|
private final ObjectProperty<SetupStatus> translatorStatus;
|
||||||
|
private final ReadOnlyBooleanWrapper translatorInstalled;
|
||||||
|
private final ReadOnlyBooleanWrapper translatorUpdateAvailable;
|
||||||
|
private final DoubleProperty translatorSetupProgress;
|
||||||
|
private final StringProperty translatorSetupProgressLabel;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
FXSetupModel() {
|
FXSetupModel() {
|
||||||
this.subtitleExtractorInstalled = new SimpleBooleanProperty(false);
|
this.subtitleExtractorStatus = new SimpleObjectProperty<>(SetupStatus.NOT_INSTALLED);
|
||||||
this.subtitleExtractorUpdateAvailable = new SimpleBooleanProperty(false);
|
this.subtitleExtractorInstalled = new ReadOnlyBooleanWrapper(false);
|
||||||
this.videoConverterInstalled = new SimpleBooleanProperty(false);
|
this.subtitleExtractorUpdateAvailable = new ReadOnlyBooleanWrapper(false);
|
||||||
this.videoConverterUpdateAvailable = new SimpleBooleanProperty(false);
|
this.subtitleExtractorSetupProgress = new SimpleDoubleProperty(0);
|
||||||
this.translatorInstalled = new SimpleBooleanProperty(false);
|
this.subtitleExtractorSetupProgressLabel = new SimpleStringProperty("");
|
||||||
this.translatorUpdateAvailable = new SimpleBooleanProperty(false);
|
this.videoConverterStatus = new SimpleObjectProperty<>(SetupStatus.NOT_INSTALLED);
|
||||||
|
this.videoConverterInstalled = new ReadOnlyBooleanWrapper(false);
|
||||||
|
this.videoConverterUpdateAvailable = new ReadOnlyBooleanWrapper(false);
|
||||||
|
this.videoConverterSetupProgress = new SimpleDoubleProperty(0);
|
||||||
|
this.videoConverterSetupProgressLabel = new SimpleStringProperty("");
|
||||||
|
this.translatorStatus = new SimpleObjectProperty<>(SetupStatus.NOT_INSTALLED);
|
||||||
|
this.translatorInstalled = new ReadOnlyBooleanWrapper(false);
|
||||||
|
this.translatorUpdateAvailable = new ReadOnlyBooleanWrapper(false);
|
||||||
|
this.translatorSetupProgress = new SimpleDoubleProperty(0);
|
||||||
|
this.translatorSetupProgressLabel = new SimpleStringProperty("");
|
||||||
|
|
||||||
|
subtitleExtractorInstalled.bind(Bindings.createBooleanBinding(() -> subtitleExtractorStatus.get().isInstalled(), subtitleExtractorStatus));
|
||||||
|
videoConverterInstalled.bind(Bindings.createBooleanBinding(() -> videoConverterStatus.get().isInstalled(), videoConverterStatus));
|
||||||
|
translatorInstalled.bind(Bindings.createBooleanBinding(() -> translatorStatus.get().isInstalled(), translatorStatus));
|
||||||
|
subtitleExtractorUpdateAvailable.bind(Bindings.createBooleanBinding(() -> subtitleExtractorStatus.get() == SetupStatus.UPDATE_AVAILABLE, subtitleExtractorStatus));
|
||||||
|
videoConverterUpdateAvailable.bind(Bindings.createBooleanBinding(() -> videoConverterStatus.get() == SetupStatus.UPDATE_AVAILABLE, videoConverterStatus));
|
||||||
|
translatorUpdateAvailable.bind(Bindings.createBooleanBinding(() -> translatorStatus.get() == SetupStatus.UPDATE_AVAILABLE, translatorStatus));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SetupStatus subtitleExtractorStatus() {
|
||||||
|
return subtitleExtractorStatus.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSubtitleExtractorStatus(final SetupStatus status) {
|
||||||
|
subtitleExtractorStatus.set(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectProperty<SetupStatus> subtitleExtractorStatusProperty() {
|
||||||
|
return subtitleExtractorStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -33,13 +82,8 @@ public class FXSetupModel implements SetupModel {
|
|||||||
return subtitleExtractorInstalled.get();
|
return subtitleExtractorInstalled.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
ReadOnlyBooleanProperty subtitleExtractorInstalledProperty() {
|
||||||
public void setSubtitleExtractorInstalled(final boolean installed) {
|
return subtitleExtractorInstalled.getReadOnlyProperty();
|
||||||
subtitleExtractorInstalled.set(installed);
|
|
||||||
}
|
|
||||||
|
|
||||||
BooleanProperty subtitleExtractorInstalledProperty() {
|
|
||||||
return subtitleExtractorInstalled;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -48,12 +92,49 @@ public class FXSetupModel implements SetupModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setSubtitleExtractorUpdateAvailable(final boolean updateAvailable) {
|
public double subtitleExtractorSetupProgress() {
|
||||||
subtitleExtractorUpdateAvailable.set(updateAvailable);
|
return subtitleExtractorSetupProgress.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
BooleanProperty subtitleExtractorUpdateAvailableProperty() {
|
@Override
|
||||||
return subtitleExtractorUpdateAvailable;
|
public void setSubtitleExtractorSetupProgress(final double progress) {
|
||||||
|
subtitleExtractorSetupProgress.set(progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
DoubleProperty subtitleExtractorSetupProgressProperty() {
|
||||||
|
return subtitleExtractorSetupProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String subtitleExtractorSetupProgressLabel() {
|
||||||
|
return subtitleExtractorSetupProgressLabel.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSubtitleExtractorSetupProgressLabel(final String label) {
|
||||||
|
subtitleExtractorSetupProgressLabel.set(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
StringProperty subtitleExtractorSetupProgressLabelProperty() {
|
||||||
|
return subtitleExtractorSetupProgressLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SetupStatus videoConverterStatus() {
|
||||||
|
return videoConverterStatus.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setVideoConverterStatus(final SetupStatus status) {
|
||||||
|
videoConverterStatus.set(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectProperty<SetupStatus> videoConverterStatusProperty() {
|
||||||
|
return videoConverterStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadOnlyBooleanProperty subtitleExtractorUpdateAvailableProperty() {
|
||||||
|
return subtitleExtractorUpdateAvailable.getReadOnlyProperty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -61,13 +142,8 @@ public class FXSetupModel implements SetupModel {
|
|||||||
return videoConverterInstalled.get();
|
return videoConverterInstalled.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
ReadOnlyBooleanProperty videoConverterInstalledProperty() {
|
||||||
public void setVideoConverterInstalled(final boolean installed) {
|
return videoConverterInstalled.getReadOnlyProperty();
|
||||||
videoConverterInstalled.set(installed);
|
|
||||||
}
|
|
||||||
|
|
||||||
BooleanProperty videoConverterInstalledProperty() {
|
|
||||||
return videoConverterInstalled;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -75,13 +151,50 @@ public class FXSetupModel implements SetupModel {
|
|||||||
return videoConverterUpdateAvailable.get();
|
return videoConverterUpdateAvailable.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
ReadOnlyBooleanProperty videoConverterUpdateAvailableProperty() {
|
||||||
public void setVideoConverterUpdateAvailable(final boolean updateAvailable) {
|
return videoConverterUpdateAvailable.getReadOnlyProperty();
|
||||||
videoConverterUpdateAvailable.set(updateAvailable);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BooleanProperty videoConverterUpdateAvailableProperty() {
|
@Override
|
||||||
return videoConverterUpdateAvailable;
|
public double videoConverterSetupProgress() {
|
||||||
|
return videoConverterSetupProgress.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setVideoConverterSetupProgress(final double progress) {
|
||||||
|
videoConverterSetupProgress.set(progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
DoubleProperty videoConverterSetupProgressProperty() {
|
||||||
|
return videoConverterSetupProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String videoConverterSetupProgressLabel() {
|
||||||
|
return videoConverterSetupProgressLabel.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setVideoConverterSetupProgressLabel(final String label) {
|
||||||
|
videoConverterSetupProgressLabel.set(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
StringProperty videoConverterSetupProgressLabelProperty() {
|
||||||
|
return videoConverterSetupProgressLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SetupStatus translatorStatus() {
|
||||||
|
return translatorStatus.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTranslatorStatus(final SetupStatus status) {
|
||||||
|
translatorStatus.set(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectProperty<SetupStatus> translatorStatusProperty() {
|
||||||
|
return translatorStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -89,13 +202,8 @@ public class FXSetupModel implements SetupModel {
|
|||||||
return translatorInstalled.get();
|
return translatorInstalled.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
ReadOnlyBooleanProperty translatorInstalledProperty() {
|
||||||
public void setTranslatorInstalled(final boolean installed) {
|
return translatorInstalled.getReadOnlyProperty();
|
||||||
translatorInstalled.set(installed);
|
|
||||||
}
|
|
||||||
|
|
||||||
BooleanProperty translatorInstalledProperty() {
|
|
||||||
return translatorInstalled;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -103,12 +211,35 @@ public class FXSetupModel implements SetupModel {
|
|||||||
return translatorUpdateAvailable.get();
|
return translatorUpdateAvailable.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
ReadOnlyBooleanProperty translatorUpdateAvailableProperty() {
|
||||||
public void setTranslatorUpdateAvailable(final boolean updateAvailable) {
|
return translatorUpdateAvailable.getReadOnlyProperty();
|
||||||
translatorUpdateAvailable.set(updateAvailable);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BooleanProperty translatorUpdateAvailableProperty() {
|
@Override
|
||||||
return translatorUpdateAvailable;
|
public double translatorSetupProgress() {
|
||||||
|
return translatorSetupProgress.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTranslatorSetupProgress(final double progress) {
|
||||||
|
translatorSetupProgress.set(progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
DoubleProperty translatorSetupProgressProperty() {
|
||||||
|
return translatorSetupProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String translatorSetupProgressLabel() {
|
||||||
|
return translatorSetupProgressLabel.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTranslatorSetupProgressLabel(final String label) {
|
||||||
|
translatorSetupProgressLabel.set(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
StringProperty translatorSetupProgressLabelProperty() {
|
||||||
|
return translatorSetupProgressLabel;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,204 @@
|
|||||||
|
package com.github.gtache.autosubtitle.gui.fx;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.Translator;
|
||||||
|
import com.github.gtache.autosubtitle.VideoConverter;
|
||||||
|
import com.github.gtache.autosubtitle.VideoLoader;
|
||||||
|
import com.github.gtache.autosubtitle.gui.WorkController;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.EditableSubtitle;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.Subtitle;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.SubtitleExtractor;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.fx.ObservableSubtitleImpl;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.impl.SubtitleCollectionImpl;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.control.Alert;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.ButtonType;
|
||||||
|
import javafx.scene.control.TableColumn;
|
||||||
|
import javafx.scene.control.TableView;
|
||||||
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.stage.FileChooser;
|
||||||
|
import javafx.stage.Window;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FX implementation of {@link WorkController}
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
public class FXWorkController implements WorkController {
|
||||||
|
|
||||||
|
private static final Logger logger = LogManager.getLogger(FXWorkController.class);
|
||||||
|
|
||||||
|
private static final List<String> VIDEO_EXTENSIONS = List.of(".webm", ".mkv", ".flv", ".vob", ".ogv", ".ogg",
|
||||||
|
".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",
|
||||||
|
".3gp", ".3g2", ".mxf", ".roq", ".nsv", ".flv", ".f4v", ".f4p", ".f4a", ".f4b");
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TextField fileField;
|
||||||
|
@FXML
|
||||||
|
private Button extractButton;
|
||||||
|
@FXML
|
||||||
|
private Button resetButton;
|
||||||
|
@FXML
|
||||||
|
private Button exportSoftButton;
|
||||||
|
@FXML
|
||||||
|
private Button exportHardButton;
|
||||||
|
@FXML
|
||||||
|
private TableView<EditableSubtitle> subtitlesTable;
|
||||||
|
@FXML
|
||||||
|
private TableColumn<EditableSubtitle, Long> startColumn;
|
||||||
|
@FXML
|
||||||
|
private TableColumn<EditableSubtitle, Long> endColumn;
|
||||||
|
@FXML
|
||||||
|
private TableColumn<EditableSubtitle, String> textColumn;
|
||||||
|
@FXML
|
||||||
|
private TextField translationField;
|
||||||
|
@FXML
|
||||||
|
private FXMediaController mediaController;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private ResourceBundle resources;
|
||||||
|
|
||||||
|
private final FXWorkModel model;
|
||||||
|
private final SubtitleExtractor subtitleExtractor;
|
||||||
|
private final VideoConverter videoConverter;
|
||||||
|
private final VideoLoader videoLoader;
|
||||||
|
private final Translator translator;
|
||||||
|
private final FXMediaBinder binder;
|
||||||
|
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
FXWorkController(final FXWorkModel model, final SubtitleExtractor subtitleExtractor, final VideoLoader videoLoader,
|
||||||
|
final VideoConverter videoConverter, final Translator translator, final FXMediaBinder binder) {
|
||||||
|
this.model = requireNonNull(model);
|
||||||
|
this.subtitleExtractor = requireNonNull(subtitleExtractor);
|
||||||
|
this.videoConverter = requireNonNull(videoConverter);
|
||||||
|
this.videoLoader = requireNonNull(videoLoader);
|
||||||
|
this.translator = requireNonNull(translator);
|
||||||
|
this.binder = requireNonNull(binder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void initialize() {
|
||||||
|
extractButton.disableProperty().bind(model.videoProperty().isNull());
|
||||||
|
resetButton.disableProperty().bind(Bindings.isEmpty(model.subtitles()));
|
||||||
|
exportSoftButton.disableProperty().bind(Bindings.isEmpty(model.subtitles()));
|
||||||
|
exportHardButton.disableProperty().bind(Bindings.isEmpty(model.subtitles()));
|
||||||
|
|
||||||
|
subtitlesTable.setItems(model.subtitles());
|
||||||
|
startColumn.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue() == null ? null : param.getValue().start()));
|
||||||
|
endColumn.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue() == null ? null : param.getValue().end()));
|
||||||
|
textColumn.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue() == null ? "" : param.getValue().content()));
|
||||||
|
|
||||||
|
model.selectedSubtitleProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
if (newValue != null) {
|
||||||
|
mediaController.seek(newValue.start());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
binder.createBindings();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void fileButtonPressed() {
|
||||||
|
final var filePicker = new FileChooser();
|
||||||
|
filePicker.setSelectedExtensionFilter(new FileChooser.ExtensionFilter("Video", VIDEO_EXTENSIONS));
|
||||||
|
final var file = filePicker.showOpenDialog(window());
|
||||||
|
if (file != null) {
|
||||||
|
loadVideo(file.toPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void extractSubtitles() {
|
||||||
|
if (model.video() != null) {
|
||||||
|
final var subtitles = subtitleExtractor.extract(model.video()).stream().sorted(Comparator.comparing(Subtitle::start).thenComparing(Subtitle::end)).toList();
|
||||||
|
final var subtitlesCopy = subtitles.stream().map(ObservableSubtitleImpl::new).toList();
|
||||||
|
model.subtitles().setAll(subtitles);
|
||||||
|
model.originalSubtitles().clear();
|
||||||
|
model.originalSubtitles().addAll(subtitlesCopy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadVideo(final Path file) {
|
||||||
|
try {
|
||||||
|
final var loadedVideo = videoLoader.loadVideo(file);
|
||||||
|
fileField.setText(file.toAbsolutePath().toString());
|
||||||
|
model.videoProperty().set(loadedVideo);
|
||||||
|
} catch (final IOException e) {
|
||||||
|
logger.error("Error loading video {}", file, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void exportSoftPressed() {
|
||||||
|
final var filePicker = new FileChooser();
|
||||||
|
final var file = filePicker.showSaveDialog(window());
|
||||||
|
if (file != null) {
|
||||||
|
final var text = model.subtitles().stream().map(Subtitle::content).collect(Collectors.joining(" "));
|
||||||
|
final var baseCollection = new SubtitleCollectionImpl(model.subtitles(), translator.getLocale(text));
|
||||||
|
final var collections = Stream.concat(Stream.of(baseCollection), model.translations().stream().map(l -> translator.translate(baseCollection, l))).toList();
|
||||||
|
try {
|
||||||
|
videoConverter.addSoftSubtitles(model.video(), collections);
|
||||||
|
} catch (final IOException e) {
|
||||||
|
logger.error("Error exporting subtitles", e);
|
||||||
|
final var alert = new Alert(Alert.AlertType.ERROR, MessageFormat.format(resources.getString("work.error.export.label"), e.getMessage()), ButtonType.OK);
|
||||||
|
alert.setTitle(resources.getString("work.error.export.title"));
|
||||||
|
alert.showAndWait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void exportHardPressed() {
|
||||||
|
final var filePicker = new FileChooser();
|
||||||
|
final var file = filePicker.showSaveDialog(window());
|
||||||
|
if (file != null) {
|
||||||
|
try {
|
||||||
|
videoConverter.addHardSubtitles(model.video(), new SubtitleCollectionImpl(model.subtitles(), Locale.getDefault()));
|
||||||
|
} catch (final IOException e) {
|
||||||
|
logger.error("Error exporting subtitles", e);
|
||||||
|
final var alert = new Alert(Alert.AlertType.ERROR, MessageFormat.format(resources.getString("work.error.export.label"), e.getMessage()), ButtonType.OK);
|
||||||
|
alert.setTitle(resources.getString("work.error.export.title"));
|
||||||
|
alert.showAndWait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FXWorkModel model() {
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Window window() {
|
||||||
|
return fileField.getScene().getWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void extractPressed() {
|
||||||
|
extractSubtitles();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void resetButtonPressed() {
|
||||||
|
model.subtitles().setAll(model.originalSubtitles());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package com.github.gtache.autosubtitle.gui.fx;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.Video;
|
||||||
|
import com.github.gtache.autosubtitle.gui.WorkModel;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.EditableSubtitle;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FX implementation of {@link WorkModel}
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
public class FXWorkModel implements WorkModel {
|
||||||
|
|
||||||
|
private final ObjectProperty<Video> video;
|
||||||
|
private final ObservableList<EditableSubtitle> subtitles;
|
||||||
|
private final List<EditableSubtitle> originalSubtitles;
|
||||||
|
private final ObjectProperty<EditableSubtitle> subtitle;
|
||||||
|
private final ObservableList<Locale> translations;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
FXWorkModel() {
|
||||||
|
this.video = new SimpleObjectProperty<>();
|
||||||
|
this.subtitles = FXCollections.observableArrayList();
|
||||||
|
this.originalSubtitles = new ArrayList<>();
|
||||||
|
this.subtitle = new SimpleObjectProperty<>();
|
||||||
|
this.translations = FXCollections.observableArrayList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Video video() {
|
||||||
|
return video.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectProperty<Video> videoProperty() {
|
||||||
|
return video;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ObservableList<EditableSubtitle> subtitles() {
|
||||||
|
return subtitles;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<EditableSubtitle> originalSubtitles() {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EditableSubtitle selectedSubtitle() {
|
||||||
|
return subtitle.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ObservableList<Locale> translations() {
|
||||||
|
return translations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectProperty<EditableSubtitle> selectedSubtitleProperty() {
|
||||||
|
return subtitle;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package com.github.gtache.autosubtitle.gui.modules.fx;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.gui.fx.FXMainController;
|
||||||
|
import com.github.gtache.autosubtitle.gui.fx.FXMediaController;
|
||||||
|
import com.github.gtache.autosubtitle.gui.fx.FXSetupController;
|
||||||
|
import com.github.gtache.autosubtitle.gui.fx.FXWorkController;
|
||||||
|
import com.github.gtache.autosubtitle.modules.gui.Pause;
|
||||||
|
import com.github.gtache.autosubtitle.modules.gui.Play;
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dagger module for FX
|
||||||
|
*/
|
||||||
|
@Module
|
||||||
|
public abstract class FXModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
static FXMLLoader providesFXMLLoader(final FXMainController mainController, final FXSetupController setupController,
|
||||||
|
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"));
|
||||||
|
loader.setResources(bundle);
|
||||||
|
loader.setControllerFactory(c -> {
|
||||||
|
if (c == FXMainController.class) {
|
||||||
|
return mainController;
|
||||||
|
} else if (c == FXSetupController.class) {
|
||||||
|
return setupController;
|
||||||
|
} else if (c == FXWorkController.class) {
|
||||||
|
return workController;
|
||||||
|
} else if (c == FXMediaController.class) {
|
||||||
|
return mediaController;
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Unknown controller " + c);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@Play
|
||||||
|
static Image providesPlayImage(@Play final byte[] playImage) {
|
||||||
|
return new Image(new ByteArrayInputStream(playImage));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@Pause
|
||||||
|
static Image providesPauseImage(@Pause final byte[] pauseImage) {
|
||||||
|
return new Image(new ByteArrayInputStream(pauseImage));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package com.github.gtache.autosubtitle.subtitle.fx;
|
|||||||
import com.github.gtache.autosubtitle.subtitle.Bounds;
|
import com.github.gtache.autosubtitle.subtitle.Bounds;
|
||||||
import com.github.gtache.autosubtitle.subtitle.EditableSubtitle;
|
import com.github.gtache.autosubtitle.subtitle.EditableSubtitle;
|
||||||
import com.github.gtache.autosubtitle.subtitle.Font;
|
import com.github.gtache.autosubtitle.subtitle.Font;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.Subtitle;
|
||||||
import com.github.gtache.autosubtitle.subtitle.impl.BoundsImpl;
|
import com.github.gtache.autosubtitle.subtitle.impl.BoundsImpl;
|
||||||
import javafx.beans.property.LongProperty;
|
import javafx.beans.property.LongProperty;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
@@ -23,11 +24,19 @@ public class ObservableSubtitleImpl implements EditableSubtitle {
|
|||||||
private final ObjectProperty<Bounds> location;
|
private final ObjectProperty<Bounds> location;
|
||||||
|
|
||||||
public ObservableSubtitleImpl() {
|
public ObservableSubtitleImpl() {
|
||||||
content = new SimpleStringProperty("");
|
this.content = new SimpleStringProperty("");
|
||||||
start = new SimpleLongProperty(0);
|
this.start = new SimpleLongProperty(0);
|
||||||
end = new SimpleLongProperty(0);
|
this.end = new SimpleLongProperty(0);
|
||||||
font = new SimpleObjectProperty<>();
|
this.font = new SimpleObjectProperty<>();
|
||||||
location = new SimpleObjectProperty<>(new BoundsImpl(0, 0, 100, 12));
|
this.location = new SimpleObjectProperty<>(new BoundsImpl(0, 0, 100, 12));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableSubtitleImpl(final Subtitle subtitle) {
|
||||||
|
this.content = new SimpleStringProperty(subtitle.content());
|
||||||
|
this.start = new SimpleLongProperty(subtitle.start());
|
||||||
|
this.end = new SimpleLongProperty(subtitle.end());
|
||||||
|
this.font = new SimpleObjectProperty<>(subtitle.font());
|
||||||
|
this.location = new SimpleObjectProperty<>(subtitle.bounds());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.github.gtache.autosubtitle.subtitle.fx;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.Subtitle;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special label for subtitles
|
||||||
|
*/
|
||||||
|
public class SubtitleLabel extends Label {
|
||||||
|
|
||||||
|
private final Subtitle subtitle;
|
||||||
|
private final Delta delta;
|
||||||
|
|
||||||
|
public SubtitleLabel(final Subtitle subtitle) {
|
||||||
|
this.subtitle = Objects.requireNonNull(subtitle);
|
||||||
|
this.delta = new Delta();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Subtitle subtitle() {
|
||||||
|
return subtitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getDraggedX() {
|
||||||
|
return delta.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getDraggedY() {
|
||||||
|
return delta.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDragged(final double x, final double y) {
|
||||||
|
delta.x = x;
|
||||||
|
delta.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class Delta {
|
||||||
|
private double x;
|
||||||
|
private double y;
|
||||||
|
}
|
||||||
|
}
|
||||||
23
fx/src/main/java/module-info.java
Normal file
23
fx/src/main/java/module-info.java
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import com.github.gtache.autosubtitle.gui.spi.MainBundleProvider;
|
||||||
|
import com.github.gtache.autosubtitle.gui.spi.SetupBundleProvider;
|
||||||
|
import com.github.gtache.autosubtitle.gui.spi.WorkBundleProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FX module for auto-subtitle
|
||||||
|
*/
|
||||||
|
module com.github.gtache.autosubtitle.fx {
|
||||||
|
requires transitive com.github.gtache.autosubtitle.core;
|
||||||
|
requires transitive com.github.gtache.autosubtitle.gui;
|
||||||
|
requires transitive javafx.controls;
|
||||||
|
requires transitive javafx.media;
|
||||||
|
requires transitive javafx.fxml;
|
||||||
|
requires org.apache.logging.log4j;
|
||||||
|
exports com.github.gtache.autosubtitle.gui.fx;
|
||||||
|
exports com.github.gtache.autosubtitle.gui.modules.fx;
|
||||||
|
|
||||||
|
opens com.github.gtache.autosubtitle.gui.fx to javafx.fxml;
|
||||||
|
|
||||||
|
uses MainBundleProvider;
|
||||||
|
uses SetupBundleProvider;
|
||||||
|
uses WorkBundleProvider;
|
||||||
|
}
|
||||||
@@ -1,51 +1,19 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<?import javafx.scene.control.Button?>
|
<?import javafx.scene.control.Tab?>
|
||||||
<?import javafx.scene.control.TableColumn?>
|
<?import javafx.scene.control.TabPane?>
|
||||||
<?import javafx.scene.control.TableView?>
|
|
||||||
<?import javafx.scene.control.TextField?>
|
|
||||||
<?import javafx.scene.layout.AnchorPane?>
|
|
||||||
<?import javafx.scene.layout.ColumnConstraints?>
|
|
||||||
<?import javafx.scene.layout.GridPane?>
|
|
||||||
<?import javafx.scene.layout.RowConstraints?>
|
|
||||||
<?import javafx.scene.layout.StackPane?>
|
|
||||||
<?import javafx.scene.media.MediaView?>
|
|
||||||
|
|
||||||
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="1080.0" prefWidth="1920.0" xmlns="http://javafx.com/javafx/22" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.github.gtache.autosubtitle.gui.fx.FXMainController">
|
<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">
|
||||||
<children>
|
<tabs>
|
||||||
<GridPane hgap="10.0" layoutX="152.0" layoutY="116.0" vgap="10.0" AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="10.0">
|
<Tab closable="false" text="%main.tab.work.label">
|
||||||
<columnConstraints>
|
<content>
|
||||||
<ColumnConstraints hgrow="SOMETIMES" />
|
<fx:include source="workView.fxml" />
|
||||||
<ColumnConstraints hgrow="SOMETIMES" />
|
</content>
|
||||||
<ColumnConstraints hgrow="SOMETIMES" />
|
</Tab>
|
||||||
</columnConstraints>
|
<Tab closable="false" text="%main.tab.setup.label">
|
||||||
<rowConstraints>
|
<content>
|
||||||
<RowConstraints vgrow="SOMETIMES" />
|
<fx:include source="setupView.fxml" />
|
||||||
<RowConstraints vgrow="ALWAYS" />
|
</content>
|
||||||
<RowConstraints vgrow="SOMETIMES" />
|
</Tab>
|
||||||
</rowConstraints>
|
</tabs>
|
||||||
<children>
|
</TabPane>
|
||||||
<TextField fx:id="fileField" editable="false" GridPane.columnIndex="1" />
|
|
||||||
<Button mnemonicParsing="false" onAction="#fileButtonPressed" text="%main.button.file.label" GridPane.columnIndex="2" />
|
|
||||||
<Button fx:id="extractButton" mnemonicParsing="false" onAction="#extractPressed" text="%main.button.extract.label" GridPane.columnIndex="1" GridPane.rowIndex="2" />
|
|
||||||
<Button fx:id="exportButton" mnemonicParsing="false" onAction="#exportPressed" text="%main.button.export.label" GridPane.columnIndex="2" GridPane.rowIndex="2" />
|
|
||||||
<TableView fx:id="subtitlesTable" GridPane.rowIndex="1">
|
|
||||||
<columns>
|
|
||||||
<TableColumn fx:id="startColumn" prefWidth="50.0" text="Column X" />
|
|
||||||
<TableColumn fx:id="endColumn" prefWidth="50.0" text="Column X" />
|
|
||||||
<TableColumn fx:id="textColumn" prefWidth="75.0" text="Column X" />
|
|
||||||
</columns>
|
|
||||||
<columnResizePolicy>
|
|
||||||
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
|
|
||||||
</columnResizePolicy>
|
|
||||||
</TableView>
|
|
||||||
<Button fx:id="resetButton" mnemonicParsing="false" onAction="#resetButtonPressed" text="%main.button.reset.label" GridPane.rowIndex="2" />
|
|
||||||
<StackPane fx:id="stackPane" prefHeight="150.0" prefWidth="200.0" GridPane.columnIndex="1" GridPane.columnSpan="2147483647" GridPane.rowIndex="1">
|
|
||||||
<children>
|
|
||||||
<MediaView fx:id="videoView" fitHeight="200.0" fitWidth="200.0" />
|
|
||||||
</children>
|
|
||||||
</StackPane>
|
|
||||||
</children>
|
|
||||||
</GridPane>
|
|
||||||
</children>
|
|
||||||
</AnchorPane>
|
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<?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.Slider?>
|
||||||
|
<?import javafx.scene.layout.BorderPane?>
|
||||||
|
<?import javafx.scene.layout.HBox?>
|
||||||
|
<?import javafx.scene.layout.StackPane?>
|
||||||
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
<?import javafx.scene.media.MediaView?>
|
||||||
|
<BorderPane xmlns="http://javafx.com/javafx/22" xmlns:fx="http://javafx.com/fxml/1"
|
||||||
|
fx:controller="com.github.gtache.autosubtitle.gui.fx.FXMediaController">
|
||||||
|
<bottom>
|
||||||
|
<VBox BorderPane.alignment="CENTER">
|
||||||
|
<children>
|
||||||
|
<HBox alignment="CENTER" spacing="10.0">
|
||||||
|
<children>
|
||||||
|
<Slider fx:id="playSlider" HBox.hgrow="ALWAYS">
|
||||||
|
<padding>
|
||||||
|
<Insets left="10.0"/>
|
||||||
|
</padding>
|
||||||
|
</Slider>
|
||||||
|
<Label fx:id="playLabel" text="Label">
|
||||||
|
<padding>
|
||||||
|
<Insets right="10.0"/>
|
||||||
|
</padding>
|
||||||
|
</Label>
|
||||||
|
</children>
|
||||||
|
<padding>
|
||||||
|
<Insets top="10.0"/>
|
||||||
|
</padding>
|
||||||
|
</HBox>
|
||||||
|
<HBox alignment="CENTER" spacing="10.0">
|
||||||
|
<children>
|
||||||
|
<Button fx:id="playButton" mnemonicParsing="false" onAction="#playPressed">
|
||||||
|
<HBox.margin>
|
||||||
|
<Insets right="20.0"/>
|
||||||
|
</HBox.margin>
|
||||||
|
</Button>
|
||||||
|
<Label text="%media.volume.label"/>
|
||||||
|
<Slider fx:id="volumeSlider"/>
|
||||||
|
<Label fx:id="volumeValueLabel" text="Label"/>
|
||||||
|
</children>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
|
||||||
|
</padding>
|
||||||
|
</HBox>
|
||||||
|
</children>
|
||||||
|
</VBox>
|
||||||
|
</bottom>
|
||||||
|
<center>
|
||||||
|
<StackPane fx:id="stackPane">
|
||||||
|
<children>
|
||||||
|
<MediaView fx:id="videoView" fitHeight="${stackPane.height}" fitWidth="${stackPane.width}"/>
|
||||||
|
</children>
|
||||||
|
</StackPane>
|
||||||
|
</center>
|
||||||
|
</BorderPane>
|
||||||
@@ -1,49 +1,63 @@
|
|||||||
<?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.Label?>
|
||||||
<?import javafx.scene.layout.*?>
|
<?import javafx.scene.control.MenuButton?>
|
||||||
<GridPane hgap="10.0" vgap="10.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/22"
|
<?import javafx.scene.control.MenuItem?>
|
||||||
fx:controller="com.github.gtache.autosubtitle.gui.fx.FXSetupController">
|
<?import javafx.scene.control.ProgressBar?>
|
||||||
|
<?import javafx.scene.layout.ColumnConstraints?>
|
||||||
|
<?import javafx.scene.layout.GridPane?>
|
||||||
|
<?import javafx.scene.layout.RowConstraints?>
|
||||||
|
|
||||||
|
<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.FXSetupController">
|
||||||
<columnConstraints>
|
<columnConstraints>
|
||||||
<ColumnConstraints hgrow="SOMETIMES"/>
|
<ColumnConstraints hgrow="SOMETIMES" />
|
||||||
<ColumnConstraints hgrow="SOMETIMES"/>
|
<ColumnConstraints hgrow="SOMETIMES" />
|
||||||
<ColumnConstraints hgrow="SOMETIMES"/>
|
<ColumnConstraints hgrow="SOMETIMES" />
|
||||||
|
<ColumnConstraints hgrow="ALWAYS" />
|
||||||
|
<ColumnConstraints hgrow="SOMETIMES" />
|
||||||
</columnConstraints>
|
</columnConstraints>
|
||||||
<rowConstraints>
|
<rowConstraints>
|
||||||
<RowConstraints vgrow="SOMETIMES"/>
|
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||||
<RowConstraints vgrow="SOMETIMES"/>
|
<RowConstraints vgrow="SOMETIMES" />
|
||||||
<RowConstraints vgrow="SOMETIMES"/>
|
<RowConstraints vgrow="SOMETIMES" />
|
||||||
|
<RowConstraints vgrow="SOMETIMES" />
|
||||||
|
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="ALWAYS" />
|
||||||
</rowConstraints>
|
</rowConstraints>
|
||||||
<children>
|
<children>
|
||||||
<Label fx:id="converterNameLabel" text="Label"/>
|
<Label fx:id="converterNameLabel" text="Label" GridPane.rowIndex="1" />
|
||||||
<Label fx:id="converterStatusLabel" text="Label" GridPane.columnIndex="1"/>
|
<Label fx:id="converterStatusLabel" text="Label" GridPane.columnIndex="1" GridPane.rowIndex="1" />
|
||||||
<Label fx:id="extractorNameLabel" text="Label" GridPane.rowIndex="1"/>
|
<Label fx:id="extractorNameLabel" text="Label" GridPane.rowIndex="2" />
|
||||||
<Label fx:id="extractorStatusLabel" text="Label" GridPane.columnIndex="1" GridPane.rowIndex="1"/>
|
<Label fx:id="extractorStatusLabel" text="Label" GridPane.columnIndex="1" GridPane.rowIndex="2" />
|
||||||
<Label fx:id="translatorNameLabel" text="Label" GridPane.rowIndex="2"/>
|
<Label fx:id="translatorNameLabel" text="Label" GridPane.rowIndex="3" />
|
||||||
<Label fx:id="translatorStatusLabel" text="Label" GridPane.columnIndex="1" GridPane.rowIndex="2"/>
|
<Label fx:id="translatorStatusLabel" text="Label" GridPane.columnIndex="1" GridPane.rowIndex="3" />
|
||||||
<MenuButton fx:id="converterButton" mnemonicParsing="false" text="MenuButton" GridPane.columnIndex="2">
|
<MenuButton fx:id="converterButton" mnemonicParsing="false" text="%setup.menu.label" GridPane.columnIndex="2" GridPane.rowIndex="1">
|
||||||
<items>
|
<items>
|
||||||
<MenuItem mnemonicParsing="false" text="Action 1"/>
|
<MenuItem mnemonicParsing="false" text="Action 1" />
|
||||||
<MenuItem mnemonicParsing="false" text="Action 2"/>
|
<MenuItem mnemonicParsing="false" text="Action 2" />
|
||||||
</items>
|
</items>
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
<MenuButton fx:id="extractorButton" mnemonicParsing="false" text="MenuButton" GridPane.columnIndex="2"
|
<MenuButton fx:id="extractorButton" mnemonicParsing="false" text="%setup.menu.label" GridPane.columnIndex="2" GridPane.rowIndex="2">
|
||||||
GridPane.rowIndex="1">
|
|
||||||
<items>
|
<items>
|
||||||
<MenuItem mnemonicParsing="false" text="Action 1"/>
|
<MenuItem mnemonicParsing="false" text="Action 1" />
|
||||||
<MenuItem mnemonicParsing="false" text="Action 2"/>
|
<MenuItem mnemonicParsing="false" text="Action 2" />
|
||||||
</items>
|
</items>
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
<MenuButton fx:id="translatorButton" mnemonicParsing="false" text="MenuButton" GridPane.columnIndex="2"
|
<MenuButton fx:id="translatorButton" mnemonicParsing="false" text="%setup.menu.label" GridPane.columnIndex="2" GridPane.rowIndex="3">
|
||||||
GridPane.rowIndex="2">
|
|
||||||
<items>
|
<items>
|
||||||
<MenuItem mnemonicParsing="false" text="Action 1"/>
|
<MenuItem mnemonicParsing="false" text="Action 1" />
|
||||||
<MenuItem mnemonicParsing="false" text="Action 2"/>
|
<MenuItem mnemonicParsing="false" text="Action 2" />
|
||||||
</items>
|
</items>
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
|
<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="extractorProgress" prefWidth="200.0" progress="0.0" GridPane.columnIndex="3" GridPane.rowIndex="2" />
|
||||||
|
<ProgressBar fx:id="translatorProgress" prefWidth="200.0" progress="0.0" GridPane.columnIndex="3" GridPane.rowIndex="3" />
|
||||||
|
<Label fx:id="converterProgressLabel" text="Label" GridPane.columnIndex="4" GridPane.rowIndex="1" />
|
||||||
|
<Label fx:id="extractorProgressLabel" text="Label" GridPane.columnIndex="4" GridPane.rowIndex="2" />
|
||||||
|
<Label fx:id="translatorProgressLabel" text="Label" GridPane.columnIndex="4" GridPane.rowIndex="3" />
|
||||||
</children>
|
</children>
|
||||||
<padding>
|
<padding>
|
||||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
|
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||||
</padding>
|
</padding>
|
||||||
</GridPane>
|
</GridPane>
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.*?>
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
<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">
|
||||||
|
<columnConstraints>
|
||||||
|
<ColumnConstraints hgrow="SOMETIMES"/>
|
||||||
|
<ColumnConstraints hgrow="ALWAYS"/>
|
||||||
|
<ColumnConstraints hgrow="SOMETIMES"/>
|
||||||
|
</columnConstraints>
|
||||||
|
<rowConstraints>
|
||||||
|
<RowConstraints vgrow="SOMETIMES"/>
|
||||||
|
<RowConstraints vgrow="ALWAYS"/>
|
||||||
|
<RowConstraints vgrow="SOMETIMES"/>
|
||||||
|
</rowConstraints>
|
||||||
|
<children>
|
||||||
|
<TextField fx:id="fileField" editable="false" GridPane.columnIndex="1"/>
|
||||||
|
<Button mnemonicParsing="false" onAction="#fileButtonPressed" text="%work.button.file.label"
|
||||||
|
GridPane.columnIndex="2"/>
|
||||||
|
<Button fx:id="extractButton" mnemonicParsing="false" onAction="#extractPressed"
|
||||||
|
text="%work.button.extract.label" GridPane.columnIndex="1" GridPane.rowIndex="2"/>
|
||||||
|
<HBox alignment="CENTER_RIGHT" spacing="10.0" GridPane.columnIndex="2" GridPane.rowIndex="2">
|
||||||
|
<children>
|
||||||
|
<Button fx:id="exportSoftButton" mnemonicParsing="false" onAction="#exportSoftPressed"
|
||||||
|
text="%work.button.export.soft.label">
|
||||||
|
<tooltip>
|
||||||
|
<Tooltip text="%work.button.export.soft.tooltip"/>
|
||||||
|
</tooltip>
|
||||||
|
</Button>
|
||||||
|
<Button fx:id="exportHardButton" mnemonicParsing="false" onAction="#exportHardPressed"
|
||||||
|
text="%work.button.export.hard.label">
|
||||||
|
<tooltip>
|
||||||
|
<Tooltip text="%work.button.export.hard.tooltip"/>
|
||||||
|
</tooltip>
|
||||||
|
</Button>
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
<TableView fx:id="subtitlesTable" GridPane.rowIndex="1">
|
||||||
|
<columns>
|
||||||
|
<TableColumn fx:id="startColumn" prefWidth="50.0" text="%work.table.column.from.label"/>
|
||||||
|
<TableColumn fx:id="endColumn" prefWidth="50.0" text="%work.table.column.to.label"/>
|
||||||
|
<TableColumn fx:id="textColumn" prefWidth="75.0" text="%work.table.column.text.label"/>
|
||||||
|
</columns>
|
||||||
|
<columnResizePolicy>
|
||||||
|
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
|
||||||
|
</columnResizePolicy>
|
||||||
|
</TableView>
|
||||||
|
<Button fx:id="resetButton" mnemonicParsing="false" onAction="#resetButtonPressed"
|
||||||
|
text="%work.button.reset.label" GridPane.rowIndex="2"/>
|
||||||
|
<fx:include fx:id="media" source="mediaView.fxml" GridPane.columnIndex="1" GridPane.columnSpan="2147483647"
|
||||||
|
GridPane.rowIndex="1"/>
|
||||||
|
<Button mnemonicParsing="false" text="+" GridPane.halignment="RIGHT" GridPane.rowIndex="2"/>
|
||||||
|
<HBox alignment="CENTER_LEFT" spacing="10.0">
|
||||||
|
<children>
|
||||||
|
<Label text="%work.translate.label"/>
|
||||||
|
<TextField fx:id="translationField">
|
||||||
|
<tooltip>
|
||||||
|
<Tooltip text="%work.translate.tooltip"/>
|
||||||
|
</tooltip>
|
||||||
|
</TextField>
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
</children>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
|
||||||
|
</padding>
|
||||||
|
</GridPane>
|
||||||
@@ -16,6 +16,11 @@
|
|||||||
<groupId>com.github.gtache.autosubtitle</groupId>
|
<groupId>com.github.gtache.autosubtitle</groupId>
|
||||||
<artifactId>autosubtitle-api</artifactId>
|
<artifactId>autosubtitle-api</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.dagger</groupId>
|
||||||
|
<artifactId>dagger</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.github.gtache.autosubtitle.gui;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combines multiple resource bundles
|
||||||
|
*/
|
||||||
|
public class CombinedResourceBundle extends ResourceBundle {
|
||||||
|
|
||||||
|
private final Map<String, String> resources;
|
||||||
|
|
||||||
|
public CombinedResourceBundle(final ResourceBundle... bundles) {
|
||||||
|
this(Arrays.asList(bundles));
|
||||||
|
}
|
||||||
|
|
||||||
|
public CombinedResourceBundle(final Iterable<ResourceBundle> bundles) {
|
||||||
|
this.resources = new HashMap<>();
|
||||||
|
bundles.forEach(rb -> rb.getKeys().asIterator().forEachRemaining(key -> resources.put(key, rb.getString(key))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Object handleGetObject(final String key) {
|
||||||
|
return resources.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Enumeration<String> getKeys() {
|
||||||
|
return Collections.enumeration(resources.keySet());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,19 @@
|
|||||||
package com.github.gtache.autosubtitle.gui;
|
package com.github.gtache.autosubtitle.gui;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
/**
|
||||||
|
* Controller for the main view
|
||||||
|
*/
|
||||||
public interface MainController {
|
public interface MainController {
|
||||||
|
|
||||||
void extractSubtitles();
|
/**
|
||||||
|
* Selects the given tab
|
||||||
void loadVideo(final Path file);
|
*
|
||||||
|
* @param index The tab index
|
||||||
|
*/
|
||||||
|
void selectTab(final int index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The model
|
||||||
|
*/
|
||||||
MainModel model();
|
MainModel model();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
package com.github.gtache.autosubtitle.gui;
|
package com.github.gtache.autosubtitle.gui;
|
||||||
|
|
||||||
import com.github.gtache.autosubtitle.subtitle.EditableSubtitle;
|
/**
|
||||||
import com.github.gtache.autosubtitle.Video;
|
* Model for the main view
|
||||||
|
*/
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface MainModel {
|
public interface MainModel {
|
||||||
|
|
||||||
Video video();
|
/**
|
||||||
|
* @return The currently selected tab index
|
||||||
|
*/
|
||||||
|
int selectedTab();
|
||||||
|
|
||||||
List<EditableSubtitle> subtitles();
|
/**
|
||||||
|
* @param index The index of the tab to select
|
||||||
EditableSubtitle selectedSubtitle();
|
*/
|
||||||
|
void selectTab(int index);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.github.gtache.autosubtitle.gui;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller for the media view
|
||||||
|
*/
|
||||||
|
public interface MediaController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plays the media
|
||||||
|
*/
|
||||||
|
void play();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pauses the media
|
||||||
|
*/
|
||||||
|
void pause();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seeks the given position in the media
|
||||||
|
*
|
||||||
|
* @param position the new position
|
||||||
|
*/
|
||||||
|
void seek(long position);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The model for this controller
|
||||||
|
*/
|
||||||
|
MediaModel model();
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package com.github.gtache.autosubtitle.gui;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.Video;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.EditableSubtitle;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model for the media view
|
||||||
|
*/
|
||||||
|
public interface MediaModel {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The current video
|
||||||
|
*/
|
||||||
|
Video video();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the video
|
||||||
|
*
|
||||||
|
* @param video the new video
|
||||||
|
*/
|
||||||
|
void setVideo(Video video);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The current volume between 0 and 1
|
||||||
|
*/
|
||||||
|
double volume();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the volume
|
||||||
|
*
|
||||||
|
* @param volume the new volume
|
||||||
|
*/
|
||||||
|
void setVolume(double volume);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Whether the media is playing
|
||||||
|
*/
|
||||||
|
boolean isPlaying();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the playing state
|
||||||
|
*
|
||||||
|
* @param playing the new playing state
|
||||||
|
*/
|
||||||
|
void setIsPlaying(boolean playing);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The duration of the media
|
||||||
|
*/
|
||||||
|
long duration();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The current position in the media
|
||||||
|
*/
|
||||||
|
long position();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the position
|
||||||
|
*
|
||||||
|
* @param position the new position
|
||||||
|
*/
|
||||||
|
void setPosition(long position);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The current list of subtitles
|
||||||
|
*/
|
||||||
|
List<EditableSubtitle> subtitles();
|
||||||
|
}
|
||||||
@@ -1,79 +1,159 @@
|
|||||||
package com.github.gtache.autosubtitle.gui;
|
package com.github.gtache.autosubtitle.gui;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.setup.SetupStatus;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model for the setup view
|
* Model for the setup view
|
||||||
*/
|
*/
|
||||||
public interface SetupModel {
|
public interface SetupModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return whether the subtitle extractor is installed
|
* @return the status of the subtitle extractor
|
||||||
*/
|
*/
|
||||||
boolean isSubtitleExtractorInstalled();
|
SetupStatus subtitleExtractorStatus();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets whether the subtitle extractor is installed
|
* Sets the status of the subtitle extractor
|
||||||
*
|
*
|
||||||
* @param installed whether the subtitle extractor is installed
|
* @param status the new status
|
||||||
*/
|
*/
|
||||||
void setSubtitleExtractorInstalled(boolean installed);
|
void setSubtitleExtractorStatus(SetupStatus status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether the subtitle extractor is installed
|
||||||
|
*/
|
||||||
|
default boolean isSubtitleExtractorInstalled() {
|
||||||
|
return subtitleExtractorStatus().isInstalled();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return whether an update is available for the subtitle extractor
|
* @return whether an update is available for the subtitle extractor
|
||||||
*/
|
*/
|
||||||
boolean isSubtitleExtractorUpdateAvailable();
|
default boolean isSubtitleExtractorUpdateAvailable() {
|
||||||
|
return subtitleExtractorStatus() == SetupStatus.UPDATE_AVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets whether an update is available for the subtitle extractor
|
* @return the progress of the subtitle extractor setup
|
||||||
*
|
|
||||||
* @param updateAvailable whether an update is available for the subtitle extractor
|
|
||||||
*/
|
*/
|
||||||
void setSubtitleExtractorUpdateAvailable(boolean updateAvailable);
|
double subtitleExtractorSetupProgress();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the progress of the subtitle extractor setup
|
||||||
|
*
|
||||||
|
* @param progress the new progress
|
||||||
|
*/
|
||||||
|
void setSubtitleExtractorSetupProgress(double progress);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the text of the subtitle extractor setup progress
|
||||||
|
*/
|
||||||
|
String subtitleExtractorSetupProgressLabel();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the text of the subtitle extractor setup progress
|
||||||
|
*
|
||||||
|
* @param label the new text
|
||||||
|
*/
|
||||||
|
void setSubtitleExtractorSetupProgressLabel(String label);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the status of the video converter
|
||||||
|
*/
|
||||||
|
SetupStatus videoConverterStatus();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the status of the video converter
|
||||||
|
*
|
||||||
|
* @param status the new status
|
||||||
|
*/
|
||||||
|
void setVideoConverterStatus(SetupStatus status);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return whether the video converter is installed
|
* @return whether the video converter is installed
|
||||||
*/
|
*/
|
||||||
boolean isVideoConverterInstalled();
|
default boolean isVideoConverterInstalled() {
|
||||||
|
return videoConverterStatus().isInstalled();
|
||||||
/**
|
}
|
||||||
* Sets whether the video converter is installed
|
|
||||||
*
|
|
||||||
* @param installed whether the video converter is installed
|
|
||||||
*/
|
|
||||||
void setVideoConverterInstalled(boolean installed);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return whether an update is available for the video converter
|
* @return whether an update is available for the video converter
|
||||||
*/
|
*/
|
||||||
boolean isVideoConverterUpdateAvailable();
|
default boolean isVideoConverterUpdateAvailable() {
|
||||||
|
return videoConverterStatus() == SetupStatus.UPDATE_AVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets whether an update is available for the video converter
|
* @return the progress of the video converter setup
|
||||||
*
|
|
||||||
* @param updateAvailable whether an update is available for the video converter
|
|
||||||
*/
|
*/
|
||||||
void setVideoConverterUpdateAvailable(boolean updateAvailable);
|
double videoConverterSetupProgress();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the progress of the video converter setup
|
||||||
|
*
|
||||||
|
* @param progress the new progress
|
||||||
|
*/
|
||||||
|
void setVideoConverterSetupProgress(double progress);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the text of the video converter setup progress
|
||||||
|
*/
|
||||||
|
String videoConverterSetupProgressLabel();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the text of the video converter setup progress
|
||||||
|
*
|
||||||
|
* @param label the new text
|
||||||
|
*/
|
||||||
|
void setVideoConverterSetupProgressLabel(String label);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the status of the translator
|
||||||
|
*/
|
||||||
|
SetupStatus translatorStatus();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the status of the translator
|
||||||
|
*
|
||||||
|
* @param status the new status
|
||||||
|
*/
|
||||||
|
void setTranslatorStatus(SetupStatus status);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return whether the translator is installed
|
* @return whether the translator is installed
|
||||||
*/
|
*/
|
||||||
boolean isTranslatorInstalled();
|
default boolean isTranslatorInstalled() {
|
||||||
|
return translatorStatus().isInstalled();
|
||||||
/**
|
}
|
||||||
* Sets whether the translator is installed
|
|
||||||
*
|
|
||||||
* @param installed whether the translator is installed
|
|
||||||
*/
|
|
||||||
void setTranslatorInstalled(boolean installed);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return whether an update is available for the translator
|
* @return whether an update is available for the translator
|
||||||
*/
|
*/
|
||||||
boolean isTranslatorUpdateAvailable();
|
default boolean isTranslatorUpdateAvailable() {
|
||||||
|
return translatorStatus() == SetupStatus.UPDATE_AVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets whether an update is available for the translator
|
* @return the progress of the translator setup
|
||||||
*
|
|
||||||
* @param updateAvailable whether an update is available for the translator
|
|
||||||
*/
|
*/
|
||||||
void setTranslatorUpdateAvailable(boolean updateAvailable);
|
double translatorSetupProgress();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the progress of the translator setup
|
||||||
|
*
|
||||||
|
* @param progress the new progress
|
||||||
|
*/
|
||||||
|
void setTranslatorSetupProgress(double progress);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the text of the translator setup progress
|
||||||
|
*/
|
||||||
|
String translatorSetupProgressLabel();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the text of the translator setup progress
|
||||||
|
*
|
||||||
|
* @param label the new text
|
||||||
|
*/
|
||||||
|
void setTranslatorSetupProgressLabel(String label);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.github.gtache.autosubtitle.gui;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller for the work view
|
||||||
|
*/
|
||||||
|
public interface WorkController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the subtitles for the current video
|
||||||
|
*/
|
||||||
|
void extractSubtitles();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a video
|
||||||
|
*
|
||||||
|
* @param file The path to the video
|
||||||
|
*/
|
||||||
|
void loadVideo(final Path file);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The model
|
||||||
|
*/
|
||||||
|
WorkModel model();
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package com.github.gtache.autosubtitle.gui;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.Video;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.EditableSubtitle;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model for the main view
|
||||||
|
*/
|
||||||
|
public interface WorkModel {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The current video
|
||||||
|
*/
|
||||||
|
Video video();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The current list of subtitles
|
||||||
|
*/
|
||||||
|
List<EditableSubtitle> subtitles();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The original extracted subtitles (used to reset)
|
||||||
|
*/
|
||||||
|
List<EditableSubtitle> originalSubtitles();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The currently selected subtitle
|
||||||
|
*/
|
||||||
|
EditableSubtitle selectedSubtitle();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The list of selected translations
|
||||||
|
*/
|
||||||
|
List<Locale> translations();
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.github.gtache.autosubtitle.gui.spi;
|
||||||
|
|
||||||
|
import java.util.spi.ResourceBundleProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provider for MainBundle
|
||||||
|
*/
|
||||||
|
public interface MainBundleProvider extends ResourceBundleProvider {
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.github.gtache.autosubtitle.gui.spi;
|
||||||
|
|
||||||
|
import java.util.spi.AbstractResourceBundleProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of {@link MainBundleProvider}
|
||||||
|
*/
|
||||||
|
public class MainBundleProviderImpl extends AbstractResourceBundleProvider implements MainBundleProvider {
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.github.gtache.autosubtitle.gui.spi;
|
||||||
|
|
||||||
|
import java.util.spi.ResourceBundleProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provider for SetupBundle
|
||||||
|
*/
|
||||||
|
public interface SetupBundleProvider extends ResourceBundleProvider {
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.github.gtache.autosubtitle.gui.spi;
|
||||||
|
|
||||||
|
import java.util.spi.AbstractResourceBundleProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of {@link SetupBundleProvider}
|
||||||
|
*/
|
||||||
|
public class SetupBundleProviderImpl extends AbstractResourceBundleProvider implements SetupBundleProvider {
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.github.gtache.autosubtitle.gui.spi;
|
||||||
|
|
||||||
|
import java.util.spi.ResourceBundleProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provider for WorkBundle
|
||||||
|
*/
|
||||||
|
public interface WorkBundleProvider extends ResourceBundleProvider {
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.github.gtache.autosubtitle.gui.spi;
|
||||||
|
|
||||||
|
import java.util.spi.AbstractResourceBundleProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of {@link WorkBundleProviderImpl}
|
||||||
|
*/
|
||||||
|
public class WorkBundleProviderImpl extends AbstractResourceBundleProvider implements WorkBundleProvider {
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.github.gtache.autosubtitle.modules.gui;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.gui.CombinedResourceBundle;
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dagger module for GUI
|
||||||
|
*/
|
||||||
|
@Module
|
||||||
|
public class GuiModule {
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
static ResourceBundle providesBundle() {
|
||||||
|
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.WorkBundle"),
|
||||||
|
ResourceBundle.getBundle("com.github.gtache.autosubtitle.gui.MediaBundle"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@Play
|
||||||
|
static byte[] providesPlayImage() {
|
||||||
|
try {
|
||||||
|
return GuiModule.class.getResourceAsStream("/com/github/gtache/autosubtitle/gui/play_64.png").readAllBytes();
|
||||||
|
} catch (final IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@Pause
|
||||||
|
static byte[] providesPauseImage() {
|
||||||
|
try {
|
||||||
|
return GuiModule.class.getResourceAsStream("/com/github/gtache/autosubtitle/gui/pause_64.png").readAllBytes();
|
||||||
|
} catch (final IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 Pause {
|
||||||
|
}
|
||||||
@@ -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 Play {
|
||||||
|
}
|
||||||
23
gui/src/main/java/module-info.java
Normal file
23
gui/src/main/java/module-info.java
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import com.github.gtache.autosubtitle.gui.spi.MainBundleProvider;
|
||||||
|
import com.github.gtache.autosubtitle.gui.spi.MainBundleProviderImpl;
|
||||||
|
import com.github.gtache.autosubtitle.gui.spi.SetupBundleProvider;
|
||||||
|
import com.github.gtache.autosubtitle.gui.spi.SetupBundleProviderImpl;
|
||||||
|
import com.github.gtache.autosubtitle.gui.spi.WorkBundleProvider;
|
||||||
|
import com.github.gtache.autosubtitle.gui.spi.WorkBundleProviderImpl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GUI module for auto-subtitle
|
||||||
|
*/
|
||||||
|
module com.github.gtache.autosubtitle.gui {
|
||||||
|
requires transitive com.github.gtache.autosubtitle.api;
|
||||||
|
requires transitive dagger;
|
||||||
|
requires transitive javax.inject;
|
||||||
|
|
||||||
|
exports com.github.gtache.autosubtitle.gui;
|
||||||
|
exports com.github.gtache.autosubtitle.gui.spi;
|
||||||
|
exports com.github.gtache.autosubtitle.modules.gui;
|
||||||
|
|
||||||
|
provides MainBundleProvider with MainBundleProviderImpl;
|
||||||
|
provides SetupBundleProvider with SetupBundleProviderImpl;
|
||||||
|
provides WorkBundleProvider with WorkBundleProviderImpl;
|
||||||
|
}
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
main.button.export.label=Export video...
|
|
||||||
main.button.extract.label=Extract subtitles
|
|
||||||
main.button.file.label=Open file...
|
|
||||||
main.button.reset.label=Reset subtitles
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
main.button.file.label=Ouvrir un fichier...
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
main.tab.setup.label=Setup
|
||||||
|
main.tab.work.label=Work
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
main.tab.setup.label=Installation
|
||||||
|
main.tab.work.label=Travail
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
media.volume.label=Volume
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
media.volume.label=Volume
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
setup.description.label=Status of the various tools used by the app
|
||||||
|
setup.install.error.label=An error occurred while installing : {0}
|
||||||
|
setup.install.error.title=Error installing
|
||||||
|
setup.menu.install.label=Install
|
||||||
|
setup.menu.label=Action
|
||||||
|
setup.menu.reinstall.label=Reinstall
|
||||||
|
setup.menu.uninstall.label=Uninstall
|
||||||
|
setup.menu.update.label=Update
|
||||||
|
setup.reinstall.error.label=An error occurred while reinstalling : {0}
|
||||||
|
setup.reinstall.error.title=Error reinstalling
|
||||||
|
setup.status.errored.label=threw an error.
|
||||||
|
setup.status.installed.label=is installed.
|
||||||
|
setup.status.not_installed.label=is not installed.
|
||||||
|
setup.status.update_available.label=has an available update.
|
||||||
|
setup.uninstall.error.label=An error occurred while uninstalling : {0}
|
||||||
|
setup.uninstall.error.title=Error uninstalling
|
||||||
|
setup.update.error.label=An error occurred while updating : {0}
|
||||||
|
setup.update.error.title=Error updating
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
setup.description.label=Statut des outils utilis\u00E9s par l'application
|
||||||
|
setup.install.error.label=Une erreur s''est produite lors de l''installation: {0}
|
||||||
|
setup.install.error.title=Erreur d'installation
|
||||||
|
setup.menu.install.label=Installer
|
||||||
|
setup.menu.label=Action
|
||||||
|
setup.menu.reinstall.label=R\u00E9installer
|
||||||
|
setup.menu.uninstall.label=D\u00E9sinstaller
|
||||||
|
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.title=Erreur de r\u00E9installation
|
||||||
|
setup.status.errored.label=a caus\u00E9 une erreur.
|
||||||
|
setup.status.installed.label=est install\u00E9.
|
||||||
|
setup.status.not_installed.label=n'est pas install\u00E9.
|
||||||
|
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.title=Erreur de d\u00E9sinstallation
|
||||||
|
setup.update.error.label=Une erreur s''est produite lors de la mise \u00E0 jour : {0}
|
||||||
|
setup.update.error.title=Erreur de mise \u00E0 jour
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
work.button.export.hard.label=Burn video...
|
||||||
|
work.button.export.hard.tooltip=Burns the subtitles into the video. This means that the subtitles are printed in the image and can't be disabled.
|
||||||
|
work.button.export.soft.label=Export video...
|
||||||
|
work.button.export.soft.tooltip=Adds the subtitles to the video. This allows a video to have multiple subtitles and to enable them at will.
|
||||||
|
work.button.extract.label=Extract subtitles
|
||||||
|
work.button.file.label=Open video...
|
||||||
|
work.button.reset.label=Reset subtitles
|
||||||
|
work.error.export.label=Error during the export : {0}
|
||||||
|
work.error.export.title=Error exporting
|
||||||
|
work.table.column.from.label=From
|
||||||
|
work.table.column.text.label=Text
|
||||||
|
work.table.column.to.label=To
|
||||||
|
work.translate.label=Automatic translation (for adding)
|
||||||
|
work.translate.tooltip=A comma-separated list of ISO 639-3 codes
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
work.table.column.from.label=De
|
||||||
|
work.table.column.text.label=Texte
|
||||||
|
work.table.column.to.label=\u00C0
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
56
pom.xml
56
pom.xml
@@ -11,16 +11,21 @@
|
|||||||
<modules>
|
<modules>
|
||||||
<module>api</module>
|
<module>api</module>
|
||||||
<module>core</module>
|
<module>core</module>
|
||||||
<module>whisper</module>
|
<module>deepl</module>
|
||||||
<module>ffmpeg</module>
|
<module>ffmpeg</module>
|
||||||
<module>gui</module>
|
|
||||||
<module>fx</module>
|
<module>fx</module>
|
||||||
|
<module>gui</module>
|
||||||
|
<module>run</module>
|
||||||
|
<module>whisper</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<maven.compiler.source>21</maven.compiler.source>
|
<maven.compiler.source>21</maven.compiler.source>
|
||||||
<maven.compiler.target>21</maven.compiler.target>
|
<maven.compiler.target>21</maven.compiler.target>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
|
||||||
|
<dagger.version>2.51.1</dagger.version>
|
||||||
|
<log4j.version>2.23.1</log4j.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
@@ -35,11 +40,21 @@
|
|||||||
<artifactId>autosubtitle-core</artifactId>
|
<artifactId>autosubtitle-core</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.gtache.autosubtitle</groupId>
|
||||||
|
<artifactId>autosubtitle-deepl</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.gtache.autosubtitle</groupId>
|
<groupId>com.github.gtache.autosubtitle</groupId>
|
||||||
<artifactId>autosubtitle-ffmpeg</artifactId>
|
<artifactId>autosubtitle-ffmpeg</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.gtache.autosubtitle</groupId>
|
||||||
|
<artifactId>autosubtitle-fx</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.gtache.autosubtitle</groupId>
|
<groupId>com.github.gtache.autosubtitle</groupId>
|
||||||
<artifactId>autosubtitle-gui</artifactId>
|
<artifactId>autosubtitle-gui</artifactId>
|
||||||
@@ -47,7 +62,7 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.gtache.autosubtitle</groupId>
|
<groupId>com.github.gtache.autosubtitle</groupId>
|
||||||
<artifactId>autosubtitle-fx</artifactId>
|
<artifactId>autosubtitle-run</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -58,9 +73,42 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.dagger</groupId>
|
<groupId>com.google.dagger</groupId>
|
||||||
<artifactId>dagger</artifactId>
|
<artifactId>dagger</artifactId>
|
||||||
<version>2.51.1</version>
|
<version>${dagger.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
|
<artifactId>log4j-api</artifactId>
|
||||||
|
<version>${log4j.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<pluginManagement>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.13.0</version>
|
||||||
|
<configuration>
|
||||||
|
<annotationProcessorPaths>
|
||||||
|
<path>
|
||||||
|
<groupId>com.google.dagger</groupId>
|
||||||
|
<artifactId>dagger-compiler</artifactId>
|
||||||
|
<version>${dagger.version}</version>
|
||||||
|
</path>
|
||||||
|
</annotationProcessorPaths>
|
||||||
|
<compilerArgs>
|
||||||
|
<arg>-Adagger.fastInit=enabled</arg>
|
||||||
|
<arg>-Adagger.ignoreProvisionKeyWildcards=ENABLED</arg>
|
||||||
|
<arg>-Xlint:all</arg>
|
||||||
|
</compilerArgs>
|
||||||
|
<showDeprecation>true</showDeprecation>
|
||||||
|
<showWarnings>true</showWarnings>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</pluginManagement>
|
||||||
|
</build>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
34
run/pom.xml
Normal file
34
run/pom.xml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?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-run</artifactId>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.gtache.autosubtitle</groupId>
|
||||||
|
<artifactId>autosubtitle-ffmpeg</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.gtache.autosubtitle</groupId>
|
||||||
|
<artifactId>autosubtitle-fx</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>
|
||||||
|
<version>${log4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package com.github.gtache.autosubtitle.modules.run;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.Audio;
|
||||||
|
import com.github.gtache.autosubtitle.Translator;
|
||||||
|
import com.github.gtache.autosubtitle.Video;
|
||||||
|
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.SubtitleCollection;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.SubtitleConverter;
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.SubtitleExtractor;
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module for missing components
|
||||||
|
*/
|
||||||
|
@Module
|
||||||
|
public abstract class MissingComponentsModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
static Translator providesTranslator() {
|
||||||
|
return new Translator() {
|
||||||
|
@Override
|
||||||
|
public Locale getLocale(final String text) {
|
||||||
|
return Locale.getDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String translate(final String text, final Locale to) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Subtitle translate(final Subtitle subtitle, final Locale to) {
|
||||||
|
return subtitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SubtitleCollection translate(final SubtitleCollection collection, final Locale to) {
|
||||||
|
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
|
||||||
|
@Singleton
|
||||||
|
@TranslatorSetup
|
||||||
|
static SetupManager providesTranslatorSetupManager() {
|
||||||
|
return new NoOpSetupManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.github.gtache.autosubtitle.modules.run;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.setup.SetupException;
|
||||||
|
import com.github.gtache.autosubtitle.setup.SetupManager;
|
||||||
|
import com.github.gtache.autosubtitle.setup.SetupStatus;
|
||||||
|
|
||||||
|
class NoOpSetupManager implements SetupManager {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String name() {
|
||||||
|
return "NoOpSetupManager";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SetupStatus status() {
|
||||||
|
return SetupStatus.NOT_INSTALLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void install() throws SetupException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void uninstall() throws SetupException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update() throws SetupException {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
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.gui.GuiModule;
|
||||||
|
import com.github.gtache.autosubtitle.modules.impl.CoreModule;
|
||||||
|
import com.github.gtache.autosubtitle.setup.modules.ffmpeg.FFmpegSetupModule;
|
||||||
|
import dagger.Component;
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main component
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
@Component(modules = {CoreModule.class, GuiModule.class, FXModule.class, FFmpegModule.class, FFmpegSetupModule.class, MissingComponentsModule.class})
|
||||||
|
public interface RunComponent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The main FXML loader
|
||||||
|
*/
|
||||||
|
FXMLLoader getMainLoader();
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.github.gtache.autosubtitle.run;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.modules.run.DaggerRunComponent;
|
||||||
|
import javafx.application.Application;
|
||||||
|
import javafx.scene.Parent;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
|
public final class Main extends Application {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(final Stage primaryStage) throws Exception {
|
||||||
|
final var component = DaggerRunComponent.create();
|
||||||
|
final var loader = component.getMainLoader();
|
||||||
|
final Parent root = loader.load();
|
||||||
|
final var scene = new Scene(root);
|
||||||
|
final var stage = new Stage();
|
||||||
|
stage.setScene(scene);
|
||||||
|
stage.sizeToScene();
|
||||||
|
stage.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(final String[] args) {
|
||||||
|
Application.launch(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
10
run/src/main/java/module-info.java
Normal file
10
run/src/main/java/module-info.java
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* Main module for auto-subtitle
|
||||||
|
*/
|
||||||
|
module com.github.gtache.autosubtitle.run {
|
||||||
|
requires com.github.gtache.autosubtitle.ffmpeg;
|
||||||
|
requires com.github.gtache.autosubtitle.fx;
|
||||||
|
requires com.github.gtache.autosubtitle.whisper;
|
||||||
|
|
||||||
|
opens com.github.gtache.autosubtitle.run to javafx.graphics;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.github.gtache.autosubtitle.modules.whisper;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.subtitle.SubtitleExtractor;
|
||||||
|
import com.github.gtache.autosubtitle.whisper.WhisperSubtitleExtractor;
|
||||||
|
import dagger.Binds;
|
||||||
|
import dagger.Module;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
public interface WhisperModule {
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
SubtitleExtractor bindsSubtitleExtractor(final WhisperSubtitleExtractor extractor);
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.github.gtache.autosubtitle.setup.modules.whisper;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.setup.SetupManager;
|
||||||
|
import com.github.gtache.autosubtitle.setup.modules.impl.SubtitleExtractorSetup;
|
||||||
|
import com.github.gtache.autosubtitle.setup.whisper.WhisperSetupManager;
|
||||||
|
import dagger.Binds;
|
||||||
|
import dagger.Module;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
public interface WhisperSetupModule {
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
@SubtitleExtractorSetup
|
||||||
|
SetupManager bindsSubtitleExtractorSetupManager(final WhisperSetupManager manager);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.github.gtache.autosubtitle.setup.whisper;
|
||||||
|
|
||||||
|
import com.github.gtache.autosubtitle.setup.SetupException;
|
||||||
|
import com.github.gtache.autosubtitle.setup.SetupManager;
|
||||||
|
import com.github.gtache.autosubtitle.setup.SetupStatus;
|
||||||
|
|
||||||
|
public class WhisperSetupManager implements SetupManager {
|
||||||
|
@Override
|
||||||
|
public String name() {
|
||||||
|
return "Whisper";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SetupStatus status() {
|
||||||
|
return SetupStatus.NOT_INSTALLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void install() throws SetupException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void uninstall() throws SetupException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update() throws SetupException {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user