Moves some modules and files, adds save subtitles
This commit is contained in:
1 parent
5efdaa6f63
commit
c2efb71195
117 files changed
+989
-353
No files matched your search
@@ -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-gui</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>autosubtitle-gui-api</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.github.gtache.autosubtitle</groupId>
|
||||
<artifactId>autosubtitle-api</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+1
-1
@@ -1,7 +1,7 @@
|
||||
package com.github.gtache.autosubtitle.gui;
|
||||
|
||||
import com.github.gtache.autosubtitle.subtitle.ExtractionModel;
|
||||
import com.github.gtache.autosubtitle.subtitle.OutputFormat;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.ExtractionModel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
+14
@@ -19,6 +19,20 @@ public interface WorkController {
|
||||
*/
|
||||
void loadVideo(final Path file);
|
||||
|
||||
/**
|
||||
* Saves the subtitles to the given path
|
||||
*
|
||||
* @param file The output path
|
||||
*/
|
||||
void saveSubtitles(final Path file);
|
||||
|
||||
/**
|
||||
* Loads a subtitles file
|
||||
*
|
||||
* @param file The path to the file
|
||||
*/
|
||||
void loadSubtitles(final Path file);
|
||||
|
||||
/**
|
||||
* @return The model
|
||||
*/
|
||||
+1
-1
@@ -3,8 +3,8 @@ package com.github.gtache.autosubtitle.gui;
|
||||
import com.github.gtache.autosubtitle.Language;
|
||||
import com.github.gtache.autosubtitle.Video;
|
||||
import com.github.gtache.autosubtitle.subtitle.EditableSubtitle;
|
||||
import com.github.gtache.autosubtitle.subtitle.ExtractionModel;
|
||||
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.ExtractionModel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
File renamed without changes.
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* GUI module for auto-subtitle
|
||||
*/
|
||||
module com.github.gtache.autosubtitle.gui.api {
|
||||
requires transitive com.github.gtache.autosubtitle.api;
|
||||
exports com.github.gtache.autosubtitle.gui;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?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-gui</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>autosubtitle-gui-core</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.github.gtache.autosubtitle</groupId>
|
||||
<artifactId>autosubtitle-gui-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.gtache.autosubtitle</groupId>
|
||||
<artifactId>autosubtitle-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.dagger</groupId>
|
||||
<artifactId>dagger</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.github.gtache.autosubtitle.gui;
|
||||
package com.github.gtache.autosubtitle.gui.impl;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.github.gtache.autosubtitle.gui.spi;
|
||||
package com.github.gtache.autosubtitle.gui.impl.spi;
|
||||
|
||||
import java.util.spi.ResourceBundleProvider;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.github.gtache.autosubtitle.gui.spi;
|
||||
package com.github.gtache.autosubtitle.gui.impl.spi;
|
||||
|
||||
import java.util.spi.AbstractResourceBundleProvider;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.github.gtache.autosubtitle.gui.spi;
|
||||
package com.github.gtache.autosubtitle.gui.impl.spi;
|
||||
|
||||
import java.util.spi.ResourceBundleProvider;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.github.gtache.autosubtitle.gui.spi;
|
||||
package com.github.gtache.autosubtitle.gui.impl.spi;
|
||||
|
||||
import java.util.spi.AbstractResourceBundleProvider;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.github.gtache.autosubtitle.gui.spi;
|
||||
package com.github.gtache.autosubtitle.gui.impl.spi;
|
||||
|
||||
import java.util.spi.ResourceBundleProvider;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.github.gtache.autosubtitle.gui.spi;
|
||||
package com.github.gtache.autosubtitle.gui.impl.spi;
|
||||
|
||||
import java.util.spi.AbstractResourceBundleProvider;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.github.gtache.autosubtitle.gui.spi;
|
||||
package com.github.gtache.autosubtitle.gui.impl.spi;
|
||||
|
||||
import java.util.spi.ResourceBundleProvider;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.github.gtache.autosubtitle.gui.spi;
|
||||
package com.github.gtache.autosubtitle.gui.impl.spi;
|
||||
|
||||
import java.util.spi.AbstractResourceBundleProvider;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.github.gtache.autosubtitle.modules.gui;
|
||||
package com.github.gtache.autosubtitle.modules.gui.impl;
|
||||
|
||||
import javax.inject.Qualifier;
|
||||
import java.lang.annotation.Documented;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.github.gtache.autosubtitle.modules.gui;
|
||||
package com.github.gtache.autosubtitle.modules.gui.impl;
|
||||
|
||||
import javax.inject.Qualifier;
|
||||
import java.lang.annotation.Documented;
|
||||
+10
-10
@@ -1,6 +1,6 @@
|
||||
package com.github.gtache.autosubtitle.modules.gui;
|
||||
package com.github.gtache.autosubtitle.modules.gui.impl;
|
||||
|
||||
import com.github.gtache.autosubtitle.gui.CombinedResourceBundle;
|
||||
import com.github.gtache.autosubtitle.gui.impl.CombinedResourceBundle;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
@@ -13,22 +13,22 @@ import java.util.ResourceBundle;
|
||||
* Dagger module for GUI
|
||||
*/
|
||||
@Module
|
||||
public class GuiModule {
|
||||
public class GuiCoreModule {
|
||||
@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.ParametersBundle"),
|
||||
ResourceBundle.getBundle("com.github.gtache.autosubtitle.gui.MediaBundle"));
|
||||
return new CombinedResourceBundle(ResourceBundle.getBundle("com.github.gtache.autosubtitle.gui.impl.MainBundle"),
|
||||
ResourceBundle.getBundle("com.github.gtache.autosubtitle.gui.impl.SetupBundle"),
|
||||
ResourceBundle.getBundle("com.github.gtache.autosubtitle.gui.impl.WorkBundle"),
|
||||
ResourceBundle.getBundle("com.github.gtache.autosubtitle.gui.impl.ParametersBundle"),
|
||||
ResourceBundle.getBundle("com.github.gtache.autosubtitle.gui.impl.MediaBundle"));
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Play
|
||||
static byte[] providesPlayImage() {
|
||||
try (final var in = GuiModule.class.getResourceAsStream("/com/github/gtache/autosubtitle/gui/play_64.png")) {
|
||||
try (final var in = GuiCoreModule.class.getResourceAsStream("/com/github/gtache/autosubtitle/gui/impl/play_64.png")) {
|
||||
return in.readAllBytes();
|
||||
} catch (final IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
@@ -39,7 +39,7 @@ public class GuiModule {
|
||||
@Singleton
|
||||
@Pause
|
||||
static byte[] providesPauseImage() {
|
||||
try (final var in = GuiModule.class.getResourceAsStream("/com/github/gtache/autosubtitle/gui/pause_64.png")) {
|
||||
try (final var in = GuiCoreModule.class.getResourceAsStream("/com/github/gtache/autosubtitle/gui/impl/pause_64.png")) {
|
||||
return in.readAllBytes();
|
||||
} catch (final IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.github.gtache.autosubtitle.modules.gui;
|
||||
package com.github.gtache.autosubtitle.modules.gui.impl;
|
||||
|
||||
import javax.inject.Qualifier;
|
||||
import java.lang.annotation.Documented;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.github.gtache.autosubtitle.modules.gui;
|
||||
package com.github.gtache.autosubtitle.modules.gui.impl;
|
||||
|
||||
import javax.inject.Qualifier;
|
||||
import java.lang.annotation.Documented;
|
||||
@@ -0,0 +1,24 @@
|
||||
import com.github.gtache.autosubtitle.gui.impl.spi.MainBundleProvider;
|
||||
import com.github.gtache.autosubtitle.gui.impl.spi.MainBundleProviderImpl;
|
||||
import com.github.gtache.autosubtitle.gui.impl.spi.ParametersBundleProvider;
|
||||
import com.github.gtache.autosubtitle.gui.impl.spi.ParametersBundleProviderImpl;
|
||||
import com.github.gtache.autosubtitle.gui.impl.spi.SetupBundleProvider;
|
||||
import com.github.gtache.autosubtitle.gui.impl.spi.SetupBundleProviderImpl;
|
||||
import com.github.gtache.autosubtitle.gui.impl.spi.WorkBundleProvider;
|
||||
import com.github.gtache.autosubtitle.gui.impl.spi.WorkBundleProviderImpl;
|
||||
|
||||
/**
|
||||
* Core gui module for autosubtitle
|
||||
*/
|
||||
module com.github.gtache.autosubtitle.gui.core {
|
||||
requires transitive com.github.gtache.autosubtitle.gui.api;
|
||||
requires transitive com.github.gtache.autosubtitle.core;
|
||||
exports com.github.gtache.autosubtitle.gui.impl;
|
||||
exports com.github.gtache.autosubtitle.gui.impl.spi;
|
||||
exports com.github.gtache.autosubtitle.modules.gui.impl;
|
||||
|
||||
provides MainBundleProvider with MainBundleProviderImpl;
|
||||
provides ParametersBundleProvider with ParametersBundleProviderImpl;
|
||||
provides SetupBundleProvider with SetupBundleProviderImpl;
|
||||
provides WorkBundleProvider with WorkBundleProviderImpl;
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+10
-2
@@ -4,14 +4,22 @@ 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.load.label=Load subtitles...
|
||||
work.button.reset.label=Reset subtitles
|
||||
work.button.subtitles.save.label=Save subtitles...
|
||||
work.export.error.label=Error during the export : {0}
|
||||
work.export.error.title=Error exporting
|
||||
work.extract.error.label=Error extracting subtitles : {0}
|
||||
work.extract.error.title=Error extracting
|
||||
work.language.label=Video language
|
||||
work.load.error.label=Error loading video : {0}
|
||||
work.load.error.title=Error loading
|
||||
work.load.subtitles.error.label=Error loading subtitles : {0}
|
||||
work.load.subtitles.error.title=Error loading
|
||||
work.load.video.error.label=Error loading video : {0}
|
||||
work.load.video.error.title=Error loading
|
||||
work.save.subtitles.error.label=Error saving subtitles : {0}
|
||||
work.save.subtitles.error.title=Error saving
|
||||
work.save.subtitles.missing.converter.label=No converter found for {0}
|
||||
work.save.subtitles.missing.converter.title=No converter found
|
||||
work.status.exporting.label=Exporting...
|
||||
work.status.extracting.label=Extracting...
|
||||
work.status.idle.label=Idle
|
||||
+8
-2
@@ -4,14 +4,20 @@ work.button.export.soft.label=Exporter la vid\u00E9o...
|
||||
work.button.export.soft.tooltip=Ajoute les sous-titres \u00E0 la vid\u00E9o. Cela permet d'avoir plusieurs pistes de sous-titres dans une m\u00EAme vid\u00E9o et de les activer comme d\u00E9sir\u00E9.
|
||||
work.button.extract.label=Extraire les sous-titres
|
||||
work.button.file.label=Ouvrir une vid\u00E9o...
|
||||
work.button.load.label=Charger des sous-titres...
|
||||
work.button.reset.label=R\u00E9initialiser les sous-titres
|
||||
work.button.subtitles.save.label=Sauvegarder les sous-titres...
|
||||
work.export.error.label=Erreur durant l''export : {0}
|
||||
work.export.error.title=Erreur d'export
|
||||
work.extract.error.label=Erreur durant l''extraction des sous-titres : {0}
|
||||
work.extract.error.title=Erreur d'extraction
|
||||
work.language.label=Language de la vid\u00E9o
|
||||
work.load.error.label=Erreur lors du chargement de la vid\u00E9o : {0}
|
||||
work.load.error.title=Erreur de chargement
|
||||
work.load.subtitles.error.label=Erreur de chargement des sous-titres : {0}
|
||||
work.load.subtitles.error.title=Erreur de chargement
|
||||
work.load.video.error.label=Erreur lors du chargement de la vid\u00E9o : {0}
|
||||
work.load.video.error.title=Erreur de chargement
|
||||
work.save.subtitles.missing.converter.label=Aucun convertisseur trouv\u00E9 pour {0}
|
||||
work.save.subtitles.missing.converter.title=Aucun convertisseur trouv\u00E9
|
||||
work.status.exporting.label=Exportation en cours...
|
||||
work.status.extracting.label=Extraction en cours...
|
||||
work.status.idle.label=Idle
|
||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,49 @@
|
||||
<?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-gui</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>autosubtitle-fx</artifactId>
|
||||
|
||||
<properties>
|
||||
<controlsfx.version>11.2.1</controlsfx.version>
|
||||
<javafx.version>22.0.1</javafx.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.github.gtache.autosubtitle</groupId>
|
||||
<artifactId>autosubtitle-gui-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.dagger</groupId>
|
||||
<artifactId>dagger</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-media</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-fxml</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.controlsfx</groupId>
|
||||
<artifactId>controlsfx</artifactId>
|
||||
<version>${controlsfx.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.github.gtache.autosubtitle.gui.fx;
|
||||
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.stage.Window;
|
||||
|
||||
/**
|
||||
* Base class for FX controllers
|
||||
*/
|
||||
public abstract class AbstractFXController {
|
||||
|
||||
/**
|
||||
* @return the current window
|
||||
*/
|
||||
protected abstract Window window();
|
||||
|
||||
/**
|
||||
* Show an error dialog
|
||||
*
|
||||
* @param title the dialog title
|
||||
* @param message the error message
|
||||
*/
|
||||
protected void showErrorDialog(final String title, final String message) {
|
||||
final var alert = new Alert(Alert.AlertType.ERROR, message, ButtonType.OK);
|
||||
alert.initOwner(window());
|
||||
alert.setHeaderText(null);
|
||||
alert.setTitle(title);
|
||||
alert.showAndWait();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.github.gtache.autosubtitle.gui.fx;
|
||||
|
||||
import com.github.gtache.autosubtitle.gui.TimeFormatter;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/**
|
||||
* {@link TimeFormatter} separating values using a colon
|
||||
*/
|
||||
@Singleton
|
||||
public class ColonTimeFormatter implements TimeFormatter {
|
||||
|
||||
@Inject
|
||||
ColonTimeFormatter() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String format(final long elapsed, final long total) {
|
||||
final var elapsedString = format(elapsed);
|
||||
final var totalString = format(total);
|
||||
return elapsedString + "/" + totalString;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String format(final long millis) {
|
||||
final var secondsInMinute = 60;
|
||||
final var secondsInHour = secondsInMinute * 60;
|
||||
var intDuration = (int) millis / 1000;
|
||||
final var durationHours = intDuration / secondsInHour;
|
||||
if (durationHours > 0) {
|
||||
intDuration -= durationHours * secondsInHour;
|
||||
}
|
||||
final var durationMinutes = intDuration / secondsInMinute;
|
||||
final var durationSeconds = intDuration - durationHours * secondsInHour
|
||||
- durationMinutes * secondsInMinute;
|
||||
if (durationHours > 0) {
|
||||
return String.format("%d:%02d:%02d", durationHours, durationMinutes, durationSeconds);
|
||||
} else {
|
||||
return String.format("%02d:%02d", durationMinutes, durationSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long parse(final String time) {
|
||||
final var split = time.split(":");
|
||||
final var secondsInMinute = 60;
|
||||
final var secondsInHour = secondsInMinute * 60;
|
||||
return switch (split.length) {
|
||||
case 1 -> toLong(split[0]) * 1000;
|
||||
case 2 -> (toLong(split[0]) * secondsInMinute + toLong(split[1])) * 1000;
|
||||
case 3 -> (toLong(split[0]) * secondsInHour + toLong(split[1]) * secondsInMinute + toLong(split[2])) * 1000;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
private long toLong(final String time) {
|
||||
if (time.startsWith("0")) {
|
||||
return Long.parseLong(time.substring(1));
|
||||
} else {
|
||||
return Long.parseLong(time);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.github.gtache.autosubtitle.gui.fx;
|
||||
|
||||
import com.github.gtache.autosubtitle.gui.MainController;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.TabPane;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* FX implementation of {@link MainController}
|
||||
*/
|
||||
@Singleton
|
||||
public class FXMainController implements MainController {
|
||||
|
||||
@FXML
|
||||
private TabPane tabPane;
|
||||
|
||||
private final FXMainModel model;
|
||||
|
||||
@Inject
|
||||
FXMainController(final FXMainModel model) {
|
||||
this.model = Objects.requireNonNull(model);
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void initialize() {
|
||||
tabPane.getSelectionModel().selectedIndexProperty().addListener((observable, oldValue, newValue) -> model.selectTab(newValue.intValue()));
|
||||
model.selectedTabProperty().addListener((observable, oldValue, newValue) -> tabPane.getSelectionModel().select(newValue.intValue()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void selectTab(final int index) {
|
||||
model.selectTab(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FXMainModel model() {
|
||||
return model;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.github.gtache.autosubtitle.gui.fx;
|
||||
|
||||
import com.github.gtache.autosubtitle.gui.MainModel;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/**
|
||||
* FX implementation of {@link MainModel}
|
||||
*/
|
||||
@Singleton
|
||||
public class FXMainModel implements MainModel {
|
||||
|
||||
private final IntegerProperty selectedTab;
|
||||
|
||||
@Inject
|
||||
FXMainModel() {
|
||||
selectedTab = new SimpleIntegerProperty(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int selectedTab() {
|
||||
return selectedTab.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void selectTab(final int index) {
|
||||
selectedTab.set(index);
|
||||
}
|
||||
|
||||
IntegerProperty selectedTabProperty() {
|
||||
return selectedTab;
|
||||
}
|
||||
}
|
||||
@@ -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(mediaModel.subtitles(), workModel.subtitles());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
package com.github.gtache.autosubtitle.gui.fx;
|
||||
|
||||
import com.github.gtache.autosubtitle.gui.MediaController;
|
||||
import com.github.gtache.autosubtitle.gui.TimeFormatter;
|
||||
import com.github.gtache.autosubtitle.impl.FileVideoImpl;
|
||||
import com.github.gtache.autosubtitle.modules.gui.impl.Pause;
|
||||
import com.github.gtache.autosubtitle.modules.gui.impl.Play;
|
||||
import com.github.gtache.autosubtitle.subtitle.Subtitle;
|
||||
import com.github.gtache.autosubtitle.subtitle.gui.fx.SubtitleLabel;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Insets;
|
||||
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.Background;
|
||||
import javafx.scene.layout.BackgroundFill;
|
||||
import javafx.scene.layout.CornerRadii;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.media.Media;
|
||||
import javafx.scene.media.MediaPlayer;
|
||||
import javafx.scene.media.MediaView;
|
||||
import javafx.scene.paint.Color;
|
||||
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 FXMediaBinder binder;
|
||||
private final TimeFormatter timeFormatter;
|
||||
private final Image playImage;
|
||||
private final Image pauseImage;
|
||||
|
||||
private boolean wasPlaying;
|
||||
|
||||
@Inject
|
||||
FXMediaController(final FXMediaModel model, final FXMediaBinder binder, final TimeFormatter timeFormatter,
|
||||
@Play final Image playImage, @Pause final Image pauseImage) {
|
||||
this.model = requireNonNull(model);
|
||||
this.binder = requireNonNull(binder);
|
||||
this.timeFormatter = requireNonNull(timeFormatter);
|
||||
this.playImage = requireNonNull(playImage);
|
||||
this.pauseImage = requireNonNull(pauseImage);
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void initialize() {
|
||||
volumeValueLabel.textProperty().bind(Bindings.createStringBinding(() -> String.valueOf((int) (model.volume() * 100)), model.volumeProperty()));
|
||||
playLabel.textProperty().bind(Bindings.createStringBinding(() -> timeFormatter.format(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)) {
|
||||
if (model.position() == model.duration()) {
|
||||
seek(0L);
|
||||
}
|
||||
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);
|
||||
stackPane.getChildren().removeIf(Label.class::isInstance);
|
||||
model.subtitles().forEach(s -> {
|
||||
//TODO optimize using e.g. direction of playback
|
||||
if (s.start() <= millis && s.end() >= millis) {
|
||||
logger.info("Adding label {} at {}", s, millis);
|
||||
final var label = createDraggableLabel(s);
|
||||
stackPane.getChildren().add(label);
|
||||
}
|
||||
});
|
||||
});
|
||||
playSlider.setOnMousePressed(e -> {
|
||||
wasPlaying = model.isPlaying();
|
||||
model.setIsPlaying(false);
|
||||
});
|
||||
playSlider.valueProperty().addListener(observable1 -> {
|
||||
if (playSlider.isValueChanging()) {
|
||||
seek((long) playSlider.getValue());
|
||||
}
|
||||
});
|
||||
playSlider.setOnMouseReleased(e -> {
|
||||
final var value = playSlider.getValue();
|
||||
Platform.runLater(() -> {
|
||||
seek((long) value);
|
||||
model.setIsPlaying(wasPlaying);
|
||||
});
|
||||
});
|
||||
player.volumeProperty().bindBidirectional(model.volumeProperty());
|
||||
player.setOnPlaying(() -> model.setIsPlaying(true));
|
||||
player.setOnPaused(() -> model.setIsPlaying(false));
|
||||
player.setOnEndOfMedia(() -> 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()));
|
||||
binder.createBindings();
|
||||
}
|
||||
|
||||
@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.setBackground(new Background(new BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY)));
|
||||
label.setTextFill(Color.WHITE);
|
||||
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 javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 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 List<EditableSubtitle> subtitles;
|
||||
|
||||
@Inject
|
||||
FXMediaModel() {
|
||||
this.video = new SimpleObjectProperty<>();
|
||||
this.volume = new SimpleDoubleProperty(1.0d);
|
||||
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 List<EditableSubtitle> subtitles() {
|
||||
return subtitles;
|
||||
}
|
||||
|
||||
LongProperty positionProperty() {
|
||||
return position;
|
||||
}
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
package com.github.gtache.autosubtitle.gui.fx;
|
||||
|
||||
import com.github.gtache.autosubtitle.gui.ParametersController;
|
||||
import com.github.gtache.autosubtitle.subtitle.OutputFormat;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.ExtractionModel;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.ExtractionModelProvider;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.control.TextFormatter;
|
||||
import javafx.stage.Window;
|
||||
import javafx.util.converter.IntegerStringConverter;
|
||||
import javafx.util.converter.NumberStringConverter;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.controlsfx.control.PrefixSelectionComboBox;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.prefs.BackingStoreException;
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
/**
|
||||
* FX implementation of {@link ParametersController}
|
||||
*/
|
||||
@Singleton
|
||||
public class FXParametersController extends AbstractFXController implements ParametersController {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(FXParametersController.class);
|
||||
@FXML
|
||||
private PrefixSelectionComboBox<ExtractionModel> extractionModelCombobox;
|
||||
@FXML
|
||||
private PrefixSelectionComboBox<OutputFormat> extractionOutputFormat;
|
||||
@FXML
|
||||
private PrefixSelectionComboBox<String> fontFamilyCombobox;
|
||||
@FXML
|
||||
private TextField fontSizeField;
|
||||
|
||||
private final FXParametersModel model;
|
||||
private final Preferences preferences;
|
||||
private final ExtractionModelProvider extractionModelProvider;
|
||||
|
||||
@Inject
|
||||
FXParametersController(final FXParametersModel model, final Preferences preferences, final ExtractionModelProvider extractionModelProvider) {
|
||||
this.model = requireNonNull(model);
|
||||
this.preferences = requireNonNull(preferences);
|
||||
this.extractionModelProvider = requireNonNull(extractionModelProvider);
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void initialize() {
|
||||
extractionModelCombobox.setItems(model.availableExtractionModels());
|
||||
extractionModelCombobox.valueProperty().bindBidirectional(model.extractionModelProperty());
|
||||
|
||||
extractionOutputFormat.setItems(model.availableOutputFormats());
|
||||
extractionOutputFormat.valueProperty().bindBidirectional(model.outputFormatProperty());
|
||||
|
||||
fontFamilyCombobox.setItems(model.availableFontFamilies());
|
||||
fontFamilyCombobox.valueProperty().bindBidirectional(model.fontFamilyProperty());
|
||||
|
||||
final UnaryOperator<TextFormatter.Change> integerFilter = change -> {
|
||||
final var newText = change.getControlNewText();
|
||||
if (newText.matches("[1-9]\\d*")) {
|
||||
return change;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
fontSizeField.setTextFormatter(new TextFormatter<>(new IntegerStringConverter(), 0, integerFilter));
|
||||
|
||||
fontSizeField.textProperty().bindBidirectional(model.fontSizeProperty(), new NumberStringConverter());
|
||||
|
||||
loadPreferences();
|
||||
}
|
||||
|
||||
private void loadPreferences() {
|
||||
final var extractionModel = preferences.get("extractionModel", model.extractionModel().name());
|
||||
final var outputFormat = preferences.get("outputFormat", model.outputFormat().name());
|
||||
final var fontFamily = preferences.get("fontFamily", model.fontFamily());
|
||||
final var fontSize = preferences.getInt("fontSize", model.fontSize());
|
||||
|
||||
model.setExtractionModel(extractionModelProvider.getExtractionModel(extractionModel));
|
||||
model.setOutputFormat(OutputFormat.valueOf(outputFormat));
|
||||
model.setFontFamily(fontFamily);
|
||||
model.setFontSize(fontSize);
|
||||
logger.info("Loaded preferences");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
logger.info("Saving preferences");
|
||||
preferences.put("extractionModel", model.extractionModel().name());
|
||||
preferences.put("outputFormat", model.outputFormat().name());
|
||||
preferences.put("fontFamily", model.fontFamily());
|
||||
preferences.putInt("fontSize", model.fontSize());
|
||||
try {
|
||||
preferences.flush();
|
||||
logger.info("Preferences saved");
|
||||
} catch (final BackingStoreException e) {
|
||||
logger.error("Error saving preferences", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
loadPreferences();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FXParametersModel model() {
|
||||
return model;
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void savePressed() {
|
||||
save();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void resetPressed() {
|
||||
reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Window window() {
|
||||
return extractionModelCombobox.getScene().getWindow();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package com.github.gtache.autosubtitle.gui.fx;
|
||||
|
||||
import com.github.gtache.autosubtitle.gui.ParametersModel;
|
||||
import com.github.gtache.autosubtitle.modules.gui.impl.FontFamily;
|
||||
import com.github.gtache.autosubtitle.modules.gui.impl.FontSize;
|
||||
import com.github.gtache.autosubtitle.subtitle.OutputFormat;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.ExtractionModel;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.ExtractionModelProvider;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/**
|
||||
* FX implementation of {@link ParametersModel}
|
||||
*/
|
||||
@Singleton
|
||||
public class FXParametersModel implements ParametersModel {
|
||||
|
||||
private final ObservableList<ExtractionModel> availableExtractionModels;
|
||||
private final ObjectProperty<ExtractionModel> extractionModel;
|
||||
private final ObservableList<OutputFormat> availableOutputFormats;
|
||||
private final ObjectProperty<OutputFormat> outputFormat;
|
||||
private final ObservableList<String> availableFontFamilies;
|
||||
private final StringProperty fontFamily;
|
||||
private final IntegerProperty fontSize;
|
||||
|
||||
@Inject
|
||||
FXParametersModel(final ExtractionModelProvider extractionModelProvider, @FontFamily final String defaultFontFamily, @FontSize final int defaultFontSize) {
|
||||
this.availableExtractionModels = FXCollections.unmodifiableObservableList(FXCollections.observableArrayList(extractionModelProvider.getAvailableExtractionModels()));
|
||||
this.extractionModel = new SimpleObjectProperty<>(extractionModelProvider.getDefaultExtractionModel());
|
||||
this.availableOutputFormats = FXCollections.unmodifiableObservableList(FXCollections.observableArrayList(OutputFormat.SRT));
|
||||
this.outputFormat = new SimpleObjectProperty<>(OutputFormat.SRT);
|
||||
this.availableFontFamilies = FXCollections.unmodifiableObservableList(FXCollections.observableArrayList("Arial"));
|
||||
this.fontFamily = new SimpleStringProperty(defaultFontFamily);
|
||||
this.fontSize = new SimpleIntegerProperty(defaultFontSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObservableList<ExtractionModel> availableExtractionModels() {
|
||||
return availableExtractionModels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtractionModel extractionModel() {
|
||||
return extractionModel.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExtractionModel(final ExtractionModel model) {
|
||||
extractionModel.set(model);
|
||||
}
|
||||
|
||||
ObjectProperty<ExtractionModel> extractionModelProperty() {
|
||||
return extractionModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObservableList<OutputFormat> availableOutputFormats() {
|
||||
return availableOutputFormats;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputFormat outputFormat() {
|
||||
return outputFormat.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOutputFormat(final OutputFormat format) {
|
||||
outputFormat.set(format);
|
||||
}
|
||||
|
||||
ObjectProperty<OutputFormat> outputFormatProperty() {
|
||||
return outputFormat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObservableList<String> availableFontFamilies() {
|
||||
return availableFontFamilies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String fontFamily() {
|
||||
return fontFamily.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFontFamily(final String fontFamily) {
|
||||
this.fontFamily.set(fontFamily);
|
||||
}
|
||||
|
||||
StringProperty fontFamilyProperty() {
|
||||
return fontFamily;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int fontSize() {
|
||||
return fontSize.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFontSize(final int fontSize) {
|
||||
this.fontSize.set(fontSize);
|
||||
}
|
||||
|
||||
IntegerProperty fontSizeProperty() {
|
||||
return fontSize;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
package com.github.gtache.autosubtitle.gui.fx;
|
||||
|
||||
import com.github.gtache.autosubtitle.gui.SetupController;
|
||||
import com.github.gtache.autosubtitle.modules.setup.impl.SubtitleExtractorSetup;
|
||||
import com.github.gtache.autosubtitle.modules.setup.impl.TranslatorSetup;
|
||||
import com.github.gtache.autosubtitle.modules.setup.impl.VideoConverterSetup;
|
||||
import com.github.gtache.autosubtitle.setup.SetupEvent;
|
||||
import com.github.gtache.autosubtitle.setup.SetupException;
|
||||
import com.github.gtache.autosubtitle.setup.SetupListener;
|
||||
import com.github.gtache.autosubtitle.setup.SetupManager;
|
||||
import com.github.gtache.autosubtitle.setup.SetupStatus;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.MenuButton;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.ProgressBar;
|
||||
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.text.MessageFormat;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* FX implementation of {@link SetupController}
|
||||
*/
|
||||
@Singleton
|
||||
public class FXSetupController extends AbstractFXController implements SetupController, SetupListener {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(FXSetupController.class);
|
||||
|
||||
@FXML
|
||||
private ResourceBundle resources;
|
||||
@FXML
|
||||
private Label converterNameLabel;
|
||||
@FXML
|
||||
private Label converterStatusLabel;
|
||||
@FXML
|
||||
private Label extractorNameLabel;
|
||||
@FXML
|
||||
private Label extractorStatusLabel;
|
||||
@FXML
|
||||
private Label translatorNameLabel;
|
||||
@FXML
|
||||
private Label translatorStatusLabel;
|
||||
@FXML
|
||||
private MenuButton converterButton;
|
||||
@FXML
|
||||
private MenuButton extractorButton;
|
||||
@FXML
|
||||
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 SetupManager converterManager;
|
||||
private final SetupManager extractorManager;
|
||||
private final SetupManager translatorManager;
|
||||
|
||||
private final Map<SetupManager, ObjectProperty<SetupStatus>> statusMap;
|
||||
private final Map<SetupManager, StringProperty> setupProgressMessageMap;
|
||||
private final Map<SetupManager, DoubleProperty> setupProgressMap;
|
||||
|
||||
@Inject
|
||||
FXSetupController(final FXSetupModel model,
|
||||
@VideoConverterSetup final SetupManager converterManager,
|
||||
@SubtitleExtractorSetup final SetupManager extractorManager,
|
||||
@TranslatorSetup final SetupManager translatorManager) {
|
||||
this.model = Objects.requireNonNull(model);
|
||||
this.converterManager = Objects.requireNonNull(converterManager);
|
||||
this.extractorManager = Objects.requireNonNull(extractorManager);
|
||||
this.translatorManager = Objects.requireNonNull(translatorManager);
|
||||
statusMap = HashMap.newHashMap(3);
|
||||
setupProgressMessageMap = HashMap.newHashMap(3);
|
||||
setupProgressMap = HashMap.newHashMap(3);
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void initialize() {
|
||||
statusMap.put(converterManager, model.videoConverterStatusProperty());
|
||||
statusMap.put(extractorManager, model.subtitleExtractorStatusProperty());
|
||||
statusMap.put(translatorManager, model.translatorStatusProperty());
|
||||
setupProgressMessageMap.put(converterManager, model.videoConverterSetupProgressLabelProperty());
|
||||
setupProgressMessageMap.put(extractorManager, model.subtitleExtractorSetupProgressLabelProperty());
|
||||
setupProgressMessageMap.put(translatorManager, model.translatorSetupProgressLabelProperty());
|
||||
setupProgressMap.put(converterManager, model.videoConverterSetupProgressProperty());
|
||||
setupProgressMap.put(extractorManager, model.subtitleExtractorSetupProgressProperty());
|
||||
setupProgressMap.put(translatorManager, model.translatorSetupProgressProperty());
|
||||
|
||||
bindMenu(converterButton, converterManager);
|
||||
bindMenu(extractorButton, extractorManager);
|
||||
bindMenu(translatorButton, translatorManager);
|
||||
|
||||
model.setSubtitleExtractorStatus(extractorManager.status());
|
||||
model.setVideoConverterStatus(converterManager.status());
|
||||
model.setTranslatorStatus(translatorManager.status());
|
||||
|
||||
converterNameLabel.setText(converterManager.name());
|
||||
extractorNameLabel.setText(extractorManager.name());
|
||||
translatorNameLabel.setText(translatorManager.name());
|
||||
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(-2));
|
||||
extractorProgress.visibleProperty().bind(model.subtitleExtractorSetupProgressProperty().greaterThan(-2));
|
||||
translatorProgress.visibleProperty().bind(model.translatorSetupProgressProperty().greaterThan(-2));
|
||||
|
||||
converterProgressLabel.textProperty().bind(model.videoConverterSetupProgressLabelProperty());
|
||||
extractorProgressLabel.textProperty().bind(model.subtitleExtractorSetupProgressLabelProperty());
|
||||
translatorProgressLabel.textProperty().bind(model.translatorSetupProgressLabelProperty());
|
||||
|
||||
converterProgressLabel.visibleProperty().bind(converterProgress.visibleProperty());
|
||||
extractorProgressLabel.visibleProperty().bind(extractorProgress.visibleProperty());
|
||||
translatorProgressLabel.visibleProperty().bind(translatorProgress.visibleProperty());
|
||||
}
|
||||
|
||||
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.label"));
|
||||
installItem.setOnAction(e -> tryInstall(setupManager));
|
||||
button.getItems().add(installItem);
|
||||
}
|
||||
case BUNDLE_INSTALLED -> {
|
||||
final var reinstallItem = new MenuItem(resources.getString("setup.menu.reinstall.label"));
|
||||
reinstallItem.setOnAction(e -> tryReinstall(setupManager));
|
||||
final var uninstallItem = new MenuItem(resources.getString("setup.menu.uninstall.label"));
|
||||
uninstallItem.setOnAction(e -> tryUninstall(setupManager));
|
||||
button.getItems().addAll(reinstallItem, uninstallItem);
|
||||
}
|
||||
case UPDATE_AVAILABLE -> {
|
||||
final var updateItem = new MenuItem(resources.getString("setup.menu.update.label"));
|
||||
updateItem.setOnAction(e -> tryUpdate(setupManager));
|
||||
final var reinstallItem = new MenuItem(resources.getString("setup.menu.reinstall.label"));
|
||||
reinstallItem.setOnAction(e -> tryReinstall(setupManager));
|
||||
final var uninstallItem = new MenuItem(resources.getString("setup.menu.uninstall.label"));
|
||||
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
|
||||
public void installVideoConverter() {
|
||||
tryInstall(converterManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uninstallVideoConverter() {
|
||||
tryUninstall(converterManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateVideoConverter() {
|
||||
tryUpdate(converterManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reinstallVideoConverter() {
|
||||
tryReinstall(converterManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void installSubtitleExtractor() {
|
||||
tryInstall(extractorManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uninstallSubtitleExtractor() {
|
||||
tryUninstall(extractorManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateSubtitleExtractor() {
|
||||
tryUpdate(extractorManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reinstallSubtitleExtractor() {
|
||||
tryReinstall(extractorManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void installTranslator() {
|
||||
tryInstall(translatorManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uninstallTranslator() {
|
||||
tryUninstall(translatorManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTranslator() {
|
||||
tryUpdate(translatorManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reinstallTranslator() {
|
||||
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) {
|
||||
manager.addListener(this);
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
consumer.accept(manager);
|
||||
Platform.runLater(() -> {
|
||||
statusMap.get(manager).set(manager.status());
|
||||
setupProgressMap.get(manager).set(-2);
|
||||
});
|
||||
} catch (final SetupException e) {
|
||||
logger.error("Error {}ing {}", operation, manager.name(), e);
|
||||
Platform.runLater(() -> {
|
||||
statusMap.get(manager).set(SetupStatus.ERRORED);
|
||||
setupProgressMap.get(manager).set(-2);
|
||||
showErrorDialog(resources.getString("setup." + operation + ".error.title"),
|
||||
MessageFormat.format(resources.getString("setup." + operation + ".error.label"), e.getMessage()));
|
||||
});
|
||||
} finally {
|
||||
manager.removeListener(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActionStart(final SetupEvent event) {
|
||||
final var action = event.action();
|
||||
final var target = event.target();
|
||||
final var display = MessageFormat.format(resources.getString("setup.event." + action.name().toLowerCase() + ".start.label"), target);
|
||||
onAction(event, display);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActionEnd(final SetupEvent event) {
|
||||
final var action = event.action();
|
||||
final var target = event.target();
|
||||
final var display = MessageFormat.format(resources.getString("setup.event." + action.name().toLowerCase() + ".end.label"), target);
|
||||
onAction(event, display);
|
||||
}
|
||||
|
||||
private void onAction(final SetupEvent event, final String display) {
|
||||
final var manager = event.setupManager();
|
||||
final var property = setupProgressMessageMap.get(manager);
|
||||
final var progress = event.progress();
|
||||
final var progressProperty = setupProgressMap.get(manager);
|
||||
Platform.runLater(() -> {
|
||||
property.set(display);
|
||||
progressProperty.set(progress);
|
||||
});
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface SetupConsumer {
|
||||
void accept(SetupManager manager) throws SetupException;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FXSetupModel model() {
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Window window() {
|
||||
return converterNameLabel.getScene().getWindow();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
package com.github.gtache.autosubtitle.gui.fx;
|
||||
|
||||
import com.github.gtache.autosubtitle.gui.SetupModel;
|
||||
import com.github.gtache.autosubtitle.setup.SetupStatus;
|
||||
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.Singleton;
|
||||
|
||||
/**
|
||||
* FX implementation of {@link SetupModel}
|
||||
*/
|
||||
@Singleton
|
||||
public class FXSetupModel implements SetupModel {
|
||||
|
||||
private final ObjectProperty<SetupStatus> subtitleExtractorStatus;
|
||||
private final ReadOnlyBooleanWrapper subtitleExtractorInstalled;
|
||||
private final ReadOnlyBooleanWrapper subtitleExtractorUpdateAvailable;
|
||||
private final DoubleProperty subtitleExtractorSetupProgress;
|
||||
private final StringProperty subtitleExtractorSetupProgressLabel;
|
||||
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
|
||||
FXSetupModel() {
|
||||
this.subtitleExtractorStatus = new SimpleObjectProperty<>(SetupStatus.ERRORED);
|
||||
this.subtitleExtractorInstalled = new ReadOnlyBooleanWrapper(false);
|
||||
this.subtitleExtractorUpdateAvailable = new ReadOnlyBooleanWrapper(false);
|
||||
this.subtitleExtractorSetupProgress = new SimpleDoubleProperty(-2);
|
||||
this.subtitleExtractorSetupProgressLabel = new SimpleStringProperty("");
|
||||
this.videoConverterStatus = new SimpleObjectProperty<>(SetupStatus.ERRORED);
|
||||
this.videoConverterInstalled = new ReadOnlyBooleanWrapper(false);
|
||||
this.videoConverterUpdateAvailable = new ReadOnlyBooleanWrapper(false);
|
||||
this.videoConverterSetupProgress = new SimpleDoubleProperty(-2);
|
||||
this.videoConverterSetupProgressLabel = new SimpleStringProperty("");
|
||||
this.translatorStatus = new SimpleObjectProperty<>(SetupStatus.ERRORED);
|
||||
this.translatorInstalled = new ReadOnlyBooleanWrapper(false);
|
||||
this.translatorUpdateAvailable = new ReadOnlyBooleanWrapper(false);
|
||||
this.translatorSetupProgress = new SimpleDoubleProperty(-2);
|
||||
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
|
||||
public boolean isSubtitleExtractorInstalled() {
|
||||
return subtitleExtractorInstalled.get();
|
||||
}
|
||||
|
||||
ReadOnlyBooleanProperty subtitleExtractorInstalledProperty() {
|
||||
return subtitleExtractorInstalled.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSubtitleExtractorUpdateAvailable() {
|
||||
return subtitleExtractorUpdateAvailable.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double subtitleExtractorSetupProgress() {
|
||||
return subtitleExtractorSetupProgress.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
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
|
||||
public boolean isVideoConverterInstalled() {
|
||||
return videoConverterInstalled.get();
|
||||
}
|
||||
|
||||
ReadOnlyBooleanProperty videoConverterInstalledProperty() {
|
||||
return videoConverterInstalled.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVideoConverterUpdateAvailable() {
|
||||
return videoConverterUpdateAvailable.get();
|
||||
}
|
||||
|
||||
ReadOnlyBooleanProperty videoConverterUpdateAvailableProperty() {
|
||||
return videoConverterUpdateAvailable.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
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
|
||||
public boolean isTranslatorInstalled() {
|
||||
return translatorInstalled.get();
|
||||
}
|
||||
|
||||
ReadOnlyBooleanProperty translatorInstalledProperty() {
|
||||
return translatorInstalled.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTranslatorUpdateAvailable() {
|
||||
return translatorUpdateAvailable.get();
|
||||
}
|
||||
|
||||
ReadOnlyBooleanProperty translatorUpdateAvailableProperty() {
|
||||
return translatorUpdateAvailable.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
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,22 @@
|
||||
package com.github.gtache.autosubtitle.gui.fx;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Objects;
|
||||
|
||||
@Singleton
|
||||
public class FXWorkBinder {
|
||||
|
||||
private final FXWorkModel workModel;
|
||||
private final FXParametersModel parametersModel;
|
||||
|
||||
@Inject
|
||||
FXWorkBinder(final FXWorkModel workModel, final FXParametersModel parametersModel) {
|
||||
this.workModel = Objects.requireNonNull(workModel);
|
||||
this.parametersModel = Objects.requireNonNull(parametersModel);
|
||||
}
|
||||
|
||||
void createBindings() {
|
||||
workModel.extractionModelProperty().bind(parametersModel.extractionModelProperty());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
package com.github.gtache.autosubtitle.gui.fx;
|
||||
|
||||
import com.github.gtache.autosubtitle.Language;
|
||||
import com.github.gtache.autosubtitle.Translator;
|
||||
import com.github.gtache.autosubtitle.VideoConverter;
|
||||
import com.github.gtache.autosubtitle.VideoLoader;
|
||||
import com.github.gtache.autosubtitle.gui.TimeFormatter;
|
||||
import com.github.gtache.autosubtitle.gui.WorkController;
|
||||
import com.github.gtache.autosubtitle.gui.WorkStatus;
|
||||
import com.github.gtache.autosubtitle.subtitle.EditableSubtitle;
|
||||
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
|
||||
import com.github.gtache.autosubtitle.subtitle.converter.ParseException;
|
||||
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.ExtractEvent;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.ExtractException;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.SubtitleExtractor;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.SubtitleExtractorListener;
|
||||
import com.github.gtache.autosubtitle.subtitle.gui.fx.ObservableSubtitleImpl;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ProgressBar;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.control.cell.TextFieldTableCell;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Window;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.controlsfx.control.CheckComboBox;
|
||||
import org.controlsfx.control.PrefixSelectionComboBox;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
/**
|
||||
* FX implementation of {@link WorkController}
|
||||
*/
|
||||
@Singleton
|
||||
public class FXWorkController extends AbstractFXController implements WorkController, SubtitleExtractorListener {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(FXWorkController.class);
|
||||
|
||||
private static final List<String> VIDEO_EXTENSIONS = Stream.of("webm", "mkv", "flv", "vob", "ogv", "ogg",
|
||||
"drc", "gif", "gifv", "mng", "avi", "mts", "m2ts", "ts", "mov", "qt", "wmv", "yuv", "rm", "rmvb",
|
||||
"viv", "asf", "amv", "mp4", "m4p", "m4v", "mpg", "mp2", "mpeg", "mpe", "mpv", "m2v", "m4v", "svi",
|
||||
"3gp", "3g2", "mxf", "roq", "nsv", "flv", "f4v", "f4p", "f4a", "f4b").map(s -> "*." + s).toList();
|
||||
|
||||
@FXML
|
||||
private TextField fileField;
|
||||
@FXML
|
||||
private Button loadSubtitlesButton;
|
||||
@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 FXMediaController mediaController;
|
||||
@FXML
|
||||
private Button saveSubtitlesButton;
|
||||
@FXML
|
||||
private Button addSubtitleButton;
|
||||
@FXML
|
||||
private PrefixSelectionComboBox<Language> languageCombobox;
|
||||
@FXML
|
||||
private CheckComboBox<Language> translationsCombobox;
|
||||
@FXML
|
||||
private Label progressLabel;
|
||||
@FXML
|
||||
private ProgressBar progressBar;
|
||||
@FXML
|
||||
private Label progressDetailLabel;
|
||||
@FXML
|
||||
private ResourceBundle resources;
|
||||
|
||||
private final FXWorkModel model;
|
||||
private final FXWorkBinder binder;
|
||||
private final SubtitleExtractor subtitleExtractor;
|
||||
private final Map<String, SubtitleConverter> subtitleConvertersMap;
|
||||
private final VideoConverter videoConverter;
|
||||
private final VideoLoader videoLoader;
|
||||
private final Translator translator;
|
||||
private final TimeFormatter timeFormatter;
|
||||
private final List<String> subtitleExtensions;
|
||||
|
||||
|
||||
@Inject
|
||||
FXWorkController(final FXWorkModel model, final FXWorkBinder binder, final SubtitleExtractor subtitleExtractor,
|
||||
final Map<String, SubtitleConverter> subtitleConvertersMap, final VideoLoader videoLoader,
|
||||
final VideoConverter videoConverter, final Translator translator, final TimeFormatter timeFormatter) {
|
||||
this.model = requireNonNull(model);
|
||||
this.binder = requireNonNull(binder);
|
||||
this.subtitleExtractor = requireNonNull(subtitleExtractor);
|
||||
this.subtitleConvertersMap = requireNonNull(subtitleConvertersMap);
|
||||
this.videoConverter = requireNonNull(videoConverter);
|
||||
this.videoLoader = requireNonNull(videoLoader);
|
||||
this.translator = requireNonNull(translator);
|
||||
this.timeFormatter = requireNonNull(timeFormatter);
|
||||
this.subtitleExtensions = subtitleConvertersMap.values().stream().map(c -> "*." + c.formatName()).sorted().toList();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void initialize() {
|
||||
languageCombobox.valueProperty().bindBidirectional(model.videoLanguageProperty());
|
||||
languageCombobox.setItems(model.availableVideoLanguages());
|
||||
languageCombobox.setConverter(new LanguageStringConverter());
|
||||
translationsCombobox.setConverter(new LanguageStringConverter());
|
||||
Bindings.bindContent(translationsCombobox.getItems(), model.availableTranslationsLanguage());
|
||||
Bindings.bindContent(model.translations(), translationsCombobox.getCheckModel().getCheckedItems());
|
||||
|
||||
extractButton.disableProperty().bind(model.videoProperty().isNull().or(model.statusProperty().isNotEqualTo(WorkStatus.IDLE)));
|
||||
loadSubtitlesButton.disableProperty().bind(model.videoProperty().isNull().or(model.statusProperty().isNotEqualTo(WorkStatus.IDLE)));
|
||||
resetButton.disableProperty().bind(Bindings.isEmpty(model.subtitles()).or(model.statusProperty().isNotEqualTo(WorkStatus.IDLE)));
|
||||
exportSoftButton.disableProperty().bind(Bindings.isEmpty(model.subtitles()).or(model.statusProperty().isNotEqualTo(WorkStatus.IDLE)));
|
||||
exportHardButton.disableProperty().bind(Bindings.isEmpty(model.subtitles()).or(model.statusProperty().isNotEqualTo(WorkStatus.IDLE)));
|
||||
addSubtitleButton.disableProperty().bind(Bindings.isEmpty(model.subtitles()).or(model.statusProperty().isNotEqualTo(WorkStatus.IDLE)));
|
||||
loadSubtitlesButton.disableProperty().bind(Bindings.isEmpty(model.subtitles()).or(model.statusProperty().isNotEqualTo(WorkStatus.IDLE)));
|
||||
|
||||
subtitlesTable.setItems(model.subtitles());
|
||||
subtitlesTable.setOnKeyPressed(e -> {
|
||||
if (e.getCode().isLetterKey() || e.getCode().isDigitKey()) {
|
||||
editFocusedCell();
|
||||
} else if (e.getCode() == KeyCode.RIGHT ||
|
||||
e.getCode() == KeyCode.TAB) {
|
||||
subtitlesTable.getSelectionModel().selectNext();
|
||||
e.consume();
|
||||
} else if (e.getCode() == KeyCode.LEFT) {
|
||||
subtitlesTable.getSelectionModel().selectPrevious();
|
||||
e.consume();
|
||||
}
|
||||
});
|
||||
startColumn.setCellFactory(TextFieldTableCell.forTableColumn(new TimeStringConverter(timeFormatter)));
|
||||
startColumn.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue() == null ? null : param.getValue().start()));
|
||||
startColumn.setOnEditCommit(e -> {
|
||||
final var subtitle = e.getRowValue();
|
||||
subtitle.setStart(e.getNewValue());
|
||||
subtitlesTable.refresh();
|
||||
subtitlesTable.requestFocus();
|
||||
});
|
||||
endColumn.setCellFactory(TextFieldTableCell.forTableColumn(new TimeStringConverter(timeFormatter)));
|
||||
endColumn.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue() == null ? null : param.getValue().end()));
|
||||
endColumn.setOnEditCommit(e -> {
|
||||
final var subtitle = e.getRowValue();
|
||||
subtitle.setEnd(e.getNewValue());
|
||||
subtitlesTable.refresh();
|
||||
subtitlesTable.requestFocus();
|
||||
});
|
||||
textColumn.setCellFactory(TextFieldTableCell.forTableColumn());
|
||||
textColumn.setCellValueFactory(param -> new SimpleStringProperty(param.getValue() == null ? null : param.getValue().content()));
|
||||
textColumn.setOnEditCommit(e -> {
|
||||
final var subtitle = e.getRowValue();
|
||||
subtitle.setContent(e.getNewValue());
|
||||
subtitlesTable.refresh();
|
||||
subtitlesTable.requestFocus();
|
||||
});
|
||||
|
||||
subtitlesTable.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> model.selectedSubtitleProperty().set(newValue));
|
||||
model.selectedSubtitleProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue != null) {
|
||||
mediaController.seek(newValue.start());
|
||||
}
|
||||
});
|
||||
progressLabel.textProperty().bind(Bindings.createStringBinding(() -> resources.getString("work.status." + model.status().name().toLowerCase() + ".label"), model.statusProperty()));
|
||||
progressLabel.visibleProperty().bind(model.statusProperty().isNotEqualTo(WorkStatus.IDLE));
|
||||
progressBar.visibleProperty().bind(model.statusProperty().isNotEqualTo(WorkStatus.IDLE));
|
||||
progressDetailLabel.visibleProperty().bind(model.statusProperty().isNotEqualTo(WorkStatus.IDLE));
|
||||
progressLabel.managedProperty().bind(model.statusProperty().isNotEqualTo(WorkStatus.IDLE));
|
||||
progressBar.managedProperty().bind(model.statusProperty().isNotEqualTo(WorkStatus.IDLE));
|
||||
progressDetailLabel.managedProperty().bind(model.statusProperty().isNotEqualTo(WorkStatus.IDLE));
|
||||
progressBar.progressProperty().bindBidirectional(model.progressProperty());
|
||||
binder.createBindings();
|
||||
|
||||
subtitleExtractor.addListener(this);
|
||||
}
|
||||
|
||||
private void editFocusedCell() {
|
||||
final var focusedCell = subtitlesTable.getFocusModel().getFocusedCell();
|
||||
if (focusedCell != null) {
|
||||
subtitlesTable.edit(focusedCell.getRow(), focusedCell.getTableColumn());
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void fileButtonPressed() {
|
||||
final var filePicker = new FileChooser();
|
||||
final var extensionFilter = new FileChooser.ExtensionFilter("All supported", VIDEO_EXTENSIONS);
|
||||
filePicker.getExtensionFilters().add(extensionFilter);
|
||||
filePicker.setSelectedExtensionFilter(extensionFilter);
|
||||
final var file = filePicker.showOpenDialog(window());
|
||||
if (file != null) {
|
||||
loadVideo(file.toPath());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void extractSubtitles() {
|
||||
if (model.video() != null) {
|
||||
model.setStatus(WorkStatus.EXTRACTING);
|
||||
CompletableFuture.supplyAsync(this::extractAsync).whenCompleteAsync(this::manageExtractResult, Platform::runLater);
|
||||
}
|
||||
}
|
||||
|
||||
private SubtitleCollection extractAsync() {
|
||||
try {
|
||||
return subtitleExtractor.extract(model.video(), model.videoLanguage(), model.extractionModel());
|
||||
} catch (final ExtractException e) {
|
||||
throw new CompletionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void manageExtractResult(final SubtitleCollection newCollection, final Throwable t) {
|
||||
if (t == null) {
|
||||
loadCollection(newCollection);
|
||||
} else {
|
||||
logger.error("Error extracting subtitles", t);
|
||||
showErrorDialog(resources.getString("work.extract.error.title"), MessageFormat.format(resources.getString("work.extract.error.label"), t.getMessage()));
|
||||
}
|
||||
model.setStatus(WorkStatus.IDLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
showErrorDialog(resources.getString("work.load.video.error.title"), MessageFormat.format(resources.getString("work.load.video.error.label"), e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveSubtitles(final Path file) {
|
||||
final var fileName = file.getFileName().toString();
|
||||
final var converter = subtitleConvertersMap.get(fileName.substring(fileName.lastIndexOf('.') + 1));
|
||||
if (converter == null) {
|
||||
logger.warn("No converter for {}", file);
|
||||
showErrorDialog(resources.getString("work.save.subtitles.missing.converter.title"), MessageFormat.format(resources.getString("work.save.subtitles.missing.converter.label"), file.getFileName()));
|
||||
} else {
|
||||
final var string = converter.format(model.subtitleCollection());
|
||||
try {
|
||||
Files.writeString(file, string);
|
||||
} catch (final IOException e) {
|
||||
logger.error("Error saving subtitles {}", file, e);
|
||||
showErrorDialog(resources.getString("work.save.subtitles.error.title"), MessageFormat.format(resources.getString("work.save.subtitles.error.label"), e.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadSubtitles(final Path file) {
|
||||
final var fileName = file.getFileName().toString();
|
||||
final var parser = subtitleConvertersMap.get(fileName.substring(fileName.lastIndexOf('.') + 1));
|
||||
if (parser != null) {
|
||||
try {
|
||||
final var collection = parser.parse(file);
|
||||
loadCollection(collection);
|
||||
} catch (final ParseException e) {
|
||||
logger.error("Error loading subtitles {}", file, e);
|
||||
showErrorDialog(resources.getString("work.load.subtitles.error.title"), MessageFormat.format(resources.getString("work.load.subtitles.error.label"), e.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadCollection(final SubtitleCollection collection) {
|
||||
model.subtitles().setAll(collection.subtitles().stream().map(ObservableSubtitleImpl::new).toList());
|
||||
model.originalSubtitles().clear();
|
||||
model.originalSubtitles().addAll(collection.subtitles().stream().map(ObservableSubtitleImpl::new).toList());
|
||||
model.videoLanguageProperty().set(collection.language());
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void exportSoftPressed() {
|
||||
final var filePicker = new FileChooser();
|
||||
final var file = filePicker.showSaveDialog(window());
|
||||
if (file != null) {
|
||||
final var baseCollection = model.subtitleCollection();
|
||||
final var translations = model.translations();
|
||||
model.setStatus(WorkStatus.TRANSLATING);
|
||||
CompletableFuture.supplyAsync(() -> Stream.concat(Stream.of(baseCollection), translations.stream().map(l -> translator.translate(baseCollection, l))).toList())
|
||||
.thenApplyAsync(c -> {
|
||||
model.setStatus(WorkStatus.EXPORTING);
|
||||
return c;
|
||||
}, Platform::runLater)
|
||||
.thenAcceptAsync(collections -> {
|
||||
try {
|
||||
videoConverter.addSoftSubtitles(model.video(), collections);
|
||||
} catch (final IOException e) {
|
||||
throw new CompletionException(e);
|
||||
}
|
||||
}).whenCompleteAsync((v, t) -> {
|
||||
if (t != null) {
|
||||
logger.error("Error exporting subtitles", t);
|
||||
showErrorDialog(resources.getString("work.export.error.title"), MessageFormat.format(resources.getString("work.export.error.label"), t.getMessage()));
|
||||
}
|
||||
model.setStatus(WorkStatus.IDLE);
|
||||
}, Platform::runLater);
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void exportHardPressed() {
|
||||
final var filePicker = new FileChooser();
|
||||
final var file = filePicker.showSaveDialog(window());
|
||||
if (file != null) {
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
videoConverter.addHardSubtitles(model.video(), model.subtitleCollection());
|
||||
} catch (final IOException e) {
|
||||
throw new CompletionException(e);
|
||||
}
|
||||
}).whenCompleteAsync((v, t) -> {
|
||||
if (t != null) {
|
||||
logger.error("Error exporting subtitles", t);
|
||||
showErrorDialog(resources.getString("work.export.error.title"), MessageFormat.format(resources.getString("work.export.error.label"), t.getMessage()));
|
||||
}
|
||||
model.setStatus(WorkStatus.IDLE);
|
||||
}, Platform::runLater);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@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());
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void addSubtitlePressed() {
|
||||
model.subtitles().add(new ObservableSubtitleImpl("Enter text here..."));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void listen(final ExtractEvent event) {
|
||||
Platform.runLater(() -> {
|
||||
model.setProgress(event.progress());
|
||||
progressDetailLabel.setText(event.message());
|
||||
});
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void loadSubtitlesPressed() {
|
||||
final var filePicker = new FileChooser();
|
||||
final var extensionFilter = new FileChooser.ExtensionFilter("All supported", subtitleExtensions);
|
||||
filePicker.getExtensionFilters().add(extensionFilter);
|
||||
filePicker.setSelectedExtensionFilter(extensionFilter);
|
||||
final var file = filePicker.showOpenDialog(window());
|
||||
loadSubtitles(file.toPath());
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void saveSubtitlesPressed() {
|
||||
final var filePicker = new FileChooser();
|
||||
final var extensionFilter = new FileChooser.ExtensionFilter("All supported", subtitleExtensions);
|
||||
filePicker.getExtensionFilters().add(extensionFilter);
|
||||
filePicker.setSelectedExtensionFilter(extensionFilter);
|
||||
final var file = filePicker.showSaveDialog(window());
|
||||
if (file != null) {
|
||||
saveSubtitles(file.toPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
package com.github.gtache.autosubtitle.gui.fx;
|
||||
|
||||
import com.github.gtache.autosubtitle.Language;
|
||||
import com.github.gtache.autosubtitle.Video;
|
||||
import com.github.gtache.autosubtitle.gui.WorkModel;
|
||||
import com.github.gtache.autosubtitle.gui.WorkStatus;
|
||||
import com.github.gtache.autosubtitle.subtitle.EditableSubtitle;
|
||||
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.ExtractionModel;
|
||||
import com.github.gtache.autosubtitle.subtitle.impl.SubtitleCollectionImpl;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* FX implementation of {@link WorkModel}
|
||||
*/
|
||||
@Singleton
|
||||
public class FXWorkModel implements WorkModel {
|
||||
|
||||
private final ObjectProperty<Video> video;
|
||||
private final ReadOnlyObjectWrapper<SubtitleCollection> subtitleCollection;
|
||||
private final ObservableList<EditableSubtitle> subtitles;
|
||||
private final List<EditableSubtitle> originalSubtitles;
|
||||
private final ObjectProperty<EditableSubtitle> subtitle;
|
||||
private final ObservableList<Language> availableVideoLanguages;
|
||||
private final ObservableList<Language> availableTranslationLanguages;
|
||||
private final ObjectProperty<ExtractionModel> extractionModel;
|
||||
private final ObjectProperty<Language> videoLanguage;
|
||||
private final ObservableList<Language> translations;
|
||||
private final ReadOnlyStringWrapper text;
|
||||
private final ObjectProperty<WorkStatus> workStatus;
|
||||
private final DoubleProperty progress;
|
||||
|
||||
@Inject
|
||||
FXWorkModel() {
|
||||
this.video = new SimpleObjectProperty<>();
|
||||
this.subtitleCollection = new ReadOnlyObjectWrapper<>();
|
||||
this.subtitles = FXCollections.observableArrayList();
|
||||
this.originalSubtitles = new ArrayList<>();
|
||||
this.subtitle = new SimpleObjectProperty<>();
|
||||
this.availableVideoLanguages =
|
||||
FXCollections.unmodifiableObservableList(FXCollections.observableArrayList(Arrays.stream(Language.values())
|
||||
.sorted((o1, o2) -> {
|
||||
if (o1 == Language.AUTO) {
|
||||
return -1;
|
||||
} else if (o2 == Language.AUTO) {
|
||||
return 1;
|
||||
} else {
|
||||
return o1.compareTo(o2);
|
||||
}
|
||||
}).toList()));
|
||||
this.availableTranslationLanguages =
|
||||
FXCollections.observableArrayList(Arrays.stream(Language.values()).filter(l -> l != Language.AUTO).toList());
|
||||
this.videoLanguage = new SimpleObjectProperty<>(Language.AUTO);
|
||||
this.translations = FXCollections.observableArrayList();
|
||||
this.text = new ReadOnlyStringWrapper("");
|
||||
this.workStatus = new SimpleObjectProperty<>(WorkStatus.IDLE);
|
||||
this.extractionModel = new SimpleObjectProperty<>();
|
||||
this.progress = new SimpleDoubleProperty(-1);
|
||||
text.bind(Bindings.createStringBinding(() ->
|
||||
subtitles.stream().map(EditableSubtitle::content).collect(Collectors.joining(" ")),
|
||||
subtitles));
|
||||
subtitleCollection.bind(Bindings.createObjectBinding(() -> new SubtitleCollectionImpl(text(), subtitles, videoLanguage())));
|
||||
videoLanguage.addListener((observable, oldValue, newValue) -> {
|
||||
FXCollections.observableArrayList(Arrays.stream(Language.values()).filter(l -> l != Language.AUTO && l != newValue).sorted(Comparator.naturalOrder()).toList());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Video video() {
|
||||
return video.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtractionModel extractionModel() {
|
||||
return extractionModel.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExtractionModel(final ExtractionModel model) {
|
||||
extractionModel.set(model);
|
||||
}
|
||||
|
||||
ObjectProperty<ExtractionModel> extractionModelProperty() {
|
||||
return extractionModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubtitleCollection subtitleCollection() {
|
||||
return subtitleCollection.get();
|
||||
}
|
||||
|
||||
ReadOnlyObjectProperty<SubtitleCollection> subtitleCollectionProperty() {
|
||||
return subtitleCollection.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
ObjectProperty<Video> videoProperty() {
|
||||
return video;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObservableList<EditableSubtitle> subtitles() {
|
||||
return subtitles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EditableSubtitle> originalSubtitles() {
|
||||
return originalSubtitles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String text() {
|
||||
return text.get();
|
||||
}
|
||||
|
||||
ReadOnlyStringProperty textProperty() {
|
||||
return text.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EditableSubtitle selectedSubtitle() {
|
||||
return subtitle.get();
|
||||
}
|
||||
|
||||
ObjectProperty<EditableSubtitle> selectedSubtitleProperty() {
|
||||
return subtitle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObservableList<Language> availableVideoLanguages() {
|
||||
return availableVideoLanguages;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObservableList<Language> availableTranslationsLanguage() {
|
||||
return availableTranslationLanguages;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Language videoLanguage() {
|
||||
return videoLanguage.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVideoLanguage(final Language language) {
|
||||
videoLanguage.set(language);
|
||||
}
|
||||
|
||||
ObjectProperty<Language> videoLanguageProperty() {
|
||||
return videoLanguage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObservableList<Language> translations() {
|
||||
return translations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorkStatus status() {
|
||||
return workStatus.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStatus(final WorkStatus status) {
|
||||
workStatus.set(status);
|
||||
}
|
||||
|
||||
ObjectProperty<WorkStatus> statusProperty() {
|
||||
return workStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double progress() {
|
||||
return progress.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProgress(final double progress) {
|
||||
this.progress.set(progress);
|
||||
}
|
||||
|
||||
DoubleProperty progressProperty() {
|
||||
return progress;
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package com.github.gtache.autosubtitle.gui.fx;
|
||||
|
||||
import com.github.gtache.autosubtitle.Language;
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
class LanguageStringConverter extends StringConverter<Language> {
|
||||
@Override
|
||||
public String toString(final Language object) {
|
||||
return object == null ? "" : object.englishName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Language fromString(final String string) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.github.gtache.autosubtitle.gui.fx;
|
||||
|
||||
import com.github.gtache.autosubtitle.gui.TimeFormatter;
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
class TimeStringConverter extends StringConverter<Long> {
|
||||
|
||||
private final TimeFormatter timeFormatter;
|
||||
|
||||
TimeStringConverter(final TimeFormatter timeFormatter) {
|
||||
this.timeFormatter = Objects.requireNonNull(timeFormatter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(final Long object) {
|
||||
return object == null ? "" : timeFormatter.format(object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long fromString(final String string) {
|
||||
return string == null ? 0L : timeFormatter.parse(string);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.github.gtache.autosubtitle.modules.gui.fx;
|
||||
|
||||
import com.github.gtache.autosubtitle.gui.TimeFormatter;
|
||||
import com.github.gtache.autosubtitle.gui.fx.ColonTimeFormatter;
|
||||
import com.github.gtache.autosubtitle.gui.fx.FXMainController;
|
||||
import com.github.gtache.autosubtitle.gui.fx.FXMediaController;
|
||||
import com.github.gtache.autosubtitle.gui.fx.FXParametersController;
|
||||
import com.github.gtache.autosubtitle.gui.fx.FXSetupController;
|
||||
import com.github.gtache.autosubtitle.gui.fx.FXWorkController;
|
||||
import com.github.gtache.autosubtitle.modules.gui.impl.Pause;
|
||||
import com.github.gtache.autosubtitle.modules.gui.impl.Play;
|
||||
import dagger.Binds;
|
||||
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;
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
/**
|
||||
* Dagger module for FX
|
||||
*/
|
||||
@Module
|
||||
public abstract class FXModule {
|
||||
|
||||
@Binds
|
||||
abstract TimeFormatter bindsTimeFormatter(final ColonTimeFormatter formatter);
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static FXMLLoader providesFXMLLoader(final FXMainController mainController, final FXSetupController setupController, final FXParametersController parametersController,
|
||||
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 if (c == FXParametersController.class) {
|
||||
return parametersController;
|
||||
} 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));
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static Preferences providesPreferences() {
|
||||
return Preferences.userNodeForPackage(FXParametersController.class);
|
||||
}
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
package com.github.gtache.autosubtitle.subtitle.gui.fx;
|
||||
|
||||
import com.github.gtache.autosubtitle.subtitle.Bounds;
|
||||
import com.github.gtache.autosubtitle.subtitle.EditableSubtitle;
|
||||
import com.github.gtache.autosubtitle.subtitle.Font;
|
||||
import com.github.gtache.autosubtitle.subtitle.Subtitle;
|
||||
import com.github.gtache.autosubtitle.subtitle.impl.BoundsImpl;
|
||||
import javafx.beans.property.LongProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleLongProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
|
||||
/**
|
||||
* FX implementation of {@link EditableSubtitle}
|
||||
*/
|
||||
public class ObservableSubtitleImpl implements EditableSubtitle {
|
||||
|
||||
private final StringProperty content;
|
||||
private final LongProperty start;
|
||||
private final LongProperty end;
|
||||
private final ObjectProperty<Font> font;
|
||||
private final ObjectProperty<Bounds> location;
|
||||
|
||||
public ObservableSubtitleImpl() {
|
||||
this("");
|
||||
}
|
||||
|
||||
public ObservableSubtitleImpl(final String content) {
|
||||
this.content = new SimpleStringProperty(content);
|
||||
this.start = new SimpleLongProperty(0);
|
||||
this.end = new SimpleLongProperty(0);
|
||||
this.font = new SimpleObjectProperty<>();
|
||||
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
|
||||
public String content() {
|
||||
return content.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContent(final String content) {
|
||||
this.content.set(content);
|
||||
}
|
||||
|
||||
public StringProperty contentProperty() {
|
||||
return content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long start() {
|
||||
return start.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStart(final long start) {
|
||||
this.start.set(start);
|
||||
}
|
||||
|
||||
public LongProperty startProperty() {
|
||||
return start;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long end() {
|
||||
return end.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnd(final long end) {
|
||||
this.end.set(end);
|
||||
}
|
||||
|
||||
public LongProperty endProperty() {
|
||||
return end;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Font font() {
|
||||
return font.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFont(final Font font) {
|
||||
this.font.set(font);
|
||||
}
|
||||
|
||||
public ObjectProperty<Font> fontProperty() {
|
||||
return font;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bounds bounds() {
|
||||
return location.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBounds(final Bounds bounds) {
|
||||
this.location.set(bounds);
|
||||
}
|
||||
|
||||
public ObjectProperty<Bounds> locationProperty() {
|
||||
return location;
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package com.github.gtache.autosubtitle.subtitle.gui.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();
|
||||
setText(subtitle.content());
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* 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.core;
|
||||
requires transitive javafx.controls;
|
||||
requires transitive javafx.media;
|
||||
requires transitive javafx.fxml;
|
||||
requires org.controlsfx.controls;
|
||||
requires org.apache.logging.log4j;
|
||||
requires java.desktop;
|
||||
requires transitive java.prefs;
|
||||
|
||||
exports com.github.gtache.autosubtitle.gui.fx;
|
||||
exports com.github.gtache.autosubtitle.modules.gui.fx;
|
||||
opens com.github.gtache.autosubtitle.gui.fx to javafx.fxml;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.control.Tab?>
|
||||
<?import javafx.scene.control.TabPane?>
|
||||
<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">
|
||||
<tabs>
|
||||
<Tab closable="false" text="%main.tab.work.label">
|
||||
<content>
|
||||
<fx:include source="workView.fxml"/>
|
||||
</content>
|
||||
</Tab>
|
||||
<Tab closable="false" text="%main.tab.setup.label">
|
||||
<content>
|
||||
<fx:include source="setupView.fxml"/>
|
||||
</content>
|
||||
</Tab>
|
||||
<Tab closable="false" text="%main.tab.parameters.label">
|
||||
<content>
|
||||
<fx:include source="parametersView.fxml"/>
|
||||
</content>
|
||||
</Tab>
|
||||
</tabs>
|
||||
</TabPane>
|
||||
@@ -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" value="100"/>
|
||||
<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>
|
||||
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.TextField?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import org.controlsfx.control.PrefixSelectionComboBox?>
|
||||
<GridPane hgap="10.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" vgap="10.0"
|
||||
xmlns="http://javafx.com/javafx/22" xmlns:fx="http://javafx.com/fxml/1"
|
||||
fx:controller="com.github.gtache.autosubtitle.gui.fx.FXParametersController">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="SOMETIMES"/>
|
||||
<ColumnConstraints hgrow="SOMETIMES"/>
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
<RowConstraints vgrow="SOMETIMES"/>
|
||||
<RowConstraints vgrow="SOMETIMES"/>
|
||||
<RowConstraints vgrow="SOMETIMES"/>
|
||||
<RowConstraints vgrow="SOMETIMES"/>
|
||||
<RowConstraints vgrow="SOMETIMES"/>
|
||||
</rowConstraints>
|
||||
<children>
|
||||
<Label text="%parameters.extraction.model.label"/>
|
||||
<PrefixSelectionComboBox fx:id="extractionModelCombobox" GridPane.columnIndex="1"/>
|
||||
<PrefixSelectionComboBox fx:id="extractionOutputFormat" GridPane.columnIndex="1" GridPane.rowIndex="1"/>
|
||||
<Label text="%parameters.subtitles.output.format" GridPane.rowIndex="1"/>
|
||||
<Label text="%parameters.subtitles.font.size" GridPane.rowIndex="3"/>
|
||||
<Label text="%parameters.subtitles.font.family" GridPane.rowIndex="2"/>
|
||||
<PrefixSelectionComboBox fx:id="fontFamilyCombobox" GridPane.columnIndex="1" GridPane.rowIndex="2"/>
|
||||
<TextField fx:id="fontSizeField" GridPane.columnIndex="1" GridPane.rowIndex="3"/>
|
||||
<Button mnemonicParsing="false" onAction="#savePressed" text="%parameters.button.save.label"
|
||||
GridPane.columnIndex="1" GridPane.rowIndex="4"/>
|
||||
<Button mnemonicParsing="false" onAction="#resetPressed" text="%parameters.button.reset.label"
|
||||
GridPane.rowIndex="4"/>
|
||||
</children>
|
||||
<padding>
|
||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
|
||||
</padding>
|
||||
</GridPane>
|
||||
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.MenuButton?>
|
||||
<?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 hgrow="SOMETIMES" />
|
||||
<ColumnConstraints hgrow="SOMETIMES" />
|
||||
<ColumnConstraints hgrow="SOMETIMES" />
|
||||
<ColumnConstraints hgrow="ALWAYS" />
|
||||
<ColumnConstraints hgrow="SOMETIMES" />
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints vgrow="SOMETIMES" />
|
||||
<RowConstraints vgrow="SOMETIMES" />
|
||||
<RowConstraints vgrow="SOMETIMES" />
|
||||
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="ALWAYS" />
|
||||
</rowConstraints>
|
||||
<children>
|
||||
<Label fx:id="converterNameLabel" text="Label" GridPane.rowIndex="1" />
|
||||
<Label fx:id="converterStatusLabel" text="Label" GridPane.columnIndex="1" GridPane.rowIndex="1" />
|
||||
<Label fx:id="extractorNameLabel" text="Label" GridPane.rowIndex="2" />
|
||||
<Label fx:id="extractorStatusLabel" text="Label" GridPane.columnIndex="1" GridPane.rowIndex="2" />
|
||||
<Label fx:id="translatorNameLabel" text="Label" GridPane.rowIndex="3" />
|
||||
<Label fx:id="translatorStatusLabel" text="Label" GridPane.columnIndex="1" GridPane.rowIndex="3" />
|
||||
<MenuButton fx:id="converterButton" mnemonicParsing="false" text="%setup.menu.label" GridPane.columnIndex="2" GridPane.rowIndex="1" />
|
||||
<MenuButton fx:id="extractorButton" mnemonicParsing="false" text="%setup.menu.label" GridPane.columnIndex="2" GridPane.rowIndex="2" />
|
||||
<MenuButton fx:id="translatorButton" mnemonicParsing="false" text="%setup.menu.label" GridPane.columnIndex="2" GridPane.rowIndex="3" />
|
||||
<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>
|
||||
<padding>
|
||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||
</padding>
|
||||
</GridPane>
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import org.controlsfx.control.CheckComboBox?>
|
||||
<?import org.controlsfx.control.PrefixSelectionComboBox?>
|
||||
<GridPane hgap="10.0" vgap="10.0" xmlns="http://javafx.com/javafx/22" xmlns:fx="http://javafx.com/fxml/1"
|
||||
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 vgrow="NEVER"/>
|
||||
</rowConstraints>
|
||||
<children>
|
||||
<TextField fx:id="fileField" editable="false" GridPane.columnIndex="1"/>
|
||||
<Button mnemonicParsing="false" onAction="#fileButtonPressed" text="%work.button.file.label"
|
||||
GridPane.columnIndex="2"/>
|
||||
<HBox spacing="10.0" GridPane.columnIndex="1" GridPane.rowIndex="2">
|
||||
<children>
|
||||
<Button fx:id="loadSubtitlesButton" mnemonicParsing="false" onAction="#loadSubtitlesPressed"
|
||||
text="%work.button.load.label"/>
|
||||
<Button fx:id="extractButton" mnemonicParsing="false" onAction="#extractPressed"
|
||||
text="%work.button.extract.label"/>
|
||||
</children>
|
||||
</HBox>
|
||||
<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" editable="true" GridPane.rowIndex="1">
|
||||
<columns>
|
||||
<TableColumn fx:id="startColumn" prefWidth="50.0" sortable="false"
|
||||
text="%work.table.column.from.label"/>
|
||||
<TableColumn fx:id="endColumn" prefWidth="50.0" sortable="false" text="%work.table.column.to.label"/>
|
||||
<TableColumn fx:id="textColumn" prefWidth="75.0" sortable="false" text="%work.table.column.text.label"/>
|
||||
</columns>
|
||||
<columnResizePolicy>
|
||||
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
|
||||
</columnResizePolicy>
|
||||
</TableView>
|
||||
<HBox spacing="10.0" GridPane.rowIndex="2">
|
||||
<children>
|
||||
<Button fx:id="resetButton" mnemonicParsing="false" onAction="#resetButtonPressed"
|
||||
text="%work.button.reset.label"/>
|
||||
<Button fx:id="saveSubtitlesButton" mnemonicParsing="false" onAction="#saveSubtitlesPressed"
|
||||
text="%work.button.subtitles.save.label"/>
|
||||
<Button fx:id="addSubtitleButton" mnemonicParsing="false" onAction="#addSubtitlePressed" text="+"/>
|
||||
</children>
|
||||
<GridPane.margin>
|
||||
<Insets/>
|
||||
</GridPane.margin>
|
||||
</HBox>
|
||||
<fx:include fx:id="media" source="mediaView.fxml" GridPane.columnIndex="1" GridPane.columnSpan="2147483647"
|
||||
GridPane.rowIndex="1"/>
|
||||
<HBox alignment="CENTER_LEFT" spacing="10.0">
|
||||
<children>
|
||||
<Label text="%work.language.label"/>
|
||||
<PrefixSelectionComboBox fx:id="languageCombobox"/>
|
||||
<Label text="%work.translate.label"/>
|
||||
<CheckComboBox fx:id="translationsCombobox"/>
|
||||
</children>
|
||||
</HBox>
|
||||
<Label fx:id="progressLabel" GridPane.columnIndex="1" GridPane.rowIndex="3"/>
|
||||
<HBox spacing="10.0" GridPane.columnIndex="2" GridPane.rowIndex="3">
|
||||
<children>
|
||||
<Label fx:id="progressDetailLabel"/>
|
||||
<ProgressBar fx:id="progressBar" maxWidth="1.7976931348623157E308" HBox.hgrow="ALWAYS"/>
|
||||
</children>
|
||||
</HBox>
|
||||
</children>
|
||||
<padding>
|
||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
|
||||
</padding>
|
||||
</GridPane>
|
||||
+26
-12
@@ -8,19 +8,33 @@
|
||||
<artifactId>autosubtitle</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>api</module>
|
||||
<module>core</module>
|
||||
<module>fx</module>
|
||||
<module>run</module>
|
||||
</modules>
|
||||
<artifactId>autosubtitle-gui</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.github.gtache.autosubtitle</groupId>
|
||||
<artifactId>autosubtitle-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.dagger</groupId>
|
||||
<artifactId>dagger</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.github.gtache.autosubtitle</groupId>
|
||||
<artifactId>autosubtitle-gui-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.gtache.autosubtitle</groupId>
|
||||
<artifactId>autosubtitle-gui-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.gtache.autosubtitle</groupId>
|
||||
<artifactId>autosubtitle-fx</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
</project>
|
||||
@@ -0,0 +1,37 @@
|
||||
<?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-gui</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>autosubtitle-gui-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>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>info.picocli</groupId>
|
||||
<artifactId>picocli</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package com.github.gtache.autosubtitle.modules.run;
|
||||
|
||||
import com.github.gtache.autosubtitle.Language;
|
||||
import com.github.gtache.autosubtitle.Translator;
|
||||
import com.github.gtache.autosubtitle.modules.setup.impl.TranslatorSetup;
|
||||
import com.github.gtache.autosubtitle.setup.SetupManager;
|
||||
import com.github.gtache.autosubtitle.subtitle.Subtitle;
|
||||
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/**
|
||||
* Module for missing components
|
||||
*/
|
||||
@Module
|
||||
public abstract class MissingComponentsModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static Translator providesTranslator() {
|
||||
return new Translator() {
|
||||
@Override
|
||||
public Language getLanguage(final String text) {
|
||||
return Language.getDefault();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String translate(final String text, final Language to) {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Subtitle translate(final Subtitle subtitle, final Language to) {
|
||||
return subtitle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubtitleCollection translate(final SubtitleCollection collection, final Language to) {
|
||||
return collection;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@TranslatorSetup
|
||||
static SetupManager providesTranslatorSetupManager() {
|
||||
return new NoOpSetupManager();
|
||||
}
|
||||
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package com.github.gtache.autosubtitle.modules.run;
|
||||
|
||||
import com.github.gtache.autosubtitle.setup.SetupException;
|
||||
import com.github.gtache.autosubtitle.setup.SetupListener;
|
||||
import com.github.gtache.autosubtitle.setup.SetupManager;
|
||||
import com.github.gtache.autosubtitle.setup.SetupStatus;
|
||||
|
||||
class NoOpSetupManager implements SetupManager {
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "NoOpSetupManager";
|
||||
}
|
||||
|
||||
@Override
|
||||
public SetupStatus status() {
|
||||
return SetupStatus.NOT_INSTALLED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInstalled() throws SetupException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void install() throws SetupException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uninstall() throws SetupException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reinstall() throws SetupException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUpdateAvailable() throws SetupException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() throws SetupException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(final SetupListener listener) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(final SetupListener listener) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListeners() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.github.gtache.autosubtitle.modules.run;
|
||||
|
||||
import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegModule;
|
||||
import com.github.gtache.autosubtitle.modules.gui.fx.FXModule;
|
||||
import com.github.gtache.autosubtitle.modules.gui.impl.GuiCoreModule;
|
||||
import com.github.gtache.autosubtitle.modules.impl.CoreModule;
|
||||
import com.github.gtache.autosubtitle.modules.setup.ffmpeg.FFmpegSetupModule;
|
||||
import com.github.gtache.autosubtitle.modules.setup.whisper.WhisperSetupModule;
|
||||
import com.github.gtache.autosubtitle.modules.subtitle.impl.SubtitleModule;
|
||||
import com.github.gtache.autosubtitle.modules.whisper.WhisperModule;
|
||||
import dagger.Component;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/**
|
||||
* Main component
|
||||
*/
|
||||
@Singleton
|
||||
@Component(modules = {CoreModule.class, GuiCoreModule.class, FXModule.class, FFmpegModule.class, FFmpegSetupModule.class,
|
||||
SubtitleModule.class, WhisperModule.class, WhisperSetupModule.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);
|
||||
}
|
||||
}
|
||||
@@ -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,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="INFO">
|
||||
<Appenders>
|
||||
<Console name="Console" target="SYSTEM_OUT">
|
||||
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
|
||||
</Console>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Root level="info">
|
||||
<AppenderRef ref="Console"/>
|
||||
</Root>
|
||||
</Loggers>
|
||||
</Configuration>
|
||||
@@ -1,26 +0,0 @@
|
||||
import com.github.gtache.autosubtitle.gui.spi.MainBundleProvider;
|
||||
import com.github.gtache.autosubtitle.gui.spi.MainBundleProviderImpl;
|
||||
import com.github.gtache.autosubtitle.gui.spi.ParametersBundleProvider;
|
||||
import com.github.gtache.autosubtitle.gui.spi.ParametersBundleProviderImpl;
|
||||
import com.github.gtache.autosubtitle.gui.spi.SetupBundleProvider;
|
||||
import com.github.gtache.autosubtitle.gui.spi.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 ParametersBundleProvider with ParametersBundleProviderImpl;
|
||||
provides SetupBundleProvider with SetupBundleProviderImpl;
|
||||
provides WorkBundleProvider with WorkBundleProviderImpl;
|
||||
}
|
||||
Reference in new issue
Block a user