From 3fa51eb95ba8cd4a14facf116963be27beaa60e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillaume=20T=C3=A2che?= Date: Sat, 17 Aug 2024 22:05:04 +0200 Subject: [PATCH] Adds WhisperX, reworks UI (still needs some work), theoretically usable --- .idea/csv-editor.xml | 219 ------------ .idea/encodings.xml | 8 + .idea/sonarlint.xml | 1 + .../gtache/autosubtitle/Translator.java | 6 +- .../gtache/autosubtitle/VideoConverter.java | 8 +- .../gtache/autosubtitle/archive/Archiver.java | 44 +++ .../autosubtitle/process/ProcessListener.java | 3 +- .../autosubtitle/process/ProcessRunner.java | 10 +- .../subtitle/SubtitleCollection.java | 4 +- .../subtitle/SubtitleImporterExporter.java | 56 +++ .../subtitle/converter/SubtitleConverter.java | 9 +- .../subtitle/extractor/SubtitleExtractor.java | 11 +- api/src/main/java/module-info.java | 1 + .../converter/TestSubtitleConverter.java | 5 +- .../process/TestProcessRunner.java | 3 +- cli/pom.xml | 2 +- .../modules/cli/CliComponent.java | 4 +- cli/src/main/java/module-info.java | 2 +- conda/pom.xml | 20 ++ .../setup/conda}/CondaBundledPath.java | 2 +- .../setup/conda}/CondaInstallerPath.java | 2 +- .../conda}/CondaMinimumMajorVersion.java | 2 +- .../conda}/CondaMinimumMinorVersion.java | 2 +- .../modules/setup/conda}/CondaRootPath.java | 2 +- .../setup/conda}/CondaSetupModule.java | 12 +- .../modules/setup/conda}/CondaSystemPath.java | 2 +- .../setup/conda}/CondaSetupConfiguration.java | 2 +- .../setup/conda}/CondaSetupManager.java | 24 +- conda/src/main/java/module-info.java | 10 + core/pom.xml | 5 + .../archive/impl}/ZipDecompresser.java | 27 +- .../autosubtitle/modules/impl/CoreModule.java | 12 +- .../modules/setup/impl/CacheRoot.java | 4 +- .../modules/setup/impl/SetupModule.java | 14 + .../modules/setup/impl/ToolsRoot.java | 16 + .../modules/subtitle/impl/SubtitleModule.java | 5 + .../process/impl/AbstractProcessRunner.java | 22 +- .../process/impl/ProcessListenerImpl.java | 33 +- .../converter/impl/SRTSubtitleConverter.java | 13 +- .../subtitle/impl/SubtitleCollectionImpl.java | 4 +- .../subtitle/impl/SubtitleImpl.java | 4 + .../impl/SubtitleImporterExporterImpl.java | 141 ++++++++ core/src/main/java/module-info.java | 1 + .../archive/impl/TestZipDecompresser.java | 56 +++ .../impl/TestAbstractProcessRunner.java | 4 +- .../impl/TestSRTSubtitleConverter.java | 2 +- .../impl/TestAbstractSubtitleExtractor.java | 5 +- .../gtache/autosubtitle/archive/impl/in.txt | 1 + .../gtache/autosubtitle/archive/impl/in.zip | Bin 0 -> 314 bytes core/src/test/resources/log4j2-test.xml | 13 + .../autosubtitle/deepl/DeepLTranslator.java | 4 +- ffmpeg/pom.xml | 5 + .../ffmpeg/TarArchiver.java} | 28 +- .../archive/ffmpeg/XZArchiver.java | 45 +++ .../ffmpeg/FFmpegVideoConverter.java | 35 +- .../ffmpeg/FFprobeVideoLoader.java | 10 +- .../{ => setup}/ffmpeg/FFBundledRoot.java | 2 +- .../ffmpeg/FFProbeInstallerPath.java | 2 +- .../{ => setup}/ffmpeg/FFmpegBundledPath.java | 2 +- .../ffmpeg/FFmpegInstallerPath.java | 2 +- .../setup/ffmpeg/FFmpegSetupModule.java | 39 +-- .../{ => setup}/ffmpeg/FFmpegSystemPath.java | 2 +- .../{ => setup}/ffmpeg/FFmpegVersion.java | 2 +- .../ffmpeg/FFprobeBundledPath.java | 2 +- .../{ => setup}/ffmpeg/FFprobeSystemPath.java | 2 +- .../setup/ffmpeg/Decompresser.java | 27 -- .../setup/ffmpeg/FFmpegSetupManager.java | 13 +- .../setup/ffmpeg/XZDecompresser.java | 28 -- ffmpeg/src/main/java/module-info.java | 1 + .../archive/ffmpeg/TestTarArchiver.java | 56 +++ .../archive/ffmpeg/TestXZArchiver.java | 46 +++ .../ffmpeg/TestFFmpegVideoConverter.java | 57 ++++ .../ffmpeg/TestFFmpegVideoLoader.java | 51 +++ .../setup/ffmpeg/TestFFmpegSetupModule.java | 84 +++++ .../ffmpeg/TestFFmpegSetupConfiguration.java | 62 ++++ .../setup/ffmpeg/TestFFmpegSetupManager.java | 4 + .../autosubtitle/archive/ffmpeg/bin.txt | 1 + .../gtache/autosubtitle/archive/ffmpeg/in.tar | Bin 0 -> 10240 bytes .../gtache/autosubtitle/archive/ffmpeg/in.txt | 1 + .../autosubtitle/archive/ffmpeg/in.txt.xz | Bin 0 -> 60 bytes .../autosubtitle/archive/ffmpeg/lib.txt | 1 + .../autosubtitle/ffmpeg/fake-ffmpeg.exe | Bin 0 -> 25600 bytes .../autosubtitle/ffmpeg/fake-ffmpeg.ps1 | 5 + .../gtache/autosubtitle/ffmpeg/fake-ffmpeg.sh | 6 + .../autosubtitle/ffmpeg/fake-ffprobe.exe | Bin 0 -> 25600 bytes .../autosubtitle/ffmpeg/fake-ffprobe.ps1 | 6 + .../autosubtitle/ffmpeg/fake-ffprobe.sh | 7 + ffmpeg/src/test/resources/log4j2-test.xml | 13 + .../gtache/autosubtitle/gui/MainModel.java | 2 +- .../gtache/autosubtitle/gui/SetupModel.java | 42 --- .../autosubtitle/gui/SubtitlesController.java | 44 +++ .../autosubtitle/gui/SubtitlesModel.java | 146 ++++++++ .../autosubtitle/gui/WorkController.java | 16 +- .../gtache/autosubtitle/gui/WorkModel.java | 91 ++--- .../gui/impl/CombinedResourceBundle.java | 41 ++- .../gui/impl/spi/SubtitlesBundleProvider.java | 9 + .../impl/spi/SubtitlesBundleProviderImpl.java | 9 + .../modules/gui/impl/GuiCoreModule.java | 1 + gui/core/src/main/java/module-info.java | 1 + .../gui/impl/SubtitlesBundle.properties | 14 + .../gui/impl/SubtitlesBundle_fr.properties | 14 + .../gui/impl/WorkBundle.properties | 16 +- .../gui/impl/WorkBundle_fr.properties | 12 - .../gui/impl/TestCombinedResourceBundle.java | 54 +++ .../modules/gui/impl/TestGuiCoreModule.java | 34 ++ .../gui/impl/MultiBundle.properties | 3 + .../gui/impl/MultiBundleTwo.properties | 2 + .../gui/impl/MultiBundleTwo_de.properties | 2 + .../gui/impl/MultiBundleTwo_fr.properties | 2 + .../gui/impl/MultiBundle_de.properties | 3 + .../gui/impl/MultiBundle_fr.properties | 3 + gui/fx/pom.xml | 20 +- .../gui/fx/ColonTimeFormatter.java | 31 +- .../gtache/autosubtitle/gui/fx/FXBinder.java | 13 + .../autosubtitle/gui/fx/FXMainController.java | 14 +- .../autosubtitle/gui/fx/FXMainModel.java | 2 +- .../autosubtitle/gui/fx/FXMediaBinder.java | 3 +- .../gui/fx/FXMediaController.java | 57 ++-- .../autosubtitle/gui/fx/FXMediaModel.java | 8 +- .../gui/fx/FXParametersController.java | 2 +- .../gui/fx/FXSetupController.java | 2 +- .../autosubtitle/gui/fx/FXSetupModel.java | 76 ----- .../gui/fx/FXSubtitlesBinder.java | 55 +++ .../gui/fx/FXSubtitlesController.java | 322 ++++++++++++++++++ .../autosubtitle/gui/fx/FXSubtitlesModel.java | 256 ++++++++++++++ .../autosubtitle/gui/fx/FXWorkBinder.java | 5 +- .../autosubtitle/gui/fx/FXWorkController.java | 265 ++------------ .../autosubtitle/gui/fx/FXWorkModel.java | 213 ++++++------ .../gui/fx/LanguageStringConverter.java | 3 + .../gui/fx/TimeStringConverter.java | 3 + .../autosubtitle/modules/gui/fx/FXModule.java | 17 +- .../fx/ObservableSubtitleCollectionImpl.java | 59 ++++ .../gui/fx/ObservableSubtitleImpl.java | 81 ++--- gui/fx/src/main/java/module-info.java | 1 + .../autosubtitle/gui/fx/subtitlesView.fxml | 51 +++ .../gtache/autosubtitle/gui/fx/workView.fxml | 38 +-- .../gui/fx/TestColonTimeFormatter.java | 43 +++ .../gui/fx/TestFXMainController.java | 94 +++++ .../autosubtitle/gui/fx/TestFXMainModel.java | 20 ++ .../gui/fx/TestFXMediaBinder.java | 41 +++ .../gui/fx/TestFXMediaController.java | 297 ++++++++++++++++ .../autosubtitle/gui/fx/TestFXMediaModel.java | 71 ++++ .../gui/fx/TestFXParametersController.java | 80 +++++ .../gui/fx/TestFXParametersModel.java | 96 ++++++ .../gui/fx/TestFXSetupController.java | 78 +++++ .../autosubtitle/gui/fx/TestFXSetupModel.java | 93 +++++ .../gui/fx/TestFXSubtitlesController.java | 81 +++++ .../gui/fx/TestFXSubtitlesModel.java | 52 +++ .../autosubtitle/gui/fx/TestFXWorkBinder.java | 28 ++ .../gui/fx/TestFXWorkController.java | 70 ++++ .../autosubtitle/gui/fx/TestFXWorkModel.java | 53 +++ .../gui/fx/TestLanguageStringConverter.java | 29 ++ .../gui/fx/TestTimeStringConverter.java | 51 +++ .../modules/gui/fx/ResourceComponent.java | 21 ++ .../modules/gui/fx/TestFXModule.java | 43 +++ .../gui/fx/TestObservableSubtitleImpl.java | 106 ++++++ .../subtitle/gui/fx/TestSubtitleLabel.java | 53 +++ .../gtache/autosubtitle/gui/fx/video.mp4 | Bin 0 -> 65453 bytes gui/fx/src/test/resources/log4j2-test.xml | 13 + gui/run/pom.xml | 2 +- .../autosubtitle/{ => gui}/run/Main.java | 7 +- .../run/MissingComponentsModule.java | 12 +- .../{ => gui}/run/NoOpSetupManager.java | 2 +- .../modules/{ => gui}/run/RunComponent.java | 6 +- gui/run/src/main/java/module-info.java | 6 +- pom.xml | 19 +- server/pom.xml | 2 +- server/src/main/java/module-info.java | 2 +- whisper/base/pom.xml | 19 ++ .../whisper/base}/WhisperSetupModule.java | 22 +- .../whisper/base}/WhisperExtractorModule.java | 4 +- .../json/whisper/base}/WhisperJsonModule.java | 4 +- .../modules/whisper/base}/WhisperModule.java | 8 +- .../whisper/base/WhisperSetupManager.java | 65 ++++ .../base/WhisperSubtitleExtractor.java | 58 ++++ .../whisper/base}/JSONSubtitleConverter.java | 10 +- .../whisper/base}/JSONSubtitleSegment.java | 2 +- .../json/whisper/base}/JSONSubtitles.java | 2 +- whisper/base/src/main/java/module-info.java | 15 + whisper/common/pom.xml | 21 ++ .../modules/setup/whisper/PythonVersion.java | 0 .../setup/whisper/WhisperBundledRoot.java | 0 .../whisper/WhisperCommonSetupModule.java | 26 ++ .../setup/whisper/WhisperVenvPath.java | 0 .../whisper/AbstractWhisperSetupManager.java} | 68 ++-- .../whisper/WhisperSetupConfiguration.java | 4 +- .../AbstractWhisperSubtitleExtractor.java} | 93 +++-- .../WhisperExtractionModelProvider.java | 0 .../autosubtitle/whisper/WhisperModels.java | 0 .../src/main/java/module-info.java | 12 +- whisper/pom.xml | 30 +- whisper/whisperx/pom.xml | 20 ++ .../setup/whisperx/WhisperXSetupModule.java | 43 +++ .../whisperx/WhisperXExtractorModule.java | 20 ++ .../json/whisperx/WhisperXJsonModule.java | 31 ++ .../modules/whisperx/WhisperXModule.java | 23 ++ .../setup/whisperx/WhisperXSetupManager.java | 65 ++++ .../whisperx/WhisperXSubtitleExtractor.java | 69 ++++ .../json/whisperx/JSONSubtitleConverter.java | 67 ++++ .../json/whisperx/JSONSubtitleSegment.java | 6 + .../json/whisperx/JSONSubtitleWords.java | 4 + .../parser/json/whisperx/JSONSubtitles.java | 6 + .../WhisperXExtractionModelProvider.java | 37 ++ .../whisperx/src/main/java/module-info.java | 16 + 204 files changed, 4787 insertions(+), 1321 deletions(-) delete mode 100644 .idea/csv-editor.xml create mode 100644 api/src/main/java/com/github/gtache/autosubtitle/archive/Archiver.java create mode 100644 api/src/main/java/com/github/gtache/autosubtitle/subtitle/SubtitleImporterExporter.java create mode 100644 conda/pom.xml rename {whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper => conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda}/CondaBundledPath.java (86%) rename {whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper => conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda}/CondaInstallerPath.java (86%) rename {whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper => conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda}/CondaMinimumMajorVersion.java (86%) rename {whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper => conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda}/CondaMinimumMinorVersion.java (86%) rename {whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper => conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda}/CondaRootPath.java (86%) rename {whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper => conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda}/CondaSetupModule.java (80%) rename {whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper => conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda}/CondaSystemPath.java (86%) rename {whisper/src/main/java/com/github/gtache/autosubtitle/setup/whisper => conda/src/main/java/com/github/gtache/autosubtitle/setup/conda}/CondaSetupConfiguration.java (95%) rename {whisper/src/main/java/com/github/gtache/autosubtitle/setup/whisper => conda/src/main/java/com/github/gtache/autosubtitle/setup/conda}/CondaSetupManager.java (89%) create mode 100644 conda/src/main/java/module-info.java rename {ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg => core/src/main/java/com/github/gtache/autosubtitle/archive/impl}/ZipDecompresser.java (76%) rename whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/WhisperVersion.java => core/src/main/java/com/github/gtache/autosubtitle/modules/setup/impl/CacheRoot.java (79%) create mode 100644 core/src/main/java/com/github/gtache/autosubtitle/modules/setup/impl/ToolsRoot.java create mode 100644 core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SubtitleImporterExporterImpl.java create mode 100644 core/src/test/java/com/github/gtache/autosubtitle/archive/impl/TestZipDecompresser.java create mode 100644 core/src/test/resources/com/github/gtache/autosubtitle/archive/impl/in.txt create mode 100644 core/src/test/resources/com/github/gtache/autosubtitle/archive/impl/in.zip create mode 100644 core/src/test/resources/log4j2-test.xml rename ffmpeg/src/main/java/com/github/gtache/autosubtitle/{setup/ffmpeg/TarDecompresser.java => archive/ffmpeg/TarArchiver.java} (73%) create mode 100644 ffmpeg/src/main/java/com/github/gtache/autosubtitle/archive/ffmpeg/XZArchiver.java rename ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/{ => setup}/ffmpeg/FFBundledRoot.java (86%) rename ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/{ => setup}/ffmpeg/FFProbeInstallerPath.java (86%) rename ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/{ => setup}/ffmpeg/FFmpegBundledPath.java (86%) rename ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/{ => setup}/ffmpeg/FFmpegInstallerPath.java (86%) rename ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/{ => setup}/ffmpeg/FFmpegSystemPath.java (86%) rename ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/{ => setup}/ffmpeg/FFmpegVersion.java (86%) rename ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/{ => setup}/ffmpeg/FFprobeBundledPath.java (86%) rename ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/{ => setup}/ffmpeg/FFprobeSystemPath.java (86%) delete mode 100644 ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/Decompresser.java delete mode 100644 ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/XZDecompresser.java create mode 100644 ffmpeg/src/test/java/com/github/gtache/autosubtitle/archive/ffmpeg/TestTarArchiver.java create mode 100644 ffmpeg/src/test/java/com/github/gtache/autosubtitle/archive/ffmpeg/TestXZArchiver.java create mode 100644 ffmpeg/src/test/java/com/github/gtache/autosubtitle/ffmpeg/TestFFmpegVideoConverter.java create mode 100644 ffmpeg/src/test/java/com/github/gtache/autosubtitle/ffmpeg/TestFFmpegVideoLoader.java create mode 100644 ffmpeg/src/test/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/TestFFmpegSetupModule.java create mode 100644 ffmpeg/src/test/java/com/github/gtache/autosubtitle/setup/ffmpeg/TestFFmpegSetupConfiguration.java create mode 100644 ffmpeg/src/test/java/com/github/gtache/autosubtitle/setup/ffmpeg/TestFFmpegSetupManager.java create mode 100644 ffmpeg/src/test/resources/com/github/gtache/autosubtitle/archive/ffmpeg/bin.txt create mode 100644 ffmpeg/src/test/resources/com/github/gtache/autosubtitle/archive/ffmpeg/in.tar create mode 100644 ffmpeg/src/test/resources/com/github/gtache/autosubtitle/archive/ffmpeg/in.txt create mode 100644 ffmpeg/src/test/resources/com/github/gtache/autosubtitle/archive/ffmpeg/in.txt.xz create mode 100644 ffmpeg/src/test/resources/com/github/gtache/autosubtitle/archive/ffmpeg/lib.txt create mode 100644 ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffmpeg.exe create mode 100644 ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffmpeg.ps1 create mode 100644 ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffmpeg.sh create mode 100644 ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffprobe.exe create mode 100644 ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffprobe.ps1 create mode 100644 ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffprobe.sh create mode 100644 ffmpeg/src/test/resources/log4j2-test.xml create mode 100644 gui/api/src/main/java/com/github/gtache/autosubtitle/gui/SubtitlesController.java create mode 100644 gui/api/src/main/java/com/github/gtache/autosubtitle/gui/SubtitlesModel.java create mode 100644 gui/core/src/main/java/com/github/gtache/autosubtitle/gui/impl/spi/SubtitlesBundleProvider.java create mode 100644 gui/core/src/main/java/com/github/gtache/autosubtitle/gui/impl/spi/SubtitlesBundleProviderImpl.java create mode 100644 gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/SubtitlesBundle.properties create mode 100644 gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/SubtitlesBundle_fr.properties create mode 100644 gui/core/src/test/java/com/github/gtache/autosubtitle/gui/impl/TestCombinedResourceBundle.java create mode 100644 gui/core/src/test/java/com/github/gtache/autosubtitle/modules/gui/impl/TestGuiCoreModule.java create mode 100644 gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundle.properties create mode 100644 gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundleTwo.properties create mode 100644 gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundleTwo_de.properties create mode 100644 gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundleTwo_fr.properties create mode 100644 gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundle_de.properties create mode 100644 gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundle_fr.properties create mode 100644 gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXBinder.java create mode 100644 gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesBinder.java create mode 100644 gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesController.java create mode 100644 gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesModel.java create mode 100644 gui/fx/src/main/java/com/github/gtache/autosubtitle/subtitle/gui/fx/ObservableSubtitleCollectionImpl.java create mode 100644 gui/fx/src/main/resources/com/github/gtache/autosubtitle/gui/fx/subtitlesView.fxml create mode 100644 gui/fx/src/test/java/com/github/gtache/autosubtitle/gui/fx/TestColonTimeFormatter.java create mode 100644 gui/fx/src/test/java/com/github/gtache/autosubtitle/gui/fx/TestFXMainController.java create mode 100644 gui/fx/src/test/java/com/github/gtache/autosubtitle/gui/fx/TestFXMainModel.java create mode 100644 gui/fx/src/test/java/com/github/gtache/autosubtitle/gui/fx/TestFXMediaBinder.java create mode 100644 gui/fx/src/test/java/com/github/gtache/autosubtitle/gui/fx/TestFXMediaController.java create mode 100644 gui/fx/src/test/java/com/github/gtache/autosubtitle/gui/fx/TestFXMediaModel.java create mode 100644 gui/fx/src/test/java/com/github/gtache/autosubtitle/gui/fx/TestFXParametersController.java create mode 100644 gui/fx/src/test/java/com/github/gtache/autosubtitle/gui/fx/TestFXParametersModel.java create mode 100644 gui/fx/src/test/java/com/github/gtache/autosubtitle/gui/fx/TestFXSetupController.java create mode 100644 gui/fx/src/test/java/com/github/gtache/autosubtitle/gui/fx/TestFXSetupModel.java create mode 100644 gui/fx/src/test/java/com/github/gtache/autosubtitle/gui/fx/TestFXSubtitlesController.java create mode 100644 gui/fx/src/test/java/com/github/gtache/autosubtitle/gui/fx/TestFXSubtitlesModel.java create mode 100644 gui/fx/src/test/java/com/github/gtache/autosubtitle/gui/fx/TestFXWorkBinder.java create mode 100644 gui/fx/src/test/java/com/github/gtache/autosubtitle/gui/fx/TestFXWorkController.java create mode 100644 gui/fx/src/test/java/com/github/gtache/autosubtitle/gui/fx/TestFXWorkModel.java create mode 100644 gui/fx/src/test/java/com/github/gtache/autosubtitle/gui/fx/TestLanguageStringConverter.java create mode 100644 gui/fx/src/test/java/com/github/gtache/autosubtitle/gui/fx/TestTimeStringConverter.java create mode 100644 gui/fx/src/test/java/com/github/gtache/autosubtitle/modules/gui/fx/ResourceComponent.java create mode 100644 gui/fx/src/test/java/com/github/gtache/autosubtitle/modules/gui/fx/TestFXModule.java create mode 100644 gui/fx/src/test/java/com/github/gtache/autosubtitle/subtitle/gui/fx/TestObservableSubtitleImpl.java create mode 100644 gui/fx/src/test/java/com/github/gtache/autosubtitle/subtitle/gui/fx/TestSubtitleLabel.java create mode 100644 gui/fx/src/test/resources/com/github/gtache/autosubtitle/gui/fx/video.mp4 create mode 100644 gui/fx/src/test/resources/log4j2-test.xml rename gui/run/src/main/java/com/github/gtache/autosubtitle/{ => gui}/run/Main.java (81%) rename gui/run/src/main/java/com/github/gtache/autosubtitle/modules/{ => gui}/run/MissingComponentsModule.java (81%) rename gui/run/src/main/java/com/github/gtache/autosubtitle/modules/{ => gui}/run/NoOpSetupManager.java (95%) rename gui/run/src/main/java/com/github/gtache/autosubtitle/modules/{ => gui}/run/RunComponent.java (76%) create mode 100644 whisper/base/pom.xml rename whisper/{src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper => base/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/base}/WhisperSetupModule.java (52%) rename whisper/{src/main/java/com/github/gtache/autosubtitle/modules/subtitle/extractor/whisper => base/src/main/java/com/github/gtache/autosubtitle/modules/subtitle/extractor/whisper/base}/WhisperExtractorModule.java (91%) rename whisper/{src/main/java/com/github/gtache/autosubtitle/modules/subtitle/parser/json/whisper => base/src/main/java/com/github/gtache/autosubtitle/modules/subtitle/parser/json/whisper/base}/WhisperJsonModule.java (93%) rename whisper/{src/main/java/com/github/gtache/autosubtitle/modules/whisper => base/src/main/java/com/github/gtache/autosubtitle/modules/whisper/base}/WhisperModule.java (75%) create mode 100644 whisper/base/src/main/java/com/github/gtache/autosubtitle/setup/whisper/base/WhisperSetupManager.java create mode 100644 whisper/base/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/base/WhisperSubtitleExtractor.java rename whisper/{src/main/java/com/github/gtache/autosubtitle/subtitle/parser/json/whisper => base/src/main/java/com/github/gtache/autosubtitle/subtitle/parser/json/whisper/base}/JSONSubtitleConverter.java (89%) rename whisper/{src/main/java/com/github/gtache/autosubtitle/subtitle/parser/json/whisper => base/src/main/java/com/github/gtache/autosubtitle/subtitle/parser/json/whisper/base}/JSONSubtitleSegment.java (97%) rename whisper/{src/main/java/com/github/gtache/autosubtitle/subtitle/parser/json/whisper => base/src/main/java/com/github/gtache/autosubtitle/subtitle/parser/json/whisper/base}/JSONSubtitles.java (94%) create mode 100644 whisper/base/src/main/java/module-info.java create mode 100644 whisper/common/pom.xml rename whisper/{ => common}/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/PythonVersion.java (100%) rename whisper/{ => common}/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/WhisperBundledRoot.java (100%) create mode 100644 whisper/common/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/WhisperCommonSetupModule.java rename whisper/{ => common}/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/WhisperVenvPath.java (100%) rename whisper/{src/main/java/com/github/gtache/autosubtitle/setup/whisper/WhisperSetupManager.java => common/src/main/java/com/github/gtache/autosubtitle/setup/whisper/AbstractWhisperSetupManager.java} (68%) rename whisper/{ => common}/src/main/java/com/github/gtache/autosubtitle/setup/whisper/WhisperSetupConfiguration.java (76%) rename whisper/{src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/WhisperSubtitleExtractor.java => common/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/AbstractWhisperSubtitleExtractor.java} (69%) rename whisper/{ => common}/src/main/java/com/github/gtache/autosubtitle/whisper/WhisperExtractionModelProvider.java (100%) rename whisper/{ => common}/src/main/java/com/github/gtache/autosubtitle/whisper/WhisperModels.java (100%) rename whisper/{ => common}/src/main/java/module-info.java (52%) create mode 100644 whisper/whisperx/pom.xml create mode 100644 whisper/whisperx/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisperx/WhisperXSetupModule.java create mode 100644 whisper/whisperx/src/main/java/com/github/gtache/autosubtitle/modules/subtitle/extractor/whisperx/WhisperXExtractorModule.java create mode 100644 whisper/whisperx/src/main/java/com/github/gtache/autosubtitle/modules/subtitle/parser/json/whisperx/WhisperXJsonModule.java create mode 100644 whisper/whisperx/src/main/java/com/github/gtache/autosubtitle/modules/whisperx/WhisperXModule.java create mode 100644 whisper/whisperx/src/main/java/com/github/gtache/autosubtitle/setup/whisperx/WhisperXSetupManager.java create mode 100644 whisper/whisperx/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisperx/WhisperXSubtitleExtractor.java create mode 100644 whisper/whisperx/src/main/java/com/github/gtache/autosubtitle/subtitle/parser/json/whisperx/JSONSubtitleConverter.java create mode 100644 whisper/whisperx/src/main/java/com/github/gtache/autosubtitle/subtitle/parser/json/whisperx/JSONSubtitleSegment.java create mode 100644 whisper/whisperx/src/main/java/com/github/gtache/autosubtitle/subtitle/parser/json/whisperx/JSONSubtitleWords.java create mode 100644 whisper/whisperx/src/main/java/com/github/gtache/autosubtitle/subtitle/parser/json/whisperx/JSONSubtitles.java create mode 100644 whisper/whisperx/src/main/java/com/github/gtache/autosubtitle/whisperx/WhisperXExtractionModelProvider.java create mode 100644 whisper/whisperx/src/main/java/module-info.java diff --git a/.idea/csv-editor.xml b/.idea/csv-editor.xml deleted file mode 100644 index 54fbf77..0000000 --- a/.idea/csv-editor.xml +++ /dev/null @@ -1,219 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml index 4697f0e..21e87df 100644 --- a/.idea/encodings.xml +++ b/.idea/encodings.xml @@ -7,6 +7,8 @@ + + @@ -27,8 +29,14 @@ + + + + + + \ No newline at end of file diff --git a/.idea/sonarlint.xml b/.idea/sonarlint.xml index 8b17604..2645de5 100644 --- a/.idea/sonarlint.xml +++ b/.idea/sonarlint.xml @@ -4,6 +4,7 @@ diff --git a/api/src/main/java/com/github/gtache/autosubtitle/Translator.java b/api/src/main/java/com/github/gtache/autosubtitle/Translator.java index 2dceed1..ce7ebd5 100644 --- a/api/src/main/java/com/github/gtache/autosubtitle/Translator.java +++ b/api/src/main/java/com/github/gtache/autosubtitle/Translator.java @@ -6,7 +6,7 @@ import com.github.gtache.autosubtitle.subtitle.SubtitleCollection; /** * Translates texts and subtitles */ -public interface Translator { +public interface Translator { /** * Guesses the language of the given text @@ -42,7 +42,7 @@ public interface Translator { * @param to The target language * @return The translated subtitle */ - Subtitle translate(Subtitle subtitle, Language to); + T translate(Subtitle subtitle, Language to); /** * Translates the given subtitles collection to the given language @@ -51,5 +51,5 @@ public interface Translator { * @param to The target language * @return The translated subtitles collection */ - SubtitleCollection translate(SubtitleCollection collection, Language to); + SubtitleCollection translate(SubtitleCollection collection, Language to); } 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 a594658..f19bc7c 100644 --- a/api/src/main/java/com/github/gtache/autosubtitle/VideoConverter.java +++ b/api/src/main/java/com/github/gtache/autosubtitle/VideoConverter.java @@ -19,7 +19,7 @@ public interface VideoConverter { * @return The modified video * @throws IOException If an I/O error occurs */ - Video addSoftSubtitles(final Video video, final Collection subtitles) throws IOException; + Video addSoftSubtitles(final Video video, final Collection> subtitles) throws IOException; /** * Adds soft subtitles to the given video @@ -29,7 +29,7 @@ public interface VideoConverter { * @param path The output path * @throws IOException If an I/O error occurs */ - void addSoftSubtitles(final Video video, final Collection subtitles, final Path path) throws IOException; + void addSoftSubtitles(final Video video, final Collection> subtitles, final Path path) throws IOException; /** * Adds hard subtitles to the given video @@ -39,7 +39,7 @@ public interface VideoConverter { * @return The modified video * @throws IOException If an I/O error occurs */ - Video addHardSubtitles(final Video video, final SubtitleCollection subtitles) throws IOException; + Video addHardSubtitles(final Video video, final SubtitleCollection subtitles) throws IOException; /** * Adds hard subtitles to the given video @@ -49,7 +49,7 @@ public interface VideoConverter { * @param path The output path * @throws IOException If an I/O error occurs */ - void addHardSubtitles(final Video video, final SubtitleCollection subtitles, final Path path) throws IOException; + void addHardSubtitles(final Video video, final SubtitleCollection subtitles, final Path path) throws IOException; /** * Extracts the audio from the given video diff --git a/api/src/main/java/com/github/gtache/autosubtitle/archive/Archiver.java b/api/src/main/java/com/github/gtache/autosubtitle/archive/Archiver.java new file mode 100644 index 0000000..381f350 --- /dev/null +++ b/api/src/main/java/com/github/gtache/autosubtitle/archive/Archiver.java @@ -0,0 +1,44 @@ +package com.github.gtache.autosubtitle.archive; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; + +/** + * Compresses and uncompresses files + */ +public interface Archiver { + + /** + * Zips multiple files to the given destination + * + * @param files The files to zip + * @param destination The zipped file + * @throws IOException if an error occurs + */ + void compress(final List files, final Path destination) throws IOException; + + /** + * 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 archive file is supported by the compresser + * + * @param path The file path + * @return True if the file is supported + */ + default boolean isPathSupported(final Path path) { + return path.toString().endsWith("." + archiveExtension()); + } + + /** + * @return The zipped archive extension + */ + String archiveExtension(); +} diff --git a/api/src/main/java/com/github/gtache/autosubtitle/process/ProcessListener.java b/api/src/main/java/com/github/gtache/autosubtitle/process/ProcessListener.java index 0e58649..a3a1cdf 100644 --- a/api/src/main/java/com/github/gtache/autosubtitle/process/ProcessListener.java +++ b/api/src/main/java/com/github/gtache/autosubtitle/process/ProcessListener.java @@ -22,10 +22,11 @@ public interface ProcessListener { String readLine() throws IOException; /** - * Waits for the process to finish + * Waits for the process to finish. Automatically reads the process output to ensure it doesn't get stuck. * * @param duration The maximum time to wait * @return The process result + * @throws IOException if an error occurs */ ProcessResult join(final Duration duration) throws IOException; } diff --git a/api/src/main/java/com/github/gtache/autosubtitle/process/ProcessRunner.java b/api/src/main/java/com/github/gtache/autosubtitle/process/ProcessRunner.java index cfbddca..5d15a4a 100644 --- a/api/src/main/java/com/github/gtache/autosubtitle/process/ProcessRunner.java +++ b/api/src/main/java/com/github/gtache/autosubtitle/process/ProcessRunner.java @@ -1,6 +1,7 @@ package com.github.gtache.autosubtitle.process; import java.io.IOException; +import java.time.Duration; import java.util.Arrays; import java.util.List; @@ -10,24 +11,25 @@ import java.util.List; public interface ProcessRunner { /** - * Runs a command + * Runs a command and waits max 1 hour for the process to run * * @param args the command * @return the result * @throws IOException if something goes wrong */ default ProcessResult run(final String... args) throws IOException { - return run(Arrays.asList(args)); + return run(Arrays.asList(args), Duration.ofHours(1)); } /** * Runs a command * - * @param args the command + * @param args the command + * @param duration The maximum duration to wait for * @return the result * @throws IOException if something goes wrong */ - ProcessResult run(final List args) throws IOException; + ProcessResult run(final List args, final Duration duration) throws IOException; /** * Starts a process diff --git a/api/src/main/java/com/github/gtache/autosubtitle/subtitle/SubtitleCollection.java b/api/src/main/java/com/github/gtache/autosubtitle/subtitle/SubtitleCollection.java index 929042c..417ae97 100644 --- a/api/src/main/java/com/github/gtache/autosubtitle/subtitle/SubtitleCollection.java +++ b/api/src/main/java/com/github/gtache/autosubtitle/subtitle/SubtitleCollection.java @@ -7,7 +7,7 @@ import java.util.Collection; /** * Represents a collection of {@link Subtitle} */ -public interface SubtitleCollection { +public interface SubtitleCollection { /** * @return The whole text of the subtitles @@ -17,7 +17,7 @@ public interface SubtitleCollection { /** * @return The subtitles */ - Collection subtitles(); + Collection subtitles(); /** * @return The language of the subtitles diff --git a/api/src/main/java/com/github/gtache/autosubtitle/subtitle/SubtitleImporterExporter.java b/api/src/main/java/com/github/gtache/autosubtitle/subtitle/SubtitleImporterExporter.java new file mode 100644 index 0000000..8fa222f --- /dev/null +++ b/api/src/main/java/com/github/gtache/autosubtitle/subtitle/SubtitleImporterExporter.java @@ -0,0 +1,56 @@ +package com.github.gtache.autosubtitle.subtitle; + +import com.github.gtache.autosubtitle.Language; +import com.github.gtache.autosubtitle.subtitle.converter.ParseException; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * Imports and exports subtitles + */ +public interface SubtitleImporterExporter { + + /** + * Imports subtitles from a file + * + * @param file The path to the file + * @return A mapping of langauge to collection + * @throws IOException If an error occurred + * @throws ParseException If an error occurred while parsing a subtitle + */ + Map> importSubtitles(final Path file) throws IOException, ParseException; + + /** + * Exports multiple collections to a file + * + * @param collections The subtitle collections + * @param file The path to the file + * @throws IOException If an error occurred + */ + void exportSubtitles(final Collection> collections, final Path file) throws IOException; + + /** + * Exports a single collection to a file + * + * @param collection The subtitle collection + * @param file The path to the file + * @throws IOException If an error occurred + */ + default void exportSubtitles(final SubtitleCollection collection, final Path file) throws IOException { + exportSubtitles(List.of(collection), file); + } + + /** + * @return The supported file extensions for multiple collection exports + */ + Collection supportedArchiveExtensions(); + + /** + * @return The supported file extensions for single collection exports + */ + Collection supportedSingleFileExtensions(); +} diff --git a/api/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/SubtitleConverter.java b/api/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/SubtitleConverter.java index c926884..2b70487 100644 --- a/api/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/SubtitleConverter.java +++ b/api/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/SubtitleConverter.java @@ -1,5 +1,6 @@ package com.github.gtache.autosubtitle.subtitle.converter; +import com.github.gtache.autosubtitle.subtitle.Subtitle; import com.github.gtache.autosubtitle.subtitle.SubtitleCollection; import java.io.IOException; @@ -9,7 +10,7 @@ import java.nio.file.Path; /** * Converts subtitles to a specific format (e.g. srt, ssa, ass, ...) and vice-versa */ -public interface SubtitleConverter { +public interface SubtitleConverter { /** * Converts the subtitle collection @@ -17,7 +18,7 @@ public interface SubtitleConverter { * @param collection The collection * @return The converted subtitles as the content of a file */ - String format(final SubtitleCollection collection); + String format(final SubtitleCollection collection); /** * Parses a subtitle collection @@ -26,7 +27,7 @@ public interface SubtitleConverter { * @return The subtitle collection * @throws ParseException If an error occurred */ - default SubtitleCollection parse(final Path file) throws ParseException { + default SubtitleCollection parse(final Path file) throws ParseException { try { final var content = Files.readString(file); return parse(content); @@ -42,7 +43,7 @@ public interface SubtitleConverter { * @return The subtitle collection * @throws ParseException If an error occurred */ - SubtitleCollection parse(String content) throws ParseException; + SubtitleCollection parse(String content) throws ParseException; /** * Check if the parser can parse the given file diff --git a/api/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/SubtitleExtractor.java b/api/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/SubtitleExtractor.java index fd91d11..f5e77bf 100644 --- a/api/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/SubtitleExtractor.java +++ b/api/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/SubtitleExtractor.java @@ -3,12 +3,13 @@ package com.github.gtache.autosubtitle.subtitle.extractor; import com.github.gtache.autosubtitle.Audio; import com.github.gtache.autosubtitle.Language; import com.github.gtache.autosubtitle.Video; +import com.github.gtache.autosubtitle.subtitle.Subtitle; import com.github.gtache.autosubtitle.subtitle.SubtitleCollection; /** * Extracts subtitles from a video or audio */ -public interface SubtitleExtractor { +public interface SubtitleExtractor { /** * Adds a listener @@ -37,7 +38,7 @@ public interface SubtitleExtractor { * @return The extracted subtitle collection * @throws ExtractException If an error occurs */ - default SubtitleCollection extract(final Video video, final ExtractionModel model) throws ExtractException { + default SubtitleCollection extract(final Video video, final ExtractionModel model) throws ExtractException { return extract(video, Language.AUTO, model); } @@ -50,7 +51,7 @@ public interface SubtitleExtractor { * @return The extracted subtitle collection * @throws ExtractException If an error occurs */ - SubtitleCollection extract(final Video video, final Language language, final ExtractionModel model) throws ExtractException; + SubtitleCollection extract(final Video video, final Language language, final ExtractionModel model) throws ExtractException; /** * Extracts the subtitles from an audio @@ -60,7 +61,7 @@ public interface SubtitleExtractor { * @return The extracted subtitle collection * @throws ExtractException If an error occurs */ - default SubtitleCollection extract(final Audio audio, final ExtractionModel model) throws ExtractException { + default SubtitleCollection extract(final Audio audio, final ExtractionModel model) throws ExtractException { return extract(audio, Language.AUTO, model); } @@ -73,5 +74,5 @@ public interface SubtitleExtractor { * @return The extracted subtitle collection * @throws ExtractException If an error occurs */ - SubtitleCollection extract(final Audio audio, final Language language, final ExtractionModel model) throws ExtractException; + SubtitleCollection extract(final Audio audio, final Language language, final ExtractionModel model) throws ExtractException; } diff --git a/api/src/main/java/module-info.java b/api/src/main/java/module-info.java index c19dfaa..2a4c54f 100644 --- a/api/src/main/java/module-info.java +++ b/api/src/main/java/module-info.java @@ -3,6 +3,7 @@ */ module com.github.gtache.autosubtitle.api { exports com.github.gtache.autosubtitle; + exports com.github.gtache.autosubtitle.archive; exports com.github.gtache.autosubtitle.process; exports com.github.gtache.autosubtitle.setup; exports com.github.gtache.autosubtitle.subtitle; diff --git a/api/src/test/java/com/github/gtache/autosubtitle/com/github/gtache/autosubtitle/subtitle/converter/TestSubtitleConverter.java b/api/src/test/java/com/github/gtache/autosubtitle/com/github/gtache/autosubtitle/subtitle/converter/TestSubtitleConverter.java index 1c47de8..54d95ee 100644 --- a/api/src/test/java/com/github/gtache/autosubtitle/com/github/gtache/autosubtitle/subtitle/converter/TestSubtitleConverter.java +++ b/api/src/test/java/com/github/gtache/autosubtitle/com/github/gtache/autosubtitle/subtitle/converter/TestSubtitleConverter.java @@ -1,5 +1,6 @@ package com.github.gtache.autosubtitle.com.github.gtache.autosubtitle.subtitle.converter; +import com.github.gtache.autosubtitle.subtitle.Subtitle; import com.github.gtache.autosubtitle.subtitle.SubtitleCollection; import com.github.gtache.autosubtitle.subtitle.converter.ParseException; import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter; @@ -23,10 +24,10 @@ import static org.mockito.Mockito.when; class TestSubtitleConverter { private final SubtitleConverter subtitleConverter; - private final SubtitleCollection subtitleCollection; + private final SubtitleCollection subtitleCollection; TestSubtitleConverter(@Mock final SubtitleConverter subtitleConverter, - @Mock final SubtitleCollection subtitleCollection) { + @Mock final SubtitleCollection subtitleCollection) { this.subtitleConverter = Objects.requireNonNull(subtitleConverter); this.subtitleCollection = Objects.requireNonNull(subtitleCollection); } diff --git a/api/src/test/java/com/github/gtache/autosubtitle/process/TestProcessRunner.java b/api/src/test/java/com/github/gtache/autosubtitle/process/TestProcessRunner.java index 579f1ac..a6389f1 100644 --- a/api/src/test/java/com/github/gtache/autosubtitle/process/TestProcessRunner.java +++ b/api/src/test/java/com/github/gtache/autosubtitle/process/TestProcessRunner.java @@ -7,6 +7,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import java.io.IOException; +import java.time.Duration; import java.util.List; import java.util.Objects; @@ -32,7 +33,7 @@ class TestProcessRunner { @Test void testRunVarargs() throws IOException { - when(processRunner.run(List.of("arg1", "arg2"))).thenReturn(result); + when(processRunner.run(List.of("arg1", "arg2"), Duration.ofHours(1))).thenReturn(result); when(processRunner.run(any(String[].class))).thenCallRealMethod(); assertEquals(result, processRunner.run("arg1", "arg2")); diff --git a/cli/pom.xml b/cli/pom.xml index 70e0d72..f4e4df9 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -22,7 +22,7 @@ com.github.gtache.autosubtitle - autosubtitle-whisper + autosubtitle-whisperx org.apache.logging.log4j 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 9ca64da..797a2b7 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 @@ -3,14 +3,14 @@ package com.github.gtache.autosubtitle.modules.cli; 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.whisperx.WhisperXModule; import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter; import dagger.Component; import javax.inject.Singleton; import java.util.Map; -@Component(modules = {CoreModule.class, DeepLModule.class, FFmpegModule.class, WhisperExtractorModule.class}) +@Component(modules = {CoreModule.class, DeepLModule.class, FFmpegModule.class, WhisperXModule.class}) @Singleton public interface CliComponent { diff --git a/cli/src/main/java/module-info.java b/cli/src/main/java/module-info.java index 74a5d6b..ec5b9fa 100644 --- a/cli/src/main/java/module-info.java +++ b/cli/src/main/java/module-info.java @@ -4,6 +4,6 @@ module com.github.gtache.autosubtitle.cli { requires com.github.gtache.autosubtitle.deepl; requires com.github.gtache.autosubtitle.ffmpeg; - requires com.github.gtache.autosubtitle.whisper; + requires com.github.gtache.autosubtitle.whisperx; requires info.picocli; } \ No newline at end of file diff --git a/conda/pom.xml b/conda/pom.xml new file mode 100644 index 0000000..535eabc --- /dev/null +++ b/conda/pom.xml @@ -0,0 +1,20 @@ + + + 4.0.0 + + com.github.gtache.autosubtitle + autosubtitle + 1.0-SNAPSHOT + + + autosubtitle-conda + + + + com.github.gtache.autosubtitle + autosubtitle-core + + + \ No newline at end of file diff --git a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaBundledPath.java b/conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaBundledPath.java similarity index 86% rename from whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaBundledPath.java rename to conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaBundledPath.java index 2d80b90..d3dd0fc 100644 --- a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaBundledPath.java +++ b/conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaBundledPath.java @@ -1,4 +1,4 @@ -package com.github.gtache.autosubtitle.modules.setup.whisper; +package com.github.gtache.autosubtitle.modules.setup.conda; import javax.inject.Qualifier; import java.lang.annotation.Documented; diff --git a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaInstallerPath.java b/conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaInstallerPath.java similarity index 86% rename from whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaInstallerPath.java rename to conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaInstallerPath.java index a514484..6a536d8 100644 --- a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaInstallerPath.java +++ b/conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaInstallerPath.java @@ -1,4 +1,4 @@ -package com.github.gtache.autosubtitle.modules.setup.whisper; +package com.github.gtache.autosubtitle.modules.setup.conda; import javax.inject.Qualifier; import java.lang.annotation.Documented; diff --git a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaMinimumMajorVersion.java b/conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaMinimumMajorVersion.java similarity index 86% rename from whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaMinimumMajorVersion.java rename to conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaMinimumMajorVersion.java index 654f0b6..b391b1e 100644 --- a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaMinimumMajorVersion.java +++ b/conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaMinimumMajorVersion.java @@ -1,4 +1,4 @@ -package com.github.gtache.autosubtitle.modules.setup.whisper; +package com.github.gtache.autosubtitle.modules.setup.conda; import javax.inject.Qualifier; import java.lang.annotation.Documented; diff --git a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaMinimumMinorVersion.java b/conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaMinimumMinorVersion.java similarity index 86% rename from whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaMinimumMinorVersion.java rename to conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaMinimumMinorVersion.java index e89879b..857f5a2 100644 --- a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaMinimumMinorVersion.java +++ b/conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaMinimumMinorVersion.java @@ -1,4 +1,4 @@ -package com.github.gtache.autosubtitle.modules.setup.whisper; +package com.github.gtache.autosubtitle.modules.setup.conda; import javax.inject.Qualifier; import java.lang.annotation.Documented; diff --git a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaRootPath.java b/conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaRootPath.java similarity index 86% rename from whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaRootPath.java rename to conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaRootPath.java index b8da150..1ce8230 100644 --- a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaRootPath.java +++ b/conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaRootPath.java @@ -1,4 +1,4 @@ -package com.github.gtache.autosubtitle.modules.setup.whisper; +package com.github.gtache.autosubtitle.modules.setup.conda; import javax.inject.Qualifier; import java.lang.annotation.Documented; diff --git a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaSetupModule.java b/conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaSetupModule.java similarity index 80% rename from whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaSetupModule.java rename to conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaSetupModule.java index e92a78e..757f94b 100644 --- a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaSetupModule.java +++ b/conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaSetupModule.java @@ -1,8 +1,10 @@ -package com.github.gtache.autosubtitle.modules.setup.whisper; +package com.github.gtache.autosubtitle.modules.setup.conda; import com.github.gtache.autosubtitle.impl.Architecture; import com.github.gtache.autosubtitle.impl.OS; -import com.github.gtache.autosubtitle.setup.whisper.CondaSetupConfiguration; +import com.github.gtache.autosubtitle.modules.setup.impl.CacheRoot; +import com.github.gtache.autosubtitle.modules.setup.impl.ToolsRoot; +import com.github.gtache.autosubtitle.setup.conda.CondaSetupConfiguration; import dagger.Module; import dagger.Provides; @@ -56,13 +58,13 @@ public final class CondaSetupModule { @Provides @CondaRootPath - static Path providesCondaRootPath(@WhisperBundledRoot final Path root, final OS os) { + static Path providesCondaRootPath(@ToolsRoot final Path root) { return root.resolve(MINICONDA3); } @Provides @CondaInstallerPath - static Path providesCondaInstallerPath(@WhisperBundledRoot final Path root, final OS os) { - return root.resolve("cache").resolve("conda-install" + (os == OS.WINDOWS ? ".exe" : ".sh")); + static Path providesCondaInstallerPath(@CacheRoot final Path root, final OS os) { + return root.resolve("conda-install" + (os == OS.WINDOWS ? ".exe" : ".sh")); } } diff --git a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaSystemPath.java b/conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaSystemPath.java similarity index 86% rename from whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaSystemPath.java rename to conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaSystemPath.java index e819bd2..a0ab21c 100644 --- a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/CondaSystemPath.java +++ b/conda/src/main/java/com/github/gtache/autosubtitle/modules/setup/conda/CondaSystemPath.java @@ -1,4 +1,4 @@ -package com.github.gtache.autosubtitle.modules.setup.whisper; +package com.github.gtache.autosubtitle.modules.setup.conda; import javax.inject.Qualifier; import java.lang.annotation.Documented; diff --git a/whisper/src/main/java/com/github/gtache/autosubtitle/setup/whisper/CondaSetupConfiguration.java b/conda/src/main/java/com/github/gtache/autosubtitle/setup/conda/CondaSetupConfiguration.java similarity index 95% rename from whisper/src/main/java/com/github/gtache/autosubtitle/setup/whisper/CondaSetupConfiguration.java rename to conda/src/main/java/com/github/gtache/autosubtitle/setup/conda/CondaSetupConfiguration.java index 6134033..698cc2d 100644 --- a/whisper/src/main/java/com/github/gtache/autosubtitle/setup/whisper/CondaSetupConfiguration.java +++ b/conda/src/main/java/com/github/gtache/autosubtitle/setup/conda/CondaSetupConfiguration.java @@ -1,4 +1,4 @@ -package com.github.gtache.autosubtitle.setup.whisper; +package com.github.gtache.autosubtitle.setup.conda; import com.github.gtache.autosubtitle.impl.Architecture; import com.github.gtache.autosubtitle.impl.OS; diff --git a/whisper/src/main/java/com/github/gtache/autosubtitle/setup/whisper/CondaSetupManager.java b/conda/src/main/java/com/github/gtache/autosubtitle/setup/conda/CondaSetupManager.java similarity index 89% rename from whisper/src/main/java/com/github/gtache/autosubtitle/setup/whisper/CondaSetupManager.java rename to conda/src/main/java/com/github/gtache/autosubtitle/setup/conda/CondaSetupManager.java index 534d41d..3b670b5 100644 --- a/whisper/src/main/java/com/github/gtache/autosubtitle/setup/whisper/CondaSetupManager.java +++ b/conda/src/main/java/com/github/gtache/autosubtitle/setup/conda/CondaSetupManager.java @@ -1,4 +1,4 @@ -package com.github.gtache.autosubtitle.setup.whisper; +package com.github.gtache.autosubtitle.setup.conda; import com.github.gtache.autosubtitle.impl.OS; import com.github.gtache.autosubtitle.setup.SetupException; @@ -13,7 +13,9 @@ import java.io.IOException; import java.net.http.HttpClient; import java.nio.file.Files; import java.nio.file.Path; +import java.time.Duration; import java.util.Arrays; +import java.util.List; import java.util.stream.Stream; import static java.util.Objects.requireNonNull; @@ -67,8 +69,8 @@ public class CondaSetupManager extends AbstractSetupManager { logger.info("Conda downloaded"); } switch (configuration.os()) { - case WINDOWS -> installWindows(); - case MAC, LINUX -> installLinux(); + case OS.WINDOWS -> installWindows(); + case OS.MAC, OS.LINUX -> installLinux(); } } @@ -77,7 +79,7 @@ public class CondaSetupManager extends AbstractSetupManager { 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()); + final var result = run(List.of("bash", installerPath.toString(), "-b", "-p", rootPath.toString()), Duration.ofMinutes(15)); if (result.exitCode() == 0) { logger.info("Installed conda to {}", rootPath); } else { @@ -93,7 +95,7 @@ public class CondaSetupManager extends AbstractSetupManager { 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()); + final var result = run(List.of(installerPath.toString(), "/InstallationType=JustMe", "/RegisterPython=0", "/S", "/D=" + rootPath.toString()), Duration.ofMinutes(15)); if (result.exitCode() == 0) { logger.info("Installed conda to {}", rootPath); } else { @@ -106,9 +108,9 @@ public class CondaSetupManager extends AbstractSetupManager { private void downloadConda() throws SetupException { switch (configuration.os()) { - case WINDOWS -> downloadCondaWindows(); - case MAC -> downloadCondaMac(); - case LINUX -> downloadCondaLinux(); + case OS.WINDOWS -> downloadCondaWindows(); + case OS.MAC -> downloadCondaMac(); + case OS.LINUX -> downloadCondaLinux(); } logger.info("Downloaded conda to {}", configuration.condaInstallerPath()); } @@ -152,7 +154,7 @@ public class CondaSetupManager extends AbstractSetupManager { @Override public void update() throws SetupException { try { - final var result = run(getCondaPath().toString(), "update", "-y", "conda"); + final var result = run(List.of(getCondaPath().toString(), "update", "-y", "conda"), Duration.ofMinutes(15)); if (result.exitCode() == 0) { logger.info("Conda updated"); } else { @@ -167,7 +169,7 @@ public class CondaSetupManager extends AbstractSetupManager { final var args = Stream.concat(Stream.of(getCondaPath().toString(), "create", "-y", "-p", path.toString(), "python=" + pythonVersion), Arrays.stream(packages)).toList(); try { logger.info("Creating venv {}", path); - final var result = run(args); + final var result = run(args, Duration.ofMinutes(15)); if (result.exitCode() == 0) { logger.info("Created venv {}", path); } else { @@ -200,7 +202,7 @@ public class CondaSetupManager extends AbstractSetupManager { private boolean isSystemCondaInstalled() throws SetupException { try { - final var result = run(configuration.condaSystemPath().toString(), "--version"); + final var result = run(List.of(configuration.condaSystemPath().toString(), "--version"), Duration.ofSeconds(5)); if (result.exitCode() == 0) { final var output = result.output().getFirst(); final var versionString = output.substring(output.indexOf(' ') + 1); diff --git a/conda/src/main/java/module-info.java b/conda/src/main/java/module-info.java new file mode 100644 index 0000000..3bc7f1d --- /dev/null +++ b/conda/src/main/java/module-info.java @@ -0,0 +1,10 @@ +/** + * Module for conda + */ +module com.github.gtache.autosubtitle.conda { + requires transitive com.github.gtache.autosubtitle.core; + requires org.apache.logging.log4j; + + exports com.github.gtache.autosubtitle.setup.conda; + exports com.github.gtache.autosubtitle.modules.setup.conda; +} \ No newline at end of file diff --git a/core/pom.xml b/core/pom.xml index ee2e5d0..03c549d 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -24,5 +24,10 @@ org.apache.logging.log4j log4j-api + + org.apache.logging.log4j + log4j-core + test + \ No newline at end of file diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/ZipDecompresser.java b/core/src/main/java/com/github/gtache/autosubtitle/archive/impl/ZipDecompresser.java similarity index 76% rename from ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/ZipDecompresser.java rename to core/src/main/java/com/github/gtache/autosubtitle/archive/impl/ZipDecompresser.java index 931d9e0..4c0625d 100644 --- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/ZipDecompresser.java +++ b/core/src/main/java/com/github/gtache/autosubtitle/archive/impl/ZipDecompresser.java @@ -1,16 +1,31 @@ -package com.github.gtache.autosubtitle.setup.ffmpeg; +package com.github.gtache.autosubtitle.archive.impl; +import com.github.gtache.autosubtitle.archive.Archiver; + +import javax.inject.Inject; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; /** - * Zip implementation of {@link Decompresser} + * Zip implementation of {@link Archiver} */ -public class ZipDecompresser implements Decompresser { +public class ZipDecompresser implements Archiver { + + @Inject + ZipDecompresser() { + + } + + @Override + public void compress(final List files, final Path destination) throws IOException { + throw new UnsupportedOperationException("Not supported"); + } + @Override public void decompress(final Path archive, final Path destination) throws IOException { if (!isPathSupported(archive)) { @@ -49,9 +64,9 @@ public class ZipDecompresser implements Decompresser { } return destPath; } - + @Override - public boolean isPathSupported(final Path path) { - return path.getFileName().toString().endsWith(".zip"); + public String archiveExtension() { + return "zip"; } } 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 1894b9a..c7736d5 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 @@ -1,23 +1,33 @@ package com.github.gtache.autosubtitle.modules.impl; +import com.github.gtache.autosubtitle.archive.Archiver; +import com.github.gtache.autosubtitle.archive.impl.ZipDecompresser; import com.github.gtache.autosubtitle.impl.Architecture; import com.github.gtache.autosubtitle.impl.DaggerException; 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.Binds; import dagger.Module; import dagger.Provides; +import dagger.multibindings.IntoMap; +import dagger.multibindings.StringKey; /** * Dagger module for Core */ @Module(includes = {SetupModule.class, SubtitleModule.class}) -public final class CoreModule { +public abstract class CoreModule { private CoreModule() { } + @Binds + @StringKey("zip") + @IntoMap + abstract Archiver bindsZipDecompresser(final ZipDecompresser decompresser); + @Provides static OS providesOS() { final var os = OS.getOS(); diff --git a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/WhisperVersion.java b/core/src/main/java/com/github/gtache/autosubtitle/modules/setup/impl/CacheRoot.java similarity index 79% rename from whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/WhisperVersion.java rename to core/src/main/java/com/github/gtache/autosubtitle/modules/setup/impl/CacheRoot.java index 96f6817..e454127 100644 --- a/whisper/src/main/java/com/github/gtache/autosubtitle/modules/setup/whisper/WhisperVersion.java +++ b/core/src/main/java/com/github/gtache/autosubtitle/modules/setup/impl/CacheRoot.java @@ -1,4 +1,4 @@ -package com.github.gtache.autosubtitle.modules.setup.whisper; +package com.github.gtache.autosubtitle.modules.setup.impl; import javax.inject.Qualifier; import java.lang.annotation.Documented; @@ -12,5 +12,5 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; @Documented @Retention(RUNTIME) @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD}) -public @interface WhisperVersion { +public @interface CacheRoot { } 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 index 0280791..767d454 100644 --- 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 @@ -4,6 +4,8 @@ import dagger.Module; import dagger.Provides; import java.net.http.HttpClient; +import java.nio.file.Path; +import java.nio.file.Paths; /** * Dagger core module for setup @@ -14,6 +16,18 @@ public final class SetupModule { private SetupModule() { } + @Provides + @CacheRoot + static Path providesCacheRoot() { + return Paths.get("cache"); + } + + @Provides + @ToolsRoot + static Path providesToolsRoot() { + return Paths.get("tools"); + } + @Provides static HttpClient providesHttpClient() { return HttpClient.newHttpClient(); diff --git a/core/src/main/java/com/github/gtache/autosubtitle/modules/setup/impl/ToolsRoot.java b/core/src/main/java/com/github/gtache/autosubtitle/modules/setup/impl/ToolsRoot.java new file mode 100644 index 0000000..5fb97fe --- /dev/null +++ b/core/src/main/java/com/github/gtache/autosubtitle/modules/setup/impl/ToolsRoot.java @@ -0,0 +1,16 @@ +package com.github.gtache.autosubtitle.modules.setup.impl; + +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 ToolsRoot { +} 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 fc04deb..2a4e256 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 @@ -1,7 +1,9 @@ package com.github.gtache.autosubtitle.modules.subtitle.impl; +import com.github.gtache.autosubtitle.subtitle.SubtitleImporterExporter; import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter; import com.github.gtache.autosubtitle.subtitle.converter.impl.SRTSubtitleConverter; +import com.github.gtache.autosubtitle.subtitle.impl.SubtitleImporterExporterImpl; import dagger.Binds; import dagger.Module; import dagger.multibindings.IntoMap; @@ -20,4 +22,7 @@ public abstract class SubtitleModule { @IntoMap @StringKey("srt") abstract SubtitleConverter bindsSubtitleConverter(final SRTSubtitleConverter converter); + + @Binds + abstract SubtitleImporterExporter bindsSubtitleImporterExporter(final SubtitleImporterExporterImpl impl); } 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 7fa315f..870c560 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 @@ -9,7 +9,6 @@ import org.apache.logging.log4j.Logger; import java.io.IOException; import java.time.Duration; import java.util.List; -import java.util.concurrent.CompletableFuture; /** * Base implementation of {@link ProcessRunner} @@ -19,19 +18,9 @@ public abstract class AbstractProcessRunner implements ProcessRunner { private static final Logger logger = LogManager.getLogger(AbstractProcessRunner.class); @Override - public ProcessResult run(final List args) throws IOException { + public ProcessResult run(final List args, final Duration duration) throws IOException { final var listener = startListen(args); - CompletableFuture.runAsync(() -> { - try { - var line = listener.readLine(); - while (line != null) { - line = listener.readLine(); - } - } catch (final IOException e) { - logger.error("Error listening to process output of {}", args, e); - } - }); - return listener.join(Duration.ofHours(1)); + return listener.join(duration); } @Override @@ -51,11 +40,12 @@ public abstract class AbstractProcessRunner implements ProcessRunner { /** * Runs a process and writes the output to the log * - * @param args the command + * @param args the command + * @param duration The maximum duration to wait for * @return the result * @throws IOException if an error occurs */ - protected ProcessResult runListen(final List args) throws IOException { + protected ProcessResult runListen(final List args, final Duration duration) throws IOException { final var listener = startListen(args); var line = listener.readLine(); final var processName = args.getFirst(); @@ -63,6 +53,6 @@ public abstract class AbstractProcessRunner implements ProcessRunner { logger.info("[{}]: {}", processName, line); line = listener.readLine(); } - return listener.join(Duration.ofHours(1)); + return listener.join(duration); } } 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 af668ff..b067a11 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 @@ -2,22 +2,25 @@ package com.github.gtache.autosubtitle.process.impl; import com.github.gtache.autosubtitle.process.ProcessListener; import com.github.gtache.autosubtitle.process.ProcessResult; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.time.Duration; -import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; /** * Implementation of {@link ProcessListener} */ public class ProcessListenerImpl implements ProcessListener { - + private static final Logger logger = LogManager.getLogger(ProcessListenerImpl.class); private final Process process; private final BufferedReader reader; private final List output; @@ -30,7 +33,7 @@ public class ProcessListenerImpl implements ProcessListener { public ProcessListenerImpl(final Process process) { this.process = Objects.requireNonNull(process); this.reader = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8)); - this.output = new ArrayList<>(); + this.output = new CopyOnWriteArrayList<>(); } @Override @@ -48,7 +51,24 @@ public class ProcessListenerImpl implements ProcessListener { } @Override - public ProcessResult join(final Duration duration) throws IOException { + public ProcessResult join(final Duration duration) { + //Read to ensure process doesn't get stuck + CompletableFuture.runAsync(() -> { + try { + var line = readLine(); + while (line != null) { + line = readLine(); + } + } catch (final IOException e) { + logger.error("Error listening to process output of {}", process, e); + } finally { + try { + reader.close(); + } catch (final IOException e) { + logger.warn("Error closing reader of {}", process, e); + } + } + }); try { process.waitFor(duration.getSeconds(), TimeUnit.SECONDS); } catch (final InterruptedException e) { @@ -58,11 +78,6 @@ public class ProcessListenerImpl implements ProcessListener { if (process.isAlive()) { process.destroyForcibly(); } - //Reads lines to output - while (readLine() != null) { - //Do nothing - } - reader.close(); return new ProcessResultImpl(process.exitValue(), output); } } 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 bf300a8..16f4304 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 @@ -9,7 +9,6 @@ import com.github.gtache.autosubtitle.subtitle.impl.SubtitleCollectionImpl; import com.github.gtache.autosubtitle.subtitle.impl.SubtitleImpl; import javax.inject.Inject; -import javax.inject.Singleton; import java.util.Arrays; import java.util.Comparator; import java.util.stream.Collectors; @@ -20,17 +19,17 @@ import static java.util.Objects.requireNonNull; /** * Converts subtitles to SRT format */ -@Singleton -public class SRTSubtitleConverter implements SubtitleConverter { +public class SRTSubtitleConverter implements SubtitleConverter { - private final Translator translator; + private final Translator translator; @Inject SRTSubtitleConverter(final Translator translator) { this.translator = requireNonNull(translator); } - public String format(final SubtitleCollection collection) { + @Override + public String format(final SubtitleCollection collection) { final var subtitles = collection.subtitles().stream().sorted(Comparator.comparing(Subtitle::start).thenComparing(Subtitle::end)).toList(); return IntStream.range(0, subtitles.size()).mapToObj(i -> { final var subtitle = subtitles.get(i); @@ -51,7 +50,7 @@ public class SRTSubtitleConverter implements SubtitleConverter { } @Override - public SubtitleCollection parse(final String content) throws ParseException { + public SubtitleCollectionImpl parse(final String content) throws ParseException { try { final var elements = content.split("\n\n"); final var subtitles = Arrays.stream(elements).filter(element -> !element.isBlank()).map(element -> { @@ -66,7 +65,7 @@ public class SRTSubtitleConverter implements SubtitleConverter { return new SubtitleImpl(text, start, end, null, null); }).toList(); final var text = subtitles.stream().map(Subtitle::content).collect(Collectors.joining(" ")); - return new SubtitleCollectionImpl(text, subtitles, translator.getLanguage(text)); + return new SubtitleCollectionImpl<>(text, subtitles, translator.getLanguage(text)); } catch (final Exception e) { throw new ParseException(e); } diff --git a/core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SubtitleCollectionImpl.java b/core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SubtitleCollectionImpl.java index 194b5f6..4b6d538 100644 --- a/core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SubtitleCollectionImpl.java +++ b/core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SubtitleCollectionImpl.java @@ -13,8 +13,8 @@ import static java.util.Objects.requireNonNull; /** * Implementation of {@link SubtitleCollection} */ -public record SubtitleCollectionImpl(String text, Collection subtitles, - Language language) implements SubtitleCollection { +public record SubtitleCollectionImpl(String text, Collection subtitles, + Language language) implements SubtitleCollection { public SubtitleCollectionImpl { Objects.requireNonNull(text); diff --git a/core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SubtitleImpl.java b/core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SubtitleImpl.java index acb864b..5330c7d 100644 --- a/core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SubtitleImpl.java +++ b/core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SubtitleImpl.java @@ -22,4 +22,8 @@ public record SubtitleImpl(String content, long start, long end, Font font, Boun throw new IllegalArgumentException("start must be <= end : " + start + " > " + end); } } + + public SubtitleImpl(final Subtitle subtitle) { + this(subtitle.content(), subtitle.start(), subtitle.end(), subtitle.font(), subtitle.bounds()); + } } diff --git a/core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SubtitleImporterExporterImpl.java b/core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SubtitleImporterExporterImpl.java new file mode 100644 index 0000000..b930f60 --- /dev/null +++ b/core/src/main/java/com/github/gtache/autosubtitle/subtitle/impl/SubtitleImporterExporterImpl.java @@ -0,0 +1,141 @@ +package com.github.gtache.autosubtitle.subtitle.impl; + +import com.github.gtache.autosubtitle.Language; +import com.github.gtache.autosubtitle.archive.Archiver; +import com.github.gtache.autosubtitle.subtitle.SubtitleCollection; +import com.github.gtache.autosubtitle.subtitle.SubtitleImporterExporter; +import com.github.gtache.autosubtitle.subtitle.converter.ParseException; +import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import javax.inject.Inject; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumMap; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Implementation of {@link SubtitleImporterExporter} + */ +public class SubtitleImporterExporterImpl implements SubtitleImporterExporter { + + private static final Logger logger = LogManager.getLogger(SubtitleImporterExporterImpl.class); + private final Map archiverMap; + private final Map> converterMap; + + @Inject + SubtitleImporterExporterImpl(final Map archiverMap, final Map converterMap) { + this.archiverMap = Map.copyOf(archiverMap); + this.converterMap = converterMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } + + @Override + public Map> importSubtitles(final Path file) throws IOException, ParseException { + final var fileName = file.getFileName().toString(); + final var extension = fileName.substring(fileName.lastIndexOf('.') + 1); + if (archiverMap.containsKey(extension)) { + return loadArchive(file); + } else { + final var loaded = loadSingleFile(file); + logger.info("Loaded {}", file); + return Map.of(loaded.language(), loaded); + } + } + + private SubtitleCollection loadSingleFile(final Path file) throws ParseException { + final var fileName = file.getFileName().toString(); + final var extension = fileName.substring(fileName.lastIndexOf('.') + 1); + final var parser = converterMap.get(extension); + if (parser == null) { + throw new ParseException("No converter found for " + file); + } else { + final var parsed = parser.parse(file); + return new SubtitleCollectionImpl<>(parsed.text(), parsed.subtitles().stream().map(SubtitleImpl::new).toList(), parsed.language()); + } + } + + private Map> loadArchive(final Path file) throws IOException, ParseException { + final var fileName = file.getFileName().toString(); + final var extension = fileName.substring(fileName.lastIndexOf('.') + 1); + final var archiver = archiverMap.get(extension); + final var tempDirectory = Files.createTempDirectory("autosubtitle"); + archiver.decompress(file, tempDirectory); + final var files = new ArrayList(); + try (final var stream = Files.list(tempDirectory)) { + stream.forEach(files::add); + } + final var map = new EnumMap>(Language.class); + for (final var path : files) { + final var loaded = loadSingleFile(path); + map.put(loaded.language(), loaded); + Files.deleteIfExists(path); + } + Files.deleteIfExists(tempDirectory); + logger.info("Loaded {}", file); + return map; + } + + @Override + public void exportSubtitles(final Collection> collections, final Path file) throws IOException { + final var fileName = file.getFileName().toString(); + final var extension = fileName.substring(fileName.lastIndexOf('.') + 1); + if (archiverMap.containsKey(extension)) { + saveArchive(file, collections); + } else if (collections.size() == 1) { + saveSingleFile(file, collections.iterator().next()); + } else { + throw new IllegalArgumentException("Cannot export multiple collections to a non-archive file : " + file); + } + } + + private void saveArchive(final Path file, final Iterable> collections) throws IOException { + final var fileName = file.getFileName().toString(); + final var extension = fileName.substring(fileName.lastIndexOf('.') + 1); + final var archiver = archiverMap.get(extension); + final var singleExporter = converterMap.getOrDefault("json", converterMap.values().iterator().next()); + final var tempDir = Files.createTempDirectory("autosubtitle"); + for (final var collection : collections) { + final var subtitleFile = tempDir.resolve(collection.language() + "." + singleExporter.formatName()); + saveSingleFile(subtitleFile, collection); + } + final var files = new ArrayList(); + try (final var stream = Files.list(tempDir)) { + stream.forEach(files::add); + } + archiver.compress(files, file); + for (final var path : files) { + Files.deleteIfExists(path); + } + Files.deleteIfExists(tempDir); + logger.info("Saved {}", file); + } + + private void saveSingleFile(final Path file, final SubtitleCollection collection) throws IOException { + final var fileName = file.getFileName().toString(); + final var extension = fileName.substring(fileName.lastIndexOf('.') + 1); + final var converter = converterMap.get(extension); + if (converter == null) { + throw new IOException("No converter found for " + file); + } else { + final var string = converter.format(collection); + Files.writeString(file, string); + logger.info("Saved {}", file); + } + } + + + @Override + public Collection supportedArchiveExtensions() { + return archiverMap.keySet(); + } + + @Override + public Collection supportedSingleFileExtensions() { + return converterMap.keySet(); + } +} diff --git a/core/src/main/java/module-info.java b/core/src/main/java/module-info.java index d98ee39..2b88992 100644 --- a/core/src/main/java/module-info.java +++ b/core/src/main/java/module-info.java @@ -9,6 +9,7 @@ module com.github.gtache.autosubtitle.core { requires org.apache.logging.log4j; exports com.github.gtache.autosubtitle.impl; + exports com.github.gtache.autosubtitle.archive.impl; exports com.github.gtache.autosubtitle.process.impl; exports com.github.gtache.autosubtitle.setup.impl; exports com.github.gtache.autosubtitle.subtitle.impl; diff --git a/core/src/test/java/com/github/gtache/autosubtitle/archive/impl/TestZipDecompresser.java b/core/src/test/java/com/github/gtache/autosubtitle/archive/impl/TestZipDecompresser.java new file mode 100644 index 0000000..c89c8cf --- /dev/null +++ b/core/src/test/java/com/github/gtache/autosubtitle/archive/impl/TestZipDecompresser.java @@ -0,0 +1,56 @@ +package com.github.gtache.autosubtitle.archive.impl; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.junit.jupiter.api.Assertions.*; + +class TestZipDecompresser { + + private final ZipDecompresser zipDecompresser; + + TestZipDecompresser() { + this.zipDecompresser = new ZipDecompresser(); + } + + @Test + void testIsPathSupported() { + assertTrue(zipDecompresser.isPathSupported(Path.of("test.zip"))); + assertFalse(zipDecompresser.isPathSupported(Path.of("test"))); + assertFalse(zipDecompresser.isPathSupported(Path.of("test.txt"))); + assertFalse(zipDecompresser.isPathSupported(Path.of("test.zip2"))); + } + + @Test + void testDecompress(@TempDir final Path tempDir) throws IOException { + final var file = tempDir.resolve("test.zip"); + try (final var in = getClass().getResourceAsStream("in.zip")) { + Files.copy(in, file); + } + zipDecompresser.decompress(file, tempDir); + final var inTxt = tempDir.resolve("in.txt"); + final var bin = tempDir.resolve("bin"); + final var binTxt = bin.resolve("bin.txt"); + final var lib = tempDir.resolve("lib"); + final var libTxt = lib.resolve("lib.txt"); + + assertTrue(Files.exists(inTxt)); + assertEquals("in", Files.readString(inTxt)); + assertTrue(Files.exists(bin)); + assertTrue(Files.exists(binTxt)); + assertEquals("bin", Files.readString(binTxt)); + assertTrue(Files.exists(lib)); + assertTrue(Files.exists(libTxt)); + assertEquals("lib", Files.readString(libTxt)); + } + + @Test + void testIllegal() { + assertThrows(IllegalArgumentException.class, () -> zipDecompresser.decompress(Paths.get("file.txt"), Paths.get("target"))); + } +} diff --git a/core/src/test/java/com/github/gtache/autosubtitle/process/impl/TestAbstractProcessRunner.java b/core/src/test/java/com/github/gtache/autosubtitle/process/impl/TestAbstractProcessRunner.java index 7e7b8de..f9a0405 100644 --- a/core/src/test/java/com/github/gtache/autosubtitle/process/impl/TestAbstractProcessRunner.java +++ b/core/src/test/java/com/github/gtache/autosubtitle/process/impl/TestAbstractProcessRunner.java @@ -22,7 +22,7 @@ class TestAbstractProcessRunner { @Test void testRun() throws IOException { final var expected = new ProcessResultImpl(0, List.of("1", "2", "3")); - final var actual = dummyProcessRunner.run(ARGS); + final var actual = dummyProcessRunner.run(ARGS, Duration.ofSeconds(5)); assertEquals(expected, actual); } @@ -50,7 +50,7 @@ class TestAbstractProcessRunner { @Test void testRunListen() throws IOException { - final var result = dummyProcessRunner.runListen(ARGS); + final var result = dummyProcessRunner.runListen(ARGS, Duration.ofSeconds(5)); assertEquals(0, result.exitCode()); assertEquals(List.of("1", "2", "3"), result.output()); } diff --git a/core/src/test/java/com/github/gtache/autosubtitle/subtitle/converter/impl/TestSRTSubtitleConverter.java b/core/src/test/java/com/github/gtache/autosubtitle/subtitle/converter/impl/TestSRTSubtitleConverter.java index 13d827a..46e6be1 100644 --- a/core/src/test/java/com/github/gtache/autosubtitle/subtitle/converter/impl/TestSRTSubtitleConverter.java +++ b/core/src/test/java/com/github/gtache/autosubtitle/subtitle/converter/impl/TestSRTSubtitleConverter.java @@ -50,7 +50,7 @@ class TestSRTSubtitleConverter { final var end2 = 12L * 60 * 60 * 1000 + 23 * 60 * 1000 + 34 * 1000 + 457; final var subtitle1 = new SubtitleImpl("test5 test6\ntest7 test8", start1, end1, null, null); final var subtitle2 = new SubtitleImpl("test1 test2\ntest3 test4", start2, end2, null, null); - final var subtitles = new SubtitleCollectionImpl(subtitle1.content() + " " + subtitle2.content(), Arrays.asList(subtitle1, subtitle2), language); + final var subtitles = new SubtitleCollectionImpl<>(subtitle1.content() + " " + subtitle2.content(), Arrays.asList(subtitle1, subtitle2), language); final var converter = new SRTSubtitleConverter(translator); assertEquals(subtitles, converter.parse(in)); assertEquals(in, converter.format(subtitles)); diff --git a/core/src/test/java/com/github/gtache/autosubtitle/subtitle/extractor/impl/TestAbstractSubtitleExtractor.java b/core/src/test/java/com/github/gtache/autosubtitle/subtitle/extractor/impl/TestAbstractSubtitleExtractor.java index b7811a6..2e80774 100644 --- a/core/src/test/java/com/github/gtache/autosubtitle/subtitle/extractor/impl/TestAbstractSubtitleExtractor.java +++ b/core/src/test/java/com/github/gtache/autosubtitle/subtitle/extractor/impl/TestAbstractSubtitleExtractor.java @@ -3,6 +3,7 @@ package com.github.gtache.autosubtitle.subtitle.extractor.impl; import com.github.gtache.autosubtitle.Audio; import com.github.gtache.autosubtitle.Language; import com.github.gtache.autosubtitle.Video; +import com.github.gtache.autosubtitle.subtitle.Subtitle; import com.github.gtache.autosubtitle.subtitle.SubtitleCollection; import com.github.gtache.autosubtitle.subtitle.extractor.ExtractEvent; import com.github.gtache.autosubtitle.subtitle.extractor.ExtractException; @@ -56,12 +57,12 @@ class TestAbstractSubtitleExtractor { private static final class DummySubtitleExtractor extends AbstractSubtitleExtractor { @Override - public SubtitleCollection extract(final Video video, final Language language, final ExtractionModel model) throws ExtractException { + public SubtitleCollection extract(final Video video, final Language language, final ExtractionModel model) throws ExtractException { throw new UnsupportedOperationException(); } @Override - public SubtitleCollection extract(final Audio audio, final Language language, final ExtractionModel model) throws ExtractException { + public SubtitleCollection extract(final Audio audio, final Language language, final ExtractionModel model) throws ExtractException { throw new UnsupportedOperationException(); } } diff --git a/core/src/test/resources/com/github/gtache/autosubtitle/archive/impl/in.txt b/core/src/test/resources/com/github/gtache/autosubtitle/archive/impl/in.txt new file mode 100644 index 0000000..f087d89 --- /dev/null +++ b/core/src/test/resources/com/github/gtache/autosubtitle/archive/impl/in.txt @@ -0,0 +1 @@ +in \ No newline at end of file diff --git a/core/src/test/resources/com/github/gtache/autosubtitle/archive/impl/in.zip b/core/src/test/resources/com/github/gtache/autosubtitle/archive/impl/in.zip new file mode 100644 index 0000000000000000000000000000000000000000..92617925170be028643dfd854a5a2f163fd18eec GIT binary patch literal 314 zcmWIWW@Zs#0D-L=xFX+1sjp%NvO$;|h?6q&^nqBfq@n~!1;EuF1giDxdlSzDR0G0n zKnziatZFw<)fsF4mB=RMWG3kYF~~$972wUtB*K8(exR`oj0_4O3J8!~gRBvoV?fFw m8g+myh|`euU~?KmPY{ra + + + + + + + + + + + + \ No newline at end of file diff --git a/deepl/src/main/java/com/github/gtache/autosubtitle/deepl/DeepLTranslator.java b/deepl/src/main/java/com/github/gtache/autosubtitle/deepl/DeepLTranslator.java index 3fa8cb4..b5b302b 100644 --- a/deepl/src/main/java/com/github/gtache/autosubtitle/deepl/DeepLTranslator.java +++ b/deepl/src/main/java/com/github/gtache/autosubtitle/deepl/DeepLTranslator.java @@ -10,7 +10,7 @@ import javax.inject.Inject; /** * DeepL implementation of {@link Translator} */ -public class DeepLTranslator implements Translator { +public class DeepLTranslator implements Translator { @Inject DeepLTranslator() { @@ -32,7 +32,7 @@ public class DeepLTranslator implements Translator { } @Override - public SubtitleCollection translate(final SubtitleCollection collection, final Language to) { + public SubtitleCollection translate(final SubtitleCollection collection, final Language to) { return null; } } diff --git a/ffmpeg/pom.xml b/ffmpeg/pom.xml index 1ae6047..2f2b087 100644 --- a/ffmpeg/pom.xml +++ b/ffmpeg/pom.xml @@ -24,6 +24,11 @@ org.tukaani xz + + org.apache.logging.log4j + log4j-core + test + \ No newline at end of file diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/TarDecompresser.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/archive/ffmpeg/TarArchiver.java similarity index 73% rename from ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/TarDecompresser.java rename to ffmpeg/src/main/java/com/github/gtache/autosubtitle/archive/ffmpeg/TarArchiver.java index a86568d..6e9bc37 100644 --- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/TarDecompresser.java +++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/archive/ffmpeg/TarArchiver.java @@ -1,17 +1,31 @@ -package com.github.gtache.autosubtitle.setup.ffmpeg; +package com.github.gtache.autosubtitle.archive.ffmpeg; -import org.apache.commons.compress.archivers.tar.TarArchiveEntry; +import com.github.gtache.autosubtitle.archive.Archiver; +import org.apache.commons.compress.archivers.ArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; +import javax.inject.Inject; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; /** - * Tar implementation of {@link Decompresser} + * Tar implementation of {@link Archiver} */ -public class TarDecompresser implements Decompresser { +public class TarArchiver implements Archiver { + + @Inject + TarArchiver() { + + } + + @Override + public void compress(final List files, final Path destination) throws IOException { + throw new UnsupportedOperationException("Not implemented yet"); + } + @Override public void decompress(final Path archive, final Path destination) throws IOException { if (!isPathSupported(archive)) { @@ -38,7 +52,7 @@ public class TarDecompresser implements Decompresser { } } - private static Path newFile(final Path destinationDir, final TarArchiveEntry entry) throws IOException { + private static Path newFile(final Path destinationDir, final ArchiveEntry entry) throws IOException { final var destPath = destinationDir.resolve(entry.getName()); final var destDirPath = destinationDir.toAbsolutePath().toString(); @@ -51,7 +65,7 @@ public class TarDecompresser implements Decompresser { } @Override - public boolean isPathSupported(final Path path) { - return path.getFileName().toString().endsWith(".tar"); + public String archiveExtension() { + return "tar"; } } diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/archive/ffmpeg/XZArchiver.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/archive/ffmpeg/XZArchiver.java new file mode 100644 index 0000000..3c13593 --- /dev/null +++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/archive/ffmpeg/XZArchiver.java @@ -0,0 +1,45 @@ +package com.github.gtache.autosubtitle.archive.ffmpeg; + +import com.github.gtache.autosubtitle.archive.Archiver; +import org.tukaani.xz.XZInputStream; + +import javax.inject.Inject; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +/** + * XZ implementation of {@link Archiver} + */ +public class XZArchiver implements Archiver { + + @Inject + XZArchiver() { + + } + + @Override + public void compress(final List files, final Path destination) throws IOException { + throw new UnsupportedOperationException("Not implemented"); + } + + @Override + public void decompress(final Path archive, final Path destination) throws IOException { + if (!isPathSupported(archive)) { + throw new IllegalArgumentException("Unsupported path : " + archive); + } + final var archiveName = archive.getFileName().toString(); + Files.createDirectories(destination); + final var destinationFile = destination.resolve(archiveName.substring(0, archiveName.lastIndexOf(".xz"))); + try (final var xzIn = new XZInputStream(Files.newInputStream(archive)); + final var out = Files.newOutputStream(destinationFile)) { + xzIn.transferTo(out); + } + } + + @Override + public String archiveExtension() { + return "xz"; + } +} 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 afebd92..4180ac7 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 @@ -8,17 +8,17 @@ import com.github.gtache.autosubtitle.impl.AudioInfoImpl; import com.github.gtache.autosubtitle.impl.FileAudioImpl; import com.github.gtache.autosubtitle.impl.FileVideoImpl; import com.github.gtache.autosubtitle.impl.VideoInfoImpl; -import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegBundledPath; -import com.github.gtache.autosubtitle.modules.ffmpeg.FFmpegSystemPath; +import com.github.gtache.autosubtitle.modules.setup.ffmpeg.FFmpegBundledPath; +import com.github.gtache.autosubtitle.modules.setup.ffmpeg.FFmpegSystemPath; import com.github.gtache.autosubtitle.process.impl.AbstractProcessRunner; import com.github.gtache.autosubtitle.subtitle.SubtitleCollection; import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter; import javax.inject.Inject; -import javax.inject.Singleton; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.time.Duration; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; @@ -32,30 +32,29 @@ import static java.util.Objects.requireNonNull; /** * FFmpeg implementation of {@link VideoConverter} */ -@Singleton public class FFmpegVideoConverter extends AbstractProcessRunner implements VideoConverter { private static final String TEMP_FILE_PREFIX = "autosubtitle"; private final Path bundledPath; private final Path systemPath; - private final SubtitleConverter subtitleConverter; + private final SubtitleConverter subtitleConverter; @Inject FFmpegVideoConverter(@FFmpegBundledPath final Path bundledPath, @FFmpegSystemPath final Path systemPath, final Map subtitleConverters) { this.bundledPath = requireNonNull(bundledPath); this.systemPath = requireNonNull(systemPath); - this.subtitleConverter = requireNonNull(subtitleConverters.get("srt")); + this.subtitleConverter = subtitleConverters.get("srt"); } @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 + public Video addSoftSubtitles(final Video video, final Collection> subtitles) throws IOException { + final var out = getTempFile(video.info().videoFormat()); addSoftSubtitles(video, subtitles, out); - return new FileVideoImpl(out, new VideoInfoImpl("mkv", video.info().width(), video.info().height(), video.info().duration())); + return new FileVideoImpl(out, new VideoInfoImpl(video.info().videoFormat(), video.info().width(), video.info().height(), video.info().duration())); } @Override - public void addSoftSubtitles(final Video video, final Collection subtitles, final Path path) throws IOException { + 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 args = new ArrayList(); @@ -96,18 +95,18 @@ public class FFmpegVideoConverter extends AbstractProcessRunner implements Video args.add("language=" + c.language().iso3()); }); args.add(path.toString()); - runListen(args); + runListen(args, Duration.ofHours(1)); } @Override - public Video addHardSubtitles(final Video video, final SubtitleCollection subtitles) throws IOException { + 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 { + 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 escapedPath = escapeVF(subtitlesPath.toString()); @@ -120,7 +119,7 @@ public class FFmpegVideoConverter extends AbstractProcessRunner implements Video subtitleArg, path.toString() ); - runListen(args); + runListen(args, Duration.ofHours(1)); } private static String escapeVF(final String path) { @@ -145,7 +144,7 @@ public class FFmpegVideoConverter extends AbstractProcessRunner implements Video "0:v", dumpVideoPath.toString() ); - runListen(args); + runListen(args, Duration.ofHours(1)); Files.deleteIfExists(dumpVideoPath); return new FileAudioImpl(audioPath, new AudioInfoImpl("wav", video.info().duration())); } @@ -166,15 +165,15 @@ public class FFmpegVideoConverter extends AbstractProcessRunner implements Video return path; } - private SequencedMap dumpCollections(final Collection collections) throws IOException { - final var ret = LinkedHashMap.newLinkedHashMap(collections.size()); + private > SequencedMap dumpCollections(final Collection collections) throws IOException { + final var ret = LinkedHashMap.newLinkedHashMap(collections.size()); for (final var subtitles : collections) { ret.put(subtitles, dumpSubtitles(subtitles)); } return ret; } - private Path dumpSubtitles(final SubtitleCollection subtitles) throws IOException { + private Path dumpSubtitles(final SubtitleCollection subtitles) throws IOException { final var path = getTempFile("srt"); Files.writeString(path, subtitleConverter.format(subtitles)); return path; diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/ffmpeg/FFprobeVideoLoader.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/ffmpeg/FFprobeVideoLoader.java index 272d55b..97d965e 100644 --- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/ffmpeg/FFprobeVideoLoader.java +++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/ffmpeg/FFprobeVideoLoader.java @@ -4,22 +4,22 @@ import com.github.gtache.autosubtitle.Video; import com.github.gtache.autosubtitle.VideoLoader; import com.github.gtache.autosubtitle.impl.FileVideoImpl; import com.github.gtache.autosubtitle.impl.VideoInfoImpl; -import com.github.gtache.autosubtitle.modules.ffmpeg.FFprobeBundledPath; -import com.github.gtache.autosubtitle.modules.ffmpeg.FFprobeSystemPath; +import com.github.gtache.autosubtitle.modules.setup.ffmpeg.FFprobeBundledPath; +import com.github.gtache.autosubtitle.modules.setup.ffmpeg.FFprobeSystemPath; import com.github.gtache.autosubtitle.process.impl.AbstractProcessRunner; import javax.inject.Inject; -import javax.inject.Singleton; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.time.Duration; +import java.util.List; import static java.util.Objects.requireNonNull; /** * FFprobe implementation of {@link VideoLoader} */ -@Singleton public class FFprobeVideoLoader extends AbstractProcessRunner implements VideoLoader { private final Path bundledPath; @@ -33,7 +33,7 @@ public class FFprobeVideoLoader extends AbstractProcessRunner implements VideoLo @Override public Video loadVideo(final Path path) throws IOException { - final var result = run(getFFprobePath(), "-v", "error", "-select_streams", "v", "-show_entries", "stream=width,height,duration", "-of", "csv=p=0", path.toString()); + final var result = run(List.of(getFFprobePath(), "-v", "error", "-select_streams", "v", "-show_entries", "stream=width,height,duration", "-of", "csv=p=0", path.toString()), Duration.ofSeconds(5)); final var resolution = result.output().getLast(); final var split = resolution.split(","); final var width = Integer.parseInt(split[0]); diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFBundledRoot.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFBundledRoot.java similarity index 86% rename from ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFBundledRoot.java rename to ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFBundledRoot.java index b96beb8..104d206 100644 --- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFBundledRoot.java +++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFBundledRoot.java @@ -1,4 +1,4 @@ -package com.github.gtache.autosubtitle.modules.ffmpeg; +package com.github.gtache.autosubtitle.modules.setup.ffmpeg; import javax.inject.Qualifier; import java.lang.annotation.Documented; 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/setup/ffmpeg/FFProbeInstallerPath.java similarity index 86% rename from ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFProbeInstallerPath.java rename to ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFProbeInstallerPath.java index b9a6142..83d0969 100644 --- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFProbeInstallerPath.java +++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFProbeInstallerPath.java @@ -1,4 +1,4 @@ -package com.github.gtache.autosubtitle.modules.ffmpeg; +package com.github.gtache.autosubtitle.modules.setup.ffmpeg; import javax.inject.Qualifier; import java.lang.annotation.Documented; diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegBundledPath.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFmpegBundledPath.java similarity index 86% rename from ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegBundledPath.java rename to ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFmpegBundledPath.java index 3d16cf7..4b7ef39 100644 --- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegBundledPath.java +++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFmpegBundledPath.java @@ -1,4 +1,4 @@ -package com.github.gtache.autosubtitle.modules.ffmpeg; +package com.github.gtache.autosubtitle.modules.setup.ffmpeg; import javax.inject.Qualifier; import java.lang.annotation.Documented; 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/setup/ffmpeg/FFmpegInstallerPath.java similarity index 86% rename from ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegInstallerPath.java rename to ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFmpegInstallerPath.java index 3e9d794..bd86030 100644 --- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegInstallerPath.java +++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFmpegInstallerPath.java @@ -1,4 +1,4 @@ -package com.github.gtache.autosubtitle.modules.ffmpeg; +package com.github.gtache.autosubtitle.modules.setup.ffmpeg; import javax.inject.Qualifier; import java.lang.annotation.Documented; 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 c617df7..acef204 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,24 +1,17 @@ package com.github.gtache.autosubtitle.modules.setup.ffmpeg; +import com.github.gtache.autosubtitle.archive.Archiver; +import com.github.gtache.autosubtitle.archive.ffmpeg.TarArchiver; +import com.github.gtache.autosubtitle.archive.ffmpeg.XZArchiver; 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; -import com.github.gtache.autosubtitle.modules.ffmpeg.FFprobeSystemPath; import com.github.gtache.autosubtitle.modules.impl.ExecutableExtension; +import com.github.gtache.autosubtitle.modules.setup.impl.CacheRoot; +import com.github.gtache.autosubtitle.modules.setup.impl.ToolsRoot; 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; @@ -41,20 +34,16 @@ public abstract class FFmpegSetupModule { } - @Binds - @StringKey("zip") - @IntoMap - abstract Decompresser bindsZipDecompresser(final ZipDecompresser decompresser); @Binds @StringKey("tar") @IntoMap - abstract Decompresser bindsTarDecompresser(final TarDecompresser decompresser); + abstract Archiver bindsTarDecompresser(final TarArchiver decompresser); @Binds @StringKey("xz") @IntoMap - abstract Decompresser bindsXzDecompresser(final XZDecompresser decompresser); + abstract Archiver bindsXZDecompresser(final XZArchiver decompresser); @Binds @VideoConverterSetup @@ -69,19 +58,19 @@ public abstract class FFmpegSetupModule { @Provides @FFmpegInstallerPath - static Path providesFFmpegInstallerPath(@FFBundledRoot final Path root, final OS os) { - return root.resolve("cache").resolve("ffmpeg-installer" + getInstallerExtension(os)); + static Path providesFFmpegInstallerPath(@CacheRoot final Path root, final OS os) { + return root.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)); + static Path providesFFProbeInstallerPath(@CacheRoot final Path root, final OS os) { + return root.resolve(FFPROBE + "-installer" + getInstallerExtension(os)); } private static String getInstallerExtension(final OS os) { if (os == OS.LINUX) { - return ".tar.gz"; + return ".tar.xz"; } else { return ".zip"; } @@ -89,8 +78,8 @@ public abstract class FFmpegSetupModule { @Provides @FFBundledRoot - static Path providesFFBundledRoot() { - return Paths.get("tools", FFMPEG); + static Path providesFFBundledRoot(@ToolsRoot final Path root) { + return root.resolve(FFMPEG); } @Provides diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegSystemPath.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFmpegSystemPath.java similarity index 86% rename from ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegSystemPath.java rename to ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFmpegSystemPath.java index 6b58fa3..c393a77 100644 --- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegSystemPath.java +++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFmpegSystemPath.java @@ -1,4 +1,4 @@ -package com.github.gtache.autosubtitle.modules.ffmpeg; +package com.github.gtache.autosubtitle.modules.setup.ffmpeg; import javax.inject.Qualifier; import java.lang.annotation.Documented; diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegVersion.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFmpegVersion.java similarity index 86% rename from ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegVersion.java rename to ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFmpegVersion.java index a883339..c798f05 100644 --- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFmpegVersion.java +++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFmpegVersion.java @@ -1,4 +1,4 @@ -package com.github.gtache.autosubtitle.modules.ffmpeg; +package com.github.gtache.autosubtitle.modules.setup.ffmpeg; import javax.inject.Qualifier; import java.lang.annotation.Documented; diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFprobeBundledPath.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFprobeBundledPath.java similarity index 86% rename from ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFprobeBundledPath.java rename to ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFprobeBundledPath.java index 9869e9e..df69cf1 100644 --- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFprobeBundledPath.java +++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFprobeBundledPath.java @@ -1,4 +1,4 @@ -package com.github.gtache.autosubtitle.modules.ffmpeg; +package com.github.gtache.autosubtitle.modules.setup.ffmpeg; import javax.inject.Qualifier; import java.lang.annotation.Documented; diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFprobeSystemPath.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFprobeSystemPath.java similarity index 86% rename from ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFprobeSystemPath.java rename to ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFprobeSystemPath.java index b1ec1a6..f63e040 100644 --- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/ffmpeg/FFprobeSystemPath.java +++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/FFprobeSystemPath.java @@ -1,4 +1,4 @@ -package com.github.gtache.autosubtitle.modules.ffmpeg; +package com.github.gtache.autosubtitle.modules.setup.ffmpeg; import javax.inject.Qualifier; import java.lang.annotation.Documented; 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 deleted file mode 100644 index d93f9db..0000000 --- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/Decompresser.java +++ /dev/null @@ -1,27 +0,0 @@ -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/FFmpegSetupManager.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/FFmpegSetupManager.java index 81a6af2..9ec7533 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,5 +1,6 @@ package com.github.gtache.autosubtitle.setup.ffmpeg; +import com.github.gtache.autosubtitle.archive.Archiver; import com.github.gtache.autosubtitle.impl.Architecture; import com.github.gtache.autosubtitle.setup.SetupException; import com.github.gtache.autosubtitle.setup.SetupManager; @@ -13,6 +14,8 @@ import java.io.IOException; import java.net.http.HttpClient; import java.nio.file.Files; import java.nio.file.Path; +import java.time.Duration; +import java.util.List; import java.util.Map; import static com.github.gtache.autosubtitle.impl.Architecture.ARMEL; @@ -26,14 +29,14 @@ import static java.util.Objects.requireNonNull; public class FFmpegSetupManager extends AbstractSetupManager { private static final Logger logger = LogManager.getLogger(FFmpegSetupManager.class); private final FFmpegSetupConfiguration configuration; - private final Map decompressers; + private final Map archivers; @Inject - FFmpegSetupManager(final FFmpegSetupConfiguration configuration, final Map decompressers, + FFmpegSetupManager(final FFmpegSetupConfiguration configuration, final Map archivers, final HttpClient httpClient) { super(httpClient); this.configuration = requireNonNull(configuration); - this.decompressers = Map.copyOf(decompressers); + this.archivers = Map.copyOf(archivers); } @Override @@ -132,7 +135,7 @@ public class FFmpegSetupManager extends AbstractSetupManager { try { final var filename = from.getFileName().toString(); final var extension = filename.substring(filename.lastIndexOf('.') + 1); - decompressers.get(extension).decompress(from, to); + archivers.get(extension).decompress(from, to); } catch (final IOException e) { throw new SetupException(e); } @@ -188,7 +191,7 @@ public class FFmpegSetupManager extends AbstractSetupManager { } private boolean checkSystemFFmpeg() throws IOException { - final var result = run(configuration.systemFFmpegPath().toString(), "-version"); + final var result = run(List.of(configuration.systemFFmpegPath().toString(), "-version"), Duration.ofSeconds(5)); return result.exitCode() == 0; } 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 deleted file mode 100644 index 81b4e77..0000000 --- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/XZDecompresser.java +++ /dev/null @@ -1,28 +0,0 @@ -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/module-info.java b/ffmpeg/src/main/java/module-info.java index 59d58fa..4605994 100644 --- a/ffmpeg/src/main/java/module-info.java +++ b/ffmpeg/src/main/java/module-info.java @@ -15,4 +15,5 @@ module com.github.gtache.autosubtitle.ffmpeg { exports com.github.gtache.autosubtitle.modules.ffmpeg; exports com.github.gtache.autosubtitle.modules.setup.ffmpeg; + exports com.github.gtache.autosubtitle.archive.ffmpeg; } \ No newline at end of file diff --git a/ffmpeg/src/test/java/com/github/gtache/autosubtitle/archive/ffmpeg/TestTarArchiver.java b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/archive/ffmpeg/TestTarArchiver.java new file mode 100644 index 0000000..0b1e818 --- /dev/null +++ b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/archive/ffmpeg/TestTarArchiver.java @@ -0,0 +1,56 @@ +package com.github.gtache.autosubtitle.archive.ffmpeg; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.junit.jupiter.api.Assertions.*; + +class TestTarArchiver { + + private final TarArchiver tarArchiver; + + TestTarArchiver() { + this.tarArchiver = new TarArchiver(); + } + + @Test + void testIsPathSupported() { + assertTrue(tarArchiver.isPathSupported(Path.of("test.tar"))); + assertFalse(tarArchiver.isPathSupported(Path.of("test"))); + assertFalse(tarArchiver.isPathSupported(Path.of("test.txt"))); + assertFalse(tarArchiver.isPathSupported(Path.of("test.tar2"))); + } + + @Test + void testDecompress(@TempDir final Path tempDir) throws IOException { + final var file = tempDir.resolve("test.tar"); + try (final var in = getClass().getResourceAsStream("in.tar")) { + Files.copy(in, file); + } + tarArchiver.decompress(file, tempDir); + final var inTxt = tempDir.resolve("in.txt"); + final var bin = tempDir.resolve("bin"); + final var binTxt = bin.resolve("bin.txt"); + final var lib = tempDir.resolve("lib"); + final var libTxt = lib.resolve("lib.txt"); + + assertTrue(Files.exists(inTxt)); + assertEquals("in", Files.readString(inTxt)); + assertTrue(Files.exists(bin)); + assertTrue(Files.exists(binTxt)); + assertEquals("bin", Files.readString(binTxt)); + assertTrue(Files.exists(lib)); + assertTrue(Files.exists(libTxt)); + assertEquals("lib", Files.readString(libTxt)); + } + + @Test + void testIllegal() { + assertThrows(IllegalArgumentException.class, () -> tarArchiver.decompress(Paths.get("file.txt"), Paths.get("target"))); + } +} diff --git a/ffmpeg/src/test/java/com/github/gtache/autosubtitle/archive/ffmpeg/TestXZArchiver.java b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/archive/ffmpeg/TestXZArchiver.java new file mode 100644 index 0000000..14ba0f6 --- /dev/null +++ b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/archive/ffmpeg/TestXZArchiver.java @@ -0,0 +1,46 @@ +package com.github.gtache.autosubtitle.archive.ffmpeg; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.junit.jupiter.api.Assertions.*; + +class TestXZArchiver { + + private final XZArchiver xzArchiver; + + TestXZArchiver() { + this.xzArchiver = new XZArchiver(); + } + + @Test + void testIsPathSupported() { + assertTrue(xzArchiver.isPathSupported(Path.of("test.xz"))); + assertFalse(xzArchiver.isPathSupported(Path.of("test"))); + assertFalse(xzArchiver.isPathSupported(Path.of("test.txt"))); + assertFalse(xzArchiver.isPathSupported(Path.of("test.xz2"))); + } + + @Test + void testDecompress(@TempDir final Path tempDir) throws IOException { + final var file = tempDir.resolve("in.txt.xz"); + try (final var in = getClass().getResourceAsStream("in.txt.xz")) { + Files.copy(in, file); + } + xzArchiver.decompress(file, tempDir); + final var inTxt = tempDir.resolve("in.txt"); + + assertTrue(Files.isRegularFile(inTxt)); + assertEquals("in", Files.readString(inTxt)); + } + + @Test + void testIllegal() { + assertThrows(IllegalArgumentException.class, () -> xzArchiver.decompress(Paths.get("file.txt"), Paths.get("target"))); + } +} diff --git a/ffmpeg/src/test/java/com/github/gtache/autosubtitle/ffmpeg/TestFFmpegVideoConverter.java b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/ffmpeg/TestFFmpegVideoConverter.java new file mode 100644 index 0000000..7ce2845 --- /dev/null +++ b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/ffmpeg/TestFFmpegVideoConverter.java @@ -0,0 +1,57 @@ +package com.github.gtache.autosubtitle.ffmpeg; + +import com.github.gtache.autosubtitle.Video; +import com.github.gtache.autosubtitle.VideoInfo; +import com.github.gtache.autosubtitle.impl.OS; +import com.github.gtache.autosubtitle.subtitle.SubtitleCollection; +import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.Map; +import java.util.Objects; + +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class TestFFmpegVideoConverter { + + private final FFmpegVideoConverter converter; + private final SubtitleConverter subtitleConverter; + private final Video video; + private final VideoInfo videoInfo; + private final Path tmpFile; + private final Path outputPath; + private final SubtitleCollection collection; + + TestFFmpegVideoConverter(@Mock final SubtitleConverter subtitleConverter, @Mock final Video video, + @Mock final VideoInfo videoInfo, @Mock final SubtitleCollection collection) throws IOException { + final var output = (OS.getOS() == OS.WINDOWS ? System.getProperty("java.io.tmpdir") : "/tmp"); + final var resource = OS.getOS() == OS.WINDOWS ? "fake-ffmpeg.exe" : "fake-ffmpeg.sh"; + this.video = Objects.requireNonNull(video); + this.videoInfo = Objects.requireNonNull(videoInfo); + when(video.info()).thenReturn(videoInfo); + this.tmpFile = Files.createTempFile("fake-ffmpeg", resource.substring(resource.lastIndexOf('.'))); + try (final var in = getClass().getResourceAsStream(resource)) { + Files.copy(in, tmpFile, StandardCopyOption.REPLACE_EXISTING); + } + this.outputPath = Path.of(output, "test-ffmpeg-output.txt"); + this.subtitleConverter = Objects.requireNonNull(subtitleConverter); + this.converter = new FFmpegVideoConverter(tmpFile, tmpFile, Map.of("srt", subtitleConverter)); + this.collection = Objects.requireNonNull(collection); + } + + @AfterEach + void afterEach() throws IOException { + Files.deleteIfExists(tmpFile); + Files.deleteIfExists(outputPath); + } + + //TODO tests +} diff --git a/ffmpeg/src/test/java/com/github/gtache/autosubtitle/ffmpeg/TestFFmpegVideoLoader.java b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/ffmpeg/TestFFmpegVideoLoader.java new file mode 100644 index 0000000..faef35e --- /dev/null +++ b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/ffmpeg/TestFFmpegVideoLoader.java @@ -0,0 +1,51 @@ +package com.github.gtache.autosubtitle.ffmpeg; + +import com.github.gtache.autosubtitle.impl.FileVideoImpl; +import com.github.gtache.autosubtitle.impl.OS; +import com.github.gtache.autosubtitle.impl.VideoInfoImpl; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class TestFFmpegVideoLoader { + + private final FFprobeVideoLoader loader; + private final Path tmpFile; + private final Path outputPath; + + TestFFmpegVideoLoader() throws IOException { + final var output = (OS.getOS() == OS.WINDOWS ? System.getProperty("java.io.tmpdir") : "/tmp"); + final var resource = OS.getOS() == OS.WINDOWS ? "fake-ffprobe.exe" : "fake-ffprobe.sh"; + this.tmpFile = Files.createTempFile("fake-ffprobe", resource.substring(resource.lastIndexOf('.'))); + try (final var in = getClass().getResourceAsStream(resource)) { + Files.copy(in, tmpFile, StandardCopyOption.REPLACE_EXISTING); + } + this.outputPath = Path.of(output, "test-ffprobe-output.txt"); + this.loader = new FFprobeVideoLoader(tmpFile, tmpFile); + } + + @AfterEach + void afterEach() throws IOException { + Files.deleteIfExists(tmpFile); + Files.deleteIfExists(outputPath); + } + + @Test + @Disabled("Doesn't work") + void testLoadVideo() throws IOException { + final var in = Paths.get("in.mp4"); + final var expectedInfo = new VideoInfoImpl("mp4", 1920, 1080, 25000L); + final var expected = new FileVideoImpl(in, expectedInfo); + final var result = loader.loadVideo(in); + assertEquals(expected, result); + assertEquals("-v error -select_streams v -show_entries stream=width,height,duration -of csv=p=0 in.mp4", Files.readString(outputPath)); + } +} diff --git a/ffmpeg/src/test/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/TestFFmpegSetupModule.java b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/TestFFmpegSetupModule.java new file mode 100644 index 0000000..a685128 --- /dev/null +++ b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/modules/setup/ffmpeg/TestFFmpegSetupModule.java @@ -0,0 +1,84 @@ +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.setup.ffmpeg.FFmpegSetupConfiguration; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import static com.github.gtache.autosubtitle.impl.OS.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; + +class TestFFmpegSetupModule { + + private static final String FFMPEG = "ffmpeg"; + private static final String FFPROBE = "ffprobe"; + + private final Path root; + private final String extension; + + TestFFmpegSetupModule() { + this.root = Paths.get("root"); + this.extension = "ext"; + } + + @Test + void testFFmpegSetupConfiguration() { + final var bundledPath = mock(Path.class); + final var systemPath = mock(Path.class); + final var ffmpegInstallerPath = mock(Path.class); + final var ffprobeInstallerPath = mock(Path.class); + final var os = mock(OS.class); + final var architecture = mock(Architecture.class); + final var expected = new FFmpegSetupConfiguration(root, bundledPath, systemPath, ffmpegInstallerPath, ffprobeInstallerPath, os, architecture); + assertEquals(expected, FFmpegSetupModule.providesFFmpegSetupConfiguration(root, bundledPath, systemPath, ffmpegInstallerPath, ffprobeInstallerPath, os, architecture)); + } + + @Test + void testProvidesFFmpegInstallerPath() { + assertEquals(root.resolve(FFMPEG + "-installer.tar.xz"), FFmpegSetupModule.providesFFmpegInstallerPath(root, LINUX)); + assertEquals(root.resolve(FFMPEG + "-installer.zip"), FFmpegSetupModule.providesFFmpegInstallerPath(root, WINDOWS)); + assertEquals(root.resolve(FFMPEG + "-installer.zip"), FFmpegSetupModule.providesFFmpegInstallerPath(root, MAC)); + } + + @Test + void testProvidesFFProbeInstallerPath() { + assertEquals(root.resolve(FFPROBE + "-installer.tar.xz"), FFmpegSetupModule.providesFFProbeInstallerPath(root, LINUX)); + assertEquals(root.resolve(FFPROBE + "-installer.zip"), FFmpegSetupModule.providesFFProbeInstallerPath(root, WINDOWS)); + assertEquals(root.resolve(FFPROBE + "-installer.zip"), FFmpegSetupModule.providesFFProbeInstallerPath(root, MAC)); + } + + @Test + void testProvidesBundledRoot() { + assertEquals(root.resolve(FFMPEG), FFmpegSetupModule.providesFFBundledRoot(root)); + } + + @Test + void testFFprobeBundledPath() { + assertEquals(root.resolve(FFPROBE + extension), FFmpegSetupModule.providesFFProbeBundledPath(root, extension)); + } + + @Test + void testFFprobeSystemPath() { + assertEquals(Paths.get(FFPROBE + extension), FFmpegSetupModule.providesFFProbeSystemPath(extension)); + } + + + @Test + void testFFmpegBundledPath() { + assertEquals(root.resolve(FFMPEG + extension), FFmpegSetupModule.providesFFmpegBundledPath(root, extension)); + } + + @Test + void testFFmpegSystemPath() { + assertEquals(Paths.get(FFMPEG + extension), FFmpegSetupModule.providesFFmpegSystemPath(extension)); + } + + @Test + void testVersion() { + assertEquals("7.0.1", FFmpegSetupModule.providesFFmpegVersion()); + } +} diff --git a/ffmpeg/src/test/java/com/github/gtache/autosubtitle/setup/ffmpeg/TestFFmpegSetupConfiguration.java b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/setup/ffmpeg/TestFFmpegSetupConfiguration.java new file mode 100644 index 0000000..6652de6 --- /dev/null +++ b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/setup/ffmpeg/TestFFmpegSetupConfiguration.java @@ -0,0 +1,62 @@ +package com.github.gtache.autosubtitle.setup.ffmpeg; + +import com.github.gtache.autosubtitle.impl.Architecture; +import com.github.gtache.autosubtitle.impl.OS; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.nio.file.Path; + +import static java.util.Objects.requireNonNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@ExtendWith(MockitoExtension.class) +class TestFFmpegSetupConfiguration { + + private final Path root; + private final Path bundledFFmpegPath; + private final Path systemFFmpegPath; + private final Path ffmpegInstallerPath; + private final Path ffprobeInstallerPath; + private final OS os; + private final Architecture architecture; + + TestFFmpegSetupConfiguration(@Mock final Path root, @Mock final Path bundledFFmpegPath, + @Mock final Path systemFFmpegPath, @Mock final Path ffmpegInstallerPath, + @Mock final Path ffprobeInstallerPath, @Mock final OS os, + @Mock final Architecture architecture) { + this.root = requireNonNull(root); + this.bundledFFmpegPath = requireNonNull(bundledFFmpegPath); + this.systemFFmpegPath = requireNonNull(systemFFmpegPath); + this.ffmpegInstallerPath = requireNonNull(ffmpegInstallerPath); + this.ffprobeInstallerPath = requireNonNull(ffprobeInstallerPath); + this.os = requireNonNull(os); + this.architecture = requireNonNull(architecture); + } + + @Test + void testGetters() { + final var config = new FFmpegSetupConfiguration(root, bundledFFmpegPath, systemFFmpegPath, ffmpegInstallerPath, ffprobeInstallerPath, os, architecture); + assertEquals(root, config.root()); + assertEquals(bundledFFmpegPath, config.bundledFFmpegPath()); + assertEquals(systemFFmpegPath, config.systemFFmpegPath()); + assertEquals(ffmpegInstallerPath, config.ffmpegInstallerPath()); + assertEquals(ffprobeInstallerPath, config.ffprobeInstallerPath()); + assertEquals(os, config.os()); + assertEquals(architecture, config.architecture()); + } + + @Test + void testIllegal() { + assertThrows(NullPointerException.class, () -> new FFmpegSetupConfiguration(null, bundledFFmpegPath, systemFFmpegPath, ffmpegInstallerPath, ffprobeInstallerPath, os, architecture)); + assertThrows(NullPointerException.class, () -> new FFmpegSetupConfiguration(root, null, systemFFmpegPath, ffmpegInstallerPath, ffprobeInstallerPath, os, architecture)); + assertThrows(NullPointerException.class, () -> new FFmpegSetupConfiguration(root, bundledFFmpegPath, null, ffmpegInstallerPath, ffprobeInstallerPath, os, architecture)); + assertThrows(NullPointerException.class, () -> new FFmpegSetupConfiguration(root, bundledFFmpegPath, systemFFmpegPath, null, ffprobeInstallerPath, os, architecture)); + assertThrows(NullPointerException.class, () -> new FFmpegSetupConfiguration(root, bundledFFmpegPath, systemFFmpegPath, ffmpegInstallerPath, null, os, architecture)); + assertThrows(NullPointerException.class, () -> new FFmpegSetupConfiguration(root, bundledFFmpegPath, systemFFmpegPath, ffmpegInstallerPath, ffprobeInstallerPath, null, architecture)); + assertThrows(NullPointerException.class, () -> new FFmpegSetupConfiguration(root, bundledFFmpegPath, systemFFmpegPath, ffmpegInstallerPath, ffprobeInstallerPath, os, null)); + } +} diff --git a/ffmpeg/src/test/java/com/github/gtache/autosubtitle/setup/ffmpeg/TestFFmpegSetupManager.java b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/setup/ffmpeg/TestFFmpegSetupManager.java new file mode 100644 index 0000000..e0cf5bc --- /dev/null +++ b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/setup/ffmpeg/TestFFmpegSetupManager.java @@ -0,0 +1,4 @@ +package com.github.gtache.autosubtitle.setup.ffmpeg; + +class TestFFmpegSetupManager { +} diff --git a/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/archive/ffmpeg/bin.txt b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/archive/ffmpeg/bin.txt new file mode 100644 index 0000000..c5e82d7 --- /dev/null +++ b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/archive/ffmpeg/bin.txt @@ -0,0 +1 @@ +bin \ No newline at end of file diff --git a/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/archive/ffmpeg/in.tar b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/archive/ffmpeg/in.tar new file mode 100644 index 0000000000000000000000000000000000000000..a565fa1732e6aa65cc59047f28976fe754b116d7 GIT binary patch literal 10240 zcmeH}(F(#a3`P4X{Q_;*cKsgrHjoYG1V7&d1jUE#DP`g*l)?&0I5#)lIGxtV>P%OV zRcm#6%KAR#Q|DstOQ|`BBKI-Wl${TzRlS1WZ|i08-YxUIiDB=)MXd%o#>pTkA%BLz zFivOdV%7B9{ts{ecTxSHa&W#_7pC$5-2bY-Y|}?zU;p%>e-Zs-Un29rMf;2PEz*$V zpaibtaIE>C)c-R76Z5}C%c%ack2C+XzW=dT{rUg5?*P%>J_#8h009U<00Izz00bZa b0SG_<0uX=z1Rwwb2tWV=5P$##nh|&c0^47* literal 0 HcmV?d00001 diff --git a/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/archive/ffmpeg/in.txt b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/archive/ffmpeg/in.txt new file mode 100644 index 0000000..f087d89 --- /dev/null +++ b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/archive/ffmpeg/in.txt @@ -0,0 +1 @@ +in \ No newline at end of file diff --git a/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/archive/ffmpeg/in.txt.xz b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/archive/ffmpeg/in.txt.xz new file mode 100644 index 0000000000000000000000000000000000000000..d6019bd45c3a55dd81954dce3928a5bbbe0f6d28 GIT binary patch literal 60 zcmexsUKJ6=z`*kC+7>q^21Q0O1_p)_{ill=85lG3fb1_n8zh~%%oG?HrI_yMEv=K^ O_PLf3D9*qV83h0vsSwEk literal 0 HcmV?d00001 diff --git a/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/archive/ffmpeg/lib.txt b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/archive/ffmpeg/lib.txt new file mode 100644 index 0000000..7951405 --- /dev/null +++ b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/archive/ffmpeg/lib.txt @@ -0,0 +1 @@ +lib \ No newline at end of file diff --git a/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffmpeg.exe b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffmpeg.exe new file mode 100644 index 0000000000000000000000000000000000000000..2bc1a2118a074cc459b77ec620273154899e3fd0 GIT binary patch literal 25600 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 $Output +exit \ No newline at end of file diff --git a/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffmpeg.sh b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffmpeg.sh new file mode 100644 index 0000000..b6cbfe0 --- /dev/null +++ b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffmpeg.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -o errexit -o noclobber -o pipefail -o nounset + +output=/tmp/test-ffmpeg-output.txt + +echo "$@" >| $output \ No newline at end of file diff --git a/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffprobe.exe b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffprobe.exe new file mode 100644 index 0000000000000000000000000000000000000000..60c6ac71db68f6ee6759fdf2421e4ea0061cdd92 GIT binary patch literal 25600 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 $Output +Write-Host "1920,1080,25" +exit \ No newline at end of file diff --git a/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffprobe.sh b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffprobe.sh new file mode 100644 index 0000000..438d99f --- /dev/null +++ b/ffmpeg/src/test/resources/com/github/gtache/autosubtitle/ffmpeg/fake-ffprobe.sh @@ -0,0 +1,7 @@ +#!/bin/bash +set -o errexit -o noclobber -o pipefail -o nounset + +output=/tmp/test-ffprobe-output.txt + +echo "$@" >| $output +echo "1920,1080,25" \ No newline at end of file diff --git a/ffmpeg/src/test/resources/log4j2-test.xml b/ffmpeg/src/test/resources/log4j2-test.xml new file mode 100644 index 0000000..98cfd73 --- /dev/null +++ b/ffmpeg/src/test/resources/log4j2-test.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/MainModel.java b/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/MainModel.java index ac1dc07..e69d43d 100644 --- a/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/MainModel.java +++ b/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/MainModel.java @@ -13,5 +13,5 @@ public interface MainModel { /** * @param index The index of the tab to select */ - void selectTab(int index); + void setSelectedTab(int index); } diff --git a/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/SetupModel.java b/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/SetupModel.java index 4352515..e384775 100644 --- a/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/SetupModel.java +++ b/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/SetupModel.java @@ -19,20 +19,6 @@ public interface SetupModel { */ void setSubtitleExtractorStatus(SetupStatus status); - /** - * @return whether the subtitle extractor is installed - */ - default boolean isSubtitleExtractorInstalled() { - return subtitleExtractorStatus().isInstalled(); - } - - /** - * @return whether an update is available for the subtitle extractor - */ - default boolean isSubtitleExtractorUpdateAvailable() { - return subtitleExtractorStatus() == SetupStatus.UPDATE_AVAILABLE; - } - /** * @return the progress of the subtitle extractor setup */ @@ -69,20 +55,6 @@ public interface SetupModel { */ void setVideoConverterStatus(SetupStatus status); - /** - * @return whether the video converter is installed - */ - default boolean isVideoConverterInstalled() { - return videoConverterStatus().isInstalled(); - } - - /** - * @return whether an update is available for the video converter - */ - default boolean isVideoConverterUpdateAvailable() { - return videoConverterStatus() == SetupStatus.UPDATE_AVAILABLE; - } - /** * @return the progress of the video converter setup */ @@ -119,20 +91,6 @@ public interface SetupModel { */ void setTranslatorStatus(SetupStatus status); - /** - * @return whether the translator is installed - */ - default boolean isTranslatorInstalled() { - return translatorStatus().isInstalled(); - } - - /** - * @return whether an update is available for the translator - */ - default boolean isTranslatorUpdateAvailable() { - return translatorStatus() == SetupStatus.UPDATE_AVAILABLE; - } - /** * @return the progress of the translator setup */ diff --git a/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/SubtitlesController.java b/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/SubtitlesController.java new file mode 100644 index 0000000..d6987fc --- /dev/null +++ b/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/SubtitlesController.java @@ -0,0 +1,44 @@ +package com.github.gtache.autosubtitle.gui; + +import com.github.gtache.autosubtitle.Language; + +import java.nio.file.Path; + +/** + * Controller for the subtitles view + */ +public interface SubtitlesController { + + /** + * Selects the given language for edition + * + * @param language The language + */ + void selectLanguage(final Language language); + + /** + * Deletes a language + * + * @param language The language + */ + void deleteLanguage(final Language language); + + /** + * Saves the subtitles to the given path + * + * @param file The output path + */ + void saveSubtitles(final Path file); + + /** + * Loads a subtitles file + * + * @param file The path to the file + */ + void loadSubtitles(final Path file); + + /** + * @return the model + */ + SubtitlesModel model(); +} diff --git a/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/SubtitlesModel.java b/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/SubtitlesModel.java new file mode 100644 index 0000000..1dec1d9 --- /dev/null +++ b/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/SubtitlesModel.java @@ -0,0 +1,146 @@ +package com.github.gtache.autosubtitle.gui; + +import com.github.gtache.autosubtitle.Language; +import com.github.gtache.autosubtitle.subtitle.Subtitle; +import com.github.gtache.autosubtitle.subtitle.SubtitleCollection; + +import java.util.List; +import java.util.Map; + +/** + * Model for the subtitles view + * + * @param The type of subtitle + * @param The type of subtitle collection + */ +public interface SubtitlesModel> { + + /** + * @return The list of available video languages + */ + List availableVideoLanguages(); + + /** + * @return the video language + */ + Language videoLanguage(); + + /** + * Sets the video language + * + * @param language The new language + */ + void setVideoLanguage(Language language); + + /** + * @return The list of available translations languages + */ + List availableTranslationsLanguage(); + + /** + * @return The list of selected translations languages + */ + List selectedTranslationsLanguages(); + + /** + * @return The currently selected language + */ + Language selectedLanguage(); + + /** + * @param language The new selected language + */ + void setSelectedLanguage(Language language); + + /** + * @return The mapping of language to subtitles + */ + Map collections(); + + /** + * @return The currently selected collection + */ + U selectedCollection(); + + /** + * @param collection The new selected collection + */ + void setSelectedCollection(U collection); + + /** + * @return The mapping of language to subtitles + */ + Map originalCollections(); + + /** + * @return The list of selected subtitles + */ + List selectedSubtitles(); + + /** + * @return The currently selected subtitle + */ + T selectedSubtitle(); + + /** + * @param subtitle The new selected subtitle + */ + void setSelectedSubtitle(T subtitle); + + /** + * @return Whether the user can load subtitles + */ + boolean canLoadSubtitles(); + + /** + * @param canLoadSubtitles Whether the user can load subtitles + */ + void setCanLoadSubtitles(boolean canLoadSubtitles); + + /** + * @return Whether the user can add subtitles + */ + boolean canAddSubtitle(); + + /** + * @param canAddSubtitle Whether the user can add subtitles + */ + void setCanAddSubtitle(boolean canAddSubtitle); + + /** + * @return Whether the user can reset subtitles + */ + boolean canResetSubtitles(); + + /** + * @param canResetSubtitles Whether the user can reset subtitles + */ + void setCanResetSubtitles(boolean canResetSubtitles); + + /** + * @return Whether the user can save subtitles + */ + boolean canSaveSubtitles(); + + /** + * @return Whether subtitles are currently being translated + */ + boolean isTranslating(); + + /** + * @param translating Whether subtitles are currently being translated + */ + void setTranslating(boolean translating); + + /** + * @return Whether the user can edit the table + */ + boolean canEditTable(); + + /** + * Sets whether the user can edit the table + * + * @param canEditTable Whether the user can edit the table + */ + void setCanEditTable(boolean canEditTable); +} diff --git a/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/WorkController.java b/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/WorkController.java index a066374..c9521a0 100644 --- a/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/WorkController.java +++ b/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/WorkController.java @@ -18,21 +18,7 @@ public interface WorkController { * @param file The path to the video */ void loadVideo(final Path file); - - /** - * Saves the subtitles to the given path - * - * @param file The output path - */ - void saveSubtitles(final Path file); - - /** - * Loads a subtitles file - * - * @param file The path to the file - */ - void loadSubtitles(final Path file); - + /** * @return The model */ diff --git a/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/WorkModel.java b/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/WorkModel.java index e5fb221..1e6d01a 100644 --- a/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/WorkModel.java +++ b/gui/api/src/main/java/com/github/gtache/autosubtitle/gui/WorkModel.java @@ -1,13 +1,9 @@ package com.github.gtache.autosubtitle.gui; -import com.github.gtache.autosubtitle.Language; import com.github.gtache.autosubtitle.Video; -import com.github.gtache.autosubtitle.subtitle.EditableSubtitle; import com.github.gtache.autosubtitle.subtitle.SubtitleCollection; import com.github.gtache.autosubtitle.subtitle.extractor.ExtractionModel; -import java.util.List; - /** * Model for the main view */ @@ -18,6 +14,11 @@ public interface WorkModel { */ Video video(); + /** + * @param video The new video + */ + void setVideo(Video video); + /** * @return The current extraction model */ @@ -28,56 +29,6 @@ public interface WorkModel { */ void setExtractionModel(ExtractionModel model); - /** - * @return The current subtitle collection - */ - SubtitleCollection subtitleCollection(); - - /** - * @return The current list of subtitles - */ - List subtitles(); - - /** - * @return The current text - */ - String text(); - - /** - * @return The original extracted subtitles (used to reset) - */ - List originalSubtitles(); - - /** - * @return The currently selected subtitle - */ - EditableSubtitle selectedSubtitle(); - - /** - * @return The list of available video languages - */ - List availableVideoLanguages(); - - /** - * @return The list of available translations languages - */ - List availableTranslationsLanguage(); - - /** - * @return The video language - */ - Language videoLanguage(); - - /** - * @param language The video language - */ - void setVideoLanguage(Language language); - - /** - * @return The list of selected translations - */ - List translations(); - /** * @return The current status */ @@ -97,4 +48,36 @@ public interface WorkModel { * @param progress The new progress */ void setProgress(double progress); + + /** + * @return Whether the user can extract subtitles + */ + boolean canExtract(); + + /** + * @return Whether the user can export subtitles + */ + boolean canExport(); + + /** + * @param canExport Whether the user can export subtitles + */ + void setCanExport(boolean canExport); + + /** + * @return Whether the progress bar and label are currently visible + */ + boolean isProgressVisible(); + + /** + * @return The last extracted collection + */ + SubtitleCollection extractedCollection(); + + /** + * Sets the last extracted collection + * + * @param collection The last extracted collection + */ + void setExtractedCollection(SubtitleCollection collection); } diff --git a/gui/core/src/main/java/com/github/gtache/autosubtitle/gui/impl/CombinedResourceBundle.java b/gui/core/src/main/java/com/github/gtache/autosubtitle/gui/impl/CombinedResourceBundle.java index f55c86a..04b9c63 100644 --- a/gui/core/src/main/java/com/github/gtache/autosubtitle/gui/impl/CombinedResourceBundle.java +++ b/gui/core/src/main/java/com/github/gtache/autosubtitle/gui/impl/CombinedResourceBundle.java @@ -1,35 +1,52 @@ package com.github.gtache.autosubtitle.gui.impl; -import java.util.Arrays; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Map; -import java.util.ResourceBundle; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.*; /** * Combines multiple resource bundles */ public class CombinedResourceBundle extends ResourceBundle { + private static final Logger logger = LogManager.getLogger(CombinedResourceBundle.class); private final Map resources; + private final Locale locale; - public CombinedResourceBundle(final ResourceBundle... bundles) { - this(Arrays.asList(bundles)); + public CombinedResourceBundle(final ResourceBundle... resourceBundles) { + this(Arrays.asList(resourceBundles)); } - public CombinedResourceBundle(final Iterable bundles) { + public CombinedResourceBundle(final List resourceBundles) { + final var filteredBundles = resourceBundles.stream().filter(Objects::nonNull).toList(); + if (filteredBundles.size() != resourceBundles.size()) { + logger.warn("There was one or more null bundles in the inner bundles"); + } + if (filteredBundles.isEmpty()) { + throw new IllegalArgumentException("The bundle should contain at least one bundle"); + } this.resources = new HashMap<>(); - bundles.forEach(rb -> rb.getKeys().asIterator().forEachRemaining(key -> resources.put(key, rb.getString(key)))); + filteredBundles.forEach(r -> r.keySet().forEach(s -> resources.put(s, r.getString(s)))); + this.locale = filteredBundles.getFirst().getLocale(); } @Override - protected Object handleGetObject(final String key) { - return resources.get(key); + public Object handleGetObject(final String key) { + if (resources.containsKey(key)) { + return resources.get(key); + } else { + throw new MissingResourceException(key + " not found", "CombinedResourceBundle", key); + } } @Override public Enumeration getKeys() { return Collections.enumeration(resources.keySet()); } + + @Override + public Locale getLocale() { + return locale; + } } diff --git a/gui/core/src/main/java/com/github/gtache/autosubtitle/gui/impl/spi/SubtitlesBundleProvider.java b/gui/core/src/main/java/com/github/gtache/autosubtitle/gui/impl/spi/SubtitlesBundleProvider.java new file mode 100644 index 0000000..409cb4e --- /dev/null +++ b/gui/core/src/main/java/com/github/gtache/autosubtitle/gui/impl/spi/SubtitlesBundleProvider.java @@ -0,0 +1,9 @@ +package com.github.gtache.autosubtitle.gui.impl.spi; + +import java.util.spi.ResourceBundleProvider; + +/** + * Provider for SubtitlesBundle + */ +public interface SubtitlesBundleProvider extends ResourceBundleProvider { +} diff --git a/gui/core/src/main/java/com/github/gtache/autosubtitle/gui/impl/spi/SubtitlesBundleProviderImpl.java b/gui/core/src/main/java/com/github/gtache/autosubtitle/gui/impl/spi/SubtitlesBundleProviderImpl.java new file mode 100644 index 0000000..5657769 --- /dev/null +++ b/gui/core/src/main/java/com/github/gtache/autosubtitle/gui/impl/spi/SubtitlesBundleProviderImpl.java @@ -0,0 +1,9 @@ +package com.github.gtache.autosubtitle.gui.impl.spi; + +import java.util.spi.AbstractResourceBundleProvider; + +/** + * Implementation of {@link SubtitlesBundleProvider} + */ +public class SubtitlesBundleProviderImpl extends AbstractResourceBundleProvider implements SubtitlesBundleProvider { +} 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 dff92ff..eaafa50 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 @@ -23,6 +23,7 @@ public final class GuiCoreModule { ResourceBundle.getBundle("com.github.gtache.autosubtitle.gui.impl.SetupBundle"), ResourceBundle.getBundle("com.github.gtache.autosubtitle.gui.impl.WorkBundle"), ResourceBundle.getBundle("com.github.gtache.autosubtitle.gui.impl.ParametersBundle"), + ResourceBundle.getBundle("com.github.gtache.autosubtitle.gui.impl.SubtitlesBundle"), ResourceBundle.getBundle("com.github.gtache.autosubtitle.gui.impl.MediaBundle")); } diff --git a/gui/core/src/main/java/module-info.java b/gui/core/src/main/java/module-info.java index 9b163ce..48e0010 100644 --- a/gui/core/src/main/java/module-info.java +++ b/gui/core/src/main/java/module-info.java @@ -13,6 +13,7 @@ import com.github.gtache.autosubtitle.gui.impl.spi.WorkBundleProviderImpl; module com.github.gtache.autosubtitle.gui.core { requires transitive com.github.gtache.autosubtitle.gui.api; requires transitive com.github.gtache.autosubtitle.core; + requires org.apache.logging.log4j; exports com.github.gtache.autosubtitle.gui.impl; exports com.github.gtache.autosubtitle.gui.impl.spi; exports com.github.gtache.autosubtitle.modules.gui.impl; diff --git a/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/SubtitlesBundle.properties b/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/SubtitlesBundle.properties new file mode 100644 index 0000000..dc37480 --- /dev/null +++ b/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/SubtitlesBundle.properties @@ -0,0 +1,14 @@ +subtitles.button.load.label=Load subtitles... +subtitles.button.reset.label=Reset subtitles +subtitles.button.subtitles.save.label=Save subtitles... +subtitles.export.error.label=Error during the export : {0} +subtitles.export.error.title=Error exporting +subtitles.language.label=Video language +subtitles.load.error.label=Error loading subtitles : {0} +subtitles.load.error.title=Error loading +subtitles.save.error.label=Error saving subtitles : {0} +subtitles.save.error.title=Error saving +subtitles.table.column.from.label=From +subtitles.table.column.text.label=Text +subtitles.table.column.to.label=To +subtitles.translate.label=Automatic translations \ No newline at end of file diff --git a/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/SubtitlesBundle_fr.properties b/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/SubtitlesBundle_fr.properties new file mode 100644 index 0000000..b3369b1 --- /dev/null +++ b/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/SubtitlesBundle_fr.properties @@ -0,0 +1,14 @@ +subtitles.button.load.label=Charger des sous-titres... +subtitles.button.reset.label=R\u00E9initialiser les sous-titres +subtitles.button.subtitles.save.label=Sauvegarder les sous-titres... +subtitles.export.error.label=Erreur durant l''export : {0} +subtitles.export.error.title=Erreur d'export +subtitles.language.label=Langage de la vid\u00E9o +subtitles.load.error.label=Erreur de chargement des sous-titres : {0} +subtitles.load.error.title=Erreur de chargement +subtitles.save.error.label=Erreur de sauvegarde des sous-titres : {0} +subtitles.save.error.title=Erreur lors de la sauvegarde +subtitles.table.column.from.label=De +subtitles.table.column.text.label=Texte +subtitles.table.column.to.label=\u00C0 +subtitles.translate.label=Traductions diff --git a/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/WorkBundle.properties b/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/WorkBundle.properties index 51db763..3fef742 100644 --- a/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/WorkBundle.properties +++ b/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/WorkBundle.properties @@ -4,27 +4,13 @@ work.button.export.soft.label=Export video... work.button.export.soft.tooltip=Adds the subtitles to the video. This allows a video to have multiple subtitles and to enable them at will. work.button.extract.label=Extract subtitles work.button.file.label=Open video... -work.button.load.label=Load subtitles... -work.button.reset.label=Reset subtitles -work.button.subtitles.save.label=Save subtitles... work.export.error.label=Error during the export : {0} work.export.error.title=Error exporting work.extract.error.label=Error extracting subtitles : {0} work.extract.error.title=Error extracting -work.language.label=Video language -work.load.subtitles.error.label=Error loading subtitles : {0} -work.load.subtitles.error.title=Error loading work.load.video.error.label=Error loading video : {0} work.load.video.error.title=Error loading -work.save.subtitles.error.label=Error saving subtitles : {0} -work.save.subtitles.error.title=Error saving -work.save.subtitles.missing.converter.label=No converter found for {0} -work.save.subtitles.missing.converter.title=No converter found work.status.exporting.label=Exporting... work.status.extracting.label=Extracting... work.status.idle.label=Idle -work.status.translating.label=Translating... -work.table.column.from.label=From -work.table.column.text.label=Text -work.table.column.to.label=To -work.translate.label=Automatic translations \ No newline at end of file +work.status.translating.label=Translating... \ No newline at end of file diff --git a/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/WorkBundle_fr.properties b/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/WorkBundle_fr.properties index 00fb672..ba0a06e 100644 --- a/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/WorkBundle_fr.properties +++ b/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/WorkBundle_fr.properties @@ -4,25 +4,13 @@ work.button.export.soft.label=Exporter la vid\u00E9o... work.button.export.soft.tooltip=Ajoute les sous-titres \u00E0 la vid\u00E9o. Cela permet d'avoir plusieurs pistes de sous-titres dans une m\u00EAme vid\u00E9o et de les activer comme d\u00E9sir\u00E9. work.button.extract.label=Extraire les sous-titres work.button.file.label=Ouvrir une vid\u00E9o... -work.button.load.label=Charger des sous-titres... -work.button.reset.label=R\u00E9initialiser les sous-titres -work.button.subtitles.save.label=Sauvegarder les sous-titres... work.export.error.label=Erreur durant l''export : {0} work.export.error.title=Erreur d'export work.extract.error.label=Erreur durant l''extraction des sous-titres : {0} work.extract.error.title=Erreur d'extraction -work.language.label=Language de la vid\u00E9o -work.load.subtitles.error.label=Erreur de chargement des sous-titres : {0} -work.load.subtitles.error.title=Erreur de chargement work.load.video.error.label=Erreur lors du chargement de la vid\u00E9o : {0} work.load.video.error.title=Erreur de chargement -work.save.subtitles.missing.converter.label=Aucun convertisseur trouv\u00E9 pour {0} -work.save.subtitles.missing.converter.title=Aucun convertisseur trouv\u00E9 work.status.exporting.label=Exportation en cours... work.status.extracting.label=Extraction en cours... work.status.idle.label=Idle work.status.translating.label=Traduction en cours... -work.table.column.from.label=De -work.table.column.text.label=Texte -work.table.column.to.label=\u00C0 -work.translate.label=Traductions automatiques diff --git a/gui/core/src/test/java/com/github/gtache/autosubtitle/gui/impl/TestCombinedResourceBundle.java b/gui/core/src/test/java/com/github/gtache/autosubtitle/gui/impl/TestCombinedResourceBundle.java new file mode 100644 index 0000000..9f6ac4e --- /dev/null +++ b/gui/core/src/test/java/com/github/gtache/autosubtitle/gui/impl/TestCombinedResourceBundle.java @@ -0,0 +1,54 @@ +package com.github.gtache.autosubtitle.gui.impl; + +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class TestCombinedResourceBundle { + + private static final ResourceBundle BUNDLE = new CombinedResourceBundle( + ResourceBundle.getBundle("com.github.gtache.autosubtitle.gui.impl.MultiBundle", Locale.FRENCH), + ResourceBundle.getBundle("com.github.gtache.autosubtitle.gui.impl.MultiBundleTwo", Locale.FRENCH)); + + @Test + void testIllegal() { + assertThrows(IllegalArgumentException.class, CombinedResourceBundle::new); + assertThrows(IllegalArgumentException.class, () -> new CombinedResourceBundle(null, null)); + } + + @Test + void testWorks() { + assertEquals("deux", BUNDLE.getString("a")); + assertEquals("deux", BUNDLE.getString("b")); + assertEquals("trois", BUNDLE.getString("c")); + assertEquals("un", BUNDLE.getString("d")); + assertEquals(Arrays.asList("a", "b", "c", "d"), Collections.list(BUNDLE.getKeys())); + } + + @Test + void testNotFound() { + assertThrows(MissingResourceException.class, () -> BUNDLE.getString("e")); + } + + @Test + void testLocale() { + final var bundle = mock(ResourceBundle.class); + when(bundle.keySet()).thenReturn(Set.of()); + when(bundle.getString(anyString())).thenReturn(""); + final var locale = mock(Locale.class); + when(bundle.getLocale()).thenReturn(locale); + final var combined = new CombinedResourceBundle(bundle); + assertEquals(locale, combined.getLocale()); + } +} diff --git a/gui/core/src/test/java/com/github/gtache/autosubtitle/modules/gui/impl/TestGuiCoreModule.java b/gui/core/src/test/java/com/github/gtache/autosubtitle/modules/gui/impl/TestGuiCoreModule.java new file mode 100644 index 0000000..cc81467 --- /dev/null +++ b/gui/core/src/test/java/com/github/gtache/autosubtitle/modules/gui/impl/TestGuiCoreModule.java @@ -0,0 +1,34 @@ +package com.github.gtache.autosubtitle.modules.gui.impl; + +import com.github.gtache.autosubtitle.gui.impl.CombinedResourceBundle; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class TestGuiCoreModule { + + @Test + void testBundle() { + assertInstanceOf(CombinedResourceBundle.class, GuiCoreModule.providesBundle()); + } + + @Test + void testPlayImage() { + assertTrue(GuiCoreModule.providesPlayImage().length > 0); + } + + @Test + void testPauseImage() { + assertTrue(GuiCoreModule.providesPauseImage().length > 0); + } + + @Test + void testFontFamily() { + assertEquals("Arial", GuiCoreModule.providesFontFamily()); + } + + @Test + void testFontSize() { + assertEquals(12, GuiCoreModule.providesFontSize()); + } +} diff --git a/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundle.properties b/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundle.properties new file mode 100644 index 0000000..65cb097 --- /dev/null +++ b/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundle.properties @@ -0,0 +1,3 @@ +a=one +b=two +c=three \ No newline at end of file diff --git a/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundleTwo.properties b/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundleTwo.properties new file mode 100644 index 0000000..02a0253 --- /dev/null +++ b/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundleTwo.properties @@ -0,0 +1,2 @@ +a=two +d=one \ No newline at end of file diff --git a/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundleTwo_de.properties b/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundleTwo_de.properties new file mode 100644 index 0000000..da5e8a2 --- /dev/null +++ b/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundleTwo_de.properties @@ -0,0 +1,2 @@ +a=zwei +d=eins \ No newline at end of file diff --git a/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundleTwo_fr.properties b/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundleTwo_fr.properties new file mode 100644 index 0000000..1c078f3 --- /dev/null +++ b/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundleTwo_fr.properties @@ -0,0 +1,2 @@ +a=deux +d=un \ No newline at end of file diff --git a/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundle_de.properties b/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundle_de.properties new file mode 100644 index 0000000..eadf97f --- /dev/null +++ b/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundle_de.properties @@ -0,0 +1,3 @@ +a=eins +b=zwei +c=drei \ No newline at end of file diff --git a/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundle_fr.properties b/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundle_fr.properties new file mode 100644 index 0000000..a35eefe --- /dev/null +++ b/gui/core/src/test/resources/com/github/gtache/autosubtitle/gui/impl/MultiBundle_fr.properties @@ -0,0 +1,3 @@ +a=un +b=deux +c=trois \ No newline at end of file diff --git a/gui/fx/pom.xml b/gui/fx/pom.xml index 40ebd2e..87a03b1 100644 --- a/gui/fx/pom.xml +++ b/gui/fx/pom.xml @@ -13,7 +13,8 @@ 11.2.1 - 22.0.1 + 22.0.2 + 4.0.18 @@ -44,6 +45,23 @@ controlsfx ${controlsfx.version} + + org.testfx + testfx-core + ${testfx.version} + test + + + org.testfx + testfx-junit5 + ${testfx.version} + test + + + org.apache.logging.log4j + log4j-core + test + \ No newline at end of file diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/ColonTimeFormatter.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/ColonTimeFormatter.java index 1fb24a0..dbf9501 100644 --- a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/ColonTimeFormatter.java +++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/ColonTimeFormatter.java @@ -27,18 +27,19 @@ public class ColonTimeFormatter implements TimeFormatter { public String format(final long millis) { final var secondsInMinute = 60; final var secondsInHour = secondsInMinute * 60; - var intDuration = (int) millis / 1000; - final var durationHours = intDuration / secondsInHour; + var secondsDuration = millis / 1000L; + final var durationHours = secondsDuration / secondsInHour; if (durationHours > 0) { - intDuration -= durationHours * secondsInHour; + secondsDuration -= durationHours * secondsInHour; } - final var durationMinutes = intDuration / secondsInMinute; - final var durationSeconds = intDuration - durationHours * secondsInHour - - durationMinutes * secondsInMinute; + final var durationMinutes = secondsDuration / secondsInMinute; + secondsDuration -= durationMinutes * secondsInMinute; + final var durationSeconds = secondsDuration; + final var durationMillis = millis % 1000L; if (durationHours > 0) { - return String.format("%d:%02d:%02d", durationHours, durationMinutes, durationSeconds); + return "%d:%02d:%02d.%03d".formatted(durationHours, durationMinutes, durationSeconds, durationMillis); } else { - return String.format("%02d:%02d", durationMinutes, durationSeconds); + return "%02d:%02d.%03d".formatted(durationMinutes, durationSeconds, durationMillis); } } @@ -48,14 +49,20 @@ public class ColonTimeFormatter implements TimeFormatter { final var secondsInMinute = 60; final var secondsInHour = secondsInMinute * 60; return switch (split.length) { - case 1 -> toLong(split[0]) * 1000; - case 2 -> (toLong(split[0]) * secondsInMinute + toLong(split[1])) * 1000; - case 3 -> (toLong(split[0]) * secondsInHour + toLong(split[1]) * secondsInMinute + toLong(split[2])) * 1000; + case 1 -> parseSecondsMillis(split[0]); + case 2 -> toLong(split[0]) * secondsInMinute * 1000 + parseSecondsMillis(split[1]); + case 3 -> + (toLong(split[0]) * secondsInHour + toLong(split[1]) * secondsInMinute) * 1000 + parseSecondsMillis(split[2]); default -> 0; }; } - private long toLong(final String time) { + private static long parseSecondsMillis(final String time) { + final var split = time.split("\\."); + return toLong(split[0]) * 1000 + (split.length > 1 ? toLong(split[1]) : 0); + } + + private static long toLong(final String time) { if (time.startsWith("0")) { return Long.parseLong(time.substring(1)); } else { diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXBinder.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXBinder.java new file mode 100644 index 0000000..0fc4a99 --- /dev/null +++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXBinder.java @@ -0,0 +1,13 @@ +package com.github.gtache.autosubtitle.gui.fx; + +/** + * Binds multiple models together + */ +@FunctionalInterface +public interface FXBinder { + + /** + * Creates the bindings between the models + */ + void createBindings(); +} diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMainController.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMainController.java index cf72ea2..14df25b 100644 --- a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMainController.java +++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMainController.java @@ -3,6 +3,7 @@ package com.github.gtache.autosubtitle.gui.fx; import com.github.gtache.autosubtitle.gui.MainController; import javafx.fxml.FXML; import javafx.scene.control.TabPane; +import javafx.stage.Window; import javax.inject.Inject; import javax.inject.Singleton; @@ -12,7 +13,7 @@ import java.util.Objects; * FX implementation of {@link MainController} */ @Singleton -public class FXMainController implements MainController { +public class FXMainController extends AbstractFXController implements MainController { @FXML private TabPane tabPane; @@ -25,18 +26,23 @@ public class FXMainController implements MainController { } @FXML - private void initialize() { - tabPane.getSelectionModel().selectedIndexProperty().addListener((observable, oldValue, newValue) -> model.selectTab(newValue.intValue())); + void initialize() { + tabPane.getSelectionModel().selectedIndexProperty().addListener((observable, oldValue, newValue) -> model.setSelectedTab(newValue.intValue())); model.selectedTabProperty().addListener((observable, oldValue, newValue) -> tabPane.getSelectionModel().select(newValue.intValue())); } @Override public void selectTab(final int index) { - model.selectTab(index); + model.setSelectedTab(index); } @Override public FXMainModel model() { return model; } + + @Override + protected Window window() { + return tabPane.getScene().getWindow(); + } } diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMainModel.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMainModel.java index 04a38a6..7aa546b 100644 --- a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMainModel.java +++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMainModel.java @@ -26,7 +26,7 @@ public class FXMainModel implements MainModel { } @Override - public void selectTab(final int index) { + public void setSelectedTab(final int index) { selectedTab.set(index); } diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMediaBinder.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMediaBinder.java index 3176cea..db6b516 100644 --- a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMediaBinder.java +++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXMediaBinder.java @@ -10,7 +10,7 @@ import java.util.Objects; * Binds the media model */ @Singleton -public class FXMediaBinder { +public class FXMediaBinder implements FXBinder { private final FXWorkModel workModel; private final FXMediaModel mediaModel; @@ -21,6 +21,7 @@ public class FXMediaBinder { this.mediaModel = Objects.requireNonNull(mediaModel); } + @Override public void createBindings() { mediaModel.videoProperty().bindBidirectional(workModel.videoProperty()); Bindings.bindContent(mediaModel.subtitles(), workModel.subtitles()); 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 07b8b39..5a252e1 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 @@ -5,12 +5,10 @@ import com.github.gtache.autosubtitle.gui.MediaController; import com.github.gtache.autosubtitle.gui.TimeFormatter; 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; @@ -34,9 +32,7 @@ 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.concurrent.CompletableFuture; import java.util.stream.Collectors; import static java.util.Objects.requireNonNull; @@ -69,8 +65,6 @@ public class FXMediaController implements MediaController { private final Image playImage; private final Image pauseImage; - private final List startTimes; - private boolean wasPlaying; @Inject @@ -81,11 +75,10 @@ public class FXMediaController implements MediaController { this.timeFormatter = requireNonNull(timeFormatter); this.playImage = requireNonNull(playImage); this.pauseImage = requireNonNull(pauseImage); - this.startTimes = new ArrayList<>(); } @FXML - private void initialize() { + void initialize() { volumeValueLabel.textProperty().bind(Bindings.createStringBinding(() -> String.valueOf((int) (model.volume() * 100)), model.volumeProperty())); playLabel.textProperty().bind(Bindings.createStringBinding(() -> timeFormatter.format(model.position(), model.duration()), model.positionProperty(), model.durationProperty())); model.positionProperty().bindBidirectional(playSlider.valueProperty()); @@ -115,13 +108,10 @@ public class FXMediaController implements MediaController { loadFileVideo(file.path()); } else { logger.error("Unsupported video type : {}", newValue); + Platform.runLater(() -> model.setVideo(null)); } }); - model.subtitles().addListener((ListChangeListener) c -> { - startTimes.clear(); - model.subtitles().stream().mapToLong(Subtitle::start).forEach(startTimes::add); - }); bindPlayButton(); binder.createBindings(); } @@ -130,8 +120,8 @@ public class FXMediaController implements MediaController { 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())); + logger.debug("New player status: {}", newValue1)); + player.currentTimeProperty().addListener((ignored, oldTime, newTime) -> currentTimeChanged(newTime.toMillis())); playSlider.setOnMousePressed(e -> { wasPlaying = model.isPlaying(); model.setIsPlaying(false); @@ -155,33 +145,37 @@ public class FXMediaController implements MediaController { playSlider.setMax(model.duration()); playSlider.setValue(0L); videoView.setMediaPlayer(player); + CompletableFuture.runAsync(() -> { + try { + Thread.sleep(3000L); + } catch (final InterruptedException ignored) { + Thread.currentThread().interrupt(); + } + Platform.runLater(() -> { + final var status = player.getStatus(); + if (status == null || status == MediaPlayer.Status.UNKNOWN) { + logger.warn("Reloading video {} because player state is unknown or null", fileVideoPath); + loadFileVideo(fileVideoPath); + } + }); + }); } - private void currentTimeChanged(final double oldMillis, final double millis) { + private void currentTimeChanged(final double millis) { + final var longMillis = (long) 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()); - + subtitleLabels.stream().filter(s -> !s.subtitle().isShowing(longMillis)).forEach(sl -> stackPane.getChildren().remove(sl)); + final var containedSubtitles = subtitleLabels.stream().map(SubtitleLabel::subtitle).filter(s -> s.isShowing(longMillis)).collect(Collectors.toSet()); + //TODO optimize? model.subtitles().forEach(s -> { - if (!containedSubtitles.contains(s)) { - logger.info("Adding label {} at {}", s, millis); + if (!containedSubtitles.contains(s) && s.isShowing(longMillis)) { 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(() -> { @@ -217,6 +211,7 @@ public class FXMediaController implements MediaController { public void seek(final long position) { if (videoView.getMediaPlayer() != null) { videoView.getMediaPlayer().seek(Duration.millis(position)); + currentTimeChanged(position); } } 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 385cbba..99f1786 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 @@ -102,12 +102,12 @@ public class FXMediaModel implements MediaModel { this.position.set(position); } + LongProperty positionProperty() { + return position; + } + @Override public ObservableList subtitles() { return subtitles; } - - LongProperty positionProperty() { - return position; - } } diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXParametersController.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXParametersController.java index e77035e..905afea 100644 --- a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXParametersController.java +++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXParametersController.java @@ -50,7 +50,7 @@ public class FXParametersController extends AbstractFXController implements Para } @FXML - private void initialize() { + void initialize() { extractionModelCombobox.setItems(model.availableExtractionModels()); extractionModelCombobox.valueProperty().bindBidirectional(model.extractionModelProperty()); diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSetupController.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSetupController.java index 1be5322..9312420 100644 --- a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSetupController.java +++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSetupController.java @@ -97,7 +97,7 @@ public class FXSetupController extends AbstractFXController implements SetupCont } @FXML - private void initialize() { + void initialize() { statusMap.put(converterManager, model.videoConverterStatusProperty()); statusMap.put(extractorManager, model.subtitleExtractorStatusProperty()); statusMap.put(translatorManager, model.translatorStatusProperty()); diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSetupModel.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSetupModel.java index 200b49c..5b9e6ac 100644 --- a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSetupModel.java +++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSetupModel.java @@ -2,11 +2,8 @@ package com.github.gtache.autosubtitle.gui.fx; import com.github.gtache.autosubtitle.gui.SetupModel; import com.github.gtache.autosubtitle.setup.SetupStatus; -import javafx.beans.binding.Bindings; import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; -import javafx.beans.property.ReadOnlyBooleanProperty; -import javafx.beans.property.ReadOnlyBooleanWrapper; import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; @@ -22,45 +19,26 @@ import javax.inject.Singleton; public class FXSetupModel implements SetupModel { private final ObjectProperty subtitleExtractorStatus; - private final ReadOnlyBooleanWrapper subtitleExtractorInstalled; - private final ReadOnlyBooleanWrapper subtitleExtractorUpdateAvailable; private final DoubleProperty subtitleExtractorSetupProgress; private final StringProperty subtitleExtractorSetupProgressLabel; private final ObjectProperty videoConverterStatus; - private final ReadOnlyBooleanWrapper videoConverterInstalled; - private final ReadOnlyBooleanWrapper videoConverterUpdateAvailable; private final DoubleProperty videoConverterSetupProgress; private final StringProperty videoConverterSetupProgressLabel; private final ObjectProperty translatorStatus; - private final ReadOnlyBooleanWrapper translatorInstalled; - private final ReadOnlyBooleanWrapper translatorUpdateAvailable; private final DoubleProperty translatorSetupProgress; private final StringProperty translatorSetupProgressLabel; @Inject FXSetupModel() { this.subtitleExtractorStatus = new SimpleObjectProperty<>(SetupStatus.ERRORED); - this.subtitleExtractorInstalled = new ReadOnlyBooleanWrapper(false); - this.subtitleExtractorUpdateAvailable = new ReadOnlyBooleanWrapper(false); this.subtitleExtractorSetupProgress = new SimpleDoubleProperty(-2); this.subtitleExtractorSetupProgressLabel = new SimpleStringProperty(""); this.videoConverterStatus = new SimpleObjectProperty<>(SetupStatus.ERRORED); - this.videoConverterInstalled = new ReadOnlyBooleanWrapper(false); - this.videoConverterUpdateAvailable = new ReadOnlyBooleanWrapper(false); this.videoConverterSetupProgress = new SimpleDoubleProperty(-2); this.videoConverterSetupProgressLabel = new SimpleStringProperty(""); this.translatorStatus = new SimpleObjectProperty<>(SetupStatus.ERRORED); - this.translatorInstalled = new ReadOnlyBooleanWrapper(false); - this.translatorUpdateAvailable = new ReadOnlyBooleanWrapper(false); this.translatorSetupProgress = new SimpleDoubleProperty(-2); this.translatorSetupProgressLabel = new SimpleStringProperty(""); - - subtitleExtractorInstalled.bind(Bindings.createBooleanBinding(() -> subtitleExtractorStatus.get().isInstalled(), subtitleExtractorStatus)); - videoConverterInstalled.bind(Bindings.createBooleanBinding(() -> videoConverterStatus.get().isInstalled(), videoConverterStatus)); - translatorInstalled.bind(Bindings.createBooleanBinding(() -> translatorStatus.get().isInstalled(), translatorStatus)); - subtitleExtractorUpdateAvailable.bind(Bindings.createBooleanBinding(() -> subtitleExtractorStatus.get() == SetupStatus.UPDATE_AVAILABLE, subtitleExtractorStatus)); - videoConverterUpdateAvailable.bind(Bindings.createBooleanBinding(() -> videoConverterStatus.get() == SetupStatus.UPDATE_AVAILABLE, videoConverterStatus)); - translatorUpdateAvailable.bind(Bindings.createBooleanBinding(() -> translatorStatus.get() == SetupStatus.UPDATE_AVAILABLE, translatorStatus)); } @Override @@ -77,20 +55,6 @@ public class FXSetupModel implements SetupModel { return subtitleExtractorStatus; } - @Override - public boolean isSubtitleExtractorInstalled() { - return subtitleExtractorInstalled.get(); - } - - ReadOnlyBooleanProperty subtitleExtractorInstalledProperty() { - return subtitleExtractorInstalled.getReadOnlyProperty(); - } - - @Override - public boolean isSubtitleExtractorUpdateAvailable() { - return subtitleExtractorUpdateAvailable.get(); - } - @Override public double subtitleExtractorSetupProgress() { return subtitleExtractorSetupProgress.get(); @@ -133,28 +97,6 @@ public class FXSetupModel implements SetupModel { return videoConverterStatus; } - ReadOnlyBooleanProperty subtitleExtractorUpdateAvailableProperty() { - return subtitleExtractorUpdateAvailable.getReadOnlyProperty(); - } - - @Override - public boolean isVideoConverterInstalled() { - return videoConverterInstalled.get(); - } - - ReadOnlyBooleanProperty videoConverterInstalledProperty() { - return videoConverterInstalled.getReadOnlyProperty(); - } - - @Override - public boolean isVideoConverterUpdateAvailable() { - return videoConverterUpdateAvailable.get(); - } - - ReadOnlyBooleanProperty videoConverterUpdateAvailableProperty() { - return videoConverterUpdateAvailable.getReadOnlyProperty(); - } - @Override public double videoConverterSetupProgress() { return videoConverterSetupProgress.get(); @@ -197,24 +139,6 @@ public class FXSetupModel implements SetupModel { return translatorStatus; } - @Override - public boolean isTranslatorInstalled() { - return translatorInstalled.get(); - } - - ReadOnlyBooleanProperty translatorInstalledProperty() { - return translatorInstalled.getReadOnlyProperty(); - } - - @Override - public boolean isTranslatorUpdateAvailable() { - return translatorUpdateAvailable.get(); - } - - ReadOnlyBooleanProperty translatorUpdateAvailableProperty() { - return translatorUpdateAvailable.getReadOnlyProperty(); - } - @Override public double translatorSetupProgress() { return translatorSetupProgress.get(); diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesBinder.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesBinder.java new file mode 100644 index 0000000..bda34c2 --- /dev/null +++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesBinder.java @@ -0,0 +1,55 @@ +package com.github.gtache.autosubtitle.gui.fx; + +import com.github.gtache.autosubtitle.Language; +import com.github.gtache.autosubtitle.gui.WorkStatus; +import com.github.gtache.autosubtitle.subtitle.gui.fx.ObservableSubtitleCollectionImpl; +import javafx.beans.binding.Bindings; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.Objects; + +/** + * Binds the subtitles model + */ +@Singleton +public class FXSubtitlesBinder implements FXBinder { + + private final FXWorkModel workModel; + private final FXSubtitlesModel subtitlesModel; + + @Inject + FXSubtitlesBinder(final FXWorkModel workModel, final FXSubtitlesModel subtitlesModel) { + this.workModel = Objects.requireNonNull(workModel); + this.subtitlesModel = Objects.requireNonNull(subtitlesModel); + } + + @Override + public void createBindings() { + subtitlesModel.canLoadSubtitlesProperty().bind(workModel.videoProperty().isNotNull().and(workModel.statusProperty().isEqualTo(WorkStatus.IDLE))); + subtitlesModel.canResetSubtitlesProperty().bind(workModel.videoProperty().isNotNull().and(workModel.statusProperty().isEqualTo(WorkStatus.IDLE))); + subtitlesModel.canAddSubtitleProperty().bind(workModel.videoProperty().isNotNull().and(workModel.statusProperty().isEqualTo(WorkStatus.IDLE)).and(subtitlesModel.videoLanguageProperty().isNotEqualTo(Language.AUTO))); + subtitlesModel.canEditTableProperty().bind(workModel.videoProperty().isNotNull().and(workModel.statusProperty().isEqualTo(WorkStatus.IDLE)).and(subtitlesModel.videoLanguageProperty().isNotEqualTo(Language.AUTO))); + + workModel.selectedSubtitleProperty().bind(subtitlesModel.selectedSubtitleProperty()); + workModel.canExportProperty().bind(Bindings.isNotEmpty(subtitlesModel.collections()).and(workModel.statusProperty().isEqualTo(WorkStatus.IDLE))); + workModel.videoLanguageProperty().bind(subtitlesModel.videoLanguageProperty()); + + subtitlesModel.translatingProperty().addListener((observable, oldValue, newValue) -> { + if (newValue) { + workModel.statusProperty().set(WorkStatus.TRANSLATING); + } else { + workModel.statusProperty().set(WorkStatus.IDLE); + } + }); + + workModel.extractedCollectionProperty().addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + subtitlesModel.collections().put(newValue.language(), new ObservableSubtitleCollectionImpl(newValue)); + } + }); + + Bindings.bindContent(workModel.collections(), subtitlesModel.collections()); + Bindings.bindContent(workModel.subtitles(), subtitlesModel.selectedSubtitles()); + } +} diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesController.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesController.java new file mode 100644 index 0000000..db151d6 --- /dev/null +++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesController.java @@ -0,0 +1,322 @@ +package com.github.gtache.autosubtitle.gui.fx; + +import com.github.gtache.autosubtitle.Language; +import com.github.gtache.autosubtitle.Translator; +import com.github.gtache.autosubtitle.gui.SubtitlesController; +import com.github.gtache.autosubtitle.gui.TimeFormatter; +import com.github.gtache.autosubtitle.subtitle.SubtitleCollection; +import com.github.gtache.autosubtitle.subtitle.SubtitleImporterExporter; +import com.github.gtache.autosubtitle.subtitle.converter.ParseException; +import com.github.gtache.autosubtitle.subtitle.gui.fx.ObservableSubtitleCollectionImpl; +import com.github.gtache.autosubtitle.subtitle.gui.fx.ObservableSubtitleImpl; +import javafx.application.Platform; +import javafx.beans.binding.Bindings; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.collections.MapChangeListener; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Tab; +import javafx.scene.control.TabPane; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.control.cell.TextFieldTableCell; +import javafx.scene.input.KeyCode; +import javafx.stage.FileChooser; +import javafx.stage.Window; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.controlsfx.control.PrefixSelectionComboBox; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.io.IOException; +import java.nio.file.Path; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.ResourceBundle; +import java.util.concurrent.CompletableFuture; + +import static java.util.Objects.requireNonNull; + +/** + * FX implementation of {@link SubtitlesController} + */ +@Singleton +public class FXSubtitlesController extends AbstractFXController implements SubtitlesController { + + private static final Logger logger = LogManager.getLogger(FXSubtitlesController.class); + private static final String ARCHIVE = "Archive"; + private static final String ALL_SUPPORTED = "All supported"; + + @FXML + private ResourceBundle resources; + @FXML + private Button loadButton; + @FXML + private Button resetButton; + @FXML + private Button saveButton; + @FXML + private Button addButton; + @FXML + private PrefixSelectionComboBox languageCombobox; + @FXML + private ComboBox translationsCombobox; + @FXML + private TabPane tabPane; + @FXML + private Tab mainSubtitlesTab; + @FXML + private TableView subtitlesTable; + @FXML + private TableColumn startColumn; + @FXML + private TableColumn endColumn; + @FXML + private TableColumn textColumn; + + private final FXSubtitlesModel model; + private final FXSubtitlesBinder binder; + private final SubtitleImporterExporter importerExporter; + private final TimeFormatter timeFormatter; + private final List subtitleExtensions; + private final Translator translator; + + @Inject + FXSubtitlesController(final FXSubtitlesModel model, final FXSubtitlesBinder binder, final SubtitleImporterExporter importerExporter, final TimeFormatter timeFormatter, + final Translator translator) { + this.model = requireNonNull(model); + this.binder = requireNonNull(binder); + this.importerExporter = requireNonNull(importerExporter); + this.timeFormatter = requireNonNull(timeFormatter); + this.subtitleExtensions = importerExporter.supportedSingleFileExtensions().stream().map(c -> "*." + c).sorted().toList(); + this.translator = requireNonNull(translator); + } + + @FXML + void initialize() { + addButton.disableProperty().bind(model.canAddSubtitleProperty().not()); + loadButton.disableProperty().bind(model.canLoadSubtitlesProperty().not()); + resetButton.disableProperty().bind(model.canResetSubtitlesProperty().not()); + saveButton.disableProperty().bind(model.canSaveSubtitlesProperty().not()); + + // Can't bind because tab calls updateDisabled which sets disableProperty + model.canEditTableProperty().addListener((observable, oldValue, newValue) -> subtitlesTable.setDisable(!newValue)); + + bindComboboxes(); + bindTable(); + mainSubtitlesTab.textProperty().bind(Bindings.createStringBinding(() -> model.videoLanguage().iso2(), model.videoLanguageProperty())); + model.collections().addListener((MapChangeListener) change -> { + manageTabs(); + }); + tabPane.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + model.setSelectedCollection(model.collections().get(Language.getLanguage(newValue.getText()))); + if (oldValue != null) { + oldValue.setContent(null); + } + newValue.setContent(subtitlesTable); + } + }); + model.selectedLanguageProperty().addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + tabPane.getTabs().stream().filter(t -> Language.getLanguage(t.getText()) == newValue) + .findFirst().ifPresent(tab -> tabPane.getSelectionModel().select(tab)); + } + }); + + translationsCombobox.valueProperty().addListener((observable, oldValue, newValue) -> { + if (newValue != null && !model.collections().containsKey(newValue)) { + model.setTranslating(true); + CompletableFuture.supplyAsync(() -> translator.translate(model.collections().get(model.videoLanguage()), newValue)) + .whenCompleteAsync((r, t) -> { + if (t == null) { + loadCollection(r); + model.setSelectedCollection(model.collections().get(newValue)); + } else { + logger.error("Error while translating to {}", newValue, t); + } + model.setTranslating(false); + }, Platform::runLater); + } + }); + binder.createBindings(); + } + + private void bindTable() { + subtitlesTable.setItems(model.selectedSubtitles()); + subtitlesTable.setOnKeyPressed(e -> { + if (e.getCode().isLetterKey() || e.getCode().isDigitKey()) { + editFocusedCell(); + } else if (e.getCode() == KeyCode.RIGHT || + e.getCode() == KeyCode.TAB) { + subtitlesTable.getSelectionModel().selectNext(); + e.consume(); + } 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))); + startColumn.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue() == null ? null : param.getValue().start())); + startColumn.setOnEditCommit(e -> { + final var subtitle = e.getRowValue(); + subtitle.setStart(e.getNewValue()); + subtitlesTable.refresh(); + subtitlesTable.requestFocus(); + }); + endColumn.setCellFactory(TextFieldTableCell.forTableColumn(new TimeStringConverter(timeFormatter))); + endColumn.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue() == null ? null : param.getValue().end())); + endColumn.setOnEditCommit(e -> { + final var subtitle = e.getRowValue(); + subtitle.setEnd(e.getNewValue()); + subtitlesTable.refresh(); + subtitlesTable.requestFocus(); + }); + textColumn.setCellFactory(TextFieldTableCell.forTableColumn()); + textColumn.setCellValueFactory(param -> new SimpleStringProperty(param.getValue() == null ? null : param.getValue().content())); + textColumn.setOnEditCommit(e -> { + final var subtitle = e.getRowValue(); + subtitle.setContent(e.getNewValue()); + subtitlesTable.refresh(); + subtitlesTable.requestFocus(); + }); + + subtitlesTable.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> model.selectedSubtitleProperty().set(newValue)); + + } + + private void manageTabs() { + final var toRemove = new ArrayList(); + final var toAdd = new ArrayList(); + tabPane.getTabs().forEach(tab -> { + if (!model.collections().containsKey(Language.getLanguage(tab.getText()))) { + toRemove.add(tab); + } + }); + model.collections().forEach((language, collection) -> { + if (tabPane.getTabs().stream().noneMatch(t -> Language.getLanguage(t.getText()) == language)) { + toAdd.add(new Tab(language.iso2())); + } + }); + tabPane.getTabs().removeAll(toRemove); + tabPane.getTabs().addAll(toAdd); + tabPane.getTabs().sort(Comparator.comparing(Tab::getText)); + } + + private void bindComboboxes() { + languageCombobox.valueProperty().bindBidirectional(model.videoLanguageProperty()); + languageCombobox.setItems(model.availableVideoLanguages()); + languageCombobox.setConverter(new LanguageStringConverter()); + translationsCombobox.setConverter(new LanguageStringConverter()); + translationsCombobox.setItems(model.availableTranslationsLanguage()); + } + + @FXML + private void resetButtonPressed() { + model.setSelectedCollection(model.originalCollections().get(model.selectedLanguage())); + } + + @FXML + private void addPressed() { + model.selectedCollection().subtitles().add(new ObservableSubtitleImpl("Enter text here...")); + } + + @FXML + private void loadPressed() { + final var filePicker = new FileChooser(); + final var archiveFilter = new FileChooser.ExtensionFilter(ARCHIVE, subtitleExtensions); + final var allSupportedFilter = new FileChooser.ExtensionFilter(ALL_SUPPORTED, subtitleExtensions); + filePicker.getExtensionFilters().addAll(archiveFilter, allSupportedFilter); + filePicker.setSelectedExtensionFilter(allSupportedFilter); + final var file = filePicker.showOpenDialog(window()); + loadSubtitles(file.toPath()); + } + + @FXML + private void savePressed() { + final var filePicker = new FileChooser(); + final var archiveFilter = new FileChooser.ExtensionFilter(ARCHIVE, subtitleExtensions); + final var allSupportedFilter = new FileChooser.ExtensionFilter(ALL_SUPPORTED, subtitleExtensions); + filePicker.getExtensionFilters().addAll(archiveFilter, allSupportedFilter); + filePicker.setSelectedExtensionFilter(allSupportedFilter); + final var file = filePicker.showSaveDialog(window()); + if (file != null) { + saveSubtitles(file.toPath()); + } + } + + @Override + public void selectLanguage(final Language language) { + model.setSelectedLanguage(language); + } + + @Override + public void deleteLanguage(final Language language) { + model.selectedTranslationsLanguages().remove(language); + } + + @Override + public void saveSubtitles(final Path file) { + try { + final var filename = file.getFileName().toString(); + final var extension = filename.substring(filename.lastIndexOf('.') + 1); + if (subtitleExtensions.contains(extension)) { + importerExporter.exportSubtitles(model.selectedCollection(), file); + } else { + importerExporter.exportSubtitles(model.collections().values(), file); + } + } catch (final IOException e) { + logger.error("Error saving subtitles {}", file, e); + showErrorDialog(resources.getString("subtitles.save.error.title"), MessageFormat.format(resources.getString("subtitles.save.error.label"), file)); + } + } + + @Override + public void loadSubtitles(final Path file) { + try { + final var map = importerExporter.importSubtitles(file); + map.values().forEach(this::loadCollection); + if (model.videoLanguage() == Language.AUTO) { + model.setVideoLanguage(map.keySet().stream().findFirst().orElse(Language.AUTO)); + } + } catch (final IOException | ParseException e) { + logger.error("Error loading subtitles {}", file, e); + showErrorDialog(resources.getString("subtitles.load.error.title"), MessageFormat.format(resources.getString("subtitles.load.error.label"), file)); + } + } + + private void loadCollection(final SubtitleCollection collection) { + final var observableCollection = new ObservableSubtitleCollectionImpl(collection); + model.originalCollections().put(observableCollection.language(), observableCollection); + model.collections().put(observableCollection.language(), observableCollection); + } + + private void deleteSelectedSubtitles() { + model.selectedCollection().observableSubtitles().removeAll(subtitlesTable.getSelectionModel().getSelectedItems()); + } + + private void editFocusedCell() { + final var focusedCell = subtitlesTable.getFocusModel().getFocusedCell(); + if (focusedCell != null) { + subtitlesTable.edit(focusedCell.getRow(), focusedCell.getTableColumn()); + } + } + + @Override + public FXSubtitlesModel model() { + return model; + } + + @Override + protected Window window() { + return saveButton.getScene().getWindow(); + } +} diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesModel.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesModel.java new file mode 100644 index 0000000..328442b --- /dev/null +++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesModel.java @@ -0,0 +1,256 @@ +package com.github.gtache.autosubtitle.gui.fx; + +import com.github.gtache.autosubtitle.Language; +import com.github.gtache.autosubtitle.gui.SubtitlesModel; +import com.github.gtache.autosubtitle.subtitle.gui.fx.ObservableSubtitleCollectionImpl; +import com.github.gtache.autosubtitle.subtitle.gui.fx.ObservableSubtitleImpl; +import javafx.beans.binding.Bindings; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.property.ReadOnlyBooleanWrapper; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.MapChangeListener; +import javafx.collections.ObservableList; +import javafx.collections.ObservableMap; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.Arrays; +import java.util.Comparator; + +/** + * FX implementation of {@link SubtitlesModel} + */ +@Singleton +public class FXSubtitlesModel implements SubtitlesModel { + + private final ObservableList availableVideoLanguages; + private final ObjectProperty videoLanguage; + private final ObservableList availableTranslationLanguages; + private final ObservableList selectedTranslationsLanguages; + private final ObjectProperty selectedLanguage; + private final ObservableMap collections; + private final ObservableMap originalCollections; + private final ObjectProperty selectedCollection; + private final ObservableList selectedSubtitles; + private final ObjectProperty selectedSubtitle; + + private final BooleanProperty canLoadSubtitles; + private final BooleanProperty canAddSubtitle; + private final BooleanProperty canResetSubtitles; + private final BooleanProperty canEditTable; + private final ReadOnlyBooleanWrapper canSaveSubtitles; + private final BooleanProperty isTranslating; + + @Inject + FXSubtitlesModel() { + this.availableVideoLanguages = FXCollections.unmodifiableObservableList(FXCollections.observableArrayList(Arrays.stream(Language.values()) + .sorted((o1, o2) -> { + if (o1 == Language.AUTO) { + return -1; + } else if (o2 == Language.AUTO) { + return 1; + } else { + return o1.englishName().compareTo(o2.englishName()); + } + }).toList())); + this.availableTranslationLanguages = FXCollections.observableArrayList(Arrays.stream(Language.values()).filter(l -> l != Language.AUTO).toList()); + this.videoLanguage = new SimpleObjectProperty<>(Language.AUTO); + this.selectedTranslationsLanguages = FXCollections.observableArrayList(); + this.selectedLanguage = new SimpleObjectProperty<>(Language.AUTO); + this.collections = FXCollections.observableHashMap(); + this.originalCollections = FXCollections.observableHashMap(); + this.selectedCollection = new SimpleObjectProperty<>(); + this.selectedSubtitles = FXCollections.observableArrayList(); + this.selectedSubtitle = new SimpleObjectProperty<>(); + this.canLoadSubtitles = new SimpleBooleanProperty(false); + this.canAddSubtitle = new SimpleBooleanProperty(false); + this.canResetSubtitles = new SimpleBooleanProperty(false); + this.canSaveSubtitles = new ReadOnlyBooleanWrapper(false); + this.canEditTable = new SimpleBooleanProperty(false); + this.isTranslating = new SimpleBooleanProperty(false); + + canSaveSubtitles.bind(Bindings.isNotEmpty(collections)); + collections.addListener((MapChangeListener) change -> + availableTranslationLanguages.setAll(Arrays.stream(Language.values()).filter(l -> l != Language.AUTO && !collections.containsKey(l)).sorted(Comparator.comparing(Language::englishName)).toList())); + + selectedCollection.addListener((observable, oldValue, newValue) -> { + selectedSubtitle.set(null); + if (newValue == null) { + selectedSubtitles.clear(); + selectedLanguage.set(Language.AUTO); + } else { + selectedSubtitles.setAll(newValue.subtitles()); + selectedLanguage.set(newValue.language()); + } + }); + } + + @Override + public ObservableList availableVideoLanguages() { + return availableVideoLanguages; + } + + @Override + public Language videoLanguage() { + return videoLanguage.get(); + } + + @Override + public void setVideoLanguage(final Language language) { + videoLanguage.set(language); + } + + ObjectProperty videoLanguageProperty() { + return videoLanguage; + } + + @Override + public ObservableList availableTranslationsLanguage() { + return FXCollections.unmodifiableObservableList(availableTranslationLanguages); + } + + @Override + public ObservableList selectedTranslationsLanguages() { + return selectedTranslationsLanguages; + } + + @Override + public Language selectedLanguage() { + return selectedLanguage.get(); + } + + @Override + public void setSelectedLanguage(final Language language) { + selectedLanguage.set(language); + } + + ObjectProperty selectedLanguageProperty() { + return selectedLanguage; + } + + @Override + public ObservableMap collections() { + return collections; + } + + @Override + public ObservableSubtitleCollectionImpl selectedCollection() { + return selectedCollection.get(); + } + + @Override + public void setSelectedCollection(final ObservableSubtitleCollectionImpl collection) { + selectedCollection.set(collection); + } + + ObjectProperty selectedCollectionProperty() { + return selectedCollection; + } + + @Override + public ObservableMap originalCollections() { + return originalCollections; + } + + @Override + public ObservableList selectedSubtitles() { + return selectedSubtitles; + } + + @Override + public ObservableSubtitleImpl selectedSubtitle() { + return selectedSubtitle.get(); + } + + @Override + public void setSelectedSubtitle(final ObservableSubtitleImpl subtitle) { + selectedSubtitle.set(subtitle); + } + + ObjectProperty selectedSubtitleProperty() { + return selectedSubtitle; + } + + @Override + public boolean canLoadSubtitles() { + return canLoadSubtitles.get(); + } + + @Override + public void setCanLoadSubtitles(final boolean canLoadSubtitles) { + this.canLoadSubtitles.set(canLoadSubtitles); + } + + BooleanProperty canLoadSubtitlesProperty() { + return canLoadSubtitles; + } + + @Override + public boolean canAddSubtitle() { + return canAddSubtitle.get(); + } + + @Override + public void setCanAddSubtitle(final boolean canAddSubtitle) { + this.canAddSubtitle.set(canAddSubtitle); + } + + BooleanProperty canAddSubtitleProperty() { + return canAddSubtitle; + } + + @Override + public boolean canResetSubtitles() { + return canResetSubtitles.get(); + } + + @Override + public void setCanResetSubtitles(final boolean canResetSubtitles) { + this.canResetSubtitles.set(canResetSubtitles); + } + + BooleanProperty canResetSubtitlesProperty() { + return canResetSubtitles; + } + + @Override + public boolean canSaveSubtitles() { + return canSaveSubtitles.get(); + } + + ReadOnlyBooleanProperty canSaveSubtitlesProperty() { + return canSaveSubtitles.getReadOnlyProperty(); + } + + @Override + public boolean isTranslating() { + return isTranslating.get(); + } + + @Override + public void setTranslating(final boolean translating) { + this.isTranslating.set(translating); + } + + BooleanProperty translatingProperty() { + return isTranslating; + } + + @Override + public boolean canEditTable() { + return canEditTable.get(); + } + + @Override + public void setCanEditTable(final boolean canEditTable) { + this.canEditTable.set(canEditTable); + } + + public BooleanProperty canEditTableProperty() { + return canEditTable; + } +} diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXWorkBinder.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXWorkBinder.java index 30665f9..7609efc 100644 --- a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXWorkBinder.java +++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXWorkBinder.java @@ -5,7 +5,7 @@ import javax.inject.Singleton; import java.util.Objects; @Singleton -public class FXWorkBinder { +public class FXWorkBinder implements FXBinder { private final FXWorkModel workModel; private final FXParametersModel parametersModel; @@ -16,7 +16,8 @@ public class FXWorkBinder { this.parametersModel = Objects.requireNonNull(parametersModel); } - void createBindings() { + @Override + public void createBindings() { workModel.extractionModelProperty().bind(parametersModel.extractionModelProperty()); } } 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 f6fc39b..b7a3a6e 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 @@ -1,49 +1,32 @@ package com.github.gtache.autosubtitle.gui.fx; -import com.github.gtache.autosubtitle.Language; -import com.github.gtache.autosubtitle.Translator; import com.github.gtache.autosubtitle.VideoConverter; import com.github.gtache.autosubtitle.VideoLoader; -import com.github.gtache.autosubtitle.gui.TimeFormatter; import com.github.gtache.autosubtitle.gui.WorkController; import com.github.gtache.autosubtitle.gui.WorkStatus; -import com.github.gtache.autosubtitle.subtitle.EditableSubtitle; import com.github.gtache.autosubtitle.subtitle.SubtitleCollection; -import com.github.gtache.autosubtitle.subtitle.converter.ParseException; -import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter; import com.github.gtache.autosubtitle.subtitle.extractor.ExtractEvent; import com.github.gtache.autosubtitle.subtitle.extractor.ExtractException; import com.github.gtache.autosubtitle.subtitle.extractor.SubtitleExtractor; import com.github.gtache.autosubtitle.subtitle.extractor.SubtitleExtractorListener; -import com.github.gtache.autosubtitle.subtitle.gui.fx.ObservableSubtitleImpl; import javafx.application.Platform; import javafx.beans.binding.Bindings; -import javafx.beans.property.SimpleObjectProperty; -import javafx.beans.property.SimpleStringProperty; import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.ProgressBar; -import javafx.scene.control.TableColumn; -import javafx.scene.control.TableView; import javafx.scene.control.TextField; -import javafx.scene.control.cell.TextFieldTableCell; -import javafx.scene.input.KeyCode; import javafx.stage.FileChooser; import javafx.stage.Window; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.controlsfx.control.CheckComboBox; -import org.controlsfx.control.PrefixSelectionComboBox; import javax.inject.Inject; import javax.inject.Singleton; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; import java.text.MessageFormat; import java.util.List; -import java.util.Map; import java.util.ResourceBundle; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; @@ -67,34 +50,18 @@ public class FXWorkController extends AbstractFXController implements WorkContro @FXML private TextField fileField; - @FXML - private Button loadSubtitlesButton; + @FXML private Button extractButton; - @FXML - private Button resetButton; + @FXML private Button exportSoftButton; @FXML private Button exportHardButton; - @FXML - private TableView subtitlesTable; - @FXML - private TableColumn startColumn; - @FXML - private TableColumn endColumn; - @FXML - private TableColumn textColumn; + @FXML private FXMediaController mediaController; - @FXML - private Button saveSubtitlesButton; - @FXML - private Button addSubtitleButton; - @FXML - private PrefixSelectionComboBox languageCombobox; - @FXML - private CheckComboBox translationsCombobox; + @FXML private Label progressLabel; @FXML @@ -107,34 +74,25 @@ public class FXWorkController extends AbstractFXController implements WorkContro private final FXWorkModel model; private final FXWorkBinder binder; private final SubtitleExtractor subtitleExtractor; - private final Map subtitleConvertersMap; private final VideoConverter videoConverter; private final VideoLoader videoLoader; - private final Translator translator; - private final TimeFormatter timeFormatter; - private final List subtitleExtensions; - @Inject FXWorkController(final FXWorkModel model, final FXWorkBinder binder, final SubtitleExtractor subtitleExtractor, - final Map subtitleConvertersMap, final VideoLoader videoLoader, - final VideoConverter videoConverter, final Translator translator, final TimeFormatter timeFormatter) { + final VideoLoader videoLoader, final VideoConverter videoConverter) { this.model = requireNonNull(model); this.binder = requireNonNull(binder); this.subtitleExtractor = requireNonNull(subtitleExtractor); - this.subtitleConvertersMap = requireNonNull(subtitleConvertersMap); this.videoConverter = requireNonNull(videoConverter); this.videoLoader = requireNonNull(videoLoader); - this.translator = requireNonNull(translator); - this.timeFormatter = requireNonNull(timeFormatter); - this.subtitleExtensions = subtitleConvertersMap.values().stream().map(c -> "*." + c.formatName()).sorted().toList(); } @FXML - private void initialize() { - bindComboboxes(); - bindButtons(); - bindTable(); + void initialize() { + extractButton.disableProperty().bind(model.canExtractProperty().not()); + exportSoftButton.disableProperty().bind(model.canExportProperty().not()); + exportHardButton.disableProperty().bind(model.canExportProperty().not()); + bindProgress(); model.selectedSubtitleProperty().addListener((observable, oldValue, newValue) -> { @@ -148,91 +106,17 @@ public class FXWorkController extends AbstractFXController implements WorkContro 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))); - 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()) { - editFocusedCell(); - } else if (e.getCode() == KeyCode.RIGHT || - e.getCode() == KeyCode.TAB) { - subtitlesTable.getSelectionModel().selectNext(); - e.consume(); - } 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))); - startColumn.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue() == null ? null : param.getValue().start())); - startColumn.setOnEditCommit(e -> { - final var subtitle = e.getRowValue(); - subtitle.setStart(e.getNewValue()); - subtitlesTable.refresh(); - subtitlesTable.requestFocus(); - }); - endColumn.setCellFactory(TextFieldTableCell.forTableColumn(new TimeStringConverter(timeFormatter))); - endColumn.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue() == null ? null : param.getValue().end())); - endColumn.setOnEditCommit(e -> { - final var subtitle = e.getRowValue(); - subtitle.setEnd(e.getNewValue()); - subtitlesTable.refresh(); - subtitlesTable.requestFocus(); - }); - textColumn.setCellFactory(TextFieldTableCell.forTableColumn()); - textColumn.setCellValueFactory(param -> new SimpleStringProperty(param.getValue() == null ? null : param.getValue().content())); - textColumn.setOnEditCommit(e -> { - final var subtitle = e.getRowValue(); - subtitle.setContent(e.getNewValue()); - subtitlesTable.refresh(); - subtitlesTable.requestFocus(); - }); - - subtitlesTable.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> model.selectedSubtitleProperty().set(newValue)); - } - 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)); - progressDetailLabel.visibleProperty().bind(model.statusProperty().isNotEqualTo(WorkStatus.IDLE)); - progressLabel.managedProperty().bind(model.statusProperty().isNotEqualTo(WorkStatus.IDLE)); - progressBar.managedProperty().bind(model.statusProperty().isNotEqualTo(WorkStatus.IDLE)); - progressDetailLabel.managedProperty().bind(model.statusProperty().isNotEqualTo(WorkStatus.IDLE)); + progressLabel.visibleProperty().bind(model.isProgressVisibleProperty()); + progressBar.visibleProperty().bind(model.isProgressVisibleProperty()); + progressDetailLabel.visibleProperty().bind(model.isProgressVisibleProperty()); + progressLabel.managedProperty().bind(model.isProgressVisibleProperty()); + progressBar.managedProperty().bind(model.isProgressVisibleProperty()); + progressDetailLabel.managedProperty().bind(model.isProgressVisibleProperty()); progressBar.progressProperty().bindBidirectional(model.progressProperty()); } - private void deleteSelectedSubtitles() { - model.subtitles().removeAll(subtitlesTable.getSelectionModel().getSelectedItems()); - } - - private void editFocusedCell() { - final var focusedCell = subtitlesTable.getFocusModel().getFocusedCell(); - if (focusedCell != null) { - subtitlesTable.edit(focusedCell.getRow(), focusedCell.getTableColumn()); - } - } @FXML private void fileButtonPressed() { @@ -254,17 +138,17 @@ public class FXWorkController extends AbstractFXController implements WorkContro } } - private SubtitleCollection extractAsync() { + private SubtitleCollection extractAsync() { try { - return subtitleExtractor.extract(model.video(), model.videoLanguage(), model.extractionModel()); + return subtitleExtractor.extract(model.video(), model.videoLanguageProperty().get(), model.extractionModel()); } catch (final ExtractException e) { throw new CompletionException(e); } } - private void manageExtractResult(final SubtitleCollection newCollection, final Throwable t) { + private void manageExtractResult(final SubtitleCollection newCollection, final Throwable t) { if (t == null) { - loadCollection(newCollection); + model.setExtractedCollection(newCollection); } else { logger.error("Error extracting subtitles", t); showErrorDialog(resources.getString("work.extract.error.title"), MessageFormat.format(resources.getString("work.extract.error.label"), t.getMessage())); @@ -284,45 +168,6 @@ public class FXWorkController extends AbstractFXController implements WorkContro } } - @Override - public void saveSubtitles(final Path file) { - final var fileName = file.getFileName().toString(); - final var converter = subtitleConvertersMap.get(fileName.substring(fileName.lastIndexOf('.') + 1)); - if (converter == null) { - logger.warn("No converter for {}", file); - showErrorDialog(resources.getString("work.save.subtitles.missing.converter.title"), MessageFormat.format(resources.getString("work.save.subtitles.missing.converter.label"), file.getFileName())); - } else { - final var string = converter.format(model.subtitleCollection()); - try { - Files.writeString(file, string); - } catch (final IOException e) { - logger.error("Error saving subtitles {}", file, e); - showErrorDialog(resources.getString("work.save.subtitles.error.title"), MessageFormat.format(resources.getString("work.save.subtitles.error.label"), e.getMessage())); - } - } - } - - @Override - public void loadSubtitles(final Path file) { - final var fileName = file.getFileName().toString(); - final var parser = subtitleConvertersMap.get(fileName.substring(fileName.lastIndexOf('.') + 1)); - if (parser != null) { - try { - final var collection = parser.parse(file); - loadCollection(collection); - } catch (final ParseException e) { - logger.error("Error loading subtitles {}", file, e); - showErrorDialog(resources.getString("work.load.subtitles.error.title"), MessageFormat.format(resources.getString("work.load.subtitles.error.label"), e.getMessage())); - } - } - } - - private void loadCollection(final SubtitleCollection collection) { - model.subtitles().setAll(collection.subtitles().stream().map(ObservableSubtitleImpl::new).toList()); - model.originalSubtitles().clear(); - model.originalSubtitles().addAll(collection.subtitles().stream().map(ObservableSubtitleImpl::new).toList()); - model.videoLanguageProperty().set(collection.language()); - } @FXML private void exportSoftPressed() { @@ -332,27 +177,20 @@ public class FXWorkController extends AbstractFXController implements WorkContro filePicker.setSelectedExtensionFilter(extensionFilter); final var file = filePicker.showSaveDialog(window()); if (file != null) { - final var baseCollection = model.subtitleCollection(); - final var translations = model.translations(); - model.setStatus(WorkStatus.TRANSLATING); - CompletableFuture.supplyAsync(() -> Stream.concat(Stream.of(baseCollection), translations.stream().map(l -> translator.translate(baseCollection, l))).toList()) - .thenApplyAsync(c -> { - model.setStatus(WorkStatus.EXPORTING); - return c; - }, Platform::runLater) - .thenAcceptAsync(collections -> { - try { - videoConverter.addSoftSubtitles(model.video(), collections, file.toPath()); - } catch (final IOException e) { - throw new CompletionException(e); - } - }).whenCompleteAsync((v, t) -> { - if (t != null) { - logger.error("Error exporting subtitles", t); - showErrorDialog(resources.getString("work.export.error.title"), MessageFormat.format(resources.getString("work.export.error.label"), t.getMessage())); - } - model.setStatus(WorkStatus.IDLE); - }, Platform::runLater); + model.setStatus(WorkStatus.EXPORTING); + CompletableFuture.runAsync(() -> { + try { + videoConverter.addSoftSubtitles(model.video(), model.collections().values(), file.toPath()); + } catch (final IOException e) { + throw new CompletionException(e); + } + }).whenCompleteAsync((v, t) -> { + if (t != null) { + logger.error("Error exporting subtitles", t); + showErrorDialog(resources.getString("work.export.error.title"), MessageFormat.format(resources.getString("work.export.error.label"), t.getMessage())); + } //else show info dialog + model.setStatus(WorkStatus.IDLE); + }, Platform::runLater); } } @@ -366,7 +204,7 @@ public class FXWorkController extends AbstractFXController implements WorkContro if (file != null) { CompletableFuture.runAsync(() -> { try { - videoConverter.addHardSubtitles(model.video(), model.subtitleCollection(), file.toPath()); + videoConverter.addHardSubtitles(model.video(), model.collections().get(model.videoLanguageProperty().get()), file.toPath()); } catch (final IOException e) { throw new CompletionException(e); } @@ -374,7 +212,7 @@ public class FXWorkController extends AbstractFXController implements WorkContro if (t != null) { logger.error("Error exporting subtitles", t); showErrorDialog(resources.getString("work.export.error.title"), MessageFormat.format(resources.getString("work.export.error.label"), t.getMessage())); - } + } //else show info dialog model.setStatus(WorkStatus.IDLE); }, Platform::runLater); } @@ -386,6 +224,7 @@ public class FXWorkController extends AbstractFXController implements WorkContro return model; } + @Override public Window window() { return fileField.getScene().getWindow(); } @@ -395,16 +234,6 @@ public class FXWorkController extends AbstractFXController implements WorkContro extractSubtitles(); } - @FXML - private void resetButtonPressed() { - model.subtitles().setAll(model.originalSubtitles()); - } - - @FXML - private void addSubtitlePressed() { - model.subtitles().add(new ObservableSubtitleImpl("Enter text here...")); - } - @Override public void listen(final ExtractEvent event) { Platform.runLater(() -> { @@ -413,25 +242,5 @@ 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); - filePicker.getExtensionFilters().add(extensionFilter); - filePicker.setSelectedExtensionFilter(extensionFilter); - final var file = filePicker.showOpenDialog(window()); - loadSubtitles(file.toPath()); - } - @FXML - private void saveSubtitlesPressed() { - final var filePicker = new FileChooser(); - final var extensionFilter = new FileChooser.ExtensionFilter(ALL_SUPPORTED, subtitleExtensions); - filePicker.getExtensionFilters().add(extensionFilter); - filePicker.setSelectedExtensionFilter(extensionFilter); - final var file = filePicker.showSaveDialog(window()); - if (file != null) { - saveSubtitles(file.toPath()); - } - } } 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 f9adc79..8a05892 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 @@ -7,26 +7,22 @@ import com.github.gtache.autosubtitle.gui.WorkStatus; import com.github.gtache.autosubtitle.subtitle.EditableSubtitle; import com.github.gtache.autosubtitle.subtitle.SubtitleCollection; import com.github.gtache.autosubtitle.subtitle.extractor.ExtractionModel; -import com.github.gtache.autosubtitle.subtitle.impl.SubtitleCollectionImpl; -import javafx.beans.binding.Bindings; +import com.github.gtache.autosubtitle.subtitle.gui.fx.ObservableSubtitleCollectionImpl; +import com.github.gtache.autosubtitle.subtitle.gui.fx.ObservableSubtitleImpl; +import javafx.beans.property.BooleanProperty; import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; -import javafx.beans.property.ReadOnlyObjectProperty; -import javafx.beans.property.ReadOnlyObjectWrapper; -import javafx.beans.property.ReadOnlyStringProperty; -import javafx.beans.property.ReadOnlyStringWrapper; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.property.ReadOnlyBooleanWrapper; +import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import javafx.collections.ObservableMap; import javax.inject.Inject; import javax.inject.Singleton; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; -import java.util.stream.Collectors; /** * FX implementation of {@link WorkModel} @@ -35,51 +31,35 @@ import java.util.stream.Collectors; public class FXWorkModel implements WorkModel { private final ObjectProperty