timecode: fix issue converting seconds to timecode and vice versa

Fixes #2091
This commit is contained in:
itsmattkc
2022-11-05 13:45:21 -07:00
parent 1c4149a0fd
commit 49ad3a1e34
7 changed files with 102 additions and 203 deletions
+74 -63
View File
@@ -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 &timestamp,
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 &timestamp,
{
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<double>(total_seconds)) * 1000);
int64_t fraction = qRound64((time_dbl - static_cast<double>(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 &timestamp,
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 &timestamp,
}
}
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,47 +164,46 @@ 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<int64_t> 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 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 == kTimecodeSeconds) {
double secs = StrToDoubleEmptyTolerant(timecode_split.at(2), &valid);
if (!valid) goto err_fatal;
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;
int64_t sec_count = (hours*3600 + mins*60 + secs);
int64_t timestamp = sec_count*rounded_frame_rate + frames;
int64_t frame_count = sec_count*rounded_frame_rate + frames;
if (display == kTimecodeDropFrame && TimebaseIsDropFrame(timebase)) {
@@ -204,16 +218,19 @@ int64_t Timecode::timecode_to_timestamp(const QString &timecode, const rational
int64_t m = real_fr_ts % framesPer10Minutes;
if (m > dropFrames) {
timestamp -= dropFrames * ((m - dropFrames) / (qRound(frame_rate)*60 - dropFrames));
frame_count -= dropFrames * ((m - dropFrames) / (qRound(frame_rate)*60 - dropFrames));
}
timestamp -= dropFrames*9*d;
frame_count -= 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 &timestamp, 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);
+1 -5
View File
@@ -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 &timestamp, 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);
-2
View File
@@ -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
)
+8 -2
View File
@@ -98,11 +98,16 @@ void RationalSlider::DisableDisplayType(RationalSlider::DisplayType type)
QString RationalSlider::ValueToString(const QVariant &v) const
{
double val = v.value<rational>().toDouble() + GetOffset().value<rational>().toDouble();
rational r = v.value<rational>();
if (r.isNaN()) {
return tr("NaN");
} else {
double val = r.toDouble() + GetOffset().value<rational>().toDouble();
switch (display_type_) {
case kTime:
return Timecode::time_to_timecode(v.value<rational>(), timebase_, Core::instance()->GetTimecodeDisplay());
return Timecode::time_to_timecode(r, timebase_, Core::instance()->GetTimecodeDisplay());
case kFloat:
return FloatToString(val, GetDecimalPlaces(), GetAutoTrimDecimalPlaces());
case kRational:
@@ -110,6 +115,7 @@ QString RationalSlider::ValueToString(const QVariant &v) const
}
return v.toString();
}
}
QVariant RationalSlider::StringToValue(const QString &s, bool *ok) const
-63
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#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());
}
}
-50
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#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
@@ -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());