2021-08-23 16:50:18 +02:00
|
|
|
|
using Flowframes.Data;
|
|
|
|
|
|
using Flowframes.IO;
|
|
|
|
|
|
using Flowframes.MiscUtils;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.IO;
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using System.Threading.Tasks;
|
2024-11-08 11:54:26 +01:00
|
|
|
|
using Win32Interop.Enums;
|
2021-08-23 16:50:18 +02:00
|
|
|
|
using static Flowframes.AvProcess;
|
|
|
|
|
|
using Utils = Flowframes.Media.FfmpegUtils;
|
|
|
|
|
|
|
|
|
|
|
|
namespace Flowframes.Media
|
|
|
|
|
|
{
|
|
|
|
|
|
partial class FfmpegEncode : FfmpegCommands
|
|
|
|
|
|
{
|
2023-01-18 14:55:38 +01:00
|
|
|
|
public static async Task FramesToVideo(string framesFile, string outPath, OutputSettings settings, Fraction fps, Fraction resampleFps, float itsScale, VidExtraData extraData, LogMode logMode = LogMode.OnlyLastLine, bool isChunk = false)
|
2021-08-23 16:50:18 +02:00
|
|
|
|
{
|
|
|
|
|
|
if (logMode != LogMode.Hidden)
|
2024-10-13 16:58:06 +02:00
|
|
|
|
Logger.Log((resampleFps.Float <= 0) ? "Encoding video..." : $"Encoding video resampled to {resampleFps.GetString()} FPS...");
|
2021-08-23 16:50:18 +02:00
|
|
|
|
|
2023-02-20 19:30:23 +01:00
|
|
|
|
IoUtils.RenameExistingFileOrDir(outPath);
|
2021-08-23 16:50:18 +02:00
|
|
|
|
Directory.CreateDirectory(outPath.GetParentDir());
|
2024-11-12 22:13:59 +01:00
|
|
|
|
string[] encArgs = Utils.GetEncArgs(settings, (Interpolate.currentSettings.OutputResolution.IsEmpty ? Interpolate.currentSettings.InputResolution : Interpolate.currentSettings.OutputResolution), Interpolate.currentSettings.outFps.Float);
|
2021-11-24 13:48:23 +01:00
|
|
|
|
|
2021-08-23 16:50:18 +02:00
|
|
|
|
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)}\"";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-24 13:48:23 +01:00
|
|
|
|
string args = "";
|
|
|
|
|
|
|
2023-01-15 17:23:49 +01:00
|
|
|
|
for (int i = 0; i < encArgs.Length; i++)
|
2021-11-24 13:48:23 +01:00
|
|
|
|
{
|
2021-11-24 21:08:29 +01:00
|
|
|
|
string pre = i == 0 ? "" : $" && ffmpeg {AvProcess.GetFfmpegDefaultArgs()}";
|
2021-11-25 22:22:45 +01:00
|
|
|
|
string post = (i == 0 && encArgs.Length > 1) ? $"-f null -" : outPath.Wrap();
|
2023-01-31 12:03:31 +01:00
|
|
|
|
args += $"{pre} {await GetFfmpegExportArgsIn(fps, itsScale)} {inArg} {encArgs[i]} {await GetFfmpegExportArgsOut(resampleFps, extraData, settings, isChunk)} {post} ";
|
2021-11-24 13:48:23 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-12-06 22:46:39 +01:00
|
|
|
|
await RunFfmpeg(args, framesFile.GetParentDir(), logMode, !isChunk);
|
2021-08-23 16:50:18 +02:00
|
|
|
|
IoUtils.TryDeleteIfExists(linksDir);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-07 15:10:36 +01:00
|
|
|
|
public static async Task<string> GetFfmpegExportArgsIn(Fraction fps, float itsScale, int rotation = 0)
|
2022-08-04 15:30:19 +02:00
|
|
|
|
{
|
2023-01-20 20:58:32 +01:00
|
|
|
|
var args = new List<string>();
|
2022-08-04 15:30:19 +02:00
|
|
|
|
fps = fps / new Fraction(itsScale);
|
2025-03-10 11:58:28 +01:00
|
|
|
|
|
2025-03-23 19:36:57 +01:00
|
|
|
|
if (fps > 0.1f)
|
2025-03-10 11:58:28 +01:00
|
|
|
|
{
|
|
|
|
|
|
args.Add($"-r {fps}");
|
|
|
|
|
|
}
|
2025-03-23 19:36:57 +01:00
|
|
|
|
|
|
|
|
|
|
if (rotation != 0)
|
2024-11-07 15:10:36 +01:00
|
|
|
|
{
|
|
|
|
|
|
args.Add($"-display_rotation {rotation}");
|
|
|
|
|
|
}
|
2023-01-20 20:58:32 +01:00
|
|
|
|
|
|
|
|
|
|
return string.Join(" ", args);
|
2022-08-04 15:30:19 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-23 19:36:57 +01:00
|
|
|
|
public static async Task<string> GetFfmpegExportArgsOut(Fraction resampleFps, VidExtraData extraData, OutputSettings settings, bool isChunk = false, string alphaPassFile = "")
|
2021-08-23 16:50:18 +02:00
|
|
|
|
{
|
2023-01-20 20:58:32 +01:00
|
|
|
|
var beforeArgs = new List<string>();
|
|
|
|
|
|
var filters = new List<string>();
|
|
|
|
|
|
var extraArgs = new List<string> { Config.Get(Config.Key.ffEncArgs) };
|
2024-11-07 22:39:48 +01:00
|
|
|
|
var mf = Interpolate.currentMediaFile;
|
2025-03-23 19:36:57 +01:00
|
|
|
|
int inputs = 1;
|
|
|
|
|
|
|
2025-06-28 23:16:14 +02:00
|
|
|
|
if (Config.GetBool(Config.Key.keepColorSpace) && extraData.HasAnyColorValues)
|
2025-03-23 19:36:57 +01:00
|
|
|
|
{
|
2025-06-28 23:16:14 +02:00
|
|
|
|
Logger.Log($"Using color data: {extraData.ColorsStr}", true, false, "ffmpeg");
|
|
|
|
|
|
extraArgs.AddIf($"-colorspace {extraData.ColSpace}", extraData.ColSpace.IsNotEmpty());
|
|
|
|
|
|
extraArgs.AddIf($"-color_primaries {extraData.ColPrimaries}", extraData.ColPrimaries.IsNotEmpty());
|
|
|
|
|
|
extraArgs.AddIf($"-color_trc {extraData.ColTransfer}", extraData.ColTransfer.IsNotEmpty());
|
|
|
|
|
|
extraArgs.AddIf($"-color_range:v {extraData.ColRange.Wrap()}", extraData.ColRange.IsNotEmpty());
|
2025-03-23 19:36:57 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-28 23:16:14 +02:00
|
|
|
|
if (!string.IsNullOrWhiteSpace(extraData.Dar) && !extraData.Dar.MatchesWildcard("*N/A*"))
|
|
|
|
|
|
extraArgs.Add($"-aspect {extraData.Dar}");
|
2025-03-23 19:36:57 +01:00
|
|
|
|
|
|
|
|
|
|
if (!isChunk && settings.Format == Enums.Output.Format.Mp4 || settings.Format == Enums.Output.Format.Mov)
|
|
|
|
|
|
extraArgs.Add($"-movflags +faststart");
|
2022-07-22 01:26:47 +02:00
|
|
|
|
|
2024-10-13 16:58:06 +02:00
|
|
|
|
if (resampleFps.Float >= 0.1f)
|
2024-11-14 20:38:41 +01:00
|
|
|
|
{
|
2024-11-26 13:45:44 +01:00
|
|
|
|
if (Interpolate.currentMediaFile.IsVfr && !Interpolate.currentSettings.dedupe)
|
2024-11-14 20:38:41 +01:00
|
|
|
|
{
|
2024-12-03 00:40:24 +01:00
|
|
|
|
Logger.Log($"Won't add fps filter as VFR handling already outputs at desired frame rate ({resampleFps.Float} FPS)", true);
|
2024-11-14 20:38:41 +01:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
filters.Add($"fps={resampleFps}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2022-07-22 01:26:47 +02:00
|
|
|
|
|
2025-03-23 19:36:57 +01:00
|
|
|
|
if (alphaPassFile.IsNotEmpty())
|
2022-07-22 01:26:47 +02:00
|
|
|
|
{
|
2025-03-23 19:36:57 +01:00
|
|
|
|
beforeArgs.Add($"-i {alphaPassFile.Wrap()}");
|
|
|
|
|
|
filters.Add($"[{inputs}:v]alphamerge");
|
|
|
|
|
|
inputs++;
|
2022-07-22 01:26:47 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-01-20 20:58:32 +01:00
|
|
|
|
if (settings.Format == Enums.Output.Format.Gif)
|
2023-01-18 14:55:38 +01:00
|
|
|
|
{
|
|
|
|
|
|
string dither = Config.Get(Config.Key.gifDitherType).Split(' ').First();
|
|
|
|
|
|
int colors = OutputUtils.GetGifColors(ParseUtils.GetEnum<Enums.Encoding.Quality.GifColors>(settings.Quality, true, Strings.VideoQuality));
|
2025-03-23 19:36:57 +01:00
|
|
|
|
string palettePath = Path.Combine(Paths.GetSessionDataPath(), "palette.png");
|
2024-11-07 22:39:48 +01:00
|
|
|
|
await FfmpegExtract.GeneratePalette(mf.ImportPath, palettePath, colors);
|
2023-01-20 20:58:32 +01:00
|
|
|
|
|
|
|
|
|
|
if (File.Exists(palettePath))
|
|
|
|
|
|
{
|
|
|
|
|
|
beforeArgs.Add($"-i {palettePath.Wrap()}");
|
2025-03-23 19:36:57 +01:00
|
|
|
|
inputs++;
|
|
|
|
|
|
filters.Add($"[{inputs - 1}:v]paletteuse=dither={dither}");
|
2023-01-20 20:58:32 +01:00
|
|
|
|
}
|
2023-01-18 14:55:38 +01:00
|
|
|
|
}
|
2024-01-10 01:50:23 +01:00
|
|
|
|
else if (settings.Encoder == Enums.Encoding.Encoder.Exr)
|
|
|
|
|
|
{
|
2025-03-23 19:36:57 +01:00
|
|
|
|
if (mf.Format.Upper() != "EXR")
|
2024-06-24 11:36:43 +02:00
|
|
|
|
filters.Add($"zscale=transfer=linear,format={settings.PixelFormat.ToString().Lower()}".Wrap());
|
2024-01-10 01:50:23 +01:00
|
|
|
|
}
|
2023-01-18 14:55:38 +01:00
|
|
|
|
|
2024-11-12 22:13:59 +01:00
|
|
|
|
filters.Add(GetPadFilter(Interpolate.currentSettings.ScaledResolution.Width, Interpolate.currentSettings.ScaledResolution.Height));
|
2023-03-30 14:13:39 +02:00
|
|
|
|
filters = filters.Where(f => f.IsNotEmpty()).ToList();
|
2023-01-20 20:58:32 +01:00
|
|
|
|
|
|
|
|
|
|
return filters.Count > 0 ?
|
2024-11-08 10:04:39 +01:00
|
|
|
|
$"{string.Join(" ", beforeArgs)} -filter_complex [0:v]{string.Join("[vf],[vf]", filters)}[vf] -map [vf] {string.Join(" ", extraArgs)}" :
|
2023-01-20 20:58:32 +01:00
|
|
|
|
$"{string.Join(" ", beforeArgs)} {string.Join(" ", extraArgs)}";
|
2021-08-23 16:50:18 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-01-15 17:23:49 +01:00
|
|
|
|
public static string GetConcatFileExt(string concatFilePath)
|
2021-08-23 16:50:18 +02:00
|
|
|
|
{
|
2022-07-22 01:26:47 +02:00
|
|
|
|
return Path.GetExtension(File.ReadAllLines(concatFilePath).FirstOrDefault().Split('\'')[1]);
|
2021-08-23 16:50:18 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-02-20 19:30:23 +01:00
|
|
|
|
public static async Task FramesToFrames(string framesFile, string outDir, int startNo, Fraction fps, Fraction resampleFps, Enums.Encoding.Encoder format = Enums.Encoding.Encoder.Png, int lossyQ = 1, LogMode logMode = LogMode.OnlyLastLine)
|
2021-08-23 16:50:18 +02:00
|
|
|
|
{
|
|
|
|
|
|
Directory.CreateDirectory(outDir);
|
|
|
|
|
|
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 {Path.GetFileName(framesFile) + Paths.symlinksSuffix}/%{Padding.interpFrames}d{GetConcatFileExt(framesFile)}";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-08 11:54:26 +01:00
|
|
|
|
var ffArgs = new List<string>()
|
|
|
|
|
|
{
|
|
|
|
|
|
$"-r {fps.ToString().Replace(",", ".")}", // Rate
|
|
|
|
|
|
inArg,
|
|
|
|
|
|
format == Enums.Encoding.Encoder.Webp ? "-c:v libwebp" : "", // Codec - Specify libwebp to avoid putting all frames into single animated WEBP
|
|
|
|
|
|
format == Enums.Encoding.Encoder.Png ? pngCompr : $"-q:v {lossyQ}", // Compression
|
|
|
|
|
|
$"-start_number {startNo}",
|
|
|
|
|
|
resampleFps.Float < 0.1f ? "" : $"-vf fps=fps={resampleFps}", // FPS Resample
|
|
|
|
|
|
"-fps_mode passthrough",
|
|
|
|
|
|
$"{outDir}/%{Padding.interpFrames}d.{format.GetInfo().OverideExtension}".Wrap(),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
await RunFfmpeg(string.Join(" ", ffArgs.Where(s => s.IsNotEmpty())), framesFile.GetParentDir(), logMode, "error", true);
|
2021-08-23 16:50:18 +02:00
|
|
|
|
IoUtils.TryDeleteIfExists(linksDir);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-08-28 10:21:52 +02:00
|
|
|
|
public static async Task FramesToGifConcat(string framesFile, string outPath, Fraction rate, bool palette, int colors, Fraction resampleFps, float itsScale, LogMode logMode = LogMode.OnlyLastLine)
|
2021-08-23 16:50:18 +02:00
|
|
|
|
{
|
2024-10-13 16:58:06 +02:00
|
|
|
|
if (rate.Float > 50f && (resampleFps.Float > 50f || resampleFps.Float < 1))
|
2021-08-23 16:50:18 +02:00
|
|
|
|
resampleFps = new Fraction(50, 1); // Force limit framerate as encoding above 50 will cause problems
|
|
|
|
|
|
|
|
|
|
|
|
if (logMode != LogMode.Hidden)
|
2024-10-13 16:58:06 +02:00
|
|
|
|
Logger.Log((resampleFps.Float <= 0) ? $"Encoding GIF..." : $"Encoding GIF resampled to {resampleFps.Float.ToString().Replace(",", ".")} FPS...");
|
2023-01-15 17:23:49 +01:00
|
|
|
|
|
2021-08-23 16:50:18 +02:00
|
|
|
|
string framesFilename = Path.GetFileName(framesFile);
|
|
|
|
|
|
string dither = Config.Get(Config.Key.gifDitherType).Split(' ').First();
|
|
|
|
|
|
string paletteFilter = palette ? $"-vf \"split[s0][s1];[s0]palettegen={colors}[p];[s1][p]paletteuse=dither={dither}\"" : "";
|
2024-10-13 16:58:06 +02:00
|
|
|
|
string fpsFilter = (resampleFps.Float <= 0) ? "" : $"fps=fps={resampleFps}";
|
2021-08-23 16:50:18 +02:00
|
|
|
|
string vf = FormatUtils.ConcatStrings(new string[] { paletteFilter, fpsFilter });
|
|
|
|
|
|
string extraArgs = Config.Get(Config.Key.ffEncArgs);
|
2021-08-28 10:21:52 +02:00
|
|
|
|
rate = rate / new Fraction(itsScale);
|
2021-08-23 16:50:18 +02:00
|
|
|
|
string args = $"-f concat -r {rate} -i {framesFilename.Wrap()} -gifflags -offsetting {vf} {extraArgs} {outPath.Wrap()}";
|
2021-12-06 22:46:39 +01:00
|
|
|
|
await RunFfmpeg(args, framesFile.GetParentDir(), LogMode.OnlyLastLine, "error");
|
2021-08-23 16:50:18 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|