Merge branch 'olive-editor:master' into av1
This commit is contained in:
@@ -24,6 +24,7 @@ on:
|
||||
env:
|
||||
DOWNLOAD_TOOL: curl -fLOSs --retry 2 --retry-delay 60
|
||||
UPLOAD_TOOL: curl -X POST --retry 2 --retry-delay 60
|
||||
CMAKE_ARGS: -DUSE_WERROR=ON -DBUILD_TESTS=ON
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
@@ -31,12 +32,12 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- build-type: RelWithDebInfo
|
||||
cc-compiler: gcc
|
||||
cxx-compiler: g++
|
||||
compiler-name: GCC 9.3.1
|
||||
cmake-gen: Ninja
|
||||
os-name: Linux (CentOS 7)
|
||||
#- build-type: RelWithDebInfo
|
||||
# cc-compiler: gcc
|
||||
# cxx-compiler: g++
|
||||
# compiler-name: GCC 9.3.1
|
||||
# cmake-gen: Ninja
|
||||
# os-name: Linux (CentOS 7)
|
||||
- build-type: RelWithDebInfo
|
||||
cc-compiler: clang
|
||||
cxx-compiler: clang++
|
||||
@@ -73,7 +74,8 @@ jobs:
|
||||
cmake .. -G "${{ matrix.cmake-gen }}" \
|
||||
-DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" \
|
||||
-DCMAKE_C_COMPILER="${{ matrix.cc-compiler }}" \
|
||||
-DCMAKE_CXX_COMPILER="${{ matrix.cxx-compiler }}"
|
||||
-DCMAKE_CXX_COMPILER="${{ matrix.cxx-compiler }}" \
|
||||
$CMAKE_ARGS
|
||||
|
||||
- name: Build
|
||||
working-directory: build
|
||||
@@ -204,7 +206,8 @@ jobs:
|
||||
working-directory: ${{ runner.workspace }}/build
|
||||
run: |
|
||||
cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} -G "${{ matrix.cmake-gen }}" \
|
||||
-DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake"
|
||||
-DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" \
|
||||
$CMAKE_ARGS
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{ runner.workspace }}/build
|
||||
@@ -356,7 +359,8 @@ jobs:
|
||||
PATH=$DEP_LOCATION:$DEP_LOCATION/bin:$DEP_LOCATION/include:$DEP_LOCATION/lib:$DEP_LOCATION/crashpad:$PATH \
|
||||
cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \
|
||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=${{ matrix.min-deploy }} -G "${{ matrix.cmake-gen }}" \
|
||||
-DCMAKE_OSX_ARCHITECTURES="${{ matrix.os-arch }}"
|
||||
-DCMAKE_OSX_ARCHITECTURES="${{ matrix.os-arch }}" \
|
||||
$CMAKE_ARGS
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{ runner.workspace }}/build
|
||||
|
||||
+2
-4
@@ -19,8 +19,8 @@ cmake_minimum_required(VERSION 3.13 FATAL_ERROR)
|
||||
project(olive-editor VERSION 0.2.0 LANGUAGES CXX)
|
||||
|
||||
option(BUILD_DOXYGEN "Build Doxygen documentation" OFF)
|
||||
option(BUILD_TESTS "Build unit tests" ON)
|
||||
option(USE_WERROR "Error on compile warning" ON)
|
||||
option(BUILD_TESTS "Build unit tests" OFF)
|
||||
option(USE_WERROR "Error on compile warning" OFF)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
@@ -100,7 +100,6 @@ set(QT_LIBRARIES
|
||||
Gui
|
||||
Widgets
|
||||
OpenGL
|
||||
Svg
|
||||
LinguistTools
|
||||
Concurrent
|
||||
)
|
||||
@@ -121,7 +120,6 @@ list(APPEND OLIVE_LIBRARIES
|
||||
Qt5::Gui
|
||||
Qt5::Widgets
|
||||
Qt5::OpenGL
|
||||
Qt5::Svg
|
||||
Qt5::Concurrent
|
||||
)
|
||||
|
||||
|
||||
+1
-2
@@ -35,7 +35,6 @@ add_subdirectory(panel)
|
||||
add_subdirectory(render)
|
||||
add_subdirectory(shaders)
|
||||
add_subdirectory(task)
|
||||
add_subdirectory(threading)
|
||||
add_subdirectory(timeline)
|
||||
add_subdirectory(ts)
|
||||
add_subdirectory(tool)
|
||||
@@ -99,7 +98,7 @@ if (WIN32)
|
||||
# Set Windows application icon
|
||||
target_sources(olive-editor PRIVATE packaging/windows/resources.rc)
|
||||
|
||||
# Preserve folder structure in visual studio
|
||||
# Preserve folder structure in visual studio
|
||||
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${OLIVE_SOURCES})
|
||||
|
||||
elseif(APPLE)
|
||||
|
||||
@@ -280,19 +280,19 @@ AudioVisualWaveform::Sample AudioVisualWaveform::GetSummaryFromTime(const ration
|
||||
return AudioVisualWaveform::Sample(channel_count(), {0, 0});
|
||||
}
|
||||
|
||||
void ExpandMinMaxChannel(const float *a, int start, int length, float &min_val, float &max_val)
|
||||
void ExpandMinMaxChannel(const float *a, size_t length, float &min_val, float &max_val)
|
||||
{
|
||||
#if defined(Q_PROCESSOR_X86) || defined(Q_PROCESSOR_ARM)
|
||||
// SSE optimized
|
||||
|
||||
// load the first 4 elements of 'a' into min and max (they are 4 * 32 = 128 bits)
|
||||
__m128 max = _mm_loadu_ps(a + start);
|
||||
__m128 min = _mm_loadu_ps(a + start);
|
||||
__m128 max = _mm_loadu_ps(a);
|
||||
__m128 min = _mm_loadu_ps(a);
|
||||
|
||||
// loop over 'a' and compare current elements with min and max 4 by 4.
|
||||
// we need to make sure we don't read out of boundaries should 'a' length be not mod. 4
|
||||
for(int i = 4; i < length-4; i+=4) {
|
||||
__m128 cur = _mm_loadu_ps(a + start + i);
|
||||
for(size_t i = 4; i < length-4; i+=4) {
|
||||
__m128 cur = _mm_loadu_ps(a + i);
|
||||
max = _mm_max_ps(max, cur);
|
||||
min = _mm_min_ps(min, cur);
|
||||
}
|
||||
@@ -316,8 +316,7 @@ void ExpandMinMaxChannel(const float *a, int start, int length, float &min_val,
|
||||
// I bet you don't find annotated low level code very often.
|
||||
#else
|
||||
// Standard unoptimized function
|
||||
int end = start + length;
|
||||
for (int i=start; i<end; i++) {
|
||||
for (size_t i=0; i<length; i++) {
|
||||
min_val = std::min(min_val, a[i]);
|
||||
max_val = std::max(max_val, a[i]);
|
||||
}
|
||||
@@ -330,7 +329,7 @@ AudioVisualWaveform::Sample AudioVisualWaveform::SumSamples(const SampleBuffer &
|
||||
AudioVisualWaveform::Sample summed_samples(channels);
|
||||
|
||||
for (int channel=0; channel<samples.audio_params().channel_count(); channel++) {
|
||||
ExpandMinMaxChannel(samples.data(channel), start_index, length, summed_samples[channel].min, summed_samples[channel].max);
|
||||
ExpandMinMaxChannel(samples.data(channel) + start_index, length, summed_samples[channel].min, summed_samples[channel].max);
|
||||
}
|
||||
|
||||
// for reference: this approximation is n x faster (and less accurate) for a n-tracks clip
|
||||
|
||||
+28
-14
@@ -37,11 +37,18 @@ namespace olive {
|
||||
|
||||
const rational Decoder::kAnyTimecode = RATIONAL_MIN;
|
||||
|
||||
Decoder::Decoder()
|
||||
Decoder::Decoder() :
|
||||
cached_texture_(nullptr)
|
||||
{
|
||||
UpdateLastAccessed();
|
||||
}
|
||||
|
||||
void Decoder::IncrementAccessTime(qint64 t)
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
last_accessed_ += t;
|
||||
}
|
||||
|
||||
bool Decoder::Open(const CodecStream &stream)
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
@@ -86,7 +93,7 @@ bool Decoder::Open(const CodecStream &stream)
|
||||
}
|
||||
}
|
||||
|
||||
TexturePtr Decoder::RetrieveVideo(Renderer *renderer, const rational &timecode, const RetrieveVideoParams ÷r, const QAtomicInt *cancelled)
|
||||
TexturePtr Decoder::RetrieveVideo(const RetrieveVideoParams &p)
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
|
||||
@@ -102,14 +109,21 @@ TexturePtr Decoder::RetrieveVideo(Renderer *renderer, const rational &timecode,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (cancelled && *cancelled) {
|
||||
if (p.cancelled && p.cancelled->IsCancelled()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return RetrieveVideoInternal(renderer, timecode, divider, cancelled);
|
||||
if (cached_texture_ && cached_time_ == p.time) {
|
||||
return cached_texture_;
|
||||
}
|
||||
|
||||
cached_texture_ = RetrieveVideoInternal(p);
|
||||
cached_time_ = p.time;
|
||||
|
||||
return cached_texture_;
|
||||
}
|
||||
|
||||
Decoder::RetrieveAudioStatus Decoder::RetrieveAudio(SampleBuffer &dest, const TimeRange &range, const AudioParams ¶ms, const QString& cache_path, Footage::LoopMode loop_mode, RenderMode::Mode mode)
|
||||
Decoder::RetrieveAudioStatus Decoder::RetrieveAudio(SampleBuffer &dest, const TimeRange &range, const AudioParams ¶ms, const QString& cache_path, LoopMode loop_mode, RenderMode::Mode mode)
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
|
||||
@@ -152,6 +166,8 @@ void Decoder::Close()
|
||||
|
||||
UpdateLastAccessed();
|
||||
|
||||
cached_texture_ = nullptr;
|
||||
|
||||
if (stream_.IsValid()) {
|
||||
CloseInternal();
|
||||
stream_.Reset();
|
||||
@@ -160,7 +176,7 @@ void Decoder::Close()
|
||||
}
|
||||
}
|
||||
|
||||
bool Decoder::ConformAudio(const QVector<QString> &output_filenames, const AudioParams ¶ms, const QAtomicInt *cancelled)
|
||||
bool Decoder::ConformAudio(const QVector<QString> &output_filenames, const AudioParams ¶ms, CancelAtom *cancelled)
|
||||
{
|
||||
return ConformAudioInternal(output_filenames, params, cancelled);
|
||||
}
|
||||
@@ -264,15 +280,13 @@ int64_t Decoder::GetImageSequenceIndex(const QString &filename)
|
||||
return number_only.toLongLong();
|
||||
}
|
||||
|
||||
TexturePtr Decoder::RetrieveVideoInternal(Renderer *renderer, const rational &timecode, const RetrieveVideoParams ÷r, const QAtomicInt *cancelled)
|
||||
TexturePtr Decoder::RetrieveVideoInternal(const RetrieveVideoParams &p)
|
||||
{
|
||||
Q_UNUSED(timecode)
|
||||
Q_UNUSED(divider)
|
||||
Q_UNUSED(cancelled)
|
||||
Q_UNUSED(p)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Decoder::ConformAudioInternal(const QVector<QString> &filenames, const AudioParams ¶ms, const QAtomicInt* cancelled)
|
||||
bool Decoder::ConformAudioInternal(const QVector<QString> &filenames, const AudioParams ¶ms, CancelAtom *cancelled)
|
||||
{
|
||||
Q_UNUSED(filenames)
|
||||
Q_UNUSED(cancelled)
|
||||
@@ -280,7 +294,7 @@ bool Decoder::ConformAudioInternal(const QVector<QString> &filenames, const Audi
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Decoder::RetrieveAudioFromConform(SampleBuffer &sample_buffer, const QVector<QString> &conform_filenames, const TimeRange& range, Footage::LoopMode loop_mode, const AudioParams &input_params)
|
||||
bool Decoder::RetrieveAudioFromConform(SampleBuffer &sample_buffer, const QVector<QString> &conform_filenames, const TimeRange& range, LoopMode loop_mode, const AudioParams &input_params)
|
||||
{
|
||||
PlanarFileDevice input;
|
||||
if (input.open(conform_filenames, QFile::ReadOnly)) {
|
||||
@@ -290,7 +304,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 == Footage::kLoopModeLoop) {
|
||||
if (loop_mode == kLoopModeLoop) {
|
||||
while (read_index >= input.size()) {
|
||||
read_index -= input.size();
|
||||
}
|
||||
@@ -335,7 +349,7 @@ void Decoder::UpdateLastAccessed()
|
||||
|
||||
uint qHash(Decoder::CodecStream stream, uint seed)
|
||||
{
|
||||
return qHash(stream.filename(), seed) ^ qHash(stream.stream(), seed);
|
||||
return qHash(stream.filename(), seed) ^ qHash(stream.stream(), seed) ^ qHash(stream.block(), seed);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+38
-34
@@ -34,7 +34,7 @@ extern "C" {
|
||||
#include "codec/frame.h"
|
||||
#include "codec/samplebuffer.h"
|
||||
#include "common/rational.h"
|
||||
#include "node/project/footage/footage.h"
|
||||
#include "node/block/block.h"
|
||||
#include "node/project/footage/footagedescription.h"
|
||||
#include "task/task.h"
|
||||
|
||||
@@ -71,6 +71,12 @@ public:
|
||||
kIndexUnavailable
|
||||
};
|
||||
|
||||
enum LoopMode {
|
||||
kLoopModeOff,
|
||||
kLoopModeLoop,
|
||||
kLoopModeClamp
|
||||
};
|
||||
|
||||
Decoder();
|
||||
|
||||
/**
|
||||
@@ -81,17 +87,21 @@ public:
|
||||
virtual bool SupportsVideo(){return false;}
|
||||
virtual bool SupportsAudio(){return false;}
|
||||
|
||||
void IncrementAccessTime(qint64 t);
|
||||
|
||||
class CodecStream
|
||||
{
|
||||
public:
|
||||
CodecStream() :
|
||||
stream_(-1)
|
||||
stream_(-1),
|
||||
block_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
CodecStream(const QString& filename, int stream) :
|
||||
CodecStream(const QString& filename, int stream, Block *block) :
|
||||
filename_(filename),
|
||||
stream_(stream)
|
||||
stream_(stream),
|
||||
block_(block)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -125,11 +135,18 @@ public:
|
||||
return stream_;
|
||||
}
|
||||
|
||||
Block *block() const
|
||||
{
|
||||
return block_;
|
||||
}
|
||||
|
||||
private:
|
||||
QString filename_;
|
||||
|
||||
int stream_;
|
||||
|
||||
Block *block_;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -147,29 +164,13 @@ public:
|
||||
|
||||
struct RetrieveVideoParams
|
||||
{
|
||||
RetrieveVideoParams()
|
||||
{
|
||||
divider = 1;
|
||||
maximum_format = VideoParams::kFormatInvalid;
|
||||
}
|
||||
|
||||
int divider;
|
||||
VideoParams::Format maximum_format;
|
||||
|
||||
void reset()
|
||||
{
|
||||
*this = RetrieveVideoParams();
|
||||
}
|
||||
|
||||
bool operator==(const RetrieveVideoParams& rhs) const
|
||||
{
|
||||
return divider == rhs.divider && maximum_format == rhs.maximum_format;
|
||||
}
|
||||
|
||||
bool operator!=(const RetrieveVideoParams& rhs) const
|
||||
{
|
||||
return !(*this == rhs);
|
||||
}
|
||||
Renderer *renderer = nullptr;
|
||||
rational time;
|
||||
int divider = 1;
|
||||
VideoParams::Format maximum_format = VideoParams::kFormatInvalid;
|
||||
CancelAtom *cancelled = nullptr;
|
||||
VideoParams::ColorRange force_range = VideoParams::kColorRangeDefault;
|
||||
VideoParams::Interlacing src_interlacing = VideoParams::kInterlaceNone;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -182,7 +183,7 @@ public:
|
||||
*
|
||||
* This function is thread safe and can only run while the decoder is open. \see Open()
|
||||
*/
|
||||
TexturePtr RetrieveVideo(Renderer *renderer, const rational& timecode, const RetrieveVideoParams& params, const QAtomicInt *cancelled = nullptr);
|
||||
TexturePtr RetrieveVideo(const RetrieveVideoParams& p);
|
||||
|
||||
enum RetrieveAudioStatus {
|
||||
kInvalid = -1,
|
||||
@@ -199,7 +200,7 @@ public:
|
||||
*
|
||||
* This function is thread safe and can only run while the decoder is open. \see Open()
|
||||
*/
|
||||
RetrieveAudioStatus RetrieveAudio(SampleBuffer &dest, const TimeRange& range, const AudioParams& params, const QString &cache_path, Footage::LoopMode loop_mode, RenderMode::Mode mode);
|
||||
RetrieveAudioStatus RetrieveAudio(SampleBuffer &dest, const TimeRange& range, const AudioParams& params, const QString &cache_path, LoopMode loop_mode, RenderMode::Mode mode);
|
||||
|
||||
/**
|
||||
* @brief Determine the last time this decoder instance was used in any way
|
||||
@@ -217,7 +218,7 @@ public:
|
||||
*
|
||||
* This function is re-entrant.
|
||||
*/
|
||||
virtual FootageDescription Probe(const QString& filename, const QAtomicInt* cancelled) const = 0;
|
||||
virtual FootageDescription Probe(const QString& filename, CancelAtom *cancelled) const = 0;
|
||||
|
||||
/**
|
||||
* @brief Closes media/deallocates memory
|
||||
@@ -229,7 +230,7 @@ public:
|
||||
/**
|
||||
* @brief Conform audio stream
|
||||
*/
|
||||
bool ConformAudio(const QVector<QString> &output_filenames, const AudioParams ¶ms, const QAtomicInt *cancelled = nullptr);
|
||||
bool ConformAudio(const QVector<QString> &output_filenames, const AudioParams ¶ms, CancelAtom *cancelled = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Create a Decoder instance using a Decoder ID
|
||||
@@ -277,9 +278,9 @@ protected:
|
||||
* Sub-classes must override this function IF they support video. Function is already mutexed
|
||||
* so sub-classes don't need to worry about thread safety.
|
||||
*/
|
||||
virtual TexturePtr RetrieveVideoInternal(Renderer *renderer, const rational& timecode, const RetrieveVideoParams& params, const QAtomicInt *cancelled);
|
||||
virtual TexturePtr RetrieveVideoInternal(const RetrieveVideoParams& p);
|
||||
|
||||
virtual bool ConformAudioInternal(const QVector<QString>& filenames, const AudioParams ¶ms, const QAtomicInt* cancelled);
|
||||
virtual bool ConformAudioInternal(const QVector<QString>& filenames, const AudioParams ¶ms, CancelAtom *cancelled);
|
||||
|
||||
void SignalProcessingProgress(int64_t ts, int64_t duration);
|
||||
|
||||
@@ -306,7 +307,7 @@ signals:
|
||||
private:
|
||||
void UpdateLastAccessed();
|
||||
|
||||
bool RetrieveAudioFromConform(SampleBuffer &sample_buffer, const QVector<QString> &conform_filenames, const TimeRange &range, Footage::LoopMode loop_mode, const AudioParams ¶ms);
|
||||
bool RetrieveAudioFromConform(SampleBuffer &sample_buffer, const QVector<QString> &conform_filenames, const TimeRange &range, LoopMode loop_mode, const AudioParams ¶ms);
|
||||
|
||||
CodecStream stream_;
|
||||
|
||||
@@ -314,6 +315,9 @@ private:
|
||||
|
||||
qint64 last_accessed_;
|
||||
|
||||
TexturePtr cached_texture_;
|
||||
rational cached_time_;
|
||||
|
||||
};
|
||||
|
||||
uint qHash(Decoder::CodecStream stream, uint seed = 0);
|
||||
|
||||
+246
-108
@@ -23,6 +23,7 @@
|
||||
#include <QFile>
|
||||
|
||||
#include "common/timecodefunctions.h"
|
||||
#include "common/xmlutils.h"
|
||||
#include "ffmpeg/ffmpegencoder.h"
|
||||
#include "oiio/oiioencoder.h"
|
||||
|
||||
@@ -92,13 +93,22 @@ EncodingParams::EncodingParams() :
|
||||
video_is_image_sequence_(false),
|
||||
audio_enabled_(false),
|
||||
audio_bit_rate_(0),
|
||||
subtitles_enabled_(false)
|
||||
subtitles_enabled_(false),
|
||||
subtitles_are_sidecar_(false),
|
||||
video_scaling_method_(kStretch),
|
||||
has_custom_range_(false)
|
||||
{
|
||||
}
|
||||
|
||||
void EncodingParams::SetFilename(const QString &filename)
|
||||
QDir EncodingParams::GetPresetPath()
|
||||
{
|
||||
filename_ = filename;
|
||||
return QDir(FileFunctions::GetConfigurationLocation()).filePath(QStringLiteral("exportpresets"));
|
||||
}
|
||||
|
||||
QStringList EncodingParams::GetListOfPresets()
|
||||
{
|
||||
QDir d = EncodingParams::GetPresetPath();
|
||||
return d.entryList(QDir::Files);
|
||||
}
|
||||
|
||||
void EncodingParams::EnableVideo(const VideoParams &video_params, const ExportCodec::Codec &vcodec)
|
||||
@@ -121,134 +131,79 @@ void EncodingParams::EnableSubtitles(const ExportCodec::Codec &scodec)
|
||||
subtitles_codec_ = scodec;
|
||||
}
|
||||
|
||||
void EncodingParams::set_video_option(const QString &key, const QString &value)
|
||||
void EncodingParams::EnableSidecarSubtitles(const ExportFormat::Format &sfmt, const ExportCodec::Codec &scodec)
|
||||
{
|
||||
video_opts_.insert(key, value);
|
||||
subtitles_enabled_ = true;
|
||||
subtitles_are_sidecar_ = true;
|
||||
subtitle_sidecar_fmt_ = sfmt;
|
||||
subtitles_codec_ = scodec;
|
||||
}
|
||||
|
||||
void EncodingParams::set_video_bit_rate(const int64_t &rate)
|
||||
void EncodingParams::DisableVideo()
|
||||
{
|
||||
video_bit_rate_ = rate;
|
||||
video_enabled_ = false;
|
||||
}
|
||||
|
||||
void EncodingParams::set_video_min_bit_rate(const int64_t &rate)
|
||||
void EncodingParams::DisableAudio()
|
||||
{
|
||||
video_min_bit_rate_ = rate;
|
||||
audio_enabled_ = false;
|
||||
}
|
||||
|
||||
void EncodingParams::set_video_max_bit_rate(const int64_t &rate)
|
||||
void EncodingParams::DisableSubtitles()
|
||||
{
|
||||
video_max_bit_rate_ = rate;
|
||||
subtitles_enabled_ = false;
|
||||
}
|
||||
|
||||
void EncodingParams::set_video_buffer_size(const int64_t &sz)
|
||||
bool EncodingParams::Load(QXmlStreamReader *reader)
|
||||
{
|
||||
video_buffer_size_ = sz;
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("export")) {
|
||||
int version = 0;
|
||||
|
||||
XMLAttributeLoop(reader, attr) {
|
||||
if (attr.name() == QStringLiteral("version")) {
|
||||
version = attr.value().toInt();
|
||||
}
|
||||
}
|
||||
|
||||
switch (version) {
|
||||
case 1:
|
||||
return LoadV1(reader);
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void EncodingParams::set_video_threads(const int &threads)
|
||||
bool EncodingParams::Load(QIODevice *device)
|
||||
{
|
||||
video_threads_ = threads;
|
||||
QXmlStreamReader reader(device);
|
||||
return Load(&reader);
|
||||
}
|
||||
|
||||
void EncodingParams::set_video_pix_fmt(const QString &s)
|
||||
void EncodingParams::Save(QIODevice *device) const
|
||||
{
|
||||
video_pix_fmt_ = s;
|
||||
}
|
||||
|
||||
const QString &EncodingParams::filename() const
|
||||
{
|
||||
return filename_;
|
||||
}
|
||||
|
||||
bool EncodingParams::video_enabled() const
|
||||
{
|
||||
return video_enabled_;
|
||||
}
|
||||
|
||||
const ExportCodec::Codec &EncodingParams::video_codec() const
|
||||
{
|
||||
return video_codec_;
|
||||
}
|
||||
|
||||
const VideoParams &EncodingParams::video_params() const
|
||||
{
|
||||
return video_params_;
|
||||
}
|
||||
|
||||
const QHash<QString, QString> &EncodingParams::video_opts() const
|
||||
{
|
||||
return video_opts_;
|
||||
}
|
||||
|
||||
const int64_t &EncodingParams::video_bit_rate() const
|
||||
{
|
||||
return video_bit_rate_;
|
||||
}
|
||||
|
||||
const int64_t &EncodingParams::video_min_bit_rate() const
|
||||
{
|
||||
return video_min_bit_rate_;
|
||||
}
|
||||
|
||||
const int64_t &EncodingParams::video_max_bit_rate() const
|
||||
{
|
||||
return video_max_bit_rate_;
|
||||
}
|
||||
|
||||
const int64_t &EncodingParams::video_buffer_size() const
|
||||
{
|
||||
return video_buffer_size_;
|
||||
}
|
||||
|
||||
const int &EncodingParams::video_threads() const
|
||||
{
|
||||
return video_threads_;
|
||||
}
|
||||
|
||||
const QString &EncodingParams::video_pix_fmt() const
|
||||
{
|
||||
return video_pix_fmt_;
|
||||
}
|
||||
|
||||
bool EncodingParams::audio_enabled() const
|
||||
{
|
||||
return audio_enabled_;
|
||||
}
|
||||
|
||||
const ExportCodec::Codec &EncodingParams::audio_codec() const
|
||||
{
|
||||
return audio_codec_;
|
||||
}
|
||||
|
||||
const AudioParams &EncodingParams::audio_params() const
|
||||
{
|
||||
return audio_params_;
|
||||
}
|
||||
|
||||
bool EncodingParams::subtitles_enabled() const
|
||||
{
|
||||
return subtitles_enabled_;
|
||||
}
|
||||
|
||||
ExportCodec::Codec EncodingParams::subtitles_codec() const
|
||||
{
|
||||
return subtitles_codec_;
|
||||
}
|
||||
|
||||
const rational &EncodingParams::GetExportLength() const
|
||||
{
|
||||
return export_length_;
|
||||
}
|
||||
|
||||
void EncodingParams::SetExportLength(const rational &export_length)
|
||||
{
|
||||
export_length_ = export_length;
|
||||
QXmlStreamWriter writer(device);
|
||||
Save(&writer);
|
||||
}
|
||||
|
||||
void EncodingParams::Save(QXmlStreamWriter *writer) const
|
||||
{
|
||||
writer->writeStartDocument();
|
||||
|
||||
writer->writeStartElement(QStringLiteral("export"));
|
||||
|
||||
writer->writeAttribute(QStringLiteral("version"), QString::number(kEncoderParamsVersion));
|
||||
|
||||
writer->writeTextElement(QStringLiteral("filename"), filename_);
|
||||
writer->writeTextElement(QStringLiteral("format"), QString::number(format_));
|
||||
|
||||
writer->writeTextElement(QStringLiteral("range"), QString::number(has_custom_range_));
|
||||
writer->writeTextElement(QStringLiteral("customrangein"), custom_range_.in().toString());
|
||||
writer->writeTextElement(QStringLiteral("customrangeout"), custom_range_.out().toString());
|
||||
|
||||
writer->writeStartElement(QStringLiteral("video"));
|
||||
|
||||
@@ -262,10 +217,18 @@ void EncodingParams::Save(QXmlStreamWriter *writer) const
|
||||
writer->writeTextElement(QStringLiteral("timebase"), video_params_.time_base().toString());
|
||||
writer->writeTextElement(QStringLiteral("divider"), QString::number(video_params_.divider()));
|
||||
writer->writeTextElement(QStringLiteral("bitrate"), QString::number(video_bit_rate_));
|
||||
writer->writeTextElement(QStringLiteral("minbitrate"), QString::number(video_max_bit_rate_));
|
||||
writer->writeTextElement(QStringLiteral("minbitrate"), QString::number(video_min_bit_rate_));
|
||||
writer->writeTextElement(QStringLiteral("maxbitrate"), QString::number(video_max_bit_rate_));
|
||||
writer->writeTextElement(QStringLiteral("bufsize"), QString::number(video_buffer_size_));
|
||||
writer->writeTextElement(QStringLiteral("threads"), QString::number(video_threads_));
|
||||
writer->writeTextElement(QStringLiteral("pixfmt"), video_pix_fmt_);
|
||||
writer->writeTextElement(QStringLiteral("imgseq"), QString::number(video_is_image_sequence_));
|
||||
|
||||
writer->writeStartElement(QStringLiteral("color"));
|
||||
writer->writeTextElement(QStringLiteral("output"), color_transform_.output());
|
||||
writer->writeEndElement(); // colortransform
|
||||
|
||||
writer->writeTextElement(QStringLiteral("vscale"), QString::number(video_scaling_method_));
|
||||
|
||||
if (!video_opts_.isEmpty()) {
|
||||
writer->writeStartElement(QStringLiteral("opts"));
|
||||
@@ -297,7 +260,24 @@ void EncodingParams::Save(QXmlStreamWriter *writer) const
|
||||
writer->writeTextElement(QStringLiteral("format"), QString::number(audio_params_.format()));
|
||||
}
|
||||
|
||||
writer->writeStartElement(QStringLiteral("subtitles"));
|
||||
|
||||
writer->writeAttribute(QStringLiteral("enabled"), QString::number(subtitles_enabled_));
|
||||
|
||||
if (subtitles_enabled_) {
|
||||
writer->writeTextElement(QStringLiteral("sidecar"), QString::number(subtitles_are_sidecar_));
|
||||
writer->writeTextElement(QStringLiteral("sidecarformat"), QString::number(subtitle_sidecar_fmt_));
|
||||
|
||||
writer->writeTextElement(QStringLiteral("codec"), QString::number(subtitles_codec_));
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // subtitles
|
||||
|
||||
writer->writeEndElement(); // audio
|
||||
|
||||
writer->writeEndElement(); // export
|
||||
|
||||
writer->writeEndDocument();
|
||||
}
|
||||
|
||||
Encoder* Encoder::CreateFromID(Type id, const EncodingParams& params)
|
||||
@@ -346,6 +326,11 @@ Encoder *Encoder::CreateFromFormat(ExportFormat::Format f, const EncodingParams
|
||||
return CreateFromID(GetTypeFromFormat(f), params);
|
||||
}
|
||||
|
||||
Encoder *Encoder::CreateFromParams(const EncodingParams ¶ms)
|
||||
{
|
||||
return CreateFromFormat(params.format(), params);
|
||||
}
|
||||
|
||||
QStringList Encoder::GetPixelFormatsForCodec(ExportCodec::Codec c) const
|
||||
{
|
||||
return QStringList();
|
||||
@@ -356,4 +341,157 @@ std::vector<AudioParams::Format> Encoder::GetSampleFormatsForCodec(ExportCodec::
|
||||
return std::vector<AudioParams::Format>();
|
||||
}
|
||||
|
||||
QMatrix4x4 EncodingParams::GenerateMatrix(EncodingParams::VideoScalingMethod method,
|
||||
int source_width, int source_height,
|
||||
int dest_width, int dest_height)
|
||||
{
|
||||
QMatrix4x4 preview_matrix;
|
||||
|
||||
if (method == EncodingParams::kStretch) {
|
||||
return preview_matrix;
|
||||
}
|
||||
|
||||
float export_ar = static_cast<float>(dest_width) / static_cast<float>(dest_height);
|
||||
float source_ar = static_cast<float>(source_width) / static_cast<float>(source_height);
|
||||
|
||||
if (qFuzzyCompare(export_ar, source_ar)) {
|
||||
return preview_matrix;
|
||||
}
|
||||
|
||||
if ((export_ar > source_ar) == (method == EncodingParams::kFit)) {
|
||||
preview_matrix.scale(source_ar / export_ar, 1.0F);
|
||||
} else {
|
||||
preview_matrix.scale(1.0F, export_ar / source_ar);
|
||||
}
|
||||
|
||||
return preview_matrix;
|
||||
}
|
||||
|
||||
bool EncodingParams::LoadV1(QXmlStreamReader *reader)
|
||||
{
|
||||
rational custom_range_in, custom_range_out;
|
||||
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("filename")) {
|
||||
filename_ = reader->readElementText();
|
||||
} else if (reader->name() == QStringLiteral("format")) {
|
||||
format_ = static_cast<ExportFormat::Format>(reader->readElementText().toInt());
|
||||
} else if (reader->name() == QStringLiteral("range")) {
|
||||
has_custom_range_ = reader->readElementText().toInt();
|
||||
} else if (reader->name() == QStringLiteral("customrangein")) {
|
||||
custom_range_in = rational::fromString(reader->readElementText());
|
||||
} else if (reader->name() == QStringLiteral("customrangeout")) {
|
||||
custom_range_out = rational::fromString(reader->readElementText());
|
||||
} else if (reader->name() == QStringLiteral("video")) {
|
||||
XMLAttributeLoop(reader, attr) {
|
||||
if (attr.name() == QStringLiteral("enabled")) {
|
||||
video_enabled_ = attr.value().toInt();
|
||||
}
|
||||
}
|
||||
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("codec")) {
|
||||
video_codec_ = static_cast<ExportCodec::Codec>(reader->readElementText().toInt());
|
||||
} else if (reader->name() == QStringLiteral("width")) {
|
||||
video_params_.set_width(reader->readElementText().toInt());
|
||||
} else if (reader->name() == QStringLiteral("height")) {
|
||||
video_params_.set_height(reader->readElementText().toInt());
|
||||
} else if (reader->name() == QStringLiteral("format")) {
|
||||
video_params_.set_format(static_cast<VideoParams::Format>(reader->readElementText().toInt()));
|
||||
} else if (reader->name() == QStringLiteral("timebase")) {
|
||||
video_params_.set_time_base(rational::fromString(reader->readElementText()));
|
||||
} else if (reader->name() == QStringLiteral("divider")) {
|
||||
video_params_.set_divider(reader->readElementText().toInt());
|
||||
} else if (reader->name() == QStringLiteral("bitrate")) {
|
||||
video_bit_rate_ = reader->readElementText().toLongLong();
|
||||
} else if (reader->name() == QStringLiteral("minbitrate")) {
|
||||
video_min_bit_rate_ = reader->readElementText().toLongLong();
|
||||
} else if (reader->name() == QStringLiteral("maxbitrate")) {
|
||||
video_max_bit_rate_ = reader->readElementText().toLongLong();
|
||||
} else if (reader->name() == QStringLiteral("bufsize")) {
|
||||
video_buffer_size_ = reader->readElementText().toLongLong();
|
||||
} else if (reader->name() == QStringLiteral("threads")) {
|
||||
video_threads_ = reader->readElementText().toInt();
|
||||
} else if (reader->name() == QStringLiteral("pixfmt")) {
|
||||
video_pix_fmt_ = reader->readElementText();
|
||||
} else if (reader->name() == QStringLiteral("imgseq")) {
|
||||
video_is_image_sequence_ = reader->readElementText().toInt();
|
||||
} else if (reader->name() == QStringLiteral("color")) {
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("output")) {
|
||||
color_transform_ = reader->readElementText();
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else if (reader->name() == QStringLiteral("vscale")) {
|
||||
video_scaling_method_ = static_cast<VideoScalingMethod>(reader->readElementText().toInt());
|
||||
} else if (reader->name() == QStringLiteral("opts")) {
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("entry")) {
|
||||
QString key, value;
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("key")) {
|
||||
key = reader->readElementText();
|
||||
} else if (reader->name() == QStringLiteral("value")) {
|
||||
value = reader->readElementText();
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
set_video_option(key, value);
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else if (reader->name() == QStringLiteral("audio")) {
|
||||
XMLAttributeLoop(reader, attr) {
|
||||
if (attr.name() == QStringLiteral("enabled")) {
|
||||
audio_enabled_ = attr.value().toInt();
|
||||
}
|
||||
}
|
||||
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("codec")) {
|
||||
audio_codec_ = static_cast<ExportCodec::Codec>(reader->readElementText().toInt());
|
||||
} else if (reader->name() == QStringLiteral("samplerate")) {
|
||||
audio_params_.set_sample_rate(reader->readElementText().toInt());
|
||||
} else if (reader->name() == QStringLiteral("channellayout")) {
|
||||
audio_params_.set_channel_layout(reader->readElementText().toULongLong());
|
||||
} else if (reader->name() == QStringLiteral("format")) {
|
||||
audio_params_.set_format(static_cast<AudioParams::Format>(reader->readElementText().toInt()));
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else if (reader->name() == QStringLiteral("subtitles")) {
|
||||
XMLAttributeLoop(reader, attr) {
|
||||
if (attr.name() == QStringLiteral("enabled")) {
|
||||
subtitles_enabled_ = attr.value().toInt();
|
||||
}
|
||||
}
|
||||
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("sidecar")) {
|
||||
subtitles_are_sidecar_ = reader->readElementText().toInt();
|
||||
} else if (reader->name() == QStringLiteral("sidecarformat")) {
|
||||
subtitle_sidecar_fmt_ = static_cast<ExportFormat::Format>(reader->readElementText().toInt());
|
||||
} else if (reader->name() == QStringLiteral("codec")) {
|
||||
subtitles_codec_ = static_cast<ExportCodec::Codec>(reader->readElementText().toInt());
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+97
-48
@@ -41,69 +41,109 @@ namespace olive {
|
||||
class Encoder;
|
||||
using EncoderPtr = std::shared_ptr<Encoder>;
|
||||
|
||||
class EncodingParams {
|
||||
class EncodingParams
|
||||
{
|
||||
public:
|
||||
enum VideoScalingMethod {
|
||||
kFit,
|
||||
kStretch,
|
||||
kCrop
|
||||
};
|
||||
|
||||
EncodingParams();
|
||||
|
||||
void SetFilename(const QString& filename);
|
||||
static QDir GetPresetPath();
|
||||
static QStringList GetListOfPresets();
|
||||
|
||||
bool IsValid() const
|
||||
{
|
||||
return video_enabled_ || audio_enabled_ || subtitles_enabled_;
|
||||
}
|
||||
|
||||
void SetFilename(const QString& filename) { filename_ = filename; }
|
||||
|
||||
void EnableVideo(const VideoParams& video_params, const ExportCodec::Codec& vcodec);
|
||||
void EnableAudio(const AudioParams& audio_params, const ExportCodec::Codec &acodec);
|
||||
void EnableSubtitles(const ExportCodec::Codec &scodec);
|
||||
void EnableSidecarSubtitles(const ExportFormat::Format &sfmt, const ExportCodec::Codec &scodec);
|
||||
|
||||
void set_video_option(const QString& key, const QString& value);
|
||||
void set_video_bit_rate(const int64_t& rate);
|
||||
void set_video_min_bit_rate(const int64_t& rate);
|
||||
void set_video_max_bit_rate(const int64_t& rate);
|
||||
void set_video_buffer_size(const int64_t& sz);
|
||||
void set_video_threads(const int& threads);
|
||||
void set_video_pix_fmt(const QString& s);
|
||||
void set_video_is_image_sequence(bool s)
|
||||
void DisableVideo();
|
||||
void DisableAudio();
|
||||
void DisableSubtitles();
|
||||
|
||||
const ExportFormat::Format &format() const { return format_; }
|
||||
void set_format(const ExportFormat::Format &format) { format_ = format; }
|
||||
|
||||
void set_video_option(const QString& key, const QString& value) { video_opts_.insert(key, value); }
|
||||
void set_video_bit_rate(const int64_t& rate) { video_bit_rate_ = rate; }
|
||||
void set_video_min_bit_rate(const int64_t& rate) { video_min_bit_rate_ = rate; }
|
||||
void set_video_max_bit_rate(const int64_t& rate) { video_max_bit_rate_ = rate; }
|
||||
void set_video_buffer_size(const int64_t& sz) { video_buffer_size_ = sz; }
|
||||
void set_video_threads(const int& threads) { video_threads_ = threads; }
|
||||
void set_video_pix_fmt(const QString& s) { video_pix_fmt_ = s; }
|
||||
void set_video_is_image_sequence(bool s) { video_is_image_sequence_ = s; }
|
||||
void set_color_transform(const ColorTransform& color_transform) { color_transform_ = color_transform; }
|
||||
|
||||
const QString& filename() const { return filename_; }
|
||||
|
||||
bool video_enabled() const { return video_enabled_; }
|
||||
const ExportCodec::Codec& video_codec() const { return video_codec_; }
|
||||
const VideoParams& video_params() const { return video_params_; }
|
||||
const QHash<QString, QString>& video_opts() const { return video_opts_; }
|
||||
QString video_option(const QString &key) const { return video_opts_.value(key); }
|
||||
bool has_video_opt(const QString &key) const { return video_opts_.contains(key); }
|
||||
const int64_t& video_bit_rate() const { return video_bit_rate_; }
|
||||
const int64_t& video_min_bit_rate() const { return video_min_bit_rate_; }
|
||||
const int64_t& video_max_bit_rate() const { return video_max_bit_rate_; }
|
||||
const int64_t& video_buffer_size() const { return video_buffer_size_; }
|
||||
const int& video_threads() const { return video_threads_; }
|
||||
const QString& video_pix_fmt() const { return video_pix_fmt_; }
|
||||
bool video_is_image_sequence() const { return video_is_image_sequence_; }
|
||||
const ColorTransform& color_transform() const { return color_transform_; }
|
||||
|
||||
bool audio_enabled() const { return audio_enabled_; }
|
||||
const ExportCodec::Codec &audio_codec() const { return audio_codec_; }
|
||||
const AudioParams& audio_params() const { return audio_params_; }
|
||||
const int64_t& audio_bit_rate() const { return audio_bit_rate_; }
|
||||
|
||||
void set_audio_bit_rate(const int64_t& b) { audio_bit_rate_ = b; }
|
||||
|
||||
bool subtitles_enabled() const { return subtitles_enabled_; }
|
||||
bool subtitles_are_sidecar() const { return subtitles_are_sidecar_; }
|
||||
ExportFormat::Format subtitle_sidecar_fmt() const { return subtitle_sidecar_fmt_; }
|
||||
ExportCodec::Codec subtitles_codec() const { return subtitles_codec_; }
|
||||
|
||||
const rational& GetExportLength() const { return export_length_; }
|
||||
void SetExportLength(const rational& export_length) { export_length_ = export_length; }
|
||||
|
||||
bool Load(QIODevice *device);
|
||||
bool Load(QXmlStreamReader *reader);
|
||||
|
||||
void Save(QIODevice *device) const;
|
||||
void Save(QXmlStreamWriter* writer) const;
|
||||
|
||||
bool has_custom_range() const { return has_custom_range_; }
|
||||
const TimeRange& custom_range() const { return custom_range_; }
|
||||
void set_custom_range(const TimeRange& custom_range)
|
||||
{
|
||||
video_is_image_sequence_ = s;
|
||||
has_custom_range_ = true;
|
||||
custom_range_ = custom_range;
|
||||
}
|
||||
|
||||
const QString& filename() const;
|
||||
const VideoScalingMethod& video_scaling_method() const { return video_scaling_method_; }
|
||||
void set_video_scaling_method(const VideoScalingMethod& video_scaling_method) { video_scaling_method_ = video_scaling_method; }
|
||||
|
||||
bool video_enabled() const;
|
||||
const ExportCodec::Codec& video_codec() const;
|
||||
const VideoParams& video_params() const;
|
||||
const QHash<QString, QString>& video_opts() const;
|
||||
const int64_t& video_bit_rate() const;
|
||||
const int64_t& video_min_bit_rate() const;
|
||||
const int64_t& video_max_bit_rate() const;
|
||||
const int64_t& video_buffer_size() const;
|
||||
const int& video_threads() const;
|
||||
const QString& video_pix_fmt() const;
|
||||
bool video_is_image_sequence() const
|
||||
{
|
||||
return video_is_image_sequence_;
|
||||
}
|
||||
|
||||
bool audio_enabled() const;
|
||||
const ExportCodec::Codec &audio_codec() const;
|
||||
const AudioParams& audio_params() const;
|
||||
|
||||
const int64_t& audio_bit_rate() const
|
||||
{
|
||||
return audio_bit_rate_;
|
||||
}
|
||||
|
||||
void set_audio_bit_rate(const int64_t& b)
|
||||
{
|
||||
audio_bit_rate_ = b;
|
||||
}
|
||||
|
||||
bool subtitles_enabled() const;
|
||||
ExportCodec::Codec subtitles_codec() const;
|
||||
|
||||
const rational& GetExportLength() const;
|
||||
void SetExportLength(const rational& GetExportLength);
|
||||
|
||||
virtual void Save(QXmlStreamWriter* writer) const;
|
||||
static QMatrix4x4 GenerateMatrix(VideoScalingMethod method,
|
||||
int source_width, int source_height,
|
||||
int dest_width, int dest_height);
|
||||
|
||||
private:
|
||||
static const int kEncoderParamsVersion = 1;
|
||||
|
||||
bool LoadV1(QXmlStreamReader *reader);
|
||||
|
||||
QString filename_;
|
||||
ExportFormat::Format format_;
|
||||
|
||||
bool video_enabled_;
|
||||
ExportCodec::Codec video_codec_;
|
||||
@@ -116,6 +156,7 @@ private:
|
||||
int video_threads_;
|
||||
QString video_pix_fmt_;
|
||||
bool video_is_image_sequence_;
|
||||
ColorTransform color_transform_;
|
||||
|
||||
bool audio_enabled_;
|
||||
ExportCodec::Codec audio_codec_;
|
||||
@@ -123,9 +164,15 @@ private:
|
||||
int64_t audio_bit_rate_;
|
||||
|
||||
bool subtitles_enabled_;
|
||||
bool subtitles_are_sidecar_;
|
||||
ExportFormat::Format subtitle_sidecar_fmt_;
|
||||
ExportCodec::Codec subtitles_codec_;
|
||||
|
||||
rational export_length_;
|
||||
VideoScalingMethod video_scaling_method_;
|
||||
|
||||
bool has_custom_range_;
|
||||
TimeRange custom_range_;
|
||||
|
||||
};
|
||||
|
||||
@@ -154,6 +201,8 @@ public:
|
||||
|
||||
static Encoder *CreateFromFormat(ExportFormat::Format f, const EncodingParams ¶ms);
|
||||
|
||||
static Encoder *CreateFromParams(const EncodingParams ¶ms);
|
||||
|
||||
virtual QStringList GetPixelFormatsForCodec(ExportCodec::Codec c) const;
|
||||
virtual std::vector<AudioParams::Format> GetSampleFormatsForCodec(ExportCodec::Codec c) const;
|
||||
|
||||
|
||||
@@ -63,7 +63,6 @@ FFmpegDecoder::FFmpegDecoder() :
|
||||
native_output_pix_fmt_(VideoParams::kFormatInvalid),
|
||||
working_frame_(nullptr),
|
||||
working_packet_(nullptr),
|
||||
is_working_(false),
|
||||
cache_at_zero_(false),
|
||||
cache_at_eof_(false)
|
||||
{
|
||||
@@ -80,6 +79,7 @@ bool FFmpegDecoder::OpenInternal()
|
||||
working_frame_ = av_frame_alloc();
|
||||
working_packet_ = av_packet_alloc();
|
||||
|
||||
frame_rate_tb_ = rational::NaN;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -142,28 +142,32 @@ bool FFmpegDecoder::OpenInternal()
|
||||
return output_frame;
|
||||
}*/
|
||||
|
||||
TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const rational &timecode, const RetrieveVideoParams ¶ms, const QAtomicInt *cancelled)
|
||||
TexturePtr FFmpegDecoder::RetrieveVideoInternal(const RetrieveVideoParams &p)
|
||||
{
|
||||
if (AVFramePtr f = RetrieveFrame(timecode, cancelled)) {
|
||||
if (cancelled && *cancelled) {
|
||||
if (AVFramePtr f = RetrieveFrame(p.time, p.cancelled)) {
|
||||
if (p.cancelled && p.cancelled->IsCancelled()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (InitScaler(f.get(), params)) {
|
||||
int &src_fmt = f.get()->format;
|
||||
src_fmt = FFmpegUtils::ConvertJPEGSpaceToRegularSpace(static_cast<AVPixelFormat>(src_fmt));
|
||||
|
||||
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,
|
||||
params.divider);
|
||||
p.divider);
|
||||
|
||||
TexturePtr tex = nullptr;
|
||||
const bool hwscale = true;
|
||||
bool hwscale = true;
|
||||
|
||||
// Attempt to use GLSL shader for faster YUV to RGB conversion
|
||||
if (hwscale) {
|
||||
AVPixelFormat src_fmt = AVPixelFormat(f.get()->format);
|
||||
if (src_fmt == AV_PIX_FMT_YUV420P
|
||||
|| src_fmt == AV_PIX_FMT_YUV422P
|
||||
|| src_fmt == AV_PIX_FMT_YUV444P
|
||||
@@ -175,7 +179,7 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const ration
|
||||
|| src_fmt == AV_PIX_FMT_YUV444P12LE) {
|
||||
if (Yuv2RgbShader.isNull()) {
|
||||
// Compile shader
|
||||
Yuv2RgbShader = renderer->CreateNativeShader(ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/yuv2rgb.frag"))));
|
||||
Yuv2RgbShader = p.renderer->CreateNativeShader(ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/yuv2rgb.frag"))));
|
||||
}
|
||||
|
||||
if (!Yuv2RgbShader.isNull()) {
|
||||
@@ -207,7 +211,7 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const ration
|
||||
plane_params.set_channel_count(1);
|
||||
plane_params.set_divider(1);
|
||||
plane_params.set_format(native_internal_pix_fmt_);
|
||||
TexturePtr y_plane = renderer->CreateTexture(plane_params, f->data[0], f->linesize[0] / px_size);
|
||||
TexturePtr y_plane = p.renderer->CreateTexture(plane_params, f->data[0], f->linesize[0] / px_size);
|
||||
|
||||
if (src_fmt == AV_PIX_FMT_YUV420P
|
||||
|| src_fmt == AV_PIX_FMT_YUV422P
|
||||
@@ -224,17 +228,47 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const ration
|
||||
plane_params.set_height(plane_params.height()/2);
|
||||
}
|
||||
|
||||
TexturePtr u_plane = renderer->CreateTexture(plane_params, f->data[1], f->linesize[1] / px_size);
|
||||
TexturePtr v_plane = renderer->CreateTexture(plane_params, f->data[2], f->linesize[2] / px_size);
|
||||
TexturePtr u_plane = p.renderer->CreateTexture(plane_params, f->data[1], f->linesize[1] / px_size);
|
||||
TexturePtr v_plane = p.renderer->CreateTexture(plane_params, f->data[2], f->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, f->color_range == AVCOL_RANGE_JPEG));
|
||||
|
||||
tex = renderer->CreateTexture(vp);
|
||||
renderer->BlitToTexture(Yuv2RgbShader, job, tex.get(), false);
|
||||
const int *yuv_coeffs = sws_getCoefficients(FFmpegUtils::GetSwsColorspaceFromAVColorSpace(f.get()->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(), f.get());
|
||||
|
||||
// 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(f->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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -242,6 +276,7 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const ration
|
||||
if (!tex) {
|
||||
// Fallback to software pixel format conversion
|
||||
int r;
|
||||
|
||||
r = av_buffersrc_add_frame_flags(buffersrc_ctx_, f.get(), AV_BUFFERSRC_FLAG_KEEP_REF);
|
||||
if (r < 0) {
|
||||
return nullptr;
|
||||
@@ -251,7 +286,7 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const ration
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
tex = renderer->CreateTexture(vp, working_frame_->data[0], working_frame_->linesize[0] / vp.GetBytesPerPixel());
|
||||
tex = p.renderer->CreateTexture(vp, working_frame_->data[0], working_frame_->linesize[0] / vp.GetBytesPerPixel());
|
||||
|
||||
av_frame_unref(working_frame_);
|
||||
}
|
||||
@@ -290,7 +325,7 @@ QString FFmpegDecoder::id() const
|
||||
return QStringLiteral("ffmpeg");
|
||||
}
|
||||
|
||||
FootageDescription FFmpegDecoder::Probe(const QString &filename, const QAtomicInt *cancelled) const
|
||||
FootageDescription FFmpegDecoder::Probe(const QString &filename, CancelAtom *cancelled) const
|
||||
{
|
||||
// Return value
|
||||
FootageDescription desc(id());
|
||||
@@ -416,6 +451,7 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, const QAtomicIn
|
||||
stream.set_start_time(avstream->start_time);
|
||||
stream.set_time_base(avstream->time_base);
|
||||
stream.set_duration(avstream->duration);
|
||||
stream.set_color_range(avstream->codecpar->color_range == AVCOL_RANGE_JPEG ? VideoParams::kColorRangeFull : VideoParams::kColorRangeLimited);
|
||||
|
||||
// Defaults to false, requires user intervention if incorrect
|
||||
stream.set_premultiplied_alpha(false);
|
||||
@@ -500,6 +536,8 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, const QAtomicIn
|
||||
|
||||
}
|
||||
|
||||
desc.SetStreamCount(fmt_ctx->nb_streams);
|
||||
|
||||
}
|
||||
|
||||
// Free all memory
|
||||
@@ -515,7 +553,7 @@ QString FFmpegDecoder::FFmpegError(int error_code)
|
||||
return QStringLiteral("%1 %2").arg(QString::number(error_code), err);
|
||||
}
|
||||
|
||||
bool FFmpegDecoder::ConformAudioInternal(const QVector<QString> &filenames, const AudioParams ¶ms, const QAtomicInt *cancelled)
|
||||
bool FFmpegDecoder::ConformAudioInternal(const QVector<QString> &filenames, const AudioParams ¶ms, CancelAtom *cancelled)
|
||||
{
|
||||
// Iterate through each audio frame and extract the PCM data
|
||||
|
||||
@@ -565,7 +603,7 @@ bool FFmpegDecoder::ConformAudioInternal(const QVector<QString> &filenames, cons
|
||||
|
||||
while (true) {
|
||||
// Check if we have a `cancelled` ptr and its value
|
||||
if (cancelled && *cancelled) {
|
||||
if (cancelled && cancelled->IsCancelled()) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -745,12 +783,12 @@ void FFmpegDecoder::ClearFrameCache()
|
||||
}
|
||||
}
|
||||
|
||||
AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, const QAtomicInt *cancelled)
|
||||
AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, CancelAtom *cancelled)
|
||||
{
|
||||
int64_t target_ts = GetTimeInTimebaseUnits(time, instance_.avstream()->time_base, instance_.avstream()->start_time);
|
||||
|
||||
const int64_t min_seek = -instance_.avstream()->start_time;
|
||||
int64_t seek_ts = target_ts;
|
||||
int64_t seek_ts = std::max(min_seek, target_ts - MaximumQueueSize());
|
||||
bool still_seeking = false;
|
||||
|
||||
if (time != kAnyTimecode) {
|
||||
@@ -783,7 +821,7 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, const QAtomicInt *
|
||||
|
||||
while (true) {
|
||||
// Break out of loop if we've cancelled
|
||||
if (cancelled && *cancelled) {
|
||||
if (cancelled && cancelled->IsCancelled()) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -794,7 +832,7 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, const QAtomicInt *
|
||||
// Pull from the decoder
|
||||
ret = instance_.GetFrame(working_packet_, filtered.get());
|
||||
|
||||
if (cancelled && *cancelled) {
|
||||
if (cancelled && cancelled->IsCancelled()) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -839,7 +877,7 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, const QAtomicInt *
|
||||
} else {
|
||||
|
||||
// Cut down to thread count - 1 before we acquire a new frame
|
||||
if (cached_frames_.size() == size_t(QThread::idealThreadCount())) {
|
||||
if (cached_frames_.size() > size_t(MaximumQueueSize())) {
|
||||
RemoveFirstFrame();
|
||||
}
|
||||
|
||||
@@ -879,7 +917,12 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, const QAtomicInt *
|
||||
|
||||
bool FFmpegDecoder::InitScaler(AVFrame *input, const RetrieveVideoParams& params)
|
||||
{
|
||||
if (params == filter_params_ && filter_graph_ && input_fmt_ == input->format) {
|
||||
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;
|
||||
}
|
||||
@@ -944,6 +987,20 @@ bool FFmpegDecoder::InitScaler(AVFrame *input, const RetrieveVideoParams& params
|
||||
// Link filters as necessary
|
||||
AVFilterContext *last_filter = buffersrc_ctx_;
|
||||
|
||||
// Add deinterlace filter if necessary
|
||||
if (filter_params_.src_interlacing != VideoParams::kInterlaceNone) {
|
||||
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
|
||||
int dst_width, dst_height;
|
||||
if (filter_params_.divider > 1) {
|
||||
@@ -1040,6 +1097,15 @@ void FFmpegDecoder::RemoveFirstFrame()
|
||||
cache_at_zero_ = false;
|
||||
}
|
||||
|
||||
int FFmpegDecoder::MaximumQueueSize()
|
||||
{
|
||||
// Fairly arbitrary size. This used to need to be the number of current threads to ensure any
|
||||
// thread that arrived would have its frame available, but if we only have one render thread,
|
||||
// that's no longer a concern. Now, this value could technically be 1, but some memory cache
|
||||
// may be useful for reversing. This value may be tweaked over time.
|
||||
return 2;
|
||||
}
|
||||
|
||||
FFmpegDecoder::Instance::Instance() :
|
||||
fmt_ctx_(nullptr),
|
||||
codec_ctx_(nullptr),
|
||||
|
||||
@@ -25,27 +25,22 @@
|
||||
#include <inttypes.h>
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavfilter/avfilter.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libswscale/swscale.h>
|
||||
#include <libswresample/swresample.h>
|
||||
}
|
||||
|
||||
#include <QAtomicInt>
|
||||
#include <QTimer>
|
||||
#include <QVector>
|
||||
#include <QWaitCondition>
|
||||
|
||||
#include "codec/decoder.h"
|
||||
#include "common/ffmpegutils.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
using AVFramePtr = std::shared_ptr<AVFrame>;
|
||||
inline AVFramePtr CreateAVFramePtr(AVFrame *f)
|
||||
{
|
||||
return std::shared_ptr<AVFrame>(f, [](AVFrame *g){ av_frame_free(&g); });
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief A Decoder derivative that wraps FFmpeg functions as on Olive decoder
|
||||
*/
|
||||
@@ -64,12 +59,12 @@ public:
|
||||
virtual bool SupportsVideo() override{return true;}
|
||||
virtual bool SupportsAudio() override{return true;}
|
||||
|
||||
virtual FootageDescription Probe(const QString &filename, const QAtomicInt *cancelled) const override;
|
||||
virtual FootageDescription Probe(const QString &filename, CancelAtom *cancelled) const override;
|
||||
|
||||
protected:
|
||||
virtual bool OpenInternal() override;
|
||||
virtual TexturePtr RetrieveVideoInternal(Renderer *renderer, const rational& timecode, const RetrieveVideoParams& params, const QAtomicInt *cancelled) override;
|
||||
virtual bool ConformAudioInternal(const QVector<QString>& filenames, const AudioParams ¶ms, const QAtomicInt* cancelled) override;
|
||||
virtual TexturePtr RetrieveVideoInternal(const RetrieveVideoParams& p) override;
|
||||
virtual bool ConformAudioInternal(const QVector<QString>& filenames, const AudioParams ¶ms, CancelAtom *cancelled) override;
|
||||
virtual void CloseInternal() override;
|
||||
|
||||
private:
|
||||
@@ -151,10 +146,12 @@ private:
|
||||
|
||||
void ClearFrameCache();
|
||||
|
||||
AVFramePtr RetrieveFrame(const rational &time, const QAtomicInt *cancelled);
|
||||
AVFramePtr RetrieveFrame(const rational &time, CancelAtom *cancelled);
|
||||
|
||||
void RemoveFirstFrame();
|
||||
|
||||
static int MaximumQueueSize();
|
||||
|
||||
RetrieveVideoParams filter_params_;
|
||||
AVFilterGraph* filter_graph_;
|
||||
AVFilterContext* buffersrc_ctx_;
|
||||
@@ -163,6 +160,7 @@ private:
|
||||
VideoParams::Format native_internal_pix_fmt_;
|
||||
VideoParams::Format native_output_pix_fmt_;
|
||||
int native_channel_count_;
|
||||
rational frame_rate_tb_;
|
||||
|
||||
AVFrame *working_frame_;
|
||||
AVPacket *working_packet_;
|
||||
@@ -171,9 +169,6 @@ private:
|
||||
|
||||
std::list<AVFramePtr> cached_frames_;
|
||||
|
||||
bool is_working_;
|
||||
QMutex is_working_mutex_;
|
||||
|
||||
bool cache_at_zero_;
|
||||
bool cache_at_eof_;
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
#include "ffmpegencoder.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavfilter/buffersink.h>
|
||||
#include <libavfilter/buffersrc.h>
|
||||
#include <libavutil/pixdesc.h>
|
||||
}
|
||||
|
||||
@@ -36,8 +38,9 @@ FFmpegEncoder::FFmpegEncoder(const EncodingParams ¶ms) :
|
||||
fmt_ctx_(nullptr),
|
||||
video_stream_(nullptr),
|
||||
video_codec_ctx_(nullptr),
|
||||
video_alpha_scale_ctx_(nullptr),
|
||||
video_noalpha_scale_ctx_(nullptr),
|
||||
video_scale_ctx_(nullptr),
|
||||
video_buffersrc_ctx_(nullptr),
|
||||
video_buffersink_ctx_(nullptr),
|
||||
audio_stream_(nullptr),
|
||||
audio_codec_ctx_(nullptr),
|
||||
audio_resample_ctx_(nullptr),
|
||||
@@ -54,6 +57,11 @@ QStringList FFmpegEncoder::GetPixelFormatsForCodec(ExportCodec::Codec c) const
|
||||
|
||||
if (codec_info) {
|
||||
for (int i=0; codec_info->pix_fmts[i]!=-1; i++) {
|
||||
if (FFmpegUtils::ConvertJPEGSpaceToRegularSpace(codec_info->pix_fmts[i]) != codec_info->pix_fmts[i]) {
|
||||
// This is a deprecated "JPEG" space, skip it
|
||||
continue;
|
||||
}
|
||||
|
||||
const char* pix_fmt_name = av_get_pix_fmt_name(codec_info->pix_fmts[i]);
|
||||
pix_fmts.append(pix_fmt_name);
|
||||
}
|
||||
@@ -142,29 +150,59 @@ bool FFmpegEncoder::Open()
|
||||
// This is the pixel format the encoder wants to encode to
|
||||
AVPixelFormat encoder_pix_fmt = video_codec_ctx_->pix_fmt;
|
||||
|
||||
// Set up a scaling context - if the native pixel format is not equal to the encoder's, we'll need to convert it
|
||||
// before encoding. Even if we don't, this may be useful for converting between linesizes, etc.
|
||||
video_alpha_scale_ctx_ = sws_getContext(params().video_params().width(),
|
||||
params().video_params().height(),
|
||||
src_alpha_pix_fmt,
|
||||
params().video_params().width(),
|
||||
params().video_params().height(),
|
||||
encoder_pix_fmt,
|
||||
0,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr);
|
||||
video_scale_ctx_ = avfilter_graph_alloc();
|
||||
if (!video_scale_ctx_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
video_noalpha_scale_ctx_ = sws_getContext(params().video_params().width(),
|
||||
params().video_params().height(),
|
||||
src_noalpha_pix_fmt,
|
||||
params().video_params().width(),
|
||||
params().video_params().height(),
|
||||
encoder_pix_fmt,
|
||||
0,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr);
|
||||
static const int FILTER_ARG_SZ = 1024;
|
||||
char filter_args[FILTER_ARG_SZ];
|
||||
|
||||
snprintf(filter_args, FILTER_ARG_SZ, "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
|
||||
params().video_params().effective_width(),
|
||||
params().video_params().effective_height(),
|
||||
src_alpha_pix_fmt,
|
||||
params().video_params().time_base().numerator(),
|
||||
params().video_params().time_base().denominator(),
|
||||
params().video_params().pixel_aspect_ratio().numerator(),
|
||||
params().video_params().pixel_aspect_ratio().denominator());
|
||||
|
||||
avfilter_graph_create_filter(&video_buffersrc_ctx_, avfilter_get_by_name("buffer"), "in", filter_args, nullptr, video_scale_ctx_);
|
||||
avfilter_graph_create_filter(&video_buffersink_ctx_, avfilter_get_by_name("buffersink"), "out", nullptr, nullptr, video_scale_ctx_);
|
||||
|
||||
AVFilterContext *last_filter = video_buffersrc_ctx_;
|
||||
|
||||
{
|
||||
// Set color range
|
||||
AVFilterContext* range_filter;
|
||||
|
||||
snprintf(filter_args, FILTER_ARG_SZ, "in_range=full:out_range=%s",
|
||||
params().video_params().color_range() == VideoParams::kColorRangeFull ? "full" : "limited");
|
||||
|
||||
avfilter_graph_create_filter(&range_filter, avfilter_get_by_name("scale"), "range", filter_args, nullptr, video_scale_ctx_);
|
||||
|
||||
avfilter_link(last_filter, 0, range_filter, 0);
|
||||
last_filter = range_filter;
|
||||
}
|
||||
|
||||
if (src_alpha_pix_fmt != encoder_pix_fmt) {
|
||||
// Transform pixel format
|
||||
AVFilterContext* format_filter;
|
||||
|
||||
snprintf(filter_args, FILTER_ARG_SZ, "pix_fmts=%u", encoder_pix_fmt);
|
||||
|
||||
avfilter_graph_create_filter(&format_filter, avfilter_get_by_name("format"), "format", filter_args, nullptr, video_scale_ctx_);
|
||||
|
||||
avfilter_link(last_filter, 0, format_filter, 0);
|
||||
last_filter = format_filter;
|
||||
}
|
||||
|
||||
avfilter_link(last_filter, 0, video_buffersink_ctx_, 0);
|
||||
|
||||
if (avfilter_graph_config(video_scale_ctx_, nullptr) < 0) {
|
||||
SetError(tr("Failed to configure filter graph"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize an audio stream if it's enabled
|
||||
@@ -203,67 +241,41 @@ bool FFmpegEncoder::Open()
|
||||
|
||||
bool FFmpegEncoder::WriteFrame(FramePtr frame, rational time)
|
||||
{
|
||||
bool success = false;
|
||||
|
||||
AVFrame* encoded_frame = av_frame_alloc();
|
||||
|
||||
int error_code;
|
||||
const char* input_data;
|
||||
int input_linesize;
|
||||
|
||||
// Frame must be video
|
||||
encoded_frame->width = frame->width();
|
||||
encoded_frame->height = frame->height();
|
||||
encoded_frame->format = video_codec_ctx_->pix_fmt;
|
||||
|
||||
// Set interlacing
|
||||
if (frame->video_params().interlacing() != VideoParams::kInterlaceNone) {
|
||||
encoded_frame->interlaced_frame = 1;
|
||||
|
||||
if (frame->video_params().interlacing() == VideoParams::kInterlacedTopFirst) {
|
||||
encoded_frame->top_field_first = 1;
|
||||
} else {
|
||||
encoded_frame->top_field_first = 0;
|
||||
}
|
||||
}
|
||||
|
||||
error_code = av_frame_get_buffer(encoded_frame, 0);
|
||||
if (error_code < 0) {
|
||||
FFmpegError(tr("Failed to create AVFrame buffer"), error_code);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// We may need to convert this frame to a frame that swscale will understand
|
||||
if (frame->format() != video_conversion_fmt_) {
|
||||
frame = frame->convert(video_conversion_fmt_);
|
||||
}
|
||||
|
||||
// Use swscale context to convert formats/linesizes
|
||||
input_data = frame->const_data();
|
||||
input_linesize = frame->linesize_bytes();
|
||||
AVFramePtr input_frame = CreateAVFramePtr(av_frame_alloc());
|
||||
input_frame->width = frame->width();
|
||||
input_frame->height = frame->height();
|
||||
input_frame->format = FFmpegUtils::GetFFmpegPixelFormat(frame->format(), frame->channel_count());
|
||||
input_frame->data[0] = reinterpret_cast<uint8_t*>(frame->data());
|
||||
input_frame->linesize[0] = frame->linesize_bytes();
|
||||
|
||||
error_code = sws_scale((frame->channel_count() == VideoParams::kRGBAChannelCount) ? video_alpha_scale_ctx_ : video_noalpha_scale_ctx_,
|
||||
reinterpret_cast<const uint8_t**>(&input_data),
|
||||
&input_linesize,
|
||||
0,
|
||||
frame->height(),
|
||||
encoded_frame->data,
|
||||
encoded_frame->linesize);
|
||||
input_frame->color_primaries = video_codec_ctx_->color_primaries;
|
||||
input_frame->color_trc = video_codec_ctx_->color_trc;
|
||||
input_frame->colorspace = video_codec_ctx_->colorspace;
|
||||
input_frame->color_range = video_codec_ctx_->color_range;
|
||||
|
||||
int r;
|
||||
r = av_buffersrc_add_frame_flags(video_buffersrc_ctx_, input_frame.get(), AV_BUFFERSRC_FLAG_KEEP_REF);
|
||||
if (r < 0) {
|
||||
FFmpegError(tr("Failed to add frame to filter graph"), r);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (error_code < 0) {
|
||||
FFmpegError(tr("Failed to scale frame"), error_code);
|
||||
goto fail;
|
||||
AVFramePtr encoded_frame = CreateAVFramePtr(av_frame_alloc());
|
||||
r = av_buffersink_get_frame(video_buffersink_ctx_, encoded_frame.get());
|
||||
if (r < 0) {
|
||||
FFmpegError(tr("Failed to retrieve frame from buffer sink"), r);
|
||||
return false;
|
||||
}
|
||||
|
||||
encoded_frame->pts = qRound64(time.toDouble() / av_q2d(video_codec_ctx_->time_base));
|
||||
|
||||
success = WriteAVFrame(encoded_frame, video_codec_ctx_, video_stream_);
|
||||
|
||||
fail:
|
||||
av_frame_free(&encoded_frame);
|
||||
|
||||
return success;
|
||||
return WriteAVFrame(encoded_frame.get(), video_codec_ctx_, video_stream_);
|
||||
}
|
||||
|
||||
bool FFmpegEncoder::WriteAudio(const SampleBuffer &audio)
|
||||
@@ -484,14 +496,11 @@ void FFmpegEncoder::Close()
|
||||
audio_frame_ = nullptr;
|
||||
}
|
||||
|
||||
if (video_alpha_scale_ctx_) {
|
||||
sws_freeContext(video_alpha_scale_ctx_);
|
||||
video_alpha_scale_ctx_ = nullptr;
|
||||
}
|
||||
|
||||
if (video_noalpha_scale_ctx_) {
|
||||
sws_freeContext(video_noalpha_scale_ctx_);
|
||||
video_noalpha_scale_ctx_ = nullptr;
|
||||
if (video_scale_ctx_) {
|
||||
avfilter_graph_free(&video_scale_ctx_);
|
||||
video_scale_ctx_ = nullptr;
|
||||
video_buffersrc_ctx_ = nullptr;
|
||||
video_buffersink_ctx_ = nullptr;
|
||||
}
|
||||
|
||||
if (video_codec_ctx_) {
|
||||
@@ -606,6 +615,7 @@ bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AV
|
||||
codec_ctx->time_base = params().video_params().frame_rate_as_time_base().toAVRational();
|
||||
codec_ctx->framerate = params().video_params().frame_rate().toAVRational();
|
||||
codec_ctx->pix_fmt = av_get_pix_fmt(params().video_pix_fmt().toUtf8());
|
||||
codec_ctx->color_range = params().video_params().color_range() == VideoParams::kColorRangeFull ? AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG;
|
||||
|
||||
if (params().video_params().interlacing() != VideoParams::kInterlaceNone) {
|
||||
// FIXME: I actually don't know what these flags do, the documentation helpfully doesn't
|
||||
@@ -628,7 +638,9 @@ bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AV
|
||||
// Set custom options
|
||||
{
|
||||
for (auto i=params().video_opts().begin();i!=params().video_opts().end();i++) {
|
||||
av_opt_set(codec_ctx->priv_data, i.key().toUtf8(), i.value().toUtf8(), AV_OPT_SEARCH_CHILDREN);
|
||||
if (!i.key().startsWith(QStringLiteral("ove_"))) {
|
||||
av_opt_set(codec_ctx->priv_data, i.key().toUtf8(), i.value().toUtf8(), AV_OPT_SEARCH_CHILDREN);
|
||||
}
|
||||
}
|
||||
|
||||
if (params().video_bit_rate() > 0) {
|
||||
@@ -646,6 +658,18 @@ bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AV
|
||||
if (params().video_buffer_size() > 0) {
|
||||
codec_ctx->rc_buffer_size = static_cast<int>(params().video_buffer_size());
|
||||
}
|
||||
|
||||
// nclc tags. See https://ffmpeg.org/doxygen/4.0/pixfmt_8h.html#ad384ee5a840bafd73daef08e6d9cafe7
|
||||
// ffprobe -v error -show_format -show_streams "C:\Users\Tom\Documents\srgb correct tags.mov"
|
||||
if (params().color_transform().output().contains(QStringLiteral("sRGB"), Qt::CaseInsensitive)) {
|
||||
codec_ctx->color_primaries = AVCOL_PRI_BT709;
|
||||
codec_ctx->color_trc = AVCOL_TRC_IEC61966_2_1;
|
||||
codec_ctx->colorspace = AVCOL_SPC_BT709;
|
||||
} else { // Assume Rec.709
|
||||
codec_ctx->color_primaries = AVCOL_PRI_BT709;
|
||||
codec_ctx->color_trc = AVCOL_TRC_BT709;
|
||||
codec_ctx->colorspace = AVCOL_SPC_BT709;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (type == AVMEDIA_TYPE_AUDIO) {
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavfilter/avfilter.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libswscale/swscale.h>
|
||||
#include <libswresample/swresample.h>
|
||||
#include <libavutil/opt.h>
|
||||
}
|
||||
@@ -88,8 +88,9 @@ private:
|
||||
|
||||
AVStream* video_stream_;
|
||||
AVCodecContext* video_codec_ctx_;
|
||||
SwsContext* video_alpha_scale_ctx_;
|
||||
SwsContext* video_noalpha_scale_ctx_;
|
||||
AVFilterGraph *video_scale_ctx_;
|
||||
AVFilterContext *video_buffersrc_ctx_;
|
||||
AVFilterContext *video_buffersink_ctx_;
|
||||
VideoParams::Format video_conversion_fmt_;
|
||||
|
||||
AVStream* audio_stream_;
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
#ifndef FOOTAGEMETA_H
|
||||
#define FOOTAGEMETA_H
|
||||
|
||||
struct FootageData {
|
||||
struct StreamData {
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
#endif // FOOTAGEMETA_H
|
||||
@@ -45,7 +45,7 @@ QString OIIODecoder::id() const
|
||||
return QStringLiteral("oiio");
|
||||
}
|
||||
|
||||
FootageDescription OIIODecoder::Probe(const QString &filename, const QAtomicInt* cancelled) const
|
||||
FootageDescription OIIODecoder::Probe(const QString &filename, CancelAtom *cancelled) const
|
||||
{
|
||||
Q_UNUSED(cancelled)
|
||||
|
||||
@@ -73,7 +73,8 @@ FootageDescription OIIODecoder::Probe(const QString &filename, const QAtomicInt*
|
||||
|
||||
bool stream_enabled = true;
|
||||
|
||||
for (int i=0; in->seek_subimage(i, 0); i++) {
|
||||
int i;
|
||||
for (i=0; in->seek_subimage(i, 0); i++) {
|
||||
OIIO::ImageSpec spec = in->spec();
|
||||
|
||||
VideoParams video_params = GetVideoParamsFromImageSpec(spec);
|
||||
@@ -104,6 +105,8 @@ FootageDescription OIIODecoder::Probe(const QString &filename, const QAtomicInt*
|
||||
desc.AddVideoStream(video_params);
|
||||
}
|
||||
|
||||
desc.SetStreamCount(i);
|
||||
|
||||
// If we're here, we have a successful image open
|
||||
in->close();
|
||||
|
||||
@@ -116,22 +119,20 @@ bool OIIODecoder::OpenInternal()
|
||||
return OpenImageHandler(stream().filename(), stream().stream());
|
||||
}
|
||||
|
||||
TexturePtr OIIODecoder::RetrieveVideoInternal(Renderer *renderer, const rational &timecode, const RetrieveVideoParams ¶ms, const QAtomicInt *cancelled)
|
||||
TexturePtr OIIODecoder::RetrieveVideoInternal(const RetrieveVideoParams &p)
|
||||
{
|
||||
Q_UNUSED(timecode)
|
||||
Q_UNUSED(cancelled)
|
||||
|
||||
VideoParams vp = GetVideoParamsFromImageSpec(image_->spec());
|
||||
vp.set_divider(params.divider);
|
||||
vp.set_divider(p.divider);
|
||||
|
||||
if (!buffer_.is_allocated() || last_params_ != params) {
|
||||
last_params_ = params;
|
||||
if (!buffer_.is_allocated()
|
||||
|| last_params_.divider != p.divider) {
|
||||
last_params_ = p;
|
||||
|
||||
buffer_.destroy();
|
||||
buffer_.set_video_params(vp);
|
||||
buffer_.allocate();
|
||||
|
||||
if (params.divider == 1) {
|
||||
if (p.divider == 1) {
|
||||
// Just upload straight to the buffer
|
||||
image_->read_image(oiio_pix_fmt_, buffer_.data(), OIIO::AutoStride, buffer_.linesize_bytes());
|
||||
} else {
|
||||
@@ -153,7 +154,7 @@ TexturePtr OIIODecoder::RetrieveVideoInternal(Renderer *renderer, const rational
|
||||
}
|
||||
}
|
||||
|
||||
return renderer->CreateTexture(vp, buffer_.data(), buffer_.linesize_pixels());
|
||||
return p.renderer->CreateTexture(vp, buffer_.data(), buffer_.linesize_pixels());
|
||||
}
|
||||
|
||||
void OIIODecoder::CloseInternal()
|
||||
|
||||
@@ -40,11 +40,11 @@ public:
|
||||
|
||||
virtual bool SupportsVideo() override{return true;}
|
||||
|
||||
virtual FootageDescription Probe(const QString& filename, const QAtomicInt* cancelled) const override;
|
||||
virtual FootageDescription Probe(const QString& filename, CancelAtom *cancelled) const override;
|
||||
|
||||
protected:
|
||||
virtual bool OpenInternal() override;
|
||||
virtual TexturePtr RetrieveVideoInternal(Renderer *renderer, const rational& timecode, const RetrieveVideoParams& params, const QAtomicInt *cancelled) override;
|
||||
virtual TexturePtr RetrieveVideoInternal(const RetrieveVideoParams& p) override;
|
||||
virtual void CloseInternal() override;
|
||||
|
||||
private:
|
||||
|
||||
@@ -55,8 +55,6 @@ set(OLIVE_SOURCES
|
||||
common/ratiodialog.h
|
||||
common/rational.cpp
|
||||
common/rational.h
|
||||
common/threadedobject.cpp
|
||||
common/threadedobject.h
|
||||
common/threadsafemap.h
|
||||
common/timecodefunctions.cpp
|
||||
common/timecodefunctions.h
|
||||
|
||||
@@ -21,35 +21,38 @@
|
||||
#ifndef CANCELABLEOBJECT_H
|
||||
#define CANCELABLEOBJECT_H
|
||||
|
||||
#include <QAtomicInt>
|
||||
|
||||
#include "common/define.h"
|
||||
#include "render/cancelatom.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class CancelableObject {
|
||||
public:
|
||||
CancelableObject() :
|
||||
cancelled_(false)
|
||||
CancelableObject()
|
||||
{
|
||||
}
|
||||
|
||||
void Cancel()
|
||||
{
|
||||
cancelled_ = true;
|
||||
cancel_.Cancel();
|
||||
CancelEvent();
|
||||
}
|
||||
|
||||
const QAtomicInt& IsCancelled() const
|
||||
CancelAtom *GetCancelAtom()
|
||||
{
|
||||
return cancelled_;
|
||||
return &cancel_;
|
||||
}
|
||||
|
||||
bool IsCancelled()
|
||||
{
|
||||
return cancel_.IsCancelled();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void CancelEvent(){}
|
||||
|
||||
private:
|
||||
QAtomicInt cancelled_;
|
||||
CancelAtom cancel_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -111,6 +111,43 @@ AVSampleFormat FFmpegUtils::GetFFmpegSampleFormat(const AudioParams::Format &smp
|
||||
return AV_SAMPLE_FMT_NONE;
|
||||
}
|
||||
|
||||
int FFmpegUtils::GetSwsColorspaceFromAVColorSpace(AVColorSpace cs)
|
||||
{
|
||||
switch (cs) {
|
||||
case AVCOL_SPC_BT709:
|
||||
return SWS_CS_ITU709;
|
||||
case AVCOL_SPC_FCC:
|
||||
return SWS_CS_FCC;
|
||||
case AVCOL_SPC_BT470BG:
|
||||
return SWS_CS_ITU624;
|
||||
case AVCOL_SPC_SMPTE170M:
|
||||
return SWS_CS_SMPTE170M;
|
||||
case AVCOL_SPC_SMPTE240M:
|
||||
return SWS_CS_SMPTE240M;
|
||||
case AVCOL_SPC_BT2020_NCL:
|
||||
return SWS_CS_BT2020;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return SWS_CS_DEFAULT;
|
||||
}
|
||||
|
||||
AVPixelFormat FFmpegUtils::ConvertJPEGSpaceToRegularSpace(AVPixelFormat f)
|
||||
{
|
||||
switch (f) {
|
||||
case AV_PIX_FMT_YUVJ420P: return AV_PIX_FMT_YUV420P;
|
||||
case AV_PIX_FMT_YUVJ422P: return AV_PIX_FMT_YUV422P;
|
||||
case AV_PIX_FMT_YUVJ444P: return AV_PIX_FMT_YUV444P;
|
||||
case AV_PIX_FMT_YUVJ440P: return AV_PIX_FMT_YUV440P;
|
||||
case AV_PIX_FMT_YUVJ411P: return AV_PIX_FMT_YUV411P;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
AVPixelFormat FFmpegUtils::GetFFmpegPixelFormat(const VideoParams::Format &pix_fmt, int channel_layout)
|
||||
{
|
||||
if (channel_layout == VideoParams::kRGBChannelCount) {
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libswscale/swscale.h>
|
||||
}
|
||||
|
||||
#include "render/audioparams.h"
|
||||
@@ -57,8 +58,31 @@ public:
|
||||
* @brief Returns an FFmpeg sample format type for a given native type
|
||||
*/
|
||||
static AVSampleFormat GetFFmpegSampleFormat(const AudioParams::Format &smp_fmt);
|
||||
|
||||
/**
|
||||
* @brief Returns an SWS_CS_* macro from an AVColorSpace enum member
|
||||
*
|
||||
* Why aren't these the same thing anyway? And for that matter, why doesn't FFmpeg provide a
|
||||
* convenience function to do this conversion for us? Who knows, but here we are.
|
||||
*/
|
||||
static int GetSwsColorspaceFromAVColorSpace(AVColorSpace cs);
|
||||
|
||||
/**
|
||||
* @brief Convert "JPEG"/full-range colorspace to its regular counterpart
|
||||
*
|
||||
* "JPEG "spaces are deprecated in favor of the regular space and setting `color_range`. For the
|
||||
* time being, FFmpeg still uses these JPEG spaces, so for simplicity (since we *are* color_range
|
||||
* aware), we use this function.
|
||||
*/
|
||||
static AVPixelFormat ConvertJPEGSpaceToRegularSpace(AVPixelFormat f);
|
||||
};
|
||||
|
||||
using AVFramePtr = std::shared_ptr<AVFrame>;
|
||||
inline AVFramePtr CreateAVFramePtr(AVFrame *f)
|
||||
{
|
||||
return std::shared_ptr<AVFrame>(f, [](AVFrame *g){ av_frame_free(&g); });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // FFMPEGABSTRACTION_H
|
||||
|
||||
+4
-9
@@ -142,7 +142,7 @@ void Html::WriteBlock(QXmlStreamWriter *writer, const QTextBlock &block)
|
||||
if (!(fmt.alignment() & Qt::AlignLeft)) {
|
||||
if (fmt.alignment() & Qt::AlignRight) {
|
||||
writer->writeAttribute(QStringLiteral("align"), QStringLiteral("right"));
|
||||
} else if (fmt.alignment() & Qt::AlignCenter) {
|
||||
} else if (fmt.alignment() & Qt::AlignHCenter) {
|
||||
writer->writeAttribute(QStringLiteral("align"), QStringLiteral("center"));
|
||||
} else if (fmt.alignment() & Qt::AlignJustify) {
|
||||
writer->writeAttribute(QStringLiteral("align"), QStringLiteral("justify"));
|
||||
@@ -161,7 +161,7 @@ void Html::WriteBlock(QXmlStreamWriter *writer, const QTextBlock &block)
|
||||
WriteCSSProperty(&style, QStringLiteral("line-height"), QStringLiteral("%1%").arg(fmt.lineHeight()));
|
||||
}
|
||||
|
||||
//WriteCharFormat(&style, block.charFormat());
|
||||
WriteCharFormat(&style, block.charFormat());
|
||||
|
||||
if (!style.isEmpty()) {
|
||||
writer->writeAttribute(QStringLiteral("style"), style);
|
||||
@@ -169,12 +169,7 @@ void Html::WriteBlock(QXmlStreamWriter *writer, const QTextBlock &block)
|
||||
|
||||
auto it = block.begin();
|
||||
|
||||
if (it == block.end()) {
|
||||
// FIXME: Might not be necessary with our custom HTML implementation
|
||||
QString s;
|
||||
s.append(QChar::Nbsp);
|
||||
writer->writeCharacters(s);
|
||||
} else {
|
||||
if (it != block.end()) {
|
||||
for (; it!=block.end(); it++) {
|
||||
WriteFragment(writer, it.fragment());
|
||||
}
|
||||
@@ -374,7 +369,7 @@ QTextBlockFormat Html::ReadBlockFormat(const QXmlStreamAttributes &attributes)
|
||||
if (StrEquals(attr.value(), QStringLiteral("right"))) {
|
||||
block_fmt.setAlignment(Qt::AlignRight);
|
||||
} else if (StrEquals(attr.value(), QStringLiteral("center"))) {
|
||||
block_fmt.setAlignment(Qt::AlignCenter);
|
||||
block_fmt.setAlignment(Qt::AlignHCenter);
|
||||
} else if (StrEquals(attr.value(), QStringLiteral("justify"))) {
|
||||
block_fmt.setAlignment(Qt::AlignJustify);
|
||||
}
|
||||
|
||||
+50
-10
@@ -20,6 +20,8 @@
|
||||
|
||||
#include "qtutils.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
namespace olive {
|
||||
|
||||
int QtUtils::QFontMetricsWidth(QFontMetrics fm, const QString& s) {
|
||||
@@ -41,11 +43,11 @@ QFrame *QtUtils::CreateHorizontalLine()
|
||||
QFrame *QtUtils::CreateVerticalLine()
|
||||
{
|
||||
QFrame *l = CreateHorizontalLine();
|
||||
l->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding);
|
||||
l->setFrameShape(QFrame::VLine);
|
||||
return l;
|
||||
}
|
||||
|
||||
int QtUtils::MessageBox(QWidget *parent, QMessageBox::Icon icon, const QString &title, const QString &message, QMessageBox::StandardButtons buttons)
|
||||
int QtUtils::MsgBox(QWidget *parent, QMessageBox::Icon icon, const QString &title, const QString &message, QMessageBox::StandardButtons buttons)
|
||||
{
|
||||
QMessageBox b(parent);
|
||||
b.setIcon(icon);
|
||||
@@ -70,7 +72,11 @@ QDateTime QtUtils::GetCreationDate(const QFileInfo &info)
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
|
||||
return info.created();
|
||||
#else
|
||||
return info.birthTime();
|
||||
QDateTime t = info.birthTime();
|
||||
if (!t.isValid()) {
|
||||
t = info.metadataChangeTime();
|
||||
}
|
||||
return t;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -90,19 +96,43 @@ QStringList QtUtils::WordWrapString(const QString &s, const QFontMetrics &fm, in
|
||||
QString this_line = lines.at(i);
|
||||
|
||||
while (this_line.size() > 1 && QFontMetricsWidth(fm, this_line) >= bounding_width) {
|
||||
int old_size = this_line.size();
|
||||
int hard_break = -1;
|
||||
|
||||
for (int j=this_line.size()-1; j>=0; j--) {
|
||||
if (this_line.at(j).isSpace()) {
|
||||
QString chopped = this_line.left(j);
|
||||
if (QFontMetricsWidth(fm, chopped) < bounding_width) {
|
||||
const QChar &char_test = this_line.at(j);
|
||||
|
||||
if (char_test.isSpace()
|
||||
|| char_test == '-') {
|
||||
if (QFontMetricsWidth(fm, this_line.left(j)) < bounding_width) {
|
||||
if (!char_test.isSpace()) {
|
||||
j++;
|
||||
}
|
||||
|
||||
QString chopped = this_line.left(j);
|
||||
|
||||
list.append(chopped);
|
||||
|
||||
int k = j+1;
|
||||
while (k < this_line.size() && this_line.at(k).isSpace()) {
|
||||
k++;
|
||||
while (j < this_line.size() && this_line.at(j).isSpace()) {
|
||||
j++;
|
||||
}
|
||||
this_line.remove(0, k);
|
||||
this_line.remove(0, j);
|
||||
break;
|
||||
}
|
||||
} else if (hard_break == -1 && QFontMetricsWidth(fm, this_line.left(j)) < bounding_width) {
|
||||
// In case we can't find a better place to split, split at the earliest time the line
|
||||
// goes under the width limit
|
||||
hard_break = j;
|
||||
}
|
||||
}
|
||||
|
||||
if (old_size == this_line.size()) {
|
||||
if (hard_break != -1) {
|
||||
list.append(this_line.left(hard_break));
|
||||
this_line.remove(0, hard_break);
|
||||
} else {
|
||||
qWarning() << "Failed to find anywhere to wrap. Returning full line.";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,4 +145,14 @@ QStringList QtUtils::WordWrapString(const QString &s, const QFontMetrics &fm, in
|
||||
return list;
|
||||
}
|
||||
|
||||
void QtUtils::SetComboBoxData(QComboBox *cb, int data)
|
||||
{
|
||||
for (int i=0; i<cb->count(); i++) {
|
||||
if (cb->itemData(i).toInt() == data) {
|
||||
cb->setCurrentIndex(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+19
-13
@@ -21,24 +21,13 @@
|
||||
#ifndef QTVERSIONABSTRACTION_H
|
||||
#define QTVERSIONABSTRACTION_H
|
||||
|
||||
/**
|
||||
*
|
||||
* A fairly simple header for reducing the amount of Qt version checks necessary throughout the code
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDateTime>
|
||||
#include <QFileInfo>
|
||||
#include <QFontMetrics>
|
||||
#include <QFrame>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "common/define.h"
|
||||
|
||||
#ifdef MessageBox
|
||||
#undef MessageBox
|
||||
#endif
|
||||
|
||||
namespace olive {
|
||||
|
||||
class QtUtils {
|
||||
@@ -56,7 +45,7 @@ public:
|
||||
|
||||
static QFrame* CreateVerticalLine();
|
||||
|
||||
static int MessageBox(QWidget *parent, QMessageBox::Icon icon, const QString& title, const QString& message, QMessageBox::StandardButtons buttons = QMessageBox::Ok);
|
||||
static int MsgBox(QWidget *parent, QMessageBox::Icon icon, const QString& title, const QString& message, QMessageBox::StandardButtons buttons = QMessageBox::Ok);
|
||||
|
||||
static QDateTime GetCreationDate(const QFileInfo &info);
|
||||
|
||||
@@ -64,6 +53,23 @@ public:
|
||||
|
||||
static QStringList WordWrapString(const QString &s, const QFontMetrics &fm, int bounding_width);
|
||||
|
||||
static void SetComboBoxData(QComboBox *cb, int data);
|
||||
|
||||
template <typename T>
|
||||
static T *GetParentOfType(const QObject *child)
|
||||
{
|
||||
QObject *t = child->parent();
|
||||
|
||||
while (t) {
|
||||
if (T *p = dynamic_cast<T*>(t)) {
|
||||
return p;
|
||||
}
|
||||
t = t->parent();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "threadedobject.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
void ThreadedObject::LockDeletes()
|
||||
{
|
||||
threadobj_delete_lock_++;
|
||||
}
|
||||
|
||||
void ThreadedObject::UnlockDeletes()
|
||||
{
|
||||
Q_ASSERT(AreDeletesLocked());
|
||||
|
||||
threadobj_delete_lock_--;
|
||||
}
|
||||
|
||||
bool ThreadedObject::AreDeletesLocked()
|
||||
{
|
||||
return (threadobj_delete_lock_ > 0);
|
||||
}
|
||||
|
||||
void ThreadedObject::LockMutex()
|
||||
{
|
||||
threadobj_main_lock_.lock();
|
||||
}
|
||||
|
||||
void ThreadedObject::UnlockMutex()
|
||||
{
|
||||
threadobj_main_lock_.unlock();
|
||||
}
|
||||
|
||||
bool ThreadedObject::TryLockMutex(int timeout)
|
||||
{
|
||||
return threadobj_main_lock_.tryLock(timeout);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef THREADEDOBJECT_H
|
||||
#define THREADEDOBJECT_H
|
||||
|
||||
#include <QMutex>
|
||||
|
||||
#include "common/define.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class ThreadedObject
|
||||
{
|
||||
public:
|
||||
|
||||
void LockMutex();
|
||||
void UnlockMutex();
|
||||
bool TryLockMutex(int timeout = 0);
|
||||
|
||||
void LockDeletes();
|
||||
void UnlockDeletes();
|
||||
bool AreDeletesLocked();
|
||||
|
||||
private:
|
||||
QMutex threadobj_main_lock_;
|
||||
|
||||
QAtomicInt threadobj_delete_lock_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // THREADEDOBJECT_H
|
||||
+10
-1
@@ -104,6 +104,15 @@ void Config::SetDefaults()
|
||||
SetEntryInternal(QStringLiteral("ReassocLinToNonLin"), NodeValue::kBoolean, false);
|
||||
SetEntryInternal(QStringLiteral("PreviewNonFloatDontAskAgain"), NodeValue::kBoolean, false);
|
||||
|
||||
SetEntryInternal(QStringLiteral("DefaultVideoTransition"), NodeValue::kText, QStringLiteral("org.olivevideoeditor.Olive.crossdissolve"));
|
||||
SetEntryInternal(QStringLiteral("DefaultAudioTransition"), NodeValue::kText, QStringLiteral("org.olivevideoeditor.Olive.crossdissolve"));
|
||||
SetEntryInternal(QStringLiteral("DefaultTransitionLength"), NodeValue::kRational, QVariant::fromValue(rational(1)));
|
||||
|
||||
SetEntryInternal(QStringLiteral("DefaultSubtitleSize"), NodeValue::kInt, 48);
|
||||
SetEntryInternal(QStringLiteral("DefaultSubtitleFamily"), NodeValue::kText, QString());
|
||||
SetEntryInternal(QStringLiteral("DefaultSubtitleWeight"), NodeValue::kInt, QFont::Bold);
|
||||
SetEntryInternal(QStringLiteral("AntialiasSubtitles"), NodeValue::kBoolean, true);
|
||||
|
||||
SetEntryInternal(QStringLiteral("AutoCacheDelay"), NodeValue::kInt, 1000);
|
||||
|
||||
SetEntryInternal(QStringLiteral("CatColor0"), NodeValue::kInt, ColorCoding::kRed);
|
||||
@@ -141,7 +150,7 @@ void Config::SetDefaults()
|
||||
SetEntryInternal(QStringLiteral("DefaultSequencePixelAspect"), NodeValue::kRational, QVariant::fromValue(rational(1)));
|
||||
SetEntryInternal(QStringLiteral("DefaultSequenceFrameRate"), NodeValue::kRational, QVariant::fromValue(rational(1001, 30000)));
|
||||
SetEntryInternal(QStringLiteral("DefaultSequenceInterlacing"), NodeValue::kInt, VideoParams::kInterlaceNone);
|
||||
SetEntryInternal(QStringLiteral("DefaultSequenceAutoCache"), NodeValue::kBoolean, false);
|
||||
SetEntryInternal(QStringLiteral("DefaultSequenceAutoCache2"), NodeValue::kBoolean, false);
|
||||
SetEntryInternal(QStringLiteral("DefaultSequenceAudioFrequency"), NodeValue::kInt, 48000);
|
||||
SetEntryInternal(QStringLiteral("DefaultSequenceAudioLayout"), NodeValue::kInt, QVariant::fromValue(static_cast<int64_t>(AV_CH_LAYOUT_STEREO)));
|
||||
|
||||
|
||||
@@ -522,6 +522,9 @@ bool Core::AddOpenProjectFromTask(Task *task)
|
||||
return true;
|
||||
} else {
|
||||
delete project;
|
||||
if (open_projects_.empty()) {
|
||||
CreateNewProject();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -583,6 +586,8 @@ void Core::ImportTaskComplete(Task* task)
|
||||
}
|
||||
|
||||
undo_stack_.pushIfHasChildren(command);
|
||||
|
||||
main_window_->SelectFootage(import_task->GetImportedFootage());
|
||||
}
|
||||
|
||||
bool Core::ConfirmImageSequence(const QString& filename)
|
||||
|
||||
@@ -153,7 +153,7 @@ void CrashHandlerDialog::ReplyFinished(QNetworkReply* reply)
|
||||
b.setIcon(QMessageBox::Critical);
|
||||
b.setWindowModality(Qt::WindowModal);
|
||||
b.setWindowTitle(tr("Upload Failed"));
|
||||
b.setText(tr("Failed to send error report. Please try again later."));
|
||||
b.setText(tr("Failed to send error report (%1). Please try again later.").arg(QString::number(reply->error())));
|
||||
b.addButton(QMessageBox::Ok);
|
||||
b.exec();
|
||||
|
||||
@@ -161,6 +161,22 @@ void CrashHandlerDialog::ReplyFinished(QNetworkReply* reply)
|
||||
}
|
||||
}
|
||||
|
||||
void CrashHandlerDialog::HandleSslErrors(QNetworkReply *reply, const QList<QSslError> &se)
|
||||
{
|
||||
QStringList errors;
|
||||
for (const QSslError &err : se) {
|
||||
errors.append(err.errorString());
|
||||
}
|
||||
|
||||
QMessageBox b(this);
|
||||
b.setIcon(QMessageBox::Critical);
|
||||
b.setWindowModality(Qt::WindowModal);
|
||||
b.setWindowTitle(tr("SSL Error"));
|
||||
b.setText(tr("Encountered the following SSL errors:\n\n%1").arg(errors.join('\n')));
|
||||
b.addButton(QMessageBox::Ok);
|
||||
b.exec();
|
||||
}
|
||||
|
||||
void CrashHandlerDialog::AttemptToFindReport()
|
||||
{
|
||||
// If we found it, use it, otherwise wait a second and try again
|
||||
@@ -198,6 +214,7 @@ void CrashHandlerDialog::SendErrorReport()
|
||||
|
||||
QNetworkAccessManager* manager = new QNetworkAccessManager();
|
||||
connect(manager, &QNetworkAccessManager::finished, this, &CrashHandlerDialog::ReplyFinished);
|
||||
connect(manager, &QNetworkAccessManager::sslErrors, this, &CrashHandlerDialog::HandleSslErrors);
|
||||
|
||||
QNetworkRequest request;
|
||||
request.setSslConfiguration(QSslConfiguration::defaultConfiguration());
|
||||
|
||||
@@ -65,6 +65,8 @@ protected:
|
||||
private slots:
|
||||
void ReplyFinished(QNetworkReply *reply);
|
||||
|
||||
void HandleSslErrors(QNetworkReply *reply, const QList<QSslError> &errors);
|
||||
|
||||
void AttemptToFindReport();
|
||||
|
||||
void ReadProcessHasData();
|
||||
|
||||
@@ -26,6 +26,8 @@ set(OLIVE_SOURCES
|
||||
dialog/export/exportaudiotab.h
|
||||
dialog/export/exportformatcombobox.cpp
|
||||
dialog/export/exportformatcombobox.h
|
||||
dialog/export/exportsavepresetdialog.cpp
|
||||
dialog/export/exportsavepresetdialog.h
|
||||
dialog/export/exportsubtitlestab.cpp
|
||||
dialog/export/exportsubtitlestab.h
|
||||
dialog/export/exportvideotab.cpp
|
||||
|
||||
@@ -82,4 +82,9 @@ void CineformSection::AddOpts(EncodingParams *params)
|
||||
params->set_video_option(QStringLiteral("quality"), QString::number(quality_combobox_->currentIndex()));
|
||||
}
|
||||
|
||||
void CineformSection::SetOpts(const EncodingParams *p)
|
||||
{
|
||||
quality_combobox_->setCurrentIndex(p->video_option(QStringLiteral("quality")).toInt());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ public:
|
||||
|
||||
virtual void AddOpts(EncodingParams* params) override;
|
||||
|
||||
virtual void SetOpts(const EncodingParams *p) override;
|
||||
|
||||
private:
|
||||
QComboBox *quality_combobox_;
|
||||
|
||||
|
||||
@@ -35,6 +35,8 @@ public:
|
||||
|
||||
virtual void AddOpts(EncodingParams* params){Q_UNUSED(params)}
|
||||
|
||||
virtual void SetOpts(const EncodingParams *p){Q_UNUSED(p)}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ H264Section::H264Section(int default_crf, QWidget *parent) :
|
||||
preset_combobox_->addItem(tr("Slow"));
|
||||
preset_combobox_->addItem(tr("Slower"));
|
||||
preset_combobox_->addItem(tr("Very Slow"));
|
||||
|
||||
|
||||
//Default to "medium"
|
||||
preset_combobox_->setCurrentIndex(5);
|
||||
|
||||
@@ -105,6 +105,10 @@ void H264Section::AddOpts(EncodingParams *params)
|
||||
|
||||
CompressionMethod method = static_cast<CompressionMethod>(compression_method_stack_->currentIndex());
|
||||
|
||||
// This option is not used by the encoder (nor is anything with the ove_ prefix), it's to help us
|
||||
// identify which option was chosen when params are restored
|
||||
params->set_video_option(QStringLiteral("ove_compressionmethod"), QString::number(method));
|
||||
|
||||
if (method == kConstantRateFactor) {
|
||||
|
||||
// Simply set CRF value
|
||||
@@ -121,9 +125,12 @@ void H264Section::AddOpts(EncodingParams *params)
|
||||
max_rate = bitrate_section_->GetMaximumBitRate();
|
||||
} else {
|
||||
// Calculate the bit rate from the file size divided by the sequence length in seconds (bits per second)
|
||||
target_rate = qRound64(static_cast<double>(filesize_section_->GetFileSize()) / params->GetExportLength().toDouble());
|
||||
int64_t target_fs = filesize_section_->GetFileSize();
|
||||
target_rate = qRound64(static_cast<double>(target_fs) / params->GetExportLength().toDouble());
|
||||
min_rate = target_rate;
|
||||
max_rate = target_rate;
|
||||
|
||||
params->set_video_option(QStringLiteral("ove_targetfilesize"), QString::number(target_fs));
|
||||
}
|
||||
|
||||
// Disable CRF encoding
|
||||
@@ -135,10 +142,33 @@ void H264Section::AddOpts(EncodingParams *params)
|
||||
params->set_video_buffer_size(2000000);
|
||||
|
||||
}
|
||||
|
||||
|
||||
params->set_video_option(QStringLiteral("preset"), QString::number(preset_combobox_->currentIndex()));
|
||||
}
|
||||
|
||||
void H264Section::SetOpts(const EncodingParams *p)
|
||||
{
|
||||
CompressionMethod method = static_cast<CompressionMethod>(p->video_option(QStringLiteral("ove_compressionmethod")).toInt());
|
||||
|
||||
compression_method_stack_->setCurrentIndex(method);
|
||||
|
||||
if (method == kConstantRateFactor) {
|
||||
crf_section_->SetValue(p->video_option(QStringLiteral("crf")).toInt());
|
||||
} else {
|
||||
int64_t target_rate = p->video_bit_rate();
|
||||
int64_t max_rate = p->video_max_bit_rate();
|
||||
|
||||
if (method == kTargetBitRate) {
|
||||
// Use user-supplied values for the bit rate
|
||||
bitrate_section_->SetTargetBitRate(target_rate);
|
||||
bitrate_section_->SetMaximumBitRate(max_rate);
|
||||
} else {
|
||||
// Calculate the bit rate from the file size divided by the sequence length in seconds (bits per second)
|
||||
filesize_section_->SetFileSize(p->video_option(QStringLiteral("ove_targetfilesize")).toLongLong());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
H264CRFSection::H264CRFSection(int default_crf, QWidget *parent) :
|
||||
QWidget(parent)
|
||||
{
|
||||
@@ -168,6 +198,11 @@ int H264CRFSection::GetValue() const
|
||||
return crf_slider_->value();
|
||||
}
|
||||
|
||||
void H264CRFSection::SetValue(int c)
|
||||
{
|
||||
crf_slider_->setValue(c);
|
||||
}
|
||||
|
||||
H264BitRateSection::H264BitRateSection(QWidget *parent) :
|
||||
QWidget(parent)
|
||||
{
|
||||
@@ -207,11 +242,21 @@ int64_t H264BitRateSection::GetTargetBitRate() const
|
||||
return qRound64(target_rate_->GetValue() * 1000000.0);
|
||||
}
|
||||
|
||||
void H264BitRateSection::SetTargetBitRate(int64_t b)
|
||||
{
|
||||
target_rate_->SetValue(double(b) * 0.000001);
|
||||
}
|
||||
|
||||
int64_t H264BitRateSection::GetMaximumBitRate() const
|
||||
{
|
||||
return qRound64(max_rate_->GetValue() * 1000000.0);
|
||||
}
|
||||
|
||||
void H264BitRateSection::SetMaximumBitRate(int64_t b)
|
||||
{
|
||||
max_rate_->SetValue(double(b) * 0.000001);
|
||||
}
|
||||
|
||||
H264FileSizeSection::H264FileSizeSection(QWidget *parent) :
|
||||
QWidget(parent)
|
||||
{
|
||||
@@ -243,6 +288,12 @@ int64_t H264FileSizeSection::GetFileSize() const
|
||||
return qRound64(file_size_->GetValue() * 1024.0 * 1024.0 * 8.0);
|
||||
}
|
||||
|
||||
void H264FileSizeSection::SetFileSize(int64_t f)
|
||||
{
|
||||
// Convert bits back to megabytes
|
||||
file_size_->SetValue(double(f) / 8.0 / 1024.0 / 1024.0);
|
||||
}
|
||||
|
||||
H265Section::H265Section(QWidget *parent) :
|
||||
H264Section(H264CRFSection::kDefaultH265CRF, parent)
|
||||
{
|
||||
|
||||
@@ -37,6 +37,7 @@ public:
|
||||
H264CRFSection(int default_crf, QWidget* parent = nullptr);
|
||||
|
||||
int GetValue() const;
|
||||
void SetValue(int c);
|
||||
|
||||
static const int kDefaultH264CRF = 18;
|
||||
static const int kDefaultH265CRF = 23;
|
||||
@@ -59,11 +60,13 @@ public:
|
||||
* @brief Get user-selected target bit rate (returns in BITS)
|
||||
*/
|
||||
int64_t GetTargetBitRate() const;
|
||||
void SetTargetBitRate(int64_t b);
|
||||
|
||||
/**
|
||||
* @brief Get user-selected maximum bit rate (returns in BITS)
|
||||
*/
|
||||
int64_t GetMaximumBitRate() const;
|
||||
void SetMaximumBitRate(int64_t b);
|
||||
|
||||
private:
|
||||
FloatSlider* target_rate_;
|
||||
@@ -82,6 +85,7 @@ public:
|
||||
* @brief Returns file size in BITS
|
||||
*/
|
||||
int64_t GetFileSize() const;
|
||||
void SetFileSize(int64_t f);
|
||||
|
||||
private:
|
||||
FloatSlider* file_size_;
|
||||
@@ -103,6 +107,8 @@ public:
|
||||
|
||||
virtual void AddOpts(EncodingParams* params) override;
|
||||
|
||||
virtual void SetOpts(const EncodingParams *p) override;
|
||||
|
||||
private:
|
||||
QStackedWidget* compression_method_stack_;
|
||||
|
||||
|
||||
@@ -39,6 +39,11 @@ public:
|
||||
return image_sequence_checkbox_->isChecked();
|
||||
}
|
||||
|
||||
void SetImageSequenceChecked(bool e)
|
||||
{
|
||||
image_sequence_checkbox_->setChecked(e);
|
||||
}
|
||||
|
||||
void SetTimebase(const rational& r)
|
||||
{
|
||||
frame_slider_->SetTimebase(r);
|
||||
|
||||
+235
-52
@@ -32,8 +32,8 @@
|
||||
|
||||
#include "common/digit.h"
|
||||
#include "common/qtutils.h"
|
||||
#include "core.h"
|
||||
#include "dialog/task/task.h"
|
||||
#include "exportsavepresetdialog.h"
|
||||
#include "node/project/project.h"
|
||||
#include "node/project/sequence/sequence.h"
|
||||
#include "task/taskmanager.h"
|
||||
@@ -42,8 +42,10 @@
|
||||
|
||||
namespace olive {
|
||||
|
||||
#define super QDialog
|
||||
|
||||
ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
super(parent),
|
||||
viewer_node_(viewer_node)
|
||||
{
|
||||
QHBoxLayout* layout = new QHBoxLayout(this);
|
||||
@@ -80,21 +82,21 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
|
||||
QLabel* preset_lbl = new QLabel(tr("Preset:"));
|
||||
preset_lbl->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
|
||||
preferences_layout->addWidget(preset_lbl, row, 0);
|
||||
QComboBox* preset_combobox = new QComboBox();
|
||||
preset_combobox->addItem(tr("Same As Source - High Quality"));
|
||||
preset_combobox->addItem(tr("Same As Source - Medium Quality"));
|
||||
preset_combobox->addItem(tr("Same As Source - Low Quality"));
|
||||
preferences_layout->addWidget(preset_combobox, row, 1);
|
||||
preset_combobox_ = new QComboBox();
|
||||
LoadPresets();
|
||||
connect(preset_combobox_, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &ExportDialog::PresetComboBoxChanged);
|
||||
preferences_layout->addWidget(preset_combobox_, row, 1, 1, 2);
|
||||
|
||||
QPushButton* preset_load_btn = new QPushButton();
|
||||
/*QPushButton* preset_load_btn = new QPushButton();
|
||||
preset_load_btn->setIcon(icon::Open);
|
||||
preset_load_btn->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
|
||||
preferences_layout->addWidget(preset_load_btn, row, 2);
|
||||
preferences_layout->addWidget(preset_load_btn, row, 2);*/
|
||||
|
||||
QPushButton* preset_save_btn = new QPushButton();
|
||||
preset_save_btn->setIcon(icon::Save);
|
||||
preset_save_btn->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
|
||||
preferences_layout->addWidget(preset_save_btn, row, 3);
|
||||
connect(preset_save_btn, &QPushButton::clicked, this, &ExportDialog::SavePreset);
|
||||
|
||||
row++;
|
||||
|
||||
@@ -107,7 +109,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
|
||||
range_combobox_ = new QComboBox();
|
||||
range_combobox_->addItem(tr("Entire Sequence"));
|
||||
range_combobox_->addItem(tr("In to Out"));
|
||||
range_combobox_->setEnabled(viewer_node_->GetTimelinePoints()->workarea()->enabled());
|
||||
range_combobox_->setEnabled(viewer_node_->GetWorkArea()->enabled());
|
||||
|
||||
preferences_layout->addWidget(range_combobox_, row, 1, 1, 3);
|
||||
|
||||
@@ -131,7 +133,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
|
||||
audio_enabled_ = new QCheckBox(tr("Export Audio"));
|
||||
av_enabled_layout->addWidget(audio_enabled_);
|
||||
|
||||
subtitles_enabled_ = new QCheckBox(tr("Export Subtitle"));
|
||||
subtitles_enabled_ = new QCheckBox(tr("Export Subtitles"));
|
||||
av_enabled_layout->addWidget(subtitles_enabled_);
|
||||
|
||||
preferences_layout->addLayout(av_enabled_layout, row, 0, 1, 4);
|
||||
@@ -182,6 +184,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
|
||||
QVBoxLayout* preview_layout = new QVBoxLayout(preview_area);
|
||||
preview_layout->addWidget(new QLabel(tr("Preview")));
|
||||
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_);
|
||||
@@ -195,25 +198,9 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
|
||||
|
||||
// Set defaults
|
||||
previously_selected_format_ = ExportFormat::kFormatMPEG4Video;
|
||||
format_combobox_->SetFormat(ExportFormat::kFormatMPEG4Video);
|
||||
connect(format_combobox_, &ExportFormatComboBox::FormatChanged, this, &ExportDialog::FormatChanged);
|
||||
FormatChanged(format_combobox_->GetFormat());
|
||||
|
||||
VideoParams vp = viewer_node_->GetVideoParams();
|
||||
AudioParams ap = viewer_node_->GetAudioParams();
|
||||
|
||||
video_tab_->width_slider()->SetValue(vp.width());
|
||||
video_tab_->width_slider()->SetDefaultValue(vp.width());
|
||||
video_tab_->height_slider()->SetValue(vp.height());
|
||||
video_tab_->height_slider()->SetDefaultValue(vp.height());
|
||||
video_tab_->SetSelectedFrameRate(vp.frame_rate());
|
||||
video_tab_->pixel_aspect_combobox()->SetPixelAspectRatio(vp.pixel_aspect_ratio());
|
||||
video_tab_->pixel_format_field()->SetPixelFormat(static_cast<VideoParams::Format>(OLIVE_CONFIG("OnlinePixelFormat").toInt()));
|
||||
video_tab_->interlaced_combobox()->SetInterlaceMode(vp.interlacing());
|
||||
audio_tab_->sample_rate_combobox()->SetSampleRate(ap.sample_rate());
|
||||
audio_tab_->sample_format_combobox()->SetAttemptToRestoreFormat(false);
|
||||
audio_tab_->channel_layout_combobox()->SetChannelLayout(ap.channel_layout());
|
||||
|
||||
video_aspect_ratio_ = static_cast<double>(vp.width()) / static_cast<double>(vp.height());
|
||||
|
||||
connect(video_tab_->width_slider(),
|
||||
@@ -245,11 +232,31 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
|
||||
this,
|
||||
&ExportDialog::ImageSequenceCheckBoxChanged);
|
||||
|
||||
// Set viewer to view the node
|
||||
// We don't check if the codec supports subtitles because we can always export to a sidecar file
|
||||
bool has_subtitle_tracks = SequenceHasSubtitles();
|
||||
connect(subtitles_enabled_, &QCheckBox::toggled, subtitle_tab_, &QWidget::setEnabled);
|
||||
subtitles_enabled_->setEnabled(has_subtitle_tracks);
|
||||
|
||||
// If the viewer already has cached params, use them
|
||||
if (viewer_node_->GetLastUsedEncodingParams().IsValid()) {
|
||||
SetParams(viewer_node_->GetLastUsedEncodingParams());
|
||||
} else {
|
||||
SetDefaults();
|
||||
}
|
||||
|
||||
// Set viewer to view the node and set its colorspace
|
||||
preview_viewer_->ConnectViewerNode(viewer_node_);
|
||||
preview_viewer_->ruler()->ConnectTimelinePoints(viewer_node_->GetTimelinePoints());
|
||||
preview_viewer_->SetColorMenuEnabled(false);
|
||||
preview_viewer_->SetColorTransform(video_tab_->CurrentOCIOColorSpace());
|
||||
|
||||
qApp->installEventFilter(this);
|
||||
|
||||
connect(video_enabled_, &QCheckBox::toggled, video_tab_, &QWidget::setEnabled);
|
||||
video_tab_->setEnabled(video_enabled_->isChecked());
|
||||
connect(audio_enabled_, &QCheckBox::toggled, audio_tab_, &QWidget::setEnabled);
|
||||
audio_tab_->setEnabled(audio_enabled_->isChecked());
|
||||
connect(subtitles_enabled_, &QCheckBox::toggled, subtitle_tab_, &QWidget::setEnabled);
|
||||
subtitle_tab_->setEnabled(subtitles_enabled_->isChecked());
|
||||
}
|
||||
|
||||
rational ExportDialog::GetSelectedTimebase() const
|
||||
@@ -257,10 +264,15 @@ rational ExportDialog::GetSelectedTimebase() const
|
||||
return video_tab_->GetSelectedFrameRate().flipped();
|
||||
}
|
||||
|
||||
void ExportDialog::SetSelectedTimebase(const rational &r)
|
||||
{
|
||||
video_tab_->SetSelectedFrameRate(r.flipped());
|
||||
}
|
||||
|
||||
void ExportDialog::StartExport()
|
||||
{
|
||||
if (!video_enabled_->isChecked() && !audio_enabled_->isChecked() && !subtitles_enabled_->isChecked()) {
|
||||
QtUtils::MessageBox(this, QMessageBox::Critical, tr("Invalid parameters"),
|
||||
QtUtils::MsgBox(this, QMessageBox::Critical, tr("Invalid parameters"),
|
||||
tr("Video, audio, and subtitles are disabled. There's nothing to export."));
|
||||
return;
|
||||
}
|
||||
@@ -272,7 +284,7 @@ void ExportDialog::StartExport()
|
||||
|
||||
// If it doesn't, see if the user wants to append it automatically. If not, we don't abort the export.
|
||||
if (!proposed_filename.endsWith(necessary_ext, Qt::CaseInsensitive)) {
|
||||
if (QtUtils::MessageBox(this, QMessageBox::Warning, tr("Invalid filename"),
|
||||
if (QtUtils::MsgBox(this, QMessageBox::Warning, tr("Invalid filename"),
|
||||
tr("The filename must contain the extension \"%1\". Would you like to append it "
|
||||
"automatically?").arg(necessary_ext),
|
||||
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
|
||||
@@ -289,7 +301,7 @@ void ExportDialog::StartExport()
|
||||
// If the directory does not exist, try to create it
|
||||
QDir dest_dir(file_info.path());
|
||||
if (!FileFunctions::DirectoryIsValid(dest_dir)) {
|
||||
QtUtils::MessageBox(this, QMessageBox::Critical, tr("Failed to create output directory"),
|
||||
QtUtils::MsgBox(this, QMessageBox::Critical, tr("Failed to create output directory"),
|
||||
tr("The intended output directory doesn't exist and Olive couldn't create it. "
|
||||
"Please choose a different filename."));
|
||||
return;
|
||||
@@ -299,7 +311,7 @@ void ExportDialog::StartExport()
|
||||
if (video_tab_->IsImageSequenceSet()) {
|
||||
// Ensure filename contains digits
|
||||
if (!Encoder::FilenameContainsDigitPlaceholder(proposed_filename)) {
|
||||
QtUtils::MessageBox(this, QMessageBox::Critical, tr("Invalid filename"),
|
||||
QtUtils::MsgBox(this, QMessageBox::Critical, tr("Invalid filename"),
|
||||
tr("Export is set to an image sequence, but the filename does not have a section for digits "
|
||||
"(formatted as [#####] where the amount of # is the amount of digits)."));
|
||||
return;
|
||||
@@ -309,7 +321,7 @@ void ExportDialog::StartExport()
|
||||
int64_t needed_digit_count = GetDigitCount(frame_count);
|
||||
int current_digit_count = Encoder::GetImageSequencePlaceholderDigitCount(proposed_filename);
|
||||
if (current_digit_count < needed_digit_count) {
|
||||
QtUtils::MessageBox(this, QMessageBox::Critical, tr("Invalid filename"),
|
||||
QtUtils::MsgBox(this, QMessageBox::Critical, tr("Invalid filename"),
|
||||
tr("Filename doesn't contain enough digits for the amount of frames "
|
||||
"this export will need (need %1 for %n frame(s)).", nullptr, frame_count)
|
||||
.arg(QString::number(needed_digit_count)));
|
||||
@@ -319,7 +331,7 @@ void ExportDialog::StartExport()
|
||||
|
||||
// Validate if the file exists and whether the user wishes to overwrite it
|
||||
if (file_info.exists()) {
|
||||
if (QtUtils::MessageBox(this, QMessageBox::Warning, tr("Confirm Overwrite"),
|
||||
if (QtUtils::MsgBox(this, QMessageBox::Warning, tr("Confirm Overwrite"),
|
||||
tr("The file \"%1\" already exists. Do you want to overwrite it?")
|
||||
.arg(proposed_filename),
|
||||
QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) {
|
||||
@@ -331,7 +343,7 @@ void ExportDialog::StartExport()
|
||||
if (video_enabled_->isChecked()
|
||||
&& (video_tab_->GetSelectedCodec() == ExportCodec::kCodecH264 || video_tab_->GetSelectedCodec() == ExportCodec::kCodecH265)
|
||||
&& (video_tab_->width_slider()->GetValue()%2 != 0 || video_tab_->height_slider()->GetValue()%2 != 0)) {
|
||||
QtUtils::MessageBox(this, QMessageBox::Critical, tr("Invalid Parameters"),
|
||||
QtUtils::MsgBox(this, QMessageBox::Critical, tr("Invalid Parameters"),
|
||||
tr("Width and height must be multiples of 2."));
|
||||
return;
|
||||
}
|
||||
@@ -385,11 +397,27 @@ void ExportDialog::ImageSequenceCheckBoxChanged(bool e)
|
||||
filename_edit_->setText(current_fileinfo.dir().filePath(basename));
|
||||
}
|
||||
|
||||
void ExportDialog::closeEvent(QCloseEvent *e)
|
||||
void ExportDialog::SavePreset()
|
||||
{
|
||||
preview_viewer_->ConnectViewerNode(nullptr);
|
||||
ExportSavePresetDialog d(GenerateParams(), this);
|
||||
if (d.exec() == QDialog::Accepted) {
|
||||
LoadPresets();
|
||||
preset_combobox_->setCurrentText(d.GetSelectedPresetName());
|
||||
}
|
||||
}
|
||||
|
||||
QDialog::closeEvent(e);
|
||||
void ExportDialog::PresetComboBoxChanged()
|
||||
{
|
||||
QComboBox *c = static_cast<QComboBox *>(sender());
|
||||
|
||||
int preset_number = c->currentData().toInt();
|
||||
if (preset_number == kPresetDefault) {
|
||||
SetDefaults();
|
||||
} else if (preset_number == kPresetLastUsed) {
|
||||
SetParams(viewer_node_->GetLastUsedEncodingParams());
|
||||
} else {
|
||||
SetParams(presets_.at(preset_number));
|
||||
}
|
||||
}
|
||||
|
||||
void ExportDialog::AddPreferencesTab(QWidget *inner_widget, const QString &title)
|
||||
@@ -445,9 +473,9 @@ void ExportDialog::FormatChanged(ExportFormat::Format current_format)
|
||||
audio_enabled_->setChecked(has_audio_codecs);
|
||||
audio_enabled_->setEnabled(has_audio_codecs);
|
||||
|
||||
bool has_subtitle_codecs = subtitle_tab_->SetFormat(current_format);
|
||||
subtitles_enabled_->setChecked(has_subtitle_codecs);
|
||||
subtitles_enabled_->setEnabled(has_subtitle_codecs);
|
||||
if (subtitles_enabled_->isEnabled()) {
|
||||
subtitle_tab_->SetFormat(current_format);
|
||||
}
|
||||
}
|
||||
|
||||
void ExportDialog::ResolutionChanged()
|
||||
@@ -484,7 +512,32 @@ void ExportDialog::ResolutionChanged()
|
||||
|
||||
void ExportDialog::LoadPresets()
|
||||
{
|
||||
preset_combobox_->clear();
|
||||
presets_.clear();
|
||||
|
||||
preset_combobox_->addItem(tr("Default"), kPresetDefault);
|
||||
|
||||
if (viewer_node_->GetLastUsedEncodingParams().IsValid()) {
|
||||
preset_combobox_->addItem(tr("Last Used"), kPresetLastUsed);
|
||||
}
|
||||
|
||||
preset_combobox_->insertSeparator(preset_combobox_->count());
|
||||
|
||||
QStringList l = EncodingParams::GetListOfPresets();
|
||||
presets_.reserve(l.size());
|
||||
|
||||
for (const QString &preset : l) {
|
||||
EncodingParams p;
|
||||
|
||||
QFile f(EncodingParams::GetPresetPath().filePath(preset));
|
||||
if (f.open(QFile::ReadOnly)) {
|
||||
if (p.Load(&f)) {
|
||||
preset_combobox_->addItem(preset, int(presets_.size()));
|
||||
presets_.push_back(p);
|
||||
}
|
||||
f.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ExportDialog::SetDefaultFilename()
|
||||
@@ -503,7 +556,43 @@ void ExportDialog::SetDefaultFilename()
|
||||
filename_edit_->setText(file_location);
|
||||
}
|
||||
|
||||
ExportParams ExportDialog::GenerateParams() const
|
||||
bool ExportDialog::SequenceHasSubtitles() const
|
||||
{
|
||||
if (Sequence *s = dynamic_cast<Sequence*>(viewer_node_)) {
|
||||
TrackList *tl = s->track_list(Track::kSubtitle);
|
||||
for (Track *t : tl->GetTracks()) {
|
||||
if (!t->IsMuted() && !t->Blocks().empty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ExportDialog::SetDefaults()
|
||||
{
|
||||
format_combobox_->SetFormat(ExportFormat::kFormatMPEG4Video);
|
||||
FormatChanged(format_combobox_->GetFormat());
|
||||
|
||||
VideoParams vp = viewer_node_->GetVideoParams();
|
||||
AudioParams ap = viewer_node_->GetAudioParams();
|
||||
|
||||
video_tab_->width_slider()->SetValue(vp.width());
|
||||
video_tab_->width_slider()->SetDefaultValue(vp.width());
|
||||
video_tab_->height_slider()->SetValue(vp.height());
|
||||
video_tab_->height_slider()->SetDefaultValue(vp.height());
|
||||
video_tab_->SetSelectedFrameRate(vp.frame_rate());
|
||||
video_tab_->pixel_aspect_combobox()->SetPixelAspectRatio(vp.pixel_aspect_ratio());
|
||||
video_tab_->pixel_format_field()->SetPixelFormat(static_cast<VideoParams::Format>(OLIVE_CONFIG("OnlinePixelFormat").toInt()));
|
||||
video_tab_->interlaced_combobox()->SetInterlaceMode(vp.interlacing());
|
||||
audio_tab_->sample_rate_combobox()->SetSampleRate(ap.sample_rate());
|
||||
audio_tab_->sample_format_combobox()->SetAttemptToRestoreFormat(false);
|
||||
audio_tab_->channel_layout_combobox()->SetChannelLayout(ap.channel_layout());
|
||||
subtitles_enabled_->setChecked(SequenceHasSubtitles());
|
||||
}
|
||||
|
||||
EncodingParams ExportDialog::GenerateParams() const
|
||||
{
|
||||
VideoParams video_render_params(static_cast<int>(video_tab_->width_slider()->GetValue()),
|
||||
static_cast<int>(video_tab_->height_slider()->GetValue()),
|
||||
@@ -518,8 +607,8 @@ ExportParams ExportDialog::GenerateParams() const
|
||||
audio_tab_->channel_layout_combobox()->GetChannelLayout(),
|
||||
audio_tab_->sample_format_combobox()->GetSampleFormat());
|
||||
|
||||
ExportParams params;
|
||||
params.set_encoder(Encoder::GetTypeFromFormat(format_combobox_->GetFormat()));
|
||||
EncodingParams params;
|
||||
params.set_format(format_combobox_->GetFormat());
|
||||
params.SetFilename(filename_edit_->text().trimmed());
|
||||
params.SetExportLength(viewer_node_->GetLength());
|
||||
|
||||
@@ -529,15 +618,18 @@ ExportParams ExportDialog::GenerateParams() const
|
||||
params.set_custom_range(TimeRange(export_time, export_time + GetSelectedTimebase()));
|
||||
} else if (range_combobox_->currentIndex() == kRangeInToOut) {
|
||||
// Assume if this combobox is enabled, workarea is enabled - a check that we make in this dialog's constructor
|
||||
params.set_custom_range(viewer_node_->GetTimelinePoints()->workarea()->range());
|
||||
params.set_custom_range(viewer_node_->GetWorkArea()->range());
|
||||
}
|
||||
|
||||
if (video_tab_->scaling_method_combobox()->isEnabled()) {
|
||||
params.set_video_scaling_method(static_cast<ExportParams::VideoScalingMethod>(video_tab_->scaling_method_combobox()->currentData().toInt()));
|
||||
params.set_video_scaling_method(static_cast<EncodingParams::VideoScalingMethod>(video_tab_->scaling_method_combobox()->currentData().toInt()));
|
||||
}
|
||||
|
||||
if (video_enabled_->isChecked()) {
|
||||
ExportCodec::Codec video_codec = video_tab_->GetSelectedCodec();
|
||||
|
||||
video_render_params.set_color_range(video_tab_->color_range());
|
||||
|
||||
params.EnableVideo(video_render_params, video_codec);
|
||||
|
||||
params.set_video_threads(video_tab_->threads());
|
||||
@@ -560,17 +652,108 @@ ExportParams ExportDialog::GenerateParams() const
|
||||
params.set_audio_bit_rate(audio_tab_->bit_rate_slider()->GetValue() * 1000);
|
||||
}
|
||||
|
||||
if (subtitles_enabled_->isChecked()) {
|
||||
params.EnableSubtitles(subtitle_tab_->GetSubtitleCodec());
|
||||
if (subtitles_enabled_->isEnabled()
|
||||
&& subtitles_enabled_->isChecked()) {
|
||||
if (!subtitle_tab_->GetSidecarEnabled()) {
|
||||
// Export subtitles embedded in container
|
||||
params.EnableSubtitles(subtitle_tab_->GetSubtitleCodec());
|
||||
} else {
|
||||
// Export subtitles to a sidecar file
|
||||
params.EnableSidecarSubtitles(subtitle_tab_->GetSidecarFormat(), subtitle_tab_->GetSubtitleCodec());
|
||||
}
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
void ExportDialog::SetParams(const EncodingParams &e)
|
||||
{
|
||||
format_combobox_->SetFormat(e.format());
|
||||
|
||||
if (e.has_custom_range() && viewer_node_->GetWorkArea()->enabled()) {
|
||||
range_combobox_->setCurrentIndex(kRangeInToOut);
|
||||
}
|
||||
|
||||
QtUtils::SetComboBoxData(video_tab_->scaling_method_combobox(), e.video_scaling_method());
|
||||
|
||||
video_enabled_->setChecked(e.video_enabled());
|
||||
if (e.video_enabled()) {
|
||||
video_tab_->width_slider()->SetValue(e.video_params().width());
|
||||
video_tab_->height_slider()->SetValue(e.video_params().height());
|
||||
SetSelectedTimebase(e.video_params().time_base());
|
||||
video_tab_->pixel_format_field()->SetPixelFormat(e.video_params().format());
|
||||
video_tab_->pixel_aspect_combobox()->SetPixelAspectRatio(e.video_params().pixel_aspect_ratio());
|
||||
video_tab_->interlaced_combobox()->SetInterlaceMode(e.video_params().interlacing());
|
||||
|
||||
video_tab_->SetSelectedCodec(e.video_codec());
|
||||
|
||||
video_tab_->SetColorRange(e.video_params().color_range());
|
||||
|
||||
video_tab_->SetThreads(e.video_threads());
|
||||
|
||||
if (video_tab_->isVisible()) {
|
||||
video_tab_->GetCodecSection()->SetOpts(&e);
|
||||
}
|
||||
|
||||
video_tab_->SetOCIOColorSpace(e.color_transform().output());
|
||||
|
||||
video_tab_->SetPixFmt(e.video_pix_fmt());
|
||||
|
||||
video_tab_->SetImageSequence(e.video_is_image_sequence());
|
||||
}
|
||||
|
||||
audio_enabled_->setChecked(e.audio_enabled());
|
||||
if (e.audio_enabled()) {
|
||||
audio_tab_->sample_rate_combobox()->SetSampleRate(e.audio_params().sample_rate());
|
||||
audio_tab_->channel_layout_combobox()->SetChannelLayout(e.audio_params().channel_layout());
|
||||
audio_tab_->sample_format_combobox()->SetSampleFormat(e.audio_params().format());
|
||||
|
||||
audio_tab_->SetCodec(e.audio_codec());
|
||||
|
||||
audio_tab_->bit_rate_slider()->SetValue(e.audio_bit_rate() / 1000);
|
||||
}
|
||||
|
||||
if (subtitles_enabled_->isEnabled()) {
|
||||
subtitles_enabled_->setChecked(e.subtitles_enabled());
|
||||
subtitle_tab_->SetSidecarEnabled(e.subtitles_are_sidecar());
|
||||
if (e.subtitles_enabled()) {
|
||||
subtitle_tab_->SetSubtitleCodec(e.subtitles_codec());
|
||||
if (e.subtitles_are_sidecar()) {
|
||||
subtitle_tab_->SetSidecarFormat(e.subtitle_sidecar_fmt());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ExportDialog::eventFilter(QObject *o, QEvent *e)
|
||||
{
|
||||
// Any parameters in scrollable areas, ignore wheel events so the user doesn't unwittingly change
|
||||
// them while trying to scroll through the pages
|
||||
if (e->type() == QEvent::Wheel) {
|
||||
while ((o = o->parent())) {
|
||||
if (o == video_tab_ || o == audio_tab_ || o == subtitle_tab_) {
|
||||
e->ignore();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return super::eventFilter(o, e);
|
||||
}
|
||||
|
||||
void ExportDialog::done(int r)
|
||||
{
|
||||
preview_viewer_->ConnectViewerNode(nullptr);
|
||||
|
||||
viewer_node_->SetLastUsedEncodingParams(GenerateParams());
|
||||
|
||||
super::done(r);
|
||||
}
|
||||
|
||||
rational ExportDialog::GetExportLength() const
|
||||
{
|
||||
if (range_combobox_->currentIndex() == kRangeInToOut) {
|
||||
return viewer_node_->GetTimelinePoints()->workarea()->range().length();
|
||||
return viewer_node_->GetWorkArea()->range().length();
|
||||
} else {
|
||||
return viewer_node_->GetLength();
|
||||
}
|
||||
@@ -588,8 +771,8 @@ void ExportDialog::UpdateViewerDimensions()
|
||||
|
||||
VideoParams vp = viewer_node_->GetVideoParams();
|
||||
|
||||
QMatrix4x4 transform = ExportParams::GenerateMatrix(
|
||||
static_cast<ExportParams::VideoScalingMethod>(video_tab_->scaling_method_combobox()->currentData().toInt()),
|
||||
QMatrix4x4 transform = EncodingParams::GenerateMatrix(
|
||||
static_cast<EncodingParams::VideoScalingMethod>(video_tab_->scaling_method_combobox()->currentData().toInt()),
|
||||
vp.width(),
|
||||
vp.height(),
|
||||
static_cast<int>(video_tab_->width_slider()->GetValue()),
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include "exportsubtitlestab.h"
|
||||
#include "exportvideotab.h"
|
||||
#include "task/export/export.h"
|
||||
#include "widget/nodeparamview/nodeparamviewwidgetbridge.h"
|
||||
#include "widget/viewer/viewer.h"
|
||||
|
||||
namespace olive {
|
||||
@@ -45,6 +46,7 @@ public:
|
||||
ExportDialog(ViewerOutput* viewer_node, QWidget* parent = nullptr);
|
||||
|
||||
rational GetSelectedTimebase() const;
|
||||
void SetSelectedTimebase(const rational &r);
|
||||
|
||||
void SetTime(const rational &time)
|
||||
{
|
||||
@@ -54,8 +56,13 @@ public:
|
||||
preview_viewer_->SetAudioScrubbingEnabled(true);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void closeEvent(QCloseEvent *e) override;
|
||||
EncodingParams GenerateParams() const;
|
||||
void SetParams(const EncodingParams &e);
|
||||
|
||||
virtual bool eventFilter(QObject *o, QEvent *e) override;
|
||||
|
||||
public slots:
|
||||
virtual void done(int r) override;
|
||||
|
||||
private:
|
||||
void AddPreferencesTab(QWidget *inner_widget, const QString &title);
|
||||
@@ -63,7 +70,9 @@ private:
|
||||
void LoadPresets();
|
||||
void SetDefaultFilename();
|
||||
|
||||
ExportParams GenerateParams() const;
|
||||
bool SequenceHasSubtitles() const;
|
||||
|
||||
void SetDefaults();
|
||||
|
||||
ViewerOutput* viewer_node_;
|
||||
|
||||
@@ -77,9 +86,16 @@ private:
|
||||
kRangeInToOut
|
||||
};
|
||||
|
||||
enum AutoPreset {
|
||||
kPresetDefault = -1,
|
||||
kPresetLastUsed = -2,
|
||||
};
|
||||
|
||||
QTabWidget* preferences_tabs_;
|
||||
|
||||
QComboBox* preset_combobox_;
|
||||
QComboBox* range_combobox_;
|
||||
std::vector<EncodingParams> presets_;
|
||||
|
||||
QCheckBox* video_enabled_;
|
||||
QCheckBox* audio_enabled_;
|
||||
@@ -115,6 +131,10 @@ private slots:
|
||||
|
||||
void ImageSequenceCheckBoxChanged(bool e);
|
||||
|
||||
void SavePreset();
|
||||
|
||||
void PresetComboBoxChanged();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -31,6 +31,12 @@ ExportAdvancedVideoDialog::ExportAdvancedVideoDialog(const QList<QString> &pix_f
|
||||
pixel_layout->addWidget(pixel_format_combobox_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
pixel_layout->addWidget(new QLabel(tr("YUV Color Range:")), row, 0);
|
||||
|
||||
yuv_color_range_combobox_ = new QComboBox();
|
||||
yuv_color_range_combobox_->addItems({tr("Limited (16-235)"), tr("Full (0-255)")});
|
||||
pixel_layout->addWidget(yuv_color_range_combobox_, row, 1);
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <QComboBox>
|
||||
#include <QDialog>
|
||||
|
||||
#include "codec/encoder.h"
|
||||
#include "widget/slider/integerslider.h"
|
||||
|
||||
namespace olive {
|
||||
@@ -35,11 +36,23 @@ public:
|
||||
pixel_format_combobox_->setCurrentText(s);
|
||||
}
|
||||
|
||||
VideoParams::ColorRange yuv_range() const
|
||||
{
|
||||
return static_cast<VideoParams::ColorRange>(yuv_color_range_combobox_->currentIndex());
|
||||
}
|
||||
|
||||
void set_yuv_range(VideoParams::ColorRange i)
|
||||
{
|
||||
yuv_color_range_combobox_->setCurrentIndex(i);
|
||||
}
|
||||
|
||||
private:
|
||||
IntegerSlider* thread_slider_;
|
||||
|
||||
QComboBox* pixel_format_combobox_;
|
||||
|
||||
QComboBox* yuv_color_range_combobox_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -46,6 +46,13 @@ ExportFormatComboBox::ExportFormatComboBox(Mode mode, QWidget *parent) :
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case kShowSubtitlesOnly:
|
||||
if (!ExportFormat::GetVideoCodecs(f).isEmpty()
|
||||
|| ExportFormat::GetSubtitleCodecs(f).isEmpty()
|
||||
|| !ExportFormat::GetAudioCodecs(f).isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
QString format_name = ExportFormat::GetName(f);
|
||||
|
||||
@@ -34,7 +34,8 @@ public:
|
||||
enum Mode {
|
||||
kShowAllFormats,
|
||||
kShowAudioOnly,
|
||||
kShowVideoOnly
|
||||
kShowVideoOnly,
|
||||
kShowSubtitlesOnly
|
||||
};
|
||||
|
||||
ExportFormatComboBox(Mode mode, QWidget *parent = nullptr);
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/***
|
||||
|
||||
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 "exportsavepresetdialog.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace olive {
|
||||
|
||||
ExportSavePresetDialog::ExportSavePresetDialog(const EncodingParams &p, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
params_(p)
|
||||
{
|
||||
auto layout = new QVBoxLayout(this);
|
||||
|
||||
name_edit_ = new QLineEdit();
|
||||
|
||||
// Populate existing list
|
||||
QStringList l = EncodingParams::GetListOfPresets();
|
||||
if (!l.empty()) {
|
||||
auto list_widget_ = new QListWidget();
|
||||
for (const QString &f : l) {
|
||||
list_widget_->addItem(f);
|
||||
}
|
||||
connect(list_widget_, &QListWidget::currentTextChanged, name_edit_, &QLineEdit::setText);
|
||||
layout->addWidget(list_widget_);
|
||||
}
|
||||
|
||||
auto name_layout = new QHBoxLayout();
|
||||
layout->addLayout(name_layout);
|
||||
|
||||
name_layout->addWidget(new QLabel(tr("Name:")));
|
||||
|
||||
name_edit_->setFocus();
|
||||
name_layout->addWidget(name_edit_);
|
||||
|
||||
auto btns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
connect(btns, &QDialogButtonBox::accepted, this, &ExportSavePresetDialog::accept);
|
||||
connect(btns, &QDialogButtonBox::rejected, this, &ExportSavePresetDialog::reject);
|
||||
layout->addWidget(btns);
|
||||
|
||||
setWindowTitle(tr("Save Export Preset"));
|
||||
}
|
||||
|
||||
void ExportSavePresetDialog::accept()
|
||||
{
|
||||
if (name_edit_->text().isEmpty()) {
|
||||
QMessageBox::critical(this, tr("Invalid Name"), tr("You must enter a name to save an export preset."));
|
||||
return;
|
||||
}
|
||||
|
||||
QDir d(EncodingParams::GetPresetPath());
|
||||
if (!d.exists()) {
|
||||
d.mkpath(QStringLiteral("."));
|
||||
}
|
||||
|
||||
QFile f(d.filePath(name_edit_->text()));
|
||||
if (f.exists()) {
|
||||
if (QMessageBox::question(this, tr("Overwrite Preset"), tr("A preset with the name \"%1\" already exists. Do you wish to overwrite it?").arg(name_edit_->text()),
|
||||
QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!f.open(QFile::WriteOnly)) {
|
||||
QMessageBox::critical(this, tr("Write Error"), tr("Failed to open file \"%1\" for writing.").arg(f.fileName()));
|
||||
return;
|
||||
}
|
||||
|
||||
params_.Save(&f);
|
||||
|
||||
f.close();
|
||||
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,36 +18,38 @@
|
||||
|
||||
***/
|
||||
|
||||
#ifndef TIMELINEPOINTS_H
|
||||
#define TIMELINEPOINTS_H
|
||||
#ifndef EXPORTSAVEPRESETDIALOG_H
|
||||
#define EXPORTSAVEPRESETDIALOG_H
|
||||
|
||||
#include <QXmlStreamReader>
|
||||
#include <QXmlStreamWriter>
|
||||
#include <QDialog>
|
||||
#include <QLineEdit>
|
||||
#include <QListWidget>
|
||||
|
||||
#include "timelinemarker.h"
|
||||
#include "timelineworkarea.h"
|
||||
#include "codec/encoder.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class TimelinePoints : public QObject
|
||||
class ExportSavePresetDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
TimelinePoints(QObject *parent = nullptr);
|
||||
ExportSavePresetDialog(const EncodingParams &p, QWidget *parent = nullptr);
|
||||
|
||||
TimelineMarkerList* markers();
|
||||
const TimelineMarkerList* markers() const;
|
||||
QString GetSelectedPresetName() const
|
||||
{
|
||||
return name_edit_->text();
|
||||
}
|
||||
|
||||
TimelineWorkArea* workarea();
|
||||
const TimelineWorkArea* workarea() const;
|
||||
public slots:
|
||||
virtual void accept() override;
|
||||
|
||||
private:
|
||||
TimelineMarkerList *markers_;
|
||||
QLineEdit *name_edit_;
|
||||
|
||||
TimelineWorkArea *workarea_;
|
||||
EncodingParams params_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // TIMELINEPOINTS_H
|
||||
#endif // EXPORTSAVEPRESETDIALOG_H
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "exportsubtitlestab.h"
|
||||
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -15,22 +14,46 @@ ExportSubtitlesTab::ExportSubtitlesTab(QWidget *parent) :
|
||||
|
||||
int row = 0;
|
||||
|
||||
sidecar_checkbox_ = new QCheckBox(tr("Export to sidecar file"));
|
||||
layout->addWidget(sidecar_checkbox_, row, 0, 1, 2);
|
||||
|
||||
row++;
|
||||
|
||||
sidecar_format_label_ = new QLabel(tr("Sidecar Format:"));
|
||||
sidecar_format_label_->setVisible(false);
|
||||
layout->addWidget(sidecar_format_label_, row, 0);
|
||||
|
||||
sidecar_format_combobox_ = new ExportFormatComboBox(ExportFormatComboBox::kShowSubtitlesOnly);
|
||||
sidecar_format_combobox_->setVisible(false);
|
||||
layout->addWidget(sidecar_format_combobox_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Codec:")), row, 0);
|
||||
|
||||
codec_combobox_ = new QComboBox();
|
||||
layout->addWidget(codec_combobox_, row, 1);
|
||||
|
||||
outer_layout->addStretch();
|
||||
|
||||
connect(sidecar_checkbox_, &QCheckBox::toggled, sidecar_format_label_, &QWidget::setVisible);
|
||||
connect(sidecar_checkbox_, &QCheckBox::toggled, sidecar_format_combobox_, &QWidget::setVisible);
|
||||
}
|
||||
|
||||
int ExportSubtitlesTab::SetFormat(ExportFormat::Format format)
|
||||
{
|
||||
auto scodecs = ExportFormat::GetSubtitleCodecs(format);
|
||||
setEnabled(!scodecs.isEmpty());
|
||||
|
||||
sidecar_checkbox_->setChecked(scodecs.empty());
|
||||
sidecar_checkbox_->setEnabled(!scodecs.empty());
|
||||
|
||||
scodecs = ExportFormat::GetSubtitleCodecs(sidecar_format_combobox_->GetFormat());
|
||||
|
||||
codec_combobox_->clear();
|
||||
foreach (ExportCodec::Codec scodec, scodecs) {
|
||||
codec_combobox_->addItem(ExportCodec::GetCodecName(scodec), scodec);
|
||||
}
|
||||
|
||||
return scodecs.size();
|
||||
}
|
||||
|
||||
|
||||
@@ -21,10 +21,13 @@
|
||||
#ifndef EXPORTSUBTITLESTAB_H
|
||||
#define EXPORTSUBTITLESTAB_H
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QLabel>
|
||||
|
||||
#include "codec/exportformat.h"
|
||||
#include "render/subtitleparams.h"
|
||||
#include "common/qtutils.h"
|
||||
#include "dialog/export/exportformatcombobox.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -34,6 +37,12 @@ class ExportSubtitlesTab : public QWidget
|
||||
public:
|
||||
ExportSubtitlesTab(QWidget *parent = nullptr);
|
||||
|
||||
bool GetSidecarEnabled() const { return sidecar_checkbox_->isChecked(); }
|
||||
void SetSidecarEnabled(bool e) { sidecar_checkbox_->setEnabled(e); }
|
||||
|
||||
ExportFormat::Format GetSidecarFormat() const { return sidecar_format_combobox_->GetFormat(); }
|
||||
void SetSidecarFormat(ExportFormat::Format f) { sidecar_format_combobox_->SetFormat(f); }
|
||||
|
||||
int SetFormat(ExportFormat::Format format);
|
||||
|
||||
ExportCodec::Codec GetSubtitleCodec()
|
||||
@@ -41,7 +50,17 @@ public:
|
||||
return static_cast<ExportCodec::Codec>(codec_combobox_->currentData().toInt());
|
||||
}
|
||||
|
||||
void SetSubtitleCodec(ExportCodec::Codec c)
|
||||
{
|
||||
QtUtils::SetComboBoxData(codec_combobox_, c);
|
||||
}
|
||||
|
||||
private:
|
||||
QCheckBox *sidecar_checkbox_;
|
||||
|
||||
QLabel *sidecar_format_label_;
|
||||
ExportFormatComboBox *sidecar_format_combobox_;
|
||||
|
||||
QComboBox *codec_combobox_;
|
||||
|
||||
};
|
||||
|
||||
@@ -29,14 +29,14 @@
|
||||
#include "core.h"
|
||||
#include "exportadvancedvideodialog.h"
|
||||
#include "node/color/colormanager/colormanager.h"
|
||||
#include "task/export/exportparams.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
ExportVideoTab::ExportVideoTab(ColorManager* color_manager, QWidget *parent) :
|
||||
QWidget(parent),
|
||||
color_manager_(color_manager),
|
||||
threads_(0)
|
||||
threads_(0),
|
||||
color_range_(VideoParams::kColorRangeDefault)
|
||||
{
|
||||
QVBoxLayout* outer_layout = new QVBoxLayout(this);
|
||||
|
||||
@@ -69,6 +69,13 @@ bool ExportVideoTab::IsImageSequenceSet() const
|
||||
return (img_section && img_section->IsImageSequenceChecked());
|
||||
}
|
||||
|
||||
void ExportVideoTab::SetImageSequence(bool e) const
|
||||
{
|
||||
if (ImageSection* img_section = dynamic_cast<ImageSection*>(codec_stack_->currentWidget())) {
|
||||
img_section->SetImageSequenceChecked(e);
|
||||
}
|
||||
}
|
||||
|
||||
QWidget* ExportVideoTab::SetupResolutionSection()
|
||||
{
|
||||
int row = 0;
|
||||
@@ -106,9 +113,9 @@ QWidget* ExportVideoTab::SetupResolutionSection()
|
||||
|
||||
scaling_method_combobox_ = new QComboBox();
|
||||
scaling_method_combobox_->setEnabled(false);
|
||||
scaling_method_combobox_->addItem(tr("Fit"), ExportParams::kFit);
|
||||
scaling_method_combobox_->addItem(tr("Stretch"), ExportParams::kStretch);
|
||||
scaling_method_combobox_->addItem(tr("Crop"), ExportParams::kCrop);
|
||||
scaling_method_combobox_->addItem(tr("Fit"), EncodingParams::kFit);
|
||||
scaling_method_combobox_->addItem(tr("Stretch"), EncodingParams::kStretch);
|
||||
scaling_method_combobox_->addItem(tr("Crop"), EncodingParams::kCrop);
|
||||
layout->addWidget(scaling_method_combobox_, row, 1);
|
||||
|
||||
// Automatically enable/disable the scaling method depending on maintain aspect ratio
|
||||
@@ -214,10 +221,12 @@ void ExportVideoTab::OpenAdvancedDialog()
|
||||
|
||||
d.set_threads(threads_);
|
||||
d.set_pix_fmt(pix_fmt_);
|
||||
d.set_yuv_range(color_range_);
|
||||
|
||||
if (d.exec() == QDialog::Accepted) {
|
||||
threads_ = d.threads();
|
||||
pix_fmt_ = d.pix_fmt();
|
||||
color_range_ = d.yuv_range();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <QComboBox>
|
||||
#include <QWidget>
|
||||
|
||||
#include "common/qtutils.h"
|
||||
#include "common/rational.h"
|
||||
#include "dialog/export/codec/av1section.h"
|
||||
#include "dialog/export/codec/cineformsection.h"
|
||||
@@ -47,6 +48,7 @@ public:
|
||||
int SetFormat(ExportFormat::Format format);
|
||||
|
||||
bool IsImageSequenceSet() const;
|
||||
void SetImageSequence(bool e) const;
|
||||
|
||||
rational GetStillImageTime() const
|
||||
{
|
||||
@@ -58,6 +60,11 @@ public:
|
||||
return static_cast<ExportCodec::Codec>(codec_combobox()->currentData().toInt());
|
||||
}
|
||||
|
||||
void SetSelectedCodec(ExportCodec::Codec c)
|
||||
{
|
||||
QtUtils::SetComboBoxData(codec_combobox(), c);
|
||||
}
|
||||
|
||||
QComboBox* codec_combobox() const
|
||||
{
|
||||
return codec_combobox_;
|
||||
@@ -99,6 +106,11 @@ public:
|
||||
return color_space_chooser_->input();
|
||||
}
|
||||
|
||||
void SetOCIOColorSpace(const QString &s)
|
||||
{
|
||||
color_space_chooser_->set_input(s);
|
||||
}
|
||||
|
||||
CodecSection* GetCodecSection() const
|
||||
{
|
||||
return static_cast<CodecSection*>(codec_stack_->currentWidget());
|
||||
@@ -134,10 +146,17 @@ public:
|
||||
return threads_;
|
||||
}
|
||||
|
||||
const QString& pix_fmt() const {
|
||||
return pix_fmt_;
|
||||
void SetThreads(int t)
|
||||
{
|
||||
threads_ = t;
|
||||
}
|
||||
|
||||
const QString& pix_fmt() const { return pix_fmt_; }
|
||||
void SetPixFmt(const QString &s) { pix_fmt_ = s; }
|
||||
|
||||
VideoParams::ColorRange color_range() const { return color_range_; }
|
||||
void SetColorRange(VideoParams::ColorRange c) { color_range_ = c; }
|
||||
|
||||
public slots:
|
||||
void VideoCodecChanged();
|
||||
|
||||
@@ -179,6 +198,7 @@ private:
|
||||
int threads_;
|
||||
|
||||
QString pix_fmt_;
|
||||
VideoParams::ColorRange color_range_;
|
||||
|
||||
ExportFormat::Format format_;
|
||||
|
||||
|
||||
@@ -83,11 +83,6 @@ private:
|
||||
*/
|
||||
QStackedWidget* stacked_widget_;
|
||||
|
||||
/**
|
||||
* @brief ComboBox for interlacing setting
|
||||
*/
|
||||
QComboBox* interlacing_box;
|
||||
|
||||
/**
|
||||
* @brief Media name text field
|
||||
*/
|
||||
|
||||
@@ -79,6 +79,17 @@ VideoStreamProperties::VideoStreamProperties(Footage *footage, int video_index)
|
||||
|
||||
video_layout->addWidget(video_color_space_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
video_layout->addWidget(new QLabel(tr("Color Range:")), row, 0);
|
||||
|
||||
color_range_combo_ = new QComboBox();
|
||||
color_range_combo_->addItem(tr("Limited (16-235)"), VideoParams::kColorRangeLimited);
|
||||
color_range_combo_->addItem(tr("Full (0-255)"), VideoParams::kColorRangeFull);
|
||||
color_range_combo_->setCurrentIndex(vp.color_range());
|
||||
|
||||
video_layout->addWidget(color_range_combo_, row, 1);
|
||||
|
||||
if (vp.channel_count() == VideoParams::kRGBAChannelCount) {
|
||||
row++;
|
||||
|
||||
@@ -136,14 +147,16 @@ void VideoStreamProperties::Accept(MultiUndoCommand *parent)
|
||||
if ((video_premultiply_alpha_ && video_premultiply_alpha_->isChecked() != vp.premultiplied_alpha())
|
||||
|| set_colorspace != vp.colorspace()
|
||||
|| static_cast<VideoParams::Interlacing>(video_interlace_combo_->currentIndex()) != vp.interlacing()
|
||||
|| pixel_aspect_combo_->GetPixelAspectRatio() != vp.pixel_aspect_ratio()) {
|
||||
|| pixel_aspect_combo_->GetPixelAspectRatio() != vp.pixel_aspect_ratio()
|
||||
|| color_range_combo_->currentData().toInt() != vp.color_range()) {
|
||||
|
||||
parent->add_child(new VideoStreamChangeCommand(footage_,
|
||||
video_index_,
|
||||
video_premultiply_alpha_ ? video_premultiply_alpha_->isChecked() : vp.premultiplied_alpha(),
|
||||
set_colorspace,
|
||||
static_cast<VideoParams::Interlacing>(video_interlace_combo_->currentIndex()),
|
||||
pixel_aspect_combo_->GetPixelAspectRatio()));
|
||||
pixel_aspect_combo_->GetPixelAspectRatio(),
|
||||
static_cast<VideoParams::ColorRange>(color_range_combo_->currentData().toInt())));
|
||||
}
|
||||
|
||||
if (vp.video_type() == VideoParams::kVideoTypeImageSequence) {
|
||||
@@ -181,13 +194,14 @@ VideoStreamProperties::VideoStreamChangeCommand::VideoStreamChangeCommand(Footag
|
||||
bool premultiplied,
|
||||
QString colorspace,
|
||||
VideoParams::Interlacing interlacing,
|
||||
const rational &pixel_ar) :
|
||||
const rational &pixel_ar, VideoParams::ColorRange range) :
|
||||
footage_(footage),
|
||||
video_index_(video_index),
|
||||
new_premultiplied_(premultiplied),
|
||||
new_colorspace_(colorspace),
|
||||
new_interlacing_(interlacing),
|
||||
new_pixel_ar_(pixel_ar)
|
||||
new_pixel_ar_(pixel_ar),
|
||||
new_range_(range)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -204,11 +218,13 @@ void VideoStreamProperties::VideoStreamChangeCommand::redo()
|
||||
old_colorspace_ = vp.colorspace();
|
||||
old_interlacing_ = vp.interlacing();
|
||||
old_pixel_ar_ = vp.pixel_aspect_ratio();
|
||||
old_range_ = vp.color_range();
|
||||
|
||||
vp.set_premultiplied_alpha(new_premultiplied_);
|
||||
vp.set_colorspace(new_colorspace_);
|
||||
vp.set_interlacing(new_interlacing_);
|
||||
vp.set_pixel_aspect_ratio(new_pixel_ar_);
|
||||
vp.set_color_range(new_range_);
|
||||
|
||||
footage_->SetVideoParams(vp, video_index_);
|
||||
}
|
||||
@@ -221,6 +237,7 @@ void VideoStreamProperties::VideoStreamChangeCommand::undo()
|
||||
vp.set_colorspace(old_colorspace_);
|
||||
vp.set_interlacing(old_interlacing_);
|
||||
vp.set_pixel_aspect_ratio(old_pixel_ar_);
|
||||
vp.set_color_range(old_range_);
|
||||
|
||||
footage_->SetVideoParams(vp, video_index_);
|
||||
}
|
||||
|
||||
@@ -56,6 +56,11 @@ private:
|
||||
*/
|
||||
QComboBox* video_color_space_;
|
||||
|
||||
/**
|
||||
* @brief Setting for this streams's color range
|
||||
*/
|
||||
QComboBox *color_range_combo_;
|
||||
|
||||
/**
|
||||
* @brief Setting for video interlacing
|
||||
*/
|
||||
@@ -88,7 +93,8 @@ private:
|
||||
bool premultiplied,
|
||||
QString colorspace,
|
||||
VideoParams::Interlacing interlacing,
|
||||
const rational& pixel_ar);
|
||||
const rational& pixel_ar,
|
||||
VideoParams::ColorRange range);
|
||||
|
||||
virtual Project* GetRelevantProject() const override;
|
||||
|
||||
@@ -104,11 +110,13 @@ private:
|
||||
QString new_colorspace_;
|
||||
VideoParams::Interlacing new_interlacing_;
|
||||
rational new_pixel_ar_;
|
||||
VideoParams::ColorRange new_range_;
|
||||
|
||||
bool old_premultiplied_;
|
||||
QString old_colorspace_;
|
||||
VideoParams::Interlacing old_interlacing_;
|
||||
rational old_pixel_ar_;
|
||||
VideoParams::ColorRange old_range_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -44,6 +44,11 @@ PreferencesBehaviorTab::PreferencesBehaviorTab()
|
||||
AddItem(tr("Enable slider ladder"),
|
||||
QStringLiteral("UseSliderLadders"),
|
||||
general_group);
|
||||
AddItem(tr("Scrolling zooms by default"),
|
||||
QStringLiteral("ScrollZooms"),
|
||||
tr("By default, scrolling will move the view around, and holding Ctrl/Cmd will make it zoom instead. "
|
||||
"Enabling this will switch those, scrolling will zoom by default, and holding Ctrl/Cmd will move the view instead."),
|
||||
general_group);
|
||||
|
||||
QTreeWidgetItem* audio_group = AddParent(tr("Audio"));
|
||||
AddItem(tr("Enable audio scrubbing"),
|
||||
|
||||
@@ -102,7 +102,7 @@ void SequenceDialog::SetNameIsEditable(bool e)
|
||||
void SequenceDialog::accept()
|
||||
{
|
||||
if (name_field_->isEnabled() && name_field_->text().isEmpty()) {
|
||||
QtUtils::MessageBox(this, QMessageBox::Critical, tr("Error editing Sequence"), tr("Please enter a name for this Sequence."));
|
||||
QtUtils::MsgBox(this, QMessageBox::Critical, tr("Error editing Sequence"), tr("Please enter a name for this Sequence."));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ void SequenceDialog::accept()
|
||||
|
||||
void SequenceDialog::SetAsDefaultClicked()
|
||||
{
|
||||
if (QtUtils::MessageBox(this, QMessageBox::Question, tr("Confirm Set As Default"),
|
||||
if (QtUtils::MsgBox(this, QMessageBox::Question, tr("Confirm Set As Default"),
|
||||
tr("Are you sure you want to set the current parameters as defaults?"),
|
||||
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
|
||||
// Maybe replace with Preset system
|
||||
|
||||
@@ -63,9 +63,9 @@ SequenceDialogPresetTab::SequenceDialogPresetTab(QWidget* parent) :
|
||||
preset_tree_->addTopLevelItem(my_presets_folder_);
|
||||
|
||||
// Add presets
|
||||
preset_tree_->addTopLevelItem(CreateHDPresetFolder(tr("4K UHD"), 3840, 2160, 6));
|
||||
preset_tree_->addTopLevelItem(CreateHDPresetFolder(tr("1080p"), 1920, 1080, 3));
|
||||
preset_tree_->addTopLevelItem(CreateHDPresetFolder(tr("720p"), 1280, 720, 2));
|
||||
preset_tree_->addTopLevelItem(CreateHDPresetFolder(tr("4K UHD"), 3840, 2160, 2));
|
||||
preset_tree_->addTopLevelItem(CreateHDPresetFolder(tr("1080p"), 1920, 1080, 1));
|
||||
preset_tree_->addTopLevelItem(CreateHDPresetFolder(tr("720p"), 1280, 720, 1));
|
||||
|
||||
preset_tree_->addTopLevelItem(CreateSDPresetFolder(tr("NTSC"), 720, 480, rational(30000, 1001),
|
||||
VideoParams::kPixelAspectNTSCStandard,
|
||||
|
||||
@@ -38,12 +38,12 @@ SpeedDurationDialog::SpeedDurationDialog(const QVector<ClipBlock *> &clips, cons
|
||||
clips_(clips),
|
||||
timebase_(timebase)
|
||||
{
|
||||
setWindowTitle(tr("Speed/Duration"));
|
||||
setWindowTitle(tr("Clip Properties"));
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
|
||||
{
|
||||
QGroupBox *speed_group = new QGroupBox();
|
||||
QGroupBox *speed_group = new QGroupBox(tr("Speed/Duration"));
|
||||
layout->addWidget(speed_group);
|
||||
|
||||
QGridLayout *speed_layout = new QGridLayout(speed_group);
|
||||
@@ -72,16 +72,39 @@ SpeedDurationDialog::SpeedDurationDialog(const QVector<ClipBlock *> &clips, cons
|
||||
link_box_ = new QCheckBox(tr("Link Speed and Duration"));
|
||||
link_box_->setChecked(true);
|
||||
speed_layout->addWidget(link_box_, row, 0, 1, 2);
|
||||
|
||||
row++;
|
||||
|
||||
reverse_box_ = new QCheckBox(tr("Reverse"));
|
||||
speed_layout->addWidget(reverse_box_, row, 0, 1, 2);
|
||||
|
||||
row++;
|
||||
|
||||
maintain_audio_pitch_box_ = new QCheckBox(tr("Maintain Audio Pitch"));
|
||||
speed_layout->addWidget(maintain_audio_pitch_box_, row, 0, 1, 2);
|
||||
|
||||
row++;
|
||||
|
||||
ripple_box_ = new QCheckBox(tr("Ripple Trailing Clips"));
|
||||
speed_layout->addWidget(ripple_box_, row, 0, 1, 2);
|
||||
}
|
||||
|
||||
reverse_box_ = new QCheckBox(tr("Reverse"));
|
||||
layout->addWidget(reverse_box_);
|
||||
{
|
||||
auto loop_box = new QGroupBox(tr("Loop"));
|
||||
layout->addWidget(loop_box);
|
||||
|
||||
maintain_audio_pitch_box_ = new QCheckBox(tr("Maintain Audio Pitch"));
|
||||
layout->addWidget(maintain_audio_pitch_box_);
|
||||
auto loop_layout = new QGridLayout(loop_box);
|
||||
|
||||
ripple_box_ = new QCheckBox(tr("Ripple Trailing Clips"));
|
||||
layout->addWidget(ripple_box_);
|
||||
int row = 0;
|
||||
|
||||
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_layout->addWidget(loop_combo_, row, 1);
|
||||
}
|
||||
|
||||
QDialogButtonBox *btns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
btns->setCenterButtons(true);
|
||||
@@ -94,6 +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();
|
||||
for (int i=1; i<clips.size(); i++) {
|
||||
ClipBlock *c = clips.at(i);
|
||||
|
||||
@@ -116,6 +140,10 @@ SpeedDurationDialog::SpeedDurationDialog(const QVector<ClipBlock *> &clips, cons
|
||||
if (start_maintain_audio_pitch_ != -1 && clip_maintain_pitch != start_maintain_audio_pitch_) {
|
||||
start_maintain_audio_pitch_ = -1;
|
||||
}
|
||||
|
||||
if (start_loop_ != -1 && c->loop_mode() != start_loop_) {
|
||||
start_loop_ = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (qIsNaN(start_speed_)) {
|
||||
@@ -141,6 +169,12 @@ SpeedDurationDialog::SpeedDurationDialog(const QVector<ClipBlock *> &clips, cons
|
||||
} else {
|
||||
maintain_audio_pitch_box_->setChecked(start_maintain_audio_pitch_);
|
||||
}
|
||||
|
||||
if (start_loop_ == -1) {
|
||||
loop_combo_->setCurrentIndex(-1);
|
||||
} else {
|
||||
loop_combo_->setCurrentIndex(start_loop_);
|
||||
}
|
||||
}
|
||||
|
||||
void SpeedDurationDialog::accept()
|
||||
@@ -211,6 +245,12 @@ void SpeedDurationDialog::accept()
|
||||
}
|
||||
}
|
||||
|
||||
if (loop_combo_->currentIndex() != -1) {
|
||||
foreach (ClipBlock *c, clips_) {
|
||||
command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(c, ClipBlock::kLoopModeInput)), loop_combo_->currentData()));
|
||||
}
|
||||
}
|
||||
|
||||
Core::instance()->undo_stack()->push(command);
|
||||
|
||||
super::accept();
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#define SPEEDDURATIONDIALOG_H
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QDialog>
|
||||
|
||||
#include "node/block/clip/clip.h"
|
||||
@@ -62,6 +63,8 @@ private:
|
||||
|
||||
QCheckBox *ripple_box_;
|
||||
|
||||
QComboBox *loop_combo_;
|
||||
|
||||
int start_reverse_;
|
||||
|
||||
int start_maintain_audio_pitch_;
|
||||
@@ -70,6 +73,8 @@ private:
|
||||
|
||||
rational start_duration_;
|
||||
|
||||
int start_loop_;
|
||||
|
||||
rational timebase_;
|
||||
|
||||
private slots:
|
||||
|
||||
@@ -69,8 +69,6 @@ void PanNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeV
|
||||
// Create a sample job
|
||||
SampleBuffer samples = value[kSamplesInput].toSamples();
|
||||
if (samples.is_allocated()) {
|
||||
bool pushed_job = false;
|
||||
|
||||
// This node is only compatible with stereo audio
|
||||
if (samples.audio_params().channel_count() == 2) {
|
||||
// If the input is static, we can just do it now which will be faster
|
||||
@@ -83,15 +81,14 @@ void PanNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeV
|
||||
samples.transform_volume_for_channel(1, 1.0f + pan_volume);
|
||||
}
|
||||
}
|
||||
|
||||
table->Push(NodeValue(NodeValue::kSamples, samples, this));
|
||||
} else {
|
||||
// Requires job
|
||||
|
||||
pushed_job = true;
|
||||
table->Push(NodeValue::kSamples, SampleJob(kSamplesInput, value), this);
|
||||
}
|
||||
}
|
||||
|
||||
if (!pushed_job) {
|
||||
} else {
|
||||
// Pass right through
|
||||
table->Push(value[kSamplesInput]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ const QString ClipBlock::kMediaInInput = QStringLiteral("media_in_in");
|
||||
const QString ClipBlock::kSpeedInput = QStringLiteral("speed_in");
|
||||
const QString ClipBlock::kReverseInput = QStringLiteral("reverse_in");
|
||||
const QString ClipBlock::kMaintainAudioPitchInput = QStringLiteral("maintain_audio_pitch_in");
|
||||
const QString ClipBlock::kLoopModeInput = QStringLiteral("loop_in");
|
||||
|
||||
ClipBlock::ClipBlock() :
|
||||
in_transition_(nullptr),
|
||||
@@ -56,6 +57,8 @@ ClipBlock::ClipBlock() :
|
||||
//SetValueHintForInput(kBufferIn, ValueHint(NodeValue::kBuffer));
|
||||
|
||||
SetEffectInput(kBufferIn);
|
||||
|
||||
AddInput(kLoopModeInput, NodeValue::kCombo, 0, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable));
|
||||
}
|
||||
|
||||
QString ClipBlock::Name() const
|
||||
@@ -89,7 +92,7 @@ void ClipBlock::set_length_and_media_out(const rational &length)
|
||||
|
||||
if (reverse()) {
|
||||
// Calculate media_in adjustment
|
||||
rational proposed_media_in = SequenceToMediaTime(this->length() - length, true);
|
||||
rational proposed_media_in = SequenceToMediaTime(this->length() - length, kSTMIgnoreReverse | kSTMIgnoreLoop);
|
||||
set_media_in(proposed_media_in);
|
||||
}
|
||||
|
||||
@@ -104,11 +107,9 @@ void ClipBlock::set_length_and_media_in(const rational &length)
|
||||
|
||||
if (!reverse()) {
|
||||
// Calculate media_in adjustment
|
||||
rational proposed_media_in = SequenceToMediaTime(this->length() - length, false, true);
|
||||
waveform_.TrimIn(SequenceToMediaTime(this->length() - length, kSTMIgnoreSpeed | kSTMIgnoreLoop) - media_in());
|
||||
|
||||
waveform_.TrimIn(proposed_media_in - media_in());
|
||||
|
||||
set_media_in(proposed_media_in);
|
||||
set_media_in(SequenceToMediaTime(this->length() - length, kSTMIgnoreLoop));
|
||||
} else {
|
||||
// Trim waveform out point
|
||||
waveform_.TrimIn(this->length() - length);
|
||||
@@ -127,7 +128,7 @@ void ClipBlock::set_media_in(const rational &media_in)
|
||||
SetStandardValue(kMediaInInput, QVariant::fromValue(media_in));
|
||||
}
|
||||
|
||||
rational ClipBlock::SequenceToMediaTime(const rational &sequence_time, bool ignore_reverse, bool ignore_speed) const
|
||||
rational ClipBlock::SequenceToMediaTime(const rational &sequence_time, uint64_t flags) const
|
||||
{
|
||||
// These constants are not considered "values" per se, so we don't modify them
|
||||
if (sequence_time == RATIONAL_MIN || sequence_time == RATIONAL_MAX) {
|
||||
@@ -136,11 +137,11 @@ rational ClipBlock::SequenceToMediaTime(const rational &sequence_time, bool igno
|
||||
|
||||
rational media_time = sequence_time;
|
||||
|
||||
if (reverse() && !ignore_reverse) {
|
||||
if (reverse() && !(flags & kSTMIgnoreReverse)) {
|
||||
media_time = length() - media_time;
|
||||
}
|
||||
|
||||
if (!ignore_speed) {
|
||||
if (!(flags & kSTMIgnoreSpeed)) {
|
||||
double speed_value = speed();
|
||||
if (qIsNull(speed_value)) {
|
||||
// Effectively holds the frame at the in point
|
||||
@@ -153,6 +154,23 @@ rational ClipBlock::SequenceToMediaTime(const rational &sequence_time, bool igno
|
||||
|
||||
media_time += media_in();
|
||||
|
||||
/*if (!(flags & kSTMIgnoreLoop)
|
||||
&& this->loop_mode() != kLoopModeOff
|
||||
&& connected_viewer_
|
||||
&& !connected_viewer_->GetLength().isNull()
|
||||
&& (media_time < 0 || media_time >= connected_viewer_->GetLength())) {
|
||||
if (loop_mode() == kLoopModeLoop) {
|
||||
while (media_time < 0) {
|
||||
media_time += connected_viewer_->GetLength();
|
||||
}
|
||||
while (media_time >= connected_viewer_->GetLength()) {
|
||||
media_time -= connected_viewer_->GetLength();
|
||||
}
|
||||
} else if (loop_mode() == kLoopModeClamp) {
|
||||
media_time = std::clamp(media_time, rational(0), connected_viewer_->GetLength()-connected_viewer_->GetVideoParams().frame_rate_as_time_base());
|
||||
}
|
||||
}*/
|
||||
|
||||
return media_time;
|
||||
}
|
||||
|
||||
@@ -204,17 +222,17 @@ void ClipBlock::InvalidateCache(const TimeRange& range, const QString& from, int
|
||||
|
||||
if (new_connected_viewer != connected_viewer_) {
|
||||
if (connected_viewer_) {
|
||||
disconnect(connected_viewer_->GetTimelinePoints()->markers(), &TimelineMarkerList::MarkerAdded, this, &ClipBlock::PreviewChanged);
|
||||
disconnect(connected_viewer_->GetTimelinePoints()->markers(), &TimelineMarkerList::MarkerRemoved, this, &ClipBlock::PreviewChanged);
|
||||
disconnect(connected_viewer_->GetTimelinePoints()->markers(), &TimelineMarkerList::MarkerModified, this, &ClipBlock::PreviewChanged);
|
||||
disconnect(connected_viewer_->GetMarkers(), &TimelineMarkerList::MarkerAdded, this, &ClipBlock::PreviewChanged);
|
||||
disconnect(connected_viewer_->GetMarkers(), &TimelineMarkerList::MarkerRemoved, this, &ClipBlock::PreviewChanged);
|
||||
disconnect(connected_viewer_->GetMarkers(), &TimelineMarkerList::MarkerModified, this, &ClipBlock::PreviewChanged);
|
||||
}
|
||||
|
||||
connected_viewer_ = new_connected_viewer;
|
||||
|
||||
if (connected_viewer_) {
|
||||
connect(connected_viewer_->GetTimelinePoints()->markers(), &TimelineMarkerList::MarkerAdded, this, &ClipBlock::PreviewChanged);
|
||||
connect(connected_viewer_->GetTimelinePoints()->markers(), &TimelineMarkerList::MarkerRemoved, this, &ClipBlock::PreviewChanged);
|
||||
connect(connected_viewer_->GetTimelinePoints()->markers(), &TimelineMarkerList::MarkerModified, this, &ClipBlock::PreviewChanged);
|
||||
connect(connected_viewer_->GetMarkers(), &TimelineMarkerList::MarkerAdded, this, &ClipBlock::PreviewChanged);
|
||||
connect(connected_viewer_->GetMarkers(), &TimelineMarkerList::MarkerRemoved, this, &ClipBlock::PreviewChanged);
|
||||
connect(connected_viewer_->GetMarkers(), &TimelineMarkerList::MarkerModified, this, &ClipBlock::PreviewChanged);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,6 +300,13 @@ void ClipBlock::Retranslate()
|
||||
SetInputName(kSpeedInput, tr("Speed"));
|
||||
SetInputName(kReverseInput, tr("Reverse"));
|
||||
SetInputName(kMaintainAudioPitchInput, tr("Maintain Audio Pitch"));
|
||||
SetInputName(kLoopModeInput, tr("Loop"));
|
||||
SetComboBoxStrings(kLoopModeInput, {tr("None"), tr("Loop"), tr("Clamp")});
|
||||
}
|
||||
|
||||
TimeRange ClipBlock::media_range() const
|
||||
{
|
||||
return InputTimeAdjustment(kBufferIn, -1, TimeRange(0, length()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#define CLIPBLOCK_H
|
||||
|
||||
#include "audio/audiovisualwaveform.h"
|
||||
#include "codec/decoder.h"
|
||||
#include "node/block/block.h"
|
||||
|
||||
namespace olive {
|
||||
@@ -119,17 +120,41 @@ public:
|
||||
return connected_viewer_;
|
||||
}
|
||||
|
||||
TimeRange media_range() const;
|
||||
|
||||
/**
|
||||
* @brief Get currently set loop mode
|
||||
*/
|
||||
Decoder::LoopMode loop_mode() const
|
||||
{
|
||||
return static_cast<Decoder::LoopMode>(GetStandardValue(kLoopModeInput).toInt());
|
||||
}
|
||||
|
||||
void set_loop_mode(Decoder::LoopMode l)
|
||||
{
|
||||
SetStandardValue(kLoopModeInput, int(l));
|
||||
}
|
||||
|
||||
static const QString kBufferIn;
|
||||
static const QString kMediaInInput;
|
||||
static const QString kSpeedInput;
|
||||
static const QString kReverseInput;
|
||||
static const QString kMaintainAudioPitchInput;
|
||||
static const QString kLoopModeInput;
|
||||
|
||||
protected:
|
||||
virtual void LinkChangeEvent() override;
|
||||
|
||||
private:
|
||||
rational SequenceToMediaTime(const rational& sequence_time, bool ignore_reverse = false, bool ignore_speed = false) const;
|
||||
enum SequenceToMediaTimeFlag
|
||||
{
|
||||
kSTMNone = 0x0,
|
||||
kSTMIgnoreReverse = 0x1,
|
||||
kSTMIgnoreSpeed = 0x2,
|
||||
kSTMIgnoreLoop = 0x4
|
||||
};
|
||||
|
||||
rational SequenceToMediaTime(const rational& sequence_time, uint64_t flags = kSTMNone) const;
|
||||
|
||||
rational MediaToSequenceTime(const rational& media_time) const;
|
||||
|
||||
|
||||
@@ -53,13 +53,6 @@ ShaderCode CrossDissolveTransition::GetShaderCode(const ShaderRequest &request)
|
||||
return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/crossdissolve.frag"), QString());
|
||||
}
|
||||
|
||||
void CrossDissolveTransition::ShaderJobEvent(const NodeValueRow &value, ShaderJob &job) const
|
||||
{
|
||||
Q_UNUSED(value)
|
||||
|
||||
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
|
||||
}
|
||||
|
||||
void CrossDissolveTransition::SampleJobEvent(const SampleBuffer &from_samples, const SampleBuffer &to_samples, SampleBuffer &out_samples, double time_in) const
|
||||
{
|
||||
for (int i=0; i<out_samples.sample_count(); i++) {
|
||||
|
||||
@@ -43,8 +43,6 @@ public:
|
||||
virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override;
|
||||
|
||||
protected:
|
||||
virtual void ShaderJobEvent(const NodeValueRow &value, ShaderJob& job) const override;
|
||||
|
||||
virtual void SampleJobEvent(const SampleBuffer &from_samples, const SampleBuffer &to_samples, SampleBuffer &out_samples, double time_in) const override;
|
||||
|
||||
};
|
||||
|
||||
@@ -71,7 +71,6 @@ void CornerPinDistortNode::Value(const NodeValueRow &value, const NodeGlobals &g
|
||||
{
|
||||
ShaderJob job;
|
||||
job.Insert(value);
|
||||
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
|
||||
job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this));
|
||||
|
||||
// Convert slider values to their pixel values and then convert to clip space (-1.0 ... 1.0) for overriding the
|
||||
|
||||
@@ -79,7 +79,6 @@ void CropDistortNode::Value(const NodeValueRow &value, const NodeGlobals &global
|
||||
{
|
||||
ShaderJob job;
|
||||
job.Insert(value);
|
||||
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
|
||||
job.SetWillChangeImageSize(false);
|
||||
|
||||
if (TexturePtr texture = job.Get(kTextureInput).toTexture()) {
|
||||
|
||||
@@ -44,7 +44,7 @@ ShaderCode MaskDistortNode::GetShaderCode(const ShaderRequest &request) const
|
||||
} else if (request.id == QStringLiteral("feather")) {
|
||||
return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/blur.frag")));
|
||||
} else {
|
||||
return ShaderCode();
|
||||
return super::GetShaderCode(request);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ void MaskDistortNode::Retranslate()
|
||||
|
||||
void MaskDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
|
||||
{
|
||||
GenerateJob job = GetGenerateJob(value);
|
||||
ShaderJob job = GetGenerateJob(value);
|
||||
|
||||
if (value[kBaseInput].toTexture()) {
|
||||
// Push as merge node
|
||||
@@ -80,7 +80,6 @@ void MaskDistortNode::Value(const NodeValueRow &value, const NodeGlobals &global
|
||||
feather.Insert(BlurFilterNode::kRadiusInput, NodeValue(NodeValue::kFloat, value[kFeatherInput].toDouble(), this));
|
||||
feather.SetIterations(2, BlurFilterNode::kTextureInput);
|
||||
feather.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this));
|
||||
feather.SetAlphaChannelRequired(ShaderJob::kAlphaForceOn);
|
||||
|
||||
merge.Insert(QStringLiteral("tex_b"), NodeValue(NodeValue::kTexture, feather, this));
|
||||
} else {
|
||||
|
||||
@@ -22,12 +22,9 @@
|
||||
|
||||
#include <QGuiApplication>
|
||||
|
||||
#include "common/range.h"
|
||||
#include "core.h"
|
||||
#include "node/traverser.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
const QString TransformDistortNode::kParentInput = QStringLiteral("parent_in");
|
||||
const QString TransformDistortNode::kTextureInput = QStringLiteral("tex_in");
|
||||
const QString TransformDistortNode::kAutoscaleInput = QStringLiteral("autoscale_in");
|
||||
const QString TransformDistortNode::kInterpolationInput = QStringLiteral("interpolation_in");
|
||||
@@ -36,6 +33,8 @@ const QString TransformDistortNode::kInterpolationInput = QStringLiteral("interp
|
||||
|
||||
TransformDistortNode::TransformDistortNode()
|
||||
{
|
||||
AddInput(kParentInput, NodeValue::kMatrix);
|
||||
|
||||
AddInput(kAutoscaleInput, NodeValue::kCombo, 0);
|
||||
|
||||
AddInput(kInterpolationInput, NodeValue::kCombo, 2);
|
||||
@@ -73,6 +72,7 @@ void TransformDistortNode::Retranslate()
|
||||
{
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(kParentInput, tr("Parent"));
|
||||
SetInputName(kAutoscaleInput, tr("Auto-Scale"));
|
||||
SetInputName(kTextureInput, tr("Texture"));
|
||||
SetInputName(kInterpolationInput, tr("Interpolation"));
|
||||
@@ -84,12 +84,12 @@ void TransformDistortNode::Retranslate()
|
||||
void TransformDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
|
||||
{
|
||||
// Generate matrix
|
||||
QMatrix4x4 generated_matrix = GenerateMatrix(value, false, false, false);
|
||||
QMatrix4x4 generated_matrix = GenerateMatrix(value, false, false, false, value[kParentInput].toMatrix());
|
||||
|
||||
// Pop texture
|
||||
NodeValue texture_meta = value[kTextureInput];
|
||||
|
||||
bool pushed_job = false;
|
||||
QVariant job_to_push;
|
||||
|
||||
// If we have a texture, generate a matrix and make it happen
|
||||
if (TexturePtr texture = texture_meta.toTexture()) {
|
||||
@@ -103,19 +103,17 @@ void TransformDistortNode::Value(const NodeValueRow &value, const NodeGlobals &g
|
||||
job.Insert(QStringLiteral("ove_mvpmat"), NodeValue(NodeValue::kMatrix, real_matrix, this));
|
||||
job.SetInterpolation(QStringLiteral("ove_maintex"), static_cast<Texture::Interpolation>(value[kInterpolationInput].toInt()));
|
||||
|
||||
// FIXME: This should be optimized, we can use matrix math to determine if this operation will
|
||||
// end up with gaps in the screen that will require an alpha channel.
|
||||
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
|
||||
|
||||
table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
|
||||
|
||||
pushed_job = true;
|
||||
job_to_push = QVariant::fromValue(job);
|
||||
}
|
||||
}
|
||||
|
||||
if (!pushed_job) {
|
||||
table->Push(NodeValue::kMatrix, QVariant::fromValue(generated_matrix), this);
|
||||
|
||||
if (job_to_push.isNull()) {
|
||||
// Re-push whatever value we received
|
||||
table->Push(texture_meta);
|
||||
} else {
|
||||
table->Push(NodeValue::kTexture, job_to_push, this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +131,7 @@ void TransformDistortNode::GizmoDragStart(const NodeValueRow &row, double x, dou
|
||||
|
||||
if (gizmo == anchor_gizmo_) {
|
||||
|
||||
gizmo_inverted_transform_ = GenerateMatrix(row, true, true, false).toTransform().inverted();
|
||||
gizmo_inverted_transform_ = GenerateMatrix(row, true, true, false, row[kParentInput].toMatrix()).toTransform().inverted();
|
||||
|
||||
} else if (IsAScaleGizmo(gizmo)) {
|
||||
|
||||
@@ -175,7 +173,7 @@ void TransformDistortNode::GizmoDragStart(const NodeValueRow &row, double x, dou
|
||||
}
|
||||
|
||||
// Store current matrix
|
||||
gizmo_inverted_transform_ = GenerateMatrix(row, true, true, true).toTransform().inverted();
|
||||
gizmo_inverted_transform_ = GenerateMatrix(row, true, true, true, row[kParentInput].toMatrix()).toTransform().inverted();
|
||||
|
||||
} else if (gizmo == rotation_gizmo_) {
|
||||
|
||||
@@ -360,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 *= AdjustMatrixByResolutions(GenerateMatrix(row, false, false, false),
|
||||
rectangle_matrix *= AdjustMatrixByResolutions(GenerateMatrix(row, false, false, false, row[kParentInput].toMatrix()),
|
||||
sequence_res,
|
||||
tex_sz,
|
||||
tex_offset,
|
||||
@@ -380,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 *= AdjustMatrixByResolutions(GenerateMatrix(row, true, false, false),
|
||||
anchor_matrix *= AdjustMatrixByResolutions(GenerateMatrix(row, true, false, false, row[kParentInput].toMatrix()),
|
||||
sequence_res,
|
||||
tex_sz,
|
||||
tex_offset,
|
||||
@@ -406,7 +404,8 @@ void TransformDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const N
|
||||
QTransform TransformDistortNode::GizmoTransformation(const NodeValueRow &row, const NodeGlobals &globals) const
|
||||
{
|
||||
if (TexturePtr texture = row[kTextureInput].toTexture()) {
|
||||
auto m = GenerateMatrix(row, false, false, false);
|
||||
//auto m = GenerateMatrix(row, false, false, false, row[kParentInput].toMatrix());
|
||||
auto m = GenerateMatrix(row, false, false, false, QMatrix4x4());
|
||||
return GenerateAutoScaledMatrix(m, row, globals, texture->params()).toTransform();
|
||||
}
|
||||
return super::GizmoTransformation(row, globals);
|
||||
|
||||
@@ -84,6 +84,7 @@ public:
|
||||
virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override;
|
||||
virtual QTransform GizmoTransformation(const NodeValueRow &row, const NodeGlobals &globals) const override;
|
||||
|
||||
static const QString kParentInput;
|
||||
static const QString kTextureInput;
|
||||
static const QString kAutoscaleInput;
|
||||
static const QString kInterpolationInput;
|
||||
|
||||
@@ -52,7 +52,6 @@ void OpacityEffect::Value(const NodeValueRow &value, const NodeGlobals &globals,
|
||||
// If there's no texture, no need to run an operation
|
||||
if (job.Get(kTextureInput).toTexture()) {
|
||||
if (!qFuzzyCompare(job.Get(kValueInput).toDouble(), 1.0)) {
|
||||
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
|
||||
table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
|
||||
} else {
|
||||
// 1.0 float is a no-op, so just push the texture
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
#include "project/folder/folder.h"
|
||||
#include "project/footage/footage.h"
|
||||
#include "project/sequence/sequence.h"
|
||||
#include "time/timeformat/timeformat.h"
|
||||
#include "time/timeoffset/timeoffsetnode.h"
|
||||
#include "time/timeremap/timeremap.h"
|
||||
|
||||
@@ -291,6 +292,8 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id)
|
||||
return new MaskDistortNode();
|
||||
case kDropShadowFilter:
|
||||
return new DropShadowFilter();
|
||||
case kTimeFormat:
|
||||
return new TimeFormatNode();
|
||||
|
||||
case kInternalNodeCount:
|
||||
break;
|
||||
|
||||
@@ -75,6 +75,7 @@ public:
|
||||
kChromaKey,
|
||||
kMaskDistort,
|
||||
kDropShadowFilter,
|
||||
kTimeFormat,
|
||||
|
||||
// Count value
|
||||
kInternalNodeCount
|
||||
|
||||
@@ -159,11 +159,6 @@ void BlurFilterNode::Value(const NodeValueRow &value, const NodeGlobals &globals
|
||||
}
|
||||
|
||||
if (can_push_job) {
|
||||
// If we're not repeating pixels, expect an alpha channel to appear
|
||||
if (!job.Get(kRepeatEdgePixelsInput).toBool()) {
|
||||
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
|
||||
}
|
||||
|
||||
table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
|
||||
} else {
|
||||
// If we're not performing the blur job, just push the texture
|
||||
|
||||
@@ -90,11 +90,11 @@ void MatrixGenerator::Retranslate()
|
||||
void MatrixGenerator::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
|
||||
{
|
||||
// Push matrix output
|
||||
QMatrix4x4 mat = GenerateMatrix(value, false, false, false);
|
||||
QMatrix4x4 mat = GenerateMatrix(value, false, false, false, QMatrix4x4());
|
||||
table->Push(NodeValue::kMatrix, mat, this);
|
||||
}
|
||||
|
||||
QMatrix4x4 MatrixGenerator::GenerateMatrix(const NodeValueRow &value, bool ignore_anchor, bool ignore_position, bool ignore_scale) const
|
||||
QMatrix4x4 MatrixGenerator::GenerateMatrix(const NodeValueRow &value, bool ignore_anchor, bool ignore_position, bool ignore_scale, const QMatrix4x4 &mat) const
|
||||
{
|
||||
QVector2D anchor;
|
||||
QVector2D position;
|
||||
@@ -116,17 +116,17 @@ QMatrix4x4 MatrixGenerator::GenerateMatrix(const NodeValueRow &value, bool ignor
|
||||
value[kRotationInput].toDouble(),
|
||||
scale,
|
||||
value[kUniformScaleInput].toBool(),
|
||||
anchor);
|
||||
anchor,
|
||||
mat);
|
||||
}
|
||||
|
||||
QMatrix4x4 MatrixGenerator::GenerateMatrix(const QVector2D& pos,
|
||||
const float& rot,
|
||||
const QVector2D& scale,
|
||||
bool uniform_scale,
|
||||
const QVector2D& anchor)
|
||||
const QVector2D& anchor,
|
||||
QMatrix4x4 mat)
|
||||
{
|
||||
QMatrix4x4 mat;
|
||||
|
||||
// Position
|
||||
mat.translate(pos);
|
||||
|
||||
|
||||
@@ -53,12 +53,13 @@ public:
|
||||
static const QString kAnchorInput;
|
||||
|
||||
protected:
|
||||
QMatrix4x4 GenerateMatrix(const NodeValueRow &value, bool ignore_anchor, bool ignore_position, bool ignore_scale) const;
|
||||
QMatrix4x4 GenerateMatrix(const NodeValueRow &value, bool ignore_anchor, bool ignore_position, bool ignore_scale, const QMatrix4x4 &mat) const;
|
||||
static QMatrix4x4 GenerateMatrix(const QVector2D &pos,
|
||||
const float &rot,
|
||||
const QVector2D &scale,
|
||||
bool uniform_scale,
|
||||
const QVector2D &anchor);
|
||||
const QVector2D &anchor,
|
||||
QMatrix4x4 mat);
|
||||
|
||||
virtual void InputValueChangedEvent(const QString& input, int element) override;
|
||||
|
||||
|
||||
@@ -23,8 +23,6 @@
|
||||
#include <QGuiApplication>
|
||||
#include <QVector2D>
|
||||
|
||||
#include "common/cpuoptimize.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
const QString PolygonGenerator::kPointsInput = QStringLiteral("points_in");
|
||||
@@ -89,20 +87,25 @@ void PolygonGenerator::Retranslate()
|
||||
SetInputName(kColorInput, tr("Color"));
|
||||
}
|
||||
|
||||
GenerateJob PolygonGenerator::GetGenerateJob(const NodeValueRow &value) const
|
||||
ShaderJob PolygonGenerator::GetGenerateJob(const NodeValueRow &value) const
|
||||
{
|
||||
GenerateJob job;
|
||||
|
||||
job.Insert(value);
|
||||
job.SetRequestedFormat(VideoParams::kFormatFloat32);
|
||||
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
|
||||
job.SetRequestedFormat(VideoParams::kFormatUnsigned8);
|
||||
|
||||
return job;
|
||||
// Conversion to RGB
|
||||
ShaderJob rgb;
|
||||
rgb.SetShaderID(QStringLiteral("rgb"));
|
||||
rgb.Insert(QStringLiteral("texture_in"), NodeValue(NodeValue::kTexture, job, this));
|
||||
rgb.Insert(QStringLiteral("color_in"), value[kColorInput]);
|
||||
|
||||
return rgb;
|
||||
}
|
||||
|
||||
void PolygonGenerator::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
|
||||
{
|
||||
GenerateJob job = GetGenerateJob(value);
|
||||
ShaderJob job = GetGenerateJob(value);
|
||||
|
||||
PushMergableJob(value, QVariant::fromValue(job), table);
|
||||
}
|
||||
@@ -113,7 +116,7 @@ void PolygonGenerator::GenerateFrame(FramePtr frame, const GenerateJob &job) con
|
||||
// QImages only support integer pixels and we use float pixels, so what we do here is draw onto
|
||||
// a single-channel QImage (alpha only) and then transplant that alpha channel to our float buffer
|
||||
// with correct float RGB.
|
||||
QImage img(frame->width(), frame->height(), QImage::Format_Grayscale8);
|
||||
QImage img((uchar *) frame->data(), frame->width(), frame->height(), frame->linesize_bytes(), QImage::Format_RGBA8888_Premultiplied);
|
||||
img.fill(Qt::transparent);
|
||||
|
||||
QVector<NodeValue> points = job.Get(kPointsInput).value< QVector<NodeValue> >();
|
||||
@@ -128,34 +131,6 @@ void PolygonGenerator::GenerateFrame(FramePtr frame, const GenerateJob &job) con
|
||||
p.setPen(Qt::NoPen);
|
||||
|
||||
p.drawPath(path);
|
||||
|
||||
// Transplant alpha channel to frame
|
||||
Color rgba = job.Get(kColorInput).toColor();
|
||||
#if defined(Q_PROCESSOR_X86) || defined(Q_PROCESSOR_ARM)
|
||||
__m128 sse_color = _mm_loadu_ps(rgba.data());
|
||||
#endif
|
||||
|
||||
float *frame_dst = reinterpret_cast<float*>(frame->data());
|
||||
for (int y=0; y<frame->height(); y++) {
|
||||
uchar *src_y = img.bits() + img.bytesPerLine() * y;
|
||||
float *dst_y = frame_dst + y*frame->linesize_pixels()*VideoParams::kRGBAChannelCount;
|
||||
|
||||
for (int x=0; x<frame->width(); x++) {
|
||||
float alpha = float(src_y[x]) / 255.0f;
|
||||
float *dst = dst_y + x*VideoParams::kRGBAChannelCount;
|
||||
|
||||
#if defined(Q_PROCESSOR_X86) || defined(Q_PROCESSOR_ARM)
|
||||
__m128 sse_alpha = _mm_load1_ps(&alpha);
|
||||
__m128 sse_res = _mm_mul_ps(sse_color, sse_alpha);
|
||||
|
||||
_mm_store_ps(dst, sse_res);
|
||||
#else
|
||||
for (int i=0; i<VideoParams::kRGBAChannelCount; i++) {
|
||||
dst[i] = rgba.data()[i] * alpha;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
@@ -241,6 +216,15 @@ void PolygonGenerator::UpdateGizmoPositions(const NodeValueRow &row, const NodeG
|
||||
poly_gizmo_->SetPath(GeneratePath(points).translated(half_res));
|
||||
}
|
||||
|
||||
ShaderCode PolygonGenerator::GetShaderCode(const ShaderRequest &request) const
|
||||
{
|
||||
if (request.id == QStringLiteral("rgb")) {
|
||||
return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/rgb.frag"));
|
||||
} else {
|
||||
return super::GetShaderCode(request);
|
||||
}
|
||||
}
|
||||
|
||||
void PolygonGenerator::GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers)
|
||||
{
|
||||
DraggableGizmo *gizmo = static_cast<DraggableGizmo*>(sender());
|
||||
|
||||
@@ -54,11 +54,13 @@ public:
|
||||
|
||||
virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override;
|
||||
|
||||
virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override;
|
||||
|
||||
static const QString kPointsInput;
|
||||
static const QString kColorInput;
|
||||
|
||||
protected:
|
||||
GenerateJob GetGenerateJob(const NodeValueRow &value) const;
|
||||
ShaderJob GetGenerateJob(const NodeValueRow &value) const;
|
||||
|
||||
protected slots:
|
||||
virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) override;
|
||||
|
||||
@@ -25,10 +25,14 @@ namespace olive {
|
||||
#define super ShapeNodeBase
|
||||
|
||||
QString ShapeNode::kTypeInput = QStringLiteral("type_in");
|
||||
QString ShapeNode::kRadiusInput = QStringLiteral("radius_in");
|
||||
|
||||
ShapeNode::ShapeNode()
|
||||
{
|
||||
PrependInput(kTypeInput, NodeValue::kCombo);
|
||||
|
||||
AddInput(kRadiusInput, NodeValue::kFloat, 20.0);
|
||||
SetInputProperty(kRadiusInput, QStringLiteral("min"), 0.0);
|
||||
}
|
||||
|
||||
QString ShapeNode::Name() const
|
||||
@@ -56,9 +60,10 @@ void ShapeNode::Retranslate()
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(kTypeInput, tr("Type"));
|
||||
SetInputName(kRadiusInput, tr("Radius"));
|
||||
|
||||
// Coordinate with Type enum
|
||||
SetComboBoxStrings(kTypeInput, {tr("Rectangle"), tr("Ellipse")});
|
||||
SetComboBoxStrings(kTypeInput, {tr("Rectangle"), tr("Ellipse"), tr("Rounded Rectangle")});
|
||||
}
|
||||
|
||||
ShaderCode ShapeNode::GetShaderCode(const ShaderRequest &request) const
|
||||
@@ -76,10 +81,23 @@ void ShapeNode::Value(const NodeValueRow &value, const NodeGlobals &globals, Nod
|
||||
|
||||
job.Insert(value);
|
||||
job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this));
|
||||
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
|
||||
job.SetShaderID(QStringLiteral("shape"));
|
||||
|
||||
PushMergableJob(value, QVariant::fromValue(job), table);
|
||||
}
|
||||
|
||||
void ShapeNode::InputValueChangedEvent(const QString &input, int element)
|
||||
{
|
||||
if (input == kTypeInput) {
|
||||
InputFlags i = GetInputFlags(kRadiusInput);
|
||||
if (GetStandardValue(kTypeInput).toInt() == kRoundedRectangle) {
|
||||
i &= InputFlag(~kInputFlagHidden);
|
||||
} else {
|
||||
i |= kInputFlagHidden;
|
||||
}
|
||||
SetInputFlags(kRadiusInput, i);
|
||||
}
|
||||
super::InputValueChangedEvent(input, element);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -33,7 +33,8 @@ public:
|
||||
|
||||
enum Type {
|
||||
kRectangle,
|
||||
kEllipse
|
||||
kEllipse,
|
||||
kRoundedRectangle
|
||||
};
|
||||
|
||||
NODE_DEFAULT_FUNCTIONS(ShapeNode)
|
||||
@@ -49,6 +50,10 @@ public:
|
||||
virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override;
|
||||
|
||||
static QString kTypeInput;
|
||||
static QString kRadiusInput;
|
||||
|
||||
protected:
|
||||
virtual void InputValueChangedEvent(const QString &input, int element) override;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -94,7 +94,6 @@ void TextGeneratorV1::Value(const NodeValueRow &value, const NodeGlobals &global
|
||||
{
|
||||
GenerateJob job;
|
||||
job.Insert(value);
|
||||
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
|
||||
|
||||
if (!job.Get(kTextInput).toString().isEmpty()) {
|
||||
table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
|
||||
|
||||
@@ -97,7 +97,6 @@ void TextGeneratorV2::Value(const NodeValueRow &value, const NodeGlobals &global
|
||||
{
|
||||
GenerateJob job;
|
||||
job.Insert(value);
|
||||
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
|
||||
job.SetRequestedFormat(VideoParams::kFormatFloat32);
|
||||
|
||||
if (!job.Get(kTextInput).toString().isEmpty()) {
|
||||
|
||||
@@ -24,9 +24,10 @@
|
||||
#include <QDateTime>
|
||||
#include <QTextDocument>
|
||||
|
||||
#include "common/functiontimer.h"
|
||||
#include "common/html.h"
|
||||
#include "core.h"
|
||||
#include "node/project/project.h"
|
||||
#include "widget/nodeparamview/nodeparamviewundo.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -39,17 +40,30 @@ enum TextVerticalAlign {
|
||||
};
|
||||
|
||||
const QString TextGeneratorV3::kTextInput = QStringLiteral("text_in");
|
||||
const QString TextGeneratorV3::kVerticalAlignmentInput = QStringLiteral("valign_in");
|
||||
const QString TextGeneratorV3::kUseArgsInput = QStringLiteral("use_args_in");
|
||||
const QString TextGeneratorV3::kArgsInput = QStringLiteral("args_in");
|
||||
|
||||
TextGeneratorV3::TextGeneratorV3() :
|
||||
ShapeNodeBase(false)
|
||||
ShapeNodeBase(false),
|
||||
dont_emit_valign_(false)
|
||||
{
|
||||
AddInput(kTextInput, NodeValue::kText, QStringLiteral("<p style='font-size: 72pt; color: white;'>%1</p>").arg(tr("Sample Text")));
|
||||
SetInputProperty(kTextInput, QStringLiteral("vieweronly"), true);
|
||||
|
||||
SetStandardValue(kSizeInput, QVector2D(400, 300));
|
||||
|
||||
AddInput(kVerticalAlignmentInput, NodeValue::kCombo, InputFlags(kInputFlagHidden | kInputFlagStatic));
|
||||
|
||||
AddInput(kUseArgsInput, NodeValue::kBoolean, true, InputFlags(kInputFlagHidden | kInputFlagStatic));
|
||||
|
||||
AddInput(kArgsInput, NodeValue::kText, InputFlags(kInputFlagArray));
|
||||
SetInputProperty(kArgsInput, QStringLiteral("arraystart"), 1);
|
||||
|
||||
text_gizmo_ = new TextGizmo(this);
|
||||
text_gizmo_->SetInput(NodeInput(this, kTextInput));
|
||||
connect(text_gizmo_, &TextGizmo::Activated, this, &TextGeneratorV3::GizmoActivated);
|
||||
connect(text_gizmo_, &TextGizmo::Deactivated, this, &TextGeneratorV3::GizmoDeactivated);
|
||||
}
|
||||
|
||||
QString TextGeneratorV3::Name() const
|
||||
@@ -77,15 +91,32 @@ void TextGeneratorV3::Retranslate()
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(kTextInput, tr("Text"));
|
||||
SetInputName(kVerticalAlignmentInput, tr("Vertical Alignment"));
|
||||
SetComboBoxStrings(kVerticalAlignmentInput, {tr("Top"), tr("Middle"), tr("Bottom")});
|
||||
SetInputName(kArgsInput, tr("Arguments"));
|
||||
}
|
||||
|
||||
void TextGeneratorV3::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
|
||||
{
|
||||
GenerateJob job;
|
||||
job.Insert(value);
|
||||
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
|
||||
job.SetRequestedFormat(VideoParams::kFormatUnsigned8);
|
||||
|
||||
if (value[kUseArgsInput].toBool()) {
|
||||
auto args = value[kArgsInput].toArray();
|
||||
if (!args.empty()) {
|
||||
QStringList list;
|
||||
list.reserve(args.size());
|
||||
for (int i=0; i<args.size(); i++) {
|
||||
list.append(args[i].toString());
|
||||
}
|
||||
|
||||
NodeValue v = job.Get(kTextInput);
|
||||
v.set_value(FormatString(v.toString(), list));
|
||||
job.Insert(kTextInput, v);
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Provide user override for this
|
||||
job.SetColorspace(project()->color_manager()->GetDefaultInputColorSpace());
|
||||
|
||||
@@ -124,6 +155,18 @@ void TextGeneratorV3::GenerateFrame(FramePtr frame, const GenerateJob& job) cons
|
||||
p.translate(frame->video_params().width()/2, frame->video_params().height()/2);
|
||||
p.setClipRect(0, 0, size.x(), size.y());
|
||||
|
||||
switch (static_cast<VerticalAlignment>(job.Get(kVerticalAlignmentInput).toInt())) {
|
||||
case kVAlignTop:
|
||||
// Do nothing
|
||||
break;
|
||||
case kVAlignMiddle:
|
||||
p.translate(0, size.y()/2-text_doc.size().height()/2);
|
||||
break;
|
||||
case kVAlignBottom:
|
||||
p.translate(0, size.y()-text_doc.size().height());
|
||||
break;
|
||||
}
|
||||
|
||||
// Ensure default text color is white
|
||||
QAbstractTextDocumentLayout::PaintContext ctx;
|
||||
ctx.palette.setColor(QPalette::Text, Qt::white);
|
||||
@@ -140,4 +183,97 @@ void TextGeneratorV3::UpdateGizmoPositions(const NodeValueRow &row, const NodeGl
|
||||
text_gizmo_->SetHtml(row[kTextInput].toString());
|
||||
}
|
||||
|
||||
Qt::Alignment TextGeneratorV3::GetQtAlignmentFromOurs(VerticalAlignment v)
|
||||
{
|
||||
switch (v) {
|
||||
case kVAlignTop:
|
||||
return Qt::AlignTop;
|
||||
case kVAlignMiddle:
|
||||
return Qt::AlignVCenter;
|
||||
case kVAlignBottom:
|
||||
return Qt::AlignBottom;
|
||||
}
|
||||
return Qt::Alignment();
|
||||
}
|
||||
|
||||
TextGeneratorV3::VerticalAlignment TextGeneratorV3::GetOurAlignmentFromQts(Qt::Alignment v)
|
||||
{
|
||||
switch (v) {
|
||||
case Qt::AlignTop:
|
||||
return kVAlignTop;
|
||||
case Qt::AlignVCenter:
|
||||
return kVAlignMiddle;
|
||||
case Qt::AlignBottom:
|
||||
return kVAlignBottom;
|
||||
}
|
||||
|
||||
return kVAlignTop;
|
||||
}
|
||||
|
||||
QString TextGeneratorV3::FormatString(const QString &input, const QStringList &args)
|
||||
{
|
||||
QString output;
|
||||
output.reserve(input.size());
|
||||
|
||||
for (int i=0; i<input.size(); i++) {
|
||||
const QChar &this_char = input.at(i);
|
||||
|
||||
if (i < input.size()-1 && this_char == '%') {
|
||||
const QChar &next_char = input.at(i+1);
|
||||
if (next_char == '%') {
|
||||
// Double percent, append a single percent
|
||||
output.append('%');
|
||||
i++;
|
||||
} else if (next_char.isDigit()) {
|
||||
// Find length of number
|
||||
QString num;
|
||||
i++;
|
||||
while (i < input.size() && input.at(i).isDigit()) {
|
||||
num.append(input.at(i));
|
||||
i++;
|
||||
}
|
||||
i--;
|
||||
int index = num.toInt()-1;
|
||||
if (index >= 0 && index < args.size()) {
|
||||
output.append(args.at(index));
|
||||
}
|
||||
} else {
|
||||
output.append(this_char);
|
||||
}
|
||||
} else {
|
||||
output.append(this_char);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
void TextGeneratorV3::InputValueChangedEvent(const QString &input, int element)
|
||||
{
|
||||
if (input == kVerticalAlignmentInput && !dont_emit_valign_) {
|
||||
text_gizmo_->SetVerticalAlignment(GetQtAlignmentFromOurs(GetVerticalAlignment()));
|
||||
}
|
||||
|
||||
super::InputValueChangedEvent(input, element);
|
||||
}
|
||||
|
||||
void TextGeneratorV3::GizmoActivated()
|
||||
{
|
||||
SetStandardValue(kUseArgsInput, false);
|
||||
connect(text_gizmo_, &TextGizmo::VerticalAlignmentChanged, this, &TextGeneratorV3::SetVerticalAlignmentUndoable);
|
||||
dont_emit_valign_ = true;
|
||||
}
|
||||
|
||||
void TextGeneratorV3::GizmoDeactivated()
|
||||
{
|
||||
SetStandardValue(kUseArgsInput, true);
|
||||
disconnect(text_gizmo_, &TextGizmo::VerticalAlignmentChanged, this, &TextGeneratorV3::SetVerticalAlignmentUndoable);
|
||||
dont_emit_valign_ = true;
|
||||
}
|
||||
|
||||
void TextGeneratorV3::SetVerticalAlignmentUndoable(Qt::Alignment a)
|
||||
{
|
||||
Core::instance()->undo_stack()->push(new NodeParamSetStandardValueCommand(NodeInput(this, kVerticalAlignmentInput), GetOurAlignmentFromQts(a)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -47,11 +47,41 @@ public:
|
||||
|
||||
virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override;
|
||||
|
||||
enum VerticalAlignment
|
||||
{
|
||||
kVAlignTop,
|
||||
kVAlignMiddle,
|
||||
kVAlignBottom
|
||||
};
|
||||
|
||||
VerticalAlignment GetVerticalAlignment() const
|
||||
{
|
||||
return static_cast<VerticalAlignment>(GetStandardValue(kVerticalAlignmentInput).toInt());
|
||||
}
|
||||
|
||||
static Qt::Alignment GetQtAlignmentFromOurs(VerticalAlignment v);
|
||||
static VerticalAlignment GetOurAlignmentFromQts(Qt::Alignment v);
|
||||
|
||||
static const QString kTextInput;
|
||||
static const QString kVerticalAlignmentInput;
|
||||
static const QString kUseArgsInput;
|
||||
static const QString kArgsInput;
|
||||
|
||||
static QString FormatString(const QString &input, const QStringList &args);
|
||||
|
||||
protected:
|
||||
virtual void InputValueChangedEvent(const QString &input, int element) override;
|
||||
|
||||
private:
|
||||
TextGizmo *text_gizmo_;
|
||||
|
||||
bool dont_emit_valign_;
|
||||
|
||||
private slots:
|
||||
void GizmoActivated();
|
||||
void GizmoDeactivated();
|
||||
void SetVerticalAlignmentUndoable(Qt::Alignment a);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -28,4 +28,9 @@ NodeGizmo::NodeGizmo(QObject *parent) :
|
||||
setParent(parent);
|
||||
}
|
||||
|
||||
NodeGizmo::~NodeGizmo()
|
||||
{
|
||||
setParent(nullptr);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ class NodeGizmo : public QObject
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit NodeGizmo(QObject *parent = nullptr);
|
||||
virtual ~NodeGizmo() override;
|
||||
|
||||
virtual void Draw(QPainter *p) const {}
|
||||
|
||||
|
||||
@@ -26,7 +26,8 @@
|
||||
namespace olive {
|
||||
|
||||
TextGizmo::TextGizmo(QObject *parent)
|
||||
: NodeGizmo{parent}
|
||||
: NodeGizmo{parent},
|
||||
valign_(Qt::AlignTop)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -42,6 +42,22 @@ public:
|
||||
|
||||
void UpdateInputHtml(const QString &s, const rational &time);
|
||||
|
||||
Qt::Alignment GetVerticalAlignment() const
|
||||
{
|
||||
return valign_;
|
||||
}
|
||||
|
||||
void SetVerticalAlignment(Qt::Alignment va)
|
||||
{
|
||||
valign_ = va;
|
||||
emit VerticalAlignmentChanged(valign_);
|
||||
}
|
||||
|
||||
signals:
|
||||
void Activated();
|
||||
void Deactivated();
|
||||
void VerticalAlignmentChanged(Qt::Alignment va);
|
||||
|
||||
private:
|
||||
QRectF rect_;
|
||||
|
||||
@@ -49,6 +65,8 @@ private:
|
||||
|
||||
NodeKeyframeTrackReference input_;
|
||||
|
||||
Qt::Alignment valign_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ const NodeKeyframe::Type &NodeKeyframe::type() const
|
||||
void NodeKeyframe::set_type(const NodeKeyframe::Type &type)
|
||||
{
|
||||
if (type_ != type) {
|
||||
type_ = type;
|
||||
set_type_no_bezier_adj(type);
|
||||
|
||||
if (type_ == kBezier) {
|
||||
// Set some sane defaults if this keyframe existed in the track and was just changed
|
||||
@@ -120,11 +120,15 @@ void NodeKeyframe::set_type(const NodeKeyframe::Type &type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emit TypeChanged(type_);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeKeyframe::set_type_no_bezier_adj(const Type &type)
|
||||
{
|
||||
type_ = type;
|
||||
emit TypeChanged(type_);
|
||||
}
|
||||
|
||||
const QPointF &NodeKeyframe::bezier_control_in() const
|
||||
{
|
||||
return bezier_control_in_;
|
||||
|
||||
@@ -98,6 +98,7 @@ public:
|
||||
*/
|
||||
const Type& type() const;
|
||||
void set_type(const Type& type);
|
||||
void set_type_no_bezier_adj(const Type& type);
|
||||
|
||||
/**
|
||||
* @brief For bezier interpolation, the control point leading into this keyframe
|
||||
|
||||
@@ -132,7 +132,6 @@ void ChromaKeyNode::Value(const NodeValueRow &value, const NodeGlobals &globals,
|
||||
ColorTransformJob job;
|
||||
|
||||
job.Insert(value);
|
||||
job.SetAlphaChannelRequired(ColorTransformJob::kAlphaForceOn);
|
||||
job.SetColorProcessor(processor());
|
||||
job.SetInputTexture(value[kTextureInput].toTexture());
|
||||
job.SetNeedsCustomShader(this);
|
||||
|
||||
@@ -95,7 +95,6 @@ void ColorDifferenceKeyNode::Value(const NodeValueRow &value, const NodeGlobals
|
||||
{
|
||||
ShaderJob job;
|
||||
job.Insert(value);
|
||||
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
|
||||
|
||||
// If there's no texture, no need to run an operation
|
||||
if (job.Get(kTextureInput).toTexture()) {
|
||||
|
||||
@@ -372,9 +372,6 @@ void MathNodeBase::ValueInternal(Operation operation, Pairing pairing, const QSt
|
||||
// Replace with adjusted matrix
|
||||
job.Insert(val_a.type() == NodeValue::kTexture ? param_b_in : param_a_in,
|
||||
NodeValue(NodeValue::kMatrix, adjusted_matrix, this));
|
||||
|
||||
// It's likely an alpha channel will result from this operation
|
||||
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -90,12 +90,6 @@ void MergeNode::Value(const NodeValueRow &value, const NodeGlobals &globals, Nod
|
||||
// We only have a base texture, no need to alpha over
|
||||
table->Push(job.Get(kBaseIn));
|
||||
} else {
|
||||
// We have both textures, push the job
|
||||
if (base_tex->channel_count() < VideoParams::kRGBAChannelCount) {
|
||||
// Base has no alpha, therefore this merge operation will not add an alpha channel
|
||||
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOff);
|
||||
}
|
||||
|
||||
table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ TrackList::TrackList(Sequence *parent, const Track::Type &type, const QString &t
|
||||
|
||||
Track *TrackList::GetTrackAt(int index) const
|
||||
{
|
||||
if (index < track_cache_.size()) {
|
||||
if (index >= 0 && index < track_cache_.size()) {
|
||||
return track_cache_.at(index);
|
||||
} else {
|
||||
return nullptr;
|
||||
@@ -81,6 +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);
|
||||
});
|
||||
|
||||
track->set_type(type_);
|
||||
track->set_sequence(parent());
|
||||
|
||||
@@ -101,6 +101,8 @@ signals:
|
||||
|
||||
void TrackRemoved(Track* track);
|
||||
|
||||
void TrackHeightChanged(Track *track, int height);
|
||||
|
||||
private:
|
||||
void UpdateTrackIndexesFrom(int index);
|
||||
|
||||
|
||||
@@ -58,7 +58,8 @@ ViewerOutput::ViewerOutput(bool create_buffer_inputs, bool create_default_stream
|
||||
|
||||
SetFlags(kDontShowInParamView);
|
||||
|
||||
timeline_points_ = new TimelinePoints(this);
|
||||
workarea_ = new TimelineWorkArea(this);
|
||||
markers_ = new TimelineMarkerList(this);
|
||||
}
|
||||
|
||||
QString ViewerOutput::Name() const
|
||||
@@ -331,7 +332,7 @@ rational ViewerOutput::VerifyLengthInternal(Track::Type type) const
|
||||
case Track::kVideo:
|
||||
if (IsInputConnected(kTextureInput)) {
|
||||
NodeValueTable t = traverser.GenerateTable(GetConnectedOutput(kTextureInput), TimeRange(0, 0));
|
||||
rational r = t.Get(NodeValue::kRational, QStringLiteral("length")).value<rational>();
|
||||
rational r = t.Get(NodeValue::kRational, QStringLiteral("length")).toRational();
|
||||
if (!r.isNaN()) {
|
||||
return r;
|
||||
}
|
||||
@@ -340,7 +341,7 @@ rational ViewerOutput::VerifyLengthInternal(Track::Type type) const
|
||||
case Track::kAudio:
|
||||
if (IsInputConnected(kSamplesInput)) {
|
||||
NodeValueTable t = traverser.GenerateTable(GetConnectedOutput(kSamplesInput), TimeRange(0, 0));
|
||||
rational r = t.Get(NodeValue::kRational, QStringLiteral("length")).value<rational>();;
|
||||
rational r = t.Get(NodeValue::kRational, QStringLiteral("length")).toRational();
|
||||
if (!r.isNaN()) {
|
||||
return r;
|
||||
}
|
||||
@@ -375,6 +376,20 @@ Node::ValueHint ViewerOutput::GetConnectedSampleValueHint()
|
||||
return GetValueHintForInput(kSamplesInput);
|
||||
}
|
||||
|
||||
void ViewerOutput::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
|
||||
{
|
||||
if (HasInputWithID(kTextureInput)) {
|
||||
NodeValue repush = value[kTextureInput];
|
||||
repush.set_tag(Track::Reference(Track::kVideo, 0).ToString());
|
||||
table->Push(repush);
|
||||
}
|
||||
if (HasInputWithID(kSamplesInput)) {
|
||||
NodeValue repush = value[kSamplesInput];
|
||||
repush.set_tag(Track::Reference(Track::kAudio, 0).ToString());
|
||||
table->Push(value[kSamplesInput]);
|
||||
}
|
||||
}
|
||||
|
||||
void ViewerOutput::InputValueChangedEvent(const QString &input, int element)
|
||||
{
|
||||
if (element == 0) {
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#ifndef VIEWER_H
|
||||
#define VIEWER_H
|
||||
|
||||
#include "codec/encoder.h"
|
||||
#include "common/rational.h"
|
||||
#include "node/node.h"
|
||||
#include "node/output/track/track.h"
|
||||
@@ -29,7 +30,8 @@
|
||||
#include "render/framehashcache.h"
|
||||
#include "render/subtitleparams.h"
|
||||
#include "render/videoparams.h"
|
||||
#include "timeline/timelinepoints.h"
|
||||
#include "timeline/timelinemarker.h"
|
||||
#include "timeline/timelineworkarea.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -125,7 +127,7 @@ public:
|
||||
return InputArraySize(kSubtitleParamsInput);
|
||||
}
|
||||
|
||||
int GetTotalStreamCount() const
|
||||
virtual int GetTotalStreamCount() const
|
||||
{
|
||||
return GetVideoStreamCount() + GetAudioStreamCount() + GetSubtitleStreamCount();
|
||||
}
|
||||
@@ -142,10 +144,8 @@ public:
|
||||
const rational &GetVideoLength() const { return video_length_; }
|
||||
const rational &GetAudioLength() const { return audio_length_; }
|
||||
|
||||
TimelinePoints* GetTimelinePoints()
|
||||
{
|
||||
return timeline_points_;
|
||||
}
|
||||
TimelineWorkArea *GetWorkArea() const { return workarea_; }
|
||||
TimelineMarkerList *GetMarkers() const { return markers_; }
|
||||
|
||||
QVector<Track::Reference> GetEnabledStreamsAsReferences() const;
|
||||
|
||||
@@ -163,6 +163,11 @@ public:
|
||||
|
||||
virtual ValueHint GetConnectedSampleValueHint();
|
||||
|
||||
virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override;
|
||||
|
||||
const EncodingParams &GetLastUsedEncodingParams() const { return last_used_encoding_params_; }
|
||||
void SetLastUsedEncodingParams(const EncodingParams &p) { last_used_encoding_params_ = p; }
|
||||
|
||||
static const QString kVideoParamsInput;
|
||||
static const QString kAudioParamsInput;
|
||||
static const QString kSubtitleParamsInput;
|
||||
@@ -212,7 +217,10 @@ private:
|
||||
|
||||
AudioParams cached_audio_params_;
|
||||
|
||||
TimelinePoints *timeline_points_;
|
||||
TimelineWorkArea *workarea_;
|
||||
TimelineMarkerList *markers_;
|
||||
|
||||
EncodingParams last_used_encoding_params_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ enum InputFlag {
|
||||
kInputFlagArray = 0x1,
|
||||
kInputFlagNotKeyframable = 0x2,
|
||||
kInputFlagNotConnectable = 0x4,
|
||||
kInputFlagStatic = kInputFlagNotKeyframable | kInputFlagNotConnectable,
|
||||
kInputFlagHidden = 0x8
|
||||
};
|
||||
|
||||
|
||||
@@ -73,6 +73,21 @@ bool Folder::ChildExistsWithName(const QString &s) const
|
||||
return ChildExistsWithNameInternal(this, s);
|
||||
}
|
||||
|
||||
bool Folder::HasChildRecursive(Node *child) const
|
||||
{
|
||||
for (Node *i : item_children_) {
|
||||
if (i == child) {
|
||||
return true;
|
||||
} else if (Folder *f = dynamic_cast<Folder*>(i)) {
|
||||
if (f->HasChildRecursive(child)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int Folder::index_of_child_in_array(Node *item) const
|
||||
{
|
||||
int index_of_item = item_children_.indexOf(item);
|
||||
|
||||
@@ -65,6 +65,8 @@ public:
|
||||
|
||||
bool ChildExistsWithName(const QString& s) const;
|
||||
|
||||
bool HasChildRecursive(Node *child) const;
|
||||
|
||||
int item_child_count() const
|
||||
{
|
||||
return item_children_.size();
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
namespace olive {
|
||||
|
||||
const QString Footage::kFilenameInput = QStringLiteral("file_in");
|
||||
const QString Footage::kLoopModeInput = QStringLiteral("loop_in");
|
||||
|
||||
#define super ViewerOutput
|
||||
|
||||
@@ -45,12 +44,11 @@ Footage::Footage(const QString &filename) :
|
||||
ViewerOutput(false, false),
|
||||
timestamp_(0),
|
||||
valid_(false),
|
||||
cancelled_(nullptr)
|
||||
cancelled_(nullptr),
|
||||
total_stream_count_(0)
|
||||
{
|
||||
SetCacheTextures(true);
|
||||
|
||||
PrependInput(kLoopModeInput, NodeValue::kCombo, 0, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable));
|
||||
|
||||
PrependInput(kFilenameInput, NodeValue::kFile, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable));
|
||||
|
||||
Clear();
|
||||
@@ -70,8 +68,6 @@ void Footage::Retranslate()
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(kFilenameInput, tr("Filename"));
|
||||
SetInputName(kLoopModeInput, tr("Loop Mode"));
|
||||
SetComboBoxStrings(kLoopModeInput, {tr("None"), tr("Loop"), tr("Clamp")});
|
||||
}
|
||||
|
||||
void Footage::InputValueChangedEvent(const QString &input, int element)
|
||||
@@ -130,6 +126,9 @@ void Footage::Clear()
|
||||
// Clear decoder link
|
||||
decoder_.clear();
|
||||
|
||||
// Clear total stream count
|
||||
total_stream_count_ = 0;
|
||||
|
||||
// Reset ready state
|
||||
valid_ = false;
|
||||
}
|
||||
@@ -139,11 +138,6 @@ void Footage::SetValid()
|
||||
valid_ = true;
|
||||
}
|
||||
|
||||
Footage::LoopMode Footage::loop_mode() const
|
||||
{
|
||||
return static_cast<LoopMode>(GetStandardValue(kLoopModeInput).toInt());
|
||||
}
|
||||
|
||||
QString Footage::filename() const
|
||||
{
|
||||
return GetStandardValue(kFilenameInput).toString();
|
||||
@@ -263,17 +257,15 @@ void Footage::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeV
|
||||
// Pop filename from table
|
||||
QString file = value[kFilenameInput].toString();
|
||||
|
||||
LoopMode loop_mode = static_cast<LoopMode>(value[kLoopModeInput].toInt());
|
||||
|
||||
// If the file exists and the reference is valid, push a footage job to the renderer
|
||||
if (QFileInfo(file).exists()) {
|
||||
if (QFileInfo::exists(file)) {
|
||||
// Push length
|
||||
table->Push(NodeValue::kRational, QVariant::fromValue(GetLength()), this, false, QStringLiteral("length"));
|
||||
table->Push(NodeValue::kRational, QVariant::fromValue(GetLength()), this, QStringLiteral("length"));
|
||||
|
||||
// Push each stream as a footage job
|
||||
for (int i=0; i<GetTotalStreamCount(); i++) {
|
||||
Track::Reference ref = GetReferenceFromRealIndex(i);
|
||||
FootageJob job(decoder_, filename(), ref.type(), GetLength(), loop_mode);
|
||||
FootageJob job(decoder_, filename(), ref.type(), GetLength());
|
||||
|
||||
NodeValue::Type type;
|
||||
|
||||
@@ -294,7 +286,7 @@ void Footage::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeV
|
||||
type = NodeValue::kSamples;
|
||||
}
|
||||
|
||||
table->Push(type, QVariant::fromValue(job), this, false, ref.ToString());
|
||||
table->Push(type, QVariant::fromValue(job), this, ref.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -339,7 +331,7 @@ bool TimeIsOutOfBounds(const rational& time, const rational& length)
|
||||
return time < 0 || time >= length;
|
||||
}
|
||||
|
||||
rational Footage::AdjustTimeByLoopMode(rational time, Footage::LoopMode loop_mode, const rational &length, VideoParams::Type type, const rational& timebase)
|
||||
rational Footage::AdjustTimeByLoopMode(rational time, Decoder::LoopMode loop_mode, const rational &length, VideoParams::Type type, const rational& timebase)
|
||||
{
|
||||
if (type == VideoParams::kVideoTypeStill) {
|
||||
// No looping for still images
|
||||
@@ -348,15 +340,15 @@ rational Footage::AdjustTimeByLoopMode(rational time, Footage::LoopMode loop_mod
|
||||
|
||||
if (TimeIsOutOfBounds(time, length)) {
|
||||
switch (loop_mode) {
|
||||
case kLoopModeOff:
|
||||
case Decoder::kLoopModeOff:
|
||||
// Return no time to indicate no frame should be shown here
|
||||
time = rational::NaN;
|
||||
break;
|
||||
case kLoopModeClamp:
|
||||
case Decoder::kLoopModeClamp:
|
||||
// Clamp footage time to length
|
||||
time = clamp(time, rational(0), length - timebase);
|
||||
break;
|
||||
case kLoopModeLoop:
|
||||
case Decoder::kLoopModeLoop:
|
||||
// Loop footage time around job length
|
||||
do {
|
||||
if (time >= length) {
|
||||
@@ -509,6 +501,8 @@ void Footage::Reprobe()
|
||||
SetStream(Track::kSubtitle, QVariant::fromValue(footage_info.GetSubtitleStreams().at(i)), i);
|
||||
}
|
||||
|
||||
total_stream_count_ = footage_info.GetStreamCount();
|
||||
|
||||
SetValid();
|
||||
}
|
||||
|
||||
@@ -524,10 +518,13 @@ VideoParams Footage::MergeVideoStream(const VideoParams &base, const VideoParams
|
||||
merged.set_interlacing(over.interlacing());
|
||||
merged.set_colorspace(over.colorspace());
|
||||
merged.set_premultiplied_alpha(over.premultiplied_alpha());
|
||||
if (merged.video_type() == VideoParams::kVideoTypeImageSequence && over.video_type() == VideoParams::kVideoTypeImageSequence) {
|
||||
merged.set_video_type(over.video_type());
|
||||
merged.set_color_range(over.color_range());
|
||||
if (merged.video_type() == VideoParams::kVideoTypeImageSequence) {
|
||||
merged.set_start_time(over.start_time());
|
||||
merged.set_duration(over.duration());
|
||||
merged.set_frame_rate(over.frame_rate());
|
||||
merged.set_time_base(over.time_base());
|
||||
}
|
||||
|
||||
return merged;
|
||||
|
||||
@@ -24,12 +24,13 @@
|
||||
#include <QList>
|
||||
#include <QDateTime>
|
||||
|
||||
#include "codec/decoder.h"
|
||||
#include "common/rational.h"
|
||||
#include "footagedescription.h"
|
||||
#include "node/output/viewer/viewer.h"
|
||||
#include "render/audioparams.h"
|
||||
#include "render/cancelatom.h"
|
||||
#include "render/videoparams.h"
|
||||
#include "timeline/timelinepoints.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -44,12 +45,6 @@ class Footage : public ViewerOutput
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum LoopMode {
|
||||
kLoopModeOff,
|
||||
kLoopModeLoop,
|
||||
kLoopModeClamp
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Footage Constructor
|
||||
*/
|
||||
@@ -101,11 +96,6 @@ public:
|
||||
*/
|
||||
void SetValid();
|
||||
|
||||
/**
|
||||
* @brief Get currently set loop mode
|
||||
*/
|
||||
LoopMode loop_mode() const;
|
||||
|
||||
/**
|
||||
* @brief Return the current filename of this Footage object
|
||||
*/
|
||||
@@ -142,7 +132,7 @@ public:
|
||||
*/
|
||||
void set_timestamp(const qint64 &t);
|
||||
|
||||
void SetCancelPointer(const QAtomicInt* c)
|
||||
void SetCancelPointer(CancelAtom *c)
|
||||
{
|
||||
cancelled_ = c;
|
||||
}
|
||||
@@ -183,15 +173,16 @@ public:
|
||||
|
||||
virtual Node *GetConnectedSampleOutput() override;
|
||||
|
||||
static rational AdjustTimeByLoopMode(rational time, LoopMode loop_mode, const rational& length, VideoParams::Type type, const rational &timebase);
|
||||
static rational AdjustTimeByLoopMode(rational time, Decoder::LoopMode loop_mode, const rational& length, VideoParams::Type type, const rational &timebase);
|
||||
|
||||
virtual void LoadFinishedEvent() override;
|
||||
|
||||
virtual qint64 creation_time() const override;
|
||||
virtual qint64 mod_time() const override;
|
||||
|
||||
virtual int GetTotalStreamCount() const override { return total_stream_count_; }
|
||||
|
||||
static const QString kFilenameInput;
|
||||
static const QString kLoopModeInput;
|
||||
|
||||
protected:
|
||||
virtual void InputValueChangedEvent(const QString &input, int element) override;
|
||||
@@ -233,7 +224,9 @@ private:
|
||||
|
||||
bool valid_;
|
||||
|
||||
const QAtomicInt* cancelled_;
|
||||
CancelAtom *cancelled_;
|
||||
|
||||
int total_stream_count_;
|
||||
|
||||
private slots:
|
||||
void CheckFootage();
|
||||
|
||||
@@ -43,9 +43,11 @@ bool FootageDescription::Load(const QString &filename)
|
||||
// Default to first version of metadata (which wasn't versioned at all)
|
||||
unsigned version = 1;
|
||||
|
||||
XMLAttributeLoop((&reader), attr) {
|
||||
if (attr.name() == QStringLiteral("version")) {
|
||||
version = attr.value().toUInt();
|
||||
{
|
||||
XMLAttributeLoop((&reader), attr) {
|
||||
if (attr.name() == QStringLiteral("version")) {
|
||||
version = attr.value().toUInt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +60,14 @@ bool FootageDescription::Load(const QString &filename)
|
||||
if (reader.name() == QStringLiteral("decoder")) {
|
||||
decoder_ = reader.readElementText();
|
||||
} else if (reader.name() == QStringLiteral("streams")) {
|
||||
{
|
||||
XMLAttributeLoop((&reader), attr) {
|
||||
if (attr.name() == QStringLiteral("count")) {
|
||||
total_stream_count_ = attr.value().toInt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (XMLReadNextStartElement(&reader)) {
|
||||
if (reader.name() == QStringLiteral("video")) {
|
||||
VideoParams vp;
|
||||
@@ -116,6 +126,8 @@ bool FootageDescription::Save(const QString &filename) const
|
||||
|
||||
writer.writeStartElement(QStringLiteral("streams"));
|
||||
|
||||
writer.writeAttribute(QStringLiteral("count"), QString::number(total_stream_count_));
|
||||
|
||||
foreach (const VideoParams& vp, video_streams_) {
|
||||
writer.writeStartElement(QStringLiteral("video"));
|
||||
vp.Save(&writer);
|
||||
|
||||
@@ -32,7 +32,8 @@ class FootageDescription
|
||||
{
|
||||
public:
|
||||
FootageDescription(const QString& decoder = QString()) :
|
||||
decoder_(decoder)
|
||||
decoder_(decoder),
|
||||
total_stream_count_(0)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -118,6 +119,9 @@ public:
|
||||
return StreamIsVideo(index) || StreamIsAudio(index) || StreamIsSubtitle(index);
|
||||
}
|
||||
|
||||
int GetStreamCount() const { return total_stream_count_; }
|
||||
void SetStreamCount(int s) { total_stream_count_ = s; }
|
||||
|
||||
bool Load(const QString& filename);
|
||||
|
||||
bool Save(const QString& filename) const;
|
||||
@@ -138,7 +142,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr unsigned kFootageMetaVersion = 2;
|
||||
static constexpr unsigned kFootageMetaVersion = 5;
|
||||
|
||||
QString decoder_;
|
||||
|
||||
@@ -148,6 +152,8 @@ private:
|
||||
|
||||
QVector<SubtitleParams> subtitle_streams_;
|
||||
|
||||
int total_stream_count_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user