diff --git a/app/common/timecodefunctions.cpp b/app/common/timecodefunctions.cpp index 1397a4dc7..ca6e8503e 100644 --- a/app/common/timecodefunctions.cpp +++ b/app/common/timecodefunctions.cpp @@ -35,16 +35,13 @@ QString padded(int64_t arg, int padding) { return QStringLiteral("%1").arg(arg, padding, 10, QChar('0')); } -QString Timecode::timestamp_to_timecode(const int64_t ×tamp, - const rational& timebase, - const Display& display, - bool show_plus_if_positive) +QString Timecode::time_to_timecode(const rational &time, const rational &timebase, const Timecode::Display &display, bool show_plus_if_positive) { if (timebase.isNull()) { return QStringLiteral("INVALID TIMEBASE"); } - double timestamp_dbl = (rational(timestamp) * timebase).toDouble(); + double time_dbl = time.toDouble(); switch (display) { case kTimecodeNonDropFrame: @@ -53,21 +50,21 @@ QString Timecode::timestamp_to_timecode(const int64_t ×tamp, { QString prefix; - if (timestamp_dbl < 0) { + if (time_dbl < 0) { prefix = "-"; } else if (show_plus_if_positive) { prefix = "+"; } if (display == kTimecodeSeconds) { - timestamp_dbl = qAbs(timestamp_dbl); + time_dbl = qAbs(time_dbl); - int64_t total_seconds = qFloor(timestamp_dbl); + int64_t total_seconds = qFloor(time_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); + int64_t fraction = qRound64((time_dbl - static_cast(total_seconds)) * 1000); return QStringLiteral("%1%2:%3:%4.%5").arg(prefix, padded(hours, 2), @@ -80,7 +77,7 @@ QString Timecode::timestamp_to_timecode(const int64_t ×tamp, double frame_rate = timebase.flipped().toDouble(); int rounded_frame_rate = qRound(frame_rate); int64_t frames, secs, mins, hours; - int64_t f = qAbs(timestamp); + int64_t f = qAbs(time_to_timestamp(time, timebase)); if (display == kTimecodeDropFrame && TimebaseIsDropFrame(timebase)) { frame_token = ";"; @@ -128,18 +125,36 @@ QString Timecode::timestamp_to_timecode(const int64_t ×tamp, } } case kFrames: - return QString::number(timestamp); + return QString::number(time_to_timestamp(time, timebase)); case kMilliseconds: - return QString::number(qRound(timestamp_dbl * 1000)); + return QString::number(qRound(time_dbl * 1000)); } return QStringLiteral("INVALID TIMECODE MODE"); } -int64_t Timecode::timecode_to_timestamp(const QString &timecode, const rational &timebase, const Display &display, bool* ok) +int64_t StrToInt64EmptyTolerant(const QString &s, bool *ok) { - double timebase_dbl = timebase.toDouble(); + if (s.isEmpty()) { + if (ok) *ok = true; + return 0; + } else { + return s.toLongLong(ok); + } +} +double StrToDoubleEmptyTolerant(const QString &s, bool *ok) +{ + if (s.isEmpty()) { + if (ok) *ok = true; + return 0; + } else { + return s.toDouble(ok); + } +} + +rational Timecode::timecode_to_time(const QString &timecode, const rational &timebase, const Timecode::Display &display, bool *ok) +{ if (timecode.isEmpty()) { goto err_fatal; } @@ -149,71 +164,73 @@ int64_t Timecode::timecode_to_timestamp(const QString &timecode, const rational case kTimecodeDropFrame: case kTimecodeSeconds: { - const int kTimecodeElementCount = 4; - QStringList timecode_split = timecode.split(QRegularExpression("(:)|(;)|(\\.)")); + QStringList timecode_split = timecode.split(QRegularExpression("(:)|(;)")); - bool valid; + const int element_count = display == kTimecodeSeconds ? 3 : 4; - // We only deal with HH, MM, SS, and FF. Any values after that are ignored. - while (timecode_split.size() > kTimecodeElementCount) { + // Remove excess tokens (we're only interested in HH:MM:SS.FF) + while (timecode_split.size() > element_count) { timecode_split.removeLast(); } - // Convert values to integers - QList timecode_numbers; + // For easier index calculations, ensure minimum size + while (timecode_split.size() < element_count) { + timecode_split.prepend(QString()); + } bool negative = timecode.trimmed().startsWith('-'); - foreach (const QString& element, timecode_split) { - valid = true; - - timecode_numbers.append((element.isEmpty()) ? 0 : qAbs(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); + bool valid; + rational time; - int64_t sec_count = (hours*3600 + mins*60 + secs); - int64_t timestamp = sec_count*rounded_frame_rate + frames; + int64_t hours = StrToInt64EmptyTolerant(timecode_split.at(0), &valid); + if (!valid) goto err_fatal; + int64_t mins = StrToInt64EmptyTolerant(timecode_split.at(1), &valid); + if (!valid) goto err_fatal; - if (display == kTimecodeDropFrame && TimebaseIsDropFrame(timebase)) { + if (display == kTimecodeSeconds) { + double secs = StrToDoubleEmptyTolerant(timecode_split.at(2), &valid); + if (!valid) goto err_fatal; - // 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)); + time = rational::fromDouble(hours * 3600 + mins * 60 + secs); + } else { + int64_t secs = StrToInt64EmptyTolerant(timecode_split.at(2), &valid); + if (!valid) goto err_fatal; + int64_t frames = StrToInt64EmptyTolerant(timecode_split.at(3), &valid); + if (!valid) goto err_fatal; - // d and m need to be calculated from - int64_t real_fr_ts = qRound64(static_cast(sec_count)*frame_rate) + frames; + int64_t sec_count = (hours*3600 + mins*60 + secs); + int64_t frame_count = sec_count*rounded_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 (display == kTimecodeDropFrame && TimebaseIsDropFrame(timebase)) { - if (m > dropFrames) { - timestamp -= dropFrames * ((m - dropFrames) / (qRound(frame_rate)*60 - dropFrames)); + // 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) { + frame_count -= dropFrames * ((m - dropFrames) / (qRound(frame_rate)*60 - dropFrames)); + } + frame_count -= dropFrames*9*d; } - timestamp -= dropFrames*9*d; + + time = timestamp_to_time(frame_count, timebase); } if (ok) *ok = true; - if (negative) timestamp = -timestamp; + if (negative) time = -time; - return timestamp; + return time; } case kMilliseconds: { @@ -224,18 +241,23 @@ int64_t Timecode::timecode_to_timestamp(const QString &timecode, const rational // Convert milliseconds to seconds timecode_secs *= 0.001; - // Convert seconds to frames - timecode_secs /= timebase_dbl; - - if (ok) *ok = true; - return qRound(timecode_secs); + // Convert seconds to rational + return rational::fromDouble(timecode_secs, ok); } else { goto err_fatal; } } case kFrames: + { + bool valid; + int64_t ts = timecode.toLongLong(&valid); + if (!valid) { + goto err_fatal; + } + if (ok) *ok = true; - return timecode.toLong(ok); + return timestamp_to_time(ts, timebase); + } } err_fatal: @@ -243,12 +265,6 @@ err_fatal: return 0; } -rational Timecode::timecode_to_time(const QString &timecode, const rational &timebase, const Timecode::Display &display, bool *ok) -{ - int64_t timestamp = timecode_to_timestamp(timecode, timebase, display, ok); - return timestamp_to_time(timestamp, timebase); -} - rational Timecode::snap_time_to_timebase(const rational &time, const rational &timebase, Rounding floor) { // Just convert to a timestamp in timebase units and back @@ -269,11 +285,6 @@ rational Timecode::timestamp_to_time(const int64_t ×tamp, const rational &t return rational(num_r, den_r); } -QString Timecode::time_to_timecode(const rational &time, const rational &timebase, const Timecode::Display &display, bool show_plus_if_positive) -{ - return timestamp_to_timecode(time_to_timestamp(time, timebase), timebase, display, show_plus_if_positive); -} - bool Timecode::TimebaseIsDropFrame(const rational &timebase) { return (timebase.numerator() != 1); diff --git a/app/common/timecodefunctions.h b/app/common/timecodefunctions.h index fe5273acb..0cdabd242 100644 --- a/app/common/timecodefunctions.h +++ b/app/common/timecodefunctions.h @@ -56,9 +56,7 @@ public: /** * @brief Convert a timestamp (according to a rational timebase) to a user-friendly string representation */ - static QString timestamp_to_timecode(const int64_t ×tamp, const rational& timebase, const Display &display, bool show_plus_if_positive = false); - - static int64_t timecode_to_timestamp(const QString& timecode, const rational& timebase, const Display& display, bool *ok = nullptr); + static QString time_to_timecode(const rational& time, const rational& timebase, const Display &display, bool show_plus_if_positive = false); static rational timecode_to_time(const QString& timecode, const rational& timebase, const Display& display, bool *ok = nullptr); static rational snap_time_to_timebase(const rational& time, const rational& timebase, Rounding floor = kRound); @@ -71,8 +69,6 @@ public: static rational timestamp_to_time(const int64_t& timestamp, const rational& timebase); - static QString time_to_timecode(const rational& time, const rational& timebase, const Display &display, bool show_plus_if_positive = false); - static bool TimebaseIsDropFrame(const rational& timebase); static QString TimeToString(int64_t ms); diff --git a/app/widget/slider/CMakeLists.txt b/app/widget/slider/CMakeLists.txt index d20c999e6..b90c18ab5 100644 --- a/app/widget/slider/CMakeLists.txt +++ b/app/widget/slider/CMakeLists.txt @@ -26,7 +26,5 @@ set(OLIVE_SOURCES widget/slider/rationalslider.cpp widget/slider/stringslider.h widget/slider/stringslider.cpp - widget/slider/timeslider.h - widget/slider/timeslider.cpp PARENT_SCOPE ) diff --git a/app/widget/slider/rationalslider.cpp b/app/widget/slider/rationalslider.cpp index 3cf9e1e85..8ac41bf0d 100644 --- a/app/widget/slider/rationalslider.cpp +++ b/app/widget/slider/rationalslider.cpp @@ -98,18 +98,24 @@ void RationalSlider::DisableDisplayType(RationalSlider::DisplayType type) QString RationalSlider::ValueToString(const QVariant &v) const { - double val = v.value().toDouble() + GetOffset().value().toDouble(); + rational r = v.value(); - switch (display_type_) { - case kTime: - return Timecode::time_to_timecode(v.value(), timebase_, Core::instance()->GetTimecodeDisplay()); - case kFloat: - return FloatToString(val, GetDecimalPlaces(), GetAutoTrimDecimalPlaces()); - case kRational: - return v.value().toString(); + if (r.isNaN()) { + return tr("NaN"); + } else { + double val = r.toDouble() + GetOffset().value().toDouble(); + + switch (display_type_) { + case kTime: + return Timecode::time_to_timecode(r, timebase_, Core::instance()->GetTimecodeDisplay()); + case kFloat: + return FloatToString(val, GetDecimalPlaces(), GetAutoTrimDecimalPlaces()); + case kRational: + return v.value().toString(); + } + + return v.toString(); } - - return v.toString(); } QVariant RationalSlider::StringToValue(const QString &s, bool *ok) const diff --git a/app/widget/slider/timeslider.cpp b/app/widget/slider/timeslider.cpp deleted file mode 100644 index 57fc557b5..000000000 --- a/app/widget/slider/timeslider.cpp +++ /dev/null @@ -1,63 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2022 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#include "timeslider.h" - -#include "common/timecodefunctions.h" -#include "core.h" - -namespace olive { - -#define super IntegerSlider - -TimeSlider::TimeSlider(QWidget *parent) : - super(parent) -{ - SetMinimum(0); - - connect(Core::instance(), &Core::TimecodeDisplayChanged, this, &TimeSlider::UpdateLabel); -} - -void TimeSlider::SetTimebase(const rational &timebase) -{ - timebase_ = timebase; - - // Refresh label since we have a new timebase to generate a timecode with - UpdateLabel(); -} - -QString TimeSlider::ValueToString(const QVariant &v) const -{ - if (timebase_.isNull()) { - // We can't generate a timecode without a timebase, so we just return the number - return super::ValueToString(v); - } - - return Timecode::timestamp_to_timecode(v.toLongLong() + GetOffset().toLongLong(), - timebase_, - Core::instance()->GetTimecodeDisplay()); -} - -QVariant TimeSlider::StringToValue(const QString &s, bool *ok) const -{ - return QVariant::fromValue(Timecode::timecode_to_timestamp(s, timebase_, Core::instance()->GetTimecodeDisplay(), ok) - GetOffset().toLongLong()); -} - -} diff --git a/app/widget/slider/timeslider.h b/app/widget/slider/timeslider.h deleted file mode 100644 index b22bb325c..000000000 --- a/app/widget/slider/timeslider.h +++ /dev/null @@ -1,50 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2022 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#ifndef TIMESLIDER_H -#define TIMESLIDER_H - -#include "common/rational.h" -#include "integerslider.h" - -namespace olive { - -class TimeSlider : public IntegerSlider -{ - Q_OBJECT -public: - TimeSlider(QWidget* parent = nullptr); - -public slots: - void SetTimebase(const rational& timebase); - -protected: - virtual QString ValueToString(const QVariant& v) const override; - - virtual QVariant StringToValue(const QString& s, bool* ok) const override; - -private: - rational timebase_; - -}; - -} - -#endif // TIMESLIDER_H diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index e4e2f7710..c342e8c32 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -289,6 +289,7 @@ void TimelineWidget::ConnectNodeEvent(ViewerOutput *n) connect(timecode_label_, &RationalSlider::ValueChanged, s, &Sequence::SetPlayhead); connect(s, &Sequence::PlayheadChanged, timecode_label_, &RationalSlider::SetValue); + timecode_label_->SetValue(s->GetPlayhead()); ruler()->SetPlaybackCache(n->video_frame_cache());