commit 8993710c38af9df4786ea3d44de4d1c1410bd5e0 Author: Guillaume Tâche Date: Fri Dec 29 20:01:04 2023 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..abcfe42 --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +.idea/fastRequest +.idea/jpa-buddy.xml +.idea/sonarlint +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..c2621b8 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..df5f35d --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..8716da8 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..4dd05b0 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,680 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..e208e59 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..6d50cd4 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..9661ac7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..47d6176 --- /dev/null +++ b/pom.xml @@ -0,0 +1,17 @@ + + + 4.0.0 + + com.github.gtache + ffmpeg + 1.0-SNAPSHOT + + + 21 + 21 + UTF-8 + + + \ No newline at end of file diff --git a/src/main/java/com/github/gtache/ffmpeg/AbstractFFmpegRunner.java b/src/main/java/com/github/gtache/ffmpeg/AbstractFFmpegRunner.java new file mode 100644 index 0000000..ae188e4 --- /dev/null +++ b/src/main/java/com/github/gtache/ffmpeg/AbstractFFmpegRunner.java @@ -0,0 +1,85 @@ +package com.github.gtache.ffmpeg; + +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.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class AbstractFFmpegRunner implements FFmpegRunner { + + private static final Logger logger = LogManager.getLogger(AbstractFFmpegRunner.class); + private final String exe; + + protected AbstractFFmpegRunner(final String exe) { + this.exe = Objects.requireNonNull(exe); + } + + @Override + public void runCommand(final List args) throws IOException { + final var copy = new ArrayList<>(args); + copy.add(0, exe); + final var builder = new ProcessBuilder(copy).redirectErrorStream(true); + logger.info("Running {}", copy); + final var process = builder.start(); + new Thread(() -> { + try (final var reader = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) { + while (reader.ready()) { + logger.info(reader.readLine()); + } + } catch (final IOException e) { + logger.error("Error reading process output", e); + } + }).start(); + try { + process.waitFor(); + logger.info("Finished {}", copy); + } catch (final InterruptedException e) { + process.destroy(); + logger.error("Couldn't wait for {}", copy, e); + } + } + + protected static void addConversionArguments(final Format format, final Quality quality, final Compression compression, final List args) { + args.add("-c:v"); + switch (format) { + case H264 -> args.add("libx264"); + case H265 -> { + args.add("libx265"); + args.add("-vtag"); + args.add("hvc1"); + } + } + switch (compression) { + case LOW -> { + args.add("-preset"); + args.add("fast"); + } + case NORMAL -> { + + } + case HIGH -> { + args.add("-preset"); + args.add("veryslow"); + } + } + switch (quality) { + case LOW -> { + args.add("-crf"); + args.add(format == Format.H264 ? "26" : "30"); + } + case NORMAL -> { + + } + case HIGH -> { + args.add("-crf"); + args.add(format == Format.H264 ? "18" : "22"); + } + } + } +} diff --git a/src/main/java/com/github/gtache/ffmpeg/BindingConstants.java b/src/main/java/com/github/gtache/ffmpeg/BindingConstants.java new file mode 100644 index 0000000..d31ece0 --- /dev/null +++ b/src/main/java/com/github/gtache/ffmpeg/BindingConstants.java @@ -0,0 +1,5 @@ +package com.github.gtache.ffmpeg; + +public class BindingConstants { + public static final String FFMPEG_EXE = "ffmpeg.exe"; +} diff --git a/src/main/java/com/github/gtache/ffmpeg/Compression.java b/src/main/java/com/github/gtache/ffmpeg/Compression.java new file mode 100644 index 0000000..9dfa148 --- /dev/null +++ b/src/main/java/com/github/gtache/ffmpeg/Compression.java @@ -0,0 +1,5 @@ +package com.github.gtache.ffmpeg; + +public enum Compression { + LOW, NORMAL, HIGH +} diff --git a/src/main/java/com/github/gtache/ffmpeg/Converter.java b/src/main/java/com/github/gtache/ffmpeg/Converter.java new file mode 100644 index 0000000..57a1012 --- /dev/null +++ b/src/main/java/com/github/gtache/ffmpeg/Converter.java @@ -0,0 +1,12 @@ +package com.github.gtache.ffmpeg; + +import java.io.IOException; + +public interface Converter { + + default void convertTo(final String file, final String output, final Format format) throws IOException { + convertTo(file, output, format, Quality.NORMAL, Compression.NORMAL); + } + + void convertTo(final String file, final String output, final Format format, final Quality quality, final Compression compression) throws IOException; +} diff --git a/src/main/java/com/github/gtache/ffmpeg/FFmpegConverter.java b/src/main/java/com/github/gtache/ffmpeg/FFmpegConverter.java new file mode 100644 index 0000000..5f14f85 --- /dev/null +++ b/src/main/java/com/github/gtache/ffmpeg/FFmpegConverter.java @@ -0,0 +1,29 @@ +package com.github.gtache.ffmpeg; + +import com.google.inject.name.Named; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.io.IOException; +import java.util.ArrayList; + +import static com.github.gtache.ffmpeg.BindingConstants.FFMPEG_EXE; + +@Singleton +public class FFmpegConverter extends AbstractFFmpegRunner implements Converter { + @Inject + FFmpegConverter(@Named(FFMPEG_EXE) final String exe) { + super(exe); + } + + @Override + public void convertTo(final String file, final String output, final Format format, final Quality quality, final Compression compression) throws IOException { + final var args = new ArrayList(); + args.add("-i"); + args.add(file); + addConversionArguments(format, quality, compression, args); + args.add(output); + args.add("-y"); + runCommand(args); + } +} diff --git a/src/main/java/com/github/gtache/ffmpeg/FFmpegHandler.java b/src/main/java/com/github/gtache/ffmpeg/FFmpegHandler.java new file mode 100644 index 0000000..9d9a54d --- /dev/null +++ b/src/main/java/com/github/gtache/ffmpeg/FFmpegHandler.java @@ -0,0 +1,49 @@ +package com.github.gtache.ffmpeg; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import static com.github.gtache.ffmpeg.BindingConstants.FFMPEG_EXE; + +@Singleton +public class FFmpegHandler extends AbstractFFmpegRunner implements Handler { + private final FFmpegConverter converter; + private final FFmpegMerger merger; + + @Inject + FFmpegHandler(@Named(FFMPEG_EXE) final String exe, final FFmpegConverter converter, final FFmpegMerger merger) { + super(exe); + this.converter = Objects.requireNonNull(converter); + this.merger = Objects.requireNonNull(merger); + } + + @Override + public void convertTo(final String file, final String output, final Format format, final Quality quality, final Compression compression) throws IOException { + converter.convertTo(file, output, format, quality, compression); + } + + @Override + public void merge(final List files, final String output) throws IOException { + merger.merge(files, output); + } + + @Override + public void mergeAndConvert(final List files, final String output, final Format format, final Quality quality, final Compression compression) throws IOException { + final var args = new ArrayList(); + files.forEach(f -> { + args.add("-i"); + args.add(f); + }); + args.add("-filter_complex"); + args.add("concat=n=" + files.size() + ":v=1:a=1"); + addConversionArguments(format, quality, compression, args); + args.add(output); + args.add("-y"); + runCommand(args); + } +} diff --git a/src/main/java/com/github/gtache/ffmpeg/FFmpegMerger.java b/src/main/java/com/github/gtache/ffmpeg/FFmpegMerger.java new file mode 100644 index 0000000..9aae27b --- /dev/null +++ b/src/main/java/com/github/gtache/ffmpeg/FFmpegMerger.java @@ -0,0 +1,32 @@ +package com.github.gtache.ffmpeg; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.List; + +import static com.github.gtache.ffmpeg.BindingConstants.FFMPEG_EXE; + +@Singleton +public class FFmpegMerger extends AbstractFFmpegRunner implements Merger { + + @Inject + FFmpegMerger(@Named(FFMPEG_EXE) final String exe) { + super(exe); + } + + @Override + public void merge(final List files, final String output) throws IOException { + final var absoluteFiles = files.stream().map(f -> "file " + Paths.get(f).toFile().getAbsolutePath()).toList(); + final var tmpFile = Files.createTempFile("merge", ".txt"); + tmpFile.toFile().deleteOnExit(); + Files.write(tmpFile, absoluteFiles, StandardCharsets.UTF_8, StandardOpenOption.TRUNCATE_EXISTING); + final var args = List.of("-safe", "0", "-f", "concat", "-i", tmpFile.toFile().getAbsolutePath(), "-c", "copy", output); + runCommand(args); + } +} diff --git a/src/main/java/com/github/gtache/ffmpeg/FFmpegModule.java b/src/main/java/com/github/gtache/ffmpeg/FFmpegModule.java new file mode 100644 index 0000000..43a181e --- /dev/null +++ b/src/main/java/com/github/gtache/ffmpeg/FFmpegModule.java @@ -0,0 +1,16 @@ +package com.github.gtache.ffmpeg; + +import com.google.inject.AbstractModule; +import com.google.inject.name.Names; + +import static com.github.gtache.ffmpeg.BindingConstants.FFMPEG_EXE; + +public class FFmpegModule extends AbstractModule { + @Override + protected void configure() { + bindConstant().annotatedWith(Names.named(FFMPEG_EXE)).to("C:\\Program Files\\FFmpeg\\bin\\ffmpeg.exe"); + bind(Merger.class).to(FFmpegMerger.class); + bind(Converter.class).to(FFmpegConverter.class); + bind(Handler.class).to(FFmpegHandler.class); + } +} diff --git a/src/main/java/com/github/gtache/ffmpeg/FFmpegRunner.java b/src/main/java/com/github/gtache/ffmpeg/FFmpegRunner.java new file mode 100644 index 0000000..ca1ec9e --- /dev/null +++ b/src/main/java/com/github/gtache/ffmpeg/FFmpegRunner.java @@ -0,0 +1,13 @@ +package com.github.gtache.ffmpeg; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +public interface FFmpegRunner { + default void runCommand(final String... args) throws IOException { + runCommand(Arrays.asList(args)); + } + + void runCommand(final List args) throws IOException; +} diff --git a/src/main/java/com/github/gtache/ffmpeg/Format.java b/src/main/java/com/github/gtache/ffmpeg/Format.java new file mode 100644 index 0000000..cac5d6b --- /dev/null +++ b/src/main/java/com/github/gtache/ffmpeg/Format.java @@ -0,0 +1,5 @@ +package com.github.gtache.ffmpeg; + +public enum Format { + H264, H265 +} diff --git a/src/main/java/com/github/gtache/ffmpeg/Handler.java b/src/main/java/com/github/gtache/ffmpeg/Handler.java new file mode 100644 index 0000000..1a80f59 --- /dev/null +++ b/src/main/java/com/github/gtache/ffmpeg/Handler.java @@ -0,0 +1,20 @@ +package com.github.gtache.ffmpeg; + +import java.io.IOException; +import java.nio.file.Files; +import java.util.List; + +public interface Handler extends Merger, Converter { + + default void mergeAndConvert(final List files, final String output, final Format format, final Quality quality, final Compression compression) throws IOException { + if (!files.isEmpty()) { + final var split = files.get(0).split("\\."); + final var extension = split[split.length - 1]; + final var tmp = Files.createTempFile("merge", extension); + final var tmpFile = tmp.toFile(); + tmpFile.deleteOnExit(); + merge(files, tmpFile.getAbsolutePath()); + convertTo(tmpFile.getAbsolutePath(), output, format, quality, compression); + } + } +} diff --git a/src/main/java/com/github/gtache/ffmpeg/Main.java b/src/main/java/com/github/gtache/ffmpeg/Main.java new file mode 100644 index 0000000..c47544a --- /dev/null +++ b/src/main/java/com/github/gtache/ffmpeg/Main.java @@ -0,0 +1,7 @@ +package com.github.gtache.ffmpeg; + +public class Main { + public static void main(String[] args) { + + } +} \ No newline at end of file diff --git a/src/main/java/com/github/gtache/ffmpeg/Merger.java b/src/main/java/com/github/gtache/ffmpeg/Merger.java new file mode 100644 index 0000000..3e6bb41 --- /dev/null +++ b/src/main/java/com/github/gtache/ffmpeg/Merger.java @@ -0,0 +1,9 @@ +package com.github.gtache.ffmpeg; + +import java.io.IOException; +import java.util.List; + +public interface Merger { + + void merge(final List files, final String output) throws IOException; +} diff --git a/src/main/java/com/github/gtache/ffmpeg/Quality.java b/src/main/java/com/github/gtache/ffmpeg/Quality.java new file mode 100644 index 0000000..c655583 --- /dev/null +++ b/src/main/java/com/github/gtache/ffmpeg/Quality.java @@ -0,0 +1,5 @@ +package com.github.gtache.ffmpeg; + +public enum Quality { + LOW, NORMAL, HIGH +} diff --git a/src/main/java/com/github/gtache/plex/BindingConstants.java b/src/main/java/com/github/gtache/plex/BindingConstants.java new file mode 100644 index 0000000..9165227 --- /dev/null +++ b/src/main/java/com/github/gtache/plex/BindingConstants.java @@ -0,0 +1,5 @@ +package com.github.gtache.plex; + +public class BindingConstants { + +} diff --git a/src/main/java/com/github/gtache/plex/Compresser.java b/src/main/java/com/github/gtache/plex/Compresser.java new file mode 100644 index 0000000..347877c --- /dev/null +++ b/src/main/java/com/github/gtache/plex/Compresser.java @@ -0,0 +1,9 @@ +package com.github.gtache.plex; + +import java.io.IOException; + +public interface Compresser { + + void compress(final String file) throws IOException; + void compressDirectory(final String path) throws IOException; +} diff --git a/src/main/java/com/github/gtache/plex/Main.java b/src/main/java/com/github/gtache/plex/Main.java new file mode 100644 index 0000000..52d7ccd --- /dev/null +++ b/src/main/java/com/github/gtache/plex/Main.java @@ -0,0 +1,28 @@ +package com.github.gtache.plex; + +import com.github.gtache.ffmpeg.Compression; +import com.github.gtache.ffmpeg.Quality; +import com.google.inject.Guice; + +import java.io.IOException; + +public class Main { + + public static void main(final String[] args) throws IOException { + final var injector = Guice.createInjector(new PlexModule()); + switch (args[0]) { + case "merge" -> { + final var merger = injector.getInstance(Merger.class); + merger.mergeDirectory(args[1], Quality.NORMAL, Compression.NORMAL); + } + case "compress" -> { + final var compresser = injector.getInstance(Compresser.class); + compresser.compress(args[1]); + } + case "rename" -> { + final var renamer = injector.getInstance(Renamer.class); + renamer.renameSeries(args[1]); + } + } + } +} diff --git a/src/main/java/com/github/gtache/plex/Merger.java b/src/main/java/com/github/gtache/plex/Merger.java new file mode 100644 index 0000000..0bdc384 --- /dev/null +++ b/src/main/java/com/github/gtache/plex/Merger.java @@ -0,0 +1,11 @@ +package com.github.gtache.plex; + +import com.github.gtache.ffmpeg.Compression; +import com.github.gtache.ffmpeg.Quality; + +import java.io.IOException; + +public interface Merger { + + void mergeDirectory(final String path, final Quality quality, final Compression compression) throws IOException; +} diff --git a/src/main/java/com/github/gtache/plex/Pair.java b/src/main/java/com/github/gtache/plex/Pair.java new file mode 100644 index 0000000..25b75f6 --- /dev/null +++ b/src/main/java/com/github/gtache/plex/Pair.java @@ -0,0 +1,5 @@ +package com.github.gtache.plex; + +public record Pair(K key, V value) { + +} diff --git a/src/main/java/com/github/gtache/plex/PlexCompresser.java b/src/main/java/com/github/gtache/plex/PlexCompresser.java new file mode 100644 index 0000000..f3c598a --- /dev/null +++ b/src/main/java/com/github/gtache/plex/PlexCompresser.java @@ -0,0 +1,45 @@ +package com.github.gtache.plex; + +import com.github.gtache.ffmpeg.Compression; +import com.github.gtache.ffmpeg.Converter; +import com.github.gtache.ffmpeg.Format; +import com.github.gtache.ffmpeg.Quality; + +import javax.inject.Inject; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Objects; +import java.util.regex.Pattern; + +public class PlexCompresser implements Compresser { + + private static final Pattern EXTENSION_PATTERN = Pattern.compile("\\.([A-Za-z0-9]{2,3})$"); + private final Converter converter; + + @Inject + PlexCompresser(final Converter converter) { + this.converter = Objects.requireNonNull(converter); + } + + @Override + public void compress(final String file) throws IOException { + final var matcher = EXTENSION_PATTERN.matcher(file); + if (matcher.find()) { + final var extension = matcher.group(1); + final var newName = matcher.replaceAll("-archived." + extension); + converter.convertTo(file, newName, Format.H265, Quality.HIGH, Compression.HIGH); + } else { + throw new IllegalArgumentException("File doesn't have an extension : " + file); + } + } + + @Override + public void compressDirectory(final String path) throws IOException { + try (final var files = Files.list(Paths.get(path))) { + for (final var file : files.toList()) { + compress(file.toFile().getAbsolutePath()); + } + } + } +} diff --git a/src/main/java/com/github/gtache/plex/PlexMerger.java b/src/main/java/com/github/gtache/plex/PlexMerger.java new file mode 100644 index 0000000..05df69e --- /dev/null +++ b/src/main/java/com/github/gtache/plex/PlexMerger.java @@ -0,0 +1,79 @@ +package com.github.gtache.plex; + +import com.github.gtache.ffmpeg.Compression; +import com.github.gtache.ffmpeg.Format; +import com.github.gtache.ffmpeg.Handler; +import com.github.gtache.ffmpeg.Quality; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import javax.inject.Inject; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class PlexMerger implements Merger { + private static final Logger logger = LogManager.getLogger(PlexMerger.class); + private static final Pattern PART_PATTERN = Pattern.compile(" - (cd|dis[ck]|dvd|p(?:ar)?t)([0-9])\\.([a-z0-9]{2,3})$"); + private final Handler handler; + + @Inject + PlexMerger(final Handler handler) { + this.handler = Objects.requireNonNull(handler); + } + + @Override + public void mergeDirectory(final String path, final Quality quality, final Compression compression) throws IOException { + final var mapping = new HashMap>>>(); + try (final var files = Files.list(Paths.get(path))) { + files.forEach(p -> { + final var name = p.getFileName().toString(); + final var matcher = PART_PATTERN.matcher(name); + if (matcher.find()) { + final var type = matcher.group(1); + final var number = Integer.parseInt(matcher.group(2)); + final var extension = matcher.group(3); + final var basename = PART_PATTERN.matcher(name).replaceAll(""); + if (mapping.containsKey(basename)) { + final var pair = mapping.get(basename); + if (pair.key().equals(type)) { + final var set = pair.value(); + if (set.stream().anyMatch(existing -> existing.key().equals(number))) { + throw new IllegalArgumentException("Different extensions for same file " + basename + " - " + number); + } + set.add(new Pair<>(number, extension)); + } else { + throw new IllegalArgumentException("Different types for " + basename + " : " + type + " and " + pair.key()); + } + } else { + final var set = new HashSet>(); + set.add(new Pair<>(number, extension)); + mapping.put(basename, new Pair<>(type, set)); + } + } + }); + } + for (final var entry : mapping.entrySet()) { + final var name = entry.getKey(); + final var pair = entry.getValue(); + final var type = pair.key(); + final var set = pair.value(); + final var numberSet = set.stream().map(Pair::key).collect(Collectors.toSet()); + if (IntStream.range(1, set.size() + 1).allMatch(numberSet::contains)) { + final var files = set.stream().sorted(Comparator.comparing(Pair::key)).map(i -> path + File.separator + name + " - " + type + i.key() + "." + i.value()).toList(); + handler.mergeAndConvert(files, path + File.separator + name + ".mp4", Format.H265, quality, compression); + } else { + logger.warn("Missing parts for {}", name); + } + } + } +} diff --git a/src/main/java/com/github/gtache/plex/PlexModule.java b/src/main/java/com/github/gtache/plex/PlexModule.java new file mode 100644 index 0000000..139cb4d --- /dev/null +++ b/src/main/java/com/github/gtache/plex/PlexModule.java @@ -0,0 +1,14 @@ +package com.github.gtache.plex; + +import com.github.gtache.ffmpeg.FFmpegModule; +import com.google.inject.AbstractModule; + +public class PlexModule extends AbstractModule { + @Override + protected void configure() { + install(new FFmpegModule()); + bind(Merger.class).to(PlexMerger.class); + bind(Compresser.class).to(PlexCompresser.class); + bind(Renamer.class).to(PlexRenamer.class); + } +} diff --git a/src/main/java/com/github/gtache/plex/PlexRenamer.java b/src/main/java/com/github/gtache/plex/PlexRenamer.java new file mode 100644 index 0000000..2bc38d9 --- /dev/null +++ b/src/main/java/com/github/gtache/plex/PlexRenamer.java @@ -0,0 +1,88 @@ +package com.github.gtache.plex; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.regex.Pattern; + +public class PlexRenamer implements Renamer { + + private static final Pattern NAME_PATTERN = Pattern.compile("([^(]+)\\(([0-9]+)\\)"); + private static final Pattern SEASON_PATTERN = Pattern.compile("[sS](?:eason )?([0-9]+)"); + private static final Pattern EPISODE_PATTERN = Pattern.compile("[eE](?:pisode )?([0-9]+)(?:( ?- ?[^.]+)?|[^.]*)\\.([a-zA-Z0-9]+)$"); + + @Override + public void renameSeries(final String path, final String name, final int year) { + final var folderO = Paths.get(path); + try (final var seasonStreams = Files.list(folderO)) { + seasonStreams.forEach(p -> { + try { + renameSeason(p, name, year); + } catch (final IOException e) { + throw new RuntimeException(e); + } + }); + } catch (final IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void renameSeries(final String path) { + final var folderO = Paths.get(path); + final var folderName = folderO.getFileName().toString(); + final var matcher = NAME_PATTERN.matcher(folderName); + if (matcher.matches()) { + final var name = matcher.group(1).trim(); + final var year = Integer.parseInt(matcher.group(2)); + renameSeries(path, name, year); + } else { + System.out.println("Not a correct series name : " + folderName); + } + } + + private static void renameSeason(final Path path, final String name, final int year) throws IOException { + final var folderName = path.getFileName().toString(); + final var matcher = SEASON_PATTERN.matcher(folderName); + if (matcher.find()) { + final var seasonInt = Integer.parseInt(matcher.group(1)); + final String seasonString; + if (seasonInt < 10) { + seasonString = "0" + seasonInt; + } else { + seasonString = String.valueOf(seasonInt); + } + try (final var episodeStream = Files.list(path)) { + episodeStream.forEach(p -> { + try { + renameEpisode(p, name, year, seasonString); + } catch (final IOException e) { + throw new RuntimeException(e); + } + }); + } + Files.move(path, path.resolveSibling("Season " + seasonString)); + } + } + + private static void renameEpisode(final Path path, final String name, final int year, final String seasonString) throws IOException { + final var fileName = path.getFileName().toString(); + final var matcher = EPISODE_PATTERN.matcher(fileName); + if (matcher.find()) { + final var episodeInt = Integer.parseInt(matcher.group(1)); + final String episodeString; + if (episodeInt < 10) { + episodeString = "0" + episodeInt; + } else { + episodeString = String.valueOf(episodeInt); + } + final var additionalInfo = matcher.group(2) == null ? "" : matcher.group(2); + final var extension = matcher.group(3); + final var newName = additionalInfo.isBlank() ? + name + " (" + year + ") - S" + seasonString + "E" + episodeString + "." + extension : + name + " (" + year + ") - S" + seasonString + "E" + episodeString + " - " + additionalInfo + "." + extension; + Files.move(path, path.resolveSibling(newName)); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/github/gtache/plex/Renamer.java b/src/main/java/com/github/gtache/plex/Renamer.java new file mode 100644 index 0000000..2be4f5c --- /dev/null +++ b/src/main/java/com/github/gtache/plex/Renamer.java @@ -0,0 +1,7 @@ +package com.github.gtache.plex; + +public interface Renamer { + + void renameSeries(final String path, final String name, final int year); + void renameSeries(final String path); +} diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml new file mode 100644 index 0000000..11b7e7d --- /dev/null +++ b/src/main/resources/log4j2.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + \ No newline at end of file