EpicKnarvik97 f0e75eb440
Some checks failed
KnarCraft/FFmpegConvert/pipeline/head There was a failure building this commit
Adds a new type of anime converter and stuff
Adds a new type of anime converter that retains all streams, but converts the video to hevc, and re-orders the streams
Adds support for several types of encoding hardware acceleration that are automatically disabled if not available on the system.
Adds automatic hardware decoding acceleration.
Automatically removes empty files if ffmpeg fails.
2024-04-17 04:39:42 +02:00

141 lines
5.7 KiB
Java

package net.knarcraft.ffmpegconverter.converter;
import net.knarcraft.ffmpegconverter.container.FFMpegCommand;
import net.knarcraft.ffmpegconverter.container.StreamProbeResult;
import net.knarcraft.ffmpegconverter.handler.AvailableHardwareEncoderHandler;
import net.knarcraft.ffmpegconverter.utility.FFMpegHelper;
import net.knarcraft.ffmpegconverter.utility.FileUtil;
import net.knarcraft.ffmpegconverter.utility.OutputUtil;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Implements all methods which can be useful for any implementation of a converter.
*/
public abstract class AbstractConverter implements Converter {
final boolean debug = false;
private final String newExtension;
protected String ffprobePath;
protected String ffmpegPath;
protected String[] audioFormats;
protected String[] videoFormats;
protected AvailableHardwareEncoderHandler encoderHandler = null;
/**
* Initializes variables used by the abstract converter
*/
AbstractConverter(@Nullable String newExtension) {
this.newExtension = newExtension;
OutputUtil.setDebug(this.debug);
try {
audioFormats = FileUtil.readFileLines("audio_formats.txt");
videoFormats = FileUtil.readFileLines("video_formats.txt");
} catch (IOException e) {
System.out.println("Unable to read audio and/or video formats from internal files.");
System.exit(1);
}
}
@Override
public void convert(@NotNull File file) throws IOException {
StreamProbeResult probeResult = FFMpegHelper.probeFile(ffprobePath, file);
if (probeResult.parsedStreams().isEmpty()) {
throw new IllegalArgumentException("The file has no valid streams. Please make sure the file exists and" +
" is not corrupt.");
}
String outExtension = newExtension != null ? newExtension : FileUtil.getExtension(file.getName());
String newPath = FileUtil.getNonCollidingPath(file.getParentFile(), file, outExtension);
OutputUtil.println();
OutputUtil.println("Preparing to start process...");
OutputUtil.println("Converting " + file);
FFMpegCommand ffMpegCommand = generateConversionCommand(ffmpegPath, probeResult, newPath);
// If the command is null, that means the file does not need conversion
if (ffMpegCommand == null) {
return;
}
String[] command = ffMpegCommand.getResult();
// If no commands were given, no conversion is necessary
if (command.length == 0) {
return;
}
ProcessBuilder processBuilder = new ProcessBuilder(command);
int exitCode = FFMpegHelper.runProcess(processBuilder, file.getParentFile(), "\n", true).exitCode();
if (exitCode != 0) {
try {
handleError(ffMpegCommand, file, newPath);
} catch (ConfigurationException e) {
throw new RuntimeException(e);
}
}
}
/**
* Handles an ffmpeg conversion error
*
* @param ffMpegCommand <p>The failed ffmpeg command</p>
* @param file <p>The file that was to be converted</p>
* @param newPath <p>The path of the output file</p>
* @throws IOException <p>If unable to produce output</p>
* @throws ConfigurationException <p>If unable </p>
*/
private void handleError(@NotNull FFMpegCommand ffMpegCommand, @NotNull File file,
@NotNull String newPath) throws IOException, ConfigurationException {
File outputFile = new File(newPath);
if (outputFile.exists() && !outputFile.delete()) {
OutputUtil.println("Failed to remove failed output file. Please remove it manually");
}
String outputCodec = ffMpegCommand.getOutputVideoCodec();
if (outputCodec != null && outputCodec.contains("_")) {
String[] parts = outputCodec.split("_");
String hardwareAcceleration = parts[parts.length - 1];
List<String> encodingMethods = getAvailableHardwareEncodingMethods();
if (encodingMethods.contains(hardwareAcceleration)) {
OutputUtil.println("Disabling " + hardwareAcceleration + " hardware acceleration");
encoderHandler.removeHardwareEncoding(hardwareAcceleration);
encoderHandler.save();
convert(file);
return;
}
}
throw new IllegalArgumentException("Conversion failed. Please check the preceding output.");
}
/**
* Gets the available methods for hardware encoding
*
* @return <p>Available hardware encoding methods</p>
*/
protected List<String> getAvailableHardwareEncodingMethods() {
try {
if (encoderHandler == null) {
encoderHandler = AvailableHardwareEncoderHandler.load();
if (encoderHandler.availableHardwareEncodings().isEmpty()) {
List<String> hardwareEncoding = new ArrayList<>(FFMpegHelper.getHWAcceleration(ffmpegPath));
hardwareEncoding.remove(0);
encoderHandler = new AvailableHardwareEncoderHandler(hardwareEncoding);
encoderHandler.save();
}
}
return encoderHandler.availableHardwareEncodings();
} catch (ConfigurationException | IOException exception) {
OutputUtil.println("Unable to get available hardware encoders: " + exception.getMessage());
return new ArrayList<>();
}
}
}