From 2fe03debecc63b411a23a2411b57701d9a1361a3 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sun, 8 Dec 2019 00:10:43 +1100 Subject: [PATCH] reimplement string timecode to frame number conversion --- app/common/timecodefunctions.cpp | 186 ++++++++++++++++++++++++++----- app/common/timecodefunctions.h | 3 + 2 files changed, 164 insertions(+), 25 deletions(-) diff --git a/app/common/timecodefunctions.cpp b/app/common/timecodefunctions.cpp index 2783fd237..2e9b294df 100644 --- a/app/common/timecodefunctions.cpp +++ b/app/common/timecodefunctions.cpp @@ -24,7 +24,7 @@ #include "config/config.h" -QString padded(int arg, int padding) { +QString padded(int64_t arg, int padding) { return QString("%1").arg(arg, padding, 10, QChar('0')); } @@ -35,21 +35,11 @@ QString olive::timestamp_to_timecode(const int64_t ×tamp, { double timestamp_dbl = (rational(timestamp) * timebase).toDouble(); - // Determine what symbol to separate frames (";" is used for drop frame, ":" is non-drop frame) - QString frame_token = ";"; - switch (display) { case kTimecodeNonDropFrame: - frame_token = ":"; - // Convert timestamp from drop frame to non-drop frame - // FIXME: There's probably a better way to do this - if (timebase == rational(1001, 30000)) { - timestamp_dbl = timestamp_dbl / (30000.0/1001.0) * 30.0; - } else if (timebase == rational(1001, 60000)) { - timestamp_dbl = timestamp_dbl / (60000.0/1001.0) * 60.0; - } else if (timebase == rational(1001, 24000)) { - timestamp_dbl = timestamp_dbl / (24000.0/1001.0) * 24.0; + if (timebase.numerator() == 1001) { + timestamp_dbl = timestamp_dbl / timebase.flipped().toDouble() * (static_cast(timebase.denominator())/1000.0); } /* fall-through */ case kTimecodeDropFrame: @@ -63,16 +53,15 @@ QString olive::timestamp_to_timecode(const int64_t ×tamp, prefix = "+"; } - timestamp_dbl = qAbs(timestamp_dbl); - - int total_seconds = qFloor(timestamp_dbl); - - int hours = total_seconds / 3600; - int mins = total_seconds / 60 - hours * 60; - int secs = total_seconds - mins * 60; - if (display == kTimecodeSeconds) { - int fraction = qRound((timestamp_dbl - total_seconds) * 1000); + timestamp_dbl = qAbs(timestamp_dbl); + + int64_t total_seconds = qFloor(timestamp_dbl); + + int64_t hours = total_seconds / 3600; + int64_t mins = total_seconds / 60 - hours * 60; + int64_t secs = total_seconds - mins * 60; + int64_t fraction = qRound64((timestamp_dbl - static_cast(total_seconds)) * 1000); return QString("%1%2:%3:%4.%5").arg(prefix, padded(hours, 2), @@ -80,9 +69,49 @@ QString olive::timestamp_to_timecode(const int64_t ×tamp, padded(secs, 2), padded(fraction, 3)); } else { - rational frame_rate = timebase.flipped(); + // Determine what symbol to separate frames (";" is used for drop frame, ":" is non-drop frame) + QString frame_token; + double frame_rate = timebase.flipped().toDouble(); + int rounded_frame_rate = qRound(frame_rate); + int64_t frames, secs, mins, hours; + int64_t f = timestamp; - int frames = qRound((timestamp_dbl - total_seconds) * frame_rate.toDouble()); + if (display == kTimecodeDropFrame && timebase.numerator() == 1001) { + frame_token = ";"; + + /** + * CONVERT A FRAME NUMBER TO DROP FRAME TIMECODE + * + * Code by David Heidelberger, adapted from Andrew Duncan, further adapted for Olive by Olive Team + * Given an int called framenumber and a double called framerate + * Framerate should be 29.97, 59.94, or 23.976, otherwise the calculations will be off. + */ + + // If frame number is greater than 24 hrs, next operation will rollover clock + f %= (qRound(frame_rate*3600)*24); + + // Number of frames per ten minutes + int64_t framesPer10Minutes = qRound(frame_rate * 600); + int64_t d = f / framesPer10Minutes; + int64_t m = f % framesPer10Minutes; + + // Number of frames to drop on the minute marks is the nearest integer to 6% of the framerate + int64_t dropFrames = qRound(frame_rate * (2.0/30.0)); + + // Number of frames per minute is the round of the framerate * 60 minus the number of dropped frames + f += dropFrames*9*d; + if (m > dropFrames) { + f += dropFrames * ((m - dropFrames) / (qRound(frame_rate)*60 - dropFrames)); + } + } else { + frame_token = ":"; + } + + // non-drop timecode + hours = f / (3600*rounded_frame_rate); + mins = f / (60*rounded_frame_rate) % 60; + secs = f / rounded_frame_rate % 60; + frames = f % rounded_frame_rate; return QString("%1%2:%3:%4%5%6").arg(prefix, padded(hours, 2), @@ -101,6 +130,108 @@ QString olive::timestamp_to_timecode(const int64_t ×tamp, return QString(); } +int64_t olive::timecode_to_timestamp(const QString &timecode, const rational &timebase, const olive::TimecodeDisplay &display, bool* ok) +{ + double timebase_dbl = timebase.toDouble(); + + if (timecode.isEmpty()) { + goto err_fatal; + } + + switch (display) { + case kTimecodeNonDropFrame: + case kTimecodeDropFrame: + case kTimecodeSeconds: + { + const int kTimecodeElementCount = 4; + QStringList timecode_split = timecode.split(QRegExp("(:)|(;)|(\\.)")); + + bool valid; + + // We only deal with HH, MM, SS, and FF. Any values after that are ignored. + while (timecode_split.size() > kTimecodeElementCount) { + timecode_split.removeLast(); + } + + // Convert values to integers + QList timecode_numbers; + + foreach (const QString& element, timecode_split) { + valid = true; + + timecode_numbers.append((element.isEmpty()) ? 0 : element.toLong(&valid)); + + // If element cannot be converted to a number, + if (!valid) { + goto err_fatal; + } + } + + // Ensure value size is always 4 + while (timecode_numbers.size() < 4) { + timecode_numbers.prepend(0); + } + + double frame_rate = timebase.flipped().toDouble(); + int rounded_frame_rate = qRound(frame_rate); + + int64_t hours = timecode_numbers.at(0); + int64_t mins = timecode_numbers.at(1); + int64_t secs = timecode_numbers.at(2); + int64_t frames = timecode_numbers.at(3); + + int64_t sec_count = (hours*3600 + mins*60 + secs); + int64_t timestamp = sec_count*rounded_frame_rate + frames; + + if (display == kTimecodeDropFrame && timebase.numerator() == 1001) { + + // Number of frames to drop on the minute marks is the nearest integer to 6% of the framerate + int64_t dropFrames = qRound64(frame_rate * (2.0/30.0)); + + // d and m need to be calculated from + int64_t real_fr_ts = qRound64(static_cast(sec_count)*frame_rate) + frames; + + int64_t framesPer10Minutes = qRound(frame_rate * 600); + int64_t d = real_fr_ts / framesPer10Minutes; + int64_t m = real_fr_ts % framesPer10Minutes; + + if (m > dropFrames) { + timestamp -= dropFrames * ((m - dropFrames) / (qRound(frame_rate)*60 - dropFrames)); + } + timestamp -= dropFrames*9*d; + } + + if (ok) *ok = true; + return timestamp; + } + case kMilliseconds: + { + bool valid; + double timecode_secs = timecode.toDouble(&valid); + + if (valid) { + // Convert milliseconds to seconds + timecode_secs *= 0.001; + + // Convert seconds to frames + timecode_secs /= timebase_dbl; + + if (ok) *ok = true; + return qRound(timecode_secs); + } else { + goto err_fatal; + } + } + case kFrames: + if (ok) *ok = true; + return timecode.toLong(ok); + } + +err_fatal: + if (ok) *ok = false; + return 0; +} + rational olive::timestamp_to_time(const int64_t ×tamp, const rational &timebase) { return rational(timestamp) * timebase; @@ -108,7 +239,12 @@ rational olive::timestamp_to_time(const int64_t ×tamp, const rational &time int64_t olive::time_to_timestamp(const rational &time, const rational &timebase) { - return qRound64(time.toDouble() * timebase.flipped().toDouble()); + return time_to_timestamp(time.toDouble(), timebase); +} + +int64_t olive::time_to_timestamp(const double &time, const rational &timebase) +{ + return qRound64(time * timebase.flipped().toDouble()); } olive::TimecodeDisplay olive::CurrentTimecodeDisplay() diff --git a/app/common/timecodefunctions.h b/app/common/timecodefunctions.h index 3bd06a72e..393168a84 100644 --- a/app/common/timecodefunctions.h +++ b/app/common/timecodefunctions.h @@ -42,7 +42,10 @@ TimecodeDisplay CurrentTimecodeDisplay(); */ QString timestamp_to_timecode(const int64_t ×tamp, const rational& timebase, const TimecodeDisplay& display, bool show_plus_if_positive = false); +int64_t timecode_to_timestamp(const QString& timecode, const rational& timebase, const TimecodeDisplay& display, bool *ok = nullptr); + int64_t time_to_timestamp(const rational& time, const rational& timebase); +int64_t time_to_timestamp(const double& time, const rational& timebase); rational timestamp_to_time(const int64_t& timestamp, const rational& timebase);