377 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			377 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | using System; | ||
|  | using System.Globalization; | ||
|  | using System.Text.RegularExpressions; | ||
|  | #if TIMELINE_FRAMEACCURATE | ||
|  | using UnityEngine.Playables; | ||
|  | #endif | ||
|  | 
 | ||
|  | namespace UnityEngine.Timeline | ||
|  | { | ||
|  |     /// <summary> | ||
|  |     /// The standard frame rates supported when locking Timeline playback to frames. | ||
|  |     /// The frame rate is expressed in frames per second (fps). | ||
|  |     /// </summary> | ||
|  |     public enum StandardFrameRates | ||
|  |     { | ||
|  |         /// <summary> | ||
|  |         /// Represents a frame rate of 24 fps. This is the common frame rate for film. | ||
|  |         /// </summary> | ||
|  |         Fps24, | ||
|  |         /// <summary> | ||
|  |         /// Represents a drop frame rate of 23.97 fps. This is the common frame rate for NTSC film broadcast. | ||
|  |         /// </summary> | ||
|  |         Fps23_97, | ||
|  |         /// <summary> | ||
|  |         /// Represents a frame rate of 25 fps. This is commonly used for non-interlaced PAL television broadcast. | ||
|  |         /// </summary> | ||
|  |         Fps25, | ||
|  |         /// <summary> | ||
|  |         /// Represents a frame rate of 30 fps. This is commonly used for HD footage. | ||
|  |         /// </summary> | ||
|  |         Fps30, | ||
|  |         /// <summary> | ||
|  |         /// Represents a drop frame rate of 29.97 fps. This is commonly used for NTSC television broadcast. | ||
|  |         /// </summary> | ||
|  |         Fps29_97, | ||
|  |         /// <summary> | ||
|  |         /// Represents a frame rate of 50 fps. This is commonly used for interlaced PAL television broadcast. | ||
|  |         /// </summary> | ||
|  |         Fps50, | ||
|  |         /// <summary> | ||
|  |         /// Represents a frame rate of 60 fps. This is commonly used for games. | ||
|  |         /// </summary> | ||
|  |         Fps60, | ||
|  |         /// <summary> | ||
|  |         /// Represents a drop frame rate of 59.94 fps. This is commonly used for interlaced NTSC television broadcast. | ||
|  |         /// </summary> | ||
|  |         Fps59_94 | ||
|  |     } | ||
|  | 
 | ||
|  |     // Sequence specific utilities for time manipulation | ||
|  |     static class TimeUtility | ||
|  |     { | ||
|  |         // chosen because it will cause no rounding errors between time/frames for frames values up to at least 10 million | ||
|  |         public static readonly double kTimeEpsilon = 1e-14; | ||
|  |         public static readonly double kFrameRateEpsilon = 1e-6; | ||
|  |         public static readonly double k_MaxTimelineDurationInSeconds = 9e6; //104 days of running time | ||
|  |         public static readonly double kFrameRateRounding = 1e-2; | ||
|  | 
 | ||
|  | 
 | ||
|  |         static void ValidateFrameRate(double frameRate) | ||
|  |         { | ||
|  |             if (frameRate <= kTimeEpsilon) | ||
|  |                 throw new ArgumentException("frame rate cannot be 0 or negative"); | ||
|  |         } | ||
|  | 
 | ||
|  |         public static int ToFrames(double time, double frameRate) | ||
|  |         { | ||
|  |             ValidateFrameRate(frameRate); | ||
|  |             time = Math.Min(Math.Max(time, -k_MaxTimelineDurationInSeconds), k_MaxTimelineDurationInSeconds); | ||
|  |             // this matches OnFrameBoundary | ||
|  |             double tolerance = GetEpsilon(time, frameRate); | ||
|  |             if (time < 0) | ||
|  |             { | ||
|  |                 return (int)Math.Ceiling(time * frameRate - tolerance); | ||
|  |             } | ||
|  |             return (int)Math.Floor(time * frameRate + tolerance); | ||
|  |         } | ||
|  | 
 | ||
|  |         public static double ToExactFrames(double time, double frameRate) | ||
|  |         { | ||
|  |             ValidateFrameRate(frameRate); | ||
|  |             return time * frameRate; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static double FromFrames(int frames, double frameRate) | ||
|  |         { | ||
|  |             ValidateFrameRate(frameRate); | ||
|  |             return (frames / frameRate); | ||
|  |         } | ||
|  | 
 | ||
|  |         public static double FromFrames(double frames, double frameRate) | ||
|  |         { | ||
|  |             ValidateFrameRate(frameRate); | ||
|  |             return frames / frameRate; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static bool OnFrameBoundary(double time, double frameRate) | ||
|  |         { | ||
|  |             return OnFrameBoundary(time, frameRate, GetEpsilon(time, frameRate)); | ||
|  |         } | ||
|  | 
 | ||
|  |         public static double GetEpsilon(double time, double frameRate) | ||
|  |         { | ||
|  |             return Math.Max(Math.Abs(time), 1) * frameRate * kTimeEpsilon; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static int PreviousFrame(double time, double frameRate) | ||
|  |         { | ||
|  |             return Math.Max(0, ToFrames(time, frameRate) - 1); | ||
|  |         } | ||
|  | 
 | ||
|  |         public static int NextFrame(double time, double frameRate) | ||
|  |         { | ||
|  |             return ToFrames(time, frameRate) + 1; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static double PreviousFrameTime(double time, double frameRate) | ||
|  |         { | ||
|  |             return FromFrames(PreviousFrame(time, frameRate), frameRate); | ||
|  |         } | ||
|  | 
 | ||
|  |         public static double NextFrameTime(double time, double frameRate) | ||
|  |         { | ||
|  |             return FromFrames(NextFrame(time, frameRate), frameRate); | ||
|  |         } | ||
|  | 
 | ||
|  |         public static bool OnFrameBoundary(double time, double frameRate, double epsilon) | ||
|  |         { | ||
|  |             ValidateFrameRate(frameRate); | ||
|  | 
 | ||
|  |             double exact = ToExactFrames(time, frameRate); | ||
|  |             double rounded = Math.Round(exact); | ||
|  | 
 | ||
|  |             return Math.Abs(exact - rounded) < epsilon; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static double RoundToFrame(double time, double frameRate) | ||
|  |         { | ||
|  |             ValidateFrameRate(frameRate); | ||
|  | 
 | ||
|  |             var frameBefore = (int)Math.Floor(time * frameRate) / frameRate; | ||
|  |             var frameAfter = (int)Math.Ceiling(time * frameRate) / frameRate; | ||
|  | 
 | ||
|  |             return Math.Abs(time - frameBefore) < Math.Abs(time - frameAfter) ? frameBefore : frameAfter; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static string TimeAsFrames(double timeValue, double frameRate, string format = "F2") | ||
|  |         { | ||
|  |             if (OnFrameBoundary(timeValue, frameRate)) // make integral values when on time borders | ||
|  |                 return ToFrames(timeValue, frameRate).ToString(); | ||
|  |             return ToExactFrames(timeValue, frameRate).ToString(format); | ||
|  |         } | ||
|  | 
 | ||
|  |         public static string TimeAsTimeCode(double timeValue, double frameRate, string format = "F2") | ||
|  |         { | ||
|  |             ValidateFrameRate(frameRate); | ||
|  | 
 | ||
|  |             int intTime = (int)Math.Abs(timeValue); | ||
|  | 
 | ||
|  |             int hours = intTime / 3600; | ||
|  |             int minutes = (intTime % 3600) / 60; | ||
|  |             int seconds = intTime % 60; | ||
|  | 
 | ||
|  |             string result; | ||
|  |             string sign = timeValue < 0 ? "-" : string.Empty; | ||
|  |             if (hours > 0) | ||
|  |                 result = hours + ":" + minutes.ToString("D2") + ":" + seconds.ToString("D2"); | ||
|  |             else if (minutes > 0) | ||
|  |                 result = minutes + ":" + seconds.ToString("D2"); | ||
|  |             else | ||
|  |                 result = seconds.ToString(); | ||
|  | 
 | ||
|  |             int frameDigits = (int)Math.Floor(Math.Log10(frameRate) + 1); | ||
|  | 
 | ||
|  |             // Add partial digits on the frame if needed. | ||
|  |             // we are testing the original value (not the truncated), because the truncation can cause rounding errors leading | ||
|  |             // to invalid strings for items on frame boundaries | ||
|  |             string frames = (ToFrames(timeValue, frameRate) - ToFrames(intTime, frameRate)).ToString().PadLeft(frameDigits, '0'); | ||
|  |             if (!OnFrameBoundary(timeValue, frameRate)) | ||
|  |             { | ||
|  |                 string decimals = ToExactFrames(timeValue, frameRate).ToString(format); | ||
|  |                 int decPlace = decimals.IndexOf('.'); | ||
|  |                 if (decPlace >= 0) | ||
|  |                     frames += " [" + decimals.Substring(decPlace) + "]"; | ||
|  |             } | ||
|  | 
 | ||
|  |             return sign + result + ":" + frames; | ||
|  |         } | ||
|  | 
 | ||
|  |         // Given a time code string, return the time in seconds | ||
|  |         // 1.5 -> 1.5 seconds | ||
|  |         // 1:1.5 -> 1 minute, 1.5 seconds | ||
|  |         // 1:1[.5] -> 1 second, 1.5 frames | ||
|  |         // 2:3:4 -> 2 minutes, 3 seconds, 4 frames | ||
|  |         // 1[.6] -> 1.6 frames | ||
|  |         public static double ParseTimeCode(string timeCode, double frameRate, double defaultValue) | ||
|  |         { | ||
|  |             timeCode = RemoveChar(timeCode, c => char.IsWhiteSpace(c)); | ||
|  |             string[] sections = timeCode.Split(':'); | ||
|  |             if (sections.Length == 0 || sections.Length > 4) | ||
|  |                 return defaultValue; | ||
|  | 
 | ||
|  |             int hours = 0; | ||
|  |             int minutes = 0; | ||
|  |             double seconds = 0; | ||
|  |             double frames = 0; | ||
|  | 
 | ||
|  |             try | ||
|  |             { | ||
|  |                 // depending on the format of the last numbers | ||
|  |                 // seconds format | ||
|  |                 string lastSection = sections[sections.Length - 1]; | ||
|  |                 if (Regex.Match(lastSection, @"^\d+\.\d+$").Success) | ||
|  |                 { | ||
|  |                     seconds = double.Parse(lastSection); | ||
|  |                     if (sections.Length > 3) return defaultValue; | ||
|  |                     if (sections.Length > 1) minutes = int.Parse(sections[sections.Length - 2]); | ||
|  |                     if (sections.Length > 2) hours = int.Parse(sections[sections.Length - 3]); | ||
|  |                 } | ||
|  |                 // frame formats | ||
|  |                 else | ||
|  |                 { | ||
|  |                     if (Regex.Match(lastSection, @"^\d+\[\.\d+\]$").Success) | ||
|  |                     { | ||
|  |                         string stripped = RemoveChar(lastSection, c => c == '[' || c == ']'); | ||
|  |                         frames = double.Parse(stripped); | ||
|  |                     } | ||
|  |                     else if (Regex.Match(lastSection, @"^\d*$").Success) | ||
|  |                     { | ||
|  |                         frames = int.Parse(lastSection); | ||
|  |                     } | ||
|  |                     else | ||
|  |                     { | ||
|  |                         return defaultValue; | ||
|  |                     } | ||
|  | 
 | ||
|  |                     if (sections.Length > 1) seconds = int.Parse(sections[sections.Length - 2]); | ||
|  |                     if (sections.Length > 2) minutes = int.Parse(sections[sections.Length - 3]); | ||
|  |                     if (sections.Length > 3) hours = int.Parse(sections[sections.Length - 4]); | ||
|  |                 } | ||
|  |             } | ||
|  |             catch (FormatException) | ||
|  |             { | ||
|  |                 return defaultValue; | ||
|  |             } | ||
|  | 
 | ||
|  |             return frames / frameRate + seconds + minutes * 60 + hours * 3600; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static double ParseTimeSeconds(string timeCode, double frameRate, double defaultValue) | ||
|  |         { | ||
|  |             timeCode = RemoveChar(timeCode, c => char.IsWhiteSpace(c)); | ||
|  |             string[] sections = timeCode.Split(':'); | ||
|  |             if (sections.Length == 0 || sections.Length > 4) | ||
|  |                 return defaultValue; | ||
|  | 
 | ||
|  |             int hours = 0; | ||
|  |             int minutes = 0; | ||
|  |             double seconds = 0; | ||
|  | 
 | ||
|  |             try | ||
|  |             { | ||
|  |                 // depending on the format of the last numbers | ||
|  |                 // seconds format | ||
|  |                 string lastSection = sections[sections.Length - 1]; | ||
|  |                 { | ||
|  |                     if (!double.TryParse(lastSection, NumberStyles.Integer, CultureInfo.InvariantCulture, out seconds)) | ||
|  |                         if (Regex.Match(lastSection, @"^\d+\.\d+$").Success) | ||
|  |                             seconds = double.Parse(lastSection); | ||
|  |                         else | ||
|  |                             return defaultValue; | ||
|  | 
 | ||
|  |                     if (!double.TryParse(lastSection, NumberStyles.Float, CultureInfo.InvariantCulture, out seconds)) | ||
|  |                         return defaultValue; | ||
|  | 
 | ||
|  |                     if (sections.Length > 3) return defaultValue; | ||
|  |                     if (sections.Length > 1) minutes = int.Parse(sections[sections.Length - 2]); | ||
|  |                     if (sections.Length > 2) hours = int.Parse(sections[sections.Length - 3]); | ||
|  |                 } | ||
|  |             } | ||
|  |             catch (FormatException) | ||
|  |             { | ||
|  |                 return defaultValue; | ||
|  |             } | ||
|  | 
 | ||
|  |             return seconds + minutes * 60 + hours * 3600; | ||
|  |         } | ||
|  | 
 | ||
|  |         // fixes rounding errors from using single precision for length | ||
|  |         public static double GetAnimationClipLength(AnimationClip clip) | ||
|  |         { | ||
|  |             if (clip == null || clip.empty) | ||
|  |                 return 0; | ||
|  | 
 | ||
|  |             double length = clip.length; | ||
|  |             if (clip.frameRate > 0) | ||
|  |             { | ||
|  |                 double frames = Mathf.Round(clip.length * clip.frameRate); | ||
|  |                 length = frames / clip.frameRate; | ||
|  |             } | ||
|  |             return length; | ||
|  |         } | ||
|  | 
 | ||
|  |         static string RemoveChar(string str, Func<char, bool> charToRemoveFunc) | ||
|  |         { | ||
|  |             var len = str.Length; | ||
|  |             var src = str.ToCharArray(); | ||
|  |             var dstIdx = 0; | ||
|  |             for (var i = 0; i < len; i++) | ||
|  |             { | ||
|  |                 if (!charToRemoveFunc(src[i])) | ||
|  |                     src[dstIdx++] = src[i]; | ||
|  |             } | ||
|  |             return new string(src, 0, dstIdx); | ||
|  |         } | ||
|  | 
 | ||
|  |         public static FrameRate GetClosestFrameRate(double frameRate) | ||
|  |         { | ||
|  |             ValidateFrameRate(frameRate); | ||
|  |             var actualFrameRate = FrameRate.DoubleToFrameRate(frameRate); | ||
|  |             return Math.Abs(frameRate - actualFrameRate.rate) < kFrameRateRounding ? actualFrameRate : new FrameRate(); | ||
|  |         } | ||
|  | 
 | ||
|  |         public static FrameRate ToFrameRate(StandardFrameRates enumValue) | ||
|  |         { | ||
|  |             switch (enumValue) | ||
|  |             { | ||
|  |                 case StandardFrameRates.Fps23_97: | ||
|  |                     return FrameRate.k_23_976Fps; | ||
|  |                 case StandardFrameRates.Fps24: | ||
|  |                     return FrameRate.k_24Fps; | ||
|  |                 case StandardFrameRates.Fps25: | ||
|  |                     return FrameRate.k_25Fps; | ||
|  |                 case StandardFrameRates.Fps29_97: | ||
|  |                     return FrameRate.k_29_97Fps; | ||
|  |                 case StandardFrameRates.Fps30: | ||
|  |                     return FrameRate.k_30Fps; | ||
|  |                 case StandardFrameRates.Fps50: | ||
|  |                     return FrameRate.k_50Fps; | ||
|  |                 case StandardFrameRates.Fps59_94: | ||
|  |                     return FrameRate.k_59_94Fps; | ||
|  |                 case StandardFrameRates.Fps60: | ||
|  |                     return FrameRate.k_60Fps; | ||
|  |                 default: | ||
|  |                     return new FrameRate(); | ||
|  |             } | ||
|  |             ; | ||
|  |         } | ||
|  | 
 | ||
|  |         internal static bool ToStandardFrameRate(FrameRate rate, out StandardFrameRates standard) | ||
|  |         { | ||
|  |             if (rate == FrameRate.k_23_976Fps) | ||
|  |                 standard = StandardFrameRates.Fps23_97; | ||
|  |             else if (rate == FrameRate.k_24Fps) | ||
|  |                 standard = StandardFrameRates.Fps24; | ||
|  |             else if (rate == FrameRate.k_25Fps) | ||
|  |                 standard = StandardFrameRates.Fps25; | ||
|  |             else if (rate == FrameRate.k_30Fps) | ||
|  |                 standard = StandardFrameRates.Fps30; | ||
|  |             else if (rate == FrameRate.k_29_97Fps) | ||
|  |                 standard = StandardFrameRates.Fps29_97; | ||
|  |             else if (rate == FrameRate.k_50Fps) | ||
|  |                 standard = StandardFrameRates.Fps50; | ||
|  |             else if (rate == FrameRate.k_59_94Fps) | ||
|  |                 standard = StandardFrameRates.Fps59_94; | ||
|  |             else if (rate == FrameRate.k_60Fps) | ||
|  |                 standard = StandardFrameRates.Fps60; | ||
|  |             else | ||
|  |             { | ||
|  |                 standard = (StandardFrameRates)Enum.GetValues(typeof(StandardFrameRates)).Length; | ||
|  |                 return false; | ||
|  |             } | ||
|  |             return true; | ||
|  |         } | ||
|  |     } | ||
|  | } |