diff --git a/.idea/csv-editor.xml b/.idea/csv-editor.xml
deleted file mode 100644
index 54fbf77..0000000
--- a/.idea/csv-editor.xml
+++ /dev/null
@@ -1,219 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
index 4697f0e..21e87df 100644
--- a/.idea/encodings.xml
+++ b/.idea/encodings.xml
@@ -7,6 +7,8 @@
+
+
@@ -27,8 +29,14 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/sonarlint.xml b/.idea/sonarlint.xml
index 8b17604..2645de5 100644
--- a/.idea/sonarlint.xml
+++ b/.idea/sonarlint.xml
@@ -4,6 +4,7 @@
diff --git a/api/src/main/java/com/github/gtache/autosubtitle/Translator.java b/api/src/main/java/com/github/gtache/autosubtitle/Translator.java
index 2dceed1..ce7ebd5 100644
--- a/api/src/main/java/com/github/gtache/autosubtitle/Translator.java
+++ b/api/src/main/java/com/github/gtache/autosubtitle/Translator.java
@@ -6,7 +6,7 @@ import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
/**
* Translates texts and subtitles
*/
-public interface Translator {
+public interface Translator {
/**
* Guesses the language of the given text
@@ -42,7 +42,7 @@ public interface Translator {
* @param to The target language
* @return The translated subtitle
*/
- Subtitle translate(Subtitle subtitle, Language to);
+ T translate(Subtitle subtitle, Language to);
/**
* Translates the given subtitles collection to the given language
@@ -51,5 +51,5 @@ public interface Translator {
* @param to The target language
* @return The translated subtitles collection
*/
- SubtitleCollection translate(SubtitleCollection collection, Language to);
+ SubtitleCollection translate(SubtitleCollection> collection, Language to);
}
diff --git a/api/src/main/java/com/github/gtache/autosubtitle/VideoConverter.java b/api/src/main/java/com/github/gtache/autosubtitle/VideoConverter.java
index a594658..f19bc7c 100644
--- a/api/src/main/java/com/github/gtache/autosubtitle/VideoConverter.java
+++ b/api/src/main/java/com/github/gtache/autosubtitle/VideoConverter.java
@@ -19,7 +19,7 @@ public interface VideoConverter {
* @return The modified video
* @throws IOException If an I/O error occurs
*/
- Video addSoftSubtitles(final Video video, final Collection subtitles) throws IOException;
+ Video addSoftSubtitles(final Video video, final Collection extends SubtitleCollection>> subtitles) throws IOException;
/**
* Adds soft subtitles to the given video
@@ -29,7 +29,7 @@ public interface VideoConverter {
* @param path The output path
* @throws IOException If an I/O error occurs
*/
- void addSoftSubtitles(final Video video, final Collection subtitles, final Path path) throws IOException;
+ void addSoftSubtitles(final Video video, final Collection extends SubtitleCollection>> subtitles, final Path path) throws IOException;
/**
* Adds hard subtitles to the given video
@@ -39,7 +39,7 @@ public interface VideoConverter {
* @return The modified video
* @throws IOException If an I/O error occurs
*/
- Video addHardSubtitles(final Video video, final SubtitleCollection subtitles) throws IOException;
+ Video addHardSubtitles(final Video video, final SubtitleCollection> subtitles) throws IOException;
/**
* Adds hard subtitles to the given video
@@ -49,7 +49,7 @@ public interface VideoConverter {
* @param path The output path
* @throws IOException If an I/O error occurs
*/
- void addHardSubtitles(final Video video, final SubtitleCollection subtitles, final Path path) throws IOException;
+ void addHardSubtitles(final Video video, final SubtitleCollection> subtitles, final Path path) throws IOException;
/**
* Extracts the audio from the given video
diff --git a/api/src/main/java/com/github/gtache/autosubtitle/archive/Archiver.java b/api/src/main/java/com/github/gtache/autosubtitle/archive/Archiver.java
new file mode 100644
index 0000000..381f350
--- /dev/null
+++ b/api/src/main/java/com/github/gtache/autosubtitle/archive/Archiver.java
@@ -0,0 +1,44 @@
+package com.github.gtache.autosubtitle.archive;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
+
+/**
+ * Compresses and uncompresses files
+ */
+public interface Archiver {
+
+ /**
+ * Zips multiple files to the given destination
+ *
+ * @param files The files to zip
+ * @param destination The zipped file
+ * @throws IOException if an error occurs
+ */
+ void compress(final List files, final Path destination) throws IOException;
+
+ /**
+ * Unzips an archive to the given destination
+ *
+ * @param archive The archive
+ * @param destination The destination folder
+ * @throws IOException if an error occurs
+ */
+ void decompress(final Path archive, final Path destination) throws IOException;
+
+ /**
+ * Checks whether the given archive file is supported by the compresser
+ *
+ * @param path The file path
+ * @return True if the file is supported
+ */
+ default boolean isPathSupported(final Path path) {
+ return path.toString().endsWith("." + archiveExtension());
+ }
+
+ /**
+ * @return The zipped archive extension
+ */
+ String archiveExtension();
+}
diff --git a/api/src/main/java/com/github/gtache/autosubtitle/process/ProcessListener.java b/api/src/main/java/com/github/gtache/autosubtitle/process/ProcessListener.java
index 0e58649..a3a1cdf 100644
--- a/api/src/main/java/com/github/gtache/autosubtitle/process/ProcessListener.java
+++ b/api/src/main/java/com/github/gtache/autosubtitle/process/ProcessListener.java
@@ -22,10 +22,11 @@ public interface ProcessListener {
String readLine() throws IOException;
/**
- * Waits for the process to finish
+ * Waits for the process to finish. Automatically reads the process output to ensure it doesn't get stuck.
*
* @param duration The maximum time to wait
* @return The process result
+ * @throws IOException if an error occurs
*/
ProcessResult join(final Duration duration) throws IOException;
}
diff --git a/api/src/main/java/com/github/gtache/autosubtitle/process/ProcessRunner.java b/api/src/main/java/com/github/gtache/autosubtitle/process/ProcessRunner.java
index cfbddca..5d15a4a 100644
--- a/api/src/main/java/com/github/gtache/autosubtitle/process/ProcessRunner.java
+++ b/api/src/main/java/com/github/gtache/autosubtitle/process/ProcessRunner.java
@@ -1,6 +1,7 @@
package com.github.gtache.autosubtitle.process;
import java.io.IOException;
+import java.time.Duration;
import java.util.Arrays;
import java.util.List;
@@ -10,24 +11,25 @@ import java.util.List;
public interface ProcessRunner {
/**
- * Runs a command
+ * Runs a command and waits max 1 hour for the process to run
*
* @param args the command
* @return the result
* @throws IOException if something goes wrong
*/
default ProcessResult run(final String... args) throws IOException {
- return run(Arrays.asList(args));
+ return run(Arrays.asList(args), Duration.ofHours(1));
}
/**
* Runs a command
*
- * @param args the command
+ * @param args the command
+ * @param duration The maximum duration to wait for
* @return the result
* @throws IOException if something goes wrong
*/
- ProcessResult run(final List args) throws IOException;
+ ProcessResult run(final List args, final Duration duration) throws IOException;
/**
* Starts a process
diff --git a/api/src/main/java/com/github/gtache/autosubtitle/subtitle/SubtitleCollection.java b/api/src/main/java/com/github/gtache/autosubtitle/subtitle/SubtitleCollection.java
index 929042c..417ae97 100644
--- a/api/src/main/java/com/github/gtache/autosubtitle/subtitle/SubtitleCollection.java
+++ b/api/src/main/java/com/github/gtache/autosubtitle/subtitle/SubtitleCollection.java
@@ -7,7 +7,7 @@ import java.util.Collection;
/**
* Represents a collection of {@link Subtitle}
*/
-public interface SubtitleCollection {
+public interface SubtitleCollection {
/**
* @return The whole text of the subtitles
@@ -17,7 +17,7 @@ public interface SubtitleCollection {
/**
* @return The subtitles
*/
- Collection extends Subtitle> subtitles();
+ Collection subtitles();
/**
* @return The language of the subtitles
diff --git a/api/src/main/java/com/github/gtache/autosubtitle/subtitle/SubtitleImporterExporter.java b/api/src/main/java/com/github/gtache/autosubtitle/subtitle/SubtitleImporterExporter.java
new file mode 100644
index 0000000..8fa222f
--- /dev/null
+++ b/api/src/main/java/com/github/gtache/autosubtitle/subtitle/SubtitleImporterExporter.java
@@ -0,0 +1,56 @@
+package com.github.gtache.autosubtitle.subtitle;
+
+import com.github.gtache.autosubtitle.Language;
+import com.github.gtache.autosubtitle.subtitle.converter.ParseException;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Imports and exports subtitles
+ */
+public interface SubtitleImporterExporter {
+
+ /**
+ * Imports subtitles from a file
+ *
+ * @param file The path to the file
+ * @return A mapping of langauge to collection
+ * @throws IOException If an error occurred
+ * @throws ParseException If an error occurred while parsing a subtitle
+ */
+ Map> importSubtitles(final Path file) throws IOException, ParseException;
+
+ /**
+ * Exports multiple collections to a file
+ *
+ * @param collections The subtitle collections
+ * @param file The path to the file
+ * @throws IOException If an error occurred
+ */
+ void exportSubtitles(final Collection extends SubtitleCollection>> collections, final Path file) throws IOException;
+
+ /**
+ * Exports a single collection to a file
+ *
+ * @param collection The subtitle collection
+ * @param file The path to the file
+ * @throws IOException If an error occurred
+ */
+ default void exportSubtitles(final SubtitleCollection> collection, final Path file) throws IOException {
+ exportSubtitles(List.of(collection), file);
+ }
+
+ /**
+ * @return The supported file extensions for multiple collection exports
+ */
+ Collection supportedArchiveExtensions();
+
+ /**
+ * @return The supported file extensions for single collection exports
+ */
+ Collection supportedSingleFileExtensions();
+}
diff --git a/api/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/SubtitleConverter.java b/api/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/SubtitleConverter.java
index c926884..2b70487 100644
--- a/api/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/SubtitleConverter.java
+++ b/api/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/SubtitleConverter.java
@@ -1,5 +1,6 @@
package com.github.gtache.autosubtitle.subtitle.converter;
+import com.github.gtache.autosubtitle.subtitle.Subtitle;
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
import java.io.IOException;
@@ -9,7 +10,7 @@ import java.nio.file.Path;
/**
* Converts subtitles to a specific format (e.g. srt, ssa, ass, ...) and vice-versa
*/
-public interface SubtitleConverter {
+public interface SubtitleConverter {
/**
* Converts the subtitle collection
@@ -17,7 +18,7 @@ public interface SubtitleConverter {
* @param collection The collection
* @return The converted subtitles as the content of a file
*/
- String format(final SubtitleCollection collection);
+ String format(final SubtitleCollection> collection);
/**
* Parses a subtitle collection
@@ -26,7 +27,7 @@ public interface SubtitleConverter {
* @return The subtitle collection
* @throws ParseException If an error occurred
*/
- default SubtitleCollection parse(final Path file) throws ParseException {
+ default SubtitleCollection parse(final Path file) throws ParseException {
try {
final var content = Files.readString(file);
return parse(content);
@@ -42,7 +43,7 @@ public interface SubtitleConverter {
* @return The subtitle collection
* @throws ParseException If an error occurred
*/
- SubtitleCollection parse(String content) throws ParseException;
+ SubtitleCollection parse(String content) throws ParseException;
/**
* Check if the parser can parse the given file
diff --git a/api/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/SubtitleExtractor.java b/api/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/SubtitleExtractor.java
index fd91d11..f5e77bf 100644
--- a/api/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/SubtitleExtractor.java
+++ b/api/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/SubtitleExtractor.java
@@ -3,12 +3,13 @@ package com.github.gtache.autosubtitle.subtitle.extractor;
import com.github.gtache.autosubtitle.Audio;
import com.github.gtache.autosubtitle.Language;
import com.github.gtache.autosubtitle.Video;
+import com.github.gtache.autosubtitle.subtitle.Subtitle;
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
/**
* Extracts subtitles from a video or audio
*/
-public interface SubtitleExtractor {
+public interface SubtitleExtractor {
/**
* Adds a listener
@@ -37,7 +38,7 @@ public interface SubtitleExtractor {
* @return The extracted subtitle collection
* @throws ExtractException If an error occurs
*/
- default SubtitleCollection extract(final Video video, final ExtractionModel model) throws ExtractException {
+ default SubtitleCollection extract(final Video video, final ExtractionModel model) throws ExtractException {
return extract(video, Language.AUTO, model);
}
@@ -50,7 +51,7 @@ public interface SubtitleExtractor {
* @return The extracted subtitle collection
* @throws ExtractException If an error occurs
*/
- SubtitleCollection extract(final Video video, final Language language, final ExtractionModel model) throws ExtractException;
+ SubtitleCollection extract(final Video video, final Language language, final ExtractionModel model) throws ExtractException;
/**
* Extracts the subtitles from an audio
@@ -60,7 +61,7 @@ public interface SubtitleExtractor {
* @return The extracted subtitle collection
* @throws ExtractException If an error occurs
*/
- default SubtitleCollection extract(final Audio audio, final ExtractionModel model) throws ExtractException {
+ default SubtitleCollection extract(final Audio audio, final ExtractionModel model) throws ExtractException {
return extract(audio, Language.AUTO, model);
}
@@ -73,5 +74,5 @@ public interface SubtitleExtractor {
* @return The extracted subtitle collection
* @throws ExtractException If an error occurs
*/
- SubtitleCollection extract(final Audio audio, final Language language, final ExtractionModel model) throws ExtractException;
+ SubtitleCollection extract(final Audio audio, final Language language, final ExtractionModel model) throws ExtractException;
}
diff --git a/api/src/main/java/module-info.java b/api/src/main/java/module-info.java
index c19dfaa..2a4c54f 100644
--- a/api/src/main/java/module-info.java
+++ b/api/src/main/java/module-info.java
@@ -3,6 +3,7 @@
*/
module com.github.gtache.autosubtitle.api {
exports com.github.gtache.autosubtitle;
+ exports com.github.gtache.autosubtitle.archive;
exports com.github.gtache.autosubtitle.process;
exports com.github.gtache.autosubtitle.setup;
exports com.github.gtache.autosubtitle.subtitle;
diff --git a/api/src/test/java/com/github/gtache/autosubtitle/com/github/gtache/autosubtitle/subtitle/converter/TestSubtitleConverter.java b/api/src/test/java/com/github/gtache/autosubtitle/com/github/gtache/autosubtitle/subtitle/converter/TestSubtitleConverter.java
index 1c47de8..54d95ee 100644
--- a/api/src/test/java/com/github/gtache/autosubtitle/com/github/gtache/autosubtitle/subtitle/converter/TestSubtitleConverter.java
+++ b/api/src/test/java/com/github/gtache/autosubtitle/com/github/gtache/autosubtitle/subtitle/converter/TestSubtitleConverter.java
@@ -1,5 +1,6 @@
package com.github.gtache.autosubtitle.com.github.gtache.autosubtitle.subtitle.converter;
+import com.github.gtache.autosubtitle.subtitle.Subtitle;
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
import com.github.gtache.autosubtitle.subtitle.converter.ParseException;
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter;
@@ -23,10 +24,10 @@ import static org.mockito.Mockito.when;
class TestSubtitleConverter {
private final SubtitleConverter subtitleConverter;
- private final SubtitleCollection subtitleCollection;
+ private final SubtitleCollection subtitleCollection;
TestSubtitleConverter(@Mock final SubtitleConverter subtitleConverter,
- @Mock final SubtitleCollection subtitleCollection) {
+ @Mock final SubtitleCollection subtitleCollection) {
this.subtitleConverter = Objects.requireNonNull(subtitleConverter);
this.subtitleCollection = Objects.requireNonNull(subtitleCollection);
}
diff --git a/api/src/test/java/com/github/gtache/autosubtitle/process/TestProcessRunner.java b/api/src/test/java/com/github/gtache/autosubtitle/process/TestProcessRunner.java
index 579f1ac..a6389f1 100644
--- a/api/src/test/java/com/github/gtache/autosubtitle/process/TestProcessRunner.java
+++ b/api/src/test/java/com/github/gtache/autosubtitle/process/TestProcessRunner.java
@@ -7,6 +7,7 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.io.IOException;
+import java.time.Duration;
import java.util.List;
import java.util.Objects;
@@ -32,7 +33,7 @@ class TestProcessRunner {
@Test
void testRunVarargs() throws IOException {
- when(processRunner.run(List.of("arg1", "arg2"))).thenReturn(result);
+ when(processRunner.run(List.of("arg1", "arg2"), Duration.ofHours(1))).thenReturn(result);
when(processRunner.run(any(String[].class))).thenCallRealMethod();
assertEquals(result, processRunner.run("arg1", "arg2"));
diff --git a/cli/pom.xml b/cli/pom.xml
index 70e0d72..f4e4df9 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -22,7 +22,7 @@
com.github.gtache.autosubtitle
- autosubtitle-whisper
+ autosubtitle-whisperx
org.apache.logging.log4j
diff --git a/cli/src/main/java/com/github/gtache/autosubtitle/modules/cli/CliComponent.java b/cli/src/main/java/com/github/gtache/autosubtitle/modules/cli/CliComponent.java
index 9ca64da..797a2b7 100644
--- a/cli/src/main/java/com/github/gtache/autosubtitle/modules/cli/CliComponent.java
+++ b/cli/src/main/java/com/github/gtache/autosubtitle/modules/cli/CliComponent.java
@@ -3,14 +3,14 @@ package com.github.gtache.autosubtitle.modules.cli;
import com.github.gtache.autosubtitle.modules.deepl.DeepLModule;
import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegModule;
import com.github.gtache.autosubtitle.modules.impl.CoreModule;
-import com.github.gtache.autosubtitle.modules.subtitle.extractor.whisper.WhisperExtractorModule;
+import com.github.gtache.autosubtitle.modules.whisperx.WhisperXModule;
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter;
import dagger.Component;
import javax.inject.Singleton;
import java.util.Map;
-@Component(modules = {CoreModule.class, DeepLModule.class, FFmpegModule.class, WhisperExtractorModule.class})
+@Component(modules = {CoreModule.class, DeepLModule.class, FFmpegModule.class, WhisperXModule.class})
@Singleton
public interface CliComponent {
diff --git a/cli/src/main/java/module-info.java b/cli/src/main/java/module-info.java
index 74a5d6b..ec5b9fa 100644
--- a/cli/src/main/java/module-info.java
+++ b/cli/src/main/java/module-info.java
@@ -4,6 +4,6 @@
module com.github.gtache.autosubtitle.cli {
requires com.github.gtache.autosubtitle.deepl;
requires com.github.gtache.autosubtitle.ffmpeg;
- requires com.github.gtache.autosubtitle.whisper;
+ requires com.github.gtache.autosubtitle.whisperx;
requires info.picocli;
}
\ No newline at end of file
diff --git a/conda/pom.xml b/conda/pom.xml
new file mode 100644
index 0000000..535eabc
--- /dev/null
+++ b/conda/pom.xml
@@ -0,0 +1,20 @@
+
+
+ 4.0.0
+
+ com.github.gtache.autosubtitle
+ autosubtitle
+ 1.0-SNAPSHOT
+
+
+ autosubtitle-conda
+
+
+
+ com.github.gtache.autosubtitle
+ autosubtitle-core
+
+
+
\ No newline at end of file
diff --git a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaBundledPath.java b/conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaBundledPath.java
similarity index 86%
rename from whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaBundledPath.java
rename to conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaBundledPath.java
index 2d80b90..d3dd0fc 100644
--- a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaBundledPath.java
+++ b/conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaBundledPath.java
@@ -1,4 +1,4 @@
-package com.github.gtache.autosubtitle.modules.setup.whisper;
+package com.github.gtache.autosubtitle.modules.setup.conda;
import javax.inject.Qualifier;
import java.lang.annotation.Documented;
diff --git a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaInstallerPath.java b/conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaInstallerPath.java
similarity index 86%
rename from whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaInstallerPath.java
rename to conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaInstallerPath.java
index a514484..6a536d8 100644
--- a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaInstallerPath.java
+++ b/conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaInstallerPath.java
@@ -1,4 +1,4 @@
-package com.github.gtache.autosubtitle.modules.setup.whisper;
+package com.github.gtache.autosubtitle.modules.setup.conda;
import javax.inject.Qualifier;
import java.lang.annotation.Documented;
diff --git a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaMinimumMajorVersion.java b/conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaMinimumMajorVersion.java
similarity index 86%
rename from whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaMinimumMajorVersion.java
rename to conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaMinimumMajorVersion.java
index 654f0b6..b391b1e 100644
--- a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaMinimumMajorVersion.java
+++ b/conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaMinimumMajorVersion.java
@@ -1,4 +1,4 @@
-package com.github.gtache.autosubtitle.modules.setup.whisper;
+package com.github.gtache.autosubtitle.modules.setup.conda;
import javax.inject.Qualifier;
import java.lang.annotation.Documented;
diff --git a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaMinimumMinorVersion.java b/conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaMinimumMinorVersion.java
similarity index 86%
rename from whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaMinimumMinorVersion.java
rename to conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaMinimumMinorVersion.java
index e89879b..857f5a2 100644
--- a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaMinimumMinorVersion.java
+++ b/conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaMinimumMinorVersion.java
@@ -1,4 +1,4 @@
-package com.github.gtache.autosubtitle.modules.setup.whisper;
+package com.github.gtache.autosubtitle.modules.setup.conda;
import javax.inject.Qualifier;
import java.lang.annotation.Documented;
diff --git a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaRootPath.java b/conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaRootPath.java
similarity index 86%
rename from whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaRootPath.java
rename to conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaRootPath.java
index b8da150..1ce8230 100644
--- a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaRootPath.java
+++ b/conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaRootPath.java
@@ -1,4 +1,4 @@
-package com.github.gtache.autosubtitle.modules.setup.whisper;
+package com.github.gtache.autosubtitle.modules.setup.conda;
import javax.inject.Qualifier;
import java.lang.annotation.Documented;
diff --git a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaSetupModule.java b/conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaSetupModule.java
similarity index 80%
rename from whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaSetupModule.java
rename to conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaSetupModule.java
index e92a78e..757f94b 100644
--- a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaSetupModule.java
+++ b/conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaSetupModule.java
@@ -1,8 +1,10 @@
-package com.github.gtache.autosubtitle.modules.setup.whisper;
+package com.github.gtache.autosubtitle.modules.setup.conda;
import com.github.gtache.autosubtitle.impl.Architecture;
import com.github.gtache.autosubtitle.impl.OS;
-import com.github.gtache.autosubtitle.setup.whisper.CondaSetupConfiguration;
+import com.github.gtache.autosubtitle.modules.setup.impl.CacheRoot;
+import com.github.gtache.autosubtitle.modules.setup.impl.ToolsRoot;
+import com.github.gtache.autosubtitle.setup.conda.CondaSetupConfiguration;
import dagger.Module;
import dagger.Provides;
@@ -56,13 +58,13 @@ public final class CondaSetupModule {
@Provides
@CondaRootPath
- static Path providesCondaRootPath(@WhisperBundledRoot final Path root, final OS os) {
+ static Path providesCondaRootPath(@ToolsRoot final Path root) {
return root.resolve(MINICONDA3);
}
@Provides
@CondaInstallerPath
- static Path providesCondaInstallerPath(@WhisperBundledRoot final Path root, final OS os) {
- return root.resolve("cache").resolve("conda-install" + (os == OS.WINDOWS ? ".exe" : ".sh"));
+ static Path providesCondaInstallerPath(@CacheRoot final Path root, final OS os) {
+ return root.resolve("conda-install" + (os == OS.WINDOWS ? ".exe" : ".sh"));
}
}
diff --git a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaSystemPath.java b/conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaSystemPath.java
similarity index 86%
rename from whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaSystemPath.java
rename to conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaSystemPath.java
index e819bd2..a0ab21c 100644
--- a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaSystemPath.java
+++ b/conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaSystemPath.java
@@ -1,4 +1,4 @@
-package com.github.gtache.autosubtitle.modules.setup.whisper;
+package com.github.gtache.autosubtitle.modules.setup.conda;
import javax.inject.Qualifier;
import java.lang.annotation.Documented;
diff --git a/whisper/src/main/java/com/github/gtache/autosubtitle/setup/whisper/CondaSetupConfiguration.java b/conda/src/main/java/com/github/gtache/autosubtitle/setup/conda/CondaSetupConfiguration.java
similarity index 95%
rename from whisper/src/main/java/com/github/gtache/autosubtitle/setup/whisper/CondaSetupConfiguration.java
rename to conda/src/main/java/com/github/gtache/autosubtitle/setup/conda/CondaSetupConfiguration.java
index 6134033..698cc2d 100644
--- a/whisper/src/main/java/com/github/gtache/autosubtitle/setup/whisper/CondaSetupConfiguration.java
+++ b/conda/src/main/java/com/github/gtache/autosubtitle/setup/conda/CondaSetupConfiguration.java
@@ -1,4 +1,4 @@
-package com.github.gtache.autosubtitle.setup.whisper;
+package com.github.gtache.autosubtitle.setup.conda;
import com.github.gtache.autosubtitle.impl.Architecture;
import com.github.gtache.autosubtitle.impl.OS;
diff --git a/whisper/src/main/java/com/github/gtache/autosubtitle/setup/whisper/CondaSetupManager.java b/conda/src/main/java/com/github/gtache/autosubtitle/setup/conda/CondaSetupManager.java
similarity index 89%
rename from whisper/src/main/java/com/github/gtache/autosubtitle/setup/whisper/CondaSetupManager.java
rename to conda/src/main/java/com/github/gtache/autosubtitle/setup/conda/CondaSetupManager.java
index 534d41d..3b670b5 100644
--- a/whisper/src/main/java/com/github/gtache/autosubtitle/setup/whisper/CondaSetupManager.java
+++ b/conda/src/main/java/com/github/gtache/autosubtitle/setup/conda/CondaSetupManager.java
@@ -1,4 +1,4 @@
-package com.github.gtache.autosubtitle.setup.whisper;
+package com.github.gtache.autosubtitle.setup.conda;
import com.github.gtache.autosubtitle.impl.OS;
import com.github.gtache.autosubtitle.setup.SetupException;
@@ -13,7 +13,9 @@ import java.io.IOException;
import java.net.http.HttpClient;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.time.Duration;
import java.util.Arrays;
+import java.util.List;
import java.util.stream.Stream;
import static java.util.Objects.requireNonNull;
@@ -67,8 +69,8 @@ public class CondaSetupManager extends AbstractSetupManager {
logger.info("Conda downloaded");
}
switch (configuration.os()) {
- case WINDOWS -> installWindows();
- case MAC, LINUX -> installLinux();
+ case OS.WINDOWS -> installWindows();
+ case OS.MAC, OS.LINUX -> installLinux();
}
}
@@ -77,7 +79,7 @@ public class CondaSetupManager extends AbstractSetupManager {
final var installerPath = configuration.condaInstallerPath();
final var rootPath = configuration.condaRootPath();
logger.info("Installing conda using {}", installerPath);
- final var result = run("bash", installerPath.toString(), "-b", "-p", rootPath.toString());
+ final var result = run(List.of("bash", installerPath.toString(), "-b", "-p", rootPath.toString()), Duration.ofMinutes(15));
if (result.exitCode() == 0) {
logger.info("Installed conda to {}", rootPath);
} else {
@@ -93,7 +95,7 @@ public class CondaSetupManager extends AbstractSetupManager {
final var installerPath = configuration.condaInstallerPath();
final var rootPath = configuration.condaRootPath();
logger.info("Installing conda using {}", installerPath);
- final var result = run(installerPath.toString(), "/InstallationType=JustMe", "/RegisterPython=0", "/S", "/D=" + rootPath.toString());
+ final var result = run(List.of(installerPath.toString(), "/InstallationType=JustMe", "/RegisterPython=0", "/S", "/D=" + rootPath.toString()), Duration.ofMinutes(15));
if (result.exitCode() == 0) {
logger.info("Installed conda to {}", rootPath);
} else {
@@ -106,9 +108,9 @@ public class CondaSetupManager extends AbstractSetupManager {
private void downloadConda() throws SetupException {
switch (configuration.os()) {
- case WINDOWS -> downloadCondaWindows();
- case MAC -> downloadCondaMac();
- case LINUX -> downloadCondaLinux();
+ case OS.WINDOWS -> downloadCondaWindows();
+ case OS.MAC -> downloadCondaMac();
+ case OS.LINUX -> downloadCondaLinux();
}
logger.info("Downloaded conda to {}", configuration.condaInstallerPath());
}
@@ -152,7 +154,7 @@ public class CondaSetupManager extends AbstractSetupManager {
@Override
public void update() throws SetupException {
try {
- final var result = run(getCondaPath().toString(), "update", "-y", "conda");
+ final var result = run(List.of(getCondaPath().toString(), "update", "-y", "conda"), Duration.ofMinutes(15));
if (result.exitCode() == 0) {
logger.info("Conda updated");
} else {
@@ -167,7 +169,7 @@ public class CondaSetupManager extends AbstractSetupManager {
final var args = Stream.concat(Stream.of(getCondaPath().toString(), "create", "-y", "-p", path.toString(), "python=" + pythonVersion), Arrays.stream(packages)).toList();
try {
logger.info("Creating venv {}", path);
- final var result = run(args);
+ final var result = run(args, Duration.ofMinutes(15));
if (result.exitCode() == 0) {
logger.info("Created venv {}", path);
} else {
@@ -200,7 +202,7 @@ public class CondaSetupManager extends AbstractSetupManager {
private boolean isSystemCondaInstalled() throws SetupException {
try {
- final var result = run(configuration.condaSystemPath().toString(), "--version");
+ final var result = run(List.of(configuration.condaSystemPath().toString(), "--version"), Duration.ofSeconds(5));
if (result.exitCode() == 0) {
final var output = result.output().getFirst();
final var versionString = output.substring(output.indexOf(' ') + 1);
diff --git a/conda/src/main/java/module-info.java b/conda/src/main/java/module-info.java
new file mode 100644
index 0000000..3bc7f1d
--- /dev/null
+++ b/conda/src/main/java/module-info.java
@@ -0,0 +1,10 @@
+/**
+ * Module for conda
+ */
+module com.github.gtache.autosubtitle.conda {
+ requires transitive com.github.gtache.autosubtitle.core;
+ requires org.apache.logging.log4j;
+
+ exports com.github.gtache.autosubtitle.setup.conda;
+ exports com.github.gtache.autosubtitle.modules.setup.conda;
+}
\ No newline at end of file
diff --git a/core/pom.xml b/core/pom.xml
index ee2e5d0..03c549d 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -24,5 +24,10 @@
org.apache.logging.log4j
log4j-api
+
+ org.apache.logging.log4j
+ log4j-core
+ test
+
\ No newline at end of file
diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/ZipDecompresser.java b/core/src/main/java/com/github/gtache/autosubtitle/archive/impl/ZipDecompresser.java
similarity index 76%
rename from ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/ZipDecompresser.java
rename to core/src/main/java/com/github/gtache/autosubtitle/archive/impl/ZipDecompresser.java
index 931d9e0..4c0625d 100644
--- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/ZipDecompresser.java
+++ b/core/src/main/java/com/github/gtache/autosubtitle/archive/impl/ZipDecompresser.java
@@ -1,16 +1,31 @@
-package com.github.gtache.autosubtitle.setup.ffmpeg;
+package com.github.gtache.autosubtitle.archive.impl;
+import com.github.gtache.autosubtitle.archive.Archiver;
+
+import javax.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
/**
- * Zip implementation of {@link Decompresser}
+ * Zip implementation of {@link Archiver}
*/
-public class ZipDecompresser implements Decompresser {
+public class ZipDecompresser implements Archiver {
+
+ @Inject
+ ZipDecompresser() {
+
+ }
+
+ @Override
+ public void compress(final List files, final Path destination) throws IOException {
+ throw new UnsupportedOperationException("Not supported");
+ }
+
@Override
public void decompress(final Path archive, final Path destination) throws IOException {
if (!isPathSupported(archive)) {
@@ -49,9 +64,9 @@ public class ZipDecompresser implements Decompresser {
}
return destPath;
}
-
+
@Override
- public boolean isPathSupported(final Path path) {
- return path.getFileName().toString().endsWith(".zip");
+ public String archiveExtension() {
+ return "zip";
}
}
diff --git a/core/src/main/java/com/github/gtache/autosubtitle/modules/impl/CoreModule.java b/core/src/main/java/com/github/gtache/autosubtitle/modules/impl/CoreModule.java
index 1894b9a..c7736d5 100644
--- a/core/src/main/java/com/github/gtache/autosubtitle/modules/impl/CoreModule.java
+++ b/core/src/main/java/com/github/gtache/autosubtitle/modules/impl/CoreModule.java
@@ -1,23 +1,33 @@
package com.github.gtache.autosubtitle.modules.impl;
+import com.github.gtache.autosubtitle.archive.Archiver;
+import com.github.gtache.autosubtitle.archive.impl.ZipDecompresser;
import com.github.gtache.autosubtitle.impl.Architecture;
import com.github.gtache.autosubtitle.impl.DaggerException;
import com.github.gtache.autosubtitle.impl.OS;
import com.github.gtache.autosubtitle.modules.setup.impl.SetupModule;
import com.github.gtache.autosubtitle.modules.subtitle.impl.SubtitleModule;
+import dagger.Binds;
import dagger.Module;
import dagger.Provides;
+import dagger.multibindings.IntoMap;
+import dagger.multibindings.StringKey;
/**
* Dagger module for Core
*/
@Module(includes = {SetupModule.class, SubtitleModule.class})
-public final class CoreModule {
+public abstract class CoreModule {
private CoreModule() {
}
+ @Binds
+ @StringKey("zip")
+ @IntoMap
+ abstract Archiver bindsZipDecompresser(final ZipDecompresser decompresser);
+
@Provides
static OS providesOS() {
final var os = OS.getOS();
diff --git a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/WhisperVersion.java b/core/src/main/java/com/github/gtache/autosubtitle/modules/setup/impl/CacheRoot.java
similarity index 79%
rename from whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/WhisperVersion.java
rename to core/src/main/java/com/github/gtache/autosubtitle/modules/setup/impl/CacheRoot.java
index 96f6817..e454127 100644
--- a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/WhisperVersion.java
+++ b/core/src/main/java/com/github/gtache/autosubtitle/modules/setup/impl/CacheRoot.java
@@ -1,4 +1,4 @@
-package com.github.gtache.autosubtitle.modules.setup.whisper;
+package com.github.gtache.autosubtitle.modules.setup.impl;
import javax.inject.Qualifier;
import java.lang.annotation.Documented;
@@ -12,5 +12,5 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Documented
@Retention(RUNTIME)
@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
-public @interface WhisperVersion {
+public @interface CacheRoot {
}
diff --git a/core/src/main/java/com/github/gtache/autosubtitle/modules/setup/impl/SetupModule.java b/core/src/main/java/com/github/gtache/autosubtitle/modules/setup/impl/SetupModule.java
index 0280791..767d454 100644
--- a/core/src/main/java/com/github/gtache/autosubtitle/modules/setup/impl/SetupModule.java
+++ b/core/src/main/java/com/github/gtache/autosubtitle/modules/setup/impl/SetupModule.java
@@ -4,6 +4,8 @@ import dagger.Module;
import dagger.Provides;
import java.net.http.HttpClient;
+import java.nio.file.Path;
+import java.nio.file.Paths;
/**
* Dagger core module for setup
@@ -14,6 +16,18 @@ public final class SetupModule {
private SetupModule() {
}
+ @Provides
+ @CacheRoot
+ static Path providesCacheRoot() {
+ return Paths.get("cache");
+ }
+
+ @Provides
+ @ToolsRoot
+ static Path providesToolsRoot() {
+ return Paths.get("tools");
+ }
+
@Provides
static HttpClient providesHttpClient() {
return HttpClient.newHttpClient();
diff --git a/core/src/main/java/com/github/gtache/autosubtitle/modules/setup/impl/ToolsRoot.java b/core/src/main/java/com/github/gtache/autosubtitle/modules/setup/impl/ToolsRoot.java
new file mode 100644
index 0000000..5fb97fe
--- /dev/null
+++ b/core/src/main/java/com/github/gtache/autosubtitle/modules/setup/impl/ToolsRoot.java
@@ -0,0 +1,16 @@
+package com.github.gtache.autosubtitle.modules.setup.impl;
+
+import javax.inject.Qualifier;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
+public @interface ToolsRoot {
+}
diff --git a/core/src/main/java/com/github/gtache/autosubtitle/modules/subtitle/impl/SubtitleModule.java b/core/src/main/java/com/github/gtache/autosubtitle/modules/subtitle/impl/SubtitleModule.java
index fc04deb..2a4e256 100644
--- a/core/src/main/java/com/github/gtache/autosubtitle/modules/subtitle/impl/SubtitleModule.java
+++ b/core/src/main/java/com/github/gtache/autosubtitle/modules/subtitle/impl/SubtitleModule.java
@@ -1,7 +1,9 @@
package com.github.gtache.autosubtitle.modules.subtitle.impl;
+import com.github.gtache.autosubtitle.subtitle.SubtitleImporterExporter;
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter;
import com.github.gtache.autosubtitle.subtitle.converter.impl.SRTSubtitleConverter;
+import com.github.gtache.autosubtitle.subtitle.impl.SubtitleImporterExporterImpl;
import dagger.Binds;
import dagger.Module;
import dagger.multibindings.IntoMap;
@@ -20,4 +22,7 @@ public abstract class SubtitleModule {
@IntoMap
@StringKey("srt")
abstract SubtitleConverter bindsSubtitleConverter(final SRTSubtitleConverter converter);
+
+ @Binds
+ abstract SubtitleImporterExporter bindsSubtitleImporterExporter(final SubtitleImporterExporterImpl impl);
}
diff --git a/core/src/main/java/com/github/gtache/autosubtitle/process/impl/AbstractProcessRunner.java b/core/src/main/java/com/github/gtache/autosubtitle/process/impl/AbstractProcessRunner.java
index 7fa315f..870c560 100644
--- a/core/src/main/java/com/github/gtache/autosubtitle/process/impl/AbstractProcessRunner.java
+++ b/core/src/main/java/com/github/gtache/autosubtitle/process/impl/AbstractProcessRunner.java
@@ -9,7 +9,6 @@ import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.time.Duration;
import java.util.List;
-import java.util.concurrent.CompletableFuture;
/**
* Base implementation of {@link ProcessRunner}
@@ -19,19 +18,9 @@ public abstract class AbstractProcessRunner implements ProcessRunner {
private static final Logger logger = LogManager.getLogger(AbstractProcessRunner.class);
@Override
- public ProcessResult run(final List args) throws IOException {
+ public ProcessResult run(final List args, final Duration duration) throws IOException {
final var listener = startListen(args);
- CompletableFuture.runAsync(() -> {
- try {
- var line = listener.readLine();
- while (line != null) {
- line = listener.readLine();
- }
- } catch (final IOException e) {
- logger.error("Error listening to process output of {}", args, e);
- }
- });
- return listener.join(Duration.ofHours(1));
+ return listener.join(duration);
}
@Override
@@ -51,11 +40,12 @@ public abstract class AbstractProcessRunner implements ProcessRunner {
/**
* Runs a process and writes the output to the log
*
- * @param args the command
+ * @param args the command
+ * @param duration The maximum duration to wait for
* @return the result
* @throws IOException if an error occurs
*/
- protected ProcessResult runListen(final List args) throws IOException {
+ protected ProcessResult runListen(final List args, final Duration duration) throws IOException {
final var listener = startListen(args);
var line = listener.readLine();
final var processName = args.getFirst();
@@ -63,6 +53,6 @@ public abstract class AbstractProcessRunner implements ProcessRunner {
logger.info("[{}]: {}", processName, line);
line = listener.readLine();
}
- return listener.join(Duration.ofHours(1));
+ return listener.join(duration);
}
}
diff --git a/core/src/main/java/com/github/gtache/autosubtitle/process/impl/ProcessListenerImpl.java b/core/src/main/java/com/github/gtache/autosubtitle/process/impl/ProcessListenerImpl.java
index af668ff..b067a11 100644
--- a/core/src/main/java/com/github/gtache/autosubtitle/process/impl/ProcessListenerImpl.java
+++ b/core/src/main/java/com/github/gtache/autosubtitle/process/impl/ProcessListenerImpl.java
@@ -2,22 +2,25 @@ package com.github.gtache.autosubtitle.process.impl;
import com.github.gtache.autosubtitle.process.ProcessListener;
import com.github.gtache.autosubtitle.process.ProcessResult;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
-import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
/**
* Implementation of {@link ProcessListener}
*/
public class ProcessListenerImpl implements ProcessListener {
-
+ private static final Logger logger = LogManager.getLogger(ProcessListenerImpl.class);
private final Process process;
private final BufferedReader reader;
private final List output;
@@ -30,7 +33,7 @@ public class ProcessListenerImpl implements ProcessListener {
public ProcessListenerImpl(final Process process) {
this.process = Objects.requireNonNull(process);
this.reader = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8));
- this.output = new ArrayList<>();
+ this.output = new CopyOnWriteArrayList<>();
}
@Override
@@ -48,7 +51,24 @@ public class ProcessListenerImpl implements ProcessListener {
}
@Override
- public ProcessResult join(final Duration duration) throws IOException {
+ public ProcessResult join(final Duration duration) {
+ //Read to ensure process doesn't get stuck
+ CompletableFuture.runAsync(() -> {
+ try {
+ var line = readLine();
+ while (line != null) {
+ line = readLine();
+ }
+ } catch (final IOException e) {
+ logger.error("Error listening to process output of {}", process, e);
+ } finally {
+ try {
+ reader.close();
+ } catch (final IOException e) {
+ logger.warn("Error closing reader of {}", process, e);
+ }
+ }
+ });
try {
process.waitFor(duration.getSeconds(), TimeUnit.SECONDS);
} catch (final InterruptedException e) {
@@ -58,11 +78,6 @@ public class ProcessListenerImpl implements ProcessListener {
if (process.isAlive()) {
process.destroyForcibly();
}
- //Reads lines to output
- while (readLine() != null) {
- //Do nothing
- }
- reader.close();
return new ProcessResultImpl(process.exitValue(), output);
}
}
diff --git a/core/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/impl/SRTSubtitleConverter.java b/core/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/impl/SRTSubtitleConverter.java
index bf300a8..16f4304 100644
--- a/core/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/impl/SRTSubtitleConverter.java
+++ b/core/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/impl/SRTSubtitleConverter.java
@@ -9,7 +9,6 @@ import com.github.gtache.autosubtitle.subtitle.impl.SubtitleCollectionImpl;
import com.github.gtache.autosubtitle.subtitle.impl.SubtitleImpl;
import javax.inject.Inject;
-import javax.inject.Singleton;
import java.util.Arrays;
import java.util.Comparator;
import java.util.stream.Collectors;
@@ -20,17 +19,17 @@ import static java.util.Objects.requireNonNull;
/**
* Converts subtitles to SRT format
*/
-@Singleton
-public class SRTSubtitleConverter implements SubtitleConverter {
+public class SRTSubtitleConverter implements SubtitleConverter {
- private final Translator translator;
+ private final Translator> translator;
@Inject
SRTSubtitleConverter(final Translator translator) {
this.translator = requireNonNull(translator);
}
- public String format(final SubtitleCollection collection) {
+ @Override
+ public String format(final SubtitleCollection> collection) {
final var subtitles = collection.subtitles().stream().sorted(Comparator.comparing(Subtitle::start).thenComparing(Subtitle::end)).toList();
return IntStream.range(0, subtitles.size()).mapToObj(i -> {
final var subtitle = subtitles.get(i);
@@ -51,7 +50,7 @@ public class SRTSubtitleConverter implements SubtitleConverter {
}
@Override
- public SubtitleCollection parse(final String content) throws ParseException {
+ public SubtitleCollectionImpl parse(final String content) throws ParseException {
try {
final var elements = content.split("\n\n");
final var subtitles = Arrays.stream(elements).filter(element -> !element.isBlank()).map(element -> {
@@ -66,7 +65,7 @@ public class SRTSubtitleConverter implements SubtitleConverter {
return new SubtitleImpl(text, start, end, null, null);
}).toList();
final var text = subtitles.stream().map(Subtitle::content).collect(Collectors.joining(" "));
- return new SubtitleCollectionImpl(text, subtitles, translator.getLanguage(text));
+ return new SubtitleCollectionImpl<>(text, subtitles, translator.getLanguage(text));
} catch (final Exception e) {
throw new ParseException(e);
}
diff --git a/core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SubtitleCollectionImpl.java b/core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SubtitleCollectionImpl.java
index 194b5f6..4b6d538 100644
--- a/core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SubtitleCollectionImpl.java
+++ b/core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SubtitleCollectionImpl.java
@@ -13,8 +13,8 @@ import static java.util.Objects.requireNonNull;
/**
* Implementation of {@link SubtitleCollection}
*/
-public record SubtitleCollectionImpl(String text, Collection extends Subtitle> subtitles,
- Language language) implements SubtitleCollection {
+public record SubtitleCollectionImpl(String text, Collection subtitles,
+ Language language) implements SubtitleCollection {
public SubtitleCollectionImpl {
Objects.requireNonNull(text);
diff --git a/core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SubtitleImpl.java b/core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SubtitleImpl.java
index acb864b..5330c7d 100644
--- a/core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SubtitleImpl.java
+++ b/core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SubtitleImpl.java
@@ -22,4 +22,8 @@ public record SubtitleImpl(String content, long start, long end, Font font, Boun
throw new IllegalArgumentException("start must be <= end : " + start + " > " + end);
}
}
+
+ public SubtitleImpl(final Subtitle subtitle) {
+ this(subtitle.content(), subtitle.start(), subtitle.end(), subtitle.font(), subtitle.bounds());
+ }
}
diff --git a/core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SubtitleImporterExporterImpl.java b/core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SubtitleImporterExporterImpl.java
new file mode 100644
index 0000000..b930f60
--- /dev/null
+++ b/core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SubtitleImporterExporterImpl.java
@@ -0,0 +1,141 @@
+package com.github.gtache.autosubtitle.subtitle.impl;
+
+import com.github.gtache.autosubtitle.Language;
+import com.github.gtache.autosubtitle.archive.Archiver;
+import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
+import com.github.gtache.autosubtitle.subtitle.SubtitleImporterExporter;
+import com.github.gtache.autosubtitle.subtitle.converter.ParseException;
+import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import javax.inject.Inject;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * Implementation of {@link SubtitleImporterExporter}
+ */
+public class SubtitleImporterExporterImpl implements SubtitleImporterExporter {
+
+ private static final Logger logger = LogManager.getLogger(SubtitleImporterExporterImpl.class);
+ private final Map archiverMap;
+ private final Map> converterMap;
+
+ @Inject
+ SubtitleImporterExporterImpl(final Map archiverMap, final Map converterMap) {
+ this.archiverMap = Map.copyOf(archiverMap);
+ this.converterMap = converterMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+ }
+
+ @Override
+ public Map> importSubtitles(final Path file) throws IOException, ParseException {
+ final var fileName = file.getFileName().toString();
+ final var extension = fileName.substring(fileName.lastIndexOf('.') + 1);
+ if (archiverMap.containsKey(extension)) {
+ return loadArchive(file);
+ } else {
+ final var loaded = loadSingleFile(file);
+ logger.info("Loaded {}", file);
+ return Map.of(loaded.language(), loaded);
+ }
+ }
+
+ private SubtitleCollection loadSingleFile(final Path file) throws ParseException {
+ final var fileName = file.getFileName().toString();
+ final var extension = fileName.substring(fileName.lastIndexOf('.') + 1);
+ final var parser = converterMap.get(extension);
+ if (parser == null) {
+ throw new ParseException("No converter found for " + file);
+ } else {
+ final var parsed = parser.parse(file);
+ return new SubtitleCollectionImpl<>(parsed.text(), parsed.subtitles().stream().map(SubtitleImpl::new).toList(), parsed.language());
+ }
+ }
+
+ private Map> loadArchive(final Path file) throws IOException, ParseException {
+ final var fileName = file.getFileName().toString();
+ final var extension = fileName.substring(fileName.lastIndexOf('.') + 1);
+ final var archiver = archiverMap.get(extension);
+ final var tempDirectory = Files.createTempDirectory("autosubtitle");
+ archiver.decompress(file, tempDirectory);
+ final var files = new ArrayList();
+ try (final var stream = Files.list(tempDirectory)) {
+ stream.forEach(files::add);
+ }
+ final var map = new EnumMap>(Language.class);
+ for (final var path : files) {
+ final var loaded = loadSingleFile(path);
+ map.put(loaded.language(), loaded);
+ Files.deleteIfExists(path);
+ }
+ Files.deleteIfExists(tempDirectory);
+ logger.info("Loaded {}", file);
+ return map;
+ }
+
+ @Override
+ public void exportSubtitles(final Collection extends SubtitleCollection>> collections, final Path file) throws IOException {
+ final var fileName = file.getFileName().toString();
+ final var extension = fileName.substring(fileName.lastIndexOf('.') + 1);
+ if (archiverMap.containsKey(extension)) {
+ saveArchive(file, collections);
+ } else if (collections.size() == 1) {
+ saveSingleFile(file, collections.iterator().next());
+ } else {
+ throw new IllegalArgumentException("Cannot export multiple collections to a non-archive file : " + file);
+ }
+ }
+
+ private void saveArchive(final Path file, final Iterable extends SubtitleCollection>> collections) throws IOException {
+ final var fileName = file.getFileName().toString();
+ final var extension = fileName.substring(fileName.lastIndexOf('.') + 1);
+ final var archiver = archiverMap.get(extension);
+ final var singleExporter = converterMap.getOrDefault("json", converterMap.values().iterator().next());
+ final var tempDir = Files.createTempDirectory("autosubtitle");
+ for (final var collection : collections) {
+ final var subtitleFile = tempDir.resolve(collection.language() + "." + singleExporter.formatName());
+ saveSingleFile(subtitleFile, collection);
+ }
+ final var files = new ArrayList();
+ try (final var stream = Files.list(tempDir)) {
+ stream.forEach(files::add);
+ }
+ archiver.compress(files, file);
+ for (final var path : files) {
+ Files.deleteIfExists(path);
+ }
+ Files.deleteIfExists(tempDir);
+ logger.info("Saved {}", file);
+ }
+
+ private void saveSingleFile(final Path file, final SubtitleCollection> collection) throws IOException {
+ final var fileName = file.getFileName().toString();
+ final var extension = fileName.substring(fileName.lastIndexOf('.') + 1);
+ final var converter = converterMap.get(extension);
+ if (converter == null) {
+ throw new IOException("No converter found for " + file);
+ } else {
+ final var string = converter.format(collection);
+ Files.writeString(file, string);
+ logger.info("Saved {}", file);
+ }
+ }
+
+
+ @Override
+ public Collection supportedArchiveExtensions() {
+ return archiverMap.keySet();
+ }
+
+ @Override
+ public Collection supportedSingleFileExtensions() {
+ return converterMap.keySet();
+ }
+}
diff --git a/core/src/main/java/module-info.java b/core/src/main/java/module-info.java
index d98ee39..2b88992 100644
--- a/core/src/main/java/module-info.java
+++ b/core/src/main/java/module-info.java
@@ -9,6 +9,7 @@ module com.github.gtache.autosubtitle.core {
requires org.apache.logging.log4j;
exports com.github.gtache.autosubtitle.impl;
+ exports com.github.gtache.autosubtitle.archive.impl;
exports com.github.gtache.autosubtitle.process.impl;
exports com.github.gtache.autosubtitle.setup.impl;
exports com.github.gtache.autosubtitle.subtitle.impl;
diff --git a/core/src/test/java/com/github/gtache/autosubtitle/archive/impl/TestZipDecompresser.java b/core/src/test/java/com/github/gtache/autosubtitle/archive/impl/TestZipDecompresser.java
new file mode 100644
index 0000000..c89c8cf
--- /dev/null
+++ b/core/src/test/java/com/github/gtache/autosubtitle/archive/impl/TestZipDecompresser.java
@@ -0,0 +1,56 @@
+package com.github.gtache.autosubtitle.archive.impl;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class TestZipDecompresser {
+
+ private final ZipDecompresser zipDecompresser;
+
+ TestZipDecompresser() {
+ this.zipDecompresser = new ZipDecompresser();
+ }
+
+ @Test
+ void testIsPathSupported() {
+ assertTrue(zipDecompresser.isPathSupported(Path.of("test.zip")));
+ assertFalse(zipDecompresser.isPathSupported(Path.of("test")));
+ assertFalse(zipDecompresser.isPathSupported(Path.of("test.txt")));
+ assertFalse(zipDecompresser.isPathSupported(Path.of("test.zip2")));
+ }
+
+ @Test
+ void testDecompress(@TempDir final Path tempDir) throws IOException {
+ final var file = tempDir.resolve("test.zip");
+ try (final var in = getClass().getResourceAsStream("in.zip")) {
+ Files.copy(in, file);
+ }
+ zipDecompresser.decompress(file, tempDir);
+ final var inTxt = tempDir.resolve("in.txt");
+ final var bin = tempDir.resolve("bin");
+ final var binTxt = bin.resolve("bin.txt");
+ final var lib = tempDir.resolve("lib");
+ final var libTxt = lib.resolve("lib.txt");
+
+ assertTrue(Files.exists(inTxt));
+ assertEquals("in", Files.readString(inTxt));
+ assertTrue(Files.exists(bin));
+ assertTrue(Files.exists(binTxt));
+ assertEquals("bin", Files.readString(binTxt));
+ assertTrue(Files.exists(lib));
+ assertTrue(Files.exists(libTxt));
+ assertEquals("lib", Files.readString(libTxt));
+ }
+
+ @Test
+ void testIllegal() {
+ assertThrows(IllegalArgumentException.class, () -> zipDecompresser.decompress(Paths.get("file.txt"), Paths.get("target")));
+ }
+}
diff --git a/core/src/test/java/com/github/gtache/autosubtitle/process/impl/TestAbstractProcessRunner.java b/core/src/test/java/com/github/gtache/autosubtitle/process/impl/TestAbstractProcessRunner.java
index 7e7b8de..f9a0405 100644
--- a/core/src/test/java/com/github/gtache/autosubtitle/process/impl/TestAbstractProcessRunner.java
+++ b/core/src/test/java/com/github/gtache/autosubtitle/process/impl/TestAbstractProcessRunner.java
@@ -22,7 +22,7 @@ class TestAbstractProcessRunner {
@Test
void testRun() throws IOException {
final var expected = new ProcessResultImpl(0, List.of("1", "2", "3"));
- final var actual = dummyProcessRunner.run(ARGS);
+ final var actual = dummyProcessRunner.run(ARGS, Duration.ofSeconds(5));
assertEquals(expected, actual);
}
@@ -50,7 +50,7 @@ class TestAbstractProcessRunner {
@Test
void testRunListen() throws IOException {
- final var result = dummyProcessRunner.runListen(ARGS);
+ final var result = dummyProcessRunner.runListen(ARGS, Duration.ofSeconds(5));
assertEquals(0, result.exitCode());
assertEquals(List.of("1", "2", "3"), result.output());
}
diff --git a/core/src/test/java/com/github/gtache/autosubtitle/subtitle/converter/impl/TestSRTSubtitleConverter.java b/core/src/test/java/com/github/gtache/autosubtitle/subtitle/converter/impl/TestSRTSubtitleConverter.java
index 13d827a..46e6be1 100644
--- a/core/src/test/java/com/github/gtache/autosubtitle/subtitle/converter/impl/TestSRTSubtitleConverter.java
+++ b/core/src/test/java/com/github/gtache/autosubtitle/subtitle/converter/impl/TestSRTSubtitleConverter.java
@@ -50,7 +50,7 @@ class TestSRTSubtitleConverter {
final var end2 = 12L * 60 * 60 * 1000 + 23 * 60 * 1000 + 34 * 1000 + 457;
final var subtitle1 = new SubtitleImpl("test5 test6\ntest7 test8", start1, end1, null, null);
final var subtitle2 = new SubtitleImpl("test1 test2\ntest3 test4", start2, end2, null, null);
- final var subtitles = new SubtitleCollectionImpl(subtitle1.content() + " " + subtitle2.content(), Arrays.asList(subtitle1, subtitle2), language);
+ final var subtitles = new SubtitleCollectionImpl<>(subtitle1.content() + " " + subtitle2.content(), Arrays.asList(subtitle1, subtitle2), language);
final var converter = new SRTSubtitleConverter(translator);
assertEquals(subtitles, converter.parse(in));
assertEquals(in, converter.format(subtitles));
diff --git a/core/src/test/java/com/github/gtache/autosubtitle/subtitle/extractor/impl/TestAbstractSubtitleExtractor.java b/core/src/test/java/com/github/gtache/autosubtitle/subtitle/extractor/impl/TestAbstractSubtitleExtractor.java
index b7811a6..2e80774 100644
--- a/core/src/test/java/com/github/gtache/autosubtitle/subtitle/extractor/impl/TestAbstractSubtitleExtractor.java
+++ b/core/src/test/java/com/github/gtache/autosubtitle/subtitle/extractor/impl/TestAbstractSubtitleExtractor.java
@@ -3,6 +3,7 @@ package com.github.gtache.autosubtitle.subtitle.extractor.impl;
import com.github.gtache.autosubtitle.Audio;
import com.github.gtache.autosubtitle.Language;
import com.github.gtache.autosubtitle.Video;
+import com.github.gtache.autosubtitle.subtitle.Subtitle;
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
import com.github.gtache.autosubtitle.subtitle.extractor.ExtractEvent;
import com.github.gtache.autosubtitle.subtitle.extractor.ExtractException;
@@ -56,12 +57,12 @@ class TestAbstractSubtitleExtractor {
private static final class DummySubtitleExtractor extends AbstractSubtitleExtractor {
@Override
- public SubtitleCollection extract(final Video video, final Language language, final ExtractionModel model) throws ExtractException {
+ public SubtitleCollection extract(final Video video, final Language language, final ExtractionModel model) throws ExtractException {
throw new UnsupportedOperationException();
}
@Override
- public SubtitleCollection extract(final Audio audio, final Language language, final ExtractionModel model) throws ExtractException {
+ public SubtitleCollection extract(final Audio audio, final Language language, final ExtractionModel model) throws ExtractException {
throw new UnsupportedOperationException();
}
}
diff --git a/core/src/test/resources/com/github/gtache/autosubtitle/archive/impl/in.txt b/core/src/test/resources/com/github/gtache/autosubtitle/archive/impl/in.txt
new file mode 100644
index 0000000..f087d89
--- /dev/null
+++ b/core/src/test/resources/com/github/gtache/autosubtitle/archive/impl/in.txt
@@ -0,0 +1 @@
+in
\ No newline at end of file
diff --git a/core/src/test/resources/com/github/gtache/autosubtitle/archive/impl/in.zip b/core/src/test/resources/com/github/gtache/autosubtitle/archive/impl/in.zip
new file mode 100644
index 0000000..9261792
Binary files /dev/null and b/core/src/test/resources/com/github/gtache/autosubtitle/archive/impl/in.zip differ
diff --git a/core/src/test/resources/log4j2-test.xml b/core/src/test/resources/log4j2-test.xml
new file mode 100644
index 0000000..98cfd73
--- /dev/null
+++ b/core/src/test/resources/log4j2-test.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/deepl/src/main/java/com/github/gtache/autosubtitle/deepl/DeepLTranslator.java b/deepl/src/main/java/com/github/gtache/autosubtitle/deepl/DeepLTranslator.java
index 3fa8cb4..b5b302b 100644
--- a/deepl/src/main/java/com/github/gtache/autosubtitle/deepl/DeepLTranslator.java
+++ b/deepl/src/main/java/com/github/gtache/autosubtitle/deepl/DeepLTranslator.java
@@ -10,7 +10,7 @@ import javax.inject.Inject;
/**
* DeepL implementation of {@link Translator}
*/
-public class DeepLTranslator implements Translator {
+public class DeepLTranslator implements Translator {
@Inject
DeepLTranslator() {
@@ -32,7 +32,7 @@ public class DeepLTranslator implements Translator {
}
@Override
- public SubtitleCollection translate(final SubtitleCollection collection, final Language to) {
+ public SubtitleCollection translate(final SubtitleCollection> collection, final Language to) {
return null;
}
}
diff --git a/ffmpeg/pom.xml b/ffmpeg/pom.xml
index 1ae6047..2f2b087 100644
--- a/ffmpeg/pom.xml
+++ b/ffmpeg/pom.xml
@@ -24,6 +24,11 @@
org.tukaani
xz
+
+ org.apache.logging.log4j
+ log4j-core
+ test
+
\ No newline at end of file
diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/TarDecompresser.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/archive/ffmpeg/TarArchiver.java
similarity index 73%
rename from ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/TarDecompresser.java
rename to ffmpeg/src/main/java/com/github/gtache/autosubtitle/archive/ffmpeg/TarArchiver.java
index a86568d..6e9bc37 100644
--- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/TarDecompresser.java
+++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/archive/ffmpeg/TarArchiver.java
@@ -1,17 +1,31 @@
-package com.github.gtache.autosubtitle.setup.ffmpeg;
+package com.github.gtache.autosubtitle.archive.ffmpeg;
-import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
+import com.github.gtache.autosubtitle.archive.Archiver;
+import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
+import javax.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.List;
/**
- * Tar implementation of {@link Decompresser}
+ * Tar implementation of {@link Archiver}
*/
-public class TarDecompresser implements Decompresser {
+public class TarArchiver implements Archiver {
+
+ @Inject
+ TarArchiver() {
+
+ }
+
+ @Override
+ public void compress(final List files, final Path destination) throws IOException {
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
@Override
public void decompress(final Path archive, final Path destination) throws IOException {
if (!isPathSupported(archive)) {
@@ -38,7 +52,7 @@ public class TarDecompresser implements Decompresser {
}
}
- private static Path newFile(final Path destinationDir, final TarArchiveEntry entry) throws IOException {
+ private static Path newFile(final Path destinationDir, final ArchiveEntry entry) throws IOException {
final var destPath = destinationDir.resolve(entry.getName());
final var destDirPath = destinationDir.toAbsolutePath().toString();
@@ -51,7 +65,7 @@ public class TarDecompresser implements Decompresser {
}
@Override
- public boolean isPathSupported(final Path path) {
- return path.getFileName().toString().endsWith(".tar");
+ public String archiveExtension() {
+ return "tar";
}
}
diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/archive/ffmpeg/XZArchiver.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/archive/ffmpeg/XZArchiver.java
new file mode 100644
index 0000000..3c13593
--- /dev/null
+++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/archive/ffmpeg/XZArchiver.java
@@ -0,0 +1,45 @@
+package com.github.gtache.autosubtitle.archive.ffmpeg;
+
+import com.github.gtache.autosubtitle.archive.Archiver;
+import org.tukaani.xz.XZInputStream;
+
+import javax.inject.Inject;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+
+/**
+ * XZ implementation of {@link Archiver}
+ */
+public class XZArchiver implements Archiver {
+
+ @Inject
+ XZArchiver() {
+
+ }
+
+ @Override
+ public void compress(final List files, final Path destination) throws IOException {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public void decompress(final Path archive, final Path destination) throws IOException {
+ if (!isPathSupported(archive)) {
+ throw new IllegalArgumentException("Unsupported path : " + archive);
+ }
+ final var archiveName = archive.getFileName().toString();
+ Files.createDirectories(destination);
+ final var destinationFile = destination.resolve(archiveName.substring(0, archiveName.lastIndexOf(".xz")));
+ try (final var xzIn = new XZInputStream(Files.newInputStream(archive));
+ final var out = Files.newOutputStream(destinationFile)) {
+ xzIn.transferTo(out);
+ }
+ }
+
+ @Override
+ public String archiveExtension() {
+ return "xz";
+ }
+}
diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/ffmpeg/FFmpegVideoConverter.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/ffmpeg/FFmpegVideoConverter.java
index afebd92..4180ac7 100644
--- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/ffmpeg/FFmpegVideoConverter.java
+++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/ffmpeg/FFmpegVideoConverter.java
@@ -8,17 +8,17 @@ import com.github.gtache.autosubtitle.impl.AudioInfoImpl;
import com.github.gtache.autosubtitle.impl.FileAudioImpl;
import com.github.gtache.autosubtitle.impl.FileVideoImpl;
import com.github.gtache.autosubtitle.impl.VideoInfoImpl;
-import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegBundledPath;
-import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegSystemPath;
+import com.github.gtache.autosubtitle.modules.setup.ffmpeg.FFmpegBundledPath;
+import com.github.gtache.autosubtitle.modules.setup.ffmpeg.FFmpegSystemPath;
import com.github.gtache.autosubtitle.process.impl.AbstractProcessRunner;
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter;
import javax.inject.Inject;
-import javax.inject.Singleton;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
@@ -32,30 +32,29 @@ import static java.util.Objects.requireNonNull;
/**
* FFmpeg implementation of {@link VideoConverter}
*/
-@Singleton
public class FFmpegVideoConverter extends AbstractProcessRunner implements VideoConverter {
private static final String TEMP_FILE_PREFIX = "autosubtitle";
private final Path bundledPath;
private final Path systemPath;
- private final SubtitleConverter subtitleConverter;
+ private final SubtitleConverter> subtitleConverter;
@Inject
FFmpegVideoConverter(@FFmpegBundledPath final Path bundledPath, @FFmpegSystemPath final Path systemPath, final Map subtitleConverters) {
this.bundledPath = requireNonNull(bundledPath);
this.systemPath = requireNonNull(systemPath);
- this.subtitleConverter = requireNonNull(subtitleConverters.get("srt"));
+ this.subtitleConverter = subtitleConverters.get("srt");
}
@Override
- public Video addSoftSubtitles(final Video video, final Collection subtitles) throws IOException {
- final var out = getTempFile("mkv"); //Soft ass subtitles are only supported by mkv apparently
+ public Video addSoftSubtitles(final Video video, final Collection extends SubtitleCollection>> subtitles) throws IOException {
+ final var out = getTempFile(video.info().videoFormat());
addSoftSubtitles(video, subtitles, out);
- return new FileVideoImpl(out, new VideoInfoImpl("mkv", video.info().width(), video.info().height(), video.info().duration()));
+ return new FileVideoImpl(out, new VideoInfoImpl(video.info().videoFormat(), video.info().width(), video.info().height(), video.info().duration()));
}
@Override
- public void addSoftSubtitles(final Video video, final Collection subtitles, final Path path) throws IOException {
+ public void addSoftSubtitles(final Video video, final Collection extends SubtitleCollection>> subtitles, final Path path) throws IOException {
final var videoPath = getPath(video);
final var collectionMap = dumpCollections(subtitles);
final var args = new ArrayList();
@@ -96,18 +95,18 @@ public class FFmpegVideoConverter extends AbstractProcessRunner implements Video
args.add("language=" + c.language().iso3());
});
args.add(path.toString());
- runListen(args);
+ runListen(args, Duration.ofHours(1));
}
@Override
- public Video addHardSubtitles(final Video video, final SubtitleCollection subtitles) throws IOException {
+ public Video addHardSubtitles(final Video video, final SubtitleCollection> subtitles) throws IOException {
final var out = getTempFile(video.info().videoFormat());
addHardSubtitles(video, subtitles, out);
return new FileVideoImpl(out, video.info());
}
@Override
- public void addHardSubtitles(final Video video, final SubtitleCollection subtitles, final Path path) throws IOException {
+ public void addHardSubtitles(final Video video, final SubtitleCollection> subtitles, final Path path) throws IOException {
final var videoPath = getPath(video);
final var subtitlesPath = dumpSubtitles(subtitles);
final var escapedPath = escapeVF(subtitlesPath.toString());
@@ -120,7 +119,7 @@ public class FFmpegVideoConverter extends AbstractProcessRunner implements Video
subtitleArg,
path.toString()
);
- runListen(args);
+ runListen(args, Duration.ofHours(1));
}
private static String escapeVF(final String path) {
@@ -145,7 +144,7 @@ public class FFmpegVideoConverter extends AbstractProcessRunner implements Video
"0:v",
dumpVideoPath.toString()
);
- runListen(args);
+ runListen(args, Duration.ofHours(1));
Files.deleteIfExists(dumpVideoPath);
return new FileAudioImpl(audioPath, new AudioInfoImpl("wav", video.info().duration()));
}
@@ -166,15 +165,15 @@ public class FFmpegVideoConverter extends AbstractProcessRunner implements Video
return path;
}
- private SequencedMap dumpCollections(final Collection collections) throws IOException {
- final var ret = LinkedHashMap.newLinkedHashMap(collections.size());
+ private > SequencedMap dumpCollections(final Collection collections) throws IOException {
+ final var ret = LinkedHashMap.newLinkedHashMap(collections.size());
for (final var subtitles : collections) {
ret.put(subtitles, dumpSubtitles(subtitles));
}
return ret;
}
- private Path dumpSubtitles(final SubtitleCollection subtitles) throws IOException {
+ private Path dumpSubtitles(final SubtitleCollection> subtitles) throws IOException {
final var path = getTempFile("srt");
Files.writeString(path, subtitleConverter.format(subtitles));
return path;
diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/ffmpeg/FFprobeVideoLoader.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/ffmpeg/FFprobeVideoLoader.java
index 272d55b..97d965e 100644
--- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/ffmpeg/FFprobeVideoLoader.java
+++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/ffmpeg/FFprobeVideoLoader.java
@@ -4,22 +4,22 @@ import com.github.gtache.autosubtitle.Video;
import com.github.gtache.autosubtitle.VideoLoader;
import com.github.gtache.autosubtitle.impl.FileVideoImpl;
import com.github.gtache.autosubtitle.impl.VideoInfoImpl;
-import com.github.gtache.autosubtitle.modules.ffmpeg.FFprobeBundledPath;
-import com.github.gtache.autosubtitle.modules.ffmpeg.FFprobeSystemPath;
+import com.github.gtache.autosubtitle.modules.setup.ffmpeg.FFprobeBundledPath;
+import com.github.gtache.autosubtitle.modules.setup.ffmpeg.FFprobeSystemPath;
import com.github.gtache.autosubtitle.process.impl.AbstractProcessRunner;
import javax.inject.Inject;
-import javax.inject.Singleton;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.time.Duration;
+import java.util.List;
import static java.util.Objects.requireNonNull;
/**
* FFprobe implementation of {@link VideoLoader}
*/
-@Singleton
public class FFprobeVideoLoader extends AbstractProcessRunner implements VideoLoader {
private final Path bundledPath;
@@ -33,7 +33,7 @@ public class FFprobeVideoLoader extends AbstractProcessRunner implements VideoLo
@Override
public Video loadVideo(final Path path) throws IOException {
- final var result = run(getFFprobePath(), "-v", "error", "-select_streams", "v", "-show_entries", "stream=width,height,duration", "-of", "csv=p=0", path.toString());
+ final var result = run(List.of(getFFprobePath(), "-v", "error", "-select_streams", "v", "-show_entries", "stream=width,height,duration", "-of", "csv=p=0", path.toString()), Duration.ofSeconds(5));
final var resolution = result.output().getLast();
final var split = resolution.split(",");
final var width = Integer.parseInt(split[0]);
diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFBundledRoot.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFBundledRoot.java
similarity index 86%
rename from ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFBundledRoot.java
rename to ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFBundledRoot.java
index b96beb8..104d206 100644
--- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFBundledRoot.java
+++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFBundledRoot.java
@@ -1,4 +1,4 @@
-package com.github.gtache.autosubtitle.modules.ffmpeg;
+package com.github.gtache.autosubtitle.modules.setup.ffmpeg;
import javax.inject.Qualifier;
import java.lang.annotation.Documented;
diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFProbeInstallerPath.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFProbeInstallerPath.java
similarity index 86%
rename from ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFProbeInstallerPath.java
rename to ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFProbeInstallerPath.java
index b9a6142..83d0969 100644
--- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFProbeInstallerPath.java
+++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFProbeInstallerPath.java
@@ -1,4 +1,4 @@
-package com.github.gtache.autosubtitle.modules.ffmpeg;
+package com.github.gtache.autosubtitle.modules.setup.ffmpeg;
import javax.inject.Qualifier;
import java.lang.annotation.Documented;
diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegBundledPath.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFmpegBundledPath.java
similarity index 86%
rename from ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegBundledPath.java
rename to ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFmpegBundledPath.java
index 3d16cf7..4b7ef39 100644
--- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegBundledPath.java
+++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFmpegBundledPath.java
@@ -1,4 +1,4 @@
-package com.github.gtache.autosubtitle.modules.ffmpeg;
+package com.github.gtache.autosubtitle.modules.setup.ffmpeg;
import javax.inject.Qualifier;
import java.lang.annotation.Documented;
diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegInstallerPath.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFmpegInstallerPath.java
similarity index 86%
rename from ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegInstallerPath.java
rename to ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFmpegInstallerPath.java
index 3e9d794..bd86030 100644
--- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegInstallerPath.java
+++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFmpegInstallerPath.java
@@ -1,4 +1,4 @@
-package com.github.gtache.autosubtitle.modules.ffmpeg;
+package com.github.gtache.autosubtitle.modules.setup.ffmpeg;
import javax.inject.Qualifier;
import java.lang.annotation.Documented;
diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFmpegSetupModule.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFmpegSetupModule.java
index c617df7..acef204 100644
--- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFmpegSetupModule.java
+++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFmpegSetupModule.java
@@ -1,24 +1,17 @@
package com.github.gtache.autosubtitle.modules.setup.ffmpeg;
+import com.github.gtache.autosubtitle.archive.Archiver;
+import com.github.gtache.autosubtitle.archive.ffmpeg.TarArchiver;
+import com.github.gtache.autosubtitle.archive.ffmpeg.XZArchiver;
import com.github.gtache.autosubtitle.impl.Architecture;
import com.github.gtache.autosubtitle.impl.OS;
-import com.github.gtache.autosubtitle.modules.ffmpeg.FFBundledRoot;
-import com.github.gtache.autosubtitle.modules.ffmpeg.FFProbeInstallerPath;
-import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegBundledPath;
-import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegInstallerPath;
-import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegSystemPath;
-import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegVersion;
-import com.github.gtache.autosubtitle.modules.ffmpeg.FFprobeBundledPath;
-import com.github.gtache.autosubtitle.modules.ffmpeg.FFprobeSystemPath;
import com.github.gtache.autosubtitle.modules.impl.ExecutableExtension;
+import com.github.gtache.autosubtitle.modules.setup.impl.CacheRoot;
+import com.github.gtache.autosubtitle.modules.setup.impl.ToolsRoot;
import com.github.gtache.autosubtitle.modules.setup.impl.VideoConverterSetup;
import com.github.gtache.autosubtitle.setup.SetupManager;
-import com.github.gtache.autosubtitle.setup.ffmpeg.Decompresser;
import com.github.gtache.autosubtitle.setup.ffmpeg.FFmpegSetupConfiguration;
import com.github.gtache.autosubtitle.setup.ffmpeg.FFmpegSetupManager;
-import com.github.gtache.autosubtitle.setup.ffmpeg.TarDecompresser;
-import com.github.gtache.autosubtitle.setup.ffmpeg.XZDecompresser;
-import com.github.gtache.autosubtitle.setup.ffmpeg.ZipDecompresser;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
@@ -41,20 +34,16 @@ public abstract class FFmpegSetupModule {
}
- @Binds
- @StringKey("zip")
- @IntoMap
- abstract Decompresser bindsZipDecompresser(final ZipDecompresser decompresser);
@Binds
@StringKey("tar")
@IntoMap
- abstract Decompresser bindsTarDecompresser(final TarDecompresser decompresser);
+ abstract Archiver bindsTarDecompresser(final TarArchiver decompresser);
@Binds
@StringKey("xz")
@IntoMap
- abstract Decompresser bindsXzDecompresser(final XZDecompresser decompresser);
+ abstract Archiver bindsXZDecompresser(final XZArchiver decompresser);
@Binds
@VideoConverterSetup
@@ -69,19 +58,19 @@ public abstract class FFmpegSetupModule {
@Provides
@FFmpegInstallerPath
- static Path providesFFmpegInstallerPath(@FFBundledRoot final Path root, final OS os) {
- return root.resolve("cache").resolve("ffmpeg-installer" + getInstallerExtension(os));
+ static Path providesFFmpegInstallerPath(@CacheRoot final Path root, final OS os) {
+ return root.resolve(FFMPEG + "-installer" + getInstallerExtension(os));
}
@Provides
@FFProbeInstallerPath
- static Path providesFFProbeInstallerPath(@FFBundledRoot final Path root, final OS os) {
- return root.resolve("cache").resolve("ffprobe-installer" + getInstallerExtension(os));
+ static Path providesFFProbeInstallerPath(@CacheRoot final Path root, final OS os) {
+ return root.resolve(FFPROBE + "-installer" + getInstallerExtension(os));
}
private static String getInstallerExtension(final OS os) {
if (os == OS.LINUX) {
- return ".tar.gz";
+ return ".tar.xz";
} else {
return ".zip";
}
@@ -89,8 +78,8 @@ public abstract class FFmpegSetupModule {
@Provides
@FFBundledRoot
- static Path providesFFBundledRoot() {
- return Paths.get("tools", FFMPEG);
+ static Path providesFFBundledRoot(@ToolsRoot final Path root) {
+ return root.resolve(FFMPEG);
}
@Provides
diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegSystemPath.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFmpegSystemPath.java
similarity index 86%
rename from ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegSystemPath.java
rename to ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFmpegSystemPath.java
index 6b58fa3..c393a77 100644
--- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegSystemPath.java
+++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFmpegSystemPath.java
@@ -1,4 +1,4 @@
-package com.github.gtache.autosubtitle.modules.ffmpeg;
+package com.github.gtache.autosubtitle.modules.setup.ffmpeg;
import javax.inject.Qualifier;
import java.lang.annotation.Documented;
diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegVersion.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFmpegVersion.java
similarity index 86%
rename from ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegVersion.java
rename to ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFmpegVersion.java
index a883339..c798f05 100644
--- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegVersion.java
+++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFmpegVersion.java
@@ -1,4 +1,4 @@
-package com.github.gtache.autosubtitle.modules.ffmpeg;
+package com.github.gtache.autosubtitle.modules.setup.ffmpeg;
import javax.inject.Qualifier;
import java.lang.annotation.Documented;
diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFprobeBundledPath.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFprobeBundledPath.java
similarity index 86%
rename from ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFprobeBundledPath.java
rename to ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFprobeBundledPath.java
index 9869e9e..df69cf1 100644
--- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFprobeBundledPath.java
+++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFprobeBundledPath.java
@@ -1,4 +1,4 @@
-package com.github.gtache.autosubtitle.modules.ffmpeg;
+package com.github.gtache.autosubtitle.modules.setup.ffmpeg;
import javax.inject.Qualifier;
import java.lang.annotation.Documented;
diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFprobeSystemPath.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFprobeSystemPath.java
similarity index 86%
rename from ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFprobeSystemPath.java
rename to ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFprobeSystemPath.java
index b1ec1a6..f63e040 100644
--- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFprobeSystemPath.java
+++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFprobeSystemPath.java
@@ -1,4 +1,4 @@
-package com.github.gtache.autosubtitle.modules.ffmpeg;
+package com.github.gtache.autosubtitle.modules.setup.ffmpeg;
import javax.inject.Qualifier;
import java.lang.annotation.Documented;
diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/Decompresser.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/Decompresser.java
deleted file mode 100644
index d93f9db..0000000
--- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/Decompresser.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.github.gtache.autosubtitle.setup.ffmpeg;
-
-import java.io.IOException;
-import java.nio.file.Path;
-
-/**
- * Unzips files
- */
-public interface Decompresser {
-
- /**
- * Unzips an archive to the given destination
- *
- * @param archive The archive
- * @param destination The destination folder
- * @throws IOException if an error occurs
- */
- void decompress(final Path archive, final Path destination) throws IOException;
-
- /**
- * Checks whether the given file is supported by the decompresser
- *
- * @param path The file path
- * @return True if the file is supported
- */
- boolean isPathSupported(final Path path);
-}
diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/FFmpegSetupManager.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/FFmpegSetupManager.java
index 81a6af2..9ec7533 100644
--- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/FFmpegSetupManager.java
+++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/FFmpegSetupManager.java
@@ -1,5 +1,6 @@
package com.github.gtache.autosubtitle.setup.ffmpeg;
+import com.github.gtache.autosubtitle.archive.Archiver;
import com.github.gtache.autosubtitle.impl.Architecture;
import com.github.gtache.autosubtitle.setup.SetupException;
import com.github.gtache.autosubtitle.setup.SetupManager;
@@ -13,6 +14,8 @@ import java.io.IOException;
import java.net.http.HttpClient;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.time.Duration;
+import java.util.List;
import java.util.Map;
import static com.github.gtache.autosubtitle.impl.Architecture.ARMEL;
@@ -26,14 +29,14 @@ import static java.util.Objects.requireNonNull;
public class FFmpegSetupManager extends AbstractSetupManager {
private static final Logger logger = LogManager.getLogger(FFmpegSetupManager.class);
private final FFmpegSetupConfiguration configuration;
- private final Map decompressers;
+ private final Map archivers;
@Inject
- FFmpegSetupManager(final FFmpegSetupConfiguration configuration, final Map decompressers,
+ FFmpegSetupManager(final FFmpegSetupConfiguration configuration, final Map archivers,
final HttpClient httpClient) {
super(httpClient);
this.configuration = requireNonNull(configuration);
- this.decompressers = Map.copyOf(decompressers);
+ this.archivers = Map.copyOf(archivers);
}
@Override
@@ -132,7 +135,7 @@ public class FFmpegSetupManager extends AbstractSetupManager {
try {
final var filename = from.getFileName().toString();
final var extension = filename.substring(filename.lastIndexOf('.') + 1);
- decompressers.get(extension).decompress(from, to);
+ archivers.get(extension).decompress(from, to);
} catch (final IOException e) {
throw new SetupException(e);
}
@@ -188,7 +191,7 @@ public class FFmpegSetupManager extends AbstractSetupManager {
}
private boolean checkSystemFFmpeg() throws IOException {
- final var result = run(configuration.systemFFmpegPath().toString(), "-version");
+ final var result = run(List.of(configuration.systemFFmpegPath().toString(), "-version"), Duration.ofSeconds(5));
return result.exitCode() == 0;
}
diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/XZDecompresser.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/XZDecompresser.java
deleted file mode 100644
index 81b4e77..0000000
--- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/XZDecompresser.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.github.gtache.autosubtitle.setup.ffmpeg;
-
-import org.tukaani.xz.XZInputStream;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-/**
- * XZ implementation of {@link Decompresser}
- */
-public class XZDecompresser implements Decompresser {
- @Override
- public void decompress(final Path archive, final Path destination) throws IOException {
- if (!isPathSupported(archive)) {
- throw new IllegalArgumentException("Unsupported path : " + archive);
- }
- try (final var xzIn = new XZInputStream(Files.newInputStream(archive));
- final var out = Files.newOutputStream(destination)) {
- xzIn.transferTo(out);
- }
- }
-
- @Override
- public boolean isPathSupported(final Path path) {
- return path.getFileName().toString().endsWith(".xz");
- }
-}
diff --git a/ffmpeg/src/main/java/module-info.java b/ffmpeg/src/main/java/module-info.java
index 59d58fa..4605994 100644
--- a/ffmpeg/src/main/java/module-info.java
+++ b/ffmpeg/src/main/java/module-info.java
@@ -15,4 +15,5 @@ module com.github.gtache.autosubtitle.ffmpeg {
exports com.github.gtache.autosubtitle.modules.ffmpeg;
exports com.github.gtache.autosubtitle.modules.setup.ffmpeg;
+ exports com.github.gtache.autosubtitle.archive.ffmpeg;
}
\ No newline at end of file
diff --git a/ffmpeg/src/test/java/com/github/gtache/autosubtitle/archive/ffmpeg/TestTarArchiver.java b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/archive/ffmpeg/TestTarArchiver.java
new file mode 100644
index 0000000..0b1e818
--- /dev/null
+++ b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/archive/ffmpeg/TestTarArchiver.java
@@ -0,0 +1,56 @@
+package com.github.gtache.autosubtitle.archive.ffmpeg;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class TestTarArchiver {
+
+ private final TarArchiver tarArchiver;
+
+ TestTarArchiver() {
+ this.tarArchiver = new TarArchiver();
+ }
+
+ @Test
+ void testIsPathSupported() {
+ assertTrue(tarArchiver.isPathSupported(Path.of("test.tar")));
+ assertFalse(tarArchiver.isPathSupported(Path.of("test")));
+ assertFalse(tarArchiver.isPathSupported(Path.of("test.txt")));
+ assertFalse(tarArchiver.isPathSupported(Path.of("test.tar2")));
+ }
+
+ @Test
+ void testDecompress(@TempDir final Path tempDir) throws IOException {
+ final var file = tempDir.resolve("test.tar");
+ try (final var in = getClass().getResourceAsStream("in.tar")) {
+ Files.copy(in, file);
+ }
+ tarArchiver.decompress(file, tempDir);
+ final var inTxt = tempDir.resolve("in.txt");
+ final var bin = tempDir.resolve("bin");
+ final var binTxt = bin.resolve("bin.txt");
+ final var lib = tempDir.resolve("lib");
+ final var libTxt = lib.resolve("lib.txt");
+
+ assertTrue(Files.exists(inTxt));
+ assertEquals("in", Files.readString(inTxt));
+ assertTrue(Files.exists(bin));
+ assertTrue(Files.exists(binTxt));
+ assertEquals("bin", Files.readString(binTxt));
+ assertTrue(Files.exists(lib));
+ assertTrue(Files.exists(libTxt));
+ assertEquals("lib", Files.readString(libTxt));
+ }
+
+ @Test
+ void testIllegal() {
+ assertThrows(IllegalArgumentException.class, () -> tarArchiver.decompress(Paths.get("file.txt"), Paths.get("target")));
+ }
+}
diff --git a/ffmpeg/src/test/java/com/github/gtache/autosubtitle/archive/ffmpeg/TestXZArchiver.java b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/archive/ffmpeg/TestXZArchiver.java
new file mode 100644
index 0000000..14ba0f6
--- /dev/null
+++ b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/archive/ffmpeg/TestXZArchiver.java
@@ -0,0 +1,46 @@
+package com.github.gtache.autosubtitle.archive.ffmpeg;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class TestXZArchiver {
+
+ private final XZArchiver xzArchiver;
+
+ TestXZArchiver() {
+ this.xzArchiver = new XZArchiver();
+ }
+
+ @Test
+ void testIsPathSupported() {
+ assertTrue(xzArchiver.isPathSupported(Path.of("test.xz")));
+ assertFalse(xzArchiver.isPathSupported(Path.of("test")));
+ assertFalse(xzArchiver.isPathSupported(Path.of("test.txt")));
+ assertFalse(xzArchiver.isPathSupported(Path.of("test.xz2")));
+ }
+
+ @Test
+ void testDecompress(@TempDir final Path tempDir) throws IOException {
+ final var file = tempDir.resolve("in.txt.xz");
+ try (final var in = getClass().getResourceAsStream("in.txt.xz")) {
+ Files.copy(in, file);
+ }
+ xzArchiver.decompress(file, tempDir);
+ final var inTxt = tempDir.resolve("in.txt");
+
+ assertTrue(Files.isRegularFile(inTxt));
+ assertEquals("in", Files.readString(inTxt));
+ }
+
+ @Test
+ void testIllegal() {
+ assertThrows(IllegalArgumentException.class, () -> xzArchiver.decompress(Paths.get("file.txt"), Paths.get("target")));
+ }
+}
diff --git a/ffmpeg/src/test/java/com/github/gtache/autosubtitle/ffmpeg/TestFFmpegVideoConverter.java b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/ffmpeg/TestFFmpegVideoConverter.java
new file mode 100644
index 0000000..7ce2845
--- /dev/null
+++ b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/ffmpeg/TestFFmpegVideoConverter.java
@@ -0,0 +1,57 @@
+package com.github.gtache.autosubtitle.ffmpeg;
+
+import com.github.gtache.autosubtitle.Video;
+import com.github.gtache.autosubtitle.VideoInfo;
+import com.github.gtache.autosubtitle.impl.OS;
+import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
+import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.Map;
+import java.util.Objects;
+
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class TestFFmpegVideoConverter {
+
+ private final FFmpegVideoConverter converter;
+ private final SubtitleConverter subtitleConverter;
+ private final Video video;
+ private final VideoInfo videoInfo;
+ private final Path tmpFile;
+ private final Path outputPath;
+ private final SubtitleCollection> collection;
+
+ TestFFmpegVideoConverter(@Mock final SubtitleConverter subtitleConverter, @Mock final Video video,
+ @Mock final VideoInfo videoInfo, @Mock final SubtitleCollection> collection) throws IOException {
+ final var output = (OS.getOS() == OS.WINDOWS ? System.getProperty("java.io.tmpdir") : "/tmp");
+ final var resource = OS.getOS() == OS.WINDOWS ? "fake-ffmpeg.exe" : "fake-ffmpeg.sh";
+ this.video = Objects.requireNonNull(video);
+ this.videoInfo = Objects.requireNonNull(videoInfo);
+ when(video.info()).thenReturn(videoInfo);
+ this.tmpFile = Files.createTempFile("fake-ffmpeg", resource.substring(resource.lastIndexOf('.')));
+ try (final var in = getClass().getResourceAsStream(resource)) {
+ Files.copy(in, tmpFile, StandardCopyOption.REPLACE_EXISTING);
+ }
+ this.outputPath = Path.of(output, "test-ffmpeg-output.txt");
+ this.subtitleConverter = Objects.requireNonNull(subtitleConverter);
+ this.converter = new FFmpegVideoConverter(tmpFile, tmpFile, Map.of("srt", subtitleConverter));
+ this.collection = Objects.requireNonNull(collection);
+ }
+
+ @AfterEach
+ void afterEach() throws IOException {
+ Files.deleteIfExists(tmpFile);
+ Files.deleteIfExists(outputPath);
+ }
+
+ //TODO tests
+}
diff --git a/ffmpeg/src/test/java/com/github/gtache/autosubtitle/ffmpeg/TestFFmpegVideoLoader.java b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/ffmpeg/TestFFmpegVideoLoader.java
new file mode 100644
index 0000000..faef35e
--- /dev/null
+++ b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/ffmpeg/TestFFmpegVideoLoader.java
@@ -0,0 +1,51 @@
+package com.github.gtache.autosubtitle.ffmpeg;
+
+import com.github.gtache.autosubtitle.impl.FileVideoImpl;
+import com.github.gtache.autosubtitle.impl.OS;
+import com.github.gtache.autosubtitle.impl.VideoInfoImpl;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class TestFFmpegVideoLoader {
+
+ private final FFprobeVideoLoader loader;
+ private final Path tmpFile;
+ private final Path outputPath;
+
+ TestFFmpegVideoLoader() throws IOException {
+ final var output = (OS.getOS() == OS.WINDOWS ? System.getProperty("java.io.tmpdir") : "/tmp");
+ final var resource = OS.getOS() == OS.WINDOWS ? "fake-ffprobe.exe" : "fake-ffprobe.sh";
+ this.tmpFile = Files.createTempFile("fake-ffprobe", resource.substring(resource.lastIndexOf('.')));
+ try (final var in = getClass().getResourceAsStream(resource)) {
+ Files.copy(in, tmpFile, StandardCopyOption.REPLACE_EXISTING);
+ }
+ this.outputPath = Path.of(output, "test-ffprobe-output.txt");
+ this.loader = new FFprobeVideoLoader(tmpFile, tmpFile);
+ }
+
+ @AfterEach
+ void afterEach() throws IOException {
+ Files.deleteIfExists(tmpFile);
+ Files.deleteIfExists(outputPath);
+ }
+
+ @Test
+ @Disabled("Doesn't work")
+ void testLoadVideo() throws IOException {
+ final var in = Paths.get("in.mp4");
+ final var expectedInfo = new VideoInfoImpl("mp4", 1920, 1080, 25000L);
+ final var expected = new FileVideoImpl(in, expectedInfo);
+ final var result = loader.loadVideo(in);
+ assertEquals(expected, result);
+ assertEquals("-v error -select_streams v -show_entries stream=width,height,duration -of csv=p=0 in.mp4", Files.readString(outputPath));
+ }
+}
diff --git a/ffmpeg/src/test/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/TestFFmpegSetupModule.java b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/TestFFmpegSetupModule.java
new file mode 100644
index 0000000..a685128
--- /dev/null
+++ b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/TestFFmpegSetupModule.java
@@ -0,0 +1,84 @@
+package com.github.gtache.autosubtitle.modules.setup.ffmpeg;
+
+import com.github.gtache.autosubtitle.impl.Architecture;
+import com.github.gtache.autosubtitle.impl.OS;
+import com.github.gtache.autosubtitle.setup.ffmpeg.FFmpegSetupConfiguration;
+import org.junit.jupiter.api.Test;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import static com.github.gtache.autosubtitle.impl.OS.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.mock;
+
+class TestFFmpegSetupModule {
+
+ private static final String FFMPEG = "ffmpeg";
+ private static final String FFPROBE = "ffprobe";
+
+ private final Path root;
+ private final String extension;
+
+ TestFFmpegSetupModule() {
+ this.root = Paths.get("root");
+ this.extension = "ext";
+ }
+
+ @Test
+ void testFFmpegSetupConfiguration() {
+ final var bundledPath = mock(Path.class);
+ final var systemPath = mock(Path.class);
+ final var ffmpegInstallerPath = mock(Path.class);
+ final var ffprobeInstallerPath = mock(Path.class);
+ final var os = mock(OS.class);
+ final var architecture = mock(Architecture.class);
+ final var expected = new FFmpegSetupConfiguration(root, bundledPath, systemPath, ffmpegInstallerPath, ffprobeInstallerPath, os, architecture);
+ assertEquals(expected, FFmpegSetupModule.providesFFmpegSetupConfiguration(root, bundledPath, systemPath, ffmpegInstallerPath, ffprobeInstallerPath, os, architecture));
+ }
+
+ @Test
+ void testProvidesFFmpegInstallerPath() {
+ assertEquals(root.resolve(FFMPEG + "-installer.tar.xz"), FFmpegSetupModule.providesFFmpegInstallerPath(root, LINUX));
+ assertEquals(root.resolve(FFMPEG + "-installer.zip"), FFmpegSetupModule.providesFFmpegInstallerPath(root, WINDOWS));
+ assertEquals(root.resolve(FFMPEG + "-installer.zip"), FFmpegSetupModule.providesFFmpegInstallerPath(root, MAC));
+ }
+
+ @Test
+ void testProvidesFFProbeInstallerPath() {
+ assertEquals(root.resolve(FFPROBE + "-installer.tar.xz"), FFmpegSetupModule.providesFFProbeInstallerPath(root, LINUX));
+ assertEquals(root.resolve(FFPROBE + "-installer.zip"), FFmpegSetupModule.providesFFProbeInstallerPath(root, WINDOWS));
+ assertEquals(root.resolve(FFPROBE + "-installer.zip"), FFmpegSetupModule.providesFFProbeInstallerPath(root, MAC));
+ }
+
+ @Test
+ void testProvidesBundledRoot() {
+ assertEquals(root.resolve(FFMPEG), FFmpegSetupModule.providesFFBundledRoot(root));
+ }
+
+ @Test
+ void testFFprobeBundledPath() {
+ assertEquals(root.resolve(FFPROBE + extension), FFmpegSetupModule.providesFFProbeBundledPath(root, extension));
+ }
+
+ @Test
+ void testFFprobeSystemPath() {
+ assertEquals(Paths.get(FFPROBE + extension), FFmpegSetupModule.providesFFProbeSystemPath(extension));
+ }
+
+
+ @Test
+ void testFFmpegBundledPath() {
+ assertEquals(root.resolve(FFMPEG + extension), FFmpegSetupModule.providesFFmpegBundledPath(root, extension));
+ }
+
+ @Test
+ void testFFmpegSystemPath() {
+ assertEquals(Paths.get(FFMPEG + extension), FFmpegSetupModule.providesFFmpegSystemPath(extension));
+ }
+
+ @Test
+ void testVersion() {
+ assertEquals("7.0.1", FFmpegSetupModule.providesFFmpegVersion());
+ }
+}
diff --git a/ffmpeg/src/test/java/com/github/gtache/autosubtitle/setup/ffmpeg/TestFFmpegSetupConfiguration.java b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/setup/ffmpeg/TestFFmpegSetupConfiguration.java
new file mode 100644
index 0000000..6652de6
--- /dev/null
+++ b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/setup/ffmpeg/TestFFmpegSetupConfiguration.java
@@ -0,0 +1,62 @@
+package com.github.gtache.autosubtitle.setup.ffmpeg;
+
+import com.github.gtache.autosubtitle.impl.Architecture;
+import com.github.gtache.autosubtitle.impl.OS;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.nio.file.Path;
+
+import static java.util.Objects.requireNonNull;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@ExtendWith(MockitoExtension.class)
+class TestFFmpegSetupConfiguration {
+
+ private final Path root;
+ private final Path bundledFFmpegPath;
+ private final Path systemFFmpegPath;
+ private final Path ffmpegInstallerPath;
+ private final Path ffprobeInstallerPath;
+ private final OS os;
+ private final Architecture architecture;
+
+ TestFFmpegSetupConfiguration(@Mock final Path root, @Mock final Path bundledFFmpegPath,
+ @Mock final Path systemFFmpegPath, @Mock final Path ffmpegInstallerPath,
+ @Mock final Path ffprobeInstallerPath, @Mock final OS os,
+ @Mock final Architecture architecture) {
+ this.root = requireNonNull(root);
+ this.bundledFFmpegPath = requireNonNull(bundledFFmpegPath);
+ this.systemFFmpegPath = requireNonNull(systemFFmpegPath);
+ this.ffmpegInstallerPath = requireNonNull(ffmpegInstallerPath);
+ this.ffprobeInstallerPath = requireNonNull(ffprobeInstallerPath);
+ this.os = requireNonNull(os);
+ this.architecture = requireNonNull(architecture);
+ }
+
+ @Test
+ void testGetters() {
+ final var config = new FFmpegSetupConfiguration(root, bundledFFmpegPath, systemFFmpegPath, ffmpegInstallerPath, ffprobeInstallerPath, os, architecture);
+ assertEquals(root, config.root());
+ assertEquals(bundledFFmpegPath, config.bundledFFmpegPath());
+ assertEquals(systemFFmpegPath, config.systemFFmpegPath());
+ assertEquals(ffmpegInstallerPath, config.ffmpegInstallerPath());
+ assertEquals(ffprobeInstallerPath, config.ffprobeInstallerPath());
+ assertEquals(os, config.os());
+ assertEquals(architecture, config.architecture());
+ }
+
+ @Test
+ void testIllegal() {
+ assertThrows(NullPointerException.class, () -> new FFmpegSetupConfiguration(null, bundledFFmpegPath, systemFFmpegPath, ffmpegInstallerPath, ffprobeInstallerPath, os, architecture));
+ assertThrows(NullPointerException.class, () -> new FFmpegSetupConfiguration(root, null, systemFFmpegPath, ffmpegInstallerPath, ffprobeInstallerPath, os, architecture));
+ assertThrows(NullPointerException.class, () -> new FFmpegSetupConfiguration(root, bundledFFmpegPath, null, ffmpegInstallerPath, ffprobeInstallerPath, os, architecture));
+ assertThrows(NullPointerException.class, () -> new FFmpegSetupConfiguration(root, bundledFFmpegPath, systemFFmpegPath, null, ffprobeInstallerPath, os, architecture));
+ assertThrows(NullPointerException.class, () -> new FFmpegSetupConfiguration(root, bundledFFmpegPath, systemFFmpegPath, ffmpegInstallerPath, null, os, architecture));
+ assertThrows(NullPointerException.class, () -> new FFmpegSetupConfiguration(root, bundledFFmpegPath, systemFFmpegPath, ffmpegInstallerPath, ffprobeInstallerPath, null, architecture));
+ assertThrows(NullPointerException.class, () -> new FFmpegSetupConfiguration(root, bundledFFmpegPath, systemFFmpegPath, ffmpegInstallerPath, ffprobeInstallerPath, os, null));
+ }
+}
diff --git a/ffmpeg/src/test/java/com/github/gtache/autosubtitle/setup/ffmpeg/TestFFmpegSetupManager.java b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/setup/ffmpeg/TestFFmpegSetupManager.java
new file mode 100644
index 0000000..e0cf5bc
--- /dev/null
+++ b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/setup/ffmpeg/TestFFmpegSetupManager.java
@@ -0,0 +1,4 @@
+package com.github.gtache.autosubtitle.setup.ffmpeg;
+
+class TestFFmpegSetupManager {
+}
diff --git a/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/archive/ffmpeg/bin.txt b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/archive/ffmpeg/bin.txt
new file mode 100644
index 0000000..c5e82d7
--- /dev/null
+++ b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/archive/ffmpeg/bin.txt
@@ -0,0 +1 @@
+bin
\ No newline at end of file
diff --git a/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/archive/ffmpeg/in.tar b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/archive/ffmpeg/in.tar
new file mode 100644
index 0000000..a565fa1
Binary files /dev/null and b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/archive/ffmpeg/in.tar differ
diff --git a/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/archive/ffmpeg/in.txt b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/archive/ffmpeg/in.txt
new file mode 100644
index 0000000..f087d89
--- /dev/null
+++ b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/archive/ffmpeg/in.txt
@@ -0,0 +1 @@
+in
\ No newline at end of file
diff --git a/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/archive/ffmpeg/in.txt.xz b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/archive/ffmpeg/in.txt.xz
new file mode 100644
index 0000000..d6019bd
Binary files /dev/null and b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/archive/ffmpeg/in.txt.xz differ
diff --git a/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/archive/ffmpeg/lib.txt b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/archive/ffmpeg/lib.txt
new file mode 100644
index 0000000..7951405
--- /dev/null
+++ b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/archive/ffmpeg/lib.txt
@@ -0,0 +1 @@
+lib
\ No newline at end of file
diff --git a/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffmpeg.exe b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffmpeg.exe
new file mode 100644
index 0000000..2bc1a21
Binary files /dev/null and b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffmpeg.exe differ
diff --git a/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffmpeg.ps1 b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffmpeg.ps1
new file mode 100644
index 0000000..cfd8317
--- /dev/null
+++ b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffmpeg.ps1
@@ -0,0 +1,5 @@
+$TempDir = [System.IO.Path]::GetTempPath()
+$Output = "$TempDir\test-ffmpeg-output.txt"
+
+Write-Output "$args" > $Output
+exit
\ No newline at end of file
diff --git a/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffmpeg.sh b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffmpeg.sh
new file mode 100644
index 0000000..b6cbfe0
--- /dev/null
+++ b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffmpeg.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+set -o errexit -o noclobber -o pipefail -o nounset
+
+output=/tmp/test-ffmpeg-output.txt
+
+echo "$@" >| $output
\ No newline at end of file
diff --git a/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffprobe.exe b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffprobe.exe
new file mode 100644
index 0000000..60c6ac7
Binary files /dev/null and b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffprobe.exe differ
diff --git a/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffprobe.ps1 b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffprobe.ps1
new file mode 100644
index 0000000..e11cb1a
--- /dev/null
+++ b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffprobe.ps1
@@ -0,0 +1,6 @@
+$TempDir = [System.IO.Path]::GetTempPath()
+$Output = "$TempDir\test-ffprobe-output.txt"
+
+Write-Output "$args" > $Output
+Write-Host "1920,1080,25"
+exit
\ No newline at end of file
diff --git a/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffprobe.sh b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffprobe.sh
new file mode 100644
index 0000000..438d99f
--- /dev/null
+++ b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffprobe.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+set -o errexit -o noclobber -o pipefail -o nounset
+
+output=/tmp/test-ffprobe-output.txt
+
+echo "$@" >| $output
+echo "1920,1080,25"
\ No newline at end of file
diff --git a/ffmpeg/src/test/resources/log4j2-test.xml b/ffmpeg/src/test/resources/log4j2-test.xml
new file mode 100644
index 0000000..98cfd73
--- /dev/null
+++ b/ffmpeg/src/test/resources/log4j2-test.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/MainModel.java b/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/MainModel.java
index ac1dc07..e69d43d 100644
--- a/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/MainModel.java
+++ b/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/MainModel.java
@@ -13,5 +13,5 @@ public interface MainModel {
/**
* @param index The index of the tab to select
*/
- void selectTab(int index);
+ void setSelectedTab(int index);
}
diff --git a/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/SetupModel.java b/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/SetupModel.java
index 4352515..e384775 100644
--- a/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/SetupModel.java
+++ b/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/SetupModel.java
@@ -19,20 +19,6 @@ public interface SetupModel {
*/
void setSubtitleExtractorStatus(SetupStatus status);
- /**
- * @return whether the subtitle extractor is installed
- */
- default boolean isSubtitleExtractorInstalled() {
- return subtitleExtractorStatus().isInstalled();
- }
-
- /**
- * @return whether an update is available for the subtitle extractor
- */
- default boolean isSubtitleExtractorUpdateAvailable() {
- return subtitleExtractorStatus() == SetupStatus.UPDATE_AVAILABLE;
- }
-
/**
* @return the progress of the subtitle extractor setup
*/
@@ -69,20 +55,6 @@ public interface SetupModel {
*/
void setVideoConverterStatus(SetupStatus status);
- /**
- * @return whether the video converter is installed
- */
- default boolean isVideoConverterInstalled() {
- return videoConverterStatus().isInstalled();
- }
-
- /**
- * @return whether an update is available for the video converter
- */
- default boolean isVideoConverterUpdateAvailable() {
- return videoConverterStatus() == SetupStatus.UPDATE_AVAILABLE;
- }
-
/**
* @return the progress of the video converter setup
*/
@@ -119,20 +91,6 @@ public interface SetupModel {
*/
void setTranslatorStatus(SetupStatus status);
- /**
- * @return whether the translator is installed
- */
- default boolean isTranslatorInstalled() {
- return translatorStatus().isInstalled();
- }
-
- /**
- * @return whether an update is available for the translator
- */
- default boolean isTranslatorUpdateAvailable() {
- return translatorStatus() == SetupStatus.UPDATE_AVAILABLE;
- }
-
/**
* @return the progress of the translator setup
*/
diff --git a/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/SubtitlesController.java b/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/SubtitlesController.java
new file mode 100644
index 0000000..d6987fc
--- /dev/null
+++ b/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/SubtitlesController.java
@@ -0,0 +1,44 @@
+package com.github.gtache.autosubtitle.gui;
+
+import com.github.gtache.autosubtitle.Language;
+
+import java.nio.file.Path;
+
+/**
+ * Controller for the subtitles view
+ */
+public interface SubtitlesController {
+
+ /**
+ * Selects the given language for edition
+ *
+ * @param language The language
+ */
+ void selectLanguage(final Language language);
+
+ /**
+ * Deletes a language
+ *
+ * @param language The language
+ */
+ void deleteLanguage(final Language language);
+
+ /**
+ * 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
+ */
+ SubtitlesModel model();
+}
diff --git a/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/SubtitlesModel.java b/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/SubtitlesModel.java
new file mode 100644
index 0000000..1dec1d9
--- /dev/null
+++ b/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/SubtitlesModel.java
@@ -0,0 +1,146 @@
+package com.github.gtache.autosubtitle.gui;
+
+import com.github.gtache.autosubtitle.Language;
+import com.github.gtache.autosubtitle.subtitle.Subtitle;
+import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Model for the subtitles view
+ *
+ * @param The type of subtitle
+ * @param The type of subtitle collection
+ */
+public interface SubtitlesModel> {
+
+ /**
+ * @return The list of available video languages
+ */
+ List availableVideoLanguages();
+
+ /**
+ * @return the video language
+ */
+ Language videoLanguage();
+
+ /**
+ * Sets the video language
+ *
+ * @param language The new language
+ */
+ void setVideoLanguage(Language language);
+
+ /**
+ * @return The list of available translations languages
+ */
+ List availableTranslationsLanguage();
+
+ /**
+ * @return The list of selected translations languages
+ */
+ List selectedTranslationsLanguages();
+
+ /**
+ * @return The currently selected language
+ */
+ Language selectedLanguage();
+
+ /**
+ * @param language The new selected language
+ */
+ void setSelectedLanguage(Language language);
+
+ /**
+ * @return The mapping of language to subtitles
+ */
+ Map collections();
+
+ /**
+ * @return The currently selected collection
+ */
+ U selectedCollection();
+
+ /**
+ * @param collection The new selected collection
+ */
+ void setSelectedCollection(U collection);
+
+ /**
+ * @return The mapping of language to subtitles
+ */
+ Map originalCollections();
+
+ /**
+ * @return The list of selected subtitles
+ */
+ List selectedSubtitles();
+
+ /**
+ * @return The currently selected subtitle
+ */
+ T selectedSubtitle();
+
+ /**
+ * @param subtitle The new selected subtitle
+ */
+ void setSelectedSubtitle(T subtitle);
+
+ /**
+ * @return Whether the user can load subtitles
+ */
+ boolean canLoadSubtitles();
+
+ /**
+ * @param canLoadSubtitles Whether the user can load subtitles
+ */
+ void setCanLoadSubtitles(boolean canLoadSubtitles);
+
+ /**
+ * @return Whether the user can add subtitles
+ */
+ boolean canAddSubtitle();
+
+ /**
+ * @param canAddSubtitle Whether the user can add subtitles
+ */
+ void setCanAddSubtitle(boolean canAddSubtitle);
+
+ /**
+ * @return Whether the user can reset subtitles
+ */
+ boolean canResetSubtitles();
+
+ /**
+ * @param canResetSubtitles Whether the user can reset subtitles
+ */
+ void setCanResetSubtitles(boolean canResetSubtitles);
+
+ /**
+ * @return Whether the user can save subtitles
+ */
+ boolean canSaveSubtitles();
+
+ /**
+ * @return Whether subtitles are currently being translated
+ */
+ boolean isTranslating();
+
+ /**
+ * @param translating Whether subtitles are currently being translated
+ */
+ void setTranslating(boolean translating);
+
+ /**
+ * @return Whether the user can edit the table
+ */
+ boolean canEditTable();
+
+ /**
+ * Sets whether the user can edit the table
+ *
+ * @param canEditTable Whether the user can edit the table
+ */
+ void setCanEditTable(boolean canEditTable);
+}
diff --git a/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/WorkController.java b/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/WorkController.java
index a066374..c9521a0 100644
--- a/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/WorkController.java
+++ b/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/WorkController.java
@@ -18,21 +18,7 @@ public interface WorkController {
* @param file The path to the video
*/
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
*/
diff --git a/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/WorkModel.java b/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/WorkModel.java
index e5fb221..1e6d01a 100644
--- a/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/WorkModel.java
+++ b/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/WorkModel.java
@@ -1,13 +1,9 @@
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.SubtitleCollection;
import com.github.gtache.autosubtitle.subtitle.extractor.ExtractionModel;
-import java.util.List;
-
/**
* Model for the main view
*/
@@ -18,6 +14,11 @@ public interface WorkModel {
*/
Video video();
+ /**
+ * @param video The new video
+ */
+ void setVideo(Video video);
+
/**
* @return The current extraction model
*/
@@ -28,56 +29,6 @@ public interface WorkModel {
*/
void setExtractionModel(ExtractionModel model);
- /**
- * @return The current subtitle collection
- */
- SubtitleCollection subtitleCollection();
-
- /**
- * @return The current list of subtitles
- */
- List subtitles();
-
- /**
- * @return The current text
- */
- String text();
-
- /**
- * @return The original extracted subtitles (used to reset)
- */
- List originalSubtitles();
-
- /**
- * @return The currently selected subtitle
- */
- EditableSubtitle selectedSubtitle();
-
- /**
- * @return The list of available video languages
- */
- List availableVideoLanguages();
-
- /**
- * @return The list of available translations languages
- */
- List availableTranslationsLanguage();
-
- /**
- * @return The video language
- */
- Language videoLanguage();
-
- /**
- * @param language The video language
- */
- void setVideoLanguage(Language language);
-
- /**
- * @return The list of selected translations
- */
- List translations();
-
/**
* @return The current status
*/
@@ -97,4 +48,36 @@ public interface WorkModel {
* @param progress The new progress
*/
void setProgress(double progress);
+
+ /**
+ * @return Whether the user can extract subtitles
+ */
+ boolean canExtract();
+
+ /**
+ * @return Whether the user can export subtitles
+ */
+ boolean canExport();
+
+ /**
+ * @param canExport Whether the user can export subtitles
+ */
+ void setCanExport(boolean canExport);
+
+ /**
+ * @return Whether the progress bar and label are currently visible
+ */
+ boolean isProgressVisible();
+
+ /**
+ * @return The last extracted collection
+ */
+ SubtitleCollection> extractedCollection();
+
+ /**
+ * Sets the last extracted collection
+ *
+ * @param collection The last extracted collection
+ */
+ void setExtractedCollection(SubtitleCollection> collection);
}
diff --git a/gui/core/src/main/java/com/github/gtache/autosubtitle/gui/impl/CombinedResourceBundle.java b/gui/core/src/main/java/com/github/gtache/autosubtitle/gui/impl/CombinedResourceBundle.java
index f55c86a..04b9c63 100644
--- a/gui/core/src/main/java/com/github/gtache/autosubtitle/gui/impl/CombinedResourceBundle.java
+++ b/gui/core/src/main/java/com/github/gtache/autosubtitle/gui/impl/CombinedResourceBundle.java
@@ -1,35 +1,52 @@
package com.github.gtache.autosubtitle.gui.impl;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.ResourceBundle;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.*;
/**
* Combines multiple resource bundles
*/
public class CombinedResourceBundle extends ResourceBundle {
+ private static final Logger logger = LogManager.getLogger(CombinedResourceBundle.class);
private final Map resources;
+ private final Locale locale;
- public CombinedResourceBundle(final ResourceBundle... bundles) {
- this(Arrays.asList(bundles));
+ public CombinedResourceBundle(final ResourceBundle... resourceBundles) {
+ this(Arrays.asList(resourceBundles));
}
- public CombinedResourceBundle(final Iterable bundles) {
+ public CombinedResourceBundle(final List resourceBundles) {
+ final var filteredBundles = resourceBundles.stream().filter(Objects::nonNull).toList();
+ if (filteredBundles.size() != resourceBundles.size()) {
+ logger.warn("There was one or more null bundles in the inner bundles");
+ }
+ if (filteredBundles.isEmpty()) {
+ throw new IllegalArgumentException("The bundle should contain at least one bundle");
+ }
this.resources = new HashMap<>();
- bundles.forEach(rb -> rb.getKeys().asIterator().forEachRemaining(key -> resources.put(key, rb.getString(key))));
+ filteredBundles.forEach(r -> r.keySet().forEach(s -> resources.put(s, r.getString(s))));
+ this.locale = filteredBundles.getFirst().getLocale();
}
@Override
- protected Object handleGetObject(final String key) {
- return resources.get(key);
+ public Object handleGetObject(final String key) {
+ if (resources.containsKey(key)) {
+ return resources.get(key);
+ } else {
+ throw new MissingResourceException(key + " not found", "CombinedResourceBundle", key);
+ }
}
@Override
public Enumeration getKeys() {
return Collections.enumeration(resources.keySet());
}
+
+ @Override
+ public Locale getLocale() {
+ return locale;
+ }
}
diff --git a/gui/core/src/main/java/com/github/gtache/autosubtitle/gui/impl/spi/SubtitlesBundleProvider.java b/gui/core/src/main/java/com/github/gtache/autosubtitle/gui/impl/spi/SubtitlesBundleProvider.java
new file mode 100644
index 0000000..409cb4e
--- /dev/null
+++ b/gui/core/src/main/java/com/github/gtache/autosubtitle/gui/impl/spi/SubtitlesBundleProvider.java
@@ -0,0 +1,9 @@
+package com.github.gtache.autosubtitle.gui.impl.spi;
+
+import java.util.spi.ResourceBundleProvider;
+
+/**
+ * Provider for SubtitlesBundle
+ */
+public interface SubtitlesBundleProvider extends ResourceBundleProvider {
+}
diff --git a/gui/core/src/main/java/com/github/gtache/autosubtitle/gui/impl/spi/SubtitlesBundleProviderImpl.java b/gui/core/src/main/java/com/github/gtache/autosubtitle/gui/impl/spi/SubtitlesBundleProviderImpl.java
new file mode 100644
index 0000000..5657769
--- /dev/null
+++ b/gui/core/src/main/java/com/github/gtache/autosubtitle/gui/impl/spi/SubtitlesBundleProviderImpl.java
@@ -0,0 +1,9 @@
+package com.github.gtache.autosubtitle.gui.impl.spi;
+
+import java.util.spi.AbstractResourceBundleProvider;
+
+/**
+ * Implementation of {@link SubtitlesBundleProvider}
+ */
+public class SubtitlesBundleProviderImpl extends AbstractResourceBundleProvider implements SubtitlesBundleProvider {
+}
diff --git a/gui/core/src/main/java/com/github/gtache/autosubtitle/modules/gui/impl/GuiCoreModule.java b/gui/core/src/main/java/com/github/gtache/autosubtitle/modules/gui/impl/GuiCoreModule.java
index dff92ff..eaafa50 100644
--- a/gui/core/src/main/java/com/github/gtache/autosubtitle/modules/gui/impl/GuiCoreModule.java
+++ b/gui/core/src/main/java/com/github/gtache/autosubtitle/modules/gui/impl/GuiCoreModule.java
@@ -23,6 +23,7 @@ public final class GuiCoreModule {
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.SubtitlesBundle"),
ResourceBundle.getBundle("com.github.gtache.autosubtitle.gui.impl.MediaBundle"));
}
diff --git a/gui/core/src/main/java/module-info.java b/gui/core/src/main/java/module-info.java
index 9b163ce..48e0010 100644
--- a/gui/core/src/main/java/module-info.java
+++ b/gui/core/src/main/java/module-info.java
@@ -13,6 +13,7 @@ import com.github.gtache.autosubtitle.gui.impl.spi.WorkBundleProviderImpl;
module com.github.gtache.autosubtitle.gui.core {
requires transitive com.github.gtache.autosubtitle.gui.api;
requires transitive com.github.gtache.autosubtitle.core;
+ requires org.apache.logging.log4j;
exports com.github.gtache.autosubtitle.gui.impl;
exports com.github.gtache.autosubtitle.gui.impl.spi;
exports com.github.gtache.autosubtitle.modules.gui.impl;
diff --git a/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/SubtitlesBundle.properties b/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/SubtitlesBundle.properties
new file mode 100644
index 0000000..dc37480
--- /dev/null
+++ b/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/SubtitlesBundle.properties
@@ -0,0 +1,14 @@
+subtitles.button.load.label=Load subtitles...
+subtitles.button.reset.label=Reset subtitles
+subtitles.button.subtitles.save.label=Save subtitles...
+subtitles.export.error.label=Error during the export : {0}
+subtitles.export.error.title=Error exporting
+subtitles.language.label=Video language
+subtitles.load.error.label=Error loading subtitles : {0}
+subtitles.load.error.title=Error loading
+subtitles.save.error.label=Error saving subtitles : {0}
+subtitles.save.error.title=Error saving
+subtitles.table.column.from.label=From
+subtitles.table.column.text.label=Text
+subtitles.table.column.to.label=To
+subtitles.translate.label=Automatic translations
\ No newline at end of file
diff --git a/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/SubtitlesBundle_fr.properties b/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/SubtitlesBundle_fr.properties
new file mode 100644
index 0000000..b3369b1
--- /dev/null
+++ b/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/SubtitlesBundle_fr.properties
@@ -0,0 +1,14 @@
+subtitles.button.load.label=Charger des sous-titres...
+subtitles.button.reset.label=R\u00E9initialiser les sous-titres
+subtitles.button.subtitles.save.label=Sauvegarder les sous-titres...
+subtitles.export.error.label=Erreur durant l''export : {0}
+subtitles.export.error.title=Erreur d'export
+subtitles.language.label=Langage de la vid\u00E9o
+subtitles.load.error.label=Erreur de chargement des sous-titres : {0}
+subtitles.load.error.title=Erreur de chargement
+subtitles.save.error.label=Erreur de sauvegarde des sous-titres : {0}
+subtitles.save.error.title=Erreur lors de la sauvegarde
+subtitles.table.column.from.label=De
+subtitles.table.column.text.label=Texte
+subtitles.table.column.to.label=\u00C0
+subtitles.translate.label=Traductions
diff --git a/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/WorkBundle.properties b/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/WorkBundle.properties
index 51db763..3fef742 100644
--- a/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/WorkBundle.properties
+++ b/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/WorkBundle.properties
@@ -4,27 +4,13 @@ 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.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
-work.status.translating.label=Translating...
-work.table.column.from.label=From
-work.table.column.text.label=Text
-work.table.column.to.label=To
-work.translate.label=Automatic translations
\ No newline at end of file
+work.status.translating.label=Translating...
\ No newline at end of file
diff --git a/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/WorkBundle_fr.properties b/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/WorkBundle_fr.properties
index 00fb672..ba0a06e 100644
--- a/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/WorkBundle_fr.properties
+++ b/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/WorkBundle_fr.properties
@@ -4,25 +4,13 @@ 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.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
work.status.translating.label=Traduction en cours...
-work.table.column.from.label=De
-work.table.column.text.label=Texte
-work.table.column.to.label=\u00C0
-work.translate.label=Traductions automatiques
diff --git a/gui/core/src/test/java/com/github/gtache/autosubtitle/gui/impl/TestCombinedResourceBundle.java b/gui/core/src/test/java/com/github/gtache/autosubtitle/gui/impl/TestCombinedResourceBundle.java
new file mode 100644
index 0000000..9f6ac4e
--- /dev/null
+++ b/gui/core/src/test/java/com/github/gtache/autosubtitle/gui/impl/TestCombinedResourceBundle.java
@@ -0,0 +1,54 @@
+package com.github.gtache.autosubtitle.gui.impl;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class TestCombinedResourceBundle {
+
+ private static final ResourceBundle BUNDLE = new CombinedResourceBundle(
+ ResourceBundle.getBundle("com.github.gtache.autosubtitle.gui.impl.MultiBundle", Locale.FRENCH),
+ ResourceBundle.getBundle("com.github.gtache.autosubtitle.gui.impl.MultiBundleTwo", Locale.FRENCH));
+
+ @Test
+ void testIllegal() {
+ assertThrows(IllegalArgumentException.class, CombinedResourceBundle::new);
+ assertThrows(IllegalArgumentException.class, () -> new CombinedResourceBundle(null, null));
+ }
+
+ @Test
+ void testWorks() {
+ assertEquals("deux", BUNDLE.getString("a"));
+ assertEquals("deux", BUNDLE.getString("b"));
+ assertEquals("trois", BUNDLE.getString("c"));
+ assertEquals("un", BUNDLE.getString("d"));
+ assertEquals(Arrays.asList("a", "b", "c", "d"), Collections.list(BUNDLE.getKeys()));
+ }
+
+ @Test
+ void testNotFound() {
+ assertThrows(MissingResourceException.class, () -> BUNDLE.getString("e"));
+ }
+
+ @Test
+ void testLocale() {
+ final var bundle = mock(ResourceBundle.class);
+ when(bundle.keySet()).thenReturn(Set.of());
+ when(bundle.getString(anyString())).thenReturn("");
+ final var locale = mock(Locale.class);
+ when(bundle.getLocale()).thenReturn(locale);
+ final var combined = new CombinedResourceBundle(bundle);
+ assertEquals(locale, combined.getLocale());
+ }
+}
diff --git a/gui/core/src/test/java/com/github/gtache/autosubtitle/modules/gui/impl/TestGuiCoreModule.java b/gui/core/src/test/java/com/github/gtache/autosubtitle/modules/gui/impl/TestGuiCoreModule.java
new file mode 100644
index 0000000..cc81467
--- /dev/null
+++ b/gui/core/src/test/java/com/github/gtache/autosubtitle/modules/gui/impl/TestGuiCoreModule.java
@@ -0,0 +1,34 @@
+package com.github.gtache.autosubtitle.modules.gui.impl;
+
+import com.github.gtache.autosubtitle.gui.impl.CombinedResourceBundle;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class TestGuiCoreModule {
+
+ @Test
+ void testBundle() {
+ assertInstanceOf(CombinedResourceBundle.class, GuiCoreModule.providesBundle());
+ }
+
+ @Test
+ void testPlayImage() {
+ assertTrue(GuiCoreModule.providesPlayImage().length > 0);
+ }
+
+ @Test
+ void testPauseImage() {
+ assertTrue(GuiCoreModule.providesPauseImage().length > 0);
+ }
+
+ @Test
+ void testFontFamily() {
+ assertEquals("Arial", GuiCoreModule.providesFontFamily());
+ }
+
+ @Test
+ void testFontSize() {
+ assertEquals(12, GuiCoreModule.providesFontSize());
+ }
+}
diff --git a/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundle.properties b/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundle.properties
new file mode 100644
index 0000000..65cb097
--- /dev/null
+++ b/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundle.properties
@@ -0,0 +1,3 @@
+a=one
+b=two
+c=three
\ No newline at end of file
diff --git a/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundleTwo.properties b/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundleTwo.properties
new file mode 100644
index 0000000..02a0253
--- /dev/null
+++ b/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundleTwo.properties
@@ -0,0 +1,2 @@
+a=two
+d=one
\ No newline at end of file
diff --git a/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundleTwo_de.properties b/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundleTwo_de.properties
new file mode 100644
index 0000000..da5e8a2
--- /dev/null
+++ b/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundleTwo_de.properties
@@ -0,0 +1,2 @@
+a=zwei
+d=eins
\ No newline at end of file
diff --git a/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundleTwo_fr.properties b/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundleTwo_fr.properties
new file mode 100644
index 0000000..1c078f3
--- /dev/null
+++ b/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundleTwo_fr.properties
@@ -0,0 +1,2 @@
+a=deux
+d=un
\ No newline at end of file
diff --git a/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundle_de.properties b/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundle_de.properties
new file mode 100644
index 0000000..eadf97f
--- /dev/null
+++ b/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundle_de.properties
@@ -0,0 +1,3 @@
+a=eins
+b=zwei
+c=drei
\ No newline at end of file
diff --git a/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundle_fr.properties b/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundle_fr.properties
new file mode 100644
index 0000000..a35eefe
--- /dev/null
+++ b/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundle_fr.properties
@@ -0,0 +1,3 @@
+a=un
+b=deux
+c=trois
\ No newline at end of file
diff --git a/gui/fx/pom.xml b/gui/fx/pom.xml
index 40ebd2e..87a03b1 100644
--- a/gui/fx/pom.xml
+++ b/gui/fx/pom.xml
@@ -13,7 +13,8 @@
11.2.1
- 22.0.1
+ 22.0.2
+ 4.0.18
@@ -44,6 +45,23 @@
controlsfx
${controlsfx.version}
+
+ org.testfx
+ testfx-core
+ ${testfx.version}
+ test
+
+
+ org.testfx
+ testfx-junit5
+ ${testfx.version}
+ test
+
+
+ org.apache.logging.log4j
+ log4j-core
+ test
+
\ No newline at end of file
diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/ColonTimeFormatter.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/ColonTimeFormatter.java
index 1fb24a0..dbf9501 100644
--- a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/ColonTimeFormatter.java
+++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/ColonTimeFormatter.java
@@ -27,18 +27,19 @@ public class ColonTimeFormatter implements TimeFormatter {
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;
+ var secondsDuration = millis / 1000L;
+ final var durationHours = secondsDuration / secondsInHour;
if (durationHours > 0) {
- intDuration -= durationHours * secondsInHour;
+ secondsDuration -= durationHours * secondsInHour;
}
- final var durationMinutes = intDuration / secondsInMinute;
- final var durationSeconds = intDuration - durationHours * secondsInHour
- - durationMinutes * secondsInMinute;
+ final var durationMinutes = secondsDuration / secondsInMinute;
+ secondsDuration -= durationMinutes * secondsInMinute;
+ final var durationSeconds = secondsDuration;
+ final var durationMillis = millis % 1000L;
if (durationHours > 0) {
- return String.format("%d:%02d:%02d", durationHours, durationMinutes, durationSeconds);
+ return "%d:%02d:%02d.%03d".formatted(durationHours, durationMinutes, durationSeconds, durationMillis);
} else {
- return String.format("%02d:%02d", durationMinutes, durationSeconds);
+ return "%02d:%02d.%03d".formatted(durationMinutes, durationSeconds, durationMillis);
}
}
@@ -48,14 +49,20 @@ public class ColonTimeFormatter implements TimeFormatter {
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;
+ case 1 -> parseSecondsMillis(split[0]);
+ case 2 -> toLong(split[0]) * secondsInMinute * 1000 + parseSecondsMillis(split[1]);
+ case 3 ->
+ (toLong(split[0]) * secondsInHour + toLong(split[1]) * secondsInMinute) * 1000 + parseSecondsMillis(split[2]);
default -> 0;
};
}
- private long toLong(final String time) {
+ private static long parseSecondsMillis(final String time) {
+ final var split = time.split("\\.");
+ return toLong(split[0]) * 1000 + (split.length > 1 ? toLong(split[1]) : 0);
+ }
+
+ private static long toLong(final String time) {
if (time.startsWith("0")) {
return Long.parseLong(time.substring(1));
} else {
diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXBinder.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXBinder.java
new file mode 100644
index 0000000..0fc4a99
--- /dev/null
+++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXBinder.java
@@ -0,0 +1,13 @@
+package com.github.gtache.autosubtitle.gui.fx;
+
+/**
+ * Binds multiple models together
+ */
+@FunctionalInterface
+public interface FXBinder {
+
+ /**
+ * Creates the bindings between the models
+ */
+ void createBindings();
+}
diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMainController.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMainController.java
index cf72ea2..14df25b 100644
--- a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMainController.java
+++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMainController.java
@@ -3,6 +3,7 @@ package com.github.gtache.autosubtitle.gui.fx;
import com.github.gtache.autosubtitle.gui.MainController;
import javafx.fxml.FXML;
import javafx.scene.control.TabPane;
+import javafx.stage.Window;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -12,7 +13,7 @@ import java.util.Objects;
* FX implementation of {@link MainController}
*/
@Singleton
-public class FXMainController implements MainController {
+public class FXMainController extends AbstractFXController implements MainController {
@FXML
private TabPane tabPane;
@@ -25,18 +26,23 @@ public class FXMainController implements MainController {
}
@FXML
- private void initialize() {
- tabPane.getSelectionModel().selectedIndexProperty().addListener((observable, oldValue, newValue) -> model.selectTab(newValue.intValue()));
+ void initialize() {
+ tabPane.getSelectionModel().selectedIndexProperty().addListener((observable, oldValue, newValue) -> model.setSelectedTab(newValue.intValue()));
model.selectedTabProperty().addListener((observable, oldValue, newValue) -> tabPane.getSelectionModel().select(newValue.intValue()));
}
@Override
public void selectTab(final int index) {
- model.selectTab(index);
+ model.setSelectedTab(index);
}
@Override
public FXMainModel model() {
return model;
}
+
+ @Override
+ protected Window window() {
+ return tabPane.getScene().getWindow();
+ }
}
diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMainModel.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMainModel.java
index 04a38a6..7aa546b 100644
--- a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMainModel.java
+++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMainModel.java
@@ -26,7 +26,7 @@ public class FXMainModel implements MainModel {
}
@Override
- public void selectTab(final int index) {
+ public void setSelectedTab(final int index) {
selectedTab.set(index);
}
diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMediaBinder.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMediaBinder.java
index 3176cea..db6b516 100644
--- a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMediaBinder.java
+++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMediaBinder.java
@@ -10,7 +10,7 @@ import java.util.Objects;
* Binds the media model
*/
@Singleton
-public class FXMediaBinder {
+public class FXMediaBinder implements FXBinder {
private final FXWorkModel workModel;
private final FXMediaModel mediaModel;
@@ -21,6 +21,7 @@ public class FXMediaBinder {
this.mediaModel = Objects.requireNonNull(mediaModel);
}
+ @Override
public void createBindings() {
mediaModel.videoProperty().bindBidirectional(workModel.videoProperty());
Bindings.bindContent(mediaModel.subtitles(), workModel.subtitles());
diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMediaController.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMediaController.java
index 07b8b39..5a252e1 100644
--- a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMediaController.java
+++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMediaController.java
@@ -5,12 +5,10 @@ import com.github.gtache.autosubtitle.gui.MediaController;
import com.github.gtache.autosubtitle.gui.TimeFormatter;
import com.github.gtache.autosubtitle.modules.gui.impl.Pause;
import com.github.gtache.autosubtitle.modules.gui.impl.Play;
-import com.github.gtache.autosubtitle.subtitle.EditableSubtitle;
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.collections.ListChangeListener;
import javafx.fxml.FXML;
import javafx.geometry.Insets;
import javafx.scene.Cursor;
@@ -34,9 +32,7 @@ import org.apache.logging.log4j.Logger;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
+import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import static java.util.Objects.requireNonNull;
@@ -69,8 +65,6 @@ public class FXMediaController implements MediaController {
private final Image playImage;
private final Image pauseImage;
- private final List startTimes;
-
private boolean wasPlaying;
@Inject
@@ -81,11 +75,10 @@ public class FXMediaController implements MediaController {
this.timeFormatter = requireNonNull(timeFormatter);
this.playImage = requireNonNull(playImage);
this.pauseImage = requireNonNull(pauseImage);
- this.startTimes = new ArrayList<>();
}
@FXML
- private void initialize() {
+ 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());
@@ -115,13 +108,10 @@ public class FXMediaController implements MediaController {
loadFileVideo(file.path());
} else {
logger.error("Unsupported video type : {}", newValue);
+ Platform.runLater(() -> model.setVideo(null));
}
});
- model.subtitles().addListener((ListChangeListener) c -> {
- startTimes.clear();
- model.subtitles().stream().mapToLong(Subtitle::start).forEach(startTimes::add);
- });
bindPlayButton();
binder.createBindings();
}
@@ -130,8 +120,8 @@ public class FXMediaController implements MediaController {
final var media = new Media(fileVideoPath.toUri().toString());
final var player = new MediaPlayer(media);
player.statusProperty().addListener((observable12, oldValue1, newValue1) ->
- logger.info("New status: {}", newValue1));
- player.currentTimeProperty().addListener((ignored, oldTime, newTime) -> currentTimeChanged(oldTime.toMillis(), newTime.toMillis()));
+ logger.debug("New player status: {}", newValue1));
+ player.currentTimeProperty().addListener((ignored, oldTime, newTime) -> currentTimeChanged(newTime.toMillis()));
playSlider.setOnMousePressed(e -> {
wasPlaying = model.isPlaying();
model.setIsPlaying(false);
@@ -155,33 +145,37 @@ public class FXMediaController implements MediaController {
playSlider.setMax(model.duration());
playSlider.setValue(0L);
videoView.setMediaPlayer(player);
+ CompletableFuture.runAsync(() -> {
+ try {
+ Thread.sleep(3000L);
+ } catch (final InterruptedException ignored) {
+ Thread.currentThread().interrupt();
+ }
+ Platform.runLater(() -> {
+ final var status = player.getStatus();
+ if (status == null || status == MediaPlayer.Status.UNKNOWN) {
+ logger.warn("Reloading video {} because player state is unknown or null", fileVideoPath);
+ loadFileVideo(fileVideoPath);
+ }
+ });
+ });
}
- private void currentTimeChanged(final double oldMillis, final double millis) {
+ private void currentTimeChanged(final double millis) {
+ final var longMillis = (long) millis;
playSlider.setValue(millis);
final var subtitleLabels = stackPane.getChildren().stream().filter(SubtitleLabel.class::isInstance).map(SubtitleLabel.class::cast).toList();
- subtitleLabels.stream().filter(s -> !s.subtitle().isShowing((long) millis)).forEach(sl -> stackPane.getChildren().remove(sl));
- final var containedSubtitles = subtitleLabels.stream().map(SubtitleLabel::subtitle).filter(s -> s.isShowing((long) millis)).collect(Collectors.toSet());
-
+ subtitleLabels.stream().filter(s -> !s.subtitle().isShowing(longMillis)).forEach(sl -> stackPane.getChildren().remove(sl));
+ final var containedSubtitles = subtitleLabels.stream().map(SubtitleLabel::subtitle).filter(s -> s.isShowing(longMillis)).collect(Collectors.toSet());
+ //TODO optimize?
model.subtitles().forEach(s -> {
- if (!containedSubtitles.contains(s)) {
- logger.info("Adding label {} at {}", s, millis);
+ if (!containedSubtitles.contains(s) && s.isShowing(longMillis)) {
final var label = createDraggableLabel(s);
stackPane.getChildren().add(label);
}
});
}
- private void currentTimeChangedOptimized(final double oldMillis, final double millis) {
- final var forward = oldMillis <= millis;
-
- var index = Collections.binarySearch(startTimes, (long) millis);
- if (index < 0) {
- index = forward ? -(index + 1) : -(index + 2);
- }
- //TODO
- }
-
private void bindPlayButton() {
playButton.disableProperty().bind(model.videoProperty().isNull());
playButton.graphicProperty().bind(Bindings.createObjectBinding(() -> {
@@ -217,6 +211,7 @@ public class FXMediaController implements MediaController {
public void seek(final long position) {
if (videoView.getMediaPlayer() != null) {
videoView.getMediaPlayer().seek(Duration.millis(position));
+ currentTimeChanged(position);
}
}
diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMediaModel.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMediaModel.java
index 385cbba..99f1786 100644
--- a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMediaModel.java
+++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMediaModel.java
@@ -102,12 +102,12 @@ public class FXMediaModel implements MediaModel {
this.position.set(position);
}
+ LongProperty positionProperty() {
+ return position;
+ }
+
@Override
public ObservableList subtitles() {
return subtitles;
}
-
- LongProperty positionProperty() {
- return position;
- }
}
diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXParametersController.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXParametersController.java
index e77035e..905afea 100644
--- a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXParametersController.java
+++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXParametersController.java
@@ -50,7 +50,7 @@ public class FXParametersController extends AbstractFXController implements Para
}
@FXML
- private void initialize() {
+ void initialize() {
extractionModelCombobox.setItems(model.availableExtractionModels());
extractionModelCombobox.valueProperty().bindBidirectional(model.extractionModelProperty());
diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSetupController.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSetupController.java
index 1be5322..9312420 100644
--- a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSetupController.java
+++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSetupController.java
@@ -97,7 +97,7 @@ public class FXSetupController extends AbstractFXController implements SetupCont
}
@FXML
- private void initialize() {
+ void initialize() {
statusMap.put(converterManager, model.videoConverterStatusProperty());
statusMap.put(extractorManager, model.subtitleExtractorStatusProperty());
statusMap.put(translatorManager, model.translatorStatusProperty());
diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSetupModel.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSetupModel.java
index 200b49c..5b9e6ac 100644
--- a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSetupModel.java
+++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSetupModel.java
@@ -2,11 +2,8 @@ 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;
@@ -22,45 +19,26 @@ import javax.inject.Singleton;
public class FXSetupModel implements SetupModel {
private final ObjectProperty subtitleExtractorStatus;
- private final ReadOnlyBooleanWrapper subtitleExtractorInstalled;
- private final ReadOnlyBooleanWrapper subtitleExtractorUpdateAvailable;
private final DoubleProperty subtitleExtractorSetupProgress;
private final StringProperty subtitleExtractorSetupProgressLabel;
private final ObjectProperty videoConverterStatus;
- private final ReadOnlyBooleanWrapper videoConverterInstalled;
- private final ReadOnlyBooleanWrapper videoConverterUpdateAvailable;
private final DoubleProperty videoConverterSetupProgress;
private final StringProperty videoConverterSetupProgressLabel;
private final ObjectProperty 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
@@ -77,20 +55,6 @@ public class FXSetupModel implements SetupModel {
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();
@@ -133,28 +97,6 @@ public class FXSetupModel implements SetupModel {
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();
@@ -197,24 +139,6 @@ public class FXSetupModel implements SetupModel {
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();
diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesBinder.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesBinder.java
new file mode 100644
index 0000000..bda34c2
--- /dev/null
+++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesBinder.java
@@ -0,0 +1,55 @@
+package com.github.gtache.autosubtitle.gui.fx;
+
+import com.github.gtache.autosubtitle.Language;
+import com.github.gtache.autosubtitle.gui.WorkStatus;
+import com.github.gtache.autosubtitle.subtitle.gui.fx.ObservableSubtitleCollectionImpl;
+import javafx.beans.binding.Bindings;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import java.util.Objects;
+
+/**
+ * Binds the subtitles model
+ */
+@Singleton
+public class FXSubtitlesBinder implements FXBinder {
+
+ private final FXWorkModel workModel;
+ private final FXSubtitlesModel subtitlesModel;
+
+ @Inject
+ FXSubtitlesBinder(final FXWorkModel workModel, final FXSubtitlesModel subtitlesModel) {
+ this.workModel = Objects.requireNonNull(workModel);
+ this.subtitlesModel = Objects.requireNonNull(subtitlesModel);
+ }
+
+ @Override
+ public void createBindings() {
+ subtitlesModel.canLoadSubtitlesProperty().bind(workModel.videoProperty().isNotNull().and(workModel.statusProperty().isEqualTo(WorkStatus.IDLE)));
+ subtitlesModel.canResetSubtitlesProperty().bind(workModel.videoProperty().isNotNull().and(workModel.statusProperty().isEqualTo(WorkStatus.IDLE)));
+ subtitlesModel.canAddSubtitleProperty().bind(workModel.videoProperty().isNotNull().and(workModel.statusProperty().isEqualTo(WorkStatus.IDLE)).and(subtitlesModel.videoLanguageProperty().isNotEqualTo(Language.AUTO)));
+ subtitlesModel.canEditTableProperty().bind(workModel.videoProperty().isNotNull().and(workModel.statusProperty().isEqualTo(WorkStatus.IDLE)).and(subtitlesModel.videoLanguageProperty().isNotEqualTo(Language.AUTO)));
+
+ workModel.selectedSubtitleProperty().bind(subtitlesModel.selectedSubtitleProperty());
+ workModel.canExportProperty().bind(Bindings.isNotEmpty(subtitlesModel.collections()).and(workModel.statusProperty().isEqualTo(WorkStatus.IDLE)));
+ workModel.videoLanguageProperty().bind(subtitlesModel.videoLanguageProperty());
+
+ subtitlesModel.translatingProperty().addListener((observable, oldValue, newValue) -> {
+ if (newValue) {
+ workModel.statusProperty().set(WorkStatus.TRANSLATING);
+ } else {
+ workModel.statusProperty().set(WorkStatus.IDLE);
+ }
+ });
+
+ workModel.extractedCollectionProperty().addListener((observable, oldValue, newValue) -> {
+ if (newValue != null) {
+ subtitlesModel.collections().put(newValue.language(), new ObservableSubtitleCollectionImpl(newValue));
+ }
+ });
+
+ Bindings.bindContent(workModel.collections(), subtitlesModel.collections());
+ Bindings.bindContent(workModel.subtitles(), subtitlesModel.selectedSubtitles());
+ }
+}
diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesController.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesController.java
new file mode 100644
index 0000000..db151d6
--- /dev/null
+++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesController.java
@@ -0,0 +1,322 @@
+package com.github.gtache.autosubtitle.gui.fx;
+
+import com.github.gtache.autosubtitle.Language;
+import com.github.gtache.autosubtitle.Translator;
+import com.github.gtache.autosubtitle.gui.SubtitlesController;
+import com.github.gtache.autosubtitle.gui.TimeFormatter;
+import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
+import com.github.gtache.autosubtitle.subtitle.SubtitleImporterExporter;
+import com.github.gtache.autosubtitle.subtitle.converter.ParseException;
+import com.github.gtache.autosubtitle.subtitle.gui.fx.ObservableSubtitleCollectionImpl;
+import com.github.gtache.autosubtitle.subtitle.gui.fx.ObservableSubtitleImpl;
+import javafx.application.Platform;
+import javafx.beans.binding.Bindings;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.beans.property.SimpleStringProperty;
+import javafx.collections.MapChangeListener;
+import javafx.fxml.FXML;
+import javafx.scene.control.Button;
+import javafx.scene.control.ComboBox;
+import javafx.scene.control.Tab;
+import javafx.scene.control.TabPane;
+import javafx.scene.control.TableColumn;
+import javafx.scene.control.TableView;
+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.PrefixSelectionComboBox;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.ResourceBundle;
+import java.util.concurrent.CompletableFuture;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * FX implementation of {@link SubtitlesController}
+ */
+@Singleton
+public class FXSubtitlesController extends AbstractFXController implements SubtitlesController {
+
+ private static final Logger logger = LogManager.getLogger(FXSubtitlesController.class);
+ private static final String ARCHIVE = "Archive";
+ private static final String ALL_SUPPORTED = "All supported";
+
+ @FXML
+ private ResourceBundle resources;
+ @FXML
+ private Button loadButton;
+ @FXML
+ private Button resetButton;
+ @FXML
+ private Button saveButton;
+ @FXML
+ private Button addButton;
+ @FXML
+ private PrefixSelectionComboBox languageCombobox;
+ @FXML
+ private ComboBox translationsCombobox;
+ @FXML
+ private TabPane tabPane;
+ @FXML
+ private Tab mainSubtitlesTab;
+ @FXML
+ private TableView subtitlesTable;
+ @FXML
+ private TableColumn startColumn;
+ @FXML
+ private TableColumn endColumn;
+ @FXML
+ private TableColumn textColumn;
+
+ private final FXSubtitlesModel model;
+ private final FXSubtitlesBinder binder;
+ private final SubtitleImporterExporter> importerExporter;
+ private final TimeFormatter timeFormatter;
+ private final List subtitleExtensions;
+ private final Translator> translator;
+
+ @Inject
+ FXSubtitlesController(final FXSubtitlesModel model, final FXSubtitlesBinder binder, final SubtitleImporterExporter importerExporter, final TimeFormatter timeFormatter,
+ final Translator translator) {
+ this.model = requireNonNull(model);
+ this.binder = requireNonNull(binder);
+ this.importerExporter = requireNonNull(importerExporter);
+ this.timeFormatter = requireNonNull(timeFormatter);
+ this.subtitleExtensions = importerExporter.supportedSingleFileExtensions().stream().map(c -> "*." + c).sorted().toList();
+ this.translator = requireNonNull(translator);
+ }
+
+ @FXML
+ void initialize() {
+ addButton.disableProperty().bind(model.canAddSubtitleProperty().not());
+ loadButton.disableProperty().bind(model.canLoadSubtitlesProperty().not());
+ resetButton.disableProperty().bind(model.canResetSubtitlesProperty().not());
+ saveButton.disableProperty().bind(model.canSaveSubtitlesProperty().not());
+
+ // Can't bind because tab calls updateDisabled which sets disableProperty
+ model.canEditTableProperty().addListener((observable, oldValue, newValue) -> subtitlesTable.setDisable(!newValue));
+
+ bindComboboxes();
+ bindTable();
+ mainSubtitlesTab.textProperty().bind(Bindings.createStringBinding(() -> model.videoLanguage().iso2(), model.videoLanguageProperty()));
+ model.collections().addListener((MapChangeListener) change -> {
+ manageTabs();
+ });
+ tabPane.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
+ if (newValue != null) {
+ model.setSelectedCollection(model.collections().get(Language.getLanguage(newValue.getText())));
+ if (oldValue != null) {
+ oldValue.setContent(null);
+ }
+ newValue.setContent(subtitlesTable);
+ }
+ });
+ model.selectedLanguageProperty().addListener((observable, oldValue, newValue) -> {
+ if (newValue != null) {
+ tabPane.getTabs().stream().filter(t -> Language.getLanguage(t.getText()) == newValue)
+ .findFirst().ifPresent(tab -> tabPane.getSelectionModel().select(tab));
+ }
+ });
+
+ translationsCombobox.valueProperty().addListener((observable, oldValue, newValue) -> {
+ if (newValue != null && !model.collections().containsKey(newValue)) {
+ model.setTranslating(true);
+ CompletableFuture.supplyAsync(() -> translator.translate(model.collections().get(model.videoLanguage()), newValue))
+ .whenCompleteAsync((r, t) -> {
+ if (t == null) {
+ loadCollection(r);
+ model.setSelectedCollection(model.collections().get(newValue));
+ } else {
+ logger.error("Error while translating to {}", newValue, t);
+ }
+ model.setTranslating(false);
+ }, Platform::runLater);
+ }
+ });
+ binder.createBindings();
+ }
+
+ private void bindTable() {
+ subtitlesTable.setItems(model.selectedSubtitles());
+ 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();
+ } else if (e.getCode() == KeyCode.DELETE) {
+ deleteSelectedSubtitles();
+ 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));
+
+ }
+
+ private void manageTabs() {
+ final var toRemove = new ArrayList();
+ final var toAdd = new ArrayList();
+ tabPane.getTabs().forEach(tab -> {
+ if (!model.collections().containsKey(Language.getLanguage(tab.getText()))) {
+ toRemove.add(tab);
+ }
+ });
+ model.collections().forEach((language, collection) -> {
+ if (tabPane.getTabs().stream().noneMatch(t -> Language.getLanguage(t.getText()) == language)) {
+ toAdd.add(new Tab(language.iso2()));
+ }
+ });
+ tabPane.getTabs().removeAll(toRemove);
+ tabPane.getTabs().addAll(toAdd);
+ tabPane.getTabs().sort(Comparator.comparing(Tab::getText));
+ }
+
+ private void bindComboboxes() {
+ languageCombobox.valueProperty().bindBidirectional(model.videoLanguageProperty());
+ languageCombobox.setItems(model.availableVideoLanguages());
+ languageCombobox.setConverter(new LanguageStringConverter());
+ translationsCombobox.setConverter(new LanguageStringConverter());
+ translationsCombobox.setItems(model.availableTranslationsLanguage());
+ }
+
+ @FXML
+ private void resetButtonPressed() {
+ model.setSelectedCollection(model.originalCollections().get(model.selectedLanguage()));
+ }
+
+ @FXML
+ private void addPressed() {
+ model.selectedCollection().subtitles().add(new ObservableSubtitleImpl("Enter text here..."));
+ }
+
+ @FXML
+ private void loadPressed() {
+ final var filePicker = new FileChooser();
+ final var archiveFilter = new FileChooser.ExtensionFilter(ARCHIVE, subtitleExtensions);
+ final var allSupportedFilter = new FileChooser.ExtensionFilter(ALL_SUPPORTED, subtitleExtensions);
+ filePicker.getExtensionFilters().addAll(archiveFilter, allSupportedFilter);
+ filePicker.setSelectedExtensionFilter(allSupportedFilter);
+ final var file = filePicker.showOpenDialog(window());
+ loadSubtitles(file.toPath());
+ }
+
+ @FXML
+ private void savePressed() {
+ final var filePicker = new FileChooser();
+ final var archiveFilter = new FileChooser.ExtensionFilter(ARCHIVE, subtitleExtensions);
+ final var allSupportedFilter = new FileChooser.ExtensionFilter(ALL_SUPPORTED, subtitleExtensions);
+ filePicker.getExtensionFilters().addAll(archiveFilter, allSupportedFilter);
+ filePicker.setSelectedExtensionFilter(allSupportedFilter);
+ final var file = filePicker.showSaveDialog(window());
+ if (file != null) {
+ saveSubtitles(file.toPath());
+ }
+ }
+
+ @Override
+ public void selectLanguage(final Language language) {
+ model.setSelectedLanguage(language);
+ }
+
+ @Override
+ public void deleteLanguage(final Language language) {
+ model.selectedTranslationsLanguages().remove(language);
+ }
+
+ @Override
+ public void saveSubtitles(final Path file) {
+ try {
+ final var filename = file.getFileName().toString();
+ final var extension = filename.substring(filename.lastIndexOf('.') + 1);
+ if (subtitleExtensions.contains(extension)) {
+ importerExporter.exportSubtitles(model.selectedCollection(), file);
+ } else {
+ importerExporter.exportSubtitles(model.collections().values(), file);
+ }
+ } catch (final IOException e) {
+ logger.error("Error saving subtitles {}", file, e);
+ showErrorDialog(resources.getString("subtitles.save.error.title"), MessageFormat.format(resources.getString("subtitles.save.error.label"), file));
+ }
+ }
+
+ @Override
+ public void loadSubtitles(final Path file) {
+ try {
+ final var map = importerExporter.importSubtitles(file);
+ map.values().forEach(this::loadCollection);
+ if (model.videoLanguage() == Language.AUTO) {
+ model.setVideoLanguage(map.keySet().stream().findFirst().orElse(Language.AUTO));
+ }
+ } catch (final IOException | ParseException e) {
+ logger.error("Error loading subtitles {}", file, e);
+ showErrorDialog(resources.getString("subtitles.load.error.title"), MessageFormat.format(resources.getString("subtitles.load.error.label"), file));
+ }
+ }
+
+ private void loadCollection(final SubtitleCollection> collection) {
+ final var observableCollection = new ObservableSubtitleCollectionImpl(collection);
+ model.originalCollections().put(observableCollection.language(), observableCollection);
+ model.collections().put(observableCollection.language(), observableCollection);
+ }
+
+ private void deleteSelectedSubtitles() {
+ model.selectedCollection().observableSubtitles().removeAll(subtitlesTable.getSelectionModel().getSelectedItems());
+ }
+
+ private void editFocusedCell() {
+ final var focusedCell = subtitlesTable.getFocusModel().getFocusedCell();
+ if (focusedCell != null) {
+ subtitlesTable.edit(focusedCell.getRow(), focusedCell.getTableColumn());
+ }
+ }
+
+ @Override
+ public FXSubtitlesModel model() {
+ return model;
+ }
+
+ @Override
+ protected Window window() {
+ return saveButton.getScene().getWindow();
+ }
+}
diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesModel.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesModel.java
new file mode 100644
index 0000000..328442b
--- /dev/null
+++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesModel.java
@@ -0,0 +1,256 @@
+package com.github.gtache.autosubtitle.gui.fx;
+
+import com.github.gtache.autosubtitle.Language;
+import com.github.gtache.autosubtitle.gui.SubtitlesModel;
+import com.github.gtache.autosubtitle.subtitle.gui.fx.ObservableSubtitleCollectionImpl;
+import com.github.gtache.autosubtitle.subtitle.gui.fx.ObservableSubtitleImpl;
+import javafx.beans.binding.Bindings;
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.ReadOnlyBooleanProperty;
+import javafx.beans.property.ReadOnlyBooleanWrapper;
+import javafx.beans.property.SimpleBooleanProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.collections.FXCollections;
+import javafx.collections.MapChangeListener;
+import javafx.collections.ObservableList;
+import javafx.collections.ObservableMap;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * FX implementation of {@link SubtitlesModel}
+ */
+@Singleton
+public class FXSubtitlesModel implements SubtitlesModel {
+
+ private final ObservableList availableVideoLanguages;
+ private final ObjectProperty videoLanguage;
+ private final ObservableList availableTranslationLanguages;
+ private final ObservableList selectedTranslationsLanguages;
+ private final ObjectProperty selectedLanguage;
+ private final ObservableMap collections;
+ private final ObservableMap originalCollections;
+ private final ObjectProperty selectedCollection;
+ private final ObservableList selectedSubtitles;
+ private final ObjectProperty selectedSubtitle;
+
+ private final BooleanProperty canLoadSubtitles;
+ private final BooleanProperty canAddSubtitle;
+ private final BooleanProperty canResetSubtitles;
+ private final BooleanProperty canEditTable;
+ private final ReadOnlyBooleanWrapper canSaveSubtitles;
+ private final BooleanProperty isTranslating;
+
+ @Inject
+ FXSubtitlesModel() {
+ 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.englishName().compareTo(o2.englishName());
+ }
+ }).toList()));
+ this.availableTranslationLanguages = FXCollections.observableArrayList(Arrays.stream(Language.values()).filter(l -> l != Language.AUTO).toList());
+ this.videoLanguage = new SimpleObjectProperty<>(Language.AUTO);
+ this.selectedTranslationsLanguages = FXCollections.observableArrayList();
+ this.selectedLanguage = new SimpleObjectProperty<>(Language.AUTO);
+ this.collections = FXCollections.observableHashMap();
+ this.originalCollections = FXCollections.observableHashMap();
+ this.selectedCollection = new SimpleObjectProperty<>();
+ this.selectedSubtitles = FXCollections.observableArrayList();
+ this.selectedSubtitle = new SimpleObjectProperty<>();
+ this.canLoadSubtitles = new SimpleBooleanProperty(false);
+ this.canAddSubtitle = new SimpleBooleanProperty(false);
+ this.canResetSubtitles = new SimpleBooleanProperty(false);
+ this.canSaveSubtitles = new ReadOnlyBooleanWrapper(false);
+ this.canEditTable = new SimpleBooleanProperty(false);
+ this.isTranslating = new SimpleBooleanProperty(false);
+
+ canSaveSubtitles.bind(Bindings.isNotEmpty(collections));
+ collections.addListener((MapChangeListener) change ->
+ availableTranslationLanguages.setAll(Arrays.stream(Language.values()).filter(l -> l != Language.AUTO && !collections.containsKey(l)).sorted(Comparator.comparing(Language::englishName)).toList()));
+
+ selectedCollection.addListener((observable, oldValue, newValue) -> {
+ selectedSubtitle.set(null);
+ if (newValue == null) {
+ selectedSubtitles.clear();
+ selectedLanguage.set(Language.AUTO);
+ } else {
+ selectedSubtitles.setAll(newValue.subtitles());
+ selectedLanguage.set(newValue.language());
+ }
+ });
+ }
+
+ @Override
+ public ObservableList availableVideoLanguages() {
+ return availableVideoLanguages;
+ }
+
+ @Override
+ public Language videoLanguage() {
+ return videoLanguage.get();
+ }
+
+ @Override
+ public void setVideoLanguage(final Language language) {
+ videoLanguage.set(language);
+ }
+
+ ObjectProperty videoLanguageProperty() {
+ return videoLanguage;
+ }
+
+ @Override
+ public ObservableList availableTranslationsLanguage() {
+ return FXCollections.unmodifiableObservableList(availableTranslationLanguages);
+ }
+
+ @Override
+ public ObservableList selectedTranslationsLanguages() {
+ return selectedTranslationsLanguages;
+ }
+
+ @Override
+ public Language selectedLanguage() {
+ return selectedLanguage.get();
+ }
+
+ @Override
+ public void setSelectedLanguage(final Language language) {
+ selectedLanguage.set(language);
+ }
+
+ ObjectProperty selectedLanguageProperty() {
+ return selectedLanguage;
+ }
+
+ @Override
+ public ObservableMap collections() {
+ return collections;
+ }
+
+ @Override
+ public ObservableSubtitleCollectionImpl selectedCollection() {
+ return selectedCollection.get();
+ }
+
+ @Override
+ public void setSelectedCollection(final ObservableSubtitleCollectionImpl collection) {
+ selectedCollection.set(collection);
+ }
+
+ ObjectProperty selectedCollectionProperty() {
+ return selectedCollection;
+ }
+
+ @Override
+ public ObservableMap originalCollections() {
+ return originalCollections;
+ }
+
+ @Override
+ public ObservableList selectedSubtitles() {
+ return selectedSubtitles;
+ }
+
+ @Override
+ public ObservableSubtitleImpl selectedSubtitle() {
+ return selectedSubtitle.get();
+ }
+
+ @Override
+ public void setSelectedSubtitle(final ObservableSubtitleImpl subtitle) {
+ selectedSubtitle.set(subtitle);
+ }
+
+ ObjectProperty selectedSubtitleProperty() {
+ return selectedSubtitle;
+ }
+
+ @Override
+ public boolean canLoadSubtitles() {
+ return canLoadSubtitles.get();
+ }
+
+ @Override
+ public void setCanLoadSubtitles(final boolean canLoadSubtitles) {
+ this.canLoadSubtitles.set(canLoadSubtitles);
+ }
+
+ BooleanProperty canLoadSubtitlesProperty() {
+ return canLoadSubtitles;
+ }
+
+ @Override
+ public boolean canAddSubtitle() {
+ return canAddSubtitle.get();
+ }
+
+ @Override
+ public void setCanAddSubtitle(final boolean canAddSubtitle) {
+ this.canAddSubtitle.set(canAddSubtitle);
+ }
+
+ BooleanProperty canAddSubtitleProperty() {
+ return canAddSubtitle;
+ }
+
+ @Override
+ public boolean canResetSubtitles() {
+ return canResetSubtitles.get();
+ }
+
+ @Override
+ public void setCanResetSubtitles(final boolean canResetSubtitles) {
+ this.canResetSubtitles.set(canResetSubtitles);
+ }
+
+ BooleanProperty canResetSubtitlesProperty() {
+ return canResetSubtitles;
+ }
+
+ @Override
+ public boolean canSaveSubtitles() {
+ return canSaveSubtitles.get();
+ }
+
+ ReadOnlyBooleanProperty canSaveSubtitlesProperty() {
+ return canSaveSubtitles.getReadOnlyProperty();
+ }
+
+ @Override
+ public boolean isTranslating() {
+ return isTranslating.get();
+ }
+
+ @Override
+ public void setTranslating(final boolean translating) {
+ this.isTranslating.set(translating);
+ }
+
+ BooleanProperty translatingProperty() {
+ return isTranslating;
+ }
+
+ @Override
+ public boolean canEditTable() {
+ return canEditTable.get();
+ }
+
+ @Override
+ public void setCanEditTable(final boolean canEditTable) {
+ this.canEditTable.set(canEditTable);
+ }
+
+ public BooleanProperty canEditTableProperty() {
+ return canEditTable;
+ }
+}
diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXWorkBinder.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXWorkBinder.java
index 30665f9..7609efc 100644
--- a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXWorkBinder.java
+++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXWorkBinder.java
@@ -5,7 +5,7 @@ import javax.inject.Singleton;
import java.util.Objects;
@Singleton
-public class FXWorkBinder {
+public class FXWorkBinder implements FXBinder {
private final FXWorkModel workModel;
private final FXParametersModel parametersModel;
@@ -16,7 +16,8 @@ public class FXWorkBinder {
this.parametersModel = Objects.requireNonNull(parametersModel);
}
- void createBindings() {
+ @Override
+ public void createBindings() {
workModel.extractionModelProperty().bind(parametersModel.extractionModelProperty());
}
}
diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXWorkController.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXWorkController.java
index f6fc39b..b7a3a6e 100644
--- a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXWorkController.java
+++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXWorkController.java
@@ -1,49 +1,32 @@
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;
@@ -67,34 +50,18 @@ public class FXWorkController extends AbstractFXController implements WorkContro
@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 subtitlesTable;
- @FXML
- private TableColumn startColumn;
- @FXML
- private TableColumn endColumn;
- @FXML
- private TableColumn textColumn;
+
@FXML
private FXMediaController mediaController;
- @FXML
- private Button saveSubtitlesButton;
- @FXML
- private Button addSubtitleButton;
- @FXML
- private PrefixSelectionComboBox languageCombobox;
- @FXML
- private CheckComboBox translationsCombobox;
+
@FXML
private Label progressLabel;
@FXML
@@ -107,34 +74,25 @@ public class FXWorkController extends AbstractFXController implements WorkContro
private final FXWorkModel model;
private final FXWorkBinder binder;
private final SubtitleExtractor subtitleExtractor;
- private final Map subtitleConvertersMap;
private final VideoConverter videoConverter;
private final VideoLoader videoLoader;
- private final Translator translator;
- private final TimeFormatter timeFormatter;
- private final List subtitleExtensions;
-
@Inject
FXWorkController(final FXWorkModel model, final FXWorkBinder binder, final SubtitleExtractor subtitleExtractor,
- final Map subtitleConvertersMap, final VideoLoader videoLoader,
- final VideoConverter videoConverter, final Translator translator, final TimeFormatter timeFormatter) {
+ final VideoLoader videoLoader, final VideoConverter videoConverter) {
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() {
- bindComboboxes();
- bindButtons();
- bindTable();
+ void initialize() {
+ extractButton.disableProperty().bind(model.canExtractProperty().not());
+ exportSoftButton.disableProperty().bind(model.canExportProperty().not());
+ exportHardButton.disableProperty().bind(model.canExportProperty().not());
+
bindProgress();
model.selectedSubtitleProperty().addListener((observable, oldValue, newValue) -> {
@@ -148,91 +106,17 @@ public class FXWorkController extends AbstractFXController implements WorkContro
subtitleExtractor.addListener(this);
}
- private void bindComboboxes() {
- 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());
- }
-
- private void bindButtons() {
- extractButton.disableProperty().bind(model.videoProperty().isNull().or(model.statusProperty().isNotEqualTo(WorkStatus.IDLE)));
- addSubtitleButton.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)));
- saveSubtitlesButton.disableProperty().bind(Bindings.isEmpty(model.subtitles()).or(model.statusProperty().isNotEqualTo(WorkStatus.IDLE)));
- }
-
- private void bindTable() {
- 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();
- } else if (e.getCode() == KeyCode.DELETE) {
- deleteSelectedSubtitles();
- 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));
- }
-
private void bindProgress() {
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));
+ progressLabel.visibleProperty().bind(model.isProgressVisibleProperty());
+ progressBar.visibleProperty().bind(model.isProgressVisibleProperty());
+ progressDetailLabel.visibleProperty().bind(model.isProgressVisibleProperty());
+ progressLabel.managedProperty().bind(model.isProgressVisibleProperty());
+ progressBar.managedProperty().bind(model.isProgressVisibleProperty());
+ progressDetailLabel.managedProperty().bind(model.isProgressVisibleProperty());
progressBar.progressProperty().bindBidirectional(model.progressProperty());
}
- private void deleteSelectedSubtitles() {
- model.subtitles().removeAll(subtitlesTable.getSelectionModel().getSelectedItems());
- }
-
- private void editFocusedCell() {
- final var focusedCell = subtitlesTable.getFocusModel().getFocusedCell();
- if (focusedCell != null) {
- subtitlesTable.edit(focusedCell.getRow(), focusedCell.getTableColumn());
- }
- }
@FXML
private void fileButtonPressed() {
@@ -254,17 +138,17 @@ public class FXWorkController extends AbstractFXController implements WorkContro
}
}
- private SubtitleCollection extractAsync() {
+ private SubtitleCollection> extractAsync() {
try {
- return subtitleExtractor.extract(model.video(), model.videoLanguage(), model.extractionModel());
+ return subtitleExtractor.extract(model.video(), model.videoLanguageProperty().get(), model.extractionModel());
} catch (final ExtractException e) {
throw new CompletionException(e);
}
}
- private void manageExtractResult(final SubtitleCollection newCollection, final Throwable t) {
+ private void manageExtractResult(final SubtitleCollection> newCollection, final Throwable t) {
if (t == null) {
- loadCollection(newCollection);
+ model.setExtractedCollection(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()));
@@ -284,45 +168,6 @@ public class FXWorkController extends AbstractFXController implements WorkContro
}
}
- @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() {
@@ -332,27 +177,20 @@ public class FXWorkController extends AbstractFXController implements WorkContro
filePicker.setSelectedExtensionFilter(extensionFilter);
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, file.toPath());
- } 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);
+ model.setStatus(WorkStatus.EXPORTING);
+ CompletableFuture.runAsync(() -> {
+ try {
+ videoConverter.addSoftSubtitles(model.video(), model.collections().values(), file.toPath());
+ } 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()));
+ } //else show info dialog
+ model.setStatus(WorkStatus.IDLE);
+ }, Platform::runLater);
}
}
@@ -366,7 +204,7 @@ public class FXWorkController extends AbstractFXController implements WorkContro
if (file != null) {
CompletableFuture.runAsync(() -> {
try {
- videoConverter.addHardSubtitles(model.video(), model.subtitleCollection(), file.toPath());
+ videoConverter.addHardSubtitles(model.video(), model.collections().get(model.videoLanguageProperty().get()), file.toPath());
} catch (final IOException e) {
throw new CompletionException(e);
}
@@ -374,7 +212,7 @@ public class FXWorkController extends AbstractFXController implements WorkContro
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()));
- }
+ } //else show info dialog
model.setStatus(WorkStatus.IDLE);
}, Platform::runLater);
}
@@ -386,6 +224,7 @@ public class FXWorkController extends AbstractFXController implements WorkContro
return model;
}
+ @Override
public Window window() {
return fileField.getScene().getWindow();
}
@@ -395,16 +234,6 @@ public class FXWorkController extends AbstractFXController implements WorkContro
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(() -> {
@@ -413,25 +242,5 @@ public class FXWorkController extends AbstractFXController implements WorkContro
});
}
- @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());
- }
- }
}
diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXWorkModel.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXWorkModel.java
index f9adc79..8a05892 100644
--- a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXWorkModel.java
+++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXWorkModel.java
@@ -7,26 +7,22 @@ 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 com.github.gtache.autosubtitle.subtitle.gui.fx.ObservableSubtitleCollectionImpl;
+import com.github.gtache.autosubtitle.subtitle.gui.fx.ObservableSubtitleImpl;
+import javafx.beans.property.BooleanProperty;
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.ReadOnlyBooleanProperty;
+import javafx.beans.property.ReadOnlyBooleanWrapper;
+import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
+import javafx.collections.ObservableMap;
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}
@@ -35,51 +31,35 @@ import java.util.stream.Collectors;
public class FXWorkModel implements WorkModel {
private final ObjectProperty