Merge branch 'master' into marker
This commit is contained in:
+13
-16
@@ -64,6 +64,7 @@ jobs:
|
||||
cd $GITHUB_WORKSPACE/app/dialog/about
|
||||
python3 patreon.py
|
||||
if: github.event_name == 'push'
|
||||
continue-on-error: true
|
||||
|
||||
- name: Configure CMake
|
||||
run: |
|
||||
@@ -196,6 +197,7 @@ jobs:
|
||||
cd $GITHUB_WORKSPACE/app/dialog/about
|
||||
python3 patreon.py
|
||||
if: github.event_name == 'push'
|
||||
continue-on-error: true
|
||||
|
||||
- name: Configure CMake
|
||||
shell: bash
|
||||
@@ -283,6 +285,8 @@ jobs:
|
||||
os-arch: x86_64
|
||||
os: macos-10.15
|
||||
cmake-gen: Ninja
|
||||
env:
|
||||
DEP_LOCATION: /opt/olive-editor
|
||||
name: |
|
||||
${{ matrix.os-name }}
|
||||
<${{ matrix.compiler-name }},
|
||||
@@ -294,9 +298,6 @@ jobs:
|
||||
- name: Checkout Source Code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Homebrew
|
||||
uses: Homebrew/actions/setup-homebrew@master
|
||||
|
||||
- name: Automatically Generate Package Name
|
||||
shell: bash
|
||||
env:
|
||||
@@ -313,14 +314,8 @@ jobs:
|
||||
shell: bash
|
||||
working-directory: ${{ runner.workspace }}
|
||||
run: |
|
||||
# HACK: Workaround for https://github.com/actions/virtual-environments/issues/4020
|
||||
rm -rf /usr/local/bin/2to3
|
||||
|
||||
brew update
|
||||
brew upgrade
|
||||
brew tap olive-editor/homebrew
|
||||
brew install -f qt5 ffmpeg-olive openimageio-olive opencolorio opentimelineio portaudio
|
||||
echo "/usr/local/opt/qt@5/bin" >> $GITHUB_PATH
|
||||
$DOWNLOAD_TOOL https://github.com/olive-editor/dependencies/releases/download/continuous/olive-dep-mac-x86_64.tar.gz
|
||||
sudo tar xzf olive-dep-mac-x86_64.tar.gz -C /
|
||||
|
||||
- name: Acquire Google Crashpad
|
||||
shell: bash
|
||||
@@ -337,14 +332,16 @@ jobs:
|
||||
cd $GITHUB_WORKSPACE/app/dialog/about
|
||||
python3 patreon.py
|
||||
if: github.event_name == 'push'
|
||||
continue-on-error: true
|
||||
|
||||
- name: Configure CMake
|
||||
shell: bash
|
||||
working-directory: ${{ runner.workspace }}/build
|
||||
run: |
|
||||
brew install ninja
|
||||
PATH=/opt/olive-editor/bin:/opt/olive-editor/crashpad:$PATH \
|
||||
cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} -G "${{ matrix.cmake-gen }}"
|
||||
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=10.13 -G "${{ matrix.cmake-gen }}"
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{ runner.workspace }}/build
|
||||
@@ -366,9 +363,9 @@ jobs:
|
||||
run: |
|
||||
# Use macdeployqt and macdeployqt to bundle dependencies
|
||||
mv app/$BUNDLE_NAME .
|
||||
macdeployqt $BUNDLE_NAME -executable=$BUNDLE_NAME/Contents/MacOS/olive-crashhandler
|
||||
$DEP_LOCATION/bin/macdeployqt $BUNDLE_NAME -executable=$BUNDLE_NAME/Contents/MacOS/olive-crashhandler
|
||||
$DOWNLOAD_TOOL https://github.com/arl/macdeployqtfix/raw/master/macdeployqtfix.py
|
||||
python2 macdeployqtfix.py $BUNDLE_NAME/Contents/MacOS/Olive $(dirname $(which qmake))
|
||||
python2 macdeployqtfix.py $BUNDLE_NAME/Contents/MacOS/Olive $VCPKG_ROOT/installed/x64-osx
|
||||
|
||||
# Manual corrections
|
||||
for f in $BUNDLE_NAME/Contents/Frameworks/*.dylib
|
||||
@@ -380,7 +377,7 @@ jobs:
|
||||
done
|
||||
|
||||
# Crashpad symbols
|
||||
/opt/olive-editor/bin/dump_syms $BUNDLE_NAME/Contents/MacOS/Olive > Olive.sym
|
||||
$DEP_LOCATION/bin/dump_syms $BUNDLE_NAME/Contents/MacOS/Olive > Olive.sym
|
||||
SYM_HEADER=($(head -n 1 Olive.sym)) # Read first line of symbol file
|
||||
SYM_DIR=$BUNDLE_NAME/Contents/Resources/symbols/Olive/${SYM_HEADER[3]}
|
||||
mkdir -p "$SYM_DIR"
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
CmakeSettings.json
|
||||
*.code-workspace
|
||||
|
||||
# clangd's index and likely other things that need not be in the repository
|
||||
.cache/
|
||||
|
||||
# macOS General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
|
||||
+35
-5
@@ -20,6 +20,7 @@ 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)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
@@ -32,7 +33,6 @@ set(CMAKE_AUTORCC ON)
|
||||
# Set compiler options
|
||||
if(MSVC)
|
||||
set(OLIVE_COMPILE_OPTIONS
|
||||
/WX
|
||||
/wd4267
|
||||
/wd4244
|
||||
/experimental:external
|
||||
@@ -41,10 +41,12 @@ if(MSVC)
|
||||
"$<$<CONFIG:RELEASE>:/O2>"
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:/MP>"
|
||||
)
|
||||
if (USE_WERROR)
|
||||
list(APPEND OLIVE_COMPILE_OPTIONS "/WX")
|
||||
endif()
|
||||
else()
|
||||
set(OLIVE_COMPILE_OPTIONS
|
||||
"$<$<CONFIG:RELEASE>:-O2>"
|
||||
-Werror
|
||||
-Wuninitialized
|
||||
-pedantic-errors
|
||||
-Wall
|
||||
@@ -52,9 +54,15 @@ else()
|
||||
-Wno-unused-parameter
|
||||
-Wshadow
|
||||
)
|
||||
if (USE_WERROR)
|
||||
list(APPEND OLIVE_COMPILE_OPTIONS "-Werror")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(OLIVE_DEFINITIONS -DQT_DEPRECATED_WARNINGS)
|
||||
if (WIN32)
|
||||
list(APPEND OLIVE_DEFINITIONS -DUNICODE -D_UNICODE)
|
||||
endif()
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
|
||||
|
||||
@@ -81,8 +89,7 @@ list(APPEND OLIVE_LIBRARIES ${OPENEXR_LIBRARIES})
|
||||
list(APPEND OLIVE_INCLUDE_DIRS ${OPENEXR_INCLUDES})
|
||||
|
||||
# Link Qt 5
|
||||
find_package(Qt5 5.6 REQUIRED
|
||||
COMPONENTS
|
||||
set(QT_LIBRARIES
|
||||
Core
|
||||
Gui
|
||||
Widgets
|
||||
@@ -90,8 +97,15 @@ find_package(Qt5 5.6 REQUIRED
|
||||
Svg
|
||||
LinguistTools
|
||||
Concurrent
|
||||
)
|
||||
if (UNIX AND NOT APPLE)
|
||||
list(APPEND QT_LIBRARIES DBus)
|
||||
endif()
|
||||
find_package(Qt5 5.6 REQUIRED
|
||||
COMPONENTS
|
||||
${QT_LIBRARIES}
|
||||
OPTIONAL_COMPONENTS
|
||||
Network
|
||||
Network
|
||||
)
|
||||
if (NOT Qt5Network_FOUND)
|
||||
message(" Qt5::Network module not found, crash reporting will be disabled.")
|
||||
@@ -127,6 +141,13 @@ list(APPEND OLIVE_LIBRARIES
|
||||
|
||||
# Link PortAudio
|
||||
find_package(PortAudio REQUIRED)
|
||||
set(CMAKE_REQUIRED_INCLUDES ${PORTAUDIO_INCLUDE_DIRS})
|
||||
include(CheckIncludeFileCXX)
|
||||
check_include_file_cxx( "pa_jack.h" PA_HAS_JACK)
|
||||
if (PA_HAS_JACK)
|
||||
list(APPEND OLIVE_DEFINITIONS PA_HAS_JACK)
|
||||
endif()
|
||||
|
||||
list(APPEND OLIVE_INCLUDE_DIRS ${PORTAUDIO_INCLUDE_DIRS})
|
||||
list(APPEND OLIVE_LIBRARIES ${PORTAUDIO_LIBRARIES})
|
||||
|
||||
@@ -156,6 +177,14 @@ else()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
list(APPEND OLIVE_DEFINITIONS "-DUNICODE -D_UNICODE")
|
||||
elseif (APPLE)
|
||||
list(APPEND OLIVE_LIBRARIES "-framework IOKit")
|
||||
elseif(UNIX)
|
||||
list(APPEND OLIVE_LIBRARIES Qt5::DBus)
|
||||
endif()
|
||||
|
||||
# Generate Git hash
|
||||
set(PROJECT_LONG_VERSION ${PROJECT_VERSION})
|
||||
if(EXISTS "${CMAKE_SOURCE_DIR}/.git")
|
||||
@@ -177,6 +206,7 @@ if(BUILD_DOXYGEN)
|
||||
endif()
|
||||
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
list(APPEND OLIVE_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/ext)
|
||||
|
||||
add_subdirectory(app)
|
||||
|
||||
|
||||
+4
-3
@@ -66,9 +66,7 @@ add_library(olive-version-obj
|
||||
version.h
|
||||
)
|
||||
target_link_libraries(olive-version-obj PRIVATE Qt5::Core)
|
||||
if(DEFINED GIT_HASH)
|
||||
target_compile_options(olive-version-obj PRIVATE -DAPPVERSION="${PROJECT_VERSION}" -DAPPVERSIONLONG="${PROJECT_LONG_VERSION}" )
|
||||
endif()
|
||||
target_compile_options(olive-version-obj PRIVATE -DAPPVERSION="${PROJECT_VERSION}" -DAPPVERSIONLONG="${PROJECT_LONG_VERSION}" )
|
||||
|
||||
# Add main library
|
||||
add_library(libolive-editor
|
||||
@@ -101,6 +99,9 @@ if (WIN32)
|
||||
# Set Windows application icon
|
||||
target_sources(olive-editor PRIVATE packaging/windows/resources.rc)
|
||||
|
||||
# Preserve folder structure in visual studio
|
||||
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${OLIVE_SOURCES})
|
||||
|
||||
elseif(APPLE)
|
||||
# Set Mac application icon
|
||||
set(OLIVE_ICON packaging/macos/olive.icns)
|
||||
|
||||
+45
-100
@@ -20,6 +20,10 @@
|
||||
|
||||
#include "audiomanager.h"
|
||||
|
||||
#ifdef PA_HAS_JACK
|
||||
#include <pa_jack.h>
|
||||
#endif
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
#include "config/config.h"
|
||||
@@ -46,20 +50,6 @@ AudioManager *AudioManager::instance()
|
||||
return instance_;
|
||||
}
|
||||
|
||||
void AudioManager::RefreshDevices()
|
||||
{
|
||||
}
|
||||
|
||||
bool AudioManager::IsRefreshingOutputs()
|
||||
{
|
||||
return is_refreshing_outputs_;
|
||||
}
|
||||
|
||||
bool AudioManager::IsRefreshingInputs()
|
||||
{
|
||||
return is_refreshing_inputs_;
|
||||
}
|
||||
|
||||
void AudioManager::SetOutputNotifyInterval(int n)
|
||||
{
|
||||
output_buffer_->set_notify_interval(n);
|
||||
@@ -166,33 +156,6 @@ void AudioManager::SetOutputDevice(PaDeviceIndex device)
|
||||
output_device_ = device;
|
||||
|
||||
CloseOutputStream();
|
||||
|
||||
/*qInfo() << "Setting output audio device to" << info.deviceName();
|
||||
|
||||
StopOutput();
|
||||
|
||||
output_device_info_ = info;
|
||||
|
||||
if (output_params_.is_valid()) {
|
||||
QAudioFormat format;
|
||||
format.setSampleRate(output_params_.sample_rate());
|
||||
format.setChannelCount(output_params_.channel_count());
|
||||
format.setCodec("audio/pcm");
|
||||
format.setByteOrder(QAudioFormat::LittleEndian);
|
||||
format.setSampleSize(output_params_.bits_per_sample());
|
||||
format.setSampleType(AudioParams::GetQtSampleType(output_params_.format()));
|
||||
|
||||
if (info.isFormatSupported(format)) {
|
||||
QMetaObject::invokeMethod(output_manager_,
|
||||
"SetOutputDevice",
|
||||
Qt::QueuedConnection,
|
||||
Q_ARG(const QAudioDeviceInfo&, info),
|
||||
Q_ARG(const QAudioFormat&, format));
|
||||
output_is_set_ = true;
|
||||
} else {
|
||||
qWarning() << "Output format not supported by device";
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
void AudioManager::SetInputDevice(PaDeviceIndex device)
|
||||
@@ -206,17 +169,53 @@ void AudioManager::SetInputDevice(PaDeviceIndex device)
|
||||
input_device_ = device;
|
||||
}
|
||||
|
||||
void AudioManager::HardReset()
|
||||
{
|
||||
CloseOutputStream();
|
||||
Pa_Terminate();
|
||||
Pa_Initialize();
|
||||
}
|
||||
|
||||
PaDeviceIndex AudioManager::FindConfigDeviceByName(bool is_output_device)
|
||||
{
|
||||
QString entry = is_output_device ? QStringLiteral("AudioOutput") : QStringLiteral("AudioInput");
|
||||
|
||||
return FindDeviceByName(Config::Current()[entry].toString(), is_output_device);
|
||||
}
|
||||
|
||||
PaDeviceIndex AudioManager::FindDeviceByName(const QString &s, bool is_output_device)
|
||||
{
|
||||
if (!s.isEmpty()) {
|
||||
for (PaDeviceIndex i=0, end=Pa_GetDeviceCount(); i<end; i++) {
|
||||
const PaDeviceInfo *device = Pa_GetDeviceInfo(i);
|
||||
|
||||
if (((is_output_device && device->maxOutputChannels) || (!is_output_device && device->maxInputChannels))
|
||||
&& !s.compare(device->name)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return is_output_device ? Pa_GetDefaultOutputDevice() : Pa_GetDefaultInputDevice();
|
||||
}
|
||||
|
||||
AudioManager::AudioManager() :
|
||||
is_refreshing_inputs_(false),
|
||||
is_refreshing_outputs_(false),
|
||||
output_stream_(nullptr)
|
||||
{
|
||||
//RefreshDevices();
|
||||
#ifdef PA_HAS_JACK
|
||||
// PortAudio doesn't do a strcpy, so we need a const char that's readily accessible (i.e. not
|
||||
// a QString converted to UTF-8)
|
||||
PaJack_SetClientName("Olive");
|
||||
#endif
|
||||
|
||||
Pa_Initialize();
|
||||
|
||||
SetOutputDevice(Pa_GetDefaultOutputDevice());
|
||||
SetInputDevice(Pa_GetDefaultInputDevice());
|
||||
// Get device from config
|
||||
PaDeviceIndex output_device = FindConfigDeviceByName(true);
|
||||
PaDeviceIndex input_device = FindConfigDeviceByName(false);
|
||||
|
||||
SetOutputDevice(output_device);
|
||||
SetInputDevice(input_device);
|
||||
|
||||
output_buffer_ = new PreviewAudioDevice(this);
|
||||
output_buffer_->open(PreviewAudioDevice::ReadWrite);
|
||||
@@ -230,58 +229,4 @@ AudioManager::~AudioManager()
|
||||
Pa_Terminate();
|
||||
}
|
||||
|
||||
void AudioManager::OutputDevicesRefreshed()
|
||||
{
|
||||
/*QFutureWatcher< QList<QAudioDeviceInfo> >* watcher = static_cast<QFutureWatcher< QList<QAudioDeviceInfo> >*>(sender());
|
||||
|
||||
output_devices_ = watcher->result();
|
||||
watcher->deleteLater();
|
||||
is_refreshing_outputs_ = false;
|
||||
|
||||
QString preferred_audio_output = Config::Current()["AudioOutput"].toString();
|
||||
|
||||
if (output_ == paNoDevice
|
||||
|| (!preferred_audio_output.isEmpty() && output_device_info_.deviceName() != preferred_audio_output)) {
|
||||
if (preferred_audio_output.isEmpty()) {
|
||||
SetOutputDevice(QAudioDeviceInfo::defaultOutputDevice());
|
||||
} else {
|
||||
foreach (const QAudioDeviceInfo& info, output_devices_) {
|
||||
if (info.deviceName() == preferred_audio_output) {
|
||||
SetOutputDevice(info);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emit OutputListReady();*/
|
||||
}
|
||||
|
||||
void AudioManager::InputDevicesRefreshed()
|
||||
{
|
||||
/*QFutureWatcher< QList<QAudioDeviceInfo> >* watcher = static_cast<QFutureWatcher< QList<QAudioDeviceInfo> >*>(sender());
|
||||
|
||||
input_devices_ = watcher->result();
|
||||
watcher->deleteLater();
|
||||
is_refreshing_inputs_ = false;
|
||||
|
||||
QString preferred_audio_input = Config::Current()["AudioInput"].toString();
|
||||
|
||||
if (input_ == nullptr
|
||||
|| (!preferred_audio_input.isEmpty() && input_device_info_.deviceName() != preferred_audio_input)) {
|
||||
if (preferred_audio_input.isEmpty()) {
|
||||
SetInputDevice(QAudioDeviceInfo::defaultInputDevice());
|
||||
} else {
|
||||
foreach (const QAudioDeviceInfo& info, input_devices_) {
|
||||
if (info.deviceName() == preferred_audio_input) {
|
||||
SetInputDevice(info);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emit InputListReady();*/
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+15
-18
@@ -49,12 +49,6 @@ public:
|
||||
|
||||
static AudioManager* instance();
|
||||
|
||||
void RefreshDevices();
|
||||
|
||||
bool IsRefreshingOutputs();
|
||||
|
||||
bool IsRefreshingInputs();
|
||||
|
||||
void SetOutputNotifyInterval(int n);
|
||||
|
||||
void PushToOutput(const AudioParams ¶ms, const QByteArray& samples);
|
||||
@@ -63,17 +57,28 @@ public:
|
||||
|
||||
void StopOutput();
|
||||
|
||||
PaDeviceIndex GetOutputDevice() const
|
||||
{
|
||||
return output_device_;
|
||||
}
|
||||
|
||||
PaDeviceIndex GetInputDevice() const
|
||||
{
|
||||
return input_device_;
|
||||
}
|
||||
|
||||
void SetOutputDevice(PaDeviceIndex device);
|
||||
|
||||
void SetInputDevice(PaDeviceIndex device);
|
||||
|
||||
void HardReset();
|
||||
|
||||
static PaDeviceIndex FindConfigDeviceByName(bool is_output_device);
|
||||
static PaDeviceIndex FindDeviceByName(const QString &s, bool is_output_device);
|
||||
|
||||
signals:
|
||||
void OutputListReady();
|
||||
|
||||
void OutputNotify();
|
||||
|
||||
void InputListReady();
|
||||
|
||||
private:
|
||||
AudioManager();
|
||||
|
||||
@@ -83,9 +88,6 @@ private:
|
||||
|
||||
void CloseOutputStream();
|
||||
|
||||
bool is_refreshing_inputs_;
|
||||
bool is_refreshing_outputs_;
|
||||
|
||||
static AudioManager* instance_;
|
||||
|
||||
PaDeviceIndex output_device_;
|
||||
@@ -95,11 +97,6 @@ private:
|
||||
|
||||
PaDeviceIndex input_device_;
|
||||
|
||||
private slots:
|
||||
void OutputDevicesRefreshed();
|
||||
|
||||
void InputDevicesRefreshed();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -23,11 +23,8 @@
|
||||
#include <QDebug>
|
||||
#include <QtGlobal>
|
||||
|
||||
#ifdef Q_PROCESSOR_X86
|
||||
#include <xmmintrin.h>
|
||||
#endif
|
||||
|
||||
#include "config/config.h"
|
||||
#include "common/cpuoptimize.h"
|
||||
#include "common/functiontimer.h"
|
||||
|
||||
namespace olive {
|
||||
@@ -280,33 +277,24 @@ AudioVisualWaveform::Sample AudioVisualWaveform::GetSummaryFromTime(const ration
|
||||
return AudioVisualWaveform::Sample(channel_count(), {0, 0});
|
||||
}
|
||||
|
||||
AudioVisualWaveform::Sample AudioVisualWaveform::SumSamples(const float *samples, int nb_samples, int nb_channels)
|
||||
void ExpandMinMaxChannel(float *a, int start, int length, float &min_val, float &max_val)
|
||||
{
|
||||
AudioVisualWaveform::Sample summed_samples(nb_channels);
|
||||
#if defined(Q_PROCESSOR_X86) || defined(Q_PROCESSOR_ARM)
|
||||
// SSE optimized
|
||||
|
||||
for (int i=0;i<nb_samples;i++) {
|
||||
ExpandMinMax(summed_samples[i%nb_channels], samples[i]);
|
||||
}
|
||||
|
||||
return summed_samples;
|
||||
}
|
||||
|
||||
#ifdef Q_PROCESSOR_X86
|
||||
void ExpandMinMaxSSE(float *a, int start, int end, float &min_val, float &max_val)
|
||||
{
|
||||
// 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);
|
||||
|
||||
// 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' lenght be not mod. 4
|
||||
for(int i = 4; i < end-4; i+=4) {
|
||||
for(int i = 4; i < length-4; i+=4) {
|
||||
__m128 cur = _mm_loadu_ps(a + start + i);
|
||||
max = _mm_max_ps(max, cur);
|
||||
min = _mm_min_ps(min, cur);
|
||||
}
|
||||
// so we read the last 4 (or less) elements in a safe manner.
|
||||
__m128 cur = _mm_loadu_ps(a + end - 4);
|
||||
__m128 cur = _mm_loadu_ps(a + length - 4);
|
||||
max = _mm_max_ps(max, cur);
|
||||
min = _mm_min_ps(min, cur);
|
||||
// this potentially overlaps up to the last 3 elements but it's not an issue.
|
||||
@@ -323,30 +311,29 @@ void ExpandMinMaxSSE(float *a, int start, int end, float &min_val, float &max_va
|
||||
_mm_store_ss(&max_val, max);
|
||||
_mm_store_ss(&min_val, min);
|
||||
// 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++) {
|
||||
min_val = std::min(min_val, a[i]);
|
||||
max_val = std::max(max_val, a[i]);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
AudioVisualWaveform::Sample AudioVisualWaveform::SumSamples(SampleBufferPtr samples, int start_index, int length)
|
||||
{
|
||||
int channels = samples->audio_params().channel_count();
|
||||
AudioVisualWaveform::Sample summed_samples(channels);
|
||||
|
||||
#ifdef Q_PROCESSOR_X86
|
||||
for (int channel=0; channel<samples->audio_params().channel_count(); channel++) {
|
||||
ExpandMinMaxSSE(samples->data(channel), start_index, length, summed_samples[channel].min, summed_samples[channel].max);
|
||||
}
|
||||
#else
|
||||
int end_index = start_index + length;
|
||||
for (int channel=0; channel<samples->audio_params().channel_count(); channel++) {
|
||||
for (int i=start_index; i<end_index; i++) {
|
||||
ExpandMinMax(summed_samples[channel], samples->data(channel)[i]);
|
||||
}
|
||||
}
|
||||
// for reference: this approximation is n x faster (and less accurate) for a n-tracks clip
|
||||
// for (int i=start_index; i<end_index; i++) {
|
||||
// ExpandMinMax(summed_samples[i%channels], samples->data(i%channels)[i]);
|
||||
// }
|
||||
#endif
|
||||
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);
|
||||
}
|
||||
|
||||
// for reference: this approximation is n x faster (and less accurate) for a n-tracks clip
|
||||
// for (int i=start_index; i<end_index; i++) {
|
||||
// ExpandMinMax(summed_samples[i%channels], samples->data(i%channels)[i]);
|
||||
// }
|
||||
|
||||
return summed_samples;
|
||||
}
|
||||
@@ -485,19 +472,4 @@ std::map<rational, AudioVisualWaveform::Sample>::const_iterator AudioVisualWavef
|
||||
return std::prev(mipmapped_data_.cend());
|
||||
}
|
||||
|
||||
void AudioVisualWaveform::ExpandMinMax(AudioVisualWaveform::SamplePerChannel &sum, float value)
|
||||
{
|
||||
if (value < sum.min) {
|
||||
sum.min = value;
|
||||
}
|
||||
|
||||
if (value > sum.max) {
|
||||
sum.max = value;
|
||||
}
|
||||
|
||||
// to avoid branching
|
||||
// sum.min = std::min(value, sum.min);
|
||||
// sum.max = std::max(value, sum.max);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -98,7 +98,6 @@ public:
|
||||
|
||||
Sample GetSummaryFromTime(const rational& start, const rational& length) const;
|
||||
|
||||
static Sample SumSamples(const float* samples, int nb_samples, int nb_channels);
|
||||
static Sample SumSamples(SampleBufferPtr samples, int start_index, int length);
|
||||
|
||||
static Sample ReSumSamples(const SamplePerChannel *samples, int nb_samples, int nb_channels);
|
||||
@@ -112,8 +111,6 @@ public:
|
||||
static const rational kMaximumSampleRate;
|
||||
|
||||
private:
|
||||
static void ExpandMinMax(SamplePerChannel &sum, float value);
|
||||
|
||||
void OverwriteSamplesFromBuffer(SampleBufferPtr samples, int sample_rate, const rational& start, double target_rate, Sample &data, int &start_index, int &samples_length);
|
||||
|
||||
void OverwriteSamplesFromMipmap(const Sample& input, double input_sample_rate, int &input_start, int &input_length, const rational& start, double output_rate, Sample &output_data);
|
||||
|
||||
@@ -34,6 +34,8 @@ QString ExportCodec::GetCodecName(ExportCodec::Codec c)
|
||||
return tr("DNxHD");
|
||||
case kCodecH264:
|
||||
return tr("H.264");
|
||||
case kCodecH264rgb:
|
||||
return tr("H.264 RGB");
|
||||
case kCodecH265:
|
||||
return tr("H.265");
|
||||
case kCodecOpenEXR:
|
||||
@@ -74,6 +76,7 @@ bool ExportCodec::IsCodecAStillImage(ExportCodec::Codec c)
|
||||
switch (c) {
|
||||
case kCodecDNxHD:
|
||||
case kCodecH264:
|
||||
case kCodecH264rgb:
|
||||
case kCodecH265:
|
||||
case kCodecProRes:
|
||||
case kCodecMP2:
|
||||
|
||||
@@ -37,6 +37,7 @@ public:
|
||||
// Video codecs
|
||||
kCodecDNxHD,
|
||||
kCodecH264,
|
||||
kCodecH264rgb,
|
||||
kCodecH265,
|
||||
kCodecOpenEXR,
|
||||
kCodecPNG,
|
||||
|
||||
@@ -107,9 +107,9 @@ QList<ExportCodec::Codec> ExportFormat::GetVideoCodecs(ExportFormat::Format f)
|
||||
case kFormatDNxHD:
|
||||
return {ExportCodec::kCodecDNxHD};
|
||||
case kFormatMatroska:
|
||||
return {ExportCodec::kCodecH264, ExportCodec::kCodecH265};
|
||||
return {ExportCodec::kCodecH264, ExportCodec::kCodecH264rgb, ExportCodec::kCodecH265, ExportCodec::kCodecVP9};
|
||||
case kFormatMPEG4:
|
||||
return {ExportCodec::kCodecH264, ExportCodec::kCodecH265};
|
||||
return {ExportCodec::kCodecH264, ExportCodec::kCodecH264rgb, ExportCodec::kCodecH265};
|
||||
case kFormatOpenEXR:
|
||||
return {ExportCodec::kCodecOpenEXR};
|
||||
case kFormatPNG:
|
||||
@@ -117,7 +117,7 @@ QList<ExportCodec::Codec> ExportFormat::GetVideoCodecs(ExportFormat::Format f)
|
||||
case kFormatTIFF:
|
||||
return {ExportCodec::kCodecTIFF};
|
||||
case kFormatQuickTime:
|
||||
return {ExportCodec::kCodecH264, ExportCodec::kCodecH265, ExportCodec::kCodecProRes};
|
||||
return {ExportCodec::kCodecH264, ExportCodec::kCodecH264rgb, ExportCodec::kCodecH265, ExportCodec::kCodecProRes};
|
||||
case kFormatWebM:
|
||||
return {ExportCodec::kCodecVP9};
|
||||
case kFormatOgg:
|
||||
@@ -140,7 +140,7 @@ QList<ExportCodec::Codec> ExportFormat::GetAudioCodecs(ExportFormat::Format f)
|
||||
case kFormatDNxHD:
|
||||
return {ExportCodec::kCodecPCM};
|
||||
case kFormatMatroska:
|
||||
return {ExportCodec::kCodecAAC, ExportCodec::kCodecMP2, ExportCodec::kCodecMP3, ExportCodec::kCodecPCM, ExportCodec::kCodecVorbis, ExportCodec::kCodecOpus};
|
||||
return {ExportCodec::kCodecAAC, ExportCodec::kCodecMP2, ExportCodec::kCodecMP3, ExportCodec::kCodecPCM, ExportCodec::kCodecVorbis, ExportCodec::kCodecOpus, ExportCodec::kCodecFLAC};
|
||||
case kFormatMPEG4:
|
||||
return {ExportCodec::kCodecAAC, ExportCodec::kCodecMP2, ExportCodec::kCodecMP3};
|
||||
case kFormatQuickTime:
|
||||
|
||||
@@ -50,45 +50,7 @@ QStringList FFmpegEncoder::GetPixelFormatsForCodec(ExportCodec::Codec c) const
|
||||
{
|
||||
QStringList pix_fmts;
|
||||
|
||||
AVCodec* codec_info = nullptr;
|
||||
|
||||
switch (c) {
|
||||
case ExportCodec::kCodecH264:
|
||||
codec_info = avcodec_find_encoder(AV_CODEC_ID_H264);
|
||||
break;
|
||||
case ExportCodec::kCodecDNxHD:
|
||||
codec_info = avcodec_find_encoder(AV_CODEC_ID_DNXHD);
|
||||
break;
|
||||
case ExportCodec::kCodecProRes:
|
||||
codec_info = avcodec_find_encoder(AV_CODEC_ID_PRORES);
|
||||
break;
|
||||
case ExportCodec::kCodecH265:
|
||||
codec_info = avcodec_find_encoder(AV_CODEC_ID_HEVC);
|
||||
break;
|
||||
case ExportCodec::kCodecVP9:
|
||||
codec_info = avcodec_find_encoder(AV_CODEC_ID_VP9);
|
||||
break;
|
||||
case ExportCodec::kCodecOpenEXR:
|
||||
codec_info = avcodec_find_encoder(AV_CODEC_ID_EXR);
|
||||
break;
|
||||
case ExportCodec::kCodecPNG:
|
||||
codec_info = avcodec_find_encoder(AV_CODEC_ID_PNG);
|
||||
break;
|
||||
case ExportCodec::kCodecTIFF:
|
||||
codec_info = avcodec_find_encoder(AV_CODEC_ID_TIFF);
|
||||
break;
|
||||
case ExportCodec::kCodecMP2:
|
||||
case ExportCodec::kCodecMP3:
|
||||
case ExportCodec::kCodecAAC:
|
||||
case ExportCodec::kCodecPCM:
|
||||
case ExportCodec::kCodecFLAC:
|
||||
case ExportCodec::kCodecOpus:
|
||||
case ExportCodec::kCodecVorbis:
|
||||
case ExportCodec::kCodecSRT:
|
||||
case ExportCodec::kCodecCount:
|
||||
// These are audio or invalid codecs and therefore have no pixel formats
|
||||
break;
|
||||
}
|
||||
AVCodec* codec_info = GetEncoder(c);
|
||||
|
||||
if (codec_info) {
|
||||
for (int i=0; codec_info->pix_fmts[i]!=-1; i++) {
|
||||
@@ -607,72 +569,10 @@ bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AV
|
||||
return false;
|
||||
}
|
||||
|
||||
// Retrieve codec
|
||||
AVCodecID codec_id = AV_CODEC_ID_NONE;
|
||||
|
||||
switch (codec) {
|
||||
case ExportCodec::kCodecDNxHD:
|
||||
codec_id = AV_CODEC_ID_DNXHD;
|
||||
break;
|
||||
case ExportCodec::kCodecAAC:
|
||||
codec_id = AV_CODEC_ID_AAC;
|
||||
break;
|
||||
case ExportCodec::kCodecMP2:
|
||||
codec_id = AV_CODEC_ID_MP2;
|
||||
break;
|
||||
case ExportCodec::kCodecMP3:
|
||||
codec_id = AV_CODEC_ID_MP3;
|
||||
break;
|
||||
case ExportCodec::kCodecH264:
|
||||
codec_id = AV_CODEC_ID_H264;
|
||||
break;
|
||||
case ExportCodec::kCodecH265:
|
||||
codec_id = AV_CODEC_ID_HEVC;
|
||||
break;
|
||||
case ExportCodec::kCodecOpenEXR:
|
||||
codec_id = AV_CODEC_ID_EXR;
|
||||
break;
|
||||
case ExportCodec::kCodecPNG:
|
||||
codec_id = AV_CODEC_ID_PNG;
|
||||
break;
|
||||
case ExportCodec::kCodecTIFF:
|
||||
codec_id = AV_CODEC_ID_TIFF;
|
||||
break;
|
||||
case ExportCodec::kCodecProRes:
|
||||
codec_id = AV_CODEC_ID_PRORES;
|
||||
break;
|
||||
case ExportCodec::kCodecPCM:
|
||||
codec_id = AV_CODEC_ID_PCM_S16LE;
|
||||
break;
|
||||
case ExportCodec::kCodecVP9:
|
||||
codec_id = AV_CODEC_ID_VP9;
|
||||
break;
|
||||
case ExportCodec::kCodecOpus:
|
||||
codec_id = AV_CODEC_ID_OPUS;
|
||||
break;
|
||||
case ExportCodec::kCodecVorbis:
|
||||
codec_id = AV_CODEC_ID_VORBIS;
|
||||
break;
|
||||
case ExportCodec::kCodecFLAC:
|
||||
codec_id = AV_CODEC_ID_FLAC;
|
||||
break;
|
||||
case ExportCodec::kCodecSRT:
|
||||
codec_id = AV_CODEC_ID_SUBRIP;
|
||||
break;
|
||||
case ExportCodec::kCodecCount:
|
||||
break;
|
||||
}
|
||||
|
||||
if (codec_id == AV_CODEC_ID_NONE) {
|
||||
SetError(tr("Unknown internal codec"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find encoder with this name
|
||||
AVCodec* encoder = avcodec_find_encoder(codec_id);
|
||||
|
||||
// Find encoder
|
||||
AVCodec* encoder = GetEncoder(codec);
|
||||
if (!encoder) {
|
||||
SetError(tr("Failed to find codec for %1").arg(codec));
|
||||
SetError(tr("Failed to find codec for 0x%1").arg(codec, 16));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -708,7 +608,7 @@ bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AV
|
||||
} else {
|
||||
codec_ctx->field_order = AV_FIELD_BB;
|
||||
|
||||
if (codec_id == AV_CODEC_ID_H264) {
|
||||
if (codec == ExportCodec::kCodecH264 || codec == ExportCodec::kCodecH264rgb) {
|
||||
// For some reason, FFmpeg doesn't set libx264's bff flag so we have to do it ourselves
|
||||
av_opt_set(codec_ctx->priv_data, "x264opts", "bff=1", AV_OPT_SEARCH_CHILDREN);
|
||||
}
|
||||
@@ -933,4 +833,49 @@ bool FFmpegEncoder::InitializeResampleContext(SampleBufferPtr audio)
|
||||
return true;
|
||||
}
|
||||
|
||||
AVCodec *FFmpegEncoder::GetEncoder(ExportCodec::Codec c)
|
||||
{
|
||||
switch (c) {
|
||||
case ExportCodec::kCodecH264:
|
||||
return avcodec_find_encoder_by_name("libx264");
|
||||
case ExportCodec::kCodecH264rgb:
|
||||
return avcodec_find_encoder_by_name("libx264rgb");
|
||||
case ExportCodec::kCodecDNxHD:
|
||||
return avcodec_find_encoder(AV_CODEC_ID_DNXHD);
|
||||
case ExportCodec::kCodecProRes:
|
||||
return avcodec_find_encoder(AV_CODEC_ID_PRORES);
|
||||
case ExportCodec::kCodecH265:
|
||||
return avcodec_find_encoder(AV_CODEC_ID_HEVC);
|
||||
case ExportCodec::kCodecVP9:
|
||||
return avcodec_find_encoder(AV_CODEC_ID_VP9);
|
||||
case ExportCodec::kCodecOpenEXR:
|
||||
return avcodec_find_encoder(AV_CODEC_ID_EXR);
|
||||
case ExportCodec::kCodecPNG:
|
||||
return avcodec_find_encoder(AV_CODEC_ID_PNG);
|
||||
case ExportCodec::kCodecTIFF:
|
||||
return avcodec_find_encoder(AV_CODEC_ID_TIFF);
|
||||
case ExportCodec::kCodecMP2:
|
||||
return avcodec_find_encoder(AV_CODEC_ID_MP2);
|
||||
case ExportCodec::kCodecMP3:
|
||||
return avcodec_find_encoder(AV_CODEC_ID_MP3);
|
||||
case ExportCodec::kCodecAAC:
|
||||
return avcodec_find_encoder(AV_CODEC_ID_AAC);
|
||||
case ExportCodec::kCodecPCM:
|
||||
return avcodec_find_encoder(AV_CODEC_ID_PCM_S16LE);
|
||||
case ExportCodec::kCodecFLAC:
|
||||
return avcodec_find_encoder(AV_CODEC_ID_FLAC);
|
||||
case ExportCodec::kCodecOpus:
|
||||
return avcodec_find_encoder(AV_CODEC_ID_OPUS);
|
||||
case ExportCodec::kCodecVorbis:
|
||||
return avcodec_find_encoder(AV_CODEC_ID_VORBIS);
|
||||
case ExportCodec::kCodecSRT:
|
||||
return avcodec_find_encoder(AV_CODEC_ID_SUBRIP);
|
||||
case ExportCodec::kCodecCount:
|
||||
// These are audio or invalid codecs and therefore have no pixel formats
|
||||
break;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -77,6 +77,8 @@ private:
|
||||
|
||||
bool InitializeResampleContext(SampleBufferPtr audio);
|
||||
|
||||
static AVCodec *GetEncoder(ExportCodec::Codec c);
|
||||
|
||||
AVFormatContext* fmt_ctx_;
|
||||
|
||||
AVStream* video_stream_;
|
||||
|
||||
@@ -26,6 +26,36 @@
|
||||
|
||||
namespace olive {
|
||||
|
||||
Bezier::Bezier() :
|
||||
x_(0),
|
||||
y_(0),
|
||||
cp1_x_(0),
|
||||
cp1_y_(0),
|
||||
cp2_x_(0),
|
||||
cp2_y_(0)
|
||||
{
|
||||
}
|
||||
|
||||
Bezier::Bezier(double x, double y) :
|
||||
x_(x),
|
||||
y_(y),
|
||||
cp1_x_(0),
|
||||
cp1_y_(0),
|
||||
cp2_x_(0),
|
||||
cp2_y_(0)
|
||||
{
|
||||
}
|
||||
|
||||
Bezier::Bezier(double x, double y, double cp1_x, double cp1_y, double cp2_x, double cp2_y) :
|
||||
x_(x),
|
||||
y_(y),
|
||||
cp1_x_(cp1_x),
|
||||
cp1_y_(cp1_y),
|
||||
cp2_x_(cp2_x),
|
||||
cp2_y_(cp2_y)
|
||||
{
|
||||
}
|
||||
|
||||
double Bezier::QuadraticXtoT(double x, double a, double b, double c)
|
||||
{
|
||||
// Clamp to prevent infinite loop
|
||||
@@ -58,6 +88,10 @@ double Bezier::CalculateTFromX(bool cubic, double x, double a, double b, double
|
||||
double top = 1.0;
|
||||
|
||||
while (true) {
|
||||
if (bottom == top) {
|
||||
return bottom;
|
||||
}
|
||||
|
||||
double mid = (bottom + top) * 0.5;
|
||||
double test = cubic ? CubicTtoY(a, b, c, d, mid) : QuadraticTtoY(a, b, c, mid);
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#define BEZIER_H
|
||||
|
||||
#include <QPointF>
|
||||
#include <QObject>
|
||||
|
||||
#include "common/define.h"
|
||||
|
||||
@@ -30,6 +31,39 @@ namespace olive {
|
||||
class Bezier
|
||||
{
|
||||
public:
|
||||
Bezier();
|
||||
Bezier(double x, double y);
|
||||
Bezier(double x, double y, double cp1_x, double cp1_y, double cp2_x, double cp2_y);
|
||||
|
||||
const double &x() const {return x_; }
|
||||
const double &y() const {return y_; }
|
||||
const double &cp1_x() const { return cp1_x_; }
|
||||
const double &cp1_y() const { return cp1_y_; }
|
||||
const double &cp2_x() const { return cp2_x_; }
|
||||
const double &cp2_y() const { return cp2_y_; }
|
||||
|
||||
QPointF ToPointF() const
|
||||
{
|
||||
return QPointF(x_, y_);
|
||||
}
|
||||
|
||||
QPointF ControlPoint1ToPointF() const
|
||||
{
|
||||
return QPointF(cp1_x_, cp1_y_);
|
||||
}
|
||||
|
||||
QPointF ControlPoint2ToPointF() const
|
||||
{
|
||||
return QPointF(cp2_x_, cp2_y_);
|
||||
}
|
||||
|
||||
void set_x(const double &x) { x_ = x; }
|
||||
void set_y(const double &y) { y_ = y; }
|
||||
void set_cp1_x(const double &cp1_x) { cp1_x_ = cp1_x; }
|
||||
void set_cp1_y(const double &cp1_y) { cp1_y_ = cp1_y; }
|
||||
void set_cp2_x(const double &cp2_x) { cp2_x_ = cp2_x; }
|
||||
void set_cp2_y(const double &cp2_y) { cp2_y_ = cp2_y; }
|
||||
|
||||
static double QuadraticXtoT(double x, double a, double b, double c);
|
||||
|
||||
static double QuadraticTtoY(double a, double b, double c, double t);
|
||||
@@ -51,8 +85,19 @@ public:
|
||||
private:
|
||||
static double CalculateTFromX(bool cubic, double x, double a, double b, double c, double d);
|
||||
|
||||
double x_;
|
||||
double y_;
|
||||
|
||||
double cp1_x_;
|
||||
double cp1_y_;
|
||||
|
||||
double cp2_x_;
|
||||
double cp2_y_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(olive::Bezier)
|
||||
|
||||
#endif // BEZIER_H
|
||||
|
||||
@@ -52,16 +52,16 @@ const CommandLineParser::PositionalArgument *CommandLineParser::AddPositionalArg
|
||||
return a;
|
||||
}
|
||||
|
||||
void CommandLineParser::Process(int argc, char **argv)
|
||||
void CommandLineParser::Process(const QVector<QString> &argv)
|
||||
{
|
||||
int positional_index = 0;
|
||||
|
||||
for (int i=1; i<argc; i++) {
|
||||
for (int i=1; i<argv.size(); i++) {
|
||||
if (argv[i][0] == '-') {
|
||||
// Must be an option
|
||||
|
||||
// Skip past first dashes
|
||||
const char* arg_basename = &argv[i][1];
|
||||
QString arg_basename = argv[i].mid(1);
|
||||
|
||||
bool matched_known = false;
|
||||
|
||||
@@ -73,7 +73,7 @@ void CommandLineParser::Process(int argc, char **argv)
|
||||
// Flag discovered!
|
||||
o.option->Set();
|
||||
|
||||
if (o.takes_arg && i+1 < argc) {
|
||||
if (o.takes_arg && i+1 < argv.size()) {
|
||||
o.option->SetSetting(argv[i+1]);
|
||||
i++;
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ public:
|
||||
|
||||
const PositionalArgument* AddPositionalArgument(const QString& name, const QString& description, bool required = false);
|
||||
|
||||
void Process(int argc, char** argv);
|
||||
void Process(const QVector<QString> &argv);
|
||||
|
||||
void PrintHelp(const char* filename);
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/***
|
||||
|
||||
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 CPUOPTIMIZE_H
|
||||
#define CPUOPTIMIZE_H
|
||||
|
||||
#if defined(Q_PROCESSOR_X86)
|
||||
#include <xmmintrin.h>
|
||||
#elif defined(Q_PROCESSOR_ARM)
|
||||
#include <sse2neon.h>
|
||||
#endif
|
||||
|
||||
#endif // CPUOPTIMIZE_H
|
||||
@@ -25,6 +25,8 @@
|
||||
#include <QDebug>
|
||||
|
||||
#define TIME_THIS_FUNCTION FunctionTimer __f(__FUNCTION__)
|
||||
#define START_TIMING {FunctionTimer *__f = new FunctionTimer(__FUNCTION__)
|
||||
#define STOP_TIMING delete __f;}void()
|
||||
|
||||
class FunctionTimer {
|
||||
public:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef TOHEX_H
|
||||
#define TOHEX_H
|
||||
|
||||
#include <QString>
|
||||
#include <QtGlobal>
|
||||
|
||||
#include "common/define.h"
|
||||
|
||||
@@ -27,34 +27,6 @@
|
||||
|
||||
namespace olive {
|
||||
|
||||
void XMLConnectNodes(const XMLNodeData &xml_node_data, uint version, MultiUndoCommand *command)
|
||||
{
|
||||
foreach (const XMLNodeData::SerializedConnection& con, xml_node_data.desired_connections) {
|
||||
Node *out = xml_node_data.node_ptrs.value(con.output_node);
|
||||
|
||||
if (out) {
|
||||
// Use output param as hint tag since we grandfathered those in
|
||||
Node::ValueHint hint(con.output_param);
|
||||
|
||||
if (command) {
|
||||
command->add_child(new NodeEdgeAddCommand(out, con.input));
|
||||
|
||||
if (version < 210907) {
|
||||
/// Deprecated: backwards compatibility only
|
||||
command->add_child(new NodeSetValueHintCommand(con.input, hint));
|
||||
}
|
||||
} else {
|
||||
Node::ConnectEdge(out, con.input);
|
||||
|
||||
if (version < 210907) {
|
||||
/// Deprecated: backwards compatibility only
|
||||
con.input.node()->SetValueHintForInput(con.input.input(), hint, con.input.element());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool XMLReadNextStartElement(QXmlStreamReader *reader)
|
||||
{
|
||||
QXmlStreamReader::TokenType token;
|
||||
@@ -71,11 +43,4 @@ bool XMLReadNextStartElement(QXmlStreamReader *reader)
|
||||
return false;
|
||||
}
|
||||
|
||||
void XMLLinkBlocks(const XMLNodeData &xml_node_data)
|
||||
{
|
||||
foreach (const XMLNodeData::BlockLink& l, xml_node_data.block_links) {
|
||||
Block::Link(l.block, static_cast<Block*>(xml_node_data.node_ptrs.value(l.link)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-24
@@ -31,32 +31,11 @@ namespace olive {
|
||||
class Block;
|
||||
class Node;
|
||||
class NodeInput;
|
||||
class NodeGroup;
|
||||
|
||||
#define XMLAttributeLoop(reader, item) \
|
||||
foreach (const QXmlStreamAttribute& item, reader->attributes())
|
||||
|
||||
struct XMLNodeData {
|
||||
struct SerializedConnection {
|
||||
NodeInput input;
|
||||
quintptr output_node;
|
||||
|
||||
/// Deprecated
|
||||
QString output_param;
|
||||
};
|
||||
|
||||
struct BlockLink {
|
||||
Node* block;
|
||||
quintptr link;
|
||||
};
|
||||
|
||||
QHash<quintptr, Node*> node_ptrs;
|
||||
QList<SerializedConnection> desired_connections;
|
||||
QList<BlockLink> block_links;
|
||||
|
||||
};
|
||||
|
||||
void XMLConnectNodes(const XMLNodeData& xml_node_data, uint version, MultiUndoCommand *command = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Workaround for QXmlStreamReader::readNextStartElement not detecting the end of a document
|
||||
*
|
||||
@@ -68,8 +47,6 @@ void XMLConnectNodes(const XMLNodeData& xml_node_data, uint version, MultiUndoCo
|
||||
*/
|
||||
bool XMLReadNextStartElement(QXmlStreamReader* reader);
|
||||
|
||||
void XMLLinkBlocks(const XMLNodeData& xml_node_data);
|
||||
|
||||
}
|
||||
|
||||
#endif // XMLREADLOOP_H
|
||||
|
||||
@@ -100,6 +100,7 @@ void Config::SetDefaults()
|
||||
SetEntryInternal(QStringLiteral("ShowClipWhileDragging"), NodeValue::kBoolean, true);
|
||||
SetEntryInternal(QStringLiteral("StopPlaybackOnLastFrame"), NodeValue::kBoolean, false);
|
||||
SetEntryInternal(QStringLiteral("UseLegacyColorInInputTab"), NodeValue::kBoolean, false);
|
||||
SetEntryInternal(QStringLiteral("ReassocLinToNonLin"), NodeValue::kBoolean, false);
|
||||
|
||||
SetEntryInternal(QStringLiteral("AutoCacheDelay"), NodeValue::kInt, 1000);
|
||||
|
||||
|
||||
+63
-25
@@ -43,11 +43,16 @@
|
||||
#include "dialog/autorecovery/autorecoverydialog.h"
|
||||
#include "dialog/export/export.h"
|
||||
#include "dialog/footagerelink/footagerelinkdialog.h"
|
||||
#ifdef USE_OTIO
|
||||
#include "dialog/otioproperties/otiopropertiesdialog.h"
|
||||
#endif
|
||||
#include "dialog/projectproperties/projectproperties.h"
|
||||
#include "dialog/sequence/sequence.h"
|
||||
#include "dialog/task/task.h"
|
||||
#include "dialog/preferences/preferences.h"
|
||||
#include "node/color/colormanager/colormanager.h"
|
||||
#include "node/factory.h"
|
||||
#include "node/project/serializer/serializer.h"
|
||||
#include "panel/panelmanager.h"
|
||||
#include "panel/project/project.h"
|
||||
#include "panel/viewer/viewer.h"
|
||||
@@ -75,7 +80,6 @@
|
||||
namespace olive {
|
||||
|
||||
Core* Core::instance_ = nullptr;
|
||||
const uint Core::kProjectVersion = 210907;
|
||||
|
||||
Core::Core(const CoreParams& params) :
|
||||
main_window_(nullptr),
|
||||
@@ -149,6 +153,9 @@ void Core::Start()
|
||||
// Initialize ConformManager
|
||||
ConformManager::CreateInstance();
|
||||
|
||||
// Initialize project serializers
|
||||
ProjectSerializer::Initialize();
|
||||
|
||||
//
|
||||
// Start application
|
||||
//
|
||||
@@ -199,14 +206,13 @@ void Core::Stop()
|
||||
{
|
||||
QFile recent_projects_file(GetRecentProjectsFilePath());
|
||||
if (recent_projects_file.open(QFile::WriteOnly | QFile::Text)) {
|
||||
QTextStream ts(&recent_projects_file);
|
||||
|
||||
ts << recent_projects_.join('\n');
|
||||
|
||||
recent_projects_file.write(recent_projects_.join('\n').toUtf8());
|
||||
recent_projects_file.close();
|
||||
}
|
||||
}
|
||||
|
||||
ProjectSerializer::Destroy();
|
||||
|
||||
ConformManager::DestroyInstance();
|
||||
|
||||
FrameManager::DestroyInstance();
|
||||
@@ -368,6 +374,21 @@ void Core::DialogPreferencesShow()
|
||||
pd.exec();
|
||||
}
|
||||
|
||||
void Core::DialogProjectPropertiesShow()
|
||||
{
|
||||
Project *proj = GetActiveProject();
|
||||
|
||||
if (proj) {
|
||||
ProjectPropertiesDialog ppd(proj, main_window_);
|
||||
ppd.exec();
|
||||
} else {
|
||||
QMessageBox::critical(main_window_,
|
||||
tr("No Active Project"),
|
||||
tr("No project is currently open to set the properties for"),
|
||||
QMessageBox::Ok);
|
||||
}
|
||||
}
|
||||
|
||||
void Core::DialogExportShow()
|
||||
{
|
||||
ViewerOutput* viewer = GetSequenceToExport();
|
||||
@@ -379,6 +400,14 @@ void Core::DialogExportShow()
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_OTIO
|
||||
bool Core::DialogImportOTIOShow(const QList<Sequence*>& sequences) {
|
||||
Project* active_project = GetActiveProject();
|
||||
OTIOPropertiesDialog opd(sequences, active_project);
|
||||
return opd.exec() == QDialog::Accepted;
|
||||
}
|
||||
#endif
|
||||
|
||||
void Core::CreateNewFolder()
|
||||
{
|
||||
// Locate the most recently focused Project panel (assume that's the panel the user wants to import into)
|
||||
@@ -437,7 +466,7 @@ void Core::CreateNewSequence()
|
||||
|
||||
command->add_child(new NodeAddCommand(active_project, new_sequence));
|
||||
command->add_child(new FolderAddChild(GetSelectedFolderInActiveProject(), new_sequence));
|
||||
command->add_child(new NodeSetPositionCommand(new_sequence, new_sequence, QPointF(0, 0), false));
|
||||
command->add_child(new NodeSetPositionCommand(new_sequence, new_sequence, Node::Position()));
|
||||
|
||||
// Create and connect default nodes to new sequence
|
||||
new_sequence->add_default_nodes(command);
|
||||
@@ -482,19 +511,20 @@ bool Core::AddOpenProjectFromTask(Task *task)
|
||||
{
|
||||
ProjectLoadBaseTask* load_task = static_cast<ProjectLoadBaseTask*>(task);
|
||||
|
||||
Project* project = load_task->GetLoadedProject();
|
||||
MainWindowLayoutInfo layout = load_task->GetLoadedLayout();
|
||||
if (!load_task->IsCancelled()) {
|
||||
Project* project = load_task->GetLoadedProject();
|
||||
|
||||
if (ValidateFootageInLoadedProject(project, load_task->GetFilenameProjectWasSavedAs())) {
|
||||
AddOpenProject(project);
|
||||
main_window_->LoadLayout(layout);
|
||||
if (ValidateFootageInLoadedProject(project, project->GetSavedURL())) {
|
||||
AddOpenProject(project);
|
||||
main_window_->LoadLayout(project->GetLayoutInfo());
|
||||
|
||||
return true;
|
||||
} else {
|
||||
delete project;
|
||||
|
||||
return false;
|
||||
return true;
|
||||
} else {
|
||||
delete project;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Core::ImportTaskComplete(Task* task)
|
||||
@@ -729,13 +759,10 @@ void Core::StartGUI(bool full_screen)
|
||||
{
|
||||
QFile recent_projects_file(GetRecentProjectsFilePath());
|
||||
if (recent_projects_file.open(QFile::ReadOnly | QFile::Text)) {
|
||||
QTextStream ts(&recent_projects_file);
|
||||
|
||||
QString s;
|
||||
while (!(s = ts.readLine()).isEmpty()) {
|
||||
recent_projects_.append(s);
|
||||
QString r = QString::fromUtf8(recent_projects_file.readAll());
|
||||
if (!r.isEmpty()) {
|
||||
recent_projects_ = r.split('\n');
|
||||
}
|
||||
|
||||
recent_projects_file.close();
|
||||
}
|
||||
|
||||
@@ -748,6 +775,9 @@ void Core::SaveProjectInternal(Project* project, const QString& override_filenam
|
||||
// Create save manager
|
||||
Task* psm;
|
||||
|
||||
// Put layout into project
|
||||
project->SetLayoutInfo(main_window_->SaveLayout());
|
||||
|
||||
if (project->filename().endsWith(QStringLiteral(".otio"), Qt::CaseInsensitive)) {
|
||||
#ifdef USE_OTIO
|
||||
psm = new SaveOTIOTask(project);
|
||||
@@ -1358,10 +1388,10 @@ void Core::SetPreferenceForRenderMode(RenderMode::Mode mode, const QString &pref
|
||||
Config::Current()[GetRenderModePreferencePrefix(mode, preference)] = value;
|
||||
}
|
||||
|
||||
void Core::LabelNodes(const QVector<Node *> &nodes)
|
||||
bool Core::LabelNodes(const QVector<Node *> &nodes, MultiUndoCommand *parent)
|
||||
{
|
||||
if (nodes.isEmpty()) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok;
|
||||
@@ -1390,8 +1420,16 @@ void Core::LabelNodes(const QVector<Node *> &nodes)
|
||||
rename_command->AddNode(n, s);
|
||||
}
|
||||
|
||||
undo_stack_.push(rename_command);
|
||||
if (parent) {
|
||||
parent->add_child(rename_command);
|
||||
} else {
|
||||
undo_stack_.push(rename_command);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Sequence *Core::CreateNewSequenceForProject(Project* project) const
|
||||
|
||||
+13
-3
@@ -253,7 +253,7 @@ public:
|
||||
/**
|
||||
* @brief Show a dialog to the user to rename a set of nodes
|
||||
*/
|
||||
void LabelNodes(const QVector<Node *> &nodes);
|
||||
bool LabelNodes(const QVector<Node *> &nodes, MultiUndoCommand *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Create a new sequence named appropriately for the active project
|
||||
@@ -316,8 +316,6 @@ public:
|
||||
|
||||
void OpenNodeInViewer(ViewerOutput* viewer);
|
||||
|
||||
static const uint kProjectVersion;
|
||||
|
||||
public slots:
|
||||
/**
|
||||
* @brief Starts an open file dialog to load a project from file
|
||||
@@ -393,11 +391,23 @@ public slots:
|
||||
*/
|
||||
void DialogPreferencesShow();
|
||||
|
||||
/**
|
||||
* @brief Show Project Properties dialog
|
||||
*/
|
||||
void DialogProjectPropertiesShow();
|
||||
|
||||
/**
|
||||
* @brief Show Export dialog
|
||||
*/
|
||||
void DialogExportShow();
|
||||
|
||||
/**
|
||||
* @brief Show OTIO import dialog
|
||||
*/
|
||||
#ifdef USE_OTIO
|
||||
bool DialogImportOTIOShow(const QList<Sequence*>& sequences);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Create a new folder in the currently active project
|
||||
*/
|
||||
|
||||
@@ -21,11 +21,15 @@ add_subdirectory(color)
|
||||
add_subdirectory(configbase)
|
||||
add_subdirectory(diskcache)
|
||||
add_subdirectory(export)
|
||||
add_subdirectory(footageproperties)
|
||||
add_subdirectory(footagerelink)
|
||||
add_subdirectory(keyframeproperties)
|
||||
add_subdirectory(nodeproperties)
|
||||
if(OpenTimelineIO_FOUND)
|
||||
add_subdirectory(otioproperties)
|
||||
endif()
|
||||
add_subdirectory(preferences)
|
||||
add_subdirectory(progress)
|
||||
add_subdirectory(projectproperties)
|
||||
add_subdirectory(rendercancel)
|
||||
add_subdirectory(sequence)
|
||||
add_subdirectory(speedduration)
|
||||
|
||||
@@ -27,6 +27,6 @@ while True:
|
||||
else:
|
||||
break
|
||||
|
||||
text_file = open("patreon.h", "w")
|
||||
text_file = open("patreon.h", "w", encoding="utf-8")
|
||||
text_file.write("#ifndef PATREON_H\n#define PATREON_H\n\n#include <QStringList>\n\nQStringList patrons = {\n%s\n};\n\n#endif // PATREON_H\n" % name_list)
|
||||
text_file.close()
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include "dialog/task/task.h"
|
||||
#include "node/project/project.h"
|
||||
#include "node/project/sequence/sequence.h"
|
||||
#include "task/taskmanager.h"
|
||||
#include "ui/icons/icons.h"
|
||||
|
||||
namespace olive {
|
||||
@@ -152,13 +153,27 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
|
||||
|
||||
row++;
|
||||
|
||||
buttons_ = new QDialogButtonBox();
|
||||
buttons_->setCenterButtons(true);
|
||||
buttons_->addButton(tr("Export"), QDialogButtonBox::AcceptRole);
|
||||
buttons_->addButton(QDialogButtonBox::Cancel);
|
||||
connect(buttons_, &QDialogButtonBox::accepted, this, &ExportDialog::StartExport);
|
||||
connect(buttons_, &QDialogButtonBox::rejected, this, &ExportDialog::reject);
|
||||
preferences_layout->addWidget(buttons_, row, 0, 1, 4);
|
||||
QHBoxLayout *btn_layout = new QHBoxLayout();
|
||||
btn_layout->setMargin(0);
|
||||
preferences_layout->addLayout(btn_layout, row, 0, 1, 4);
|
||||
|
||||
btn_layout->addStretch();
|
||||
|
||||
QPushButton *export_btn = new QPushButton(tr("Export"));
|
||||
btn_layout->addWidget(export_btn);
|
||||
connect(export_btn, &QPushButton::clicked, this, &ExportDialog::StartExport);
|
||||
|
||||
QPushButton *cancel_btn = new QPushButton(tr("Cancel"));
|
||||
btn_layout->addWidget(cancel_btn);
|
||||
connect(cancel_btn, &QPushButton::clicked, this, &ExportDialog::reject);
|
||||
|
||||
export_bkg_box_ = new QCheckBox(tr("Run In Background"));
|
||||
export_bkg_box_->setToolTip(tr("Exporting in the background allows you to continue using Olive while "
|
||||
"exporting, but may result in slower export speeds, and may"
|
||||
"severely impact editing and playback performance."));
|
||||
btn_layout->addWidget(export_bkg_box_);
|
||||
|
||||
btn_layout->addStretch();
|
||||
|
||||
splitter->addWidget(preferences_area_);
|
||||
|
||||
@@ -346,9 +361,17 @@ void ExportDialog::StartExport()
|
||||
}
|
||||
|
||||
ExportTask* task = new ExportTask(viewer_node_, color_manager_, GenerateParams());
|
||||
TaskDialog* td = new TaskDialog(task, tr("Export"), this);
|
||||
connect(td, &TaskDialog::TaskSucceeded, this, &ExportDialog::ExportFinished);
|
||||
td->open();
|
||||
|
||||
if (export_bkg_box_->isChecked()) {
|
||||
// Send to TaskManager to export in background
|
||||
TaskManager::instance()->AddTask(task);
|
||||
this->accept();
|
||||
} else {
|
||||
// Use modal dialog box
|
||||
TaskDialog* td = new TaskDialog(task, tr("Export"), this);
|
||||
connect(td, &TaskDialog::TaskSucceeded, this, &ExportDialog::ExportFinished);
|
||||
td->open();
|
||||
}
|
||||
}
|
||||
|
||||
void ExportDialog::ExportFinished()
|
||||
|
||||
@@ -93,7 +93,7 @@ private:
|
||||
ColorManager* color_manager_;
|
||||
|
||||
QWidget* preferences_area_;
|
||||
QDialogButtonBox* buttons_;
|
||||
QCheckBox *export_bkg_box_;
|
||||
|
||||
private slots:
|
||||
void BrowseFilename();
|
||||
|
||||
@@ -232,14 +232,16 @@ void ExportVideoTab::VideoCodecChanged()
|
||||
{
|
||||
ExportCodec::Codec codec = GetSelectedCodec();
|
||||
|
||||
if (codec == ExportCodec::kCodecH264) {
|
||||
SetCodecSection(h264_section_);
|
||||
} else if (codec == ExportCodec::kCodecH265) {
|
||||
SetCodecSection(h265_section_);
|
||||
} else if (ExportCodec::IsCodecAStillImage(codec)) {
|
||||
SetCodecSection(image_section_);
|
||||
} else {
|
||||
SetCodecSection(nullptr);
|
||||
switch (codec) {
|
||||
case ExportCodec::kCodecH264:
|
||||
case ExportCodec::kCodecH264rgb:
|
||||
SetCodecSection(h264_section_);
|
||||
break;
|
||||
case ExportCodec::kCodecH265:
|
||||
SetCodecSection(h265_section_);
|
||||
break;
|
||||
default:
|
||||
SetCodecSection(ExportCodec::IsCodecAStillImage(codec) ? image_section_ : nullptr);
|
||||
}
|
||||
|
||||
// Set default pixel format
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2020 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/>.
|
||||
|
||||
add_subdirectory(streamproperties)
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
dialog/footageproperties/footageproperties.cpp
|
||||
dialog/footageproperties/footageproperties.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,251 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2020 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 "footageproperties.h"
|
||||
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
#include <QComboBox>
|
||||
#include <QLineEdit>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QTreeWidgetItem>
|
||||
#include <QGroupBox>
|
||||
#include <QListWidget>
|
||||
#include <QCheckBox>
|
||||
#include <QSpinBox>
|
||||
|
||||
#include "core.h"
|
||||
#include "streamproperties/audiostreamproperties.h"
|
||||
#include "streamproperties/videostreamproperties.h"
|
||||
#include "widget/nodeview/nodeviewundo.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
FootagePropertiesDialog::FootagePropertiesDialog(QWidget *parent, Footage *footage) :
|
||||
QDialog(parent),
|
||||
footage_(footage)
|
||||
{
|
||||
QGridLayout* layout = new QGridLayout(this);
|
||||
|
||||
setWindowTitle(tr("\"%1\" Properties").arg(footage_->GetLabelOrName()));
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
|
||||
int row = 0;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Name:")), row, 0);
|
||||
|
||||
footage_name_field_ = new QLineEdit(footage_->GetLabel());
|
||||
layout->addWidget(footage_name_field_, row, 1);
|
||||
row++;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Tracks:")), row, 0, 1, 2);
|
||||
row++;
|
||||
|
||||
track_list = new QListWidget();
|
||||
layout->addWidget(track_list, row, 0, 1, 2);
|
||||
|
||||
row++;
|
||||
|
||||
stacked_widget_ = new QStackedWidget();
|
||||
layout->addWidget(stacked_widget_, row, 0, 1, 2);
|
||||
|
||||
int first_usable_stream = -1;
|
||||
|
||||
for (int i=0; i<footage_->GetTotalStreamCount(); i++) {
|
||||
Track::Reference reference = footage_->GetReferenceFromRealIndex(i);
|
||||
|
||||
QString description;
|
||||
bool is_enabled = false;
|
||||
|
||||
switch (reference.type()) {
|
||||
case Track::kVideo:
|
||||
{
|
||||
stacked_widget_->addWidget(new VideoStreamProperties(footage_, reference.index()));
|
||||
|
||||
VideoParams vp = footage_->GetVideoParams(reference.index());
|
||||
is_enabled = vp.enabled();
|
||||
description = tr("%1x%2 %3 FPS").arg(QString::number(vp.width()), QString::number(vp.height()), QString::number(vp.frame_rate().toDouble()));
|
||||
break;
|
||||
}
|
||||
case Track::kAudio:
|
||||
{
|
||||
stacked_widget_->addWidget(new AudioStreamProperties(footage_, reference.index()));
|
||||
|
||||
AudioParams ap = footage_->GetAudioParams(reference.index());
|
||||
is_enabled = ap.enabled();
|
||||
description = tr("%1 Hz %2 channels").arg(QString::number(ap.sample_rate()), QString::number(ap.channel_count()));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
stacked_widget_->addWidget(new StreamProperties());
|
||||
description = tr("Unknown");
|
||||
break;
|
||||
}
|
||||
|
||||
QListWidgetItem* item = new QListWidgetItem(description, track_list);
|
||||
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||
item->setCheckState(is_enabled ? Qt::Checked : Qt::Unchecked);
|
||||
track_list->addItem(item);
|
||||
|
||||
if (first_usable_stream == -1
|
||||
&& (reference.type() == Track::kVideo
|
||||
|| reference.type() == Track::kAudio)) {
|
||||
first_usable_stream = i;
|
||||
}
|
||||
}
|
||||
|
||||
row++;
|
||||
|
||||
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
buttons->setCenterButtons(true);
|
||||
layout->addWidget(buttons, row, 0, 1, 2);
|
||||
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
||||
connect(track_list, &QListWidget::currentRowChanged, stacked_widget_, &QStackedWidget::setCurrentIndex);
|
||||
|
||||
// Auto-select first item that actually has properties
|
||||
if (first_usable_stream >= 0) {
|
||||
track_list->setCurrentRow(first_usable_stream);
|
||||
}
|
||||
track_list->setFocus();
|
||||
}
|
||||
|
||||
void FootagePropertiesDialog::accept()
|
||||
{
|
||||
// Perform sanity check on all pages
|
||||
for (int i=0;i<stacked_widget_->count();i++) {
|
||||
if (!static_cast<StreamProperties*>(stacked_widget_->widget(i))->SanityCheck()) {
|
||||
// Switch to the failed panel in question
|
||||
stacked_widget_->setCurrentIndex(i);
|
||||
|
||||
// Do nothing (it's up to the property panel itself to throw the error message)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
MultiUndoCommand* command = new MultiUndoCommand();
|
||||
|
||||
if (footage_->GetLabel() != footage_name_field_->text()) {
|
||||
NodeRenameCommand *nrc = new NodeRenameCommand();
|
||||
nrc->AddNode(footage_, footage_name_field_->text());
|
||||
command->add_child(nrc);
|
||||
}
|
||||
|
||||
for (int i=0; i<footage_->GetTotalStreamCount(); i++) {
|
||||
Track::Reference reference = footage_->GetReferenceFromRealIndex(i);
|
||||
bool new_stream_enabled = (track_list->item(i)->checkState() == Qt::Checked);
|
||||
bool old_stream_enabled = new_stream_enabled;
|
||||
|
||||
switch (reference.type()) {
|
||||
case Track::kVideo:
|
||||
old_stream_enabled = footage_->GetVideoParams(reference.index()).enabled();
|
||||
break;
|
||||
case Track::kAudio:
|
||||
old_stream_enabled = footage_->GetAudioParams(reference.index()).enabled();
|
||||
break;
|
||||
case Track::kSubtitle:
|
||||
case Track::kNone:
|
||||
case Track::kCount:
|
||||
break;
|
||||
}
|
||||
|
||||
if (old_stream_enabled != new_stream_enabled) {
|
||||
command->add_child(new StreamEnableChangeCommand(footage_,
|
||||
reference.type(),
|
||||
reference.index(),
|
||||
new_stream_enabled));
|
||||
}
|
||||
}
|
||||
|
||||
for (int i=0;i<stacked_widget_->count();i++) {
|
||||
static_cast<StreamProperties*>(stacked_widget_->widget(i))->Accept(command);
|
||||
}
|
||||
|
||||
Core::instance()->undo_stack()->pushIfHasChildren(command);
|
||||
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
FootagePropertiesDialog::StreamEnableChangeCommand::StreamEnableChangeCommand(Footage *footage, Track::Type type, int index_in_type, bool enabled) :
|
||||
footage_(footage),
|
||||
type_(type),
|
||||
index_(index_in_type),
|
||||
new_enabled_(enabled)
|
||||
{
|
||||
}
|
||||
|
||||
Project *FootagePropertiesDialog::StreamEnableChangeCommand::GetRelevantProject() const
|
||||
{
|
||||
return footage_->project();
|
||||
}
|
||||
|
||||
void FootagePropertiesDialog::StreamEnableChangeCommand::redo()
|
||||
{
|
||||
switch (type_) {
|
||||
case Track::kVideo:
|
||||
{
|
||||
VideoParams vp = footage_->GetVideoParams(index_);
|
||||
old_enabled_ = vp.enabled();
|
||||
vp.set_enabled(new_enabled_);
|
||||
footage_->SetVideoParams(vp, index_);
|
||||
break;
|
||||
}
|
||||
case Track::kAudio:
|
||||
{
|
||||
AudioParams ap = footage_->GetAudioParams(index_);
|
||||
old_enabled_ = ap.enabled();
|
||||
ap.set_enabled(new_enabled_);
|
||||
footage_->SetAudioParams(ap, index_);
|
||||
break;
|
||||
}
|
||||
case Track::kSubtitle:
|
||||
case Track::kNone:
|
||||
case Track::kCount:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void FootagePropertiesDialog::StreamEnableChangeCommand::undo()
|
||||
{
|
||||
switch (type_) {
|
||||
case Track::kVideo:
|
||||
{
|
||||
VideoParams vp = footage_->GetVideoParams(index_);
|
||||
vp.set_enabled(old_enabled_);
|
||||
footage_->SetVideoParams(vp, index_);
|
||||
break;
|
||||
}
|
||||
case Track::kAudio:
|
||||
{
|
||||
AudioParams ap = footage_->GetAudioParams(index_);
|
||||
ap.set_enabled(old_enabled_);
|
||||
footage_->SetAudioParams(ap, index_);
|
||||
break;
|
||||
}
|
||||
case Track::kSubtitle:
|
||||
case Track::kNone:
|
||||
case Track::kCount:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2020 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 MEDIAPROPERTIESDIALOG_H
|
||||
#define MEDIAPROPERTIESDIALOG_H
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QDialog>
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QLineEdit>
|
||||
#include <QListWidget>
|
||||
#include <QStackedWidget>
|
||||
|
||||
#include "node/project/footage/footage.h"
|
||||
#include "undo/undocommand.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
/**
|
||||
* @brief The MediaPropertiesDialog class
|
||||
*
|
||||
* A dialog for setting properties on Media. This can be loaded from any part of the application provided it's given
|
||||
* a valid Media object.
|
||||
*/
|
||||
class FootagePropertiesDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
/**
|
||||
* @brief MediaPropertiesDialog Constructor
|
||||
*
|
||||
* @param parent
|
||||
*
|
||||
* QWidget parent. Usually MainWindow or Project panel.
|
||||
*
|
||||
* @param i
|
||||
*
|
||||
* Media object to set properties for.
|
||||
*/
|
||||
FootagePropertiesDialog(QWidget *parent, Footage* footage);
|
||||
private:
|
||||
class StreamEnableChangeCommand : public UndoCommand {
|
||||
public:
|
||||
StreamEnableChangeCommand(Footage *footage,
|
||||
Track::Type type,
|
||||
int index_in_type,
|
||||
bool enabled);
|
||||
|
||||
virtual Project* GetRelevantProject() const override;
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
Footage *footage_;
|
||||
Track::Type type_;
|
||||
int index_;
|
||||
|
||||
bool old_enabled_;
|
||||
bool new_enabled_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Stack of widgets that changes based on whether the stream is a video or audio stream
|
||||
*/
|
||||
QStackedWidget* stacked_widget_;
|
||||
|
||||
/**
|
||||
* @brief ComboBox for interlacing setting
|
||||
*/
|
||||
QComboBox* interlacing_box;
|
||||
|
||||
/**
|
||||
* @brief Media name text field
|
||||
*/
|
||||
QLineEdit* footage_name_field_;
|
||||
|
||||
/**
|
||||
* @brief Internal pointer to Media object (set in constructor)
|
||||
*/
|
||||
Footage* footage_;
|
||||
|
||||
/**
|
||||
* @brief A list widget for listing the tracks in Media
|
||||
*/
|
||||
QListWidget* track_list;
|
||||
|
||||
/**
|
||||
* @brief Frame rate to conform to
|
||||
*/
|
||||
QDoubleSpinBox* conform_fr;
|
||||
|
||||
private slots:
|
||||
/**
|
||||
* @brief Overridden accept function for saving the properties back to the Media class
|
||||
*/
|
||||
void accept();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // MEDIAPROPERTIESDIALOG_H
|
||||
@@ -0,0 +1,26 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2020 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}
|
||||
dialog/footageproperties/streamproperties/streamproperties.h
|
||||
dialog/footageproperties/streamproperties/streamproperties.cpp
|
||||
dialog/footageproperties/streamproperties/audiostreamproperties.h
|
||||
dialog/footageproperties/streamproperties/audiostreamproperties.cpp
|
||||
dialog/footageproperties/streamproperties/videostreamproperties.h
|
||||
dialog/footageproperties/streamproperties/videostreamproperties.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,37 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2020 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 "audiostreamproperties.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
AudioStreamProperties::AudioStreamProperties(Footage *footage, int audio_index) :
|
||||
footage_(footage),
|
||||
audio_index_(audio_index)
|
||||
{
|
||||
}
|
||||
|
||||
void AudioStreamProperties::Accept(MultiUndoCommand*)
|
||||
{
|
||||
Q_UNUSED(footage_)
|
||||
Q_UNUSED(audio_index_)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2020 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 AUDIOSTREAMPROPERTIES_H
|
||||
#define AUDIOSTREAMPROPERTIES_H
|
||||
|
||||
#include "node/project/footage/footage.h"
|
||||
#include "streamproperties.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class AudioStreamProperties : public StreamProperties
|
||||
{
|
||||
public:
|
||||
AudioStreamProperties(Footage *footage, int audio_index);
|
||||
|
||||
virtual void Accept(MultiUndoCommand* parent) override;
|
||||
|
||||
private:
|
||||
Footage *footage_;
|
||||
|
||||
int audio_index_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // AUDIOSTREAMPROPERTIES_H
|
||||
@@ -0,0 +1,30 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2020 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 "streamproperties.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
StreamProperties::StreamProperties(QWidget *parent) :
|
||||
QWidget(parent)
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2020 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 STREAMPROPERTIES_H
|
||||
#define STREAMPROPERTIES_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include "common/define.h"
|
||||
#include "undo/undocommand.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class StreamProperties : public QWidget
|
||||
{
|
||||
public:
|
||||
StreamProperties(QWidget* parent = nullptr);
|
||||
|
||||
virtual void Accept(MultiUndoCommand*){}
|
||||
|
||||
virtual bool SanityCheck(){return true;}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // STREAMPROPERTIES_H
|
||||
@@ -0,0 +1,271 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2020 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 "videostreamproperties.h"
|
||||
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QInputDialog>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "common/ocioutils.h"
|
||||
#include "core.h"
|
||||
#include "undo/undostack.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
VideoStreamProperties::VideoStreamProperties(Footage *footage, int video_index) :
|
||||
footage_(footage),
|
||||
video_index_(video_index),
|
||||
video_premultiply_alpha_(nullptr)
|
||||
{
|
||||
QGridLayout* video_layout = new QGridLayout(this);
|
||||
video_layout->setMargin(0);
|
||||
|
||||
int row = 0;
|
||||
|
||||
video_layout->addWidget(new QLabel(tr("Pixel Aspect:")), row, 0);
|
||||
|
||||
VideoParams vp = footage_->GetVideoParams(video_index_);
|
||||
|
||||
pixel_aspect_combo_ = new PixelAspectRatioComboBox();
|
||||
pixel_aspect_combo_->SetPixelAspectRatio(vp.pixel_aspect_ratio());
|
||||
video_layout->addWidget(pixel_aspect_combo_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
video_layout->addWidget(new QLabel(tr("Interlacing:")), row, 0);
|
||||
|
||||
video_interlace_combo_ = new InterlacedComboBox();
|
||||
video_interlace_combo_->SetInterlaceMode(vp.interlacing());
|
||||
|
||||
video_layout->addWidget(video_interlace_combo_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
video_layout->addWidget(new QLabel(tr("Color Space:")), row, 0);
|
||||
|
||||
video_color_space_ = new QComboBox();
|
||||
OCIO::ConstConfigRcPtr config = footage_->project()->color_manager()->GetConfig();
|
||||
int number_of_colorspaces = config->getNumColorSpaces();
|
||||
|
||||
video_color_space_->addItem(tr("Default (%1)").arg(footage_->project()->color_manager()->GetDefaultInputColorSpace()));
|
||||
|
||||
for (int i=0;i<number_of_colorspaces;i++) {
|
||||
QString colorspace = config->getColorSpaceNameByIndex(i);
|
||||
|
||||
video_color_space_->addItem(colorspace);
|
||||
}
|
||||
|
||||
video_color_space_->setCurrentText(vp.colorspace());
|
||||
|
||||
video_layout->addWidget(video_color_space_, row, 1);
|
||||
|
||||
if (vp.channel_count() == VideoParams::kRGBAChannelCount) {
|
||||
row++;
|
||||
|
||||
video_premultiply_alpha_ = new QCheckBox(tr("Premultiplied Alpha"));
|
||||
video_premultiply_alpha_->setChecked(vp.premultiplied_alpha());
|
||||
video_layout->addWidget(video_premultiply_alpha_, row, 0, 1, 2);
|
||||
}
|
||||
|
||||
row++;
|
||||
|
||||
if (vp.video_type() == VideoParams::kVideoTypeImageSequence) {
|
||||
QGroupBox* imgseq_group = new QGroupBox(tr("Image Sequence"));
|
||||
QGridLayout* imgseq_layout = new QGridLayout(imgseq_group);
|
||||
|
||||
int imgseq_row = 0;
|
||||
|
||||
imgseq_layout->addWidget(new QLabel(tr("Start Index:")), imgseq_row, 0);
|
||||
|
||||
imgseq_start_time_ = new IntegerSlider();
|
||||
imgseq_start_time_->SetMinimum(0);
|
||||
imgseq_start_time_->SetValue(vp.start_time());
|
||||
imgseq_layout->addWidget(imgseq_start_time_, imgseq_row, 1);
|
||||
|
||||
imgseq_row++;
|
||||
|
||||
imgseq_layout->addWidget(new QLabel(tr("End Index:")), imgseq_row, 0);
|
||||
|
||||
imgseq_end_time_ = new IntegerSlider();
|
||||
imgseq_end_time_->SetMinimum(0);
|
||||
imgseq_end_time_->SetValue(vp.start_time() + vp.duration() - 1);
|
||||
imgseq_layout->addWidget(imgseq_end_time_, imgseq_row, 1);
|
||||
|
||||
imgseq_row++;
|
||||
|
||||
imgseq_layout->addWidget(new QLabel(tr("Frame Rate:")), imgseq_row, 0);
|
||||
|
||||
imgseq_frame_rate_ = new FrameRateComboBox();
|
||||
imgseq_frame_rate_->SetFrameRate(vp.frame_rate());
|
||||
imgseq_layout->addWidget(imgseq_frame_rate_, imgseq_row, 1);
|
||||
|
||||
video_layout->addWidget(imgseq_group, row, 0, 1, 2);
|
||||
}
|
||||
}
|
||||
|
||||
void VideoStreamProperties::Accept(MultiUndoCommand *parent)
|
||||
{
|
||||
QString set_colorspace;
|
||||
|
||||
if (video_color_space_->currentIndex() > 0) {
|
||||
set_colorspace = video_color_space_->currentText();
|
||||
}
|
||||
|
||||
VideoParams vp = footage_->GetVideoParams(video_index_);
|
||||
|
||||
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()) {
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
if (vp.video_type() == VideoParams::kVideoTypeImageSequence) {
|
||||
int64_t new_dur = imgseq_end_time_->GetValue() - imgseq_start_time_->GetValue() + 1;
|
||||
|
||||
if (vp.start_time() != imgseq_start_time_->GetValue()
|
||||
|| vp.duration() != new_dur
|
||||
|| vp.frame_rate() != imgseq_frame_rate_->GetFrameRate()) {
|
||||
parent->add_child(new ImageSequenceChangeCommand(footage_,
|
||||
video_index_,
|
||||
imgseq_start_time_->GetValue(),
|
||||
new_dur,
|
||||
imgseq_frame_rate_->GetFrameRate()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool VideoStreamProperties::SanityCheck()
|
||||
{
|
||||
if (footage_->GetVideoParams(video_index_).video_type() == VideoParams::kVideoTypeImageSequence) {
|
||||
if (imgseq_start_time_->GetValue() >= imgseq_end_time_->GetValue()) {
|
||||
QMessageBox::critical(this,
|
||||
tr("Invalid Configuration"),
|
||||
tr("Image sequence end index must be a value higher than the start index."),
|
||||
QMessageBox::Ok);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
VideoStreamProperties::VideoStreamChangeCommand::VideoStreamChangeCommand(Footage *footage,
|
||||
int video_index,
|
||||
bool premultiplied,
|
||||
QString colorspace,
|
||||
VideoParams::Interlacing interlacing,
|
||||
const rational &pixel_ar) :
|
||||
footage_(footage),
|
||||
video_index_(video_index),
|
||||
new_premultiplied_(premultiplied),
|
||||
new_colorspace_(colorspace),
|
||||
new_interlacing_(interlacing),
|
||||
new_pixel_ar_(pixel_ar)
|
||||
{
|
||||
}
|
||||
|
||||
Project *VideoStreamProperties::VideoStreamChangeCommand::GetRelevantProject() const
|
||||
{
|
||||
return footage_->project();
|
||||
}
|
||||
|
||||
void VideoStreamProperties::VideoStreamChangeCommand::redo()
|
||||
{
|
||||
VideoParams vp = footage_->GetVideoParams(video_index_);
|
||||
|
||||
old_premultiplied_ = vp.premultiplied_alpha();
|
||||
old_colorspace_ = vp.colorspace();
|
||||
old_interlacing_ = vp.interlacing();
|
||||
old_pixel_ar_ = vp.pixel_aspect_ratio();
|
||||
|
||||
vp.set_premultiplied_alpha(new_premultiplied_);
|
||||
vp.set_colorspace(new_colorspace_);
|
||||
vp.set_interlacing(new_interlacing_);
|
||||
vp.set_pixel_aspect_ratio(new_pixel_ar_);
|
||||
|
||||
footage_->SetVideoParams(vp, video_index_);
|
||||
}
|
||||
|
||||
void VideoStreamProperties::VideoStreamChangeCommand::undo()
|
||||
{
|
||||
VideoParams vp = footage_->GetVideoParams(video_index_);
|
||||
|
||||
vp.set_premultiplied_alpha(old_premultiplied_);
|
||||
vp.set_colorspace(old_colorspace_);
|
||||
vp.set_interlacing(old_interlacing_);
|
||||
vp.set_pixel_aspect_ratio(old_pixel_ar_);
|
||||
|
||||
footage_->SetVideoParams(vp, video_index_);
|
||||
}
|
||||
|
||||
VideoStreamProperties::ImageSequenceChangeCommand::ImageSequenceChangeCommand(Footage *footage, int video_index, int64_t start_index, int64_t duration, const rational &frame_rate) :
|
||||
footage_(footage),
|
||||
video_index_(video_index),
|
||||
new_start_index_(start_index),
|
||||
new_duration_(duration),
|
||||
new_frame_rate_(frame_rate)
|
||||
{
|
||||
}
|
||||
|
||||
Project *VideoStreamProperties::ImageSequenceChangeCommand::GetRelevantProject() const
|
||||
{
|
||||
return footage_->project();
|
||||
}
|
||||
|
||||
void VideoStreamProperties::ImageSequenceChangeCommand::redo()
|
||||
{
|
||||
VideoParams vp = footage_->GetVideoParams(video_index_);
|
||||
|
||||
old_start_index_ = vp.start_time();
|
||||
vp.set_start_time(new_start_index_);
|
||||
|
||||
old_duration_ = vp.duration();
|
||||
vp.set_duration(new_duration_);
|
||||
|
||||
old_frame_rate_ = vp.frame_rate();
|
||||
vp.set_frame_rate(new_frame_rate_);
|
||||
vp.set_time_base(new_frame_rate_.flipped());
|
||||
|
||||
footage_->SetVideoParams(vp, video_index_);
|
||||
}
|
||||
|
||||
void VideoStreamProperties::ImageSequenceChangeCommand::undo()
|
||||
{
|
||||
VideoParams vp = footage_->GetVideoParams(video_index_);
|
||||
|
||||
vp.set_start_time(old_start_index_);
|
||||
vp.set_duration(old_duration_);
|
||||
vp.set_frame_rate(old_frame_rate_);
|
||||
vp.set_time_base(old_frame_rate_.flipped());
|
||||
|
||||
footage_->SetVideoParams(vp, video_index_);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2020 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 VIDEOSTREAMPROPERTIES_H
|
||||
#define VIDEOSTREAMPROPERTIES_H
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
|
||||
#include "node/project/footage/footage.h"
|
||||
#include "streamproperties.h"
|
||||
#include "widget/slider/integerslider.h"
|
||||
#include "widget/standardcombos/standardcombos.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class VideoStreamProperties : public StreamProperties
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
VideoStreamProperties(Footage *footage, int video_index);
|
||||
|
||||
virtual void Accept(MultiUndoCommand *parent) override;
|
||||
|
||||
virtual bool SanityCheck() override;
|
||||
|
||||
private:
|
||||
Footage *footage_;
|
||||
|
||||
int video_index_;
|
||||
|
||||
/**
|
||||
* @brief Setting for associated/premultiplied alpha
|
||||
*/
|
||||
QCheckBox* video_premultiply_alpha_;
|
||||
|
||||
/**
|
||||
* @brief Setting for this media's color space
|
||||
*/
|
||||
QComboBox* video_color_space_;
|
||||
|
||||
/**
|
||||
* @brief Setting for video interlacing
|
||||
*/
|
||||
InterlacedComboBox* video_interlace_combo_;
|
||||
|
||||
/**
|
||||
* @brief Sets the start index for image sequences
|
||||
*/
|
||||
IntegerSlider* imgseq_start_time_;
|
||||
|
||||
/**
|
||||
* @brief Sets the end index for image sequences
|
||||
*/
|
||||
IntegerSlider* imgseq_end_time_;
|
||||
|
||||
/**
|
||||
* @brief Sets the frame rate for image sequences
|
||||
*/
|
||||
FrameRateComboBox* imgseq_frame_rate_;
|
||||
|
||||
/**
|
||||
* @brief Sets the pixel aspect ratio of the stream
|
||||
*/
|
||||
PixelAspectRatioComboBox* pixel_aspect_combo_;
|
||||
|
||||
class VideoStreamChangeCommand : public UndoCommand {
|
||||
public:
|
||||
VideoStreamChangeCommand(Footage *footage,
|
||||
int video_index,
|
||||
bool premultiplied,
|
||||
QString colorspace,
|
||||
VideoParams::Interlacing interlacing,
|
||||
const rational& pixel_ar);
|
||||
|
||||
virtual Project* GetRelevantProject() const override;
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
Footage *footage_;
|
||||
int video_index_;
|
||||
|
||||
bool new_premultiplied_;
|
||||
QString new_colorspace_;
|
||||
VideoParams::Interlacing new_interlacing_;
|
||||
rational new_pixel_ar_;
|
||||
|
||||
bool old_premultiplied_;
|
||||
QString old_colorspace_;
|
||||
VideoParams::Interlacing old_interlacing_;
|
||||
rational old_pixel_ar_;
|
||||
|
||||
};
|
||||
|
||||
class ImageSequenceChangeCommand : public UndoCommand {
|
||||
public:
|
||||
ImageSequenceChangeCommand(Footage *footage,
|
||||
int video_index,
|
||||
int64_t start_index,
|
||||
int64_t duration,
|
||||
const rational& frame_rate);
|
||||
|
||||
virtual Project* GetRelevantProject() const override;
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
Footage *footage_;
|
||||
int video_index_;
|
||||
|
||||
int64_t new_start_index_;
|
||||
int64_t old_start_index_;
|
||||
|
||||
int64_t new_duration_;
|
||||
int64_t old_duration_;
|
||||
|
||||
rational new_frame_rate_;
|
||||
rational old_frame_rate_;
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // VIDEOSTREAMPROPERTIES_H
|
||||
@@ -1,74 +0,0 @@
|
||||
/***
|
||||
|
||||
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 "nodepropertiesdialog.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QHBoxLayout>
|
||||
|
||||
#include "core.h"
|
||||
#include "widget/nodeview/nodeviewundo.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
NodePropertiesDialog::NodePropertiesDialog(Node *node, const rational &timebase, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
node_(node)
|
||||
{
|
||||
setWindowTitle(tr("Node Properties"));
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
|
||||
QHBoxLayout *label_layout = new QHBoxLayout();
|
||||
label_layout->setMargin(0);
|
||||
layout->addLayout(label_layout);
|
||||
|
||||
label_layout->addWidget(new QLabel(tr("Name:")));
|
||||
|
||||
label_edit_ = new QLineEdit();
|
||||
label_edit_->setText(node->GetLabel());
|
||||
label_layout->addWidget(label_edit_);
|
||||
|
||||
NodeParamViewItem *item = new NodeParamViewItem(node);
|
||||
item->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||
item->SetTimebase(timebase);
|
||||
item->setTitleBarWidget(new QWidget());
|
||||
layout->addWidget(item);
|
||||
|
||||
layout->addStretch();
|
||||
|
||||
QDialogButtonBox *btns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
connect(btns, &QDialogButtonBox::accepted, this, &NodePropertiesDialog::accept);
|
||||
connect(btns, &QDialogButtonBox::rejected, this, &NodePropertiesDialog::reject);
|
||||
layout->addWidget(btns);
|
||||
}
|
||||
|
||||
void NodePropertiesDialog::accept()
|
||||
{
|
||||
if (label_edit_->text() != node_->GetLabel()) {
|
||||
NodeRenameCommand* rename_command = new NodeRenameCommand();
|
||||
rename_command->AddNode(node_, label_edit_->text());
|
||||
Core::instance()->undo_stack()->push(rename_command);
|
||||
}
|
||||
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2019 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}
|
||||
dialog/otioproperties/otiopropertiesdialog.h
|
||||
dialog/otioproperties/otiopropertiesdialog.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,89 @@
|
||||
/***
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 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 "otiopropertiesdialog.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFileDialog>
|
||||
#include <QFileInfo>
|
||||
#include <QHeaderView>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "core.h"
|
||||
#include "dialog/sequence/sequence.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
OTIOPropertiesDialog::OTIOPropertiesDialog(const QList<Sequence*>& sequences, Project* active_project, QWidget* parent)
|
||||
:
|
||||
QDialog(parent),
|
||||
sequences_(sequences)
|
||||
{
|
||||
QVBoxLayout* layout = new QVBoxLayout(this);
|
||||
|
||||
QLabel *msg = new QLabel(tr("OpenTimelineIO files do not store sequence parameters (resolution, frame rate, etc.)\n\n"
|
||||
"Please set the correct parameters on the sequences below (they have been set to your default sequence parameters as a starting point)."));
|
||||
msg->setWordWrap(true);
|
||||
layout->addWidget(msg);
|
||||
|
||||
table_ = new QTreeWidget();
|
||||
table_->setColumnCount(2);
|
||||
table_->setHeaderLabels({tr("Sequence"), tr("Actions")});
|
||||
table_->setRootIsDecorated(false);
|
||||
|
||||
for (int i = 0; i < sequences.size(); i++) {
|
||||
QTreeWidgetItem* item = new QTreeWidgetItem();
|
||||
Sequence* s = sequences.at(i);
|
||||
|
||||
QWidget* item_actions = new QWidget();
|
||||
QHBoxLayout* item_actions_layout = new QHBoxLayout(item_actions);
|
||||
QPushButton* item_settings_btn = new QPushButton(tr("Settings"));
|
||||
item_settings_btn->setProperty("index", i);
|
||||
connect(item_settings_btn, &QPushButton::clicked, this, &OTIOPropertiesDialog::SetupSequence);
|
||||
item_actions_layout->addWidget(item_settings_btn);
|
||||
|
||||
item->setText(0, s->GetLabel());
|
||||
|
||||
table_->addTopLevelItem(item);
|
||||
|
||||
table_->setItemWidget(item, 1, item_actions);
|
||||
}
|
||||
|
||||
// Stretch first column to take up as much space as possible, and second column to take as little
|
||||
table_->header()->setSectionResizeMode(0, QHeaderView::Stretch);
|
||||
table_->header()->setSectionResizeMode(1, QHeaderView::Fixed);
|
||||
table_->header()->setStretchLastSection(false);
|
||||
|
||||
layout->addWidget(table_);
|
||||
|
||||
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &OTIOPropertiesDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &OTIOPropertiesDialog::reject);
|
||||
layout->addWidget(buttons);
|
||||
|
||||
setWindowTitle(tr("Load OpenTimelineIO Project"));
|
||||
}
|
||||
|
||||
void OTIOPropertiesDialog::SetupSequence() {
|
||||
int index = sender()->property("index").toInt();
|
||||
Sequence* s = sequences_.at(index);
|
||||
SequenceDialog sd(s, SequenceDialog::kNew);
|
||||
sd.SetUndoable(false);
|
||||
sd.exec();
|
||||
}
|
||||
|
||||
} // namespace olive
|
||||
@@ -0,0 +1,39 @@
|
||||
|
||||
#ifndef OTIOPROPERTIESDIALOG_H
|
||||
#define OTIOPROPERTIESDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QTreeWidget>
|
||||
|
||||
#include "common/define.h"
|
||||
#include "opentimelineio/timeline.h"
|
||||
#include "node/project/sequence/sequence.h"
|
||||
#include "node/project/project.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
/**
|
||||
* @brief Dialog to load setting for OTIO sequences.
|
||||
*
|
||||
* Takes a list of Sequences and allows the setting of options for each.
|
||||
*/
|
||||
class OTIOPropertiesDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
OTIOPropertiesDialog(const QList<Sequence*>& sequences, Project* active_project, QWidget* parent = nullptr);
|
||||
|
||||
private:
|
||||
QTreeWidget* table_;
|
||||
|
||||
const QList<Sequence*> sequences_;
|
||||
|
||||
private slots:
|
||||
/**
|
||||
* @brief Brings up the Sequence settings dialog.
|
||||
*/
|
||||
void SetupSequence();
|
||||
};
|
||||
|
||||
} //namespace olive
|
||||
|
||||
#endif // OTIOPROPERTIESDIALOG_H
|
||||
@@ -33,12 +33,7 @@ PreferencesAudioTab::PreferencesAudioTab()
|
||||
{
|
||||
QVBoxLayout* audio_tab_layout = new QVBoxLayout(this);
|
||||
|
||||
QLabel *wip_lbl = new QLabel(tr("We just ported our audio backend to PortAudio so this section will need to be redone. Come back later..."));
|
||||
wip_lbl->setWordWrap(true);
|
||||
wip_lbl->setAlignment(Qt::AlignCenter);
|
||||
audio_tab_layout->addWidget(wip_lbl);
|
||||
|
||||
/*{
|
||||
{
|
||||
// Backend Layout
|
||||
QGridLayout* main_layout = new QGridLayout();
|
||||
main_layout->setMargin(0);
|
||||
@@ -48,195 +43,166 @@ PreferencesAudioTab::PreferencesAudioTab()
|
||||
main_layout->addWidget(new QLabel(tr("Backend:")), row, 0);
|
||||
|
||||
audio_backend_combobox_ = new QComboBox();
|
||||
for (int i=0; i<AudioManager::kAudioBackendCount; i++) {
|
||||
audio_backend_combobox_->addItem(AudioManager::GetAudioBackendName(static_cast<AudioManager::Backend>(i)));
|
||||
}
|
||||
connect(audio_backend_combobox_, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &PreferencesAudioTab::RefreshDevices);
|
||||
main_layout->addWidget(audio_backend_combobox_, row, 1);
|
||||
|
||||
audio_tab_layout->addLayout(main_layout);
|
||||
}
|
||||
|
||||
{
|
||||
// Qt-Backend Layout
|
||||
QGroupBox* qt_groupbox = new QGroupBox();
|
||||
audio_tab_layout->addWidget(qt_groupbox);
|
||||
QGroupBox* groupbox = new QGroupBox();
|
||||
audio_tab_layout->addWidget(groupbox);
|
||||
|
||||
QVBoxLayout* qt_layout = new QVBoxLayout(qt_groupbox);
|
||||
QVBoxLayout* layout = new QVBoxLayout(groupbox);
|
||||
|
||||
int row = 0;
|
||||
|
||||
{
|
||||
// Output Group
|
||||
QGroupBox* qt_output_group = new QGroupBox();
|
||||
qt_output_group->setTitle(tr("Output"));
|
||||
qt_layout->addWidget(qt_output_group);
|
||||
QGroupBox* output_group = new QGroupBox();
|
||||
output_group->setTitle(tr("Output"));
|
||||
layout->addWidget(output_group);
|
||||
|
||||
QGridLayout* qt_output_layout = new QGridLayout(qt_output_group);
|
||||
QGridLayout* output_layout = new QGridLayout(output_group);
|
||||
|
||||
qt_output_layout->addWidget(new QLabel(tr("Device:")), row, 0);
|
||||
output_layout->addWidget(new QLabel(tr("Device:")), row, 0);
|
||||
|
||||
audio_output_devices_ = new QComboBox();
|
||||
qt_output_layout->addWidget(audio_output_devices_, row, 1);
|
||||
output_layout->addWidget(audio_output_devices_, row, 1);
|
||||
}
|
||||
|
||||
row = 0;
|
||||
|
||||
{
|
||||
QGroupBox* qt_input_group = new QGroupBox();
|
||||
qt_input_group->setTitle(tr("Input"));
|
||||
qt_layout->addWidget(qt_input_group);
|
||||
QGroupBox* input_group = new QGroupBox();
|
||||
input_group->setTitle(tr("Input"));
|
||||
layout->addWidget(input_group);
|
||||
|
||||
QGridLayout* qt_input_layout = new QGridLayout(qt_input_group);
|
||||
QGridLayout* input_layout = new QGridLayout(input_group);
|
||||
|
||||
qt_input_layout->addWidget(new QLabel(tr("Device:")), row, 0);
|
||||
input_layout->addWidget(new QLabel(tr("Device:")), row, 0);
|
||||
|
||||
audio_input_devices_ = new QComboBox();
|
||||
qt_input_layout->addWidget(audio_input_devices_, row, 1);
|
||||
input_layout->addWidget(audio_input_devices_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
qt_input_layout->addWidget(new QLabel(tr("Recording Mode:"), this), row, 0);
|
||||
input_layout->addWidget(new QLabel(tr("Recording Mode:"), this), row, 0);
|
||||
|
||||
recording_combobox_ = new QComboBox();
|
||||
recording_combobox_->addItem(tr("Mono"));
|
||||
recording_combobox_->addItem(tr("Stereo"));
|
||||
qt_input_layout->addWidget(recording_combobox_, row, 1);
|
||||
input_layout->addWidget(recording_combobox_, row, 1);
|
||||
}
|
||||
|
||||
QHBoxLayout* qt_refresh_layout = new QHBoxLayout();
|
||||
qt_layout->addLayout(qt_refresh_layout);
|
||||
qt_refresh_layout->addStretch();
|
||||
QHBoxLayout* refresh_layout = new QHBoxLayout();
|
||||
layout->addLayout(refresh_layout);
|
||||
refresh_layout->addStretch();
|
||||
|
||||
refresh_devices_btn_ = new QPushButton(tr("Refresh Devices"));
|
||||
qt_refresh_layout->addWidget(refresh_devices_btn_);
|
||||
refresh_layout->addWidget(refresh_devices_btn_);
|
||||
|
||||
RetrieveDeviceLists();
|
||||
|
||||
connect(refresh_devices_btn_, &QPushButton::clicked, this, &PreferencesAudioTab::RefreshDevices);
|
||||
connect(AudioManager::instance(), &AudioManager::OutputListReady, this, &PreferencesAudioTab::RetrieveOutputList);
|
||||
connect(AudioManager::instance(), &AudioManager::InputListReady, this, &PreferencesAudioTab::RetrieveInputList);
|
||||
connect(refresh_devices_btn_, &QPushButton::clicked, this, &PreferencesAudioTab::HardRefreshBackends);
|
||||
}
|
||||
|
||||
audio_tab_layout->addStretch();*/
|
||||
audio_tab_layout->addStretch();
|
||||
|
||||
// Populate lists
|
||||
RefreshBackends();
|
||||
}
|
||||
|
||||
void PreferencesAudioTab::Accept(MultiUndoCommand *command)
|
||||
{
|
||||
/*
|
||||
Q_UNUSED(command)
|
||||
|
||||
// FIXME: Qt documentation states that QAudioDeviceInfo::deviceName() is a "unique identifiers", which would make them
|
||||
// ideal for saving in preferences, but in practice they don't actually appear to be unique.
|
||||
// See: https://bugreports.qt.io/browse/QTBUG-16841
|
||||
// Get device indexes
|
||||
PaDeviceIndex output_device = audio_output_devices_->currentData().value<PaDeviceIndex>();
|
||||
PaDeviceIndex input_device = audio_input_devices_->currentData().value<PaDeviceIndex>();
|
||||
|
||||
// If we don't have the device list, we can't set it
|
||||
if (audio_output_devices_->isEnabled()) {
|
||||
// Get device info
|
||||
QAudioDeviceInfo selected_output;
|
||||
QString selected_output_name;
|
||||
// Get device names, which seem to be the closest thing we have to a "unique identifier" for them
|
||||
Config::Current()[QStringLiteral("AudioOutput")] = audio_output_devices_->currentText();
|
||||
Config::Current()[QStringLiteral("AudioInput")] = audio_input_devices_->currentText();
|
||||
|
||||
// Index 0 is always the default device
|
||||
if (audio_output_devices_->currentIndex() == 0) {
|
||||
selected_output = QAudioDeviceInfo::defaultOutputDevice();
|
||||
} else {
|
||||
selected_output = AudioManager::instance()->ListOutputDevices().at(audio_output_devices_->currentData().toInt());
|
||||
selected_output_name = selected_output.deviceName();
|
||||
}
|
||||
// Set devices to be used from now on
|
||||
AudioManager::instance()->SetOutputDevice(output_device);
|
||||
AudioManager::instance()->SetInputDevice(input_device);
|
||||
}
|
||||
|
||||
// Save it in the global application preferences
|
||||
if (Config::Current()["AudioOutput"] != selected_output_name) {
|
||||
Config::Current()["AudioOutput"] = selected_output_name;
|
||||
AudioManager::instance()->SetOutputDevice(selected_output);
|
||||
}
|
||||
void PreferencesAudioTab::RefreshBackends()
|
||||
{
|
||||
audio_backend_combobox_->clear();
|
||||
for (PaHostApiIndex i=0, end=Pa_GetHostApiCount(); i<end; i++) {
|
||||
const PaHostApiInfo *info = Pa_GetHostApiInfo(i);
|
||||
|
||||
audio_backend_combobox_->addItem(info->name);
|
||||
}
|
||||
|
||||
if (audio_input_devices_->isEnabled()) {
|
||||
QAudioDeviceInfo selected_input;
|
||||
QString selected_input_name;
|
||||
RefreshDevices();
|
||||
|
||||
// Index 0 is always the default device
|
||||
if (audio_input_devices_->currentIndex() == 0) {
|
||||
selected_input = QAudioDeviceInfo::defaultInputDevice();
|
||||
} else {
|
||||
selected_input = AudioManager::instance()->ListInputDevices().at(audio_input_devices_->currentData().toInt());
|
||||
selected_input_name = selected_input.deviceName();
|
||||
}
|
||||
|
||||
if (Config::Current()["AudioInput"] != selected_input_name) {
|
||||
Config::Current()["AudioInput"] = selected_input_name;
|
||||
AudioManager::instance()->SetInputDevice(selected_input);
|
||||
}
|
||||
}
|
||||
*/
|
||||
AttemptToSetDevicesFromConfig();
|
||||
}
|
||||
|
||||
void PreferencesAudioTab::RefreshDevices()
|
||||
{
|
||||
AudioManager::instance()->RefreshDevices();
|
||||
if (audio_backend_combobox_->count() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
RetrieveDeviceLists();
|
||||
}
|
||||
PaHostApiIndex host_index = audio_backend_combobox_->currentIndex();
|
||||
const PaHostApiInfo *host = Pa_GetHostApiInfo(host_index);
|
||||
|
||||
void PreferencesAudioTab::RetrieveOutputList()
|
||||
{
|
||||
/*PopulateComboBox(audio_output_devices_,
|
||||
AudioManager::instance()->IsRefreshingOutputs(),
|
||||
AudioManager::instance()->ListOutputDevices(),
|
||||
Config::Current()["AudioOutput"].toString());*/
|
||||
audio_output_devices_->clear();
|
||||
audio_input_devices_->clear();
|
||||
|
||||
UpdateRefreshButtonEnabled();
|
||||
}
|
||||
|
||||
void PreferencesAudioTab::RetrieveInputList()
|
||||
{
|
||||
/*PopulateComboBox(audio_input_devices_,
|
||||
AudioManager::instance()->IsRefreshingInputs(),
|
||||
AudioManager::instance()->ListInputDevices(),
|
||||
Config::Current()["AudioInput"].toString());*/
|
||||
|
||||
UpdateRefreshButtonEnabled();
|
||||
}
|
||||
|
||||
void PreferencesAudioTab::RetrieveDeviceLists()
|
||||
{
|
||||
RetrieveOutputList();
|
||||
RetrieveInputList();
|
||||
}
|
||||
|
||||
void PreferencesAudioTab::UpdateRefreshButtonEnabled()
|
||||
{
|
||||
refresh_devices_btn_->setEnabled(audio_output_devices_->isEnabled()
|
||||
&& audio_input_devices_->isEnabled());
|
||||
}
|
||||
|
||||
/*void PreferencesAudioTab::PopulateComboBox(QComboBox *cb, bool still_refreshing, const QList<QAudioDeviceInfo> &list, const QString& preferred)
|
||||
{
|
||||
cb->clear();
|
||||
|
||||
cb->setEnabled(!still_refreshing);
|
||||
|
||||
if (still_refreshing) {
|
||||
cb->addItem(tr("Please wait..."));
|
||||
} else {
|
||||
bool found_preferred_device = false;
|
||||
|
||||
// Add null default item
|
||||
cb->addItem(tr("Default"), QVariant());
|
||||
|
||||
// For each entry, add it to the combobox
|
||||
for (int i=0;i<list.size();i++) {
|
||||
|
||||
cb->addItem(list.at(i).deviceName(), i);
|
||||
|
||||
if (!found_preferred_device
|
||||
&& list.at(i).deviceName() == preferred) {
|
||||
cb->setCurrentIndex(cb->count()-1);
|
||||
found_preferred_device = true;
|
||||
}
|
||||
for (int i=0; i<host->deviceCount; i++) {
|
||||
PaDeviceIndex device_index = Pa_HostApiDeviceIndexToDeviceIndex(host_index, i);
|
||||
const PaDeviceInfo *device = Pa_GetDeviceInfo(device_index);
|
||||
|
||||
if (device->maxOutputChannels) {
|
||||
audio_output_devices_->addItem(device->name, device_index);
|
||||
}
|
||||
|
||||
if (device->maxInputChannels) {
|
||||
audio_input_devices_->addItem(device->name, device_index);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
void PreferencesAudioTab::HardRefreshBackends()
|
||||
{
|
||||
AudioManager::instance()->HardReset();
|
||||
RefreshBackends();
|
||||
}
|
||||
|
||||
void PreferencesAudioTab::AttemptToSetDevicesFromConfig()
|
||||
{
|
||||
// Load with currently active devices
|
||||
PaDeviceIndex current_output_index = AudioManager::instance()->GetOutputDevice();
|
||||
PaDeviceIndex current_input_index = AudioManager::instance()->GetInputDevice();
|
||||
|
||||
const PaDeviceInfo *current_output = nullptr, *current_input = nullptr;
|
||||
if (current_output_index != paNoDevice) {
|
||||
current_output = Pa_GetDeviceInfo(current_output_index);
|
||||
}
|
||||
if (current_input_index != paNoDevice) {
|
||||
current_input = Pa_GetDeviceInfo(current_input_index);
|
||||
}
|
||||
|
||||
if (current_output || current_input) {
|
||||
PaHostApiIndex host = current_output ? current_output->hostApi : current_input->hostApi;
|
||||
|
||||
// Set backend accordingly
|
||||
audio_backend_combobox_->setCurrentIndex(host);
|
||||
|
||||
// Device comboboxes should be populated correctly now
|
||||
if (current_output) {
|
||||
audio_output_devices_->setCurrentText(current_output->name);
|
||||
}
|
||||
|
||||
if (current_input) {
|
||||
audio_input_devices_->setCurrentText(current_input->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -60,18 +60,13 @@ private:
|
||||
QPushButton* refresh_devices_btn_;
|
||||
|
||||
private slots:
|
||||
void RefreshBackends();
|
||||
|
||||
void RefreshDevices();
|
||||
|
||||
void RetrieveOutputList();
|
||||
void HardRefreshBackends();
|
||||
|
||||
void RetrieveInputList();
|
||||
|
||||
private:
|
||||
void RetrieveDeviceLists();
|
||||
|
||||
void UpdateRefreshButtonEnabled();
|
||||
|
||||
//static void PopulateComboBox(QComboBox* cb, bool still_refreshing, const QList<QAudioDeviceInfo>& list, const QString &preferred);
|
||||
void AttemptToSetDevicesFromConfig();
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2020 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}
|
||||
dialog/projectproperties/projectproperties.h
|
||||
dialog/projectproperties/projectproperties.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,255 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2020 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 "projectproperties.h"
|
||||
|
||||
#include <QButtonGroup>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFileDialog>
|
||||
#include <QGroupBox>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
|
||||
#include "common/filefunctions.h"
|
||||
#include "common/ocioutils.h"
|
||||
#include "config/config.h"
|
||||
#include "core.h"
|
||||
#include "node/color/colormanager/colormanager.h"
|
||||
#include "render/diskmanager.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
#define super QDialog
|
||||
|
||||
ProjectPropertiesDialog::ProjectPropertiesDialog(Project* p, QWidget *parent) :
|
||||
super(parent),
|
||||
working_project_(p),
|
||||
ocio_config_is_valid_(true)
|
||||
{
|
||||
QVBoxLayout* layout = new QVBoxLayout(this);
|
||||
|
||||
setWindowTitle(tr("Project Properties for '%1'").arg(working_project_->name()));
|
||||
|
||||
QTabWidget* tabs = new QTabWidget;
|
||||
layout->addWidget(tabs);
|
||||
|
||||
{
|
||||
// Color management group
|
||||
QWidget* color_group = new QWidget();
|
||||
|
||||
QVBoxLayout* color_outer_layout = new QVBoxLayout(color_group);
|
||||
|
||||
QGridLayout* color_layout = new QGridLayout();
|
||||
color_outer_layout->addLayout(color_layout);
|
||||
|
||||
int row = 0;
|
||||
|
||||
color_layout->addWidget(new QLabel(tr("OpenColorIO Configuration:")), row, 0);
|
||||
|
||||
ocio_filename_ = new QLineEdit();
|
||||
ocio_filename_->setPlaceholderText(tr("(default)"));
|
||||
color_layout->addWidget(ocio_filename_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
color_layout->addWidget(new QLabel(tr("Default Input Color Space:")), row, 0);
|
||||
|
||||
default_input_colorspace_ = new QComboBox();
|
||||
color_layout->addWidget(default_input_colorspace_, row, 1, 1, 2);
|
||||
|
||||
row++;
|
||||
|
||||
QPushButton* browse_btn = new QPushButton(tr("Browse"));
|
||||
color_layout->addWidget(browse_btn, 0, 2);
|
||||
connect(browse_btn, &QPushButton::clicked, this, &ProjectPropertiesDialog::BrowseForOCIOConfig);
|
||||
|
||||
ocio_filename_->setText(working_project_->color_manager()->GetConfigFilename());
|
||||
|
||||
connect(ocio_filename_, &QLineEdit::textChanged, this, &ProjectPropertiesDialog::OCIOFilenameUpdated);
|
||||
OCIOFilenameUpdated();
|
||||
|
||||
tabs->addTab(color_group, tr("Color Management"));
|
||||
|
||||
color_outer_layout->addStretch();
|
||||
}
|
||||
|
||||
{
|
||||
// Cache group
|
||||
QWidget* cache_group = new QWidget();
|
||||
|
||||
QVBoxLayout* cache_layout = new QVBoxLayout(cache_group);
|
||||
|
||||
QButtonGroup* disk_cache_btn_group = new QButtonGroup();
|
||||
|
||||
// Create radio buttons and add to widget and button group
|
||||
disk_cache_radios_[ProjectSettingsNode::kCacheUseDefaultLocation] = new QRadioButton(tr("Use Default Location"));
|
||||
disk_cache_radios_[ProjectSettingsNode::kCacheStoreAlongsideProject] = new QRadioButton(tr("Store Alongside Project"));
|
||||
disk_cache_radios_[ProjectSettingsNode::kCacheCustomPath] = new QRadioButton(tr("Use Custom Location:"));
|
||||
for (int i=0; i<kDiskCacheRadioCount; i++) {
|
||||
disk_cache_btn_group->addButton(disk_cache_radios_[i]);
|
||||
cache_layout->addWidget(disk_cache_radios_[i]);
|
||||
}
|
||||
|
||||
// Create custom cache path widget
|
||||
custom_cache_path_ = new PathWidget(working_project_->settings()->GetCustomCachePath(), this);
|
||||
custom_cache_path_->setEnabled(false);
|
||||
cache_layout->addWidget(custom_cache_path_);
|
||||
|
||||
// Ensure custom cache path "enabled" is tied to the radio button being checked
|
||||
connect(disk_cache_radios_[ProjectSettingsNode::kCacheCustomPath], &QRadioButton::toggled, custom_cache_path_, &PathWidget::setEnabled);
|
||||
|
||||
// Check the radio button that should currently be active
|
||||
disk_cache_radios_[working_project_->settings()->GetCacheSetting()]->setChecked(true);
|
||||
|
||||
// Add disk cache settings button
|
||||
QPushButton* disk_cache_settings_btn = new QPushButton(tr("Disk Cache Settings"));
|
||||
connect(disk_cache_settings_btn, &QPushButton::clicked, this, &ProjectPropertiesDialog::OpenDiskCacheSettings);
|
||||
cache_layout->addWidget(disk_cache_settings_btn);
|
||||
|
||||
tabs->addTab(cache_group, tr("Disk Cache"));
|
||||
}
|
||||
|
||||
QDialogButtonBox* dialog_btns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel,
|
||||
Qt::Horizontal);
|
||||
layout->addWidget(dialog_btns);
|
||||
connect(dialog_btns, &QDialogButtonBox::accepted, this, &ProjectPropertiesDialog::accept);
|
||||
connect(dialog_btns, &QDialogButtonBox::rejected, this, &ProjectPropertiesDialog::reject);
|
||||
}
|
||||
|
||||
void ProjectPropertiesDialog::accept()
|
||||
{
|
||||
if (!ocio_config_is_valid_) {
|
||||
QMessageBox mb(this);
|
||||
mb.setWindowModality(Qt::WindowModal);
|
||||
mb.setIcon(QMessageBox::Critical);
|
||||
mb.setWindowTitle(tr("OpenColorIO Config Error"));
|
||||
mb.setText(tr("Failed to set OpenColorIO configuration: %1").arg(ocio_config_error_));
|
||||
mb.addButton(QMessageBox::Ok);
|
||||
mb.exec();
|
||||
return;
|
||||
}
|
||||
|
||||
if (disk_cache_radios_[ProjectSettingsNode::kCacheUseDefaultLocation]->isChecked()) {
|
||||
// Keep new cache path empty, which means default
|
||||
} else if (disk_cache_radios_[ProjectSettingsNode::kCacheStoreAlongsideProject]->isChecked()) {
|
||||
// Ensure alongside project path is valid
|
||||
if (!VerifyPathAndWarnIfBad(working_project_->get_cache_alongside_project_path())) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Ensure custom path is valid
|
||||
if (!VerifyPathAndWarnIfBad(custom_cache_path_->text())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (custom_cache_path_->text() != working_project_->settings()->GetCustomCachePath()) {
|
||||
// Check if the user is okay with invalidating the current cache
|
||||
if (!DiskManager::ShowDiskCacheChangeConfirmationDialog(this)) {
|
||||
return;
|
||||
}
|
||||
|
||||
working_project_->settings()->SetCustomCachePath(custom_cache_path_->text());
|
||||
|
||||
emit DiskManager::instance()->InvalidateProject(working_project_);
|
||||
}
|
||||
|
||||
// This should ripple changes throughout the graph/cache that the color config has changed, and
|
||||
// therefore should be done after the cache path is changed
|
||||
if (working_project_->color_manager()->GetConfigFilename() != ocio_filename_->text()) {
|
||||
working_project_->color_manager()->SetConfigFilename(ocio_filename_->text());
|
||||
}
|
||||
if (working_project_->color_manager()->GetDefaultInputColorSpace() != default_input_colorspace_->currentText()) {
|
||||
working_project_->color_manager()->SetDefaultInputColorSpace(default_input_colorspace_->currentText());
|
||||
}
|
||||
|
||||
super::accept();
|
||||
}
|
||||
|
||||
bool ProjectPropertiesDialog::VerifyPathAndWarnIfBad(const QString &path)
|
||||
{
|
||||
if (!FileFunctions::DirectoryIsValid(path, true)) {
|
||||
QMessageBox mb(this);
|
||||
mb.setWindowModality(Qt::WindowModal);
|
||||
mb.setIcon(QMessageBox::Critical);
|
||||
mb.setWindowTitle(tr("Invalid path"));
|
||||
mb.setText(tr("The custom cache path is invalid. Please check it and try again."));
|
||||
mb.addButton(QMessageBox::Ok);
|
||||
mb.exec();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ProjectPropertiesDialog::BrowseForOCIOConfig()
|
||||
{
|
||||
QString fn = QFileDialog::getOpenFileName(this, tr("Browse for OpenColorIO configuration"));
|
||||
if (!fn.isEmpty()) {
|
||||
ocio_filename_->setText(fn);
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectPropertiesDialog::OCIOFilenameUpdated()
|
||||
{
|
||||
default_input_colorspace_->clear();
|
||||
|
||||
try {
|
||||
OCIO::ConstConfigRcPtr c;
|
||||
|
||||
if (ocio_filename_->text().isEmpty()) {
|
||||
c = ColorManager::GetDefaultConfig();
|
||||
} else {
|
||||
c = ColorManager::CreateConfigFromFile(ocio_filename_->text());
|
||||
}
|
||||
|
||||
ocio_filename_->setStyleSheet(QString());
|
||||
ocio_config_is_valid_ = true;
|
||||
|
||||
// List input color spaces
|
||||
QStringList input_cs = ColorManager::ListAvailableColorspaces(c);
|
||||
|
||||
foreach (QString cs, input_cs) {
|
||||
default_input_colorspace_->addItem(cs);
|
||||
|
||||
if (cs == working_project_->color_manager()->GetDefaultInputColorSpace()) {
|
||||
default_input_colorspace_->setCurrentIndex(default_input_colorspace_->count()-1);
|
||||
}
|
||||
}
|
||||
} catch (OCIO::Exception& e) {
|
||||
ocio_config_is_valid_ = false;
|
||||
ocio_filename_->setStyleSheet(QStringLiteral("QLineEdit {color: red;}"));
|
||||
ocio_config_error_ = e.what();
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectPropertiesDialog::OpenDiskCacheSettings()
|
||||
{
|
||||
if (disk_cache_radios_[ProjectSettingsNode::kCacheUseDefaultLocation]->isChecked()) {
|
||||
DiskManager::instance()->ShowDiskCacheSettingsDialog(DiskManager::instance()->GetDefaultCacheFolder(), this);
|
||||
} else if (disk_cache_radios_[ProjectSettingsNode::kCacheStoreAlongsideProject]->isChecked()) {
|
||||
DiskManager::instance()->ShowDiskCacheSettingsDialog(working_project_->get_cache_alongside_project_path(), this);
|
||||
} else {
|
||||
DiskManager::instance()->ShowDiskCacheSettingsDialog(custom_cache_path_->text(), this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2020 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 PROJECTPROPERTIESDIALOG_H
|
||||
#define PROJECTPROPERTIESDIALOG_H
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QDialog>
|
||||
#include <QGridLayout>
|
||||
#include <QLineEdit>
|
||||
#include <QRadioButton>
|
||||
|
||||
#include "node/project/project.h"
|
||||
#include "widget/path/pathwidget.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class ProjectPropertiesDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ProjectPropertiesDialog(Project *p, QWidget* parent);
|
||||
|
||||
public slots:
|
||||
virtual void accept() override;
|
||||
|
||||
private:
|
||||
bool VerifyPathAndWarnIfBad(const QString &path);
|
||||
|
||||
Project* working_project_;
|
||||
|
||||
QLineEdit* ocio_filename_;
|
||||
|
||||
QComboBox* default_input_colorspace_;
|
||||
|
||||
bool ocio_config_is_valid_;
|
||||
|
||||
QString ocio_config_error_;
|
||||
|
||||
PathWidget* custom_cache_path_;
|
||||
|
||||
static const int kDiskCacheRadioCount = 3;
|
||||
QRadioButton *disk_cache_radios_[kDiskCacheRadioCount];
|
||||
|
||||
private slots:
|
||||
void BrowseForOCIOConfig();
|
||||
|
||||
void OCIOFilenameUpdated();
|
||||
|
||||
void OpenDiskCacheSettings();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // PROJECTPROPERTIESDIALOG_H
|
||||
@@ -63,6 +63,8 @@ private:
|
||||
|
||||
};
|
||||
|
||||
using PresetPtr = std::shared_ptr<Preset>;
|
||||
|
||||
template <typename T>
|
||||
class PresetManager
|
||||
{
|
||||
@@ -80,7 +82,7 @@ public:
|
||||
if (reader.name() == QStringLiteral("presets")) {
|
||||
while (XMLReadNextStartElement(&reader)) {
|
||||
if (reader.name() == QStringLiteral("preset")) {
|
||||
Preset* p = new T();
|
||||
PresetPtr p = std::make_unique<T>();
|
||||
|
||||
p->Load(&reader);
|
||||
|
||||
@@ -110,7 +112,7 @@ public:
|
||||
|
||||
writer.writeStartElement(QStringLiteral("presets"));
|
||||
|
||||
foreach (Preset* p, custom_preset_data_) {
|
||||
foreach (PresetPtr p, custom_preset_data_) {
|
||||
writer.writeStartElement(QStringLiteral("preset"));
|
||||
|
||||
p->Save(&writer);
|
||||
@@ -124,8 +126,6 @@ public:
|
||||
|
||||
preset_file.close();
|
||||
}
|
||||
|
||||
qDeleteAll(custom_preset_data_);
|
||||
}
|
||||
|
||||
QString GetPresetName(QString start) const
|
||||
@@ -159,7 +159,13 @@ public:
|
||||
return start;
|
||||
}
|
||||
|
||||
bool SavePreset(Preset* preset)
|
||||
enum SaveStatus {
|
||||
kAppended,
|
||||
kReplaced,
|
||||
kNotSaved
|
||||
};
|
||||
|
||||
SaveStatus SavePreset(PresetPtr preset)
|
||||
{
|
||||
QString preset_name;
|
||||
int existing_preset;
|
||||
@@ -169,7 +175,7 @@ public:
|
||||
|
||||
if (preset_name.isEmpty()) {
|
||||
// Dialog cancelled - leave function entirely
|
||||
return false;
|
||||
return kNotSaved;
|
||||
}
|
||||
|
||||
existing_preset = -1;
|
||||
@@ -194,10 +200,10 @@ public:
|
||||
|
||||
if (existing_preset >= 0) {
|
||||
custom_preset_data_.replace(existing_preset, preset);
|
||||
return false;
|
||||
return kReplaced;
|
||||
} else {
|
||||
custom_preset_data_.append(preset);
|
||||
return true;
|
||||
return kAppended;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,7 +212,7 @@ public:
|
||||
return QDir(FileFunctions::GetConfigurationLocation()).filePath(preset_name_);
|
||||
}
|
||||
|
||||
Preset* GetPreset(int index)
|
||||
PresetPtr GetPreset(int index)
|
||||
{
|
||||
return custom_preset_data_.at(index);
|
||||
}
|
||||
@@ -221,13 +227,13 @@ public:
|
||||
return custom_preset_data_.size();
|
||||
}
|
||||
|
||||
const QVector<Preset*>& GetPresetData() const
|
||||
const QVector<PresetPtr>& GetPresetData() const
|
||||
{
|
||||
return custom_preset_data_;
|
||||
}
|
||||
|
||||
private:
|
||||
QVector<Preset*> custom_preset_data_;
|
||||
QVector<PresetPtr> custom_preset_data_;
|
||||
|
||||
QString preset_name_;
|
||||
|
||||
|
||||
@@ -19,11 +19,28 @@ SequenceDialogParameterTab::SequenceDialogParameterTab(Sequence* sequence, QWidg
|
||||
// Set up video section
|
||||
QGroupBox* video_group = new QGroupBox();
|
||||
video_group->setTitle(tr("Video"));
|
||||
QHBoxLayout* video_layout = new QHBoxLayout(video_group);
|
||||
video_section_ = new VideoParamEdit();
|
||||
video_section_->SetParameterMask(Sequence::kVideoParamEditMask);
|
||||
connect(video_section_, &VideoParamEdit::Changed, this, &SequenceDialogParameterTab::UpdatePreviewResolutionLabel);
|
||||
video_layout->addWidget(video_section_);
|
||||
QGridLayout *video_layout = new QGridLayout(video_group);
|
||||
video_layout->addWidget(new QLabel(tr("Width:")), row, 0);
|
||||
width_slider_ = new IntegerSlider();
|
||||
width_slider_->SetMinimum(0);
|
||||
video_layout->addWidget(width_slider_, row, 1);
|
||||
row++;
|
||||
video_layout->addWidget(new QLabel(tr("Height:")), row, 0);
|
||||
height_slider_ = new IntegerSlider();
|
||||
height_slider_->SetMinimum(0);
|
||||
video_layout->addWidget(height_slider_, row, 1);
|
||||
row++;
|
||||
video_layout->addWidget(new QLabel(tr("Frame Rate:")), row, 0);
|
||||
framerate_combo_ = new FrameRateComboBox();
|
||||
video_layout->addWidget(framerate_combo_, row, 1);
|
||||
row++;
|
||||
video_layout->addWidget(new QLabel(tr("Pixel Aspect Ratio:")), row, 0);
|
||||
pixelaspect_combo_ = new PixelAspectRatioComboBox();
|
||||
video_layout->addWidget(pixelaspect_combo_, row, 1);
|
||||
row++;
|
||||
video_layout->addWidget(new QLabel(tr("Interlacing:")), row, 0);
|
||||
interlacing_combo_ = new InterlacedComboBox();
|
||||
video_layout->addWidget(interlacing_combo_, row, 1);
|
||||
layout->addWidget(video_group);
|
||||
|
||||
row = 0;
|
||||
@@ -65,7 +82,11 @@ SequenceDialogParameterTab::SequenceDialogParameterTab(Sequence* sequence, QWidg
|
||||
// Set values based on input sequence
|
||||
VideoParams vp = sequence->GetVideoParams();
|
||||
AudioParams ap = sequence->GetAudioParams();
|
||||
video_section_->SetVideoParams(vp);
|
||||
width_slider_->SetValue(vp.width());
|
||||
height_slider_->SetValue(vp.height());
|
||||
framerate_combo_->SetFrameRate(vp.time_base().flipped());
|
||||
pixelaspect_combo_->SetPixelAspectRatio(vp.pixel_aspect_ratio());
|
||||
interlacing_combo_->SetInterlaceMode(vp.interlacing());
|
||||
preview_resolution_field_->SetDivider(vp.divider());
|
||||
preview_format_field_->SetPixelFormat(vp.format());
|
||||
preview_autocache_field_->setChecked(sequence->GetVideoAutoCacheEnabled());
|
||||
@@ -86,11 +107,11 @@ SequenceDialogParameterTab::SequenceDialogParameterTab(Sequence* sequence, QWidg
|
||||
|
||||
void SequenceDialogParameterTab::PresetChanged(const SequencePreset &preset)
|
||||
{
|
||||
video_section_->SetWidth(preset.width());
|
||||
video_section_->SetHeight(preset.height());
|
||||
video_section_->SetFrameRate(preset.frame_rate());
|
||||
video_section_->SetPixelAspectRatio(preset.pixel_aspect());
|
||||
video_section_->SetInterlaceMode(preset.interlacing());
|
||||
width_slider_->SetValue(preset.width());
|
||||
height_slider_->SetValue(preset.height());
|
||||
framerate_combo_->SetFrameRate(preset.frame_rate());
|
||||
pixelaspect_combo_->SetPixelAspectRatio(preset.pixel_aspect());
|
||||
interlacing_combo_->SetInterlaceMode(preset.interlacing());
|
||||
audio_sample_rate_field_->SetSampleRate(preset.sample_rate());
|
||||
audio_channels_field_->SetChannelLayout(preset.channel_layout());
|
||||
preview_resolution_field_->SetDivider(preset.preview_divider());
|
||||
@@ -115,8 +136,8 @@ void SequenceDialogParameterTab::SavePresetClicked()
|
||||
|
||||
void SequenceDialogParameterTab::UpdatePreviewResolutionLabel()
|
||||
{
|
||||
VideoParams test_param(video_section_->GetWidth(),
|
||||
video_section_->GetHeight(),
|
||||
VideoParams test_param(GetSelectedVideoWidth(),
|
||||
GetSelectedVideoHeight(),
|
||||
VideoParams::kFormatInvalid,
|
||||
VideoParams::kInternalChannelCount,
|
||||
rational(1),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef SEQUENCEDIALOGPARAMETERTAB_H
|
||||
#define SEQUENCEDIALOGPARAMETERTAB_H
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QList>
|
||||
#include <QSpinBox>
|
||||
@@ -9,7 +10,6 @@
|
||||
#include "sequencepreset.h"
|
||||
#include "widget/slider/integerslider.h"
|
||||
#include "widget/standardcombos/standardcombos.h"
|
||||
#include "widget/videoparamedit/videoparamedit.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -21,27 +21,27 @@ public:
|
||||
|
||||
int GetSelectedVideoWidth() const
|
||||
{
|
||||
return video_section_->GetWidth();
|
||||
return width_slider_->GetValue();
|
||||
}
|
||||
|
||||
int GetSelectedVideoHeight() const
|
||||
{
|
||||
return video_section_->GetHeight();
|
||||
return height_slider_->GetValue();
|
||||
}
|
||||
|
||||
rational GetSelectedVideoFrameRate() const
|
||||
{
|
||||
return video_section_->GetFrameRate();
|
||||
return framerate_combo_->GetFrameRate();
|
||||
}
|
||||
|
||||
rational GetSelectedVideoPixelAspect() const
|
||||
{
|
||||
return video_section_->GetPixelAspectRatio();
|
||||
return pixelaspect_combo_->GetPixelAspectRatio();
|
||||
}
|
||||
|
||||
VideoParams::Interlacing GetSelectedVideoInterlacingMode() const
|
||||
{
|
||||
return video_section_->GetInterlaceMode();
|
||||
return interlacing_combo_->GetInterlaceMode();
|
||||
}
|
||||
|
||||
int GetSelectedAudioSampleRate() const
|
||||
@@ -76,7 +76,15 @@ signals:
|
||||
void SaveParametersAsPreset(const SequencePreset& preset);
|
||||
|
||||
private:
|
||||
VideoParamEdit* video_section_;
|
||||
IntegerSlider *width_slider_;
|
||||
|
||||
IntegerSlider *height_slider_;
|
||||
|
||||
FrameRateComboBox *framerate_combo_;
|
||||
|
||||
PixelAspectRatioComboBox *pixelaspect_combo_;
|
||||
|
||||
InterlacedComboBox *interlacing_combo_;
|
||||
|
||||
SampleRateComboBox* audio_sample_rate_field_;
|
||||
|
||||
|
||||
@@ -79,19 +79,13 @@ SequenceDialogPresetTab::SequenceDialogPresetTab(QWidget* parent) :
|
||||
}
|
||||
}
|
||||
|
||||
SequenceDialogPresetTab::~SequenceDialogPresetTab()
|
||||
{
|
||||
qDeleteAll(default_preset_data_);
|
||||
}
|
||||
|
||||
void SequenceDialogPresetTab::SaveParametersAsPreset(SequencePreset preset)
|
||||
{
|
||||
Preset* preset_ptr = new SequencePreset(preset);
|
||||
PresetPtr preset_ptr = std::make_shared<SequencePreset>(preset);
|
||||
|
||||
if (SavePreset(preset_ptr)) {
|
||||
// If replaced, no need to make another item. If not saved, shared ptr will delete itself
|
||||
if (SavePreset(preset_ptr) == kAppended) {
|
||||
AddCustomItem(my_presets_folder_, preset_ptr, GetNumberOfPresets() - 1);
|
||||
} else {
|
||||
delete preset_ptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +102,7 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateHDPresetFolder(const QString &na
|
||||
const VideoParams::Format default_format = static_cast<VideoParams::Format>(Config::Current()["OfflinePixelFormat"].toInt());
|
||||
const bool default_autocache = Config::Current()[QStringLiteral("DefaultSequenceAutoCache")].toBool();
|
||||
QTreeWidgetItem* parent = CreateFolder(name);
|
||||
AddStandardItem(parent, new SequencePreset(tr("%1 23.976 FPS").arg(name),
|
||||
AddStandardItem(parent, std::make_shared<SequencePreset>(tr("%1 23.976 FPS").arg(name),
|
||||
width,
|
||||
height,
|
||||
rational(24000, 1001),
|
||||
@@ -119,7 +113,7 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateHDPresetFolder(const QString &na
|
||||
divider,
|
||||
default_format,
|
||||
default_autocache));
|
||||
AddStandardItem(parent, new SequencePreset(tr("%1 25 FPS").arg(name),
|
||||
AddStandardItem(parent, std::make_shared<SequencePreset>(tr("%1 25 FPS").arg(name),
|
||||
width,
|
||||
height,
|
||||
rational(25, 1),
|
||||
@@ -130,7 +124,7 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateHDPresetFolder(const QString &na
|
||||
divider,
|
||||
default_format,
|
||||
default_autocache));
|
||||
AddStandardItem(parent, new SequencePreset(tr("%1 29.97 FPS").arg(name),
|
||||
AddStandardItem(parent, std::make_shared<SequencePreset>(tr("%1 29.97 FPS").arg(name),
|
||||
width,
|
||||
height,
|
||||
rational(30000, 1001),
|
||||
@@ -141,7 +135,7 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateHDPresetFolder(const QString &na
|
||||
divider,
|
||||
default_format,
|
||||
default_autocache));
|
||||
AddStandardItem(parent, new SequencePreset(tr("%1 50 FPS").arg(name),
|
||||
AddStandardItem(parent, std::make_shared<SequencePreset>(tr("%1 50 FPS").arg(name),
|
||||
width,
|
||||
height,
|
||||
rational(50, 1),
|
||||
@@ -152,7 +146,7 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateHDPresetFolder(const QString &na
|
||||
divider,
|
||||
default_format,
|
||||
default_autocache));
|
||||
AddStandardItem(parent, new SequencePreset(tr("%1 59.94 FPS").arg(name),
|
||||
AddStandardItem(parent, std::make_shared<SequencePreset>(tr("%1 59.94 FPS").arg(name),
|
||||
width,
|
||||
height,
|
||||
rational(60000, 1001),
|
||||
@@ -172,7 +166,7 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateSDPresetFolder(const QString &na
|
||||
const bool default_autocache = Config::Current()[QStringLiteral("DefaultSequenceAutoCache")].toBool();
|
||||
QTreeWidgetItem* parent = CreateFolder(name);
|
||||
preset_tree_->addTopLevelItem(parent);
|
||||
AddStandardItem(parent, new SequencePreset(tr("%1 Standard").arg(name),
|
||||
AddStandardItem(parent, std::make_shared<SequencePreset>(tr("%1 Standard").arg(name),
|
||||
width,
|
||||
height,
|
||||
frame_rate,
|
||||
@@ -183,7 +177,7 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateSDPresetFolder(const QString &na
|
||||
divider,
|
||||
default_format,
|
||||
default_autocache));
|
||||
AddStandardItem(parent, new SequencePreset(tr("%1 Widescreen").arg(name),
|
||||
AddStandardItem(parent, std::make_shared<SequencePreset>(tr("%1 Widescreen").arg(name),
|
||||
width,
|
||||
height,
|
||||
frame_rate,
|
||||
@@ -221,19 +215,19 @@ QTreeWidgetItem *SequenceDialogPresetTab::GetSelectedCustomPreset()
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void SequenceDialogPresetTab::AddStandardItem(QTreeWidgetItem *folder, Preset* preset, const QString& description)
|
||||
void SequenceDialogPresetTab::AddStandardItem(QTreeWidgetItem *folder, PresetPtr preset, const QString& description)
|
||||
{
|
||||
int index = default_preset_data_.size();
|
||||
default_preset_data_.append(preset);
|
||||
AddItemInternal(folder, preset, false, index, description);
|
||||
}
|
||||
|
||||
void SequenceDialogPresetTab::AddCustomItem(QTreeWidgetItem *folder, Preset* preset, int index, const QString &description)
|
||||
void SequenceDialogPresetTab::AddCustomItem(QTreeWidgetItem *folder, PresetPtr preset, int index, const QString &description)
|
||||
{
|
||||
AddItemInternal(folder, preset, true, index, description);
|
||||
}
|
||||
|
||||
void SequenceDialogPresetTab::AddItemInternal(QTreeWidgetItem *folder, Preset* preset, bool is_custom, int index, const QString &description)
|
||||
void SequenceDialogPresetTab::AddItemInternal(QTreeWidgetItem *folder, PresetPtr preset, bool is_custom, int index, const QString &description)
|
||||
{
|
||||
QTreeWidgetItem* item = new QTreeWidgetItem();
|
||||
|
||||
@@ -254,11 +248,11 @@ void SequenceDialogPresetTab::SelectedItemChanged(QTreeWidgetItem* current, QTre
|
||||
if (current->data(0, kDataIsPreset).toBool()) {
|
||||
int preset_index = current->data(0, kDataPresetDataRole).toInt();
|
||||
|
||||
Preset* preset_data = (current->data(0, kDataPresetIsCustomRole).toBool())
|
||||
PresetPtr preset_data = (current->data(0, kDataPresetIsCustomRole).toBool())
|
||||
? GetPreset(preset_index)
|
||||
: default_preset_data_.at(preset_index);
|
||||
|
||||
emit PresetChanged(*static_cast<SequencePreset*>(preset_data));
|
||||
emit PresetChanged(*static_cast<SequencePreset*>(preset_data.get()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,8 +36,6 @@ class SequenceDialogPresetTab : public QWidget, public PresetManager<SequencePre
|
||||
public:
|
||||
SequenceDialogPresetTab(QWidget* parent = nullptr);
|
||||
|
||||
virtual ~SequenceDialogPresetTab() override;
|
||||
|
||||
public slots:
|
||||
void SaveParametersAsPreset(SequencePreset preset);
|
||||
|
||||
@@ -56,17 +54,17 @@ private:
|
||||
QTreeWidgetItem* GetSelectedItem();
|
||||
QTreeWidgetItem* GetSelectedCustomPreset();
|
||||
|
||||
void AddStandardItem(QTreeWidgetItem* folder, Preset* preset, const QString &description = QString());
|
||||
void AddStandardItem(QTreeWidgetItem* folder, PresetPtr preset, const QString &description = QString());
|
||||
|
||||
void AddCustomItem(QTreeWidgetItem* folder, Preset* preset, int index, const QString& description = QString());
|
||||
void AddCustomItem(QTreeWidgetItem* folder, PresetPtr preset, int index, const QString& description = QString());
|
||||
|
||||
void AddItemInternal(QTreeWidgetItem* folder, Preset* preset, bool is_custom, int index, const QString& description = QString());
|
||||
void AddItemInternal(QTreeWidgetItem* folder, PresetPtr preset, bool is_custom, int index, const QString& description = QString());
|
||||
|
||||
QTreeWidget* preset_tree_;
|
||||
|
||||
QTreeWidgetItem* my_presets_folder_;
|
||||
|
||||
QVector<Preset*> default_preset_data_;
|
||||
QVector<PresetPtr> default_preset_data_;
|
||||
|
||||
private slots:
|
||||
void SelectedItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous);
|
||||
|
||||
+17
-1
@@ -71,6 +71,22 @@ int main(int argc, char *argv[])
|
||||
// Parse command line arguments
|
||||
//
|
||||
|
||||
QVector<QString> args;
|
||||
#if defined(_WIN32) && defined(UNICODE)
|
||||
int wargc;
|
||||
LPWSTR *wargv = CommandLineToArgvW(GetCommandLineW(), &wargc);
|
||||
args.resize(wargc);
|
||||
for (int i=0; i<wargc; i++) {
|
||||
args[i] = QString::fromWCharArray(wargv[i]);
|
||||
}
|
||||
LocalFree(wargv);
|
||||
#else
|
||||
args.resize(argc);
|
||||
for (int i=0; i<argc; i++) {
|
||||
args[i] = QString::fromLocal8Bit(argv[i]);
|
||||
}
|
||||
#endif
|
||||
|
||||
olive::Core::CoreParams startup_params;
|
||||
|
||||
CommandLineParser parser;
|
||||
@@ -130,7 +146,7 @@ int main(int argc, char *argv[])
|
||||
// Hidden crash option for debugging the crash handling
|
||||
auto crash_option = parser.AddOption({QStringLiteral("-crash")}, QString(), true, QString(), true);
|
||||
|
||||
parser.Process(argc, argv);
|
||||
parser.Process(args);
|
||||
|
||||
if (help_option->IsSet()) {
|
||||
// Show help
|
||||
|
||||
@@ -18,8 +18,10 @@ add_subdirectory(audio)
|
||||
add_subdirectory(block)
|
||||
add_subdirectory(color)
|
||||
add_subdirectory(distort)
|
||||
add_subdirectory(effect)
|
||||
add_subdirectory(filter)
|
||||
add_subdirectory(generator)
|
||||
add_subdirectory(group)
|
||||
add_subdirectory(input)
|
||||
add_subdirectory(math)
|
||||
add_subdirectory(output)
|
||||
|
||||
@@ -47,6 +47,8 @@ Block::Block() :
|
||||
IgnoreHashingFrom(kLengthInput);
|
||||
|
||||
AddInput(kEnabledInput, NodeValue::kBoolean, true, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable));
|
||||
|
||||
SetFlags(kDontShowInParamView);
|
||||
}
|
||||
|
||||
QVector<Node::CategoryID> Block::Category() const
|
||||
|
||||
@@ -77,7 +77,7 @@ OCIO::ConstConfigRcPtr ColorManager::GetDefaultConfig()
|
||||
|
||||
void ColorManager::SetUpDefaultConfig()
|
||||
{
|
||||
if (!qgetenv("OCIO").isEmpty()) {
|
||||
if (!qEnvironmentVariableIsEmpty("OCIO")) {
|
||||
// Attempt to set config from "OCIO" environment variable
|
||||
try {
|
||||
OCIO_SET_C_LOCALE_FOR_SCOPE;
|
||||
@@ -251,19 +251,6 @@ void ColorManager::GetDefaultLumaCoefs(double *rgb) const
|
||||
config_->getDefaultLumaCoefs(rgb);
|
||||
}
|
||||
|
||||
Color ColorManager::GetDefaultLumaCoefs() const
|
||||
{
|
||||
Color c;
|
||||
|
||||
// Just a default value, shouldn't be significant
|
||||
c.set_alpha(1.0f);
|
||||
|
||||
// The float data in Color lines up with the "rgb" param of this function
|
||||
GetDefaultLumaCoefs(c.data());
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
void ColorManager::Retranslate()
|
||||
{
|
||||
SetInputName(kConfigFilenameIn, tr("Configuration"));
|
||||
|
||||
@@ -100,7 +100,6 @@ public:
|
||||
static QStringList ListAvailableColorspaces(OCIO::ConstConfigRcPtr config);
|
||||
|
||||
void GetDefaultLumaCoefs(double *rgb) const;
|
||||
Color GetDefaultLumaCoefs() const;
|
||||
|
||||
class SetLocale
|
||||
{
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
add_subdirectory(crop)
|
||||
add_subdirectory(flip)
|
||||
add_subdirectory(transform)
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
|
||||
@@ -108,8 +108,6 @@ void CropDistortNode::DrawGizmos(const NodeValueRow &row, const NodeGlobals &glo
|
||||
gizmo_resize_handle_[kGizmoScaleCenterLeft] = CreateGizmoHandleRect(QPointF(left_pt, center_y_pt), handle_radius);
|
||||
gizmo_resize_handle_[kGizmoScaleCenterRight] = CreateGizmoHandleRect(QPointF(right_pt, center_y_pt), handle_radius);
|
||||
|
||||
p->setPen(Qt::NoPen);
|
||||
p->setBrush(Qt::white);
|
||||
DrawAndExpandGizmoHandles(p, handle_radius, gizmo_resize_handle_, kGizmoScaleCount);
|
||||
}
|
||||
|
||||
@@ -233,13 +231,11 @@ void CropDistortNode::GizmoMove(const QPointF &p, const rational &time, const Qt
|
||||
}
|
||||
}
|
||||
|
||||
void CropDistortNode::GizmoRelease()
|
||||
void CropDistortNode::GizmoRelease(MultiUndoCommand *command)
|
||||
{
|
||||
MultiUndoCommand *command = new MultiUndoCommand();
|
||||
for (NodeInputDragger& i : gizmo_dragger_) {
|
||||
i.End(command);
|
||||
}
|
||||
Core::instance()->undo_stack()->push(command);
|
||||
gizmo_dragger_.clear();
|
||||
|
||||
gizmo_start_.clear();
|
||||
|
||||
@@ -76,7 +76,7 @@ public:
|
||||
|
||||
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() override;
|
||||
virtual void GizmoRelease(MultiUndoCommand *command) override;
|
||||
|
||||
static const QString kTextureInput;
|
||||
static const QString kLeftInput;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
widget/videoparamedit/videoparamedit.cpp
|
||||
widget/videoparamedit/videoparamedit.h
|
||||
node/distort/flip/flipdistortnode.cpp
|
||||
node/distort/flip/flipdistortnode.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,95 @@
|
||||
/***
|
||||
|
||||
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 "flipdistortnode.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
const QString FlipDistortNode::kTextureInput = QStringLiteral("tex_in");
|
||||
const QString FlipDistortNode::kHorizontalInput = QStringLiteral("horiz_in");
|
||||
const QString FlipDistortNode::kVerticalInput = QStringLiteral("vert_in");
|
||||
|
||||
FlipDistortNode::FlipDistortNode()
|
||||
{
|
||||
AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
|
||||
|
||||
AddInput(kHorizontalInput, NodeValue::kBoolean, false);
|
||||
|
||||
AddInput(kVerticalInput, NodeValue::kBoolean, false);
|
||||
}
|
||||
|
||||
Node* FlipDistortNode::copy() const
|
||||
{
|
||||
return new FlipDistortNode();
|
||||
}
|
||||
|
||||
QString FlipDistortNode::Name() const
|
||||
{
|
||||
return tr("Flip");
|
||||
}
|
||||
|
||||
QString FlipDistortNode::id() const
|
||||
{
|
||||
return QStringLiteral("org.oliveeditor.Olive.flip");
|
||||
}
|
||||
|
||||
QVector<Node::CategoryID> FlipDistortNode::Category() const
|
||||
{
|
||||
return {kCategoryDistort};
|
||||
}
|
||||
|
||||
QString FlipDistortNode::Description() const
|
||||
{
|
||||
return tr("Flips an image horizontally or vertically");
|
||||
}
|
||||
|
||||
void FlipDistortNode::Retranslate()
|
||||
{
|
||||
SetInputName(kTextureInput, tr("Input"));
|
||||
SetInputName(kHorizontalInput, tr("Horizontal"));
|
||||
SetInputName(kVerticalInput, tr("Vertical"));
|
||||
}
|
||||
|
||||
ShaderCode FlipDistortNode::GetShaderCode(const QString& shader_id) const
|
||||
{
|
||||
Q_UNUSED(shader_id)
|
||||
return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/flip.frag"));
|
||||
}
|
||||
|
||||
void FlipDistortNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
|
||||
{
|
||||
ShaderJob job;
|
||||
|
||||
job.InsertValue(value);
|
||||
|
||||
// If there's no texture, no need to run an operation
|
||||
if (!job.GetValue(kTextureInput).data().isNull()) {
|
||||
// Only run shader if at least one of flip or flop are selected
|
||||
if (job.GetValue(kHorizontalInput).data().toBool() || job.GetValue(kVerticalInput).data().toBool()) {
|
||||
table->Push(NodeValue::kShaderJob, QVariant::fromValue(job), this);
|
||||
} else {
|
||||
// If we're not flipping or flopping just push the texture
|
||||
table->Push(job.GetValue(kTextureInput));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/***
|
||||
|
||||
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 FLIPDISTORTNODE_H
|
||||
#define FLIPDISTORTNODE_H
|
||||
|
||||
#include "node/node.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class FlipDistortNode : public Node
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
FlipDistortNode();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(FlipDistortNode)
|
||||
|
||||
virtual Node* copy() const override;
|
||||
|
||||
virtual QString Name() const override;
|
||||
virtual QString id() const override;
|
||||
virtual QVector<CategoryID> Category() const override;
|
||||
virtual QString Description() const override;
|
||||
|
||||
virtual void Retranslate() override;
|
||||
|
||||
virtual ShaderCode GetShaderCode(const QString &shader_id) const override;
|
||||
virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override;
|
||||
|
||||
static const QString kTextureInput;
|
||||
static const QString kHorizontalInput;
|
||||
static const QString kVerticalInput;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FLIPDISTORTNODE_H
|
||||
@@ -301,13 +301,11 @@ void TransformDistortNode::GizmoMove(const QPointF &p, const rational &time, con
|
||||
}
|
||||
}
|
||||
|
||||
void TransformDistortNode::GizmoRelease()
|
||||
void TransformDistortNode::GizmoRelease(MultiUndoCommand *command)
|
||||
{
|
||||
MultiUndoCommand *command = new MultiUndoCommand();
|
||||
for (NodeInputDragger& i : gizmo_dragger_) {
|
||||
i.End(command);
|
||||
}
|
||||
Core::instance()->undo_stack()->push(command);
|
||||
gizmo_dragger_.clear();
|
||||
|
||||
gizmo_start_.clear();
|
||||
@@ -475,9 +473,6 @@ void TransformDistortNode::DrawGizmos(const NodeValueRow &row, const NodeGlobals
|
||||
anchor_pt.x(), anchor_pt.y() + anchor_pt_radius)});
|
||||
|
||||
// Draw scale handles
|
||||
p->setPen(Qt::NoPen);
|
||||
p->setBrush(Qt::white);
|
||||
|
||||
gizmo_resize_handle_[kGizmoScaleTopLeft] = CreateGizmoHandleRect(CreateScalePoint(-1, -1, sequence_half_res_pt, rectangle_matrix), resize_handle_rad);
|
||||
gizmo_resize_handle_[kGizmoScaleTopCenter] = CreateGizmoHandleRect(CreateScalePoint( 0, -1, sequence_half_res_pt, rectangle_matrix), resize_handle_rad);
|
||||
gizmo_resize_handle_[kGizmoScaleTopRight] = CreateGizmoHandleRect(CreateScalePoint( 1, -1, sequence_half_res_pt, rectangle_matrix), resize_handle_rad);
|
||||
|
||||
@@ -78,7 +78,7 @@ public:
|
||||
|
||||
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() override;
|
||||
virtual void GizmoRelease(MultiUndoCommand *command) override;
|
||||
|
||||
enum AutoScaleType {
|
||||
kAutoScaleNone,
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
add_subdirectory(opacity)
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
dialog/nodeproperties/nodepropertiesdialog.cpp
|
||||
dialog/nodeproperties/nodepropertiesdialog.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -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/effect/opacity/opacityeffect.cpp
|
||||
node/effect/opacity/opacityeffect.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,38 @@
|
||||
#include "opacityeffect.h"
|
||||
|
||||
#include "node/math/math/math.h"
|
||||
#include "widget/slider/floatslider.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
#define super NodeGroup
|
||||
|
||||
OpacityEffect::OpacityEffect()
|
||||
{
|
||||
MathNode *math = new MathNode();
|
||||
|
||||
math->SetOperation(MathNode::kOpMultiply);
|
||||
|
||||
SetNodePositionInContext(math, QPointF(0, 0));
|
||||
|
||||
tex_in_pass_ = AddInputPassthrough(NodeInput(math, MathNode::kParamAIn), InputFlags(kInputFlagNotKeyframable));
|
||||
SetInputDataType(tex_in_pass_, NodeValue::kTexture);
|
||||
|
||||
value_in_pass_ = AddInputPassthrough(NodeInput(math, MathNode::kParamBIn));
|
||||
SetInputProperty(value_in_pass_, QStringLiteral("view"), FloatSlider::kPercentage);
|
||||
SetInputProperty(value_in_pass_, QStringLiteral("min"), 0.0);
|
||||
SetInputProperty(value_in_pass_, QStringLiteral("max"), 1.0);
|
||||
math->SetStandardValue(MathNode::kParamBIn, 1.0);
|
||||
|
||||
SetOutputPassthrough(math);
|
||||
}
|
||||
|
||||
void OpacityEffect::Retranslate()
|
||||
{
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(tex_in_pass_, tr("Texture"));
|
||||
SetInputName(value_in_pass_, tr("Opacity"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#ifndef OPACITYEFFECT_H
|
||||
#define OPACITYEFFECT_H
|
||||
|
||||
#include "node/group/group.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class OpacityEffect : public NodeGroup
|
||||
{
|
||||
public:
|
||||
OpacityEffect();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(OpacityEffect)
|
||||
|
||||
NODE_COPY_FUNCTION(OpacityEffect)
|
||||
|
||||
virtual QString Name() const override
|
||||
{
|
||||
return tr("Opacity");
|
||||
}
|
||||
|
||||
virtual QString id() const override
|
||||
{
|
||||
return QStringLiteral("org.olivevideoeditor.Olive.opacityeffect");
|
||||
}
|
||||
|
||||
virtual QVector<CategoryID> Category() const override
|
||||
{
|
||||
return {kCategoryFilter, kCategoryVideoEffect};
|
||||
}
|
||||
|
||||
virtual QString Description() const override
|
||||
{
|
||||
return tr("Alter a video's opacity.\n\nThis is equivalent to multiplying a video by a number between 0.0 and 1.0.");
|
||||
}
|
||||
|
||||
virtual void Retranslate() override;
|
||||
|
||||
private:
|
||||
QString tex_in_pass_;
|
||||
QString value_in_pass_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OPACITYEFFECT_H
|
||||
@@ -30,12 +30,16 @@
|
||||
#include "block/transition/crossdissolve/crossdissolvetransition.h"
|
||||
#include "block/transition/diptocolor/diptocolortransition.h"
|
||||
#include "distort/crop/cropdistortnode.h"
|
||||
#include "distort/flip/flipdistortnode.h"
|
||||
#include "distort/transform/transformdistortnode.h"
|
||||
#include "effect/opacity/opacityeffect.h"
|
||||
#include "generator/matrix/matrix.h"
|
||||
#include "generator/noise/noise.h"
|
||||
#include "generator/polygon/polygon.h"
|
||||
#include "generator/shape/shapenode.h"
|
||||
#include "generator/solid/solid.h"
|
||||
#include "generator/text/text.h"
|
||||
#include "generator/text/textlegacy.h"
|
||||
#include "filter/blur/blur.h"
|
||||
#include "filter/mosaic/mosaicfilternode.h"
|
||||
#include "filter/stroke/stroke.h"
|
||||
@@ -53,6 +57,7 @@
|
||||
|
||||
namespace olive {
|
||||
QList<Node*> NodeFactory::library_;
|
||||
QVector<int> NodeFactory::hidden_;
|
||||
|
||||
void NodeFactory::Initialize()
|
||||
{
|
||||
@@ -64,6 +69,9 @@ void NodeFactory::Initialize()
|
||||
|
||||
library_.append(created_node);
|
||||
}
|
||||
|
||||
hidden_.append(kTextGeneratorLegacy);
|
||||
hidden_.append(kGroupNode);
|
||||
}
|
||||
|
||||
void NodeFactory::Destroy()
|
||||
@@ -85,6 +93,11 @@ Menu *NodeFactory::CreateMenu(QWidget* parent, bool create_none_item, Node::Cate
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hidden_.contains(i)) {
|
||||
// Skip this node
|
||||
continue;
|
||||
}
|
||||
|
||||
// Make sure nodes are up-to-date with the current translation
|
||||
n->Retranslate();
|
||||
|
||||
@@ -214,6 +227,8 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id)
|
||||
return new MergeNode();
|
||||
case kStrokeFilter:
|
||||
return new StrokeFilterNode();
|
||||
case kTextGeneratorLegacy:
|
||||
return new TextGeneratorLegacy();
|
||||
case kTextGenerator:
|
||||
return new TextGenerator();
|
||||
case kCrossDissolveTransition:
|
||||
@@ -238,6 +253,14 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id)
|
||||
return new SubtitleBlock();
|
||||
case kShapeGenerator:
|
||||
return new ShapeNode();
|
||||
case kGroupNode:
|
||||
return new NodeGroup();
|
||||
case kOpacityEffect:
|
||||
return new OpacityEffect();
|
||||
case kFlipDistort:
|
||||
return new FlipDistortNode();
|
||||
case kNoiseGenerator:
|
||||
return new NoiseGeneratorNode();
|
||||
|
||||
case kInternalNodeCount:
|
||||
break;
|
||||
|
||||
@@ -48,6 +48,7 @@ public:
|
||||
kSolidGenerator,
|
||||
kMerge,
|
||||
kStrokeFilter,
|
||||
kTextGeneratorLegacy,
|
||||
kTextGenerator,
|
||||
kCrossDissolveTransition,
|
||||
kDipToColorTransition,
|
||||
@@ -60,6 +61,10 @@ public:
|
||||
kTimeRemapNode,
|
||||
kSubtitleBlock,
|
||||
kShapeGenerator,
|
||||
kGroupNode,
|
||||
kOpacityEffect,
|
||||
kFlipDistort,
|
||||
kNoiseGenerator,
|
||||
|
||||
// Count value
|
||||
kInternalNodeCount
|
||||
@@ -86,6 +91,8 @@ public:
|
||||
private:
|
||||
static QList<Node*> library_;
|
||||
|
||||
static QVector<int> hidden_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
add_subdirectory(matrix)
|
||||
add_subdirectory(noise)
|
||||
add_subdirectory(polygon)
|
||||
add_subdirectory(shape)
|
||||
add_subdirectory(solid)
|
||||
|
||||
@@ -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/generator/noise/noise.h
|
||||
node/generator/noise/noise.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,82 @@
|
||||
/***
|
||||
|
||||
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 "noise.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
const QString NoiseGeneratorNode::kColorInput = QStringLiteral("color_in");
|
||||
const QString NoiseGeneratorNode::kStrengthInput = QStringLiteral("strength_in");
|
||||
|
||||
NoiseGeneratorNode::NoiseGeneratorNode()
|
||||
{
|
||||
AddInput(kStrengthInput, NodeValue::kFloat, 20);
|
||||
|
||||
AddInput(kColorInput, NodeValue::kBoolean, false);
|
||||
}
|
||||
|
||||
Node* NoiseGeneratorNode::copy() const
|
||||
{
|
||||
return new NoiseGeneratorNode();
|
||||
}
|
||||
|
||||
QString NoiseGeneratorNode::Name() const
|
||||
{
|
||||
return tr("Noise");
|
||||
}
|
||||
|
||||
QString NoiseGeneratorNode::id() const
|
||||
{
|
||||
return QStringLiteral("org.olivevideoeditor.Olive.noise");
|
||||
}
|
||||
|
||||
QVector<Node::CategoryID> NoiseGeneratorNode::Category() const
|
||||
{
|
||||
return {kCategoryGenerator};
|
||||
}
|
||||
|
||||
QString NoiseGeneratorNode::Description() const
|
||||
{
|
||||
return tr("Generates noise patterns");
|
||||
}
|
||||
|
||||
void NoiseGeneratorNode::Retranslate()
|
||||
{
|
||||
SetInputName(kStrengthInput, tr("Strength"));
|
||||
SetInputName(kColorInput, tr("Color"));
|
||||
}
|
||||
|
||||
ShaderCode NoiseGeneratorNode::GetShaderCode(const QString& shader_id) const {
|
||||
Q_UNUSED(shader_id)
|
||||
return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/noise.frag"));
|
||||
}
|
||||
|
||||
void NoiseGeneratorNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
|
||||
{
|
||||
ShaderJob job;
|
||||
|
||||
job.InsertValue(value);
|
||||
job.InsertValue(QStringLiteral("time_in"), NodeValue(NodeValue::kFloat, globals.time().in().toDouble(), this));
|
||||
|
||||
|
||||
table->Push(NodeValue::kShaderJob, QVariant::fromValue(job), this);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef NOISEGENERATORNODE_H
|
||||
#define NOISEGENERATORNODE_H
|
||||
|
||||
#include "node/node.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class NoiseGeneratorNode : public Node {
|
||||
Q_OBJECT
|
||||
public:
|
||||
NoiseGeneratorNode();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(NoiseGeneratorNode)
|
||||
|
||||
virtual Node *copy() const override;
|
||||
|
||||
virtual QString Name() const override;
|
||||
virtual QString id() const override;
|
||||
virtual QVector<CategoryID> Category() const override;
|
||||
virtual QString Description() const override;
|
||||
|
||||
virtual void Retranslate() override;
|
||||
|
||||
virtual ShaderCode GetShaderCode(const QString &shader_id) const override;
|
||||
virtual void Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const override;
|
||||
|
||||
static const QString kColorInput;
|
||||
static const QString kStrengthInput;
|
||||
};
|
||||
|
||||
} // namespace olive
|
||||
|
||||
#endif // NOISEGENERATORNODE_H
|
||||
@@ -23,6 +23,8 @@
|
||||
#include <QGuiApplication>
|
||||
#include <QVector2D>
|
||||
|
||||
#include "common/cpuoptimize.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
const QString PolygonGenerator::kPointsInput = QStringLiteral("points_in");
|
||||
@@ -30,22 +32,28 @@ const QString PolygonGenerator::kColorInput = QStringLiteral("color_in");
|
||||
|
||||
PolygonGenerator::PolygonGenerator()
|
||||
{
|
||||
AddInput(kPointsInput, NodeValue::kVec2, QVector2D(0, 0), InputFlags(kInputFlagArray));
|
||||
AddInput(kPointsInput, NodeValue::kBezier, QVector2D(0, 0), InputFlags(kInputFlagArray));
|
||||
|
||||
AddInput(kColorInput, NodeValue::kColor, QVariant::fromValue(Color(1.0, 1.0, 1.0)));
|
||||
|
||||
const int kMiddleX = 135;
|
||||
const int kMiddleY = 45;
|
||||
const int kBottomX = 90;
|
||||
const int kBottomY = 120;
|
||||
const int kTopY = 135;
|
||||
|
||||
// The Default Pentagon(tm)
|
||||
InputArrayResize(kPointsInput, 5);
|
||||
SetSplitStandardValueOnTrack(kPointsInput, 0, 960, 0);
|
||||
SetSplitStandardValueOnTrack(kPointsInput, 1, 240, 0);
|
||||
SetSplitStandardValueOnTrack(kPointsInput, 0, 640, 1);
|
||||
SetSplitStandardValueOnTrack(kPointsInput, 1, 480, 1);
|
||||
SetSplitStandardValueOnTrack(kPointsInput, 0, 760, 2);
|
||||
SetSplitStandardValueOnTrack(kPointsInput, 1, 800, 2);
|
||||
SetSplitStandardValueOnTrack(kPointsInput, 0, 1100, 3);
|
||||
SetSplitStandardValueOnTrack(kPointsInput, 1, 800, 3);
|
||||
SetSplitStandardValueOnTrack(kPointsInput, 0, 1200, 4);
|
||||
SetSplitStandardValueOnTrack(kPointsInput, 1, 480, 4);
|
||||
SetSplitStandardValueOnTrack(kPointsInput, 0, 0, 0);
|
||||
SetSplitStandardValueOnTrack(kPointsInput, 1, -kTopY, 0);
|
||||
SetSplitStandardValueOnTrack(kPointsInput, 0, kMiddleX, 1);
|
||||
SetSplitStandardValueOnTrack(kPointsInput, 1, -kMiddleY, 1);
|
||||
SetSplitStandardValueOnTrack(kPointsInput, 0, kBottomX, 2);
|
||||
SetSplitStandardValueOnTrack(kPointsInput, 1, kBottomY, 2);
|
||||
SetSplitStandardValueOnTrack(kPointsInput, 0, -kBottomX, 3);
|
||||
SetSplitStandardValueOnTrack(kPointsInput, 1, kBottomY, 3);
|
||||
SetSplitStandardValueOnTrack(kPointsInput, 0, -kMiddleX, 4);
|
||||
SetSplitStandardValueOnTrack(kPointsInput, 1, -kMiddleY, 4);
|
||||
}
|
||||
|
||||
Node *PolygonGenerator::copy() const
|
||||
@@ -79,22 +87,66 @@ void PolygonGenerator::Retranslate()
|
||||
SetInputName(kColorInput, tr("Color"));
|
||||
}
|
||||
|
||||
ShaderCode PolygonGenerator::GetShaderCode(const QString &shader_id) const
|
||||
{
|
||||
Q_UNUSED(shader_id)
|
||||
|
||||
return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/polygon.frag")));
|
||||
}
|
||||
|
||||
void PolygonGenerator::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
|
||||
{
|
||||
ShaderJob job;
|
||||
GenerateJob job;
|
||||
|
||||
job.InsertValue(value);
|
||||
job.InsertValue(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this));
|
||||
job.SetRequestedFormat(VideoParams::kFormatFloat32);
|
||||
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
|
||||
|
||||
table->Push(NodeValue::kShaderJob, QVariant::fromValue(job), this);
|
||||
table->Push(NodeValue::kGenerateJob, QVariant::fromValue(job), this);
|
||||
}
|
||||
|
||||
void PolygonGenerator::GenerateFrame(FramePtr frame, const GenerateJob &job) const
|
||||
{
|
||||
// This could probably be more optimized, but for now we use Qt to draw to a QImage.
|
||||
// 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);
|
||||
img.fill(Qt::transparent);
|
||||
|
||||
QVector<NodeValue> points = job.GetValue(kPointsInput).data().value< QVector<NodeValue> >();
|
||||
|
||||
QPainterPath path = GeneratePath(points);
|
||||
|
||||
QPainter p(&img);
|
||||
double par = frame->video_params().pixel_aspect_ratio().toDouble();
|
||||
p.scale(1.0 / frame->video_params().divider() / par, 1.0 / frame->video_params().divider());
|
||||
p.translate(frame->video_params().width()/2 * par, frame->video_params().height()/2);
|
||||
p.setBrush(Qt::white);
|
||||
p.setPen(Qt::NoPen);
|
||||
|
||||
p.drawPath(path);
|
||||
|
||||
// Transplant alpha channel to frame
|
||||
Color rgba = job.GetValue(kColorInput).data().value<Color>();
|
||||
#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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool PolygonGenerator::HasGizmos() const
|
||||
@@ -102,100 +154,159 @@ bool PolygonGenerator::HasGizmos() const
|
||||
return true;
|
||||
}
|
||||
|
||||
/*void PolygonGenerator::DrawGizmos(NodeValueDatabase &db, QPainter *p) const
|
||||
void PolygonGenerator::DrawGizmos(const NodeValueRow &row, const NodeGlobals &globals, QPainter *p)
|
||||
{
|
||||
Q_UNUSED(viewport)
|
||||
|
||||
if (!points_input_->GetSize()) {
|
||||
return;
|
||||
}
|
||||
const double handle_radius = GetGizmoHandleRadius(p->transform());
|
||||
const double bezier_radius = handle_radius/2;
|
||||
|
||||
p->setPen(Qt::white);
|
||||
p->setBrush(Qt::white);
|
||||
p->translate(globals.resolution_by_par().x()/2, globals.resolution_by_par().y()/2);
|
||||
|
||||
QVector<QPointF> points = GetGizmoCoordinates(db, scale);
|
||||
QVector<QRectF> rects = GetGizmoRects(points);
|
||||
QVector<NodeValue> points = row[kPointsInput].data().value< QVector<NodeValue> >();
|
||||
|
||||
points.append(points.first());
|
||||
gizmo_position_handles_.resize(points.size());
|
||||
gizmo_bezier_handles_.resize(points.size() * 2);
|
||||
|
||||
p->drawPolyline(points.constData(), points.size());
|
||||
p->drawRects(rects);
|
||||
p->setPen(QPen(Qt::white, 0));
|
||||
p->setBrush(Qt::NoBrush);
|
||||
|
||||
if (!points.isEmpty()) {
|
||||
QVector<QLineF> lines(points.size() * 2);
|
||||
|
||||
for (int i=0; i<points.size(); i++) {
|
||||
const Bezier &pt = points.at(i).data().value<Bezier>();
|
||||
|
||||
QPointF main = pt.ToPointF();
|
||||
QPointF cp1 = main + pt.ControlPoint1ToPointF();
|
||||
QPointF cp2 = main + pt.ControlPoint2ToPointF();
|
||||
|
||||
gizmo_position_handles_[i] = CreateGizmoHandleRect(main, handle_radius);
|
||||
|
||||
gizmo_bezier_handles_[i*2] = CreateGizmoHandleRect(cp1, bezier_radius);
|
||||
lines[i*2] = QLineF(main, cp1);
|
||||
|
||||
gizmo_bezier_handles_[i*2+1] = CreateGizmoHandleRect(cp2, bezier_radius);
|
||||
lines[i*2+1] = QLineF(main, cp2);
|
||||
}
|
||||
|
||||
p->drawLines(lines);
|
||||
}
|
||||
|
||||
gizmo_polygon_path_ = GeneratePath(points);
|
||||
p->drawPath(gizmo_polygon_path_);
|
||||
|
||||
DrawAndExpandGizmoHandles(p, handle_radius, gizmo_position_handles_.data(), gizmo_position_handles_.size());
|
||||
DrawAndExpandGizmoHandles(p, handle_radius, gizmo_bezier_handles_.data(), gizmo_bezier_handles_.size());
|
||||
}
|
||||
|
||||
bool PolygonGenerator::GizmoPress(NodeValueDatabase &db, const QPointF &p, const QVector2D &scale, const QSize& viewport)
|
||||
bool PolygonGenerator::GizmoPress(const NodeValueRow& row, const NodeGlobals &globals, const QPointF &p)
|
||||
{
|
||||
Q_UNUSED(viewport)
|
||||
QPointF adjusted = p - (globals.resolution_by_par() / 2).toPointF();
|
||||
|
||||
QVector<QPointF> points = GetGizmoCoordinates(db, scale);
|
||||
QVector<QRectF> rects = GetGizmoRects(points);
|
||||
// First, look for main points
|
||||
|
||||
for (int i=0;i<rects.size();i++) {
|
||||
const QRectF& r = rects.at(i);
|
||||
|
||||
if (r.contains(p)) {
|
||||
gizmo_drag_ = points_input_->At(i);
|
||||
gizmo_drag_start_ = points.at(i);
|
||||
return true;
|
||||
for (int i=0; i<gizmo_position_handles_.size(); i++) {
|
||||
if (gizmo_position_handles_.at(i).contains(adjusted)) {
|
||||
gizmo_x_active_.append(NodeKeyframeTrackReference(NodeInput(this, kPointsInput, i), 0));
|
||||
gizmo_y_active_.append(NodeKeyframeTrackReference(NodeInput(this, kPointsInput, i), 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
// Next, if no main points were found, look for beziers
|
||||
if (gizmo_x_active_.isEmpty() && gizmo_y_active_.isEmpty()) {
|
||||
for (int i=0; i<gizmo_bezier_handles_.size(); i++) {
|
||||
if (gizmo_bezier_handles_.at(i).contains(adjusted)) {
|
||||
int start = (i%2 == 0) ? 2 : 4;
|
||||
int element = i/2;
|
||||
gizmo_x_active_.append(NodeKeyframeTrackReference(NodeInput(this, kPointsInput, element), start + 0));
|
||||
gizmo_y_active_.append(NodeKeyframeTrackReference(NodeInput(this, kPointsInput, element), start + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void PolygonGenerator::GizmoMove(const QPointF &p, const QVector2D &scale, const rational& time)
|
||||
{
|
||||
QVector2D new_pos = QVector2D(p) / scale;
|
||||
|
||||
if (!gizmo_x_dragger_.IsStarted()) {
|
||||
gizmo_x_dragger_.Start(gizmo_drag_, time, 0);
|
||||
// Finally, see if the cursor is inside the polygon
|
||||
if (gizmo_x_active_.isEmpty() && gizmo_y_active_.isEmpty()) {
|
||||
if (gizmo_polygon_path_.contains(adjusted)) {
|
||||
gizmo_x_active_.resize(gizmo_position_handles_.size());
|
||||
gizmo_y_active_.resize(gizmo_position_handles_.size());
|
||||
for (int i=0; i<gizmo_position_handles_.size(); i++) {
|
||||
gizmo_x_active_[i] = NodeKeyframeTrackReference(NodeInput(this, kPointsInput, i), 0);
|
||||
gizmo_y_active_[i] = NodeKeyframeTrackReference(NodeInput(this, kPointsInput, i), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!gizmo_y_dragger_.IsStarted()) {
|
||||
gizmo_y_dragger_.Start(gizmo_drag_, time, 1);
|
||||
gizmo_drag_start_ = p;
|
||||
|
||||
return !gizmo_x_active_.isEmpty() || !gizmo_y_active_.isEmpty();
|
||||
}
|
||||
|
||||
void PolygonGenerator::GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers &modifiers)
|
||||
{
|
||||
if (gizmo_x_draggers_.isEmpty() && gizmo_y_draggers_.isEmpty()) {
|
||||
gizmo_x_draggers_.resize(gizmo_x_active_.size());
|
||||
gizmo_y_draggers_.resize(gizmo_y_active_.size());
|
||||
for (int i=0; i<gizmo_x_active_.size(); i++) {
|
||||
gizmo_x_draggers_[i].Start(gizmo_x_active_.at(i), time);
|
||||
}
|
||||
for (int i=0; i<gizmo_y_active_.size(); i++) {
|
||||
gizmo_y_draggers_[i].Start(gizmo_y_active_.at(i), time);
|
||||
}
|
||||
}
|
||||
|
||||
gizmo_x_dragger_.Drag(new_pos.x());
|
||||
gizmo_y_dragger_.Drag(new_pos.y());
|
||||
}
|
||||
QPointF diff = p - gizmo_drag_start_;
|
||||
|
||||
void PolygonGenerator::GizmoRelease()
|
||||
{
|
||||
gizmo_x_dragger_.End();
|
||||
gizmo_y_dragger_.End();
|
||||
}*/
|
||||
|
||||
QVector<QPointF> PolygonGenerator::GetGizmoCoordinates(NodeValueDatabase &db, const QVector2D& scale) const
|
||||
{
|
||||
// FIXME: Should Get() use a `kArray` type instead of a `kVec2` type?
|
||||
QVector<NodeValueTable> array_tbl = db[kPointsInput].Get(NodeValue::kVec2).value< QVector<NodeValueTable> >();
|
||||
QVector<QPointF> points(array_tbl.size());
|
||||
|
||||
for (int i=0;i<array_tbl.size();i++) {
|
||||
QVector2D v = array_tbl.at(i).Get(NodeValue::kVec2).value<QVector2D>();
|
||||
|
||||
v *= scale;
|
||||
|
||||
QPointF pt = v.toPointF();
|
||||
points[i] = pt;
|
||||
for (NodeInputDragger &dragger : gizmo_x_draggers_) {
|
||||
dragger.Drag(dragger.GetStartValue().toDouble() + diff.x());
|
||||
}
|
||||
|
||||
return points;
|
||||
for (NodeInputDragger &dragger : gizmo_y_draggers_) {
|
||||
dragger.Drag(dragger.GetStartValue().toDouble() + diff.y());
|
||||
}
|
||||
}
|
||||
|
||||
QVector<QRectF> PolygonGenerator::GetGizmoRects(const QVector<QPointF> &points) const
|
||||
void PolygonGenerator::GizmoRelease(MultiUndoCommand *command)
|
||||
{
|
||||
QVector<QRectF> rects(points.size());
|
||||
for (NodeInputDragger &dragger : gizmo_x_draggers_) {
|
||||
dragger.End(command);
|
||||
}
|
||||
gizmo_x_draggers_.clear();
|
||||
|
||||
int rect_sz = QFontMetrics(qApp->font()).height() / 8;
|
||||
for (NodeInputDragger &dragger : gizmo_y_draggers_) {
|
||||
dragger.End(command);
|
||||
}
|
||||
gizmo_y_draggers_.clear();
|
||||
|
||||
for (int i=0;i<points.size();i++) {
|
||||
const QPointF& p = points.at(i);
|
||||
gizmo_x_active_.clear();
|
||||
gizmo_y_active_.clear();
|
||||
}
|
||||
|
||||
rects[i] = QRectF(p - QPointF(rect_sz, rect_sz),
|
||||
p + QPointF(rect_sz, rect_sz));
|
||||
void PolygonGenerator::AddPointToPath(QPainterPath *path, const Bezier &before, const Bezier &after)
|
||||
{
|
||||
path->cubicTo(before.ToPointF() + before.ControlPoint2ToPointF(),
|
||||
after.ToPointF() + after.ControlPoint1ToPointF(),
|
||||
after.ToPointF());
|
||||
}
|
||||
|
||||
QPainterPath PolygonGenerator::GeneratePath(const QVector<NodeValue> &points)
|
||||
{
|
||||
QPainterPath path;
|
||||
|
||||
if (!points.isEmpty()) {
|
||||
const Bezier &first_pt = points.first().data().value<Bezier>();
|
||||
path.moveTo(first_pt.ToPointF());
|
||||
|
||||
for (int i=1; i<points.size(); i++) {
|
||||
AddPointToPath(&path, points.at(i-1).data().value<Bezier>(), points.at(i).data().value<Bezier>());
|
||||
}
|
||||
|
||||
AddPointToPath(&path, points.last().data().value<Bezier>(), first_pt);
|
||||
}
|
||||
|
||||
return rects;
|
||||
return path;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
#ifndef POLYGONGENERATOR_H
|
||||
#define POLYGONGENERATOR_H
|
||||
|
||||
#include <QPainterPath>
|
||||
|
||||
#include "common/bezier.h"
|
||||
#include "node/node.h"
|
||||
#include "node/inputdragger.h"
|
||||
|
||||
@@ -43,30 +46,35 @@ public:
|
||||
|
||||
virtual void Retranslate() override;
|
||||
|
||||
virtual ShaderCode GetShaderCode(const QString& shader_id) const override;
|
||||
virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override;
|
||||
|
||||
virtual bool HasGizmos() const override;
|
||||
//virtual void DrawGizmos(NodeValueDatabase& db, QPainter *p) const override;
|
||||
virtual void GenerateFrame(FramePtr frame, const GenerateJob &job) const override;
|
||||
|
||||
//virtual bool GizmoPress(NodeValueDatabase &db, const QPointF &p) override;
|
||||
//virtual void GizmoMove(const QPointF &p, const QVector2D &scale, const rational &time) override;
|
||||
//virtual void GizmoRelease() override;
|
||||
virtual bool HasGizmos() const override;
|
||||
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;
|
||||
|
||||
static const QString kPointsInput;
|
||||
static const QString kColorInput;
|
||||
|
||||
private:
|
||||
QVector<QPointF> GetGizmoCoordinates(NodeValueDatabase &db, const QVector2D &scale) const;
|
||||
static void AddPointToPath(QPainterPath *path, const Bezier &before, const Bezier &after);
|
||||
|
||||
QVector<QRectF> GetGizmoRects(const QVector<QPointF>& points) const;
|
||||
static QPainterPath GeneratePath(const QVector<NodeValue> &points);
|
||||
|
||||
NodeInput* gizmo_drag_;
|
||||
QPainterPath gizmo_polygon_path_;
|
||||
QVector<QRectF> gizmo_position_handles_;
|
||||
QVector<QRectF> gizmo_bezier_handles_;
|
||||
|
||||
QVector<NodeKeyframeTrackReference> gizmo_x_active_;
|
||||
QVector<NodeKeyframeTrackReference> gizmo_y_active_;
|
||||
QVector<NodeInputDragger> gizmo_x_draggers_;
|
||||
QVector<NodeInputDragger> gizmo_y_draggers_;
|
||||
QPointF gizmo_drag_start_;
|
||||
|
||||
NodeInputDragger gizmo_x_dragger_;
|
||||
NodeInputDragger gizmo_y_dragger_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -85,8 +85,6 @@ void ShapeNodeBase::DrawGizmos(const NodeValueRow &row, const NodeGlobals &globa
|
||||
gizmo_resize_handle_[kGizmoScaleCenterLeft] = CreateGizmoHandleRect(QPointF(left_pt, center_y_pt), handle_radius);
|
||||
gizmo_resize_handle_[kGizmoScaleCenterRight] = CreateGizmoHandleRect(QPointF(right_pt, center_y_pt), handle_radius);
|
||||
|
||||
p->setPen(Qt::NoPen);
|
||||
p->setBrush(Qt::white);
|
||||
DrawAndExpandGizmoHandles(p, handle_radius, gizmo_resize_handle_, kGizmoScaleCount);
|
||||
}
|
||||
|
||||
@@ -297,13 +295,11 @@ void ShapeNodeBase::GizmoMove(const QPointF &p, const rational &time, const Qt::
|
||||
}
|
||||
}
|
||||
|
||||
void ShapeNodeBase::GizmoRelease()
|
||||
void ShapeNodeBase::GizmoRelease(MultiUndoCommand *command)
|
||||
{
|
||||
MultiUndoCommand *command = new MultiUndoCommand();
|
||||
for (NodeInputDragger& i : gizmo_dragger_) {
|
||||
i.End(command);
|
||||
}
|
||||
Core::instance()->undo_stack()->push(command);
|
||||
gizmo_dragger_.clear();
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ public:
|
||||
|
||||
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() override;
|
||||
virtual void GizmoRelease(MultiUndoCommand *command) override;
|
||||
|
||||
private:
|
||||
static QVector2D GenerateGizmoAnchor(const QVector2D &pos, const QVector2D &size, int drag, QVector2D *pt);
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/generator/text/text.h
|
||||
node/generator/text/text.cpp
|
||||
node/generator/text/text.h
|
||||
node/generator/text/textlegacy.cpp
|
||||
node/generator/text/textlegacy.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
@@ -21,10 +21,16 @@
|
||||
#include "text.h"
|
||||
|
||||
#include <QAbstractTextDocumentLayout>
|
||||
#include <QDateTime>
|
||||
#include <QTextDocument>
|
||||
|
||||
#include "common/cpuoptimize.h"
|
||||
#include "common/functiontimer.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
#define super ShapeNodeBase
|
||||
|
||||
enum TextVerticalAlign {
|
||||
kVerticalAlignTop,
|
||||
kVerticalAlignCenter,
|
||||
@@ -33,7 +39,6 @@ enum TextVerticalAlign {
|
||||
|
||||
const QString TextGenerator::kTextInput = QStringLiteral("text_in");
|
||||
const QString TextGenerator::kHtmlInput = QStringLiteral("html_in");
|
||||
const QString TextGenerator::kColorInput = QStringLiteral("color_in");
|
||||
const QString TextGenerator::kVAlignInput = QStringLiteral("valign_in");
|
||||
const QString TextGenerator::kFontInput = QStringLiteral("font_in");
|
||||
const QString TextGenerator::kFontSizeInput = QStringLiteral("font_size_in");
|
||||
@@ -44,18 +49,14 @@ TextGenerator::TextGenerator()
|
||||
|
||||
AddInput(kHtmlInput, NodeValue::kBoolean, false);
|
||||
|
||||
AddInput(kColorInput, NodeValue::kColor, QVariant::fromValue(Color(1.0f, 1.0f, 1.0)));
|
||||
|
||||
AddInput(kVAlignInput, NodeValue::kCombo, 1);
|
||||
AddInput(kVAlignInput, NodeValue::kCombo, kVerticalAlignTop);
|
||||
|
||||
AddInput(kFontInput, NodeValue::kFont);
|
||||
|
||||
AddInput(kFontSizeInput, NodeValue::kFloat, 72.0f);
|
||||
}
|
||||
|
||||
Node *TextGenerator::copy() const
|
||||
{
|
||||
return new TextGenerator();
|
||||
SetStandardValue(kColorInput, QVariant::fromValue(Color(1.0f, 1.0f, 1.0)));
|
||||
SetStandardValue(kSizeInput, QVector2D(400, 300));
|
||||
}
|
||||
|
||||
QString TextGenerator::Name() const
|
||||
@@ -65,7 +66,7 @@ QString TextGenerator::Name() const
|
||||
|
||||
QString TextGenerator::id() const
|
||||
{
|
||||
return QStringLiteral("org.olivevideoeditor.Olive.textgenerator");
|
||||
return QStringLiteral("org.olivevideoeditor.Olive.text2");
|
||||
}
|
||||
|
||||
QVector<Node::CategoryID> TextGenerator::Category() const
|
||||
@@ -80,11 +81,12 @@ QString TextGenerator::Description() const
|
||||
|
||||
void TextGenerator::Retranslate()
|
||||
{
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(kTextInput, tr("Text"));
|
||||
SetInputName(kHtmlInput, tr("Enable HTML"));
|
||||
SetInputName(kFontInput, tr("Font"));
|
||||
SetInputName(kFontSizeInput, tr("Font Size"));
|
||||
SetInputName(kColorInput, tr("Color"));
|
||||
SetInputName(kVAlignInput, tr("Vertical Align"));
|
||||
SetComboBoxStrings(kVAlignInput, {tr("Top"), tr("Center"), tr("Bottom")});
|
||||
}
|
||||
@@ -94,6 +96,7 @@ void TextGenerator::Value(const NodeValueRow &value, const NodeGlobals &globals,
|
||||
GenerateJob job;
|
||||
job.InsertValue(value);
|
||||
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
|
||||
job.SetRequestedFormat(VideoParams::kFormatFloat32);
|
||||
|
||||
if (!job.GetValue(kTextInput).data().toString().isEmpty()) {
|
||||
table->Push(NodeValue::kGenerateJob, QVariant::fromValue(job), this);
|
||||
@@ -107,9 +110,15 @@ void TextGenerator::GenerateFrame(FramePtr frame, const GenerateJob& job) const
|
||||
// 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);
|
||||
img.fill(0);
|
||||
img.fill(Qt::transparent);
|
||||
|
||||
// 72 DPI in DPM (72 / 2.54 * 100)
|
||||
const int dpm = 2835;
|
||||
img.setDotsPerMeterX(dpm);
|
||||
img.setDotsPerMeterY(dpm);
|
||||
|
||||
QTextDocument text_doc;
|
||||
text_doc.documentLayout()->setPaintDevice(&img);
|
||||
|
||||
// Set default font
|
||||
QFont default_font;
|
||||
@@ -117,9 +126,6 @@ void TextGenerator::GenerateFrame(FramePtr frame, const GenerateJob& job) const
|
||||
default_font.setPointSizeF(job.GetValue(kFontSizeInput).data().toFloat());
|
||||
text_doc.setDefaultFont(default_font);
|
||||
|
||||
// Center by default
|
||||
text_doc.setDefaultTextOption(QTextOption(Qt::AlignCenter));
|
||||
|
||||
QString html = job.GetValue(kTextInput).data().toString();
|
||||
if (job.GetValue(kHtmlInput).data().toBool()) {
|
||||
html.replace('\n', QStringLiteral("<br>"));
|
||||
@@ -128,47 +134,65 @@ void TextGenerator::GenerateFrame(FramePtr frame, const GenerateJob& job) const
|
||||
text_doc.setPlainText(html);
|
||||
}
|
||||
|
||||
// Align to 80% width because that's considered the "title safe" area
|
||||
int tenth_of_width = frame->video_params().width() / 10;
|
||||
text_doc.setTextWidth(tenth_of_width * 8);
|
||||
QVector2D size = job.GetValue(kSizeInput).data().value<QVector2D>();
|
||||
text_doc.setTextWidth(size.x());
|
||||
|
||||
// Draw rich text onto image
|
||||
QPainter p(&img);
|
||||
p.scale(1.0 / frame->video_params().divider(), 1.0 / frame->video_params().divider());
|
||||
|
||||
// Push 10% inwards to compensate for title safe area
|
||||
p.translate(tenth_of_width, 0);
|
||||
|
||||
QVector2D pos = job.GetValue(kPositionInput).data().value<QVector2D>();
|
||||
p.translate(pos.x() - size.x()/2, pos.y() - size.y()/2);
|
||||
p.translate(frame->video_params().width()/2, frame->video_params().height()/2);
|
||||
p.setClipRect(0, 0, size.x(), size.y());
|
||||
|
||||
TextVerticalAlign valign = static_cast<TextVerticalAlign>(job.GetValue(kVAlignInput).data().toInt());
|
||||
int doc_height = text_doc.size().height();
|
||||
|
||||
switch (valign) {
|
||||
case kVerticalAlignTop:
|
||||
// Push 10% inwards for title safe area
|
||||
p.translate(0, frame->video_params().height() / 10);
|
||||
// Do nothing
|
||||
break;
|
||||
case kVerticalAlignCenter:
|
||||
// Center align
|
||||
p.translate(0, frame->video_params().height() / 2 - doc_height / 2);
|
||||
p.translate(0, size.y() / 2 - doc_height / 2);
|
||||
break;
|
||||
case kVerticalAlignBottom:
|
||||
// Push 10% inwards for title safe area
|
||||
p.translate(0, frame->video_params().height() - doc_height - frame->video_params().height() / 10);
|
||||
p.translate(0, size.y() - doc_height);
|
||||
break;
|
||||
}
|
||||
|
||||
QAbstractTextDocumentLayout::PaintContext ctx;
|
||||
ctx.palette.setColor(QPalette::Text, Qt::white);
|
||||
|
||||
text_doc.documentLayout()->draw(&p, ctx);
|
||||
|
||||
// Transplant alpha channel to frame
|
||||
Color rgb = job.GetValue(kColorInput).data().value<Color>();
|
||||
for (int x=0; x<frame->width(); x++) {
|
||||
for (int y=0; y<frame->height(); y++) {
|
||||
uchar src_alpha = img.bits()[img.bytesPerLine() * y + x];
|
||||
float alpha = float(src_alpha) / 255.0f;
|
||||
Color rgba = job.GetValue(kColorInput).data().value<Color>();
|
||||
#if defined(Q_PROCESSOR_X86) || defined(Q_PROCESSOR_ARM)
|
||||
__m128 sse_color = _mm_loadu_ps(rgba.data());
|
||||
#endif
|
||||
|
||||
frame->set_pixel(x, y, Color(rgb.red() * alpha, rgb.green() * alpha, rgb.blue() * alpha, alpha));
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,19 +21,18 @@
|
||||
#ifndef TEXTGENERATOR_H
|
||||
#define TEXTGENERATOR_H
|
||||
|
||||
#include "node/node.h"
|
||||
#include "node/generator/shape/shapenodebase.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class TextGenerator : public Node
|
||||
class TextGenerator : public ShapeNodeBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
TextGenerator();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(TextGenerator)
|
||||
|
||||
virtual Node* copy() const override;
|
||||
NODE_COPY_FUNCTION(TextGenerator)
|
||||
|
||||
virtual QString Name() const override;
|
||||
virtual QString id() const override;
|
||||
@@ -48,7 +47,6 @@ public:
|
||||
|
||||
static const QString kTextInput;
|
||||
static const QString kHtmlInput;
|
||||
static const QString kColorInput;
|
||||
static const QString kVAlignInput;
|
||||
static const QString kFontInput;
|
||||
static const QString kFontSizeInput;
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
/***
|
||||
|
||||
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 "textlegacy.h"
|
||||
|
||||
#include <QAbstractTextDocumentLayout>
|
||||
#include <QTextDocument>
|
||||
|
||||
namespace olive {
|
||||
|
||||
enum TextVerticalAlign {
|
||||
kVerticalAlignTop,
|
||||
kVerticalAlignCenter,
|
||||
kVerticalAlignBottom,
|
||||
};
|
||||
|
||||
const QString TextGeneratorLegacy::kTextInput = QStringLiteral("text_in");
|
||||
const QString TextGeneratorLegacy::kHtmlInput = QStringLiteral("html_in");
|
||||
const QString TextGeneratorLegacy::kColorInput = QStringLiteral("color_in");
|
||||
const QString TextGeneratorLegacy::kVAlignInput = QStringLiteral("valign_in");
|
||||
const QString TextGeneratorLegacy::kFontInput = QStringLiteral("font_in");
|
||||
const QString TextGeneratorLegacy::kFontSizeInput = QStringLiteral("font_size_in");
|
||||
|
||||
TextGeneratorLegacy::TextGeneratorLegacy()
|
||||
{
|
||||
AddInput(kTextInput, NodeValue::kText, tr("Sample Text"));
|
||||
|
||||
AddInput(kHtmlInput, NodeValue::kBoolean, false);
|
||||
|
||||
AddInput(kColorInput, NodeValue::kColor, QVariant::fromValue(Color(1.0f, 1.0f, 1.0)));
|
||||
|
||||
AddInput(kVAlignInput, NodeValue::kCombo, 1);
|
||||
|
||||
AddInput(kFontInput, NodeValue::kFont);
|
||||
|
||||
AddInput(kFontSizeInput, NodeValue::kFloat, 72.0f);
|
||||
}
|
||||
|
||||
QString TextGeneratorLegacy::Name() const
|
||||
{
|
||||
return tr("Text (Legacy)");
|
||||
}
|
||||
|
||||
QString TextGeneratorLegacy::id() const
|
||||
{
|
||||
return QStringLiteral("org.olivevideoeditor.Olive.textgenerator");
|
||||
}
|
||||
|
||||
QVector<Node::CategoryID> TextGeneratorLegacy::Category() const
|
||||
{
|
||||
return {kCategoryGenerator};
|
||||
}
|
||||
|
||||
QString TextGeneratorLegacy::Description() const
|
||||
{
|
||||
return tr("Generate rich text.");
|
||||
}
|
||||
|
||||
void TextGeneratorLegacy::Retranslate()
|
||||
{
|
||||
SetInputName(kTextInput, tr("Text"));
|
||||
SetInputName(kHtmlInput, tr("Enable HTML"));
|
||||
SetInputName(kFontInput, tr("Font"));
|
||||
SetInputName(kFontSizeInput, tr("Font Size"));
|
||||
SetInputName(kColorInput, tr("Color"));
|
||||
SetInputName(kVAlignInput, tr("Vertical Align"));
|
||||
SetComboBoxStrings(kVAlignInput, {tr("Top"), tr("Center"), tr("Bottom")});
|
||||
}
|
||||
|
||||
void TextGeneratorLegacy::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
|
||||
{
|
||||
GenerateJob job;
|
||||
job.InsertValue(value);
|
||||
job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
|
||||
|
||||
if (!job.GetValue(kTextInput).data().toString().isEmpty()) {
|
||||
table->Push(NodeValue::kGenerateJob, QVariant::fromValue(job), this);
|
||||
}
|
||||
}
|
||||
|
||||
void TextGeneratorLegacy::GenerateFrame(FramePtr frame, const GenerateJob& job) const
|
||||
{
|
||||
// This could probably be more optimized, but for now we use Qt to draw to a QImage.
|
||||
// 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);
|
||||
img.fill(0);
|
||||
|
||||
QTextDocument text_doc;
|
||||
|
||||
// Set default font
|
||||
QFont default_font;
|
||||
default_font.setFamily(job.GetValue(kFontInput).data().toString());
|
||||
default_font.setPointSizeF(job.GetValue(kFontSizeInput).data().toFloat());
|
||||
text_doc.setDefaultFont(default_font);
|
||||
|
||||
// Center by default
|
||||
text_doc.setDefaultTextOption(QTextOption(Qt::AlignCenter));
|
||||
|
||||
QString html = job.GetValue(kTextInput).data().toString();
|
||||
if (job.GetValue(kHtmlInput).data().toBool()) {
|
||||
html.replace('\n', QStringLiteral("<br>"));
|
||||
text_doc.setHtml(html);
|
||||
} else {
|
||||
text_doc.setPlainText(html);
|
||||
}
|
||||
|
||||
// Align to 80% width because that's considered the "title safe" area
|
||||
int tenth_of_width = frame->video_params().width() / 10;
|
||||
text_doc.setTextWidth(tenth_of_width * 8);
|
||||
|
||||
// Draw rich text onto image
|
||||
QPainter p(&img);
|
||||
p.scale(1.0 / frame->video_params().divider(), 1.0 / frame->video_params().divider());
|
||||
|
||||
// Push 10% inwards to compensate for title safe area
|
||||
p.translate(tenth_of_width, 0);
|
||||
|
||||
TextVerticalAlign valign = static_cast<TextVerticalAlign>(job.GetValue(kVAlignInput).data().toInt());
|
||||
int doc_height = text_doc.size().height();
|
||||
|
||||
switch (valign) {
|
||||
case kVerticalAlignTop:
|
||||
// Push 10% inwards for title safe area
|
||||
p.translate(0, frame->video_params().height() / 10);
|
||||
break;
|
||||
case kVerticalAlignCenter:
|
||||
// Center align
|
||||
p.translate(0, frame->video_params().height() / 2 - doc_height / 2);
|
||||
break;
|
||||
case kVerticalAlignBottom:
|
||||
// Push 10% inwards for title safe area
|
||||
p.translate(0, frame->video_params().height() - doc_height - frame->video_params().height() / 10);
|
||||
break;
|
||||
}
|
||||
|
||||
QAbstractTextDocumentLayout::PaintContext ctx;
|
||||
ctx.palette.setColor(QPalette::Text, Qt::white);
|
||||
text_doc.documentLayout()->draw(&p, ctx);
|
||||
|
||||
// Transplant alpha channel to frame
|
||||
Color rgb = job.GetValue(kColorInput).data().value<Color>();
|
||||
for (int x=0; x<frame->width(); x++) {
|
||||
for (int y=0; y<frame->height(); y++) {
|
||||
uchar src_alpha = img.bits()[img.bytesPerLine() * y + x];
|
||||
float alpha = float(src_alpha) / 255.0f;
|
||||
|
||||
frame->set_pixel(x, y, Color(rgb.red() * alpha, rgb.green() * alpha, rgb.blue() * alpha, alpha));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/***
|
||||
|
||||
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 TEXTGENERATORLEGACY_H
|
||||
#define TEXTGENERATORLEGACY_H
|
||||
|
||||
#include "node/node.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class TextGeneratorLegacy : public Node
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
TextGeneratorLegacy();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(TextGeneratorLegacy)
|
||||
NODE_COPY_FUNCTION(TextGeneratorLegacy)
|
||||
|
||||
virtual QString Name() const override;
|
||||
virtual QString id() const override;
|
||||
virtual QVector<CategoryID> Category() const override;
|
||||
virtual QString Description() const override;
|
||||
|
||||
virtual void Retranslate() override;
|
||||
|
||||
virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override;
|
||||
|
||||
virtual void GenerateFrame(FramePtr frame, const GenerateJob &job) const override;
|
||||
|
||||
static const QString kTextInput;
|
||||
static const QString kHtmlInput;
|
||||
static const QString kColorInput;
|
||||
static const QString kVAlignInput;
|
||||
static const QString kFontInput;
|
||||
static const QString kFontSizeInput;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // TEXTGENERATORLEGACY_H
|
||||
+19
-2
@@ -30,16 +30,29 @@ namespace olive {
|
||||
class NodeGlobals
|
||||
{
|
||||
public:
|
||||
NodeGlobals(const QVector2D &resolution, const TimeRange &time) :
|
||||
NodeGlobals(const QVector2D &resolution, const rational &pixel_aspect, const TimeRange &time) :
|
||||
resolution_(resolution),
|
||||
pixel_aspect_(pixel_aspect),
|
||||
time_(time)
|
||||
{}
|
||||
{
|
||||
resolution_by_par_ = QVector2D(resolution_.x() * pixel_aspect_.toDouble(), resolution_.y());
|
||||
}
|
||||
|
||||
const QVector2D &resolution() const
|
||||
{
|
||||
return resolution_;
|
||||
}
|
||||
|
||||
const QVector2D &resolution_by_par() const
|
||||
{
|
||||
return resolution_by_par_;
|
||||
}
|
||||
|
||||
const rational &pixel_aspect() const
|
||||
{
|
||||
return pixel_aspect_;
|
||||
}
|
||||
|
||||
const TimeRange &time() const
|
||||
{
|
||||
return time_;
|
||||
@@ -53,6 +66,10 @@ public:
|
||||
private:
|
||||
QVector2D resolution_;
|
||||
|
||||
rational pixel_aspect_;
|
||||
|
||||
QVector2D resolution_by_par_;
|
||||
|
||||
TimeRange time_;
|
||||
|
||||
};
|
||||
|
||||
+32
-40
@@ -44,26 +44,12 @@ void NodeGraph::Clear()
|
||||
}
|
||||
}
|
||||
|
||||
qreal NodeGraph::GetNodeContextHeight(Node *context)
|
||||
{
|
||||
const PositionMap &map = position_map_.value(context);
|
||||
|
||||
qreal top = 0, bottom = 0;
|
||||
|
||||
foreach (const QPointF &pt, map) {
|
||||
top = qMin(pt.y(), top);
|
||||
bottom = qMax(pt.y(), bottom);
|
||||
}
|
||||
|
||||
return bottom - top;
|
||||
}
|
||||
|
||||
int NodeGraph::GetNumberOfContextsNodeIsIn(Node *node) const
|
||||
int NodeGraph::GetNumberOfContextsNodeIsIn(Node *node, bool except_itself) const
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
for (auto it=position_map_.cbegin(); it!=position_map_.cend(); it++) {
|
||||
if (it.value().contains(node)) {
|
||||
foreach (Node *ctx, node_children_) {
|
||||
if (ctx->ContextContainsNode(node) && (!except_itself || ctx != node)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
@@ -71,18 +57,6 @@ int NodeGraph::GetNumberOfContextsNodeIsIn(Node *node) const
|
||||
return count;
|
||||
}
|
||||
|
||||
bool NodeGraph::NodeOutputsToContext(Node *node) const
|
||||
{
|
||||
for (auto it=position_map_.cbegin(); it!=position_map_.cend(); it++) {
|
||||
const PositionMap &pm = it.value();
|
||||
if (pm.contains(node) && node->OutputsTo(it.key(), true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void NodeGraph::childEvent(QChildEvent *event)
|
||||
{
|
||||
super::childEvent(event);
|
||||
@@ -100,9 +74,29 @@ void NodeGraph::childEvent(QChildEvent *event)
|
||||
connect(node, &Node::ValueChanged, this, &NodeGraph::ValueChanged, Qt::DirectConnection);
|
||||
connect(node, &Node::InputValueHintChanged, this, &NodeGraph::InputValueHintChanged, Qt::DirectConnection);
|
||||
|
||||
if (NodeGroup *group = dynamic_cast<NodeGroup*>(node)) {
|
||||
connect(group, &NodeGroup::InputPassthroughAdded, this, &NodeGraph::GroupAddedInputPassthrough, Qt::DirectConnection);
|
||||
connect(group, &NodeGroup::InputPassthroughRemoved, this, &NodeGraph::GroupRemovedInputPassthrough, Qt::DirectConnection);
|
||||
connect(group, &NodeGroup::OutputPassthroughChanged, this, &NodeGraph::GroupChangedOutputPassthrough, Qt::DirectConnection);
|
||||
}
|
||||
|
||||
emit NodeAdded(node);
|
||||
emit node->AddedToGraph(this);
|
||||
|
||||
// Emit input connections
|
||||
for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) {
|
||||
if (nodes().contains(it->second)) {
|
||||
emit InputConnected(it->second, it->first);
|
||||
}
|
||||
}
|
||||
|
||||
// Emit output connections
|
||||
for (auto it=node->output_connections().cbegin(); it!=node->output_connections().cend(); it++) {
|
||||
if (nodes().contains(it->second.node())) {
|
||||
emit InputConnected(it->first, it->second);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (event->type() == QEvent::ChildRemoved) {
|
||||
|
||||
node_children_.removeOne(node);
|
||||
@@ -113,21 +107,19 @@ void NodeGraph::childEvent(QChildEvent *event)
|
||||
disconnect(node, &Node::ValueChanged, this, &NodeGraph::ValueChanged);
|
||||
disconnect(node, &Node::InputValueHintChanged, this, &NodeGraph::InputValueHintChanged);
|
||||
|
||||
if (NodeGroup *group = dynamic_cast<NodeGroup*>(node)) {
|
||||
disconnect(group, &NodeGroup::InputPassthroughAdded, this, &NodeGraph::GroupAddedInputPassthrough);
|
||||
disconnect(group, &NodeGroup::InputPassthroughRemoved, this, &NodeGraph::GroupRemovedInputPassthrough);
|
||||
disconnect(group, &NodeGroup::OutputPassthroughChanged, this, &NodeGraph::GroupChangedOutputPassthrough);
|
||||
}
|
||||
|
||||
emit NodeRemoved(node);
|
||||
emit node->RemovedFromGraph(this);
|
||||
|
||||
for (auto it=position_map_.begin(); it!=position_map_.end(); it++) {
|
||||
PositionMap &map = it.value();
|
||||
for (auto jt=map.begin(); jt!=map.end(); ) {
|
||||
if (jt.key() == node) {
|
||||
jt = map.erase(jt);
|
||||
emit NodePositionRemoved(node, it.key());
|
||||
} else {
|
||||
jt++;
|
||||
}
|
||||
}
|
||||
// Remove from any contexts
|
||||
foreach (Node *context, node_children_) {
|
||||
context->RemoveNodeFromContext(node);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-54
@@ -21,6 +21,7 @@
|
||||
#ifndef NODEGRAPH_H
|
||||
#define NODEGRAPH_H
|
||||
|
||||
#include "node/group/group.h"
|
||||
#include "node/node.h"
|
||||
|
||||
namespace olive {
|
||||
@@ -63,54 +64,7 @@ public:
|
||||
return default_nodes_;
|
||||
}
|
||||
|
||||
bool NodeMapContainsNode(Node* node, Node* context) const
|
||||
{
|
||||
return position_map_.value(context).contains(node);
|
||||
}
|
||||
|
||||
QPointF GetNodePosition(Node* node, Node* context)
|
||||
{
|
||||
return position_map_.value(context).value(node);
|
||||
}
|
||||
|
||||
void SetNodePosition(Node* node, Node* context, const QPointF& pos)
|
||||
{
|
||||
position_map_[context].insert(node, pos);
|
||||
emit NodePositionAdded(node, context, pos);
|
||||
}
|
||||
|
||||
void RemoveNodePosition(Node* node, Node* context)
|
||||
{
|
||||
PositionMap& map = position_map_[context];
|
||||
map.remove(node);
|
||||
if (map.isEmpty()) {
|
||||
position_map_.remove(context);
|
||||
}
|
||||
emit NodePositionRemoved(node, context);
|
||||
}
|
||||
|
||||
bool ContextContainsNode(Node *node, Node *context)
|
||||
{
|
||||
return position_map_[context].contains(node);
|
||||
}
|
||||
|
||||
qreal GetNodeContextHeight(Node *context);
|
||||
|
||||
using PositionMap = QHash<Node*, QPointF>;
|
||||
|
||||
const PositionMap &GetNodesForContext(Node *context)
|
||||
{
|
||||
return position_map_[context];
|
||||
}
|
||||
|
||||
const QMap<Node *, PositionMap> &GetPositionMap() const
|
||||
{
|
||||
return position_map_;
|
||||
}
|
||||
|
||||
int GetNumberOfContextsNodeIsIn(Node *node) const;
|
||||
|
||||
bool NodeOutputsToContext(Node *node) const;
|
||||
int GetNumberOfContextsNodeIsIn(Node *node, bool except_itself = false) const;
|
||||
|
||||
signals:
|
||||
/**
|
||||
@@ -131,9 +85,11 @@ signals:
|
||||
|
||||
void InputValueHintChanged(const NodeInput& input);
|
||||
|
||||
void NodePositionAdded(Node *node, Node *relative, const QPointF &position);
|
||||
void GroupAddedInputPassthrough(NodeGroup *group, const NodeInput &input);
|
||||
|
||||
void NodePositionRemoved(Node *node, Node *relative);
|
||||
void GroupRemovedInputPassthrough(NodeGroup *group, const NodeInput &input);
|
||||
|
||||
void GroupChangedOutputPassthrough(NodeGroup *group, Node *output);
|
||||
|
||||
protected:
|
||||
void AddDefaultNode(Node* n)
|
||||
@@ -148,10 +104,6 @@ private:
|
||||
|
||||
QVector<Node*> default_nodes_;
|
||||
|
||||
QMap<Node *, PositionMap> position_map_;
|
||||
|
||||
PositionMap root_position_map_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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/group/group.cpp
|
||||
node/group/group.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,189 @@
|
||||
/***
|
||||
|
||||
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 "group.h"
|
||||
|
||||
#include "node/graph.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
#define super Node
|
||||
|
||||
NodeGroup::NodeGroup() :
|
||||
output_passthrough_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
QString NodeGroup::Name() const
|
||||
{
|
||||
return tr("Group");
|
||||
}
|
||||
|
||||
QString NodeGroup::id() const
|
||||
{
|
||||
return QStringLiteral("org.olivevideoeditor.Olive.group");
|
||||
}
|
||||
|
||||
QVector<Node::CategoryID> NodeGroup::Category() const
|
||||
{
|
||||
return {kCategoryGeneral};
|
||||
}
|
||||
|
||||
QString NodeGroup::Description() const
|
||||
{
|
||||
return tr("A group of nodes that is represented as a single node.");
|
||||
}
|
||||
|
||||
void NodeGroup::Retranslate()
|
||||
{
|
||||
for (auto it=GetContextPositions().cbegin(); it!=GetContextPositions().cend(); it++) {
|
||||
it.key()->Retranslate();
|
||||
}
|
||||
}
|
||||
|
||||
QString NodeGroup::AddInputPassthrough(const NodeInput &input, const InputFlags &flags)
|
||||
{
|
||||
Q_ASSERT(ContextContainsNode(input.node()));
|
||||
|
||||
for (auto it=input_passthroughs_.cbegin(); it!=input_passthroughs_.cend(); it++) {
|
||||
if (it.value() == input) {
|
||||
// Already passing this input through
|
||||
return it.key();
|
||||
}
|
||||
}
|
||||
|
||||
// Add input
|
||||
QString id = GetGroupInputIDFromInput(input);
|
||||
|
||||
AddInput(id, input.GetDataType(), input.GetDefaultValue(), input.GetFlags() | flags);
|
||||
|
||||
input_passthroughs_.insert(id, input);
|
||||
|
||||
emit InputPassthroughAdded(this, input);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
void NodeGroup::RemoveInputPassthrough(const NodeInput &input)
|
||||
{
|
||||
for (auto it=input_passthroughs_.cbegin(); it!=input_passthroughs_.cend(); it++) {
|
||||
if (it.value() == input) {
|
||||
RemoveInput(it.key());
|
||||
input_passthroughs_.erase(it);
|
||||
emit InputPassthroughRemoved(this, it.value());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NodeGroup::SetOutputPassthrough(Node *node)
|
||||
{
|
||||
Q_ASSERT(!node || ContextContainsNode(node));
|
||||
|
||||
output_passthrough_ = node;
|
||||
|
||||
emit OutputPassthroughChanged(this, output_passthrough_);
|
||||
}
|
||||
|
||||
QString NodeGroup::GetGroupInputIDFromInput(const NodeInput &input)
|
||||
{
|
||||
QCryptographicHash hash(QCryptographicHash::Sha1);
|
||||
|
||||
hash.addData(input.node()->GetUUID().toByteArray());
|
||||
|
||||
hash.addData(input.input().toUtf8());
|
||||
|
||||
hash.addData((const char*) &input.element(), sizeof(input.element()));
|
||||
|
||||
return QString::fromLatin1(hash.result().toHex());
|
||||
}
|
||||
|
||||
bool NodeGroup::ContainsInputPassthrough(const NodeInput &input) const
|
||||
{
|
||||
for (auto it=input_passthroughs_.cbegin(); it!=input_passthroughs_.cend(); it++) {
|
||||
if (it.value() == input) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QString NodeGroup::GetInputName(const QString &id) const
|
||||
{
|
||||
// If an override name was set, use that
|
||||
QString override = super::GetInputName(id);
|
||||
if (!override.isEmpty()) {
|
||||
return override;
|
||||
}
|
||||
|
||||
// Call GetInputName of passed through node, which may be another group
|
||||
NodeInput pass = input_passthroughs_.value(id);
|
||||
return pass.node()->GetInputName(pass.input());
|
||||
}
|
||||
|
||||
NodeInput NodeGroup::ResolveInput(NodeInput input)
|
||||
{
|
||||
while (GetInner(&input)) {}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
bool NodeGroup::GetInner(NodeInput *input)
|
||||
{
|
||||
if (NodeGroup *g = dynamic_cast<NodeGroup*>(input->node())) {
|
||||
const NodeInput &passthrough = g->GetInputPassthroughs().value(input->input());
|
||||
input->set_node(passthrough.node());
|
||||
input->set_input(passthrough.input());
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void NodeGroupAddInputPassthrough::redo()
|
||||
{
|
||||
if (!group_->ContainsInputPassthrough(input_)) {
|
||||
group_->AddInputPassthrough(input_);
|
||||
actually_added_ = true;
|
||||
} else {
|
||||
actually_added_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void NodeGroupAddInputPassthrough::undo()
|
||||
{
|
||||
if (actually_added_) {
|
||||
group_->RemoveInputPassthrough(input_);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeGroupSetOutputPassthrough::redo()
|
||||
{
|
||||
old_output_ = group_->GetOutputPassthrough();
|
||||
group_->SetOutputPassthrough(new_output_);
|
||||
}
|
||||
|
||||
void NodeGroupSetOutputPassthrough::undo()
|
||||
{
|
||||
group_->SetOutputPassthrough(old_output_);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/***
|
||||
|
||||
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 NODEGROUP_H
|
||||
#define NODEGROUP_H
|
||||
|
||||
#include "node/node.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class NodeGroup : public Node
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
NodeGroup();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(NodeGroup)
|
||||
NODE_COPY_FUNCTION(NodeGroup)
|
||||
|
||||
virtual QString Name() const override;
|
||||
virtual QString id() const override;
|
||||
virtual QVector<CategoryID> Category() const override;
|
||||
virtual QString Description() const override;
|
||||
|
||||
virtual void Retranslate() override;
|
||||
|
||||
QString AddInputPassthrough(const NodeInput &input, const InputFlags &flags = InputFlags());
|
||||
|
||||
void RemoveInputPassthrough(const NodeInput &input);
|
||||
|
||||
Node *GetOutputPassthrough() const
|
||||
{
|
||||
return output_passthrough_;
|
||||
}
|
||||
|
||||
void SetOutputPassthrough(Node *node);
|
||||
|
||||
static QString GetGroupInputIDFromInput(const NodeInput &input);
|
||||
|
||||
const QHash<QString, NodeInput> &GetInputPassthroughs() const
|
||||
{
|
||||
return input_passthroughs_;
|
||||
}
|
||||
|
||||
bool ContainsInputPassthrough(const NodeInput &input) const;
|
||||
|
||||
virtual QString GetInputName(const QString& id) const override;
|
||||
|
||||
static NodeInput ResolveInput(NodeInput input);
|
||||
static bool GetInner(NodeInput *input);
|
||||
|
||||
signals:
|
||||
void InputPassthroughAdded(NodeGroup *group, const NodeInput &input);
|
||||
|
||||
void InputPassthroughRemoved(NodeGroup *group, const NodeInput &input);
|
||||
|
||||
void OutputPassthroughChanged(NodeGroup *group, Node *output);
|
||||
|
||||
private:
|
||||
QHash<QString, NodeInput> input_passthroughs_;
|
||||
|
||||
Node *output_passthrough_;
|
||||
|
||||
};
|
||||
|
||||
class NodeGroupAddInputPassthrough : public UndoCommand
|
||||
{
|
||||
public:
|
||||
NodeGroupAddInputPassthrough(NodeGroup *group, const NodeInput &input) :
|
||||
group_(group),
|
||||
input_(input),
|
||||
actually_added_(false)
|
||||
{}
|
||||
|
||||
virtual Project * GetRelevantProject() const override
|
||||
{
|
||||
return group_->project();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
NodeGroup *group_;
|
||||
|
||||
NodeInput input_;
|
||||
|
||||
bool actually_added_;
|
||||
|
||||
};
|
||||
|
||||
class NodeGroupSetOutputPassthrough : public UndoCommand
|
||||
{
|
||||
public:
|
||||
NodeGroupSetOutputPassthrough(NodeGroup *group, Node *output) :
|
||||
group_(group),
|
||||
new_output_(output)
|
||||
{}
|
||||
|
||||
virtual Project * GetRelevantProject() const override
|
||||
{
|
||||
return group_->project();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
NodeGroup *group_;
|
||||
|
||||
Node *new_output_;
|
||||
Node *old_output_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // NODEGROUP_H
|
||||
@@ -155,9 +155,7 @@ void HashTraverser::HashNodeValue(const NodeValue &value)
|
||||
id_for_buffer = texture_ids_.value(samples.get());
|
||||
}
|
||||
|
||||
if (id_for_buffer.isEmpty()) {
|
||||
qWarning() << "Found ID-less buffer while hashing, collisions are likely to occur";
|
||||
} else {
|
||||
if (!id_for_buffer.isEmpty()) {
|
||||
Hash(id_for_buffer);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -46,6 +46,11 @@ public:
|
||||
return input_being_dragged;
|
||||
}
|
||||
|
||||
const QVariant &GetStartValue() const
|
||||
{
|
||||
return start_value_;
|
||||
}
|
||||
|
||||
private:
|
||||
NodeKeyframeTrackReference input_;
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ public:
|
||||
* @brief Methods of interpolation to use with this keyframe
|
||||
*/
|
||||
enum Type {
|
||||
kInvalid = -1,
|
||||
kLinear,
|
||||
kHold,
|
||||
kBezier
|
||||
|
||||
@@ -23,10 +23,7 @@
|
||||
#include <QMatrix4x4>
|
||||
#include <QVector2D>
|
||||
|
||||
#ifdef Q_PROCESSOR_X86
|
||||
#include <xmmintrin.h>
|
||||
#endif
|
||||
|
||||
#include "common/cpuoptimize.h"
|
||||
#include "common/tohex.h"
|
||||
#include "node/distort/transform/transformdistortnode.h"
|
||||
#include "render/color.h"
|
||||
@@ -103,7 +100,9 @@ ShaderCode MathNodeBase::GetShaderCodeInternal(const QString &shader_id, const Q
|
||||
"varying vec2 ove_texcoord;\n"
|
||||
"\n"
|
||||
"void main(void) {\n"
|
||||
" gl_FragColor = %5;\n"
|
||||
" vec4 c = %5;\n"
|
||||
" c.a = clamp(c.a, 0.0, 1.0);\n" // Ensure alpha is between 0.0 and 1.0
|
||||
" gl_FragColor = c;\n"
|
||||
"}\n").arg(GetShaderUniformType(type_a),
|
||||
GetShaderUniformType(type_b),
|
||||
param_a_in,
|
||||
@@ -174,7 +173,7 @@ void MathNodeBase::PerformAllOnFloatBuffer(Operation operation, float *a, float
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef Q_PROCESSOR_X86
|
||||
#if defined(Q_PROCESSOR_X86) || defined(Q_PROCESSOR_ARM)
|
||||
void MathNodeBase::PerformAllOnFloatBufferSSE(Operation operation, float *a, float b, int start, int end)
|
||||
{
|
||||
int end_divisible_4 = (end / 4) * 4;
|
||||
@@ -402,7 +401,7 @@ void MathNodeBase::ValueInternal(Operation operation, Pairing pairing, const QSt
|
||||
if (IsInputStatic(number_param)) {
|
||||
if (!NumberIsNoOp(operation, number)) {
|
||||
for (int i=0;i<job.samples()->audio_params().channel_count();i++) {
|
||||
#ifdef Q_PROCESSOR_X86
|
||||
#if defined(Q_PROCESSOR_X86) || defined(Q_PROCESSOR_ARM)
|
||||
// Use SSE instructions for optimization
|
||||
PerformAllOnFloatBufferSSE(operation, job.samples()->data(i), number, 0, job.samples()->sample_count());
|
||||
#else
|
||||
|
||||
@@ -103,7 +103,7 @@ protected:
|
||||
|
||||
static void PerformAllOnFloatBuffer(Operation operation, float *a, float b, int start, int end);
|
||||
|
||||
#ifdef Q_PROCESSOR_X86
|
||||
#if defined(Q_PROCESSOR_X86) || defined(Q_PROCESSOR_ARM)
|
||||
static void PerformAllOnFloatBufferSSE(Operation operation, float *a, float b, int start, int end);
|
||||
#endif
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@ MergeNode::MergeNode()
|
||||
AddInput(kBaseIn, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
|
||||
|
||||
AddInput(kBlendIn, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
|
||||
|
||||
SetFlags(kDontShowInParamView);
|
||||
}
|
||||
|
||||
Node *MergeNode::copy() const
|
||||
|
||||
@@ -74,6 +74,8 @@ void TrigonometryNode::Retranslate()
|
||||
SetComboBoxStrings(kMethodIn, strings);
|
||||
|
||||
SetInputName(kMethodIn, tr("Method"));
|
||||
|
||||
SetInputName(kXIn, tr("Value"));
|
||||
}
|
||||
|
||||
void TrigonometryNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
|
||||
|
||||
+128
-575
@@ -47,8 +47,10 @@ Node::Node() :
|
||||
override_color_(-1),
|
||||
folder_(nullptr),
|
||||
operation_stack_(0),
|
||||
cache_result_(false)
|
||||
cache_result_(false),
|
||||
flags_(kNone)
|
||||
{
|
||||
uuid_ = QUuid::createUuid();
|
||||
}
|
||||
|
||||
Node::~Node()
|
||||
@@ -75,151 +77,18 @@ NodeGraph *Node::parent() const
|
||||
return static_cast<NodeGraph*>(QObject::parent());
|
||||
}
|
||||
|
||||
void Node::Load(QXmlStreamReader *reader, XMLNodeData& xml_node_data, uint version, const QAtomicInt* cancelled)
|
||||
{
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (cancelled && *cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (reader->name() == QStringLiteral("input")) {
|
||||
LoadInput(reader, xml_node_data, cancelled);
|
||||
} else if (reader->name() == QStringLiteral("ptr")) {
|
||||
xml_node_data.node_ptrs.insert(reader->readElementText().toULongLong(), this);
|
||||
} else if (reader->name() == QStringLiteral("label")) {
|
||||
SetLabel(reader->readElementText());
|
||||
} else if (reader->name() == QStringLiteral("color")) {
|
||||
override_color_ = reader->readElementText().toInt();
|
||||
} else if (reader->name() == QStringLiteral("links")) {
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("link")) {
|
||||
xml_node_data.block_links.append({this, reader->readElementText().toULongLong()});
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else if (reader->name() == QStringLiteral("custom")) {
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (!LoadCustom(reader, xml_node_data, version, cancelled)) {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else if (reader->name() == QStringLiteral("connections")) {
|
||||
// Load connections
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("connection")) {
|
||||
QString param_id;
|
||||
int ele = -1;
|
||||
|
||||
XMLAttributeLoop(reader, attr) {
|
||||
if (attr.name() == QStringLiteral("element")) {
|
||||
ele = attr.value().toInt();
|
||||
} else if (attr.name() == QStringLiteral("input")) {
|
||||
param_id = attr.value().toString();
|
||||
}
|
||||
}
|
||||
|
||||
QString output_node_id;
|
||||
QString output_param_id;
|
||||
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if ((version >= 210907 && reader->name() == QStringLiteral("output")) || reader->name() == QStringLiteral("node")) {
|
||||
output_node_id = reader->readElementText();
|
||||
} else if (version < 210907 && reader->name() == QStringLiteral("output")) {
|
||||
output_param_id = reader->readElementText();
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
xml_node_data.desired_connections.append({NodeInput(this, param_id, ele), output_node_id.toULongLong(), output_param_id});
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else if (reader->name() == QStringLiteral("hints")) {
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("hint")) {
|
||||
QString input;
|
||||
int element = -1;
|
||||
|
||||
XMLAttributeLoop(reader, attr) {
|
||||
if (attr.name() == QStringLiteral("input")) {
|
||||
input = attr.value().toString();
|
||||
} else if (attr.name() == QStringLiteral("element")) {
|
||||
element = attr.value().toInt();
|
||||
}
|
||||
}
|
||||
|
||||
ValueHint vh;
|
||||
vh.Load(reader);
|
||||
|
||||
value_hints_.insert({input, element}, vh);
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Node::Save(QXmlStreamWriter *writer) const
|
||||
{
|
||||
writer->writeTextElement(QStringLiteral("ptr"), QString::number(reinterpret_cast<quintptr>(this)));
|
||||
|
||||
writer->writeTextElement(QStringLiteral("label"), GetLabel());
|
||||
writer->writeTextElement(QStringLiteral("color"), QString::number(override_color_));
|
||||
|
||||
foreach (const QString& input, input_ids_) {
|
||||
writer->writeStartElement(QStringLiteral("input"));
|
||||
|
||||
SaveInput(writer, input);
|
||||
|
||||
writer->writeEndElement(); // input
|
||||
}
|
||||
|
||||
writer->writeStartElement(QStringLiteral("links"));
|
||||
foreach (Node* link, links_) {
|
||||
writer->writeTextElement(QStringLiteral("link"), QString::number(reinterpret_cast<quintptr>(link)));
|
||||
}
|
||||
writer->writeEndElement(); // links
|
||||
|
||||
writer->writeStartElement(QStringLiteral("connections"));
|
||||
for (auto it=input_connections().cbegin(); it!=input_connections().cend(); it++) {
|
||||
writer->writeStartElement(QStringLiteral("connection"));
|
||||
|
||||
writer->writeAttribute(QStringLiteral("input"), it->first.input());
|
||||
writer->writeAttribute(QStringLiteral("element"), QString::number(it->first.element()));
|
||||
|
||||
writer->writeTextElement(QStringLiteral("output"), QString::number(reinterpret_cast<quintptr>(it->second)));
|
||||
|
||||
writer->writeEndElement(); // connection
|
||||
}
|
||||
writer->writeEndElement(); // connections
|
||||
|
||||
writer->writeStartElement(QStringLiteral("hints"));
|
||||
for (auto it=value_hints_.cbegin(); it!=value_hints_.cend(); it++) {
|
||||
writer->writeStartElement(QStringLiteral("hint"));
|
||||
|
||||
writer->writeAttribute(QStringLiteral("input"), it.key().input);
|
||||
writer->writeAttribute(QStringLiteral("element"), QString::number(it.key().element));
|
||||
|
||||
it.value().Save(writer);
|
||||
|
||||
writer->writeEndElement(); // hint
|
||||
}
|
||||
writer->writeEndElement();
|
||||
|
||||
writer->writeStartElement(QStringLiteral("custom"));
|
||||
SaveCustom(writer);
|
||||
writer->writeEndElement(); // custom
|
||||
}
|
||||
|
||||
Project* Node::project() const
|
||||
{
|
||||
return dynamic_cast<Project*>(parent());
|
||||
QObject *t = this->parent();
|
||||
|
||||
while (t) {
|
||||
if (Project *p = dynamic_cast<Project*>(t)) {
|
||||
return p;
|
||||
}
|
||||
t = t->parent();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QString Node::ShortName() const
|
||||
@@ -243,6 +112,40 @@ QIcon Node::icon() const
|
||||
return icon::New;
|
||||
}
|
||||
|
||||
bool Node::SetNodePositionInContext(Node *node, const QPointF &pos)
|
||||
{
|
||||
Position p = context_positions_.value(node);
|
||||
|
||||
p.position = pos;
|
||||
|
||||
return SetNodePositionInContext(node, p);
|
||||
}
|
||||
|
||||
bool Node::SetNodePositionInContext(Node *node, const Position &pos)
|
||||
{
|
||||
bool added = !ContextContainsNode(node);
|
||||
context_positions_.insert(node, pos);
|
||||
|
||||
if (added) {
|
||||
emit NodeAddedToContext(node);
|
||||
}
|
||||
|
||||
emit NodePositionInContextChanged(node, pos.position);
|
||||
|
||||
return added;
|
||||
}
|
||||
|
||||
bool Node::RemoveNodeFromContext(Node *node)
|
||||
{
|
||||
if (ContextContainsNode(node)) {
|
||||
context_positions_.remove(node);
|
||||
emit NodeRemovedFromContext(node);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Color Node::color() const
|
||||
{
|
||||
int c;
|
||||
@@ -345,88 +248,9 @@ QString Node::GetInputName(const QString &id) const
|
||||
}
|
||||
}
|
||||
|
||||
void Node::LoadInput(QXmlStreamReader *reader, XMLNodeData &xml_node_data, const QAtomicInt *cancelled)
|
||||
bool Node::IsInputHidden(const QString &input) const
|
||||
{
|
||||
QString param_id;
|
||||
|
||||
XMLAttributeLoop(reader, attr) {
|
||||
if (attr.name() == QStringLiteral("id")) {
|
||||
param_id = attr.value().toString();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (param_id.isEmpty()) {
|
||||
qWarning() << "Failed to load parameter with missing ID";
|
||||
reader->skipCurrentElement();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!HasInputWithID(param_id)) {
|
||||
qWarning() << "Failed to load parameter that didn't exist:" << param_id;
|
||||
reader->skipCurrentElement();
|
||||
return;
|
||||
}
|
||||
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (cancelled && *cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (reader->name() == QStringLiteral("primary")) {
|
||||
// Load primary immediate
|
||||
LoadImmediate(reader, param_id, -1, xml_node_data, cancelled);
|
||||
} else if (reader->name() == QStringLiteral("subelements")) {
|
||||
// Load subelements
|
||||
XMLAttributeLoop(reader, attr) {
|
||||
if (attr.name() == QStringLiteral("count")) {
|
||||
InputArrayResize(param_id, attr.value().toInt());
|
||||
}
|
||||
}
|
||||
|
||||
int element_counter = 0;
|
||||
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("element")) {
|
||||
LoadImmediate(reader, param_id, element_counter, xml_node_data, cancelled);
|
||||
|
||||
element_counter++;
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Node::SaveInput(QXmlStreamWriter *writer, const QString &id) const
|
||||
{
|
||||
writer->writeAttribute(QStringLiteral("id"), id);
|
||||
|
||||
writer->writeStartElement(QStringLiteral("primary"));
|
||||
|
||||
SaveImmediate(writer, id, -1);
|
||||
|
||||
writer->writeEndElement(); // primary
|
||||
|
||||
writer->writeStartElement(QStringLiteral("subelements"));
|
||||
|
||||
int arr_sz = InputArraySize(id);
|
||||
|
||||
writer->writeAttribute(QStringLiteral("count"), QString::number(arr_sz));
|
||||
|
||||
for (int i=0; i<arr_sz; i++) {
|
||||
writer->writeStartElement(QStringLiteral("element"));
|
||||
|
||||
SaveImmediate(writer, id, i);
|
||||
|
||||
writer->writeEndElement(); // element
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // subelements
|
||||
return (GetInputFlags(input) & kInputFlagHidden);
|
||||
}
|
||||
|
||||
bool Node::IsInputConnectable(const QString &input) const
|
||||
@@ -931,7 +755,7 @@ void Node::InputArrayInsert(const QString &id, int index, bool undoable)
|
||||
// Move connections down
|
||||
InputConnections copied_edges = input_connections();
|
||||
for (auto it=copied_edges.crbegin(); it!=copied_edges.crend(); it++) {
|
||||
if (it->first.element() >= index) {
|
||||
if (it->first.input() == id && it->first.element() >= index) {
|
||||
// Disconnect this and reconnect it one element down
|
||||
NodeInput new_edge = it->first;
|
||||
new_edge.set_element(new_edge.element() + 1);
|
||||
@@ -978,7 +802,7 @@ void Node::InputArrayRemove(const QString &id, int index, bool undoable)
|
||||
// Move connections up
|
||||
InputConnections copied_edges = input_connections();
|
||||
for (auto it=copied_edges.cbegin(); it!=copied_edges.cend(); it++) {
|
||||
if (it->first.element() >= index) {
|
||||
if (it->first.input() == id && it->first.element() >= index) {
|
||||
// Disconnect this and reconnect it one element up if it's not the element being removed
|
||||
DisconnectEdge(it->second, it->first);
|
||||
|
||||
@@ -1050,7 +874,7 @@ NodeInputImmediate *Node::GetImmediate(const QString &input, int element) const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Node::InputFlags Node::GetInputFlags(const QString &input) const
|
||||
InputFlags Node::GetInputFlags(const QString &input) const
|
||||
{
|
||||
const Input* i = GetInternalInputData(input);
|
||||
|
||||
@@ -1196,13 +1020,10 @@ Node *Node::CopyNodeAndDependencyGraphMinusItemsInternal(QMap<Node*, Node*>& cre
|
||||
command->add_child(new NodeSetValueHintCommand(copied_input, node->GetValueHintForInput(input.input(), input.element())));
|
||||
}
|
||||
|
||||
if (node->parent()->GetPositionMap().contains(node)) {
|
||||
// This node is a context, copy the context
|
||||
const NodeGraph::PositionMap &map = node->parent()->GetPositionMap().value(node);
|
||||
for (auto it=map.cbegin(); it!=map.cend(); it++) {
|
||||
// Add either the copy (if it exists) or the original node to the context
|
||||
command->add_child(new NodeSetPositionCommand(created.value(it.key(), it.key()), copy, it.value(), false));
|
||||
}
|
||||
const PositionMap &map = node->GetContextPositions();
|
||||
for (auto it=map.cbegin(); it!=map.cend(); it++) {
|
||||
// Add either the copy (if it exists) or the original node to the context
|
||||
command->add_child(new NodeSetPositionCommand(created.value(it.key(), it.key()), copy, it.value()));
|
||||
}
|
||||
|
||||
return copy;
|
||||
@@ -1229,13 +1050,10 @@ Node *Node::CopyNodeInGraph(Node *node, MultiUndoCommand *command)
|
||||
|
||||
command->add_child(new NodeCopyInputsCommand(node, copy, true));
|
||||
|
||||
if (node->parent()->GetPositionMap().contains(node)) {
|
||||
// This node is a context, copy the context
|
||||
const NodeGraph::PositionMap &map = node->parent()->GetPositionMap().value(node);
|
||||
for (auto it=map.cbegin(); it!=map.cend(); it++) {
|
||||
// Add to the context
|
||||
command->add_child(new NodeSetPositionCommand(it.key(), copy, it.value(), false));
|
||||
}
|
||||
const PositionMap &map = node->GetContextPositions();
|
||||
for (auto it=map.cbegin(); it!=map.cend(); it++) {
|
||||
// Add to the context
|
||||
command->add_child(new NodeSetPositionCommand(it.key(), copy, it.value()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1310,7 +1128,7 @@ void Node::HashAddNodeSignature(QCryptographicHash &hash) const
|
||||
hash.addData(id().toUtf8());
|
||||
}
|
||||
|
||||
void Node::InsertInput(const QString &id, NodeValue::Type type, const QVariant &default_value, Node::InputFlags flags, int index)
|
||||
void Node::InsertInput(const QString &id, NodeValue::Type type, const QVariant &default_value, InputFlags flags, int index)
|
||||
{
|
||||
if (id.isEmpty()) {
|
||||
qWarning() << "Rejected adding input with an empty ID on node" << this->id();
|
||||
@@ -1430,15 +1248,6 @@ void Node::IgnoreHashingFrom(const QString &input_id)
|
||||
ignore_when_hashing_.append(input_id);
|
||||
}
|
||||
|
||||
bool Node::LoadCustom(QXmlStreamReader *, XMLNodeData &, uint, const QAtomicInt*)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void Node::SaveCustom(QXmlStreamWriter *) const
|
||||
{
|
||||
}
|
||||
|
||||
bool Node::HasGizmos() const
|
||||
{
|
||||
return false;
|
||||
@@ -1457,7 +1266,7 @@ void Node::GizmoMove(const QPointF &, const rational&, const Qt::KeyboardModifie
|
||||
{
|
||||
}
|
||||
|
||||
void Node::GizmoRelease()
|
||||
void Node::GizmoRelease(MultiUndoCommand *)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1564,7 +1373,9 @@ void Node::CopyValuesOfElement(const Node *src, Node *dst, const QString &input,
|
||||
dst->SetSplitStandardValue(input, src->GetSplitStandardValue(input, src_element), dst_element);
|
||||
|
||||
// Copy keyframes
|
||||
dst->GetImmediate(input, dst_element)->delete_all_keyframes();
|
||||
if (NodeInputImmediate *immediate = dst->GetImmediate(input, dst_element)) {
|
||||
immediate->delete_all_keyframes();
|
||||
}
|
||||
foreach (const NodeKeyframeTrack& track, src->GetImmediate(input, src_element)->keyframe_tracks()) {
|
||||
foreach (NodeKeyframe* key, track) {
|
||||
key->copy(dst_element, dst);
|
||||
@@ -1832,6 +1643,10 @@ QString Node::GetCategoryName(const CategoryID &c)
|
||||
return tr("Transition");
|
||||
case kCategoryProject:
|
||||
return tr("Project");
|
||||
case kCategoryVideoEffect:
|
||||
return tr("Video Effect");
|
||||
case kCategoryAudioEffect:
|
||||
return tr("Audio Effect");
|
||||
case kCategoryUnknown:
|
||||
case kCategoryCount:
|
||||
break;
|
||||
@@ -1896,186 +1711,6 @@ void Node::ParameterValueChanged(const QString& input, int element, const TimeRa
|
||||
InvalidateCache(range, input, element);
|
||||
}
|
||||
|
||||
void Node::LoadImmediate(QXmlStreamReader *reader, const QString& input, int element, XMLNodeData &xml_node_data, const QAtomicInt *cancelled)
|
||||
{
|
||||
Q_UNUSED(xml_node_data)
|
||||
|
||||
NodeValue::Type data_type = GetInputDataType(input);
|
||||
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("standard")) {
|
||||
// Load standard value
|
||||
int val_index = 0;
|
||||
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (cancelled && *cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (reader->name() == QStringLiteral("track")) {
|
||||
QVariant value_on_track;
|
||||
|
||||
if (data_type == NodeValue::kVideoParams) {
|
||||
VideoParams vp;
|
||||
vp.Load(reader);
|
||||
value_on_track = QVariant::fromValue(vp);
|
||||
} else if (data_type == NodeValue::kAudioParams) {
|
||||
AudioParams ap;
|
||||
ap.Load(reader);
|
||||
value_on_track = QVariant::fromValue(ap);
|
||||
} else {
|
||||
QString value_text = reader->readElementText();
|
||||
|
||||
if (!value_text.isEmpty()) {
|
||||
value_on_track = NodeValue::StringToValue(data_type, value_text, element);
|
||||
}
|
||||
}
|
||||
|
||||
SetSplitStandardValueOnTrack(input, val_index, value_on_track, element);
|
||||
|
||||
val_index++;
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else if (reader->name() == QStringLiteral("keyframing") && IsInputKeyframable(input)) {
|
||||
SetInputIsKeyframing(input, reader->readElementText().toInt(), element);
|
||||
} else if (reader->name() == QStringLiteral("keyframes")) {
|
||||
int track = 0;
|
||||
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (cancelled && *cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (reader->name() == QStringLiteral("track")) {
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (cancelled && *cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (reader->name() == QStringLiteral("key")) {
|
||||
QString key_input;
|
||||
rational key_time;
|
||||
NodeKeyframe::Type key_type = NodeKeyframe::kLinear;
|
||||
QVariant key_value;
|
||||
QPointF key_in_handle;
|
||||
QPointF key_out_handle;
|
||||
|
||||
XMLAttributeLoop(reader, attr) {
|
||||
if (cancelled && *cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (attr.name() == QStringLiteral("input")) {
|
||||
key_input = attr.value().toString();
|
||||
} else if (attr.name() == QStringLiteral("time")) {
|
||||
key_time = rational::fromString(attr.value().toString());
|
||||
} else if (attr.name() == QStringLiteral("type")) {
|
||||
key_type = static_cast<NodeKeyframe::Type>(attr.value().toInt());
|
||||
} else if (attr.name() == QStringLiteral("inhandlex")) {
|
||||
key_in_handle.setX(attr.value().toDouble());
|
||||
} else if (attr.name() == QStringLiteral("inhandley")) {
|
||||
key_in_handle.setY(attr.value().toDouble());
|
||||
} else if (attr.name() == QStringLiteral("outhandlex")) {
|
||||
key_out_handle.setX(attr.value().toDouble());
|
||||
} else if (attr.name() == QStringLiteral("outhandley")) {
|
||||
key_out_handle.setY(attr.value().toDouble());
|
||||
}
|
||||
}
|
||||
|
||||
key_value = NodeValue::StringToValue(data_type, reader->readElementText(), true);
|
||||
|
||||
NodeKeyframe* key = new NodeKeyframe(key_time, key_value, key_type, track, element, key_input, this);
|
||||
key->set_bezier_control_in(key_in_handle);
|
||||
key->set_bezier_control_out(key_out_handle);
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
track++;
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else if (reader->name() == QStringLiteral("csinput")) {
|
||||
SetInputProperty(input, QStringLiteral("col_input"), reader->readElementText());
|
||||
} else if (reader->name() == QStringLiteral("csdisplay")) {
|
||||
SetInputProperty(input, QStringLiteral("col_display"), reader->readElementText());
|
||||
} else if (reader->name() == QStringLiteral("csview")) {
|
||||
SetInputProperty(input, QStringLiteral("col_view"), reader->readElementText());
|
||||
} else if (reader->name() == QStringLiteral("cslook")) {
|
||||
SetInputProperty(input, QStringLiteral("col_look"), reader->readElementText());
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Node::SaveImmediate(QXmlStreamWriter *writer, const QString& input, int element) const
|
||||
{
|
||||
if (IsInputKeyframable(input)) {
|
||||
writer->writeTextElement(QStringLiteral("keyframing"), QString::number(IsInputKeyframing(input, element)));
|
||||
}
|
||||
|
||||
NodeValue::Type data_type = GetInputDataType(input);
|
||||
|
||||
// Write standard value
|
||||
writer->writeStartElement(QStringLiteral("standard"));
|
||||
|
||||
foreach (const QVariant& v, GetSplitStandardValue(input, element)) {
|
||||
writer->writeStartElement(QStringLiteral("track"));
|
||||
|
||||
if (data_type == NodeValue::kVideoParams) {
|
||||
v.value<VideoParams>().Save(writer);
|
||||
} else if (data_type == NodeValue::kAudioParams) {
|
||||
v.value<AudioParams>().Save(writer);
|
||||
} else {
|
||||
writer->writeCharacters(NodeValue::ValueToString(data_type, v, true));
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // track
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // standard
|
||||
|
||||
// Write keyframes
|
||||
writer->writeStartElement(QStringLiteral("keyframes"));
|
||||
|
||||
foreach (const NodeKeyframeTrack& track, GetKeyframeTracks(input, element)) {
|
||||
writer->writeStartElement(QStringLiteral("track"));
|
||||
|
||||
foreach (NodeKeyframe* key, track) {
|
||||
writer->writeStartElement(QStringLiteral("key"));
|
||||
|
||||
writer->writeAttribute(QStringLiteral("input"), key->input());
|
||||
writer->writeAttribute(QStringLiteral("time"), key->time().toString());
|
||||
writer->writeAttribute(QStringLiteral("type"), QString::number(key->type()));
|
||||
writer->writeAttribute(QStringLiteral("inhandlex"), QString::number(key->bezier_control_in().x()));
|
||||
writer->writeAttribute(QStringLiteral("inhandley"), QString::number(key->bezier_control_in().y()));
|
||||
writer->writeAttribute(QStringLiteral("outhandlex"), QString::number(key->bezier_control_out().x()));
|
||||
writer->writeAttribute(QStringLiteral("outhandley"), QString::number(key->bezier_control_out().y()));
|
||||
|
||||
writer->writeCharacters(NodeValue::ValueToString(data_type, key->value(), true));
|
||||
|
||||
writer->writeEndElement(); // key
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // track
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // keyframes
|
||||
|
||||
if (data_type == NodeValue::kColor) {
|
||||
// Save color management information
|
||||
writer->writeTextElement(QStringLiteral("csinput"), GetInputProperty(input, QStringLiteral("col_input")).toString());
|
||||
writer->writeTextElement(QStringLiteral("csdisplay"), GetInputProperty(input, QStringLiteral("col_display")).toString());
|
||||
writer->writeTextElement(QStringLiteral("csview"), GetInputProperty(input, QStringLiteral("col_view")).toString());
|
||||
writer->writeTextElement(QStringLiteral("cslook"), GetInputProperty(input, QStringLiteral("col_look")).toString());
|
||||
}
|
||||
}
|
||||
|
||||
TimeRange Node::GetRangeAffectedByKeyframe(NodeKeyframe *key) const
|
||||
{
|
||||
const NodeKeyframeTrack& key_track = GetTrackFromKeyframe(key);
|
||||
@@ -2144,6 +1779,9 @@ double Node::GetGizmoHandleRadius(const QTransform &transform)
|
||||
|
||||
void Node::DrawAndExpandGizmoHandles(QPainter *p, int handle_radius, QRectF *rects, int count)
|
||||
{
|
||||
p->setPen(Qt::NoPen);
|
||||
p->setBrush(Qt::white);
|
||||
|
||||
for (int i=0; i<count; i++) {
|
||||
QRectF& r = rects[i];
|
||||
|
||||
@@ -2198,7 +1836,6 @@ void Node::childEvent(QChildEvent *event)
|
||||
GetImmediate(key->input(), key->element())->insert_keyframe(key);
|
||||
|
||||
connect(key, &NodeKeyframe::TimeChanged, this, &Node::InvalidateFromKeyframeTimeChange);
|
||||
connect(key, &NodeKeyframe::TimeChanged, this, &Node::KeyframeTimeChanged);
|
||||
connect(key, &NodeKeyframe::ValueChanged, this, &Node::InvalidateFromKeyframeValueChange);
|
||||
connect(key, &NodeKeyframe::TypeChanged, this, &Node::InvalidateFromKeyframeTypeChanged);
|
||||
connect(key, &NodeKeyframe::BezierControlInChanged, this, &Node::InvalidateFromKeyframeBezierInChange);
|
||||
@@ -2210,15 +1847,14 @@ void Node::childEvent(QChildEvent *event)
|
||||
TimeRange time_affected = GetRangeAffectedByKeyframe(key);
|
||||
|
||||
disconnect(key, &NodeKeyframe::TimeChanged, this, &Node::InvalidateFromKeyframeTimeChange);
|
||||
disconnect(key, &NodeKeyframe::TimeChanged, this, &Node::KeyframeTimeChanged);
|
||||
disconnect(key, &NodeKeyframe::ValueChanged, this, &Node::InvalidateFromKeyframeValueChange);
|
||||
disconnect(key, &NodeKeyframe::TypeChanged, this, &Node::InvalidateFromKeyframeTypeChanged);
|
||||
disconnect(key, &NodeKeyframe::BezierControlInChanged, this, &Node::InvalidateFromKeyframeBezierInChange);
|
||||
disconnect(key, &NodeKeyframe::BezierControlOutChanged, this, &Node::InvalidateFromKeyframeBezierOutChange);
|
||||
|
||||
GetImmediate(key->input(), key->element())->remove_keyframe(key);
|
||||
|
||||
emit KeyframeRemoved(key);
|
||||
|
||||
GetImmediate(key->input(), key->element())->remove_keyframe(key);
|
||||
ParameterValueChanged(i, time_affected);
|
||||
}
|
||||
}
|
||||
@@ -2281,12 +1917,16 @@ void Node::InvalidateFromKeyframeTimeChange()
|
||||
foreach (const TimeRange& r, invalidate_range) {
|
||||
ParameterValueChanged(key->key_track_ref().input(), r);
|
||||
}
|
||||
|
||||
emit KeyframeTimeChanged(key);
|
||||
}
|
||||
|
||||
void Node::InvalidateFromKeyframeValueChange()
|
||||
{
|
||||
NodeKeyframe* key = static_cast<NodeKeyframe*>(sender());
|
||||
ParameterValueChanged(key->key_track_ref().input(), GetRangeAffectedByKeyframe(key));
|
||||
|
||||
emit KeyframeValueChanged(key);
|
||||
}
|
||||
|
||||
void Node::InvalidateFromKeyframeTypeChanged()
|
||||
@@ -2301,6 +1941,8 @@ void Node::InvalidateFromKeyframeTypeChanged()
|
||||
|
||||
// Invalidate entire range
|
||||
ParameterValueChanged(key->key_track_ref().input(), GetRangeAroundIndex(key->input(), track.indexOf(key), key->track(), key->element()));
|
||||
|
||||
emit KeyframeTypeChanged(key);
|
||||
}
|
||||
|
||||
Project *Node::ArrayInsertCommand::GetRelevantProject() const
|
||||
@@ -2318,119 +1960,40 @@ Project *Node::ArrayResizeCommand::GetRelevantProject() const
|
||||
return node_->project();
|
||||
}
|
||||
|
||||
void NodeSetPositionAndShiftSurroundingsCommand::redo()
|
||||
{
|
||||
if (commands_.isEmpty()) {
|
||||
// Move first node
|
||||
NodeSetPositionCommand* set_pos_command = new NodeSetPositionCommand(node_, relative_, position_, move_dependencies_);
|
||||
set_pos_command->redo_now();
|
||||
commands_.append(set_pos_command);
|
||||
|
||||
// Get bounding rect
|
||||
qreal bounding_rect_sz = 1.0;
|
||||
qreal bounding_rect_half_sz = bounding_rect_sz * 0.5;
|
||||
QRectF bounding_rect(position_.x() - bounding_rect_half_sz, position_.y() - bounding_rect_half_sz, bounding_rect_sz, bounding_rect_sz);
|
||||
|
||||
// Start moving other nodes
|
||||
foreach (Node* surrounding, node_->parent()->nodes()) {
|
||||
if (surrounding != node_) {
|
||||
QPointF surrounding_position = node_->parent()->GetNodePosition(surrounding, relative_);
|
||||
if (bounding_rect.contains(surrounding_position)) {
|
||||
QPointF new_pos = surrounding_position;
|
||||
|
||||
qreal move_rate = 0.50;
|
||||
|
||||
if (surrounding_position.y() < position_.y()) {
|
||||
move_rate = -move_rate;
|
||||
}
|
||||
|
||||
new_pos.setY(new_pos.y() + move_rate);
|
||||
|
||||
auto sur_command = new NodeSetPositionAndShiftSurroundingsCommand(surrounding, relative_, new_pos, true);
|
||||
sur_command->redo();
|
||||
commands_.append(sur_command);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i=0; i<commands_.size(); i++) {
|
||||
commands_.at(i)->redo_now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NodeSetPositionCommand::redo()
|
||||
{
|
||||
graph_ = node_->parent();
|
||||
if (!(added_ = !graph_->NodeMapContainsNode(node_, relevant_))) {
|
||||
old_pos_ = graph_->GetNodePosition(node_, relevant_);
|
||||
added_ = !context_->ContextContainsNode(node_);
|
||||
|
||||
if (!added_) {
|
||||
old_pos_ = context_->GetNodePositionDataInContext(node_);
|
||||
}
|
||||
graph_->SetNodePosition(node_, relevant_, pos_);
|
||||
|
||||
context_->SetNodePositionInContext(node_, pos_);
|
||||
}
|
||||
|
||||
void NodeSetPositionCommand::undo()
|
||||
{
|
||||
if (added_) {
|
||||
graph_->RemoveNodePosition(node_, relevant_);
|
||||
context_->RemoveNodeFromContext(node_);
|
||||
} else {
|
||||
graph_->SetNodePosition(node_, relevant_, old_pos_);
|
||||
context_->SetNodePositionInContext(node_, old_pos_);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeSetPositionAsChildCommand::redo()
|
||||
{
|
||||
if (!sub_command_) {
|
||||
// Calculate position of node
|
||||
NodeGraph *graph = parent_->parent();
|
||||
QPointF pos = graph->GetNodePosition(parent_, relative_);
|
||||
|
||||
// This is a dependency, so we'll place it one X before
|
||||
pos.setX(pos.x() - 1);
|
||||
|
||||
// The Y will be calculated using the index and child count
|
||||
pos.setY(pos.y() - (double(child_count_)*0.5) + this_index_ + 0.5);
|
||||
|
||||
sub_command_ = new MultiUndoCommand();
|
||||
if (shift_surroundings_) {
|
||||
sub_command_->add_child(new NodeSetPositionAndShiftSurroundingsCommand(node_, relative_, pos, true));
|
||||
} else {
|
||||
sub_command_->add_child(new NodeSetPositionCommand(node_, relative_, pos, true));
|
||||
}
|
||||
}
|
||||
|
||||
sub_command_->redo_now();
|
||||
}
|
||||
|
||||
void NodeSetPositionToOffsetOfAnotherNodeCommand::redo()
|
||||
{
|
||||
NodeGraph *graph = node_->parent();
|
||||
old_pos_ = graph->GetNodePosition(node_, relative_);
|
||||
graph->SetNodePosition(node_, relative_, graph->GetNodePosition(other_node_, relative_) + offset_);
|
||||
}
|
||||
|
||||
void NodeSetPositionToOffsetOfAnotherNodeCommand::undo()
|
||||
{
|
||||
NodeGraph *graph = node_->parent();
|
||||
graph->SetNodePosition(node_, relative_, old_pos_);
|
||||
}
|
||||
|
||||
void NodeRemovePositionFromContextCommand::redo()
|
||||
{
|
||||
NodeGraph *graph = node_->parent();
|
||||
|
||||
contained_ = graph->ContextContainsNode(node_, context_);
|
||||
contained_ = context_->ContextContainsNode(node_);
|
||||
|
||||
if (contained_) {
|
||||
old_pos_ = graph->GetNodePosition(node_, context_);
|
||||
graph->RemoveNodePosition(node_, context_);
|
||||
old_pos_ = context_->GetNodePositionDataInContext(node_);
|
||||
context_->RemoveNodeFromContext(node_);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeRemovePositionFromContextCommand::undo()
|
||||
{
|
||||
if (contained_) {
|
||||
NodeGraph *graph = node_->parent();
|
||||
graph->SetNodePosition(node_, context_, old_pos_);
|
||||
context_->SetNodePositionInContext(node_, old_pos_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2438,28 +2001,21 @@ void NodeRemovePositionFromAllContextsCommand::redo()
|
||||
{
|
||||
NodeGraph *graph = node_->parent();
|
||||
|
||||
if (points_.empty()) {
|
||||
// No points yet, let's see what points we should remove
|
||||
auto map = graph->GetPositionMap();
|
||||
for (auto it=map.cbegin(); it!=map.cend(); it++) {
|
||||
if (it.value().contains(node_)) {
|
||||
points_.insert({it.key(), it.value().value(node_)});
|
||||
}
|
||||
foreach (Node* context, graph->nodes()) {
|
||||
if (context->ContextContainsNode(node_)) {
|
||||
contexts_.insert({context, context->GetNodePositionInContext(node_)});
|
||||
context->RemoveNodeFromContext(node_);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it=points_.cbegin(); it!=points_.cend(); it++) {
|
||||
graph->RemoveNodePosition(node_, it->first);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeRemovePositionFromAllContextsCommand::undo()
|
||||
{
|
||||
NodeGraph *graph = node_->parent();
|
||||
|
||||
for (auto it=points_.crbegin(); it!=points_.crend(); it++) {
|
||||
graph->SetNodePosition(node_, it->first, it->second);
|
||||
for (auto it = contexts_.crbegin(); it != contexts_.crend(); it++) {
|
||||
it->first->SetNodePositionInContext(node_, it->second);
|
||||
}
|
||||
|
||||
contexts_.clear();
|
||||
}
|
||||
|
||||
void Node::ValueHint::Hash(QCryptographicHash &hash) const
|
||||
@@ -2472,40 +2028,37 @@ void Node::ValueHint::Hash(QCryptographicHash &hash) const
|
||||
hash.addData(tag().toUtf8());
|
||||
}
|
||||
|
||||
void Node::ValueHint::Load(QXmlStreamReader *reader)
|
||||
void NodeSetPositionAndDependenciesRecursivelyCommand::prepare()
|
||||
{
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("types")) {
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("type")) {
|
||||
type_.append(static_cast<NodeValue::Type>(reader->readElementText().toInt()));
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else if (reader->name() == QStringLiteral("index")) {
|
||||
index_ = reader->readElementText().toInt();
|
||||
} else if (reader->name() == QStringLiteral("tag")) {
|
||||
tag_ = reader->readElementText();
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
move_recursively(node_, pos_.position - context_->GetNodePositionDataInContext(node_).position);
|
||||
}
|
||||
|
||||
void NodeSetPositionAndDependenciesRecursivelyCommand::redo()
|
||||
{
|
||||
for (auto it=commands_.cbegin(); it!=commands_.cend(); it++) {
|
||||
(*it)->redo_now();
|
||||
}
|
||||
}
|
||||
|
||||
void NodeSetPositionAndDependenciesRecursivelyCommand::undo()
|
||||
{
|
||||
for (auto it=commands_.crbegin(); it!=commands_.crend(); it++) {
|
||||
(*it)->undo_now();
|
||||
}
|
||||
}
|
||||
|
||||
void NodeSetPositionAndDependenciesRecursivelyCommand::move_recursively(Node *node, const QPointF &diff)
|
||||
{
|
||||
Node::Position pos = context_->GetNodePositionDataInContext(node);
|
||||
pos += diff;
|
||||
commands_.append(new NodeSetPositionCommand(node_, context_, pos));
|
||||
|
||||
for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) {
|
||||
Node *output = it->second;
|
||||
if (context_->ContextContainsNode(output)) {
|
||||
move_recursively(output, diff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Node::ValueHint::Save(QXmlStreamWriter *writer) const
|
||||
{
|
||||
writer->writeStartElement(QStringLiteral("types"));
|
||||
|
||||
for (auto it=type_.cbegin(); it!=type_.cend(); it++) {
|
||||
writer->writeTextElement(QStringLiteral("type"), QString::number(*it));
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // types
|
||||
|
||||
writer->writeTextElement(QStringLiteral("index"), QString::number(index_));
|
||||
|
||||
writer->writeTextElement(QStringLiteral("tag"), tag_);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user