Can playback video with controls, need to fix performance and reducing window size
This commit is contained in:
@@ -8,20 +8,22 @@ import com.github.gtache.autosubtitle.impl.AudioInfoImpl;
|
||||
import com.github.gtache.autosubtitle.impl.FileAudioImpl;
|
||||
import com.github.gtache.autosubtitle.impl.FileVideoImpl;
|
||||
import com.github.gtache.autosubtitle.impl.VideoInfoImpl;
|
||||
import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegBundledPath;
|
||||
import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegSystemPath;
|
||||
import com.github.gtache.autosubtitle.process.impl.AbstractProcessRunner;
|
||||
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
|
||||
import com.github.gtache.autosubtitle.subtitle.SubtitleConverter;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.SequencedMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
@@ -29,15 +31,18 @@ import static java.util.Objects.requireNonNull;
|
||||
/**
|
||||
* FFmpeg implementation of {@link VideoConverter}
|
||||
*/
|
||||
public class FFmpegVideoConverter implements VideoConverter {
|
||||
@Singleton
|
||||
public class FFmpegVideoConverter extends AbstractProcessRunner implements VideoConverter {
|
||||
|
||||
private static final String TEMP_FILE_PREFIX = "autosubtitle";
|
||||
private final Path ffmpegPath;
|
||||
private final Path bundledPath;
|
||||
private final Path systemPath;
|
||||
private final SubtitleConverter subtitleConverter;
|
||||
|
||||
@Inject
|
||||
FFmpegVideoConverter(final Path ffmpegPath, final SubtitleConverter subtitleConverter) {
|
||||
this.ffmpegPath = requireNonNull(ffmpegPath);
|
||||
FFmpegVideoConverter(@FFmpegBundledPath final Path bundledPath, @FFmpegSystemPath final Path systemPath, final SubtitleConverter subtitleConverter) {
|
||||
this.bundledPath = requireNonNull(bundledPath);
|
||||
this.systemPath = requireNonNull(systemPath);
|
||||
this.subtitleConverter = requireNonNull(subtitleConverter);
|
||||
}
|
||||
|
||||
@@ -47,7 +52,7 @@ public class FFmpegVideoConverter implements VideoConverter {
|
||||
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(ffmpegPath.toString());
|
||||
args.add(getFFmpegPath());
|
||||
args.add("-i");
|
||||
args.add(videoPath.toString());
|
||||
collectionMap.forEach((c, p) -> {
|
||||
@@ -66,11 +71,11 @@ public class FFmpegVideoConverter implements VideoConverter {
|
||||
args.add("-map");
|
||||
args.add(String.valueOf(n));
|
||||
args.add("-metadata:s:s:" + n);
|
||||
args.add("language=" + c.language());
|
||||
args.add("language=" + c.locale().getISO3Language());
|
||||
});
|
||||
args.add(out.toString());
|
||||
run(args);
|
||||
return new FileVideoImpl(out, new VideoInfoImpl("mkv"));
|
||||
return new FileVideoImpl(out, new VideoInfoImpl("mkv", video.info().width(), video.info().height(), video.info().duration()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -80,7 +85,7 @@ public class FFmpegVideoConverter implements VideoConverter {
|
||||
final var out = getTempFile(video.info().videoFormat());
|
||||
final var subtitleArg = subtitleConverter.formatName().equalsIgnoreCase("ass") ? "ass=" + subtitlesPath : "subtitles=" + subtitlesPath;
|
||||
final var args = List.of(
|
||||
ffmpegPath.toString(),
|
||||
getFFmpegPath(),
|
||||
"-i",
|
||||
videoPath.toString(),
|
||||
"-vf",
|
||||
@@ -97,7 +102,7 @@ public class FFmpegVideoConverter implements VideoConverter {
|
||||
final var audioPath = getTempFile(".wav");
|
||||
final var dumpVideoPath = getTempFile("." + video.info().videoFormat());
|
||||
final var args = List.of(
|
||||
ffmpegPath.toString(),
|
||||
getFFmpegPath(),
|
||||
"-i",
|
||||
videoPath.toString(),
|
||||
"-map",
|
||||
@@ -129,7 +134,7 @@ public class FFmpegVideoConverter implements VideoConverter {
|
||||
}
|
||||
|
||||
private SequencedMap<SubtitleCollection, Path> dumpCollections(final Collection<SubtitleCollection> collections) throws IOException {
|
||||
final var ret = new LinkedHashMap<SubtitleCollection, Path>(collections.size());
|
||||
final var ret = LinkedHashMap.<SubtitleCollection, Path>newLinkedHashMap(collections.size());
|
||||
for (final var subtitles : collections) {
|
||||
ret.put(subtitles, dumpSubtitles(subtitles));
|
||||
}
|
||||
@@ -148,23 +153,7 @@ public class FFmpegVideoConverter implements VideoConverter {
|
||||
return path;
|
||||
}
|
||||
|
||||
private void run(final String... args) throws IOException {
|
||||
run(Arrays.asList(args));
|
||||
}
|
||||
|
||||
private void run(final List<String> args) throws IOException {
|
||||
final var builder = new ProcessBuilder(args);
|
||||
builder.inheritIO();
|
||||
builder.redirectErrorStream(true);
|
||||
final var process = builder.start();
|
||||
try {
|
||||
process.waitFor(1, TimeUnit.HOURS);
|
||||
} catch (final InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
process.destroy();
|
||||
}
|
||||
if (process.exitValue() != 0) {
|
||||
throw new IOException("FFmpeg exited with code " + process.exitValue());
|
||||
}
|
||||
private String getFFmpegPath() {
|
||||
return Files.isRegularFile(bundledPath) ? bundledPath.toString() : systemPath.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.github.gtache.autosubtitle.ffmpeg;
|
||||
|
||||
import com.github.gtache.autosubtitle.Video;
|
||||
import com.github.gtache.autosubtitle.VideoLoader;
|
||||
import com.github.gtache.autosubtitle.impl.FileVideoImpl;
|
||||
import com.github.gtache.autosubtitle.impl.VideoInfoImpl;
|
||||
import com.github.gtache.autosubtitle.modules.ffmpeg.FFprobeBundledPath;
|
||||
import com.github.gtache.autosubtitle.modules.ffmpeg.FFprobeSystemPath;
|
||||
import com.github.gtache.autosubtitle.process.impl.AbstractProcessRunner;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
/**
|
||||
* FFprobe implementation of {@link VideoLoader}
|
||||
*/
|
||||
@Singleton
|
||||
public class FFprobeVideoLoader extends AbstractProcessRunner implements VideoLoader {
|
||||
|
||||
private final Path bundledPath;
|
||||
private final Path systemPath;
|
||||
|
||||
@Inject
|
||||
FFprobeVideoLoader(@FFprobeBundledPath final Path bundledPath, @FFprobeSystemPath final Path systemPath) {
|
||||
this.bundledPath = requireNonNull(bundledPath);
|
||||
this.systemPath = requireNonNull(systemPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Video loadVideo(final Path path) throws IOException {
|
||||
final var result = run(getFFprobePath(), "-v", "error", "-select_streams", "v", "-show_entries", "stream=width,height,duration", "-of", "csv=p=0", path.toString());
|
||||
final var resolution = result.output().getLast();
|
||||
final var split = resolution.split(",");
|
||||
final var width = Integer.parseInt(split[0]);
|
||||
final var height = Integer.parseInt(split[1]);
|
||||
final var filename = path.getFileName().toString();
|
||||
final var extension = filename.substring(filename.lastIndexOf('.') + 1);
|
||||
final var duration = (long) (Double.parseDouble(split[2]) * 1000L);
|
||||
final var info = new VideoInfoImpl(extension, width, height, duration);
|
||||
return new FileVideoImpl(path, info);
|
||||
}
|
||||
|
||||
private String getFFprobePath() {
|
||||
return Files.isRegularFile(bundledPath) ? bundledPath.toString() : systemPath.toString();
|
||||
}
|
||||
}
|
||||
@@ -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 FFBundledRoot {
|
||||
}
|
||||
@@ -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 FFmpegBundledPath {
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.github.gtache.autosubtitle.modules.ffmpeg;
|
||||
|
||||
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 dagger.Binds;
|
||||
import dagger.Module;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@Module
|
||||
public interface FFmpegModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
VideoConverter bindsVideoConverter(final FFmpegVideoConverter converter);
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
VideoLoader bindsVideoLoader(final FFprobeVideoLoader loader);
|
||||
|
||||
}
|
||||
@@ -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 FFmpegSystemPath {
|
||||
}
|
||||
@@ -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 FFmpegVersion {
|
||||
}
|
||||
@@ -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 FFprobeBundledPath {
|
||||
}
|
||||
@@ -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 FFprobeSystemPath {
|
||||
}
|
||||
@@ -1,15 +1,62 @@
|
||||
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.process.impl.AbstractProcessRunner;
|
||||
import com.github.gtache.autosubtitle.setup.SetupException;
|
||||
import com.github.gtache.autosubtitle.setup.SetupManager;
|
||||
import com.github.gtache.autosubtitle.setup.SetupStatus;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Manager managing the FFmpeg installation
|
||||
*/
|
||||
public class FFmpegSetupManager implements SetupManager {
|
||||
public class FFmpegSetupManager extends AbstractProcessRunner implements SetupManager {
|
||||
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;
|
||||
|
||||
@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" : "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInstalled() throws SetupException {
|
||||
return checkSystemFFmpeg() || checkBundledFFmpeg();
|
||||
public String name() {
|
||||
return "FFmpeg";
|
||||
}
|
||||
|
||||
@Override
|
||||
public SetupStatus status() {
|
||||
try {
|
||||
if (checkSystemFFmpeg() || checkBundledFFmpeg()) {
|
||||
return SetupStatus.INSTALLED;
|
||||
} else {
|
||||
return SetupStatus.NOT_INSTALLED;
|
||||
}
|
||||
} catch (final IOException e) {
|
||||
logger.error("Error checking status of {}", name(), e);
|
||||
return SetupStatus.ERRORED;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -22,21 +69,17 @@ public class FFmpegSetupManager implements SetupManager {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUpdateAvailable() throws SetupException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() throws SetupException {
|
||||
|
||||
}
|
||||
|
||||
private boolean checkSystemFFmpeg() {
|
||||
return false;
|
||||
private boolean checkSystemFFmpeg() throws IOException {
|
||||
final var result = run(systemPath.toString(), "-h");
|
||||
return result.exitCode() == 0;
|
||||
}
|
||||
|
||||
private boolean checkBundledFFmpeg() {
|
||||
return false;
|
||||
private boolean checkBundledFFmpeg() throws IOException {
|
||||
return Files.isRegularFile(bundledPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.github.gtache.autosubtitle.setup.modules.ffmpeg;
|
||||
|
||||
import com.github.gtache.autosubtitle.modules.ffmpeg.FFBundledRoot;
|
||||
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.modules.ffmpeg.FFprobeBundledPath;
|
||||
import com.github.gtache.autosubtitle.modules.ffmpeg.FFprobeSystemPath;
|
||||
import com.github.gtache.autosubtitle.modules.impl.ExecutableExtension;
|
||||
import com.github.gtache.autosubtitle.setup.SetupManager;
|
||||
import com.github.gtache.autosubtitle.setup.ffmpeg.FFmpegSetupManager;
|
||||
import com.github.gtache.autosubtitle.setup.modules.impl.VideoConverterSetup;
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* Dagger module for FFmpeg setup
|
||||
*/
|
||||
@Module
|
||||
public abstract class FFmpegSetupModule {
|
||||
|
||||
private static final String FFMPEG = "ffmpeg";
|
||||
private static final String FFPROBE = "ffprobe";
|
||||
|
||||
@Binds
|
||||
@VideoConverterSetup
|
||||
abstract SetupManager bindsFFmpegSetupManager(final FFmpegSetupManager manager);
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@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";
|
||||
}
|
||||
}
|
||||
14
ffmpeg/src/main/java/module-info.java
Normal file
14
ffmpeg/src/main/java/module-info.java
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* FFmpeg module for auto-subtitle
|
||||
*/
|
||||
module com.github.gtache.autosubtitle.ffmpeg {
|
||||
requires transitive com.github.gtache.autosubtitle.core;
|
||||
requires transitive dagger;
|
||||
requires transitive javax.inject;
|
||||
requires org.apache.logging.log4j;
|
||||
|
||||
exports com.github.gtache.autosubtitle.ffmpeg;
|
||||
exports com.github.gtache.autosubtitle.modules.ffmpeg;
|
||||
exports com.github.gtache.autosubtitle.setup.ffmpeg;
|
||||
exports com.github.gtache.autosubtitle.setup.modules.ffmpeg;
|
||||
}
|
||||
Reference in New Issue
Block a user