package mara.mybox.controller; import com.github.kokorin.jaffree.ffmpeg.FFmpeg; import com.github.kokorin.jaffree.ffmpeg.FFmpegProgress; import com.github.kokorin.jaffree.ffmpeg.FFmpegResult; import com.github.kokorin.jaffree.ffmpeg.NullOutput; import com.github.kokorin.jaffree.ffmpeg.ProgressListener; import com.github.kokorin.jaffree.ffmpeg.UrlInput; import com.github.kokorin.jaffree.ffmpeg.UrlOutput; import java.io.File; import java.nio.file.Paths; import java.util.Arrays; import java.util.Optional; import java.util.concurrent.atomic.AtomicLong; import javafx.application.Platform; import javafx.scene.control.Alert; import javafx.scene.control.ButtonType; import javafx.scene.layout.Region; import javafx.stage.Stage; import mara.mybox.fxml.FxmlStage; import mara.mybox.tools.DateTools; import mara.mybox.tools.DoubleTools; import mara.mybox.tools.FileTools; import mara.mybox.tools.StringTools; import mara.mybox.value.AppVariables; import static mara.mybox.value.AppVariables.logger; import static mara.mybox.value.AppVariables.message; import mara.mybox.value.CommonValues; /** * @Author Mara * @CreateDate 2019-12-1 * @License Apache License Version 2.0 */ public class FFmpegConvertMediaFilesController extends FFmpegBaseController { public FFmpegConvertMediaFilesController() { baseTitle = AppVariables.message("FFmpegConvertMediaFiles"); mustSetResolution = false; } @Override public void checkExecutableInput() { try { super.checkExecutableInput(); readMuxers(); readEncoders(); } catch (Exception e) { logger.error(e.toString()); } } @Override public String handleFile(File srcFile, File targetPath) { try { countHandling(srcFile); String ext = extensionInput.getText().trim(); if (ext.isEmpty() || message("OriginalFormat").equals(ext)) { ext = FileTools.getFileSuffix(srcFile.getName()); } File target = makeTargetFile(FileTools.getFilePrefix(srcFile.getName()), "." + ext, targetPath); if (target == null) { return AppVariables.message("Skip"); } convert(srcFile, target, -1); targetFileGenerated(target); return AppVariables.message("Successful"); } catch (Exception e) { updateLogs(e.toString(), true); return AppVariables.message("Failed"); } } @Override public void updateFileProgress(long number, long total) { Platform.runLater(new Runnable() { @Override public void run() { double p = (number * 1d) / total; String s = DateTools.showSeconds(number / 1000) + "/" + DateTools.showSeconds(total / 1000); progressBar.setProgress(p); progressValue.setText(s); } }); } protected void convert(File sourceFile, File targetFile, long duration) throws Exception { convert(UrlInput.fromPath(Paths.get(sourceFile.getAbsolutePath())), targetFile, duration); } protected void convert(UrlInput input, File targetFile, long inDuration) throws Exception { final long duration; if (inDuration < 0) { final AtomicLong countedDuration = new AtomicLong(); if (verboseCheck == null || verboseCheck.isSelected()) { updateLogs(message("CountingMediaDuration"), true); } FFmpeg.atPath(executable.toPath().getParent()) .addInput(input) .addOutput(new NullOutput()) .setOverwriteOutput(true) .setProgressListener((FFmpegProgress progress) -> { countedDuration.set(progress.getTimeMillis()); }) .execute(); duration = countedDuration.get(); if (verboseCheck == null || verboseCheck.isSelected()) { updateLogs(message("Duration") + ": " + DateTools.showDuration(duration), true); } } else { duration = inDuration; } ProgressListener listener = new ProgressListener() { private long lastProgress = System.currentTimeMillis(); private long lastStatus = System.currentTimeMillis(); @Override public void onProgress(FFmpegProgress progress) { Platform.runLater(() -> { long now = System.currentTimeMillis(); if (now > lastProgress + 500) { if (duration > 0) { updateFileProgress(progress.getTimeMillis(), duration); } else { if (verboseCheck == null || verboseCheck.isSelected()) { updateLogs(message("Handled") + ":" + DateTools.showSeconds(progress.getTimeMillis() / 1000), true); } progressValue.setText(DateTools.showSeconds(progress.getTimeMillis() / 1000)); } lastProgress = now; } if (now > lastStatus + 3000) { long cost = now - mediaStart; if (duration > 0) { double p = DoubleTools.scale2(progress.getTimeMillis() * 100.0d / duration); long left = Math.round(cost * (100 - p) / p); String s = message("Percentage") + ": " + p + "% " + message("Cost") + ": " + DateTools.showTime(cost) + " " + message("EstimatedLeft") + ": " + DateTools.showTime(left); statusLabel.setText(s); } else { String s = message("Cost") + ": " + DateTools.showTime(cost); statusLabel.setText(s); } lastStatus = now; } }); } }; mediaStart = System.currentTimeMillis(); FFmpeg ffmpeg = FFmpeg.atPath(executable.toPath().getParent()) .addInput(input) .addOutput(UrlOutput.toPath(targetFile.toPath())) .setProgressListener(listener) .setOverwriteOutput(true); if (disableVideo) { ffmpeg.addArgument("-vn"); } else if (videoCodec != null) { ffmpeg.addArguments("-vcodec", videoCodec); } if (disbaleAudio) { ffmpeg.addArgument("-an"); } else if (audioCodec != null) { ffmpeg.addArguments("-acodec", audioCodec); } if (disbaleSubtitle) { ffmpeg.addArgument("-sn"); } else if (subtitleCodec != null) { ffmpeg.addArguments("-scodec", subtitleCodec); } if (aspect != null) { ffmpeg.addArguments("-aspect", aspect); } if (videoFrameRate > 0) { ffmpeg.addArguments("-r", videoFrameRate + ""); } if (width > 0 && height > 0) { ffmpeg.addArguments("-s", width + "x" + height); } if (videoBitrate > 0) { ffmpeg.addArguments("-b:v", videoBitrate + "k"); } if (audioBitrate > 0) { ffmpeg.addArguments("-b:a", audioBitrate + "k"); } if (audioSampleRate > 0) { ffmpeg.addArguments("-ar", audioSampleRate + ""); } String volumn = volumnInput.getText().trim(); if (!volumn.isBlank()) { ffmpeg.addArguments("-af", "volume=" + volumn); } String more = moreInput.getText().trim(); if (!more.isBlank()) { String[] args = StringTools.splitBySpace(more); if (args != null && args.length > 0) { for (String arg : args) { ffmpeg.addArgument(arg); } } } if (verboseCheck == null || verboseCheck.isSelected()) { updateLogs(message("ConvertingMedia") + " " + message("TargetFile") + ":" + targetFile, true); } FFmpegResult result = ffmpeg.execute(); } @Override public void viewTarget(File file) { if (file == null) { return; } if (Arrays.asList(CommonValues.MediaPlayerSupports).contains(FileTools.getFileSuffix(file))) { FxmlStage.openMediaPlayer(null, file); } else { openTarget(null); } } @Override public boolean checkBeforeNextAction() { if ((encoderTask != null && encoderTask.isRunning()) || (muxerTask != null && muxerTask.isRunning()) || (queryTask != null && queryTask.isRunning())) { Alert alert = new Alert(Alert.AlertType.CONFIRMATION); alert.setTitle(getMyStage().getTitle()); alert.setContentText(AppVariables.message("TaskRunning")); alert.getDialogPane().setMinHeight(Region.USE_PREF_SIZE); ButtonType buttonSure = new ButtonType(AppVariables.message("Sure")); ButtonType buttonCancel = new ButtonType(AppVariables.message("Cancel")); alert.getButtonTypes().setAll(buttonSure, buttonCancel); Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); stage.setAlwaysOnTop(true); stage.toFront(); Optional<ButtonType> result = alert.showAndWait(); if (result.get() == buttonSure) { if (encoderTask != null) { encoderTask.cancel(); encoderTask = null; } if (muxerTask != null) { muxerTask.cancel(); muxerTask = null; } if (queryTask != null) { queryTask.cancel(); queryTask = null; } } else { return false; } } return true; } }