Skip to content

Commit

Permalink
Support for preserving VFR timings and video rotation, better duratio…
Browse files Browse the repository at this point in the history
…n readout
  • Loading branch information
n00mkrad committed Nov 7, 2024
1 parent 6f0b3b3 commit b89ce78
Show file tree
Hide file tree
Showing 12 changed files with 663 additions and 855 deletions.
37 changes: 34 additions & 3 deletions CodeLegacy/Data/MediaFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Stream = Flowframes.Data.Streams.Stream;

Expand Down Expand Up @@ -40,6 +39,8 @@ public class MediaFile
public long CreationTime;
public bool Initialized = false;
public bool SequenceInitialized = false;
public bool IsVfr = false;
public List<float> InputTimestamps = new List<float>();

public int FileCount = 1;
public int FrameCount { get { return VideoStreams.Count > 0 ? VideoStreams[0].FrameCount : 0; } }
Expand Down Expand Up @@ -103,7 +104,7 @@ public async Task Initialize(bool progressBar = true, bool countFrames = true)
await InitializeSequence();

await LoadFormatInfo(ImportPath);
AllStreams = await FfmpegUtils.GetStreams(ImportPath, progressBar, StreamCount, InputRate, countFrames);
AllStreams = await FfmpegUtils.GetStreams(ImportPath, progressBar, StreamCount, InputRate, countFrames, this);
VideoStreams = AllStreams.Where(x => x.Type == Stream.StreamType.Video).Select(x => (VideoStream)x).ToList();
AudioStreams = AllStreams.Where(x => x.Type == Stream.StreamType.Audio).Select(x => (AudioStream)x).ToList();
SubtitleStreams = AllStreams.Where(x => x.Type == Stream.StreamType.Subtitle).Select(x => (SubtitleStream)x).ToList();
Expand All @@ -123,11 +124,41 @@ private async Task LoadFormatInfo(string path)
{
Title = await GetVideoInfo.GetFfprobeInfoAsync(path, GetVideoInfo.FfprobeMode.ShowFormat, "TAG:title");
Language = await GetVideoInfo.GetFfprobeInfoAsync(path, GetVideoInfo.FfprobeMode.ShowFormat, "TAG:language");
DurationMs = (await FfmpegCommands.GetDurationMs(path));
DurationMs = await FfmpegCommands.GetDurationMs(path, this);
FfmpegCommands.CheckVfr(path, this);
StreamCount = await FfmpegUtils.GetStreamCount(path);
TotalKbits = (await GetVideoInfo.GetFfprobeInfoAsync(path, GetVideoInfo.FfprobeMode.ShowFormat, "bit_rate")).GetInt() / 1000;
}

public List<float> GetResampledTimestamps(List<float> timestamps, double factor)
{
int originalCount = timestamps.Count;
int newCount = (int)Math.Round(originalCount * factor);
List<float> resampledTimestamps = new List<float>();

for (int i = 0; i < newCount; i++)
{
double x = i / factor;

if (x >= originalCount - 1)
{
resampledTimestamps.Add(timestamps[originalCount - 1]);
}
else
{
int index = (int)Math.Floor(x);
double fraction = x - index;
float startTime = timestamps[index];
float endTime = timestamps[index + 1];

float interpolatedTime = (float)(startTime + (endTime - startTime) * fraction);
resampledTimestamps.Add(interpolatedTime);
}
}

return resampledTimestamps;
}

public string GetName()
{
if (IsDirectory)
Expand Down
9 changes: 7 additions & 2 deletions CodeLegacy/Data/ModelCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,14 @@ public class ModelInfo

public ModelInfo() { }

public string GetUiString()
public string GetUiString(bool addRecommendedStr = false)
{
return $"{Name} - {Desc}{(SupportsAlpha ? " (Supports Transparency)" : "")}{(FixedFactors.Count() > 0 ? $" ({GetFactorsString()})" : "")}{(IsDefault ? " (Recommended)" : "")}";
string s = $"{Name} - {Desc}{(SupportsAlpha ? " - Transparency Support" : "")}{(FixedFactors.Count() > 0 ? $" ({GetFactorsString()})" : "")}";

if(addRecommendedStr && IsDefault)
s += " (Recommended)";

return s;
}

public string GetFactorsString ()
Expand Down
73 changes: 33 additions & 40 deletions CodeLegacy/Data/VidExtraData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ class VidExtraData
// Aspect Ratio
public string displayRatio = "";

// Rotation
public int Rotation = 0;

private readonly string[] validColorSpaces = new string[] { "bt709", "bt470m", "bt470bg", "smpte170m", "smpte240m", "linear", "log100",
"log316", "iec61966-2-4", "bt1361e", "iec61966-2-1", "bt2020-10", "bt2020-12", "smpte2084", "smpte428", "arib-std-b67" };

Expand All @@ -23,37 +26,44 @@ public VidExtraData () { }
public VidExtraData(string ffprobeOutput)
{
string[] lines = ffprobeOutput.SplitIntoLines();

if (!Config.GetBool(Config.Key.keepColorSpace, true))
return;
bool keepColorSpace = Config.GetBool(Config.Key.keepColorSpace, true);

foreach (string line in lines)
{
if (line.Contains("color_range"))
{
colorRange = line.Split('=').LastOrDefault();
continue;
}

if (line.Contains("color_space"))
{
colorSpace = line.Split('=').LastOrDefault();
continue;
}

if (line.Contains("color_transfer"))
if(line.StartsWith("rotation="))
{
colorTransfer = line.Split('=').LastOrDefault();
Rotation = line.Split('=').LastOrDefault().GetInt();
continue;
}

if (line.Contains("color_primaries"))
if (keepColorSpace)
{
colorPrimaries = line.Split('=').LastOrDefault();
continue;
if (line.StartsWith("color_range"))
{
colorRange = line.Split('=').LastOrDefault().Lower();
continue;
}

if (line.StartsWith("color_space"))
{
colorSpace = line.Split('=').LastOrDefault().Lower();
continue;
}

if (line.StartsWith("color_transfer"))
{
colorTransfer = line.Split('=').LastOrDefault().Lower();
continue;
}

if (line.StartsWith("color_primaries"))
{
colorPrimaries = line.Split('=').LastOrDefault().Lower();
continue;
}
}

if (line.Contains("display_aspect_ratio") && Config.GetBool(Config.Key.keepAspectRatio, true))
if (line.StartsWith("display_aspect_ratio") && Config.GetBool(Config.Key.keepAspectRatio, true))
{
displayRatio = line.Split('=').LastOrDefault();
continue;
Expand All @@ -76,7 +86,7 @@ public VidExtraData(string ffprobeOutput)
}
else
{
colorTransfer = colorTransfer.Replace("bt470bg", "gamma28").Replace("bt470m", "gamma28"); // https://forum.videohelp.com/threads/394596-Color-Matrix
colorTransfer = colorTransfer.Replace("bt470bg", "gamma28").Replace("bt470m", "gamma28"); // https://forum.videohelp.com/threads/394596-Color-Matrix
}

if (!validColorSpaces.Contains(colorPrimaries.Trim()))
Expand All @@ -86,24 +96,7 @@ public VidExtraData(string ffprobeOutput)
}
}

public bool HasAnyValues ()
{
if (!string.IsNullOrWhiteSpace(colorSpace))
return true;

if (!string.IsNullOrWhiteSpace(colorRange))
return true;

if (!string.IsNullOrWhiteSpace(colorTransfer))
return true;

if (!string.IsNullOrWhiteSpace(colorPrimaries))
return true;

return false;
}

public bool HasAllValues()
public bool HasAllColorValues()
{
if (string.IsNullOrWhiteSpace(colorSpace))
return false;
Expand Down
23 changes: 14 additions & 9 deletions CodeLegacy/Extensions/ExtensionMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,17 @@ namespace Flowframes
{
public static class ExtensionMethods
{
public static string TrimNumbers(this string s, bool allowDotComma = false)
/// <summary> Remove anything from a string that is not a number, optionally allowing scientific notation (<paramref name="allowScientific"/>) </summary>
public static string TrimNumbers(this string s, bool allowDotComma = false, bool allowScientific = false)
{
if (!allowDotComma)
s = Regex.Replace(s, "[^0-9]", "");
else
s = Regex.Replace(s, "[^.,0-9]", "");
return s.Trim();
if (s == null)
return s;

// string og = s;
string regex = $@"[^{(allowDotComma ? ".," : "")}0-9\-{(allowScientific ? "e" : "")}]";
s = Regex.Replace(s, regex, "").Trim();
// Logger.Log($"Trimmed {og} -> {s} - Pattern: {regex}", true);
return s;
}

public static int GetInt(this TextBox textbox)
Expand All @@ -43,7 +47,8 @@ public static int GetInt(this string str)

try
{
return int.Parse(str.TrimNumbers());
str = str.TrimNumbers(allowDotComma: false);
return int.Parse(str);
}
catch (Exception e)
{
Expand Down Expand Up @@ -92,11 +97,11 @@ public static float GetFloat(this ComboBox combobox)

public static float GetFloat(this string str)
{
if (str == null || str.Length < 1)
if (str.Length < 1 || str == null)
return 0f;

string num = str.TrimNumbers(true).Replace(",", ".");
float.TryParse(num, out float value);
float.TryParse(num, NumberStyles.Any, CultureInfo.InvariantCulture, out float value);
return value;
}

Expand Down
30 changes: 28 additions & 2 deletions CodeLegacy/Main/Export.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,14 @@ public static async Task<string> GetPipedFfmpegCmd(bool ffplay = false)

bool gifInput = I.currentMediaFile.Format.Upper() == "GIF"; // If input is GIF, we don't need to check the color space etc
VidExtraData extraData = gifInput ? new VidExtraData() : await FfmpegCommands.GetVidExtraInfo(s.inPath);
string extraArgsIn = await FfmpegEncode.GetFfmpegExportArgsIn(s.outFps, s.outItsScale);
string extraArgsIn = await FfmpegEncode.GetFfmpegExportArgsIn(s.outFps, s.outItsScale, extraData.Rotation);
string extraArgsOut = await FfmpegEncode.GetFfmpegExportArgsOut(fpsLimit ? maxFps : new Fraction(), extraData, s.outSettings);

if(s.outSettings.Encoder == Enums.Encoding.Encoder.Exr)
{
extraArgsIn += " -color_trc bt709 -color_primaries bt709 -colorspace bt709";
}


if (ffplay)
{
bool useNutPipe = true; // TODO: Make this bool a config flag
Expand Down Expand Up @@ -393,5 +392,32 @@ public static async Task MuxOutputVideo(string inputPath, string outVideo, bool
Logger.Log("MergeAudio() Exception: " + e.Message, true);
}
}

public static void MuxTimestamps (string vidPath)
{
Logger.Log($"Muxing timestamps for '{vidPath}'", hidden: true);
var resampledTs = I.currentMediaFile.GetResampledTimestamps(I.currentMediaFile.InputTimestamps, I.currentSettings.interpFactor);
var tsFileLines = new List<string>() { "# timecode format v2" };

for (int i = 0; i < (resampledTs.Count - 1); i++)
{
tsFileLines.Add((resampledTs[i] * 1000f).ToString("0.000000"));
}

string tsFile = Path.Combine(Paths.GetSessionDataPath(), "ts.out.txt");
File.WriteAllLines(tsFile, tsFileLines);
string outPath = Path.ChangeExtension(vidPath, ".tmp.mkv");
string args = $"mkvmerge --output {outPath.Wrap()} --timestamps \"0:{tsFile}\" {vidPath.Wrap()}";
var outputMux = NUtilsTemp.OsUtils.RunCommand($"cd /D {Path.Combine(Paths.GetPkgPath(), Paths.audioVideoDir).Wrap()} && {args}");
Logger.Log(outputMux, hidden: true);

// Check if file exists and is not too small (min. 80% of input file)
if (File.Exists(outPath) && ((double)new FileInfo(outPath).Length / (double)new FileInfo(vidPath).Length) > 0.8d)
{
Logger.Log($"Deleting '{vidPath}' and moving '{outPath}' to '{vidPath}'", hidden: true);
File.Delete(vidPath);
File.Move(outPath, vidPath);
}
}
}
}
6 changes: 6 additions & 0 deletions CodeLegacy/Media/FfmpegAudioAndMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using static Flowframes.AvProcess;
using Utils = Flowframes.Media.FfmpegUtils;
using I = Flowframes.Interpolate;
using Flowframes.Main;

namespace Flowframes.Media
{
Expand Down Expand Up @@ -59,6 +60,11 @@ public static async Task MergeStreamsFromInput (string inputVideo, string interp
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 (I.currentMediaFile.IsVfr)
{
Export.MuxTimestamps(tempPath);
}

if (QuickSettingsTab.trimEnabled)
{
string otherStreamsName = $"otherStreams{containerExt}";
Expand Down
Loading

0 comments on commit b89ce78

Please sign in to comment.