Merge branch 'olive-editor:master' into av1
This commit is contained in:
@@ -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
@@ -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
@@ -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()
|
||||
|
||||
@@ -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
@@ -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
@@ -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 ÷r)
|
||||
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, ©_data, ©_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
|
||||
|
||||
@@ -134,7 +134,6 @@ private:
|
||||
*/
|
||||
static QString FFmpegError(int error_code);
|
||||
|
||||
bool InitScaler(AVFrame *input, const RetrieveVideoParams ¶ms);
|
||||
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_;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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 ¤t_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);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include <QApplication>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QLinkedList>
|
||||
#include <QMutex>
|
||||
#include <QTimer>
|
||||
#include <stdint.h>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 ×tamp,
|
||||
const rational& timebase,
|
||||
const Display& display,
|
||||
bool show_plus_if_positive)
|
||||
QString Timecode::time_to_timecode(const rational &time, const rational &timebase, const Timecode::Display &display, bool show_plus_if_positive)
|
||||
{
|
||||
if (timebase.isNull()) {
|
||||
return QStringLiteral("INVALID TIMEBASE");
|
||||
}
|
||||
|
||||
double timestamp_dbl = (rational(timestamp) * timebase).toDouble();
|
||||
double time_dbl = time.toDouble();
|
||||
|
||||
switch (display) {
|
||||
case kTimecodeNonDropFrame:
|
||||
@@ -52,21 +50,21 @@ QString Timecode::timestamp_to_timecode(const int64_t ×tamp,
|
||||
{
|
||||
QString prefix;
|
||||
|
||||
if (timestamp_dbl < 0) {
|
||||
if (time_dbl < 0) {
|
||||
prefix = "-";
|
||||
} else if (show_plus_if_positive) {
|
||||
prefix = "+";
|
||||
}
|
||||
|
||||
if (display == kTimecodeSeconds) {
|
||||
timestamp_dbl = qAbs(timestamp_dbl);
|
||||
time_dbl = qAbs(time_dbl);
|
||||
|
||||
int64_t total_seconds = qFloor(timestamp_dbl);
|
||||
int64_t total_seconds = qFloor(time_dbl);
|
||||
|
||||
int64_t hours = total_seconds / 3600;
|
||||
int64_t mins = total_seconds / 60 - hours * 60;
|
||||
int64_t secs = total_seconds - mins * 60;
|
||||
int64_t fraction = qRound64((timestamp_dbl - static_cast<double>(total_seconds)) * 1000);
|
||||
int64_t fraction = qRound64((time_dbl - static_cast<double>(total_seconds)) * 1000);
|
||||
|
||||
return QStringLiteral("%1%2:%3:%4.%5").arg(prefix,
|
||||
padded(hours, 2),
|
||||
@@ -79,7 +77,7 @@ QString Timecode::timestamp_to_timecode(const int64_t ×tamp,
|
||||
double frame_rate = timebase.flipped().toDouble();
|
||||
int rounded_frame_rate = qRound(frame_rate);
|
||||
int64_t frames, secs, mins, hours;
|
||||
int64_t f = qAbs(timestamp);
|
||||
int64_t f = qAbs(time_to_timestamp(time, timebase));
|
||||
|
||||
if (display == kTimecodeDropFrame && TimebaseIsDropFrame(timebase)) {
|
||||
frame_token = ";";
|
||||
@@ -127,18 +125,36 @@ QString Timecode::timestamp_to_timecode(const int64_t ×tamp,
|
||||
}
|
||||
}
|
||||
case kFrames:
|
||||
return QString::number(timestamp);
|
||||
return QString::number(time_to_timestamp(time, timebase));
|
||||
case kMilliseconds:
|
||||
return QString::number(qRound(timestamp_dbl * 1000));
|
||||
return QString::number(qRound(time_dbl * 1000));
|
||||
}
|
||||
|
||||
return QStringLiteral("INVALID TIMECODE MODE");
|
||||
}
|
||||
|
||||
int64_t Timecode::timecode_to_timestamp(const QString &timecode, const rational &timebase, const Display &display, bool* ok)
|
||||
int64_t StrToInt64EmptyTolerant(const QString &s, bool *ok)
|
||||
{
|
||||
double timebase_dbl = timebase.toDouble();
|
||||
if (s.isEmpty()) {
|
||||
if (ok) *ok = true;
|
||||
return 0;
|
||||
} else {
|
||||
return s.toLongLong(ok);
|
||||
}
|
||||
}
|
||||
|
||||
double StrToDoubleEmptyTolerant(const QString &s, bool *ok)
|
||||
{
|
||||
if (s.isEmpty()) {
|
||||
if (ok) *ok = true;
|
||||
return 0;
|
||||
} else {
|
||||
return s.toDouble(ok);
|
||||
}
|
||||
}
|
||||
|
||||
rational Timecode::timecode_to_time(const QString &timecode, const rational &timebase, const Timecode::Display &display, bool *ok)
|
||||
{
|
||||
if (timecode.isEmpty()) {
|
||||
goto err_fatal;
|
||||
}
|
||||
@@ -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 ×tamp, const rational &t
|
||||
return rational(num_r, den_r);
|
||||
}
|
||||
|
||||
QString Timecode::time_to_timecode(const rational &time, const rational &timebase, const Timecode::Display &display, bool show_plus_if_positive)
|
||||
{
|
||||
return timestamp_to_timecode(time_to_timestamp(time, timebase), timebase, display, show_plus_if_positive);
|
||||
}
|
||||
|
||||
bool Timecode::TimebaseIsDropFrame(const rational &timebase)
|
||||
{
|
||||
return (timebase.numerator() != 1);
|
||||
|
||||
@@ -56,9 +56,7 @@ public:
|
||||
/**
|
||||
* @brief Convert a timestamp (according to a rational timebase) to a user-friendly string representation
|
||||
*/
|
||||
static QString timestamp_to_timecode(const int64_t ×tamp, const rational& timebase, const Display &display, bool show_plus_if_positive = false);
|
||||
|
||||
static int64_t timecode_to_timestamp(const QString& timecode, const rational& timebase, const Display& display, bool *ok = nullptr);
|
||||
static QString time_to_timecode(const rational& time, const rational& timebase, const Display &display, bool show_plus_if_positive = false);
|
||||
static rational timecode_to_time(const QString& timecode, const rational& timebase, const Display& display, bool *ok = nullptr);
|
||||
|
||||
static rational snap_time_to_timebase(const rational& time, const rational& timebase, Rounding floor = kRound);
|
||||
@@ -71,8 +69,6 @@ public:
|
||||
|
||||
static rational timestamp_to_time(const int64_t& timestamp, const rational& timebase);
|
||||
|
||||
static QString time_to_timecode(const rational& time, const rational& timebase, const Display &display, bool show_plus_if_positive = false);
|
||||
|
||||
static bool TimebaseIsDropFrame(const rational& timebase);
|
||||
|
||||
static QString TimeToString(int64_t ms);
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
|
||||
@@ -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}
|
||||
)
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,9 @@ public:
|
||||
frame_slider_->SetValue(t);
|
||||
}
|
||||
|
||||
signals:
|
||||
void TimeChanged(const rational &t);
|
||||
|
||||
private:
|
||||
QCheckBox* image_sequence_checkbox_;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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,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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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++) {
|
||||
|
||||
@@ -38,6 +38,8 @@ public:
|
||||
kOpPower
|
||||
};
|
||||
|
||||
static QString GetOperationName(Operation o);
|
||||
|
||||
protected:
|
||||
enum Pairing {
|
||||
kPairNone = -1,
|
||||
|
||||
+38
-134
@@ -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
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef LOOPMODE_H
|
||||
#define LOOPMODE_H
|
||||
|
||||
namespace olive {
|
||||
|
||||
enum class LoopMode {
|
||||
kLoopModeOff,
|
||||
kLoopModeLoop,
|
||||
kLoopModeClamp
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // LOOPMODE_H
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -22,6 +22,7 @@
|
||||
#define RENDERTEXTURE_H
|
||||
|
||||
#include <memory>
|
||||
#include <QVariant>
|
||||
|
||||
#include "render/videoparams.h"
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -54,8 +54,6 @@ private:
|
||||
|
||||
static QMap<QString, QString> available_themes_;
|
||||
|
||||
static QPalette platform_palette_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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")));
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user