public static async Task FramesToVideo(string framesFile, string outPath, Interpolate.OutMode outMode, Fraction fps, Fraction resampleFps, float itsScale, VidExtraData extraData, LogMode logMode = LogMode.OnlyLastLine, bool isChunk = false) { if (logMode != LogMode.Hidden) { Logger.Log((resampleFps.GetFloat() <= 0) ? "Encoding video..." : $"Encoding video resampled to {resampleFps.GetString()} FPS..."); } IoUtils.RenameExistingFile(outPath); Directory.CreateDirectory(outPath.GetParentDir()); string[] encArgs = Utils.GetEncArgs(Utils.GetCodec(outMode), (Interpolate.current.ScaledResolution.IsEmpty ? Interpolate.current.InputResolution : Interpolate.current.ScaledResolution), Interpolate.current.outFps.GetFloat()); string inArg = $"-f concat -i {Path.GetFileName(framesFile)}"; string linksDir = Path.Combine(framesFile + Paths.symlinksSuffix); if (Config.GetBool(Config.Key.allowSymlinkEncoding, true) && Symlinks.SymlinksAllowed()) { if (await Symlinks.MakeSymlinksForEncode(framesFile, linksDir, Padding.interpFrames)) { inArg = $"-i \"{linksDir}/%{Padding.interpFrames}d{GetConcatFileExt(framesFile)}\""; } } string extraArgs = Config.Get(Config.Key.ffEncArgs); List <string> filters = new List <string>(); if (resampleFps.GetFloat() >= 0.1f) { filters.Add($"fps=fps={resampleFps}"); } if (Config.GetBool(Config.Key.keepColorSpace) && extraData.HasAllValues()) { Logger.Log($"Applying color transfer ({extraData.colorSpace}).", true, false, "ffmpeg"); filters.Add($"scale=out_color_matrix={extraData.colorSpace}"); extraArgs += $" -colorspace {extraData.colorSpace} -color_primaries {extraData.colorPrimaries} -color_trc {extraData.colorTransfer} -color_range:v \"{extraData.colorRange}\""; } string vf = filters.Count > 0 ? $"-vf {string.Join(",", filters)}" : ""; fps = fps / new Fraction(itsScale); string args = ""; for (int i = 0; i < encArgs.Length; i++) { string pre = i == 0 ? "" : $" && ffmpeg {AvProcess.GetFfmpegDefaultArgs()}"; string post = (i == 0 && encArgs.Length > 1) ? $"-f null -" : outPath.Wrap(); string fs = (!isChunk && outMode == Interpolate.OutMode.VidMp4) ? $"-movflags +faststart" : ""; args += $"{pre} -vsync 0 -r {fps} {inArg} {encArgs[i]} {vf} {GetAspectArg(extraData)} {extraArgs} -threads {Config.GetInt(Config.Key.ffEncThreads)} {fs} {post} "; } //string argsOld = $"-vsync 0 -r {fps} {inArg} {encArgs} {vf} {GetAspectArg(extraData)} {extraArgs} -threads {Config.GetInt(Config.Key.ffEncThreads)} {outPath.Wrap()}"; await RunFfmpeg(args, framesFile.GetParentDir(), logMode, !isChunk); IoUtils.TryDeleteIfExists(linksDir); }
public static async Task MergeStreamsFromInput(string inputVideo, string interpVideo, string tempFolder, bool shortest) { if (!File.Exists(inputVideo) && !I.current.inputIsFrames) { Logger.Log("Warning: Input video file not found, can't copy audio/subtitle streams to output video!"); return; } string containerExt = Path.GetExtension(interpVideo); string tempPath = Path.Combine(tempFolder, $"vid{containerExt}"); string outPath = Path.Combine(tempFolder, $"muxed{containerExt}"); IoUtils.TryDeleteIfExists(tempPath); File.Move(interpVideo, tempPath); string inName = Path.GetFileName(tempPath); string outName = Path.GetFileName(outPath); string subArgs = "-c:s " + Utils.GetSubCodecForContainer(containerExt); bool audioCompat = Utils.ContainerSupportsAllAudioFormats(I.current.outMode, GetAudioCodecs(inputVideo)); bool slowmo = I.current.outItsScale != 0 && I.current.outItsScale != 1; string audioArgs = audioCompat && !slowmo ? "" : await Utils.GetAudioFallbackArgs(inputVideo, I.current.outMode, I.current.outItsScale); if (!audioCompat && !slowmo) { Logger.Log("Warning: Input audio format(s) not fully supported in output container - Will re-encode.", true, false, "ffmpeg"); } bool audio = Config.GetBool(Config.Key.keepAudio); bool subs = Config.GetBool(Config.Key.keepSubs); bool meta = Config.GetBool(Config.Key.keepMeta); if (!audio) { audioArgs = "-an"; } if (!subs || (subs && !Utils.ContainerSupportsSubs(containerExt))) { subArgs = "-sn"; } bool isMkv = I.current.outMode == I.OutMode.VidMkv; string mkvFix = isMkv ? "-max_interleave_delta 0" : ""; // https://reddit.com/r/ffmpeg/comments/efddfs/starting_new_cluster_due_to_timestamp/ string metaArg = (isMkv && meta) ? "-map 1:t?" : ""; // https://reddit.com/r/ffmpeg/comments/fw4jnh/how_to_make_ffmpeg_keep_attached_images_in_mkv_as/ string shortestArg = shortest ? "-shortest" : ""; if (QuickSettingsTab.trimEnabled) { string otherStreamsName = $"otherStreams{containerExt}"; string[] trim = FfmpegExtract.GetTrimArgs(); string args1 = $"{trim[0]} -i {inputVideo.Wrap()} {trim[1]} -map 0 -map -0:v -map -0:d -c copy {audioArgs} {subArgs} {otherStreamsName}"; // Extract trimmed await RunFfmpeg(args1, tempFolder, LogMode.Hidden); string args2 = $"-i {inName} -i {otherStreamsName} -map 0:v:0 -map 1:a:? -map 1:s:? {metaArg} -c copy {audioArgs} {subArgs} {mkvFix} {shortestArg} {outName}"; // Merge interp + trimmed original await RunFfmpeg(args2, tempFolder, LogMode.Hidden); IoUtils.TryDeleteIfExists(Path.Combine(tempFolder, otherStreamsName)); } else // If trimming is disabled we can pull the streams directly from the input file { string args = $"-i {inName} -i {inputVideo.Wrap()} -map 0:v:0 -map 1:a:? -map 1:s:? {metaArg} -c copy {audioArgs} {subArgs} {mkvFix} {shortestArg} {outName}"; await RunFfmpeg(args, tempFolder, LogMode.Hidden); } if (File.Exists(outPath) && IoUtils.GetFilesize(outPath) > 512) { File.Delete(tempPath); File.Move(outPath, interpVideo); } else { File.Move(tempPath, interpVideo); // Muxing failed, move unmuxed video file back } }