Merge branch 'olive-editor:master' into av1

This commit is contained in:
jazztickets
2022-11-25 11:18:49 -07:00
committed by GitHub
181 changed files with 2366 additions and 2305 deletions
+1 -1
View File
@@ -287,7 +287,7 @@ jobs:
compiler-name: Clang LLVM
os-name: macOS
os-arch: x86_64
os: macos-10.15
os: macos-11.0
cmake-gen: Ninja
min-deploy: 10.13
- build-type: RelWithDebInfo
+31 -10
View File
@@ -94,7 +94,7 @@ find_package(OpenEXR REQUIRED)
list(APPEND OLIVE_LIBRARIES ${OPENEXR_LIBRARIES})
list(APPEND OLIVE_INCLUDE_DIRS ${OPENEXR_INCLUDES})
# Link Qt 5
# Link Qt
set(QT_LIBRARIES
Core
Gui
@@ -106,23 +106,44 @@ set(QT_LIBRARIES
if (UNIX AND NOT APPLE)
list(APPEND QT_LIBRARIES DBus)
endif()
find_package(Qt5 5.6 REQUIRED
find_package(QT
NAMES
Qt6
Qt5
REQUIRED
COMPONENTS
${QT_LIBRARIES}
OPTIONAL_COMPONENTS
Network
)
if (NOT Qt5Network_FOUND)
message(" Qt5::Network module not found, crash reporting will be disabled.")
find_package(Qt${QT_VERSION_MAJOR} REQUIRED
COMPONENTS
${QT_LIBRARIES}
OPTIONAL_COMPONENTS
Network
)
if (NOT Qt${QT_VERSION_MAJOR}Network_FOUND)
message(" Qt${QT_VERSION_MAJOR}::Network module not found, crash reporting will be disabled.")
endif()
list(APPEND OLIVE_LIBRARIES
Qt5::Core
Qt5::Gui
Qt5::Widgets
Qt5::OpenGL
Qt5::Concurrent
Qt${QT_VERSION_MAJOR}::Core
Qt${QT_VERSION_MAJOR}::Gui
Qt${QT_VERSION_MAJOR}::Widgets
Qt${QT_VERSION_MAJOR}::OpenGL
Qt${QT_VERSION_MAJOR}::Concurrent
)
if (${QT_VERSION_MAJOR} EQUAL "6")
find_package(Qt${QT_VERSION_MAJOR}
REQUIRED
OpenGLWidgets
)
list(APPEND OLIVE_LIBRARIES
Qt${QT_VERSION_MAJOR}::OpenGLWidgets
)
endif()
# Link FFmpeg
find_package(FFMPEG 3.0 REQUIRED
COMPONENTS
@@ -186,7 +207,7 @@ if (WIN32)
elseif (APPLE)
list(APPEND OLIVE_LIBRARIES "-framework IOKit")
elseif(UNIX)
list(APPEND OLIVE_LIBRARIES Qt5::DBus)
list(APPEND OLIVE_LIBRARIES Qt${QT_VERSION_MAJOR}::DBus)
endif()
# Generate Git hash
+3 -3
View File
@@ -44,7 +44,7 @@ add_subdirectory(widget)
add_subdirectory(window)
# Add translations
qt5_add_translation(OLIVE_QM_FILES ${OLIVE_TS_FILES})
qt_add_translation(OLIVE_QM_FILES ${OLIVE_TS_FILES})
set(QRC_BODY "")
foreach(QM_FILE ${OLIVE_QM_FILES})
@@ -64,7 +64,7 @@ add_library(olive-version-obj
version.cpp
version.h
)
target_link_libraries(olive-version-obj PRIVATE Qt5::Core)
target_link_libraries(olive-version-obj PRIVATE Qt${QT_VERSION_MAJOR}::Core)
target_compile_options(olive-version-obj PRIVATE -DAPPVERSION="${PROJECT_VERSION}" -DAPPVERSIONLONG="${PROJECT_LONG_VERSION}" )
# Add main library
@@ -142,6 +142,6 @@ target_include_directories(olive-editor PRIVATE ${OLIVE_INCLUDE_DIRS})
target_include_directories(libolive-editor PRIVATE ${OLIVE_INCLUDE_DIRS})
# Add crash handler
if (GoogleCrashpad_FOUND AND Qt5Network_FOUND)
if (GoogleCrashpad_FOUND AND Qt${QT_VERSION_MAJOR}Network_FOUND)
add_subdirectory(crashhandler)
endif()
+3 -2
View File
@@ -112,12 +112,13 @@ TexturePtr Decoder::RetrieveVideo(const RetrieveVideoParams &p)
return nullptr;
}
if (cached_texture_ && cached_time_ == p.time) {
if (cached_texture_ && cached_time_ == p.time && cached_divider_ == p.divider) {
return cached_texture_;
}
cached_texture_ = RetrieveVideoInternal(p);
cached_time_ = p.time;
cached_divider_ = p.divider;
return cached_texture_;
}
@@ -292,7 +293,7 @@ bool Decoder::RetrieveAudioFromConform(SampleBuffer &sample_buffer, const QVecto
const qint64 buffer_length_in_bytes = sample_buffer.sample_count() * input_params.bytes_per_sample_per_channel();
while (write_index < buffer_length_in_bytes) {
if (loop_mode == kLoopModeLoop) {
if (loop_mode == LoopMode::kLoopModeLoop) {
while (read_index >= input.size()) {
read_index -= input.size();
}
+2 -8
View File
@@ -31,12 +31,11 @@ extern "C" {
#include <QWaitCondition>
#include <stdint.h>
#include "codec/frame.h"
#include "codec/samplebuffer.h"
#include "common/rational.h"
#include "node/block/block.h"
#include "node/project/footage/footagedescription.h"
#include "task/task.h"
#include "render/cancelatom.h"
namespace olive {
@@ -71,12 +70,6 @@ public:
kIndexUnavailable
};
enum LoopMode {
kLoopModeOff,
kLoopModeLoop,
kLoopModeClamp
};
Decoder();
/**
@@ -316,6 +309,7 @@ private:
TexturePtr cached_texture_;
rational cached_time_;
int cached_divider_;
};
+280 -426
View File
@@ -49,15 +49,10 @@ extern "C" {
namespace olive {
QVariant Yuv2RgbShader;
QVariant DeinterlaceShader;
FFmpegDecoder::FFmpegDecoder() :
filter_graph_(nullptr),
buffersrc_ctx_(nullptr),
buffersink_ctx_(nullptr),
input_fmt_(AV_PIX_FMT_NONE),
native_internal_pix_fmt_(VideoParams::kFormatInvalid),
native_output_pix_fmt_(VideoParams::kFormatInvalid),
working_frame_(nullptr),
sws_ctx_(nullptr),
working_packet_(nullptr),
cache_at_zero_(false),
cache_at_eof_(false)
@@ -72,217 +67,195 @@ bool FFmpegDecoder::OpenInternal()
// Store one second in the source's timebase
second_ts_ = qRound64(av_q2d(av_inv_q(s->time_base)));
working_frame_ = av_frame_alloc();
working_packet_ = av_packet_alloc();
frame_rate_tb_ = rational::NaN;
return true;
}
return false;
}
/*FramePtr FFmpegDecoder::RetrieveStillImage(const rational &timecode, const int &divider)
TexturePtr FFmpegDecoder::ProcessFrameIntoTexture(AVFramePtr f, const RetrieveVideoParams &p, const AVFramePtr original)
{
// This is a still image
QString img_filename = stream().filename();
// Determine native format
AVPixelFormat ideal_fmt = FFmpegUtils::GetCompatiblePixelFormat(static_cast<AVPixelFormat>(f->format));
VideoParams::Format native_fmt = GetNativePixelFormat(ideal_fmt);
int native_channels = GetNativeChannelCount(ideal_fmt);
int64_t ts;
// Set up video params
VideoParams vp(original->width,
original->height,
native_fmt,
native_channels,
av_guess_sample_aspect_ratio(instance_.fmt_ctx(), instance_.avstream(), nullptr),
VideoParams::kInterlaceNone,
p.divider);
// If it's an image sequence, we'll probably need to transform the filename
if (stream().GetStream().video_type() == Track::kVideoTypeImageSequence) {
ts = stream().GetTimeInTimebaseUnits(timecode);
// Create texture
TexturePtr tex = p.renderer->CreateTexture(vp);
img_filename = TransformImageSequenceFileName(stream().filename(), ts);
} else {
ts = 0;
switch (f->format) {
case AV_PIX_FMT_YUV420P:
case AV_PIX_FMT_YUV422P:
case AV_PIX_FMT_YUV444P:
case AV_PIX_FMT_YUV420P10LE:
case AV_PIX_FMT_YUV422P10LE:
case AV_PIX_FMT_YUV444P10LE:
case AV_PIX_FMT_YUV420P12LE:
case AV_PIX_FMT_YUV422P12LE:
case AV_PIX_FMT_YUV444P12LE:
{
// Run through YUV to RGB shader
if (Yuv2RgbShader.isNull()) {
// Compile shader
Yuv2RgbShader = p.renderer->CreateNativeShader(ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/yuv2rgb.frag"))));
if (Yuv2RgbShader.isNull()) {
return nullptr;
}
}
int px_size;
int bits_per_pixel;
switch (f->format) {
case AV_PIX_FMT_YUV420P:
case AV_PIX_FMT_YUV422P:
case AV_PIX_FMT_YUV444P:
default:
px_size = 1;
bits_per_pixel = 8;
break;
case AV_PIX_FMT_YUV420P10LE:
case AV_PIX_FMT_YUV422P10LE:
case AV_PIX_FMT_YUV444P10LE:
px_size = 2;
bits_per_pixel = 10;
break;
case AV_PIX_FMT_YUV420P12LE:
case AV_PIX_FMT_YUV422P12LE:
case AV_PIX_FMT_YUV444P12LE:
px_size = 2;
bits_per_pixel = 12;
break;
}
AVFrame *hw_in = f.get();
VideoParams plane_params = vp;
plane_params.set_channel_count(1);
plane_params.set_format(native_fmt);
TexturePtr y_plane = p.renderer->CreateTexture(plane_params, hw_in->data[0], hw_in->linesize[0] / px_size);
switch (f->format) {
case AV_PIX_FMT_YUV420P:
case AV_PIX_FMT_YUV422P:
case AV_PIX_FMT_YUV420P10LE:
case AV_PIX_FMT_YUV422P10LE:
case AV_PIX_FMT_YUV420P12LE:
case AV_PIX_FMT_YUV422P12LE:
plane_params.set_width(plane_params.width()/2);
break;
}
switch (f->format) {
case AV_PIX_FMT_YUV420P:
case AV_PIX_FMT_YUV420P10LE:
case AV_PIX_FMT_YUV420P12LE:
plane_params.set_height(plane_params.height()/2);
break;
}
TexturePtr u_plane = p.renderer->CreateTexture(plane_params, hw_in->data[1], hw_in->linesize[1] / px_size);
TexturePtr v_plane = p.renderer->CreateTexture(plane_params, hw_in->data[2], hw_in->linesize[2] / px_size);
ShaderJob job;
job.Insert(QStringLiteral("y_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(y_plane)));
job.Insert(QStringLiteral("u_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(u_plane)));
job.Insert(QStringLiteral("v_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(v_plane)));
job.Insert(QStringLiteral("bits_per_pixel"), NodeValue(NodeValue::kInt, bits_per_pixel));
job.Insert(QStringLiteral("full_range"), NodeValue(NodeValue::kBoolean, hw_in->color_range == AVCOL_RANGE_JPEG));
const int *yuv_coeffs = sws_getCoefficients(FFmpegUtils::GetSwsColorspaceFromAVColorSpace(hw_in->colorspace));
job.Insert(QStringLiteral("yuv_crv"), NodeValue(NodeValue::kFloat, yuv_coeffs[0]/65536.0));
job.Insert(QStringLiteral("yuv_cgu"), NodeValue(NodeValue::kFloat, yuv_coeffs[2]/65536.0));
job.Insert(QStringLiteral("yuv_cgv"), NodeValue(NodeValue::kFloat, yuv_coeffs[3]/65536.0));
job.Insert(QStringLiteral("yuv_cbu"), NodeValue(NodeValue::kFloat, yuv_coeffs[1]/65536.0));
tex = p.renderer->CreateTexture(vp);
p.renderer->BlitToTexture(Yuv2RgbShader, job, tex.get(), false);
break;
}
case AV_PIX_FMT_RGBA:
case AV_PIX_FMT_RGBA64LE:
// RGBA can be uploaded directly to the texture
tex->Upload(f->data[0], f->linesize[0] / vp.GetBytesPerPixel());
break;
}
AVPacket* pkt = av_packet_alloc();
AVFrame* frame = av_frame_alloc();
FramePtr output_frame = nullptr;
// Deinterlace if necessary
if (p.src_interlacing != VideoParams::kInterlaceNone) {
if (DeinterlaceShader.isNull()) {
// Compile shader
DeinterlaceShader = p.renderer->CreateNativeShader(ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/deinterlace2.frag"))));
if (DeinterlaceShader.isNull()) {
return nullptr;
}
}
Instance i;
i.Open(img_filename.toUtf8(), stream().GetRealStreamIndex());
rational frame_rate_tb = av_guess_frame_rate(instance_.fmt_ctx(), instance_.avstream(), original.get());
int ret = i.GetFrame(pkt, frame);
// Double frame rate for interlaced fields
frame_rate_tb *= 2;
if (ret >= 0) {
VideoParams video_params = stream().video_params();
// Flip frame rate so it can be used as a timebase
frame_rate_tb.flip();
// Create frame to return
output_frame = Frame::Create();
output_frame->set_video_params(VideoParams(frame->width,
frame->height,
native_pix_fmt_,
native_channel_count_,
video_params.pixel_aspect_ratio(),
video_params.interlacing(),
divider));
output_frame->set_timestamp(timecode);
output_frame->allocate();
int64_t req = Timecode::time_to_timestamp(p.time + rational(instance_.fmt_ctx()->start_time, AV_TIME_BASE), frame_rate_tb);
int64_t frm = Timecode::rescale_timestamp(original->pts, instance_.avstream()->time_base, frame_rate_tb);
uint8_t* copy_data = reinterpret_cast<uint8_t*>(output_frame->data());
int copy_linesize = output_frame->linesize_bytes();
bool first = (req == frm);
bool top_first = (p.src_interlacing == VideoParams::kInterlacedTopFirst);
FFmpegBufferToNativeBuffer(frame->data, frame->linesize, &copy_data, &copy_linesize);
} else {
qWarning() << "Failed to retrieve still image from decoder";
int interlacing = (first == top_first) ? 1 : 2;
TexturePtr deinterlaced = p.renderer->CreateTexture(tex->params());
ShaderJob job;
job.Insert(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, tex));
job.Insert(QStringLiteral("interlacing"), NodeValue(NodeValue::kInt, interlacing));
job.Insert(QStringLiteral("pixel_height"), NodeValue(NodeValue::kInt, original->height));
p.renderer->BlitToTexture(DeinterlaceShader, job, deinterlaced.get(), false);
tex = deinterlaced;
}
i.Close();
av_frame_free(&frame);
av_packet_free(&pkt);
return output_frame;
}*/
return tex;
}
TexturePtr FFmpegDecoder::RetrieveVideoInternal(const RetrieveVideoParams &p)
{
if (AVFramePtr f = RetrieveFrame(p.time, p.src_interlacing, p.cancelled)) {
if (AVFramePtr f = RetrieveFrame(p.time, p.cancelled)) {
if (p.cancelled && p.cancelled->IsCancelled()) {
return nullptr;
}
int &src_fmt = f.get()->format;
src_fmt = FFmpegUtils::ConvertJPEGSpaceToRegularSpace(static_cast<AVPixelFormat>(src_fmt));
AVFramePtr original = f;
// Disregard "JPEG" pixel formats because we allow the user to override that
f->format = FFmpegUtils::ConvertJPEGSpaceToRegularSpace(static_cast<AVPixelFormat>(f->format));
// Force frame's color range to whatever it's set to in Olive
f->color_range = p.force_range == VideoParams::kColorRangeFull ? AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG;
if (InitScaler(f.get(), p)) {
VideoParams vp(instance_.avstream()->codecpar->width,
instance_.avstream()->codecpar->height,
native_output_pix_fmt_,
native_channel_count_,
av_guess_sample_aspect_ratio(instance_.fmt_ctx(), instance_.avstream(), nullptr),
VideoParams::kInterlaceNone,
p.divider);
TexturePtr tex = nullptr;
// Attempt to use GLSL shader for faster YUV to RGB conversion
if (IsPixelFormatGLSLCompatible(static_cast<AVPixelFormat>(src_fmt))) {
if (Yuv2RgbShader.isNull()) {
// Compile shader
Yuv2RgbShader = p.renderer->CreateNativeShader(ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/yuv2rgb.frag"))));
}
if (!Yuv2RgbShader.isNull()) {
int px_size;
int bits_per_pixel;
switch (src_fmt) {
case AV_PIX_FMT_YUV420P:
case AV_PIX_FMT_YUV422P:
case AV_PIX_FMT_YUV444P:
default:
px_size = 1;
bits_per_pixel = 8;
break;
case AV_PIX_FMT_YUV420P10LE:
case AV_PIX_FMT_YUV422P10LE:
case AV_PIX_FMT_YUV444P10LE:
px_size = 2;
bits_per_pixel = 10;
break;
case AV_PIX_FMT_YUV420P12LE:
case AV_PIX_FMT_YUV422P12LE:
case AV_PIX_FMT_YUV444P12LE:
px_size = 2;
bits_per_pixel = 12;
break;
}
AVFrame *hw_in = f.get();
VideoParams plane_params = vp;
plane_params.set_channel_count(1);
plane_params.set_format(native_internal_pix_fmt_);
if (p.divider != 1) {
ApplyScaler(f.get());
hw_in = working_frame_;
} else {
// Fallback: shouldn't ever really get here, but just in case
plane_params.set_divider(1);
}
TexturePtr y_plane = p.renderer->CreateTexture(plane_params, hw_in->data[0], hw_in->linesize[0] / px_size);
if (src_fmt == AV_PIX_FMT_YUV420P
|| src_fmt == AV_PIX_FMT_YUV422P
|| src_fmt == AV_PIX_FMT_YUV420P10LE
|| src_fmt == AV_PIX_FMT_YUV422P10LE
|| src_fmt == AV_PIX_FMT_YUV420P12LE
|| src_fmt == AV_PIX_FMT_YUV422P12LE) {
plane_params.set_width(plane_params.width()/2);
}
if (src_fmt == AV_PIX_FMT_YUV420P
|| src_fmt == AV_PIX_FMT_YUV420P10LE
|| src_fmt == AV_PIX_FMT_YUV420P12LE) {
plane_params.set_height(plane_params.height()/2);
}
TexturePtr u_plane = p.renderer->CreateTexture(plane_params, hw_in->data[1], hw_in->linesize[1] / px_size);
TexturePtr v_plane = p.renderer->CreateTexture(plane_params, hw_in->data[2], hw_in->linesize[2] / px_size);
ShaderJob job;
job.Insert(QStringLiteral("y_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(y_plane)));
job.Insert(QStringLiteral("u_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(u_plane)));
job.Insert(QStringLiteral("v_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(v_plane)));
job.Insert(QStringLiteral("bits_per_pixel"), NodeValue(NodeValue::kInt, bits_per_pixel));
job.Insert(QStringLiteral("full_range"), NodeValue(NodeValue::kBoolean, hw_in->color_range == AVCOL_RANGE_JPEG));
const int *yuv_coeffs = sws_getCoefficients(FFmpegUtils::GetSwsColorspaceFromAVColorSpace(hw_in->colorspace));
job.Insert(QStringLiteral("yuv_crv"), NodeValue(NodeValue::kInt, yuv_coeffs[0]));
job.Insert(QStringLiteral("yuv_cgu"), NodeValue(NodeValue::kInt, yuv_coeffs[2]));
job.Insert(QStringLiteral("yuv_cgv"), NodeValue(NodeValue::kInt, yuv_coeffs[3]));
job.Insert(QStringLiteral("yuv_cbu"), NodeValue(NodeValue::kInt, yuv_coeffs[1]));
int interlacing = 0;
if (p.src_interlacing != VideoParams::kInterlaceNone) {
if (frame_rate_tb_.isNull()) {
frame_rate_tb_ = av_guess_frame_rate(instance_.fmt_ctx(), instance_.avstream(), hw_in);
// Double frame rate for interlaced fields
frame_rate_tb_ *= 2;
// Flip frame rate so it can be used as a timebase
frame_rate_tb_.flip();
}
int64_t req = Timecode::time_to_timestamp(p.time, frame_rate_tb_);
int64_t frm = Timecode::rescale_timestamp(hw_in->pts - instance_.avstream()->start_time, instance_.avstream()->time_base, frame_rate_tb_);
bool first = (req == frm);
bool top_first = (p.src_interlacing == VideoParams::kInterlacedTopFirst);
interlacing = (first == top_first) ? 1 : 2;
}
job.Insert(QStringLiteral("interlacing"), NodeValue(NodeValue::kInt, interlacing));
job.Insert(QStringLiteral("pixel_height"), NodeValue(NodeValue::kInt, f->height));
tex = p.renderer->CreateTexture(vp);
p.renderer->BlitToTexture(Yuv2RgbShader, job, tex.get(), false);
av_frame_unref(working_frame_);
}
}
if (!tex) {
// Fallback to software pixel format conversion
if (!ApplyScaler(f.get())) {
return nullptr;
}
tex = p.renderer->CreateTexture(vp, working_frame_->data[0], working_frame_->linesize[0] / vp.GetBytesPerPixel());
av_frame_unref(working_frame_);
}
return tex;
// Perform any CPU processing required
f = PreProcessFrame(f, p);
if (!f) {
// Error occurred while software scaling
return nullptr;
}
// Finally, perform any GPU processing required
return ProcessFrameIntoTexture(f, p, original);
}
return nullptr;
@@ -295,19 +268,10 @@ void FFmpegDecoder::CloseInternal()
working_packet_ = nullptr;
}
if (working_frame_) {
av_frame_free(&working_frame_);
working_frame_ = nullptr;
}
ClearFrameCache();
FreeScaler();
instance_.Close();
input_fmt_ = AV_PIX_FMT_NONE;
native_internal_pix_fmt_ = VideoParams::kFormatInvalid;
native_output_pix_fmt_ = VideoParams::kFormatInvalid;
}
rational FFmpegDecoder::GetAudioStartOffset() const
@@ -350,6 +314,11 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, CancelAtom *can
int64_t footage_duration = fmt_ctx->duration;
bool duration_guessed_from_bitrate = (fmt_ctx->duration_estimation_method == AVFMT_DURATION_FROM_BITRATE);
if (duration_guessed_from_bitrate) {
qWarning() << "Unreliable duration detected - we will manually determine it ourselves (this may take some time)";
}
// Dump it into the Footage object
for (unsigned int i=0;i<fmt_ctx->nb_streams;i++) {
@@ -409,15 +378,15 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, CancelAtom *can
if (ret >= 0) {
// Check if we need a manual duration
if (avstream->duration == AV_NOPTS_VALUE) {
if (footage_duration == AV_NOPTS_VALUE) {
if (avstream->duration == AV_NOPTS_VALUE || duration_guessed_from_bitrate) {
if (footage_duration == AV_NOPTS_VALUE || duration_guessed_from_bitrate) {
// Manually read through file for duration
int64_t new_dur;
do {
new_dur = frame->best_effort_timestamp;
} while (instance.GetFrame(pkt, frame) >= 0);
} while (instance.GetFrame(pkt, frame) >= 0 && (!cancelled || !cancelled->IsCancelled()));
avstream->duration = new_dur;
@@ -468,9 +437,9 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, CancelAtom *can
channel_layout = static_cast<uint64_t>(av_get_default_channel_layout(avstream->codecpar->channels));
}
if (avstream->duration == AV_NOPTS_VALUE) {
if (avstream->duration == AV_NOPTS_VALUE || duration_guessed_from_bitrate) {
// Loop through stream until we get the whole duration
if (footage_duration == AV_NOPTS_VALUE) {
if (footage_duration == AV_NOPTS_VALUE || duration_guessed_from_bitrate) {
Instance instance;
instance.Open(filename_c, avstream->index);
@@ -481,7 +450,7 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, CancelAtom *can
do {
new_dur = frame->best_effort_timestamp;
} while (instance.GetFrame(pkt, frame) >= 0);
} while (instance.GetFrame(pkt, frame) >= 0 && (!cancelled || !cancelled->IsCancelled()));
avstream->duration = new_dur;
@@ -721,73 +690,25 @@ const char *FFmpegDecoder::GetInterlacingModeInFFmpeg(VideoParams::Interlacing i
bool FFmpegDecoder::IsPixelFormatGLSLCompatible(AVPixelFormat f)
{
return f == AV_PIX_FMT_YUV420P
|| f == AV_PIX_FMT_YUV422P
|| f == AV_PIX_FMT_YUV444P
|| f == AV_PIX_FMT_YUV420P10LE
|| f == AV_PIX_FMT_YUV422P10LE
|| f == AV_PIX_FMT_YUV444P10LE
|| f == AV_PIX_FMT_YUV420P12LE
|| f == AV_PIX_FMT_YUV422P12LE
|| f == AV_PIX_FMT_YUV444P12LE;
}
/* OLD UNUSED CODE: Keeping this around in case the code proves useful
void FFmpegDecoder::CacheFrameToDisk(AVFrame *f)
{
QFile save_frame(GetIndexFilename().append(QString::number(f->pts)));
if (save_frame.open(QFile::WriteOnly)) {
// Save frame to media index
int cached_buffer_sz = av_image_get_buffer_size(static_cast<AVPixelFormat>(f->format),
f->width,
f->height,
1);
QByteArray cached_frame(cached_buffer_sz, Qt::Uninitialized);
av_image_copy_to_buffer(reinterpret_cast<uint8_t*>(cached_frame.data()),
cached_frame.size(),
f->data,
f->linesize,
static_cast<AVPixelFormat>(f->format),
f->width,
f->height,
1);
save_frame.write(qCompress(cached_frame, 1));
save_frame.close();
DiskManager::instance()->CreatedFile(save_frame.fileName(), QByteArray());
}
// See if we stored this frame in the disk cache
QByteArray frame_loader;
if (!got_frame) {
QFile compressed_frame(GetIndexFilename().append(QString::number(target_ts)));
if (compressed_frame.exists()
&& compressed_frame.size() > 0
&& compressed_frame.open(QFile::ReadOnly)) {
DiskManager::instance()->Accessed(compressed_frame.fileName());
// Read data
frame_loader = qUncompress(compressed_frame.readAll());
av_image_fill_arrays(input_data,
input_linesize,
reinterpret_cast<uint8_t*>(frame_loader.data()),
static_cast<AVPixelFormat>(avstream_->codecpar->format),
avstream_->codecpar->width,
avstream_->codecpar->height,
1);
got_frame = true;
}
// NOTE: We don't include RGB24 or RGB48 here because those are slow on the GPU and performance
// should be better if we convert to RGBA on the CPU beforehand
switch (f) {
case AV_PIX_FMT_YUV420P:
case AV_PIX_FMT_YUV422P:
case AV_PIX_FMT_YUV444P:
case AV_PIX_FMT_YUV420P10LE:
case AV_PIX_FMT_YUV422P10LE:
case AV_PIX_FMT_YUV444P10LE:
case AV_PIX_FMT_YUV420P12LE:
case AV_PIX_FMT_YUV422P12LE:
case AV_PIX_FMT_YUV444P12LE:
case AV_PIX_FMT_RGBA:
case AV_PIX_FMT_RGBA64LE:
return true;
default:
return false;
}
}
*/
void FFmpegDecoder::ClearFrameCache()
{
@@ -798,14 +719,98 @@ void FFmpegDecoder::ClearFrameCache()
}
}
AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, VideoParams::Interlacing interlacing, CancelAtom *cancelled)
AVFramePtr FFmpegDecoder::PreProcessFrame(AVFramePtr f, const RetrieveVideoParams &p)
{
// In pre-processing, we try to achieve the following:
// - If a divider is being used, scale down the image
// - If a pixel format is not compatible with the GLSL shader, convert it to RGBA ourselves
if (p.divider == 1 && IsPixelFormatGLSLCompatible(static_cast<AVPixelFormat>(f->format))) {
// No CPU processing required, the user wants this in full resolution and the pixel format can
// be converted on the GPU
return f;
}
// Some scaling and/or format conversion needs to be done
AVFramePtr dest = CreateAVFramePtr();
dest->width = f->width;
dest->height = f->height;
dest->format = f->format;
dest->color_range = f->color_range;
dest->colorspace = f->colorspace;
if (p.divider > 1) {
dest->width = VideoParams::GetScaledDimension(dest->width, p.divider);
dest->height = VideoParams::GetScaledDimension(dest->height, p.divider);
}
if (!IsPixelFormatGLSLCompatible(static_cast<AVPixelFormat>(dest->format))) {
dest->format = FFmpegUtils::GetCompatiblePixelFormat(static_cast<AVPixelFormat>(dest->format), p.maximum_format);
}
int r = av_frame_get_buffer(dest.get(), 0);
if (r < 0) {
FFmpegError(r);
return nullptr;
}
if (!sws_ctx_
|| sws_src_width_ != f->width
|| sws_src_height_ != f->height
|| sws_src_format_ != f->format
|| sws_dst_width_ != dest->width
|| sws_dst_height_ != dest->height
|| sws_dst_format_ != dest->format
|| sws_colrange_ != dest->color_range
|| sws_colspace_ != dest->colorspace) {
// SwsContext must be recreated, destroy current if it exists
FreeScaler();
// Cache info
sws_src_width_ = f->width;
sws_src_height_ = f->height;
sws_src_format_ = static_cast<AVPixelFormat>(f->format);
sws_dst_width_ = dest->width;
sws_dst_height_ = dest->height;
sws_dst_format_ = static_cast<AVPixelFormat>(dest->format);
sws_colrange_ = dest->color_range;
sws_colspace_ = dest->colorspace;
// Create new scaler
sws_ctx_ = sws_getContext(sws_src_width_,
sws_src_height_,
sws_src_format_,
sws_dst_width_,
sws_dst_height_,
sws_dst_format_,
SWS_POINT,
nullptr,
nullptr,
nullptr);
// Set swscale's colorspace details
sws_setColorspaceDetails(sws_ctx_,
sws_getCoefficients(FFmpegUtils::GetSwsColorspaceFromAVColorSpace(dest->colorspace)),
dest->color_range == AVCOL_RANGE_JPEG ? 1 : 0,
sws_getCoefficients(FFmpegUtils::GetSwsColorspaceFromAVColorSpace(dest->colorspace)),
dest->color_range == AVCOL_RANGE_JPEG ? 1 : 0,
0, 0x10000, 0x10000);
}
r = sws_scale(sws_ctx_, f->data, f->linesize, 0, f->height, dest->data, dest->linesize);
if (r < 0) {
FFmpegError(r);
return nullptr;
}
return dest;
}
AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, CancelAtom *cancelled)
{
int64_t target_ts = Timecode::time_to_timestamp(time, instance_.avstream()->time_base);
if (interlacing != VideoParams::kInterlaceNone && !IsPixelFormatGLSLCompatible(static_cast<AVPixelFormat>(instance_.avstream()->codecpar->format))) {
target_ts *= 2;
}
if (instance_.fmt_ctx()->start_time != AV_NOPTS_VALUE) {
target_ts += av_rescale_q(instance_.fmt_ctx()->start_time, {1, AV_TIME_BASE}, instance_.avstream()->time_base);
}
@@ -820,9 +825,6 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, VideoParams::Inter
|| (target_ts < cached_frames_.front()->pts || target_ts > cached_frames_.back()->pts + 2*second_ts_)) {
ClearFrameCache();
// Filter graph may rely on "continuous" video frames, so we free the scaler here
//ResetScaler();
instance_.Seek(seek_ts);
if (seek_ts == min_seek) {
cache_at_zero_ = true;
@@ -849,7 +851,7 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, VideoParams::Inter
}
if (!filtered) {
filtered = CreateAVFramePtr(av_frame_alloc());
filtered = CreateAVFramePtr();
}
// Pull from the decoder
@@ -938,143 +940,11 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, VideoParams::Inter
return return_frame;
}
bool FFmpegDecoder::InitScaler(AVFrame *input, const RetrieveVideoParams& params)
{
if (params.divider == filter_params_.divider
&& params.force_range == filter_params_.force_range
&& params.maximum_format == filter_params_.maximum_format
&& params.src_interlacing == filter_params_.src_interlacing
&& filter_graph_
&& input_fmt_ == input->format) {
// We have an appropriate filter for these parameters, just return true
return true;
}
// We need to (re)create the filter, delete current if necessary
ClearFrameCache();
FreeScaler();
// Set our params to this
filter_params_ = params;
input_fmt_ = static_cast<AVPixelFormat>(input->format);
if (input_fmt_ == AV_PIX_FMT_NONE) {
return false;
}
// Get an Olive compatible AVPixelFormat
AVPixelFormat ideal_pix_fmt = FFmpegUtils::GetCompatiblePixelFormat(static_cast<AVPixelFormat>(input_fmt_), params.maximum_format);
// Determine which Olive native pixel format we retrieved
// Note that FFmpeg doesn't support float formats
native_output_pix_fmt_ = GetNativePixelFormat(ideal_pix_fmt);
native_channel_count_ = GetNativeChannelCount(ideal_pix_fmt);
AVPixelFormat ideal_internal_pix_fmt = FFmpegUtils::GetCompatiblePixelFormat(static_cast<AVPixelFormat>(input_fmt_));
native_internal_pix_fmt_ = GetNativePixelFormat(ideal_internal_pix_fmt);
if (native_output_pix_fmt_ == VideoParams::kFormatInvalid
|| native_internal_pix_fmt_ == VideoParams::kFormatInvalid
|| native_channel_count_ == 0) {
qCritical() << "Failed to find valid native pixel format for" << ideal_pix_fmt;
return false;
}
// Allocate filter graph
filter_graph_ = avfilter_graph_alloc();
if (!filter_graph_) {
qWarning() << "Failed to allocate filter graph";
return false;
}
AVStream* s = instance_.avstream();
int src_width = s->codecpar->width;
int src_height = s->codecpar->height;
// Define filter parameters
static const int kFilterArgSz = 1024;
char filter_args[kFilterArgSz];
snprintf(filter_args, kFilterArgSz, "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
src_width,
src_height,
input->format,
s->time_base.num,
s->time_base.den,
s->codecpar->sample_aspect_ratio.num,
s->codecpar->sample_aspect_ratio.den);
// Create path in and out of the filter graph (the buffer in and the buffersink out)
avfilter_graph_create_filter(&buffersrc_ctx_, avfilter_get_by_name("buffer"), "in", filter_args, nullptr, filter_graph_);
avfilter_graph_create_filter(&buffersink_ctx_, avfilter_get_by_name("buffersink"), "out", nullptr, nullptr, filter_graph_);
// Link filters as necessary
AVFilterContext *last_filter = buffersrc_ctx_;
bool glsl_available = IsPixelFormatGLSLCompatible(static_cast<AVPixelFormat>(input->format));
// Add deinterlace filter if necessary
if (filter_params_.src_interlacing != VideoParams::kInterlaceNone && !glsl_available) {
AVFilterContext* deint_filter;
snprintf(filter_args, kFilterArgSz, "mode=1:parity=%s",
filter_params_.src_interlacing == VideoParams::kInterlacedTopFirst ? "0" : "1");
avfilter_graph_create_filter(&deint_filter, avfilter_get_by_name("yadif"), "deint", filter_args, nullptr, filter_graph_);
avfilter_link(last_filter, 0, deint_filter, 0);
last_filter = deint_filter;
}
// Add scale filter if necessary
if (filter_params_.divider > 1) {
AVFilterContext* scale_filter;
int dst_width, dst_height;
dst_width = VideoParams::GetScaledDimension(src_width, filter_params_.divider);
dst_height = VideoParams::GetScaledDimension(src_height, filter_params_.divider);
snprintf(filter_args, kFilterArgSz, "w=%d:h=%d:flags=fast_bilinear:interl=0",
dst_width,
dst_height);
avfilter_graph_create_filter(&scale_filter, avfilter_get_by_name("scale"), "scale", filter_args, nullptr, filter_graph_);
avfilter_link(last_filter, 0, scale_filter, 0);
last_filter = scale_filter;
}
// Add format filter if necessary
if (ideal_pix_fmt != input->format && !glsl_available) {
AVFilterContext* format_filter;
snprintf(filter_args, kFilterArgSz, "pix_fmts=%u", ideal_pix_fmt);
avfilter_graph_create_filter(&format_filter, avfilter_get_by_name("format"), "format", filter_args, nullptr, filter_graph_);
avfilter_link(last_filter, 0, format_filter, 0);
last_filter = format_filter;
}
// Finally, link the last filter with the buffersink
avfilter_link(last_filter, 0, buffersink_ctx_, 0);
// Configure graph
if (int ret = avfilter_graph_config(filter_graph_, nullptr) < 0) {
qCritical() << "Failed to configure graph:" << FFmpegError(ret);
return false;
}
return true;
}
void FFmpegDecoder::FreeScaler()
{
if (filter_graph_) {
avfilter_graph_free(&filter_graph_);
filter_graph_ = nullptr;
buffersrc_ctx_ = nullptr;
buffersink_ctx_ = nullptr;
if (sws_ctx_) {
sws_freeContext(sws_ctx_);
sws_ctx_ = nullptr;
}
}
@@ -1119,22 +989,6 @@ void FFmpegDecoder::RemoveFirstFrame()
cache_at_zero_ = false;
}
bool FFmpegDecoder::ApplyScaler(AVFrame *in)
{
int r;
r = av_buffersrc_add_frame_flags(buffersrc_ctx_, in, AV_BUFFERSRC_FLAG_KEEP_REF);
if (r < 0) {
return false;
}
r = av_buffersink_get_frame(buffersink_ctx_, working_frame_);
if (r < 0) {
return false;
}
return true;
}
int FFmpegDecoder::MaximumQueueSize()
{
// Fairly arbitrary size. This used to need to be the number of current threads to ensure any
+14 -14
View File
@@ -134,7 +134,6 @@ private:
*/
static QString FFmpegError(int error_code);
bool InitScaler(AVFrame *input, const RetrieveVideoParams &params);
void FreeScaler();
static VideoParams::Format GetNativePixelFormat(AVPixelFormat pix_fmt);
@@ -150,25 +149,26 @@ private:
void ClearFrameCache();
AVFramePtr RetrieveFrame(const rational &time, VideoParams::Interlacing interlacing, CancelAtom *cancelled);
AVFramePtr PreProcessFrame(AVFramePtr f, const RetrieveVideoParams &p);
TexturePtr ProcessFrameIntoTexture(AVFramePtr f, const RetrieveVideoParams &p, const AVFramePtr original);
AVFramePtr RetrieveFrame(const rational &time, CancelAtom *cancelled);
void RemoveFirstFrame();
bool ApplyScaler(AVFrame *in);
static int MaximumQueueSize();
RetrieveVideoParams filter_params_;
AVFilterGraph* filter_graph_;
AVFilterContext* buffersrc_ctx_;
AVFilterContext* buffersink_ctx_;
AVPixelFormat input_fmt_;
VideoParams::Format native_internal_pix_fmt_;
VideoParams::Format native_output_pix_fmt_;
int native_channel_count_;
rational frame_rate_tb_;
SwsContext *sws_ctx_;
int sws_src_width_;
int sws_src_height_;
AVPixelFormat sws_src_format_;
int sws_dst_width_;
int sws_dst_height_;
AVPixelFormat sws_dst_format_;
AVColorRange sws_colrange_;
AVColorSpace sws_colspace_;
AVFrame *working_frame_;
AVPacket *working_packet_;
int64_t second_ts_;
+34 -19
View File
@@ -280,28 +280,43 @@ bool FFmpegEncoder::WriteFrame(FramePtr frame, rational time)
bool FFmpegEncoder::WriteAudio(const SampleBuffer &audio)
{
bool result = true;
// Create input buffer
int input_sample_count = 0;
uint8_t** input_data = nullptr;
if (audio.is_allocated()) {
input_sample_count = audio.sample_count();
int input_linesize;
av_samples_alloc_array_and_samples(&input_data, &input_linesize, audio.audio_params().channel_count(),
input_sample_count, FFmpegUtils::GetFFmpegSampleFormat(audio.audio_params().format()), 0);
for (int i=0; i<audio.audio_params().channel_count(); i++) {
memcpy(input_data[i], audio.data(i), input_sample_count * audio.audio_params().bytes_per_sample_per_channel());
}
if (!audio.is_allocated()) {
return true;
}
result = WriteAudioData(audio.audio_params().is_valid() ? audio.audio_params() : params().audio_params(), const_cast<const uint8_t**>(input_data), input_sample_count);
bool result = true;
if (input_data) {
av_freep(&input_data[0]);
av_freep(&input_data);
size_t start = 0;
size_t end = audio.sample_count();
const size_t max_frame = 48000;
while (result && start < end) {
// Create input buffer
uint8_t** input_data = nullptr;
size_t input_sample_count = std::min(end - start, max_frame);
int input_linesize;
int r = av_samples_alloc_array_and_samples(&input_data, &input_linesize, audio.audio_params().channel_count(),
input_sample_count, FFmpegUtils::GetFFmpegSampleFormat(audio.audio_params().format()), 0);
if (r < 0) {
FFmpegError(tr("Failed to allocate sample array"), r);
return false;
} else {
int bpsc = audio.audio_params().bytes_per_sample_per_channel();
for (int i=0; i<audio.audio_params().channel_count(); i++) {
memcpy(input_data[i], audio.data(i) + start, input_sample_count * bpsc);
}
start += input_sample_count;
}
result = WriteAudioData(audio.audio_params().is_valid() ? audio.audio_params() : params().audio_params(), const_cast<const uint8_t**>(input_data), input_sample_count);
if (input_data) {
av_freep(&input_data[0]);
av_freep(&input_data);
}
}
return result;
+2 -2
View File
@@ -24,7 +24,7 @@ namespace olive {
AVPixelFormat FFmpegUtils::GetCompatiblePixelFormat(const AVPixelFormat &pix_fmt, VideoParams::Format maximum)
{
std::vector<AVPixelFormat> possible_pix_fmts(3);
AVPixelFormat possible_pix_fmts[3];
possible_pix_fmts[0] = AV_PIX_FMT_RGBA;
@@ -35,7 +35,7 @@ AVPixelFormat FFmpegUtils::GetCompatiblePixelFormat(const AVPixelFormat &pix_fmt
possible_pix_fmts[2] = AV_PIX_FMT_NONE;
}
return avcodec_find_best_pix_fmt_of_list(possible_pix_fmts.data(),
return avcodec_find_best_pix_fmt_of_list(possible_pix_fmts,
pix_fmt,
1,
nullptr);
+4
View File
@@ -82,6 +82,10 @@ inline AVFramePtr CreateAVFramePtr(AVFrame *f)
{
return std::shared_ptr<AVFrame>(f, [](AVFrame *g){ av_frame_free(&g); });
}
inline AVFramePtr CreateAVFramePtr()
{
return CreateAVFramePtr(av_frame_alloc());
}
}
+7 -21
View File
@@ -12,22 +12,7 @@ const QVector<QString> Html::kBlockTags = {
QStringLiteral("div")
};
inline bool StrEquals(const QString &a, const QStringRef &b)
{
return !a.compare(b, Qt::CaseInsensitive);
}
inline bool StrEquals(const QString &a, const QString &b)
{
return !a.compare(b, Qt::CaseInsensitive);
}
inline bool StrEquals(const QStringRef &a, const QString &b)
{
return !a.compare(b, Qt::CaseInsensitive);
}
inline bool StrEquals(const QStringRef &a, const QStringRef &b)
inline bool StrEquals(const QStringView &a, const QStringView &b)
{
return !a.compare(b, Qt::CaseInsensitive);
}
@@ -223,8 +208,9 @@ void Html::WriteCSSProperty(QString *style, const QString &key, const QStringLis
void Html::WriteCharFormat(QString *style, const QTextCharFormat &fmt)
{
if (!fmt.fontFamily().isEmpty()) {
WriteCSSProperty(style, QStringLiteral("font-family"), fmt.fontFamily());
QStringList families = fmt.fontFamilies().toStringList();
if (!families.isEmpty()) {
WriteCSSProperty(style, QStringLiteral("font-family"), families.first());
}
if (fmt.hasProperty(QTextFormat::FontPointSize)) {
@@ -302,7 +288,7 @@ QTextCharFormat Html::ReadCharFormat(const QXmlStreamAttributes &attributes)
const QString &first_val = it.value().first();
if (it.key() == QStringLiteral("font-family")) {
fmt.setFontFamily(first_val);
fmt.setFontFamilies({first_val});
} else if (it.key() == QStringLiteral("font-size")) {
if (first_val.endsWith(QStringLiteral("pt"), Qt::CaseInsensitive)) {
fmt.setFontPointSize(first_val.chopped(2).toDouble());
@@ -421,7 +407,7 @@ QMap<QString, QStringList> Html::GetCSSFromStyle(const QString &s)
// match. Also commas should be filtered out.
QStringList values;
const QString &val = kv.at(1);
QChar in_quote = 0;
QChar in_quote(0);
QString current_str;
for (int i=0; i<val.size(); i++) {
const QChar &current_char = val.at(i);
@@ -429,7 +415,7 @@ QMap<QString, QStringList> Html::GetCSSFromStyle(const QString &s)
if (!in_quote.isNull()) {
// If inside quotes and character isn't quote, indiscriminately append char
if (current_char == in_quote) {
in_quote = 0;
in_quote = QChar(0);
} else {
current_str.append(current_char);
}
-1
View File
@@ -25,7 +25,6 @@
#include <QApplication>
#include <QDateTime>
#include <QDebug>
#include <QLinkedList>
#include <QMutex>
#include <QTimer>
#include <stdint.h>
+2 -1
View File
@@ -22,6 +22,7 @@
#include <QCoreApplication>
#include <QMessageBox>
#include <QRegularExpression>
namespace olive {
@@ -49,7 +50,7 @@ double GetFloatRatioFromUser(QWidget* parent,
return qSNaN();
}
QStringList ratio_components = s.split(QRegExp(QStringLiteral(":|;|\\/")));
QStringList ratio_components = s.split(QRegularExpression(QStringLiteral(":|;|\\/")));
if (ratio_components.size() == 1) {
bool float_ok;
+85 -73
View File
@@ -24,6 +24,7 @@ extern "C" {
#include <libavutil/mathematics.h>
}
#include <QRegularExpression>
#include <QtMath>
#include "config/config.h"
@@ -34,16 +35,13 @@ QString padded(int64_t arg, int padding) {
return QStringLiteral("%1").arg(arg, padding, 10, QChar('0'));
}
QString Timecode::timestamp_to_timecode(const int64_t &timestamp,
const rational& timebase,
const Display& display,
bool show_plus_if_positive)
QString Timecode::time_to_timecode(const rational &time, const rational &timebase, const Timecode::Display &display, bool show_plus_if_positive)
{
if (timebase.isNull()) {
return QStringLiteral("INVALID TIMEBASE");
}
double timestamp_dbl = (rational(timestamp) * timebase).toDouble();
double time_dbl = time.toDouble();
switch (display) {
case kTimecodeNonDropFrame:
@@ -52,21 +50,21 @@ QString Timecode::timestamp_to_timecode(const int64_t &timestamp,
{
QString prefix;
if (timestamp_dbl < 0) {
if (time_dbl < 0) {
prefix = "-";
} else if (show_plus_if_positive) {
prefix = "+";
}
if (display == kTimecodeSeconds) {
timestamp_dbl = qAbs(timestamp_dbl);
time_dbl = qAbs(time_dbl);
int64_t total_seconds = qFloor(timestamp_dbl);
int64_t total_seconds = qFloor(time_dbl);
int64_t hours = total_seconds / 3600;
int64_t mins = total_seconds / 60 - hours * 60;
int64_t secs = total_seconds - mins * 60;
int64_t fraction = qRound64((timestamp_dbl - static_cast<double>(total_seconds)) * 1000);
int64_t fraction = qRound64((time_dbl - static_cast<double>(total_seconds)) * 1000);
return QStringLiteral("%1%2:%3:%4.%5").arg(prefix,
padded(hours, 2),
@@ -79,7 +77,7 @@ QString Timecode::timestamp_to_timecode(const int64_t &timestamp,
double frame_rate = timebase.flipped().toDouble();
int rounded_frame_rate = qRound(frame_rate);
int64_t frames, secs, mins, hours;
int64_t f = qAbs(timestamp);
int64_t f = qAbs(time_to_timestamp(time, timebase));
if (display == kTimecodeDropFrame && TimebaseIsDropFrame(timebase)) {
frame_token = ";";
@@ -127,18 +125,36 @@ QString Timecode::timestamp_to_timecode(const int64_t &timestamp,
}
}
case kFrames:
return QString::number(timestamp);
return QString::number(time_to_timestamp(time, timebase));
case kMilliseconds:
return QString::number(qRound(timestamp_dbl * 1000));
return QString::number(qRound(time_dbl * 1000));
}
return QStringLiteral("INVALID TIMECODE MODE");
}
int64_t Timecode::timecode_to_timestamp(const QString &timecode, const rational &timebase, const Display &display, bool* ok)
int64_t StrToInt64EmptyTolerant(const QString &s, bool *ok)
{
double timebase_dbl = timebase.toDouble();
if (s.isEmpty()) {
if (ok) *ok = true;
return 0;
} else {
return s.toLongLong(ok);
}
}
double StrToDoubleEmptyTolerant(const QString &s, bool *ok)
{
if (s.isEmpty()) {
if (ok) *ok = true;
return 0;
} else {
return s.toDouble(ok);
}
}
rational Timecode::timecode_to_time(const QString &timecode, const rational &timebase, const Timecode::Display &display, bool *ok)
{
if (timecode.isEmpty()) {
goto err_fatal;
}
@@ -148,71 +164,73 @@ int64_t Timecode::timecode_to_timestamp(const QString &timecode, const rational
case kTimecodeDropFrame:
case kTimecodeSeconds:
{
const int kTimecodeElementCount = 4;
QStringList timecode_split = timecode.split(QRegExp("(:)|(;)|(\\.)"));
QStringList timecode_split = timecode.split(QRegularExpression("(:)|(;)"));
bool valid;
const int element_count = display == kTimecodeSeconds ? 3 : 4;
// We only deal with HH, MM, SS, and FF. Any values after that are ignored.
while (timecode_split.size() > kTimecodeElementCount) {
// Remove excess tokens (we're only interested in HH:MM:SS.FF)
while (timecode_split.size() > element_count) {
timecode_split.removeLast();
}
// Convert values to integers
QList<int64_t> timecode_numbers;
// For easier index calculations, ensure minimum size
while (timecode_split.size() < element_count) {
timecode_split.prepend(QString());
}
bool negative = timecode.trimmed().startsWith('-');
foreach (const QString& element, timecode_split) {
valid = true;
timecode_numbers.append((element.isEmpty()) ? 0 : qAbs(element.toLong(&valid)));
// If element cannot be converted to a number,
if (!valid) {
goto err_fatal;
}
}
// Ensure value size is always 4
while (timecode_numbers.size() < 4) {
timecode_numbers.prepend(0);
}
double frame_rate = timebase.flipped().toDouble();
int rounded_frame_rate = qRound(frame_rate);
int64_t hours = timecode_numbers.at(0);
int64_t mins = timecode_numbers.at(1);
int64_t secs = timecode_numbers.at(2);
int64_t frames = timecode_numbers.at(3);
bool valid;
rational time;
int64_t sec_count = (hours*3600 + mins*60 + secs);
int64_t timestamp = sec_count*rounded_frame_rate + frames;
int64_t hours = StrToInt64EmptyTolerant(timecode_split.at(0), &valid);
if (!valid) goto err_fatal;
int64_t mins = StrToInt64EmptyTolerant(timecode_split.at(1), &valid);
if (!valid) goto err_fatal;
if (display == kTimecodeDropFrame && TimebaseIsDropFrame(timebase)) {
if (display == kTimecodeSeconds) {
double secs = StrToDoubleEmptyTolerant(timecode_split.at(2), &valid);
if (!valid) goto err_fatal;
// Number of frames to drop on the minute marks is the nearest integer to 6% of the framerate
int64_t dropFrames = qRound64(frame_rate * (2.0/30.0));
time = rational::fromDouble(hours * 3600 + mins * 60 + secs);
} else {
int64_t secs = StrToInt64EmptyTolerant(timecode_split.at(2), &valid);
if (!valid) goto err_fatal;
int64_t frames = StrToInt64EmptyTolerant(timecode_split.at(3), &valid);
if (!valid) goto err_fatal;
// d and m need to be calculated from
int64_t real_fr_ts = qRound64(static_cast<double>(sec_count)*frame_rate) + frames;
int64_t sec_count = (hours*3600 + mins*60 + secs);
int64_t frame_count = sec_count*rounded_frame_rate + frames;
int64_t framesPer10Minutes = qRound(frame_rate * 600);
int64_t d = real_fr_ts / framesPer10Minutes;
int64_t m = real_fr_ts % framesPer10Minutes;
if (display == kTimecodeDropFrame && TimebaseIsDropFrame(timebase)) {
if (m > dropFrames) {
timestamp -= dropFrames * ((m - dropFrames) / (qRound(frame_rate)*60 - dropFrames));
// Number of frames to drop on the minute marks is the nearest integer to 6% of the framerate
int64_t dropFrames = qRound64(frame_rate * (2.0/30.0));
// d and m need to be calculated from
int64_t real_fr_ts = qRound64(static_cast<double>(sec_count)*frame_rate) + frames;
int64_t framesPer10Minutes = qRound(frame_rate * 600);
int64_t d = real_fr_ts / framesPer10Minutes;
int64_t m = real_fr_ts % framesPer10Minutes;
if (m > dropFrames) {
frame_count -= dropFrames * ((m - dropFrames) / (qRound(frame_rate)*60 - dropFrames));
}
frame_count -= dropFrames*9*d;
}
timestamp -= dropFrames*9*d;
time = timestamp_to_time(frame_count, timebase);
}
if (ok) *ok = true;
if (negative) timestamp = -timestamp;
if (negative) time = -time;
return timestamp;
return time;
}
case kMilliseconds:
{
@@ -223,18 +241,23 @@ int64_t Timecode::timecode_to_timestamp(const QString &timecode, const rational
// Convert milliseconds to seconds
timecode_secs *= 0.001;
// Convert seconds to frames
timecode_secs /= timebase_dbl;
if (ok) *ok = true;
return qRound(timecode_secs);
// Convert seconds to rational
return rational::fromDouble(timecode_secs, ok);
} else {
goto err_fatal;
}
}
case kFrames:
{
bool valid;
int64_t ts = timecode.toLongLong(&valid);
if (!valid) {
goto err_fatal;
}
if (ok) *ok = true;
return timecode.toLong(ok);
return timestamp_to_time(ts, timebase);
}
}
err_fatal:
@@ -242,12 +265,6 @@ err_fatal:
return 0;
}
rational Timecode::timecode_to_time(const QString &timecode, const rational &timebase, const Timecode::Display &display, bool *ok)
{
int64_t timestamp = timecode_to_timestamp(timecode, timebase, display, ok);
return timestamp_to_time(timestamp, timebase);
}
rational Timecode::snap_time_to_timebase(const rational &time, const rational &timebase, Rounding floor)
{
// Just convert to a timestamp in timebase units and back
@@ -268,11 +285,6 @@ rational Timecode::timestamp_to_time(const int64_t &timestamp, const rational &t
return rational(num_r, den_r);
}
QString Timecode::time_to_timecode(const rational &time, const rational &timebase, const Timecode::Display &display, bool show_plus_if_positive)
{
return timestamp_to_timecode(time_to_timestamp(time, timebase), timebase, display, show_plus_if_positive);
}
bool Timecode::TimebaseIsDropFrame(const rational &timebase)
{
return (timebase.numerator() != 1);
+1 -5
View File
@@ -56,9 +56,7 @@ public:
/**
* @brief Convert a timestamp (according to a rational timebase) to a user-friendly string representation
*/
static QString timestamp_to_timecode(const int64_t &timestamp, const rational& timebase, const Display &display, bool show_plus_if_positive = false);
static int64_t timecode_to_timestamp(const QString& timecode, const rational& timebase, const Display& display, bool *ok = nullptr);
static QString time_to_timecode(const rational& time, const rational& timebase, const Display &display, bool show_plus_if_positive = false);
static rational timecode_to_time(const QString& timecode, const rational& timebase, const Display& display, bool *ok = nullptr);
static rational snap_time_to_timebase(const rational& time, const rational& timebase, Rounding floor = kRound);
@@ -71,8 +69,6 @@ public:
static rational timestamp_to_time(const int64_t& timestamp, const rational& timebase);
static QString time_to_timecode(const rational& time, const rational& timebase, const Display &display, bool show_plus_if_positive = false);
static bool TimebaseIsDropFrame(const rational& timebase);
static QString TimeToString(int64_t ms);
+2 -1
View File
@@ -82,7 +82,7 @@ void Config::SetDefaults()
SetEntryInternal(QStringLiteral("InvertTimelineScrollAxes"), NodeValue::kBoolean, true);
SetEntryInternal(QStringLiteral("SelectAlsoSeeks"), NodeValue::kBoolean, false);
SetEntryInternal(QStringLiteral("PasteSeeks"), NodeValue::kBoolean, true);
SetEntryInternal(QStringLiteral("SelectAlsoSeeks"), NodeValue::kBoolean, false);
SetEntryInternal(QStringLiteral("SeekAlsoSelects"), NodeValue::kBoolean, false);
SetEntryInternal(QStringLiteral("SetNameWithMarker"), NodeValue::kBoolean, false);
SetEntryInternal(QStringLiteral("AutoSeekToBeginning"), NodeValue::kBoolean, true);
SetEntryInternal(QStringLiteral("DropFileOnMediaToReplace"), NodeValue::kBoolean, false);
@@ -104,6 +104,7 @@ void Config::SetDefaults()
SetEntryInternal(QStringLiteral("UseLegacyColorInInputTab"), NodeValue::kBoolean, false);
SetEntryInternal(QStringLiteral("ReassocLinToNonLin"), NodeValue::kBoolean, false);
SetEntryInternal(QStringLiteral("PreviewNonFloatDontAskAgain"), NodeValue::kBoolean, false);
SetEntryInternal(QStringLiteral("UseGLFinish"), NodeValue::kBoolean, false);
SetEntryInternal(QStringLiteral("TimelineThumbnailMode"), NodeValue::kInt, Timeline::kThumbnailInOut);
SetEntryInternal(QStringLiteral("TimelineWaveformMode"), NodeValue::kInt, Timeline::kWaveformsEnabled);
+79 -48
View File
@@ -87,6 +87,7 @@ Core::Core(const CoreParams& params) :
addable_object_(Tool::kAddableEmpty),
snapping_(true),
core_params_(params),
magic_(false),
pixel_sampling_users_(0),
shown_cache_full_warning_(false)
{
@@ -198,15 +199,6 @@ void Core::Stop()
// Save Config
Config::Save();
// Save recently opened projects
{
QFile recent_projects_file(GetRecentProjectsFilePath());
if (recent_projects_file.open(QFile::WriteOnly | QFile::Text)) {
recent_projects_file.write(recent_projects_.join('\n').toUtf8());
recent_projects_file.close();
}
}
ProjectSerializer::Destroy();
ConformManager::DestroyInstance();
@@ -292,6 +284,7 @@ void Core::SetSelectedTransitionObject(const QString &obj)
void Core::ClearOpenRecentList()
{
recent_projects_.clear();
SaveRecentProjectsList();
emit OpenRecentListChanged();
}
@@ -387,11 +380,8 @@ void Core::DialogProjectPropertiesShow()
void Core::DialogExportShow()
{
ViewerOutput* viewer;
rational time;
if (GetSequenceToExport(&viewer, &time)) {
OpenExportDialogForViewer(viewer, time, false);
if (ViewerOutput* viewer = GetSequenceToExport()) {
OpenExportDialogForViewer(viewer, false);
}
}
@@ -478,7 +468,7 @@ void Core::CreateNewSequence()
}
}
void Core::AddOpenProject(Project* p)
void Core::AddOpenProject(Project* p, bool add_to_recents)
{
// Ensure project is not open at the moment
foreach (Project* already_open, open_projects_) {
@@ -497,12 +487,14 @@ void Core::AddOpenProject(Project* p)
connect(p, &Project::ModifiedChanged, this, &Core::ProjectWasModified);
open_projects_.append(p);
PushRecentlyOpenedProject(p->filename());
if (!p->filename().isEmpty() && add_to_recents) {
PushRecentlyOpenedProject(p->filename());
}
emit ProjectOpened(p);
}
bool Core::AddOpenProjectFromTask(Task *task)
bool Core::AddOpenProjectFromTask(Task *task, bool add_to_recents)
{
ProjectLoadBaseTask* load_task = static_cast<ProjectLoadBaseTask*>(task);
@@ -510,7 +502,7 @@ bool Core::AddOpenProjectFromTask(Task *task)
Project* project = load_task->GetLoadedProject();
if (ValidateFootageInLoadedProject(project, project->GetSavedURL())) {
AddOpenProject(project);
AddOpenProject(project, add_to_recents);
main_window_->LoadLayout(project->GetLayoutInfo());
return true;
@@ -733,7 +725,7 @@ void Core::OpenStartupProject()
void Core::AddRecoveryProjectFromTask(Task *task)
{
if (AddOpenProjectFromTask(task)) {
if (AddOpenProjectFromTask(task, false)) {
ProjectLoadBaseTask* load_task = static_cast<ProjectLoadBaseTask*>(task);
Project* project = load_task->GetLoadedProject();
@@ -858,7 +850,7 @@ void Core::SaveProjectInternal(Project* project, const QString& override_filenam
psm->deleteLater();
}
bool Core::GetSequenceToExport(ViewerOutput **viewer, rational *time)
ViewerOutput *Core::GetSequenceToExport()
{
// First try the most recently focused time based window
TimeBasedPanel* time_panel = PanelManager::instance()->MostRecentlyFocused<TimeBasedPanel>();
@@ -876,9 +868,7 @@ bool Core::GetSequenceToExport(ViewerOutput **viewer, rational *time)
tr("This Sequence is empty. There is nothing to export."),
QMessageBox::Ok);
} else {
*viewer = time_panel->GetConnectedViewer();
*time = time_panel->GetTime();
return true;
return time_panel->GetConnectedViewer();
}
} else {
QMessageBox::critical(main_window_,
@@ -887,7 +877,7 @@ bool Core::GetSequenceToExport(ViewerOutput **viewer, rational *time)
QMessageBox::Ok);
}
return false;
return nullptr;
}
QString Core::GetAutoRecoveryIndexFilename()
@@ -960,6 +950,16 @@ bool Core::RevertProjectInternal(Project *p, bool by_opening_existing)
return false;
}
void Core::SaveRecentProjectsList()
{
// Save recently opened projects
QFile recent_projects_file(GetRecentProjectsFilePath());
if (recent_projects_file.open(QFile::WriteOnly | QFile::Text)) {
recent_projects_file.write(recent_projects_.join('\n').toUtf8());
recent_projects_file.close();
}
}
void Core::SaveAutorecovery()
{
if (OLIVE_CONFIG("AutorecoveryEnabled").toBool()) {
@@ -1040,6 +1040,8 @@ void Core::ProjectSaveSucceeded(Task* task)
autorecovered_projects_.removeOne(p->GetUuid());
SaveUnrecoveredList();
ShowStatusBarMessage(tr("Saved to \"%1\" successfully").arg(p->filename()));
}
Project* Core::GetActiveProject() const
@@ -1247,10 +1249,9 @@ void Core::OpenNodeInViewer(ViewerOutput *viewer)
main_window_->OpenNodeInViewer(viewer);
}
void Core::OpenExportDialogForViewer(ViewerOutput *viewer, const rational &time, bool start_still_image)
void Core::OpenExportDialogForViewer(ViewerOutput *viewer, bool start_still_image)
{
ExportDialog* ed = new ExportDialog(viewer, start_still_image, main_window_);
ed->SetTime(time);
connect(ed, &ExportDialog::finished, ed, &ExportDialog::deleteLater);
ed->open();
connect(ed, &ExportDialog::RequestImportFile, this, &Core::ImportSingleFile);
@@ -1372,6 +1373,8 @@ void Core::PushRecentlyOpenedProject(const QString& s)
}
}
SaveRecentProjectsList();
emit OpenRecentListChanged();
}
@@ -1418,7 +1421,7 @@ void Core::OpenProjectInternal(const QString &filename, bool recovery_project)
if (recovery_project) {
connect(task_dialog, &TaskDialog::TaskSucceeded, this, &Core::AddRecoveryProjectFromTask);
} else {
connect(task_dialog, &TaskDialog::TaskSucceeded, this, &Core::AddOpenProjectFromTask);
connect(task_dialog, &TaskDialog::TaskSucceeded, this, &Core::AddOpenProjectFromTaskAndAddToRecents);
}
task_dialog->open();
@@ -1523,6 +1526,8 @@ void Core::OpenProjectFromRecentList(int index)
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
recent_projects_.removeAt(index);
SaveRecentProjectsList();
emit OpenRecentListChanged();
}
}
@@ -1688,37 +1693,63 @@ void Core::CacheActiveSequence(bool in_out_only)
}
}
QString StripWindowsDriveLetter(QString s)
{
// HACK: On Windows, absolute paths are saved with a drive letter (e.g. "C:\video.mp4"). Below,
// we use Qt's relative path system to resolve when an entire project may be in a different
// folder, but the files are all in the same place relatively to the project. Unfortunately,
// Qt chooses not to understand paths from Windows on non-Windows platforms, which causes
// this to break when a project is moving from Windows to non-Windows. To resolve that, if
// we're on a non-Windows platform and we detect a Windows path (i.e. a path with a drive
// letter at the start), we strip it off. We also convert any back-slashes to forward-slashes
// because on Windows they are interchangeable and on non-Windows they are not.
#ifndef Q_OS_WINDOWS
if (s.size() >= 2) {
if (s.at(0).isLetter() && s.at(1) == ':') {
s = s.mid(2);
s.replace('\\', '/');
}
}
#endif
return s;
}
bool Core::ValidateFootageInLoadedProject(Project* project, const QString& project_saved_url)
{
QVector<Footage*> project_footage = project->root()->ListChildrenOfType<Footage>();
QVector<Footage*> footage_we_couldnt_validate;
foreach (Footage* footage, project_footage) {
if (!QFileInfo::exists(footage->filename()) && !project_saved_url.isEmpty()) {
// If the footage doesn't exist, it might have moved with the project
const QString& project_current_url = project->filename();
for (Node *n : project->nodes()) {
if (Footage *footage = dynamic_cast<Footage*>(n)) {
QString footage_fn = StripWindowsDriveLetter(footage->filename());
QString project_fn = StripWindowsDriveLetter(project_saved_url);
if (project_current_url != project_saved_url) {
// Project has definitely moved, try to resolve relative paths
QDir saved_dir(QFileInfo(project_saved_url).dir());
QDir true_dir(QFileInfo(project_current_url).dir());
if (!QFileInfo::exists(footage_fn) && !project_saved_url.isEmpty()) {
// If the footage doesn't exist, it might have moved with the project
const QString& project_current_url = project->filename();
QString relative_filename = saved_dir.relativeFilePath(footage->filename());
QString transformed_abs_filename = true_dir.filePath(relative_filename);
if (project_current_url != project_fn) {
// Project has definitely moved, try to resolve relative paths
QDir saved_dir(QFileInfo(project_fn).dir());
QDir true_dir(QFileInfo(project_current_url).dir());
if (QFileInfo::exists(transformed_abs_filename)) {
// Use this file instead
qInfo() << "Resolved" << footage->filename() << "relatively to" << transformed_abs_filename;
footage->set_filename(transformed_abs_filename);
QString relative_filename = saved_dir.relativeFilePath(footage_fn);
QString transformed_abs_filename = true_dir.filePath(relative_filename);
if (QFileInfo::exists(transformed_abs_filename)) {
// Use this file instead
qInfo() << "Resolved" << footage_fn << "relatively to" << transformed_abs_filename;
footage->set_filename(transformed_abs_filename);
}
}
}
}
if (QFileInfo::exists(footage->filename())) {
// Assume valid
footage->SetValid();
} else {
footage_we_couldnt_validate.append(footage);
if (QFileInfo::exists(footage->filename())) {
// Assume valid
footage->SetValid();
} else {
footage_we_couldnt_validate.append(footage);
}
}
}
+27 -8
View File
@@ -317,7 +317,9 @@ public:
void OpenNodeInViewer(ViewerOutput* viewer);
void OpenExportDialogForViewer(ViewerOutput *viewer, const rational &time, bool start_still_image);
void OpenExportDialogForViewer(ViewerOutput *viewer, bool start_still_image);
bool IsMagicEnabled() const { return magic_; }
public slots:
/**
@@ -449,6 +451,11 @@ public slots:
void WarnCacheFull();
void SetMagic(bool e)
{
magic_ = e;
}
signals:
/**
* @brief Signal emitted when a project is opened
@@ -551,7 +558,7 @@ private:
/**
* @brief Retrieves the currently most active sequence for exporting
*/
bool GetSequenceToExport(ViewerOutput **viewer, rational *time);
ViewerOutput *GetSequenceToExport();
static QString GetAutoRecoveryIndexFilename();
@@ -559,6 +566,15 @@ private:
bool RevertProjectInternal(Project *p, bool by_opening_existing);
void SaveRecentProjectsList();
/**
* @brief Adds a project to the "open projects" list
*/
void AddOpenProject(olive::Project* p, bool add_to_recents = false);
bool AddOpenProjectFromTask(Task* task, bool add_to_recents);
/**
* @brief Internal main window object
*/
@@ -624,6 +640,11 @@ private:
*/
QVector<QUuid> autorecovered_projects_;
/**
* @brief Do something debug related
*/
bool magic_;
/**
* @brief How many widgets currently need pixel sampling access
*/
@@ -636,12 +657,10 @@ private slots:
void ProjectSaveSucceeded(Task *task);
/**
* @brief Adds a project to the "open projects" list
*/
void AddOpenProject(olive::Project* p);
bool AddOpenProjectFromTask(Task* task);
bool AddOpenProjectFromTaskAndAddToRecents(Task* task)
{
return AddOpenProjectFromTask(task, true);
}
void ImportTaskComplete(Task *task);
+4 -4
View File
@@ -39,10 +39,10 @@ target_include_directories(
target_link_libraries(
olive-crashhandler
PRIVATE
Qt5::Core
Qt5::Gui
Qt5::Widgets
Qt5::Network
Qt${QT_VERSION_MAJOR}::Core
Qt${QT_VERSION_MAJOR}::Gui
Qt${QT_VERSION_MAJOR}::Widgets
Qt${QT_VERSION_MAJOR}::Network
${CRASHPAD_LIBRARIES}
)
+3 -3
View File
@@ -44,10 +44,10 @@ AboutDialog::AboutDialog(bool welcome_dialog, QWidget *parent) :
QFontMetrics fm = fontMetrics();
QVBoxLayout* layout = new QVBoxLayout(this);
layout->setMargin(fm.height());
layout->setContentsMargins(fm.height(), fm.height(), fm.height(), fm.height());
QHBoxLayout *horiz_layout = new QHBoxLayout();
horiz_layout->setMargin(fm.height());
horiz_layout->setContentsMargins(fm.height(), fm.height(), fm.height(), fm.height());
horiz_layout->setSpacing(fm.height()*2);
QLabel* icon = new QLabel(QStringLiteral("<html><img src=':/graphics/olive-splash.png'></html>"));
@@ -108,7 +108,7 @@ AboutDialog::AboutDialog(bool welcome_dialog, QWidget *parent) :
layout->addWidget(new QLabel());
QHBoxLayout *btn_layout = new QHBoxLayout();
btn_layout->setMargin(0);
btn_layout->setContentsMargins(0, 0, 0, 0);
btn_layout->setSpacing(0);
if (welcome_dialog) {
+1 -1
View File
@@ -30,7 +30,7 @@ CineformSection::CineformSection(QWidget *parent) :
{
QGridLayout *layout = new QGridLayout(this);
layout->setMargin(0);
layout->setContentsMargins(0, 0, 0, 0);
int row = 0;
+4 -4
View File
@@ -39,7 +39,7 @@ H264Section::H264Section(int default_crf, QWidget *parent) :
CodecSection(parent)
{
QGridLayout* layout = new QGridLayout(this);
layout->setMargin(0);
layout->setContentsMargins(0, 0, 0, 0);
int row = 0;
layout->addWidget(new QLabel(tr("Encode Speed:")), row, 0);
@@ -173,7 +173,7 @@ H264CRFSection::H264CRFSection(int default_crf, QWidget *parent) :
QWidget(parent)
{
QHBoxLayout* layout = new QHBoxLayout(this);
layout->setMargin(0);
layout->setContentsMargins(0, 0, 0, 0);
crf_slider_ = new QSlider(Qt::Horizontal);
crf_slider_->setMinimum(kMinimumCRF);
@@ -207,7 +207,7 @@ H264BitRateSection::H264BitRateSection(QWidget *parent) :
QWidget(parent)
{
QGridLayout* layout = new QGridLayout(this);
layout->setMargin(0);
layout->setContentsMargins(0, 0, 0, 0);
int row = 0;
@@ -261,7 +261,7 @@ H264FileSizeSection::H264FileSizeSection(QWidget *parent) :
QWidget(parent)
{
QGridLayout* layout = new QGridLayout(this);
layout->setMargin(0);
layout->setContentsMargins(0, 0, 0, 0);
int row = 0;
+2 -1
View File
@@ -29,7 +29,7 @@ ImageSection::ImageSection(QWidget* parent) :
CodecSection(parent)
{
QGridLayout* layout = new QGridLayout(this);
layout->setMargin(0);
layout->setContentsMargins(0, 0, 0, 0);
int row = 0;
@@ -47,6 +47,7 @@ ImageSection::ImageSection(QWidget* parent) :
frame_slider_->SetMinimum(0);
frame_slider_->SetValue(0);
frame_slider_->SetDisplayType(RationalSlider::kTime);
connect(frame_slider_, &RationalSlider::ValueChanged, this, &ImageSection::TimeChanged);
layout->addWidget(frame_slider_, row, 1);
}
+3
View File
@@ -59,6 +59,9 @@ public:
frame_slider_->SetValue(t);
}
signals:
void TimeChanged(const rational &t);
private:
QCheckBox* image_sequence_checkbox_;
+9 -5
View File
@@ -58,7 +58,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, bool stills_only_mode, QWi
preferences_area_ = new QWidget();
QGridLayout* preferences_layout = new QGridLayout(preferences_area_);
preferences_layout->setMargin(0);
preferences_layout->setContentsMargins(0, 0, 0, 0);
int row = 0;
@@ -148,6 +148,11 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, bool stills_only_mode, QWi
video_tab_ = new ExportVideoTab(color_manager_);
AddPreferencesTab(video_tab_, tr("Video"));
// Set video tab time and make connections
connect(viewer_node, &ViewerOutput::PlayheadChanged, video_tab_, &ExportVideoTab::SetTime);
connect(video_tab_, &ExportVideoTab::TimeChanged, viewer_node, &ViewerOutput::SetPlayhead);
video_tab_->SetTime(viewer_node->GetPlayhead());
audio_tab_ = new ExportAudioTab();
AddPreferencesTab(audio_tab_, tr("Audio"));
@@ -183,7 +188,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, bool stills_only_mode, QWi
row++;
QHBoxLayout *btn_layout = new QHBoxLayout();
btn_layout->setMargin(0);
btn_layout->setContentsMargins(0, 0, 0, 0);
preferences_layout->addLayout(btn_layout, row, 0, 1, 4);
btn_layout->addStretch();
@@ -206,7 +211,6 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, bool stills_only_mode, QWi
preview_viewer_ = new ViewerWidget();
preview_viewer_->ruler()->SetMarkerEditingEnabled(false);
preview_viewer_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
connect(preview_viewer_, &ViewerWidget::TimeChanged, video_tab_, &ExportVideoTab::SetTime);
preview_layout->addWidget(preview_viewer_);
splitter->addWidget(preview_area);
@@ -391,7 +395,7 @@ void ExportDialog::ExportFinished()
// If this task was cancelled, we stay open so the user can potentially queue another export
} else {
// Accept this dialog and close
if (import_file_after_export_) {
if (import_file_after_export_->isEnabled() && import_file_after_export_->isChecked()) {
QString filename = filename_edit_->text().trimmed();
emit RequestImportFile(filename);
}
@@ -437,7 +441,7 @@ void ExportDialog::PresetComboBoxChanged()
if (loading_presets_) {
return;
}
QComboBox *c = static_cast<QComboBox *>(sender());
int preset_number = c->currentData().toInt();
-8
View File
@@ -51,14 +51,6 @@ public:
rational GetSelectedTimebase() const;
void SetSelectedTimebase(const rational &r);
void SetTime(const rational &time)
{
preview_viewer_->SetAudioScrubbingEnabled(false);
preview_viewer_->SetTime(time);
video_tab_->SetTime(time);
preview_viewer_->SetAudioScrubbingEnabled(true);
}
EncodingParams GenerateParams() const;
void SetParams(const EncodingParams &e);
+1
View File
@@ -184,6 +184,7 @@ QWidget *ExportVideoTab::SetupCodecSection()
codec_layout->addWidget(codec_stack_, row, 0, 1, 2);
image_section_ = new ImageSection();
connect(image_section_, &ImageSection::TimeChanged, this, &ExportVideoTab::TimeChanged);
codec_stack_->addWidget(image_section_);
h264_section_ = new H264Section();
+2
View File
@@ -167,6 +167,8 @@ signals:
void ImageSequenceCheckBoxChanged(bool e);
void TimeChanged(const rational &time);
private:
QWidget* SetupResolutionSection();
QWidget* SetupColorSection();
@@ -38,7 +38,7 @@ VideoStreamProperties::VideoStreamProperties(Footage *footage, int video_index)
video_premultiply_alpha_(nullptr)
{
QGridLayout* video_layout = new QGridLayout(this);
video_layout->setMargin(0);
video_layout->setContentsMargins(0, 0, 0, 0);
int row = 0;
@@ -102,8 +102,16 @@ void FootageRelinkDialog::BrowseForFootage()
QString new_fn = QFileDialog::getOpenFileName(this,
tr("Relink \"%1\"").arg(f->GetLabel()),
info.absolutePath(),
QStringLiteral("%1;;%2 (**)").arg(info.fileName(), tr("All Files")));
info.absolutePath());
// Originally, this function would attempt to filter to the exact filename of the missing file.
// However, this would break on Windows if the filename had any spaces in it. The reason is
// Windows separates its extensions with ';' while Qt separates them with ' '. Qt isn't
// intelligent enough to determine whether it's a list of extensions or a single filename with a
// space in it, it just does a global replace of ' ' to ';'. There's no way around it, outside of
// bypassing Qt entirely and using Win32's GetOpenFileName() directly. As annoying as it is, I've
// just disabled it for now.
//QStringLiteral("%1 (\"%1\");;%2 (*)").arg(info.fileName(), tr("All Files")));
// We received a new filename
if (!new_fn.isEmpty()) {
@@ -36,7 +36,7 @@ PreferencesAudioTab::PreferencesAudioTab()
{
// Backend Layout
QGridLayout* main_layout = new QGridLayout();
main_layout->setMargin(0);
main_layout->setContentsMargins(0, 0, 0, 0);
int row = 0;
@@ -73,7 +73,7 @@ PreferencesBehaviorTab::PreferencesBehaviorTab()
tr("Hold ALT on any UI element to switch scrolling axes"),
timeline_group);
AddItem(tr("Seek Also Selects"),
QStringLiteral("SelectAlsoSeeks"),
QStringLiteral("SeekAlsoSelects"),
timeline_group);
AddItem(tr("Seek to the End of Pastes"),
QStringLiteral("PasteSeeks"),
@@ -107,6 +107,11 @@ PreferencesBehaviorTab::PreferencesBehaviorTab()
tr("Multiple clips can share the same nodes. Disable this to automatically share node "
"dependencies among clips when copying or splitting them."),
node_group);
QTreeWidgetItem* opengl_group = AddParent(tr("OpenGL"));
AddItem(tr("Use glFinish"),
QStringLiteral("UseGLFinish"),
opengl_group);
}
void PreferencesBehaviorTab::Accept(MultiUndoCommand *command)
+1 -1
View File
@@ -55,7 +55,7 @@ ProgressDialog::ProgressDialog(const QString& message, const QString& title, QWi
QHBoxLayout* cancel_layout = new QHBoxLayout();
layout->addLayout(cancel_layout);
cancel_layout->setMargin(0);
cancel_layout->setContentsMargins(0, 0, 0, 0);
cancel_layout->setSpacing(0);
cancel_layout->addStretch();
@@ -47,7 +47,7 @@ SequenceDialogPresetTab::SequenceDialogPresetTab(QWidget* parent) :
PresetManager<SequencePreset>(this, QStringLiteral("sequencepresets"))
{
QVBoxLayout* outer_layout = new QVBoxLayout(this);
outer_layout->setMargin(0);
outer_layout->setContentsMargins(0, 0, 0, 0);
preset_tree_ = new QTreeWidget();
preset_tree_->setColumnCount(1);
@@ -100,9 +100,9 @@ SpeedDurationDialog::SpeedDurationDialog(const QVector<ClipBlock *> &clips, cons
loop_layout->addWidget(new QLabel(tr("Loop:")), row, 0);
loop_combo_ = new QComboBox();
loop_combo_->addItem(tr("None"), Decoder::kLoopModeOff);
loop_combo_->addItem(tr("Loop"), Decoder::kLoopModeLoop);
loop_combo_->addItem(tr("Clamp"), Decoder::kLoopModeClamp);
loop_combo_->addItem(tr("None"), int(LoopMode::kLoopModeOff));
loop_combo_->addItem(tr("Loop"), int(LoopMode::kLoopModeLoop));
loop_combo_->addItem(tr("Clamp"), int(LoopMode::kLoopModeClamp));
loop_layout->addWidget(loop_combo_, row, 1);
}
@@ -117,7 +117,7 @@ SpeedDurationDialog::SpeedDurationDialog(const QVector<ClipBlock *> &clips, cons
start_duration_ = clips.first()->length();
start_reverse_ = clips.first()->reverse();
start_maintain_audio_pitch_ = clips.first()->maintain_audio_pitch();
start_loop_ = clips.first()->loop_mode();
start_loop_ = int(clips.first()->loop_mode());
for (int i=1; i<clips.size(); i++) {
ClipBlock *c = clips.at(i);
@@ -141,7 +141,7 @@ SpeedDurationDialog::SpeedDurationDialog(const QVector<ClipBlock *> &clips, cons
start_maintain_audio_pitch_ = -1;
}
if (start_loop_ != -1 && c->loop_mode() != start_loop_) {
if (start_loop_ != -1 && int(c->loop_mode()) != start_loop_) {
start_loop_ = -1;
}
}
+7 -1
View File
@@ -56,7 +56,13 @@ void TaskDialog::showEvent(QShowEvent *e)
this, &TaskDialog::TaskFinished, Qt::QueuedConnection);
// Run task in another thread with QtConcurrent
task_watcher->setFuture(QtConcurrent::run(task_, &Task::Start));
task_watcher->setFuture(
#if QT_VERSION_MAJOR >= 6
QtConcurrent::run(&Task::Start, task_)
#else
QtConcurrent::run(task_, &Task::Start)
#endif
);
already_shown_ = true;
}
+1 -1
View File
@@ -39,7 +39,7 @@ Block::Block() :
track_(nullptr),
index_(-1)
{
AddInput(kLengthInput, NodeValue::kRational, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable));
AddInput(kLengthInput, NodeValue::kRational, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagHidden));
SetInputProperty(kLengthInput, QStringLiteral("min"), QVariant::fromValue(rational(0, 1)));
SetInputProperty(kLengthInput, QStringLiteral("view"), RationalSlider::kTime);
SetInputProperty(kLengthInput, QStringLiteral("viewlock"), true);
+6 -3
View File
@@ -21,6 +21,7 @@
#include "clip.h"
#include "config/config.h"
#include "node/block/transition/transition.h"
#include "node/output/track/track.h"
#include "node/output/viewer/viewer.h"
#include "widget/slider/floatslider.h"
@@ -453,10 +454,12 @@ void ClipBlock::InputValueChangedEvent(const QString &input, int element)
}
}
}
} else if (input == kLoopModeInput) {
emit PreviewChanged();
}
}
TimeRange ClipBlock::InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const
TimeRange ClipBlock::InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time, bool clamp) const
{
Q_UNUSED(element)
@@ -464,7 +467,7 @@ TimeRange ClipBlock::InputTimeAdjustment(const QString& input, int element, cons
return TimeRange(SequenceToMediaTime(input_time.in()), SequenceToMediaTime(input_time.out()));
}
return super::InputTimeAdjustment(input, element, input_time);
return super::InputTimeAdjustment(input, element, input_time, clamp);
}
TimeRange ClipBlock::OutputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const
@@ -538,7 +541,7 @@ void ClipBlock::ConnectedToPreviewEvent()
TimeRange ClipBlock::media_range() const
{
return InputTimeAdjustment(kBufferIn, -1, TimeRange(0, length()));
return InputTimeAdjustment(kBufferIn, -1, TimeRange(0, length()), false);
}
MultiCamNode *ClipBlock::FindMulticam()
+4 -4
View File
@@ -68,7 +68,7 @@ public:
virtual void InvalidateCache(const TimeRange& range, const QString& from, int element, InvalidateCacheOptions options) override;
virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const override;
virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time, bool clamp) const override;
virtual TimeRange OutputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const override;
@@ -188,12 +188,12 @@ public:
/**
* @brief Get currently set loop mode
*/
Decoder::LoopMode loop_mode() const
LoopMode loop_mode() const
{
return static_cast<Decoder::LoopMode>(GetStandardValue(kLoopModeInput).toInt());
return static_cast<LoopMode>(GetStandardValue(kLoopModeInput).toInt());
}
void set_loop_mode(Decoder::LoopMode l)
void set_loop_mode(LoopMode l)
{
SetStandardValue(kLoopModeInput, int(l));
}
@@ -24,6 +24,8 @@ namespace olive {
const QString DipToColorTransition::kColorInput = QStringLiteral("color_in");
#define super TransitionBlock
DipToColorTransition::DipToColorTransition()
{
AddInput(kColorInput, NodeValue::kColor, QVariant::fromValue(Color(0, 0, 0)));
@@ -56,6 +58,13 @@ ShaderCode DipToColorTransition::GetShaderCode(const ShaderRequest &request) con
return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/diptoblack.frag"), QString());
}
void DipToColorTransition::Retranslate()
{
super::Retranslate();
SetInputName(kColorInput, tr("Color"));
}
void DipToColorTransition::ShaderJobEvent(const NodeValueRow &value, ShaderJob *job) const
{
job->Insert(kColorInput, value);
@@ -40,6 +40,8 @@ public:
virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override;
virtual void Retranslate() override;
static const QString kColorInput;
protected:
+9 -2
View File
@@ -47,6 +47,8 @@ TransitionBlock::TransitionBlock() :
AddInput(kCenterInput, NodeValue::kRational, InputFlags(kInputFlagNotKeyframable | kInputFlagNotConnectable));
SetInputProperty(kCenterInput, QStringLiteral("view"), RationalSlider::kTime);
SetInputProperty(kCenterInput, QStringLiteral("viewlock"), true);
SetFlags(GetFlags() & ~kDontShowInParamView);
}
void TransitionBlock::Retranslate()
@@ -171,10 +173,14 @@ void TransitionBlock::Value(const NodeValueRow &value, const NodeGlobals &global
if (out_buffer.type() != NodeValue::kNone) {
job.Insert(kOutBlockInput, out_buffer);
} else {
job.Insert(kOutBlockInput, NodeValue(NodeValue::kTexture, nullptr));
}
if (in_buffer.type() != NodeValue::kNone) {
job.Insert(kInBlockInput, in_buffer);
} else {
job.Insert(kInBlockInput, NodeValue(NodeValue::kTexture, nullptr));
}
job.Insert(kCurveInput, value);
@@ -281,16 +287,17 @@ void TransitionBlock::InputDisconnectedEvent(const QString &input, int element,
}
}
TimeRange TransitionBlock::InputTimeAdjustment(const QString &input, int element, const TimeRange &input_time) const
TimeRange TransitionBlock::InputTimeAdjustment(const QString &input, int element, const TimeRange &input_time, bool clamp) const
{
if (input == kInBlockInput || input == kOutBlockInput) {
Block* block = dynamic_cast<Block*>(GetConnectedOutput(input));
if (block) {
// Retransform time as if it came from the track
return input_time + in() - block->in();
}
}
return super::InputTimeAdjustment(input, element, input_time);
return super::InputTimeAdjustment(input, element, input_time, clamp);
}
TimeRange TransitionBlock::OutputTimeAdjustment(const QString &input, int element, const TimeRange &input_time) const
+1 -1
View File
@@ -83,7 +83,7 @@ protected:
virtual void InputDisconnectedEvent(const QString& input, int element, Node *output) override;
virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const override;
virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time, bool clamp) const override;
virtual TimeRange OutputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const override;
@@ -297,7 +297,7 @@ QMatrix4x4 TransformDistortNode::AdjustMatrixByResolutions(const QMatrix4x4 &mat
adjusted_matrix.scale(2.0 / sequence_res.x(), 2.0 / sequence_res.y(), 1.0);
// Apply offset if applicable
adjusted_matrix.translate(offset);
adjusted_matrix.translate(offset.x(), offset.y());
// Adjust by the matrix we generated earlier
adjusted_matrix *= mat;
@@ -358,7 +358,7 @@ void TransformDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const N
// Fold values into a matrix for the rectangle
QMatrix4x4 rectangle_matrix;
rectangle_matrix.scale(sequence_half_res);
rectangle_matrix.scale(sequence_half_res.x(), sequence_half_res.y());
rectangle_matrix *= AdjustMatrixByResolutions(GenerateMatrix(row, false, false, false, row[kParentInput].toMatrix()),
sequence_res,
tex_sz,
@@ -378,7 +378,7 @@ void TransformDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const N
// Draw anchor point
QMatrix4x4 anchor_matrix;
anchor_matrix.scale(sequence_half_res);
anchor_matrix.scale(sequence_half_res.x(), sequence_half_res.y());
anchor_matrix *= AdjustMatrixByResolutions(GenerateMatrix(row, true, false, false, row[kParentInput].toMatrix()),
sequence_res,
tex_sz,
+10 -3
View File
@@ -39,15 +39,22 @@ void OpacityEffect::Retranslate()
ShaderCode OpacityEffect::GetShaderCode(const ShaderRequest &request) const
{
Q_UNUSED(request)
return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/opacity.frag"));
if (request.id == QStringLiteral("rgbmult")) {
return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/opacity_rgb.frag"));
} else {
return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/opacity.frag"));
}
}
void OpacityEffect::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
{
// If there's no texture, no need to run an operation
if (TexturePtr tex = value[kTextureInput].toTexture()) {
if (!qFuzzyCompare(value[kValueInput].toDouble(), 1.0)) {
if (TexturePtr opacity_tex = value[kValueInput].toTexture()) {
ShaderJob job(value);
job.SetShaderID(QStringLiteral("rgbmult"));
table->Push(NodeValue::kTexture, tex->toJob(job), this);
} else if (!qFuzzyCompare(value[kValueInput].toDouble(), 1.0)) {
table->Push(NodeValue::kTexture, tex->toJob(ShaderJob(value)), this);
} else {
// 1.0 float is a no-op, so just push the texture
+2 -2
View File
@@ -128,7 +128,7 @@ QMatrix4x4 MatrixGenerator::GenerateMatrix(const QVector2D& pos,
QMatrix4x4 mat)
{
// Position
mat.translate(pos);
mat.translate(pos.x(), pos.y());
// Rotation
mat.rotate(rot, 0, 0, 1);
@@ -143,7 +143,7 @@ QMatrix4x4 MatrixGenerator::GenerateMatrix(const QVector2D& pos,
mat.scale(full_scale);
// Anchor Point
mat.translate(-anchor);
mat.translate(-anchor.x(), -anchor.y());
return mat;
}
+11 -2
View File
@@ -25,6 +25,7 @@
#include "common/timerange.h"
#include "render/audioparams.h"
#include "render/loopmode.h"
#include "render/videoparams.h"
namespace olive {
@@ -34,10 +35,16 @@ class NodeGlobals
public:
NodeGlobals(){}
NodeGlobals(const VideoParams &vparam, const AudioParams &aparam, const TimeRange &time) :
NodeGlobals(const VideoParams &vparam, const AudioParams &aparam, const TimeRange &time, LoopMode loop_mode) :
video_params_(vparam),
audio_params_(aparam),
time_(time)
time_(time),
loop_mode_(loop_mode)
{
}
NodeGlobals(const VideoParams &vparam, const AudioParams &aparam, const rational &time, LoopMode loop_mode) :
NodeGlobals(vparam, aparam, TimeRange(time, time + vparam.frame_rate_as_time_base()), loop_mode)
{
}
@@ -46,11 +53,13 @@ public:
const AudioParams &aparams() const { return audio_params_; }
const VideoParams &vparams() const { return video_params_; }
const TimeRange &time() const { return time_; }
LoopMode loop_mode() const { return loop_mode_; }
private:
VideoParams video_params_;
AudioParams audio_params_;
TimeRange time_;
LoopMode loop_mode_;
};
+13 -5
View File
@@ -13,11 +13,7 @@ const QString MultiCamNode::kSequenceTypeInput = QStringLiteral("sequence_type_i
MultiCamNode::MultiCamNode()
{
AddInput(kCurrentInput, NodeValue::kInt, InputFlags(kInputFlagStatic));
// Make current index start at 1 instead of 0
SetInputProperty(kCurrentInput, QStringLiteral("offset"), 1);
SetInputProperty(kCurrentInput, QStringLiteral("min"), 0);
AddInput(kCurrentInput, NodeValue::kCombo, InputFlags(kInputFlagStatic));
AddInput(kSourcesInput, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable | kInputFlagArray));
SetInputProperty(kSourcesInput, QStringLiteral("arraystart"), 1);
@@ -135,6 +131,18 @@ void MultiCamNode::Retranslate()
SetInputName(kSequenceInput, tr("Sequence"));
SetInputName(kSequenceTypeInput, tr("Sequence Type"));
SetComboBoxStrings(kSequenceTypeInput, {tr("Video"), tr("Audio")});
QStringList names;
int name_count = GetSourceCount();
names.reserve(name_count);
for (int i=0; i<name_count; i++) {
QString src_name;
if (Node *n = GetConnectedRenderOutput(kSourcesInput, i)) {
src_name = n->Name();
}
names.append(tr("%1: %2").arg(QString::number(i+1), src_name));
}
SetComboBoxStrings(kCurrentInput, names);
}
int MultiCamNode::GetSourceCount() const
+13 -5
View File
@@ -44,6 +44,14 @@ MathNode::MathNode()
QString MathNode::Name() const
{
// Default to naming after the operation
if (parent()) {
QString op_name = GetOperationName(GetOperation());
if (!op_name.isEmpty()) {
return op_name;
}
}
return tr("Math");
}
@@ -70,12 +78,12 @@ void MathNode::Retranslate()
SetInputName(kParamAIn, tr("Value"));
SetInputName(kParamBIn, tr("Value"));
QStringList operations = {tr("Add"),
tr("Subtract"),
tr("Multiply"),
tr("Divide"),
QStringList operations = {GetOperationName(kOpAdd),
GetOperationName(kOpSubtract),
GetOperationName(kOpMultiply),
GetOperationName(kOpDivide),
QString(),
tr("Power")};
GetOperationName(kOpPower)};
SetComboBoxStrings(kMethodIn, operations);
}
+15 -2
View File
@@ -141,9 +141,9 @@ QVector4D MathNodeBase::RetrieveVector(const NodeValue &val)
// QVariant doesn't know that QVector*D can convert themselves so we do it here
switch (val.type()) {
case NodeValue::kVec2:
return val.toVec2();
return QVector4D(val.toVec2());
case NodeValue::kVec3:
return val.toVec3();
return QVector4D(val.toVec3());
case NodeValue::kVec4:
default:
return val.toVec4();
@@ -167,6 +167,19 @@ void MathNodeBase::PushVector(NodeValueTable *output, olive::NodeValue::Type typ
}
}
QString MathNodeBase::GetOperationName(Operation o)
{
switch (o) {
case kOpAdd: return tr("Add");
case kOpSubtract: return tr("Subtract");
case kOpMultiply: return tr("Multiply");
case kOpDivide: return tr("Divide");
case kOpPower: return tr("Power");
}
return QString();
}
void MathNodeBase::PerformAllOnFloatBuffer(Operation operation, float *a, float b, int start, int end)
{
for (int j=start;j<end;j++) {
+2
View File
@@ -38,6 +38,8 @@ public:
kOpPower
};
static QString GetOperationName(Operation o);
protected:
enum Pairing {
kPairNone = -1,
+38 -134
View File
@@ -959,7 +959,7 @@ void Node::InvalidateCache(const TimeRange &range, const QString &from, int elem
SendInvalidateCache(range, options);
}
TimeRange Node::InputTimeAdjustment(const QString &, int, const TimeRange &input_time) const
TimeRange Node::InputTimeAdjustment(const QString &, int, const TimeRange &input_time, bool clamp) const
{
// Default behavior is no time adjustment at all
return input_time;
@@ -1555,64 +1555,6 @@ void Node::GenerateFrame(FramePtr frame, const GenerateJob &job) const
Q_UNUSED(job)
}
bool Node::OutputsTo(Node *n, bool recursively, const OutputConnections &ignore_edges, const OutputConnection &added_edge) const
{
for (const OutputConnection& conn : output_connections_) {
if (std::find(ignore_edges.cbegin(), ignore_edges.cend(), conn) != ignore_edges.cend()) {
// If this edge is in the "ignore edges" list, skip it
continue;
}
Node* connected = conn.second.node();
if (connected == n) {
return true;
} else if (recursively && connected->OutputsTo(n, recursively, ignore_edges, added_edge)) {
return true;
} else if (added_edge.first == this) {
Node *proposed_connected = added_edge.second.node();
if (proposed_connected == n) {
return true;
} else if (recursively && proposed_connected->OutputsTo(n, recursively, ignore_edges, added_edge)) {
return true;
}
}
}
return false;
}
bool Node::OutputsTo(const QString &id, bool recursively) const
{
for (const OutputConnection& conn : output_connections_) {
Node* connected = conn.second.node();
if (connected->id() == id) {
return true;
} else if (recursively && connected->OutputsTo(id, recursively)) {
return true;
}
}
return false;
}
bool Node::OutputsTo(const NodeInput &input, bool recursively) const
{
for (const OutputConnection& conn : output_connections_) {
const NodeInput& connected = conn.second;
if (connected == input) {
return true;
} else if (recursively && connected.node()->OutputsTo(input, recursively)) {
return true;
}
}
return false;
}
bool Node::InputsFrom(Node *n, bool recursively) const
{
for (auto it=input_connections_.cbegin(); it!=input_connections_.cend(); it++) {
@@ -1643,28 +1585,6 @@ bool Node::InputsFrom(const QString &id, bool recursively) const
return false;
}
int Node::GetNumberOfRoutesTo(Node *n) const
{
bool outputs_directly = false;
int routes = 0;
foreach (const OutputConnection& conn, output_connections_) {
Node* connected_node = conn.second.node();
if (connected_node == n) {
outputs_directly = true;
} else {
routes += connected_node->GetNumberOfRoutesTo(n);
}
}
if (outputs_directly) {
routes++;
}
return routes;
}
void Node::DisconnectAll()
{
// Disconnect inputs (copy map since internal map will change as we disconnect)
@@ -1712,42 +1632,33 @@ QString Node::GetCategoryName(const CategoryID &c)
return tr("Uncategorized");
}
QVector<TimeRange> Node::TransformTimeTo(const TimeRange &time, Node *target, bool input_dir)
TimeRange Node::TransformTimeTo(TimeRange time, Node *target, TransformTimeDirection dir, int path_index)
{
QVector<TimeRange> paths_found;
Node *from = this;
Node *to = target;
if (input_dir) {
// If this input is connected, traverse it to see if we stumble across the specified `node`
for (auto it=input_connections_.cbegin(); it!=input_connections_.cend(); it++) {
TimeRange input_adjustment = InputTimeAdjustment(it->first.input(), it->first.element(), time);
Node* connected = it->second;
if (dir == kTransformTowardsInput) {
std::swap(from, to);
}
if (connected == target) {
// We found the target, no need to keep traversing
if (!paths_found.contains(input_adjustment)) {
paths_found.append(input_adjustment);
}
} else {
// We did NOT find the target, traverse this
paths_found.append(connected->TransformTimeTo(input_adjustment, target, input_dir));
std::list<NodeInput> path = FindPath(from, to, path_index);
if (!path.empty()) {
if (dir == kTransformTowardsInput) {
for (auto it=path.crbegin(); it!=path.crend(); it++) {
const NodeInput &i = (*it);
time = i.node()->InputTimeAdjustment(i.input(), i.element(), time, false);
}
}
} else {
// If this input is connected, traverse it to see if we stumble across the specified `node`
foreach (const OutputConnection& conn, output_connections_) {
Node* connected_node = conn.second.node();
TimeRange output_adjustment = connected_node->OutputTimeAdjustment(conn.second.input(), conn.second.element(), time);
if (connected_node == target) {
paths_found.append(output_adjustment);
} else {
paths_found.append(connected_node->TransformTimeTo(output_adjustment, target, input_dir));
} else {
// Traverse in output direction
for (auto it=path.cbegin(); it!=path.cend(); it++) {
const NodeInput &i = (*it);
time = i.node()->OutputTimeAdjustment(i.input(), i.element(), time);
}
}
}
return paths_found;
return time;
}
QVariant Node::PtrToValue(void *ptr)
@@ -2011,46 +1922,39 @@ void Node::SetValueAtTime(const NodeInput &input, const rational &time, const QV
}
}
void FindPathInternal(std::list<Node *> &vec, Node *to, int &path_index)
bool FindPathInternal(std::list<NodeInput> &vec, Node *from, Node *to, int &path_index)
{
Node *from = vec.back();
for (auto it=from->output_connections().cbegin(); it!=from->output_connections().cend(); it++) {
const NodeInput &next = it->second;
for (auto it=from->input_connections().cbegin(); it!=from->input_connections().cend(); it++) {
vec.push_back(it->second);
if (it->second == to) {
// Found a path, determine if it's the one we want
vec.push_back(next);
if (next.node() == to) {
// Found a path! Determine if it's the index we want
if (path_index == 0) {
// It is!
break;
return true;
} else {
// It isn't, keep looking...
path_index--;
}
}
// Recurse to see if we can find it here
FindPathInternal(vec, to, path_index);
if (vec.back() == to) {
// Found through recursion
break;
} else {
// Must not be available through this path
vec.pop_back();
if (FindPathInternal(vec, next.node(), to, path_index)) {
return true;
}
vec.pop_back();
}
return false;
}
std::list<Node *> Node::FindPath(Node *from, Node *to, int path_index)
std::list<NodeInput> Node::FindPath(Node *from, Node *to, int path_index)
{
std::list<Node *> v;
std::list<NodeInput> v;
v.push_back(from);
FindPathInternal(v, to, path_index);
if (v.size() == 1) {
// Failed to find path, return empty list
v.pop_back();
}
FindPathInternal(v, from, to, path_index);
return v;
}
+11 -67
View File
@@ -786,30 +786,6 @@ public:
*/
virtual void GenerateFrame(FramePtr frame, const GenerateJob &job) const;
/**
* @brief Returns whether this Node outputs to `n`
*
* @param n
*
* The node instance to check.
*
* @param recursively
*
* Whether to keep traversing down outputs to find this node (TRUE) or stick to immediate outputs
* (FALSE).
*/
bool OutputsTo(Node* n, bool recursively, const OutputConnections &ignore_edges = OutputConnections(), const OutputConnection &added_edge = OutputConnection()) const;
/**
* @brief Same as OutputsTo(Node*), but for a node ID rather than a specific instance.
*/
bool OutputsTo(const QString& id, bool recursively) const;
/**
* @brief Same as OutputsTo(Node*), but for a specific node input rather than just a node.
*/
bool OutputsTo(const NodeInput &input, bool recursively) const;
/**
* @brief Returns whether this node ever receives an input from a particular node instance
*/
@@ -820,7 +796,6 @@ public:
*/
bool InputsFrom(const QString& id, bool recursively) const;
/**
* @brief Find inputs that `output` outputs to in order to arrive at this node
*
@@ -829,11 +804,6 @@ public:
*/
QVector<NodeInput> FindWaysNodeArrivesHere(const Node *output) const;
/**
* @brief Determines how many paths go from this node out to another node
*/
int GetNumberOfRoutesTo(Node* n) const;
/**
* @brief Severs all input and output connections
*/
@@ -844,10 +814,15 @@ public:
*/
static QString GetCategoryName(const CategoryID &c);
enum TransformTimeDirection {
kTransformTowardsInput,
kTransformTowardsOutput
};
/**
* @brief Transforms time from this node through the connections it takes to get to the specified node
*/
QVector<TimeRange> TransformTimeTo(const TimeRange& time, Node* target, bool input_dir);
TimeRange TransformTimeTo(TimeRange time, Node* target, TransformTimeDirection dir, int path_index);
/**
* @brief Find nodes of a certain type that this Node takes inputs from
@@ -861,12 +836,6 @@ public:
template<class T>
static QVector<T*> FindInputNodesConnectedToInput(const NodeInput &input, int maximum = 0);
template<class T>
/**
* @brief Find a node of a certain type that this Node outputs to
*/
QVector<T *> FindOutputNode();
/**
* @brief Convert a pointer to a value that can be sent between NodeParams
*/
@@ -902,7 +871,7 @@ public:
* If this node modifies the `time` (i.e. a clip converting sequence time to media time), this function should be
* overridden to do so. Also make sure to override OutputTimeAdjustment() to provide the inverse function.
*/
virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const;
virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time, bool clamp) const;
/**
* @brief The inverse of InputTimeAdjustment()
@@ -1147,7 +1116,10 @@ public:
static void SetValueAtTime(const NodeInput &input, const rational &time, const QVariant &value, int track, MultiUndoCommand *command, bool insert_on_all_tracks_if_no_key);
static std::list<Node*> FindPath(Node *from, Node *to, int path_index = 0);
/**
* @brief Find path starting at `from` that outputs to arrive at `to`
*/
static std::list<NodeInput> FindPath(Node *from, Node *to, int path_index);
static const QString kEnabledInput;
@@ -1405,9 +1377,6 @@ private:
template<class T>
static void FindInputNodeInternal(const Node* n, QVector<T *>& list, int maximum);
template<class T>
static void FindOutputNodeInternal(const Node* n, QVector<T *>& list);
QVector<Node*> GetDependenciesInternal(bool traverse, bool exclusive_only) const;
void ParameterValueChanged(const QString &input, int element, const olive::TimeRange &range);
@@ -1565,31 +1534,6 @@ T* Node::ValueToPtr(const QVariant &ptr)
return reinterpret_cast<T*>(ptr.value<quintptr>());
}
template<class T>
void Node::FindOutputNodeInternal(const Node* n, QVector<T *>& list)
{
foreach (const OutputConnection& output, n->output_connections_) {
Node* connected = output.second.node();
T* cast_test = dynamic_cast<T*>(connected);
if (cast_test) {
list.append(cast_test);
}
FindOutputNodeInternal<T>(connected, list);
}
}
template<class T>
QVector<T *> Node::FindOutputNode()
{
QVector<T *> list;
FindOutputNodeInternal<T>(this, list);
return list;
}
using NodePtr = std::shared_ptr<Node>;
class NodeSetPositionCommand : public UndoCommand
+19 -8
View File
@@ -113,6 +113,10 @@ Node::ActiveElements Track::GetActiveElementsAtTime(const QString &input, const
end = blocks_.size()-1;
}
if (blocks_.at(end)->in() == r.out()) {
end--;
}
ActiveElements a;
for (int i=start; i<=end; i++) {
Block *b = blocks_.at(i);
@@ -146,17 +150,24 @@ void Track::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeVal
}
}
TimeRange Track::InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const
TimeRange Track::InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time, bool clamp) const
{
if (input == kBlockInput && element >= 0) {
int cache_index = GetCacheIndexFromArrayIndex(element);
if (cache_index > -1) {
return TransformRangeForBlock(blocks_.at(cache_index), input_time);
TimeRange r = input_time;
Block *b = blocks_.at(cache_index);
if (clamp) {
r.set_range(std::max(r.in(), b->in()), std::min(r.out(), b->out()));
}
return TransformRangeForBlock(b, r);
}
}
return Node::InputTimeAdjustment(input, element, input_time);
return Node::InputTimeAdjustment(input, element, input_time, clamp);
}
TimeRange Track::OutputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const
@@ -180,7 +191,7 @@ const double &Track::GetTrackHeight() const
void Track::SetTrackHeight(const double &height)
{
track_height_ = height;
emit TrackHeightChangedInPixels(GetTrackHeightInPixels());
emit TrackHeightChanged(track_height_);
}
void Track::InputConnectedEvent(const QString &input, int element, Node *output)
@@ -647,16 +658,16 @@ void Track::ProcessAudioTrack(const NodeValueRow &value, const NodeGlobals &glob
TimeRange range_for_block(qMax(b->in(), range.in()),
qMin(b->out(), range.out()));
qint64 source_offset = 0;
qint64 destination_offset = globals.aparams().time_to_samples(range_for_block.in() - range.in());
qint64 max_dest_sz = globals.aparams().time_to_samples(range_for_block.length());
// Destination buffer
SampleBuffer samples_from_this_block = it->second.toSamples();
ClipBlock *clip_cast = dynamic_cast<ClipBlock*>(b);
if (samples_from_this_block.is_allocated()) {
// If this is a clip, we might have extra speed/reverse information
if (clip_cast) {
if (ClipBlock *clip_cast = dynamic_cast<ClipBlock*>(b)) {
double speed_value = clip_cast->speed();
bool reversed = clip_cast->reverse();
@@ -711,11 +722,11 @@ void Track::ProcessAudioTrack(const NodeValueRow &value, const NodeGlobals &glob
}
}
qint64 copy_length = qMin(max_dest_sz, qint64(samples_from_this_block.sample_count() - destination_offset));
qint64 copy_length = qMin(max_dest_sz, qint64(samples_from_this_block.sample_count() - source_offset));
// Copy samples into destination buffer
for (int i=0; i<samples_from_this_block.audio_params().channel_count(); i++) {
block_range_buffer.set(i, samples_from_this_block.data(i) + destination_offset, destination_offset, copy_length);
block_range_buffer.set(i, samples_from_this_block.data(i) + source_offset, destination_offset, copy_length);
}
}
}
+2 -2
View File
@@ -57,7 +57,7 @@ public:
virtual ActiveElements GetActiveElementsAtTime(const QString &input, const TimeRange &r) const override;
virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override;
virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const override;
virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time, bool clamp) const override;
virtual TimeRange OutputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const override;
@@ -425,7 +425,7 @@ signals:
/**
* @brief Signal emitted when the height of the track has changed
*/
void TrackHeightChangedInPixels(int pixel_height);
void TrackHeightChanged(qreal virtual_height);
/**
* @brief Signal emitted when the muted setting changes
+3 -2
View File
@@ -81,8 +81,9 @@ void TrackList::TrackConnected(Node *node, int element)
UpdateTrackIndexesFrom(cache_index);
connect(track, &Track::TrackLengthChanged, this, &TrackList::UpdateTotalLength);
connect(track, &Track::TrackHeightChangedInPixels, this, [this](int height){
emit TrackHeightChanged(static_cast<Track*>(sender()), height);
connect(track, &Track::TrackHeightChanged, this, [this](){
Track *t = static_cast<Track*>(sender());
emit TrackHeightChanged(t, t->GetTrackHeightInPixels());
});
track->set_type(type_);
+9 -3
View File
@@ -227,11 +227,11 @@ void ViewerOutput::InvalidateCache(const TimeRange& range, const QString& from,
if (from == kTextureInput) {
//connected->thumbnail_cache()->Request(range.Intersected(max_range), PlaybackCache::kPreviewsOnly);
if (autocache_input_video_) {
TimeRange max_range = InputTimeAdjustment(from, element, TimeRange(0, GetVideoLength()));
TimeRange max_range = InputTimeAdjustment(from, element, TimeRange(0, GetVideoLength()), false);
connected->video_frame_cache()->Request(range.Intersected(max_range));
}
} else if (from == kSamplesInput) {
TimeRange max_range = InputTimeAdjustment(from, element, TimeRange(0, GetAudioLength()));
TimeRange max_range = InputTimeAdjustment(from, element, TimeRange(0, GetAudioLength()), false);
if (waveform_requests_enabled_) {
connected->waveform_cache()->Request(range.Intersected(max_range));
}
@@ -316,6 +316,12 @@ void ViewerOutput::VerifyLength()
}
}
void ViewerOutput::SetPlayhead(const rational &t)
{
playhead_ = t;
emit PlayheadChanged(t);
}
void ViewerOutput::InputConnectedEvent(const QString &input, int element, Node *output)
{
if (input == kTextureInput) {
@@ -394,7 +400,7 @@ void ViewerOutput::SetWaveformEnabled(bool e)
{
if ((waveform_requests_enabled_ = e)) {
if (Node *connected = this->GetConnectedSampleOutput()) {
TimeRange max_range = InputTimeAdjustment(kSamplesInput, -1, TimeRange(0, GetAudioLength()));
TimeRange max_range = InputTimeAdjustment(kSamplesInput, -1, TimeRange(0, GetAudioLength()), false);
TimeRangeList invalid = connected->waveform_cache()->GetInvalidatedRanges(max_range);
for (const TimeRange &r : invalid) {
connected->waveform_cache()->Request(r);
+8
View File
@@ -97,6 +97,8 @@ public:
}
}
const rational &GetPlayhead() { return playhead_; }
void SetVideoParams(const VideoParams &video, int index = 0)
{
SetStandardValue(kVideoParamsInput, QVariant::fromValue(video), index);
@@ -219,9 +221,13 @@ signals:
void ConnectedWaveformChanged();
void PlayheadChanged(const rational &t);
public slots:
void VerifyLength();
void SetPlayhead(const rational &t);
protected:
virtual void InputConnectedEvent(const QString &input, int element, Node *output) override;
@@ -253,6 +259,8 @@ private:
bool waveform_requests_enabled_;
rational playhead_;
};
}
+10 -8
View File
@@ -265,7 +265,7 @@ void Footage::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeV
// Push each stream as a footage job
for (int i=0; i<GetTotalStreamCount(); i++) {
Track::Reference ref = GetReferenceFromRealIndex(i);
FootageJob job(globals.time(), decoder_, filename(), ref.type(), GetLength());
FootageJob job(globals.time(), decoder_, filename(), ref.type(), GetLength(), globals.loop_mode());
if (ref.type() == Track::kVideo) {
VideoParams vp = GetVideoParams(ref.index());
@@ -286,7 +286,7 @@ void Footage::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeV
job.set_video_params(vp);
table->Push(NodeValue::kTexture, Texture::Job(vp, job), this, ref.ToString());
} else {
} else if (ref.type() == Track::kAudio) {
AudioParams ap = GetAudioParams(ref.index());
job.set_audio_params(ap);
job.set_cache_path(project()->cache_path());
@@ -337,7 +337,7 @@ bool TimeIsOutOfBounds(const rational& time, const rational& length)
return time < 0 || time >= length;
}
rational Footage::AdjustTimeByLoopMode(rational time, Decoder::LoopMode loop_mode, const rational &length, VideoParams::Type type, const rational& timebase)
rational Footage::AdjustTimeByLoopMode(rational time, LoopMode loop_mode, const rational &length, VideoParams::Type type, const rational& timebase)
{
if (type == VideoParams::kVideoTypeStill) {
// No looping for still images
@@ -346,15 +346,15 @@ rational Footage::AdjustTimeByLoopMode(rational time, Decoder::LoopMode loop_mod
if (TimeIsOutOfBounds(time, length)) {
switch (loop_mode) {
case Decoder::kLoopModeOff:
case LoopMode::kLoopModeOff:
// Return no time to indicate no frame should be shown here
time = rational::NaN;
break;
case Decoder::kLoopModeClamp:
case LoopMode::kLoopModeClamp:
// Clamp footage time to length
time = clamp(time, rational(0), length - timebase);
break;
case Decoder::kLoopModeLoop:
case LoopMode::kLoopModeLoop:
// Loop footage time around job length
do {
if (time >= length) {
@@ -474,8 +474,10 @@ void Footage::Reprobe()
}
}
if (!footage_info.Save(meta_cache_file)) {
qWarning() << "Failed to save stream cache, footage will have to be re-probed";
if (!cancelled_ || !cancelled_->HeardCancel()) {
if (!footage_info.Save(meta_cache_file)) {
qWarning() << "Failed to save stream cache, footage will have to be re-probed";
}
}
}
+1 -1
View File
@@ -173,7 +173,7 @@ public:
virtual Node *GetConnectedSampleOutput() override;
static rational AdjustTimeByLoopMode(rational time, Decoder::LoopMode loop_mode, const rational& length, VideoParams::Type type, const rational &timebase);
static rational AdjustTimeByLoopMode(rational time, LoopMode loop_mode, const rational& length, VideoParams::Type type, const rational &timebase);
virtual void LoadFinishedEvent() override;
@@ -570,13 +570,13 @@ void ProjectSerializer220403::LoadNode(Node *node, XMLNodeData &xml_node_data, Q
} else if (reader->name() == QStringLiteral("caches")) {
while (XMLReadNextStartElement(reader)) {
if (reader->name() == QStringLiteral("audio")) {
node->audio_playback_cache()->SetUuid(reader->readElementText());
node->audio_playback_cache()->SetUuid(QUuid::fromString(reader->readElementText()));
} else if (reader->name() == QStringLiteral("video")) {
node->video_frame_cache()->SetUuid(reader->readElementText());
node->video_frame_cache()->SetUuid(QUuid::fromString(reader->readElementText()));
} else if (reader->name() == QStringLiteral("thumb")) {
node->thumbnail_cache()->SetUuid(reader->readElementText());
node->thumbnail_cache()->SetUuid(QUuid::fromString(reader->readElementText()));
} else if (reader->name() == QStringLiteral("waveform")) {
node->waveform_cache()->SetUuid(reader->readElementText());
node->waveform_cache()->SetUuid(QUuid::fromString(reader->readElementText()));
} else {
reader->skipCurrentElement();
}
+2 -2
View File
@@ -46,12 +46,12 @@ void TimeOffsetNode::Retranslate()
SetInputName(kInputInput, QStringLiteral("Input"));
}
TimeRange TimeOffsetNode::InputTimeAdjustment(const QString &input, int element, const TimeRange &input_time) const
TimeRange TimeOffsetNode::InputTimeAdjustment(const QString &input, int element, const TimeRange &input_time, bool clamp) const
{
if (input == kInputInput) {
return TimeRange(GetRemappedTime(input_time.in()), GetRemappedTime(input_time.out()));
} else {
return super::InputTimeAdjustment(input, element, input_time);
return super::InputTimeAdjustment(input, element, input_time, clamp);
}
}
+1 -1
View File
@@ -52,7 +52,7 @@ public:
return tr("Offset time passing through the graph.");
}
virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const override;
virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time, bool clamp) const override;
virtual TimeRange OutputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const override;
virtual void Retranslate() override;
+2 -2
View File
@@ -58,12 +58,12 @@ QString TimeRemapNode::Description() const
return tr("Arbitrarily remap time through the nodes.");
}
TimeRange TimeRemapNode::InputTimeAdjustment(const QString &input, int element, const TimeRange &input_time) const
TimeRange TimeRemapNode::InputTimeAdjustment(const QString &input, int element, const TimeRange &input_time, bool clamp) const
{
if (input == kInputInput) {
return TimeRange(GetRemappedTime(input_time.in()), GetRemappedTime(input_time.out()));
} else {
return super::InputTimeAdjustment(input, element, input_time);
return super::InputTimeAdjustment(input, element, input_time, clamp);
}
}
+1 -1
View File
@@ -38,7 +38,7 @@ public:
virtual QVector<CategoryID> Category() const override;
virtual QString Description() const override;
virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const override;
virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time, bool clamp) const override;
virtual TimeRange OutputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const override;
virtual void Retranslate() override;
+7 -12
View File
@@ -32,7 +32,7 @@ NodeValueDatabase NodeTraverser::GenerateDatabase(const Node* node, const TimeRa
NodeValueDatabase database;
// HACK: Pick up loop mode from clips
Decoder::LoopMode old_loop_mode = loop_mode_;
LoopMode old_loop_mode = loop_mode_;
if (const ClipBlock *clip = dynamic_cast<const ClipBlock*>(node)) {
loop_mode_ = clip->loop_mode();
}
@@ -184,17 +184,12 @@ void NodeTraverser::Transform(QTransform *transform, const Node *start, const No
transform_ = nullptr;
}
NodeGlobals NodeTraverser::GenerateGlobals(const VideoParams &vparams, const AudioParams &aparams, const TimeRange &time)
{
return NodeGlobals(vparams, aparams, time);
}
NodeValueTable NodeTraverser::ProcessInput(const Node* node, const QString& input, const TimeRange& range)
{
// If input is connected, retrieve value directly
if (node->IsInputConnectedForRender(input)) {
TimeRange adjusted_range = node->InputTimeAdjustment(input, -1, range);
TimeRange adjusted_range = node->InputTimeAdjustment(input, -1, range, true);
// Value will equal something from the connected node, follow it
Node *output = node->GetConnectedRenderOutput(input);
@@ -229,7 +224,7 @@ NodeValueTable NodeTraverser::ProcessInput(const Node* node, const QString& inpu
} else {
// Not connected or an array, just pull the immediate
TimeRange adjusted_range = node->InputTimeAdjustment(input, -1, range);
TimeRange adjusted_range = node->InputTimeAdjustment(input, -1, range, true);
return_val = node->GetValueAtTime(input, adjusted_range.in());
@@ -245,7 +240,7 @@ NodeValueTable NodeTraverser::ProcessInput(const Node* node, const QString& inpu
void NodeTraverser::ProcessInputElement(NodeValueTableArray &array_tbl, const Node *node, const QString &input, int element, const TimeRange &range)
{
NodeValueTable& sub_tbl = array_tbl[element];
TimeRange adjusted_range = node->InputTimeAdjustment(input, element, range);
TimeRange adjusted_range = node->InputTimeAdjustment(input, element, range, true);
if (node->IsInputConnectedForRender(input, element)) {
Node *output = node->GetConnectedRenderOutput(input, element);
@@ -259,7 +254,7 @@ void NodeTraverser::ProcessInputElement(NodeValueTableArray &array_tbl, const No
NodeTraverser::NodeTraverser() :
cancel_(nullptr),
transform_(nullptr),
loop_mode_(Decoder::kLoopModeOff)
loop_mode_(LoopMode::kLoopModeOff)
{
}
@@ -309,7 +304,7 @@ NodeValueTable NodeTraverser::GenerateTable(const Node *n, const TimeRange& rang
table = database.Merge();
// By this point, the node should have all the inputs it needs to render correctly
NodeGlobals globals = GenerateGlobals(video_params_, audio_params_, range);
NodeGlobals globals(video_params_, audio_params_, range, loop_mode_);
n->Value(row, globals, &table);
// `transform_now_` is the next node in the path that needs to be traversed. It only ever goes
@@ -430,7 +425,7 @@ void NodeTraverser::ResolveJobs(NodeValue &val)
} else if (FootageJob *fj = dynamic_cast<FootageJob*>(base_job)) {
rational footage_time = Footage::AdjustTimeByLoopMode(fj->time().in(), loop_mode_, fj->length(), fj->video_params().video_type(), fj->video_params().frame_rate_as_time_base());
rational footage_time = Footage::AdjustTimeByLoopMode(fj->time().in(), fj->loop_mode(), fj->length(), fj->video_params().video_type(), fj->video_params().frame_rate_as_time_base());
TexturePtr tex;
+2 -8
View File
@@ -54,12 +54,6 @@ public:
void Transform(QTransform *transform, const Node *start, const Node *end, const TimeRange &range);
static NodeGlobals GenerateGlobals(const VideoParams &vparams, const AudioParams &aparams, const TimeRange &time);
static NodeGlobals GenerateGlobals(const VideoParams &vparams, const AudioParams &aparams, const rational &time)
{
return GenerateGlobals(vparams, aparams, TimeRange(time, time + vparams.frame_rate_as_time_base()));
}
const VideoParams& GetCacheVideoParams() const
{
return video_params_;
@@ -144,7 +138,7 @@ protected:
return block_stack_.empty() ? nullptr : block_stack_.back();
}
Decoder::LoopMode loop_mode() const { return loop_mode_; }
LoopMode loop_mode() const { return loop_mode_; }
virtual bool UseCache() const { return false; }
@@ -163,7 +157,7 @@ private:
std::list<Block*> block_stack_;
Decoder::LoopMode loop_mode_;
LoopMode loop_mode_;
QHash<const Node*, QHash<TimeRange, NodeValueTable> > value_cache_;
QHash<Texture*, TexturePtr> resolved_texture_cache_;
+1 -1
View File
@@ -40,7 +40,7 @@ ProjectPanel::ProjectPanel(QWidget *parent) :
// Create main widget and its layout
QWidget* central_widget = new QWidget(this);
QVBoxLayout* layout = new QVBoxLayout(central_widget);
layout->setMargin(0);
layout->setContentsMargins(0, 0, 0, 0);
SetWidgetWithPadding(central_widget);
+1 -1
View File
@@ -36,7 +36,7 @@ ScopePanel::ScopePanel(QWidget* parent) :
QVBoxLayout* layout = new QVBoxLayout(central);
QHBoxLayout* toolbar_layout = new QHBoxLayout();
toolbar_layout->setMargin(0);
toolbar_layout->setContentsMargins(0, 0, 0, 0);
scope_type_combobox_ = new QComboBox();
+2 -11
View File
@@ -29,9 +29,9 @@ TimeBasedPanel::TimeBasedPanel(const QString &object_name, QWidget *parent) :
{
}
rational TimeBasedPanel::GetTime()
TimeBasedPanel::~TimeBasedPanel()
{
return widget_->GetTime();
delete widget_;
}
const rational& TimeBasedPanel::timebase()
@@ -74,11 +74,6 @@ void TimeBasedPanel::SetTimebase(const rational &timebase)
widget_->SetTimebase(timebase);
}
void TimeBasedPanel::SetTime(const rational &time)
{
widget_->SetTime(time);
}
void TimeBasedPanel::GoToPrevCut()
{
widget_->GoToPrevCut();
@@ -122,16 +117,12 @@ void TimeBasedPanel::ConnectViewerNode(ViewerOutput *node)
void TimeBasedPanel::SetTimeBasedWidget(TimeBasedWidget *widget)
{
if (widget_) {
disconnect(widget_, &TimeBasedWidget::TimeChanged, this, &TimeBasedPanel::TimeChanged);
disconnect(widget_, &TimeBasedWidget::TimebaseChanged, this, &TimeBasedPanel::TimebaseChanged);
disconnect(widget_, &TimeBasedWidget::ConnectedNodeChanged, this, &TimeBasedPanel::ConnectedNodeChanged);
}
widget_ = widget;
if (widget_) {
connect(widget_, &TimeBasedWidget::TimeChanged, this, &TimeBasedPanel::TimeChanged);
connect(widget_, &TimeBasedWidget::TimebaseChanged, this, &TimeBasedPanel::TimebaseChanged);
connect(widget_, &TimeBasedWidget::ConnectedNodeChanged, this, &TimeBasedPanel::ConnectedNodeChanged);
}
+2 -8
View File
@@ -32,6 +32,8 @@ class TimeBasedPanel : public PanelWidget
public:
TimeBasedPanel(const QString& object_name, QWidget *parent = nullptr);
virtual ~TimeBasedPanel() override;
void ConnectViewerNode(ViewerOutput *node);
void DisconnectViewerNode()
@@ -39,8 +41,6 @@ public:
ConnectViewerNode(nullptr);
}
rational GetTime();
// Get the timebase of this panels widget
const rational& timebase();
@@ -111,13 +111,7 @@ public:
public slots:
void SetTimebase(const rational& timebase);
void SetTime(const rational &time);
signals:
void TimeChanged(const rational& time);
void TimebaseChanged(const rational& timebase);
void PlayPauseRequested();
void PlayInToOutRequested();
+3
View File
@@ -38,6 +38,7 @@ set(OLIVE_SOURCES
render/framehashcache.h
render/framemanager.cpp
render/framemanager.h
render/loopmode.h
render/managedcolor.cpp
render/managedcolor.h
render/playbackcache.cpp
@@ -46,6 +47,8 @@ set(OLIVE_SOURCES
render/previewaudiodevice.h
render/previewautocacher.cpp
render/previewautocacher.h
render/projectcopier.cpp
render/projectcopier.h
render/renderer.cpp
render/renderer.h
render/rendercache.h
+8 -2
View File
@@ -33,12 +33,13 @@ public:
{
}
FootageJob(const TimeRange &time, const QString& decoder, const QString& filename, Track::Type type, const rational& length) :
FootageJob(const TimeRange &time, const QString& decoder, const QString& filename, Track::Type type, const rational& length, LoopMode loop_mode) :
time_(time),
decoder_(decoder),
filename_(filename),
type_(type),
length_(length)
length_(length),
loop_mode_(loop_mode)
{
}
@@ -99,6 +100,9 @@ public:
const TimeRange &time() const { return time_; }
LoopMode loop_mode() const { return loop_mode_; }
void set_loop_mode(LoopMode m) { loop_mode_ = m; }
private:
TimeRange time_;
@@ -116,6 +120,8 @@ private:
rational length_;
LoopMode loop_mode_;
};
}
+14
View File
@@ -0,0 +1,14 @@
#ifndef LOOPMODE_H
#define LOOPMODE_H
namespace olive {
enum class LoopMode {
kLoopModeOff,
kLoopModeLoop,
kLoopModeClamp
};
}
#endif // LOOPMODE_H
+7 -1
View File
@@ -25,6 +25,8 @@
#include <QDebug>
#include <QOpenGLExtraFunctions>
#include "config/config.h"
namespace olive {
const int OpenGLRenderer::kTextureCacheMaxSize = 5000;
@@ -364,7 +366,11 @@ void OpenGLRenderer::Flush()
{
GL_PREAMBLE;
functions_->glFlush();
if (OLIVE_CONFIG("UseGLFinish").toBool()) {
functions_->glFinish();
} else {
functions_->glFlush();
}
}
Color OpenGLRenderer::GetPixelFromTexture(Texture *texture, const QPointF &pt)
+38 -240
View File
@@ -28,11 +28,6 @@
#include "node/inputdragger.h"
#include "node/project/project.h"
#include "render/diskmanager.h"
#include "render/renderprocessor.h"
#include "task/customcache/customcachetask.h"
#include "task/taskmanager.h"
#include "widget/slider/base/numericsliderbase.h"
#include "widget/viewer/viewer.h"
namespace olive {
@@ -47,6 +42,10 @@ PreviewAutoCacher::PreviewAutoCacher(QObject *parent) :
multicam_(nullptr),
ignore_cache_requests_(false)
{
copier_ = new ProjectCopier(this);
connect(copier_, &ProjectCopier::AddedNode, this, &PreviewAutoCacher::ConnectToNodeCache);
connect(copier_, &ProjectCopier::RemovedNode, this, &PreviewAutoCacher::DisconnectFromNodeCache);
// Set defaults
SetPlayhead(0);
@@ -157,7 +156,7 @@ void PreviewAutoCacher::AudioRendered()
if (running_audio_tasks_.removeOne(watcher)) {
// Assume that a "result" is a fully completed image and a non-result is a cancelled ticket
TimeRange range = watcher->property("time").value<TimeRange>();
Node *node = copy_map_.key(Node::ValueToPtr<Node>(watcher->property("node")));
Node *node = copier_->GetOriginal(Node::ValueToPtr<Node>(watcher->property("node")));
if (watcher->HasResult() && node) {
if (PlaybackCache *cache = Node::ValueToPtr<PlaybackCache>(watcher->property("cache"))) {
@@ -252,140 +251,12 @@ void PreviewAutoCacher::VideoRendered()
delete watcher;
}
void PreviewAutoCacher::ProcessUpdateQueue()
{
// Iterate everything that happened to the graph and do the same thing on our end
while (!graph_update_queue_.empty()) {
QueuedJob job = graph_update_queue_.front();
graph_update_queue_.pop_front();
switch (job.type) {
case QueuedJob::kNodeAdded:
AddNode(job.node);
break;
case QueuedJob::kNodeRemoved:
RemoveNode(job.node);
break;
case QueuedJob::kEdgeAdded:
AddEdge(job.output, job.input);
break;
case QueuedJob::kEdgeRemoved:
RemoveEdge(job.output, job.input);
break;
case QueuedJob::kValueChanged:
CopyValue(job.input);
break;
case QueuedJob::kValueHintChanged:
CopyValueHint(job.input);
break;
}
}
// Indicate that we have synchronized to this point, which is compared with the graph change
// time to see if our copied graph is up to date
UpdateLastSyncedValue();
}
void PreviewAutoCacher::AddNode(Node *node)
{
if (dynamic_cast<NodeGroup*>(node)) {
// Group nodes are just dummy nodes, no need to copy them
return;
}
// Copy node
Node* copy = node->copy();
// Add to project
copy->setParent(&copied_project_);
// Disable caches for copy
copy->SetCachesEnabled(false);
// Copy cache UUIDs
copy->CopyCacheUuidsFrom(node);
// Insert into map
InsertIntoCopyMap(node, copy);
// Keep track of our nodes
created_nodes_.append(copy);
}
void PreviewAutoCacher::RemoveNode(Node *node)
{
// Find our copy and remove it
Node* copy = copy_map_.take(node);
// Disconnect from node's caches
DisconnectFromNodeCache(node);
// Remove from created list
created_nodes_.removeOne(copy);
// Delete it
delete copy;
}
void PreviewAutoCacher::AddEdge(Node *output, const NodeInput &input)
{
// Create same connection with our copied graph
Node* our_output = copy_map_.value(output);
Node* our_input = copy_map_.value(input.node());
Node::ConnectEdge(our_output, NodeInput(our_input, input.input(), input.element()));
}
void PreviewAutoCacher::RemoveEdge(Node *output, const NodeInput &input)
{
// Remove same connection with our copied graph
Node* our_output = copy_map_.value(output);
Node* our_input = copy_map_.value(input.node());
Node::DisconnectEdge(our_output, NodeInput(our_input, input.input(), input.element()));
}
void PreviewAutoCacher::CopyValue(const NodeInput &input)
{
if (dynamic_cast<NodeGroup*>(input.node())) {
// Group nodes are just dummy nodes, no need to copy them
return;
}
// Copy all values to our graph
Node* our_input = copy_map_.value(input.node());
Node::CopyValuesOfElement(input.node(), our_input, input.input(), input.element());
}
void PreviewAutoCacher::CopyValueHint(const NodeInput &input)
{
if (dynamic_cast<NodeGroup*>(input.node())) {
// Group nodes are just dummy nodes, no need to copy them
return;
}
// Copy value hint to our graph
Node* our_input = copy_map_.value(input.node());
Node::ValueHint hint = input.node()->GetValueHintForInput(input.input(), input.element());
our_input->SetValueHintForInput(input.input(), hint, input.element());
}
void PreviewAutoCacher::InsertIntoCopyMap(Node *node, Node *copy)
{
// Insert into map
copy_map_.insert(node, copy);
// Copy parameters
Node::CopyInputs(node, copy, false);
// Connect to node's cache
if (!ignore_cache_requests_) {
ConnectToNodeCache(node);
}
}
void PreviewAutoCacher::ConnectToNodeCache(Node *node)
{
if (ignore_cache_requests_) {
return;
}
connect(node->video_frame_cache(),
&PlaybackCache::Requested,
this,
@@ -455,16 +326,6 @@ void PreviewAutoCacher::DisconnectFromNodeCache(Node *node)
&PreviewAutoCacher::CancelForCache);
}
void PreviewAutoCacher::UpdateGraphChangeValue()
{
graph_changed_time_.Acquire();
}
void PreviewAutoCacher::UpdateLastSyncedValue()
{
last_update_time_.Acquire();
}
void PreviewAutoCacher::CancelQueuedSingleFrameRender()
{
if (single_frame_render_) {
@@ -477,7 +338,7 @@ void PreviewAutoCacher::CancelQueuedSingleFrameRender()
void PreviewAutoCacher::StartCachingRange(const TimeRange &range, TimeRangeList *range_list, RenderJobTracker *tracker)
{
range_list->insert(range);
tracker->insert(range, graph_changed_time_);
tracker->insert(range, copier_->GetGraphChangeTime());
}
void PreviewAutoCacher::StartCachingVideoRange(PlaybackCache *cache, const TimeRange &range)
@@ -494,7 +355,7 @@ void PreviewAutoCacher::StartCachingVideoRange(PlaybackCache *cache, const TimeR
TimeRangeListFrameIterator iterator({range}, using_tb);
pending_video_jobs_.push_back({node, cache, range, iterator});
video_cache_data_[cache].job_tracker.insert(TimeRange(iterator.Snap(range.in()), range.out()), graph_changed_time_);
video_cache_data_[cache].job_tracker.insert(TimeRange(iterator.Snap(range.in()), range.out()), copier_->GetGraphChangeTime());
TryRender();
}
@@ -505,12 +366,17 @@ void PreviewAutoCacher::StartCachingAudioRange(PlaybackCache *cache, const TimeR
cache->ClearRequestRange(range);
pending_audio_jobs_.push_back({node, cache, range});
audio_cache_data_[cache].job_tracker.insert(range, graph_changed_time_);
audio_cache_data_[cache].job_tracker.insert(range, copier_->GetGraphChangeTime());
TryRender();
}
void PreviewAutoCacher::VideoInvalidatedFromNode(PlaybackCache *cache, const TimeRange &range)
{
// Ignore render requests if no video is present
if (!viewer_node_ || !viewer_node_->GetVideoParams().is_valid()) {
return;
}
// Stop any current render tasks because a) they might be out of date now anyway, and b) we
// want to dedicate all our rendering power to realtime feedback for the user
//CancelVideoTasks(node);
@@ -525,6 +391,11 @@ void PreviewAutoCacher::VideoInvalidatedFromNode(PlaybackCache *cache, const Tim
void PreviewAutoCacher::AudioInvalidatedFromNode(PlaybackCache *cache, const TimeRange &range)
{
// Ignore render requests if no video is present
if (!viewer_node_ || !viewer_node_->GetAudioParams().is_valid()) {
return;
}
// We don't stop rendering audio because currently there's no system of requeuing audio if it's
// cancelled, so some areas may end up unrendered forever
// ClearAudioQueue();
@@ -592,47 +463,11 @@ void PreviewAutoCacher::SetThumbnailsPaused(bool e)
}
}
void PreviewAutoCacher::NodeAdded(Node *node)
{
graph_update_queue_.push_back({QueuedJob::kNodeAdded, node, NodeInput(), nullptr});
UpdateGraphChangeValue();
}
void PreviewAutoCacher::NodeRemoved(Node *node)
{
graph_update_queue_.push_back({QueuedJob::kNodeRemoved, node, NodeInput(), nullptr});
UpdateGraphChangeValue();
}
void PreviewAutoCacher::EdgeAdded(Node *output, const NodeInput &input)
{
graph_update_queue_.push_back({QueuedJob::kEdgeAdded, nullptr, input, output});
UpdateGraphChangeValue();
}
void PreviewAutoCacher::EdgeRemoved(Node *output, const NodeInput &input)
{
graph_update_queue_.push_back({QueuedJob::kEdgeRemoved, nullptr, input, output});
UpdateGraphChangeValue();
}
void PreviewAutoCacher::ValueChanged(const NodeInput &input)
{
graph_update_queue_.push_back({QueuedJob::kValueChanged, nullptr, input, nullptr});
UpdateGraphChangeValue();
}
void PreviewAutoCacher::ValueHintChanged(const NodeInput &input)
{
graph_update_queue_.push_back({QueuedJob::kValueHintChanged, nullptr, input, nullptr});
UpdateGraphChangeValue();
}
void PreviewAutoCacher::TryRender()
{
delayed_requeue_timer_.stop();
if (!graph_update_queue_.empty()) {
if (copier_->HasUpdatesInQueue()) {
// Check if we have jobs running in other threads that shouldn't be interrupted right now
// NOTE: We don't check for downloads because, while they run in another thread, they don't
// require any access to the graph and therefore don't risk race conditions.
@@ -642,7 +477,7 @@ void PreviewAutoCacher::TryRender()
}
// No jobs are active, we can process the update queue
ProcessUpdateQueue();
copier_->ProcessUpdateQueue();
}
if (single_frame_render_) {
@@ -653,7 +488,7 @@ void PreviewAutoCacher::TryRender()
// Check if already caching this
Node *n = Node::ValueToPtr<Node>(t->property("node"));
Node *copy = copy_map_.value(n);
Node *copy = copier_->GetCopy(n);
if (copy) {
RenderTicketWatcher *watcher = RenderFrame(copy,
@@ -676,7 +511,7 @@ void PreviewAutoCacher::TryRender()
while (!pending_video_jobs_.empty()) {
VideoJob &d = pending_video_jobs_.front();
if (Node *copy = copy_map_.value(d.node)) {
if (Node *copy = copier_->GetCopy(d.node)) {
// Queue next frames
rational t;
while (running_video_tasks_.size() < max_tasks && d.iterator.GetNext(&t)) {
@@ -707,7 +542,7 @@ void PreviewAutoCacher::TryRender()
bool pop = true;
// Start job
if (Node *copy = copy_map_.value(d.node)) {
if (Node *copy = copier_->GetCopy(d.node)) {
TimeRange &queued_range = d.range;
TimeRange use_range = queued_range;
@@ -736,7 +571,7 @@ void PreviewAutoCacher::TryRender()
RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& time, PlaybackCache *cache, bool dry)
{
RenderTicketWatcher* watcher = new RenderTicketWatcher();
watcher->setProperty("job", QVariant::fromValue(last_update_time_));
watcher->setProperty("job", QVariant::fromValue(copier_->GetLastUpdateTime()));
watcher->setProperty("cache", Node::PtrToValue(cache));
watcher->setProperty("time", QVariant::fromValue(time));
connect(watcher, &RenderTicketWatcher::Finished, this, &PreviewAutoCacher::VideoRendered);
@@ -768,7 +603,7 @@ RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational&
rvp.use_cache = true;
// Multicam
rvp.multicam = static_cast<MultiCamNode*>(copy_map_.value(multicam_));
rvp.multicam = copier_->GetCopy(multicam_);
watcher->SetTicket(RenderManager::instance()->RenderFrame(rvp));
@@ -778,7 +613,7 @@ RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational&
RenderTicketPtr PreviewAutoCacher::RenderAudio(Node *node, const TimeRange &r, PlaybackCache *cache)
{
RenderTicketWatcher* watcher = new RenderTicketWatcher();
watcher->setProperty("job", QVariant::fromValue(last_update_time_));
watcher->setProperty("job", QVariant::fromValue(copier_->GetLastUpdateTime()));
watcher->setProperty("node", Node::PtrToValue(node));
watcher->setProperty("cache", Node::PtrToValue(cache));
watcher->setProperty("time", QVariant::fromValue(r));
@@ -861,16 +696,13 @@ void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node)
video_immediate_passthroughs_.clear();
// Disconnect from all node cache's
for (auto it=copy_map_.cbegin(); it!=copy_map_.cend(); it++) {
for (auto it=copier_->GetNodeMap().cbegin(); it!=copier_->GetNodeMap().cend(); it++) {
DisconnectFromNodeCache(it.key());
}
// Delete all of our copied nodes
qDeleteAll(created_nodes_);
created_nodes_.clear();
copy_map_.clear();
copier_->SetProject(nullptr);
copied_viewer_node_ = nullptr;
graph_update_queue_.clear();
// Ensure all cache data is cleared
video_cache_data_.clear();
@@ -878,59 +710,25 @@ void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node)
// Clear multicam reference
multicam_ = nullptr;
// Disconnect signals for future node additions/deletions
NodeGraph* graph = viewer_node_->parent();
disconnect(graph, &NodeGraph::NodeAdded, this, &PreviewAutoCacher::NodeAdded);
disconnect(graph, &NodeGraph::NodeRemoved, this, &PreviewAutoCacher::NodeRemoved);
disconnect(graph, &NodeGraph::InputConnected, this, &PreviewAutoCacher::EdgeAdded);
disconnect(graph, &NodeGraph::InputDisconnected, this, &PreviewAutoCacher::EdgeRemoved);
disconnect(graph, &NodeGraph::ValueChanged, this, &PreviewAutoCacher::ValueChanged);
disconnect(graph, &NodeGraph::InputValueHintChanged, this, &PreviewAutoCacher::ValueHintChanged);
}
viewer_node_ = viewer_node;
if (viewer_node_) {
// Copy graph
NodeGraph* graph = viewer_node_->parent();
// Copy graph (this should always be a Project)
Project* graph = static_cast<Project*>(viewer_node_->parent());
SetRendersPaused(true);
// Add all nodes
for (int i=0; i<copied_project_.nodes().size(); i++) {
InsertIntoCopyMap(graph->nodes().at(i), copied_project_.nodes().at(i));
}
for (int i=copied_project_.nodes().size(); i<graph->nodes().size(); i++) {
AddNode(graph->nodes().at(i));
}
copier_->SetProject(graph);
for (int i=0; i<graph->nodes().size(); i++) {
graph->nodes().at(i)->ConnectedToPreviewEvent();
}
// Find copied viewer node
copied_viewer_node_ = static_cast<ViewerOutput*>(copy_map_.value(viewer_node_));
copied_color_manager_ = static_cast<ColorManager*>(copy_map_.value(viewer_node_->project()->color_manager()));
// Add all connections
foreach (Node* node, graph->nodes()) {
for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) {
AddEdge(it->second, it->first);
}
}
// Ensure graph change value is just before the sync value
UpdateGraphChangeValue();
UpdateLastSyncedValue();
// Connect signals for future node additions/deletions
connect(graph, &NodeGraph::NodeAdded, this, &PreviewAutoCacher::NodeAdded, Qt::DirectConnection);
connect(graph, &NodeGraph::NodeRemoved, this, &PreviewAutoCacher::NodeRemoved, Qt::DirectConnection);
connect(graph, &NodeGraph::InputConnected, this, &PreviewAutoCacher::EdgeAdded, Qt::DirectConnection);
connect(graph, &NodeGraph::InputDisconnected, this, &PreviewAutoCacher::EdgeRemoved, Qt::DirectConnection);
connect(graph, &NodeGraph::ValueChanged, this, &PreviewAutoCacher::ValueChanged, Qt::DirectConnection);
connect(graph, &NodeGraph::InputValueHintChanged, this, &PreviewAutoCacher::ValueHintChanged, Qt::DirectConnection);
copied_viewer_node_ = copier_->GetCopy(viewer_node_);
copied_color_manager_ = copier_->GetCopy(graph->color_manager());
SetRendersPaused(false);
}
+5 -60
View File
@@ -31,6 +31,7 @@
#include "node/output/viewer/viewer.h"
#include "node/project/project.h"
#include "render/audioparams.h"
#include "render/projectcopier.h"
#include "render/renderjobtracker.h"
#include "render/rendermanager.h"
@@ -113,29 +114,9 @@ private:
RenderTicketPtr RenderAudio(Node *node, const TimeRange &range, PlaybackCache *cache);
/**
* @brief Process all changes to internal NodeGraph copy
*
* PreviewAutoCacher staggers updates to its internal NodeGraph copy, only applying them when the
* RenderManager is not reading from it. This function is called when such an opportunity arises.
*/
void ProcessUpdateQueue();
void AddNode(Node* node);
void RemoveNode(Node* node);
void AddEdge(Node *output, const NodeInput& input);
void RemoveEdge(Node *output, const NodeInput& input);
void CopyValue(const NodeInput& input);
void CopyValueHint(const NodeInput& input);
void InsertIntoCopyMap(Node* node, Node* copy);
void ConnectToNodeCache(Node *node);
void DisconnectFromNodeCache(Node *node);
void UpdateGraphChangeValue();
void UpdateLastSyncedValue();
void CancelQueuedSingleFrameRender();
void StartCachingRange(const TimeRange &range, TimeRangeList *range_list, RenderJobTracker *tracker);
@@ -145,33 +126,9 @@ private:
void VideoInvalidatedFromNode(PlaybackCache *cache, const olive::TimeRange &range);
void AudioInvalidatedFromNode(PlaybackCache *cache, const olive::TimeRange &range);
class QueuedJob {
public:
enum Type {
kNodeAdded,
kNodeRemoved,
kEdgeAdded,
kEdgeRemoved,
kValueChanged,
kValueHintChanged
};
Type type;
Node* node;
NodeInput input;
Node *output;
};
ViewerOutput* viewer_node_;
Project copied_project_;
std::list<QueuedJob> graph_update_queue_;
QHash<Node*, Node*> copy_map_;
QHash<NodeGraph*, NodeGraph*> graph_map_;
ViewerOutput* copied_viewer_node_;
ColorManager* copied_color_manager_;
QVector<Node*> created_nodes_;
ProjectCopier *copier_;
TimeRange cache_range_;
@@ -184,9 +141,6 @@ private:
RenderTicketPtr single_frame_render_;
QMap<RenderTicketWatcher*, QVector<RenderTicketPtr> > video_immediate_passthroughs_;
JobTime graph_changed_time_;
JobTime last_update_time_;
QTimer delayed_requeue_timer_;
JobTime last_conform_task_;
@@ -194,6 +148,9 @@ private:
QVector<RenderTicketWatcher*> running_video_tasks_;
QVector<RenderTicketWatcher*> running_audio_tasks_;
ViewerOutput* copied_viewer_node_;
ColorManager* copied_color_manager_;
struct VideoJob {
Node *node;
PlaybackCache *cache;
@@ -251,18 +208,6 @@ private slots:
*/
void VideoRendered();
void NodeAdded(Node* node);
void NodeRemoved(Node* node);
void EdgeAdded(Node *output, const NodeInput& input);
void EdgeRemoved(Node *output, const NodeInput& input);
void ValueChanged(const NodeInput& input);
void ValueHintChanged(const NodeInput &input);
/**
* @brief Generic function called whenever the frames to render need to be (re)queued
*/
+259
View File
@@ -0,0 +1,259 @@
/***
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 "projectcopier.h"
namespace olive {
ProjectCopier::ProjectCopier(QObject *parent) :
QObject(parent)
{
original_ = nullptr;
copy_ = new Project();
copy_->setParent(this);
}
void ProjectCopier::SetProject(Project *project)
{
if (original_) {
// Clear current project
qDeleteAll(created_nodes_);
created_nodes_.clear();
copy_map_.clear();
graph_update_queue_.clear();
disconnect(original_, &NodeGraph::NodeAdded, this, &ProjectCopier::QueueNodeAdd);
disconnect(original_, &NodeGraph::NodeRemoved, this, &ProjectCopier::QueueNodeRemove);
disconnect(original_, &NodeGraph::InputConnected, this, &ProjectCopier::QueueEdgeAdd);
disconnect(original_, &NodeGraph::InputDisconnected, this, &ProjectCopier::QueueEdgeRemove);
disconnect(original_, &NodeGraph::ValueChanged, this, &ProjectCopier::QueueValueChange);
disconnect(original_, &NodeGraph::InputValueHintChanged, this, &ProjectCopier::QueueValueHintChange);
}
original_ = project;
if (original_) {
// Add all nodes
for (int i=0; i<copy_->nodes().size(); i++) {
InsertIntoCopyMap(original_->nodes().at(i), copy_->nodes().at(i));
}
for (int i=copy_->nodes().size(); i<original_->nodes().size(); i++) {
DoNodeAdd(original_->nodes().at(i));
}
// Add all connections
foreach (Node* node, original_->nodes()) {
for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) {
DoEdgeAdd(it->second, it->first);
}
}
// Ensure graph change value is just before the sync value
UpdateGraphChangeValue();
UpdateLastSyncedValue();
// Connect signals for future node additions/deletions
connect(original_, &NodeGraph::NodeAdded, this, &ProjectCopier::QueueNodeAdd, Qt::DirectConnection);
connect(original_, &NodeGraph::NodeRemoved, this, &ProjectCopier::QueueNodeRemove, Qt::DirectConnection);
connect(original_, &NodeGraph::InputConnected, this, &ProjectCopier::QueueEdgeAdd, Qt::DirectConnection);
connect(original_, &NodeGraph::InputDisconnected, this, &ProjectCopier::QueueEdgeRemove, Qt::DirectConnection);
connect(original_, &NodeGraph::ValueChanged, this, &ProjectCopier::QueueValueChange, Qt::DirectConnection);
connect(original_, &NodeGraph::InputValueHintChanged, this, &ProjectCopier::QueueValueHintChange, Qt::DirectConnection);
}
}
void ProjectCopier::ProcessUpdateQueue()
{
// Iterate everything that happened to the graph and do the same thing on our end
while (!graph_update_queue_.empty()) {
QueuedJob job = graph_update_queue_.front();
graph_update_queue_.pop_front();
switch (job.type) {
case QueuedJob::kNodeAdded:
DoNodeAdd(job.node);
break;
case QueuedJob::kNodeRemoved:
DoNodeRemove(job.node);
break;
case QueuedJob::kEdgeAdded:
DoEdgeAdd(job.output, job.input);
break;
case QueuedJob::kEdgeRemoved:
DoEdgeRemove(job.output, job.input);
break;
case QueuedJob::kValueChanged:
DoValueChange(job.input);
break;
case QueuedJob::kValueHintChanged:
DoValueHintChange(job.input);
break;
}
}
// Indicate that we have synchronized to this point, which is compared with the graph change
// time to see if our copied graph is up to date
UpdateLastSyncedValue();
}
void ProjectCopier::DoNodeAdd(Node *node)
{
if (dynamic_cast<NodeGroup*>(node)) {
// Group nodes are just dummy nodes, no need to copy them
return;
}
// Copy node
Node* copy = node->copy();
// Add to project
copy->setParent(copy_);
// Disable caches for copy
copy->SetCachesEnabled(false);
// Copy cache UUIDs
copy->CopyCacheUuidsFrom(node);
// Insert into map
InsertIntoCopyMap(node, copy);
// Keep track of our nodes
created_nodes_.append(copy);
}
void ProjectCopier::DoNodeRemove(Node *node)
{
// Find our copy and remove it
Node* copy = copy_map_.take(node);
// Disconnect from node's caches
emit RemovedNode(node);
// Remove from created list
created_nodes_.removeOne(copy);
// Delete it
delete copy;
}
void ProjectCopier::DoEdgeAdd(Node *output, const NodeInput &input)
{
// Create same connection with our copied graph
Node* our_output = copy_map_.value(output);
Node* our_input = copy_map_.value(input.node());
Node::ConnectEdge(our_output, NodeInput(our_input, input.input(), input.element()));
}
void ProjectCopier::DoEdgeRemove(Node *output, const NodeInput &input)
{
// Remove same connection with our copied graph
Node* our_output = copy_map_.value(output);
Node* our_input = copy_map_.value(input.node());
Node::DisconnectEdge(our_output, NodeInput(our_input, input.input(), input.element()));
}
void ProjectCopier::DoValueChange(const NodeInput &input)
{
if (dynamic_cast<NodeGroup*>(input.node())) {
// Group nodes are just dummy nodes, no need to copy them
return;
}
// Copy all values to our graph
Node* our_input = copy_map_.value(input.node());
Node::CopyValuesOfElement(input.node(), our_input, input.input(), input.element());
}
void ProjectCopier::DoValueHintChange(const NodeInput &input)
{
if (dynamic_cast<NodeGroup*>(input.node())) {
// Group nodes are just dummy nodes, no need to copy them
return;
}
// Copy value hint to our graph
Node* our_input = copy_map_.value(input.node());
Node::ValueHint hint = input.node()->GetValueHintForInput(input.input(), input.element());
our_input->SetValueHintForInput(input.input(), hint, input.element());
}
void ProjectCopier::InsertIntoCopyMap(Node *node, Node *copy)
{
// Insert into map
copy_map_.insert(node, copy);
// Copy parameters
Node::CopyInputs(node, copy, false);
// Connect to node's cache
emit AddedNode(node);
}
void ProjectCopier::QueueNodeAdd(Node *node)
{
graph_update_queue_.push_back({QueuedJob::kNodeAdded, node, NodeInput(), nullptr});
UpdateGraphChangeValue();
}
void ProjectCopier::QueueNodeRemove(Node *node)
{
graph_update_queue_.push_back({QueuedJob::kNodeRemoved, node, NodeInput(), nullptr});
UpdateGraphChangeValue();
}
void ProjectCopier::QueueEdgeAdd(Node *output, const NodeInput &input)
{
graph_update_queue_.push_back({QueuedJob::kEdgeAdded, nullptr, input, output});
UpdateGraphChangeValue();
}
void ProjectCopier::QueueEdgeRemove(Node *output, const NodeInput &input)
{
graph_update_queue_.push_back({QueuedJob::kEdgeRemoved, nullptr, input, output});
UpdateGraphChangeValue();
}
void ProjectCopier::QueueValueChange(const NodeInput &input)
{
graph_update_queue_.push_back({QueuedJob::kValueChanged, nullptr, input, nullptr});
UpdateGraphChangeValue();
}
void ProjectCopier::QueueValueHintChange(const NodeInput &input)
{
graph_update_queue_.push_back({QueuedJob::kValueHintChanged, nullptr, input, nullptr});
UpdateGraphChangeValue();
}
void ProjectCopier::UpdateGraphChangeValue()
{
graph_changed_time_.Acquire();
}
void ProjectCopier::UpdateLastSyncedValue()
{
last_update_time_.Acquire();
}
}
+125
View File
@@ -0,0 +1,125 @@
/***
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 PROJECTCOPIER_H
#define PROJECTCOPIER_H
#include "node/project/project.h"
namespace olive {
class ProjectCopier : public QObject
{
Q_OBJECT
public:
ProjectCopier(QObject *parent = nullptr);
void SetProject(Project *project);
template <typename T>
T *GetCopy(T *original)
{
return static_cast<T*>(copy_map_.value(original));
}
template <typename T>
T *GetOriginal(T *copy)
{
return static_cast<T*>(copy_map_.key(copy));
}
const QHash<Node*, Node*> &GetNodeMap() const { return copy_map_; }
const JobTime &GetGraphChangeTime() const { return graph_changed_time_; }
const JobTime &GetLastUpdateTime() const { return last_update_time_; }
bool HasUpdatesInQueue() const { return !graph_update_queue_.empty(); }
/**
* @brief Process all changes to internal NodeGraph copy
*
* PreviewAutoCacher staggers updates to its internal NodeGraph copy, only applying them when the
* RenderManager is not reading from it. This function is called when such an opportunity arises.
*/
void ProcessUpdateQueue();
signals:
void AddedNode(Node *n);
void RemovedNode(Node *n);
private:
void DoNodeAdd(Node* node);
void DoNodeRemove(Node* node);
void DoEdgeAdd(Node *output, const NodeInput& input);
void DoEdgeRemove(Node *output, const NodeInput& input);
void DoValueChange(const NodeInput& input);
void DoValueHintChange(const NodeInput& input);
void InsertIntoCopyMap(Node* node, Node* copy);
void UpdateGraphChangeValue();
void UpdateLastSyncedValue();
Project *original_;
Project *copy_;
class QueuedJob {
public:
enum Type {
kNodeAdded,
kNodeRemoved,
kEdgeAdded,
kEdgeRemoved,
kValueChanged,
kValueHintChanged
};
Type type;
Node* node;
NodeInput input;
Node *output;
};
std::list<QueuedJob> graph_update_queue_;
QHash<Node*, Node*> copy_map_;
QHash<NodeGraph*, NodeGraph*> graph_map_;
QVector<Node*> created_nodes_;
JobTime graph_changed_time_;
JobTime last_update_time_;
private slots:
void QueueNodeAdd(Node* node);
void QueueNodeRemove(Node* node);
void QueueEdgeAdd(Node *output, const NodeInput& input);
void QueueEdgeRemove(Node *output, const NodeInput& input);
void QueueValueChange(const NodeInput& input);
void QueueValueHintChange(const NodeInput &input);
};
}
#endif // PROJECTCOPIER_H
+1
View File
@@ -22,6 +22,7 @@
#define RENDERTEXTURE_H
#include <memory>
#include <QVariant>
#include "render/videoparams.h"
+21
View File
@@ -0,0 +1,21 @@
uniform sampler2D ove_maintex;
uniform int interlacing;
uniform int pixel_height;
in vec2 ove_texcoord;
out vec4 frag_color;
void main() {
vec2 real_coord = ove_texcoord;
if (interlacing != 0) {
float field_height = float(pixel_height / 2);
real_coord.y = floor(real_coord.y * field_height) + 0.25;
if (interlacing == 2) {
real_coord.y += 0.5;
}
real_coord.y /= field_height;
}
frag_color = texture(ove_maintex, real_coord);
}
+20 -4
View File
@@ -1,8 +1,13 @@
#define LINEAR_CURVE 0
#define EXPONENTIAL_CURVE 1
#define LOGARITHMIC_CURVE 2
uniform sampler2D out_block_in;
uniform sampler2D in_block_in;
uniform bool out_block_in_enabled;
uniform bool in_block_in_enabled;
uniform vec4 color_in;
uniform int curve_in;
uniform float ove_tprog_all;
uniform float ove_tprog_out;
@@ -11,16 +16,27 @@ uniform float ove_tprog_in;
in vec2 ove_texcoord;
out vec4 frag_color;
float TransformCurve(float linear) {
if (curve_in == EXPONENTIAL_CURVE) {
return linear * linear;
} else if (curve_in == LOGARITHMIC_CURVE) {
return sqrt(linear);
} else {
return linear;
}
}
void main(void) {
if (out_block_in_enabled && in_block_in_enabled) {
vec4 out_block_col = mix(texture(out_block_in, ove_texcoord), color_in, ove_tprog_out);
vec4 in_block_col = mix(texture(in_block_in, ove_texcoord), color_in, 1.0 - ove_tprog_in);
// mix(x, y , a): a(1-x) + b(x)
vec4 out_block_col = ove_tprog_out == 0.0 ? vec4(0.0) : mix(color_in, texture(out_block_in, ove_texcoord),TransformCurve(ove_tprog_out));
vec4 in_block_col = ove_tprog_out != 0.0 ? vec4(0.0) : mix(color_in, texture(in_block_in, ove_texcoord), TransformCurve(ove_tprog_in));
frag_color = out_block_col + in_block_col;
} else if (out_block_in_enabled) {
frag_color = mix(texture(out_block_in, ove_texcoord), color_in, ove_tprog_all);
frag_color = mix(color_in, texture(out_block_in, ove_texcoord), TransformCurve(ove_tprog_out));
} else if (in_block_in_enabled) {
frag_color = mix(texture(in_block_in, ove_texcoord), color_in, 1.0 - ove_tprog_all);
frag_color = mix(texture(in_block_in, ove_texcoord), color_in, TransformCurve(1.0 - ove_tprog_in));
} else {
frag_color = vec4(0.0);
}
+27
View File
@@ -0,0 +1,27 @@
// Inputs
uniform sampler2D tex_in;
uniform sampler2D opacity_in;
// Input texture coordinate
in vec2 ove_texcoord;
out vec4 frag_color;
vec3 rgb2hsv(vec3 c)
{
vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
float d = q.x - min(q.w, q.y);
float e = 1.0e-10;
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
void main() {
vec4 value = texture(opacity_in, ove_texcoord);
float v = rgb2hsv(value.rgb).b;
vec4 c = texture(tex_in, ove_texcoord);
c *= v;
frag_color = c;
}
+20 -33
View File
@@ -5,40 +5,36 @@ uniform sampler2D v_channel;
uniform int bits_per_pixel;
uniform bool full_range;
uniform int yuv_crv;
uniform int yuv_cgu;
uniform int yuv_cgv;
uniform int yuv_cbu;
uniform int interlacing;
uniform int pixel_height;
uniform float yuv_crv;
uniform float yuv_cgu;
uniform float yuv_cgv;
uniform float yuv_cbu;
in vec2 ove_texcoord;
out vec4 frag_color;
void main()
{
vec2 real_coord = ove_texcoord;
if (interlacing != 0) {
float field_height = float(pixel_height / 2);
real_coord.y = floor(real_coord.y * field_height) + 0.25;
if (interlacing == 2) {
real_coord.y += 0.5;
}
real_coord.y /= field_height;
}
// Sample YUV planes
vec3 yuv;
yuv.r = texture(y_channel, real_coord).r;
yuv.g = texture(u_channel, real_coord).r;
yuv.b = texture(v_channel, real_coord).r;
yuv.r = texture(y_channel, ove_texcoord).r;
yuv.g = texture(u_channel, ove_texcoord).r;
yuv.b = texture(v_channel, ove_texcoord).r;
// Pixels will have come in aligned to 16-bit regardless of their actual bit depth, so they must
// be scaled as if they were actually 16-bit
if (bits_per_pixel == 10) {
if (bits_per_pixel == 8) {
// Convert 0.0-1.0 to -0.5-0.5
yuv.gb -= (128.0/255.0);
} else if (bits_per_pixel == 10) {
// Convert 0.0-1.0 to -0.5-0.5
yuv.gb -= (512.0/1023.0);
yuv *= 64.0;
} else if (bits_per_pixel == 12) {
// Convert 0.0-1.0 to -0.5-0.5
yuv.gb -= (2048.0/4095.0);
yuv *= 16.0;
}
@@ -46,20 +42,11 @@ void main()
yuv.r -= 0.0625; // 16/256
yuv.r *= 1.1643; // 255/219
// Convert 0.0-1.0 to -0.5-0.5
yuv.g = yuv.g - 0.5;
yuv.b = yuv.b - 0.5;
// Use coefficients to weigh YUV into RGB
float crv = float(yuv_crv) / 65536.0;
float cgu = float(yuv_cgu) / 65536.0;
float cgv = float(yuv_cgv) / 65536.0;
float cbu = float(yuv_cbu) / 65536.0;
vec4 rgba;
rgba.r = yuv.r + crv * yuv.b;
rgba.g = yuv.r - cgu * yuv.g - cgv * yuv.b;
rgba.b = yuv.r + cbu * yuv.g;
rgba.r = yuv.r + yuv_crv * yuv.b;
rgba.g = yuv.r - yuv_cgu * yuv.g - yuv_cgv * yuv.b;
rgba.b = yuv.r + yuv_cbu * yuv.g;
// If the expected value is full range, transform to full range here
if (full_range) {
+6 -2
View File
@@ -28,10 +28,14 @@ namespace olive {
ExportTask::ExportTask(ViewerOutput *viewer_node,
ColorManager* color_manager,
const EncodingParams& params) :
color_manager_(color_manager),
params_(params)
{
set_viewer(viewer_node);
// Create a copy of the project
copier_ = new ProjectCopier(this);
copier_->SetProject(viewer_node->project());
set_viewer(copier_->GetCopy(viewer_node));
color_manager_ = copier_->GetCopy(color_manager);
// Adjust video params to have no divider
VideoParams vp = viewer_node->GetVideoParams();
+3
View File
@@ -24,6 +24,7 @@
#include "codec/encoder.h"
#include "node/output/viewer/viewer.h"
#include "render/colorprocessor.h"
#include "render/projectcopier.h"
#include "task/render/render.h"
#include "task/task.h"
@@ -52,6 +53,8 @@ protected:
private:
bool WriteAudioLoop(const TimeRange &time, const SampleBuffer &samples);
ProjectCopier *copier_;
QHash<rational, FramePtr> time_map_;
QHash<TimeRange, SampleBuffer> audio_map_;
+7 -2
View File
@@ -35,7 +35,7 @@ ProjectImportTask::ProjectImportTask(Folder *folder, const QStringList &filename
folder_(folder)
{
foreach (const QString& f, filenames) {
filenames_.append(f);
filenames_.append(QFileInfo(f));
}
file_count_ = Core::CountFilesInFileList(filenames_);
@@ -106,10 +106,15 @@ void ProjectImportTask::Import(Folder *folder, QFileInfoList import, int &counte
} else {
Footage* footage = new Footage(file_info.absoluteFilePath());
Footage* footage = new Footage();
footage->SetCancelPointer(this->GetCancelAtom());
footage->set_filename(file_info.absoluteFilePath());
footage->SetLabel(file_info.fileName());
footage->SetCancelPointer(nullptr);
if (footage->IsValid()) {
// See if this footage is an image sequence
ValidateImageSequence(footage, import, i);
+2 -1
View File
@@ -61,7 +61,8 @@ bool LoadOTIOTask::Run()
auto root = OTIO::SerializableObjectWithMetadata::from_json_file(GetFilename().toStdString(), &es);
if (es.outcome != OTIO::ErrorStatus::Outcome::OK) {
SetError(tr("Failed to load OpenTimelineIO from file \"%1\"").arg(GetFilename()));
SetError(tr("Failed to load OpenTimelineIO from file \"%1\" \n\nOpenTimelineIO Error:\n\n%2")
.arg(GetFilename(), QString::fromStdString(es.full_description)));
return false;
}
+3
View File
@@ -93,6 +93,7 @@ public slots:
bool Start()
{
start_time_ = QDateTime::currentMSecsSinceEpoch();
emit Started(start_time_);
bool ret = Run();
@@ -150,6 +151,8 @@ protected:
}
signals:
void Started(qint64 start_time);
/**
* @brief Signal emitted whenever progress is made
*
+8 -1
View File
@@ -29,6 +29,7 @@ TaskManager* TaskManager::instance_ = nullptr;
TaskManager::TaskManager()
{
thread_pool_.setMaxThreadCount(1);
}
TaskManager::~TaskManager()
@@ -93,7 +94,13 @@ void TaskManager::AddTask(Task* t)
tasks_.insert(watcher, t);
// Run task concurrently
watcher->setFuture(QtConcurrent::run(t, &Task::Start));
watcher->setFuture(
#if QT_VERSION_MAJOR >= 6
QtConcurrent::run(&thread_pool_, &Task::Start, t)
#else
QtConcurrent::run(&thread_pool_, t, &Task::Start)
#endif
);
// Emit signal that a Task was added
emit TaskAdded(t);
-5
View File
@@ -36,7 +36,6 @@ namespace olive {
const char* StyleManager::kDefaultStyle = "olive-dark";
QString StyleManager::current_style_;
QMap<QString, QString> StyleManager::available_themes_;
QPalette StyleManager::platform_palette_;
QPalette StyleManager::ParsePalette(const QString& ini_path)
{
@@ -127,10 +126,6 @@ void StyleManager::ParsePaletteColor(QSettings *ini, QPalette *palette, QPalette
void StyleManager::Init()
{
// Store standard palette before replacing it with our own
platform_palette_ = qApp->palette();
platform_palette_.resolve(-1);
qApp->setStyle(QStyleFactory::create("Fusion"));
available_themes_.insert(QStringLiteral("olive-dark"), QStringLiteral("Olive Dark"));
-2
View File
@@ -54,8 +54,6 @@ private:
static QMap<QString, QString> available_themes_;
static QPalette platform_palette_;
};
}
+1 -1
View File
@@ -44,7 +44,7 @@ ColorValuesWidget::ColorValuesWidget(ColorManager *manager, QWidget *parent) :
{
QHBoxLayout* preview_layout = new QHBoxLayout();
preview_layout->setMargin(0);
preview_layout->setContentsMargins(0, 0, 0, 0);
preview_layout->addWidget(new QLabel(tr("Preview")));
+1 -1
View File
@@ -519,7 +519,7 @@ void CurveView::ZoomToFitInternal(bool selected_only)
rational transformed_time = GetAdjustedTime(key->parent(),
GetTimeTarget(),
key->time(),
false);
Node::kTransformTowardsOutput);
qreal key_y = GetUnscaledItemYFromKeyframeValue(key);
+5 -20
View File
@@ -53,13 +53,12 @@ CurveWidget::CurveWidget(QWidget *parent) :
QWidget* workarea = new QWidget();
QVBoxLayout* layout = new QVBoxLayout(workarea);
layout->setMargin(0);
layout->setContentsMargins(0, 0, 0, 0);
splitter->addWidget(workarea);
QHBoxLayout* top_controls = new QHBoxLayout();
key_control_ = new NodeParamViewKeyframeControl(false);
connect(key_control_, &NodeParamViewKeyframeControl::RequestSetTime, this, &CurveWidget::SetTimeAndSignal);
top_controls->addWidget(key_control_);
top_controls->addStretch();
@@ -86,7 +85,7 @@ CurveWidget::CurveWidget(QWidget *parent) :
// We use a separate layout for the ruler+view combination so that there's no spacing between them
QVBoxLayout* ruler_view_layout = new QVBoxLayout();
ruler_view_layout->setMargin(0);
ruler_view_layout->setContentsMargins(0, 0, 0, 0);
ruler_view_layout->setSpacing(0);
ruler_view_layout->addWidget(ruler());
@@ -99,15 +98,12 @@ CurveWidget::CurveWidget(QWidget *parent) :
layout->addLayout(ruler_view_layout);
// Connect ruler and view together
connect(view_, &CurveView::TimeChanged, this, &CurveWidget::SetTimeAndSignal);
connect(view_, &CurveView::SelectionChanged, this, &CurveWidget::SelectionChanged);
connect(view_, &CurveView::ScaleChanged, this, &CurveWidget::SetScale);
connect(view_, &CurveView::Dragged, this, &CurveWidget::KeyframeViewDragged);
connect(view_, &CurveView::Released, this, &CurveWidget::KeyframeViewReleased);
// TimeBasedWidget's scrollbar has extra functionality that we can take advantage of
view_->setHorizontalScrollBar(scrollbar());
connect(view_->horizontalScrollBar(), &QScrollBar::valueChanged, ruler(), &TimeRuler::SetScroll);
// Disable collapsing the main curve view (but allow collapsing the tree)
splitter->setCollapsible(1, false);
@@ -193,14 +189,6 @@ void CurveWidget::SetNodes(const QVector<Node *> &nodes)
}
}
void CurveWidget::TimeChangedEvent(const rational &time)
{
super::TimeChangedEvent(time);
view_->SetTime(time);
UpdateBridgeTime(time);
}
void CurveWidget::TimebaseChangedEvent(const rational &timebase)
{
super::TimebaseChangedEvent(timebase);
@@ -215,7 +203,7 @@ void CurveWidget::ScaleChangedEvent(const double &scale)
view_->SetScale(scale);
}
void CurveWidget::TimeTargetChangedEvent(Node *target)
void CurveWidget::TimeTargetChangedEvent(ViewerOutput *target)
{
TimeTargetObject::TimeTargetChangedEvent(target);
@@ -228,6 +216,8 @@ void CurveWidget::ConnectedNodeChangeEvent(ViewerOutput *n)
{
super::ConnectedNodeChangeEvent(n);
key_control_->SetTimeTarget(n);
SetTimeTarget(n);
}
@@ -252,11 +242,6 @@ void CurveWidget::SetKeyframeButtonCheckedFromType(NodeKeyframe::Type type)
hold_button_->setChecked(type == NodeKeyframe::kHold);
}
void CurveWidget::UpdateBridgeTime(const rational &time)
{
key_control_->SetTime(time);
}
void CurveWidget::ConnectInput(Node *node, const QString &input, int element)
{
if (element == -1 && node->InputIsArray(input)) {
+1 -4
View File
@@ -65,11 +65,10 @@ public slots:
void SetNodes(const QVector<Node *> &nodes);
protected:
virtual void TimeChangedEvent(const rational &) override;
virtual void TimebaseChangedEvent(const rational &) override;
virtual void ScaleChangedEvent(const double &) override;
virtual void TimeTargetChangedEvent(Node* target) override;
virtual void TimeTargetChangedEvent(ViewerOutput *target) override;
virtual void ConnectedNodeChangeEvent(ViewerOutput* n) override;
@@ -95,8 +94,6 @@ private:
void SetKeyframeButtonCheckedFromType(NodeKeyframe::Type type);
void UpdateBridgeTime(const rational &time);
void ConnectInput(Node *node, const QString &input, int element);
void ConnectInputInternal(Node *node, const QString &input, int element);
+1 -1
View File
@@ -34,7 +34,7 @@ FileField::FileField(QWidget* parent) :
{
QHBoxLayout* layout = new QHBoxLayout(this);
layout->setMargin(0);
layout->setContentsMargins(0, 0, 0, 0);
line_edit_ = new QLineEdit();
connect(line_edit_, &QLineEdit::textChanged, this, &FileField::LineEditChanged);
+1 -1
View File
@@ -146,7 +146,7 @@ QSize FlowLayout::minimumSize() const
foreach (item, itemList)
size = size.expandedTo(item->minimumSize());
size += QSize(2*margin(), 2*margin());
size += QSize(2*contentsMargins().left(), 2*contentsMargins().top());
return size;
}
+65 -8
View File
@@ -31,7 +31,8 @@ namespace olive {
HandMovableView::HandMovableView(QWidget* parent) :
super(parent),
dragging_hand_(false)
dragging_hand_(false),
is_timeline_axes_(false)
{
connect(Core::instance(), &Core::ToolChanged, this, &HandMovableView::ApplicationToolChanged);
}
@@ -60,7 +61,7 @@ bool HandMovableView::HandPress(QMouseEvent *event)
// Transform mouse event to act like the left button is pressed
QMouseEvent transformed(event->type(),
event->localPos(),
event->pos(),
Qt::LeftButton,
Qt::LeftButton,
event->modifiers());
@@ -82,15 +83,15 @@ bool HandMovableView::HandMove(QMouseEvent *event)
QPoint adjustment(0, 0);
QMouseEvent transformed(event->type(),
event->localPos() - transformed_pos_,
event->pos() - transformed_pos_,
Qt::LeftButton,
Qt::LeftButton,
event->modifiers());
if (event->localPos().x() < 0) {
if (event->pos().x() < 0) {
transformed_pos_.setX(transformed_pos_.x() + width());
adjustment.setX(width());
} else if (event->localPos().x() >= width()) {
} else if (event->pos().x() >= width()) {
transformed_pos_.setX(transformed_pos_.x() - width());
adjustment.setX(-width());
}
@@ -118,9 +119,12 @@ bool HandMovableView::HandRelease(QMouseEvent *event)
// Transform mouse event to act like the left button is pressed
QMouseEvent transformed(event->type(),
event->localPos(),
event->windowPos(),
event->screenPos(),
Qt::LeftButton,
Qt::LeftButton,
event->modifiers());
event->modifiers(),
event->source());
super::mouseReleaseEvent(&transformed);
@@ -146,16 +150,21 @@ const HandMovableView::DragMode &HandMovableView::GetDefaultDragMode() const
return default_drag_mode_;
}
bool HandMovableView::WheelEventIsAZoomEvent(QWheelEvent *event) const
bool HandMovableView::WheelEventIsAZoomEvent(QWheelEvent *event)
{
return (static_cast<bool>(event->modifiers() & Qt::ControlModifier) == !OLIVE_CONFIG("ScrollZooms").toBool());
}
qreal HandMovableView::GetScrollZoomMultiplier(QWheelEvent *event)
{
return 1.0 + (static_cast<qreal>(event->angleDelta().x() + event->angleDelta().y()) * 0.001);
}
void HandMovableView::wheelEvent(QWheelEvent *event)
{
if (WheelEventIsAZoomEvent(event)) {
if (!event->angleDelta().isNull()) {
qreal multiplier = 1.0 + (static_cast<qreal>(event->angleDelta().x() + event->angleDelta().y()) * 0.001);
qreal multiplier = GetScrollZoomMultiplier(event);
QPointF cursor_pos;
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
@@ -166,6 +175,54 @@ void HandMovableView::wheelEvent(QWheelEvent *event)
ZoomIntoCursorPosition(event, multiplier, cursor_pos);
}
} else if (is_timeline_axes_) {
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
QPoint angle_delta = event->angleDelta();
if (OLIVE_CONFIG("InvertTimelineScrollAxes").toBool() // Check if config is set to invert timeline axes
&& event->source() != Qt::MouseEventSynthesizedBySystem) { // Never flip axes on Apple trackpads though
angle_delta = QPoint(angle_delta.y(), angle_delta.x());
}
QWheelEvent e(
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
event->position(),
event->globalPosition(),
#else
event->pos(),
event->globalPos(),
#endif
event->pixelDelta(),
angle_delta,
event->buttons(),
event->modifiers(),
event->phase(),
event->inverted(),
event->source()
);
#else
Qt::Orientation orientation = event->orientation();
if (OLIVE_CONFIG("InvertTimelineScrollAxes").toBool()) {
orientation = (orientation == Qt::Horizontal) ? Qt::Vertical : Qt::Horizontal;
}
QWheelEvent e(
event->pos(),
event->globalPos(),
event->pixelDelta(),
event->angleDelta(),
event->delta(),
orientation,
event->buttons(),
event->modifiers()
);
#endif
super::wheelEvent(&e);
} else {
super::wheelEvent(event);
}
+8 -2
View File
@@ -34,6 +34,10 @@ class HandMovableView : public QGraphicsView
public:
HandMovableView(QWidget* parent = nullptr);
static bool WheelEventIsAZoomEvent(QWheelEvent* event);
static qreal GetScrollZoomMultiplier(QWheelEvent* event);
protected:
virtual void ToolChangedEvent(Tool::Item tool){Q_UNUSED(tool)}
@@ -44,12 +48,12 @@ protected:
void SetDefaultDragMode(DragMode mode);
const DragMode& GetDefaultDragMode() const;
bool WheelEventIsAZoomEvent(QWheelEvent* event) const;
virtual void wheelEvent(QWheelEvent* event) override;
virtual void ZoomIntoCursorPosition(QWheelEvent* event, double multiplier, const QPointF &cursor_pos);
void SetIsTimelineAxes(bool e) { is_timeline_axes_ = e; }
private:
bool dragging_hand_;
DragMode pre_hand_drag_mode_;
@@ -58,6 +62,8 @@ private:
QPointF transformed_pos_;
bool is_timeline_axes_;
private slots:
void ApplicationToolChanged(Tool::Item tool);

Some files were not shown because too many files have changed in this diff Show More