libpromeki 1.0.0-alpha
PROfessional MEdia toolKIt
 
Loading...
Searching...
No Matches
timecode.h
Go to the documentation of this file.
1
8#pragma once
9
10
11#include <promeki/config.h>
12#if PROMEKI_ENABLE_CORE
13#include <cstdint>
14#include <promeki/namespace.h>
15#include <promeki/string.h>
16#include <promeki/error.h>
17#include <promeki/result.h>
18#include <promeki/enums.h>
19#include <promeki/framenumber.h>
20#include <promeki/array.h>
21#include <promeki/datatype.h>
23#include <vtc/vtc.h>
24
25PROMEKI_NAMESPACE_BEGIN
26
27class DataStream;
28class FrameRate;
29class Duration;
30
58class Timecode {
59 public:
60 PROMEKI_DATATYPE(Timecode, DataTypeTimecode, 2)
61
62 using DigitType = uint8_t;
63 using FlagsType = uint32_t;
64
66 Error writeToStream(DataStream &s) const;
68 template <uint32_t V> static Result<Timecode> readFromStream(DataStream &s);
69
83 enum TimecodeType {
84 NDF24,
85 NDF25,
86 NDF30,
87 DF30,
88 NDF48,
89 NDF50,
90 NDF60,
91 DF60,
92 NDF72,
93 NDF96,
94 NDF100,
95 NDF120,
96 DF120,
97 NDF120_24x5,
98 };
99
101 enum Flags {
102 DropFrame = 0x00000001,
103 FirstField = 0x00000002
104 };
105
111 class Mode {
112 public:
114 Mode() = default;
115
118 Mode(const VtcFormat *format) : _format(format), _valid(format != nullptr) {}
119
139 Mode(uint32_t fps, uint32_t flags) : _valid(true) {
140 if (fps == 0) return;
141 const bool wantDf = (flags & DropFrame) != 0;
142 // Prefer integer-rate formats over their NTSC
143 // (1000/1001) siblings. Without an explicit
144 // wall-clock-rate hint a caller asking for "30 fps
145 // NDF" wants exact 30, not 29.97.
146 for (int i = 0; i < VTC_STANDARD_FORMATS_COUNT; ++i) {
147 const VtcFormat *f = VTC_STANDARD_FORMATS[i];
148 if (vtc_format_fps(f) != fps) continue;
149 if (vtc_format_is_drop_frame(f) != wantDf) continue;
150 if (vtc_format_is_ntsc(f)) continue;
151 _format = f;
152 return;
153 }
154 // Second pass: accept NTSC variants if no integer
155 // match exists at this rate (the only DF HFR rates
156 // 59.94 DF and 119.88 DF are NTSC-only).
157 for (int i = 0; i < VTC_STANDARD_FORMATS_COUNT; ++i) {
158 const VtcFormat *f = VTC_STANDARD_FORMATS[i];
159 if (vtc_format_fps(f) != fps) continue;
160 if (vtc_format_is_drop_frame(f) != wantDf) continue;
161 _format = f;
162 return;
163 }
164 uint32_t vtcFlags = 0;
165 if (wantDf) vtcFlags |= VTC_FORMAT_FLAG_DROP_FRAME;
166 _format = vtc_format_find_or_create(fps, 1, fps, vtcFlags);
167 }
168
171 Mode(TimecodeType type) : _valid(true) {
172 switch (type) {
173 case NDF24: _format = &VTC_FORMAT_24; break;
174 case NDF25: _format = &VTC_FORMAT_25; break;
175 case NDF30: _format = &VTC_FORMAT_30_NDF; break;
176 case DF30: _format = &VTC_FORMAT_29_97_DF; break;
177 case NDF48: _format = &VTC_FORMAT_48; break;
178 case NDF50: _format = &VTC_FORMAT_50; break;
179 case NDF60: _format = &VTC_FORMAT_60; break;
180 case DF60: _format = &VTC_FORMAT_59_94_DF; break;
181 case NDF72: _format = &VTC_FORMAT_72; break;
182 case NDF96: _format = &VTC_FORMAT_96; break;
183 case NDF100: _format = &VTC_FORMAT_100; break;
184 case NDF120: _format = &VTC_FORMAT_120_30X4; break;
185 case DF120: _format = &VTC_FORMAT_119_88_DF; break;
186 case NDF120_24x5: _format = &VTC_FORMAT_120_24X5; break;
187 }
188 }
189
190 bool operator==(const Mode &other) const {
191 if (_format == other._format) return _valid == other._valid;
192 if (_format == nullptr || other._format == nullptr) {
193 // Both valid with no format = equal
194 return _valid == other._valid && _format == other._format;
195 }
196 return _format->tc_fps == other._format->tc_fps &&
197 vtc_format_is_drop_frame(_format) ==
198 vtc_format_is_drop_frame(other._format);
199 }
200
201 bool operator!=(const Mode &other) const { return !(*this == other); }
202
204 uint32_t fps() const { return _format ? vtc_format_fps(_format) : 0; }
206 bool isValid() const { return _valid; }
208 bool isDropFrame() const { return _format ? vtc_format_is_drop_frame(_format) : false; }
210 bool hasFormat() const { return _format != nullptr; }
211
222 uint32_t framesPerSuperFrame() const {
223 return _format ? vtc_format_hfr_n(_format) + 1u : 1u;
224 }
225
237 uint32_t superFrameRate() const {
238 return _format ? _format->tc_fps : 0u;
239 }
240
242 const VtcFormat *vtcFormat() const { return _format; }
243
244 private:
245 const VtcFormat *_format = nullptr;
246 bool _valid = false;
247 };
248
260 static Timecode fromFrameNumber(const Mode &mode, const FrameNumber &frameNumber);
261
283 static Result<Timecode> fromString(const String &str);
284
286 Timecode() = default;
289 Timecode(const Mode &md) : _mode(md) {}
295 Timecode(DigitType h, DigitType m, DigitType s, DigitType f)
296 : _mode(Mode(0u, 0u)), _hour(h), _min(m), _sec(s), _frame(f) {}
303 Timecode(const Mode &md, DigitType h, DigitType m, DigitType s, DigitType f)
304 : _mode(md), _hour(h), _min(m), _sec(s), _frame(f) {}
307 Timecode(const String &str) {
308 auto [tc, err] = fromString(str);
309 if (err.isOk()) *this = tc;
310 }
311
312 bool operator==(const Timecode &other) const {
313 return _mode == other._mode && _hour == other._hour && _min == other._min &&
314 _sec == other._sec && _frame == other._frame &&
315 _colorFrame == other._colorFrame && _userbits == other._userbits;
316 }
317
318 bool operator!=(const Timecode &other) const { return !(*this == other); }
319
337 bool operator>(const Timecode &other) const {
338 if (compareByDigits(other)) return digitTuple() > other.digitTuple();
339 return toFrameNumber() > other.toFrameNumber();
340 }
341
342 bool operator<(const Timecode &other) const {
343 if (compareByDigits(other)) return digitTuple() < other.digitTuple();
344 return toFrameNumber() < other.toFrameNumber();
345 }
346
347 bool operator>=(const Timecode &other) const {
348 if (compareByDigits(other)) return digitTuple() >= other.digitTuple();
349 return toFrameNumber() >= other.toFrameNumber();
350 }
351
352 bool operator<=(const Timecode &other) const {
353 if (compareByDigits(other)) return digitTuple() <= other.digitTuple();
354 return toFrameNumber() <= other.toFrameNumber();
355 }
356
358 bool isValid() const { return _mode.isValid(); }
360 bool isDropFrame() const { return _mode.isDropFrame(); }
362 bool isFirstField() const { return _flags & FirstField; }
364 uint32_t fps() const { return _mode.fps(); }
366 Mode mode() const { return _mode; }
371 void setMode(const Mode &md) {
372 _mode = md;
373 return;
374 }
375
377 Timecode &operator++();
379 Timecode operator++(int) {
380 Timecode ret = *this;
381 ++(*this);
382 return ret;
383 }
385 Timecode &operator--();
387 Timecode operator--(int) {
388 Timecode ret = *this;
389 --(*this);
390 return ret;
391 }
392
398 void set(DigitType h, DigitType m, DigitType s, DigitType f) {
399 _hour = h;
400 _min = m;
401 _sec = s;
402 _frame = f;
403 }
404
406 DigitType hour() const { return _hour; }
408 DigitType min() const { return _min; }
410 DigitType sec() const { return _sec; }
412 DigitType frame() const { return _frame; }
413
422 bool colorFrame() const { return _colorFrame; }
424 void setColorFrame(bool on) { _colorFrame = on; }
425
427 const TimecodeUserbits &userbits() const { return _userbits; }
429 void setUserbits(const TimecodeUserbits &ub) { _userbits = ub; }
430
441 uint32_t superFrameIndex() const {
442 const uint32_t n = _mode.framesPerSuperFrame();
443 return n > 0u ? static_cast<uint32_t>(_frame) / n : static_cast<uint32_t>(_frame);
444 }
445
456 uint32_t subFrameIndex() const {
457 const uint32_t n = _mode.framesPerSuperFrame();
458 return n > 0u ? static_cast<uint32_t>(_frame) % n : 0u;
459 }
460
463 bool isHfr() const { return _mode.framesPerSuperFrame() > 1u; }
464
469 bool isSuperFrameBoundary() const { return subFrameIndex() == 0u; }
470
472 const VtcFormat *vtcFormat() const { return _mode.vtcFormat(); }
473
475 operator String() const { return toString(); }
490 String toString() const { return toFormatString().first(); }
491
518 Result<String> toFormatString(const VtcStringFormat *fmt = &VTC_STR_FMT_SMPTE) const;
528 FrameNumber toFrameNumber() const;
529
549 Result<Duration> toRuntime(const FrameRate &rate) const;
550
591 uint64_t toBcd64(TimecodePackFormat fmt = TimecodePackFormat::Vitc) const;
592
633 static Result<Timecode> fromBcd64(uint64_t bcd, TimecodePackFormat fmt, const Mode &mode);
634
636 static Result<Timecode> fromBcd64(uint64_t bcd) {
637 return fromBcd64(bcd, TimecodePackFormat::Vitc, Mode());
638 }
639
641 static Result<Timecode> fromBcd64(uint64_t bcd, const Mode &mode) {
642 return fromBcd64(bcd, TimecodePackFormat::Vitc, mode);
643 }
644
645 private:
646 VtcTimecode toVtc() const;
647 void fromVtc(const VtcTimecode &vtc);
648
649 // Tuple of the four digit fields for lexicographic
650 // ordering. Used by the comparison operators whenever a
651 // frame-number conversion would be undefined or
652 // unnecessary (same mode, or either side lacks a rate).
653 using DigitTuple = Array<DigitType, 4>;
654 DigitTuple digitTuple() const { return DigitTuple(_hour, _min, _sec, _frame); }
655
656 // Returns true when @ref operator< / @c > / @c <= / @c >=
657 // should use digit ordering rather than converting to
658 // absolute frame numbers. See the operator block above
659 // for the full rules.
660 bool compareByDigits(const Timecode &other) const {
661 if (_mode == other._mode) return true;
662 return !_mode.hasFormat() || !other._mode.hasFormat();
663 }
664
665 Mode _mode;
666 FlagsType _flags = 0;
667 DigitType _hour = 0;
668 DigitType _min = 0;
669 DigitType _sec = 0;
670 DigitType _frame = 0;
671 bool _colorFrame = false;
672 TimecodeUserbits _userbits;
673};
674
675
676PROMEKI_NAMESPACE_END
677
705template <> struct std::formatter<promeki::Timecode> {
706 enum class Style {
707 Smpte,
708 SmpteWithFps,
709 SmpteSpaceFps,
710 Field
711 };
712
713 Style _style = Style::Smpte;
714 std::formatter<std::string_view> _base;
715
716 constexpr auto parse(std::format_parse_context &ctx) {
717 auto it = ctx.begin();
718 auto end = ctx.end();
719
720 // Try to match a recognised style keyword at the start of
721 // the spec. Each candidate is checked in longest-first
722 // order so prefixes do not collide ("smpte-fps" must beat
723 // "smpte"). On match, advance past the keyword.
724 auto tryKeyword = [&](const char *kw, Style s) {
725 auto p = it;
726 while (*kw && p != end && *p == *kw) {
727 ++p;
728 ++kw;
729 }
730 if (*kw == 0 && (p == end || *p == '}' || *p == ':')) {
731 it = p;
732 _style = s;
733 return true;
734 }
735 return false;
736 };
737
738 if (!tryKeyword("smpte-fps", Style::SmpteWithFps) &&
739 !tryKeyword("smpte-space", Style::SmpteSpaceFps) && !tryKeyword("smpte", Style::Smpte) &&
740 !tryKeyword("field", Style::Field)) {
741 // No keyword — leave _style at its default and let
742 // the base parser consume the entire remaining spec.
743 }
744
745 // A separating ':' between the hint and a standard string
746 // format spec is consumed here so the base parser sees a
747 // bare ">16" rather than ":>16".
748 if (it != end && *it == ':') ++it;
749
750 // Forward whatever remains to the standard string format
751 // parser so width / fill / alignment / precision still work.
752 ctx.advance_to(it);
753 return _base.parse(ctx);
754 }
755
756 template <typename FormatContext> auto format(const promeki::Timecode &tc, FormatContext &ctx) const {
757 const VtcStringFormat *fmt = &VTC_STR_FMT_SMPTE;
758 switch (_style) {
759 case Style::Smpte: fmt = &VTC_STR_FMT_SMPTE; break;
760 case Style::SmpteWithFps: fmt = &VTC_STR_FMT_SMPTE_WITH_FPS; break;
761 case Style::SmpteSpaceFps: fmt = &VTC_STR_FMT_SMPTE_SPACE_FPS; break;
762 case Style::Field: fmt = &VTC_STR_FMT_FIELD; break;
763 }
764 auto [s, err] = tc.toFormatString(fmt);
765 (void)err; // Formatter consumers do not get the error path.
766 return _base.format(std::string_view(s.cstr(), s.byteCount()), ctx);
767 }
768};
769
770#endif // PROMEKI_ENABLE_CORE