submodule: change core into nomarl folder, and move KDockWidgets into third_party.
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2023 Olive Studios LLC
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 "render/audioparams.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace olive::core
|
||||
{
|
||||
|
||||
const std::vector<int> AudioParams::kSupportedSampleRates = {
|
||||
8000, // 8000 Hz
|
||||
11025, // 11025 Hz
|
||||
16000, // 16000 Hz
|
||||
22050, // 22050 Hz
|
||||
24000, // 24000 Hz
|
||||
32000, // 32000 Hz
|
||||
44100, // 44100 Hz
|
||||
48000, // 48000 Hz
|
||||
88200, // 88200 Hz
|
||||
96000 // 96000 Hz
|
||||
};
|
||||
|
||||
const std::vector<uint64_t> AudioParams::kSupportedChannelLayouts = {
|
||||
AV_CH_LAYOUT_MONO, AV_CH_LAYOUT_STEREO, AV_CH_LAYOUT_2_1,
|
||||
AV_CH_LAYOUT_5POINT1, AV_CH_LAYOUT_7POINT1
|
||||
};
|
||||
|
||||
bool AudioParams::operator==(const AudioParams &other) const
|
||||
{
|
||||
return format() == other.format() && sample_rate() == other.sample_rate() &&
|
||||
time_base() == other.time_base() &&
|
||||
av_channel_layout_compare(&channel_layout_,
|
||||
&other.channel_layout()) == 0;
|
||||
}
|
||||
|
||||
bool AudioParams::operator!=(const AudioParams &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
int64_t AudioParams::time_to_bytes(const double &time) const
|
||||
{
|
||||
return time_to_bytes_per_channel(time) * channel_count();
|
||||
}
|
||||
|
||||
int64_t AudioParams::time_to_bytes(const rational &time) const
|
||||
{
|
||||
return time_to_bytes(time.toDouble());
|
||||
}
|
||||
|
||||
int64_t AudioParams::time_to_bytes_per_channel(const double &time) const
|
||||
{
|
||||
assert(is_valid());
|
||||
|
||||
return int64_t(time_to_samples(time)) * bytes_per_sample_per_channel();
|
||||
}
|
||||
|
||||
int64_t AudioParams::time_to_bytes_per_channel(const rational &time) const
|
||||
{
|
||||
return time_to_bytes_per_channel(time.toDouble());
|
||||
}
|
||||
|
||||
int64_t AudioParams::time_to_samples(const double &time) const
|
||||
{
|
||||
assert(is_valid());
|
||||
|
||||
return std::round(double(sample_rate()) * time);
|
||||
}
|
||||
|
||||
int64_t AudioParams::time_to_samples(const rational &time) const
|
||||
{
|
||||
return time_to_samples(time.toDouble());
|
||||
}
|
||||
|
||||
int64_t AudioParams::samples_to_bytes(const int64_t &samples) const
|
||||
{
|
||||
assert(is_valid());
|
||||
|
||||
return samples_to_bytes_per_channel(samples) * channel_count();
|
||||
}
|
||||
|
||||
int64_t AudioParams::samples_to_bytes_per_channel(const int64_t &samples) const
|
||||
{
|
||||
assert(is_valid());
|
||||
|
||||
return samples * bytes_per_sample_per_channel();
|
||||
}
|
||||
|
||||
rational AudioParams::samples_to_time(const int64_t &samples) const
|
||||
{
|
||||
return sample_rate_as_time_base() * samples;
|
||||
}
|
||||
|
||||
int64_t AudioParams::bytes_to_samples(const int64_t &bytes) const
|
||||
{
|
||||
assert(is_valid());
|
||||
|
||||
return bytes / (channel_count() * bytes_per_sample_per_channel());
|
||||
}
|
||||
|
||||
rational AudioParams::bytes_to_time(const int64_t &bytes) const
|
||||
{
|
||||
assert(is_valid());
|
||||
|
||||
return samples_to_time(bytes_to_samples(bytes));
|
||||
}
|
||||
|
||||
rational AudioParams::bytes_per_channel_to_time(const int64_t &bytes) const
|
||||
{
|
||||
assert(is_valid());
|
||||
|
||||
return samples_to_time(bytes_to_samples(bytes * channel_count()));
|
||||
}
|
||||
|
||||
int AudioParams::channel_count() const
|
||||
{
|
||||
return channel_count_;
|
||||
}
|
||||
|
||||
int AudioParams::bytes_per_sample_per_channel() const
|
||||
{
|
||||
return format_.byte_count();
|
||||
}
|
||||
|
||||
int AudioParams::bits_per_sample() const
|
||||
{
|
||||
return bytes_per_sample_per_channel() * 8;
|
||||
}
|
||||
|
||||
bool AudioParams::is_valid() const
|
||||
{
|
||||
return (!time_base().isNull() &&
|
||||
av_channel_layout_check(&channel_layout_) &&
|
||||
format_ > SampleFormat::INVALID && format_ < SampleFormat::COUNT);
|
||||
}
|
||||
|
||||
void AudioParams::calculate_channel_count()
|
||||
{
|
||||
channel_count_ = channel_layout().nb_channels;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copy constructor - deep copies AVChannelLayout
|
||||
*
|
||||
* This is critical because AVChannelLayout::u.map is a pointer for custom
|
||||
* channel layouts. Default copy would share the pointer, leading to double-free.
|
||||
*
|
||||
* The member initializer list initializes channel_layout_ to zero ({}),
|
||||
* then av_channel_layout_copy performs the deep copy from other.
|
||||
*
|
||||
* @param other Source AudioParams to copy from
|
||||
*/
|
||||
AudioParams::AudioParams(const AudioParams &other)
|
||||
: sample_rate_(other.sample_rate_)
|
||||
, channel_layout_{} // Zero-initialize before FFmpeg copy
|
||||
, channel_count_(other.channel_count_)
|
||||
, format_(other.format_)
|
||||
, enabled_(other.enabled_)
|
||||
, stream_index_(other.stream_index_)
|
||||
, duration_(other.duration_)
|
||||
, timebase_(other.timebase_)
|
||||
{
|
||||
// Deep copy AVChannelLayout using FFmpeg API
|
||||
// This handles all layout types: unspecified, native (mask), and custom (map)
|
||||
av_channel_layout_copy(&channel_layout_, &other.channel_layout_);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copy assignment - cleans up existing layout before copying
|
||||
*
|
||||
* CRITICAL ORDER OF OPERATIONS:
|
||||
* 1. Check for self-assignment (this != &other)
|
||||
* 2. Copy all scalar members
|
||||
* 3. Uninitialize current channel_layout_ (frees old u.map if present)
|
||||
* 4. Deep copy from other's channel_layout_
|
||||
*
|
||||
* Step 3 must happen before step 4 to avoid memory leaks. If we copied first,
|
||||
* we'd lose the pointer to the old u.map that needs to be freed.
|
||||
*
|
||||
* @param other Source AudioParams to copy from
|
||||
* @return Reference to this for chaining
|
||||
*/
|
||||
AudioParams &AudioParams::operator=(const AudioParams &other)
|
||||
{
|
||||
if (this != &other) {
|
||||
// Copy scalar members first (no dependencies)
|
||||
sample_rate_ = other.sample_rate_;
|
||||
format_ = other.format_;
|
||||
channel_count_ = other.channel_count_;
|
||||
enabled_ = other.enabled_;
|
||||
stream_index_ = other.stream_index_;
|
||||
duration_ = other.duration_;
|
||||
timebase_ = other.timebase_;
|
||||
|
||||
// Free current layout's dynamic memory (u.map if custom)
|
||||
av_channel_layout_uninit(&channel_layout_);
|
||||
|
||||
// Deep copy from other (includes allocating new u.map if needed)
|
||||
av_channel_layout_copy(&channel_layout_, &other.channel_layout_);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destructor - frees AVChannelLayout dynamic memory
|
||||
*
|
||||
* av_channel_layout_uninit() handles all cases:
|
||||
* - Unspecified/Native: No-op (no dynamic memory)
|
||||
* - Custom: Frees u.map array
|
||||
*
|
||||
* Without this, custom channel layouts would leak memory.
|
||||
*/
|
||||
AudioParams::~AudioParams()
|
||||
{
|
||||
av_channel_layout_uninit(&channel_layout_);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2023 Olive Studios LLC
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 "render/samplebuffer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <assert.h>
|
||||
#include <cmath>
|
||||
#include <string.h>
|
||||
|
||||
#include "util/cpuoptimize.h"
|
||||
#include "util/log.h"
|
||||
|
||||
namespace olive::core
|
||||
{
|
||||
|
||||
SampleBuffer::SampleBuffer()
|
||||
: sample_count_per_channel_(0)
|
||||
{
|
||||
}
|
||||
|
||||
SampleBuffer::SampleBuffer(const AudioParams &audio_params,
|
||||
const rational &length)
|
||||
: audio_params_(audio_params)
|
||||
{
|
||||
sample_count_per_channel_ = audio_params_.time_to_samples(length);
|
||||
allocate();
|
||||
}
|
||||
|
||||
SampleBuffer::SampleBuffer(const AudioParams &audio_params,
|
||||
size_t samples_per_channel)
|
||||
: audio_params_(audio_params)
|
||||
, sample_count_per_channel_(samples_per_channel)
|
||||
{
|
||||
allocate();
|
||||
}
|
||||
|
||||
SampleBuffer SampleBuffer::rip_channel(int channel) const
|
||||
{
|
||||
AudioParams p = this->audio_params_;
|
||||
AVChannelLayout layout;
|
||||
av_channel_layout_from_mask(&layout, AV_CH_LAYOUT_MONO);
|
||||
p.set_channel_layout(layout);
|
||||
av_channel_layout_uninit(&layout);
|
||||
|
||||
SampleBuffer b(p, this->sample_count_per_channel_);
|
||||
b.fast_set(*this, 0, channel);
|
||||
return b;
|
||||
}
|
||||
|
||||
std::vector<float> SampleBuffer::rip_channel_vector(int channel) const
|
||||
{
|
||||
return data_.at(channel);
|
||||
}
|
||||
|
||||
const AudioParams &SampleBuffer::audio_params() const
|
||||
{
|
||||
return audio_params_;
|
||||
}
|
||||
|
||||
void SampleBuffer::set_audio_params(const AudioParams ¶ms)
|
||||
{
|
||||
if (is_allocated()) {
|
||||
Log::Warning() << "Tried to set parameters on allocated sample buffer";
|
||||
return;
|
||||
}
|
||||
|
||||
audio_params_ = params;
|
||||
}
|
||||
|
||||
void SampleBuffer::set_sample_count(const size_t &sample_count)
|
||||
{
|
||||
if (is_allocated()) {
|
||||
Log::Warning()
|
||||
<< "Tried to set sample count on allocated sample buffer";
|
||||
return;
|
||||
}
|
||||
|
||||
sample_count_per_channel_ = sample_count;
|
||||
}
|
||||
|
||||
void SampleBuffer::allocate()
|
||||
{
|
||||
if (!audio_params_.is_valid()) {
|
||||
Log::Warning()
|
||||
<< "Tried to allocate sample buffer with invalid audio parameters";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sample_count_per_channel_) {
|
||||
Log::Warning()
|
||||
<< "Tried to allocate sample buffer with zero sample count";
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_allocated()) {
|
||||
Log::Warning() << "Tried to allocate already allocated sample buffer";
|
||||
return;
|
||||
}
|
||||
|
||||
data_.resize(audio_params_.channel_count());
|
||||
for (int i = 0; i < audio_params_.channel_count(); i++) {
|
||||
data_[i].resize(sample_count_per_channel_);
|
||||
}
|
||||
}
|
||||
|
||||
void SampleBuffer::destroy()
|
||||
{
|
||||
data_.clear();
|
||||
}
|
||||
|
||||
void SampleBuffer::reverse()
|
||||
{
|
||||
if (!is_allocated()) {
|
||||
Log::Warning() << "Tried to reverse an unallocated sample buffer";
|
||||
return;
|
||||
}
|
||||
|
||||
size_t half_nb_sample = sample_count_per_channel_ / 2;
|
||||
|
||||
for (size_t i = 0; i < half_nb_sample; i++) {
|
||||
size_t opposite_ind = sample_count_per_channel_ - i - 1;
|
||||
|
||||
for (int j = 0; j < audio_params_.channel_count(); j++) {
|
||||
std::swap(data_[j][i], data_[j][opposite_ind]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SampleBuffer::speed(double speed)
|
||||
{
|
||||
if (!is_allocated()) {
|
||||
Log::Warning() << "Tried to speed an unallocated sample buffer";
|
||||
return;
|
||||
}
|
||||
|
||||
sample_count_per_channel_ =
|
||||
std::llround(static_cast<double>(sample_count_per_channel_) / speed);
|
||||
|
||||
std::vector<std::vector<float>> output_data;
|
||||
|
||||
output_data.resize(audio_params_.channel_count());
|
||||
for (int i = 0; i < audio_params_.channel_count(); i++) {
|
||||
output_data[i].resize(sample_count_per_channel_);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < sample_count_per_channel_; i++) {
|
||||
size_t input_index = std::floor(static_cast<double>(i) * speed);
|
||||
|
||||
for (int j = 0; j < audio_params_.channel_count(); j++) {
|
||||
output_data[j][i] = data_[j][input_index];
|
||||
}
|
||||
}
|
||||
|
||||
data_ = output_data;
|
||||
}
|
||||
|
||||
void SampleBuffer::transform_volume(float f)
|
||||
{
|
||||
transform_volume(f, this, this);
|
||||
}
|
||||
|
||||
void SampleBuffer::transform_volume_for_channel(int channel, float volume)
|
||||
{
|
||||
transform_volume_for_channel(channel, volume, this, this);
|
||||
}
|
||||
|
||||
void SampleBuffer::transform_volume(float f, const SampleBuffer *input,
|
||||
SampleBuffer *output)
|
||||
{
|
||||
assert(input->channel_count() == output->channel_count());
|
||||
assert(input->sample_count_per_channel_ ==
|
||||
output->sample_count_per_channel_);
|
||||
|
||||
for (int i = 0; i < input->audio_params().channel_count(); i++) {
|
||||
transform_volume_for_channel(i, f, input, output);
|
||||
}
|
||||
}
|
||||
|
||||
void SampleBuffer::transform_volume_for_channel(int channel, float volume,
|
||||
const SampleBuffer *input,
|
||||
SampleBuffer *output)
|
||||
{
|
||||
const float *cdat = input->data_[channel].data();
|
||||
float *odat = output->data_[channel].data();
|
||||
size_t unopt_start = 0;
|
||||
|
||||
assert(input->channel_count() == output->channel_count());
|
||||
assert(input->sample_count_per_channel_ ==
|
||||
output->sample_count_per_channel_);
|
||||
|
||||
#if defined(OLIVE_PROCESSOR_X86) || defined(OLIVE_PROCESSOR_ARM)
|
||||
__m128 mult = _mm_load1_ps(&volume);
|
||||
unopt_start = (input->sample_count_per_channel_ / 4) * 4;
|
||||
for (size_t j = 0; j < unopt_start; j += 4) {
|
||||
const float *in_here = cdat + j;
|
||||
float *out_here = odat + j;
|
||||
__m128 samples = _mm_loadu_ps(in_here);
|
||||
__m128 multiplied = _mm_mul_ps(samples, mult);
|
||||
_mm_storeu_ps(out_here, multiplied);
|
||||
}
|
||||
#endif
|
||||
|
||||
for (size_t j = unopt_start; j < input->sample_count_per_channel_; j++) {
|
||||
odat[j] = cdat[j] * volume;
|
||||
}
|
||||
}
|
||||
|
||||
void SampleBuffer::transform_volume_for_sample(size_t sample_index,
|
||||
float volume)
|
||||
{
|
||||
for (int i = 0; i < audio_params().channel_count(); i++) {
|
||||
transform_volume_for_sample_on_channel(sample_index, i, volume);
|
||||
}
|
||||
}
|
||||
|
||||
void SampleBuffer::transform_volume_for_sample_on_channel(size_t sample_index,
|
||||
int channel,
|
||||
float volume)
|
||||
{
|
||||
data_[channel][sample_index] *= volume;
|
||||
}
|
||||
|
||||
void SampleBuffer::clamp()
|
||||
{
|
||||
for (int i = 0; i < channel_count(); i++) {
|
||||
clamp_channel(i);
|
||||
}
|
||||
}
|
||||
|
||||
void SampleBuffer::silence()
|
||||
{
|
||||
silence(0, sample_count_per_channel_);
|
||||
}
|
||||
|
||||
void SampleBuffer::silence(size_t start_sample, size_t end_sample)
|
||||
{
|
||||
silence_bytes(start_sample * sizeof(float), end_sample * sizeof(float));
|
||||
}
|
||||
|
||||
void SampleBuffer::silence_bytes(size_t start_byte, size_t end_byte)
|
||||
{
|
||||
if (!is_allocated()) {
|
||||
Log::Warning() << "Tried to fill an unallocated sample buffer";
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < audio_params().channel_count(); i++) {
|
||||
memset(reinterpret_cast<char *>(data_[i].data()) + start_byte, 0,
|
||||
end_byte - start_byte);
|
||||
}
|
||||
}
|
||||
|
||||
void SampleBuffer::set(int channel, const float *data, size_t sample_offset,
|
||||
size_t sample_length)
|
||||
{
|
||||
if (!is_allocated()) {
|
||||
Log::Warning() << "Tried to fill an unallocated sample buffer";
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(&data_[channel].data()[sample_offset], data,
|
||||
sizeof(float) * sample_length);
|
||||
}
|
||||
|
||||
void SampleBuffer::fast_set(const SampleBuffer &other, int to, int from)
|
||||
{
|
||||
if (from == -1) {
|
||||
from = to;
|
||||
}
|
||||
|
||||
data_[to] = other.data_[from];
|
||||
}
|
||||
|
||||
void SampleBuffer::clamp_channel(int channel)
|
||||
{
|
||||
const float min = -1.0f;
|
||||
const float max = 1.0f;
|
||||
|
||||
float *cdat = data_[channel].data();
|
||||
size_t unopt_start = 0;
|
||||
|
||||
#if defined(OLIVE_PROCESSOR_X86) || defined(OLIVE_PROCESSOR_ARM)
|
||||
__m128 min_sse = _mm_load1_ps(&min);
|
||||
__m128 max_sse = _mm_load1_ps(&max);
|
||||
|
||||
unopt_start = (sample_count_per_channel_ / 4) * 4;
|
||||
for (size_t j = 0; j < unopt_start; j += 4) {
|
||||
float *here = cdat + j;
|
||||
__m128 samples = _mm_loadu_ps(here);
|
||||
|
||||
samples = _mm_max_ps(samples, min_sse);
|
||||
samples = _mm_min_ps(samples, max_sse);
|
||||
|
||||
_mm_storeu_ps(here, samples);
|
||||
}
|
||||
#endif
|
||||
|
||||
for (size_t sample = unopt_start; sample < sample_count(); sample++) {
|
||||
float &s = data(channel)[sample];
|
||||
s = std::clamp(s, min, max);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2023 Olive Studios LLC
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 "util/bezier.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace olive::core
|
||||
{
|
||||
|
||||
Bezier::Bezier()
|
||||
: x_(0)
|
||||
, y_(0)
|
||||
, cp1_x_(0)
|
||||
, cp1_y_(0)
|
||||
, cp2_x_(0)
|
||||
, cp2_y_(0)
|
||||
{
|
||||
}
|
||||
|
||||
Bezier::Bezier(double x, double y)
|
||||
: x_(x)
|
||||
, y_(y)
|
||||
, cp1_x_(0)
|
||||
, cp1_y_(0)
|
||||
, cp2_x_(0)
|
||||
, cp2_y_(0)
|
||||
{
|
||||
}
|
||||
|
||||
Bezier::Bezier(double x, double y, double cp1_x, double cp1_y, double cp2_x,
|
||||
double cp2_y)
|
||||
: x_(x)
|
||||
, y_(y)
|
||||
, cp1_x_(cp1_x)
|
||||
, cp1_y_(cp1_y)
|
||||
, cp2_x_(cp2_x)
|
||||
, cp2_y_(cp2_y)
|
||||
{
|
||||
}
|
||||
|
||||
double Bezier::QuadraticXtoT(double x, double a, double b, double c)
|
||||
{
|
||||
// Clamp to prevent infinite loop
|
||||
x = std::clamp(x, a, c);
|
||||
|
||||
return CalculateTFromX(false, x, a, b, c, 0);
|
||||
}
|
||||
|
||||
double Bezier::QuadraticTtoY(double a, double b, double c, double t)
|
||||
{
|
||||
return std::pow(1.0 - t, 2) * a + 2 * (1.0 - t) * t * b +
|
||||
std::pow(t, 2) * c;
|
||||
}
|
||||
|
||||
double Bezier::CubicXtoT(double x, double a, double b, double c, double d)
|
||||
{
|
||||
// Clamp to prevent infinite loop
|
||||
x = std::clamp(x, a, d);
|
||||
|
||||
return CalculateTFromX(true, x, a, b, c, d);
|
||||
}
|
||||
|
||||
double Bezier::CubicTtoY(double a, double b, double c, double d, double t)
|
||||
{
|
||||
return std::pow(1.0 - t, 3) * a + 3 * std::pow(1.0 - t, 2) * t * b +
|
||||
3 * (1.0 - t) * std::pow(t, 2) * c + std::pow(t, 3) * d;
|
||||
}
|
||||
|
||||
double Bezier::CalculateTFromX(bool cubic, double x, double a, double b,
|
||||
double c, double d)
|
||||
{
|
||||
double bottom = 0.0;
|
||||
double top = 1.0;
|
||||
|
||||
while (true) {
|
||||
if (bottom == top) {
|
||||
return bottom;
|
||||
}
|
||||
|
||||
double mid = (bottom + top) * 0.5;
|
||||
double test = cubic ? CubicTtoY(a, b, c, d, mid) :
|
||||
QuadraticTtoY(a, b, c, mid);
|
||||
|
||||
if (std::abs(test - x) < 0.000001) {
|
||||
return mid;
|
||||
} else if (x > test) {
|
||||
bottom = mid;
|
||||
} else {
|
||||
top = mid;
|
||||
}
|
||||
}
|
||||
|
||||
return NAN;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2023 Olive Studios LLC
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 "util/color.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <Imath/half.h>
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace olive::core
|
||||
{
|
||||
|
||||
Color Color::fromHsv(const DataType &h, const DataType &s, const DataType &v)
|
||||
{
|
||||
DataType C = s * v;
|
||||
DataType X = C * (1.0 - std::abs(std::fmod(h / 60.0, 2.0) - 1.0));
|
||||
DataType m = v - C;
|
||||
DataType Rs, Gs, Bs;
|
||||
|
||||
if (h >= 0.0 && h < 60.0) {
|
||||
Rs = C;
|
||||
Gs = X;
|
||||
Bs = 0.0;
|
||||
} else if (h >= 60.0 && h < 120.0) {
|
||||
Rs = X;
|
||||
Gs = C;
|
||||
Bs = 0.0;
|
||||
} else if (h >= 120.0 && h < 180.0) {
|
||||
Rs = 0.0;
|
||||
Gs = C;
|
||||
Bs = X;
|
||||
} else if (h >= 180.0 && h < 240.0) {
|
||||
Rs = 0.0;
|
||||
Gs = X;
|
||||
Bs = C;
|
||||
} else if (h >= 240.0 && h < 300.0) {
|
||||
Rs = X;
|
||||
Gs = 0.0;
|
||||
Bs = C;
|
||||
} else {
|
||||
Rs = C;
|
||||
Gs = 0.0;
|
||||
Bs = X;
|
||||
}
|
||||
|
||||
return Color(Rs + m, Gs + m, Bs + m);
|
||||
}
|
||||
|
||||
Color::Color(const char *data, const PixelFormat &format, int ch_layout)
|
||||
{
|
||||
*this = fromData(data, format, ch_layout);
|
||||
}
|
||||
|
||||
void Color::toHsv(DataType *hue, DataType *sat, DataType *val) const
|
||||
{
|
||||
DataType fCMax = std::max(std::max(red(), green()), blue());
|
||||
DataType fCMin = std::min(std::min(red(), green()), blue());
|
||||
DataType fDelta = fCMax - fCMin;
|
||||
|
||||
if (fDelta > 0) {
|
||||
if (fCMax == red()) {
|
||||
*hue = 60 * (fmod(((green() - blue()) / fDelta), 6));
|
||||
} else if (fCMax == green()) {
|
||||
*hue = 60 * (((blue() - red()) / fDelta) + 2);
|
||||
} else if (fCMax == blue()) {
|
||||
*hue = 60 * (((red() - green()) / fDelta) + 4);
|
||||
}
|
||||
|
||||
if (fCMax > 0) {
|
||||
*sat = fDelta / fCMax;
|
||||
} else {
|
||||
*sat = 0;
|
||||
}
|
||||
|
||||
*val = fCMax;
|
||||
} else {
|
||||
*hue = 0;
|
||||
*sat = 0;
|
||||
*val = fCMax;
|
||||
}
|
||||
|
||||
if (*hue < 0) {
|
||||
*hue = 360 + *hue;
|
||||
}
|
||||
}
|
||||
|
||||
Color::DataType Color::hsv_hue() const
|
||||
{
|
||||
DataType h, s, v;
|
||||
toHsv(&h, &s, &v);
|
||||
return h;
|
||||
}
|
||||
|
||||
Color::DataType Color::hsv_saturation() const
|
||||
{
|
||||
DataType h, s, v;
|
||||
toHsv(&h, &s, &v);
|
||||
return s;
|
||||
}
|
||||
|
||||
Color::DataType Color::value() const
|
||||
{
|
||||
DataType h, s, v;
|
||||
toHsv(&h, &s, &v);
|
||||
return v;
|
||||
}
|
||||
|
||||
void Color::toHsl(DataType *hue, DataType *sat, DataType *lightness) const
|
||||
{
|
||||
DataType fCMin = std::min(red(), std::min(green(), blue()));
|
||||
DataType fCMax = std::max(red(), std::max(green(), blue()));
|
||||
|
||||
*lightness = 0.5 * (fCMin + fCMax);
|
||||
|
||||
if (fCMin == fCMax) {
|
||||
*sat = 0;
|
||||
*hue = 0;
|
||||
return;
|
||||
|
||||
} else if (*lightness < 0.5) {
|
||||
*sat = (fCMax - fCMin) / (fCMax + fCMin);
|
||||
} else {
|
||||
*sat = (fCMax - fCMin) / (2.0 - fCMax - fCMin);
|
||||
}
|
||||
|
||||
if (fCMax == red()) {
|
||||
*hue = 60 * (green() - blue()) / (fCMax - fCMin);
|
||||
}
|
||||
if (fCMax == green()) {
|
||||
*hue = 60 * (blue() - red()) / (fCMax - fCMin) + 120;
|
||||
}
|
||||
if (fCMax == blue()) {
|
||||
*hue = 60 * (red() - green()) / (fCMax - fCMin) + 240;
|
||||
}
|
||||
if (*hue < 0) {
|
||||
*hue = *hue + 360;
|
||||
}
|
||||
}
|
||||
|
||||
Color::DataType Color::hsl_hue() const
|
||||
{
|
||||
DataType h, s, l;
|
||||
toHsl(&h, &s, &l);
|
||||
return h;
|
||||
}
|
||||
|
||||
Color::DataType Color::hsl_saturation() const
|
||||
{
|
||||
DataType h, s, l;
|
||||
toHsl(&h, &s, &l);
|
||||
return s;
|
||||
}
|
||||
|
||||
Color::DataType Color::lightness() const
|
||||
{
|
||||
DataType h, s, l;
|
||||
toHsl(&h, &s, &l);
|
||||
return l;
|
||||
}
|
||||
|
||||
void Color::toData(char *out, const PixelFormat &format,
|
||||
unsigned int nb_channels) const
|
||||
{
|
||||
unsigned int count = std::min(RGBA, nb_channels);
|
||||
|
||||
if (format == PixelFormat::U10 && count == 4) {
|
||||
const uint32_t r = static_cast<uint32_t>(std::clamp(data_[0], DataType(0.0), DataType(1.0)) * 1023.0 + 0.5);
|
||||
const uint32_t g = static_cast<uint32_t>(std::clamp(data_[1], DataType(0.0), DataType(1.0)) * 1023.0 + 0.5);
|
||||
const uint32_t b = static_cast<uint32_t>(std::clamp(data_[2], DataType(0.0), DataType(1.0)) * 1023.0 + 0.5);
|
||||
const uint32_t a = static_cast<uint32_t>(std::clamp(data_[3], DataType(0.0), DataType(1.0)) * 3.0 + 0.5);
|
||||
reinterpret_cast<uint32_t *>(out)[0] = r | (g << 10) | (b << 20) | (a << 30);
|
||||
return;
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < count; i++) {
|
||||
DataType f = data_[i];
|
||||
|
||||
switch (format) {
|
||||
case PixelFormat::INVALID:
|
||||
case PixelFormat::COUNT:
|
||||
break;
|
||||
case PixelFormat::U8:
|
||||
reinterpret_cast<uint8_t *>(out)[i] = f * 255.0;
|
||||
break;
|
||||
case PixelFormat::U10:
|
||||
// handled above
|
||||
break;
|
||||
case PixelFormat::U16:
|
||||
reinterpret_cast<uint16_t *>(out)[i] = f * 65535.0;
|
||||
break;
|
||||
case PixelFormat::F16:
|
||||
reinterpret_cast<Imath::half *>(out)[i] = f;
|
||||
break;
|
||||
case PixelFormat::F32:
|
||||
reinterpret_cast<float *>(out)[i] = f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Color Color::fromData(const char *in, const PixelFormat &format,
|
||||
unsigned int nb_channels)
|
||||
{
|
||||
Color c;
|
||||
|
||||
unsigned int count = std::min(RGBA, nb_channels);
|
||||
|
||||
if (format == PixelFormat::U10 && count == 4) {
|
||||
const uint32_t word = reinterpret_cast<const uint32_t *>(in)[0];
|
||||
c.data_[0] = DataType((word & 0x3ff) / 1023.0);
|
||||
c.data_[1] = DataType(((word >> 10) & 0x3ff) / 1023.0);
|
||||
c.data_[2] = DataType(((word >> 20) & 0x3ff) / 1023.0);
|
||||
c.data_[3] = DataType(((word >> 30) & 0x3) / 3.0);
|
||||
return c;
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < count; i++) {
|
||||
DataType &f = c.data_[i];
|
||||
|
||||
switch (format) {
|
||||
case PixelFormat::INVALID:
|
||||
case PixelFormat::COUNT:
|
||||
break;
|
||||
case PixelFormat::U8:
|
||||
f = DataType(reinterpret_cast<const uint8_t *>(in)[i]) / 255.0;
|
||||
break;
|
||||
case PixelFormat::U10:
|
||||
// handled above
|
||||
break;
|
||||
case PixelFormat::U16:
|
||||
f = DataType(reinterpret_cast<const uint16_t *>(in)[i]) / 65535.0;
|
||||
break;
|
||||
case PixelFormat::F16:
|
||||
f = DataType(reinterpret_cast<const Imath::half *>(in)[i]);
|
||||
break;
|
||||
case PixelFormat::F32:
|
||||
f = DataType(reinterpret_cast<const float *>(in)[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
Color::DataType Color::GetRoughLuminance() const
|
||||
{
|
||||
return (2 * red() + blue() + 3 * green()) / 6.0;
|
||||
}
|
||||
|
||||
Color &Color::operator+=(const Color &rhs)
|
||||
{
|
||||
for (int i = 0; i < RGBA; i++) {
|
||||
data_[i] += rhs.data_[i];
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Color &Color::operator-=(const Color &rhs)
|
||||
{
|
||||
for (int i = 0; i < RGBA; i++) {
|
||||
data_[i] -= rhs.data_[i];
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Color &Color::operator+=(const DataType &rhs)
|
||||
{
|
||||
for (int i = 0; i < RGBA; i++) {
|
||||
data_[i] += rhs;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Color &Color::operator-=(const DataType &rhs)
|
||||
{
|
||||
for (int i = 0; i < RGBA; i++) {
|
||||
data_[i] -= rhs;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Color &Color::operator*=(const DataType &rhs)
|
||||
{
|
||||
for (int i = 0; i < RGBA; i++) {
|
||||
data_[i] *= rhs;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Color &Color::operator/=(const DataType &rhs)
|
||||
{
|
||||
for (int i = 0; i < RGBA; i++) {
|
||||
data_[i] /= rhs;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2023 Olive Studios LLC
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 "util/rational.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include "util/stringutils.h"
|
||||
|
||||
namespace olive::core
|
||||
{
|
||||
|
||||
const rational rational::NaN = rational(0, 0);
|
||||
|
||||
rational rational::fromDouble(const double &flt, bool *ok)
|
||||
{
|
||||
if (isnan(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 std::string &str, bool *ok)
|
||||
{
|
||||
std::vector<std::string> elements = StringUtils::split(str, '/');
|
||||
|
||||
switch (elements.size()) {
|
||||
case 1:
|
||||
return rational(StringUtils::to_int(elements.front(), ok));
|
||||
case 2:
|
||||
return rational(StringUtils::to_int(elements.at(0), ok),
|
||||
StringUtils::to_int(elements.at(1), 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 std::numeric_limits<double>::quiet_NaN();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
fix_signs();
|
||||
}
|
||||
}
|
||||
|
||||
std::string rational::toString() const
|
||||
{
|
||||
return StringUtils::format("%d/%d", r_.num, r_.den);
|
||||
}
|
||||
|
||||
void rational::fix_signs()
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (*this == RATIONAL_MIN || *this == RATIONAL_MAX || rhs == RATIONAL_MIN ||
|
||||
rhs == RATIONAL_MAX) {
|
||||
*this = NaN;
|
||||
} else if (!isNaN()) {
|
||||
if (rhs.isNaN()) {
|
||||
*this = NaN;
|
||||
} else {
|
||||
r_ = av_add_q(r_, rhs.r_);
|
||||
fix_signs();
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
const rational &rational::operator-=(const rational &rhs)
|
||||
{
|
||||
if (*this == RATIONAL_MIN || *this == RATIONAL_MAX || rhs == RATIONAL_MIN ||
|
||||
rhs == RATIONAL_MAX) {
|
||||
*this = NaN;
|
||||
} else if (!isNaN()) {
|
||||
if (rhs.isNaN()) {
|
||||
*this = NaN;
|
||||
} else {
|
||||
r_ = av_sub_q(r_, rhs.r_);
|
||||
fix_signs();
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
const rational &rational::operator*=(const rational &rhs)
|
||||
{
|
||||
if (*this == RATIONAL_MIN || *this == RATIONAL_MAX || rhs == RATIONAL_MIN ||
|
||||
rhs == RATIONAL_MAX) {
|
||||
*this = NaN;
|
||||
} else if (!isNaN()) {
|
||||
if (rhs.isNaN()) {
|
||||
*this = NaN;
|
||||
} else {
|
||||
r_ = av_mul_q(r_, rhs.r_);
|
||||
fix_signs();
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
const rational &rational::operator/=(const rational &rhs)
|
||||
{
|
||||
if (*this == RATIONAL_MIN || *this == RATIONAL_MAX || rhs == RATIONAL_MIN ||
|
||||
rhs == RATIONAL_MAX) {
|
||||
*this = NaN;
|
||||
} else if (!isNaN()) {
|
||||
if (rhs.isNaN()) {
|
||||
*this = NaN;
|
||||
} else {
|
||||
r_ = av_div_q(r_, rhs.r_);
|
||||
fix_signs();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2023 Olive Studios LLC
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 "util/stringutils.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace olive::core
|
||||
{
|
||||
|
||||
std::vector<std::string> StringUtils::split(const std::string &s,
|
||||
char separator)
|
||||
{
|
||||
std::vector<std::string> output;
|
||||
|
||||
std::string::size_type prev_pos = 0, pos = 0;
|
||||
|
||||
while ((pos = s.find(separator, pos)) != std::string::npos) {
|
||||
std::string substring(s.substr(prev_pos, pos - prev_pos));
|
||||
|
||||
output.push_back(substring);
|
||||
|
||||
prev_pos = ++pos;
|
||||
}
|
||||
|
||||
output.push_back(s.substr(prev_pos, pos - prev_pos)); // Last word
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
std::vector<std::string> StringUtils::split_regex(const std::string &s,
|
||||
const std::regex ®ex)
|
||||
{
|
||||
std::vector<std::string> output;
|
||||
|
||||
std::sregex_token_iterator iter(s.begin(), s.end(), regex, -1);
|
||||
std::sregex_token_iterator end;
|
||||
for (; iter != end; iter++) {
|
||||
output.push_back(*iter);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
int StringUtils::to_int(const std::string &s, int base, bool *ok)
|
||||
{
|
||||
try {
|
||||
int x = std::stoi(s, nullptr, base);
|
||||
if (ok) {
|
||||
*ok = true;
|
||||
}
|
||||
return x;
|
||||
} catch (const std::invalid_argument &e) {
|
||||
if (ok) {
|
||||
*ok = false;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::string StringUtils::format(const char *fmt, ...)
|
||||
{
|
||||
va_list ap1, ap2;
|
||||
va_start(ap1, fmt);
|
||||
|
||||
// Need to duplicate because we call vsnprintf twice and it consumes the va_list each time
|
||||
va_copy(ap2, ap1);
|
||||
|
||||
int s = std::vsnprintf(nullptr, 0, fmt, ap1);
|
||||
|
||||
// Create string with size, adding 1 because vsnprintf will want to write a null terminator
|
||||
std::string r;
|
||||
s++;
|
||||
r.resize(s);
|
||||
|
||||
// Write into string
|
||||
std::vsnprintf(r.data(), s, fmt, ap2);
|
||||
|
||||
// Pop null terminator
|
||||
r.pop_back();
|
||||
|
||||
va_end(ap2);
|
||||
va_end(ap1);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2023 Olive Studios LLC
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 "util/tests.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <stdarg.h>
|
||||
|
||||
namespace olive::core
|
||||
{
|
||||
|
||||
bool Tester::run()
|
||||
{
|
||||
size_t index = 1;
|
||||
size_t count = test_functions_.size();
|
||||
|
||||
while (!test_functions_.empty()) {
|
||||
echo("[%lu/%lu] %s :: ", index, count, test_names_.front());
|
||||
|
||||
if (test_functions_.front()()) {
|
||||
echo("PASSED\n");
|
||||
} else {
|
||||
echo("FAILED\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
test_names_.pop_front();
|
||||
test_functions_.pop_front();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Tester::echo(const char *fmt, ...)
|
||||
{
|
||||
va_list a;
|
||||
va_start(a, fmt);
|
||||
|
||||
vfprintf(stderr, fmt, a);
|
||||
|
||||
va_end(a);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2023 Olive Studios LLC
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 "util/timecodefunctions.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/mathematics.h>
|
||||
}
|
||||
|
||||
#include "util/stringutils.h"
|
||||
|
||||
namespace olive::core
|
||||
{
|
||||
|
||||
std::string Timecode::time_to_timecode(const rational &time,
|
||||
const rational &timebase,
|
||||
const Timecode::Display &display,
|
||||
bool show_plus_if_positive)
|
||||
{
|
||||
if (timebase.isNull() || timebase.flipped().toDouble() < 1) {
|
||||
return "INVALID TIMEBASE";
|
||||
}
|
||||
|
||||
double time_dbl = time.toDouble();
|
||||
|
||||
switch (display) {
|
||||
case kTimecodeNonDropFrame:
|
||||
case kTimecodeDropFrame:
|
||||
case kTimecodeSeconds: {
|
||||
const char *prefix = "";
|
||||
|
||||
if (time_dbl < 0) {
|
||||
prefix = "-";
|
||||
} else if (show_plus_if_positive) {
|
||||
prefix = "+";
|
||||
}
|
||||
|
||||
if (display == kTimecodeSeconds) {
|
||||
time_dbl = std::abs(time_dbl);
|
||||
|
||||
int64_t total_seconds = std::floor(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 = std::llround(
|
||||
(time_dbl - static_cast<double>(total_seconds)) * 1000);
|
||||
|
||||
return StringUtils::format(
|
||||
"%s%s:%s:%s.%s", prefix,
|
||||
StringUtils::to_string_leftpad(hours, 2).c_str(),
|
||||
StringUtils::to_string_leftpad(mins, 2).c_str(),
|
||||
StringUtils::to_string_leftpad(secs, 2).c_str(),
|
||||
StringUtils::to_string_leftpad(fraction, 3).c_str());
|
||||
} else {
|
||||
// Determine what symbol to separate frames (";" is used for drop frame, ":" is non-drop frame)
|
||||
const char *frame_token;
|
||||
double frame_rate = timebase.flipped().toDouble();
|
||||
int rounded_frame_rate = std::llround(frame_rate);
|
||||
int64_t frames, secs, mins, hours;
|
||||
int64_t f = std::abs(time_to_timestamp(time, timebase));
|
||||
|
||||
if (display == kTimecodeDropFrame &&
|
||||
timebase_is_drop_frame(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 %= (std::llround(frame_rate * 3600) * 24);
|
||||
|
||||
// Number of frames per ten minutes
|
||||
int64_t framesPer10Minutes = std::llround(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 = std::llround(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) /
|
||||
(std::llround(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 StringUtils::format(
|
||||
"%s%s:%s:%s%s%s", prefix,
|
||||
StringUtils::to_string_leftpad(hours, 2).c_str(),
|
||||
StringUtils::to_string_leftpad(mins, 2).c_str(),
|
||||
StringUtils::to_string_leftpad(secs, 2).c_str(), frame_token,
|
||||
StringUtils::to_string_leftpad(frames, 2).c_str());
|
||||
}
|
||||
}
|
||||
case kFrames:
|
||||
return std::to_string(time_to_timestamp(time, timebase));
|
||||
case kMilliseconds:
|
||||
return std::to_string(std::llround(time_dbl * 1000));
|
||||
}
|
||||
|
||||
return "INVALID TIMECODE MODE";
|
||||
}
|
||||
|
||||
int64_t StrToInt64EmptyTolerant(const std::string &s, bool *ok)
|
||||
{
|
||||
if (s.empty()) {
|
||||
if (ok)
|
||||
*ok = true;
|
||||
return 0;
|
||||
} else {
|
||||
try {
|
||||
int64_t ll = std::stoll(s);
|
||||
if (ok)
|
||||
*ok = true;
|
||||
return ll;
|
||||
} catch (const std::invalid_argument &e) {
|
||||
if (ok)
|
||||
*ok = false;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double StrToDoubleEmptyTolerant(const std::string &s, bool *ok)
|
||||
{
|
||||
if (s.empty()) {
|
||||
if (ok)
|
||||
*ok = true;
|
||||
return 0;
|
||||
} else {
|
||||
try {
|
||||
double d = std::stod(s);
|
||||
if (ok)
|
||||
*ok = true;
|
||||
return d;
|
||||
} catch (const std::invalid_argument &e) {
|
||||
if (ok)
|
||||
*ok = false;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rational Timecode::timecode_to_time(std::string timecode,
|
||||
const rational &timebase,
|
||||
const Timecode::Display &display, bool *ok)
|
||||
{
|
||||
StringUtils::trim(timecode);
|
||||
if (timecode.empty()) {
|
||||
goto err_fatal;
|
||||
}
|
||||
|
||||
switch (display) {
|
||||
case kTimecodeNonDropFrame:
|
||||
case kTimecodeDropFrame:
|
||||
case kTimecodeSeconds: {
|
||||
std::vector<std::string> timecode_split =
|
||||
StringUtils::split_regex(timecode, std::regex("(:)|(;)"));
|
||||
|
||||
const int element_count = display == kTimecodeSeconds ? 3 : 4;
|
||||
|
||||
// Remove excess tokens (we're only interested in HH:MM:SS.FF)
|
||||
if (timecode_split.size() > element_count) {
|
||||
timecode_split.resize(element_count);
|
||||
}
|
||||
|
||||
// For easier index calculations, ensure minimum size
|
||||
if (timecode_split.size() < element_count) {
|
||||
timecode_split.insert(timecode_split.begin(),
|
||||
element_count - timecode_split.size(),
|
||||
std::string());
|
||||
}
|
||||
|
||||
bool negative = (timecode.at(0) == '-');
|
||||
|
||||
double frame_rate = timebase.flipped().toDouble();
|
||||
int rounded_frame_rate = std::lround(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 &&
|
||||
timebase_is_drop_frame(timebase)) {
|
||||
// Number of frames to drop on the minute marks is the nearest integer to 6% of the framerate
|
||||
int64_t dropFrames = std::llround(frame_rate * (2.0 / 30.0));
|
||||
|
||||
// d and m need to be calculated from
|
||||
int64_t real_fr_ts =
|
||||
std::llround(static_cast<double>(sec_count) * frame_rate) +
|
||||
frames;
|
||||
|
||||
int64_t framesPer10Minutes = std::llround(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) /
|
||||
(std::llround(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: {
|
||||
try {
|
||||
double timecode_secs = std::stod(timecode);
|
||||
|
||||
// Convert milliseconds to seconds
|
||||
timecode_secs *= 0.001;
|
||||
|
||||
// Convert seconds to rational
|
||||
return rational::fromDouble(timecode_secs, ok);
|
||||
} catch (const std::invalid_argument &e) {
|
||||
goto err_fatal;
|
||||
}
|
||||
}
|
||||
case kFrames: {
|
||||
try {
|
||||
int64_t ts = std::stoll(timecode);
|
||||
if (ok)
|
||||
*ok = true;
|
||||
return timestamp_to_time(ts, timebase);
|
||||
} catch (const std::invalid_argument &e) {
|
||||
goto err_fatal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err_fatal:
|
||||
if (ok)
|
||||
*ok = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string Timecode::time_to_string(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 StringUtils::format("%s:%s:%s",
|
||||
StringUtils::to_string_leftpad(hh, 2).c_str(),
|
||||
StringUtils::to_string_leftpad(mm, 2).c_str(),
|
||||
StringUtils::to_string_leftpad(ss, 2).c_str());
|
||||
}
|
||||
|
||||
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::timebase_is_drop_frame(const rational &timebase)
|
||||
{
|
||||
return (timebase.numerator() != 1);
|
||||
}
|
||||
|
||||
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 std::llround(d);
|
||||
case kFloor:
|
||||
if (d > std::ceil(d) - eps) {
|
||||
return std::ceil(d);
|
||||
} else {
|
||||
return std::floor(d);
|
||||
}
|
||||
case kCeil:
|
||||
if (d < std::floor(d) + eps) {
|
||||
return std::floor(d);
|
||||
} else {
|
||||
return std::ceil(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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,398 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2023 Olive Studios LLC
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 "util/timerange.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <utility>
|
||||
|
||||
#include "util/timecodefunctions.h"
|
||||
|
||||
namespace olive::core
|
||||
{
|
||||
|
||||
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
|
||||
{
|
||||
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(std::min(a.in(), b.in()), std::max(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(std::max(a.in(), b.in()), std::min(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 =
|
||||
std::floor(this->in().toDouble() / static_cast<double>(chunk_size)) *
|
||||
chunk_size;
|
||||
int end_time =
|
||||
std::ceil(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(std::max(this->in(), rational(i)),
|
||||
std::min(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 (auto it = array_.begin(); it != array_.end();) {
|
||||
const TimeRange &compare = *it;
|
||||
|
||||
if (compare.OverlapsWith(range_to_add)) {
|
||||
range_to_add = TimeRange::Combine(range_to_add, compare);
|
||||
it = array_.erase(it);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
array_.push_back(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();
|
||||
|
||||
for (auto it = temp.array_.begin(); it != temp.array_.end(); it++) {
|
||||
TimeRange &r = *it;
|
||||
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();
|
||||
|
||||
for (auto it = temp.array_.begin(); it != temp.array_.end(); it++) {
|
||||
TimeRange &r = *it;
|
||||
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(std::max(range.in(), compare.in()),
|
||||
std::min(range.out(), compare.out()));
|
||||
|
||||
intersect_list.insert(cropped);
|
||||
}
|
||||
}
|
||||
|
||||
return intersect_list;
|
||||
}
|
||||
|
||||
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()) {
|
||||
std::cerr
|
||||
<< "TimeRangeListFrameIterator created with null timebase but non-empty list, this will likely lead to infinite loops"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
for (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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2023 Olive Studios LLC
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 "util/value.h"
|
||||
|
||||
namespace olive::core
|
||||
{
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user