Adds WhisperX, reworks UI (still needs some work), theoretically usable

This commit is contained in:
Guillaume Tâche
2024-08-17 22:05:04 +02:00
parent 7bddf53bab
commit 3fa51eb95b
204 changed files with 4787 additions and 1321 deletions

219
.idea/csv-editor.xml generated
View File

@@ -1,219 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CsvFileAttributes">
<option name="attributeMap">
<map>
<entry key="\tools\whisper\whisper-env\Lib\site-packages\numpy\core\tests\data\umath-validation-set-arccos.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="\tools\whisper\whisper-env\Lib\site-packages\numpy\core\tests\data\umath-validation-set-arccosh.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="\tools\whisper\whisper-env\Lib\site-packages\numpy\core\tests\data\umath-validation-set-arcsin.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="\tools\whisper\whisper-env\Lib\site-packages\numpy\core\tests\data\umath-validation-set-arcsinh.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="\tools\whisper\whisper-env\Lib\site-packages\numpy\core\tests\data\umath-validation-set-arctan.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="\tools\whisper\whisper-env\Lib\site-packages\numpy\core\tests\data\umath-validation-set-arctanh.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="\tools\whisper\whisper-env\Lib\site-packages\numpy\core\tests\data\umath-validation-set-cbrt.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="\tools\whisper\whisper-env\Lib\site-packages\numpy\core\tests\data\umath-validation-set-cos.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="\tools\whisper\whisper-env\Lib\site-packages\numpy\core\tests\data\umath-validation-set-cosh.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="\tools\whisper\whisper-env\Lib\site-packages\numpy\core\tests\data\umath-validation-set-exp.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="\tools\whisper\whisper-env\Lib\site-packages\numpy\core\tests\data\umath-validation-set-exp2.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="\tools\whisper\whisper-env\Lib\site-packages\numpy\core\tests\data\umath-validation-set-expm1.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="\tools\whisper\whisper-env\Lib\site-packages\numpy\core\tests\data\umath-validation-set-log.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="\tools\whisper\whisper-env\Lib\site-packages\numpy\core\tests\data\umath-validation-set-log10.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="\tools\whisper\whisper-env\Lib\site-packages\numpy\core\tests\data\umath-validation-set-log1p.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="\tools\whisper\whisper-env\Lib\site-packages\numpy\core\tests\data\umath-validation-set-log2.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="\tools\whisper\whisper-env\Lib\site-packages\numpy\core\tests\data\umath-validation-set-sin.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="\tools\whisper\whisper-env\Lib\site-packages\numpy\core\tests\data\umath-validation-set-sinh.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="\tools\whisper\whisper-env\Lib\site-packages\numpy\core\tests\data\umath-validation-set-tan.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="\tools\whisper\whisper-env\Lib\site-packages\numpy\core\tests\data\umath-validation-set-tanh.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="\tools\whisper\whisper-env\Lib\site-packages\numpy\random\tests\data\mt19937-testset-1.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="\tools\whisper\whisper-env\Lib\site-packages\numpy\random\tests\data\mt19937-testset-2.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="\tools\whisper\whisper-env\Lib\site-packages\numpy\random\tests\data\pcg64-testset-1.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="\tools\whisper\whisper-env\Lib\site-packages\numpy\random\tests\data\pcg64-testset-2.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="\tools\whisper\whisper-env\Lib\site-packages\numpy\random\tests\data\pcg64dxsm-testset-1.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="\tools\whisper\whisper-env\Lib\site-packages\numpy\random\tests\data\pcg64dxsm-testset-2.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="\tools\whisper\whisper-env\Lib\site-packages\numpy\random\tests\data\philox-testset-1.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="\tools\whisper\whisper-env\Lib\site-packages\numpy\random\tests\data\philox-testset-2.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="\tools\whisper\whisper-env\Lib\site-packages\numpy\random\tests\data\sfc64-testset-1.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="\tools\whisper\whisper-env\Lib\site-packages\numpy\random\tests\data\sfc64-testset-2.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
</map>
</option>
</component>
</project>

8
.idea/encodings.xml generated
View File

@@ -7,6 +7,8 @@
<file url="file://$PROJECT_DIR$/cli/src/main/resources" charset="UTF-8" /> <file url="file://$PROJECT_DIR$/cli/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/client/src/main/java" charset="UTF-8" /> <file url="file://$PROJECT_DIR$/client/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/client/src/main/resources" charset="UTF-8" /> <file url="file://$PROJECT_DIR$/client/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/conda/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/conda/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/core/src/main/java" charset="UTF-8" /> <file url="file://$PROJECT_DIR$/core/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/core/src/main/resources" charset="UTF-8" /> <file url="file://$PROJECT_DIR$/core/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/deepl/src/main/java" charset="UTF-8" /> <file url="file://$PROJECT_DIR$/deepl/src/main/java" charset="UTF-8" />
@@ -27,8 +29,14 @@
<file url="file://$PROJECT_DIR$/server/src/main/resources" charset="UTF-8" /> <file url="file://$PROJECT_DIR$/server/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" /> <file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" /> <file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/whisper/base/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/whisper/base/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/whisper/common/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/whisper/common/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/whisper/src/main/java" charset="UTF-8" /> <file url="file://$PROJECT_DIR$/whisper/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/whisper/src/main/resources" charset="UTF-8" /> <file url="file://$PROJECT_DIR$/whisper/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/whisper/whisperx/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/whisper/whisperx/src/main/resources" charset="UTF-8" />
<file url="PROJECT" charset="UTF-8" /> <file url="PROJECT" charset="UTF-8" />
</component> </component>
</project> </project>

1
.idea/sonarlint.xml generated
View File

@@ -4,6 +4,7 @@
<option name="moduleMapping"> <option name="moduleMapping">
<map> <map>
<entry key="autosubtitle-gui-fx" value="autosubtitle-fx" /> <entry key="autosubtitle-gui-fx" value="autosubtitle-fx" />
<entry key="whisper-base" value="whisperbase" />
</map> </map>
</option> </option>
</component> </component>

View File

@@ -6,7 +6,7 @@ import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
/** /**
* Translates texts and subtitles * Translates texts and subtitles
*/ */
public interface Translator { public interface Translator<T extends Subtitle> {
/** /**
* Guesses the language of the given text * Guesses the language of the given text
@@ -42,7 +42,7 @@ public interface Translator {
* @param to The target language * @param to The target language
* @return The translated subtitle * @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 * Translates the given subtitles collection to the given language
@@ -51,5 +51,5 @@ public interface Translator {
* @param to The target language * @param to The target language
* @return The translated subtitles collection * @return The translated subtitles collection
*/ */
SubtitleCollection translate(SubtitleCollection collection, Language to); SubtitleCollection<T> translate(SubtitleCollection<?> collection, Language to);
} }

View File

@@ -19,7 +19,7 @@ public interface VideoConverter {
* @return The modified video * @return The modified video
* @throws IOException If an I/O error occurs * @throws IOException If an I/O error occurs
*/ */
Video addSoftSubtitles(final Video video, final Collection<SubtitleCollection> subtitles) throws IOException; Video addSoftSubtitles(final Video video, final Collection<? extends SubtitleCollection<?>> subtitles) throws IOException;
/** /**
* Adds soft subtitles to the given video * Adds soft subtitles to the given video
@@ -29,7 +29,7 @@ public interface VideoConverter {
* @param path The output path * @param path The output path
* @throws IOException If an I/O error occurs * @throws IOException If an I/O error occurs
*/ */
void addSoftSubtitles(final Video video, final Collection<SubtitleCollection> 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 * Adds hard subtitles to the given video
@@ -39,7 +39,7 @@ public interface VideoConverter {
* @return The modified video * @return The modified video
* @throws IOException If an I/O error occurs * @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 * Adds hard subtitles to the given video
@@ -49,7 +49,7 @@ public interface VideoConverter {
* @param path The output path * @param path The output path
* @throws IOException If an I/O error occurs * @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 * Extracts the audio from the given video

View File

@@ -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<Path> 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();
}

View File

@@ -22,10 +22,11 @@ public interface ProcessListener {
String readLine() throws IOException; 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 * @param duration The maximum time to wait
* @return The process result * @return The process result
* @throws IOException if an error occurs
*/ */
ProcessResult join(final Duration duration) throws IOException; ProcessResult join(final Duration duration) throws IOException;
} }

View File

@@ -1,6 +1,7 @@
package com.github.gtache.autosubtitle.process; package com.github.gtache.autosubtitle.process;
import java.io.IOException; import java.io.IOException;
import java.time.Duration;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@@ -10,24 +11,25 @@ import java.util.List;
public interface ProcessRunner { public interface ProcessRunner {
/** /**
* Runs a command * Runs a command and waits max 1 hour for the process to run
* *
* @param args the command * @param args the command
* @return the result * @return the result
* @throws IOException if something goes wrong * @throws IOException if something goes wrong
*/ */
default ProcessResult run(final String... args) throws IOException { default ProcessResult run(final String... args) throws IOException {
return run(Arrays.asList(args)); return run(Arrays.asList(args), Duration.ofHours(1));
} }
/** /**
* Runs a command * Runs a command
* *
* @param args the command * @param args the command
* @param duration The maximum duration to wait for
* @return the result * @return the result
* @throws IOException if something goes wrong * @throws IOException if something goes wrong
*/ */
ProcessResult run(final List<String> args) throws IOException; ProcessResult run(final List<String> args, final Duration duration) throws IOException;
/** /**
* Starts a process * Starts a process

View File

@@ -7,7 +7,7 @@ import java.util.Collection;
/** /**
* Represents a collection of {@link Subtitle} * Represents a collection of {@link Subtitle}
*/ */
public interface SubtitleCollection { public interface SubtitleCollection<T extends Subtitle> {
/** /**
* @return The whole text of the subtitles * @return The whole text of the subtitles
@@ -17,7 +17,7 @@ public interface SubtitleCollection {
/** /**
* @return The subtitles * @return The subtitles
*/ */
Collection<? extends Subtitle> subtitles(); Collection<T> subtitles();
/** /**
* @return The language of the subtitles * @return The language of the subtitles

View File

@@ -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<T extends Subtitle> {
/**
* 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<Language, SubtitleCollection<T>> 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<String> supportedArchiveExtensions();
/**
* @return The supported file extensions for single collection exports
*/
Collection<String> supportedSingleFileExtensions();
}

View File

@@ -1,5 +1,6 @@
package com.github.gtache.autosubtitle.subtitle.converter; package 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.SubtitleCollection;
import java.io.IOException; 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 * Converts subtitles to a specific format (e.g. srt, ssa, ass, ...) and vice-versa
*/ */
public interface SubtitleConverter { public interface SubtitleConverter<T extends Subtitle> {
/** /**
* Converts the subtitle collection * Converts the subtitle collection
@@ -17,7 +18,7 @@ public interface SubtitleConverter {
* @param collection The collection * @param collection The collection
* @return The converted subtitles as the content of a file * @return The converted subtitles as the content of a file
*/ */
String format(final SubtitleCollection collection); String format(final SubtitleCollection<?> collection);
/** /**
* Parses a subtitle collection * Parses a subtitle collection
@@ -26,7 +27,7 @@ public interface SubtitleConverter {
* @return The subtitle collection * @return The subtitle collection
* @throws ParseException If an error occurred * @throws ParseException If an error occurred
*/ */
default SubtitleCollection parse(final Path file) throws ParseException { default SubtitleCollection<T> parse(final Path file) throws ParseException {
try { try {
final var content = Files.readString(file); final var content = Files.readString(file);
return parse(content); return parse(content);
@@ -42,7 +43,7 @@ public interface SubtitleConverter {
* @return The subtitle collection * @return The subtitle collection
* @throws ParseException If an error occurred * @throws ParseException If an error occurred
*/ */
SubtitleCollection parse(String content) throws ParseException; SubtitleCollection<T> parse(String content) throws ParseException;
/** /**
* Check if the parser can parse the given file * Check if the parser can parse the given file

View File

@@ -3,12 +3,13 @@ package com.github.gtache.autosubtitle.subtitle.extractor;
import com.github.gtache.autosubtitle.Audio; import com.github.gtache.autosubtitle.Audio;
import com.github.gtache.autosubtitle.Language; import com.github.gtache.autosubtitle.Language;
import com.github.gtache.autosubtitle.Video; 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.SubtitleCollection;
/** /**
* Extracts subtitles from a video or audio * Extracts subtitles from a video or audio
*/ */
public interface SubtitleExtractor { public interface SubtitleExtractor<T extends Subtitle> {
/** /**
* Adds a listener * Adds a listener
@@ -37,7 +38,7 @@ public interface SubtitleExtractor {
* @return The extracted subtitle collection * @return The extracted subtitle collection
* @throws ExtractException If an error occurs * @throws ExtractException If an error occurs
*/ */
default SubtitleCollection extract(final Video video, final ExtractionModel model) throws ExtractException { default SubtitleCollection<T> extract(final Video video, final ExtractionModel model) throws ExtractException {
return extract(video, Language.AUTO, model); return extract(video, Language.AUTO, model);
} }
@@ -50,7 +51,7 @@ public interface SubtitleExtractor {
* @return The extracted subtitle collection * @return The extracted subtitle collection
* @throws ExtractException If an error occurs * @throws ExtractException If an error occurs
*/ */
SubtitleCollection extract(final Video video, final Language language, final ExtractionModel model) throws ExtractException; SubtitleCollection<T> extract(final Video video, final Language language, final ExtractionModel model) throws ExtractException;
/** /**
* Extracts the subtitles from an audio * Extracts the subtitles from an audio
@@ -60,7 +61,7 @@ public interface SubtitleExtractor {
* @return The extracted subtitle collection * @return The extracted subtitle collection
* @throws ExtractException If an error occurs * @throws ExtractException If an error occurs
*/ */
default SubtitleCollection extract(final Audio audio, final ExtractionModel model) throws ExtractException { default SubtitleCollection<T> extract(final Audio audio, final ExtractionModel model) throws ExtractException {
return extract(audio, Language.AUTO, model); return extract(audio, Language.AUTO, model);
} }
@@ -73,5 +74,5 @@ public interface SubtitleExtractor {
* @return The extracted subtitle collection * @return The extracted subtitle collection
* @throws ExtractException If an error occurs * @throws ExtractException If an error occurs
*/ */
SubtitleCollection extract(final Audio audio, final Language language, final ExtractionModel model) throws ExtractException; SubtitleCollection<T> extract(final Audio audio, final Language language, final ExtractionModel model) throws ExtractException;
} }

View File

@@ -3,6 +3,7 @@
*/ */
module com.github.gtache.autosubtitle.api { module com.github.gtache.autosubtitle.api {
exports com.github.gtache.autosubtitle; exports com.github.gtache.autosubtitle;
exports com.github.gtache.autosubtitle.archive;
exports com.github.gtache.autosubtitle.process; exports com.github.gtache.autosubtitle.process;
exports com.github.gtache.autosubtitle.setup; exports com.github.gtache.autosubtitle.setup;
exports com.github.gtache.autosubtitle.subtitle; exports com.github.gtache.autosubtitle.subtitle;

View File

@@ -1,5 +1,6 @@
package com.github.gtache.autosubtitle.com.github.gtache.autosubtitle.subtitle.converter; 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.SubtitleCollection;
import com.github.gtache.autosubtitle.subtitle.converter.ParseException; import com.github.gtache.autosubtitle.subtitle.converter.ParseException;
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter; import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter;
@@ -23,10 +24,10 @@ import static org.mockito.Mockito.when;
class TestSubtitleConverter { class TestSubtitleConverter {
private final SubtitleConverter subtitleConverter; private final SubtitleConverter subtitleConverter;
private final SubtitleCollection subtitleCollection; private final SubtitleCollection<Subtitle> subtitleCollection;
TestSubtitleConverter(@Mock final SubtitleConverter subtitleConverter, TestSubtitleConverter(@Mock final SubtitleConverter subtitleConverter,
@Mock final SubtitleCollection subtitleCollection) { @Mock final SubtitleCollection<Subtitle> subtitleCollection) {
this.subtitleConverter = Objects.requireNonNull(subtitleConverter); this.subtitleConverter = Objects.requireNonNull(subtitleConverter);
this.subtitleCollection = Objects.requireNonNull(subtitleCollection); this.subtitleCollection = Objects.requireNonNull(subtitleCollection);
} }

View File

@@ -7,6 +7,7 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import java.io.IOException; import java.io.IOException;
import java.time.Duration;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@@ -32,7 +33,7 @@ class TestProcessRunner {
@Test @Test
void testRunVarargs() throws IOException { 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(); when(processRunner.run(any(String[].class))).thenCallRealMethod();
assertEquals(result, processRunner.run("arg1", "arg2")); assertEquals(result, processRunner.run("arg1", "arg2"));

View File

@@ -22,7 +22,7 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.github.gtache.autosubtitle</groupId> <groupId>com.github.gtache.autosubtitle</groupId>
<artifactId>autosubtitle-whisper</artifactId> <artifactId>autosubtitle-whisperx</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.logging.log4j</groupId> <groupId>org.apache.logging.log4j</groupId>

View File

@@ -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.deepl.DeepLModule;
import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegModule; import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegModule;
import com.github.gtache.autosubtitle.modules.impl.CoreModule; 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 com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter;
import dagger.Component; import dagger.Component;
import javax.inject.Singleton; import javax.inject.Singleton;
import java.util.Map; 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 @Singleton
public interface CliComponent { public interface CliComponent {

View File

@@ -4,6 +4,6 @@
module com.github.gtache.autosubtitle.cli { module com.github.gtache.autosubtitle.cli {
requires com.github.gtache.autosubtitle.deepl; requires com.github.gtache.autosubtitle.deepl;
requires com.github.gtache.autosubtitle.ffmpeg; requires com.github.gtache.autosubtitle.ffmpeg;
requires com.github.gtache.autosubtitle.whisper; requires com.github.gtache.autosubtitle.whisperx;
requires info.picocli; requires info.picocli;
} }

20
conda/pom.xml Normal file
View File

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

View File

@@ -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 javax.inject.Qualifier;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;

View File

@@ -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 javax.inject.Qualifier;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;

View File

@@ -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 javax.inject.Qualifier;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;

View File

@@ -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 javax.inject.Qualifier;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;

View File

@@ -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 javax.inject.Qualifier;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;

View File

@@ -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.Architecture;
import com.github.gtache.autosubtitle.impl.OS; 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.Module;
import dagger.Provides; import dagger.Provides;
@@ -56,13 +58,13 @@ public final class CondaSetupModule {
@Provides @Provides
@CondaRootPath @CondaRootPath
static Path providesCondaRootPath(@WhisperBundledRoot final Path root, final OS os) { static Path providesCondaRootPath(@ToolsRoot final Path root) {
return root.resolve(MINICONDA3); return root.resolve(MINICONDA3);
} }
@Provides @Provides
@CondaInstallerPath @CondaInstallerPath
static Path providesCondaInstallerPath(@WhisperBundledRoot final Path root, final OS os) { static Path providesCondaInstallerPath(@CacheRoot final Path root, final OS os) {
return root.resolve("cache").resolve("conda-install" + (os == OS.WINDOWS ? ".exe" : ".sh")); return root.resolve("conda-install" + (os == OS.WINDOWS ? ".exe" : ".sh"));
} }
} }

View File

@@ -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 javax.inject.Qualifier;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;

View File

@@ -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.Architecture;
import com.github.gtache.autosubtitle.impl.OS; import com.github.gtache.autosubtitle.impl.OS;

View File

@@ -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.impl.OS;
import com.github.gtache.autosubtitle.setup.SetupException; import com.github.gtache.autosubtitle.setup.SetupException;
@@ -13,7 +13,9 @@ import java.io.IOException;
import java.net.http.HttpClient; import java.net.http.HttpClient;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.Duration;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream; import java.util.stream.Stream;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
@@ -67,8 +69,8 @@ public class CondaSetupManager extends AbstractSetupManager {
logger.info("Conda downloaded"); logger.info("Conda downloaded");
} }
switch (configuration.os()) { switch (configuration.os()) {
case WINDOWS -> installWindows(); case OS.WINDOWS -> installWindows();
case MAC, LINUX -> installLinux(); case OS.MAC, OS.LINUX -> installLinux();
} }
} }
@@ -77,7 +79,7 @@ public class CondaSetupManager extends AbstractSetupManager {
final var installerPath = configuration.condaInstallerPath(); final var installerPath = configuration.condaInstallerPath();
final var rootPath = configuration.condaRootPath(); final var rootPath = configuration.condaRootPath();
logger.info("Installing conda using {}", installerPath); 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) { if (result.exitCode() == 0) {
logger.info("Installed conda to {}", rootPath); logger.info("Installed conda to {}", rootPath);
} else { } else {
@@ -93,7 +95,7 @@ public class CondaSetupManager extends AbstractSetupManager {
final var installerPath = configuration.condaInstallerPath(); final var installerPath = configuration.condaInstallerPath();
final var rootPath = configuration.condaRootPath(); final var rootPath = configuration.condaRootPath();
logger.info("Installing conda using {}", installerPath); 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) { if (result.exitCode() == 0) {
logger.info("Installed conda to {}", rootPath); logger.info("Installed conda to {}", rootPath);
} else { } else {
@@ -106,9 +108,9 @@ public class CondaSetupManager extends AbstractSetupManager {
private void downloadConda() throws SetupException { private void downloadConda() throws SetupException {
switch (configuration.os()) { switch (configuration.os()) {
case WINDOWS -> downloadCondaWindows(); case OS.WINDOWS -> downloadCondaWindows();
case MAC -> downloadCondaMac(); case OS.MAC -> downloadCondaMac();
case LINUX -> downloadCondaLinux(); case OS.LINUX -> downloadCondaLinux();
} }
logger.info("Downloaded conda to {}", configuration.condaInstallerPath()); logger.info("Downloaded conda to {}", configuration.condaInstallerPath());
} }
@@ -152,7 +154,7 @@ public class CondaSetupManager extends AbstractSetupManager {
@Override @Override
public void update() throws SetupException { public void update() throws SetupException {
try { 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) { if (result.exitCode() == 0) {
logger.info("Conda updated"); logger.info("Conda updated");
} else { } 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(); final var args = Stream.concat(Stream.of(getCondaPath().toString(), "create", "-y", "-p", path.toString(), "python=" + pythonVersion), Arrays.stream(packages)).toList();
try { try {
logger.info("Creating venv {}", path); logger.info("Creating venv {}", path);
final var result = run(args); final var result = run(args, Duration.ofMinutes(15));
if (result.exitCode() == 0) { if (result.exitCode() == 0) {
logger.info("Created venv {}", path); logger.info("Created venv {}", path);
} else { } else {
@@ -200,7 +202,7 @@ public class CondaSetupManager extends AbstractSetupManager {
private boolean isSystemCondaInstalled() throws SetupException { private boolean isSystemCondaInstalled() throws SetupException {
try { 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) { if (result.exitCode() == 0) {
final var output = result.output().getFirst(); final var output = result.output().getFirst();
final var versionString = output.substring(output.indexOf(' ') + 1); final var versionString = output.substring(output.indexOf(' ') + 1);

View File

@@ -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;
}

View File

@@ -24,5 +24,10 @@
<groupId>org.apache.logging.log4j</groupId> <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId> <artifactId>log4j-api</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -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.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; 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<Path> files, final Path destination) throws IOException {
throw new UnsupportedOperationException("Not supported");
}
@Override @Override
public void decompress(final Path archive, final Path destination) throws IOException { public void decompress(final Path archive, final Path destination) throws IOException {
if (!isPathSupported(archive)) { if (!isPathSupported(archive)) {
@@ -49,9 +64,9 @@ public class ZipDecompresser implements Decompresser {
} }
return destPath; return destPath;
} }
@Override @Override
public boolean isPathSupported(final Path path) { public String archiveExtension() {
return path.getFileName().toString().endsWith(".zip"); return "zip";
} }
} }

View File

@@ -1,23 +1,33 @@
package com.github.gtache.autosubtitle.modules.impl; 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.Architecture;
import com.github.gtache.autosubtitle.impl.DaggerException; import com.github.gtache.autosubtitle.impl.DaggerException;
import com.github.gtache.autosubtitle.impl.OS; import com.github.gtache.autosubtitle.impl.OS;
import com.github.gtache.autosubtitle.modules.setup.impl.SetupModule; import com.github.gtache.autosubtitle.modules.setup.impl.SetupModule;
import com.github.gtache.autosubtitle.modules.subtitle.impl.SubtitleModule; import com.github.gtache.autosubtitle.modules.subtitle.impl.SubtitleModule;
import dagger.Binds;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import dagger.multibindings.IntoMap;
import dagger.multibindings.StringKey;
/** /**
* Dagger module for Core * Dagger module for Core
*/ */
@Module(includes = {SetupModule.class, SubtitleModule.class}) @Module(includes = {SetupModule.class, SubtitleModule.class})
public final class CoreModule { public abstract class CoreModule {
private CoreModule() { private CoreModule() {
} }
@Binds
@StringKey("zip")
@IntoMap
abstract Archiver bindsZipDecompresser(final ZipDecompresser decompresser);
@Provides @Provides
static OS providesOS() { static OS providesOS() {
final var os = OS.getOS(); final var os = OS.getOS();

View File

@@ -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 javax.inject.Qualifier;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
@@ -12,5 +12,5 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Documented @Documented
@Retention(RUNTIME) @Retention(RUNTIME)
@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD}) @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
public @interface WhisperVersion { public @interface CacheRoot {
} }

View File

@@ -4,6 +4,8 @@ import dagger.Module;
import dagger.Provides; import dagger.Provides;
import java.net.http.HttpClient; import java.net.http.HttpClient;
import java.nio.file.Path;
import java.nio.file.Paths;
/** /**
* Dagger core module for setup * Dagger core module for setup
@@ -14,6 +16,18 @@ public final class SetupModule {
private SetupModule() { private SetupModule() {
} }
@Provides
@CacheRoot
static Path providesCacheRoot() {
return Paths.get("cache");
}
@Provides
@ToolsRoot
static Path providesToolsRoot() {
return Paths.get("tools");
}
@Provides @Provides
static HttpClient providesHttpClient() { static HttpClient providesHttpClient() {
return HttpClient.newHttpClient(); return HttpClient.newHttpClient();

View File

@@ -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 {
}

View File

@@ -1,7 +1,9 @@
package com.github.gtache.autosubtitle.modules.subtitle.impl; 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.SubtitleConverter;
import com.github.gtache.autosubtitle.subtitle.converter.impl.SRTSubtitleConverter; import com.github.gtache.autosubtitle.subtitle.converter.impl.SRTSubtitleConverter;
import com.github.gtache.autosubtitle.subtitle.impl.SubtitleImporterExporterImpl;
import dagger.Binds; import dagger.Binds;
import dagger.Module; import dagger.Module;
import dagger.multibindings.IntoMap; import dagger.multibindings.IntoMap;
@@ -20,4 +22,7 @@ public abstract class SubtitleModule {
@IntoMap @IntoMap
@StringKey("srt") @StringKey("srt")
abstract SubtitleConverter bindsSubtitleConverter(final SRTSubtitleConverter converter); abstract SubtitleConverter bindsSubtitleConverter(final SRTSubtitleConverter converter);
@Binds
abstract SubtitleImporterExporter bindsSubtitleImporterExporter(final SubtitleImporterExporterImpl impl);
} }

View File

@@ -9,7 +9,6 @@ import org.apache.logging.log4j.Logger;
import java.io.IOException; import java.io.IOException;
import java.time.Duration; import java.time.Duration;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture;
/** /**
* Base implementation of {@link ProcessRunner} * Base implementation of {@link ProcessRunner}
@@ -19,19 +18,9 @@ public abstract class AbstractProcessRunner implements ProcessRunner {
private static final Logger logger = LogManager.getLogger(AbstractProcessRunner.class); private static final Logger logger = LogManager.getLogger(AbstractProcessRunner.class);
@Override @Override
public ProcessResult run(final List<String> args) throws IOException { public ProcessResult run(final List<String> args, final Duration duration) throws IOException {
final var listener = startListen(args); final var listener = startListen(args);
CompletableFuture.runAsync(() -> { return listener.join(duration);
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));
} }
@Override @Override
@@ -51,11 +40,12 @@ public abstract class AbstractProcessRunner implements ProcessRunner {
/** /**
* Runs a process and writes the output to the log * 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 * @return the result
* @throws IOException if an error occurs * @throws IOException if an error occurs
*/ */
protected ProcessResult runListen(final List<String> args) throws IOException { protected ProcessResult runListen(final List<String> args, final Duration duration) throws IOException {
final var listener = startListen(args); final var listener = startListen(args);
var line = listener.readLine(); var line = listener.readLine();
final var processName = args.getFirst(); final var processName = args.getFirst();
@@ -63,6 +53,6 @@ public abstract class AbstractProcessRunner implements ProcessRunner {
logger.info("[{}]: {}", processName, line); logger.info("[{}]: {}", processName, line);
line = listener.readLine(); line = listener.readLine();
} }
return listener.join(Duration.ofHours(1)); return listener.join(duration);
} }
} }

View File

@@ -2,22 +2,25 @@ package com.github.gtache.autosubtitle.process.impl;
import com.github.gtache.autosubtitle.process.ProcessListener; import com.github.gtache.autosubtitle.process.ProcessListener;
import com.github.gtache.autosubtitle.process.ProcessResult; 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.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.Duration; import java.time.Duration;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
* Implementation of {@link ProcessListener} * Implementation of {@link ProcessListener}
*/ */
public class ProcessListenerImpl implements ProcessListener { public class ProcessListenerImpl implements ProcessListener {
private static final Logger logger = LogManager.getLogger(ProcessListenerImpl.class);
private final Process process; private final Process process;
private final BufferedReader reader; private final BufferedReader reader;
private final List<String> output; private final List<String> output;
@@ -30,7 +33,7 @@ public class ProcessListenerImpl implements ProcessListener {
public ProcessListenerImpl(final Process process) { public ProcessListenerImpl(final Process process) {
this.process = Objects.requireNonNull(process); this.process = Objects.requireNonNull(process);
this.reader = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8)); this.reader = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8));
this.output = new ArrayList<>(); this.output = new CopyOnWriteArrayList<>();
} }
@Override @Override
@@ -48,7 +51,24 @@ public class ProcessListenerImpl implements ProcessListener {
} }
@Override @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 { try {
process.waitFor(duration.getSeconds(), TimeUnit.SECONDS); process.waitFor(duration.getSeconds(), TimeUnit.SECONDS);
} catch (final InterruptedException e) { } catch (final InterruptedException e) {
@@ -58,11 +78,6 @@ public class ProcessListenerImpl implements ProcessListener {
if (process.isAlive()) { if (process.isAlive()) {
process.destroyForcibly(); process.destroyForcibly();
} }
//Reads lines to output
while (readLine() != null) {
//Do nothing
}
reader.close();
return new ProcessResultImpl(process.exitValue(), output); return new ProcessResultImpl(process.exitValue(), output);
} }
} }

View File

@@ -9,7 +9,6 @@ import com.github.gtache.autosubtitle.subtitle.impl.SubtitleCollectionImpl;
import com.github.gtache.autosubtitle.subtitle.impl.SubtitleImpl; import com.github.gtache.autosubtitle.subtitle.impl.SubtitleImpl;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -20,17 +19,17 @@ import static java.util.Objects.requireNonNull;
/** /**
* Converts subtitles to SRT format * Converts subtitles to SRT format
*/ */
@Singleton public class SRTSubtitleConverter implements SubtitleConverter<SubtitleImpl> {
public class SRTSubtitleConverter implements SubtitleConverter {
private final Translator translator; private final Translator<?> translator;
@Inject @Inject
SRTSubtitleConverter(final Translator translator) { SRTSubtitleConverter(final Translator translator) {
this.translator = requireNonNull(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(); final var subtitles = collection.subtitles().stream().sorted(Comparator.comparing(Subtitle::start).thenComparing(Subtitle::end)).toList();
return IntStream.range(0, subtitles.size()).mapToObj(i -> { return IntStream.range(0, subtitles.size()).mapToObj(i -> {
final var subtitle = subtitles.get(i); final var subtitle = subtitles.get(i);
@@ -51,7 +50,7 @@ public class SRTSubtitleConverter implements SubtitleConverter {
} }
@Override @Override
public SubtitleCollection parse(final String content) throws ParseException { public SubtitleCollectionImpl<SubtitleImpl> parse(final String content) throws ParseException {
try { try {
final var elements = content.split("\n\n"); final var elements = content.split("\n\n");
final var subtitles = Arrays.stream(elements).filter(element -> !element.isBlank()).map(element -> { 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); return new SubtitleImpl(text, start, end, null, null);
}).toList(); }).toList();
final var text = subtitles.stream().map(Subtitle::content).collect(Collectors.joining(" ")); 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) { } catch (final Exception e) {
throw new ParseException(e); throw new ParseException(e);
} }

View File

@@ -13,8 +13,8 @@ import static java.util.Objects.requireNonNull;
/** /**
* Implementation of {@link SubtitleCollection} * Implementation of {@link SubtitleCollection}
*/ */
public record SubtitleCollectionImpl(String text, Collection<? extends Subtitle> subtitles, public record SubtitleCollectionImpl<T extends Subtitle>(String text, Collection<T> subtitles,
Language language) implements SubtitleCollection { Language language) implements SubtitleCollection<T> {
public SubtitleCollectionImpl { public SubtitleCollectionImpl {
Objects.requireNonNull(text); Objects.requireNonNull(text);

View File

@@ -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); 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());
}
} }

View File

@@ -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<SubtitleImpl> {
private static final Logger logger = LogManager.getLogger(SubtitleImporterExporterImpl.class);
private final Map<String, Archiver> archiverMap;
private final Map<String, SubtitleConverter<?>> converterMap;
@Inject
SubtitleImporterExporterImpl(final Map<String, Archiver> archiverMap, final Map<String, SubtitleConverter> converterMap) {
this.archiverMap = Map.copyOf(archiverMap);
this.converterMap = converterMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
@Override
public Map<Language, SubtitleCollection<SubtitleImpl>> 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<SubtitleImpl> 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<Language, SubtitleCollection<SubtitleImpl>> 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<Path>();
try (final var stream = Files.list(tempDirectory)) {
stream.forEach(files::add);
}
final var map = new EnumMap<Language, SubtitleCollection<SubtitleImpl>>(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<Path>();
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<String> supportedArchiveExtensions() {
return archiverMap.keySet();
}
@Override
public Collection<String> supportedSingleFileExtensions() {
return converterMap.keySet();
}
}

View File

@@ -9,6 +9,7 @@ module com.github.gtache.autosubtitle.core {
requires org.apache.logging.log4j; requires org.apache.logging.log4j;
exports com.github.gtache.autosubtitle.impl; exports com.github.gtache.autosubtitle.impl;
exports com.github.gtache.autosubtitle.archive.impl;
exports com.github.gtache.autosubtitle.process.impl; exports com.github.gtache.autosubtitle.process.impl;
exports com.github.gtache.autosubtitle.setup.impl; exports com.github.gtache.autosubtitle.setup.impl;
exports com.github.gtache.autosubtitle.subtitle.impl; exports com.github.gtache.autosubtitle.subtitle.impl;

View File

@@ -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")));
}
}

View File

@@ -22,7 +22,7 @@ class TestAbstractProcessRunner {
@Test @Test
void testRun() throws IOException { void testRun() throws IOException {
final var expected = new ProcessResultImpl(0, List.of("1", "2", "3")); 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); assertEquals(expected, actual);
} }
@@ -50,7 +50,7 @@ class TestAbstractProcessRunner {
@Test @Test
void testRunListen() throws IOException { void testRunListen() throws IOException {
final var result = dummyProcessRunner.runListen(ARGS); final var result = dummyProcessRunner.runListen(ARGS, Duration.ofSeconds(5));
assertEquals(0, result.exitCode()); assertEquals(0, result.exitCode());
assertEquals(List.of("1", "2", "3"), result.output()); assertEquals(List.of("1", "2", "3"), result.output());
} }

View File

@@ -50,7 +50,7 @@ class TestSRTSubtitleConverter {
final var end2 = 12L * 60 * 60 * 1000 + 23 * 60 * 1000 + 34 * 1000 + 457; 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 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 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); final var converter = new SRTSubtitleConverter(translator);
assertEquals(subtitles, converter.parse(in)); assertEquals(subtitles, converter.parse(in));
assertEquals(in, converter.format(subtitles)); assertEquals(in, converter.format(subtitles));

View File

@@ -3,6 +3,7 @@ package com.github.gtache.autosubtitle.subtitle.extractor.impl;
import com.github.gtache.autosubtitle.Audio; import com.github.gtache.autosubtitle.Audio;
import com.github.gtache.autosubtitle.Language; import com.github.gtache.autosubtitle.Language;
import com.github.gtache.autosubtitle.Video; 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.SubtitleCollection;
import com.github.gtache.autosubtitle.subtitle.extractor.ExtractEvent; import com.github.gtache.autosubtitle.subtitle.extractor.ExtractEvent;
import com.github.gtache.autosubtitle.subtitle.extractor.ExtractException; import com.github.gtache.autosubtitle.subtitle.extractor.ExtractException;
@@ -56,12 +57,12 @@ class TestAbstractSubtitleExtractor {
private static final class DummySubtitleExtractor extends AbstractSubtitleExtractor { private static final class DummySubtitleExtractor extends AbstractSubtitleExtractor {
@Override @Override
public SubtitleCollection extract(final Video video, final Language language, final ExtractionModel model) throws ExtractException { public SubtitleCollection<Subtitle> extract(final Video video, final Language language, final ExtractionModel model) throws ExtractException {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override @Override
public SubtitleCollection extract(final Audio audio, final Language language, final ExtractionModel model) throws ExtractException { public SubtitleCollection<Subtitle> extract(final Audio audio, final Language language, final ExtractionModel model) throws ExtractException {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
} }

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>

View File

@@ -10,7 +10,7 @@ import javax.inject.Inject;
/** /**
* DeepL implementation of {@link Translator} * DeepL implementation of {@link Translator}
*/ */
public class DeepLTranslator implements Translator { public class DeepLTranslator implements Translator<Subtitle> {
@Inject @Inject
DeepLTranslator() { DeepLTranslator() {
@@ -32,7 +32,7 @@ public class DeepLTranslator implements Translator {
} }
@Override @Override
public SubtitleCollection translate(final SubtitleCollection collection, final Language to) { public SubtitleCollection<Subtitle> translate(final SubtitleCollection<?> collection, final Language to) {
return null; return null;
} }
} }

View File

@@ -24,6 +24,11 @@
<groupId>org.tukaani</groupId> <groupId>org.tukaani</groupId>
<artifactId>xz</artifactId> <artifactId>xz</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -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 org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import javax.inject.Inject;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.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<Path> files, final Path destination) throws IOException {
throw new UnsupportedOperationException("Not implemented yet");
}
@Override @Override
public void decompress(final Path archive, final Path destination) throws IOException { public void decompress(final Path archive, final Path destination) throws IOException {
if (!isPathSupported(archive)) { 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 destPath = destinationDir.resolve(entry.getName());
final var destDirPath = destinationDir.toAbsolutePath().toString(); final var destDirPath = destinationDir.toAbsolutePath().toString();
@@ -51,7 +65,7 @@ public class TarDecompresser implements Decompresser {
} }
@Override @Override
public boolean isPathSupported(final Path path) { public String archiveExtension() {
return path.getFileName().toString().endsWith(".tar"); return "tar";
} }
} }

View File

@@ -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<Path> 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";
}
}

View File

@@ -8,17 +8,17 @@ import com.github.gtache.autosubtitle.impl.AudioInfoImpl;
import com.github.gtache.autosubtitle.impl.FileAudioImpl; import com.github.gtache.autosubtitle.impl.FileAudioImpl;
import com.github.gtache.autosubtitle.impl.FileVideoImpl; import com.github.gtache.autosubtitle.impl.FileVideoImpl;
import com.github.gtache.autosubtitle.impl.VideoInfoImpl; import com.github.gtache.autosubtitle.impl.VideoInfoImpl;
import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegBundledPath; import com.github.gtache.autosubtitle.modules.setup.ffmpeg.FFmpegBundledPath;
import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegSystemPath; import com.github.gtache.autosubtitle.modules.setup.ffmpeg.FFmpegSystemPath;
import com.github.gtache.autosubtitle.process.impl.AbstractProcessRunner; import com.github.gtache.autosubtitle.process.impl.AbstractProcessRunner;
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection; import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter; import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@@ -32,30 +32,29 @@ import static java.util.Objects.requireNonNull;
/** /**
* FFmpeg implementation of {@link VideoConverter} * FFmpeg implementation of {@link VideoConverter}
*/ */
@Singleton
public class FFmpegVideoConverter extends AbstractProcessRunner implements VideoConverter { public class FFmpegVideoConverter extends AbstractProcessRunner implements VideoConverter {
private static final String TEMP_FILE_PREFIX = "autosubtitle"; private static final String TEMP_FILE_PREFIX = "autosubtitle";
private final Path bundledPath; private final Path bundledPath;
private final Path systemPath; private final Path systemPath;
private final SubtitleConverter subtitleConverter; private final SubtitleConverter<?> subtitleConverter;
@Inject @Inject
FFmpegVideoConverter(@FFmpegBundledPath final Path bundledPath, @FFmpegSystemPath final Path systemPath, final Map<String, SubtitleConverter> subtitleConverters) { FFmpegVideoConverter(@FFmpegBundledPath final Path bundledPath, @FFmpegSystemPath final Path systemPath, final Map<String, SubtitleConverter> subtitleConverters) {
this.bundledPath = requireNonNull(bundledPath); this.bundledPath = requireNonNull(bundledPath);
this.systemPath = requireNonNull(systemPath); this.systemPath = requireNonNull(systemPath);
this.subtitleConverter = requireNonNull(subtitleConverters.get("srt")); this.subtitleConverter = subtitleConverters.get("srt");
} }
@Override @Override
public Video addSoftSubtitles(final Video video, final Collection<SubtitleCollection> subtitles) throws IOException { public Video addSoftSubtitles(final Video video, final Collection<? extends SubtitleCollection<?>> subtitles) throws IOException {
final var out = getTempFile("mkv"); //Soft ass subtitles are only supported by mkv apparently final var out = getTempFile(video.info().videoFormat());
addSoftSubtitles(video, subtitles, out); 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 @Override
public void addSoftSubtitles(final Video video, final Collection<SubtitleCollection> 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 videoPath = getPath(video);
final var collectionMap = dumpCollections(subtitles); final var collectionMap = dumpCollections(subtitles);
final var args = new ArrayList<String>(); final var args = new ArrayList<String>();
@@ -96,18 +95,18 @@ public class FFmpegVideoConverter extends AbstractProcessRunner implements Video
args.add("language=" + c.language().iso3()); args.add("language=" + c.language().iso3());
}); });
args.add(path.toString()); args.add(path.toString());
runListen(args); runListen(args, Duration.ofHours(1));
} }
@Override @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()); final var out = getTempFile(video.info().videoFormat());
addHardSubtitles(video, subtitles, out); addHardSubtitles(video, subtitles, out);
return new FileVideoImpl(out, video.info()); return new FileVideoImpl(out, video.info());
} }
@Override @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 videoPath = getPath(video);
final var subtitlesPath = dumpSubtitles(subtitles); final var subtitlesPath = dumpSubtitles(subtitles);
final var escapedPath = escapeVF(subtitlesPath.toString()); final var escapedPath = escapeVF(subtitlesPath.toString());
@@ -120,7 +119,7 @@ public class FFmpegVideoConverter extends AbstractProcessRunner implements Video
subtitleArg, subtitleArg,
path.toString() path.toString()
); );
runListen(args); runListen(args, Duration.ofHours(1));
} }
private static String escapeVF(final String path) { private static String escapeVF(final String path) {
@@ -145,7 +144,7 @@ public class FFmpegVideoConverter extends AbstractProcessRunner implements Video
"0:v", "0:v",
dumpVideoPath.toString() dumpVideoPath.toString()
); );
runListen(args); runListen(args, Duration.ofHours(1));
Files.deleteIfExists(dumpVideoPath); Files.deleteIfExists(dumpVideoPath);
return new FileAudioImpl(audioPath, new AudioInfoImpl("wav", video.info().duration())); return new FileAudioImpl(audioPath, new AudioInfoImpl("wav", video.info().duration()));
} }
@@ -166,15 +165,15 @@ public class FFmpegVideoConverter extends AbstractProcessRunner implements Video
return path; return path;
} }
private SequencedMap<SubtitleCollection, Path> dumpCollections(final Collection<SubtitleCollection> collections) throws IOException { private <T extends SubtitleCollection<?>> SequencedMap<T, Path> dumpCollections(final Collection<T> collections) throws IOException {
final var ret = LinkedHashMap.<SubtitleCollection, Path>newLinkedHashMap(collections.size()); final var ret = LinkedHashMap.<T, Path>newLinkedHashMap(collections.size());
for (final var subtitles : collections) { for (final var subtitles : collections) {
ret.put(subtitles, dumpSubtitles(subtitles)); ret.put(subtitles, dumpSubtitles(subtitles));
} }
return ret; return ret;
} }
private Path dumpSubtitles(final SubtitleCollection subtitles) throws IOException { private Path dumpSubtitles(final SubtitleCollection<?> subtitles) throws IOException {
final var path = getTempFile("srt"); final var path = getTempFile("srt");
Files.writeString(path, subtitleConverter.format(subtitles)); Files.writeString(path, subtitleConverter.format(subtitles));
return path; return path;

View File

@@ -4,22 +4,22 @@ import com.github.gtache.autosubtitle.Video;
import com.github.gtache.autosubtitle.VideoLoader; import com.github.gtache.autosubtitle.VideoLoader;
import com.github.gtache.autosubtitle.impl.FileVideoImpl; import com.github.gtache.autosubtitle.impl.FileVideoImpl;
import com.github.gtache.autosubtitle.impl.VideoInfoImpl; import com.github.gtache.autosubtitle.impl.VideoInfoImpl;
import com.github.gtache.autosubtitle.modules.ffmpeg.FFprobeBundledPath; import com.github.gtache.autosubtitle.modules.setup.ffmpeg.FFprobeBundledPath;
import com.github.gtache.autosubtitle.modules.ffmpeg.FFprobeSystemPath; import com.github.gtache.autosubtitle.modules.setup.ffmpeg.FFprobeSystemPath;
import com.github.gtache.autosubtitle.process.impl.AbstractProcessRunner; import com.github.gtache.autosubtitle.process.impl.AbstractProcessRunner;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.Duration;
import java.util.List;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
/** /**
* FFprobe implementation of {@link VideoLoader} * FFprobe implementation of {@link VideoLoader}
*/ */
@Singleton
public class FFprobeVideoLoader extends AbstractProcessRunner implements VideoLoader { public class FFprobeVideoLoader extends AbstractProcessRunner implements VideoLoader {
private final Path bundledPath; private final Path bundledPath;
@@ -33,7 +33,7 @@ public class FFprobeVideoLoader extends AbstractProcessRunner implements VideoLo
@Override @Override
public Video loadVideo(final Path path) throws IOException { 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 resolution = result.output().getLast();
final var split = resolution.split(","); final var split = resolution.split(",");
final var width = Integer.parseInt(split[0]); final var width = Integer.parseInt(split[0]);

View File

@@ -1,4 +1,4 @@
package com.github.gtache.autosubtitle.modules.ffmpeg; package com.github.gtache.autosubtitle.modules.setup.ffmpeg;
import javax.inject.Qualifier; import javax.inject.Qualifier;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;

View File

@@ -1,4 +1,4 @@
package com.github.gtache.autosubtitle.modules.ffmpeg; package com.github.gtache.autosubtitle.modules.setup.ffmpeg;
import javax.inject.Qualifier; import javax.inject.Qualifier;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;

View File

@@ -1,4 +1,4 @@
package com.github.gtache.autosubtitle.modules.ffmpeg; package com.github.gtache.autosubtitle.modules.setup.ffmpeg;
import javax.inject.Qualifier; import javax.inject.Qualifier;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;

View File

@@ -1,4 +1,4 @@
package com.github.gtache.autosubtitle.modules.ffmpeg; package com.github.gtache.autosubtitle.modules.setup.ffmpeg;
import javax.inject.Qualifier; import javax.inject.Qualifier;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;

View File

@@ -1,24 +1,17 @@
package com.github.gtache.autosubtitle.modules.setup.ffmpeg; 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.Architecture;
import com.github.gtache.autosubtitle.impl.OS; 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.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.modules.setup.impl.VideoConverterSetup;
import com.github.gtache.autosubtitle.setup.SetupManager; import com.github.gtache.autosubtitle.setup.SetupManager;
import com.github.gtache.autosubtitle.setup.ffmpeg.Decompresser;
import com.github.gtache.autosubtitle.setup.ffmpeg.FFmpegSetupConfiguration; import com.github.gtache.autosubtitle.setup.ffmpeg.FFmpegSetupConfiguration;
import com.github.gtache.autosubtitle.setup.ffmpeg.FFmpegSetupManager; 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.Binds;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
@@ -41,20 +34,16 @@ public abstract class FFmpegSetupModule {
} }
@Binds
@StringKey("zip")
@IntoMap
abstract Decompresser bindsZipDecompresser(final ZipDecompresser decompresser);
@Binds @Binds
@StringKey("tar") @StringKey("tar")
@IntoMap @IntoMap
abstract Decompresser bindsTarDecompresser(final TarDecompresser decompresser); abstract Archiver bindsTarDecompresser(final TarArchiver decompresser);
@Binds @Binds
@StringKey("xz") @StringKey("xz")
@IntoMap @IntoMap
abstract Decompresser bindsXzDecompresser(final XZDecompresser decompresser); abstract Archiver bindsXZDecompresser(final XZArchiver decompresser);
@Binds @Binds
@VideoConverterSetup @VideoConverterSetup
@@ -69,19 +58,19 @@ public abstract class FFmpegSetupModule {
@Provides @Provides
@FFmpegInstallerPath @FFmpegInstallerPath
static Path providesFFmpegInstallerPath(@FFBundledRoot final Path root, final OS os) { static Path providesFFmpegInstallerPath(@CacheRoot final Path root, final OS os) {
return root.resolve("cache").resolve("ffmpeg-installer" + getInstallerExtension(os)); return root.resolve(FFMPEG + "-installer" + getInstallerExtension(os));
} }
@Provides @Provides
@FFProbeInstallerPath @FFProbeInstallerPath
static Path providesFFProbeInstallerPath(@FFBundledRoot final Path root, final OS os) { static Path providesFFProbeInstallerPath(@CacheRoot final Path root, final OS os) {
return root.resolve("cache").resolve("ffprobe-installer" + getInstallerExtension(os)); return root.resolve(FFPROBE + "-installer" + getInstallerExtension(os));
} }
private static String getInstallerExtension(final OS os) { private static String getInstallerExtension(final OS os) {
if (os == OS.LINUX) { if (os == OS.LINUX) {
return ".tar.gz"; return ".tar.xz";
} else { } else {
return ".zip"; return ".zip";
} }
@@ -89,8 +78,8 @@ public abstract class FFmpegSetupModule {
@Provides @Provides
@FFBundledRoot @FFBundledRoot
static Path providesFFBundledRoot() { static Path providesFFBundledRoot(@ToolsRoot final Path root) {
return Paths.get("tools", FFMPEG); return root.resolve(FFMPEG);
} }
@Provides @Provides

View File

@@ -1,4 +1,4 @@
package com.github.gtache.autosubtitle.modules.ffmpeg; package com.github.gtache.autosubtitle.modules.setup.ffmpeg;
import javax.inject.Qualifier; import javax.inject.Qualifier;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;

View File

@@ -1,4 +1,4 @@
package com.github.gtache.autosubtitle.modules.ffmpeg; package com.github.gtache.autosubtitle.modules.setup.ffmpeg;
import javax.inject.Qualifier; import javax.inject.Qualifier;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;

View File

@@ -1,4 +1,4 @@
package com.github.gtache.autosubtitle.modules.ffmpeg; package com.github.gtache.autosubtitle.modules.setup.ffmpeg;
import javax.inject.Qualifier; import javax.inject.Qualifier;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;

View File

@@ -1,4 +1,4 @@
package com.github.gtache.autosubtitle.modules.ffmpeg; package com.github.gtache.autosubtitle.modules.setup.ffmpeg;
import javax.inject.Qualifier; import javax.inject.Qualifier;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;

View File

@@ -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);
}

View File

@@ -1,5 +1,6 @@
package com.github.gtache.autosubtitle.setup.ffmpeg; 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.impl.Architecture;
import com.github.gtache.autosubtitle.setup.SetupException; import com.github.gtache.autosubtitle.setup.SetupException;
import com.github.gtache.autosubtitle.setup.SetupManager; import com.github.gtache.autosubtitle.setup.SetupManager;
@@ -13,6 +14,8 @@ import java.io.IOException;
import java.net.http.HttpClient; import java.net.http.HttpClient;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.Duration;
import java.util.List;
import java.util.Map; import java.util.Map;
import static com.github.gtache.autosubtitle.impl.Architecture.ARMEL; import static com.github.gtache.autosubtitle.impl.Architecture.ARMEL;
@@ -26,14 +29,14 @@ import static java.util.Objects.requireNonNull;
public class FFmpegSetupManager extends AbstractSetupManager { public class FFmpegSetupManager extends AbstractSetupManager {
private static final Logger logger = LogManager.getLogger(FFmpegSetupManager.class); private static final Logger logger = LogManager.getLogger(FFmpegSetupManager.class);
private final FFmpegSetupConfiguration configuration; private final FFmpegSetupConfiguration configuration;
private final Map<String, Decompresser> decompressers; private final Map<String, Archiver> archivers;
@Inject @Inject
FFmpegSetupManager(final FFmpegSetupConfiguration configuration, final Map<String, Decompresser> decompressers, FFmpegSetupManager(final FFmpegSetupConfiguration configuration, final Map<String, Archiver> archivers,
final HttpClient httpClient) { final HttpClient httpClient) {
super(httpClient); super(httpClient);
this.configuration = requireNonNull(configuration); this.configuration = requireNonNull(configuration);
this.decompressers = Map.copyOf(decompressers); this.archivers = Map.copyOf(archivers);
} }
@Override @Override
@@ -132,7 +135,7 @@ public class FFmpegSetupManager extends AbstractSetupManager {
try { try {
final var filename = from.getFileName().toString(); final var filename = from.getFileName().toString();
final var extension = filename.substring(filename.lastIndexOf('.') + 1); final var extension = filename.substring(filename.lastIndexOf('.') + 1);
decompressers.get(extension).decompress(from, to); archivers.get(extension).decompress(from, to);
} catch (final IOException e) { } catch (final IOException e) {
throw new SetupException(e); throw new SetupException(e);
} }
@@ -188,7 +191,7 @@ public class FFmpegSetupManager extends AbstractSetupManager {
} }
private boolean checkSystemFFmpeg() throws IOException { 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; return result.exitCode() == 0;
} }

View File

@@ -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");
}
}

View File

@@ -15,4 +15,5 @@ module com.github.gtache.autosubtitle.ffmpeg {
exports com.github.gtache.autosubtitle.modules.ffmpeg; exports com.github.gtache.autosubtitle.modules.ffmpeg;
exports com.github.gtache.autosubtitle.modules.setup.ffmpeg; exports com.github.gtache.autosubtitle.modules.setup.ffmpeg;
exports com.github.gtache.autosubtitle.archive.ffmpeg;
} }

View File

@@ -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")));
}
}

View File

@@ -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")));
}
}

View File

@@ -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
}

View File

@@ -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));
}
}

View File

@@ -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());
}
}

View File

@@ -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));
}
}

View File

@@ -0,0 +1,4 @@
package com.github.gtache.autosubtitle.setup.ffmpeg;
class TestFFmpegSetupManager {
}

View File

@@ -0,0 +1,5 @@
$TempDir = [System.IO.Path]::GetTempPath()
$Output = "$TempDir\test-ffmpeg-output.txt"
Write-Output "$args" > $Output
exit

View File

@@ -0,0 +1,6 @@
#!/bin/bash
set -o errexit -o noclobber -o pipefail -o nounset
output=/tmp/test-ffmpeg-output.txt
echo "$@" >| $output

View File

@@ -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

View File

@@ -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"

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>

View File

@@ -13,5 +13,5 @@ public interface MainModel {
/** /**
* @param index The index of the tab to select * @param index The index of the tab to select
*/ */
void selectTab(int index); void setSelectedTab(int index);
} }

View File

@@ -19,20 +19,6 @@ public interface SetupModel {
*/ */
void setSubtitleExtractorStatus(SetupStatus status); 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 * @return the progress of the subtitle extractor setup
*/ */
@@ -69,20 +55,6 @@ public interface SetupModel {
*/ */
void setVideoConverterStatus(SetupStatus status); 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 * @return the progress of the video converter setup
*/ */
@@ -119,20 +91,6 @@ public interface SetupModel {
*/ */
void setTranslatorStatus(SetupStatus status); 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 * @return the progress of the translator setup
*/ */

View File

@@ -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();
}

View File

@@ -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 <T> The type of subtitle
* @param <U> The type of subtitle collection
*/
public interface SubtitlesModel<T extends Subtitle, U extends SubtitleCollection<T>> {
/**
* @return The list of available video languages
*/
List<Language> 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<Language> availableTranslationsLanguage();
/**
* @return The list of selected translations languages
*/
List<Language> 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<Language, U> 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<Language, U> originalCollections();
/**
* @return The list of selected subtitles
*/
List<T> 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);
}

View File

@@ -18,21 +18,7 @@ public interface WorkController {
* @param file The path to the video * @param file The path to the video
*/ */
void loadVideo(final Path file); 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 * @return The model
*/ */

View File

@@ -1,13 +1,9 @@
package com.github.gtache.autosubtitle.gui; package com.github.gtache.autosubtitle.gui;
import com.github.gtache.autosubtitle.Language;
import com.github.gtache.autosubtitle.Video; import com.github.gtache.autosubtitle.Video;
import com.github.gtache.autosubtitle.subtitle.EditableSubtitle;
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection; import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
import com.github.gtache.autosubtitle.subtitle.extractor.ExtractionModel; import com.github.gtache.autosubtitle.subtitle.extractor.ExtractionModel;
import java.util.List;
/** /**
* Model for the main view * Model for the main view
*/ */
@@ -18,6 +14,11 @@ public interface WorkModel {
*/ */
Video video(); Video video();
/**
* @param video The new video
*/
void setVideo(Video video);
/** /**
* @return The current extraction model * @return The current extraction model
*/ */
@@ -28,56 +29,6 @@ public interface WorkModel {
*/ */
void setExtractionModel(ExtractionModel model); void setExtractionModel(ExtractionModel model);
/**
* @return The current subtitle collection
*/
SubtitleCollection subtitleCollection();
/**
* @return The current list of subtitles
*/
List<EditableSubtitle> subtitles();
/**
* @return The current text
*/
String text();
/**
* @return The original extracted subtitles (used to reset)
*/
List<EditableSubtitle> originalSubtitles();
/**
* @return The currently selected subtitle
*/
EditableSubtitle selectedSubtitle();
/**
* @return The list of available video languages
*/
List<Language> availableVideoLanguages();
/**
* @return The list of available translations languages
*/
List<Language> availableTranslationsLanguage();
/**
* @return The video language
*/
Language videoLanguage();
/**
* @param language The video language
*/
void setVideoLanguage(Language language);
/**
* @return The list of selected translations
*/
List<Language> translations();
/** /**
* @return The current status * @return The current status
*/ */
@@ -97,4 +48,36 @@ public interface WorkModel {
* @param progress The new progress * @param progress The new progress
*/ */
void setProgress(double 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);
} }

View File

@@ -1,35 +1,52 @@
package com.github.gtache.autosubtitle.gui.impl; package com.github.gtache.autosubtitle.gui.impl;
import java.util.Arrays; import org.apache.logging.log4j.LogManager;
import java.util.Collections; import org.apache.logging.log4j.Logger;
import java.util.Enumeration;
import java.util.HashMap; import java.util.*;
import java.util.Map;
import java.util.ResourceBundle;
/** /**
* Combines multiple resource bundles * Combines multiple resource bundles
*/ */
public class CombinedResourceBundle extends ResourceBundle { public class CombinedResourceBundle extends ResourceBundle {
private static final Logger logger = LogManager.getLogger(CombinedResourceBundle.class);
private final Map<String, String> resources; private final Map<String, String> resources;
private final Locale locale;
public CombinedResourceBundle(final ResourceBundle... bundles) { public CombinedResourceBundle(final ResourceBundle... resourceBundles) {
this(Arrays.asList(bundles)); this(Arrays.asList(resourceBundles));
} }
public CombinedResourceBundle(final Iterable<ResourceBundle> bundles) { public CombinedResourceBundle(final List<ResourceBundle> 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<>(); 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 @Override
protected Object handleGetObject(final String key) { public Object handleGetObject(final String key) {
return resources.get(key); if (resources.containsKey(key)) {
return resources.get(key);
} else {
throw new MissingResourceException(key + " not found", "CombinedResourceBundle", key);
}
} }
@Override @Override
public Enumeration<String> getKeys() { public Enumeration<String> getKeys() {
return Collections.enumeration(resources.keySet()); return Collections.enumeration(resources.keySet());
} }
@Override
public Locale getLocale() {
return locale;
}
} }

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -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.SetupBundle"),
ResourceBundle.getBundle("com.github.gtache.autosubtitle.gui.impl.WorkBundle"), 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.ParametersBundle"),
ResourceBundle.getBundle("com.github.gtache.autosubtitle.gui.impl.SubtitlesBundle"),
ResourceBundle.getBundle("com.github.gtache.autosubtitle.gui.impl.MediaBundle")); ResourceBundle.getBundle("com.github.gtache.autosubtitle.gui.impl.MediaBundle"));
} }

View File

@@ -13,6 +13,7 @@ import com.github.gtache.autosubtitle.gui.impl.spi.WorkBundleProviderImpl;
module com.github.gtache.autosubtitle.gui.core { module com.github.gtache.autosubtitle.gui.core {
requires transitive com.github.gtache.autosubtitle.gui.api; requires transitive com.github.gtache.autosubtitle.gui.api;
requires transitive com.github.gtache.autosubtitle.core; 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;
exports com.github.gtache.autosubtitle.gui.impl.spi; exports com.github.gtache.autosubtitle.gui.impl.spi;
exports com.github.gtache.autosubtitle.modules.gui.impl; exports com.github.gtache.autosubtitle.modules.gui.impl;

View File

@@ -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

Some files were not shown because too many files have changed in this diff Show More