Allows choosing and managing each tool

This commit is contained in:
Guillaume Tâche
2024-10-03 21:55:14 +02:00
parent 0a2f9e0c31
commit df58cf4585
117 changed files with 1547 additions and 1515 deletions

View File

@@ -1,11 +1,14 @@
package com.github.gtache.autosubtitle.modules.setup.impl;
import com.github.gtache.autosubtitle.ToolType;
import com.github.gtache.autosubtitle.setup.SetupManager;
import dagger.Module;
import dagger.Provides;
import java.net.http.HttpClient;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
/**
* Dagger core module for setup
@@ -32,4 +35,22 @@ public final class SetupModule {
static HttpClient providesHttpClient() {
return HttpClient.newHttpClient();
}
@Provides
static Map<ToolType, Map<String, SetupManager>> providesSetupManagers(@VideoConverterSetup final Map<String, SetupManager> convertersManagers,
@SubtitleExtractorSetup final Map<String, SetupManager> extractorsManagers,
@TranslatorSetup final Map<String, SetupManager> translatorsManagers) {
return Map.of(ToolType.VIDEO_CONVERTER, convertersManagers,
ToolType.SUBTITLE_EXTRACTOR, extractorsManagers,
ToolType.TRANSLATOR, translatorsManagers);
}
@Provides
static Map<ToolType, SetupManager> providesDefaultManagers(@VideoConverterSetup final SetupManager videoConverterManager,
@SubtitleExtractorSetup final SetupManager subtitleExtractorManager,
@TranslatorSetup final SetupManager translatorManager) {
return Map.of(ToolType.VIDEO_CONVERTER, videoConverterManager,
ToolType.SUBTITLE_EXTRACTOR, subtitleExtractorManager,
ToolType.TRANSLATOR, translatorManager);
}
}

View File

@@ -1,5 +1,6 @@
package com.github.gtache.autosubtitle.modules.subtitle.converter.impl;
import com.github.gtache.autosubtitle.modules.subtitle.converter.json.impl.JSONConverterModule;
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter;
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverterProvider;
import com.github.gtache.autosubtitle.subtitle.converter.impl.ASSSubtitleConverter;
@@ -13,7 +14,7 @@ import dagger.multibindings.StringKey;
/**
* Dagger module for the subtitle converters
*/
@Module
@Module(includes = JSONConverterModule.class)
public abstract class SubtitleConverterModule {
private SubtitleConverterModule() {

View File

@@ -0,0 +1,33 @@
package com.github.gtache.autosubtitle.modules.subtitle.converter.json.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter;
import com.github.gtache.autosubtitle.subtitle.converter.json.impl.JSONSubtitleConverter;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
import dagger.multibindings.StringKey;
import javax.inject.Singleton;
/**
* Dagger module for the json subtitle converter
*/
@Module
public abstract class JSONConverterModule {
private JSONConverterModule() {
}
@Binds
@IntoMap
@StringKey("json")
abstract SubtitleConverter bindsJsonSubtitleConverter(final JSONSubtitleConverter converter);
@Provides
@Singleton
static ObjectMapper providesObjectMapper() {
return new ObjectMapper();
}
}

View File

@@ -23,8 +23,6 @@ import java.util.Objects;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static java.util.Objects.requireNonNull;
/**
* Converts subtitles to SRT format
*/
@@ -36,11 +34,11 @@ public class ASSSubtitleConverter implements SubtitleConverter<SubtitleImpl> {
private static final String EVENTS_SECTION = "[Events]";
private static final String STYLES_SECTION = "[V4+ Styles]";
private static final Pattern NEWLINE_PATTERN = Pattern.compile("\\n");
private final Translator<?> translator;
private final Map<String, Translator<?>> translators;
@Inject
ASSSubtitleConverter(final Translator translator) {
this.translator = requireNonNull(translator);
ASSSubtitleConverter(final Map<String, Translator<?>> translators) {
this.translators = Map.copyOf(translators);
}
@Override
@@ -99,7 +97,7 @@ public class ASSSubtitleConverter implements SubtitleConverter<SubtitleImpl> {
final var fonts = parseFonts(content, options.defaultFont());
final var subtitles = parseSubtitles(content, fonts);
final var text = subtitles.stream().map(Subtitle::content).collect(Collectors.joining());
final var language = translator.getLanguage(text);
final var language = translators.values().iterator().next().getLanguage(text); //Use any translator for now
return new SubtitleCollectionImpl<>(text, subtitles, language);
}

View File

@@ -0,0 +1,74 @@
package com.github.gtache.autosubtitle.subtitle.converter.json.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.gtache.autosubtitle.subtitle.Subtitle;
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
import com.github.gtache.autosubtitle.subtitle.converter.FormatException;
import com.github.gtache.autosubtitle.subtitle.converter.FormatOptions;
import com.github.gtache.autosubtitle.subtitle.converter.ParseException;
import com.github.gtache.autosubtitle.subtitle.converter.ParseOptions;
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter;
import com.github.gtache.autosubtitle.subtitle.impl.SubtitleCollectionImpl;
import com.github.gtache.autosubtitle.subtitle.impl.SubtitleImpl;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.nio.file.Path;
import java.util.Comparator;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* {@link SubtitleConverter} implementation for JSON files
*/
@Singleton
public class JSONSubtitleConverter implements SubtitleConverter<SubtitleImpl> {
private final ObjectMapper mapper;
@Inject
JSONSubtitleConverter(final ObjectMapper mapper) {
this.mapper = Objects.requireNonNull(mapper);
}
@Override
public String format(final SubtitleCollection<?> collection, final FormatOptions options) throws FormatException {
final var segments = collection.subtitles().stream().map(s -> new JSONSubtitleSegment(s.start(),
s.end(), s.content())).toList();
final var subtitles = new JSONSubtitles(segments, collection.language());
try {
return mapper.writeValueAsString(subtitles);
} catch (final JsonProcessingException e) {
throw new FormatException(e);
}
}
@Override
public SubtitleCollectionImpl<SubtitleImpl> parse(final String content, final ParseOptions options) throws ParseException {
try {
final var json = mapper.readValue(content, JSONSubtitles.class);
final var subtitles = json.segments().stream().flatMap(s -> {
final var start = s.start();
final var end = s.end();
return Stream.of(new SubtitleImpl(s.text(), start, end, null, null));
}).sorted(Comparator.comparing(Subtitle::start).thenComparing(Subtitle::end)).toList();
final var language = json.language();
final var subtitlesText = subtitles.stream().map(s -> s.content().trim()).collect(Collectors.joining(" "));
return new SubtitleCollectionImpl<>(subtitlesText, subtitles, language);
} catch (final Exception e) {
throw new ParseException(e);
}
}
@Override
public boolean canParse(final Path file) {
return file.getFileName().toString().endsWith(".json");
}
@Override
public String formatName() {
return "json";
}
}

View File

@@ -0,0 +1,26 @@
package com.github.gtache.autosubtitle.subtitle.converter.json.impl;
import static java.util.Objects.requireNonNull;
/**
* A subtitle segment as a JSON
*
* @param start The start in milliseconds
* @param end The end in milliseconds
* @param text The text
*/
public record JSONSubtitleSegment(long start, long end, String text) {
public JSONSubtitleSegment {
if (start < 0) {
throw new IllegalArgumentException("start must be >= 0 : " + start);
}
if (end < 0) {
throw new IllegalArgumentException("end must be >= 0 : " + end);
}
if (start > end) {
throw new IllegalArgumentException("start must be <= end : " + start + " > " + end);
}
requireNonNull(text);
}
}

View File

@@ -0,0 +1,21 @@
package com.github.gtache.autosubtitle.subtitle.converter.json.impl;
import com.github.gtache.autosubtitle.Language;
import java.util.List;
import static java.util.Objects.requireNonNull;
/**
* Root json object for subtitles
*
* @param segments The list of subtitle segments
* @param language The subtitles language
*/
public record JSONSubtitles(List<JSONSubtitleSegment> segments, Language language) {
public JSONSubtitles {
segments = List.copyOf(segments);
requireNonNull(language);
}
}

View File

@@ -6,18 +6,22 @@ module com.github.gtache.autosubtitle.core {
requires transitive dagger;
requires transitive java.net.http;
requires transitive javax.inject;
requires transitive java.prefs;
requires transitive com.fasterxml.jackson.databind;
requires org.apache.logging.log4j;
requires java.prefs;
exports com.github.gtache.autosubtitle.impl;
exports com.github.gtache.autosubtitle.archive.impl;
exports com.github.gtache.autosubtitle.process.impl;
exports com.github.gtache.autosubtitle.setup.impl;
exports com.github.gtache.autosubtitle.subtitle.impl;
exports com.github.gtache.autosubtitle.subtitle.converter.impl;
exports com.github.gtache.autosubtitle.subtitle.converter.json.impl;
exports com.github.gtache.autosubtitle.subtitle.extractor.impl;
exports com.github.gtache.autosubtitle.modules.impl;
exports com.github.gtache.autosubtitle.modules.setup.impl;
exports com.github.gtache.autosubtitle.subtitle.extractor.impl;
exports com.github.gtache.autosubtitle.subtitle.converter.impl;
exports com.github.gtache.autosubtitle.modules.subtitle.impl;
exports com.github.gtache.autosubtitle.modules.subtitle.converter.impl;
exports com.github.gtache.autosubtitle.modules.subtitle.converter.json.impl;
}

View File

@@ -0,0 +1,14 @@
package com.github.gtache.autosubtitle.modules.subtitle.converter.json.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
class TestJSONConverterModule {
@Test
void testProvidesObjectMapper() {
assertInstanceOf(ObjectMapper.class, JSONConverterModule.providesObjectMapper());
}
}

View File

@@ -1,5 +1,6 @@
package com.github.gtache.autosubtitle.setup.impl;
import com.github.gtache.autosubtitle.ToolType;
import com.github.gtache.autosubtitle.process.ProcessRunner;
import com.github.gtache.autosubtitle.setup.SetupAction;
import com.github.gtache.autosubtitle.setup.SetupEvent;
@@ -244,6 +245,11 @@ class TestAbstractSetupManager {
return "dummy";
}
@Override
public ToolType type() {
return ToolType.VIDEO_CONVERTER;
}
@Override
public void install() {
}

View File

@@ -17,6 +17,7 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Arrays;
import java.util.Map;
import static java.util.Objects.requireNonNull;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -44,7 +45,7 @@ class TestASSSubtitleConverter {
this.formatOptions = requireNonNull(formatOptions);
this.parseOptions = requireNonNull(parseOptions);
this.videoInfo = requireNonNull(videoInfo);
this.converter = new ASSSubtitleConverter(translator);
this.converter = new ASSSubtitleConverter(Map.of("first", translator));
}
@BeforeEach

View File

@@ -0,0 +1,81 @@
package com.github.gtache.autosubtitle.subtitle.converter.json.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.gtache.autosubtitle.Language;
import com.github.gtache.autosubtitle.subtitle.Subtitle;
import com.github.gtache.autosubtitle.subtitle.converter.FormatException;
import com.github.gtache.autosubtitle.subtitle.converter.FormatOptions;
import com.github.gtache.autosubtitle.subtitle.converter.ParseException;
import com.github.gtache.autosubtitle.subtitle.converter.ParseOptions;
import com.github.gtache.autosubtitle.subtitle.impl.SubtitleCollectionImpl;
import com.github.gtache.autosubtitle.subtitle.impl.SubtitleImpl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.List;
import java.util.Objects;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class TestJSONSubtitleConverter {
private final ParseOptions parseOptions;
private final FormatOptions formatOptions;
private final JSONSubtitleConverter converter;
TestJSONSubtitleConverter(@Mock final ParseOptions parseOptions, @Mock final FormatOptions formatOptions) {
this.parseOptions = Objects.requireNonNull(parseOptions);
this.formatOptions = Objects.requireNonNull(formatOptions);
this.converter = new JSONSubtitleConverter(new ObjectMapper());
}
@BeforeEach
void beforeEach() {
when(parseOptions.maxLineLength()).thenReturn(100);
when(parseOptions.maxLines()).thenReturn(2);
}
@Test
void testFormatName() {
assertEquals("json", converter.formatName());
}
@Test
void testParseFormat() throws IOException, ParseException, FormatException {
try (final var inStream = getClass().getResourceAsStream("in.json");
final var outStream = getClass().getResourceAsStream("out.json")) {
if (inStream == null || outStream == null) {
throw new IOException("File not found");
}
final var in = new String(inStream.readAllBytes(), StandardCharsets.UTF_8);
final var out = new String(outStream.readAllBytes(), StandardCharsets.UTF_8);
final var expected = new SubtitleCollectionImpl<Subtitle>("This is a test. Yes.", List.of(new SubtitleImpl("This is a test.", 9, 410, null, null), new SubtitleImpl("Yes.", 450, 6963, null, null)), Language.FR);
assertEquals(expected, converter.parse(in, parseOptions));
assertEquals(out, converter.format(expected, formatOptions));
}
}
@ParameterizedTest
@CsvSource({
"test.json,true",
".json,true",
"abcd.json,true",
"abcd.json2,false",
"abcd.js,false",
"abcd.jso,false",
"json,false",
})
void testCanParse(final String name, final boolean expected) {
assertEquals(expected, converter.canParse(Path.of(name)));
}
}

View File

@@ -0,0 +1,36 @@
package com.github.gtache.autosubtitle.subtitle.converter.json.impl;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
class TestJSONSubtitleSegment {
private final long start;
private final long end;
private final String text;
private final JSONSubtitleSegment segment;
TestJSONSubtitleSegment() {
this.start = 2;
this.end = 4;
this.text = "test";
this.segment = new JSONSubtitleSegment(start, end, text);
}
@Test
void testGetters() {
assertEquals(start, segment.start());
assertEquals(end, segment.end());
assertEquals(text, segment.text());
}
@Test
void testIllegal() {
assertThrows(NullPointerException.class, () -> new JSONSubtitleSegment(start, end, null));
assertThrows(IllegalArgumentException.class, () -> new JSONSubtitleSegment(-1, end, text));
assertThrows(IllegalArgumentException.class, () -> new JSONSubtitleSegment(start, -1, text));
assertThrows(IllegalArgumentException.class, () -> new JSONSubtitleSegment(end, start, text));
}
}

View File

@@ -0,0 +1,39 @@
package com.github.gtache.autosubtitle.subtitle.converter.json.impl;
import com.github.gtache.autosubtitle.Language;
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.util.List;
import java.util.Objects;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@ExtendWith(MockitoExtension.class)
class TestJSONSubtitles {
private final List<JSONSubtitleSegment> segments;
private final Language language;
private final JSONSubtitles subtitles;
TestJSONSubtitles(@Mock final JSONSubtitleSegment segment, @Mock final Language language) {
this.segments = List.of(segment);
this.language = Objects.requireNonNull(language);
this.subtitles = new JSONSubtitles(segments, language);
}
@Test
void testGetters() {
assertEquals(segments, subtitles.segments());
assertEquals(language, subtitles.language());
}
@Test
void testIllegal() {
assertThrows(NullPointerException.class, () -> new JSONSubtitles(null, language));
assertThrows(NullPointerException.class, () -> new JSONSubtitles(segments, null));
}
}

View File

@@ -0,0 +1,15 @@
{
"segments": [
{
"start": 9,
"end": 410,
"text": "This is a test."
},
{
"start": 450,
"end": 6963,
"text": "Yes."
}
],
"language": "FR"
}

View File

@@ -0,0 +1 @@
{"segments":[{"start":9,"end":410,"text":"This is a test."},{"start":450,"end":6963,"text":"Yes."}],"language":"FR"}