SRT works ; adds support for export/import ass

This commit is contained in:
Guillaume Tâche
2024-08-24 20:52:01 +02:00
parent d14e32bfd0
commit 728b563d8b
37 changed files with 552 additions and 145 deletions

View File

@@ -4,6 +4,7 @@ import com.github.gtache.autosubtitle.Audio;
import com.github.gtache.autosubtitle.File;
import com.github.gtache.autosubtitle.Video;
import com.github.gtache.autosubtitle.VideoConverter;
import com.github.gtache.autosubtitle.VideoInfo;
import com.github.gtache.autosubtitle.impl.AudioInfoImpl;
import com.github.gtache.autosubtitle.impl.FileAudioImpl;
import com.github.gtache.autosubtitle.impl.FileVideoImpl;
@@ -13,6 +14,7 @@ import com.github.gtache.autosubtitle.modules.setup.ffmpeg.FFmpegSystemPath;
import com.github.gtache.autosubtitle.process.impl.AbstractProcessRunner;
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter;
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverterProvider;
import javax.inject.Inject;
import java.io.IOException;
@@ -23,9 +25,9 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.SequencedMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.prefs.Preferences;
import static java.util.Objects.requireNonNull;
@@ -37,13 +39,16 @@ public class FFmpegVideoConverter extends AbstractProcessRunner implements Video
private static final String TEMP_FILE_PREFIX = "autosubtitle";
private final Path bundledPath;
private final Path systemPath;
private final SubtitleConverter<?> subtitleConverter;
private final SubtitleConverterProvider converterProvider;
private final Preferences preferences;
@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 SubtitleConverterProvider converterProvider, final Preferences preferences) {
this.bundledPath = requireNonNull(bundledPath);
this.systemPath = requireNonNull(systemPath);
this.subtitleConverter = subtitleConverters.get("srt");
this.converterProvider = requireNonNull(converterProvider);
this.preferences = requireNonNull(preferences);
}
@Override
@@ -56,7 +61,7 @@ public class FFmpegVideoConverter extends AbstractProcessRunner implements Video
@Override
public void addSoftSubtitles(final Video video, final Collection<? extends SubtitleCollection<?>> subtitles, final Path path) throws IOException {
final var videoPath = getPath(video);
final var collectionMap = dumpCollections(subtitles);
final var collectionMap = dumpCollections(subtitles, video.info());
final var args = new ArrayList<String>();
args.add(getFFmpegPath());
args.add("-y");
@@ -86,7 +91,7 @@ public class FFmpegVideoConverter extends AbstractProcessRunner implements Video
args.add("mov_text");
} else {
args.add("-c:s");
args.add(subtitleConverter.formatName());
args.add("copy");
}
final var j = new AtomicInteger(0);
collectionMap.forEach((c, p) -> {
@@ -108,9 +113,9 @@ public class FFmpegVideoConverter extends AbstractProcessRunner implements Video
@Override
public void addHardSubtitles(final Video video, final SubtitleCollection<?> subtitles, final Path path) throws IOException {
final var videoPath = getPath(video);
final var subtitlesPath = dumpSubtitles(subtitles);
final var subtitlesPath = dumpSubtitles(subtitles, video.info());
final var escapedPath = escapeVF(subtitlesPath.toString());
final var subtitleArg = subtitleConverter.formatName().equalsIgnoreCase("ass") ? "ass='" + escapedPath + "'" : "subtitles='" + escapedPath + "'";
final var subtitleArg = getSubtitleConverter().formatName().equalsIgnoreCase("ass") ? "ass='" + escapedPath + "'" : "subtitles='" + escapedPath + "'";
final var args = List.of(
getFFmpegPath(),
"-i",
@@ -122,6 +127,10 @@ public class FFmpegVideoConverter extends AbstractProcessRunner implements Video
runListen(args, Duration.ofHours(1));
}
private SubtitleConverter<?> getSubtitleConverter() {
return converterProvider.getConverter(preferences.get("outputFormat", "ass"));
}
private static String escapeVF(final String path) {
return path.replace("\\", "\\\\").replace(":", "\\:").replace("'", "'\\''")
.replace("%", "\\%");
@@ -165,17 +174,17 @@ public class FFmpegVideoConverter extends AbstractProcessRunner implements Video
return path;
}
private <T extends SubtitleCollection<?>> SequencedMap<T, Path> dumpCollections(final Collection<T> collections) throws IOException {
private <T extends SubtitleCollection<?>> SequencedMap<T, Path> dumpCollections(final Collection<T> collections, final VideoInfo videoInfo) throws IOException {
final var ret = LinkedHashMap.<T, Path>newLinkedHashMap(collections.size());
for (final var subtitles : collections) {
ret.put(subtitles, dumpSubtitles(subtitles));
ret.put(subtitles, dumpSubtitles(subtitles, videoInfo));
}
return ret;
}
private Path dumpSubtitles(final SubtitleCollection<?> subtitles) throws IOException {
final var path = getTempFile("srt");
Files.writeString(path, subtitleConverter.format(subtitles));
private Path dumpSubtitles(final SubtitleCollection<?> subtitles, final VideoInfo videoInfo) throws IOException {
final var path = getTempFile(getSubtitleConverter().formatName().toLowerCase());
Files.writeString(path, getSubtitleConverter().format(subtitles, videoInfo));
return path;
}

View File

@@ -1,6 +1,6 @@
package com.github.gtache.autosubtitle.setup.ffmpeg;
import com.github.gtache.autosubtitle.archive.Archiver;
import com.github.gtache.autosubtitle.archive.ArchiverProvider;
import com.github.gtache.autosubtitle.impl.Architecture;
import com.github.gtache.autosubtitle.setup.SetupException;
import com.github.gtache.autosubtitle.setup.SetupManager;
@@ -16,7 +16,6 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import static com.github.gtache.autosubtitle.impl.Architecture.ARMEL;
import static com.github.gtache.autosubtitle.impl.Architecture.ARMHF;
@@ -29,14 +28,14 @@ import static java.util.Objects.requireNonNull;
public class FFmpegSetupManager extends AbstractSetupManager {
private static final Logger logger = LogManager.getLogger(FFmpegSetupManager.class);
private final FFmpegSetupConfiguration configuration;
private final Map<String, Archiver> archivers;
private final ArchiverProvider archiverProvider;
@Inject
FFmpegSetupManager(final FFmpegSetupConfiguration configuration, final Map<String, Archiver> archivers,
FFmpegSetupManager(final FFmpegSetupConfiguration configuration, final ArchiverProvider archiverProvider,
final HttpClient httpClient) {
super(httpClient);
this.configuration = requireNonNull(configuration);
this.archivers = Map.copyOf(archivers);
this.archiverProvider = requireNonNull(archiverProvider);
}
@Override
@@ -135,7 +134,7 @@ public class FFmpegSetupManager extends AbstractSetupManager {
try {
final var filename = from.getFileName().toString();
final var extension = filename.substring(filename.lastIndexOf('.') + 1);
archivers.get(extension).decompress(from, to);
archiverProvider.getArchiver(extension).decompress(from, to);
} catch (final IOException e) {
throw new SetupException(e);
}

View File

@@ -9,6 +9,7 @@ module com.github.gtache.autosubtitle.ffmpeg {
requires org.apache.logging.log4j;
requires org.tukaani.xz;
requires org.apache.commons.compress;
requires java.prefs;
exports com.github.gtache.autosubtitle.ffmpeg;
exports com.github.gtache.autosubtitle.setup.ffmpeg;

View File

@@ -5,6 +5,7 @@ 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 com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverterProvider;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
@@ -14,8 +15,8 @@ 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 java.util.prefs.Preferences;
import static org.mockito.Mockito.when;
@@ -24,14 +25,16 @@ class TestFFmpegVideoConverter {
private final FFmpegVideoConverter converter;
private final SubtitleConverter subtitleConverter;
private final SubtitleConverterProvider subtitleConverterProvider;
private final Video video;
private final VideoInfo videoInfo;
private final Path tmpFile;
private final Path outputPath;
private final SubtitleCollection<?> collection;
private final Preferences preferences;
TestFFmpegVideoConverter(@Mock final SubtitleConverter subtitleConverter, @Mock final Video video,
@Mock final VideoInfo videoInfo, @Mock final SubtitleCollection<?> collection) throws IOException {
TestFFmpegVideoConverter(@Mock final SubtitleConverter subtitleConverter, @Mock final SubtitleConverterProvider subtitleConverterProvider, @Mock final Video video,
@Mock final VideoInfo videoInfo, @Mock final SubtitleCollection<?> collection, @Mock final Preferences preferences) 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);
@@ -43,7 +46,9 @@ class TestFFmpegVideoConverter {
}
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.subtitleConverterProvider = Objects.requireNonNull(subtitleConverterProvider);
this.preferences = Objects.requireNonNull(preferences);
this.converter = new FFmpegVideoConverter(tmpFile, tmpFile, subtitleConverterProvider, preferences);
this.collection = Objects.requireNonNull(collection);
}