Rework to avoid using preferences object to retrieve options
This commit is contained in:
@@ -15,5 +15,9 @@
|
||||
<groupId>com.github.gtache.autosubtitle</groupId>
|
||||
<artifactId>autosubtitle-whisper-common</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.github.gtache.autosubtitle.modules.subtitle.parser.json.whisper.base;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter;
|
||||
import com.github.gtache.autosubtitle.subtitle.parser.json.whisper.base.JSONSubtitleConverter;
|
||||
import com.google.gson.Gson;
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
@@ -25,7 +25,7 @@ public abstract class WhisperJsonModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static Gson providesGson() {
|
||||
return new Gson();
|
||||
static ObjectMapper providesObjectMapper() {
|
||||
return new ObjectMapper();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import com.github.gtache.autosubtitle.impl.OS;
|
||||
import com.github.gtache.autosubtitle.modules.setup.whisper.WhisperVenvPath;
|
||||
import com.github.gtache.autosubtitle.process.ProcessRunner;
|
||||
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverterProvider;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.ExtractionModel;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.ExtractOptions;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.SubtitleExtractor;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.whisper.AbstractWhisperSubtitleExtractor;
|
||||
import com.github.gtache.autosubtitle.whisper.WhisperModels;
|
||||
@@ -30,7 +30,9 @@ public class WhisperSubtitleExtractor extends AbstractWhisperSubtitleExtractor {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> createArgs(final Path path, final Language language, final ExtractionModel model, final Path outputDir) {
|
||||
protected List<String> createArgs(final Path path, final ExtractOptions options, final Path outputDir) {
|
||||
final var model = options.model();
|
||||
final var language = options.language();
|
||||
final var args = new ArrayList<String>(14);
|
||||
args.add(getPythonPath().toString());
|
||||
args.add("-m");
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
package com.github.gtache.autosubtitle.subtitle.parser.json.whisper.base;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.github.gtache.autosubtitle.Language;
|
||||
import com.github.gtache.autosubtitle.VideoInfo;
|
||||
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 com.google.gson.Gson;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
@@ -28,26 +31,30 @@ import java.util.stream.Collectors;
|
||||
public class JSONSubtitleConverter implements SubtitleConverter<SubtitleImpl> {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(JSONSubtitleConverter.class);
|
||||
private final Gson gson;
|
||||
private final ObjectMapper mapper;
|
||||
|
||||
@Inject
|
||||
JSONSubtitleConverter(final Gson gson) {
|
||||
this.gson = Objects.requireNonNull(gson);
|
||||
JSONSubtitleConverter(final ObjectMapper mapper) {
|
||||
this.mapper = Objects.requireNonNull(mapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String format(final SubtitleCollection<?> collection, final VideoInfo videoInfo) {
|
||||
public String format(final SubtitleCollection<?> collection, final FormatOptions options) throws FormatException {
|
||||
final var id = new AtomicInteger(0);
|
||||
final var segments = collection.subtitles().stream().map(s -> new JSONSubtitleSegment(id.incrementAndGet(), 0, s.start() / (double) 1000,
|
||||
s.end() / (double) 1000, s.content(), List.of(), 0, 0, 0, 0)).toList();
|
||||
final var subtitles = new JSONSubtitles(collection.text(), segments, collection.language().iso2());
|
||||
return gson.toJson(subtitles);
|
||||
try {
|
||||
return mapper.writeValueAsString(subtitles);
|
||||
} catch (final JsonProcessingException e) {
|
||||
throw new FormatException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubtitleCollectionImpl<SubtitleImpl> parse(final String content) throws ParseException {
|
||||
public SubtitleCollectionImpl<SubtitleImpl> parse(final String content, final ParseOptions options) throws ParseException {
|
||||
try {
|
||||
final var json = gson.fromJson(content, JSONSubtitles.class);
|
||||
final var json = mapper.readValue(content, JSONSubtitles.class);
|
||||
final var subtitles = json.segments().stream().map(s -> {
|
||||
final var start = (long) (s.start() * 1000L);
|
||||
final var end = (long) (s.end() * 1000L);
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
*/
|
||||
module com.github.gtache.autosubtitle.whisper.base {
|
||||
requires transitive com.github.gtache.autosubtitle.whisper.common;
|
||||
requires com.fasterxml.jackson.databind;
|
||||
requires com.github.gtache.autosubtitle.core;
|
||||
requires org.apache.logging.log4j;
|
||||
requires com.google.gson;
|
||||
|
||||
exports com.github.gtache.autosubtitle.setup.whisper.base;
|
||||
exports com.github.gtache.autosubtitle.subtitle.extractor.whisper.base;
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.github.gtache.autosubtitle.subtitle.extractor.whisper;
|
||||
|
||||
import com.github.gtache.autosubtitle.Audio;
|
||||
import com.github.gtache.autosubtitle.File;
|
||||
import com.github.gtache.autosubtitle.Language;
|
||||
import com.github.gtache.autosubtitle.Video;
|
||||
import com.github.gtache.autosubtitle.impl.OS;
|
||||
import com.github.gtache.autosubtitle.process.ProcessRunner;
|
||||
@@ -13,7 +12,7 @@ import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter;
|
||||
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverterProvider;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.ExtractEvent;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.ExtractException;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.ExtractionModel;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.ExtractOptions;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.SubtitleExtractor;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.SubtitleExtractorListener;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.impl.ExtractEventImpl;
|
||||
@@ -79,41 +78,41 @@ public abstract class AbstractWhisperSubtitleExtractor implements SubtitleExtrac
|
||||
|
||||
|
||||
@Override
|
||||
public SubtitleCollection<Subtitle> extract(final Video video, final Language language, final ExtractionModel model) throws ExtractException {
|
||||
return extract(new AudioOrVideo(video), language, model);
|
||||
public SubtitleCollection<Subtitle> extract(final Video video, final ExtractOptions options) throws ExtractException {
|
||||
return extract(new AudioOrVideo(video), options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubtitleCollection<Subtitle> extract(final Audio audio, final Language language, final ExtractionModel model) throws ExtractException {
|
||||
return extract(new AudioOrVideo(audio), language, model);
|
||||
public SubtitleCollection<Subtitle> extract(final Audio audio, final ExtractOptions options) throws ExtractException {
|
||||
return extract(new AudioOrVideo(audio), options);
|
||||
}
|
||||
|
||||
private SubtitleCollection<Subtitle> extract(final AudioOrVideo av, final Language language, final ExtractionModel model) throws ExtractException {
|
||||
private SubtitleCollection<Subtitle> extract(final AudioOrVideo av, final ExtractOptions options) throws ExtractException {
|
||||
if (av.inner() instanceof final File f) {
|
||||
return extract(f.path(), language, model, av.info().duration());
|
||||
return extract(f.path(), options, av.info().duration());
|
||||
} else {
|
||||
try {
|
||||
return dumpExtract(av, language, model);
|
||||
return dumpExtract(av, options);
|
||||
} catch (final IOException e) {
|
||||
throw new ExtractException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private SubtitleCollection<Subtitle> dumpExtract(final AudioOrVideo av, final Language language, final ExtractionModel model) throws ExtractException, IOException {
|
||||
private SubtitleCollection<Subtitle> dumpExtract(final AudioOrVideo av, final ExtractOptions options) throws ExtractException, IOException {
|
||||
final var path = Files.createTempFile(AUTOSUBTITLE, "." + av.info().format());
|
||||
try (final var in = av.getInputStream()) {
|
||||
Files.copy(in, path, StandardCopyOption.REPLACE_EXISTING);
|
||||
return extract(path, language, model, av.info().duration());
|
||||
return extract(path, options, av.info().duration());
|
||||
} finally {
|
||||
Files.deleteIfExists(path);
|
||||
}
|
||||
}
|
||||
|
||||
private SubtitleCollection<Subtitle> extract(final Path path, final Language language, final ExtractionModel model, final long duration) throws ExtractException {
|
||||
private SubtitleCollection<Subtitle> extract(final Path path, final ExtractOptions options, final long duration) throws ExtractException {
|
||||
try {
|
||||
final var outputDir = Files.createTempDirectory(AUTOSUBTITLE);
|
||||
final var args = createArgs(path, language, model, outputDir);
|
||||
final var args = createArgs(path, options, outputDir);
|
||||
final var processListener = processRunner.startListen(args);
|
||||
var oldProgress = -1.0;
|
||||
var line = processListener.readLine();
|
||||
@@ -129,7 +128,7 @@ public abstract class AbstractWhisperSubtitleExtractor implements SubtitleExtrac
|
||||
final var filename = path.getFileName().toString();
|
||||
final var subtitleFilename = filename.substring(0, filename.lastIndexOf('.')) + ".json";
|
||||
final var subtitleFile = outputDir.resolve(subtitleFilename);
|
||||
return parseResult(subtitleFile);
|
||||
return parseResult(subtitleFile, options);
|
||||
} else {
|
||||
throw new ExtractException("Error extracting subtitles: " + result.output());
|
||||
}
|
||||
@@ -138,9 +137,9 @@ public abstract class AbstractWhisperSubtitleExtractor implements SubtitleExtrac
|
||||
}
|
||||
}
|
||||
|
||||
private SubtitleCollection<Subtitle> parseResult(final Path subtitleFile) throws ExtractException {
|
||||
private SubtitleCollection<Subtitle> parseResult(final Path subtitleFile, final ExtractOptions options) throws ExtractException {
|
||||
try {
|
||||
return converter.parse(subtitleFile);
|
||||
return converter.parse(subtitleFile, options.parseOptions());
|
||||
} catch (final ParseException e) {
|
||||
throw new ExtractException(e);
|
||||
}
|
||||
@@ -175,12 +174,11 @@ public abstract class AbstractWhisperSubtitleExtractor implements SubtitleExtrac
|
||||
* Creates the command line arguments for Whisper
|
||||
*
|
||||
* @param path the path to the file
|
||||
* @param language the language
|
||||
* @param model the model
|
||||
* @param options the extraction options
|
||||
* @param outputDir the output directory
|
||||
* @return the list of arguments
|
||||
*/
|
||||
protected abstract List<String> createArgs(final Path path, final Language language, final ExtractionModel model, final Path outputDir);
|
||||
protected abstract List<String> createArgs(final Path path, final ExtractOptions options, final Path outputDir);
|
||||
|
||||
/**
|
||||
* @return the path to the python executable
|
||||
|
||||
@@ -5,7 +5,6 @@ module com.github.gtache.autosubtitle.whisper.common {
|
||||
requires transitive com.github.gtache.autosubtitle.conda;
|
||||
requires transitive java.net.http;
|
||||
requires org.apache.logging.log4j;
|
||||
requires transitive com.google.gson;
|
||||
requires transitive java.compiler; //Don't know why dagger generates @Generated here, need to debug
|
||||
|
||||
exports com.github.gtache.autosubtitle.whisper;
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.github.gtache.autosubtitle.subtitle.extractor.whisper;
|
||||
|
||||
import com.github.gtache.autosubtitle.Audio;
|
||||
import com.github.gtache.autosubtitle.AudioInfo;
|
||||
import com.github.gtache.autosubtitle.Language;
|
||||
import com.github.gtache.autosubtitle.Video;
|
||||
import com.github.gtache.autosubtitle.VideoInfo;
|
||||
import com.github.gtache.autosubtitle.impl.FileAudioImpl;
|
||||
@@ -13,11 +12,12 @@ import com.github.gtache.autosubtitle.process.ProcessRunner;
|
||||
import com.github.gtache.autosubtitle.subtitle.Subtitle;
|
||||
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
|
||||
import com.github.gtache.autosubtitle.subtitle.converter.ParseException;
|
||||
import com.github.gtache.autosubtitle.subtitle.converter.ParseOptions;
|
||||
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter;
|
||||
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverterProvider;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.ExtractEvent;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.ExtractException;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.ExtractionModel;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.ExtractOptions;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.SubtitleExtractorListener;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.impl.ExtractEventImpl;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -52,15 +52,16 @@ class TestAbstractWhisperSubtitleExtractor {
|
||||
|
||||
private final AudioInfo audioInfo;
|
||||
private final VideoInfo videoInfo;
|
||||
private final ExtractionModel extractionModel;
|
||||
private final ExtractOptions options;
|
||||
private final ParseOptions parseOptions;
|
||||
private final SubtitleCollection<Subtitle> collection;
|
||||
|
||||
|
||||
TestAbstractWhisperSubtitleExtractor(@Mock final SubtitleConverterProvider converterProvider, @Mock final SubtitleConverter<Subtitle> converter,
|
||||
@Mock final ProcessRunner processRunner, @Mock final ProcessListener processListener,
|
||||
@Mock final ProcessResult processResult, @Mock final VideoInfo videoInfo,
|
||||
@Mock final AudioInfo audioInfo, @Mock final ExtractionModel extractionModel,
|
||||
@Mock final SubtitleCollection<Subtitle> collection) {
|
||||
@Mock final AudioInfo audioInfo, @Mock final ExtractOptions options,
|
||||
@Mock final ParseOptions parseOptions, @Mock final SubtitleCollection<Subtitle> collection) {
|
||||
this.venvPath = Path.of("venv");
|
||||
this.os = OS.LINUX;
|
||||
this.converterProvider = Objects.requireNonNull(converterProvider);
|
||||
@@ -70,7 +71,8 @@ class TestAbstractWhisperSubtitleExtractor {
|
||||
this.processResult = Objects.requireNonNull(processResult);
|
||||
this.audioInfo = Objects.requireNonNull(audioInfo);
|
||||
this.videoInfo = Objects.requireNonNull(videoInfo);
|
||||
this.extractionModel = Objects.requireNonNull(extractionModel);
|
||||
this.options = Objects.requireNonNull(options);
|
||||
this.parseOptions = Objects.requireNonNull(parseOptions);
|
||||
this.collection = Objects.requireNonNull(collection);
|
||||
}
|
||||
|
||||
@@ -81,6 +83,7 @@ class TestAbstractWhisperSubtitleExtractor {
|
||||
when(processListener.join(Duration.ofHours(1))).thenReturn(processResult);
|
||||
when(audioInfo.format()).thenReturn("mp3");
|
||||
when(videoInfo.format()).thenReturn("mp4");
|
||||
when(options.parseOptions()).thenReturn(parseOptions);
|
||||
this.extractor = new DummyWhisperSubtitleExtractor(venvPath, converterProvider, processRunner, os);
|
||||
}
|
||||
|
||||
@@ -126,7 +129,7 @@ class TestAbstractWhisperSubtitleExtractor {
|
||||
final var audio = mock(Audio.class);
|
||||
when(audio.info()).thenReturn(audioInfo);
|
||||
when(audio.getInputStream()).thenThrow(IOException.class);
|
||||
assertThrows(ExtractException.class, () -> extractor.extract(audio, Language.EN, extractionModel));
|
||||
assertThrows(ExtractException.class, () -> extractor.extract(audio, options));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -134,16 +137,16 @@ class TestAbstractWhisperSubtitleExtractor {
|
||||
final var path = Paths.get("path");
|
||||
final var audio = new FileAudioImpl(path, audioInfo);
|
||||
doThrow(IOException.class).when(processListener).readLine();
|
||||
assertThrows(ExtractException.class, () -> extractor.extract(audio, Language.EN, extractionModel));
|
||||
assertThrows(ExtractException.class, () -> extractor.extract(audio, options));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExtractAudioFileParseException() throws ParseException {
|
||||
final var path = Paths.get("path.path");
|
||||
final var audio = new FileAudioImpl(path, audioInfo);
|
||||
doThrow(ParseException.class).when(converter).parse(any(Path.class));
|
||||
assertThrows(ExtractException.class, () -> extractor.extract(audio, Language.EN, extractionModel));
|
||||
verify(converter).parse(any(Path.class));
|
||||
doThrow(ParseException.class).when(converter).parse(any(Path.class), eq(parseOptions));
|
||||
assertThrows(ExtractException.class, () -> extractor.extract(audio, options));
|
||||
verify(converter).parse(any(Path.class), eq(parseOptions));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -151,7 +154,7 @@ class TestAbstractWhisperSubtitleExtractor {
|
||||
final var path = Paths.get("path");
|
||||
final var audio = new FileAudioImpl(path, audioInfo);
|
||||
when(processResult.exitCode()).thenReturn(1);
|
||||
assertThrows(ExtractException.class, () -> extractor.extract(audio, Language.EN, extractionModel));
|
||||
assertThrows(ExtractException.class, () -> extractor.extract(audio, options));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -159,7 +162,7 @@ class TestAbstractWhisperSubtitleExtractor {
|
||||
final var video = mock(Video.class);
|
||||
when(video.info()).thenReturn(videoInfo);
|
||||
when(video.getInputStream()).thenThrow(IOException.class);
|
||||
assertThrows(ExtractException.class, () -> extractor.extract(video, Language.EN, extractionModel));
|
||||
assertThrows(ExtractException.class, () -> extractor.extract(video, options));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -168,8 +171,8 @@ class TestAbstractWhisperSubtitleExtractor {
|
||||
when(video.info()).thenReturn(videoInfo);
|
||||
final var in = new ByteArrayInputStream("test".getBytes());
|
||||
when(video.getInputStream()).thenReturn(in);
|
||||
when(converter.parse(any(Path.class))).thenReturn(collection);
|
||||
assertEquals(collection, extractor.extract(video, Language.EN, extractionModel));
|
||||
when(converter.parse(any(Path.class), eq(parseOptions))).thenReturn(collection);
|
||||
assertEquals(collection, extractor.extract(video, options));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -179,11 +182,11 @@ class TestAbstractWhisperSubtitleExtractor {
|
||||
when(videoInfo.duration()).thenReturn(100000L);
|
||||
final var in = new ByteArrayInputStream("test".getBytes());
|
||||
when(video.getInputStream()).thenReturn(in);
|
||||
when(converter.parse(any(Path.class))).thenReturn(collection);
|
||||
when(converter.parse(any(Path.class), eq(parseOptions))).thenReturn(collection);
|
||||
when(processListener.readLine()).thenReturn("Progress: 1.7abcd", "[00:12.234 --> 00:13.234] Hello", "98%|bbb", "abcd", null);
|
||||
final var listener = mock(SubtitleExtractorListener.class);
|
||||
extractor.addListener(listener);
|
||||
assertEquals(collection, extractor.extract(video, Language.EN, extractionModel));
|
||||
assertEquals(collection, extractor.extract(video, options));
|
||||
verify(listener).listen(new ExtractEventImpl("Progress: 1.7abcd", 0.017));
|
||||
verify(listener).listen(new ExtractEventImpl("[00:12.234 --> 00:13.234] Hello", 0.13234));
|
||||
verify(listener).listen(new ExtractEventImpl("98%|bbb", 0.98));
|
||||
@@ -208,8 +211,8 @@ class TestAbstractWhisperSubtitleExtractor {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> createArgs(final Path path, final Language language, final ExtractionModel model, final Path outputDir) {
|
||||
return List.of(path.toString(), language.toString(), model.toString(), outputDir.toString());
|
||||
protected List<String> createArgs(final Path path, final ExtractOptions options, final Path outputDir) {
|
||||
return List.of(path.toString(), options.toString(), outputDir.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,15 +38,4 @@
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.github.gtache.autosubtitle</groupId>
|
||||
<artifactId>autosubtitle-conda</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -16,5 +16,9 @@
|
||||
<groupId>com.github.gtache.autosubtitle</groupId>
|
||||
<artifactId>autosubtitle-whisper-common</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.github.gtache.autosubtitle.modules.subtitle.parser.json.whisperx;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter;
|
||||
import com.github.gtache.autosubtitle.subtitle.parser.json.whisperx.JSONSubtitleConverter;
|
||||
import com.google.gson.Gson;
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
@@ -25,7 +25,7 @@ public abstract class WhisperXJsonModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static Gson providesGson() {
|
||||
return new Gson();
|
||||
static ObjectMapper providesObjectMapper() {
|
||||
return new ObjectMapper();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import com.github.gtache.autosubtitle.impl.OS;
|
||||
import com.github.gtache.autosubtitle.modules.setup.whisper.WhisperVenvPath;
|
||||
import com.github.gtache.autosubtitle.process.ProcessRunner;
|
||||
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverterProvider;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.ExtractionModel;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.ExtractOptions;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.SubtitleExtractor;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.whisper.AbstractWhisperSubtitleExtractor;
|
||||
import com.github.gtache.autosubtitle.whisper.WhisperModels;
|
||||
@@ -28,7 +28,9 @@ public class WhisperXSubtitleExtractor extends AbstractWhisperSubtitleExtractor
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> createArgs(final Path path, final Language language, final ExtractionModel model, final Path outputDir) {
|
||||
protected List<String> createArgs(final Path path, final ExtractOptions options, final Path outputDir) {
|
||||
final var model = options.model();
|
||||
final var language = options.language();
|
||||
final var args = new ArrayList<String>();
|
||||
args.add(getPythonPath().toString());
|
||||
args.add("-m");
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
package com.github.gtache.autosubtitle.subtitle.parser.json.whisperx;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.github.gtache.autosubtitle.Language;
|
||||
import com.github.gtache.autosubtitle.VideoInfo;
|
||||
import com.github.gtache.autosubtitle.modules.impl.MaxLineLength;
|
||||
import com.github.gtache.autosubtitle.modules.impl.MaxLines;
|
||||
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 com.google.gson.Gson;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -19,7 +20,6 @@ import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.prefs.Preferences;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
@@ -30,42 +30,38 @@ import java.util.stream.Stream;
|
||||
@Singleton
|
||||
public class JSONSubtitleConverter implements SubtitleConverter<SubtitleImpl> {
|
||||
|
||||
private static final String MAX_LINE_LENGTH = "maxLineLength";
|
||||
private static final String MAX_LINES = "maxLines";
|
||||
private static final Pattern SPLIT_PATTERN = Pattern.compile("[ \n]+");
|
||||
|
||||
private final Gson gson;
|
||||
private final Preferences preferences;
|
||||
private final int defaultMaxLineLength;
|
||||
private final int defaultMaxLines;
|
||||
private final ObjectMapper mapper;
|
||||
|
||||
@Inject
|
||||
JSONSubtitleConverter(final Gson gson, final Preferences preferences, @MaxLineLength final int defaultMaxLineLength, @MaxLines final int defaultMaxLines) {
|
||||
this.gson = Objects.requireNonNull(gson);
|
||||
this.preferences = Objects.requireNonNull(preferences);
|
||||
this.defaultMaxLineLength = defaultMaxLineLength;
|
||||
this.defaultMaxLines = defaultMaxLines;
|
||||
JSONSubtitleConverter(final ObjectMapper mapper) {
|
||||
this.mapper = Objects.requireNonNull(mapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String format(final SubtitleCollection<?> collection, final VideoInfo videoInfo) {
|
||||
public String format(final SubtitleCollection<?> collection, final FormatOptions options) throws FormatException {
|
||||
final var segments = collection.subtitles().stream().map(s -> new JSONSubtitleSegment(s.start() / (double) 1000,
|
||||
s.end() / (double) 1000, s.content(), List.of())).toList();
|
||||
final var subtitles = new JSONSubtitles(segments, collection.language().iso2());
|
||||
return gson.toJson(subtitles);
|
||||
try {
|
||||
return mapper.writeValueAsString(subtitles);
|
||||
} catch (final JsonProcessingException e) {
|
||||
throw new FormatException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubtitleCollectionImpl<SubtitleImpl> parse(final String content) throws ParseException {
|
||||
public SubtitleCollectionImpl<SubtitleImpl> parse(final String content, final ParseOptions options) throws ParseException {
|
||||
try {
|
||||
final var json = gson.fromJson(content, JSONSubtitles.class);
|
||||
final var json = mapper.readValue(content, JSONSubtitles.class);
|
||||
final var subtitles = json.segments().stream().flatMap(s -> {
|
||||
final var start = (long) (s.start() * 1000L);
|
||||
final var end = (long) (s.end() * 1000L);
|
||||
if (s.words().isEmpty()) {
|
||||
return Stream.of(new SubtitleImpl(s.text(), start, end, null, null));
|
||||
} else {
|
||||
return splitSubtitle(s);
|
||||
return splitSubtitle(s, options);
|
||||
|
||||
}
|
||||
}).sorted(Comparator.comparing(Subtitle::start).thenComparing(Subtitle::end)).toList();
|
||||
@@ -77,23 +73,23 @@ public class JSONSubtitleConverter implements SubtitleConverter<SubtitleImpl> {
|
||||
}
|
||||
}
|
||||
|
||||
private Stream<SubtitleImpl> splitSubtitle(final JSONSubtitleSegment segment) {
|
||||
final var maxLineLength = preferences.getInt(MAX_LINE_LENGTH, defaultMaxLineLength);
|
||||
final var maxLines = preferences.getInt(MAX_LINES, defaultMaxLines);
|
||||
private static Stream<SubtitleImpl> splitSubtitle(final JSONSubtitleSegment segment, final ParseOptions options) {
|
||||
final var maxLineLength = options.maxLineLength();
|
||||
final var maxLines = options.maxLines();
|
||||
final var text = segment.text();
|
||||
if (text.length() <= maxLineLength) {
|
||||
final var start = (long) (segment.start() * 1000L);
|
||||
final var end = (long) (segment.end() * 1000L);
|
||||
return Stream.of(new SubtitleImpl(text.replace("\n", " "), start, end, null, null));
|
||||
} else if (text.length() <= maxLines * maxLineLength) {
|
||||
return splitSubtitleLines(segment);
|
||||
return splitSubtitleLines(segment, options);
|
||||
} else {
|
||||
return splitSubtitleWords(segment);
|
||||
return splitSubtitleWords(segment, options);
|
||||
}
|
||||
}
|
||||
|
||||
private Stream<SubtitleImpl> splitSubtitleLines(final JSONSubtitleSegment segment) {
|
||||
final var maxLineLength = preferences.getInt(MAX_LINE_LENGTH, defaultMaxLineLength);
|
||||
private static Stream<SubtitleImpl> splitSubtitleLines(final JSONSubtitleSegment segment, final ParseOptions options) {
|
||||
final var maxLineLength = options.maxLineLength();
|
||||
final var text = segment.text();
|
||||
final var split = SPLIT_PATTERN.split(text);
|
||||
final var builder = new StringBuilder(text.length());
|
||||
@@ -116,9 +112,9 @@ public class JSONSubtitleConverter implements SubtitleConverter<SubtitleImpl> {
|
||||
return currentLength / (maxLength + 1) < newLength / (maxLength + 1);
|
||||
}
|
||||
|
||||
private Stream<SubtitleImpl> splitSubtitleWords(final JSONSubtitleSegment segment) {
|
||||
final var maxLineLength = preferences.getInt(MAX_LINE_LENGTH, defaultMaxLineLength);
|
||||
final var maxLines = preferences.getInt(MAX_LINES, defaultMaxLines);
|
||||
private static Stream<SubtitleImpl> splitSubtitleWords(final JSONSubtitleSegment segment, final ParseOptions options) {
|
||||
final var maxLineLength = options.maxLineLength();
|
||||
final var maxLines = options.maxLines();
|
||||
final var ret = new ArrayList<SubtitleImpl>(segment.text().length() / (maxLines * maxLineLength));
|
||||
final var builder = new StringBuilder(maxLines * maxLineLength);
|
||||
final var words = segment.words();
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package com.github.gtache.autosubtitle.subtitle.parser.json.whisperx;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@JsonIgnoreProperties("word_segments")
|
||||
public record JSONSubtitles(List<JSONSubtitleSegment> segments, String language) {
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
*/
|
||||
module com.github.gtache.autosubtitle.whisperx {
|
||||
requires transitive com.github.gtache.autosubtitle.whisper.common;
|
||||
requires transitive com.fasterxml.jackson.databind;
|
||||
requires org.apache.logging.log4j;
|
||||
requires java.prefs;
|
||||
|
||||
exports com.github.gtache.autosubtitle.whisperx;
|
||||
exports com.github.gtache.autosubtitle.setup.whisperx;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.github.gtache.autosubtitle.modules.subtitle.parser.json.whisperx;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||
@@ -8,7 +8,7 @@ import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||
class TestWhisperXJsonModule {
|
||||
|
||||
@Test
|
||||
void testGson() {
|
||||
assertInstanceOf(Gson.class, WhisperXJsonModule.providesGson());
|
||||
void testObjectMapper() {
|
||||
assertInstanceOf(ObjectMapper.class, WhisperXJsonModule.providesObjectMapper());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.github.gtache.autosubtitle.process.ProcessRunner;
|
||||
import com.github.gtache.autosubtitle.subtitle.Subtitle;
|
||||
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter;
|
||||
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverterProvider;
|
||||
import com.github.gtache.autosubtitle.subtitle.extractor.ExtractOptions;
|
||||
import com.github.gtache.autosubtitle.whisper.WhisperModels;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -16,11 +17,12 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class TestWhisperXSubtitleExtractor {
|
||||
@@ -29,14 +31,16 @@ class TestWhisperXSubtitleExtractor {
|
||||
private final SubtitleConverterProvider converterProvider;
|
||||
private final SubtitleConverter<Subtitle> converter;
|
||||
private final ProcessRunner processRunner;
|
||||
private final ExtractOptions options;
|
||||
private final OS os;
|
||||
private WhisperXSubtitleExtractor whisperXSubtitleExtractor;
|
||||
|
||||
TestWhisperXSubtitleExtractor(@Mock final SubtitleConverterProvider converterProvider, @Mock final SubtitleConverter<Subtitle> converter,
|
||||
@Mock final ProcessRunner processRunner) {
|
||||
this.converterProvider = Objects.requireNonNull(converterProvider);
|
||||
this.converter = Objects.requireNonNull(converter);
|
||||
this.processRunner = Objects.requireNonNull(processRunner);
|
||||
@Mock final ProcessRunner processRunner, @Mock final ExtractOptions options) {
|
||||
this.converterProvider = requireNonNull(converterProvider);
|
||||
this.converter = requireNonNull(converter);
|
||||
this.processRunner = requireNonNull(processRunner);
|
||||
this.options = requireNonNull(options);
|
||||
this.venvPath = Paths.get("path");
|
||||
this.os = OS.LINUX;
|
||||
}
|
||||
@@ -51,44 +55,44 @@ class TestWhisperXSubtitleExtractor {
|
||||
void testEN() {
|
||||
final var path = Paths.get("in");
|
||||
final var outputPath = Paths.get("out");
|
||||
final var language = Language.EN;
|
||||
final var model = WhisperModels.MEDIUM;
|
||||
when(options.model()).thenReturn(WhisperModels.MEDIUM);
|
||||
when(options.language()).thenReturn(Language.EN);
|
||||
final var expected = List.of(venvPath.resolve("python").toString(),
|
||||
"-m", "whisperx", "--verbose", "False", "--model", "medium.en", "--task", "transcribe",
|
||||
"--output_dir", outputPath.toString(), "--output_format", "json", "--device", "cpu",
|
||||
"--condition_on_previous_text", "True", "--print_progress", "True", "--compute_type",
|
||||
"int8", "--threads", String.valueOf(Runtime.getRuntime().availableProcessors()), "--language",
|
||||
"en", path.toString());
|
||||
assertEquals(expected, whisperXSubtitleExtractor.createArgs(path, language, model, outputPath));
|
||||
assertEquals(expected, whisperXSubtitleExtractor.createArgs(path, options, outputPath));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testENLarge() {
|
||||
final var path = Paths.get("in");
|
||||
final var outputPath = Paths.get("out");
|
||||
final var language = Language.EN;
|
||||
final var model = WhisperModels.LARGE;
|
||||
when(options.model()).thenReturn(WhisperModels.LARGE);
|
||||
when(options.language()).thenReturn(Language.EN);
|
||||
final var expected = List.of(venvPath.resolve("python").toString(),
|
||||
"-m", "whisperx", "--verbose", "False", "--model", "large", "--task", "transcribe",
|
||||
"--output_dir", outputPath.toString(), "--output_format", "json", "--device", "cpu",
|
||||
"--condition_on_previous_text", "True", "--print_progress", "True", "--compute_type",
|
||||
"int8", "--threads", String.valueOf(Runtime.getRuntime().availableProcessors()), "--language",
|
||||
"en", path.toString());
|
||||
assertEquals(expected, whisperXSubtitleExtractor.createArgs(path, language, model, outputPath));
|
||||
assertEquals(expected, whisperXSubtitleExtractor.createArgs(path, options, outputPath));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAuto() {
|
||||
final var path = Paths.get("in");
|
||||
final var outputPath = Paths.get("out");
|
||||
final var language = Language.AUTO;
|
||||
final var model = WhisperModels.SMALL;
|
||||
when(options.model()).thenReturn(WhisperModels.SMALL);
|
||||
when(options.language()).thenReturn(Language.AUTO);
|
||||
final var expected = List.of(venvPath.resolve("python").toString(),
|
||||
"-m", "whisperx", "--verbose", "False", "--model", "small", "--task", "transcribe",
|
||||
"--output_dir", outputPath.toString(), "--output_format", "json", "--device", "cpu",
|
||||
"--condition_on_previous_text", "True", "--print_progress", "True", "--compute_type",
|
||||
"int8", "--threads", String.valueOf(Runtime.getRuntime().availableProcessors()), path.toString());
|
||||
assertEquals(expected, whisperXSubtitleExtractor.createArgs(path, language, model, outputPath));
|
||||
assertEquals(expected, whisperXSubtitleExtractor.createArgs(path, options, outputPath));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,44 +1,48 @@
|
||||
package com.github.gtache.autosubtitle.subtitle.parser.json.whisperx;
|
||||
|
||||
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 com.google.gson.Gson;
|
||||
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.prefs.Preferences;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class TestJSONSubtitleConverter {
|
||||
|
||||
private final Preferences preferences;
|
||||
private final int defaultMaxLineLength;
|
||||
private final int defaultMaxLines;
|
||||
private JSONSubtitleConverter converter;
|
||||
private final ParseOptions parseOptions;
|
||||
private final FormatOptions formatOptions;
|
||||
private final JSONSubtitleConverter converter;
|
||||
|
||||
TestJSONSubtitleConverter() {
|
||||
this.preferences = mock(Preferences.class);
|
||||
this.defaultMaxLineLength = 100;
|
||||
this.defaultMaxLines = 2;
|
||||
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(preferences.getInt("maxLineLength", defaultMaxLineLength)).thenReturn(defaultMaxLineLength);
|
||||
when(preferences.getInt("maxLines", defaultMaxLines)).thenReturn(defaultMaxLines);
|
||||
this.converter = new JSONSubtitleConverter(new Gson(), preferences, defaultMaxLineLength, defaultMaxLines);
|
||||
when(parseOptions.maxLineLength()).thenReturn(100);
|
||||
when(parseOptions.maxLines()).thenReturn(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -47,7 +51,7 @@ class TestJSONSubtitleConverter {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseFormat() throws IOException, ParseException {
|
||||
void testParseFormat() throws IOException, ParseException, FormatException {
|
||||
try (final var inStream = getClass().getResourceAsStream("whisperx-in.json");
|
||||
final var outStream = getClass().getResourceAsStream("whisperx-out.json")) {
|
||||
if (inStream == null || outStream == null) {
|
||||
@@ -56,8 +60,8 @@ class TestJSONSubtitleConverter {
|
||||
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));
|
||||
assertEquals(out, converter.format(expected, null));
|
||||
assertEquals(expected, converter.parse(in, parseOptions));
|
||||
assertEquals(out, converter.format(expected, formatOptions));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +76,7 @@ class TestJSONSubtitleConverter {
|
||||
List.of(new SubtitleImpl("aaaaaaaaaa bbbbbbbbbb cccccccccc dddddddddd eeeeeeeeee ffffffffff gggggggggg hhhhhhhhhh iiiiiiiiii\njjjjjjjjjj kkkkkkkkkk llllllllll mmmmmmmmmm nnnnnnnnnn oooooooooo pppppppppp qqqqqqqqqq rrrrrrrrrr", 0, 18000, null, null),
|
||||
new SubtitleImpl("ssssssssss tttttttttt uuuuuuuuuu vvvvvvvvvv wwwwwwwwww xxxxxxxxxx yyyyyyyyyy zzzzzzzzzz", 18000, 26000, null, null),
|
||||
new SubtitleImpl("Yes.", 30000, 31000, null, null)), Language.EN);
|
||||
assertEquals(expected, converter.parse(in));
|
||||
assertEquals(expected, converter.parse(in, parseOptions));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +90,7 @@ class TestJSONSubtitleConverter {
|
||||
final var expected = new SubtitleCollectionImpl<Subtitle>("aaaaaaaaaa bbbbbbbbbb cccccccccc dddddddddd eeeeeeeeee ffffffffff gggggggggg hhhhhhhhhh iiiiiiiiii\njjjjjjjjjj kkkkkkkkkk llllllllll Yes.",
|
||||
List.of(new SubtitleImpl("aaaaaaaaaa bbbbbbbbbb cccccccccc dddddddddd eeeeeeeeee ffffffffff gggggggggg hhhhhhhhhh iiiiiiiiii\njjjjjjjjjj kkkkkkkkkk llllllllll", 0, 18000, null, null),
|
||||
new SubtitleImpl("Yes.", 30000, 31000, null, null)), Language.EN);
|
||||
assertEquals(expected, converter.parse(in));
|
||||
assertEquals(expected, converter.parse(in, parseOptions));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user