diff --git a/.idea/scala_compiler.xml b/.idea/scala_compiler.xml
new file mode 100644
index 0000000..0717315
--- /dev/null
+++ b/.idea/scala_compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/sonarlint.xml b/.idea/sonarlint.xml
new file mode 100644
index 0000000..8b17604
--- /dev/null
+++ b/.idea/sonarlint.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/api/src/main/java/com/github/gtache/autosubtitle/VideoConverter.java b/api/src/main/java/com/github/gtache/autosubtitle/VideoConverter.java
index f8b8e7e..504804d 100644
--- a/api/src/main/java/com/github/gtache/autosubtitle/VideoConverter.java
+++ b/api/src/main/java/com/github/gtache/autosubtitle/VideoConverter.java
@@ -3,13 +3,18 @@ package com.github.gtache.autosubtitle;
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
import java.io.IOException;
+import java.nio.file.Path;
import java.util.Collection;
public interface VideoConverter {
Video addSoftSubtitles(final Video video, final Collection subtitles) throws IOException;
+ void addSoftSubtitles(final Video video, final Collection subtitles, final Path path) throws IOException;
+
Video addHardSubtitles(final Video video, final SubtitleCollection subtitles) throws IOException;
+ void addHardSubtitles(final Video video, final SubtitleCollection subtitles, final Path path) throws IOException;
+
Audio getAudio(final Video video) throws IOException;
}
diff --git a/api/src/main/java/com/github/gtache/autosubtitle/subtitle/Subtitle.java b/api/src/main/java/com/github/gtache/autosubtitle/subtitle/Subtitle.java
index 773f932..3cac88e 100644
--- a/api/src/main/java/com/github/gtache/autosubtitle/subtitle/Subtitle.java
+++ b/api/src/main/java/com/github/gtache/autosubtitle/subtitle/Subtitle.java
@@ -29,6 +29,16 @@ public interface Subtitle {
return Duration.ofMillis(end() - start());
}
+ /**
+ * Checks if the subtitle is shown at the given time
+ *
+ * @param time the time
+ * @return true if the subtitle is shown
+ */
+ default boolean isShowing(final long time) {
+ return time >= start() && time <= end();
+ }
+
/**
* @return the font of the subtitle
*/
diff --git a/cli/src/main/java/com/github/gtache/autosubtitle/modules/cli/CliComponent.java b/cli/src/main/java/com/github/gtache/autosubtitle/modules/cli/CliComponent.java
index ffccb48..9ca64da 100644
--- a/cli/src/main/java/com/github/gtache/autosubtitle/modules/cli/CliComponent.java
+++ b/cli/src/main/java/com/github/gtache/autosubtitle/modules/cli/CliComponent.java
@@ -4,14 +4,13 @@ import com.github.gtache.autosubtitle.modules.deepl.DeepLModule;
import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegModule;
import com.github.gtache.autosubtitle.modules.impl.CoreModule;
import com.github.gtache.autosubtitle.modules.subtitle.extractor.whisper.WhisperExtractorModule;
-import com.github.gtache.autosubtitle.modules.subtitle.impl.SubtitleModule;
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter;
import dagger.Component;
import javax.inject.Singleton;
import java.util.Map;
-@Component(modules = {SubtitleModule.class, CoreModule.class, DeepLModule.class, FFmpegModule.class, WhisperExtractorModule.class})
+@Component(modules = {CoreModule.class, DeepLModule.class, FFmpegModule.class, WhisperExtractorModule.class})
@Singleton
public interface CliComponent {
diff --git a/core/src/main/java/com/github/gtache/autosubtitle/impl/Architecture.java b/core/src/main/java/com/github/gtache/autosubtitle/impl/Architecture.java
index 8a26262..20c031b 100644
--- a/core/src/main/java/com/github/gtache/autosubtitle/impl/Architecture.java
+++ b/core/src/main/java/com/github/gtache/autosubtitle/impl/Architecture.java
@@ -11,7 +11,7 @@ public enum Architecture {
//64 bit
X86, X86_64, AMD64,
//ARM 32 bit
- ARM32, ARM, ARMV1, ARMV2, ARMV3, ARMV4, ARMV5, ARMV6, ARMV7, AARCH32,
+ ARM32, ARM, ARMV1, ARMV2, ARMV3, ARMV4, ARMV5, ARMV6, ARMV7, AARCH32, ARMHF, ARMEL,
//ARM 64 bit
ARM64, ARMV8, ARMV9, AARCH64,
UNKNOWN;
diff --git a/core/src/main/java/com/github/gtache/autosubtitle/impl/VideoInfoImpl.java b/core/src/main/java/com/github/gtache/autosubtitle/impl/VideoInfoImpl.java
index 051ef21..911faca 100644
--- a/core/src/main/java/com/github/gtache/autosubtitle/impl/VideoInfoImpl.java
+++ b/core/src/main/java/com/github/gtache/autosubtitle/impl/VideoInfoImpl.java
@@ -21,9 +21,4 @@ public record VideoInfoImpl(String videoFormat, int width, int height, long dura
throw new IllegalArgumentException("Duration must be greater than 0 : " + duration);
}
}
-
- @Override
- public String videoFormat() {
- return videoFormat;
- }
}
diff --git a/core/src/main/java/com/github/gtache/autosubtitle/modules/impl/CoreModule.java b/core/src/main/java/com/github/gtache/autosubtitle/modules/impl/CoreModule.java
index 96b6728..18768af 100644
--- a/core/src/main/java/com/github/gtache/autosubtitle/modules/impl/CoreModule.java
+++ b/core/src/main/java/com/github/gtache/autosubtitle/modules/impl/CoreModule.java
@@ -2,16 +2,22 @@ package com.github.gtache.autosubtitle.modules.impl;
import com.github.gtache.autosubtitle.impl.Architecture;
import com.github.gtache.autosubtitle.impl.OS;
+import com.github.gtache.autosubtitle.modules.setup.impl.SetupModule;
+import com.github.gtache.autosubtitle.modules.subtitle.impl.SubtitleModule;
import dagger.Module;
import dagger.Provides;
-import javax.inject.Singleton;
+/**
+ * Dagger module for Core
+ */
+@Module(includes = {SetupModule.class, SubtitleModule.class})
+public final class CoreModule {
-@Module
-public abstract class CoreModule {
+ private CoreModule() {
+
+ }
@Provides
- @Singleton
static OS providesOS() {
final var name = System.getProperty("os.name");
if (name.contains("Windows")) {
@@ -24,14 +30,12 @@ public abstract class CoreModule {
}
@Provides
- @Singleton
static Architecture providesArchitecture() {
final var arch = System.getProperty("os.arch");
return Architecture.getArchitecture(arch);
}
@Provides
- @Singleton
@ExecutableExtension
static String providesExecutableExtension(final OS os) {
return os == OS.WINDOWS ? ".exe" : "";
diff --git a/core/src/main/java/com/github/gtache/autosubtitle/modules/setup/impl/SetupModule.java b/core/src/main/java/com/github/gtache/autosubtitle/modules/setup/impl/SetupModule.java
new file mode 100644
index 0000000..0280791
--- /dev/null
+++ b/core/src/main/java/com/github/gtache/autosubtitle/modules/setup/impl/SetupModule.java
@@ -0,0 +1,21 @@
+package com.github.gtache.autosubtitle.modules.setup.impl;
+
+import dagger.Module;
+import dagger.Provides;
+
+import java.net.http.HttpClient;
+
+/**
+ * Dagger core module for setup
+ */
+@Module
+public final class SetupModule {
+
+ private SetupModule() {
+ }
+
+ @Provides
+ static HttpClient providesHttpClient() {
+ return HttpClient.newHttpClient();
+ }
+}
diff --git a/core/src/main/java/com/github/gtache/autosubtitle/modules/subtitle/impl/SubtitleModule.java b/core/src/main/java/com/github/gtache/autosubtitle/modules/subtitle/impl/SubtitleModule.java
index 7631560..fc04deb 100644
--- a/core/src/main/java/com/github/gtache/autosubtitle/modules/subtitle/impl/SubtitleModule.java
+++ b/core/src/main/java/com/github/gtache/autosubtitle/modules/subtitle/impl/SubtitleModule.java
@@ -11,10 +11,13 @@ import dagger.multibindings.StringKey;
* Dagger module for subtitles
*/
@Module
-public interface SubtitleModule {
+public abstract class SubtitleModule {
+
+ private SubtitleModule() {
+ }
@Binds
@IntoMap
@StringKey("srt")
- SubtitleConverter bindsSubtitleConverter(final SRTSubtitleConverter converter);
+ abstract SubtitleConverter bindsSubtitleConverter(final SRTSubtitleConverter converter);
}
diff --git a/core/src/main/java/com/github/gtache/autosubtitle/process/impl/AbstractProcessRunner.java b/core/src/main/java/com/github/gtache/autosubtitle/process/impl/AbstractProcessRunner.java
index fd5a461..7fa315f 100644
--- a/core/src/main/java/com/github/gtache/autosubtitle/process/impl/AbstractProcessRunner.java
+++ b/core/src/main/java/com/github/gtache/autosubtitle/process/impl/AbstractProcessRunner.java
@@ -36,6 +36,7 @@ public abstract class AbstractProcessRunner implements ProcessRunner {
@Override
public Process start(final List args) throws IOException {
+ logger.info("Running {}", args);
final var builder = new ProcessBuilder(args);
builder.redirectErrorStream(true);
return builder.start();
@@ -46,4 +47,22 @@ public abstract class AbstractProcessRunner implements ProcessRunner {
final var process = start(args);
return new ProcessListenerImpl(process);
}
+
+ /**
+ * Runs a process and writes the output to the log
+ *
+ * @param args the command
+ * @return the result
+ * @throws IOException if an error occurs
+ */
+ protected ProcessResult runListen(final List args) throws IOException {
+ final var listener = startListen(args);
+ var line = listener.readLine();
+ final var processName = args.getFirst();
+ while (line != null) {
+ logger.info("[{}]: {}", processName, line);
+ line = listener.readLine();
+ }
+ return listener.join(Duration.ofHours(1));
+ }
}
diff --git a/core/src/main/java/com/github/gtache/autosubtitle/process/impl/ProcessListenerImpl.java b/core/src/main/java/com/github/gtache/autosubtitle/process/impl/ProcessListenerImpl.java
index 976fd18..57d8f0d 100644
--- a/core/src/main/java/com/github/gtache/autosubtitle/process/impl/ProcessListenerImpl.java
+++ b/core/src/main/java/com/github/gtache/autosubtitle/process/impl/ProcessListenerImpl.java
@@ -3,7 +3,6 @@ package com.github.gtache.autosubtitle.process.impl;
import com.github.gtache.autosubtitle.process.ProcessListener;
import com.github.gtache.autosubtitle.process.ProcessResult;
-import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -30,7 +29,7 @@ public class ProcessListenerImpl implements ProcessListener {
*/
public ProcessListenerImpl(final Process process) {
this.process = Objects.requireNonNull(process);
- this.reader = new BufferedReader(new InputStreamReader(new BufferedInputStream(process.getInputStream()), StandardCharsets.UTF_8));
+ this.reader = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8));
this.output = new ArrayList<>();
}
diff --git a/core/src/main/java/com/github/gtache/autosubtitle/setup/impl/AbstractSetupManager.java b/core/src/main/java/com/github/gtache/autosubtitle/setup/impl/AbstractSetupManager.java
index 2c887a7..3d26e26 100644
--- a/core/src/main/java/com/github/gtache/autosubtitle/setup/impl/AbstractSetupManager.java
+++ b/core/src/main/java/com/github/gtache/autosubtitle/setup/impl/AbstractSetupManager.java
@@ -12,6 +12,10 @@ import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.io.UncheckedIOException;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Comparator;
@@ -19,6 +23,8 @@ import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
+import static java.util.Objects.requireNonNull;
+
/**
* Base class for all {@link SetupManager} implementations
*/
@@ -26,12 +32,23 @@ public abstract class AbstractSetupManager extends AbstractProcessRunner impleme
private static final Logger logger = LogManager.getLogger(AbstractSetupManager.class);
private final Set listeners;
+ private final HttpClient httpClient;
/**
- * Instantiates the manager
+ * Instantiates the manager with a default client
*/
protected AbstractSetupManager() {
+ this(HttpClient.newHttpClient());
+ }
+
+ /**
+ * Instantiates the manager with the given client
+ *
+ * @param httpClient The HTTP client to use
+ */
+ protected AbstractSetupManager(final HttpClient httpClient) {
this.listeners = new HashSet<>();
+ this.httpClient = requireNonNull(httpClient);
}
@Override
@@ -158,6 +175,30 @@ public abstract class AbstractSetupManager extends AbstractProcessRunner impleme
logger.info("{} deleted", path);
}
+ /**
+ * Downloads a file
+ *
+ * @param url The file url
+ * @param path The save path
+ * @throws SetupException If an error occurs
+ */
+ protected void download(final String url, final Path path) throws SetupException {
+ final var request = HttpRequest.newBuilder().uri(URI.create(url)).GET().build();
+ try {
+ final var result = httpClient.send(request, HttpResponse.BodyHandlers.ofFile(path));
+ if (result.statusCode() == 200) {
+ logger.info("{} download successful", path);
+ } else {
+ throw new SetupException("Error downloading " + path + ": " + result.body());
+ }
+ } catch (final IOException e) {
+ throw new SetupException(e);
+ } catch (final InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new SetupException(e);
+ }
+ }
+
private static long getFilesCount(final Path path) throws IOException {
try (final var stream = Files.walk(path)) {
return stream.count();
diff --git a/core/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/impl/SRTSubtitleConverter.java b/core/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/impl/SRTSubtitleConverter.java
index e9bebd5..90068ed 100644
--- a/core/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/impl/SRTSubtitleConverter.java
+++ b/core/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/impl/SRTSubtitleConverter.java
@@ -35,11 +35,21 @@ public class SRTSubtitleConverter implements SubtitleConverter {
return IntStream.range(0, subtitles.size()).mapToObj(i -> {
final var subtitle = subtitles.get(i);
return (i + 1) + "\n" +
- subtitle.start() + " --> " + subtitle.end() + "\n" +
+ formatTime(subtitle.start()) + " --> " + formatTime(subtitle.end()) + "\n" +
subtitle.content();
}).collect(Collectors.joining("\n\n"));
}
+ private static String formatTime(final long time) {
+ final var millisPerHour = 3600000;
+ final var millisPerMinute = 60000;
+ final var hours = time / millisPerHour;
+ final var minutes = (time - hours * millisPerHour) / millisPerMinute;
+ final var seconds = (time - hours * millisPerHour - minutes * millisPerMinute) / 1000;
+ final var millis = time - hours * millisPerHour - minutes * millisPerMinute - seconds * 1000;
+ return String.format("%02d:%02d:%02d,%03d", hours, minutes, seconds, millis);
+ }
+
@Override
public SubtitleCollection parse(final String content) throws ParseException {
try {
diff --git a/core/src/main/java/module-info.java b/core/src/main/java/module-info.java
index c05f919..d98ee39 100644
--- a/core/src/main/java/module-info.java
+++ b/core/src/main/java/module-info.java
@@ -4,6 +4,7 @@
module com.github.gtache.autosubtitle.core {
requires transitive com.github.gtache.autosubtitle.api;
requires transitive dagger;
+ requires transitive java.net.http;
requires transitive javax.inject;
requires org.apache.logging.log4j;
diff --git a/ffmpeg/pom.xml b/ffmpeg/pom.xml
index 008b641..1ae6047 100644
--- a/ffmpeg/pom.xml
+++ b/ffmpeg/pom.xml
@@ -16,6 +16,14 @@
com.github.gtache.autosubtitle
autosubtitle-core
+
+ org.apache.commons
+ commons-compress
+
+
+ org.tukaani
+ xz
+
\ No newline at end of file
diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/ffmpeg/FFmpegVideoConverter.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/ffmpeg/FFmpegVideoConverter.java
index 26a18b5..afebd92 100644
--- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/ffmpeg/FFmpegVideoConverter.java
+++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/ffmpeg/FFmpegVideoConverter.java
@@ -49,19 +49,24 @@ public class FFmpegVideoConverter extends AbstractProcessRunner implements Video
@Override
public Video addSoftSubtitles(final Video video, final Collection 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 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();
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;
}
diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFProbeInstallerPath.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFProbeInstallerPath.java
new file mode 100644
index 0000000..b9a6142
--- /dev/null
+++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFProbeInstallerPath.java
@@ -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 {
+}
diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegInstallerPath.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegInstallerPath.java
new file mode 100644
index 0000000..3e9d794
--- /dev/null
+++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegInstallerPath.java
@@ -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 {
+}
diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegModule.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegModule.java
index db82566..44bb4d6 100644
--- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegModule.java
+++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegModule.java
@@ -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);
}
diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFmpegSetupModule.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFmpegSetupModule.java
index c80d7cb..c617df7 100644
--- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFmpegSetupModule.java
+++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFmpegSetupModule.java
@@ -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";
diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/Decompresser.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/Decompresser.java
new file mode 100644
index 0000000..d93f9db
--- /dev/null
+++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/Decompresser.java
@@ -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);
+}
diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/FFmpegSetupConfiguration.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/FFmpegSetupConfiguration.java
new file mode 100644
index 0000000..49830ef
--- /dev/null
+++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/FFmpegSetupConfiguration.java
@@ -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);
+ }
+}
diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/FFmpegSetupManager.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/FFmpegSetupManager.java
index 4df0850..81a6af2 100644
--- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/FFmpegSetupManager.java
+++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/FFmpegSetupManager.java
@@ -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 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 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());
}
}
diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/TarDecompresser.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/TarDecompresser.java
new file mode 100644
index 0000000..a86568d
--- /dev/null
+++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/TarDecompresser.java
@@ -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");
+ }
+}
diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/XZDecompresser.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/XZDecompresser.java
new file mode 100644
index 0000000..81b4e77
--- /dev/null
+++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/XZDecompresser.java
@@ -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");
+ }
+}
diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/ZipDecompresser.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/ZipDecompresser.java
new file mode 100644
index 0000000..931d9e0
--- /dev/null
+++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/ZipDecompresser.java
@@ -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");
+ }
+}
diff --git a/ffmpeg/src/main/java/module-info.java b/ffmpeg/src/main/java/module-info.java
index 0295b4d..59d58fa 100644
--- a/ffmpeg/src/main/java/module-info.java
+++ b/ffmpeg/src/main/java/module-info.java
@@ -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;
}
\ No newline at end of file
diff --git a/gui/core/src/main/java/com/github/gtache/autosubtitle/modules/gui/impl/GuiCoreModule.java b/gui/core/src/main/java/com/github/gtache/autosubtitle/modules/gui/impl/GuiCoreModule.java
index 8845a2d..dff92ff 100644
--- a/gui/core/src/main/java/com/github/gtache/autosubtitle/modules/gui/impl/GuiCoreModule.java
+++ b/gui/core/src/main/java/com/github/gtache/autosubtitle/modules/gui/impl/GuiCoreModule.java
@@ -4,7 +4,6 @@ import com.github.gtache.autosubtitle.gui.impl.CombinedResourceBundle;
import dagger.Module;
import dagger.Provides;
-import javax.inject.Singleton;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.ResourceBundle;
@@ -13,9 +12,12 @@ import java.util.ResourceBundle;
* Dagger module for GUI
*/
@Module
-public class GuiCoreModule {
+public final class GuiCoreModule {
+
+ private GuiCoreModule() {
+ }
+
@Provides
- @Singleton
static ResourceBundle providesBundle() {
return new CombinedResourceBundle(ResourceBundle.getBundle("com.github.gtache.autosubtitle.gui.impl.MainBundle"),
ResourceBundle.getBundle("com.github.gtache.autosubtitle.gui.impl.SetupBundle"),
@@ -25,7 +27,6 @@ public class GuiCoreModule {
}
@Provides
- @Singleton
@Play
static byte[] providesPlayImage() {
try (final var in = GuiCoreModule.class.getResourceAsStream("/com/github/gtache/autosubtitle/gui/impl/play_64.png")) {
@@ -36,7 +37,6 @@ public class GuiCoreModule {
}
@Provides
- @Singleton
@Pause
static byte[] providesPauseImage() {
try (final var in = GuiCoreModule.class.getResourceAsStream("/com/github/gtache/autosubtitle/gui/impl/pause_64.png")) {
@@ -47,14 +47,12 @@ public class GuiCoreModule {
}
@Provides
- @Singleton
@FontFamily
static String providesFontFamily() {
return "Arial";
}
@Provides
- @Singleton
@FontSize
static int providesFontSize() {
return 12;
diff --git a/gui/fx/pom.xml b/gui/fx/pom.xml
index 05a4ad7..40ebd2e 100644
--- a/gui/fx/pom.xml
+++ b/gui/fx/pom.xml
@@ -9,7 +9,7 @@
1.0-SNAPSHOT
- autosubtitle-fx
+ autosubtitle-gui-fx
11.2.1
diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMediaController.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMediaController.java
index 135a8b2..07b8b39 100644
--- a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMediaController.java
+++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMediaController.java
@@ -1,14 +1,16 @@
package com.github.gtache.autosubtitle.gui.fx;
+import com.github.gtache.autosubtitle.File;
import com.github.gtache.autosubtitle.gui.MediaController;
import com.github.gtache.autosubtitle.gui.TimeFormatter;
-import com.github.gtache.autosubtitle.impl.FileVideoImpl;
import com.github.gtache.autosubtitle.modules.gui.impl.Pause;
import com.github.gtache.autosubtitle.modules.gui.impl.Play;
+import com.github.gtache.autosubtitle.subtitle.EditableSubtitle;
import com.github.gtache.autosubtitle.subtitle.Subtitle;
import com.github.gtache.autosubtitle.subtitle.gui.fx.SubtitleLabel;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
+import javafx.collections.ListChangeListener;
import javafx.fxml.FXML;
import javafx.geometry.Insets;
import javafx.scene.Cursor;
@@ -31,6 +33,11 @@ import org.apache.logging.log4j.Logger;
import javax.inject.Inject;
import javax.inject.Singleton;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
import static java.util.Objects.requireNonNull;
@@ -62,6 +69,8 @@ public class FXMediaController implements MediaController {
private final Image playImage;
private final Image pauseImage;
+ private final List startTimes;
+
private boolean wasPlaying;
@Inject
@@ -72,6 +81,7 @@ public class FXMediaController implements MediaController {
this.timeFormatter = requireNonNull(timeFormatter);
this.playImage = requireNonNull(playImage);
this.pauseImage = requireNonNull(pauseImage);
+ this.startTimes = new ArrayList<>();
}
@FXML
@@ -101,50 +111,78 @@ public class FXMediaController implements MediaController {
if (videoView.getMediaPlayer() != null) {
videoView.getMediaPlayer().dispose();
}
- if (newValue instanceof final FileVideoImpl fileVideo) {
- final var media = new Media(fileVideo.path().toUri().toString());
- final var player = new MediaPlayer(media);
- player.currentTimeProperty().addListener((ignored, oldTime, newTime) -> {
- final var millis = newTime.toMillis();
- playSlider.setValue(millis);
- stackPane.getChildren().removeIf(Label.class::isInstance);
- model.subtitles().forEach(s -> {
- //TODO optimize using e.g. direction of playback
- if (s.start() <= millis && s.end() >= millis) {
- logger.info("Adding label {} at {}", s, millis);
- final var label = createDraggableLabel(s);
- stackPane.getChildren().add(label);
- }
- });
- });
- playSlider.setOnMousePressed(e -> {
- wasPlaying = model.isPlaying();
- model.setIsPlaying(false);
- });
- playSlider.valueProperty().addListener(observable1 -> {
- if (playSlider.isValueChanging()) {
- seek((long) playSlider.getValue());
- }
- });
- playSlider.setOnMouseReleased(e -> {
- final var value = playSlider.getValue();
- Platform.runLater(() -> {
- seek((long) value);
- model.setIsPlaying(wasPlaying);
- });
- });
- player.volumeProperty().bindBidirectional(model.volumeProperty());
- player.setOnPlaying(() -> model.setIsPlaying(true));
- player.setOnPaused(() -> model.setIsPlaying(false));
- player.setOnEndOfMedia(() -> model.setIsPlaying(false));
- playSlider.setMax(model.duration());
- playSlider.setValue(0L);
- videoView.setMediaPlayer(player);
+ if (newValue instanceof final File file) {
+ loadFileVideo(file.path());
} else {
logger.error("Unsupported video type : {}", newValue);
}
});
+ model.subtitles().addListener((ListChangeListener) c -> {
+ startTimes.clear();
+ model.subtitles().stream().mapToLong(Subtitle::start).forEach(startTimes::add);
+ });
+ bindPlayButton();
+ binder.createBindings();
+ }
+
+ private void loadFileVideo(final Path fileVideoPath) {
+ final var media = new Media(fileVideoPath.toUri().toString());
+ final var player = new MediaPlayer(media);
+ player.statusProperty().addListener((observable12, oldValue1, newValue1) ->
+ logger.info("New status: {}", newValue1));
+ player.currentTimeProperty().addListener((ignored, oldTime, newTime) -> currentTimeChanged(oldTime.toMillis(), newTime.toMillis()));
+ playSlider.setOnMousePressed(e -> {
+ wasPlaying = model.isPlaying();
+ model.setIsPlaying(false);
+ });
+ playSlider.valueProperty().addListener(observable1 -> {
+ if (playSlider.isValueChanging()) {
+ seek((long) playSlider.getValue());
+ }
+ });
+ playSlider.setOnMouseReleased(e -> {
+ final var value = playSlider.getValue();
+ Platform.runLater(() -> {
+ seek((long) value);
+ model.setIsPlaying(wasPlaying);
+ });
+ });
+ player.volumeProperty().bindBidirectional(model.volumeProperty());
+ player.setOnPlaying(() -> model.setIsPlaying(true));
+ player.setOnPaused(() -> model.setIsPlaying(false));
+ player.setOnEndOfMedia(() -> model.setIsPlaying(false));
+ playSlider.setMax(model.duration());
+ playSlider.setValue(0L);
+ videoView.setMediaPlayer(player);
+ }
+
+ private void currentTimeChanged(final double oldMillis, final double millis) {
+ playSlider.setValue(millis);
+ final var subtitleLabels = stackPane.getChildren().stream().filter(SubtitleLabel.class::isInstance).map(SubtitleLabel.class::cast).toList();
+ subtitleLabels.stream().filter(s -> !s.subtitle().isShowing((long) millis)).forEach(sl -> stackPane.getChildren().remove(sl));
+ final var containedSubtitles = subtitleLabels.stream().map(SubtitleLabel::subtitle).filter(s -> s.isShowing((long) millis)).collect(Collectors.toSet());
+
+ model.subtitles().forEach(s -> {
+ if (!containedSubtitles.contains(s)) {
+ logger.info("Adding label {} at {}", s, millis);
+ final var label = createDraggableLabel(s);
+ stackPane.getChildren().add(label);
+ }
+ });
+ }
+
+ private void currentTimeChangedOptimized(final double oldMillis, final double millis) {
+ final var forward = oldMillis <= millis;
+
+ var index = Collections.binarySearch(startTimes, (long) millis);
+ if (index < 0) {
+ index = forward ? -(index + 1) : -(index + 2);
+ }
+ //TODO
+ }
+
+ private void bindPlayButton() {
playButton.disableProperty().bind(model.videoProperty().isNull());
playButton.graphicProperty().bind(Bindings.createObjectBinding(() -> {
final ImageView view;
@@ -158,7 +196,6 @@ public class FXMediaController implements MediaController {
view.setFitHeight(24);
return view;
}, model.isPlayingProperty()));
- binder.createBindings();
}
@FXML
diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMediaModel.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMediaModel.java
index 74c7498..385cbba 100644
--- a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMediaModel.java
+++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMediaModel.java
@@ -6,10 +6,10 @@ import com.github.gtache.autosubtitle.subtitle.EditableSubtitle;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
import javax.inject.Inject;
import javax.inject.Singleton;
-import java.util.List;
/**
* FX implementation of {@link com.github.gtache.autosubtitle.gui.MediaModel}
@@ -22,7 +22,7 @@ public class FXMediaModel implements MediaModel {
private final BooleanProperty isPlaying;
private final ReadOnlyLongWrapper duration;
private final LongProperty position;
- private final List subtitles;
+ private final ObservableList subtitles;
@Inject
FXMediaModel() {
@@ -103,7 +103,7 @@ public class FXMediaModel implements MediaModel {
}
@Override
- public List subtitles() {
+ public ObservableList subtitles() {
return subtitles;
}
diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXWorkController.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXWorkController.java
index eb796cb..f6fc39b 100644
--- a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXWorkController.java
+++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXWorkController.java
@@ -59,6 +59,7 @@ public class FXWorkController extends AbstractFXController implements WorkContro
private static final Logger logger = LogManager.getLogger(FXWorkController.class);
+ private static final String ALL_SUPPORTED = "All supported";
private static final List VIDEO_EXTENSIONS = Stream.of("webm", "mkv", "flv", "vob", "ogv", "ogg",
"drc", "gif", "gifv", "mng", "avi", "mts", "m2ts", "ts", "mov", "qt", "wmv", "yuv", "rm", "rmvb",
"viv", "asf", "amv", "mp4", "m4p", "m4v", "mpg", "mp2", "mpeg", "mpe", "mpv", "m2v", "m4v", "svi",
@@ -131,21 +132,42 @@ public class FXWorkController extends AbstractFXController implements WorkContro
@FXML
private void initialize() {
+ bindComboboxes();
+ bindButtons();
+ bindTable();
+ bindProgress();
+
+ model.selectedSubtitleProperty().addListener((observable, oldValue, newValue) -> {
+ if (newValue != null) {
+ mediaController.seek(newValue.start());
+ }
+ });
+
+ binder.createBindings();
+
+ subtitleExtractor.addListener(this);
+ }
+
+ private void bindComboboxes() {
languageCombobox.valueProperty().bindBidirectional(model.videoLanguageProperty());
languageCombobox.setItems(model.availableVideoLanguages());
languageCombobox.setConverter(new LanguageStringConverter());
translationsCombobox.setConverter(new LanguageStringConverter());
Bindings.bindContent(translationsCombobox.getItems(), model.availableTranslationsLanguage());
Bindings.bindContent(model.translations(), translationsCombobox.getCheckModel().getCheckedItems());
+ }
+ private void bindButtons() {
extractButton.disableProperty().bind(model.videoProperty().isNull().or(model.statusProperty().isNotEqualTo(WorkStatus.IDLE)));
+ addSubtitleButton.disableProperty().bind(model.videoProperty().isNull().or(model.statusProperty().isNotEqualTo(WorkStatus.IDLE)));
loadSubtitlesButton.disableProperty().bind(model.videoProperty().isNull().or(model.statusProperty().isNotEqualTo(WorkStatus.IDLE)));
resetButton.disableProperty().bind(Bindings.isEmpty(model.subtitles()).or(model.statusProperty().isNotEqualTo(WorkStatus.IDLE)));
exportSoftButton.disableProperty().bind(Bindings.isEmpty(model.subtitles()).or(model.statusProperty().isNotEqualTo(WorkStatus.IDLE)));
exportHardButton.disableProperty().bind(Bindings.isEmpty(model.subtitles()).or(model.statusProperty().isNotEqualTo(WorkStatus.IDLE)));
- addSubtitleButton.disableProperty().bind(Bindings.isEmpty(model.subtitles()).or(model.statusProperty().isNotEqualTo(WorkStatus.IDLE)));
- loadSubtitlesButton.disableProperty().bind(Bindings.isEmpty(model.subtitles()).or(model.statusProperty().isNotEqualTo(WorkStatus.IDLE)));
+ saveSubtitlesButton.disableProperty().bind(Bindings.isEmpty(model.subtitles()).or(model.statusProperty().isNotEqualTo(WorkStatus.IDLE)));
+ }
+ private void bindTable() {
subtitlesTable.setItems(model.subtitles());
subtitlesTable.setOnKeyPressed(e -> {
if (e.getCode().isLetterKey() || e.getCode().isDigitKey()) {
@@ -157,6 +179,9 @@ public class FXWorkController extends AbstractFXController implements WorkContro
} else if (e.getCode() == KeyCode.LEFT) {
subtitlesTable.getSelectionModel().selectPrevious();
e.consume();
+ } else if (e.getCode() == KeyCode.DELETE) {
+ deleteSelectedSubtitles();
+ e.consume();
}
});
startColumn.setCellFactory(TextFieldTableCell.forTableColumn(new TimeStringConverter(timeFormatter)));
@@ -185,11 +210,9 @@ public class FXWorkController extends AbstractFXController implements WorkContro
});
subtitlesTable.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> model.selectedSubtitleProperty().set(newValue));
- model.selectedSubtitleProperty().addListener((observable, oldValue, newValue) -> {
- if (newValue != null) {
- mediaController.seek(newValue.start());
- }
- });
+ }
+
+ private void bindProgress() {
progressLabel.textProperty().bind(Bindings.createStringBinding(() -> resources.getString("work.status." + model.status().name().toLowerCase() + ".label"), model.statusProperty()));
progressLabel.visibleProperty().bind(model.statusProperty().isNotEqualTo(WorkStatus.IDLE));
progressBar.visibleProperty().bind(model.statusProperty().isNotEqualTo(WorkStatus.IDLE));
@@ -198,9 +221,10 @@ public class FXWorkController extends AbstractFXController implements WorkContro
progressBar.managedProperty().bind(model.statusProperty().isNotEqualTo(WorkStatus.IDLE));
progressDetailLabel.managedProperty().bind(model.statusProperty().isNotEqualTo(WorkStatus.IDLE));
progressBar.progressProperty().bindBidirectional(model.progressProperty());
- binder.createBindings();
+ }
- subtitleExtractor.addListener(this);
+ private void deleteSelectedSubtitles() {
+ model.subtitles().removeAll(subtitlesTable.getSelectionModel().getSelectedItems());
}
private void editFocusedCell() {
@@ -213,7 +237,7 @@ public class FXWorkController extends AbstractFXController implements WorkContro
@FXML
private void fileButtonPressed() {
final var filePicker = new FileChooser();
- final var extensionFilter = new FileChooser.ExtensionFilter("All supported", VIDEO_EXTENSIONS);
+ final var extensionFilter = new FileChooser.ExtensionFilter(ALL_SUPPORTED, VIDEO_EXTENSIONS);
filePicker.getExtensionFilters().add(extensionFilter);
filePicker.setSelectedExtensionFilter(extensionFilter);
final var file = filePicker.showOpenDialog(window());
@@ -303,6 +327,9 @@ public class FXWorkController extends AbstractFXController implements WorkContro
@FXML
private void exportSoftPressed() {
final var filePicker = new FileChooser();
+ final var extensionFilter = new FileChooser.ExtensionFilter(ALL_SUPPORTED, VIDEO_EXTENSIONS);
+ filePicker.getExtensionFilters().add(extensionFilter);
+ filePicker.setSelectedExtensionFilter(extensionFilter);
final var file = filePicker.showSaveDialog(window());
if (file != null) {
final var baseCollection = model.subtitleCollection();
@@ -315,7 +342,7 @@ public class FXWorkController extends AbstractFXController implements WorkContro
}, Platform::runLater)
.thenAcceptAsync(collections -> {
try {
- videoConverter.addSoftSubtitles(model.video(), collections);
+ videoConverter.addSoftSubtitles(model.video(), collections, file.toPath());
} catch (final IOException e) {
throw new CompletionException(e);
}
@@ -332,11 +359,14 @@ public class FXWorkController extends AbstractFXController implements WorkContro
@FXML
private void exportHardPressed() {
final var filePicker = new FileChooser();
+ final var extensionFilter = new FileChooser.ExtensionFilter(ALL_SUPPORTED, VIDEO_EXTENSIONS);
+ filePicker.getExtensionFilters().add(extensionFilter);
+ filePicker.setSelectedExtensionFilter(extensionFilter);
final var file = filePicker.showSaveDialog(window());
if (file != null) {
CompletableFuture.runAsync(() -> {
try {
- videoConverter.addHardSubtitles(model.video(), model.subtitleCollection());
+ videoConverter.addHardSubtitles(model.video(), model.subtitleCollection(), file.toPath());
} catch (final IOException e) {
throw new CompletionException(e);
}
@@ -386,7 +416,7 @@ public class FXWorkController extends AbstractFXController implements WorkContro
@FXML
private void loadSubtitlesPressed() {
final var filePicker = new FileChooser();
- final var extensionFilter = new FileChooser.ExtensionFilter("All supported", subtitleExtensions);
+ final var extensionFilter = new FileChooser.ExtensionFilter(ALL_SUPPORTED, subtitleExtensions);
filePicker.getExtensionFilters().add(extensionFilter);
filePicker.setSelectedExtensionFilter(extensionFilter);
final var file = filePicker.showOpenDialog(window());
@@ -396,7 +426,7 @@ public class FXWorkController extends AbstractFXController implements WorkContro
@FXML
private void saveSubtitlesPressed() {
final var filePicker = new FileChooser();
- final var extensionFilter = new FileChooser.ExtensionFilter("All supported", subtitleExtensions);
+ final var extensionFilter = new FileChooser.ExtensionFilter(ALL_SUPPORTED, subtitleExtensions);
filePicker.getExtensionFilters().add(extensionFilter);
filePicker.setSelectedExtensionFilter(extensionFilter);
final var file = filePicker.showSaveDialog(window());
diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXWorkModel.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXWorkModel.java
index 840355c..f9adc79 100644
--- a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXWorkModel.java
+++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXWorkModel.java
@@ -75,12 +75,11 @@ public class FXWorkModel implements WorkModel {
this.extractionModel = new SimpleObjectProperty<>();
this.progress = new SimpleDoubleProperty(-1);
text.bind(Bindings.createStringBinding(() ->
- subtitles.stream().map(EditableSubtitle::content).collect(Collectors.joining(" ")),
+ subtitles.stream().map(EditableSubtitle::content).collect(Collectors.joining("")),
subtitles));
- subtitleCollection.bind(Bindings.createObjectBinding(() -> new SubtitleCollectionImpl(text(), subtitles, videoLanguage())));
- videoLanguage.addListener((observable, oldValue, newValue) -> {
- FXCollections.observableArrayList(Arrays.stream(Language.values()).filter(l -> l != Language.AUTO && l != newValue).sorted(Comparator.naturalOrder()).toList());
- });
+ subtitleCollection.bind(Bindings.createObjectBinding(() -> new SubtitleCollectionImpl(text(), subtitles, videoLanguage()), text, subtitles, videoLanguage));
+ videoLanguage.addListener((observable, oldValue, newValue) -> FXCollections.observableArrayList(Arrays.stream(Language.values())
+ .filter(l -> l != Language.AUTO && l != newValue).sorted(Comparator.naturalOrder()).toList()));
}
@Override
diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/modules/gui/fx/FXModule.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/modules/gui/fx/FXModule.java
index 5027b9c..7897f1c 100644
--- a/gui/fx/src/main/java/com/github/gtache/autosubtitle/modules/gui/fx/FXModule.java
+++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/modules/gui/fx/FXModule.java
@@ -54,14 +54,12 @@ public abstract class FXModule {
}
@Provides
- @Singleton
@Play
static Image providesPlayImage(@Play final byte[] playImage) {
return new Image(new ByteArrayInputStream(playImage));
}
@Provides
- @Singleton
@Pause
static Image providesPauseImage(@Pause final byte[] pauseImage) {
return new Image(new ByteArrayInputStream(pauseImage));
diff --git a/gui/fx/src/main/java/module-info.java b/gui/fx/src/main/java/module-info.java
index 8d5bf11..9706af9 100644
--- a/gui/fx/src/main/java/module-info.java
+++ b/gui/fx/src/main/java/module-info.java
@@ -1,7 +1,7 @@
/**
* FX module for auto-subtitle
*/
-module com.github.gtache.autosubtitle.fx {
+module com.github.gtache.autosubtitle.gui.fx {
requires transitive com.github.gtache.autosubtitle.core;
requires transitive com.github.gtache.autosubtitle.gui.core;
requires transitive javafx.controls;
diff --git a/gui/fx/src/main/resources/com/github/gtache/autosubtitle/gui/fx/workView.fxml b/gui/fx/src/main/resources/com/github/gtache/autosubtitle/gui/fx/workView.fxml
index e6497a8..c018305 100644
--- a/gui/fx/src/main/resources/com/github/gtache/autosubtitle/gui/fx/workView.fxml
+++ b/gui/fx/src/main/resources/com/github/gtache/autosubtitle/gui/fx/workView.fxml
@@ -1,93 +1,93 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
-
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
-
-
-
-
+
+
+
-
+
-
-
-
+
+
+
+
-
+
-
+
-
-
-
-
+
+
+
+
-
+
-
-
+
+
-
+
diff --git a/gui/pom.xml b/gui/pom.xml
index 96910ff..f639874 100644
--- a/gui/pom.xml
+++ b/gui/pom.xml
@@ -32,7 +32,7 @@
com.github.gtache.autosubtitle
- autosubtitle-fx
+ autosubtitle-gui-fx
${project.version}
diff --git a/gui/run/pom.xml b/gui/run/pom.xml
index a84ce2e..8e9cddd 100644
--- a/gui/run/pom.xml
+++ b/gui/run/pom.xml
@@ -18,7 +18,7 @@
com.github.gtache.autosubtitle
- autosubtitle-fx
+ autosubtitle-gui-fx
com.github.gtache.autosubtitle
diff --git a/gui/run/src/main/java/com/github/gtache/autosubtitle/modules/run/MissingComponentsModule.java b/gui/run/src/main/java/com/github/gtache/autosubtitle/modules/run/MissingComponentsModule.java
index 72bcb7e..ec59bc2 100644
--- a/gui/run/src/main/java/com/github/gtache/autosubtitle/modules/run/MissingComponentsModule.java
+++ b/gui/run/src/main/java/com/github/gtache/autosubtitle/modules/run/MissingComponentsModule.java
@@ -17,6 +17,10 @@ import javax.inject.Singleton;
@Module
public abstract class MissingComponentsModule {
+ private MissingComponentsModule() {
+
+ }
+
@Provides
@Singleton
static Translator providesTranslator() {
diff --git a/gui/run/src/main/java/com/github/gtache/autosubtitle/modules/run/RunComponent.java b/gui/run/src/main/java/com/github/gtache/autosubtitle/modules/run/RunComponent.java
index 945e185..e81ceb3 100644
--- a/gui/run/src/main/java/com/github/gtache/autosubtitle/modules/run/RunComponent.java
+++ b/gui/run/src/main/java/com/github/gtache/autosubtitle/modules/run/RunComponent.java
@@ -4,9 +4,6 @@ import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegModule;
import com.github.gtache.autosubtitle.modules.gui.fx.FXModule;
import com.github.gtache.autosubtitle.modules.gui.impl.GuiCoreModule;
import com.github.gtache.autosubtitle.modules.impl.CoreModule;
-import com.github.gtache.autosubtitle.modules.setup.ffmpeg.FFmpegSetupModule;
-import com.github.gtache.autosubtitle.modules.setup.whisper.WhisperSetupModule;
-import com.github.gtache.autosubtitle.modules.subtitle.impl.SubtitleModule;
import com.github.gtache.autosubtitle.modules.whisper.WhisperModule;
import dagger.Component;
import javafx.fxml.FXMLLoader;
@@ -17,8 +14,8 @@ import javax.inject.Singleton;
* Main component
*/
@Singleton
-@Component(modules = {CoreModule.class, GuiCoreModule.class, FXModule.class, FFmpegModule.class, FFmpegSetupModule.class,
- SubtitleModule.class, WhisperModule.class, WhisperSetupModule.class, MissingComponentsModule.class})
+@Component(modules = {CoreModule.class, GuiCoreModule.class, FXModule.class, FFmpegModule.class,
+ WhisperModule.class, MissingComponentsModule.class})
public interface RunComponent {
/**
diff --git a/gui/run/src/main/java/module-info.java b/gui/run/src/main/java/module-info.java
index b335f98..e75f2a6 100644
--- a/gui/run/src/main/java/module-info.java
+++ b/gui/run/src/main/java/module-info.java
@@ -3,7 +3,7 @@
*/
module com.github.gtache.autosubtitle.run {
requires com.github.gtache.autosubtitle.ffmpeg;
- requires com.github.gtache.autosubtitle.fx;
+ requires com.github.gtache.autosubtitle.gui.fx;
requires com.github.gtache.autosubtitle.whisper;
opens com.github.gtache.autosubtitle.run to javafx.graphics;
diff --git a/pom.xml b/pom.xml
index 8873f13..bf8c3ae 100644
--- a/pom.xml
+++ b/pom.xml
@@ -25,9 +25,11 @@
21
UTF-8
+ 1.27.0
2.51.1
2.23.1
4.7.6
+ 1.10
@@ -82,6 +84,11 @@
dagger
${dagger.version}
+
+ info.picocli
+ picocli
+ ${picocli.version}
+
org.apache.logging.log4j
log4j-api
@@ -93,9 +100,14 @@
${log4j.version}
- info.picocli
- picocli
- ${picocli.version}
+ org.apache.commons
+ commons-compress
+ ${commons.compress.version}
+
+
+ org.tukaani
+ xz
+ ${xz.version}
diff --git a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaSetupModule.java b/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaSetupModule.java
index 7fbca51..e92a78e 100644
--- a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaSetupModule.java
+++ b/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaSetupModule.java
@@ -1,11 +1,11 @@
package com.github.gtache.autosubtitle.modules.setup.whisper;
+import com.github.gtache.autosubtitle.impl.Architecture;
import com.github.gtache.autosubtitle.impl.OS;
+import com.github.gtache.autosubtitle.setup.whisper.CondaSetupConfiguration;
import dagger.Module;
import dagger.Provides;
-import javax.inject.Singleton;
-import java.net.http.HttpClient;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -13,54 +13,54 @@ import java.nio.file.Paths;
* Setup module for Conda
*/
@Module
-public abstract class CondaSetupModule {
+public final class CondaSetupModule {
private static final String CONDA = "conda";
private static final String MINICONDA3 = "miniconda3";
- @Provides
- @Singleton
- static HttpClient providesHttpClient() {
- return HttpClient.newHttpClient();
+ private CondaSetupModule() {
+
+ }
+
+ @Provides
+ static CondaSetupConfiguration providesCondaSetupConfiguration(@CondaSystemPath final Path condaSystemPath, @CondaBundledPath final Path condaBundledPath,
+ @CondaMinimumMajorVersion final int condaMinimumMajorVersion, @CondaMinimumMinorVersion final int condaMinimumMinorVersion,
+ @CondaInstallerPath final Path condaInstallerPath, @CondaRootPath final Path condaRootPath,
+ final OS os, final Architecture architecture) {
+ return new CondaSetupConfiguration(condaRootPath, condaSystemPath, condaBundledPath, condaMinimumMajorVersion, condaMinimumMinorVersion, condaInstallerPath, os, architecture);
}
@Provides
- @Singleton
@CondaSystemPath
static Path providesCondaSystemPath(final OS os) {
return Paths.get(os == OS.WINDOWS ? CONDA + ".bat" : CONDA);
}
@Provides
- @Singleton
@CondaBundledPath
static Path providesCondaBundledPath(@CondaRootPath final Path root, final OS os) {
return root.resolve("condabin").resolve(Paths.get(os == OS.WINDOWS ? CONDA + ".bat" : CONDA));
}
@Provides
- @Singleton
@CondaMinimumMajorVersion
static int providesCondaMinimumMajorVersion() {
return 24;
}
@Provides
- @Singleton
@CondaMinimumMinorVersion
static int providesCondaMinimumMinorVersion() {
return 5;
}
@Provides
- @Singleton
@CondaRootPath
static Path providesCondaRootPath(@WhisperBundledRoot final Path root, final OS os) {
return root.resolve(MINICONDA3);
}
@Provides
- @Singleton
@CondaInstallerPath
static Path providesCondaInstallerPath(@WhisperBundledRoot final Path root, final OS os) {
return root.resolve("cache").resolve("conda-install" + (os == OS.WINDOWS ? ".exe" : ".sh"));
diff --git a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/WhisperSetupModule.java b/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/WhisperSetupModule.java
index a513ff5..ad04c43 100644
--- a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/WhisperSetupModule.java
+++ b/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/WhisperSetupModule.java
@@ -7,7 +7,6 @@ import dagger.Binds;
import dagger.Module;
import dagger.Provides;
-import javax.inject.Singleton;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -21,28 +20,24 @@ public abstract class WhisperSetupModule {
abstract SetupManager bindsSubtitleExtractorSetupManager(final WhisperSetupManager manager);
@Provides
- @Singleton
@PythonVersion
static String providesPythonVersion() {
return "3.9.19";
}
@Provides
- @Singleton
@WhisperVersion
static String providesWhisperVersion() {
return "20231117";
}
@Provides
- @Singleton
@WhisperBundledRoot
static Path providesWhisperBundledRoot() {
return Paths.get("tools", "whisper");
}
@Provides
- @Singleton
@WhisperVenvPath
static Path providesWhisperVenvPath(@WhisperBundledRoot final Path root) {
return root.resolve("whisper-env");
diff --git a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/subtitle/extractor/whisper/WhisperExtractorModule.java b/whisper/src/main/java/com/github/gtache/autosubtitle/modules/subtitle/extractor/whisper/WhisperExtractorModule.java
index 3964457..abbb077 100644
--- a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/subtitle/extractor/whisper/WhisperExtractorModule.java
+++ b/whisper/src/main/java/com/github/gtache/autosubtitle/modules/subtitle/extractor/whisper/WhisperExtractorModule.java
@@ -11,6 +11,10 @@ import dagger.Module;
@Module
public abstract class WhisperExtractorModule {
+ private WhisperExtractorModule() {
+
+ }
+
@Binds
abstract SubtitleExtractor bindsSubtitleExtractor(final WhisperSubtitleExtractor extractor);
}
diff --git a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/subtitle/parser/json/whisper/WhisperJsonModule.java b/whisper/src/main/java/com/github/gtache/autosubtitle/modules/subtitle/parser/json/whisper/WhisperJsonModule.java
index 500f337..1c694d0 100644
--- a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/subtitle/parser/json/whisper/WhisperJsonModule.java
+++ b/whisper/src/main/java/com/github/gtache/autosubtitle/modules/subtitle/parser/json/whisper/WhisperJsonModule.java
@@ -14,6 +14,10 @@ import javax.inject.Singleton;
@Module
public abstract class WhisperJsonModule {
+ private WhisperJsonModule() {
+
+ }
+
@Binds
@IntoMap
@StringKey("json")
diff --git a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/whisper/WhisperModule.java b/whisper/src/main/java/com/github/gtache/autosubtitle/modules/whisper/WhisperModule.java
index 5fda442..1ec8a82 100644
--- a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/whisper/WhisperModule.java
+++ b/whisper/src/main/java/com/github/gtache/autosubtitle/modules/whisper/WhisperModule.java
@@ -1,5 +1,6 @@
package com.github.gtache.autosubtitle.modules.whisper;
+import com.github.gtache.autosubtitle.modules.setup.whisper.WhisperSetupModule;
import com.github.gtache.autosubtitle.modules.subtitle.extractor.whisper.WhisperExtractorModule;
import com.github.gtache.autosubtitle.modules.subtitle.parser.json.whisper.WhisperJsonModule;
import com.github.gtache.autosubtitle.subtitle.extractor.ExtractionModelProvider;
@@ -10,9 +11,13 @@ import dagger.Module;
/**
* Dagger module for Whisper
*/
-@Module(includes = {WhisperJsonModule.class, WhisperExtractorModule.class})
+@Module(includes = {WhisperSetupModule.class, WhisperJsonModule.class, WhisperExtractorModule.class})
public abstract class WhisperModule {
+ private WhisperModule() {
+
+ }
+
@Binds
abstract ExtractionModelProvider bindsExtractionModelProvider(final WhisperExtractionModelProvider provider);
}
diff --git a/whisper/src/main/java/com/github/gtache/autosubtitle/setup/whisper/CondaSetupConfiguration.java b/whisper/src/main/java/com/github/gtache/autosubtitle/setup/whisper/CondaSetupConfiguration.java
new file mode 100644
index 0000000..6134033
--- /dev/null
+++ b/whisper/src/main/java/com/github/gtache/autosubtitle/setup/whisper/CondaSetupConfiguration.java
@@ -0,0 +1,30 @@
+package com.github.gtache.autosubtitle.setup.whisper;
+
+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 conda setup
+ */
+public record CondaSetupConfiguration(Path condaRootPath, Path condaSystemPath, Path condaBundledPath,
+ int condaMinimumMajorVersion, int condaMinimumMinorVersion,
+ Path condaInstallerPath, OS os, Architecture architecture) {
+
+ public CondaSetupConfiguration {
+ Objects.requireNonNull(condaRootPath);
+ Objects.requireNonNull(condaSystemPath);
+ Objects.requireNonNull(condaBundledPath);
+ if (condaMinimumMajorVersion <= 0) {
+ throw new IllegalArgumentException("Conda minimum major version must be > 0 : " + condaMinimumMajorVersion);
+ }
+ if (condaMinimumMinorVersion < 0) {
+ throw new IllegalArgumentException("Conda minimum minor version must be >= 0 : " + condaMinimumMinorVersion);
+ }
+ Objects.requireNonNull(condaInstallerPath);
+ Objects.requireNonNull(os);
+ Objects.requireNonNull(architecture);
+ }
+}
diff --git a/whisper/src/main/java/com/github/gtache/autosubtitle/setup/whisper/CondaSetupManager.java b/whisper/src/main/java/com/github/gtache/autosubtitle/setup/whisper/CondaSetupManager.java
index 4416e60..534d41d 100644
--- a/whisper/src/main/java/com/github/gtache/autosubtitle/setup/whisper/CondaSetupManager.java
+++ b/whisper/src/main/java/com/github/gtache/autosubtitle/setup/whisper/CondaSetupManager.java
@@ -1,13 +1,6 @@
package com.github.gtache.autosubtitle.setup.whisper;
-import com.github.gtache.autosubtitle.impl.Architecture;
import com.github.gtache.autosubtitle.impl.OS;
-import com.github.gtache.autosubtitle.modules.setup.whisper.CondaBundledPath;
-import com.github.gtache.autosubtitle.modules.setup.whisper.CondaInstallerPath;
-import com.github.gtache.autosubtitle.modules.setup.whisper.CondaMinimumMajorVersion;
-import com.github.gtache.autosubtitle.modules.setup.whisper.CondaMinimumMinorVersion;
-import com.github.gtache.autosubtitle.modules.setup.whisper.CondaRootPath;
-import com.github.gtache.autosubtitle.modules.setup.whisper.CondaSystemPath;
import com.github.gtache.autosubtitle.setup.SetupException;
import com.github.gtache.autosubtitle.setup.SetupStatus;
import com.github.gtache.autosubtitle.setup.impl.AbstractSetupManager;
@@ -17,10 +10,7 @@ import org.apache.logging.log4j.Logger;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.IOException;
-import java.net.URI;
import java.net.http.HttpClient;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
@@ -36,30 +26,12 @@ public class CondaSetupManager extends AbstractSetupManager {
private static final Logger logger = LogManager.getLogger(CondaSetupManager.class);
- private final Path condaSystemPath;
- private final Path condaBundledPath;
- private final int condaMinimumMajorVersion;
- private final int condaMinimumMinorVersion;
- private final Path condaInstallerPath;
- private final Path condaRootPath;
- private final OS os;
- private final Architecture architecture;
- private final HttpClient httpClient;
+ private final CondaSetupConfiguration configuration;
@Inject
- CondaSetupManager(@CondaSystemPath final Path condaSystemPath, @CondaBundledPath final Path condaBundledPath,
- @CondaMinimumMajorVersion final int condaMinimumMajorVersion, @CondaMinimumMinorVersion final int condaMinimumMinorVersion,
- @CondaInstallerPath final Path condaInstallerPath, @CondaRootPath final Path condaRootPath,
- final OS os, final Architecture architecture, final HttpClient httpClient) {
- this.condaSystemPath = requireNonNull(condaSystemPath);
- this.condaBundledPath = requireNonNull(condaBundledPath);
- this.condaMinimumMajorVersion = condaMinimumMajorVersion;
- this.condaMinimumMinorVersion = condaMinimumMinorVersion;
- this.condaInstallerPath = requireNonNull(condaInstallerPath);
- this.condaRootPath = requireNonNull(condaRootPath);
- this.os = requireNonNull(os);
- this.architecture = requireNonNull(architecture);
- this.httpClient = requireNonNull(httpClient);
+ CondaSetupManager(final CondaSetupConfiguration configuration, final HttpClient httpClient) {
+ super(httpClient);
+ this.configuration = requireNonNull(configuration);
}
@Override
@@ -86,14 +58,15 @@ public class CondaSetupManager extends AbstractSetupManager {
}
private void installConda() throws SetupException {
- if (Files.exists(condaInstallerPath)) {
- logger.info("Conda exists at {}", condaInstallerPath);
+ final var installerPath = configuration.condaInstallerPath();
+ if (Files.exists(installerPath)) {
+ logger.info("Conda exists at {}", installerPath);
} else {
logger.info("Conda installer not found, downloading");
downloadConda();
logger.info("Conda downloaded");
}
- switch (os) {
+ switch (configuration.os()) {
case WINDOWS -> installWindows();
case MAC, LINUX -> installLinux();
}
@@ -101,10 +74,12 @@ public class CondaSetupManager extends AbstractSetupManager {
private void installLinux() throws SetupException {
try {
- logger.info("Installing conda using {}", condaInstallerPath);
- final var result = run("bash", condaInstallerPath.toString(), "-b", "-p", condaRootPath.toString());
+ final var installerPath = configuration.condaInstallerPath();
+ final var rootPath = configuration.condaRootPath();
+ logger.info("Installing conda using {}", installerPath);
+ final var result = run("bash", installerPath.toString(), "-b", "-p", rootPath.toString());
if (result.exitCode() == 0) {
- logger.info("Installed conda to {}", condaRootPath);
+ logger.info("Installed conda to {}", rootPath);
} else {
throw new SetupException("Error installing conda: " + result);
}
@@ -115,10 +90,12 @@ public class CondaSetupManager extends AbstractSetupManager {
private void installWindows() throws SetupException {
try {
- logger.info("Installing conda using {}", condaInstallerPath);
- final var result = run(condaInstallerPath.toString(), "/InstallationType=JustMe", "/RegisterPython=0", "/S", "/D=" + condaRootPath.toString());
+ final var installerPath = configuration.condaInstallerPath();
+ final var rootPath = configuration.condaRootPath();
+ logger.info("Installing conda using {}", installerPath);
+ final var result = run(installerPath.toString(), "/InstallationType=JustMe", "/RegisterPython=0", "/S", "/D=" + rootPath.toString());
if (result.exitCode() == 0) {
- logger.info("Installed conda to {}", condaRootPath);
+ logger.info("Installed conda to {}", rootPath);
} else {
throw new SetupException("Error installing conda: " + result);
}
@@ -128,15 +105,16 @@ public class CondaSetupManager extends AbstractSetupManager {
}
private void downloadConda() throws SetupException {
- switch (os) {
+ switch (configuration.os()) {
case WINDOWS -> downloadCondaWindows();
case MAC -> downloadCondaMac();
case LINUX -> downloadCondaLinux();
}
- logger.info("Downloaded conda to {}", condaInstallerPath);
+ logger.info("Downloaded conda to {}", configuration.condaInstallerPath());
}
private void downloadCondaLinux() throws SetupException {
+ final var architecture = configuration.architecture();
if (architecture.isAMD64()) {
downloadConda("https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh");
} else if (architecture.isARM64()) {
@@ -147,6 +125,7 @@ public class CondaSetupManager extends AbstractSetupManager {
}
private void downloadCondaMac() throws SetupException {
+ final var architecture = configuration.architecture();
if (architecture.isAMD64()) {
downloadConda("https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh");
} else if (architecture.isARM64()) {
@@ -162,25 +141,12 @@ public class CondaSetupManager extends AbstractSetupManager {
}
private void downloadConda(final String url) throws SetupException {
- final var request = HttpRequest.newBuilder().uri(URI.create(url)).GET().build();
- try {
- final var result = httpClient.send(request, HttpResponse.BodyHandlers.ofFile(condaInstallerPath));
- if (result.statusCode() == 200) {
- logger.info("Conda download successful");
- } else {
- throw new SetupException("Error downloading conda: " + result.body());
- }
- } catch (final IOException e) {
- throw new SetupException(e);
- } catch (final InterruptedException e) {
- Thread.currentThread().interrupt();
- throw new SetupException(e);
- }
+ download(url, configuration.condaInstallerPath());
}
@Override
public void uninstall() throws SetupException {
- deleteFolder(condaRootPath);
+ deleteFolder(configuration.condaRootPath());
}
@Override
@@ -208,7 +174,7 @@ public class CondaSetupManager extends AbstractSetupManager {
throw new SetupException("Error creating venv " + path + ": " + result.output());
}
// On Windows, we need to copy the DLLs otherwise pip may not work
- if (os == OS.WINDOWS) {
+ if (configuration.os() == OS.WINDOWS) {
final var sourceFolder = path.resolve("Library").resolve("bin");
try (final var files = Files.find(sourceFolder, 1, (p, a) -> p.getFileName().toString().contains("libcrypto") || p.getFileName().toString().contains("libssl"))) {
final var fileList = files.toList();
@@ -225,16 +191,16 @@ public class CondaSetupManager extends AbstractSetupManager {
}
public boolean venvExists(final Path path) {
- return Files.exists(path.resolve("bin").resolve(os == OS.WINDOWS ? "python.exe" : "python"));
+ return Files.exists(path.resolve("bin").resolve(configuration.os() == OS.WINDOWS ? "python.exe" : "python"));
}
private Path getCondaPath() throws SetupException {
- return isSystemCondaInstalled() ? condaSystemPath : condaBundledPath;
+ return isSystemCondaInstalled() ? configuration.condaSystemPath() : configuration.condaBundledPath();
}
private boolean isSystemCondaInstalled() throws SetupException {
try {
- final var result = run(condaSystemPath.toString(), "--version");
+ final var result = run(configuration.condaSystemPath().toString(), "--version");
if (result.exitCode() == 0) {
final var output = result.output().getFirst();
final var versionString = output.substring(output.indexOf(' ') + 1);
@@ -242,7 +208,7 @@ public class CondaSetupManager extends AbstractSetupManager {
if (version.length == 3) {
final var major = Integer.parseInt(version[0]);
final var minor = Integer.parseInt(version[1]);
- return major >= condaMinimumMajorVersion && minor >= condaMinimumMinorVersion;
+ return major >= configuration.condaMinimumMajorVersion() && minor >= configuration.condaMinimumMinorVersion();
} else {
throw new SetupException("Unexpected python version: " + versionString);
}
@@ -255,6 +221,6 @@ public class CondaSetupManager extends AbstractSetupManager {
}
private boolean isBundledCondaInstalled() {
- return Files.isRegularFile(condaBundledPath);
+ return Files.isRegularFile(configuration.condaBundledPath());
}
}
diff --git a/whisper/src/main/java/com/github/gtache/autosubtitle/setup/whisper/WhisperSetupConfiguration.java b/whisper/src/main/java/com/github/gtache/autosubtitle/setup/whisper/WhisperSetupConfiguration.java
new file mode 100644
index 0000000..6f2244a
--- /dev/null
+++ b/whisper/src/main/java/com/github/gtache/autosubtitle/setup/whisper/WhisperSetupConfiguration.java
@@ -0,0 +1,21 @@
+package com.github.gtache.autosubtitle.setup.whisper;
+
+import com.github.gtache.autosubtitle.impl.OS;
+
+import java.nio.file.Path;
+import java.util.Objects;
+
+/**
+ * Configuration for whisper setup
+ */
+public record WhisperSetupConfiguration(Path root, Path venvPath, String pythonVersion,
+ String whisperVersion, OS os) {
+
+ public WhisperSetupConfiguration {
+ Objects.requireNonNull(root);
+ Objects.requireNonNull(venvPath);
+ Objects.requireNonNull(pythonVersion);
+ Objects.requireNonNull(whisperVersion);
+ Objects.requireNonNull(os);
+ }
+}
diff --git a/whisper/src/main/java/com/github/gtache/autosubtitle/setup/whisper/WhisperSetupManager.java b/whisper/src/main/java/com/github/gtache/autosubtitle/setup/whisper/WhisperSetupManager.java
index c0f2b50..7183d20 100644
--- a/whisper/src/main/java/com/github/gtache/autosubtitle/setup/whisper/WhisperSetupManager.java
+++ b/whisper/src/main/java/com/github/gtache/autosubtitle/setup/whisper/WhisperSetupManager.java
@@ -1,10 +1,6 @@
package com.github.gtache.autosubtitle.setup.whisper;
import com.github.gtache.autosubtitle.impl.OS;
-import com.github.gtache.autosubtitle.modules.setup.whisper.PythonVersion;
-import com.github.gtache.autosubtitle.modules.setup.whisper.WhisperBundledRoot;
-import com.github.gtache.autosubtitle.modules.setup.whisper.WhisperVenvPath;
-import com.github.gtache.autosubtitle.modules.setup.whisper.WhisperVersion;
import com.github.gtache.autosubtitle.setup.SetupAction;
import com.github.gtache.autosubtitle.setup.SetupException;
import com.github.gtache.autosubtitle.setup.SetupStatus;
@@ -20,28 +16,23 @@ import java.nio.file.Path;
import static java.util.Objects.requireNonNull;
+//TODO add gpg/signature check
+
+/**
+ * Setup manager for Whisper
+ */
@Singleton
public class WhisperSetupManager extends AbstractSetupManager {
private static final Logger logger = LogManager.getLogger(WhisperSetupManager.class);
private static final String CONDA_ENV = "conda-env";
private final CondaSetupManager condaSetupManager;
- private final String pythonVersion;
- private final Path whisperRoot;
- private final Path venvPath;
- private final OS os;
- private final String whisperVersion;
+ private final WhisperSetupConfiguration configuration;
@Inject
- WhisperSetupManager(final CondaSetupManager condaSetupManager, @PythonVersion final String pythonVersion,
- @WhisperBundledRoot final Path whisperRoot, @WhisperVenvPath final Path venvPath, final OS os,
- @WhisperVersion final String whisperVersion) {
+ WhisperSetupManager(final CondaSetupManager condaSetupManager, final WhisperSetupConfiguration configuration) {
this.condaSetupManager = requireNonNull(condaSetupManager);
- this.pythonVersion = requireNonNull(pythonVersion);
- this.whisperRoot = requireNonNull(whisperRoot);
- this.venvPath = requireNonNull(venvPath);
- this.os = requireNonNull(os);
- this.whisperVersion = requireNonNull(whisperVersion);
+ this.configuration = requireNonNull(configuration);
}
@Override
@@ -83,12 +74,12 @@ public class WhisperSetupManager extends AbstractSetupManager {
private void checkInstallVenv() throws SetupException {
sendStartEvent(SetupAction.CHECK, CONDA_ENV, 0.33);
- if (condaSetupManager.venvExists(venvPath)) {
+ if (condaSetupManager.venvExists(configuration.venvPath())) {
sendEndEvent(SetupAction.CHECK, CONDA_ENV, 0.66);
} else {
sendEndEvent(SetupAction.CHECK, CONDA_ENV, -1);
sendStartEvent(SetupAction.INSTALL, CONDA_ENV, -1);
- condaSetupManager.createVenv(venvPath, pythonVersion);
+ condaSetupManager.createVenv(configuration.venvPath(), configuration.pythonVersion());
sendEndEvent(SetupAction.INSTALL, CONDA_ENV, 0.66);
}
}
@@ -107,7 +98,7 @@ public class WhisperSetupManager extends AbstractSetupManager {
@Override
public void uninstall() throws SetupException {
- deleteFolder(whisperRoot);
+ deleteFolder(configuration.root());
}
@Override
@@ -124,19 +115,21 @@ public class WhisperSetupManager extends AbstractSetupManager {
}
private Path getPythonPath() {
- return venvPath.resolve(os == OS.WINDOWS ? "python.exe" : "python");
+ return configuration.venvPath().resolve(configuration.os() == OS.WINDOWS ? "python.exe" : "python");
}
private void installWhisper() throws SetupException {
final var path = getPythonPath();
try {
logger.info("Installing whisper");
- final var result = run(path.toString(), "-m", "pip", "install", "-U", "openai-whisper==" + whisperVersion, "numpy<2");
+ final var result = run(path.toString(), "-m", "pip", "install", "-U", "openai-whisper==" + configuration.whisperVersion(), "numpy<2");
if (result.exitCode() == 0) {
logger.info("Whisper installed");
} else {
throw new SetupException("Error installing whisper: " + result.output());
}
+ //TODO cuda?
+ final var cudaResult = run(path.toString(), "-m", "pip", "install", "-U", "torch", "--index-url", "https://download.pytorch.org/whl/cu124");
} catch (final IOException e) {
throw new SetupException(e);
}
diff --git a/whisper/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/WhisperSubtitleExtractor.java b/whisper/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/WhisperSubtitleExtractor.java
index 22eb3bb..7f715e1 100644
--- a/whisper/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/WhisperSubtitleExtractor.java
+++ b/whisper/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/WhisperSubtitleExtractor.java
@@ -41,7 +41,10 @@ import static java.util.Objects.requireNonNull;
public class WhisperSubtitleExtractor extends AbstractProcessRunner implements SubtitleExtractor {
private static final Logger logger = LogManager.getLogger(WhisperSubtitleExtractor.class);
+
+ private static final String AUTOSUBTITLE = "autosubtitle";
private static final Pattern LINE_PROGRESS_PATTERN = Pattern.compile("^\\[\\d{2}:\\d{2}\\.\\d{3} --> (?\\d{2}):(?\\d{2})\\.(?\\d{3})]");
+ private static final Pattern TQDM_PROGRESS_PATTERN = Pattern.compile("^(?