Pipeline working, implements FFmpegSetupManager

This commit is contained in:
Guillaume Tâche
2024-08-09 20:30:21 +02:00
parent c2efb71195
commit 155b011c2b
54 changed files with 984 additions and 314 deletions

View File

@@ -16,6 +16,14 @@
<groupId>com.github.gtache.autosubtitle</groupId>
<artifactId>autosubtitle-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
</dependency>
<dependency>
<groupId>org.tukaani</groupId>
<artifactId>xz</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -49,19 +49,24 @@ public class FFmpegVideoConverter extends AbstractProcessRunner implements Video
@Override
public Video addSoftSubtitles(final Video video, final Collection<SubtitleCollection> subtitles) throws IOException {
final var out = getTempFile("mkv"); //Soft ass subtitles are only supported by mkv apparently
addSoftSubtitles(video, subtitles, out);
return new FileVideoImpl(out, new VideoInfoImpl("mkv", video.info().width(), video.info().height(), video.info().duration()));
}
@Override
public void addSoftSubtitles(final Video video, final Collection<SubtitleCollection> subtitles, final Path path) throws IOException {
final var videoPath = getPath(video);
final var collectionMap = dumpCollections(subtitles);
final var out = getTempFile("mkv"); //Soft subtitles are only supported by mkv apparently
final var args = new ArrayList<String>();
args.add(getFFmpegPath());
args.add("-y");
args.add("-i");
args.add(videoPath.toString());
collectionMap.forEach((c, p) -> {
args.add("-i");
args.add(p.toString());
});
args.add("-c");
args.add("copy");
args.add("-map");
args.add("0:v");
args.add("-map");
@@ -71,30 +76,56 @@ public class FFmpegVideoConverter extends AbstractProcessRunner implements Video
final var n = i.getAndIncrement();
args.add("-map");
args.add(String.valueOf(n));
});
args.add("-c:v");
args.add("copy");
args.add("-c:a");
args.add("copy");
final var extension = path.toString().substring(path.toString().lastIndexOf('.') + 1);
if (extension.equals("mp4")) {
args.add("-c:s");
args.add("mov_text");
} else {
args.add("-c:s");
args.add(subtitleConverter.formatName());
}
final var j = new AtomicInteger(0);
collectionMap.forEach((c, p) -> {
final var n = j.getAndIncrement();
args.add("-metadata:s:s:" + n);
args.add("language=" + c.language().iso3());
});
args.add(out.toString());
run(args);
return new FileVideoImpl(out, new VideoInfoImpl("mkv", video.info().width(), video.info().height(), video.info().duration()));
args.add(path.toString());
runListen(args);
}
@Override
public Video addHardSubtitles(final Video video, final SubtitleCollection subtitles) throws IOException {
final var out = getTempFile(video.info().videoFormat());
addHardSubtitles(video, subtitles, out);
return new FileVideoImpl(out, video.info());
}
@Override
public void addHardSubtitles(final Video video, final SubtitleCollection subtitles, final Path path) throws IOException {
final var videoPath = getPath(video);
final var subtitlesPath = dumpSubtitles(subtitles);
final var out = getTempFile(video.info().videoFormat());
final var subtitleArg = subtitleConverter.formatName().equalsIgnoreCase("ass") ? "ass=" + subtitlesPath : "subtitles=" + subtitlesPath;
final var escapedPath = escapeVF(subtitlesPath.toString());
final var subtitleArg = subtitleConverter.formatName().equalsIgnoreCase("ass") ? "ass='" + escapedPath + "'" : "subtitles='" + escapedPath + "'";
final var args = List.of(
getFFmpegPath(),
"-i",
videoPath.toString(),
"-vf",
subtitleArg,
out.toString()
path.toString()
);
run(args);
return new FileVideoImpl(out, video.info());
runListen(args);
}
private static String escapeVF(final String path) {
return path.replace("\\", "\\\\").replace(":", "\\:").replace("'", "'\\''")
.replace("%", "\\%");
}
@Override
@@ -104,6 +135,7 @@ public class FFmpegVideoConverter extends AbstractProcessRunner implements Video
final var dumpVideoPath = getTempFile("." + video.info().videoFormat());
final var args = List.of(
getFFmpegPath(),
"-y",
"-i",
videoPath.toString(),
"-map",
@@ -113,7 +145,7 @@ public class FFmpegVideoConverter extends AbstractProcessRunner implements Video
"0:v",
dumpVideoPath.toString()
);
run(args);
runListen(args);
Files.deleteIfExists(dumpVideoPath);
return new FileAudioImpl(audioPath, new AudioInfoImpl("wav", video.info().duration()));
}
@@ -143,7 +175,7 @@ public class FFmpegVideoConverter extends AbstractProcessRunner implements Video
}
private Path dumpSubtitles(final SubtitleCollection subtitles) throws IOException {
final var path = getTempFile("ass");
final var path = getTempFile("srt");
Files.writeString(path, subtitleConverter.format(subtitles));
return path;
}

View File

@@ -0,0 +1,16 @@
package com.github.gtache.autosubtitle.modules.ffmpeg;
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 FFProbeInstallerPath {
}

View File

@@ -0,0 +1,16 @@
package com.github.gtache.autosubtitle.modules.ffmpeg;
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 FFmpegInstallerPath {
}

View File

@@ -4,15 +4,23 @@ import com.github.gtache.autosubtitle.VideoConverter;
import com.github.gtache.autosubtitle.VideoLoader;
import com.github.gtache.autosubtitle.ffmpeg.FFmpegVideoConverter;
import com.github.gtache.autosubtitle.ffmpeg.FFprobeVideoLoader;
import com.github.gtache.autosubtitle.modules.setup.ffmpeg.FFmpegSetupModule;
import dagger.Binds;
import dagger.Module;
@Module
public interface FFmpegModule {
/**
* Dagger module for FFmpeg
*/
@Module(includes = FFmpegSetupModule.class)
public abstract class FFmpegModule {
private FFmpegModule() {
}
@Binds
VideoConverter bindsVideoConverter(final FFmpegVideoConverter converter);
abstract VideoConverter bindsVideoConverter(final FFmpegVideoConverter converter);
@Binds
VideoLoader bindsVideoLoader(final FFprobeVideoLoader loader);
abstract VideoLoader bindsVideoLoader(final FFprobeVideoLoader loader);
}

View File

@@ -1,7 +1,11 @@
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.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;
@@ -9,12 +13,18 @@ import com.github.gtache.autosubtitle.modules.ffmpeg.FFprobeSystemPath;
import com.github.gtache.autosubtitle.modules.impl.ExecutableExtension;
import com.github.gtache.autosubtitle.modules.setup.impl.VideoConverterSetup;
import com.github.gtache.autosubtitle.setup.SetupManager;
import com.github.gtache.autosubtitle.setup.ffmpeg.Decompresser;
import com.github.gtache.autosubtitle.setup.ffmpeg.FFmpegSetupConfiguration;
import com.github.gtache.autosubtitle.setup.ffmpeg.FFmpegSetupManager;
import com.github.gtache.autosubtitle.setup.ffmpeg.TarDecompresser;
import com.github.gtache.autosubtitle.setup.ffmpeg.XZDecompresser;
import com.github.gtache.autosubtitle.setup.ffmpeg.ZipDecompresser;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
import dagger.multibindings.StringKey;
import javax.inject.Singleton;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -27,47 +37,87 @@ public abstract class FFmpegSetupModule {
private static final String FFMPEG = "ffmpeg";
private static final String FFPROBE = "ffprobe";
private FFmpegSetupModule() {
}
@Binds
@StringKey("zip")
@IntoMap
abstract Decompresser bindsZipDecompresser(final ZipDecompresser decompresser);
@Binds
@StringKey("tar")
@IntoMap
abstract Decompresser bindsTarDecompresser(final TarDecompresser decompresser);
@Binds
@StringKey("xz")
@IntoMap
abstract Decompresser bindsXzDecompresser(final XZDecompresser decompresser);
@Binds
@VideoConverterSetup
abstract SetupManager bindsFFmpegSetupManager(final FFmpegSetupManager manager);
@Provides
@Singleton
static FFmpegSetupConfiguration providesFFmpegSetupConfiguration(@FFBundledRoot final Path root, @FFmpegBundledPath final Path bundledPath, @FFmpegSystemPath final Path systemPath,
@FFmpegInstallerPath final Path ffmpegInstallerPath, @FFProbeInstallerPath final Path ffprobeInstallerPath,
final OS os, final Architecture architecture) {
return new FFmpegSetupConfiguration(root, bundledPath, systemPath, ffmpegInstallerPath, ffprobeInstallerPath, os, architecture);
}
@Provides
@FFmpegInstallerPath
static Path providesFFmpegInstallerPath(@FFBundledRoot final Path root, final OS os) {
return root.resolve("cache").resolve("ffmpeg-installer" + getInstallerExtension(os));
}
@Provides
@FFProbeInstallerPath
static Path providesFFProbeInstallerPath(@FFBundledRoot final Path root, final OS os) {
return root.resolve("cache").resolve("ffprobe-installer" + getInstallerExtension(os));
}
private static String getInstallerExtension(final OS os) {
if (os == OS.LINUX) {
return ".tar.gz";
} else {
return ".zip";
}
}
@Provides
@FFBundledRoot
static Path providesFFBundledRoot() {
return Paths.get("tools", FFMPEG);
}
@Provides
@Singleton
@FFprobeBundledPath
static Path providesFFProbeBundledPath(@FFBundledRoot final Path root, @ExecutableExtension final String extension) {
return root.resolve(FFPROBE + extension);
}
@Provides
@Singleton
@FFprobeSystemPath
static Path providesFFProbeSystemPath(@ExecutableExtension final String extension) {
return Paths.get(FFPROBE + extension);
}
@Provides
@Singleton
@FFmpegBundledPath
static Path providesFFmpegBundledPath(@FFBundledRoot final Path root, @ExecutableExtension final String extension) {
return root.resolve(FFMPEG + extension);
}
@Provides
@Singleton
@FFmpegSystemPath
static Path providesFFmpegSystemPath(@ExecutableExtension final String extension) {
return Paths.get(FFMPEG + extension);
}
@Provides
@Singleton
@FFmpegVersion
static String providesFFmpegVersion() {
return "7.0.1";

View File

@@ -0,0 +1,27 @@
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

@@ -0,0 +1,25 @@
package com.github.gtache.autosubtitle.setup.ffmpeg;
import com.github.gtache.autosubtitle.impl.Architecture;
import com.github.gtache.autosubtitle.impl.OS;
import java.nio.file.Path;
import java.util.Objects;
/**
* Configuration for FFmpeg setup
*/
public record FFmpegSetupConfiguration(Path root, Path bundledFFmpegPath, Path systemFFmpegPath,
Path ffmpegInstallerPath, Path ffprobeInstallerPath,
OS os, Architecture architecture) {
public FFmpegSetupConfiguration {
Objects.requireNonNull(root);
Objects.requireNonNull(bundledFFmpegPath);
Objects.requireNonNull(systemFFmpegPath);
Objects.requireNonNull(ffmpegInstallerPath);
Objects.requireNonNull(ffprobeInstallerPath);
Objects.requireNonNull(os);
Objects.requireNonNull(architecture);
}
}

View File

@@ -1,11 +1,8 @@
package com.github.gtache.autosubtitle.setup.ffmpeg;
import com.github.gtache.autosubtitle.impl.Architecture;
import com.github.gtache.autosubtitle.impl.OS;
import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegBundledPath;
import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegSystemPath;
import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegVersion;
import com.github.gtache.autosubtitle.setup.SetupException;
import com.github.gtache.autosubtitle.setup.SetupManager;
import com.github.gtache.autosubtitle.setup.SetupStatus;
import com.github.gtache.autosubtitle.setup.impl.AbstractSetupManager;
import org.apache.logging.log4j.LogManager;
@@ -13,30 +10,30 @@ import org.apache.logging.log4j.Logger;
import javax.inject.Inject;
import java.io.IOException;
import java.net.http.HttpClient;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
import java.util.Map;
import static com.github.gtache.autosubtitle.impl.Architecture.ARMEL;
import static com.github.gtache.autosubtitle.impl.Architecture.ARMHF;
import static java.util.Objects.requireNonNull;
/**
* Manager managing the FFmpeg installation
* {@link SetupManager} managing the FFmpeg installation
*/
//TODO add gpg/signature check
public class FFmpegSetupManager extends AbstractSetupManager {
private static final Logger logger = LogManager.getLogger(FFmpegSetupManager.class);
private final Path bundledPath;
private final Path systemPath;
private final String version;
private final OS os;
private final Architecture architecture;
private final String executableExtension;
private final FFmpegSetupConfiguration configuration;
private final Map<String, Decompresser> decompressers;
@Inject
FFmpegSetupManager(@FFmpegBundledPath final Path bundledPath, @FFmpegSystemPath final Path systemPath, @FFmpegVersion final String version, final OS os, final Architecture architecture) {
this.bundledPath = Objects.requireNonNull(bundledPath);
this.systemPath = Objects.requireNonNull(systemPath);
this.version = Objects.requireNonNull(version);
this.os = Objects.requireNonNull(os);
this.architecture = Objects.requireNonNull(architecture);
this.executableExtension = os == OS.WINDOWS ? ".exe" : "";
FFmpegSetupManager(final FFmpegSetupConfiguration configuration, final Map<String, Decompresser> decompressers,
final HttpClient httpClient) {
super(httpClient);
this.configuration = requireNonNull(configuration);
this.decompressers = Map.copyOf(decompressers);
}
@Override
@@ -61,12 +58,128 @@ public class FFmpegSetupManager extends AbstractSetupManager {
@Override
public void install() throws SetupException {
switch (configuration.os()) {
case WINDOWS -> installWindows();
case LINUX -> installLinux();
case MAC -> installMac();
}
}
private void installWindows() throws SetupException {
final var url = "https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip"; //.sha256
downloadFFmpeg(url);
decompressFFmpeg();
}
private void downloadFFmpeg(final String url) throws SetupException {
download(url, configuration.ffmpegInstallerPath());
}
private void downloadFFProbe(final String url) throws SetupException {
download(url, configuration.ffprobeInstallerPath());
}
private void installLinux() throws SetupException {
final var url = getLinuxUrl();
downloadFFmpeg(url);
decompressFFmpegLinux();
}
private void decompressFFmpegLinux() throws SetupException {
try {
final var tmp = Files.createTempFile("ffmpeg", ".tar");
decompress(configuration.ffmpegInstallerPath(), tmp);
decompress(tmp, configuration.root());
} catch (final IOException e) {
throw new SetupException(e);
}
}
private String getLinuxUrl() throws SetupException {
final var architecture = configuration.architecture();
if (architecture.isAMD64()) {
return "https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz"; // .md5
} else if (architecture.isARM64()) {
return "https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-arm64-static.tar.xz";
} else if (architecture == Architecture.I686) {
return "https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-i686-static.tar.xz";
} else if (architecture == ARMHF) {
return "https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-armhf-static.tar.xz";
} else if (architecture == ARMEL) {
return "https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-armel-static.tar.xz";
} else {
throwUnsupportedOsArchitectureException();
return null;
}
}
private void throwUnsupportedOsArchitectureException() throws SetupException {
throw new SetupException("Unsupported os - architecture : " + configuration.os() + " - " + configuration.architecture());
}
private void installMac() throws SetupException {
installFFmpegMac();
installFFprobeMac();
}
private void installFFmpegMac() throws SetupException {
final var url = getMacFFmpegUrl();
downloadFFmpeg(url);
decompress(configuration.ffmpegInstallerPath(), configuration.root());
}
private void decompress(final Path from, final Path to) throws SetupException {
try {
final var filename = from.getFileName().toString();
final var extension = filename.substring(filename.lastIndexOf('.') + 1);
decompressers.get(extension).decompress(from, to);
} catch (final IOException e) {
throw new SetupException(e);
}
}
private void decompressFFmpeg() throws SetupException {
decompress(configuration.ffmpegInstallerPath(), configuration.root());
}
private void decompressFFProbe() throws SetupException {
decompress(configuration.ffprobeInstallerPath(), configuration.root());
}
private void installFFprobeMac() throws SetupException {
final var url = getMacFFprobeUrl();
downloadFFProbe(url);
decompressFFProbe();
}
private String getMacFFmpegUrl() throws SetupException {
final var architecture = configuration.architecture();
if (architecture.isAMD64()) {
return "https://evermeet.cx/ffmpeg/getrelease/ffmpeg/zip"; // /sig
} else if (architecture.isARM64()) {
return "https://www.osxexperts.net/ffmpeg7arm.zip"; //no automatic sha?
} else {
throwUnsupportedOsArchitectureException();
return null;
}
}
private String getMacFFprobeUrl() throws SetupException {
final var architecture = configuration.architecture();
if (architecture.isAMD64()) {
return "https://evermeet.cx/ffmpeg/getrelease/ffprobe/zip";
} else if (architecture.isARM64()) {
return "https://www.osxexperts.net/ffprobe7arm.zip";
} else {
throwUnsupportedOsArchitectureException();
return null;
}
}
@Override
public void uninstall() throws SetupException {
deleteFolder(configuration.root());
}
@Override
@@ -75,11 +188,11 @@ public class FFmpegSetupManager extends AbstractSetupManager {
}
private boolean checkSystemFFmpeg() throws IOException {
final var result = run(systemPath.toString(), "-version");
final var result = run(configuration.systemFFmpegPath().toString(), "-version");
return result.exitCode() == 0;
}
private boolean checkBundledFFmpeg() throws IOException {
return Files.isRegularFile(bundledPath);
return Files.isRegularFile(configuration.bundledFFmpegPath());
}
}

View File

@@ -0,0 +1,57 @@
package com.github.gtache.autosubtitle.setup.ffmpeg;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
/**
* Tar implementation of {@link Decompresser}
*/
public class TarDecompresser 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 zis = new TarArchiveInputStream(Files.newInputStream(archive))) {
var entry = zis.getNextEntry();
while (entry != null) {
final var newFile = newFile(destination, entry);
if (entry.isDirectory()) {
Files.createDirectories(newFile);
} else {
// fix for Windows-created archives
final var parent = newFile.getParent();
Files.createDirectories(parent);
// write file content
try (final var fos = Files.newOutputStream(newFile)) {
zis.transferTo(fos);
}
}
entry = zis.getNextEntry();
}
}
}
private static Path newFile(final Path destinationDir, final TarArchiveEntry entry) throws IOException {
final var destPath = destinationDir.resolve(entry.getName());
final var destDirPath = destinationDir.toAbsolutePath().toString();
final var destFilePath = destPath.toAbsolutePath().toString();
if (!destFilePath.startsWith(destDirPath + File.separator)) {
throw new IOException("Entry is outside of the target dir: " + entry.getName());
}
return destPath;
}
@Override
public boolean isPathSupported(final Path path) {
return path.getFileName().toString().endsWith(".tar");
}
}

View File

@@ -0,0 +1,28 @@
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

@@ -0,0 +1,57 @@
package com.github.gtache.autosubtitle.setup.ffmpeg;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
/**
* Zip implementation of {@link Decompresser}
*/
public class ZipDecompresser 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 zis = new ZipInputStream(Files.newInputStream(archive))) {
var zipEntry = zis.getNextEntry();
while (zipEntry != null) {
final var newFile = newFile(destination, zipEntry);
if (zipEntry.isDirectory()) {
Files.createDirectories(newFile);
} else {
// fix for Windows-created archives
final var parent = newFile.getParent();
Files.createDirectories(parent);
// write file content
try (final var fos = Files.newOutputStream(newFile)) {
zis.transferTo(fos);
}
}
zipEntry = zis.getNextEntry();
}
zis.closeEntry();
}
}
private static Path newFile(final Path destinationDir, final ZipEntry zipEntry) throws IOException {
final var destPath = destinationDir.resolve(zipEntry.getName());
final var destDirPath = destinationDir.toAbsolutePath().toString();
final var destFilePath = destPath.toAbsolutePath().toString();
if (!destFilePath.startsWith(destDirPath + File.separator)) {
throw new IOException("Entry is outside of the target dir: " + zipEntry.getName());
}
return destPath;
}
@Override
public boolean isPathSupported(final Path path) {
return path.getFileName().toString().endsWith(".zip");
}
}

View File

@@ -5,11 +5,14 @@ module com.github.gtache.autosubtitle.ffmpeg {
requires transitive com.github.gtache.autosubtitle.core;
requires transitive dagger;
requires transitive javax.inject;
requires java.net.http;
requires org.apache.logging.log4j;
requires org.tukaani.xz;
requires org.apache.commons.compress;
exports com.github.gtache.autosubtitle.ffmpeg;
exports com.github.gtache.autosubtitle.setup.ffmpeg;
exports com.github.gtache.autosubtitle.modules.ffmpeg;
exports com.github.gtache.autosubtitle.modules.setup.ffmpeg;
}