Improves FFMpeg command generation

Adds an object for storing FFmpeg variables. This allows any argument type to be added at any time, removing the limitation of having to add to the command in a specific order.
Makes stream objects store the index of the input file they belong to.
Saves the result of probes to a record that's easy to pass around.
Always passes all probed files to the input files of ffmpeg.
Makes it easier to enable h26x encoding/decoding when necessary.
Removes the hard-coded behavior for external subtitles, and allows any stream to be an external stream.
This commit is contained in:
2024-04-08 20:00:09 +02:00
parent be88845731
commit 376d5655f2
20 changed files with 537 additions and 450 deletions

View File

@ -1,13 +1,14 @@
package net.knarcraft.ffmpegconverter.converter;
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
import net.knarcraft.ffmpegconverter.container.StreamProbeResult;
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 org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
@ -33,73 +34,50 @@ public class MkvH264Converter extends AbstractConverter {
}
@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());
public String[] generateConversionCommand(@NotNull String executable, @NotNull StreamProbeResult probeResult,
@NotNull String outFile) {
FFMpegCommand command = FFMpegHelper.getFFMpegGeneralFileCommand(executable, probeResult.parsedFiles());
List<StreamObject> streams = probeResult.parsedStreams();
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("-crf");
command.add("28");
command.add("-codec:v");
command.add("h264_nvenc");
command.add("-preset");
command.add("slow");
command.add("-movflags");
command.add("+faststart");
List<StreamObject> videoStreams = filterStreamsByType(streams, VideoStream.class);
if (!videoStreams.isEmpty()) {
for (StreamObject streamObject : videoStreams) {
if (!streamObject.getCodecName().equals("h264") && !streamObject.getCodecName().equals("h266")) {
continue;
}
FFMpegHelper.addH26xHardwareDecoding(command);
break;
}
FFMpegHelper.mapAllStreams(command, videoStreams);
FFMpegHelper.addH264HardwareEncoding(command, 17);
command.addOutputFileOption("-movflags", "+faststart");
}
// Map audio if present
if (!filterStreamsByType(streams, AudioStream.class).isEmpty()) {
command.add("-map");
command.add("0:a");
command.add("-c:a");
command.add("copy");
List<StreamObject> audioStreams = filterStreamsByType(streams, AudioStream.class);
if (!audioStreams.isEmpty()) {
FFMpegHelper.mapAllStreams(command, audioStreams);
command.addOutputFileOption("-c:a", "copy");
}
// Map subtitles if present
if (hasInternalStreams(streams)) {
command.add("-map");
command.add("0:s");
command.add("-c:s");
command.add("copy");
List<StreamObject> subtitleStreams = filterStreamsByType(streams, SubtitleStream.class);
if (!subtitleStreams.isEmpty()) {
FFMpegHelper.mapAllStreams(command, subtitleStreams);
command.addOutputFileOption("-c:s", "copy");
}
command.add("-map_metadata");
command.add("0");
command.add("-movflags");
command.add("use_metadata_tags");
command.addOutputFileOption("-f", "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;
command.setOutputFile(outFile);
return command.getResult();
}
}