Compare commits

...

10 Commits

15 changed files with 296 additions and 33 deletions

View File

@ -18,7 +18,7 @@ import java.util.List;
*/ */
public abstract class AbstractConverter implements Converter { public abstract class AbstractConverter implements Converter {
final boolean debug = false; final boolean debug = false;
private final String newExtension; private final String outputExtension;
String ffprobePath; String ffprobePath;
String ffmpegPath; String ffmpegPath;
String[] audioFormats; String[] audioFormats;
@ -27,8 +27,8 @@ public abstract class AbstractConverter implements Converter {
/** /**
* Initializes variables used by the abstract converter * Initializes variables used by the abstract converter
*/ */
AbstractConverter(String newExtension) { AbstractConverter(String outputExtension) {
this.newExtension = newExtension; this.outputExtension = outputExtension;
OutputUtil.setDebug(this.debug); OutputUtil.setDebug(this.debug);
try { try {
audioFormats = FileUtil.readFileLines("audio_formats.txt"); audioFormats = FileUtil.readFileLines("audio_formats.txt");
@ -127,7 +127,7 @@ public abstract class AbstractConverter implements Converter {
throw new IllegalArgumentException("The file has no valid streams. Please make sure the file exists and" + throw new IllegalArgumentException("The file has no valid streams. Please make sure the file exists and" +
" is not corrupt."); " is not corrupt.");
} }
String newPath = FileUtil.getNonCollidingPath(folder, file, newExtension); String newPath = FileUtil.getNonCollidingPath(folder, file, outputExtension);
OutputUtil.println(); OutputUtil.println();
OutputUtil.println("Preparing to start process..."); OutputUtil.println("Preparing to start process...");
OutputUtil.println("Converting " + file); OutputUtil.println("Converting " + file);
@ -138,11 +138,10 @@ public abstract class AbstractConverter implements Converter {
/** /**
* Gets the first audio stream from a list of streams * Gets the first audio stream from a list of streams
* *
* @param streams <p>A list of all streams.</p> * @param audioStreams <p>A list of all streams.</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>
*/ */
AudioStream getFirstAudioSteam(List<StreamObject> streams) { AudioStream getFirstAudioStream(List<AudioStream> audioStreams) {
List<AudioStream> audioStreams = filterStreamsByType(streams, AudioStream.class);
AudioStream audioStream = null; AudioStream audioStream = null;
if (audioStreams.size() > 0) { if (audioStreams.size() > 0) {
audioStream = audioStreams.get(0); audioStream = audioStreams.get(0);
@ -153,11 +152,10 @@ public abstract class AbstractConverter implements Converter {
/** /**
* Gets the first subtitle stream from a list of streams * Gets the first subtitle stream from a list of streams
* *
* @param streams <p>A list of all streams.</p> * @param subtitleStreams <p>A list of all subtitle streams.</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>
*/ */
SubtitleStream getFirstSubtitleStream(List<StreamObject> streams) { SubtitleStream getFirstSubtitleStream(List<SubtitleStream> subtitleStreams) {
List<SubtitleStream> subtitleStreams = filterStreamsByType(streams, SubtitleStream.class);
SubtitleStream subtitleStream = null; SubtitleStream subtitleStream = null;
if (subtitleStreams.size() > 0) { if (subtitleStreams.size() > 0) {
subtitleStream = subtitleStreams.get(0); subtitleStream = subtitleStreams.get(0);
@ -168,11 +166,10 @@ public abstract class AbstractConverter implements Converter {
/** /**
* Gets the first video stream from a list of streams * Gets the first video stream from a list of streams
* *
* @param streams <p>A list of all streams.</p> * @param videoStreams <p>A list of all streams.</p>
* @return <p>The first video stream found or null if no video streams were found.</p> * @return <p>The first video stream found or null if no video streams were found.</p>
*/ */
VideoStream getFirstVideoStream(List<StreamObject> streams) { VideoStream getFirstVideoStream(List<VideoStream> videoStreams) {
List<VideoStream> videoStreams = filterStreamsByType(streams, VideoStream.class);
VideoStream videoStream = null; VideoStream videoStream = null;
if (videoStreams.size() > 0) { if (videoStreams.size() > 0) {
videoStream = videoStreams.get(0); videoStream = videoStreams.get(0);

View File

@ -0,0 +1,122 @@
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 net.knarcraft.ffmpegconverter.utility.ListUtil;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class AdvancedConverter extends AbstractConverter {
private boolean burnFirstSubtitle = true;
private boolean burnFirstAudio = true;
private String videoCodec = "h264";
private String pixelFormat = "yuv420p";
private int audioSamplingFrequency = 48000;
private boolean moveHeaders = true;
private boolean convertToStereo = true;
private boolean preventPartialSubtitles = true;
private String[] audioLanguages = new String[]{"jpn", "eng", "0"};
private String[] subtitleLanguages = new String[]{"eng", "0"};
private String outputExtension = "mp4";
private boolean autoStreamSelection = false;
/**
* Initializes variables used by the abstract converter
*/
AdvancedConverter(Map<String, String> inputArguments) {
super(inputArguments.get("outputextension"));
}
@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);
}
if (!this.videoCodec.isEmpty()) {
command.add("-vcodec");
command.add(this.videoCodec);
}
if (!this.pixelFormat.isEmpty()) {
command.add("-pix_fmt");
command.add(this.pixelFormat);
}
if (this.audioSamplingFrequency > 0) {
command.add("-ar");
command.add("" + this.audioSamplingFrequency);
}
if (this.moveHeaders) {
command.add("-movflags");
command.add("+faststart");
}
//If the user wants to convert to an audio file, just select audio streams
if (ListUtil.listContains(audioFormats, (item) -> item.equals(this.outputExtension)) || autoStreamSelection) {
command.add(outFile);
return command.toArray(new String[0]);
}
if (!burnFirstAudio && !burnFirstSubtitle) {
//Copy all streams
command.add("-map");
command.add("0");
command.add("-c:a");
command.add("copy");
command.add("-c:s");
command.add("copy");
} else {
//Get the first audio stream in accordance with chosen languages
List<AudioStream> audioStreams = filterAudioStreams(filterStreamsByType(streams, AudioStream.class), audioLanguages);
AudioStream audioStream = getFirstAudioStream(new ArrayList<>(audioStreams));
//Get the first subtitle stream in accordance with chosen languages and signs and songs prevention
List<SubtitleStream> subtitleStreams = filterSubtitleStreams(filterStreamsByType(streams,
SubtitleStream.class), this.subtitleLanguages, this.preventPartialSubtitles);
SubtitleStream subtitleStream = getFirstSubtitleStream(new ArrayList<>(subtitleStreams));
//Get the first video stream
VideoStream videoStream = getFirstVideoStream(filterStreamsByType(streams, VideoStream.class));
//Add streams to output file
if (burnFirstAudio) {
FFMpegHelper.addAudioStream(command, audioStream, this.convertToStereo);
} else {
command.add("-map");
command.add("0:a");
command.add("-c:a");
command.add("copy");
}
if (burnFirstSubtitle) {
FFMpegHelper.addSubtitleAndVideoStream(command, subtitleStream, videoStream, file);
} else {
command.add("-map");
command.add("0:s");
command.add("-map");
command.add("0:v");
command.add("-c:s");
command.add("copy");
}
}
command.add(outFile);
return command.toArray(new String[0]);
}
@Override
public String[] getValidFormats() {
return ListUtil.concatenate(videoFormats, audioFormats);
}
}

View File

@ -49,7 +49,7 @@ public class AnimeConverter extends AbstractConverter {
//Get the first audio stream in accordance with chosen languages //Get the first audio stream in accordance with chosen languages
List<AudioStream> audioStreams = filterAudioStreams(filterStreamsByType(streams, AudioStream.class), audioLanguages); List<AudioStream> audioStreams = filterAudioStreams(filterStreamsByType(streams, AudioStream.class), audioLanguages);
AudioStream audioStream = getFirstAudioSteam(new ArrayList<>(audioStreams)); AudioStream audioStream = getFirstAudioStream(new ArrayList<>(audioStreams));
//Get the first subtitle stream in accordance with chosen languages and signs and songs prevention //Get the first subtitle stream in accordance with chosen languages and signs and songs prevention
List<SubtitleStream> subtitleStreams = filterSubtitleStreams(filterStreamsByType(streams, List<SubtitleStream> subtitleStreams = filterSubtitleStreams(filterStreamsByType(streams,
@ -57,7 +57,7 @@ public class AnimeConverter extends AbstractConverter {
SubtitleStream subtitleStream = getFirstSubtitleStream(new ArrayList<>(subtitleStreams)); SubtitleStream subtitleStream = getFirstSubtitleStream(new ArrayList<>(subtitleStreams));
//Get the first video stream //Get the first video stream
VideoStream videoStream = getFirstVideoStream(streams); VideoStream videoStream = getFirstVideoStream(filterStreamsByType(streams, VideoStream.class));
//Add streams to output file //Add streams to output file
FFMpegHelper.addAudioStream(command, audioStream, toStereo); FFMpegHelper.addAudioStream(command, audioStream, toStereo);

View File

@ -33,7 +33,7 @@ public class AudioConverter extends AbstractConverter {
} }
//Gets the first audio stream from the file and adds it to the output file //Gets the first audio stream from the file and adds it to the output file
AudioStream audioStream = getFirstAudioSteam(streams); AudioStream audioStream = getFirstAudioStream(filterStreamsByType(streams, AudioStream.class));
FFMpegHelper.addAudioStream(command, audioStream, false); FFMpegHelper.addAudioStream(command, audioStream, false);
command.add(outFile); command.add(outFile);

View File

@ -0,0 +1,64 @@
package net.knarcraft.ffmpegconverter.converter;
import net.knarcraft.ffmpegconverter.utility.FileUtil;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* The ConverterProfiles class is responsible for loading and retrieving settings for a converter
*/
public class ConverterProfiles {
private Map<String, Map<String, String>> loadedProfiles;
/**
* Instantiates a new converter profiles object
*/
public ConverterProfiles() {
loadedProfiles = new HashMap<>();
try {
loadProfiles();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Gets all available converter profiles as a set
* @return <p>A set of all loaded converter profiles.</p>
*/
public Set<String> getProfiles() {
return loadedProfiles.keySet();
}
/**
* Gets all profile settings for the given converter profile
* @param profileName <p>The name of the converter profile to get settings for.</p>
* @return <p>Settings for the converter profile.</p>
*/
public Map<String, String> getProfileSettings(String profileName) {
return loadedProfiles.get(profileName);
}
/**
* Loads all converter profiles
* @throws IOException <p>If unable to read the converter profiles file.</p>
*/
private void loadProfiles() throws IOException {
String[] profiles = FileUtil.readFileLines("converter_profiles.txt");
for (String profile : profiles) {
Map<String, String> profileSettings = new HashMap<>();
String[] settings = profile.split("\\|");
String profileName = settings[0];
for (int i = 1; i < settings.length; i++) {
String[] settingParts = settings[i].split(":");
profileSettings.put(settingParts[0], settingParts[1]);
}
loadedProfiles.put(profileName, profileSettings);
}
}
}

View File

@ -37,9 +37,9 @@ public class WebVideoConverter extends AbstractConverter {
} }
//Get first streams from the file //Get first streams from the file
SubtitleStream subtitleStream = getFirstSubtitleStream(streams); SubtitleStream subtitleStream = getFirstSubtitleStream(filterStreamsByType(streams, SubtitleStream.class));
VideoStream videoStream = getFirstVideoStream(streams); VideoStream videoStream = getFirstVideoStream(filterStreamsByType(streams, VideoStream.class));
AudioStream audioStream = getFirstAudioSteam(streams); AudioStream audioStream = getFirstAudioStream(filterStreamsByType(streams, AudioStream.class));
//Add streams to output //Add streams to output
FFMpegHelper.addSubtitleAndVideoStream(command, subtitleStream, videoStream, file); FFMpegHelper.addSubtitleAndVideoStream(command, subtitleStream, videoStream, file);

View File

@ -8,7 +8,7 @@ import net.knarcraft.ffmpegconverter.utility.ListUtil;
public class ConverterArgument { public class ConverterArgument {
private final String name; private final String name;
private final char shorthand; private final String shorthand;
private final boolean valueRequired; private final boolean valueRequired;
private final ConverterArgumentValue valueType; private final ConverterArgumentValue valueType;
@ -19,7 +19,7 @@ public class ConverterArgument {
* @param valueRequired <p>Whether the argument must be followed by a valid value.</p> * @param valueRequired <p>Whether the argument must be followed by a valid value.</p>
* @param valueType <p>The type of value the argument requires.</p> * @param valueType <p>The type of value the argument requires.</p>
*/ */
public ConverterArgument(String name, char shorthand, boolean valueRequired, ConverterArgumentValue valueType) { public ConverterArgument(String name, String shorthand, boolean valueRequired, ConverterArgumentValue valueType) {
this.name = name; this.name = name;
this.shorthand = shorthand; this.shorthand = shorthand;
this.valueRequired = valueRequired; this.valueRequired = valueRequired;
@ -38,7 +38,7 @@ public class ConverterArgument {
* Gets the argument shorthand * Gets the argument shorthand
* @return <p>The argument shorthand</p> * @return <p>The argument shorthand</p>
*/ */
public char getShorthand() { public String getShorthand() {
return shorthand; return shorthand;
} }

View File

@ -0,0 +1,46 @@
package net.knarcraft.ffmpegconverter.parser;
import net.knarcraft.ffmpegconverter.utility.FileUtil;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
class ConverterArgumentsLoader {
private List<ConverterArgument> converterArguments;
/**
* Instantiates a new converter arguments loader and loads converter arguments
*/
ConverterArgumentsLoader() {
this.converterArguments = new ArrayList<>();
try {
loadConverterArguments();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Gets loaded converter arguments
* @return <p>The loaded converter arguments.</p>
*/
List<ConverterArgument> getConverterArguments() {
return new ArrayList<>(this.converterArguments);
}
/**
* Loads all converter arguments
* @throws IOException <p>If unable to read the converter argument file.</p>
*/
private void loadConverterArguments() throws IOException {
String[] arguments = FileUtil.readFileLines("converter_arguments.txt");
for (String argument : arguments) {
String[] argumentFields = argument.split("\\|");
converterArguments.add(new ConverterArgument(argumentFields[0], argumentFields[1], Boolean.parseBoolean(argumentFields[2]),
ConverterArgumentValue.valueOf(argumentFields[3])));
}
}
}

View File

@ -36,7 +36,7 @@ public class SubtitleStream extends AbstractStream implements StreamObject {
* *
* @return <p>The title of the subtitle stream.</p> * @return <p>The title of the subtitle stream.</p>
*/ */
private String getTitle() { public String getTitle() {
return this.title; return this.title;
} }
@ -87,8 +87,8 @@ public class SubtitleStream extends AbstractStream implements StreamObject {
return false; return false;
} }
String titleLowercase = getTitle().toLowerCase(); String titleLowercase = getTitle().toLowerCase();
return !titleLowercase.matches("signs?[ &\\/a-z]+songs?") && return !titleLowercase.matches(".*signs?[ &/a-z]+songs?.*") &&
!titleLowercase.matches("songs?[ &\\/a-z]+signs?") && !titleLowercase.matches(".*songs?[ &/a-z]+signs?.*") &&
!titleLowercase.matches("forced"); !titleLowercase.matches(".*forced.*");
} }
} }

View File

@ -21,7 +21,7 @@ public final class ListUtil {
* @param <T> <p>The type of the two lists.</p> * @param <T> <p>The type of the two lists.</p>
* @return <p>A new array containing all elements from the two arrays.</p> * @return <p>A new array containing all elements from the two arrays.</p>
*/ */
static <T> T[] concatenate(T[] listA, T[] listB) { public static <T> T[] concatenate(T[] listA, T[] listB) {
int listALength = listA.length; int listALength = listA.length;
int listBLength = listB.length; int listBLength = listB.length;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -53,7 +53,7 @@ public final class ListUtil {
* @param <T> Anything which can be stored in a list * @param <T> Anything which can be stored in a list
* @return True if at least one element fulfills the predicate * @return True if at least one element fulfills the predicate
*/ */
static <T> boolean listContains(T[] list, Predicate<T> predicate) { public static <T> boolean listContains(T[] list, Predicate<T> predicate) {
for (T item : list) { for (T item : list) {
if (predicate.test(item)) { if (predicate.test(item)) {
return true; return true;

View File

@ -56,8 +56,8 @@ public final class Parser {
String argumentName = currentToken.substring(2); String argumentName = currentToken.substring(2);
foundArguments = ListUtil.getMatching(converterArguments, (item) -> item.getName().equals(argumentName)); foundArguments = ListUtil.getMatching(converterArguments, (item) -> item.getName().equals(argumentName));
} else if (currentToken.startsWith("-")) { } else if (currentToken.startsWith("-")) {
char argumentShorthand = currentToken.substring(1).charAt(0); String argumentShorthand = currentToken.substring(1);
foundArguments = ListUtil.getMatching(converterArguments, (item) -> item.getShorthand() == argumentShorthand); foundArguments = ListUtil.getMatching(converterArguments, (item) -> item.getShorthand().equals(argumentShorthand));
} else { } else {
throw new IllegalArgumentException("Unexpected value when not given an argument."); throw new IllegalArgumentException("Unexpected value when not given an argument.");
} }

View File

@ -0,0 +1,15 @@
14
recursions|r|true|INT
infile|i|true|STRING
audiolang|al|true|COMMA_SEPARATED_LIST
subtitlelang|sl|true|COMMA_SEPARATED_LIST
tostereo|ts|false|BOOLEAN
preventpartialsubtitles|p|false|BOOLEAN
outext|o|true|STRING
autostreamselection|as|false|BOOLEAN
burnfirstsubtitle|bs|false|BOOLEAN
burnfirstaudio|ba|false|BOOLEAN
videocodec|vc|true|STRING
pixelformat|pf|true|STRING
audiosamplingfrequency|asf|true|INT
moveheaders|m|false|BOOLEAN

View File

@ -0,0 +1,5 @@
4
Anime|audiolang:jpn,0|subtitlelang:eng,0|tostereo:true|preventpartialsubtitles:true|outext:mp4|videocodec:h264|pixelformat:yuv420p|audiosampling:48000|moveheaders:true|burnfirstaudio:true|burnfirstsubtitle:true
WebVideo|audiolang:*|subtitlelang:*|tostereo:true|preventpartialsubtitles:false|outext:|videocodec:h264|pixelformat:yuv420p|audiosampling:48000|moveheaders:true|burnfirstaudio:true|burnfirstsubtitle:true
Video|audiolang:*|subtitlelang:*|tostereo:false|preventpartialsubtitles:false|outext:|videocodec:|pixelformat:|audiosampling:|moveheaders:true|burnfirstaudio:true|burnfirstsubtitle:true
Audio|audiolang:*|subtitlelang:*|tostereo:false|preventpartialsubtitles:false|outext:|videocodec:|pixelformat:|audiosampling:|moveheaders:|burnfirstaudio:|burnfirstsubtitle:

View File

@ -0,0 +1,14 @@
package net.knarcraft.ffmpegconverter.parser;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class ConverterArgumentsLoaderTest {
@Test
public void loadTest() {
ConverterArgumentsLoader loader = new ConverterArgumentsLoader();
assertEquals(14, loader.getConverterArguments().size());
}
}

View File

@ -18,9 +18,9 @@ public class ParserTest {
@Before @Before
public void setUp() { public void setUp() {
validArguments = new ArrayList<>(); validArguments = new ArrayList<>();
validArguments.add(new ConverterArgument("anargument", 'a', true, ConverterArgumentValue.STRING)); validArguments.add(new ConverterArgument("anargument", "a", true, ConverterArgumentValue.STRING));
validArguments.add(new ConverterArgument("turnoff", 't', false, ConverterArgumentValue.BOOLEAN)); validArguments.add(new ConverterArgument("turnoff", "t", false, ConverterArgumentValue.BOOLEAN));
validArguments.add(new ConverterArgument("turnon", 'o', false, ConverterArgumentValue.BOOLEAN)); validArguments.add(new ConverterArgument("turnon", "o", false, ConverterArgumentValue.BOOLEAN));
} }
@Test @Test