Advertisement
ivandrofly

Subtitlte Edit: TimeCode with formatter

Mar 2nd, 2025
284
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 14.22 KB | None | 0 0
  1. using Nikse.SubtitleEdit.Core.SubtitleFormats;
  2. using System;
  3. using System.Globalization;
  4.  
  5. namespace Nikse.SubtitleEdit.Core.Common
  6. {
  7.     public class TimeCode
  8.     {
  9.         public static readonly char[] TimeSplitChars = { ':', ',', '.' };
  10.         public const double BaseUnit = 1000.0; // Base unit of time
  11.  
  12.         public bool IsMaxTime => Math.Abs(TotalMilliseconds - MaxTimeTotalMilliseconds) < 0.01;
  13.         public const double MaxTimeTotalMilliseconds = 359999999; // new TimeCode(99, 59, 59, 999).TotalMilliseconds
  14.  
  15.         public static TimeCode FromSeconds(double seconds)
  16.         {
  17.             return new TimeCode(seconds * BaseUnit);
  18.         }
  19.  
  20.         /// <summary>
  21.         /// Parse time string to milliseconds, format: HH[:,.]MM[:,.]SS[:,.]MSec or MM[:,.]SS[:,.]MSec
  22.         /// </summary>
  23.         /// <param name="text">Time code as string.</param>
  24.         /// <returns>Total milliseconds.</returns>
  25.         public static double ParseToMilliseconds(string text)
  26.         {
  27.             var parts = text.Split(TimeSplitChars, StringSplitOptions.RemoveEmptyEntries);
  28.             if (parts.Length == 4)
  29.             {
  30.                 var msString = parts[3].PadRight(3,'0');
  31.                 if (int.TryParse(parts[0], out var hours) && int.TryParse(parts[1], out var minutes) && int.TryParse(parts[2], out var seconds) && int.TryParse(msString, out var milliseconds))
  32.                 {
  33.                     return new TimeSpan(0, hours, minutes, seconds, milliseconds).TotalMilliseconds;
  34.                 }
  35.             }
  36.  
  37.             if (parts.Length == 3)
  38.             {
  39.                 var msString = parts[2].PadRight(3, '0');
  40.                 if (int.TryParse(parts[0], out var minutes) && int.TryParse(parts[1], out var seconds) && int.TryParse(msString, out var milliseconds))
  41.                 {
  42.                     return new TimeSpan(0, 0, minutes, seconds, milliseconds).TotalMilliseconds;
  43.                 }
  44.             }
  45.  
  46.             return 0;
  47.         }
  48.  
  49.         public static double ParseHHMMSSFFToMilliseconds(string text)
  50.         {
  51.             var parts = text.Split(TimeSplitChars, StringSplitOptions.RemoveEmptyEntries);
  52.             if (parts.Length == 4)
  53.             {
  54.                 if (int.TryParse(parts[0], out var hours) && int.TryParse(parts[1], out var minutes) && int.TryParse(parts[2], out var seconds) && int.TryParse(parts[3], out var frames))
  55.                 {
  56.                     return new TimeCode(hours, minutes, seconds, SubtitleFormat.FramesToMillisecondsMax999(frames)).TotalMilliseconds;
  57.                 }
  58.             }
  59.             return 0;
  60.         }
  61.  
  62.         public static double ParseHHMMSSToMilliseconds(string text)
  63.         {
  64.             var parts = text.Split(TimeSplitChars, StringSplitOptions.RemoveEmptyEntries);
  65.             if (parts.Length == 3)
  66.             {
  67.                 if (int.TryParse(parts[0], out var hours) && int.TryParse(parts[1], out var minutes) && int.TryParse(parts[2], out var seconds))
  68.                 {
  69.                     return new TimeCode(hours, minutes, seconds, 0).TotalMilliseconds;
  70.                 }
  71.             }
  72.  
  73.             return 0;
  74.         }
  75.  
  76.         public TimeCode()
  77.         {
  78.         }
  79.  
  80.         public TimeCode(TimeSpan timeSpan)
  81.         {
  82.             TotalMilliseconds = timeSpan.TotalMilliseconds;
  83.         }
  84.  
  85.         public TimeCode(double totalMilliseconds)
  86.         {
  87.             TotalMilliseconds = totalMilliseconds;
  88.         }
  89.  
  90.         public TimeCode(int hours, int minutes, int seconds, int milliseconds)
  91.         {
  92.             TotalMilliseconds = hours * 60 * 60 * BaseUnit + minutes * 60 * BaseUnit + seconds * BaseUnit + milliseconds;
  93.         }
  94.  
  95.         public int Hours
  96.         {
  97.             get
  98.             {
  99.                 var ts = TimeSpan;
  100.                 return ts.Hours + ts.Days * 24;
  101.             }
  102.             set
  103.             {
  104.                 var ts = TimeSpan;
  105.                 TotalMilliseconds = new TimeSpan(ts.Days, value, ts.Minutes, ts.Seconds, ts.Milliseconds).TotalMilliseconds;
  106.             }
  107.         }
  108.  
  109.         public int Minutes
  110.         {
  111.             get => TimeSpan.Minutes;
  112.             set
  113.             {
  114.                 var ts = TimeSpan;
  115.                 TotalMilliseconds = new TimeSpan(ts.Days, ts.Hours, value, ts.Seconds, ts.Milliseconds).TotalMilliseconds;
  116.             }
  117.         }
  118.  
  119.         public int Seconds
  120.         {
  121.             get => TimeSpan.Seconds;
  122.             set
  123.             {
  124.                 var ts = TimeSpan;
  125.                 TotalMilliseconds = new TimeSpan(ts.Days, ts.Hours, ts.Minutes, value, ts.Milliseconds).TotalMilliseconds;
  126.             }
  127.         }
  128.  
  129.         public int Milliseconds
  130.         {
  131.             get => TimeSpan.Milliseconds;
  132.             set
  133.             {
  134.                 var ts = TimeSpan;
  135.                 TotalMilliseconds = new TimeSpan(ts.Days, ts.Hours, ts.Minutes, ts.Seconds, value).TotalMilliseconds;
  136.             }
  137.         }
  138.  
  139.         public double TotalMilliseconds { get; set; }
  140.  
  141.         public double TotalSeconds
  142.         {
  143.             get => TotalMilliseconds / BaseUnit;
  144.             set => TotalMilliseconds = value * BaseUnit;
  145.         }
  146.  
  147.         public TimeSpan TimeSpan
  148.         {
  149.             get
  150.             {
  151.                 if (TotalMilliseconds > MaxTimeTotalMilliseconds || TotalMilliseconds < -MaxTimeTotalMilliseconds)
  152.                 {
  153.                     return TimeSpan.Zero;
  154.                 }
  155.  
  156.                 return TimeSpan.FromMilliseconds(TotalMilliseconds);
  157.             }
  158.             set => TotalMilliseconds = value.TotalMilliseconds;
  159.         }
  160.  
  161.         public override string ToString() => ToString(false);
  162.  
  163.         public string ToString(bool localize) => TimeCodeFormatter.Standard.Format(this, localize);
  164.  
  165.         public string ToString(TimeCodeFormatter formatter, bool localize = false) => formatter.Format(this, localize);
  166.  
  167.         public abstract class TimeCodeFormatter
  168.         {
  169.             public static readonly TimeCodeFormatter Standard = new StandardTimeCodeFormatter();
  170.             public static readonly TimeCodeFormatter Short = new ShortTimeCodeFormatter();
  171.            
  172.             public abstract string Format(TimeCode timeCode, bool localize);
  173.            
  174.             protected string PrefixSign(TimeCode tc, string time) => tc.TotalMilliseconds >= 0 ? time : $"-{time.RemoveChar('-')}";
  175.         }
  176.  
  177.         private class StandardTimeCodeFormatter : TimeCodeFormatter
  178.         {
  179.             public override string Format(TimeCode timeCode, bool localize)
  180.             {
  181.                 var ts = timeCode.TimeSpan;
  182.                 var decimalSeparator = localize ? CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator : ",";
  183.                 var s = $"{ts.Hours + ts.Days * 24:00}:{ts.Minutes:00}:{ts.Seconds:00}{decimalSeparator}{ts.Milliseconds:000}";
  184.  
  185.                 return PrefixSign(timeCode, s);
  186.             }
  187.         }
  188.  
  189.         private class ShortTimeCodeFormatter : TimeCodeFormatter
  190.         {
  191.             public override string Format(TimeCode timeCode, bool localize)
  192.             {
  193.                 var ts = timeCode.TimeSpan;
  194.                 var decimalSeparator = localize ? CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator : ",";
  195.                 string s;
  196.                 if (ts.Minutes == 0 && ts.Hours == 0 && ts.Days == 0)
  197.                 {
  198.                     s = $"{ts.Seconds:0}{decimalSeparator}{ts.Milliseconds:000}";
  199.  
  200.                     if (s == $"0{decimalSeparator}000")
  201.                     {
  202.                         return s; // no sign
  203.                     }
  204.                 }
  205.                 else if (ts.Hours == 0 && ts.Days == 0)
  206.                 {
  207.                     s = $"{ts.Minutes:0}:{ts.Seconds:00}{decimalSeparator}{ts.Milliseconds:000}";
  208.  
  209.                     if (s == $"0:00{decimalSeparator}000")
  210.                     {
  211.                         return s; // no sign
  212.                     }
  213.                 }
  214.                 else
  215.                 {
  216.                     s = $"{ts.Hours + ts.Days * 24:0}:{ts.Minutes:00}:{ts.Seconds:00}{decimalSeparator}{ts.Milliseconds:000}";
  217.  
  218.                     if (s == $"0:00:00{decimalSeparator}000")
  219.                     {
  220.                         return s; // no sign
  221.                     }
  222.                 }
  223.  
  224.                 return PrefixSign(timeCode, s);
  225.             }
  226.         }
  227.  
  228.         public string ToShortStringHHMMSSFF()
  229.         {
  230.             var s = ToHHMMSSFF();
  231.             var pre = string.Empty;
  232.             if (s.StartsWith('-'))
  233.             {
  234.                 pre = "-";
  235.                 s = s.TrimStart('-');
  236.             }
  237.  
  238.             var j = 0;
  239.             var len = s.Length;
  240.             while (j + 6 < len && s[j] == '0' && s[j + 1] == '0' && s[j + 2] == ':')
  241.             {
  242.                 j += 3;
  243.             }
  244.             s = j > 0 ? s.Substring(j) : s;
  245.             return pre + s;
  246.         }
  247.  
  248.         public string ToHHMMSSFF()
  249.         {
  250.             string s;
  251.             var ts = TimeSpan;
  252.             var frames = Math.Round(ts.Milliseconds / (BaseUnit / Configuration.Settings.General.CurrentFrameRate));
  253.             if (frames >= Configuration.Settings.General.CurrentFrameRate - 0.001)
  254.             {
  255.                 var newTs = new TimeSpan(ts.Ticks);
  256.                 newTs = newTs.Add(new TimeSpan(0, 0, 1));
  257.                 s = $"{newTs.Days * 24 + newTs.Hours:00}:{newTs.Minutes:00}:{newTs.Seconds:00}:{0:00}";
  258.             }
  259.             else
  260.             {
  261.                 s = $"{ts.Days * 24 + ts.Hours:00}:{ts.Minutes:00}:{ts.Seconds:00}:{SubtitleFormat.MillisecondsToFramesMaxFrameRate(ts.Milliseconds):00}";
  262.             }
  263.  
  264.             return PrefixSign(s);
  265.         }
  266.  
  267.         public string ToHHMMSS()
  268.         {
  269.             string s;
  270.             var ts = TimeSpan;
  271.             var frames = Math.Round(ts.Milliseconds / (BaseUnit / Configuration.Settings.General.CurrentFrameRate));
  272.             if (frames >= Configuration.Settings.General.CurrentFrameRate - 0.001)
  273.             {
  274.                 var newTs = new TimeSpan(ts.Ticks);
  275.                 newTs = newTs.Add(new TimeSpan(0, 0, 1));
  276.                 s = $"{newTs.Days * 24 + newTs.Hours:00}:{newTs.Minutes:00}:{newTs.Seconds:00}";
  277.             }
  278.             else
  279.             {
  280.                 s = $"{ts.Days * 24 + ts.Hours:00}:{ts.Minutes:00}:{ts.Seconds:00}";
  281.             }
  282.             return PrefixSign(s);
  283.         }
  284.  
  285.         public string ToHHMMSSFFDropFrame()
  286.         {
  287.             string s;
  288.             var ts = TimeSpan;
  289.             var frames = Math.Round(ts.Milliseconds / (BaseUnit / Configuration.Settings.General.CurrentFrameRate));
  290.             if (frames >= Configuration.Settings.General.CurrentFrameRate - 0.001)
  291.             {
  292.                 var newTs = new TimeSpan(ts.Ticks);
  293.                 newTs = newTs.Add(new TimeSpan(0, 0, 1));
  294.                 s = $"{newTs.Days * 24 + newTs.Hours:00}:{newTs.Minutes:00}:{newTs.Seconds:00};{0:00}";
  295.             }
  296.             else
  297.             {
  298.                 s = $"{ts.Days * 24 + ts.Hours:00}:{ts.Minutes:00}:{ts.Seconds:00};{SubtitleFormat.MillisecondsToFramesMaxFrameRate(ts.Milliseconds):00}";
  299.             }
  300.             return PrefixSign(s);
  301.         }
  302.  
  303.         public string ToSSFF()
  304.         {
  305.             string s;
  306.             var ts = TimeSpan;
  307.             var frames = Math.Round(ts.Milliseconds / (BaseUnit / Configuration.Settings.General.CurrentFrameRate));
  308.             if (frames >= Configuration.Settings.General.CurrentFrameRate - 0.001)
  309.             {
  310.                 s = $"{ts.Seconds + 1:00}:{0:00}";
  311.             }
  312.             else
  313.             {
  314.                 s = $"{ts.Seconds:00}:{SubtitleFormat.MillisecondsToFramesMaxFrameRate(ts.Milliseconds):00}";
  315.             }
  316.  
  317.             return PrefixSign(s);
  318.         }
  319.  
  320.         public string ToHHMMSSPeriodFF()
  321.         {
  322.             string s;
  323.             var ts = TimeSpan;
  324.             var frames = Math.Round(ts.Milliseconds / (BaseUnit / Configuration.Settings.General.CurrentFrameRate));
  325.             if (frames >= Configuration.Settings.General.CurrentFrameRate - 0.001)
  326.             {
  327.                 var newTs = new TimeSpan(ts.Ticks);
  328.                 newTs = newTs.Add(new TimeSpan(0, 0, 1));
  329.                 s = $"{newTs.Days * 24 + newTs.Hours:00}:{newTs.Minutes:00}:{newTs.Seconds:00}.{0:00}";
  330.             }
  331.             else
  332.             {
  333.                 s = $"{ts.Days * 24 + ts.Hours:00}:{ts.Minutes:00}:{ts.Seconds:00}.{SubtitleFormat.MillisecondsToFramesMaxFrameRate(ts.Milliseconds):00}";
  334.             }
  335.  
  336.             return PrefixSign(s);
  337.         }
  338.  
  339.         private string PrefixSign(string time) => TotalMilliseconds >= 0 ? time : $"-{time.RemoveChar('-')}";
  340.  
  341.         public string ToDisplayString()
  342.         {
  343.             if (IsMaxTime)
  344.             {
  345.                 return "-";
  346.             }
  347.  
  348.             if (Configuration.Settings?.General.UseTimeFormatHHMMSSFF == true)
  349.             {
  350.                 return ToHHMMSSFF();
  351.             }
  352.  
  353.             return ToString(true);
  354.         }
  355.  
  356.         public string ToShortDisplayString()
  357.         {
  358.             if (IsMaxTime)
  359.             {
  360.                 return "-";
  361.             }
  362.  
  363.             if (Configuration.Settings?.General.UseTimeFormatHHMMSSFF == true)
  364.             {
  365.                 return ToShortStringHHMMSSFF();
  366.             }
  367.  
  368.             return ToShortString(true);
  369.         }
  370.  
  371.         /// <summary>
  372.         /// Align time to frame rate.
  373.         /// </summary>
  374.         public TimeCode AlignToFrame()
  375.         {
  376.             var ts = TimeSpan.FromMilliseconds(Math.Round(TotalMilliseconds, MidpointRounding.AwayFromZero));
  377.             var frames = SubtitleFormat.MillisecondsToFrames(ts.Milliseconds);
  378.             TimeSpan ts2;
  379.             if (frames >= Configuration.Settings.General.CurrentFrameRate - 0.001)
  380.             {
  381.                 ts = ts.Add(new TimeSpan(0, 0, 1));
  382.                 ts2 = new TimeSpan(ts.Days, ts.Hours, ts.Minutes, ts.Seconds, 0);
  383.             }
  384.             else
  385.             {
  386.                 var ms = SubtitleFormat.FramesToMillisecondsMax999(SubtitleFormat.MillisecondsToFramesMaxFrameRate(ts.Milliseconds));
  387.                 ts2 = new TimeSpan(ts.Days, ts.Hours, ts.Minutes, ts.Seconds, ms);
  388.             }
  389.  
  390.             return new TimeCode(ts2.TotalMilliseconds);
  391.         }
  392.     }
  393. }
  394.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement