Ark Server API (ASA) - Wiki
Loading...
Searching...
No Matches
Timecode.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4
5#include "Containers/UnrealString.h"
6#include "GenericPlatform/GenericPlatformMath.h"
7#include "HAL/Platform.h"
8#include "Math/UnrealMathUtility.h"
9#include "Misc/FrameNumber.h"
10#include "Misc/FrameRate.h"
11#include "Misc/Timespan.h"
12
13/**
14 * A timecode that stores time in HH:MM:SS format with the remainder
15 * of time represented by an integer frame count.
16 */
18{
19 /**
20 * Default construction for UObject purposes
21 */
23 : Hours(0)
24 , Minutes(0)
25 , Seconds(0)
26 , Frames(0)
27 , bDropFrameFormat(false)
28 {}
29
30 /**
31 * User construction from a number of hours minutes seconds and frames.
32 * @param InbDropFrame - If true, this Timecode represents a "Drop Frame Timecode" format which
33 skips the first frames of every minute (except those ending in multiples of 10)
34 to account for drift when using a fractional NTSC framerate.
35 */
36 explicit FTimecode(int32 InHours, int32 InMinutes, int32 InSeconds, int32 InFrames, bool InbDropFrame)
37 : Hours(InHours)
38 , Minutes(InMinutes)
39 , Seconds(InSeconds)
40 , Frames(InFrames)
41 , bDropFrameFormat(InbDropFrame)
42 {}
43
44 /**
45 * User construction from a time in seconds
46 * @param InbDropFrame - If true, this Timecode represents a "Drop Frame Timecode" format which
47 skips the first frames of every minute (except those ending in multiples of 10)
48 to account for drift when using a fractional NTSC framerate.
49 * @param InbRollover - If true, the hours will be the modulo of 24.
50 * @note Be aware that the Cycles may not correspond to the System Time. See FDateTime and "leap seconds".
51 */
52 explicit FTimecode(double InSeconds, const FFrameRate& InFrameRate, bool InbDropFrame, bool InbRollover)
53 {
54 if (InbRollover)
55 {
56 const int32 NumberOfSecondsPerDay = 60 * 60 * 24;
57 double IntegralPart = 0.0;
58 double Fractional = FMath::Modf(InSeconds, &IntegralPart);
59 const int32 CurrentRolloverSeconds = (int32)IntegralPart % NumberOfSecondsPerDay;
60 InSeconds = (double)CurrentRolloverSeconds + Fractional;
61 }
62
63 const int32 NumberOfFrames = (int32)FMath::RoundToDouble(InSeconds * InFrameRate.AsDecimal());
64 *this = FromFrameNumber(FFrameNumber(NumberOfFrames), InFrameRate, InbDropFrame);
65 }
66
67 /**
68 * User construction from a time in seconds
69 * @param InbRollover - If true, the hours will be the modulo of 24.
70 * @note Be aware that the Cycles may not correspond to the System Time. See FDateTime and "leap seconds".
71 */
72 explicit FTimecode(double InSeconds, const FFrameRate& InFrameRate, bool InbRollover)
73 : FTimecode(InSeconds, InFrameRate, UseDropFormatTimecode(InFrameRate), InbRollover)
74 {
75 }
76
77
78
79 friend bool operator==(const FFrameRate& A, const FFrameRate& B);
80 friend bool operator!=(const FFrameRate& A, const FFrameRate& B);
81
82public:
83
84 /**
85 * Converts this Timecode back into a Frame Number at the given framerate, taking into account if this is a drop-frame format timecode.
86 */
87 FFrameNumber ToFrameNumber(const FFrameRate& InFrameRate) const
88 {
89 const int32 NumberOfFramesInSecond = FMath::CeilToInt((float)InFrameRate.AsDecimal());
90 const int32 NumberOfFramesInMinute = NumberOfFramesInSecond * 60;
91 const int32 NumberOfFramesInHour = NumberOfFramesInMinute * 60;
92
93 if (NumberOfFramesInSecond <= 0)
94 {
95 return FFrameNumber();
96 }
97
98 // Do a quick pre-pass to take any overflow values and move them into bigger time units.
99 int32 SafeSeconds = Seconds + Frames / NumberOfFramesInSecond;
100 int32 SafeFrames = Frames % NumberOfFramesInSecond;
101
102 int32 SafeMinutes = Minutes + SafeSeconds / 60;
103 SafeSeconds = SafeSeconds % 60;
104
105 int32 SafeHours = Hours + SafeMinutes / 60;
106 SafeMinutes = SafeMinutes % 60;
107
109 {
110 const int32 NumberOfTimecodesToDrop = NumberOfFramesInSecond <= 30 ? 2 : 4;
111
112 // Calculate how many minutes there are total so we can know how many times we've skipped timecodes.
113 int32 TotalMinutes = (SafeHours * 60) + SafeMinutes;
114
115 // We skip timecodes 9 times out of every 10.
116 int32 TotalDroppedFrames = NumberOfTimecodesToDrop * (TotalMinutes - (int32)FMath::RoundToZero(TotalMinutes / 10.0));
117 int32 TotalFrames = (SafeHours * NumberOfFramesInHour) + (SafeMinutes * NumberOfFramesInMinute) + (SafeSeconds * NumberOfFramesInSecond)
118 + SafeFrames - TotalDroppedFrames;
119
120 return FFrameNumber(TotalFrames);
121 }
122 else
123 {
124 int32 TotalFrames = (SafeHours *NumberOfFramesInHour) + (SafeMinutes * NumberOfFramesInMinute) + (SafeSeconds * NumberOfFramesInSecond) + SafeFrames;
125 return FFrameNumber(TotalFrames);
126 }
127 }
128
129 /**
130 * Create a FTimecode from a specific frame number at the given frame rate. Optionally supports creating a drop frame timecode,
131 * which drops certain timecode display numbers to help account for NTSC frame rates which are fractional.
132 *
133 * @param InFrameNumber - The frame number to convert into a timecode. This should already be converted to InFrameRate's resolution.
134 * @param InFrameRate - The framerate that this timecode is based in. This should be the playback framerate as it is used to determine
135 * when the Frame value wraps over.
136 * @param InbDropFrame - If true, the returned timecode will drop the first two frames on every minute (except when Minute % 10 == 0)
137 * This is only valid for NTSC framerates (29.97, 59.94) and will assert if you try to create a drop-frame format
138 * from a non-valid framerate. All framerates can be represented when in non-drop frame format.
139 */
140 static FTimecode FromFrameNumber(const FFrameNumber& InFrameNumber, const FFrameRate& InFrameRate, bool InbDropFrame)
141 {
142 const int32 NumberOfFramesInSecond = FMath::CeilToInt((float)InFrameRate.AsDecimal());
143 const int32 NumberOfFramesInMinute = NumberOfFramesInSecond * 60;
144 const int32 NumberOfFramesInHour = NumberOfFramesInMinute * 60;
145
146 if (NumberOfFramesInSecond <= 0)
147 {
148 return FTimecode();
149 }
150
151 if (InbDropFrame)
152 {
153 // Drop Frame Timecode (DFT) was created to address the issue with playing back whole frames at fractional framerates.
154 // DFT is confusingly named though, as no frame numbers are actually dropped, only the display of them. At an ideal 30fps,
155 // there are 108,000 frames in an hour. When played back at 29.97 however, there are only 107,892 frames per hour. This
156 // leaves us a difference of 108 frames per hour (roughly ~3.6s!). DFT works by accumulating error until the error is significant
157 // enough to catch up by a frame. This is accomplished by dropping two (or four) timecode numbers every minute which gives us a
158 // total difference of 2*60 = 120 frames per hour. Unfortunately 120 puts us out of sync again as the difference is only 108 frames,
159 // so we need to get 12 frames back. By not dropping frames every 10th minute, that gives us 2 frames * 6 (00, 10, 20, 30, 40, 50)
160 // which gets us the 12 frame difference we need. In short, we drop frames (skip timecode numbers) every minute, on the minute, except
161 // when (Minute % 10 == 0)
162
163 // 29.97 drops two timecode values (frames 0 and 1) while 59.94 drops four values (frames 0, 1, 2 and 3)
164 const int32 NumberOfTimecodesToDrop = NumberOfFramesInSecond <= 30 ? 2 : 4;
165
166 // At an ideal 30fps there would be 18,000 frames every 10 minutes, but at 29.97 there's only 17,982 frames.
167 const int32 NumTrueFramesPerTenMinutes = FMath::FloorToInt((float)((60 * 10) * InFrameRate.AsDecimal()));
168
169 // Calculate out how many times we've skipped dropping frames (ie: Minute 15 gives us a value of 1, as we've only didn't drop frames on the 10th minute)
170 const int32 NumTimesSkippedDroppingFrames = FMath::Abs(InFrameNumber.Value) / NumTrueFramesPerTenMinutes;
171
172 // Now we can figure out how many frame (displays) have been skipped total; 9 times out of every 10 minutes
173 const int32 NumFramesSkippedTotal = NumTimesSkippedDroppingFrames * 9 * NumberOfTimecodesToDrop;
174
175 int32 OffsetFrame = FMath::Abs(InFrameNumber.Value);
176 int FrameInTrueFrames = OffsetFrame % NumTrueFramesPerTenMinutes;
177
178 // If we end up with a value of 0 or 1 (or 2 or 3 for 59.94) then we're not skipping timecode numbers this time.
179 if (FrameInTrueFrames < NumberOfTimecodesToDrop)
180 {
181 OffsetFrame += NumFramesSkippedTotal;
182 }
183 else
184 {
185 // Each minute we slip a little bit more out of sync by a small amount, we just wait until we've accumulated enough error
186 // to skip a whole frame and can catch up.
187 const uint32 NumTrueFramesPerMinute = (uint32)FMath::FloorToInt(60 * (float)InFrameRate.AsDecimal());
188
189 // Figure out which minute we are (0-9) to see how many to skip
190 int32 CurrentMinuteOfTen = (FrameInTrueFrames - NumberOfTimecodesToDrop) / NumTrueFramesPerMinute;
191 int NumAddedFrames = NumFramesSkippedTotal + (NumberOfTimecodesToDrop * CurrentMinuteOfTen);
192 OffsetFrame += NumAddedFrames;
193 }
194
195 // Convert to negative timecode at the end if the original was negative.
196 OffsetFrame *= FMath::Sign(InFrameNumber.Value);
197
198 // Now that we've fudged what frames it thinks we're converting, we can do a standard Frame -> Timecode conversion.
199 int32 Hours = (int32)FMath::RoundToZero(OffsetFrame / (double)NumberOfFramesInHour);
200 int32 Minutes = (int32)FMath::RoundToZero(OffsetFrame / (double)NumberOfFramesInMinute) % 60;
201 int32 Seconds = (int32)FMath::RoundToZero(OffsetFrame / (double)NumberOfFramesInSecond) % 60;
202 int32 Frames = OffsetFrame % NumberOfFramesInSecond;
203
204 return FTimecode(Hours, Minutes, Seconds, Frames, true);
205 }
206 else
207 {
208 // If we're in non-drop-frame we just convert straight through without fudging the frame numbers to skip certain timecodes.
209 int32 Hours = (int32)FMath::RoundToZero(InFrameNumber.Value / (double)NumberOfFramesInHour);
210 int32 Minutes = (int32)FMath::RoundToZero(InFrameNumber.Value / (double)NumberOfFramesInMinute) % 60;
211 int32 Seconds = (int32)FMath::RoundToZero(InFrameNumber.Value / (double)NumberOfFramesInSecond) % 60;
212 int32 Frames = InFrameNumber.Value % NumberOfFramesInSecond;
213
214 return FTimecode(Hours, Minutes, Seconds, Frames, false);
215 }
216 }
217
218 /**
219 * Create a FTimecode from a specific frame number at the given frame rate.
220 *
221 * @param InFrameNumber - The frame number to convert into a timecode. This should already be converted to InFrameRate's resolution.
222 * @param InFrameRate - The framerate that this timecode is based in. This should be the playback framerate as it is used to determine
223 * when the Frame value wraps over.
224 */
225 static FTimecode FromFrameNumber(const FFrameNumber& InFrameNumber, const FFrameRate& InFrameRate)
226 {
227 return FromFrameNumber(InFrameNumber, InFrameRate, UseDropFormatTimecode(InFrameRate));
228 }
229
230 /**
231 * Converts this Timecode back into a timespan at the given framerate, taking into account if this is a drop-frame format timecode.
232 */
233 FTimespan ToTimespan(const FFrameRate& InFrameRate) const
234 {
235 const FFrameNumber ConvertedFrameNumber = ToFrameNumber(InFrameRate);
236 const double NumberOfSeconds = ConvertedFrameNumber.Value * InFrameRate.AsInterval();
237 return FTimespan::FromSeconds(NumberOfSeconds);
238 }
239
240 /**
241 * Create a FTimecode from a timespan at the given frame rate. Optionally supports creating a drop frame timecode,
242 * which drops certain timecode display numbers to help account for NTSC frame rates which are fractional.
243 *
244 * @param InTimespan - The timespan to convert into a timecode.
245 * @param InFrameRate - The framerate that this timecode is based in. This should be the playback framerate as it is used to determine
246 * when the Frame value wraps over.
247 * @param InbDropFrame - If true, the returned timecode will drop the first two frames on every minute (except when Minute % 10 == 0)
248 * This is only valid for NTSC framerates (29.97, 59.94) and will assert if you try to create a drop-frame format
249 * from a non-valid framerate. All framerates can be represented when in non-drop frame format.
250 * @param InbRollover - If true, the hours will be the modulo of 24.
251 */
252 static FTimecode FromTimespan(const FTimespan& InTimespan, const FFrameRate& InFrameRate, bool InbDropFrame, bool InbRollover)
253 {
254 return FTimecode(InTimespan.GetTotalSeconds(), InFrameRate, InbDropFrame, InbRollover);
255 }
256
257 /**
258 * Create a FTimecode from a timespan at the given frame rate.
259 *
260 * @param InTimespan - The timespan to convert into a timecode.
261 * @param InFrameRate - The framerate that this timecode is based in. This should be the playback framerate as it is used to determine
262 * when the Frame value wraps over.
263 * @param InbRollover - If true, the hours will be the modulo of 24.
264 */
265 static FTimecode FromTimespan(const FTimespan& InTimespan, const FFrameRate& InFrameRate, bool InbRollover)
266 {
267 return FTimecode(InTimespan.GetTotalSeconds(), InFrameRate, UseDropFormatTimecode(InFrameRate), InbRollover);
268 }
269
270 /** Drop frame is only support for frame rate of 29.97 or 59.94. */
271 static bool IsDropFormatTimecodeSupported(const FFrameRate& InFrameRate)
272 {
273 const double InRate = InFrameRate.AsDecimal();
274
275 return FMath::IsNearlyEqual(InRate, 30.0/1.001)
276 || FMath::IsNearlyEqual(InRate, 60.0/1.001);
277 }
278
279 /** If the frame rate support drop frame format and the app wish to use drop frame format by default. */
280 static bool UseDropFormatTimecode(const FFrameRate& InFrameRate)
281 {
283 }
284
285 /** By default, should we generate a timecode in drop frame format when the frame rate does support it. */
287
288 /**
289 * Returns true if the given frame rate string represents a supported drop frame timecode frame rate, or false otherwise.
290 *
291 * Drop frame timecode is only supported for NTSC_30 (29.97 FPS) or NTSC_60 (59.94 FPS) frame rates.
292 */
293 static bool IsValidDropFormatTimecodeRate(const FString& InRateString)
294 {
295 const FString NTSC_30_DF = "29.97df";
296 const FString NTSC_60_DF = "59.94df";
297
298 return (InRateString == NTSC_30_DF || InRateString == NTSC_60_DF);
299 }
300
301 /**
302 * Get the Qualified Timecode formatted in HH:MM:SS:FF or HH:MM:SS;FF depending on if this represents drop-frame timecode or not.
303 * @param bForceSignDisplay - Forces the timecode to be prepended with a positive or negative sign.
304 Standard behavior is to only show the sign when the value is negative.
305 */
306 FString ToString(bool bForceSignDisplay = false) const
307 {
308 bool bHasNegativeComponent = Hours < 0 || Minutes < 0 || Seconds < 0 || Frames < 0;
309
310 const TCHAR* NegativeSign = TEXT("- ");
311 const TCHAR* PositiveSign = TEXT("+ ");
312 const TCHAR* SignText = TEXT("");
313 if (bHasNegativeComponent)
314 {
315 SignText = NegativeSign;
316 }
317 else if (bForceSignDisplay)
318 {
319 SignText = PositiveSign;
320 }
321
323 {
324 return FString::Printf(TEXT("%s%02d:%02d:%02d;%02d"), SignText, FMath::Abs(Hours), FMath::Abs(Minutes), FMath::Abs(Seconds), FMath::Abs(Frames));
325 }
326 else
327 {
328 return FString::Printf(TEXT("%s%02d:%02d:%02d:%02d"), SignText, FMath::Abs(Hours), FMath::Abs(Minutes), FMath::Abs(Seconds), FMath::Abs(Frames));
329 }
330 }
331
332public:
333
334 /** How many hours does this timecode represent */
335 int32 Hours;
336
337 /** How many minutes does this timecode represent */
338 int32 Minutes;
339
340 /** How many seconds does this timecode represent */
341 int32 Seconds;
342
343 /** How many frames does this timecode represent */
344 int32 Frames;
345
346 /** If true, this Timecode represents a Drop Frame timecode used to account for fractional frame rates in NTSC play rates. */
348
349
350 friend inline bool operator==(const FTimecode& A, const FTimecode& B)
351 {
352 return A.Hours == B.Hours && A.Minutes == B.Minutes && A.Seconds == B.Seconds && A.Frames == B.Frames;
353 }
354
355 friend inline bool operator!=(const FTimecode& A, const FTimecode& B)
356 {
357 return !(A == B);
358 }
359
360};
#define TEXT(x)
Definition Platform.h:1108
UE_NODISCARD FORCEINLINE bool operator==(const FString &Rhs) const
constexpr FFrameNumber()
Definition FrameNumber.h:19
double AsDecimal() const
Definition FrameRate.h:221
friend bool operator!=(const FFrameRate &A, const FFrameRate &B)
Definition FrameRate.h:156
friend bool operator==(const FFrameRate &A, const FFrameRate &B)
Definition FrameRate.h:151
double AsInterval() const
Definition FrameRate.h:216
static UE_NODISCARD FORCEINLINE double RoundToZero(double F)
static UE_NODISCARD FORCEINLINE bool IsNearlyEqual(double A, double B, double ErrorTolerance=UE_DOUBLE_SMALL_NUMBER)
static FTimecode FromTimespan(const FTimespan &InTimespan, const FFrameRate &InFrameRate, bool InbRollover)
Definition Timecode.h:265
FTimecode(double InSeconds, const FFrameRate &InFrameRate, bool InbRollover)
Definition Timecode.h:72
static FTimecode FromFrameNumber(const FFrameNumber &InFrameNumber, const FFrameRate &InFrameRate)
Definition Timecode.h:225
static bool IsDropFormatTimecodeSupported(const FFrameRate &InFrameRate)
Definition Timecode.h:271
FString ToString(bool bForceSignDisplay=false) const
Definition Timecode.h:306
FFrameNumber ToFrameNumber(const FFrameRate &InFrameRate) const
Definition Timecode.h:87
static bool UseDropFormatTimecode(const FFrameRate &InFrameRate)
Definition Timecode.h:280
friend bool operator!=(const FTimecode &A, const FTimecode &B)
Definition Timecode.h:355
friend bool operator==(const FTimecode &A, const FTimecode &B)
Definition Timecode.h:350
int32 Frames
Definition Timecode.h:344
static bool UseDropFormatTimecodeByDefaultWhenSupported()
FTimecode()
Definition Timecode.h:22
FTimecode(int32 InHours, int32 InMinutes, int32 InSeconds, int32 InFrames, bool InbDropFrame)
Definition Timecode.h:36
static FTimecode FromTimespan(const FTimespan &InTimespan, const FFrameRate &InFrameRate, bool InbDropFrame, bool InbRollover)
Definition Timecode.h:252
static FTimecode FromFrameNumber(const FFrameNumber &InFrameNumber, const FFrameRate &InFrameRate, bool InbDropFrame)
Definition Timecode.h:140
int32 Minutes
Definition Timecode.h:338
FTimecode(double InSeconds, const FFrameRate &InFrameRate, bool InbDropFrame, bool InbRollover)
Definition Timecode.h:52
int32 Hours
Definition Timecode.h:335
FTimespan ToTimespan(const FFrameRate &InFrameRate) const
Definition Timecode.h:233
bool bDropFrameFormat
Definition Timecode.h:347
static bool IsValidDropFormatTimecodeRate(const FString &InRateString)
Definition Timecode.h:293
int32 Seconds
Definition Timecode.h:341
double GetTotalSeconds() const
Definition Timespan.h:520
static FTimespan FromSeconds(double Seconds)
Definition Timespan.h:673