start moving core frameworks to external libraries
This commit is contained in:
@@ -50,13 +50,7 @@ set(OLIVE_SOURCES
|
||||
common/range.h
|
||||
common/ratiodialog.cpp
|
||||
common/ratiodialog.h
|
||||
common/rational.cpp
|
||||
common/rational.h
|
||||
common/threadsafemap.h
|
||||
common/timecodefunctions.cpp
|
||||
common/timecodefunctions.h
|
||||
common/timerange.cpp
|
||||
common/timerange.h
|
||||
common/tohex.h
|
||||
common/util.h
|
||||
common/xmlutils.cpp
|
||||
|
||||
+25
-25
@@ -22,13 +22,13 @@
|
||||
|
||||
namespace olive {
|
||||
|
||||
AVPixelFormat FFmpegUtils::GetCompatiblePixelFormat(const AVPixelFormat &pix_fmt, VideoParams::Format maximum)
|
||||
AVPixelFormat FFmpegUtils::GetCompatiblePixelFormat(const AVPixelFormat &pix_fmt, PixelFormat maximum)
|
||||
{
|
||||
AVPixelFormat possible_pix_fmts[3];
|
||||
|
||||
possible_pix_fmts[0] = AV_PIX_FMT_RGBA;
|
||||
|
||||
if (maximum == VideoParams::kFormatUnsigned8) {
|
||||
if (maximum == PixelFormat::U8) {
|
||||
possible_pix_fmts[1] = AV_PIX_FMT_NONE;
|
||||
} else {
|
||||
possible_pix_fmts[1] = AV_PIX_FMT_RGBA64;
|
||||
@@ -148,30 +148,30 @@ AVPixelFormat FFmpegUtils::ConvertJPEGSpaceToRegularSpace(AVPixelFormat f)
|
||||
return f;
|
||||
}
|
||||
|
||||
AVPixelFormat FFmpegUtils::GetFFmpegPixelFormat(const VideoParams::Format &pix_fmt, int channel_layout)
|
||||
AVPixelFormat FFmpegUtils::GetFFmpegPixelFormat(const PixelFormat &pix_fmt, int channel_layout)
|
||||
{
|
||||
if (channel_layout == VideoParams::kRGBChannelCount) {
|
||||
switch (pix_fmt) {
|
||||
case VideoParams::kFormatUnsigned8:
|
||||
case PixelFormat::U8:
|
||||
return AV_PIX_FMT_RGB24;
|
||||
case VideoParams::kFormatUnsigned16:
|
||||
case PixelFormat::U16:
|
||||
return AV_PIX_FMT_RGB48;
|
||||
case VideoParams::kFormatFloat16:
|
||||
case VideoParams::kFormatFloat32:
|
||||
case VideoParams::kFormatInvalid:
|
||||
case VideoParams::kFormatCount:
|
||||
case PixelFormat::F16:
|
||||
case PixelFormat::F32:
|
||||
case PixelFormat::INVALID:
|
||||
case PixelFormat::FORMAT_COUNT:
|
||||
break;
|
||||
}
|
||||
} else if (channel_layout == VideoParams::kRGBAChannelCount) {
|
||||
switch (pix_fmt) {
|
||||
case VideoParams::kFormatUnsigned8:
|
||||
case PixelFormat::U8:
|
||||
return AV_PIX_FMT_RGBA;
|
||||
case VideoParams::kFormatUnsigned16:
|
||||
case PixelFormat::U16:
|
||||
return AV_PIX_FMT_RGBA64;
|
||||
case VideoParams::kFormatFloat16:
|
||||
case VideoParams::kFormatFloat32:
|
||||
case VideoParams::kFormatInvalid:
|
||||
case VideoParams::kFormatCount:
|
||||
case PixelFormat::F16:
|
||||
case PixelFormat::F32:
|
||||
case PixelFormat::INVALID:
|
||||
case PixelFormat::FORMAT_COUNT:
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -179,21 +179,21 @@ AVPixelFormat FFmpegUtils::GetFFmpegPixelFormat(const VideoParams::Format &pix_f
|
||||
return AV_PIX_FMT_NONE;
|
||||
}
|
||||
|
||||
VideoParams::Format FFmpegUtils::GetCompatiblePixelFormat(const VideoParams::Format &pix_fmt)
|
||||
PixelFormat FFmpegUtils::GetCompatiblePixelFormat(const PixelFormat &pix_fmt)
|
||||
{
|
||||
switch (pix_fmt) {
|
||||
case VideoParams::kFormatUnsigned8:
|
||||
return VideoParams::kFormatUnsigned8;
|
||||
case VideoParams::kFormatUnsigned16:
|
||||
case VideoParams::kFormatFloat16:
|
||||
case VideoParams::kFormatFloat32:
|
||||
return VideoParams::kFormatUnsigned16;
|
||||
case VideoParams::kFormatInvalid:
|
||||
case VideoParams::kFormatCount:
|
||||
case PixelFormat::U8:
|
||||
return PixelFormat::U8;
|
||||
case PixelFormat::U16:
|
||||
case PixelFormat::F16:
|
||||
case PixelFormat::F32:
|
||||
return PixelFormat::U16;
|
||||
case PixelFormat::INVALID:
|
||||
case PixelFormat::FORMAT_COUNT:
|
||||
break;
|
||||
}
|
||||
|
||||
return VideoParams::kFormatInvalid;
|
||||
return PixelFormat::INVALID;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -37,17 +37,17 @@ public:
|
||||
/**
|
||||
* @brief Returns an AVPixelFormat that can be used to convert a frame to a data type Olive supports with minimal data loss
|
||||
*/
|
||||
static AVPixelFormat GetCompatiblePixelFormat(const AVPixelFormat& pix_fmt, VideoParams::Format maximum = VideoParams::kFormatInvalid);
|
||||
static AVPixelFormat GetCompatiblePixelFormat(const AVPixelFormat& pix_fmt, PixelFormat maximum = PixelFormat::INVALID);
|
||||
|
||||
/**
|
||||
* @brief Returns a native pixel format that can be used to convert from a native frame to an AVFrame with minimal data loss
|
||||
*/
|
||||
static VideoParams::Format GetCompatiblePixelFormat(const VideoParams::Format& pix_fmt);
|
||||
static PixelFormat GetCompatiblePixelFormat(const PixelFormat& pix_fmt);
|
||||
|
||||
/**
|
||||
* @brief Returns an FFmpeg pixel format for a given native pixel format
|
||||
*/
|
||||
static AVPixelFormat GetFFmpegPixelFormat(const VideoParams::Format& pix_fmt, int channel_layout);
|
||||
static AVPixelFormat GetFFmpegPixelFormat(const PixelFormat& pix_fmt, int channel_layout);
|
||||
|
||||
/**
|
||||
* @brief Returns a native sample format type for a given AVSampleFormat
|
||||
|
||||
@@ -22,22 +22,22 @@
|
||||
|
||||
namespace olive {
|
||||
|
||||
OCIO::BitDepth OCIOUtils::GetOCIOBitDepthFromPixelFormat(VideoParams::Format format)
|
||||
OCIO::BitDepth OCIOUtils::GetOCIOBitDepthFromPixelFormat(PixelFormat format)
|
||||
{
|
||||
switch (format) {
|
||||
case VideoParams::kFormatUnsigned8:
|
||||
case PixelFormat::U8:
|
||||
return OCIO::BIT_DEPTH_UINT8;
|
||||
case VideoParams::kFormatUnsigned16:
|
||||
case PixelFormat::U16:
|
||||
return OCIO::BIT_DEPTH_UINT16;
|
||||
break;
|
||||
case VideoParams::kFormatFloat16:
|
||||
case PixelFormat::F16:
|
||||
return OCIO::BIT_DEPTH_F16;
|
||||
break;
|
||||
case VideoParams::kFormatFloat32:
|
||||
case PixelFormat::F32:
|
||||
return OCIO::BIT_DEPTH_F32;
|
||||
break;
|
||||
case VideoParams::kFormatInvalid:
|
||||
case VideoParams::kFormatCount:
|
||||
case PixelFormat::INVALID:
|
||||
case PixelFormat::FORMAT_COUNT:
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace olive {
|
||||
class OCIOUtils
|
||||
{
|
||||
public:
|
||||
static OCIO::BitDepth GetOCIOBitDepthFromPixelFormat(VideoParams::Format format);
|
||||
static OCIO::BitDepth GetOCIOBitDepthFromPixelFormat(PixelFormat format);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
|
||||
#include "oiioutils.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
namespace olive {
|
||||
|
||||
void OIIOUtils::FrameToBuffer(const Frame* frame, OIIO::ImageBuf *buf)
|
||||
@@ -45,7 +47,7 @@ rational OIIOUtils::GetPixelAspectRatioFromOIIO(const OIIO::ImageSpec &spec)
|
||||
return rational::fromDouble(spec.get_float_attribute("PixelAspectRatio", 1));
|
||||
}
|
||||
|
||||
VideoParams::Format OIIOUtils::GetFormatFromOIIOBasetype(OIIO::TypeDesc::BASETYPE type)
|
||||
PixelFormat OIIOUtils::GetFormatFromOIIOBasetype(OIIO::TypeDesc::BASETYPE type)
|
||||
{
|
||||
switch (type) {
|
||||
case OIIO::TypeDesc::UNKNOWN:
|
||||
@@ -66,16 +68,16 @@ VideoParams::Format OIIOUtils::GetFormatFromOIIOBasetype(OIIO::TypeDesc::BASETYP
|
||||
break;
|
||||
|
||||
case OIIO::TypeDesc::UINT8:
|
||||
return VideoParams::kFormatUnsigned8;
|
||||
return PixelFormat::U8;
|
||||
case OIIO::TypeDesc::UINT16:
|
||||
return VideoParams::kFormatUnsigned16;
|
||||
return PixelFormat::U16;
|
||||
case OIIO::TypeDesc::HALF:
|
||||
return VideoParams::kFormatFloat16;
|
||||
return PixelFormat::F16;
|
||||
case OIIO::TypeDesc::FLOAT:
|
||||
return VideoParams::kFormatFloat32;
|
||||
return PixelFormat::F32;
|
||||
}
|
||||
|
||||
return VideoParams::kFormatInvalid;
|
||||
return PixelFormat::INVALID;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -31,19 +31,19 @@ namespace olive {
|
||||
|
||||
class OIIOUtils {
|
||||
public:
|
||||
static OIIO::TypeDesc::BASETYPE GetOIIOBaseTypeFromFormat(VideoParams::Format format)
|
||||
static OIIO::TypeDesc::BASETYPE GetOIIOBaseTypeFromFormat(PixelFormat format)
|
||||
{
|
||||
switch (format) {
|
||||
case VideoParams::kFormatUnsigned8:
|
||||
case PixelFormat::U8:
|
||||
return OIIO::TypeDesc::UINT8;
|
||||
case VideoParams::kFormatUnsigned16:
|
||||
case PixelFormat::U16:
|
||||
return OIIO::TypeDesc::UINT16;
|
||||
case VideoParams::kFormatFloat16:
|
||||
case PixelFormat::F16:
|
||||
return OIIO::TypeDesc::HALF;
|
||||
case VideoParams::kFormatFloat32:
|
||||
case PixelFormat::F32:
|
||||
return OIIO::TypeDesc::FLOAT;
|
||||
case VideoParams::kFormatInvalid:
|
||||
case VideoParams::kFormatCount:
|
||||
case PixelFormat::INVALID:
|
||||
case PixelFormat::FORMAT_COUNT:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ public:
|
||||
|
||||
static void BufferToFrame(OIIO::ImageBuf* buf, Frame* frame);
|
||||
|
||||
static VideoParams::Format GetFormatFromOIIOBasetype(OIIO::TypeDesc::BASETYPE type);
|
||||
static PixelFormat GetFormatFromOIIOBasetype(OIIO::TypeDesc::BASETYPE type);
|
||||
|
||||
static rational GetPixelAspectRatioFromOIIO(const OIIO::ImageSpec& spec);
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "common/clamp.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
int QtUtils::QFontMetricsWidth(QFontMetrics fm, const QString& s) {
|
||||
@@ -172,4 +174,32 @@ void QtUtils::SetComboBoxData(QComboBox *cb, int data)
|
||||
}
|
||||
}
|
||||
|
||||
QColor QtUtils::toQColor(const core::Color &i)
|
||||
{
|
||||
QColor c;
|
||||
|
||||
// QColor only supports values from 0.0 to 1.0 and are only used for UI representations
|
||||
c.setRedF(clamp(i.red(), 0.0f, 1.0f));
|
||||
c.setGreenF(clamp(i.green(), 0.0f, 1.0f));
|
||||
c.setBlueF(clamp(i.blue(), 0.0f, 1.0f));
|
||||
c.setAlphaF(clamp(i.alpha(), 0.0f, 1.0f));
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
namespace core {
|
||||
|
||||
uint qHash(const core::rational &r, uint seed)
|
||||
{
|
||||
return ::qHash(r.toDouble(), seed);
|
||||
}
|
||||
|
||||
uint qHash(const core::TimeRange &r, uint seed)
|
||||
{
|
||||
return qHash(r.in(), seed) ^ qHash(r.out(), seed);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#ifndef QTVERSIONABSTRACTION_H
|
||||
#define QTVERSIONABSTRACTION_H
|
||||
|
||||
#include <olive/core/core.h>
|
||||
#include <QComboBox>
|
||||
#include <QDateTime>
|
||||
#include <QFileInfo>
|
||||
@@ -72,8 +73,21 @@ public:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static QColor toQColor(const core::Color &c);
|
||||
|
||||
};
|
||||
|
||||
namespace core {
|
||||
|
||||
uint qHash(const core::rational& r, uint seed = 0);
|
||||
uint qHash(const core::TimeRange& r, uint seed = 0);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(olive::core::rational);
|
||||
Q_DECLARE_METATYPE(olive::core::Color);
|
||||
Q_DECLARE_METATYPE(olive::core::TimeRange);
|
||||
|
||||
#endif // QTVERSIONABSTRACTION_H
|
||||
|
||||
@@ -23,8 +23,6 @@
|
||||
|
||||
#include <QInputDialog>
|
||||
|
||||
#include "common/rational.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
double GetFloatRatioFromUser(QWidget* parent,
|
||||
|
||||
@@ -1,287 +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 "rational.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
const rational rational::NaN = rational(0, 0);
|
||||
|
||||
rational rational::fromDouble(const double &flt, bool* ok)
|
||||
{
|
||||
if (qIsNaN(flt)) {
|
||||
// Return NaN rational
|
||||
if (ok) *ok = false;
|
||||
return NaN;
|
||||
}
|
||||
|
||||
// Use FFmpeg function for the time being
|
||||
AVRational r = av_d2q(flt, INT_MAX);
|
||||
|
||||
if (r.den == 0) {
|
||||
// If den == 0, we were unable to convert to a rational
|
||||
if (ok) {
|
||||
*ok = false;
|
||||
}
|
||||
} else {
|
||||
// Otherwise, assume we received a real rational
|
||||
if (ok) {
|
||||
*ok = true;
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
rational rational::fromString(const QString &str, bool* ok)
|
||||
{
|
||||
QStringList elements = str.split('/');
|
||||
|
||||
switch (elements.size()) {
|
||||
case 1:
|
||||
return rational(elements.first().toInt(ok));
|
||||
case 2:
|
||||
return rational(elements.at(0).toInt(ok), elements.at(1).toInt(ok));
|
||||
default:
|
||||
// Returns NaN with ok set to false
|
||||
if (ok) {
|
||||
*ok = false;
|
||||
}
|
||||
return NaN;
|
||||
}
|
||||
}
|
||||
|
||||
//Function: convert to double
|
||||
|
||||
double rational::toDouble() const
|
||||
{
|
||||
if (r_.den != 0) {
|
||||
return av_q2d(r_);
|
||||
} else {
|
||||
return qSNaN();
|
||||
}
|
||||
}
|
||||
|
||||
AVRational rational::toAVRational() const
|
||||
{
|
||||
return r_;
|
||||
}
|
||||
|
||||
#ifdef USE_OTIO
|
||||
opentime::RationalTime rational::toRationalTime(double framerate) const
|
||||
{
|
||||
// Is this the best way of doing this?
|
||||
// Olive can store rationals as 0/0 which causes errors in OTIO
|
||||
opentime::RationalTime time = opentime::RationalTime(r_.num, r_.den == 0 ? 1 : r_.den);
|
||||
return time.rescaled_to(framerate);
|
||||
}
|
||||
#endif
|
||||
|
||||
rational rational::flipped() const
|
||||
{
|
||||
rational r = *this;
|
||||
r.flip();
|
||||
return r;
|
||||
}
|
||||
|
||||
void rational::flip()
|
||||
{
|
||||
if (!isNull()) {
|
||||
std::swap(r_.den, r_.num);
|
||||
FixSigns();
|
||||
}
|
||||
}
|
||||
|
||||
QString rational::toString() const
|
||||
{
|
||||
return QStringLiteral("%1/%2").arg(QString::number(r_.num), QString::number(r_.den));
|
||||
}
|
||||
|
||||
void rational::FixSigns()
|
||||
{
|
||||
if (r_.den < 0) {
|
||||
// Normalize so that denominator is always positive
|
||||
r_.den = -r_.den;
|
||||
r_.num = -r_.num;
|
||||
} else if (r_.den == 0) {
|
||||
// Normalize to 0/0 (aka NaN) if denominator is zero
|
||||
r_.num = 0;
|
||||
} else if (r_.num == 0) {
|
||||
// Normalize to 0/1 if numerator is zero
|
||||
r_.den = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void rational::Reduce()
|
||||
{
|
||||
av_reduce(&r_.num, &r_.den, r_.num, r_.den, INT_MAX);
|
||||
}
|
||||
|
||||
//Assignment Operators
|
||||
|
||||
const rational& rational::operator=(const rational &rhs)
|
||||
{
|
||||
r_ = rhs.r_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const rational& rational::operator+=(const rational &rhs)
|
||||
{
|
||||
Q_ASSERT(*this != RATIONAL_MIN && *this != RATIONAL_MAX && rhs != RATIONAL_MIN && rhs != RATIONAL_MAX);
|
||||
|
||||
if (!isNaN()) {
|
||||
if (rhs.isNaN()) {
|
||||
*this = NaN;
|
||||
} else {
|
||||
r_ = av_add_q(r_, rhs.r_);
|
||||
FixSigns();
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
const rational& rational::operator-=(const rational &rhs)
|
||||
{
|
||||
Q_ASSERT(*this != RATIONAL_MIN && *this != RATIONAL_MAX && rhs != RATIONAL_MIN && rhs != RATIONAL_MAX);
|
||||
|
||||
if (!isNaN()) {
|
||||
if (rhs.isNaN()) {
|
||||
*this = NaN;
|
||||
} else {
|
||||
r_ = av_sub_q(r_, rhs.r_);
|
||||
FixSigns();
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
const rational& rational::operator*=(const rational &rhs)
|
||||
{
|
||||
Q_ASSERT(*this != RATIONAL_MIN && *this != RATIONAL_MAX && rhs != RATIONAL_MIN && rhs != RATIONAL_MAX);
|
||||
|
||||
if (!isNaN()) {
|
||||
if (rhs.isNaN()) {
|
||||
*this = NaN;
|
||||
} else {
|
||||
r_ = av_mul_q(r_, rhs.r_);
|
||||
FixSigns();
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
const rational& rational::operator/=(const rational &rhs)
|
||||
{
|
||||
Q_ASSERT(*this != RATIONAL_MIN && *this != RATIONAL_MAX && rhs != RATIONAL_MIN && rhs != RATIONAL_MAX);
|
||||
|
||||
if (!isNaN()) {
|
||||
if (rhs.isNaN()) {
|
||||
*this = NaN;
|
||||
} else {
|
||||
r_ = av_div_q(r_, rhs.r_);
|
||||
FixSigns();
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
//Binary math operators
|
||||
|
||||
rational rational::operator+(const rational &rhs) const
|
||||
{
|
||||
rational answer(*this);
|
||||
answer += rhs;
|
||||
return answer;
|
||||
}
|
||||
|
||||
rational rational::operator-(const rational &rhs) const
|
||||
{
|
||||
rational answer(*this);
|
||||
answer -= rhs;
|
||||
return answer;
|
||||
}
|
||||
|
||||
rational rational::operator/(const rational &rhs) const
|
||||
{
|
||||
rational answer(*this);
|
||||
answer /= rhs;
|
||||
return answer;
|
||||
}
|
||||
|
||||
rational rational::operator*(const rational &rhs) const
|
||||
{
|
||||
rational answer(*this);
|
||||
answer *= rhs;
|
||||
return answer;
|
||||
}
|
||||
|
||||
//Relational and equality operators
|
||||
|
||||
bool rational::operator<(const rational &rhs) const
|
||||
{
|
||||
return av_cmp_q(r_, rhs.r_) == -1;
|
||||
}
|
||||
|
||||
bool rational::operator<=(const rational &rhs) const
|
||||
{
|
||||
int cmp = av_cmp_q(r_, rhs.r_);
|
||||
return cmp == 0 || cmp == -1;
|
||||
}
|
||||
|
||||
bool rational::operator>(const rational &rhs) const
|
||||
{
|
||||
return av_cmp_q(r_, rhs.r_) == 1;
|
||||
}
|
||||
|
||||
bool rational::operator>=(const rational &rhs) const
|
||||
{
|
||||
int cmp = av_cmp_q(r_, rhs.r_);
|
||||
return cmp == 0 || cmp == 1;
|
||||
}
|
||||
|
||||
bool rational::operator==(const rational &rhs) const
|
||||
{
|
||||
return av_cmp_q(r_, rhs.r_) == 0;
|
||||
}
|
||||
|
||||
bool rational::operator!=(const rational &rhs) const
|
||||
{
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
uint qHash(const rational &r, uint seed)
|
||||
{
|
||||
return ::qHash(r.toDouble(), seed);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug debug, const olive::rational &r)
|
||||
{
|
||||
if (r.isNaN()) {
|
||||
return debug.space() << "NaN";
|
||||
} else {
|
||||
return debug.space() << r.toDouble();
|
||||
}
|
||||
}
|
||||
@@ -1,157 +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 RATIONAL_H
|
||||
#define RATIONAL_H
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/rational.h>
|
||||
}
|
||||
|
||||
#include <iostream>
|
||||
#include <QDebug>
|
||||
#include <QMetaType>
|
||||
|
||||
#ifdef USE_OTIO
|
||||
#include <opentime/rationalTime.h>
|
||||
#endif
|
||||
|
||||
#include "common/define.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class rational
|
||||
{
|
||||
public:
|
||||
rational(const int &numerator = 0)
|
||||
{
|
||||
r_.num = numerator;
|
||||
r_.den = 1;
|
||||
}
|
||||
|
||||
rational(const int &numerator, const int &denominator)
|
||||
{
|
||||
r_.num = numerator;
|
||||
r_.den = denominator;
|
||||
|
||||
FixSigns();
|
||||
Reduce();
|
||||
}
|
||||
|
||||
rational(const rational &rhs) = default;
|
||||
|
||||
rational(const AVRational& r)
|
||||
{
|
||||
r_ = r;
|
||||
|
||||
FixSigns();
|
||||
}
|
||||
|
||||
static rational fromDouble(const double& flt, bool *ok = nullptr);
|
||||
static rational fromString(const QString& str, bool* ok = nullptr);
|
||||
|
||||
static const rational NaN;
|
||||
|
||||
//Assignment Operators
|
||||
const rational& operator=(const rational &rhs);
|
||||
const rational& operator+=(const rational &rhs);
|
||||
const rational& operator-=(const rational &rhs);
|
||||
const rational& operator/=(const rational &rhs);
|
||||
const rational& operator*=(const rational &rhs);
|
||||
|
||||
//Binary math operators
|
||||
rational operator+(const rational &rhs) const;
|
||||
rational operator-(const rational &rhs) const;
|
||||
rational operator/(const rational &rhs) const;
|
||||
rational operator*(const rational &rhs) const;
|
||||
|
||||
//Relational and equality operators
|
||||
bool operator<(const rational &rhs) const;
|
||||
bool operator<=(const rational &rhs) const;
|
||||
bool operator>(const rational &rhs) const;
|
||||
bool operator>=(const rational &rhs) const;
|
||||
bool operator==(const rational &rhs) const;
|
||||
bool operator!=(const rational &rhs) const;
|
||||
|
||||
//Unary operators
|
||||
const rational& operator+() const { return *this; }
|
||||
rational operator-() const { return rational(r_.num, -r_.den); }
|
||||
bool operator!() const { return !r_.num; }
|
||||
|
||||
//Function: convert to double
|
||||
double toDouble() const;
|
||||
|
||||
AVRational toAVRational() const;
|
||||
|
||||
#ifdef USE_OTIO
|
||||
static rational fromRationalTime(const opentime::RationalTime &t)
|
||||
{
|
||||
// Is this the best way to do this?
|
||||
return fromDouble(t.to_seconds());
|
||||
}
|
||||
|
||||
// Convert Olive rationals to opentime rationals with the given framerate (defaults to 24)
|
||||
opentime::RationalTime toRationalTime(double framerate = 24) const;
|
||||
#endif
|
||||
|
||||
// Produce "flipped" version
|
||||
rational flipped() const;
|
||||
void flip();
|
||||
|
||||
// Returns whether the rational is valid but equal to zero or not
|
||||
//
|
||||
// A NaN is always a null, but a null is not always a NaN
|
||||
bool isNull() const { return r_.num == 0; }
|
||||
|
||||
// Returns whether this rational is not a valid number (denominator == 0)
|
||||
bool isNaN() const { return r_.den == 0; }
|
||||
|
||||
const int& numerator() const { return r_.num; }
|
||||
const int& denominator() const { return r_.den; }
|
||||
|
||||
QString toString() const;
|
||||
|
||||
friend std::ostream& operator<<(std::ostream &out, const rational &value)
|
||||
{
|
||||
out << value.r_.num << '/' << value.r_.den;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
private:
|
||||
void FixSigns();
|
||||
void Reduce();
|
||||
|
||||
AVRational r_;
|
||||
|
||||
};
|
||||
|
||||
#define RATIONAL_MIN rational(INT_MIN)
|
||||
#define RATIONAL_MAX rational(INT_MAX)
|
||||
|
||||
uint qHash(const rational& r, uint seed = 0);
|
||||
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug debug, const olive::rational& r);
|
||||
|
||||
Q_DECLARE_METATYPE(olive::rational)
|
||||
|
||||
#endif // RATIONAL_H
|
||||
@@ -1,358 +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 "timecodefunctions.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/mathematics.h>
|
||||
}
|
||||
|
||||
#include <QRegularExpression>
|
||||
#include <QtMath>
|
||||
|
||||
#include "config/config.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
QString padded(int64_t arg, int padding) {
|
||||
return QStringLiteral("%1").arg(arg, padding, 10, QChar('0'));
|
||||
}
|
||||
|
||||
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 time_dbl = time.toDouble();
|
||||
|
||||
switch (display) {
|
||||
case kTimecodeNonDropFrame:
|
||||
case kTimecodeDropFrame:
|
||||
case kTimecodeSeconds:
|
||||
{
|
||||
QString prefix;
|
||||
|
||||
if (time_dbl < 0) {
|
||||
prefix = "-";
|
||||
} else if (show_plus_if_positive) {
|
||||
prefix = "+";
|
||||
}
|
||||
|
||||
if (display == kTimecodeSeconds) {
|
||||
time_dbl = qAbs(time_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((time_dbl - static_cast<double>(total_seconds)) * 1000);
|
||||
|
||||
return QStringLiteral("%1%2:%3:%4.%5").arg(prefix,
|
||||
padded(hours, 2),
|
||||
padded(mins, 2),
|
||||
padded(secs, 2),
|
||||
padded(fraction, 3));
|
||||
} else {
|
||||
// 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 = qAbs(time_to_timestamp(time, timebase));
|
||||
|
||||
if (display == kTimecodeDropFrame && TimebaseIsDropFrame(timebase)) {
|
||||
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 QStringLiteral("%1%2:%3:%4%5%6").arg(prefix,
|
||||
padded(hours, 2),
|
||||
padded(mins, 2),
|
||||
padded(secs, 2),
|
||||
frame_token,
|
||||
padded(frames, 2));
|
||||
}
|
||||
}
|
||||
case kFrames:
|
||||
return QString::number(time_to_timestamp(time, timebase));
|
||||
case kMilliseconds:
|
||||
return QString::number(qRound(time_dbl * 1000));
|
||||
}
|
||||
|
||||
return QStringLiteral("INVALID TIMECODE MODE");
|
||||
}
|
||||
|
||||
int64_t StrToInt64EmptyTolerant(const QString &s, bool *ok)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
switch (display) {
|
||||
case kTimecodeNonDropFrame:
|
||||
case kTimecodeDropFrame:
|
||||
case kTimecodeSeconds:
|
||||
{
|
||||
QStringList timecode_split = timecode.split(QRegularExpression("(:)|(;)"));
|
||||
|
||||
const int element_count = display == kTimecodeSeconds ? 3 : 4;
|
||||
|
||||
// Remove excess tokens (we're only interested in HH:MM:SS.FF)
|
||||
while (timecode_split.size() > element_count) {
|
||||
timecode_split.removeLast();
|
||||
}
|
||||
|
||||
// For easier index calculations, ensure minimum size
|
||||
while (timecode_split.size() < element_count) {
|
||||
timecode_split.prepend(QString());
|
||||
}
|
||||
|
||||
bool negative = timecode.trimmed().startsWith('-');
|
||||
|
||||
double frame_rate = timebase.flipped().toDouble();
|
||||
int rounded_frame_rate = qRound(frame_rate);
|
||||
|
||||
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 frame_count = sec_count*rounded_frame_rate + frames;
|
||||
|
||||
if (display == kTimecodeDropFrame && TimebaseIsDropFrame(timebase)) {
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
time = timestamp_to_time(frame_count, timebase);
|
||||
}
|
||||
|
||||
if (ok) *ok = true;
|
||||
|
||||
if (negative) time = -time;
|
||||
|
||||
return time;
|
||||
}
|
||||
case kMilliseconds:
|
||||
{
|
||||
bool valid;
|
||||
double timecode_secs = timecode.toDouble(&valid);
|
||||
|
||||
if (valid) {
|
||||
// Convert milliseconds to seconds
|
||||
timecode_secs *= 0.001;
|
||||
|
||||
// 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 timestamp_to_time(ts, timebase);
|
||||
}
|
||||
}
|
||||
|
||||
err_fatal:
|
||||
if (ok) *ok = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
rational Timecode::snap_time_to_timebase(const rational &time, const rational &timebase, Rounding floor)
|
||||
{
|
||||
// Just convert to a timestamp in timebase units and back
|
||||
int64_t timestamp = time_to_timestamp(time, timebase, floor);
|
||||
|
||||
return timestamp_to_time(timestamp, timebase);
|
||||
}
|
||||
|
||||
rational Timecode::timestamp_to_time(const int64_t ×tamp, const rational &timebase)
|
||||
{
|
||||
int64_t num = int64_t(timebase.numerator()) * timestamp;
|
||||
int64_t den = timebase.denominator();
|
||||
|
||||
int num_r, den_r;
|
||||
|
||||
av_reduce(&num_r, &den_r, num, den, INT_MAX);
|
||||
|
||||
return rational(num_r, den_r);
|
||||
}
|
||||
|
||||
bool Timecode::TimebaseIsDropFrame(const rational &timebase)
|
||||
{
|
||||
return (timebase.numerator() != 1);
|
||||
}
|
||||
|
||||
QString Timecode::TimeToString(int64_t ms)
|
||||
{
|
||||
int64_t total_seconds = ms / 1000;
|
||||
int64_t ss = total_seconds % 60;
|
||||
int64_t mm = (total_seconds / 60) % 60;
|
||||
int64_t hh = total_seconds / 3600;
|
||||
|
||||
return QStringLiteral("%1:%2:%3")
|
||||
.arg(hh, 2, 10, QChar('0'))
|
||||
.arg(mm, 2, 10, QChar('0'))
|
||||
.arg(ss, 2, 10, QChar('0'));
|
||||
}
|
||||
|
||||
int64_t Timecode::time_to_timestamp(const rational &time, const rational &timebase, Rounding floor)
|
||||
{
|
||||
return time_to_timestamp(time.toDouble(), timebase, floor);
|
||||
}
|
||||
|
||||
int64_t Timecode::time_to_timestamp(const double &time, const rational &timebase, Rounding floor)
|
||||
{
|
||||
const double d = time * timebase.flipped().toDouble();
|
||||
|
||||
if (std::isnan(d)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const double eps = 0.000000000001;
|
||||
|
||||
switch (floor) {
|
||||
case kRound:
|
||||
default:
|
||||
return qRound64(d);
|
||||
case kFloor:
|
||||
if (d > qCeil(d)-eps) {
|
||||
return qCeil(d);
|
||||
} else {
|
||||
return qFloor(d);
|
||||
}
|
||||
case kCeil:
|
||||
if (d < qFloor(d)+eps) {
|
||||
return qFloor(d);
|
||||
} else {
|
||||
return qCeil(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int64_t Timecode::rescale_timestamp(const int64_t &ts, const rational &source, const rational &dest)
|
||||
{
|
||||
if (source == dest) {
|
||||
return ts;
|
||||
}
|
||||
|
||||
return av_rescale_q(ts, source.toAVRational(), dest.toAVRational());
|
||||
}
|
||||
|
||||
int64_t Timecode::rescale_timestamp_ceil(const int64_t &ts, const rational &source, const rational &dest)
|
||||
{
|
||||
if (source == dest) {
|
||||
return ts;
|
||||
}
|
||||
|
||||
return av_rescale_q_rnd(ts, source.toAVRational(), dest.toAVRational(), AV_ROUND_UP);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,80 +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 TIMECODEFUNCTIONS_H
|
||||
#define TIMECODEFUNCTIONS_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "common/rational.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
/**
|
||||
* @brief Functions for converting times/timecodes/timestamps
|
||||
*
|
||||
* Olive uses the following terminology through its code:
|
||||
*
|
||||
* `time` - time in seconds presented in a rational form
|
||||
* `timebase` - the base time unit of an audio/video stream in seconds
|
||||
* `timestamp` - an integer representation of a time in timebase units (in many cases is used like a frame number)
|
||||
* `timecode` a user-friendly string representation of a time according to Timecode::Display
|
||||
*/
|
||||
class Timecode {
|
||||
public:
|
||||
enum Display {
|
||||
kTimecodeDropFrame,
|
||||
kTimecodeNonDropFrame,
|
||||
kTimecodeSeconds,
|
||||
kFrames,
|
||||
kMilliseconds
|
||||
};
|
||||
|
||||
enum Rounding {
|
||||
kCeil,
|
||||
kFloor,
|
||||
kRound
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Convert a timestamp (according to a rational timebase) to a user-friendly string representation
|
||||
*/
|
||||
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);
|
||||
|
||||
static int64_t time_to_timestamp(const rational& time, const rational& timebase, Rounding floor = kRound);
|
||||
static int64_t time_to_timestamp(const double& time, const rational& timebase, Rounding floor = kRound);
|
||||
|
||||
static int64_t rescale_timestamp(const int64_t& ts, const rational& source, const rational& dest);
|
||||
static int64_t rescale_timestamp_ceil(const int64_t& ts, const rational& source, const rational& dest);
|
||||
|
||||
static rational timestamp_to_time(const int64_t& timestamp, const rational& timebase);
|
||||
|
||||
static bool TimebaseIsDropFrame(const rational& timebase);
|
||||
|
||||
static QString TimeToString(int64_t ms);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // TIMECODEFUNCTIONS_H
|
||||
@@ -1,393 +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 "timerange.h"
|
||||
|
||||
#include <QtMath>
|
||||
#include <utility>
|
||||
|
||||
namespace olive {
|
||||
|
||||
TimeRange::TimeRange(const rational &in, const rational &out) :
|
||||
in_(in),
|
||||
out_(out)
|
||||
{
|
||||
normalize();
|
||||
}
|
||||
|
||||
const rational &TimeRange::in() const
|
||||
{
|
||||
return in_;
|
||||
}
|
||||
|
||||
const rational &TimeRange::out() const
|
||||
{
|
||||
return out_;
|
||||
}
|
||||
|
||||
const rational &TimeRange::length() const
|
||||
{
|
||||
Q_ASSERT(!length_.isNaN());
|
||||
return length_;
|
||||
}
|
||||
|
||||
void TimeRange::set_in(const rational &in)
|
||||
{
|
||||
in_ = in;
|
||||
normalize();
|
||||
}
|
||||
|
||||
void TimeRange::set_out(const rational &out)
|
||||
{
|
||||
out_ = out;
|
||||
normalize();
|
||||
}
|
||||
|
||||
void TimeRange::set_range(const rational &in, const rational &out)
|
||||
{
|
||||
in_ = in;
|
||||
out_ = out;
|
||||
normalize();
|
||||
}
|
||||
|
||||
bool TimeRange::operator==(const TimeRange &r) const
|
||||
{
|
||||
return in() == r.in() && out() == r.out();
|
||||
}
|
||||
|
||||
bool TimeRange::operator!=(const TimeRange &r) const
|
||||
{
|
||||
return in() != r.in() || out() != r.out();
|
||||
}
|
||||
|
||||
bool TimeRange::OverlapsWith(const TimeRange &a, bool in_inclusive, bool out_inclusive) const
|
||||
{
|
||||
bool doesnt_overlap_in = (in_inclusive) ? (a.out() < in()) : (a.out() <= in());
|
||||
|
||||
bool doesnt_overlap_out = (out_inclusive) ? (a.in() > out()) : (a.in() >= out());
|
||||
|
||||
return !doesnt_overlap_in && !doesnt_overlap_out;
|
||||
}
|
||||
|
||||
TimeRange TimeRange::Combined(const TimeRange &a) const
|
||||
{
|
||||
return Combine(a, *this);
|
||||
}
|
||||
|
||||
bool TimeRange::Contains(const TimeRange &compare, bool in_inclusive, bool out_inclusive) const
|
||||
{
|
||||
bool contains_in = (in_inclusive) ? (compare.in() >= in()) : (compare.in() > in());
|
||||
|
||||
bool contains_out = (out_inclusive) ? (compare.out() <= out()) : (compare.out() < out());
|
||||
|
||||
return contains_in && contains_out;
|
||||
}
|
||||
|
||||
bool TimeRange::Contains(const rational &r) const
|
||||
{
|
||||
return r >= in_ && r < out_;
|
||||
}
|
||||
|
||||
TimeRange TimeRange::Combine(const TimeRange &a, const TimeRange &b)
|
||||
{
|
||||
return TimeRange(qMin(a.in(), b.in()),
|
||||
qMax(a.out(), b.out()));
|
||||
}
|
||||
|
||||
TimeRange TimeRange::Intersected(const TimeRange &a) const
|
||||
{
|
||||
return Intersect(a, *this);
|
||||
}
|
||||
|
||||
TimeRange TimeRange::Intersect(const TimeRange &a, const TimeRange &b)
|
||||
{
|
||||
return TimeRange(qMax(a.in(), b.in()),
|
||||
qMin(a.out(), b.out()));
|
||||
}
|
||||
|
||||
TimeRange TimeRange::operator+(const rational &rhs) const
|
||||
{
|
||||
TimeRange answer(*this);
|
||||
answer += rhs;
|
||||
return answer;
|
||||
}
|
||||
|
||||
TimeRange TimeRange::operator-(const rational &rhs) const
|
||||
{
|
||||
TimeRange answer(*this);
|
||||
answer -= rhs;
|
||||
return answer;
|
||||
}
|
||||
|
||||
const TimeRange &TimeRange::operator+=(const rational &rhs)
|
||||
{
|
||||
set_range(in_ + rhs, out_ + rhs);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
const TimeRange &TimeRange::operator-=(const rational &rhs)
|
||||
{
|
||||
set_range(in_ - rhs, out_ - rhs);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::list<TimeRange> TimeRange::Split(const int &chunk_size) const
|
||||
{
|
||||
std::list<TimeRange> split_ranges;
|
||||
|
||||
int start_time = qFloor(this->in().toDouble() / static_cast<double>(chunk_size)) * chunk_size;
|
||||
int end_time = qCeil(this->out().toDouble() / static_cast<double>(chunk_size)) * chunk_size;
|
||||
|
||||
for (int i=start_time; i<end_time; i+=chunk_size) {
|
||||
split_ranges.push_back(TimeRange(qMax(this->in(), rational(i)),
|
||||
qMin(this->out(), rational(i + chunk_size))));
|
||||
}
|
||||
|
||||
return split_ranges;
|
||||
}
|
||||
|
||||
void TimeRange::normalize()
|
||||
{
|
||||
// If `out` is earlier than `in`, swap them
|
||||
if (out_ < in_)
|
||||
{
|
||||
std::swap(out_, in_);
|
||||
}
|
||||
|
||||
// Calculate length
|
||||
if (out_ == RATIONAL_MIN || out_ == RATIONAL_MAX || in_ == RATIONAL_MIN || in_ == RATIONAL_MAX) {
|
||||
length_ = rational::NaN;
|
||||
} else {
|
||||
length_ = out_ - in_;
|
||||
}
|
||||
}
|
||||
|
||||
void TimeRangeList::insert(const TimeRangeList &list_to_add)
|
||||
{
|
||||
for (auto it=list_to_add.cbegin(); it!=list_to_add.cend(); it++) {
|
||||
insert(*it);
|
||||
}
|
||||
}
|
||||
|
||||
void TimeRangeList::insert(TimeRange range_to_add)
|
||||
{
|
||||
// See if list contains this range
|
||||
if (contains(range_to_add)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Does not contain range, so we'll almost certainly be adding it in some way
|
||||
for (int i=0;i<size();i++) {
|
||||
const TimeRange& compare = array_.at(i);
|
||||
|
||||
if (compare.OverlapsWith(range_to_add)) {
|
||||
range_to_add = TimeRange::Combine(range_to_add, compare);
|
||||
array_.removeAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
array_.append(range_to_add);
|
||||
}
|
||||
|
||||
void TimeRangeList::remove(const TimeRange &remove)
|
||||
{
|
||||
util_remove(&array_, remove);
|
||||
}
|
||||
|
||||
void TimeRangeList::remove(const TimeRangeList &list)
|
||||
{
|
||||
for (const TimeRange &r : list) {
|
||||
remove(r);
|
||||
}
|
||||
}
|
||||
|
||||
bool TimeRangeList::contains(const TimeRange &range, bool in_inclusive, bool out_inclusive) const
|
||||
{
|
||||
for (int i=0;i<size();i++) {
|
||||
if (array_.at(i).Contains(range, in_inclusive, out_inclusive)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void TimeRangeList::shift(const rational &diff)
|
||||
{
|
||||
for (int i=0; i<array_.size(); i++) {
|
||||
array_[i] += diff;
|
||||
}
|
||||
}
|
||||
|
||||
void TimeRangeList::trim_in(const rational &diff)
|
||||
{
|
||||
// Re-do list since we want to handle overlaps
|
||||
TimeRangeList temp = *this;
|
||||
|
||||
clear();
|
||||
|
||||
foreach (TimeRange r, temp) {
|
||||
r.set_in(r.in() + diff);
|
||||
insert(r);
|
||||
}
|
||||
}
|
||||
|
||||
void TimeRangeList::trim_out(const rational &diff)
|
||||
{
|
||||
// Re-do list since we want to handle overlaps
|
||||
TimeRangeList temp = *this;
|
||||
|
||||
clear();
|
||||
|
||||
foreach (TimeRange r, temp) {
|
||||
r.set_out(r.out() + diff);
|
||||
insert(r);
|
||||
}
|
||||
}
|
||||
|
||||
TimeRangeList TimeRangeList::Intersects(const TimeRange &range) const
|
||||
{
|
||||
TimeRangeList intersect_list;
|
||||
|
||||
for (int i=0;i<size();i++) {
|
||||
const TimeRange& compare = array_.at(i);
|
||||
|
||||
if (compare.out() <= range.in() || compare.in() >= range.out()) {
|
||||
// No intersect
|
||||
continue;
|
||||
} else {
|
||||
// Crop the time range to the range and add it to the list
|
||||
TimeRange cropped(qMax(range.in(), compare.in()),
|
||||
qMin(range.out(), compare.out()));
|
||||
|
||||
intersect_list.insert(cropped);
|
||||
}
|
||||
}
|
||||
|
||||
return intersect_list;
|
||||
}
|
||||
|
||||
uint qHash(const TimeRange &r, uint seed)
|
||||
{
|
||||
return qHash(r.in(), seed) ^ qHash(r.out(), seed);
|
||||
}
|
||||
|
||||
TimeRangeListFrameIterator::TimeRangeListFrameIterator() :
|
||||
TimeRangeListFrameIterator(TimeRangeList(), rational::NaN)
|
||||
{
|
||||
}
|
||||
|
||||
TimeRangeListFrameIterator::TimeRangeListFrameIterator(const TimeRangeList &list, const rational &timebase) :
|
||||
list_(list),
|
||||
timebase_(timebase),
|
||||
range_index_(-1),
|
||||
size_(-1),
|
||||
frame_index_(0),
|
||||
custom_range_(false)
|
||||
{
|
||||
if (!list_.isEmpty() && timebase_.isNull()) {
|
||||
qCritical() << "TimeRangeListFrameIterator created with null timebase but non-empty list, this will likely lead to infinite loops";
|
||||
}
|
||||
|
||||
UpdateIndexIfNecessary();
|
||||
}
|
||||
|
||||
rational TimeRangeListFrameIterator::Snap(const rational &r) const
|
||||
{
|
||||
return Timecode::snap_time_to_timebase(r, timebase_, Timecode::kFloor);
|
||||
}
|
||||
|
||||
bool TimeRangeListFrameIterator::GetNext(rational *out)
|
||||
{
|
||||
if (!HasNext()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Output current value
|
||||
*out = current_;
|
||||
|
||||
// Determine next value by adding timebase
|
||||
current_ += timebase_;
|
||||
|
||||
// If this time is outside the current range, jump to the next one
|
||||
UpdateIndexIfNecessary();
|
||||
|
||||
// Increment frame index
|
||||
frame_index_++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TimeRangeListFrameIterator::HasNext() const
|
||||
{
|
||||
return range_index_ < list_.size();
|
||||
}
|
||||
|
||||
int TimeRangeListFrameIterator::size()
|
||||
{
|
||||
if (size_ == -1) {
|
||||
// Size isn't calculated automatically for optimization, so we'll calculate it now
|
||||
size_ = 0;
|
||||
|
||||
foreach (const TimeRange &range, list_) {
|
||||
rational start = Snap(range.in());
|
||||
rational end = Timecode::snap_time_to_timebase(range.out(), timebase_, Timecode::kFloor);
|
||||
|
||||
if (end == range.out()) {
|
||||
end -= timebase_;
|
||||
}
|
||||
|
||||
int64_t start_ts = Timecode::time_to_timestamp(start, timebase_);
|
||||
int64_t end_ts = Timecode::time_to_timestamp(end, timebase_);
|
||||
|
||||
size_ += 1 + (end_ts - start_ts);
|
||||
}
|
||||
}
|
||||
|
||||
return size_;
|
||||
}
|
||||
|
||||
void TimeRangeListFrameIterator::UpdateIndexIfNecessary()
|
||||
{
|
||||
while (range_index_ < list_.size() && (range_index_ == -1 || current_ >= list_.at(range_index_).out())) {
|
||||
range_index_++;
|
||||
|
||||
if (range_index_ < list_.size()) {
|
||||
current_ = Snap(list_.at(range_index_).in());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug debug, const olive::TimeRange &r)
|
||||
{
|
||||
debug.nospace() << r.in().toDouble() << " - " << r.out().toDouble();
|
||||
return debug.space();
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug debug, const olive::TimeRangeList &r)
|
||||
{
|
||||
debug << r.internal_array();
|
||||
return debug.space();
|
||||
}
|
||||
@@ -1,298 +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 TIMERANGE_H
|
||||
#define TIMERANGE_H
|
||||
|
||||
#include "rational.h"
|
||||
#include "timecodefunctions.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class TimeRange {
|
||||
public:
|
||||
TimeRange() = default;
|
||||
TimeRange(const rational& in, const rational& out);
|
||||
|
||||
const rational& in() const;
|
||||
const rational& out() const;
|
||||
const rational& length() const;
|
||||
|
||||
void set_in(const rational& in);
|
||||
void set_out(const rational& out);
|
||||
void set_range(const rational& in, const rational& out);
|
||||
|
||||
bool operator==(const TimeRange& r) const;
|
||||
bool operator!=(const TimeRange& r) const;
|
||||
|
||||
bool OverlapsWith(const TimeRange& a, bool in_inclusive = true, bool out_inclusive = true) const;
|
||||
bool Contains(const TimeRange& a, bool in_inclusive = true, bool out_inclusive = true) const;
|
||||
bool Contains(const rational& r) const;
|
||||
|
||||
TimeRange Combined(const TimeRange& a) const;
|
||||
static TimeRange Combine(const TimeRange &a, const TimeRange &b);
|
||||
TimeRange Intersected(const TimeRange& a) const;
|
||||
static TimeRange Intersect(const TimeRange &a, const TimeRange &b);
|
||||
|
||||
TimeRange operator+(const rational& rhs) const;
|
||||
TimeRange operator-(const rational& rhs) const;
|
||||
|
||||
const TimeRange& operator+=(const rational &rhs);
|
||||
const TimeRange& operator-=(const rational &rhs);
|
||||
|
||||
std::list<TimeRange> Split(const int &chunk_size) const;
|
||||
|
||||
private:
|
||||
void normalize();
|
||||
|
||||
rational in_;
|
||||
rational out_;
|
||||
rational length_;
|
||||
|
||||
};
|
||||
|
||||
class TimeRangeList {
|
||||
public:
|
||||
TimeRangeList() = default;
|
||||
|
||||
TimeRangeList(std::initializer_list<TimeRange> r) :
|
||||
array_(r)
|
||||
{
|
||||
}
|
||||
|
||||
void insert(const TimeRangeList &list_to_add);
|
||||
void insert(TimeRange range_to_add);
|
||||
|
||||
void remove(const TimeRange& remove);
|
||||
void remove(const TimeRangeList &list);
|
||||
|
||||
template <typename T>
|
||||
static void util_remove(QVector<T> *list, const TimeRange &remove)
|
||||
{
|
||||
int sz = list->size();
|
||||
|
||||
for (int i=0;i<sz;i++) {
|
||||
T& compare = (*list)[i];
|
||||
|
||||
if (remove.Contains(compare)) {
|
||||
// This element is entirely encompassed in this range, remove it
|
||||
list->removeAt(i);
|
||||
i--;
|
||||
sz--;
|
||||
} else if (compare.Contains(remove, false, false)) {
|
||||
// The remove range is within this element, only choice is to split the element into two
|
||||
T new_range = compare;
|
||||
new_range.set_in(remove.out());
|
||||
compare.set_out(remove.in());
|
||||
list->append(new_range);
|
||||
break;
|
||||
} else if (compare.in() < remove.in() && compare.out() > remove.in()) {
|
||||
// This element's out point overlaps the range's in, we'll trim it
|
||||
compare.set_out(remove.in());
|
||||
} else if (compare.in() < remove.out() && compare.out() > remove.out()) {
|
||||
// This element's in point overlaps the range's out, we'll trim it
|
||||
compare.set_in(remove.out());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool contains(const TimeRange& range, bool in_inclusive = true, bool out_inclusive = true) const;
|
||||
|
||||
bool contains(const rational &r) const
|
||||
{
|
||||
for (const TimeRange &range : array_) {
|
||||
if (range.Contains(r)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OverlapsWith(const TimeRange& r, bool in_inclusive = true, bool out_inclusive = true) const
|
||||
{
|
||||
for (const TimeRange &range : array_) {
|
||||
if (range.OverlapsWith(r, in_inclusive, out_inclusive)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isEmpty() const
|
||||
{
|
||||
return array_.isEmpty();
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
array_.clear();
|
||||
}
|
||||
|
||||
int size() const
|
||||
{
|
||||
return array_.size();
|
||||
}
|
||||
|
||||
void shift(const rational& diff);
|
||||
|
||||
void trim_in(const rational& diff);
|
||||
|
||||
void trim_out(const rational& diff);
|
||||
|
||||
TimeRangeList Intersects(const TimeRange& range) const;
|
||||
|
||||
using const_iterator = QVector<TimeRange>::const_iterator;
|
||||
|
||||
const_iterator begin() const
|
||||
{
|
||||
return array_.constBegin();
|
||||
}
|
||||
|
||||
const_iterator end() const
|
||||
{
|
||||
return array_.constEnd();
|
||||
}
|
||||
|
||||
const_iterator cbegin() const
|
||||
{
|
||||
return begin();
|
||||
}
|
||||
|
||||
const_iterator cend() const
|
||||
{
|
||||
return end();
|
||||
}
|
||||
|
||||
const TimeRange& first() const
|
||||
{
|
||||
return array_.first();
|
||||
}
|
||||
|
||||
const TimeRange& last() const
|
||||
{
|
||||
return array_.last();
|
||||
}
|
||||
|
||||
const TimeRange& at(int index) const
|
||||
{
|
||||
return array_.at(index);
|
||||
}
|
||||
|
||||
const QVector<TimeRange>& internal_array() const
|
||||
{
|
||||
return array_;
|
||||
}
|
||||
|
||||
bool operator==(const TimeRangeList &rhs) const
|
||||
{
|
||||
return array_ == rhs.array_;
|
||||
}
|
||||
|
||||
private:
|
||||
QVector<TimeRange> array_;
|
||||
|
||||
};
|
||||
|
||||
class TimeRangeListFrameIterator
|
||||
{
|
||||
public:
|
||||
TimeRangeListFrameIterator();
|
||||
TimeRangeListFrameIterator(const TimeRangeList &list, const rational &timebase);
|
||||
|
||||
rational Snap(const rational &r) const;
|
||||
|
||||
bool GetNext(rational *out);
|
||||
|
||||
bool HasNext() const;
|
||||
|
||||
QVector<rational> ToVector() const
|
||||
{
|
||||
TimeRangeListFrameIterator copy(list_, timebase_);
|
||||
QVector<rational> times;
|
||||
rational r;
|
||||
while (copy.GetNext(&r)) {
|
||||
times.append(r);
|
||||
}
|
||||
return times;
|
||||
}
|
||||
|
||||
int size();
|
||||
|
||||
void reset()
|
||||
{
|
||||
*this = TimeRangeListFrameIterator();
|
||||
}
|
||||
|
||||
void insert(const TimeRange &range)
|
||||
{
|
||||
list_.insert(range);
|
||||
}
|
||||
|
||||
void insert(const TimeRangeList &list)
|
||||
{
|
||||
list_.insert(list);
|
||||
}
|
||||
|
||||
bool IsCustomRange() const
|
||||
{
|
||||
return custom_range_;
|
||||
}
|
||||
|
||||
void SetCustomRange(bool e)
|
||||
{
|
||||
custom_range_ = e;
|
||||
}
|
||||
|
||||
int frame_index() const
|
||||
{
|
||||
return frame_index_;
|
||||
}
|
||||
|
||||
private:
|
||||
void UpdateIndexIfNecessary();
|
||||
|
||||
TimeRangeList list_;
|
||||
|
||||
rational timebase_;
|
||||
|
||||
rational current_;
|
||||
|
||||
int range_index_;
|
||||
|
||||
int size_;
|
||||
|
||||
int frame_index_;
|
||||
|
||||
bool custom_range_;
|
||||
|
||||
};
|
||||
|
||||
uint qHash(const TimeRange& r, uint seed = 0);
|
||||
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug debug, const olive::TimeRange& r);
|
||||
QDebug operator<<(QDebug debug, const olive::TimeRangeList& r);
|
||||
|
||||
Q_DECLARE_METATYPE(olive::TimeRange)
|
||||
|
||||
#endif // TIMERANGE_H
|
||||
Reference in New Issue
Block a user