Initial commit

This commit is contained in:
Guillaume Tâche
2024-07-27 17:45:46 +02:00
commit 75829244b9
66 changed files with 2906 additions and 0 deletions

39
fx/pom.xml Normal file
View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.github.gtache.autosubtitle</groupId>
<artifactId>autosubtitle</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>autosubtitle-fx</artifactId>
<dependencies>
<dependency>
<groupId>com.github.gtache.autosubtitle</groupId>
<artifactId>autosubtitle-gui</artifactId>
</dependency>
<dependency>
<groupId>com.github.gtache.autosubtitle</groupId>
<artifactId>autosubtitle-core</artifactId>
</dependency>
<dependency>
<groupId>com.google.dagger</groupId>
<artifactId>dagger</artifactId>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-media</artifactId>
<version>22.0.1</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>22.0.1</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,146 @@
package com.github.gtache.autosubtitle.gui.fx;
import com.github.gtache.autosubtitle.subtitle.EditableSubtitle;
import com.github.gtache.autosubtitle.subtitle.Subtitle;
import com.github.gtache.autosubtitle.subtitle.SubtitleExtractor;
import com.github.gtache.autosubtitle.VideoConverter;
import com.github.gtache.autosubtitle.gui.MainController;
import com.github.gtache.autosubtitle.impl.FileVideoImpl;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.layout.StackPane;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.media.MediaView;
import javafx.stage.FileChooser;
import javafx.stage.Window;
import javax.inject.Inject;
import java.nio.file.Path;
import java.time.Duration;
import java.time.LocalTime;
import java.util.Comparator;
import static java.util.Objects.requireNonNull;
/**
* FX implementation of {@link MainController}
*/
public class FXMainController implements MainController {
@FXML
private MediaView videoView;
@FXML
private TextField fileField;
@FXML
private Button extractButton;
@FXML
private Button resetButton;
@FXML
private Button exportButton;
@FXML
private TableView<EditableSubtitle> subtitlesTable;
@FXML
private TableColumn<EditableSubtitle, LocalTime> startColumn;
@FXML
private TableColumn<EditableSubtitle, LocalTime> endColumn;
@FXML
private TableColumn<EditableSubtitle, String> textColumn;
@FXML
private StackPane stackPane;
private final FXMainModel model;
private final SubtitleExtractor subtitleExtractor;
private final VideoConverter videoConverter;
@Inject
FXMainController(final FXMainModel model, final SubtitleExtractor subtitleExtractor, final VideoConverter videoConverter) {
this.model = requireNonNull(model);
this.subtitleExtractor = requireNonNull(subtitleExtractor);
this.videoConverter = requireNonNull(videoConverter);
}
@FXML
private void initialize() {
extractButton.disableProperty().bind(model.videoProperty().isNull());
resetButton.disableProperty().bind(Bindings.isEmpty(model.subtitles()));
exportButton.disableProperty().bind(Bindings.isEmpty(model.subtitles()));
subtitlesTable.setItems(model.subtitles());
startColumn.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue() == null ? null : param.getValue().start()));
endColumn.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue() == null ? null : param.getValue().end()));
textColumn.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue() == null ? "" : param.getValue().content()));
model.selectedSubtitleProperty().addListener(new ChangeListener<EditableSubtitle>() {
@Override
public void changed(final ObservableValue<? extends EditableSubtitle> observable, final EditableSubtitle oldValue, final EditableSubtitle newValue) {
if (newValue != null) {
videoView.getMediaPlayer().seek(Duration.of(newValue.start().to));
}
}
});
}
@FXML
private void fileButtonPressed() {
final var filePicker = new FileChooser();
final var file = filePicker.showOpenDialog(window());
if (file != null) {
loadVideo(file.toPath());
}
}
@Override
public void extractSubtitles() {
if (model.video() != null) {
final var subtitles = subtitleExtractor.extract(model.video());
model.subtitles().setAll(subtitles.stream().sorted(Comparator.comparing(Subtitle::start)).toList());
}
}
@Override
public void loadVideo(final Path file) {
fileField.setText(file.toAbsolutePath().toString());
model.videoProperty().set(new FileVideoImpl(file));
final var media = new Media(file.toUri().toString());
final var player = new MediaPlayer(media);
videoView.getMediaPlayer().dispose();
videoView.setMediaPlayer(player);
}
@Override
public FXMainModel model() {
return model;
}
public Window window() {
return videoView.getScene().getWindow();
}
@FXML
private void extractPressed(final ActionEvent actionEvent) {
extractSubtitles();
}
@FXML
private void exportPressed(final ActionEvent actionEvent) {
final var filePicker = new FileChooser();
final var file = filePicker.showSaveDialog(window());
if (file != null) {
videoConverter.addSoftSubtitles(model.video(), model.subtitles());
}
}
@FXML
private void resetButtonPressed(final ActionEvent actionEvent) {
}
}

View File

@@ -0,0 +1,51 @@
package com.github.gtache.autosubtitle.gui.fx;
import com.github.gtache.autosubtitle.subtitle.EditableSubtitle;
import com.github.gtache.autosubtitle.Video;
import com.github.gtache.autosubtitle.gui.MainModel;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javax.inject.Inject;
/**
* FX implementation of {@link MainModel}
*/
public class FXMainModel implements MainModel {
private final ObjectProperty<Video> video;
private final ObservableList<EditableSubtitle> subtitles;
private final ObjectProperty<EditableSubtitle> subtitle;
@Inject
FXMainModel() {
video = new SimpleObjectProperty<>();
subtitles = FXCollections.observableArrayList();
subtitle = new SimpleObjectProperty<>();
}
@Override
public Video video() {
return video.get();
}
public ObjectProperty<Video> videoProperty() {
return video;
}
@Override
public ObservableList<EditableSubtitle> subtitles() {
return subtitles;
}
@Override
public EditableSubtitle selectedSubtitle() {
return subtitle.get();
}
public ObjectProperty<EditableSubtitle> selectedSubtitleProperty() {
return subtitle;
}
}

View File

@@ -0,0 +1,135 @@
package com.github.gtache.autosubtitle.gui.fx;
import com.github.gtache.autosubtitle.gui.SetupController;
import com.github.gtache.autosubtitle.setup.SetupManager;
import com.github.gtache.autosubtitle.setup.modules.impl.SubtitleExtractor;
import com.github.gtache.autosubtitle.setup.modules.impl.Translator;
import com.github.gtache.autosubtitle.setup.modules.impl.VideoConverter;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.MenuButton;
import javafx.stage.Window;
import javax.inject.Inject;
import java.util.Objects;
import java.util.ResourceBundle;
/**
* FX implementation of {@link SetupController}
*/
public class FXSetupController implements SetupController {
@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;
private final FXSetupModel model;
private final SetupManager converterManager;
private final SetupManager extractorManager;
private final SetupManager translatorManager;
@Inject
FXSetupController(final FXSetupModel model, @VideoConverter final SetupManager converterManager,
@SubtitleExtractor final SetupManager extractorManager, @Translator final SetupManager translatorManager) {
this.model = Objects.requireNonNull(model);
this.converterManager = Objects.requireNonNull(converterManager);
this.extractorManager = Objects.requireNonNull(extractorManager);
this.translatorManager = Objects.requireNonNull(translatorManager);
}
@FXML
private void initialize() {
converterNameLabel.setText(converterManager.name());
converterStatusLabel.setText(converterManager.status().toString());
extractorNameLabel.setText(extractorManager.name());
extractorStatusLabel.setText(extractorManager.status().toString());
translatorNameLabel.setText(translatorManager.name());
translatorStatusLabel.setText(translatorManager.status().toString());
}
@Override
public void installVideoConverter() {
converterManager.install();
}
@Override
public void uninstallVideoConverter() {
converterManager.uninstall();
}
@Override
public void updateVideoConverter() {
converterManager.update();
}
@Override
public void reinstallVideoConverter() {
converterManager.reinstall();
}
@Override
public void installSubtitleExtractor() {
extractorManager.install();
}
@Override
public void uninstallSubtitleExtractor() {
extractorManager.uninstall();
}
@Override
public void updateSubtitleExtractor() {
extractorManager.update();
}
@Override
public void reinstallSubtitleExtractor() {
extractorManager.reinstall();
}
@Override
public void installTranslator() {
translatorManager.install();
}
@Override
public void uninstallTranslator() {
translatorManager.uninstall();
}
@Override
public void updateTranslator() {
translatorManager.update();
}
@Override
public void reinstallTranslator() {
translatorManager.reinstall();
}
@Override
public FXSetupModel model() {
return model;
}
public Window window() {
return extractorButton.getScene().getWindow();
}
}

View File

@@ -0,0 +1,114 @@
package com.github.gtache.autosubtitle.gui.fx;
import com.github.gtache.autosubtitle.gui.SetupModel;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javax.inject.Inject;
/**
* FX implementation of {@link SetupModel}
*/
public class FXSetupModel implements SetupModel {
private final BooleanProperty subtitleExtractorInstalled;
private final BooleanProperty subtitleExtractorUpdateAvailable;
private final BooleanProperty videoConverterInstalled;
private final BooleanProperty videoConverterUpdateAvailable;
private final BooleanProperty translatorInstalled;
private final BooleanProperty translatorUpdateAvailable;
@Inject
FXSetupModel() {
this.subtitleExtractorInstalled = new SimpleBooleanProperty(false);
this.subtitleExtractorUpdateAvailable = new SimpleBooleanProperty(false);
this.videoConverterInstalled = new SimpleBooleanProperty(false);
this.videoConverterUpdateAvailable = new SimpleBooleanProperty(false);
this.translatorInstalled = new SimpleBooleanProperty(false);
this.translatorUpdateAvailable = new SimpleBooleanProperty(false);
}
@Override
public boolean isSubtitleExtractorInstalled() {
return subtitleExtractorInstalled.get();
}
@Override
public void setSubtitleExtractorInstalled(final boolean installed) {
subtitleExtractorInstalled.set(installed);
}
BooleanProperty subtitleExtractorInstalledProperty() {
return subtitleExtractorInstalled;
}
@Override
public boolean isSubtitleExtractorUpdateAvailable() {
return subtitleExtractorUpdateAvailable.get();
}
@Override
public void setSubtitleExtractorUpdateAvailable(final boolean updateAvailable) {
subtitleExtractorUpdateAvailable.set(updateAvailable);
}
BooleanProperty subtitleExtractorUpdateAvailableProperty() {
return subtitleExtractorUpdateAvailable;
}
@Override
public boolean isVideoConverterInstalled() {
return videoConverterInstalled.get();
}
@Override
public void setVideoConverterInstalled(final boolean installed) {
videoConverterInstalled.set(installed);
}
BooleanProperty videoConverterInstalledProperty() {
return videoConverterInstalled;
}
@Override
public boolean isVideoConverterUpdateAvailable() {
return videoConverterUpdateAvailable.get();
}
@Override
public void setVideoConverterUpdateAvailable(final boolean updateAvailable) {
videoConverterUpdateAvailable.set(updateAvailable);
}
BooleanProperty videoConverterUpdateAvailableProperty() {
return videoConverterUpdateAvailable;
}
@Override
public boolean isTranslatorInstalled() {
return translatorInstalled.get();
}
@Override
public void setTranslatorInstalled(final boolean installed) {
translatorInstalled.set(installed);
}
BooleanProperty translatorInstalledProperty() {
return translatorInstalled;
}
@Override
public boolean isTranslatorUpdateAvailable() {
return translatorUpdateAvailable.get();
}
@Override
public void setTranslatorUpdateAvailable(final boolean updateAvailable) {
translatorUpdateAvailable.set(updateAvailable);
}
BooleanProperty translatorUpdateAvailableProperty() {
return translatorUpdateAvailable;
}
}

View File

@@ -0,0 +1,102 @@
package com.github.gtache.autosubtitle.subtitle.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.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() {
content = new SimpleStringProperty("");
start = new SimpleLongProperty(0);
end = new SimpleLongProperty(0);
font = new SimpleObjectProperty<>();
location = new SimpleObjectProperty<>(new BoundsImpl(0, 0, 100, 12));
}
@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;
}
}

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.media.MediaView?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="1080.0" prefWidth="1920.0" xmlns="http://javafx.com/javafx/22" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.github.gtache.autosubtitle.gui.fx.FXMainController">
<children>
<GridPane hgap="10.0" layoutX="152.0" layoutY="116.0" vgap="10.0" AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="10.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" />
<ColumnConstraints hgrow="SOMETIMES" />
<ColumnConstraints hgrow="SOMETIMES" />
</columnConstraints>
<rowConstraints>
<RowConstraints vgrow="SOMETIMES" />
<RowConstraints vgrow="ALWAYS" />
<RowConstraints vgrow="SOMETIMES" />
</rowConstraints>
<children>
<TextField fx:id="fileField" editable="false" GridPane.columnIndex="1" />
<Button mnemonicParsing="false" onAction="#fileButtonPressed" text="%main.button.file.label" GridPane.columnIndex="2" />
<Button fx:id="extractButton" mnemonicParsing="false" onAction="#extractPressed" text="%main.button.extract.label" GridPane.columnIndex="1" GridPane.rowIndex="2" />
<Button fx:id="exportButton" mnemonicParsing="false" onAction="#exportPressed" text="%main.button.export.label" GridPane.columnIndex="2" GridPane.rowIndex="2" />
<TableView fx:id="subtitlesTable" GridPane.rowIndex="1">
<columns>
<TableColumn fx:id="startColumn" prefWidth="50.0" text="Column X" />
<TableColumn fx:id="endColumn" prefWidth="50.0" text="Column X" />
<TableColumn fx:id="textColumn" prefWidth="75.0" text="Column X" />
</columns>
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
</columnResizePolicy>
</TableView>
<Button fx:id="resetButton" mnemonicParsing="false" onAction="#resetButtonPressed" text="%main.button.reset.label" GridPane.rowIndex="2" />
<StackPane fx:id="stackPane" prefHeight="150.0" prefWidth="200.0" GridPane.columnIndex="1" GridPane.columnSpan="2147483647" GridPane.rowIndex="1">
<children>
<MediaView fx:id="videoView" fitHeight="200.0" fitWidth="200.0" />
</children>
</StackPane>
</children>
</GridPane>
</children>
</AnchorPane>

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<GridPane hgap="10.0" vgap="10.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/22"
fx:controller="com.github.gtache.autosubtitle.gui.fx.FXSetupController">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES"/>
<ColumnConstraints hgrow="SOMETIMES"/>
<ColumnConstraints hgrow="SOMETIMES"/>
</columnConstraints>
<rowConstraints>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
</rowConstraints>
<children>
<Label fx:id="converterNameLabel" text="Label"/>
<Label fx:id="converterStatusLabel" text="Label" GridPane.columnIndex="1"/>
<Label fx:id="extractorNameLabel" text="Label" GridPane.rowIndex="1"/>
<Label fx:id="extractorStatusLabel" text="Label" GridPane.columnIndex="1" GridPane.rowIndex="1"/>
<Label fx:id="translatorNameLabel" text="Label" GridPane.rowIndex="2"/>
<Label fx:id="translatorStatusLabel" text="Label" GridPane.columnIndex="1" GridPane.rowIndex="2"/>
<MenuButton fx:id="converterButton" mnemonicParsing="false" text="MenuButton" GridPane.columnIndex="2">
<items>
<MenuItem mnemonicParsing="false" text="Action 1"/>
<MenuItem mnemonicParsing="false" text="Action 2"/>
</items>
</MenuButton>
<MenuButton fx:id="extractorButton" mnemonicParsing="false" text="MenuButton" GridPane.columnIndex="2"
GridPane.rowIndex="1">
<items>
<MenuItem mnemonicParsing="false" text="Action 1"/>
<MenuItem mnemonicParsing="false" text="Action 2"/>
</items>
</MenuButton>
<MenuButton fx:id="translatorButton" mnemonicParsing="false" text="MenuButton" GridPane.columnIndex="2"
GridPane.rowIndex="2">
<items>
<MenuItem mnemonicParsing="false" text="Action 1"/>
<MenuItem mnemonicParsing="false" text="Action 2"/>
</items>
</MenuButton>
</children>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
</padding>
</GridPane>