Merge branch 'master' into ocio_node

This commit is contained in:
ThomasWilshaw
2022-03-08 16:39:14 +00:00
committed by GitHub
52 changed files with 1464 additions and 142 deletions
+43 -6
View File
@@ -285,12 +285,14 @@ jobs:
os-arch: x86_64 os-arch: x86_64
os: macos-10.15 os: macos-10.15
cmake-gen: Ninja cmake-gen: Ninja
min-deploy: 10.13
- build-type: RelWithDebInfo - build-type: RelWithDebInfo
compiler-name: Clang LLVM compiler-name: Clang LLVM
os-name: macOS os-name: macOS
os-arch: arm64 os-arch: arm64
os: macos-11.0 os: macos-11.0
cmake-gen: Ninja cmake-gen: Ninja
min-deploy: 11.0
env: env:
DEP_LOCATION: /opt/olive-editor DEP_LOCATION: /opt/olive-editor
name: | name: |
@@ -349,7 +351,7 @@ jobs:
brew install ninja brew install ninja
PATH=$DEP_LOCATION:$DEP_LOCATION/bin:$DEP_LOCATION/include:$DEP_LOCATION/lib:$DEP_LOCATION/crashpad:$PATH \ 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 }} \ cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 -G "${{ matrix.cmake-gen }}" \ -DCMAKE_OSX_DEPLOYMENT_TARGET=${{ matrix.min-deploy }} -G "${{ matrix.cmake-gen }}" \
-DCMAKE_OSX_ARCHITECTURES="${{ matrix.os-arch }}" -DCMAKE_OSX_ARCHITECTURES="${{ matrix.os-arch }}"
- name: Build - name: Build
@@ -367,10 +369,9 @@ jobs:
- name: Create Package - name: Create Package
working-directory: ${{ runner.workspace }}/build working-directory: ${{ runner.workspace }}/build
env:
BUNDLE_NAME: "Olive.app"
shell: bash shell: bash
run: | run: |
BUNDLE_NAME="Olive.app"
brew install dylibbundler brew install dylibbundler
if [ "${{ matrix.os-arch }}" == "x86_64" ] if [ "${{ matrix.os-arch }}" == "x86_64" ]
@@ -383,12 +384,12 @@ jobs:
dylibbundler -b -ns -x "$BUNDLE_NAME/Contents/MacOS/Olive" -s "$DEP_LOCATION/lib" -d "$BUNDLE_NAME/Contents/Frameworks" -p "@executable_path/../Frameworks" $DYLIBBUNDLER_EXTRA_ARGS dylibbundler -b -ns -x "$BUNDLE_NAME/Contents/MacOS/Olive" -s "$DEP_LOCATION/lib" -d "$BUNDLE_NAME/Contents/Frameworks" -p "@executable_path/../Frameworks" $DYLIBBUNDLER_EXTRA_ARGS
# Copy Qt frameworks and plugins # Copy Qt frameworks and plugins
cp -R $DEP_LOCATION/lib/Qt*.framework $BUNDLE_NAME/Contents/Frameworks cp -Ra $DEP_LOCATION/lib/Qt*.framework $BUNDLE_NAME/Contents/Frameworks
cp -R $DEP_LOCATION/plugins $BUNDLE_NAME/Contents cp -Ra $DEP_LOCATION/plugins $BUNDLE_NAME/Contents
# HACK: On x86_64, dylibbundler doesn't resolve this symlink. Weirdly it does on ARM64, # HACK: On x86_64, dylibbundler doesn't resolve this symlink. Weirdly it does on ARM64,
# but perhaps I'll bring it up with them soon. # but perhaps I'll bring it up with them soon.
cp $BUNDLE_NAME/Contents/Frameworks/libpng16.16.37.0.dylib $BUNDLE_NAME/Contents/Frameworks/libpng16.16.dylib cp -a $BUNDLE_NAME/Contents/Frameworks/libpng16.16.37.0.dylib $BUNDLE_NAME/Contents/Frameworks/libpng16.16.dylib
if [ "${{ matrix.os-arch }}" == "x86_64" ] if [ "${{ matrix.os-arch }}" == "x86_64" ]
then then
@@ -400,6 +401,42 @@ jobs:
mv Olive.sym "$SYM_DIR" mv Olive.sym "$SYM_DIR"
fi fi
- name: Sign Application
working-directory: ${{ runner.workspace }}/build
shell: bash
env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
BUNDLE_NAME="Olive.app"
# Install certificate
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
# import certificate from secrets
echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode --output $CERTIFICATE_PATH
# create temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# import certificate to keychain
security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
# HACK: Remove unsignable frameworks
rm -r $BUNDLE_NAME/Contents/Frameworks/QtUiPlugin.framework
if [ "${{ matrix.os-arch }}" == "arm64" ]
then
rm -r $BUNDLE_NAME/Contents/Frameworks/QtZlib.framework
fi
# Sign application
codesign --deep --sign "Developer ID Application: Olive Studios LLC" $BUNDLE_NAME
- name: Deploy Packages - name: Deploy Packages
working-directory: ${{ runner.workspace }}/build working-directory: ${{ runner.workspace }}/build
shell: bash shell: bash
+2
View File
@@ -22,6 +22,8 @@ set(OLIVE_SOURCES
audio/audiovisualwaveform.h audio/audiovisualwaveform.h
audio/packedprocessor.cpp audio/packedprocessor.cpp
audio/packedprocessor.h audio/packedprocessor.h
audio/planarprocessor.cpp
audio/planarprocessor.h
audio/tempoprocessor.cpp audio/tempoprocessor.cpp
audio/tempoprocessor.h audio/tempoprocessor.h
PARENT_SCOPE PARENT_SCOPE
+3 -8
View File
@@ -76,17 +76,12 @@ QByteArray PackedProcessor::Convert(SampleBufferPtr planar)
return QByteArray(); return QByteArray();
} }
int nb_channels = planar->audio_params().channel_count();
QByteArray output(planar->audio_params().samples_to_bytes(nb_samples), Qt::Uninitialized); QByteArray output(planar->audio_params().samples_to_bytes(nb_samples), Qt::Uninitialized);
uint8_t *output_data = reinterpret_cast<uint8_t*>(output.data()); uint8_t *output_data = reinterpret_cast<uint8_t*>(output.data());
QVector<const uint8_t*> input_arrays(nb_channels); int ret = swr_convert(swr_ctx_, &output_data, nb_samples,
for (int i=0; i<nb_channels; i++) { const_cast<const uint8_t**>(reinterpret_cast<uint8_t**>(planar->to_raw_ptrs())),
input_arrays[i] = reinterpret_cast<const uint8_t*>(planar->data(i)); nb_samples);
}
int ret = swr_convert(swr_ctx_, &output_data, nb_samples, input_arrays.data(), nb_samples);
if (ret < 0) { if (ret < 0) {
char buf[200]; char buf[200];
av_strerror(ret, buf, 200); av_strerror(ret, buf, 200);
+104
View File
@@ -0,0 +1,104 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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 "planarprocessor.h"
#include "common/ffmpegutils.h"
namespace olive {
PlanarProcessor::PlanarProcessor() :
swr_ctx_(nullptr)
{
}
PlanarProcessor::~PlanarProcessor()
{
Close();
}
bool PlanarProcessor::Open(const AudioParams &params)
{
if (IsOpen()) {
return true;
}
swr_ctx_ = swr_alloc_set_opts(nullptr,
params.channel_layout(),
FFmpegUtils::GetFFmpegSampleFormat(params.format(), true),
params.sample_rate(),
params.channel_layout(),
FFmpegUtils::GetFFmpegSampleFormat(params.format(), false),
params.sample_rate(),
0,
nullptr);
if (!swr_ctx_) {
qCritical() << "Failed to allocate resample context";
return false;
}
if (swr_init(swr_ctx_) < 0) {
qCritical() << "Failed to init resample context";
swr_free(&swr_ctx_);
return false;
}
params_ = params;
return true;
}
SampleBufferPtr PlanarProcessor::Convert(const QByteArray &packed)
{
if (!IsOpen()) {
qCritical() << "Tried to convert while closed";
return nullptr;
}
if (packed.isEmpty()) {
return nullptr;
}
int nb_samples_per_channel = params_.bytes_to_samples(packed.size());
SampleBufferPtr output = SampleBuffer::CreateAllocated(params_, nb_samples_per_channel);
const uint8_t *input = reinterpret_cast<const uint8_t*>(packed.constData());
int ret = swr_convert(swr_ctx_,
reinterpret_cast<uint8_t**>(output->to_raw_ptrs()), nb_samples_per_channel,
&input, nb_samples_per_channel);
if (ret < 0) {
char buf[200];
av_strerror(ret, buf, 200);
qDebug() << "Planar processor failed with error:" << buf << ret;
}
return output;
}
void PlanarProcessor::Close()
{
if (swr_ctx_) {
swr_free(&swr_ctx_);
}
}
}
+62
View File
@@ -0,0 +1,62 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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 PLANARPROCESSOR_H
#define PLANARPROCESSOR_H
extern "C" {
#include <libswresample/swresample.h>
}
#include "codec/samplebuffer.h"
#include "render/audioparams.h"
namespace olive {
class PlanarProcessor
{
public:
PlanarProcessor();
~PlanarProcessor();
DISABLE_COPY_MOVE(PlanarProcessor)
bool Open(const AudioParams &params);
SampleBufferPtr Convert(const QByteArray &packed);
void Close();
bool IsOpen() const
{
return swr_ctx_;
}
private:
SwrContext *swr_ctx_;
AudioParams params_;
};
}
#endif // PLANARPROCESSOR_H
+56 -68
View File
@@ -36,7 +36,6 @@ TempoProcessor::TempoProcessor() :
filter_graph_(nullptr), filter_graph_(nullptr),
buffersrc_ctx_(nullptr), buffersrc_ctx_(nullptr),
buffersink_ctx_(nullptr), buffersink_ctx_(nullptr),
processed_frame_(nullptr),
open_(false) open_(false)
{ {
} }
@@ -150,47 +149,42 @@ bool TempoProcessor::Open(const AudioParams &params, const double& speed)
return true; return true;
} }
void TempoProcessor::Push(const char *data, int length) void TempoProcessor::Push(const QByteArray &packed)
{ {
if (flushed_) { if (!IsOpen()) {
if (length > 0) { qWarning() << "Tried to push to closed TempoProcessor";
qCritical() << "Tried to push" << length << "bytes after TempoProcessor was closed";
}
return; return;
} }
AVFrame* src_frame; if (flushed_) {
qWarning() << "Tried to push to flushed TempoProcessor";
if (length == 0) { return;
// No audio data, flush the last out of the filter graph
src_frame = nullptr;
flushed_ = true;
} else {
src_frame = av_frame_alloc();
if (!src_frame) {
qCritical() << "Failed to allocate source frame";
return;
}
// Allocate a buffer for the number of samples we got
src_frame->sample_rate = params_.sample_rate();
src_frame->format = FFmpegUtils::GetFFmpegSampleFormat(params_.format());
src_frame->channel_layout = params_.channel_layout();
src_frame->nb_samples = params_.bytes_to_samples(length);
src_frame->pts = timestamp_;
timestamp_ += src_frame->nb_samples;
if (av_frame_get_buffer(src_frame, 0) < 0) {
qCritical() << "Failed to allocate buffer for source frame";
av_frame_free(&src_frame);
return;
}
// Copy buffer from data array to frame
memcpy(src_frame->data[0], data, static_cast<size_t>(length));
} }
AVFrame* src_frame = av_frame_alloc();
if (!src_frame) {
qCritical() << "Failed to allocate source frame";
return;
}
// Allocate a buffer for the number of samples we got
src_frame->sample_rate = params_.sample_rate();
src_frame->format = FFmpegUtils::GetFFmpegSampleFormat(params_.format());
src_frame->channel_layout = params_.channel_layout();
src_frame->nb_samples = params_.bytes_to_samples(packed.size());
src_frame->pts = timestamp_;
timestamp_ += src_frame->nb_samples;
if (av_frame_get_buffer(src_frame, 0) < 0) {
qCritical() << "Failed to allocate buffer for source frame";
av_frame_free(&src_frame);
return;
}
// Copy buffer from data array to frame
memcpy(src_frame->data[0], packed, packed.size());
int ret = av_buffersrc_add_frame_flags(buffersrc_ctx_, src_frame, AV_BUFFERSRC_FLAG_KEEP_REF); int ret = av_buffersrc_add_frame_flags(buffersrc_ctx_, src_frame, AV_BUFFERSRC_FLAG_KEEP_REF);
if (ret < 0) { if (ret < 0) {
@@ -202,46 +196,45 @@ void TempoProcessor::Push(const char *data, int length)
} }
} }
int TempoProcessor::Pull(char *data, int max_length) void TempoProcessor::Flush()
{ {
if (!processed_frame_) { if (!flushed_) {
processed_frame_ = av_frame_alloc(); int ret = av_buffersrc_add_frame_flags(buffersrc_ctx_, nullptr, AV_BUFFERSRC_FLAG_KEEP_REF);
// Try to pull samples from the buffersink
int ret = av_buffersink_get_frame(buffersink_ctx_, processed_frame_);
if (ret < 0) { if (ret < 0) {
// We couldn't pull for some reason, if the error was EAGAIN, we just need to send more samples. Otherwise the qCritical() << "Failed to feed buffer source" << ret;
// error might be fatal... }
if (ret != AVERROR(EAGAIN)) { flushed_ = true;
qCritical() << "Failed to pull from buffersink" << ret; }
} }
av_frame_free(&processed_frame_); QByteArray TempoProcessor::Pull()
{
QByteArray b;
AVFrame *processed_frame = av_frame_alloc();
return 0; // Try to pull samples from the buffersink
int ret = av_buffersink_get_frame(buffersink_ctx_, processed_frame);
if (ret < 0) {
// We couldn't pull for some reason, if the error was EAGAIN, we just need to send more samples. Otherwise the
// error might be fatal...
if (ret != AVERROR(EAGAIN)) {
qCritical() << "Failed to pull from buffersink" << ret;
} }
processed_frame_byte_index_ = 0; av_frame_free(&processed_frame);
processed_frame_max_bytes_ = params_.samples_to_bytes(processed_frame_->nb_samples); return b;
} }
// Determine how many bytes we should copy into the data array b.resize(params_.samples_to_bytes(processed_frame->nb_samples));
int copy_length = qMin(max_length, processed_frame_max_bytes_ - processed_frame_byte_index_);
// Copy the bytes // Copy the bytes
memcpy(data, processed_frame_->data[0] + processed_frame_byte_index_, static_cast<size_t>(copy_length)); memcpy(b.data(), processed_frame->data[0], b.size());
// Add the copied amount to the current index
processed_frame_byte_index_ += copy_length;
// If the index has reached the limit of this processed frame, we can dispose of the frame now // If the index has reached the limit of this processed frame, we can dispose of the frame now
if (processed_frame_byte_index_ == processed_frame_max_bytes_) { av_frame_free(&processed_frame);
av_frame_free(&processed_frame_);
processed_frame_ = nullptr;
}
return copy_length; return b;
} }
void TempoProcessor::Close() void TempoProcessor::Close()
@@ -253,11 +246,6 @@ void TempoProcessor::Close()
filter_graph_ = nullptr; filter_graph_ = nullptr;
} }
if (processed_frame_) {
av_frame_free(&processed_frame_);
processed_frame_ = nullptr;
}
buffersrc_ctx_ = nullptr; buffersrc_ctx_ = nullptr;
buffersink_ctx_ = nullptr; buffersink_ctx_ = nullptr;
} }
+4 -6
View File
@@ -52,9 +52,11 @@ public:
bool Open(const AudioParams& params, const double &speed); bool Open(const AudioParams& params, const double &speed);
void Push(const char *data, int length); void Push(const QByteArray &packed);
int Pull(char* data, int max_length); void Flush();
QByteArray Pull();
void Close(); void Close();
@@ -67,10 +69,6 @@ private:
AVFilterContext* buffersink_ctx_; AVFilterContext* buffersink_ctx_;
AVFrame* processed_frame_;
int processed_frame_byte_index_;
int processed_frame_max_bytes_;
AudioParams params_; AudioParams params_;
int64_t timestamp_; int64_t timestamp_;
+3
View File
@@ -44,6 +44,8 @@ QString ExportCodec::GetCodecName(ExportCodec::Codec c)
return tr("PNG"); return tr("PNG");
case kCodecProRes: case kCodecProRes:
return tr("ProRes"); return tr("ProRes");
case kCodecCineform:
return tr("Cineform");
case kCodecTIFF: case kCodecTIFF:
return tr("TIFF"); return tr("TIFF");
case kCodecMP2: case kCodecMP2:
@@ -79,6 +81,7 @@ bool ExportCodec::IsCodecAStillImage(ExportCodec::Codec c)
case kCodecH264rgb: case kCodecH264rgb:
case kCodecH265: case kCodecH265:
case kCodecProRes: case kCodecProRes:
case kCodecCineform:
case kCodecMP2: case kCodecMP2:
case kCodecMP3: case kCodecMP3:
case kCodecAAC: case kCodecAAC:
+1
View File
@@ -42,6 +42,7 @@ public:
kCodecOpenEXR, kCodecOpenEXR,
kCodecPNG, kCodecPNG,
kCodecProRes, kCodecProRes,
kCodecCineform,
kCodecTIFF, kCodecTIFF,
kCodecVP9, kCodecVP9,
+1 -1
View File
@@ -117,7 +117,7 @@ QList<ExportCodec::Codec> ExportFormat::GetVideoCodecs(ExportFormat::Format f)
case kFormatTIFF: case kFormatTIFF:
return {ExportCodec::kCodecTIFF}; return {ExportCodec::kCodecTIFF};
case kFormatQuickTime: case kFormatQuickTime:
return {ExportCodec::kCodecH264, ExportCodec::kCodecH264rgb, ExportCodec::kCodecH265, ExportCodec::kCodecProRes}; return {ExportCodec::kCodecH264, ExportCodec::kCodecH264rgb, ExportCodec::kCodecH265, ExportCodec::kCodecProRes, ExportCodec::kCodecCineform};
case kFormatWebM: case kFormatWebM:
return {ExportCodec::kCodecVP9}; return {ExportCodec::kCodecVP9};
case kFormatOgg: case kFormatOgg:
+2 -2
View File
@@ -268,7 +268,7 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, const QAtomicIn
AVStream* avstream = fmt_ctx->streams[i]; AVStream* avstream = fmt_ctx->streams[i];
// Find decoder for this stream, if it exists we can proceed // Find decoder for this stream, if it exists we can proceed
AVCodec* decoder = avcodec_find_decoder(avstream->codecpar->codec_id); const AVCodec* decoder = avcodec_find_decoder(avstream->codecpar->codec_id);
if (decoder if (decoder
&& (avstream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && (avstream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO
@@ -1010,7 +1010,7 @@ bool FFmpegDecoder::Instance::Open(const char *filename, int stream_index)
avstream_ = fmt_ctx_->streams[stream_index]; avstream_ = fmt_ctx_->streams[stream_index];
// Find decoder // Find decoder
AVCodec* codec = avcodec_find_decoder(avstream_->codecpar->codec_id); const AVCodec* codec = avcodec_find_decoder(avstream_->codecpar->codec_id);
// Handle failure to find decoder // Handle failure to find decoder
if (codec == nullptr) { if (codec == nullptr) {
+7 -5
View File
@@ -50,7 +50,7 @@ QStringList FFmpegEncoder::GetPixelFormatsForCodec(ExportCodec::Codec c) const
{ {
QStringList pix_fmts; QStringList pix_fmts;
AVCodec* codec_info = GetEncoder(c); const AVCodec* codec_info = GetEncoder(c);
if (codec_info) { if (codec_info) {
for (int i=0; codec_info->pix_fmts[i]!=-1; i++) { for (int i=0; codec_info->pix_fmts[i]!=-1; i++) {
@@ -570,7 +570,7 @@ bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AV
} }
// Find encoder // Find encoder
AVCodec* encoder = GetEncoder(codec); const AVCodec* encoder = GetEncoder(codec);
if (!encoder) { if (!encoder) {
SetError(tr("Failed to find codec for 0x%1").arg(codec, 16)); SetError(tr("Failed to find codec for 0x%1").arg(codec, 16));
return false; return false;
@@ -669,7 +669,7 @@ bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AV
return true; return true;
} }
bool FFmpegEncoder::InitializeCodecContext(AVStream **stream, AVCodecContext **codec_ctx, AVCodec* codec) bool FFmpegEncoder::InitializeCodecContext(AVStream **stream, AVCodecContext **codec_ctx, const AVCodec* codec)
{ {
*stream = avformat_new_stream(fmt_ctx_, nullptr); *stream = avformat_new_stream(fmt_ctx_, nullptr);
if (!(*stream)) { if (!(*stream)) {
@@ -687,7 +687,7 @@ bool FFmpegEncoder::InitializeCodecContext(AVStream **stream, AVCodecContext **c
return true; return true;
} }
bool FFmpegEncoder::SetupCodecContext(AVStream* stream, AVCodecContext* codec_ctx, AVCodec* codec) bool FFmpegEncoder::SetupCodecContext(AVStream* stream, AVCodecContext* codec_ctx, const AVCodec* codec)
{ {
int error_code; int error_code;
@@ -833,7 +833,7 @@ bool FFmpegEncoder::InitializeResampleContext(SampleBufferPtr audio)
return true; return true;
} }
AVCodec *FFmpegEncoder::GetEncoder(ExportCodec::Codec c) const AVCodec *FFmpegEncoder::GetEncoder(ExportCodec::Codec c)
{ {
switch (c) { switch (c) {
case ExportCodec::kCodecH264: case ExportCodec::kCodecH264:
@@ -844,6 +844,8 @@ AVCodec *FFmpegEncoder::GetEncoder(ExportCodec::Codec c)
return avcodec_find_encoder(AV_CODEC_ID_DNXHD); return avcodec_find_encoder(AV_CODEC_ID_DNXHD);
case ExportCodec::kCodecProRes: case ExportCodec::kCodecProRes:
return avcodec_find_encoder(AV_CODEC_ID_PRORES); return avcodec_find_encoder(AV_CODEC_ID_PRORES);
case ExportCodec::kCodecCineform:
return avcodec_find_encoder(AV_CODEC_ID_CFHD);
case ExportCodec::kCodecH265: case ExportCodec::kCodecH265:
return avcodec_find_encoder(AV_CODEC_ID_HEVC); return avcodec_find_encoder(AV_CODEC_ID_HEVC);
case ExportCodec::kCodecVP9: case ExportCodec::kCodecVP9:
+4 -3
View File
@@ -22,6 +22,7 @@
#define FFMPEGENCODER_H #define FFMPEGENCODER_H
extern "C" { extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include <libswscale/swscale.h> #include <libswscale/swscale.h>
#include <libswresample/swresample.h> #include <libswresample/swresample.h>
@@ -69,15 +70,15 @@ private:
bool WriteAVFrame(AVFrame* frame, AVCodecContext *codec_ctx, AVStream *stream); bool WriteAVFrame(AVFrame* frame, AVCodecContext *codec_ctx, AVStream *stream);
bool InitializeStream(enum AVMediaType type, AVStream** stream, AVCodecContext** codec_ctx, const ExportCodec::Codec &codec); bool InitializeStream(enum AVMediaType type, AVStream** stream, AVCodecContext** codec_ctx, const ExportCodec::Codec &codec);
bool InitializeCodecContext(AVStream** stream, AVCodecContext** codec_ctx, AVCodec* codec); bool InitializeCodecContext(AVStream** stream, AVCodecContext** codec_ctx, const AVCodec* codec);
bool SetupCodecContext(AVStream *stream, AVCodecContext *codec_ctx, AVCodec *codec); bool SetupCodecContext(AVStream *stream, AVCodecContext *codec_ctx, const AVCodec *codec);
void FlushEncoders(); void FlushEncoders();
void FlushCodecCtx(AVCodecContext* codec_ctx, AVStream *stream); void FlushCodecCtx(AVCodecContext* codec_ctx, AVStream *stream);
bool InitializeResampleContext(SampleBufferPtr audio); bool InitializeResampleContext(SampleBufferPtr audio);
static AVCodec *GetEncoder(ExportCodec::Codec c); static const AVCodec *GetEncoder(ExportCodec::Codec c);
AVFormatContext* fmt_ctx_; AVFormatContext* fmt_ctx_;
+1
View File
@@ -22,6 +22,7 @@
#define FFMPEGABSTRACTION_H #define FFMPEGABSTRACTION_H
extern "C" { extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
} }
+6 -1
View File
@@ -157,10 +157,12 @@ int64_t Timecode::timecode_to_timestamp(const QString &timecode, const rational
// Convert values to integers // Convert values to integers
QList<int64_t> timecode_numbers; QList<int64_t> timecode_numbers;
bool negative = timecode.trimmed().startsWith('-');
foreach (const QString& element, timecode_split) { foreach (const QString& element, timecode_split) {
valid = true; valid = true;
timecode_numbers.append((element.isEmpty()) ? 0 : element.toLong(&valid)); timecode_numbers.append((element.isEmpty()) ? 0 : qAbs(element.toLong(&valid)));
// If element cannot be converted to a number, // If element cannot be converted to a number,
if (!valid) { if (!valid) {
@@ -203,6 +205,9 @@ int64_t Timecode::timecode_to_timestamp(const QString &timecode, const rational
} }
if (ok) *ok = true; if (ok) *ok = true;
if (negative) timestamp = -timestamp;
return timestamp; return timestamp;
} }
case kMilliseconds: case kMilliseconds:
+7 -3
View File
@@ -16,11 +16,15 @@
set(OLIVE_SOURCES set(OLIVE_SOURCES
${OLIVE_SOURCES} ${OLIVE_SOURCES}
dialog/export/codec/codecsection.h dialog/export/codec/cineformsection.cpp
dialog/export/codec/cineformsection.h
dialog/export/codec/codecsection.cpp dialog/export/codec/codecsection.cpp
dialog/export/codec/h264section.h dialog/export/codec/codecsection.h
dialog/export/codec/codecstack.cpp
dialog/export/codec/codecstack.h
dialog/export/codec/h264section.cpp dialog/export/codec/h264section.cpp
dialog/export/codec/imagesection.h dialog/export/codec/h264section.h
dialog/export/codec/imagesection.cpp dialog/export/codec/imagesection.cpp
dialog/export/codec/imagesection.h
PARENT_SCOPE PARENT_SCOPE
) )
@@ -0,0 +1,85 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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 "cineformsection.h"
#include <QGridLayout>
#include <QLabel>
namespace olive {
CineformSection::CineformSection(QWidget *parent) :
CodecSection(parent)
{
QGridLayout *layout = new QGridLayout(this);
layout->setMargin(0);
int row = 0;
layout->addWidget(new QLabel(tr("Quality:")), row, 0);
quality_combobox_ = new QComboBox();
/* Correspond to the following indexes for FFmpeg
*
* -quality <int> E..V....... set quality (from 0 to 12) (default film3+)
* film3+ 0 E..V.......
* film3 1 E..V.......
* film2+ 2 E..V.......
* film2 3 E..V.......
* film1.5 4 E..V.......
* film1+ 5 E..V.......
* film1 6 E..V.......
* high+ 7 E..V.......
* high 8 E..V.......
* medium+ 9 E..V.......
* medium 10 E..V.......
* low+ 11 E..V.......
* low 12 E..V.......
*
*/
quality_combobox_->addItem(tr("Film Scan 3+"));
quality_combobox_->addItem(tr("Film Scan 3"));
quality_combobox_->addItem(tr("Film Scan 2+"));
quality_combobox_->addItem(tr("Film Scan 2"));
quality_combobox_->addItem(tr("Film Scan 1.5"));
quality_combobox_->addItem(tr("Film Scan 1+"));
quality_combobox_->addItem(tr("Film Scan 1"));
quality_combobox_->addItem(tr("High+"));
quality_combobox_->addItem(tr("High"));
quality_combobox_->addItem(tr("Medium+"));
quality_combobox_->addItem(tr("Medium"));
quality_combobox_->addItem(tr("Low+"));
quality_combobox_->addItem(tr("Low"));
// Default to "medium"
quality_combobox_->setCurrentIndex(10);
layout->addWidget(quality_combobox_, row, 1);
}
void CineformSection::AddOpts(EncodingParams *params)
{
params->set_video_option(QStringLiteral("quality"), QString::number(quality_combobox_->currentIndex()));
}
}
+45
View File
@@ -0,0 +1,45 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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 CINEFORMSECTION_H
#define CINEFORMSECTION_H
#include <QComboBox>
#include "codecsection.h"
namespace olive {
class CineformSection : public CodecSection
{
Q_OBJECT
public:
CineformSection(QWidget *parent = nullptr);
virtual void AddOpts(EncodingParams* params) override;
private:
QComboBox *quality_combobox_;
};
}
#endif // CINEFORMSECTION_H
+53
View File
@@ -0,0 +1,53 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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 "codecstack.h"
namespace olive {
#define super QStackedWidget
CodecStack::CodecStack(QWidget *parent)
: super{parent}
{
connect(this, &CodecStack::currentChanged, this, &CodecStack::OnChange);
}
void CodecStack::addWidget(QWidget *widget)
{
super::addWidget(widget);
OnChange(currentIndex());
}
void CodecStack::OnChange(int index)
{
for (int i=0; i<count(); i++) {
if (i == index) {
widget(i)->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
} else {
widget(i)->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
}
widget(i)->adjustSize();
}
adjustSize();
}
}
+45
View File
@@ -0,0 +1,45 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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 CODECSTACK_H
#define CODECSTACK_H
#include <QStackedWidget>
namespace olive {
class CodecStack : public QStackedWidget
{
Q_OBJECT
public:
explicit CodecStack(QWidget *parent = nullptr);
void addWidget(QWidget *widget);
signals:
private slots:
void OnChange(int index);
};
}
#endif // CODECSTACK_H
+27
View File
@@ -42,6 +42,31 @@ H264Section::H264Section(int default_crf, QWidget *parent) :
layout->setMargin(0); layout->setMargin(0);
int row = 0; int row = 0;
layout->addWidget(new QLabel(tr("Encode Speed:")), row, 0);
preset_combobox_ = new QComboBox();
preset_combobox_->setToolTip(tr("This setting allows you to tweak the ratio of export speed to compression quality. \n\n"
"If using Constant Rate Factor, slower speeds will result in smaller file sizes for the same quality. \n\n"
"If using Target Bit Rate or Target File Size, slower speeds will result in higher quality for the same bitrate/filesize. \n\n"
"This setting is equivalent to the `preset` setting in libx264."));
preset_combobox_->addItem(tr("Ultra Fast"));
preset_combobox_->addItem(tr("Super Fast"));
preset_combobox_->addItem(tr("Very Fast"));
preset_combobox_->addItem(tr("Faster"));
preset_combobox_->addItem(tr("Fast"));
preset_combobox_->addItem(tr("Medium"));
preset_combobox_->addItem(tr("Slow"));
preset_combobox_->addItem(tr("Slower"));
preset_combobox_->addItem(tr("Very Slow"));
//Default to "medium"
preset_combobox_->setCurrentIndex(5);
layout->addWidget(preset_combobox_, row, 1);
row++;
layout->addWidget(new QLabel(tr("Compression Method:")), row, 0); layout->addWidget(new QLabel(tr("Compression Method:")), row, 0);
@@ -110,6 +135,8 @@ void H264Section::AddOpts(EncodingParams *params)
params->set_video_buffer_size(2000000); params->set_video_buffer_size(2000000);
} }
params->set_video_option(QStringLiteral("preset"), QString::number(preset_combobox_->currentIndex()));
} }
H264CRFSection::H264CRFSection(int default_crf, QWidget *parent) : H264CRFSection::H264CRFSection(int default_crf, QWidget *parent) :
+2
View File
@@ -23,6 +23,7 @@
#include <QSlider> #include <QSlider>
#include <QStackedWidget> #include <QStackedWidget>
#include <QComboBox>
#include "codecsection.h" #include "codecsection.h"
#include "widget/slider/floatslider.h" #include "widget/slider/floatslider.h"
@@ -111,6 +112,7 @@ private:
H264FileSizeSection* filesize_section_; H264FileSizeSection* filesize_section_;
QComboBox *preset_combobox_;
}; };
class H265Section : public H264Section class H265Section : public H264Section
@@ -48,6 +48,7 @@ ExportAdvancedVideoDialog::ExportAdvancedVideoDialog(const QList<QString> &pix_f
thread_slider_ = new IntegerSlider(); thread_slider_ = new IntegerSlider();
thread_slider_->SetMinimum(0); thread_slider_->SetMinimum(0);
thread_slider_->SetDefaultValue(0); thread_slider_->SetDefaultValue(0);
thread_slider_->InsertLabelSubstitution(0, tr("Auto"));
performance_layout->addWidget(thread_slider_, row, 1); performance_layout->addWidget(thread_slider_, row, 1);
row++; row++;
+7 -2
View File
@@ -173,7 +173,7 @@ QWidget *ExportVideoTab::SetupCodecSection()
row++; row++;
codec_stack_ = new QStackedWidget(); codec_stack_ = new CodecStack();
codec_layout->addWidget(codec_stack_, row, 0, 1, 2); codec_layout->addWidget(codec_stack_, row, 0, 1, 2);
image_section_ = new ImageSection(); image_section_ = new ImageSection();
@@ -185,6 +185,9 @@ QWidget *ExportVideoTab::SetupCodecSection()
h265_section_ = new H265Section(); h265_section_ = new H265Section();
codec_stack_->addWidget(h265_section_); codec_stack_->addWidget(h265_section_);
cineform_section_ = new CineformSection();
codec_stack_->addWidget(cineform_section_);
row++; row++;
QPushButton* advanced_btn = new QPushButton(tr("Advanced")); QPushButton* advanced_btn = new QPushButton(tr("Advanced"));
@@ -240,6 +243,9 @@ void ExportVideoTab::VideoCodecChanged()
case ExportCodec::kCodecH265: case ExportCodec::kCodecH265:
SetCodecSection(h265_section_); SetCodecSection(h265_section_);
break; break;
case ExportCodec::kCodecCineform:
SetCodecSection(cineform_section_);
break;
default: default:
SetCodecSection(ExportCodec::IsCodecAStillImage(codec) ? image_section_ : nullptr); SetCodecSection(ExportCodec::IsCodecAStillImage(codec) ? image_section_ : nullptr);
} }
@@ -251,7 +257,6 @@ void ExportVideoTab::VideoCodecChanged()
} else { } else {
pix_fmt_.clear(); pix_fmt_.clear();
} }
qDebug() << "Set default pix fmt" << pix_fmt_;
} }
void ExportVideoTab::SetTime(const rational &time) void ExportVideoTab::SetTime(const rational &time)
+4 -1
View File
@@ -26,6 +26,8 @@
#include <QWidget> #include <QWidget>
#include "common/rational.h" #include "common/rational.h"
#include "dialog/export/codec/cineformsection.h"
#include "dialog/export/codec/codecstack.h"
#include "dialog/export/codec/h264section.h" #include "dialog/export/codec/h264section.h"
#include "dialog/export/codec/imagesection.h" #include "dialog/export/codec/imagesection.h"
#include "node/color/colormanager/colormanager.h" #include "node/color/colormanager/colormanager.h"
@@ -155,10 +157,11 @@ private:
QCheckBox* maintain_aspect_checkbox_; QCheckBox* maintain_aspect_checkbox_;
QComboBox* scaling_method_combobox_; QComboBox* scaling_method_combobox_;
QStackedWidget* codec_stack_; CodecStack* codec_stack_;
ImageSection* image_section_; ImageSection* image_section_;
H264Section* h264_section_; H264Section* h264_section_;
H264Section* h265_section_; H264Section* h265_section_;
CineformSection *cineform_section_;
ColorSpaceChooser* color_space_chooser_; ColorSpaceChooser* color_space_chooser_;
@@ -31,6 +31,7 @@
#include "common/filefunctions.h" #include "common/filefunctions.h"
#include "config/config.h" #include "config/config.h"
#include "render/audioparams.h"
#include "render/videoparams.h" #include "render/videoparams.h"
#include "ui/icons/icons.h" #include "ui/icons/icons.h"
#include "widget/menu/menu.h" #include "widget/menu/menu.h"
@@ -22,6 +22,7 @@
#include <QDialogButtonBox> #include <QDialogButtonBox>
#include <QGridLayout> #include <QGridLayout>
#include <QGroupBox>
#include <QMessageBox> #include <QMessageBox>
#include "core.h" #include "core.h"
@@ -39,58 +40,82 @@ SpeedDurationDialog::SpeedDurationDialog(const QVector<ClipBlock *> &clips, cons
{ {
setWindowTitle(tr("Speed/Duration")); setWindowTitle(tr("Speed/Duration"));
QGridLayout *layout = new QGridLayout(this); QVBoxLayout *layout = new QVBoxLayout(this);
int row = 0; {
QGroupBox *speed_group = new QGroupBox();
layout->addWidget(speed_group);
layout->addWidget(new QLabel(tr("Speed:")), row, 0); QGridLayout *speed_layout = new QGridLayout(speed_group);
speed_slider_ = new FloatSlider(); int row = 0;
speed_slider_->SetDisplayType(FloatSlider::kPercentage);
connect(speed_slider_, &FloatSlider::ValueChanged, this, &SpeedDurationDialog::SpeedChanged);
layout->addWidget(speed_slider_, row, 1);
row++; speed_layout->addWidget(new QLabel(tr("Speed:")), row, 0);
layout->addWidget(new QLabel(tr("Duration:")), row, 0); speed_slider_ = new FloatSlider();
speed_slider_->SetDisplayType(FloatSlider::kPercentage);
connect(speed_slider_, &FloatSlider::ValueChanged, this, &SpeedDurationDialog::SpeedChanged);
speed_layout->addWidget(speed_slider_, row, 1);
dur_slider_ = new RationalSlider(); row++;
dur_slider_->SetTimebase(timebase);
dur_slider_->SetDisplayType(RationalSlider::kTime);
connect(dur_slider_, &RationalSlider::ValueChanged, this, &SpeedDurationDialog::DurationChanged);
layout->addWidget(dur_slider_, row, 1);
row++; speed_layout->addWidget(new QLabel(tr("Duration:")), row, 0);
link_box_ = new QCheckBox(tr("Link Speed and Duration")); dur_slider_ = new RationalSlider();
link_box_->setChecked(true); dur_slider_->SetTimebase(timebase);
layout->addWidget(link_box_, row, 0, 1, 2); dur_slider_->SetDisplayType(RationalSlider::kTime);
connect(dur_slider_, &RationalSlider::ValueChanged, this, &SpeedDurationDialog::DurationChanged);
speed_layout->addWidget(dur_slider_, row, 1);
row++; row++;
link_box_ = new QCheckBox(tr("Link Speed and Duration"));
link_box_->setChecked(true);
speed_layout->addWidget(link_box_, row, 0, 1, 2);
}
reverse_box_ = new QCheckBox(tr("Reverse"));
layout->addWidget(reverse_box_);
maintain_audio_pitch_box_ = new QCheckBox(tr("Maintain Audio Pitch"));
layout->addWidget(maintain_audio_pitch_box_);
ripple_box_ = new QCheckBox(tr("Ripple Trailing Clips")); ripple_box_ = new QCheckBox(tr("Ripple Trailing Clips"));
layout->addWidget(ripple_box_, row, 0, 1, 2); layout->addWidget(ripple_box_);
row++;
QDialogButtonBox *btns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); QDialogButtonBox *btns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
btns->setCenterButtons(true); btns->setCenterButtons(true);
connect(btns, &QDialogButtonBox::accepted, this, &SpeedDurationDialog::accept); connect(btns, &QDialogButtonBox::accepted, this, &SpeedDurationDialog::accept);
connect(btns, &QDialogButtonBox::rejected, this, &SpeedDurationDialog::reject); connect(btns, &QDialogButtonBox::rejected, this, &SpeedDurationDialog::reject);
layout->addWidget(btns, row, 0, 1, 2); layout->addWidget(btns);
// Determine which speed value to use // Determine which speed value to use
start_speed_ = clips.first()->speed(); start_speed_ = clips.first()->speed();
start_duration_ = clips.first()->length(); start_duration_ = clips.first()->length();
start_reverse_ = clips.first()->reverse();
start_maintain_audio_pitch_ = clips.first()->maintain_audio_pitch();
for (int i=1; i<clips.size(); i++) { for (int i=1; i<clips.size(); i++) {
if (!qIsNaN(start_speed_) && !qFuzzyCompare(start_speed_, clips.at(i)->speed())) { ClipBlock *c = clips.at(i);
if (!qIsNaN(start_speed_) && !qFuzzyCompare(start_speed_, c->speed())) {
// Speed differs per clip // Speed differs per clip
start_speed_ = qSNaN(); start_speed_ = qSNaN();
} }
if (start_duration_ != -1 && clips.at(i)->length() != start_duration_) { if (start_duration_ != -1 && c->length() != start_duration_) {
start_duration_ = -1; start_duration_ = -1;
} }
// Yes, in theory a bool should only ever be 0 or 1 anyway, but MSVC complained and it is
// *possible* that a bool could be something else, so this code is safer
int clip_reverse = c->reverse() ? 1 : 0;
int clip_maintain_pitch = c->maintain_audio_pitch() ? 1 : 0;
if (start_reverse_ != -1 && clip_reverse != start_reverse_) {
start_reverse_ = -1;
}
if (start_maintain_audio_pitch_ != -1 && clip_maintain_pitch != start_maintain_audio_pitch_) {
start_maintain_audio_pitch_ = -1;
}
} }
if (qIsNaN(start_speed_)) { if (qIsNaN(start_speed_)) {
@@ -104,6 +129,18 @@ SpeedDurationDialog::SpeedDurationDialog(const QVector<ClipBlock *> &clips, cons
} else { } else {
dur_slider_->SetValue(start_duration_); dur_slider_->SetValue(start_duration_);
} }
if (start_reverse_ == -1) {
reverse_box_->setTristate();
} else {
reverse_box_->setChecked(start_reverse_);
}
if (start_maintain_audio_pitch_ == -1) {
maintain_audio_pitch_box_->setTristate();
} else {
maintain_audio_pitch_box_->setChecked(start_maintain_audio_pitch_);
}
} }
void SpeedDurationDialog::accept() void SpeedDurationDialog::accept()
@@ -133,6 +170,20 @@ void SpeedDurationDialog::accept()
} }
} }
// Set reverse values
if (!reverse_box_->isTristate()) {
foreach (ClipBlock *c, clips_) {
command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(c, ClipBlock::kReverseInput)), reverse_box_->isChecked()));
}
}
// Set reverse values
if (!maintain_audio_pitch_box_->isTristate()) {
foreach (ClipBlock *c, clips_) {
command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(c, ClipBlock::kMaintainAudioPitchInput)), maintain_audio_pitch_box_->isChecked()));
}
}
// Set duration values // Set duration values
foreach (ClipBlock *c, clips_) { foreach (ClipBlock *c, clips_) {
rational proposed_length = c->length(); rational proposed_length = c->length();
@@ -56,8 +56,16 @@ private:
QCheckBox *link_box_; QCheckBox *link_box_;
QCheckBox *reverse_box_;
QCheckBox *maintain_audio_pitch_box_;
QCheckBox *ripple_box_; QCheckBox *ripple_box_;
int start_reverse_;
int start_maintain_audio_pitch_;
double start_speed_; double start_speed_;
rational start_duration_; rational start_duration_;
+3
View File
@@ -33,6 +33,7 @@ const QString ClipBlock::kBufferIn = QStringLiteral("buffer_in");
const QString ClipBlock::kMediaInInput = QStringLiteral("media_in_in"); const QString ClipBlock::kMediaInInput = QStringLiteral("media_in_in");
const QString ClipBlock::kSpeedInput = QStringLiteral("speed_in"); const QString ClipBlock::kSpeedInput = QStringLiteral("speed_in");
const QString ClipBlock::kReverseInput = QStringLiteral("reverse_in"); const QString ClipBlock::kReverseInput = QStringLiteral("reverse_in");
const QString ClipBlock::kMaintainAudioPitchInput = QStringLiteral("maintain_audio_pitch_in");
ClipBlock::ClipBlock() : ClipBlock::ClipBlock() :
in_transition_(nullptr), in_transition_(nullptr),
@@ -52,6 +53,8 @@ ClipBlock::ClipBlock() :
AddInput(kReverseInput, NodeValue::kBoolean, false, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); AddInput(kReverseInput, NodeValue::kBoolean, false, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable));
IgnoreHashingFrom(kReverseInput); IgnoreHashingFrom(kReverseInput);
AddInput(kMaintainAudioPitchInput, NodeValue::kBoolean, false, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable));
PrependInput(kBufferIn, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable)); PrependInput(kBufferIn, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable));
SetValueHintForInput(kBufferIn, ValueHint(NodeValue::kBuffer)); SetValueHintForInput(kBufferIn, ValueHint(NodeValue::kBuffer));
} }
+16
View File
@@ -71,6 +71,21 @@ public:
return GetStandardValue(kReverseInput).toBool(); return GetStandardValue(kReverseInput).toBool();
} }
void set_reverse(bool e)
{
SetStandardValue(kReverseInput, e);
}
bool maintain_audio_pitch() const
{
return GetStandardValue(kMaintainAudioPitchInput).toBool();
}
void set_maintain_audio_pitch(bool e)
{
SetStandardValue(kMaintainAudioPitchInput, e);
}
TransitionBlock* in_transition() TransitionBlock* in_transition()
{ {
return in_transition_; return in_transition_;
@@ -110,6 +125,7 @@ public:
static const QString kMediaInInput; static const QString kMediaInInput;
static const QString kSpeedInput; static const QString kSpeedInput;
static const QString kReverseInput; static const QString kReverseInput;
static const QString kMaintainAudioPitchInput;
protected: protected:
virtual void LinkChangeEvent() override; virtual void LinkChangeEvent() override;
+1
View File
@@ -14,6 +14,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
add_subdirectory(cornerpin)
add_subdirectory(crop) add_subdirectory(crop)
add_subdirectory(flip) add_subdirectory(flip)
add_subdirectory(transform) add_subdirectory(transform)
+22
View File
@@ -0,0 +1,22 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2021 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/>.
set(OLIVE_SOURCES
${OLIVE_SOURCES}
node/distort/cornerpin/cornerpindistortnode.cpp
node/distort/cornerpin/cornerpindistortnode.h
PARENT_SCOPE
)
@@ -0,0 +1,226 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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 "cornerpindistortnode.h"
#include "common/lerp.h"
#include "core.h"
#include "widget/slider/floatslider.h"
namespace olive {
const QString CornerPinDistortNode::kTextureInput = QStringLiteral("tex_in");
const QString CornerPinDistortNode::kTopLeftInput = QStringLiteral("top_left_in");
const QString CornerPinDistortNode::kTopRightInput = QStringLiteral("top_right_in");
const QString CornerPinDistortNode::kBottomRightInput = QStringLiteral("bottom_right_in");
const QString CornerPinDistortNode::kBottomLeftInput = QStringLiteral("bottom_left_in");
const QString CornerPinDistortNode::kPerspectiveInput = QStringLiteral("perspective_in");
CornerPinDistortNode::CornerPinDistortNode()
{
AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
AddInput(kPerspectiveInput, NodeValue::kBoolean, true);
AddInput(kTopLeftInput, NodeValue::kVec2, QVector2D(0.0, 0.0));
AddInput(kTopRightInput, NodeValue::kVec2, QVector2D(0.0, 0.0));
AddInput(kBottomRightInput, NodeValue::kVec2, QVector2D(0.0, 0.0));
AddInput(kBottomLeftInput, NodeValue::kVec2, QVector2D(0.0, 0.0));
}
void CornerPinDistortNode::Retranslate()
{
SetInputName(kTextureInput, tr("Texture"));
SetInputName(kPerspectiveInput, tr("Perspective"));
SetInputName(kTopLeftInput, tr("Top Left"));
SetInputName(kTopRightInput, tr("Top Right"));
SetInputName(kBottomRightInput, tr("Bottom Right"));
SetInputName(kBottomLeftInput, tr("Bottom Left"));
}
void CornerPinDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
{
ShaderJob job;
job.InsertValue(value);
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
job.InsertValue(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
// vertex coordinates.
const QVector2D &resolution = globals.resolution();
QVector2D half_resolution = resolution * 0.5;
QVector2D top_left = QVector2D(ValueToPixel(0, value, resolution)) / half_resolution - QVector2D(1.0, 1.0);
QVector2D top_right = QVector2D(ValueToPixel(1, value, resolution)) / half_resolution - QVector2D(1.0, 1.0);
QVector2D bottom_right = QVector2D(ValueToPixel(2, value, resolution)) / half_resolution - QVector2D(1.0, 1.0);
QVector2D bottom_left = QVector2D(ValueToPixel(3, value, resolution)) / half_resolution - QVector2D(1.0, 1.0);
// Override default vertex coordinates.
QVector<float> adjusted_vertices = {top_left.x(), top_left.y(), 0.0f,
top_right.x(), top_right.y(), 0.0f,
bottom_right.x(), bottom_right.y(), 0.0f,
top_left.x(), top_left.y(), 0.0f,
bottom_left.x(), bottom_left.y(), 0.0f,
bottom_right.x(), bottom_right.y(), 0.0f};
job.SetVertexCoordinates(adjusted_vertices);
// If no texture do nothing
if (!job.GetValue(kTextureInput).data().isNull()) {
// In the special case that all sliders are in their default position just
// push the texture.
if (!(job.GetValue(kTopLeftInput).data().value<QVector2D>().isNull()
&& job.GetValue(kTopRightInput).data().value<QVector2D>().isNull() &&
job.GetValue(kBottomRightInput).data().value<QVector2D>().isNull() &&
job.GetValue(kBottomLeftInput).data().value<QVector2D>().isNull())) {
table->Push(NodeValue::kShaderJob, QVariant::fromValue(job), this);
} else {
table->Push(NodeValue::kTexture, job.GetValue(kTextureInput).data(), this);
}
}
}
ShaderCode CornerPinDistortNode::GetShaderCode(const QString &shader_id) const
{
Q_UNUSED(shader_id)
QString frag = FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/cornerpin.frag"));
QString vert = FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/cornerpin.vert"));
// HACK: No good, very bad hack
#ifndef Q_OS_MAC
frag.prepend(QStringLiteral("#version 130\n\n"));
vert.prepend(QStringLiteral("#version 130\n\n"));
#else
vert.prepend(QStringLiteral("#extension GL_EXT_gpu_shader4 : require\n\n"));
#endif
return ShaderCode(frag, vert);
}
QPointF CornerPinDistortNode::ValueToPixel(int value, const NodeValueRow& row, const QVector2D &resolution) const
{
Q_ASSERT(value >= 0 && value <= 3);
switch (value) {
case 0: // Top left
return QPointF(row[kTopLeftInput].data().value<QVector2D>().x(),
row[kTopLeftInput].data().value<QVector2D>().y());
break;
case 1: // Top right
return QPointF(resolution.x() + row[kTopRightInput].data().value<QVector2D>().x(),
row[kTopRightInput].data().value<QVector2D>().y());
break;
case 2: // Bottom right
return QPointF(resolution.x() + row[kBottomRightInput].data().value<QVector2D>().x(),
resolution.y() + row[kBottomRightInput].data().value<QVector2D>().y());
break;
case 3: //Bottom left
return QPointF(row[kBottomLeftInput].data().value<QVector2D>().x(),
row[kBottomLeftInput].data().value<QVector2D>().y() + resolution.y());
break;
default: // We should never get here
return QPointF();
}
}
void CornerPinDistortNode::DrawGizmos(const NodeValueRow &row, const NodeGlobals &globals, QPainter *p)
{
const QVector2D &resolution = globals.resolution();
const double handle_radius = GetGizmoHandleRadius(p->transform());
p->setPen(QPen(Qt::white, 0));
QPointF top_left = ValueToPixel(0, row, resolution);
QPointF top_right = ValueToPixel(1, row, resolution);
QPointF bottom_right = ValueToPixel(2, row, resolution);
QPointF bottom_left = ValueToPixel(3, row, resolution);
// Add the correct offset to each slider
SetInputProperty(kTopLeftInput, QStringLiteral("offset"), QVector2D(0.0, 0.0));
SetInputProperty(kTopRightInput, QStringLiteral("offset"), QVector2D(resolution.x() , 0.0));
SetInputProperty(kBottomRightInput, QStringLiteral("offset"), resolution);
SetInputProperty(kBottomLeftInput, QStringLiteral("offset"), QVector2D(0.0, resolution.y()));
// Draw bounding box
p->drawLine(QLineF(top_left, top_right));
p->drawLine(QLineF(top_right, bottom_right));
p->drawLine(QLineF(bottom_right, bottom_left));
p->drawLine(QLineF(bottom_left, top_left));
// Create handles
gizmo_resize_handle_[0] = CreateGizmoHandleRect(top_left, handle_radius);
gizmo_resize_handle_[1] = CreateGizmoHandleRect(top_right, handle_radius);
gizmo_resize_handle_[2] = CreateGizmoHandleRect(bottom_right, handle_radius);
gizmo_resize_handle_[3] = CreateGizmoHandleRect(bottom_left, handle_radius);
// Draw handles
DrawAndExpandGizmoHandles(p, handle_radius, gizmo_resize_handle_, kGizmoCornerCount);
}
bool CornerPinDistortNode::GizmoPress(const NodeValueRow &row, const NodeGlobals &globals, const QPointF &p)
{
bool gizmo_active[kGizmoCornerCount] = {false};
for (int i = 0; i < kGizmoCornerCount; i++) {
gizmo_active[i] = gizmo_resize_handle_[i].contains(p);
if (gizmo_active[i]) {
gizmo_drag_start_ = p;
gizmo_res_ = globals.resolution();
gizmo_drag_ = i;
return true;
}
}
return false;
}
void CornerPinDistortNode::GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers &modifiers)
{
if (gizmo_dragger_.isEmpty()) {
gizmo_dragger_.resize(2);
if (gizmo_drag_ == 0) {
gizmo_dragger_[0].Start(NodeKeyframeTrackReference(NodeInput(this, kTopLeftInput), 0), time);
gizmo_dragger_[1].Start(NodeKeyframeTrackReference(NodeInput(this, kTopLeftInput), 1), time);
}
if (gizmo_drag_ == 1) {
gizmo_dragger_[0].Start(NodeKeyframeTrackReference(NodeInput(this, kTopRightInput), 0), time);
gizmo_dragger_[1].Start(NodeKeyframeTrackReference(NodeInput(this, kTopRightInput), 1), time);
}
if (gizmo_drag_ == 2) {
gizmo_dragger_[0].Start(NodeKeyframeTrackReference(NodeInput(this, kBottomRightInput), 0), time);
gizmo_dragger_[1].Start(NodeKeyframeTrackReference(NodeInput(this, kBottomRightInput), 1), time);
}
if (gizmo_drag_ == 3) {
gizmo_dragger_[0].Start(NodeKeyframeTrackReference(NodeInput(this, kBottomLeftInput), 0), time);
gizmo_dragger_[1].Start(NodeKeyframeTrackReference(NodeInput(this, kBottomLeftInput), 1), time);
}
}
QPointF diff = p - gizmo_drag_start_;
gizmo_dragger_[0].Drag(gizmo_dragger_[0].GetStartValue().toDouble() + diff.x());
gizmo_dragger_[1].Drag(gizmo_dragger_[1].GetStartValue().toDouble() + diff.y());
}
void CornerPinDistortNode::GizmoRelease(MultiUndoCommand *command) {
for (NodeInputDragger &i : gizmo_dragger_) {
i.End(command);
}
gizmo_dragger_.clear();
}
}
@@ -0,0 +1,109 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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 CORNERPINDISTORTNODE_H
#define CORNERPINDISTORTNODE_H
#include <QVector2D>
#include "node/inputdragger.h"
#include "node/node.h"
namespace olive {
class CornerPinDistortNode : public Node
{
Q_OBJECT
public:
CornerPinDistortNode();
NODE_DEFAULT_DESTRUCTOR(CornerPinDistortNode)
virtual Node* copy() const override
{
return new CornerPinDistortNode();
}
virtual QString Name() const override
{
return tr("Corner Pin");
}
virtual QString id() const override
{
return QStringLiteral("org.olivevideoeditor.Olive.cornerpin");
}
virtual QVector<CategoryID> Category() const override
{
return {kCategoryDistort};
}
virtual QString Description() const override
{
return tr("Distort the image by dragging the corners.");
}
virtual void Retranslate() override;
virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override;
virtual ShaderCode GetShaderCode(const QString &shader_id) const override;
virtual bool HasGizmos() const override
{
return true;
}
virtual void DrawGizmos(const NodeValueRow& row, const NodeGlobals &globals, QPainter *p) override;
virtual bool GizmoPress(const NodeValueRow& row, const NodeGlobals &globals, const QPointF &p) override;
virtual void GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers &modifiers) override;
virtual void GizmoRelease(MultiUndoCommand *command) override;
/**
* @brief Convenience function - converts the 2D slider values from being
* an offset to the actual pixel value.
*/
QPointF ValueToPixel(int value, const NodeValueRow &row, const QVector2D &resolution) const;
static const QString kTextureInput;
static const QString kPerspectiveInput;
static const QString kTopLeftInput;
static const QString kTopRightInput;
static const QString kBottomRightInput;
static const QString kBottomLeftInput;
private:
// Gizmo variables
static const int kGizmoCornerCount = 4;
QRectF gizmo_resize_handle_[kGizmoCornerCount];
QRectF gizmo_whole_rect_;
int gizmo_drag_;
QVector<NodeInputDragger> gizmo_dragger_;
QPointF gizmo_drag_start_;
QVector2D gizmo_res_;
};
}
#endif // CORNERPINDISTORTNODE_H
+6
View File
@@ -30,6 +30,7 @@
#include "block/transition/crossdissolve/crossdissolvetransition.h" #include "block/transition/crossdissolve/crossdissolvetransition.h"
#include "block/transition/diptocolor/diptocolortransition.h" #include "block/transition/diptocolor/diptocolortransition.h"
#include "color/displaytransform/displaytransform.h" #include "color/displaytransform/displaytransform.h"
#include "distort/cornerpin/cornerpindistortnode.h"
#include "distort/crop/cropdistortnode.h" #include "distort/crop/cropdistortnode.h"
#include "distort/flip/flipdistortnode.h" #include "distort/flip/flipdistortnode.h"
#include "distort/transform/transformdistortnode.h" #include "distort/transform/transformdistortnode.h"
@@ -54,6 +55,7 @@
#include "project/folder/folder.h" #include "project/folder/folder.h"
#include "project/footage/footage.h" #include "project/footage/footage.h"
#include "project/sequence/sequence.h" #include "project/sequence/sequence.h"
#include "time/timeoffset/timeoffsetnode.h"
#include "time/timeremap/timeremap.h" #include "time/timeremap/timeremap.h"
namespace olive { namespace olive {
@@ -262,6 +264,10 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id)
return new FlipDistortNode(); return new FlipDistortNode();
case kNoiseGenerator: case kNoiseGenerator:
return new NoiseGeneratorNode(); return new NoiseGeneratorNode();
case kTimeOffsetNode:
return new TimeOffsetNode();
case kCornerPinDistort:
return new CornerPinDistortNode();
case kDisplayTransform: case kDisplayTransform:
return new DisplayTransformNode(); return new DisplayTransformNode();
+2
View File
@@ -65,6 +65,8 @@ public:
kOpacityEffect, kOpacityEffect,
kFlipDistort, kFlipDistort,
kNoiseGenerator, kNoiseGenerator,
kTimeOffsetNode,
kCornerPinDistort,
kDisplayTransform, kDisplayTransform,
// Count value // Count value
+1
View File
@@ -14,6 +14,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
add_subdirectory(timeoffset)
add_subdirectory(timeremap) add_subdirectory(timeremap)
set(OLIVE_SOURCES set(OLIVE_SOURCES
+22
View File
@@ -0,0 +1,22 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2021 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/>.
set(OLIVE_SOURCES
${OLIVE_SOURCES}
node/time/timeoffset/timeoffsetnode.cpp
node/time/timeoffset/timeoffsetnode.h
PARENT_SCOPE
)
@@ -0,0 +1,91 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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 "timeoffsetnode.h"
#include "widget/slider/rationalslider.h"
namespace olive {
const QString TimeOffsetNode::kTimeInput = QStringLiteral("time_in");
const QString TimeOffsetNode::kInputInput = QStringLiteral("input_in");
#define super Node
TimeOffsetNode::TimeOffsetNode()
{
AddInput(kTimeInput, NodeValue::kRational, QVariant::fromValue(rational(0)), InputFlags(kInputFlagNotConnectable));
SetInputProperty(kTimeInput, QStringLiteral("view"), RationalSlider::kTime);
SetInputProperty(kTimeInput, QStringLiteral("viewlock"), true);
IgnoreHashingFrom(kTimeInput);
AddInput(kInputInput, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable));
}
void TimeOffsetNode::Retranslate()
{
SetInputName(kTimeInput, QStringLiteral("Time"));
SetInputName(kInputInput, QStringLiteral("Input"));
}
TimeRange TimeOffsetNode::InputTimeAdjustment(const QString &input, int element, const TimeRange &input_time) const
{
if (input == kInputInput) {
return TimeRange(GetRemappedTime(input_time.in()), GetRemappedTime(input_time.out()));
} else {
return super::InputTimeAdjustment(input, element, input_time);
}
}
TimeRange TimeOffsetNode::OutputTimeAdjustment(const QString &input, int element, const TimeRange &input_time) const
{
/*if (input == kInputInput) {
rational target_time = GetValueAtTime(kTimeInput, input_time.in()).value<rational>();
return TimeRange(target_time, target_time + input_time.length());
} else {
return super::OutputTimeAdjustment(input, element, input_time);
}*/
return super::OutputTimeAdjustment(input, element, input_time);
}
void TimeOffsetNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
{
table->Push(value[kInputInput]);
}
void TimeOffsetNode::Hash(QCryptographicHash &hash, const NodeGlobals &globals, const VideoParams &video_params) const
{
// Don't hash anything of our own, just pass-through to the connected node at the remapped tmie
if (IsInputConnected(kInputInput)) {
Node *out = GetConnectedOutput(kInputInput);
NodeGlobals new_globals = globals;
new_globals.set_time(TimeRange(GetRemappedTime(globals.time().in()), GetRemappedTime(globals.time().out())));
Node::Hash(out, GetValueHintForInput(kInputInput), hash, new_globals, video_params);
}
}
rational TimeOffsetNode::GetRemappedTime(const rational &input) const
{
return input + GetValueAtTime(kTimeInput, input).value<rational>();
}
}
+76
View File
@@ -0,0 +1,76 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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 TIMEOFFSETNODE_H
#define TIMEOFFSETNODE_H
#include "node/node.h"
namespace olive {
class TimeOffsetNode : public Node
{
public:
TimeOffsetNode();
NODE_DEFAULT_DESTRUCTOR(TimeOffsetNode)
NODE_COPY_FUNCTION(TimeOffsetNode)
virtual QString Name() const override
{
return tr("Time Offset");
}
virtual QString id() const override
{
return QStringLiteral("org.olivevideoeditor.Olive.timeoffset");
}
virtual QVector<CategoryID> Category() const override
{
return {kCategoryGeneral};
}
virtual QString Description() const override
{
return tr("Offset time passing through the graph.");
}
virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const override;
virtual TimeRange OutputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const override;
virtual void Retranslate() override;
virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override;
static const QString kTimeInput;
static const QString kInputInput;
protected:
virtual void Hash(QCryptographicHash &hash, const NodeGlobals &globals, const VideoParams& video_params) const override;
private:
rational GetRemappedTime(const rational& input) const;
};
}
#endif // TIMEOFFSETNODE_H
+4
View File
@@ -21,6 +21,10 @@
#ifndef AUDIOPARAMS_H #ifndef AUDIOPARAMS_H
#define AUDIOPARAMS_H #define AUDIOPARAMS_H
extern "C" {
#include <libavutil/channel_layout.h>
}
#include <QtMath> #include <QtMath>
#include <QXmlStreamReader> #include <QXmlStreamReader>
#include <QXmlStreamWriter> #include <QXmlStreamWriter>
+12
View File
@@ -22,6 +22,7 @@
#define SHADERJOB_H #define SHADERJOB_H
#include <QMatrix4x4> #include <QMatrix4x4>
#include <QVector>
#include "generatejob.h" #include "generatejob.h"
#include "render/colorprocessor.h" #include "render/colorprocessor.h"
@@ -119,7 +120,15 @@ public:
shader_desc_ = shader_desc; shader_desc_ = shader_desc;
} }
void SetVertexCoordinates(const QVector<float> &vertex_coords)
{
vertex_overrides_ = vertex_coords;
}
const QVector<float>& GetVertexCoordinates()
{
return vertex_overrides_;
}
private: private:
QString shader_id_; QString shader_id_;
@@ -133,7 +142,10 @@ private:
bool use_ocio_; bool use_ocio_;
ColorProcessorPtr color_processor_; ColorProcessorPtr color_processor_;
OCIO::GpuShaderDescRcPtr shader_desc_; OCIO::GpuShaderDescRcPtr shader_desc_;
QVector<float> vertex_overrides_;
}; };
+12 -2
View File
@@ -569,7 +569,13 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video
QOpenGLBuffer vert_vbo_; QOpenGLBuffer vert_vbo_;
vert_vbo_.create(); vert_vbo_.create();
vert_vbo_.bind(); vert_vbo_.bind();
vert_vbo_.allocate(blit_vertices.constData(), blit_vertices.size() * sizeof(GLfloat)); // If the job has vertex coordinate overrides use them instead of the defaults.
if (!job.GetVertexCoordinates().isEmpty()) {
Q_ASSERT(job.GetVertexCoordinates().size() == 18);
vert_vbo_.allocate(job.GetVertexCoordinates().constData(), job.GetVertexCoordinates().size() * sizeof(float));
} else {
vert_vbo_.allocate(blit_vertices.constData(), blit_vertices.size() * sizeof(GLfloat));
}
vert_vbo_.release(); vert_vbo_.release();
QOpenGLBuffer frag_vbo_; QOpenGLBuffer frag_vbo_;
@@ -886,7 +892,11 @@ GLuint OpenGLRenderer::CompileShader(GLenum type, const QString &code)
QStringLiteral("#version 120\n\n"); QStringLiteral("#version 120\n\n");
#endif #endif
QString complete_code = shader_preamble; QString complete_code;
if (!code.startsWith(QStringLiteral("#version"))) {
complete_code = shader_preamble;
}
if (code.isEmpty()) { if (code.isEmpty()) {
// Use default code // Use default code
+32 -2
View File
@@ -25,6 +25,9 @@
#include <QVector3D> #include <QVector3D>
#include <QVector4D> #include <QVector4D>
#include "audio/packedprocessor.h"
#include "audio/planarprocessor.h"
#include "audio/tempoprocessor.h"
#include "node/block/clip/clip.h" #include "node/block/clip/clip.h"
#include "node/block/transition/transition.h" #include "node/block/transition/transition.h"
#include "node/project/project.h" #include "node/project/project.h"
@@ -288,8 +291,35 @@ NodeValueTable RenderProcessor::GenerateBlockTable(const Track *track, const Tim
// Just silence, don't think there's any other practical application of 0 speed audio // Just silence, don't think there's any other practical application of 0 speed audio
samples_from_this_block->silence(); samples_from_this_block->silence();
} else if (!qFuzzyCompare(speed_value, 1.0)) { } else if (!qFuzzyCompare(speed_value, 1.0)) {
// Multiply time if (clip_cast->maintain_audio_pitch()) {
samples_from_this_block->speed(speed_value); PackedProcessor packer;
packer.Open(samples_from_this_block->audio_params());
QByteArray packed = packer.Convert(samples_from_this_block);
if (!packed.isEmpty()) {
TempoProcessor tp;
tp.Open(samples_from_this_block->audio_params(), speed_value);
// FIXME: This is not the best way to do this, the TempoProcessor works best
// when it's given a continuous stream of audio, which is challenging
// in our current "modular" audio system. This should still work reasonably
// well on export (assuming audio is all generated at once on export), but
// users may hear clicks and pops in the audio during preview due to this
// approach.
tp.Push(packed);
tp.Flush();
packed = tp.Pull();
tp.Close();
PlanarProcessor planar;
planar.Open(samples_from_this_block->audio_params());
samples_from_this_block = planar.Convert(packed);
}
} else {
// Multiply time
samples_from_this_block->speed(speed_value);
}
} }
if (reversed) { if (reversed) {
+48
View File
@@ -0,0 +1,48 @@
// Input texture
uniform sampler2D ove_maintex;
uniform sampler2D tex_in;
uniform bool perspective_in;
// Input texture coordinate
varying vec2 ove_texcoord;
varying vec2 q;
varying vec2 b1;
varying vec2 b2;
varying vec2 b3;
float Wedge2D(vec2 v, vec2 w) {
return (v.x*w.y) - (v.y*w.x);
}
void main() {
if(perspective_in){
gl_FragColor = texture2D(tex_in, ove_texcoord);
} else {
float A = Wedge2D(b2, b3);
float B = Wedge2D(b3, q) - Wedge2D(b1, b2);
float C = Wedge2D(b1, q);
vec2 uv;
// solve for v
if (abs(A) < 0.001) {
uv.y = -C/B;
} else {
float discrim = B*B - 4.0*A*C;
uv.y = 0.5 * (-B + sqrt(discrim)) / A;
}
// solve for u
vec2 denom = b1 + uv.y * b3;
if (abs(denom.x) > abs(denom.y)) {
uv.x = (q.x - b2.x * uv.y) / denom.x;
} else {
uv.x = (q.y - b2.y * uv.y) / denom.y;
}
uv.y = 1.0 - uv.y;
gl_FragColor = texture2D(tex_in, uv);
}
}
+99
View File
@@ -0,0 +1,99 @@
uniform bool perspective_in;
uniform vec2 top_left_in;
uniform vec2 top_right_in;
uniform vec2 bottom_left_in;
uniform vec2 bottom_right_in;
uniform vec2 resolution_in;
uniform mat4 ove_mvpmat;
attribute vec4 a_position;
attribute vec2 a_texcoord;
varying vec2 ove_texcoord;
varying vec2 q;
varying vec2 b1;
varying vec2 b2;
varying vec2 b3;
void main() {
// The slider inputs only contain the amount they have changed rather than
// their pixel locations so we adjust them here.
vec2 t_l = top_left_in;
vec2 t_r = top_right_in + vec2(resolution_in.x, 0.0);
vec2 b_r = bottom_right_in + resolution_in;
vec2 b_l = bottom_left_in + vec2(0.0, resolution_in.y);
gl_Position = ove_mvpmat * a_position;
if (perspective_in){
// Find the center of the quadrilateral by finding where the two diagonals intersect.
// https://www.reedbeta.com/blog/quadrilateral-interpolation-part-1/
// Here we calculate the gradient and constant (y = mx + c) for each diagonal.
float m1 = (t_r.y - b_l.y)/(t_r.x - b_l.x);
float c1 = b_l.y - m1 * b_l.x;
float m2 = (b_r.y - t_l.y)/(b_r.x - t_l.x);
float c2 = t_l.y - m2 * t_l.x;
// Find the intersection by setting the two line equations equal and rearrange.
float mid_x = (c2 - c1) / (m1 - m2);
float mid_y = m1 * mid_x + c1;
// Find the distance from each corner to our center point
float d0 = length(vec2(mid_x - b_l.x, mid_y - b_l.y));
float d1 = length(vec2(b_r.x - mid_x, mid_y - b_r.y));
float d2 = length(vec2(t_r.x - mid_x, t_r.y - mid_y));
float d3 = length(vec2(mid_x - t_l.x, t_l.y - mid_y));
float q = 1.0;
/*
Vertex IDs (aspect ratio irrelevant):
0_____1
3|\ |
| \ |
| \ |
| \ |
|____\|2
4 5
*/
if (gl_VertexID == 0 || gl_VertexID == 3) {
q = (d1+d3)/d3;
} else if (gl_VertexID == 1) {
q = (d0+d2)/d2;
} else if (gl_VertexID == 2 || gl_VertexID == 5) {
q = (d3+d1)/d1;
} else {
q = (d2+d0)/d0;
}
gl_Position[0] *= q;
gl_Position[1] *= q;
gl_Position[3] = q;
} else{
// https://www.reedbeta.com/blog/quadrilateral-interpolation-part-2/
vec2 pos;
if (gl_VertexID == 0 || gl_VertexID == 3) { // top left
pos = t_l;
} else if (gl_VertexID == 1) { // top right
pos = t_r;
} else if (gl_VertexID == 2 || gl_VertexID == 5) { // bottom right
pos = b_r;
} else if (gl_VertexID == 4) { // bottom left
pos = b_l;
}
q = pos - b_l;
b1 = b_r - b_l;
b2 = t_l - b_l;
b3 = b_l - b_r - t_l + t_r;
}
ove_texcoord = a_texcoord;
}
+1 -1
View File
@@ -4883,7 +4883,7 @@ y salida.</translation>
<message> <message>
<location filename="../widget/viewer/viewer.cpp" line="928"/> <location filename="../widget/viewer/viewer.cpp" line="928"/>
<source>On</source> <source>On</source>
<translation>Avtivado</translation> <translation>Activado</translation>
</message> </message>
<message> <message>
<location filename="../widget/viewer/viewer.cpp" line="933"/> <location filename="../widget/viewer/viewer.cpp" line="933"/>
Executable → Regular
View File
Executable → Regular
View File
+11 -1
View File
@@ -104,7 +104,17 @@ void SliderBase::changeEvent(QEvent *e)
void SliderBase::UpdateLabel() void SliderBase::UpdateLabel()
{ {
label_->setText(tristate_ ? tr("---") : GetFormattedValueToString()); QString s;
if (tristate_) {
s = tr("---");
} else if (label_substitutions_.contains(GetValueInternal())) {
s = label_substitutions_.value(GetValueInternal());
} else {
s = GetFormattedValueToString();
}
label_->setText(s);
} }
QVariant SliderBase::AdjustValue(const QVariant &value) const QVariant SliderBase::AdjustValue(const QVariant &value) const
+8
View File
@@ -49,6 +49,12 @@ public:
QString GetFormattedValueToString(const QVariant& v) const; QString GetFormattedValueToString(const QVariant& v) const;
void InsertLabelSubstitution(const QVariant &value, const QString &label)
{
label_substitutions_.insert(value, label);
UpdateLabel();
}
public slots: public slots:
void ShowEditor(); void ShowEditor();
@@ -92,6 +98,8 @@ private:
bool format_plural_; bool format_plural_;
QMap<QVariant, QString> label_substitutions_;
private slots: private slots:
void LineEditConfirmed(); void LineEditConfirmed();
+2 -5
View File
@@ -466,11 +466,8 @@ void ViewerWidget::ReceivedAudioBufferForPlayback()
// If the tempo must be adjusted, adjust now // If the tempo must be adjusted, adjust now
if (tempo_processor_.IsOpen()) { if (tempo_processor_.IsOpen()) {
tempo_processor_.Push(pack.data(), pack.size()); tempo_processor_.Push(pack);
int actual = tempo_processor_.Pull(pack.data(), pack.size()); pack = tempo_processor_.Pull();
if (actual != pack.size()) {
pack.resize(actual);
}
} }
// TempoProcessor may have emptied the array // TempoProcessor may have emptied the array