Adds new converters
Adds a MKV to HEVC converter which aims to reduce file-sizes Adds a MKV to MP4 transcoder which allows selection of each type of stream
This commit is contained in:
parent
f81a21b9e9
commit
2346e651ef
6
pom.xml
6
pom.xml
@ -104,5 +104,11 @@
|
||||
<version>4.11</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains</groupId>
|
||||
<artifactId>annotations</artifactId>
|
||||
<version>20.1.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -3,7 +3,9 @@ package net.knarcraft.ffmpegconverter;
|
||||
import net.knarcraft.ffmpegconverter.converter.AnimeConverter;
|
||||
import net.knarcraft.ffmpegconverter.converter.AudioConverter;
|
||||
import net.knarcraft.ffmpegconverter.converter.Converter;
|
||||
import net.knarcraft.ffmpegconverter.converter.MKVToMP4Transcoder;
|
||||
import net.knarcraft.ffmpegconverter.converter.MkvH264Converter;
|
||||
import net.knarcraft.ffmpegconverter.converter.MkvH265ReducedConverter;
|
||||
import net.knarcraft.ffmpegconverter.converter.VideoConverter;
|
||||
import net.knarcraft.ffmpegconverter.converter.WebVideoConverter;
|
||||
import net.knarcraft.ffmpegconverter.utility.FileUtil;
|
||||
@ -12,6 +14,7 @@ import net.knarcraft.ffmpegconverter.utility.OutputUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Scanner;
|
||||
|
||||
@ -61,11 +64,12 @@ class Main {
|
||||
*/
|
||||
private static Converter loadConverter() throws IOException {
|
||||
int choice = getChoice("Which converter do you want do use?\n1. Anime to web mp4\n2. Audio converter\n" +
|
||||
"3. Video converter\n4. Web video converter\n5. MKV to h264 converter", 1, 5);
|
||||
"3. Video converter\n4. Web video converter\n5. MKV to h264 converter\n6. MKV to h265 reduced " +
|
||||
"converter\n7. MKV to MP4 transcoder", 1, 7);
|
||||
|
||||
switch (choice) {
|
||||
case 1:
|
||||
return animeConverter();
|
||||
return generateAnimeConverter();
|
||||
case 2:
|
||||
return new AudioConverter(FFPROBE_PATH, FFMPEG_PATH, getChoice("<output extension>"));
|
||||
case 3:
|
||||
@ -74,6 +78,10 @@ class Main {
|
||||
return new WebVideoConverter(FFPROBE_PATH, FFMPEG_PATH, getChoice("<output extension>"));
|
||||
case 5:
|
||||
return new MkvH264Converter(FFPROBE_PATH, FFMPEG_PATH);
|
||||
case 6:
|
||||
return new MkvH265ReducedConverter(FFPROBE_PATH, FFMPEG_PATH);
|
||||
case 7:
|
||||
return generateMKVToMP4Transcoder();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -96,18 +104,56 @@ class Main {
|
||||
OutputUtil.println("No valid files found in folder.");
|
||||
}
|
||||
} else if (fileOrFolder.exists()) {
|
||||
converter.convert(fileOrFolder);
|
||||
String path = fileOrFolder.getPath();
|
||||
if (Arrays.stream(converter.getValidFormats()).anyMatch((format) -> format.equalsIgnoreCase(
|
||||
path.substring(path.lastIndexOf('.') + 1)))) {
|
||||
converter.convert(fileOrFolder);
|
||||
} else {
|
||||
OutputUtil.println("The specified file " + fileOrFolder.getAbsolutePath() + " is not supported for " +
|
||||
"the selected converter.");
|
||||
}
|
||||
} else {
|
||||
OutputUtil.println("Path " + fileOrFolder.getAbsolutePath() + " does not point to any file or folder.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the anime converter
|
||||
* Initializes and returns the MKV to MP4 transcoder
|
||||
*
|
||||
* @return <p>The initialized transcoder</p>
|
||||
* @throws IOException <p>If unable to print to output</p>
|
||||
*/
|
||||
private static Converter generateMKVToMP4Transcoder() throws IOException {
|
||||
OutputUtil.println("[Audio stream index 0-n] [Subtitle stream index 0-n] [Video stream index 0-n]\nYour input: ");
|
||||
List<String> input = readInput(3);
|
||||
int audioStreamIndex = -1;
|
||||
int subtitleStreamIndex = -1;
|
||||
int videoStreamIndex = -1;
|
||||
|
||||
try {
|
||||
if (input.size() > 0) {
|
||||
audioStreamIndex = Integer.parseInt(input.get(0));
|
||||
}
|
||||
if (input.size() > 1) {
|
||||
subtitleStreamIndex = Integer.parseInt(input.get(1));
|
||||
}
|
||||
if (input.size() > 2) {
|
||||
videoStreamIndex = Integer.parseInt(input.get(2));
|
||||
}
|
||||
return new MKVToMP4Transcoder(FFPROBE_PATH, FFMPEG_PATH, audioStreamIndex, subtitleStreamIndex, videoStreamIndex);
|
||||
} catch (NumberFormatException exception) {
|
||||
OutputUtil.println("Audio, Subtitle or Video stream index is not a number");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes and returns the anime converter
|
||||
*
|
||||
* @return <p>The initialized anime converter</p>
|
||||
* @throws IOException <p>If reading or writing fails.</p>
|
||||
*/
|
||||
private static Converter animeConverter() throws IOException {
|
||||
private static Converter generateAnimeConverter() throws IOException {
|
||||
OutputUtil.println("[Audio languages jpn,eng,ger,fre] [Subtitle languages eng,ger,fre] [Convert to stereo if " +
|
||||
"necessary true/false] [Prevent signs&songs subtitles true/false] [Forced audio index 0-n] " +
|
||||
"[Forced subtitle index 0-n] [Subtitle name filter]\nYour input: ");
|
||||
|
@ -7,6 +7,8 @@ import net.knarcraft.ffmpegconverter.streams.VideoStream;
|
||||
import net.knarcraft.ffmpegconverter.utility.FFMpegHelper;
|
||||
import net.knarcraft.ffmpegconverter.utility.FileUtil;
|
||||
import net.knarcraft.ffmpegconverter.utility.OutputUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@ -135,13 +137,16 @@ public abstract class AbstractConverter implements Converter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the n-th audio stream from a list of streams
|
||||
* Gets the nth audio stream from a list of streams
|
||||
*
|
||||
* @param streams <p>A list of all streams.</p>
|
||||
* @param streams <p>A list of all streams</p>
|
||||
* @param n <p>The index of the audio stream to get</p>
|
||||
* @return <p>The first audio stream found or null if no audio streams were found.</p>
|
||||
* @return <p>The first audio stream found or null if no audio streams were found</p>
|
||||
*/
|
||||
protected AudioStream getNthAudioSteam(List<StreamObject> streams, int n) {
|
||||
if (n < 0) {
|
||||
throw new IllegalArgumentException("N cannot be negative!");
|
||||
}
|
||||
List<AudioStream> audioStreams = filterStreamsByType(streams, AudioStream.class);
|
||||
AudioStream audioStream = null;
|
||||
if (audioStreams.size() > n) {
|
||||
@ -153,13 +158,16 @@ public abstract class AbstractConverter implements Converter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first subtitle stream from a list of streams
|
||||
* Gets the nth subtitle stream from a list of streams
|
||||
*
|
||||
* @param streams <p>A list of all streams.</p>
|
||||
* @param streams <p>A list of all streams</p>
|
||||
* @param n <p>The index of the subtitle stream to get</p>
|
||||
* @return <p>The first subtitle stream found or null if no subtitle streams were found.</p>
|
||||
* @return <p>The first subtitle stream found or null if no subtitle streams were found</p>
|
||||
*/
|
||||
protected SubtitleStream getNthSubtitleStream(List<StreamObject> streams, int n) {
|
||||
if (n < 0) {
|
||||
throw new IllegalArgumentException("N cannot be negative!");
|
||||
}
|
||||
List<SubtitleStream> subtitleStreams = filterStreamsByType(streams, SubtitleStream.class);
|
||||
SubtitleStream subtitleStream = null;
|
||||
if (subtitleStreams.size() > n) {
|
||||
@ -171,25 +179,28 @@ public abstract class AbstractConverter implements Converter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first video stream from a list of streams
|
||||
* Gets the nth video stream from a list of streams
|
||||
*
|
||||
* @param streams <p>A list of all streams.</p>
|
||||
* @return <p>The first video stream found or null if no video streams were found.</p>
|
||||
* @param streams <p>A list of all streams</p>
|
||||
* @param n <p>The index of the video stream to get</p>
|
||||
* @return <p>The nth video stream, or null if no video streams were found</p>
|
||||
*/
|
||||
protected VideoStream getFirstVideoStream(List<StreamObject> streams) {
|
||||
protected @Nullable VideoStream getNthVideoStream(@NotNull List<StreamObject> streams, int n) {
|
||||
if (n < 0) {
|
||||
throw new IllegalArgumentException("N cannot be negative!");
|
||||
}
|
||||
List<VideoStream> videoStreams = filterStreamsByType(streams, VideoStream.class);
|
||||
VideoStream videoStream = null;
|
||||
if (videoStreams.size() > 0) {
|
||||
if (videoStreams.size() > n) {
|
||||
videoStream = videoStreams.get(n);
|
||||
} else if (videoStreams.size() > 0) {
|
||||
videoStream = videoStreams.get(0);
|
||||
}
|
||||
if (videoStream == null) {
|
||||
throw new IllegalArgumentException("The file does not have any valid video streams.");
|
||||
}
|
||||
return videoStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void convert(File file) throws IOException {
|
||||
public void convert(@NotNull File file) throws IOException {
|
||||
processFile(file.getParentFile(), file);
|
||||
}
|
||||
|
||||
|
@ -70,7 +70,7 @@ public class AnimeConverter extends AbstractConverter {
|
||||
Math.max(this.forcedSubtitleIndex, 0));
|
||||
|
||||
//Get the first video stream
|
||||
VideoStream videoStream = getFirstVideoStream(streams);
|
||||
VideoStream videoStream = getNthVideoStream(streams, 0);
|
||||
|
||||
//Add streams to output file
|
||||
FFMpegHelper.addAudioStream(command, audioStream, this.toStereo);
|
||||
|
@ -0,0 +1,81 @@
|
||||
package net.knarcraft.ffmpegconverter.converter;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.streams.AudioStream;
|
||||
import net.knarcraft.ffmpegconverter.streams.StreamObject;
|
||||
import net.knarcraft.ffmpegconverter.streams.SubtitleStream;
|
||||
import net.knarcraft.ffmpegconverter.streams.VideoStream;
|
||||
import net.knarcraft.ffmpegconverter.utility.FFMpegHelper;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A transcoder which takes one of each stream from an MKV file and produces an MP4 file
|
||||
*/
|
||||
public class MKVToMP4Transcoder extends AbstractConverter {
|
||||
|
||||
private final int videoStreamIndex;
|
||||
private final int audioStreamIndex;
|
||||
private final int subtitleStreamIndex;
|
||||
|
||||
/**
|
||||
* Instantiates a new mkv to mp4 transcoder
|
||||
*
|
||||
* @param ffprobePath <p>Path/command to ffprobe.</p>
|
||||
* @param ffmpegPath <p>Path/command to ffmpeg.</p>
|
||||
* @param audioStreamIndex <p>The relative index of the audio stream to use (0 or below selects the first)</p>
|
||||
* @param subtitleStreamIndex <p>The relative index of the subtitle stream to use (0 or below selects the first)</p>
|
||||
* @param videoStreamIndex <p>The relative index of the video stream to use (0 or below selects the first)</p>
|
||||
*/
|
||||
public MKVToMP4Transcoder(String ffprobePath, String ffmpegPath, int audioStreamIndex, int subtitleStreamIndex,
|
||||
int videoStreamIndex) {
|
||||
super("mp4");
|
||||
this.ffprobePath = ffprobePath;
|
||||
this.ffmpegPath = ffmpegPath;
|
||||
this.videoStreamIndex = videoStreamIndex;
|
||||
this.audioStreamIndex = audioStreamIndex;
|
||||
this.subtitleStreamIndex = subtitleStreamIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getValidFormats() {
|
||||
return new String[]{"mkv"};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] generateConversionCommand(String executable, File file, List<StreamObject> streams, String outFile) {
|
||||
List<String> command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, file.getName());
|
||||
if (this.debug) {
|
||||
FFMpegHelper.addDebugArguments(command, 50, 120);
|
||||
}
|
||||
|
||||
//Get the nth audio stream
|
||||
List<AudioStream> audioStreams = filterStreamsByType(streams, AudioStream.class);
|
||||
AudioStream audioStream = getNthAudioSteam(new ArrayList<>(audioStreams), Math.max(this.audioStreamIndex, 0));
|
||||
|
||||
//Get the nth subtitle stream
|
||||
List<SubtitleStream> allSubtitleStreams = filterStreamsByType(streams, SubtitleStream.class);
|
||||
SubtitleStream subtitleStream = getNthSubtitleStream(new ArrayList<>(allSubtitleStreams),
|
||||
Math.max(this.subtitleStreamIndex, 0));
|
||||
|
||||
//Get the nth video stream
|
||||
VideoStream videoStream = getNthVideoStream(streams, Math.max(this.videoStreamIndex, 0));
|
||||
|
||||
// Copy stream info
|
||||
command.add("-map_metadata");
|
||||
command.add("0");
|
||||
command.add("-movflags");
|
||||
command.add("use_metadata_tags");
|
||||
command.add("-c");
|
||||
command.add("copy");
|
||||
|
||||
//Add streams to output file
|
||||
FFMpegHelper.addAudioStream(command, audioStream, false);
|
||||
FFMpegHelper.addSubtitleAndVideoStream(command, subtitleStream, videoStream, file);
|
||||
|
||||
command.add(outFile);
|
||||
return command.toArray(new String[0]);
|
||||
}
|
||||
|
||||
}
|
@ -7,6 +7,7 @@ import net.knarcraft.ffmpegconverter.streams.VideoStream;
|
||||
import net.knarcraft.ffmpegconverter.utility.FFMpegHelper;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -34,7 +35,14 @@ public class MkvH264Converter extends AbstractConverter {
|
||||
@Override
|
||||
public String[] generateConversionCommand(String executable, File file, List<StreamObject> streams,
|
||||
String outFile) {
|
||||
List<String> command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, file.getName());
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add(executable);
|
||||
command.add("-hwaccel");
|
||||
command.add("cuda");
|
||||
command.add("-hwaccel_output_format");
|
||||
command.add("cuda");
|
||||
command.add("-i");
|
||||
command.add(file.getName());
|
||||
if (this.debug) {
|
||||
FFMpegHelper.addDebugArguments(command, 50, 120);
|
||||
}
|
||||
@ -43,8 +51,14 @@ public class MkvH264Converter extends AbstractConverter {
|
||||
if (!filterStreamsByType(streams, VideoStream.class).isEmpty()) {
|
||||
command.add("-map");
|
||||
command.add("0:v");
|
||||
command.add("-vcodec");
|
||||
command.add("h264");
|
||||
command.add("-crf");
|
||||
command.add("28");
|
||||
command.add("-codec:v");
|
||||
command.add("h264_nvenc");
|
||||
command.add("-preset");
|
||||
command.add("slow");
|
||||
command.add("-movflags");
|
||||
command.add("+faststart");
|
||||
}
|
||||
|
||||
// Map audio if present
|
||||
|
@ -0,0 +1,108 @@
|
||||
package net.knarcraft.ffmpegconverter.converter;
|
||||
|
||||
import net.knarcraft.ffmpegconverter.streams.AudioStream;
|
||||
import net.knarcraft.ffmpegconverter.streams.StreamObject;
|
||||
import net.knarcraft.ffmpegconverter.streams.SubtitleStream;
|
||||
import net.knarcraft.ffmpegconverter.streams.VideoStream;
|
||||
import net.knarcraft.ffmpegconverter.utility.FFMpegHelper;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A converter solely for the purpose of converting video streams of MKV files into h264
|
||||
*/
|
||||
public class MkvH265ReducedConverter extends AbstractConverter {
|
||||
|
||||
/**
|
||||
* Initializes variables used by the abstract converter
|
||||
*
|
||||
* @param ffprobePath <p>Path/command to ffprobe.</p>
|
||||
* @param ffmpegPath <p>Path/command to ffmpeg.</p>
|
||||
*/
|
||||
public MkvH265ReducedConverter(String ffprobePath, String ffmpegPath) {
|
||||
super("mkv");
|
||||
this.ffprobePath = ffprobePath;
|
||||
this.ffmpegPath = ffmpegPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getValidFormats() {
|
||||
return new String[]{"mkv"};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] generateConversionCommand(String executable, File file, List<StreamObject> streams,
|
||||
String outFile) {
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add(executable);
|
||||
command.add("-hwaccel");
|
||||
command.add("cuda");
|
||||
command.add("-hwaccel_output_format");
|
||||
command.add("cuda");
|
||||
command.add("-i");
|
||||
command.add(file.getName());
|
||||
|
||||
if (this.debug) {
|
||||
FFMpegHelper.addDebugArguments(command, 50, 120);
|
||||
}
|
||||
|
||||
// Map video if present
|
||||
if (!filterStreamsByType(streams, VideoStream.class).isEmpty()) {
|
||||
command.add("-map");
|
||||
command.add("0:v");
|
||||
command.add("-codec:v");
|
||||
command.add("hevc_nvenc");
|
||||
command.add("-crf");
|
||||
command.add("28");
|
||||
command.add("-preset");
|
||||
command.add("slow");
|
||||
command.add("-tag:v");
|
||||
command.add("hvc1");
|
||||
command.add("-movflags");
|
||||
command.add("+faststart");
|
||||
}
|
||||
|
||||
// Map audio if present
|
||||
if (!filterStreamsByType(streams, AudioStream.class).isEmpty()) {
|
||||
command.add("-map");
|
||||
command.add("0:a");
|
||||
}
|
||||
|
||||
// Map subtitles if present
|
||||
if (hasInternalStreams(streams)) {
|
||||
command.add("-map");
|
||||
command.add("0:s");
|
||||
command.add("-c:s");
|
||||
command.add("copy");
|
||||
}
|
||||
|
||||
command.add("-map_metadata");
|
||||
command.add("0");
|
||||
command.add("-movflags");
|
||||
command.add("use_metadata_tags");
|
||||
command.add("-f");
|
||||
command.add("matroska");
|
||||
|
||||
command.add(outFile);
|
||||
return command.toArray(new String[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the processed file has any internal subtitle streams
|
||||
*
|
||||
* @param streams <p>All parsed streams for the video file</p>
|
||||
* @return <p>True if the file has at least one internal subtitle stream</p>
|
||||
*/
|
||||
private boolean hasInternalStreams(List<StreamObject> streams) {
|
||||
for (StreamObject subtitleStream : filterStreamsByType(streams, SubtitleStream.class)) {
|
||||
if (((SubtitleStream) subtitleStream).isInternalSubtitle()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@ -41,7 +41,7 @@ public class WebVideoConverter extends AbstractConverter {
|
||||
|
||||
//Get first streams from the file
|
||||
SubtitleStream subtitleStream = getNthSubtitleStream(streams, 0);
|
||||
VideoStream videoStream = getFirstVideoStream(streams);
|
||||
VideoStream videoStream = getNthVideoStream(streams, 0);
|
||||
AudioStream audioStream = getNthAudioSteam(streams, 0);
|
||||
|
||||
//Add streams to output
|
||||
|
@ -103,7 +103,9 @@ public class SubtitleStream extends AbstractStream implements StreamObject {
|
||||
return !titleLowercase.matches(".*si(ng|gn)s?[ &/a-z]+songs?.*") &&
|
||||
!titleLowercase.matches(".*songs?[ &/a-z]+si(gn|ng)s?.*") &&
|
||||
!titleLowercase.matches(".*forced.*") &&
|
||||
!titleLowercase.matches(".*s&s.*");
|
||||
!titleLowercase.matches(".*s&s.*") &&
|
||||
!titleLowercase.matches("signs?") &&
|
||||
!titleLowercase.matches("songs?");
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user