timecode: fix issue converting seconds to timecode and vice versa
Fixes #2091
This commit is contained in:
@@ -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<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 ×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<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 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<double>(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<double>(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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -98,18 +98,24 @@ 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>();
|
||||
|
||||
switch (display_type_) {
|
||||
case kTime:
|
||||
return Timecode::time_to_timecode(v.value<rational>(), timebase_, Core::instance()->GetTimecodeDisplay());
|
||||
case kFloat:
|
||||
return FloatToString(val, GetDecimalPlaces(), GetAutoTrimDecimalPlaces());
|
||||
case kRational:
|
||||
return v.value<rational>().toString();
|
||||
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(r, timebase_, Core::instance()->GetTimecodeDisplay());
|
||||
case kFloat:
|
||||
return FloatToString(val, GetDecimalPlaces(), GetAutoTrimDecimalPlaces());
|
||||
case kRational:
|
||||
return v.value<rational>().toString();
|
||||
}
|
||||
|
||||
return v.toString();
|
||||
}
|
||||
|
||||
return v.toString();
|
||||
}
|
||||
|
||||
QVariant RationalSlider::StringToValue(const QString &s, bool *ok) const
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user