2021-08-23 16:50:18 +02:00
|
|
|
|
using Flowframes.IO;
|
|
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
2021-10-25 15:30:47 +02:00
|
|
|
|
using System.Drawing;
|
2021-08-23 16:50:18 +02:00
|
|
|
|
using System.IO;
|
|
|
|
|
|
using System.Linq;
|
2021-09-14 18:28:10 +02:00
|
|
|
|
using System.Threading.Tasks;
|
2021-08-23 16:50:18 +02:00
|
|
|
|
|
|
|
|
|
|
namespace Flowframes.Media
|
|
|
|
|
|
{
|
|
|
|
|
|
class FfmpegUtils
|
|
|
|
|
|
{
|
|
|
|
|
|
public enum Codec { H264, H265, H264Nvenc, H265Nvenc, Av1, Vp9, ProRes, AviRaw }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static Codec GetCodec(Interpolate.OutMode mode)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (mode == Interpolate.OutMode.VidMp4 || mode == Interpolate.OutMode.VidMkv)
|
|
|
|
|
|
{
|
|
|
|
|
|
int mp4MkvEnc = Config.GetInt(Config.Key.mp4Enc);
|
|
|
|
|
|
if (mp4MkvEnc == 0) return Codec.H264;
|
|
|
|
|
|
if (mp4MkvEnc == 1) return Codec.H265;
|
|
|
|
|
|
if (mp4MkvEnc == 2) return Codec.H264Nvenc;
|
|
|
|
|
|
if (mp4MkvEnc == 3) return Codec.H265Nvenc;
|
|
|
|
|
|
if (mp4MkvEnc == 4) return Codec.Av1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (mode == Interpolate.OutMode.VidWebm)
|
|
|
|
|
|
return Codec.Vp9;
|
|
|
|
|
|
|
|
|
|
|
|
if (mode == Interpolate.OutMode.VidProRes)
|
|
|
|
|
|
return Codec.ProRes;
|
|
|
|
|
|
|
|
|
|
|
|
if (mode == Interpolate.OutMode.VidAvi)
|
|
|
|
|
|
return Codec.AviRaw;
|
|
|
|
|
|
|
|
|
|
|
|
return Codec.H264;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static string GetEnc(Codec codec)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (codec)
|
|
|
|
|
|
{
|
|
|
|
|
|
case Codec.H264: return "libx264";
|
|
|
|
|
|
case Codec.H265: return "libx265";
|
|
|
|
|
|
case Codec.H264Nvenc: return "h264_nvenc";
|
|
|
|
|
|
case Codec.H265Nvenc: return "hevc_nvenc";
|
|
|
|
|
|
case Codec.Av1: return "libsvtav1";
|
|
|
|
|
|
case Codec.Vp9: return "libvpx-vp9";
|
|
|
|
|
|
case Codec.ProRes: return "prores_ks";
|
|
|
|
|
|
case Codec.AviRaw: return Config.Get(Config.Key.aviCodec);
|
|
|
|
|
|
}
|
2022-05-31 22:17:22 +02:00
|
|
|
|
|
2021-08-23 16:50:18 +02:00
|
|
|
|
return "libx264";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-24 13:48:23 +01:00
|
|
|
|
public static string[] GetEncArgs(Codec codec, Size res, float fps) // Array contains as many entries as there are encoding passes
|
2021-08-23 16:50:18 +02:00
|
|
|
|
{
|
2021-10-27 11:04:18 +02:00
|
|
|
|
int keyint = 10;
|
2021-08-23 16:50:18 +02:00
|
|
|
|
|
2021-11-24 13:48:23 +01:00
|
|
|
|
if (codec == Codec.H264)
|
2021-08-23 16:50:18 +02:00
|
|
|
|
{
|
|
|
|
|
|
string preset = Config.Get(Config.Key.ffEncPreset).ToLower().Remove(" ");
|
2021-10-27 11:04:18 +02:00
|
|
|
|
string g = GetKeyIntArg(fps, keyint);
|
2021-11-24 13:48:23 +01:00
|
|
|
|
return new string[] { $"-c:v {GetEnc(codec)} -crf {Config.GetInt(Config.Key.h264Crf)} -preset {preset} {g} -pix_fmt {GetPixFmt()}" };
|
2021-08-23 16:50:18 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (codec == Codec.H265)
|
|
|
|
|
|
{
|
|
|
|
|
|
string preset = Config.Get(Config.Key.ffEncPreset).ToLower().Remove(" ");
|
|
|
|
|
|
int crf = Config.GetInt(Config.Key.h265Crf);
|
2021-10-27 11:04:18 +02:00
|
|
|
|
string g = GetKeyIntArg(fps, keyint);
|
2021-11-24 13:48:23 +01:00
|
|
|
|
return new string[] { $"-c:v {GetEnc(codec)} {(crf > 0 ? $"-crf {crf}" : "-x265-params lossless=1")} -preset {preset} {g} -pix_fmt {GetPixFmt()}" };
|
2021-08-23 16:50:18 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (codec == Codec.H264Nvenc)
|
|
|
|
|
|
{
|
|
|
|
|
|
int cq = (Config.GetInt(Config.Key.h264Crf) * 1.1f).RoundToInt();
|
2021-11-24 13:48:23 +01:00
|
|
|
|
return new string[] { $"-c:v {GetEnc(codec)} -b:v 0 {(cq > 0 ? $"-cq {cq} -preset p7" : "-preset lossless")} -pix_fmt {GetPixFmt()}" };
|
2021-08-23 16:50:18 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (codec == Codec.H265Nvenc)
|
|
|
|
|
|
{
|
|
|
|
|
|
int cq = (Config.GetInt(Config.Key.h265Crf) * 1.1f).RoundToInt();
|
2021-11-24 13:48:23 +01:00
|
|
|
|
return new string[] { $"-c:v {GetEnc(codec)} -b:v 0 {(cq > 0 ? $"-cq {cq} -preset p7" : "-preset lossless")} -pix_fmt {GetPixFmt()}" };
|
2021-08-23 16:50:18 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (codec == Codec.Av1)
|
|
|
|
|
|
{
|
2021-10-25 15:30:47 +02:00
|
|
|
|
int cq = Config.GetInt(Config.Key.av1Crf);
|
2021-10-27 11:04:18 +02:00
|
|
|
|
string g = GetKeyIntArg(fps, keyint);
|
2022-04-18 04:33:06 +02:00
|
|
|
|
return new string[] { $"-c:v {GetEnc(codec)} -b:v 0 -qp {cq} {GetKeyIntArg(fps, keyint)} {GetSvtAv1Speed()} {g} -pix_fmt {GetPixFmt()}" };
|
2021-08-23 16:50:18 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (codec == Codec.Vp9)
|
|
|
|
|
|
{
|
|
|
|
|
|
int crf = Config.GetInt(Config.Key.vp9Crf);
|
2021-11-24 13:48:23 +01:00
|
|
|
|
string qualityStr = (crf > 0) ? $"-crf {crf}" : "-lossless 1";
|
2021-10-27 11:04:18 +02:00
|
|
|
|
string g = GetKeyIntArg(fps, keyint);
|
2021-11-24 21:08:29 +01:00
|
|
|
|
string t = GetTilingArgs(res, "-tile-columns ", "-tile-rows ");
|
|
|
|
|
|
return new string[] { $"-c:v {GetEnc(codec)} -b:v 0 {qualityStr} {GetVp9Speed()} {t} -row-mt 1 {g} -pass 1 -pix_fmt {GetPixFmt()} -an",
|
|
|
|
|
|
$"-c:v {GetEnc(codec)} -b:v 0 {qualityStr} {GetVp9Speed()} {t} -row-mt 1 {g} -pass 2 -pix_fmt {GetPixFmt()}" };
|
2021-08-23 16:50:18 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-24 13:48:23 +01:00
|
|
|
|
if (codec == Codec.ProRes)
|
2021-08-23 16:50:18 +02:00
|
|
|
|
{
|
2021-11-24 13:48:23 +01:00
|
|
|
|
return new string[] { $"-c:v {GetEnc(codec)} -profile:v {Config.GetInt(Config.Key.proResProfile)} -pix_fmt {GetPixFmt()}" };
|
2021-08-23 16:50:18 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (codec == Codec.AviRaw)
|
|
|
|
|
|
{
|
2021-11-24 13:48:23 +01:00
|
|
|
|
return new string[] { $"-c:v {GetEnc(codec)} -pix_fmt {Config.Get(Config.Key.aviColors)}" };
|
2021-08-23 16:50:18 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-24 13:48:23 +01:00
|
|
|
|
return new string[0];
|
2021-08-23 16:50:18 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-25 15:30:47 +02:00
|
|
|
|
public static string GetTilingArgs(Size resolution, string colArg, string rowArg)
|
|
|
|
|
|
{
|
|
|
|
|
|
int cols = 0;
|
|
|
|
|
|
if (resolution.Width >= 1920) cols = 1;
|
|
|
|
|
|
if (resolution.Width >= 3840) cols = 2;
|
|
|
|
|
|
if (resolution.Width >= 7680) cols = 3;
|
|
|
|
|
|
|
|
|
|
|
|
int rows = 0;
|
2021-11-24 21:08:29 +01:00
|
|
|
|
if (resolution.Height >= 1600) rows = 1;
|
|
|
|
|
|
if (resolution.Height >= 3200) rows = 2;
|
|
|
|
|
|
if (resolution.Height >= 6400) rows = 3;
|
2021-10-25 15:30:47 +02:00
|
|
|
|
|
2021-11-24 21:08:29 +01:00
|
|
|
|
Logger.Log($"GetTilingArgs: Video resolution is {resolution.Width}x{resolution.Height} - Using 2^{cols} columns, 2^{rows} rows (=> {Math.Pow(2, cols)}x{Math.Pow(2, rows)} = {Math.Pow(2, cols) * Math.Pow(2, rows)} Tiles)", true);
|
|
|
|
|
|
|
|
|
|
|
|
return $"{(cols > 0 ? colArg+cols : "")} {(rows > 0 ? rowArg + rows : "")}";
|
2021-10-25 15:30:47 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-27 11:04:18 +02:00
|
|
|
|
public static string GetKeyIntArg(float fps, int intervalSeconds, string arg = "-g ")
|
|
|
|
|
|
{
|
2021-11-24 21:08:29 +01:00
|
|
|
|
int keyInt = (fps * intervalSeconds).RoundToInt().Clamp(20, 300);
|
2021-10-27 11:04:18 +02:00
|
|
|
|
return $"{arg}{keyInt}";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-24 13:48:23 +01:00
|
|
|
|
static string GetVp9Speed()
|
2021-08-23 16:50:18 +02:00
|
|
|
|
{
|
|
|
|
|
|
string preset = Config.Get(Config.Key.ffEncPreset).ToLower().Remove(" ");
|
|
|
|
|
|
string arg = "";
|
|
|
|
|
|
|
|
|
|
|
|
if (preset == "veryslow") arg = "0";
|
|
|
|
|
|
if (preset == "slower") arg = "1";
|
|
|
|
|
|
if (preset == "slow") arg = "2";
|
|
|
|
|
|
if (preset == "medium") arg = "3";
|
|
|
|
|
|
if (preset == "fast") arg = "4";
|
|
|
|
|
|
if (preset == "faster") arg = "5";
|
|
|
|
|
|
if (preset == "veryfast") arg = "4 -deadline realtime";
|
|
|
|
|
|
|
|
|
|
|
|
return $"-cpu-used {arg}";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static string GetSvtAv1Speed()
|
|
|
|
|
|
{
|
|
|
|
|
|
string preset = Config.Get(Config.Key.ffEncPreset).ToLower().Remove(" ");
|
|
|
|
|
|
string arg = "8";
|
|
|
|
|
|
|
2022-04-18 04:30:37 +02:00
|
|
|
|
if (preset == "veryslow") arg = "3";
|
|
|
|
|
|
if (preset == "slower") arg = "4";
|
|
|
|
|
|
if (preset == "slow") arg = "5";
|
|
|
|
|
|
if (preset == "medium") arg = "6";
|
|
|
|
|
|
if (preset == "fast") arg = "7";
|
|
|
|
|
|
if (preset == "faster") arg = "8";
|
|
|
|
|
|
if (preset == "veryfast") arg = "9";
|
2021-08-23 16:50:18 +02:00
|
|
|
|
|
|
|
|
|
|
return $"-preset {arg}";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-24 13:48:23 +01:00
|
|
|
|
static string GetPixFmt()
|
2021-08-23 16:50:18 +02:00
|
|
|
|
{
|
|
|
|
|
|
switch (Config.GetInt(Config.Key.pixFmt))
|
|
|
|
|
|
{
|
|
|
|
|
|
case 0: return "yuv420p";
|
|
|
|
|
|
case 1: return "yuv444p";
|
|
|
|
|
|
case 2: return "yuv420p10le";
|
|
|
|
|
|
case 3: return "yuv444p10le";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return "yuv420p";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-24 13:48:23 +01:00
|
|
|
|
public static bool ContainerSupportsAllAudioFormats(Interpolate.OutMode outMode, List<string> codecs)
|
2021-08-23 16:50:18 +02:00
|
|
|
|
{
|
2021-11-24 13:48:23 +01:00
|
|
|
|
if (codecs.Count < 1)
|
2021-08-23 16:50:18 +02:00
|
|
|
|
Logger.Log($"Warning: ContainerSupportsAllAudioFormats() was called, but codec list has {codecs.Count} entries.", true, false, "ffmpeg");
|
|
|
|
|
|
|
|
|
|
|
|
foreach (string format in codecs)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!ContainerSupportsAudioFormat(outMode, format))
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-24 13:48:23 +01:00
|
|
|
|
public static bool ContainerSupportsAudioFormat(Interpolate.OutMode outMode, string format)
|
2021-08-23 16:50:18 +02:00
|
|
|
|
{
|
|
|
|
|
|
bool supported = false;
|
|
|
|
|
|
string alias = GetAudioExt(format);
|
|
|
|
|
|
|
2021-09-27 13:03:23 +02:00
|
|
|
|
string[] formatsMp4 = new string[] { "m4a", "mp3", "ac3", "dts" };
|
|
|
|
|
|
string[] formatsMkv = new string[] { "m4a", "mp3", "ac3", "dts", "ogg", "mp2", "wav", "wma" };
|
2021-08-23 16:50:18 +02:00
|
|
|
|
string[] formatsWebm = new string[] { "ogg" };
|
|
|
|
|
|
string[] formatsProres = new string[] { "m4a", "ac3", "dts", "wav" };
|
|
|
|
|
|
string[] formatsAvi = new string[] { "m4a", "ac3", "dts" };
|
|
|
|
|
|
|
|
|
|
|
|
switch (outMode)
|
|
|
|
|
|
{
|
|
|
|
|
|
case Interpolate.OutMode.VidMp4: supported = formatsMp4.Contains(alias); break;
|
|
|
|
|
|
case Interpolate.OutMode.VidMkv: supported = formatsMkv.Contains(alias); break;
|
|
|
|
|
|
case Interpolate.OutMode.VidWebm: supported = formatsWebm.Contains(alias); break;
|
|
|
|
|
|
case Interpolate.OutMode.VidProRes: supported = formatsProres.Contains(alias); break;
|
|
|
|
|
|
case Interpolate.OutMode.VidAvi: supported = formatsAvi.Contains(alias); break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Logger.Log($"Checking if {outMode} supports audio format '{format}' ({alias}): {supported}", true, false, "ffmpeg");
|
|
|
|
|
|
return supported;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static string GetExt(Interpolate.OutMode outMode, bool dot = true)
|
|
|
|
|
|
{
|
|
|
|
|
|
string ext = dot ? "." : "";
|
|
|
|
|
|
|
|
|
|
|
|
switch (outMode)
|
|
|
|
|
|
{
|
|
|
|
|
|
case Interpolate.OutMode.VidMp4: ext += "mp4"; break;
|
|
|
|
|
|
case Interpolate.OutMode.VidMkv: ext += "mkv"; break;
|
|
|
|
|
|
case Interpolate.OutMode.VidWebm: ext += "webm"; break;
|
|
|
|
|
|
case Interpolate.OutMode.VidProRes: ext += "mov"; break;
|
|
|
|
|
|
case Interpolate.OutMode.VidAvi: ext += "avi"; break;
|
|
|
|
|
|
case Interpolate.OutMode.VidGif: ext += "gif"; break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return ext;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-24 13:48:23 +01:00
|
|
|
|
public static string GetAudioExt(string codec)
|
2021-08-23 16:50:18 +02:00
|
|
|
|
{
|
|
|
|
|
|
if (codec.StartsWith("pcm_"))
|
|
|
|
|
|
return "wav";
|
|
|
|
|
|
|
|
|
|
|
|
switch (codec)
|
|
|
|
|
|
{
|
|
|
|
|
|
case "vorbis": return "ogg";
|
|
|
|
|
|
case "opus": return "ogg";
|
|
|
|
|
|
case "mp2": return "mp2";
|
|
|
|
|
|
case "mp3": return "mp3";
|
|
|
|
|
|
case "aac": return "m4a";
|
|
|
|
|
|
case "ac3": return "ac3";
|
|
|
|
|
|
case "eac3": return "ac3";
|
|
|
|
|
|
case "dts": return "dts";
|
|
|
|
|
|
case "alac": return "wav";
|
|
|
|
|
|
case "flac": return "wav";
|
|
|
|
|
|
case "wmav1": return "wma";
|
|
|
|
|
|
case "wmav2": return "wma";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return "unsupported";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-24 13:48:23 +01:00
|
|
|
|
public static async Task<string> GetAudioFallbackArgs(string videoPath, Interpolate.OutMode outMode, float itsScale)
|
2021-08-23 16:50:18 +02:00
|
|
|
|
{
|
2021-09-13 22:42:33 +02:00
|
|
|
|
bool opusMp4 = Config.GetBool(Config.Key.allowOpusInMp4);
|
2021-09-14 18:28:10 +02:00
|
|
|
|
int opusBr = Config.GetInt(Config.Key.opusBitrate, 128);
|
|
|
|
|
|
int aacBr = Config.GetInt(Config.Key.aacBitrate, 160);
|
2021-10-20 20:28:21 +02:00
|
|
|
|
int ac = (await GetVideoInfo.GetFfprobeInfoAsync(videoPath, GetVideoInfo.FfprobeMode.ShowStreams, "channels", 0)).GetInt();
|
|
|
|
|
|
string af = GetAudioFilters(itsScale);
|
2021-08-23 16:50:18 +02:00
|
|
|
|
|
2021-09-13 22:42:33 +02:00
|
|
|
|
if (outMode == Interpolate.OutMode.VidMkv || outMode == Interpolate.OutMode.VidWebm || (outMode == Interpolate.OutMode.VidMp4 && opusMp4))
|
2021-10-20 20:28:21 +02:00
|
|
|
|
return $"-c:a libopus -b:a {(ac > 4 ? $"{opusBr * 2}" : $"{opusBr}")}k -ac {(ac > 0 ? $"{ac}" : "2")} {af}"; // Double bitrate if 5ch or more, ignore ac if <= 0
|
2021-09-13 22:42:33 +02:00
|
|
|
|
else
|
2021-10-20 20:28:21 +02:00
|
|
|
|
return $"-c:a aac -b:a {(ac > 4 ? $"{aacBr * 2}" : $"{aacBr}")}k -aac_coder twoloop -ac {(ac > 0 ? $"{ac}" : "2")} {af}";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static string GetAudioFilters(float itsScale)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (itsScale == 0 || itsScale == 1)
|
|
|
|
|
|
return "";
|
|
|
|
|
|
|
2021-11-24 13:48:23 +01:00
|
|
|
|
if (itsScale > 4)
|
2021-10-20 20:30:27 +02:00
|
|
|
|
return $"-af atempo=0.5,atempo=0.5,atempo={((1f / itsScale) * 4).ToStringDot()}";
|
2021-10-20 20:28:21 +02:00
|
|
|
|
else if (itsScale > 2)
|
|
|
|
|
|
return $"-af atempo=0.5,atempo={((1f / itsScale) * 2).ToStringDot()}";
|
|
|
|
|
|
else
|
|
|
|
|
|
return $"-af atempo={(1f / itsScale).ToStringDot()}";
|
2021-08-23 16:50:18 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static string GetSubCodecForContainer(string containerExt)
|
|
|
|
|
|
{
|
2021-11-24 13:48:23 +01:00
|
|
|
|
containerExt = containerExt.Remove(".");
|
2021-08-23 16:50:18 +02:00
|
|
|
|
|
|
|
|
|
|
if (containerExt == "mp4") return "mov_text";
|
|
|
|
|
|
if (containerExt == "webm") return "webvtt";
|
|
|
|
|
|
|
2021-09-13 22:44:16 +02:00
|
|
|
|
return "copy"; // Default: Copy subs
|
2021-08-23 16:50:18 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static bool ContainerSupportsSubs(string containerExt, bool showWarningIfNotSupported = true)
|
|
|
|
|
|
{
|
|
|
|
|
|
containerExt = containerExt.Remove(".");
|
|
|
|
|
|
bool supported = (containerExt == "mp4" || containerExt == "mkv" || containerExt == "webm" || containerExt == "mov");
|
|
|
|
|
|
Logger.Log($"Subtitles {(supported ? "are supported" : "not supported")} by {containerExt.ToUpper()}", true);
|
|
|
|
|
|
|
|
|
|
|
|
if (showWarningIfNotSupported && Config.GetBool(Config.Key.keepSubs) && !supported)
|
|
|
|
|
|
Logger.Log($"Warning: Subtitle transfer is enabled, but {containerExt.ToUpper()} does not support subtitles properly. MKV is recommended instead.");
|
2021-11-24 13:48:23 +01:00
|
|
|
|
|
2021-08-23 16:50:18 +02:00
|
|
|
|
return supported;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-24 13:48:23 +01:00
|
|
|
|
public static void CreateConcatFile(string inputFilesDir, string outputPath, string[] validExtensions = null)
|
2021-08-23 16:50:18 +02:00
|
|
|
|
{
|
|
|
|
|
|
string concatFileContent = "";
|
|
|
|
|
|
string[] files = IoUtils.GetFilesSorted(inputFilesDir);
|
|
|
|
|
|
|
|
|
|
|
|
foreach (string file in files)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (validExtensions != null && !validExtensions.Contains(Path.GetExtension(file).ToLower()))
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
concatFileContent += $"file '{file.Replace(@"\", "/")}'\n";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
File.WriteAllText(outputPath, concatFileContent);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|