file/folder restructuring
This commit is contained in:
@@ -1,634 +0,0 @@
|
||||
/***
|
||||
|
||||
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 "clip.h"
|
||||
|
||||
#include <QtMath>
|
||||
|
||||
#include "project/effect.h"
|
||||
#include "project/transition.h"
|
||||
#include "project/footage.h"
|
||||
#include "io/config.h"
|
||||
#include "rendering/cacher.h"
|
||||
#include "rendering/renderfunctions.h"
|
||||
#include "panels/project.h"
|
||||
#include "project/sequence.h"
|
||||
#include "panels/timeline.h"
|
||||
#include "project/media.h"
|
||||
#include "io/clipboard.h"
|
||||
#include "undo.h"
|
||||
#include "debug.h"
|
||||
|
||||
const int kRGBAComponentCount = 4;
|
||||
|
||||
Clip::Clip(Sequence* s) :
|
||||
sequence(s),
|
||||
cacher(this)
|
||||
{
|
||||
enabled_ = true;
|
||||
clip_in_ = 0;
|
||||
timeline_in_ = 0;
|
||||
timeline_out_ = 0;
|
||||
track_ = 0;
|
||||
media_ = nullptr;
|
||||
speed_.value = 1.0;
|
||||
speed_.maintain_audio_pitch = false;
|
||||
reverse_ = false;
|
||||
autoscale_ = olive::CurrentConfig.autoscale_by_default;
|
||||
opening_transition = nullptr;
|
||||
closing_transition = nullptr;
|
||||
undeletable = false;
|
||||
replaced = false;
|
||||
fbo = nullptr;
|
||||
open_ = false;
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
ClipPtr Clip::copy(Sequence* s) {
|
||||
ClipPtr copy = std::make_shared<Clip>(s);
|
||||
|
||||
copy->set_enabled(enabled());
|
||||
copy->set_name(name());
|
||||
copy->set_clip_in(clip_in());
|
||||
copy->set_timeline_in(timeline_in());
|
||||
copy->set_timeline_out(timeline_out());
|
||||
copy->set_track(track());
|
||||
copy->set_color(color());
|
||||
copy->set_media(media(), media_stream_index());
|
||||
copy->set_autoscaled(autoscaled());
|
||||
copy->set_speed(speed());
|
||||
copy->set_reversed(reversed());
|
||||
|
||||
for (int i=0;i<effects.size();i++) {
|
||||
copy->effects.append(effects.at(i)->copy(copy.get()));
|
||||
}
|
||||
|
||||
copy->set_cached_frame_rate((this->sequence == nullptr) ? cached_frame_rate() : this->sequence->frame_rate);
|
||||
|
||||
copy->refresh();
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
bool Clip::IsActiveAt(long timecode)
|
||||
{
|
||||
// these buffers allow clips to be opened and prepared well before they're displayed
|
||||
// as well as closed a little after they're not needed anymore
|
||||
int open_buffer = qCeil(this->sequence->frame_rate*2);
|
||||
int close_buffer = qCeil(this->sequence->frame_rate);
|
||||
|
||||
|
||||
return enabled()
|
||||
&& timeline_in(true) < timecode + open_buffer
|
||||
&& timeline_out(true) > timecode - close_buffer
|
||||
&& timecode - timeline_in(true) + clip_in(true) < media_length();
|
||||
}
|
||||
|
||||
const QColor &Clip::color()
|
||||
{
|
||||
return color_;
|
||||
}
|
||||
|
||||
void Clip::set_color(int r, int g, int b)
|
||||
{
|
||||
color_.setRed(r);
|
||||
color_.setGreen(g);
|
||||
color_.setBlue(b);
|
||||
}
|
||||
|
||||
void Clip::set_color(const QColor &c)
|
||||
{
|
||||
color_ = c;
|
||||
}
|
||||
|
||||
Media *Clip::media()
|
||||
{
|
||||
return media_;
|
||||
}
|
||||
|
||||
FootageStream *Clip::media_stream()
|
||||
{
|
||||
if (media() != nullptr
|
||||
&& media()->get_type() == MEDIA_TYPE_FOOTAGE) {
|
||||
return media()->to_footage()->get_stream_from_file_index(track() < 0, media_stream_index());
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int Clip::media_stream_index()
|
||||
{
|
||||
return media_stream_;
|
||||
}
|
||||
|
||||
void Clip::set_media(Media *m, int s)
|
||||
{
|
||||
media_ = m;
|
||||
media_stream_ = s;
|
||||
}
|
||||
|
||||
bool Clip::enabled()
|
||||
{
|
||||
return enabled_;
|
||||
}
|
||||
|
||||
void Clip::set_enabled(bool e)
|
||||
{
|
||||
enabled_ = e;
|
||||
}
|
||||
|
||||
void Clip::move(ComboAction* ca, long iin, long iout, long iclip_in, int itrack, bool verify_transitions, bool relative)
|
||||
{
|
||||
ca->append(new MoveClipAction(this, iin, iout, iclip_in, itrack, relative));
|
||||
|
||||
if (verify_transitions) {
|
||||
|
||||
// if this is a shared transition, and the corresponding clip will be moved away somehow
|
||||
if (opening_transition != nullptr
|
||||
&& opening_transition->secondary_clip != nullptr
|
||||
&& opening_transition->secondary_clip->timeline_out() != iin) {
|
||||
// separate transition
|
||||
ca->append(new SetPointer(reinterpret_cast<void**>(&opening_transition->secondary_clip), nullptr));
|
||||
ca->append(new AddTransitionCommand(nullptr,
|
||||
opening_transition->secondary_clip,
|
||||
opening_transition,
|
||||
nullptr,
|
||||
0));
|
||||
}
|
||||
|
||||
if (closing_transition != nullptr
|
||||
&& closing_transition->secondary_clip != nullptr
|
||||
&& closing_transition->parent_clip->timeline_in() != iout) {
|
||||
// separate transition
|
||||
ca->append(new SetPointer(reinterpret_cast<void**>(&closing_transition->secondary_clip), nullptr));
|
||||
ca->append(new AddTransitionCommand(nullptr,
|
||||
this,
|
||||
closing_transition,
|
||||
nullptr,
|
||||
0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Clip::reset() {
|
||||
texture = nullptr;
|
||||
}
|
||||
|
||||
void Clip::reset_audio() {
|
||||
if (UsesCacher()) {
|
||||
cacher.ResetAudio();
|
||||
}
|
||||
if (media() != nullptr && media()->get_type() == MEDIA_TYPE_SEQUENCE) {
|
||||
Sequence* nested_sequence = media()->to_sequence().get();
|
||||
for (int i=0;i<nested_sequence->clips.size();i++) {
|
||||
Clip* c = nested_sequence->clips.at(i).get();
|
||||
if (c != nullptr) {
|
||||
c->reset_audio();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Clip::refresh() {
|
||||
// validates media if it was replaced
|
||||
if (replaced && media() != nullptr && media()->get_type() == MEDIA_TYPE_FOOTAGE) {
|
||||
Footage* m = media()->to_footage();
|
||||
|
||||
if (track() < 0 && m->video_tracks.size() > 0) {
|
||||
set_media(media(), m->video_tracks.at(0).file_index);
|
||||
} else if (track() >= 0 && m->audio_tracks.size() > 0) {
|
||||
set_media(media(), m->audio_tracks.at(0).file_index);
|
||||
}
|
||||
}
|
||||
replaced = false;
|
||||
|
||||
// reinitializes all effects... just in case
|
||||
for (int i=0;i<effects.size();i++) {
|
||||
effects.at(i)->refresh();
|
||||
}
|
||||
}
|
||||
|
||||
QVector<Marker> &Clip::get_markers() {
|
||||
if (media() != nullptr) {
|
||||
return media()->get_markers();
|
||||
}
|
||||
return markers;
|
||||
}
|
||||
|
||||
int Clip::IndexOfEffect(Effect *e)
|
||||
{
|
||||
for (int i=0;i<effects.size();i++) {
|
||||
if (effects.at(i).get() == e) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
Clip::~Clip() {
|
||||
if (IsOpen()) {
|
||||
Close(true);
|
||||
}
|
||||
|
||||
effects.clear();
|
||||
}
|
||||
|
||||
long Clip::clip_in(bool with_transition) {
|
||||
if (with_transition && opening_transition != nullptr && opening_transition->secondary_clip != nullptr) {
|
||||
// we must be the secondary clip, so return (timeline in - length)
|
||||
return clip_in_ - opening_transition->get_true_length();
|
||||
}
|
||||
return clip_in_;
|
||||
}
|
||||
|
||||
void Clip::set_clip_in(long c)
|
||||
{
|
||||
clip_in_ = c;
|
||||
}
|
||||
|
||||
long Clip::timeline_in(bool with_transition) {
|
||||
if (with_transition && opening_transition != nullptr && opening_transition->secondary_clip != nullptr) {
|
||||
// we must be the secondary clip, so return (timeline in - length)
|
||||
return timeline_in_ - opening_transition->get_true_length();
|
||||
}
|
||||
return timeline_in_;
|
||||
}
|
||||
|
||||
void Clip::set_timeline_in(long t)
|
||||
{
|
||||
timeline_in_ = t;
|
||||
}
|
||||
|
||||
long Clip::timeline_out(bool with_transitions) {
|
||||
if (with_transitions && closing_transition != nullptr && closing_transition->secondary_clip != nullptr) {
|
||||
// we must be the primary clip, so return (timeline out + length2)
|
||||
return timeline_out_ + closing_transition->get_true_length();
|
||||
} else {
|
||||
return timeline_out_;
|
||||
}
|
||||
}
|
||||
|
||||
void Clip::set_timeline_out(long t)
|
||||
{
|
||||
timeline_out_ = t;
|
||||
}
|
||||
|
||||
bool Clip::reversed()
|
||||
{
|
||||
return reverse_;
|
||||
}
|
||||
|
||||
void Clip::set_reversed(bool r)
|
||||
{
|
||||
reverse_ = r;
|
||||
}
|
||||
|
||||
bool Clip::autoscaled()
|
||||
{
|
||||
return autoscale_;
|
||||
}
|
||||
|
||||
void Clip::set_autoscaled(bool b)
|
||||
{
|
||||
autoscale_ = b;
|
||||
}
|
||||
|
||||
double Clip::cached_frame_rate()
|
||||
{
|
||||
return cached_fr_;
|
||||
}
|
||||
|
||||
void Clip::set_cached_frame_rate(double d)
|
||||
{
|
||||
cached_fr_ = d;
|
||||
}
|
||||
|
||||
const QString &Clip::name()
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
void Clip::set_name(const QString &s)
|
||||
{
|
||||
name_ = s;
|
||||
}
|
||||
|
||||
const ClipSpeed& Clip::speed()
|
||||
{
|
||||
return speed_;
|
||||
}
|
||||
|
||||
void Clip::set_speed(const ClipSpeed& d)
|
||||
{
|
||||
speed_ = d;
|
||||
}
|
||||
|
||||
AVRational Clip::time_base()
|
||||
{
|
||||
return cacher.media_time_base();
|
||||
}
|
||||
|
||||
int Clip::track()
|
||||
{
|
||||
return track_;
|
||||
}
|
||||
|
||||
void Clip::set_track(int t)
|
||||
{
|
||||
track_ = t;
|
||||
}
|
||||
|
||||
// timeline functions
|
||||
long Clip::length() {
|
||||
return timeline_out_ - timeline_in_;
|
||||
}
|
||||
|
||||
double Clip::media_frame_rate() {
|
||||
Q_ASSERT(track_ < 0);
|
||||
if (media_ != nullptr) {
|
||||
double rate = media_->get_frame_rate(media_stream_index());
|
||||
if (!qIsNaN(rate)) return rate;
|
||||
}
|
||||
if (sequence != nullptr) return sequence->frame_rate;
|
||||
return qSNaN();
|
||||
}
|
||||
|
||||
long Clip::media_length() {
|
||||
if (this->sequence != nullptr) {
|
||||
double fr = this->sequence->frame_rate;
|
||||
|
||||
fr /= speed_.value;
|
||||
|
||||
if (media_ == nullptr) {
|
||||
return LONG_MAX;
|
||||
} else {
|
||||
switch (media_->get_type()) {
|
||||
case MEDIA_TYPE_FOOTAGE:
|
||||
{
|
||||
Footage* m = media_->to_footage();
|
||||
const FootageStream* ms = m->get_stream_from_file_index(track_ < 0, media_stream_index());
|
||||
if (ms != nullptr && ms->infinite_length) {
|
||||
return LONG_MAX;
|
||||
} else {
|
||||
return m->get_length_in_frames(fr);
|
||||
}
|
||||
}
|
||||
case MEDIA_TYPE_SEQUENCE:
|
||||
{
|
||||
Sequence* s = media_->to_sequence().get();
|
||||
return rescale_frame_number(s->getEndFrame(), s->frame_rate, fr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Clip::media_width() {
|
||||
if (media_ == nullptr && sequence != nullptr) return sequence->width;
|
||||
switch (media_->get_type()) {
|
||||
case MEDIA_TYPE_FOOTAGE:
|
||||
{
|
||||
const FootageStream* ms = media_stream();
|
||||
if (ms != nullptr) return ms->video_width;
|
||||
if (sequence != nullptr) return sequence->width;
|
||||
break;
|
||||
}
|
||||
case MEDIA_TYPE_SEQUENCE:
|
||||
{
|
||||
Sequence* s = media_->to_sequence().get();
|
||||
return s->width;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Clip::media_height() {
|
||||
if (media_ == nullptr && sequence != nullptr) return sequence->height;
|
||||
switch (media_->get_type()) {
|
||||
case MEDIA_TYPE_FOOTAGE:
|
||||
{
|
||||
const FootageStream* ms = media_stream();
|
||||
if (ms != nullptr) return ms->video_height;
|
||||
if (sequence != nullptr) return sequence->height;
|
||||
}
|
||||
break;
|
||||
case MEDIA_TYPE_SEQUENCE:
|
||||
{
|
||||
Sequence* s = media_->to_sequence().get();
|
||||
return s->height;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Clip::refactor_frame_rate(ComboAction* ca, double multiplier, bool change_timeline_points) {
|
||||
if (change_timeline_points) {
|
||||
this->move(ca,
|
||||
qRound(double(timeline_in_) * multiplier),
|
||||
qRound(double(timeline_out_) * multiplier),
|
||||
qRound(double(clip_in_) * multiplier),
|
||||
track_);
|
||||
}
|
||||
|
||||
// move keyframes
|
||||
for (int i=0;i<effects.size();i++) {
|
||||
EffectPtr e = effects.at(i);
|
||||
for (int j=0;j<e->row_count();j++) {
|
||||
EffectRow* r = e->row(j);
|
||||
for (int l=0;l<r->FieldCount();l++) {
|
||||
EffectField* f = r->Field(l);
|
||||
for (int k=0;k<f->keyframes.size();k++) {
|
||||
ca->append(new SetLong(&f->keyframes[k].time, f->keyframes[k].time, qRound(f->keyframes[k].time * multiplier)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Clip::Open() {
|
||||
if (!open_ && state_change_lock.tryLock()) {
|
||||
open_ = true;
|
||||
|
||||
for (int i=0;i<effects.size();i++) {
|
||||
effects.at(i)->open();
|
||||
}
|
||||
|
||||
// reset variable used to optimize uploading frame data
|
||||
texture_frame = -1;
|
||||
|
||||
if (UsesCacher()) {
|
||||
// cacher will unlock open_lock
|
||||
cacher.Open();
|
||||
} else {
|
||||
// this media doesn't use a cacher, so we unlock here
|
||||
state_change_lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Clip::Close(bool wait) {
|
||||
// thread safety, prevents Close() running from two separate threads simultaneously
|
||||
if (open_ && state_change_lock.tryLock()) {
|
||||
open_ = false;
|
||||
|
||||
if (media() != nullptr && media()->get_type() == MEDIA_TYPE_SEQUENCE) {
|
||||
close_active_clips(media()->to_sequence().get());
|
||||
}
|
||||
|
||||
// destroy opengl texture in main thread
|
||||
delete texture;
|
||||
texture = nullptr;
|
||||
|
||||
// close all effects
|
||||
for (int i=0;i<effects.size();i++) {
|
||||
if (effects.at(i)->is_open()) {
|
||||
effects.at(i)->close();
|
||||
}
|
||||
}
|
||||
|
||||
// delete framebuffers
|
||||
if (fbo != nullptr) {
|
||||
// delete 3 fbos for nested sequences, 2 for most clips
|
||||
int fbo_count = (media() != nullptr && media()->get_type() == MEDIA_TYPE_SEQUENCE) ? 3 : 2;
|
||||
|
||||
for (int j=0;j<fbo_count;j++) {
|
||||
delete fbo[j];
|
||||
}
|
||||
|
||||
delete [] fbo;
|
||||
|
||||
fbo = nullptr;
|
||||
}
|
||||
|
||||
if (UsesCacher()) {
|
||||
cacher.Close(wait);
|
||||
} else {
|
||||
state_change_lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Clip::IsOpen()
|
||||
{
|
||||
return open_;
|
||||
}
|
||||
|
||||
void Clip::Cache(long playhead, bool scrubbing, QVector<Clip*>& nests, int playback_speed) {
|
||||
cacher.Cache(playhead, scrubbing, nests, playback_speed);
|
||||
cacher_frame = playhead;
|
||||
}
|
||||
|
||||
bool Clip::Retrieve()
|
||||
{
|
||||
bool ret = false;
|
||||
|
||||
if (UsesCacher()) {
|
||||
|
||||
// Retrieve the frame from the cacher that we requested in Cache().
|
||||
AVFrame* frame = cacher.Retrieve();
|
||||
|
||||
// Wait for exclusive control of the queue to avoid any threading collisions
|
||||
cacher.queue()->lock();
|
||||
|
||||
// Check if we retrieved a frame (nullptr) and if the queue stil contains this frame.
|
||||
//
|
||||
// `nullptr` is returned if the cacher failed to get any sort of frame and is uncommon, but we do need
|
||||
// to handle it.
|
||||
//
|
||||
// We check the queue because in some situations (e.g. intensive scrubbing), in the time it took to gain
|
||||
// exclusive control of the queue, the cacher may have deleted the frame.
|
||||
// Therefore we check to ensure the queue still contains the frame now that we have exclusive control,
|
||||
// to avoid any attempt to utilize now-freed memory.
|
||||
|
||||
if (frame != nullptr && cacher.queue()->contains(frame)) {
|
||||
|
||||
// check if the opengl texture exists yet, create it if not
|
||||
if (texture == nullptr) {
|
||||
texture = new QOpenGLTexture(QOpenGLTexture::Target2D);
|
||||
|
||||
// the raw frame size may differ from the one we're using (e.g. a lower resolution proxy), so we make sure
|
||||
// the texture is using the correct dimensions, but then treat it as if it's the original resolution in the
|
||||
// composition
|
||||
texture->setSize(cacher.media_width(), cacher.media_height());
|
||||
|
||||
texture->setFormat(QOpenGLTexture::RGBA8_UNorm);
|
||||
texture->setMipLevels(texture->maximumMipLevels());
|
||||
texture->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear);
|
||||
texture->allocateStorage(QOpenGLTexture::RGBA, QOpenGLTexture::UInt8);
|
||||
}
|
||||
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, frame->linesize[0]/kRGBAComponentCount);
|
||||
|
||||
// 2 data buffers to ping-pong between
|
||||
bool using_db_1 = true;
|
||||
uint8_t* data_buffer_1 = frame->data[0];
|
||||
uint8_t* data_buffer_2 = nullptr;
|
||||
|
||||
int frame_size = frame->linesize[0]*frame->height;
|
||||
|
||||
for (int i=0;i<effects.size();i++) {
|
||||
Effect* e = effects.at(i).get();
|
||||
if ((e->Flags() & Effect::ImageFlag) && e->IsEnabled()) {
|
||||
if (data_buffer_1 == frame->data[0]) {
|
||||
data_buffer_1 = new uint8_t[frame_size];
|
||||
data_buffer_2 = new uint8_t[frame_size];
|
||||
|
||||
memcpy(data_buffer_1, frame->data[0], frame_size);
|
||||
}
|
||||
|
||||
e->process_image(get_timecode(this, cacher_frame),
|
||||
using_db_1 ? data_buffer_1 : data_buffer_2,
|
||||
using_db_1 ? data_buffer_2 : data_buffer_1,
|
||||
frame_size
|
||||
);
|
||||
|
||||
using_db_1 = !using_db_1;
|
||||
}
|
||||
}
|
||||
|
||||
texture->setData(QOpenGLTexture::RGBA,
|
||||
QOpenGLTexture::UInt8,
|
||||
const_cast<const uint8_t*>(using_db_1 ? data_buffer_1 : data_buffer_2));
|
||||
|
||||
if (data_buffer_1 != frame->data[0]) {
|
||||
qDebug() << data_buffer_1 << frame->data[0];
|
||||
delete [] data_buffer_1;
|
||||
delete [] data_buffer_2;
|
||||
}
|
||||
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
|
||||
ret = true;
|
||||
} else {
|
||||
qCritical() << "Failed to retrieve frame for clip" << name();
|
||||
}
|
||||
|
||||
cacher.queue()->unlock();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool Clip::UsesCacher()
|
||||
{
|
||||
return track() >= 0 || (media() != nullptr && media()->get_type() == MEDIA_TYPE_FOOTAGE);
|
||||
}
|
||||
-181
@@ -1,181 +0,0 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef CLIP_H
|
||||
#define CLIP_H
|
||||
|
||||
#include <memory>
|
||||
#include <QWaitCondition>
|
||||
#include <QMutex>
|
||||
#include <QVector>
|
||||
#include <QOpenGLFramebufferObject>
|
||||
#include <QOpenGLTexture>
|
||||
|
||||
#include "rendering/cacher.h"
|
||||
|
||||
#include "project/effect.h"
|
||||
#include "project/transition.h"
|
||||
#include "project/comboaction.h"
|
||||
#include "project/media.h"
|
||||
#include "footage.h"
|
||||
|
||||
#include "marker.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavfilter/avfilter.h>
|
||||
}
|
||||
|
||||
struct ClipSpeed {
|
||||
double value;
|
||||
bool maintain_audio_pitch;
|
||||
};
|
||||
|
||||
using ClipPtr = std::shared_ptr<Clip>;
|
||||
|
||||
class Sequence;
|
||||
|
||||
class Clip {
|
||||
public:
|
||||
Clip(Sequence *s);
|
||||
~Clip();
|
||||
ClipPtr copy(Sequence *s);
|
||||
|
||||
bool IsActiveAt(long timecode);
|
||||
|
||||
const QColor& color();
|
||||
void set_color(int r, int g, int b);
|
||||
void set_color(const QColor& c);
|
||||
|
||||
Media* media();
|
||||
FootageStream* media_stream();
|
||||
int media_stream_index();
|
||||
int media_width();
|
||||
int media_height();
|
||||
double media_frame_rate();
|
||||
long media_length();
|
||||
void set_media(Media* m, int s);
|
||||
|
||||
bool enabled();
|
||||
void set_enabled(bool e);
|
||||
|
||||
void move(ComboAction* ca,
|
||||
long iin,
|
||||
long iout,
|
||||
long iclip_in,
|
||||
int itrack,
|
||||
bool verify_transitions = true,
|
||||
bool relative = false);
|
||||
|
||||
long clip_in(bool with_transition = false);
|
||||
void set_clip_in(long c);
|
||||
|
||||
long timeline_in(bool with_transition = false);
|
||||
void set_timeline_in(long t);
|
||||
|
||||
long timeline_out(bool with_transition = false);
|
||||
void set_timeline_out(long t);
|
||||
|
||||
int track();
|
||||
void set_track(int t);
|
||||
|
||||
bool reversed();
|
||||
void set_reversed(bool r);
|
||||
|
||||
bool autoscaled();
|
||||
void set_autoscaled(bool b);
|
||||
|
||||
double cached_frame_rate();
|
||||
void set_cached_frame_rate(double d);
|
||||
|
||||
const QString& name();
|
||||
void set_name(const QString& s);
|
||||
|
||||
const ClipSpeed& speed();
|
||||
void set_speed(const ClipSpeed& s);
|
||||
|
||||
AVRational time_base();
|
||||
|
||||
void reset_audio();
|
||||
void reset();
|
||||
void refresh();
|
||||
|
||||
long length();
|
||||
|
||||
void refactor_frame_rate(ComboAction* ca, double multiplier, bool change_timeline_points);
|
||||
Sequence* sequence;
|
||||
|
||||
// markers
|
||||
QVector<Marker>& get_markers();
|
||||
|
||||
// other variables (should be deep copied/duplicated in copy())
|
||||
int IndexOfEffect(Effect* e);
|
||||
QList<EffectPtr> effects;
|
||||
QVector<int> linked;
|
||||
TransitionPtr opening_transition;
|
||||
TransitionPtr closing_transition;
|
||||
|
||||
// playback functions
|
||||
void Open();
|
||||
void Cache(long playhead, bool scrubbing, QVector<Clip*> &nests, int playback_speed);
|
||||
bool Retrieve();
|
||||
void Close(bool wait);
|
||||
bool IsOpen();
|
||||
|
||||
bool UsesCacher();
|
||||
|
||||
// temporary variables
|
||||
int load_id;
|
||||
bool undeletable;
|
||||
bool replaced;
|
||||
|
||||
// caching functions
|
||||
QMutex state_change_lock;
|
||||
QMutex cache_lock;
|
||||
|
||||
// video playback variables
|
||||
QOpenGLFramebufferObject** fbo;
|
||||
QOpenGLTexture* texture;
|
||||
long texture_frame;
|
||||
|
||||
private:
|
||||
// timeline variables (should be copied in copy())
|
||||
bool enabled_;
|
||||
long clip_in_;
|
||||
long timeline_in_;
|
||||
long timeline_out_;
|
||||
int track_;
|
||||
QString name_;
|
||||
Media* media_;
|
||||
int media_stream_;
|
||||
ClipSpeed speed_;
|
||||
double cached_fr_;
|
||||
bool reverse_;
|
||||
bool autoscale_;
|
||||
|
||||
Cacher cacher;
|
||||
long cacher_frame;
|
||||
|
||||
QVector<Marker> markers;
|
||||
QColor color_;
|
||||
bool open_;
|
||||
};
|
||||
|
||||
#endif // CLIP_H
|
||||
@@ -1,36 +1,34 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef SELECTION_H
|
||||
#define SELECTION_H
|
||||
|
||||
struct Selection {
|
||||
long in;
|
||||
long out;
|
||||
int track;
|
||||
|
||||
long old_in;
|
||||
long old_out;
|
||||
int old_track;
|
||||
|
||||
bool trim_in;
|
||||
};
|
||||
|
||||
#endif // SELECTION_H
|
||||
/***
|
||||
|
||||
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 "clipboard.h"
|
||||
|
||||
#include "timeline/clip.h"
|
||||
#include "effects/effect.h"
|
||||
#include "effects/transition.h"
|
||||
|
||||
int clipboard_type = CLIPBOARD_TYPE_CLIP;
|
||||
QVector<VoidPtr> clipboard;
|
||||
QVector<TransitionPtr> clipboard_transitions;
|
||||
|
||||
void clear_clipboard() {
|
||||
clipboard.clear();
|
||||
clipboard_transitions.clear();
|
||||
}
|
||||
@@ -1,36 +1,38 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef EFFECTLOADERS_H
|
||||
#define EFFECTLOADERS_H
|
||||
|
||||
#include <QList>
|
||||
#include <QThread>
|
||||
|
||||
void init_effects();
|
||||
|
||||
class EffectInit : public QThread {
|
||||
public:
|
||||
EffectInit();
|
||||
protected:
|
||||
void run();
|
||||
};
|
||||
|
||||
#endif // EFFECTLOADERS_H
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef CLIPBOARD_H
|
||||
#define CLIPBOARD_H
|
||||
|
||||
#include <QVector>
|
||||
|
||||
#include <effects/transition.h>
|
||||
|
||||
#define CLIPBOARD_TYPE_CLIP 0
|
||||
#define CLIPBOARD_TYPE_EFFECT 1
|
||||
|
||||
using VoidPtr = std::shared_ptr<void>;
|
||||
|
||||
extern int clipboard_type;
|
||||
extern QVector<TransitionPtr> clipboard_transitions;
|
||||
extern QVector<VoidPtr> clipboard;
|
||||
void clear_clipboard();
|
||||
|
||||
#endif // CLIPBOARD_H
|
||||
@@ -1,40 +0,0 @@
|
||||
#include "comboaction.h"
|
||||
|
||||
ComboAction::ComboAction() {}
|
||||
|
||||
ComboAction::~ComboAction() {
|
||||
for (int i=0;i<commands.size();i++) {
|
||||
delete commands.at(i);
|
||||
}
|
||||
}
|
||||
|
||||
void ComboAction::undo() {
|
||||
for (int i=commands.size()-1;i>=0;i--) {
|
||||
commands.at(i)->undo();
|
||||
}
|
||||
for (int i=0;i<post_commands.size();i++) {
|
||||
post_commands.at(i)->undo();
|
||||
}
|
||||
}
|
||||
|
||||
void ComboAction::redo() {
|
||||
for (int i=0;i<commands.size();i++) {
|
||||
commands.at(i)->redo();
|
||||
}
|
||||
for (int i=0;i<post_commands.size();i++) {
|
||||
post_commands.at(i)->redo();
|
||||
}
|
||||
}
|
||||
|
||||
void ComboAction::append(QUndoCommand* u) {
|
||||
commands.append(u);
|
||||
}
|
||||
|
||||
void ComboAction::appendPost(QUndoCommand* u) {
|
||||
post_commands.append(u);
|
||||
}
|
||||
|
||||
bool ComboAction::hasActions()
|
||||
{
|
||||
return commands.size() > 0;
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
#ifndef COMBOACTION_H
|
||||
#define COMBOACTION_H
|
||||
|
||||
#include <QUndoCommand>
|
||||
#include <QVector>
|
||||
|
||||
/**
|
||||
* @brief The ComboAction class
|
||||
*
|
||||
* The Undo/Redo system works by stacking an action that knows how to "do" and also "undo" itself. As Olive is
|
||||
* a very complex program, there are many actions that can in one "user action". For example, moving a clip over
|
||||
* another will delete the clip under it, which is at least two actions that need to be undone if the user clicks
|
||||
* undo, however the user only (knowingly) did one thing and would find it confusing if this one user action required
|
||||
* to undos to complete undo.
|
||||
*
|
||||
* To address this, ComboAction is an undo action that simply compiles several possible actions into one, doing them
|
||||
* all on every redo, and undoing them all on every undo.
|
||||
*/
|
||||
class ComboAction : public QUndoCommand {
|
||||
public:
|
||||
/**
|
||||
* @brief ComboAction Constructor. Currently empty.
|
||||
*/
|
||||
ComboAction();
|
||||
|
||||
/**
|
||||
* @brief ~ComboAction Destructor. Cleans up all QUndoCommand classes that have been added to it.
|
||||
*/
|
||||
virtual ~ComboAction() override;
|
||||
|
||||
/**
|
||||
* @brief Undo Function
|
||||
*
|
||||
* Called by the QUndoStack to undo.
|
||||
*
|
||||
* Calls QUndoCommand::undo() on all QUndoCommand objects added by append()
|
||||
* in REVERSE order to how they were added. Then calls QUndoCommand::redo() on every action added by appendPost()
|
||||
* in the order they were added (not in reverse).
|
||||
*/
|
||||
virtual void undo() override;
|
||||
|
||||
/**
|
||||
* @brief Redo Function
|
||||
*
|
||||
* Called by the QUndoStack to redo.
|
||||
*
|
||||
* Calls QUndoCommand::redo() on all QUndoCommand objects added by append()
|
||||
* in the order they were added. Then calls QUndoCommand::redo() on every action added by appendPost() in
|
||||
* the order they were added.
|
||||
*/
|
||||
virtual void redo() override;
|
||||
|
||||
/**
|
||||
* @brief Add an undo action
|
||||
*
|
||||
* Add an action to be done/undone. ComboAction takes ownership of this QUndoCommand and will delete it when
|
||||
* it is deleted.
|
||||
*
|
||||
* @param u
|
||||
*
|
||||
* The QUndoCommand to add.
|
||||
*/
|
||||
void append(QUndoCommand* u);
|
||||
|
||||
/**
|
||||
* @brief Add a post-undo PostAction
|
||||
*
|
||||
* Sometimes the results of all the actions require another function to be called (e.g. repainting the Viewer).
|
||||
* QUndoCommand objects added by appendPost() will run after EVERY QUndoCommand added by append() has been run.
|
||||
*
|
||||
* @param u
|
||||
*
|
||||
* The PostAction to add
|
||||
*/
|
||||
void appendPost(QUndoCommand* u);
|
||||
|
||||
/**
|
||||
* @brief Returns whether actions have been appended or not
|
||||
*
|
||||
* @return **TRUE** if actions have been appended, **FALSE** if not.
|
||||
*/
|
||||
bool hasActions();
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Internal array of QUndoCommand objects
|
||||
*/
|
||||
QVector<QUndoCommand*> commands;
|
||||
|
||||
/**
|
||||
* @brief Internal array of PostAction objects
|
||||
*/
|
||||
QVector<QUndoCommand*> post_commands;
|
||||
};
|
||||
|
||||
#endif // COMBOACTION_H
|
||||
-1056
File diff suppressed because it is too large
Load Diff
@@ -1,261 +0,0 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef EFFECT_H
|
||||
#define EFFECT_H
|
||||
|
||||
#include <memory>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include <QColor>
|
||||
#include <QOpenGLFunctions>
|
||||
#include <QOpenGLShaderProgram>
|
||||
#include <QOpenGLTexture>
|
||||
#include <QMutex>
|
||||
#include <QThread>
|
||||
#include <QLabel>
|
||||
#include <QWidget>
|
||||
#include <QGridLayout>
|
||||
#include <QPushButton>
|
||||
#include <QMouseEvent>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QXmlStreamWriter>
|
||||
#include <random>
|
||||
|
||||
#include "ui/collapsiblewidget.h"
|
||||
#include "ui/checkboxex.h"
|
||||
#include "effectfields.h"
|
||||
#include "effectrow.h"
|
||||
#include "effectgizmo.h"
|
||||
|
||||
class Clip;
|
||||
|
||||
class Effect;
|
||||
using EffectPtr = std::shared_ptr<Effect>;
|
||||
|
||||
struct EffectMeta {
|
||||
QString name;
|
||||
QString category;
|
||||
QString filename;
|
||||
QString path;
|
||||
QString tooltip;
|
||||
int internal;
|
||||
int type;
|
||||
int subtype;
|
||||
};
|
||||
extern QVector<EffectMeta> effects;
|
||||
|
||||
double log_volume(double linear);
|
||||
|
||||
enum EffectType {
|
||||
EFFECT_TYPE_INVALID,
|
||||
EFFECT_TYPE_VIDEO,
|
||||
EFFECT_TYPE_AUDIO,
|
||||
EFFECT_TYPE_EFFECT,
|
||||
EFFECT_TYPE_TRANSITION
|
||||
};
|
||||
|
||||
enum EffectKeyframeType {
|
||||
EFFECT_KEYFRAME_LINEAR,
|
||||
EFFECT_KEYFRAME_BEZIER,
|
||||
EFFECT_KEYFRAME_HOLD
|
||||
};
|
||||
|
||||
enum EffectInternal {
|
||||
EFFECT_INTERNAL_TRANSFORM,
|
||||
EFFECT_INTERNAL_TEXT,
|
||||
EFFECT_INTERNAL_SOLID,
|
||||
EFFECT_INTERNAL_NOISE,
|
||||
EFFECT_INTERNAL_VOLUME,
|
||||
EFFECT_INTERNAL_PAN,
|
||||
EFFECT_INTERNAL_TONE,
|
||||
EFFECT_INTERNAL_SHAKE,
|
||||
EFFECT_INTERNAL_TIMECODE,
|
||||
EFFECT_INTERNAL_MASK,
|
||||
EFFECT_INTERNAL_FILLLEFTRIGHT,
|
||||
EFFECT_INTERNAL_VST,
|
||||
EFFECT_INTERNAL_CORNERPIN,
|
||||
EFFECT_INTERNAL_FREI0R,
|
||||
EFFECT_INTERNAL_COUNT
|
||||
};
|
||||
|
||||
struct GLTextureCoords {
|
||||
int grid_size;
|
||||
|
||||
int vertexTopLeftX;
|
||||
int vertexTopLeftY;
|
||||
int vertexTopLeftZ;
|
||||
int vertexTopRightX;
|
||||
int vertexTopRightY;
|
||||
int vertexTopRightZ;
|
||||
int vertexBottomLeftX;
|
||||
int vertexBottomLeftY;
|
||||
int vertexBottomLeftZ;
|
||||
int vertexBottomRightX;
|
||||
int vertexBottomRightY;
|
||||
int vertexBottomRightZ;
|
||||
|
||||
float textureTopLeftX;
|
||||
float textureTopLeftY;
|
||||
float textureTopLeftQ;
|
||||
float textureTopRightX;
|
||||
float textureTopRightY;
|
||||
float textureTopRightQ;
|
||||
float textureBottomRightX;
|
||||
float textureBottomRightY;
|
||||
float textureBottomRightQ;
|
||||
float textureBottomLeftX;
|
||||
float textureBottomLeftY;
|
||||
float textureBottomLeftQ;
|
||||
|
||||
int blendmode;
|
||||
float opacity;
|
||||
};
|
||||
|
||||
const EffectMeta* get_meta_from_name(const QString& input);
|
||||
|
||||
qint16 mix_audio_sample(qint16 a, qint16 b);
|
||||
|
||||
class Effect : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Effect(Clip *c, const EffectMeta* em);
|
||||
~Effect();
|
||||
|
||||
Clip* parent_clip;
|
||||
const EffectMeta* meta;
|
||||
int id;
|
||||
QString name;
|
||||
|
||||
void AddRow(EffectRow* row);
|
||||
|
||||
EffectRow* row(int i);
|
||||
int row_count();
|
||||
|
||||
EffectGizmo* add_gizmo(int type);
|
||||
EffectGizmo* gizmo(int i);
|
||||
int gizmo_count();
|
||||
|
||||
bool IsEnabled();
|
||||
void SetEnabled(bool b);
|
||||
|
||||
virtual void refresh();
|
||||
|
||||
virtual EffectPtr copy(Clip* c);
|
||||
void copy_field_keyframes(EffectPtr e);
|
||||
|
||||
virtual void load(QXmlStreamReader& stream);
|
||||
virtual void custom_load(QXmlStreamReader& stream);
|
||||
virtual void save(QXmlStreamWriter& stream);
|
||||
|
||||
void load_from_string(const QByteArray &s);
|
||||
QByteArray save_to_string();
|
||||
|
||||
// glsl handling
|
||||
bool is_open();
|
||||
void open();
|
||||
void close();
|
||||
bool is_glsl_linked();
|
||||
virtual void startEffect();
|
||||
virtual void endEffect();
|
||||
|
||||
enum VideoEffectFlags {
|
||||
ShaderFlag = 0x1,
|
||||
CoordsFlag = 0x2,
|
||||
SuperimposeFlag = 0x4,
|
||||
ImageFlag = 0x8
|
||||
};
|
||||
int Flags();
|
||||
void SetFlags(int flags);
|
||||
|
||||
int getIterations();
|
||||
void setIterations(int i);
|
||||
|
||||
const char* ffmpeg_filter;
|
||||
|
||||
virtual void process_image(double timecode, uint8_t* input, uint8_t* output, int size);
|
||||
virtual void process_shader(double timecode, GLTextureCoords&, int iteration);
|
||||
virtual void process_coords(double timecode, GLTextureCoords& coords, int data);
|
||||
virtual GLuint process_superimpose(double timecode);
|
||||
virtual void process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int channel_count);
|
||||
|
||||
virtual void gizmo_draw(double timecode, GLTextureCoords& coords);
|
||||
void gizmo_move(EffectGizmo* sender, int x_movement, int y_movement, double timecode, bool done);
|
||||
void gizmo_world_to_screen();
|
||||
bool are_gizmos_enabled();
|
||||
|
||||
template <typename T>
|
||||
T randomNumber()
|
||||
{
|
||||
static std::random_device device;
|
||||
static std::mt19937 generator(device());
|
||||
static std::uniform_int_distribution<> distribution(std::numeric_limits<T>::min(), std::numeric_limits<T>::max());
|
||||
return distribution(generator);
|
||||
}
|
||||
|
||||
static EffectPtr Create(Clip *c, const EffectMeta *em);
|
||||
static const EffectMeta* GetInternalMeta(int internal_id, int type);
|
||||
public slots:
|
||||
void FieldChanged();
|
||||
signals:
|
||||
void EnabledChanged(bool);
|
||||
private slots:
|
||||
void delete_self();
|
||||
void move_up();
|
||||
void move_down();
|
||||
void save_to_file();
|
||||
void load_from_file();
|
||||
protected:
|
||||
// glsl effect
|
||||
QOpenGLShaderProgram* glslProgram;
|
||||
QString vertPath;
|
||||
QString fragPath;
|
||||
|
||||
// superimpose effect
|
||||
QImage img;
|
||||
QOpenGLTexture* texture;
|
||||
|
||||
bool AlwaysUpdate();
|
||||
void SetAlwaysUpdate(bool b);
|
||||
|
||||
private:
|
||||
bool isOpen;
|
||||
QVector<EffectRow*> rows;
|
||||
QVector<EffectGizmo*> gizmos;
|
||||
bool bound;
|
||||
int iterations;
|
||||
|
||||
bool enabled_;
|
||||
|
||||
int flags_;
|
||||
|
||||
// enable effect to update constantly
|
||||
bool enable_always_update_;
|
||||
|
||||
// superimpose functions
|
||||
virtual void redraw(double timecode);
|
||||
bool valueHasChanged(double timecode);
|
||||
QVector<QVariant> cachedValues;
|
||||
void delete_texture();
|
||||
void validate_meta_path();
|
||||
};
|
||||
|
||||
#endif // EFFECT_H
|
||||
@@ -1,14 +0,0 @@
|
||||
#ifndef EFFECTFIELDS_H
|
||||
#define EFFECTFIELDS_H
|
||||
|
||||
#include "effectfields/boolfield.h"
|
||||
#include "effectfields/buttonfield.h"
|
||||
#include "effectfields/colorfield.h"
|
||||
#include "effectfields/combofield.h"
|
||||
#include "effectfields/doublefield.h"
|
||||
#include "effectfields/filefield.h"
|
||||
#include "effectfields/fontfield.h"
|
||||
#include "effectfields/labelfield.h"
|
||||
#include "effectfields/stringfield.h"
|
||||
|
||||
#endif // EFFECTFIELDS_H
|
||||
@@ -1,38 +0,0 @@
|
||||
#include "boolfield.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
|
||||
BoolField::BoolField(EffectRow *parent, const QString &id) :
|
||||
EffectField(parent, id, EFFECT_FIELD_BOOL)
|
||||
{}
|
||||
|
||||
bool BoolField::GetBoolAt(double timecode)
|
||||
{
|
||||
return GetValueAt(timecode).toBool();
|
||||
}
|
||||
|
||||
QWidget *BoolField::CreateWidget()
|
||||
{
|
||||
QCheckBox* cb = new QCheckBox();
|
||||
|
||||
connect(cb, SIGNAL(toggled(bool)), this, SLOT(UpdateFromWidget(bool)));
|
||||
connect(this, SIGNAL(EnabledChanged(bool)), cb, SLOT(setEnabled(bool)));
|
||||
connect(cb, SIGNAL(toggled(bool)), this, SIGNAL(Toggled(bool)));
|
||||
|
||||
return cb;
|
||||
}
|
||||
|
||||
QVariant BoolField::ConvertStringToValue(const QString &s)
|
||||
{
|
||||
return (s == "1");
|
||||
}
|
||||
|
||||
QString BoolField::ConvertValueToString(const QVariant &v)
|
||||
{
|
||||
return QString::number(v.toBool());
|
||||
}
|
||||
|
||||
void BoolField::UpdateFromWidget(bool b)
|
||||
{
|
||||
SetValueAt(Now(), b);
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
#ifndef BOOLFIELD_H
|
||||
#define BOOLFIELD_H
|
||||
|
||||
#include "effectfield.h"
|
||||
|
||||
class BoolField : public EffectField
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
BoolField(EffectRow* parent, const QString& id);
|
||||
|
||||
bool GetBoolAt(double timecode);
|
||||
|
||||
virtual QWidget* CreateWidget() override;
|
||||
|
||||
virtual QVariant ConvertStringToValue(const QString& s) override;
|
||||
virtual QString ConvertValueToString(const QVariant& v) override;
|
||||
signals:
|
||||
void Toggled(bool);
|
||||
private slots:
|
||||
void UpdateFromWidget(bool b);
|
||||
};
|
||||
|
||||
#endif // BOOLFIELD_H
|
||||
@@ -1,36 +0,0 @@
|
||||
#include "buttonfield.h"
|
||||
|
||||
#include <QPushButton>
|
||||
|
||||
ButtonField::ButtonField(EffectRow *parent, const QString &string) :
|
||||
EffectField(parent, nullptr, EFFECT_FIELD_UI),
|
||||
button_text_(string)
|
||||
{}
|
||||
|
||||
void ButtonField::SetCheckable(bool c)
|
||||
{
|
||||
checkable_ = c;
|
||||
}
|
||||
|
||||
void ButtonField::SetChecked(bool c)
|
||||
{
|
||||
checked_ = c;
|
||||
emit CheckedChanged(c);
|
||||
}
|
||||
|
||||
QWidget *ButtonField::CreateWidget()
|
||||
{
|
||||
QPushButton* button = new QPushButton();
|
||||
|
||||
button->setCheckable(checkable_);
|
||||
button->setEnabled(IsEnabled());
|
||||
button->setText(button_text_);
|
||||
|
||||
connect(this, SIGNAL(CheckedChanged(bool)), button, SLOT(setChecked(bool)));
|
||||
connect(this, SIGNAL(EnabledChanged(bool)), button, SLOT(setEnabled(bool)));
|
||||
connect(button, SIGNAL(clicked(bool)), this, SIGNAL(Clicked()));
|
||||
connect(button, SIGNAL(toggled(bool)), this, SLOT(SetChecked(bool)));
|
||||
connect(button, SIGNAL(toggled(bool)), this, SIGNAL(Toggled(bool)));
|
||||
|
||||
return button;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
#ifndef BUTTONFIELD_H
|
||||
#define BUTTONFIELD_H
|
||||
|
||||
#include "effectfield.h"
|
||||
|
||||
class ButtonField : public EffectField
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ButtonField(EffectRow* parent, const QString& string);
|
||||
|
||||
void SetCheckable(bool c);
|
||||
virtual QWidget* CreateWidget() override;
|
||||
|
||||
public slots:
|
||||
void SetChecked(bool c);
|
||||
|
||||
signals:
|
||||
void CheckedChanged(bool);
|
||||
void Toggled(bool);
|
||||
|
||||
private:
|
||||
bool checkable_;
|
||||
bool checked_;
|
||||
|
||||
QString button_text_;
|
||||
};
|
||||
|
||||
#endif // BUTTONFIELD_H
|
||||
@@ -1,39 +0,0 @@
|
||||
#include "colorfield.h"
|
||||
|
||||
#include <QColor>
|
||||
|
||||
#include "ui/colorbutton.h"
|
||||
|
||||
ColorField::ColorField(EffectRow* parent, const QString& id) :
|
||||
EffectField(parent, id, EFFECT_FIELD_COLOR)
|
||||
{}
|
||||
|
||||
QColor ColorField::GetColorAt(double timecode)
|
||||
{
|
||||
return GetValueAt(timecode).value<QColor>();
|
||||
}
|
||||
|
||||
QWidget *ColorField::CreateWidget()
|
||||
{
|
||||
ColorButton* cb = new ColorButton();
|
||||
|
||||
connect(cb, SIGNAL(color_changed(const QColor &)), this, SLOT(UpdateFromWidget(const QColor &)));
|
||||
connect(this, SIGNAL(EnabledChanged(bool)), cb, SLOT(setEnabled(bool)));
|
||||
|
||||
return cb;
|
||||
}
|
||||
|
||||
QVariant ColorField::ConvertStringToValue(const QString &s)
|
||||
{
|
||||
return QColor(s);
|
||||
}
|
||||
|
||||
QString ColorField::ConvertValueToString(const QVariant &v)
|
||||
{
|
||||
return v.value<QColor>().name();
|
||||
}
|
||||
|
||||
void ColorField::UpdateFromWidget(const QColor& c)
|
||||
{
|
||||
SetValueAt(Now(), c);
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
#ifndef COLORFIELD_H
|
||||
#define COLORFIELD_H
|
||||
|
||||
#include "effectfield.h"
|
||||
|
||||
class ColorField : public EffectField
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ColorField(EffectRow* parent, const QString& id);
|
||||
|
||||
QColor GetColorAt(double timecode);
|
||||
|
||||
virtual QWidget* CreateWidget() override;
|
||||
|
||||
virtual QVariant ConvertStringToValue(const QString& s) override;
|
||||
virtual QString ConvertValueToString(const QVariant& v) override;
|
||||
private slots:
|
||||
void UpdateFromWidget(const QColor &c);
|
||||
};
|
||||
|
||||
#endif // COLORFIELD_H
|
||||
@@ -1,38 +0,0 @@
|
||||
#include "combofield.h"
|
||||
|
||||
#include "ui/comboboxex.h"
|
||||
|
||||
ComboField::ComboField(EffectRow* parent, const QString& id) :
|
||||
EffectField(parent, id, EFFECT_FIELD_COMBO)
|
||||
{}
|
||||
|
||||
void ComboField::AddItem(const QString &text, const QVariant &data)
|
||||
{
|
||||
ComboFieldItem item;
|
||||
|
||||
item.name = text;
|
||||
item.data = data;
|
||||
|
||||
items_.append(item);
|
||||
}
|
||||
|
||||
QWidget *ComboField::CreateWidget()
|
||||
{
|
||||
ComboBoxEx* cb = new ComboBoxEx();
|
||||
|
||||
cb->setScrollingEnabled(false);
|
||||
|
||||
for (int i=0;i<items_.size();i++) {
|
||||
cb->addItem(items_.at(i).name);
|
||||
}
|
||||
|
||||
connect(cb, SIGNAL(activated(int)), this, SLOT(UpdateFromWidget(int)));
|
||||
connect(this, SIGNAL(EnabledChanged(bool)), cb, SLOT(setEnabled(bool)));
|
||||
|
||||
return cb;
|
||||
}
|
||||
|
||||
void ComboField::UpdateFromWidget(int index)
|
||||
{
|
||||
SetValueAt(Now(), items_.at(index).data);
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
#ifndef COMBOFIELD_H
|
||||
#define COMBOFIELD_H
|
||||
|
||||
#include "effectfield.h"
|
||||
|
||||
struct ComboFieldItem {
|
||||
QString name;
|
||||
QVariant data;
|
||||
};
|
||||
|
||||
class ComboField : public EffectField
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ComboField(EffectRow* parent, const QString& id);
|
||||
|
||||
void AddItem(const QString& text, const QVariant& data);
|
||||
|
||||
virtual QWidget *CreateWidget() override;
|
||||
|
||||
signals:
|
||||
void IndexChanged(int i);
|
||||
|
||||
private:
|
||||
QVector<ComboFieldItem> items_;
|
||||
|
||||
private slots:
|
||||
void UpdateFromWidget(int index);
|
||||
};
|
||||
|
||||
#endif // COMBOFIELD_H
|
||||
@@ -1,91 +0,0 @@
|
||||
#include "doublefield.h"
|
||||
|
||||
DoubleField::DoubleField(EffectRow* parent, const QString& id) :
|
||||
EffectField(parent, id, EFFECT_FIELD_DOUBLE),
|
||||
min_(qSNaN()),
|
||||
max_(qSNaN()),
|
||||
default_(0),
|
||||
display_type_(LabelSlider::LABELSLIDER_NORMAL),
|
||||
frame_rate_(30),
|
||||
value_set_(false)
|
||||
{
|
||||
connect(this, SIGNAL(Changed()), this, SLOT(ValueHasBeenSet()), Qt::DirectConnection);
|
||||
}
|
||||
|
||||
double DoubleField::GetDoubleAt(double timecode)
|
||||
{
|
||||
return GetValueAt(timecode).toDouble();
|
||||
}
|
||||
|
||||
void DoubleField::SetMinimum(double minimum)
|
||||
{
|
||||
min_ = minimum;
|
||||
}
|
||||
|
||||
void DoubleField::SetMaximum(double maximum)
|
||||
{
|
||||
max_ = maximum;
|
||||
}
|
||||
|
||||
void DoubleField::SetDefault(double d)
|
||||
{
|
||||
default_ = d;
|
||||
|
||||
if (!value_set_) {
|
||||
SetValueAt(0, d);
|
||||
value_set_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void DoubleField::SetDisplayType(LabelSlider::DisplayType type)
|
||||
{
|
||||
display_type_ = type;
|
||||
}
|
||||
|
||||
void DoubleField::SetFrameRate(const double &rate)
|
||||
{
|
||||
frame_rate_ = rate;
|
||||
}
|
||||
|
||||
QVariant DoubleField::ConvertStringToValue(const QString &s)
|
||||
{
|
||||
return s.toDouble();
|
||||
}
|
||||
|
||||
QString DoubleField::ConvertValueToString(const QVariant &v)
|
||||
{
|
||||
return QString::number(v.toDouble());
|
||||
}
|
||||
|
||||
QWidget *DoubleField::CreateWidget()
|
||||
{
|
||||
LabelSlider* ls = new LabelSlider();
|
||||
|
||||
if (!qIsNaN(min_)) {
|
||||
ls->set_minimum_value(min_);
|
||||
}
|
||||
ls->set_default_value(default_);
|
||||
if (!qIsNaN(max_)) {
|
||||
ls->set_maximum_value(max_);
|
||||
}
|
||||
ls->set_display_type(display_type_);
|
||||
ls->set_frame_rate(frame_rate_);
|
||||
|
||||
ls->setEnabled(IsEnabled());
|
||||
|
||||
connect(ls, SIGNAL(valueChanged(double)), this, SLOT(UpdateFromWidget(double)));
|
||||
connect(ls, SIGNAL(clicked()), this, SIGNAL(Clicked()));
|
||||
connect(this, SIGNAL(EnabledChanged(bool)), ls, SLOT(setEnabled(bool)));
|
||||
|
||||
return ls;
|
||||
}
|
||||
|
||||
void DoubleField::ValueHasBeenSet()
|
||||
{
|
||||
value_set_ = true;
|
||||
}
|
||||
|
||||
void DoubleField::UpdateFromWidget(double d)
|
||||
{
|
||||
SetValueAt(Now(), d);
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
#ifndef DOUBLEFIELD_H
|
||||
#define DOUBLEFIELD_H
|
||||
|
||||
#include "effectfield.h"
|
||||
#include "ui/labelslider.h"
|
||||
|
||||
class DoubleField : public EffectField
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
DoubleField(EffectRow* parent, const QString& id);
|
||||
|
||||
/**
|
||||
* @brief Get double value at timecode
|
||||
*
|
||||
* Convenience function. Equivalent to GetValueAt().toDouble()
|
||||
*
|
||||
* @param timecode
|
||||
*
|
||||
* Timecode to retrieve value at
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* Double value at the set timecode
|
||||
*/
|
||||
double GetDoubleAt(double timecode);
|
||||
|
||||
void SetMinimum(double minimum);
|
||||
void SetMaximum(double maximum);
|
||||
void SetDefault(double maximum);
|
||||
|
||||
void SetDisplayType(LabelSlider::DisplayType type);
|
||||
void SetFrameRate(const double& rate);
|
||||
|
||||
virtual QVariant ConvertStringToValue(const QString& s);
|
||||
virtual QString ConvertValueToString(const QVariant& v);
|
||||
|
||||
virtual QWidget* CreateWidget() override;
|
||||
private:
|
||||
double min_;
|
||||
double max_;
|
||||
double default_;
|
||||
|
||||
LabelSlider::DisplayType display_type_;
|
||||
double frame_rate_;
|
||||
|
||||
bool value_set_;
|
||||
private slots:
|
||||
void ValueHasBeenSet();
|
||||
void UpdateFromWidget(double d);
|
||||
};
|
||||
|
||||
#endif // DOUBLEFIELD_H
|
||||
@@ -1,416 +0,0 @@
|
||||
/***
|
||||
|
||||
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 "effectfield.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QtMath>
|
||||
|
||||
#include "ui/labelslider.h"
|
||||
#include "ui/colorbutton.h"
|
||||
#include "ui/texteditex.h"
|
||||
#include "ui/checkboxex.h"
|
||||
#include "ui/comboboxex.h"
|
||||
#include "ui/fontcombobox.h"
|
||||
#include "ui/embeddedfilechooser.h"
|
||||
|
||||
#include "rendering/renderfunctions.h"
|
||||
|
||||
#include "io/config.h"
|
||||
|
||||
#include "project/effectrow.h"
|
||||
#include "project/effect.h"
|
||||
|
||||
#include "project/undo.h"
|
||||
#include "project/clip.h"
|
||||
#include "project/sequence.h"
|
||||
|
||||
#include "io/math.h"
|
||||
|
||||
#include "debug.h"
|
||||
|
||||
EffectField::EffectField(EffectRow* parent, const QString &i, EffectFieldType t) :
|
||||
QObject(parent),
|
||||
type_(t),
|
||||
id_(i),
|
||||
enabled_(true),
|
||||
colspan_(1)
|
||||
{
|
||||
// EffectField MUST be created with a parent.
|
||||
Q_ASSERT(parent != nullptr);
|
||||
Q_ASSERT(!i.isEmpty() || t == EFFECT_FIELD_UI);
|
||||
|
||||
// Add this field to the parent row specified
|
||||
parent->AddField(this);
|
||||
|
||||
// Set a very base default value
|
||||
SetValueAt(0, 0);
|
||||
|
||||
// Connect this field to the effect's changed function
|
||||
connect(this, SIGNAL(Changed()), parent->GetParentEffect(), SLOT(FieldChanged()));
|
||||
}
|
||||
|
||||
EffectField::~EffectField() {}
|
||||
|
||||
EffectRow *EffectField::GetParentRow()
|
||||
{
|
||||
return static_cast<EffectRow*>(parent());
|
||||
}
|
||||
|
||||
int EffectField::GetColumnSpan()
|
||||
{
|
||||
return colspan_;
|
||||
}
|
||||
|
||||
void EffectField::SetColumnSpan(int i)
|
||||
{
|
||||
Q_ASSERT(i >= 1);
|
||||
colspan_ = i;
|
||||
}
|
||||
|
||||
QVariant EffectField::ConvertStringToValue(const QString &s)
|
||||
{
|
||||
return s;
|
||||
}
|
||||
|
||||
QString EffectField::ConvertValueToString(const QVariant &v)
|
||||
{
|
||||
return v.toString();
|
||||
}
|
||||
|
||||
QWidget *EffectField::CreateWidget()
|
||||
{
|
||||
return new QLabel(tr("(Invalid field)"));
|
||||
}
|
||||
|
||||
QVariant EffectField::GetValueAt(double timecode)
|
||||
{
|
||||
Q_ASSERT(!keyframes.isEmpty());
|
||||
|
||||
if (HasKeyframes()) {
|
||||
int before_keyframe;
|
||||
int after_keyframe;
|
||||
double progress;
|
||||
get_keyframe_data(timecode, before_keyframe, after_keyframe, progress);
|
||||
|
||||
const QVariant& before_data = keyframes.at(before_keyframe).data;
|
||||
switch (type_) {
|
||||
case EFFECT_FIELD_DOUBLE:
|
||||
{
|
||||
double value;
|
||||
if (before_keyframe == after_keyframe) {
|
||||
value = keyframes.at(before_keyframe).data.toDouble();
|
||||
} else {
|
||||
const EffectKeyframe& before_key = keyframes.at(before_keyframe);
|
||||
const EffectKeyframe& after_key = keyframes.at(after_keyframe);
|
||||
|
||||
double before_dbl = before_key.data.toDouble();
|
||||
double after_dbl = after_key.data.toDouble();
|
||||
|
||||
if (before_key.type == EFFECT_KEYFRAME_HOLD) {
|
||||
|
||||
// Hold keyframes will always return the previous keyframe with no interpolation
|
||||
value = before_dbl;
|
||||
|
||||
} else if (before_key.type == EFFECT_KEYFRAME_BEZIER || after_key.type == EFFECT_KEYFRAME_BEZIER) {
|
||||
|
||||
// bezier interpolation
|
||||
if (before_key.type == EFFECT_KEYFRAME_BEZIER && after_key.type == EFFECT_KEYFRAME_BEZIER) {
|
||||
|
||||
// cubic bezier
|
||||
double t = cubic_t_from_x(timecodeToFrame(timecode),
|
||||
before_key.time,
|
||||
before_key.time+GetValidKeyframeHandlePosition(before_keyframe, true),
|
||||
after_key.time+GetValidKeyframeHandlePosition(after_keyframe, false),
|
||||
after_key.time);
|
||||
|
||||
value = cubic_from_t(before_dbl,
|
||||
before_dbl+before_key.post_handle_y,
|
||||
after_dbl+after_key.pre_handle_y,
|
||||
after_dbl,
|
||||
t);
|
||||
|
||||
} else if (after_key.type == EFFECT_KEYFRAME_LINEAR) { // quadratic bezier
|
||||
|
||||
// last keyframe is the bezier one
|
||||
double t = quad_t_from_x(timecodeToFrame(timecode),
|
||||
before_key.time,
|
||||
before_key.time+GetValidKeyframeHandlePosition(before_keyframe, true),
|
||||
after_key.time);
|
||||
|
||||
value = quad_from_t(before_dbl,
|
||||
before_dbl+before_key.post_handle_y,
|
||||
after_dbl,
|
||||
t);
|
||||
|
||||
} else {
|
||||
// this keyframe is the bezier one
|
||||
double t = quad_t_from_x(timecodeToFrame(timecode),
|
||||
before_key.time,
|
||||
after_key.time+GetValidKeyframeHandlePosition(after_keyframe, false),
|
||||
after_key.time);
|
||||
|
||||
value = quad_from_t(before_dbl,
|
||||
after_dbl+after_key.pre_handle_y,
|
||||
after_dbl,
|
||||
t);
|
||||
}
|
||||
} else {
|
||||
|
||||
// Linear interpolation (default)
|
||||
value = double_lerp(before_dbl, after_dbl, progress);
|
||||
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
case EFFECT_FIELD_COLOR:
|
||||
{
|
||||
QColor value;
|
||||
if (before_keyframe == after_keyframe) {
|
||||
value = keyframes.at(before_keyframe).data.value<QColor>();
|
||||
} else {
|
||||
QColor before_data = keyframes.at(before_keyframe).data.value<QColor>();
|
||||
QColor after_data = keyframes.at(after_keyframe).data.value<QColor>();
|
||||
value = QColor(lerp(before_data.red(), after_data.red(), progress), lerp(before_data.green(), after_data.green(), progress), lerp(before_data.blue(), after_data.blue(), progress));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
case EFFECT_FIELD_STRING:
|
||||
case EFFECT_FIELD_BOOL:
|
||||
case EFFECT_FIELD_COMBO:
|
||||
case EFFECT_FIELD_FONT:
|
||||
case EFFECT_FIELD_FILE:
|
||||
return before_data;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return keyframes.first().data;
|
||||
}
|
||||
|
||||
void EffectField::SetValueAt(double time, const QVariant &value)
|
||||
{
|
||||
if (keyframes.isEmpty()) {
|
||||
EffectKeyframe key;
|
||||
key.data = value;
|
||||
keyframes.append(key);
|
||||
return;
|
||||
}
|
||||
|
||||
if (GetParentRow()->IsKeyframing()) {
|
||||
|
||||
// Create keyframe here
|
||||
|
||||
// Convert seconds timecode to frame
|
||||
long frame_timecode = timecodeToFrame(time);
|
||||
|
||||
// Check array if a keyframe at this time already exists
|
||||
int keyframe_index = -1;
|
||||
for (int i=0;i<keyframes.size();i++) {
|
||||
if (keyframes.at(i).time == frame_timecode) {
|
||||
keyframe_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If keyframe doesn't exist, make it
|
||||
if (keyframe_index == -1) {
|
||||
EffectKeyframe key;
|
||||
key.time = frame_timecode;
|
||||
key.data = value;
|
||||
keyframes.append(key);
|
||||
} else {
|
||||
EffectKeyframe& key = keyframes[keyframe_index];
|
||||
key.data = value;
|
||||
}
|
||||
|
||||
} else {
|
||||
keyframes.first().data = value;
|
||||
}
|
||||
|
||||
emit Changed();
|
||||
}
|
||||
|
||||
double EffectField::Now()
|
||||
{
|
||||
Clip* c = GetParentRow()->GetParentEffect()->parent_clip;
|
||||
return playhead_to_clip_seconds(c, c->sequence->playhead);
|
||||
}
|
||||
|
||||
void EffectField::PrepareDataForKeyframing(bool enabled, ComboAction *ca)
|
||||
{
|
||||
if (enabled) {
|
||||
|
||||
// Convert "perpetual" keyframe to a keyframe at this time
|
||||
ca->append(new SetLong(&keyframes.first().time, keyframes.first().time, timecodeToFrame(Now())));
|
||||
|
||||
} else {
|
||||
|
||||
// Convert keyframes to one "perpetual" keyframe
|
||||
|
||||
// Set first keyframe to whatever the data is now
|
||||
ca->append(new SetQVariant(&keyframes.first().data, keyframes.first().data, GetValueAt(Now())));
|
||||
|
||||
// Delete all keyframes except the first
|
||||
for (int i=1;i<keyframes.size();i++) {
|
||||
ca->append(new KeyframeDelete(this, 1));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
const EffectField::EffectFieldType &EffectField::type()
|
||||
{
|
||||
return type_;
|
||||
}
|
||||
|
||||
const QString &EffectField::id()
|
||||
{
|
||||
return id_;
|
||||
}
|
||||
|
||||
double EffectField::GetValidKeyframeHandlePosition(int key, bool post) {
|
||||
int comp_key = -1;
|
||||
|
||||
// find keyframe before or after this one
|
||||
for (int i=0;i<keyframes.size();i++) {
|
||||
if (i != key
|
||||
&& ((keyframes.at(i).time > keyframes.at(key).time) == post)
|
||||
&& (comp_key == -1
|
||||
|| ((keyframes.at(i).time < keyframes.at(comp_key).time) == post))) {
|
||||
// compare with next keyframe for post or previous frame for pre
|
||||
comp_key = i;
|
||||
}
|
||||
}
|
||||
|
||||
double adjusted_key = post ? keyframes.at(key).post_handle_x : keyframes.at(key).pre_handle_x;
|
||||
|
||||
// if this is the earliest/latest keyframe, no validation is required
|
||||
if (comp_key == -1) {
|
||||
return adjusted_key;
|
||||
}
|
||||
|
||||
double comp = keyframes.at(comp_key).time - keyframes.at(key).time;
|
||||
|
||||
// if comp keyframe is bezier, validate with its accompanying handle
|
||||
if (keyframes.at(comp_key).type == EFFECT_KEYFRAME_BEZIER) {
|
||||
double relative_comp_handle = comp + (post ? keyframes.at(comp_key).pre_handle_x : keyframes.at(comp_key).post_handle_x);
|
||||
// return an average
|
||||
if ((post && keyframes.at(key).post_handle_x > relative_comp_handle)
|
||||
|| (!post && keyframes.at(key).pre_handle_x < relative_comp_handle)) {
|
||||
adjusted_key = (adjusted_key + relative_comp_handle)*0.5;
|
||||
}
|
||||
}
|
||||
|
||||
// don't let handle go beyond the compare keyframe's time
|
||||
if (post == (adjusted_key > comp)) {
|
||||
return comp;
|
||||
}
|
||||
|
||||
if (post == (adjusted_key < 0)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// original value is valid
|
||||
return adjusted_key;
|
||||
}
|
||||
|
||||
double EffectField::frameToTimecode(long frame) {
|
||||
return (double(frame) / GetParentRow()->GetParentEffect()->parent_clip->sequence->frame_rate);
|
||||
}
|
||||
|
||||
long EffectField::timecodeToFrame(double timecode) {
|
||||
return qRound(timecode * GetParentRow()->GetParentEffect()->parent_clip->sequence->frame_rate);
|
||||
}
|
||||
|
||||
void EffectField::get_keyframe_data(double timecode, int &before, int &after, double &progress) {
|
||||
int before_keyframe_index = -1;
|
||||
int after_keyframe_index = -1;
|
||||
long before_keyframe_time = LONG_MIN;
|
||||
long after_keyframe_time = LONG_MAX;
|
||||
long frame = timecodeToFrame(timecode);
|
||||
|
||||
for (int i=0;i<keyframes.size();i++) {
|
||||
long eval_keyframe_time = keyframes.at(i).time;
|
||||
if (eval_keyframe_time == frame) {
|
||||
before = i;
|
||||
after = i;
|
||||
return;
|
||||
} else if (eval_keyframe_time < frame && eval_keyframe_time > before_keyframe_time) {
|
||||
before_keyframe_index = i;
|
||||
before_keyframe_time = eval_keyframe_time;
|
||||
} else if (eval_keyframe_time > frame && eval_keyframe_time < after_keyframe_time) {
|
||||
after_keyframe_index = i;
|
||||
after_keyframe_time = eval_keyframe_time;
|
||||
}
|
||||
}
|
||||
|
||||
if ((type_ == EFFECT_FIELD_DOUBLE || type_ == EFFECT_FIELD_COLOR) && (before_keyframe_index > -1 && after_keyframe_index > -1)) {
|
||||
// interpolate
|
||||
before = before_keyframe_index;
|
||||
after = after_keyframe_index;
|
||||
progress = (timecode-frameToTimecode(before_keyframe_time))/(frameToTimecode(after_keyframe_time)-frameToTimecode(before_keyframe_time));
|
||||
} else if (before_keyframe_index > -1) {
|
||||
before = before_keyframe_index;
|
||||
after = before_keyframe_index;
|
||||
} else {
|
||||
before = after_keyframe_index;
|
||||
after = after_keyframe_index;
|
||||
}
|
||||
}
|
||||
|
||||
bool EffectField::HasKeyframes() {
|
||||
return (GetParentRow()->IsKeyframing() && keyframes.size() > 1);
|
||||
}
|
||||
|
||||
/*
|
||||
void EffectField::ui_element_change() {
|
||||
// TODO address this
|
||||
//bool dragging_double = (type_ == EFFECT_FIELD_DOUBLE && static_cast<LabelSlider*>(ui_element)->is_dragging());
|
||||
bool dragging_double = false;
|
||||
ComboAction* ca = nullptr;
|
||||
if (!dragging_double) ca = new ComboAction();
|
||||
//make_key_from_change(ca);
|
||||
if (!dragging_double) olive::UndoStack.push(ca);
|
||||
emit Changed();
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
void EffectField::make_key_from_change(ComboAction* ca) {
|
||||
if (GetParentRow()->isKeyframing()) {
|
||||
GetParentRow()->set_keyframe_now(ca);
|
||||
} else if (ca != nullptr) {
|
||||
// set undo
|
||||
ca->append(new EffectFieldUndo(this));
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
bool EffectField::IsEnabled() {
|
||||
return enabled_;
|
||||
}
|
||||
|
||||
void EffectField::SetEnabled(bool e) {
|
||||
enabled_ = e;
|
||||
emit EnabledChanged(enabled_);
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef EFFECTFIELD_H
|
||||
#define EFFECTFIELD_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QVariant>
|
||||
#include <QVector>
|
||||
|
||||
#include "project/keyframe.h"
|
||||
|
||||
class EffectRow;
|
||||
class ComboAction;
|
||||
|
||||
class EffectField : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum EffectFieldType {
|
||||
EFFECT_FIELD_DOUBLE,
|
||||
EFFECT_FIELD_COLOR,
|
||||
EFFECT_FIELD_STRING,
|
||||
EFFECT_FIELD_BOOL,
|
||||
EFFECT_FIELD_COMBO,
|
||||
EFFECT_FIELD_FONT,
|
||||
EFFECT_FIELD_FILE,
|
||||
EFFECT_FIELD_UI
|
||||
};
|
||||
|
||||
EffectField(EffectRow* parent, const QString& i, EffectFieldType t);
|
||||
~EffectField();
|
||||
|
||||
EffectRow* GetParentRow();
|
||||
|
||||
const EffectFieldType& type();
|
||||
const QString& id();
|
||||
|
||||
QVariant GetValueAt(double timecode);
|
||||
void SetValueAt(double time, const QVariant& value);
|
||||
|
||||
double Now();
|
||||
|
||||
void PrepareDataForKeyframing(bool enabled, ComboAction* ca);
|
||||
|
||||
int GetColumnSpan();
|
||||
void SetColumnSpan(int i);
|
||||
|
||||
virtual QVariant ConvertStringToValue(const QString& s);
|
||||
virtual QString ConvertValueToString(const QVariant& v);
|
||||
|
||||
virtual QWidget* CreateWidget();
|
||||
|
||||
double GetValidKeyframeHandlePosition(int key, bool post);
|
||||
|
||||
bool IsEnabled();
|
||||
void SetEnabled(bool e);
|
||||
|
||||
QVector<EffectKeyframe> keyframes;
|
||||
|
||||
signals:
|
||||
void Changed();
|
||||
void Clicked();
|
||||
|
||||
void EnabledChanged(bool);
|
||||
|
||||
private:
|
||||
EffectFieldType type_;
|
||||
QString id_;
|
||||
|
||||
bool HasKeyframes();
|
||||
double frameToTimecode(long frame);
|
||||
long timecodeToFrame(double timecode);
|
||||
void get_keyframe_data(double timecode, int& before, int& after, double& d);
|
||||
|
||||
bool enabled_;
|
||||
int colspan_;
|
||||
|
||||
};
|
||||
|
||||
#endif // EFFECTFIELD_H
|
||||
@@ -1,29 +0,0 @@
|
||||
#include "filefield.h"
|
||||
|
||||
#include "ui/embeddedfilechooser.h"
|
||||
|
||||
FileField::FileField(EffectRow* parent, const QString &id) :
|
||||
EffectField(parent, id, EFFECT_FIELD_FILE)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QString FileField::GetFileAt(double timecode)
|
||||
{
|
||||
return GetValueAt(timecode).toString();
|
||||
}
|
||||
|
||||
QWidget *FileField::CreateWidget()
|
||||
{
|
||||
EmbeddedFileChooser* efc = new EmbeddedFileChooser();
|
||||
|
||||
connect(efc, SIGNAL(changed(const QString&)), this, SLOT(UpdateFromWidget(const QString&)));
|
||||
connect(this, SIGNAL(EnabledChanged(bool)), efc, SLOT(setEnabled(bool)));
|
||||
|
||||
return efc;
|
||||
}
|
||||
|
||||
void FileField::UpdateFromWidget(const QString &s)
|
||||
{
|
||||
SetValueAt(Now(), s);
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef FILEFIELD_H
|
||||
#define FILEFIELD_H
|
||||
|
||||
#include "effectfield.h"
|
||||
|
||||
class FileField : public EffectField
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
FileField(EffectRow* parent, const QString& id);
|
||||
|
||||
QString GetFileAt(double timecode);
|
||||
|
||||
virtual QWidget* CreateWidget() override;
|
||||
private slots:
|
||||
void UpdateFromWidget(const QString &s);
|
||||
};
|
||||
|
||||
#endif // FILEFIELD_H
|
||||
@@ -1,37 +0,0 @@
|
||||
#include "fontfield.h"
|
||||
|
||||
#include <QFontDatabase>
|
||||
|
||||
#include "ui/comboboxex.h"
|
||||
|
||||
FontField::FontField(EffectRow* parent, const QString &id) :
|
||||
EffectField(parent, id, EFFECT_FIELD_FONT)
|
||||
{
|
||||
font_list = QFontDatabase().families();
|
||||
|
||||
SetValueAt(0, font_list.first());
|
||||
}
|
||||
|
||||
QString FontField::GetFontAt(double timecode)
|
||||
{
|
||||
return GetValueAt(timecode).toString();
|
||||
}
|
||||
|
||||
QWidget *FontField::CreateWidget()
|
||||
{
|
||||
ComboBoxEx* fcb = new ComboBoxEx();
|
||||
|
||||
fcb->setScrollingEnabled(false);
|
||||
|
||||
fcb->addItems(font_list);
|
||||
|
||||
connect(fcb, SIGNAL(currentTextChanged(const QString &)), this, SLOT(UpdateFromWidget(const QString &)));
|
||||
connect(this, SIGNAL(EnabledChanged(bool)), fcb, SLOT(setEnabled(bool)));
|
||||
|
||||
return fcb;
|
||||
}
|
||||
|
||||
void FontField::UpdateFromWidget(const QString& s)
|
||||
{
|
||||
SetValueAt(Now(), s);
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
#ifndef FONTFIELD_H
|
||||
#define FONTFIELD_H
|
||||
|
||||
#include "effectfield.h"
|
||||
|
||||
class FontField : public EffectField {
|
||||
Q_OBJECT
|
||||
public:
|
||||
FontField(EffectRow* parent, const QString& id);
|
||||
|
||||
QString GetFontAt(double timecode);
|
||||
|
||||
virtual QWidget *CreateWidget() override;
|
||||
private:
|
||||
QStringList font_list;
|
||||
private slots:
|
||||
void UpdateFromWidget(const QString& index);
|
||||
};
|
||||
|
||||
#endif // FONTFIELD_H
|
||||
@@ -1,18 +0,0 @@
|
||||
#include "labelfield.h"
|
||||
|
||||
#include <QLabel>
|
||||
|
||||
LabelField::LabelField(EffectRow *parent, const QString &string) :
|
||||
EffectField(parent, nullptr, EFFECT_FIELD_UI),
|
||||
label_text_(string)
|
||||
{}
|
||||
|
||||
QWidget *LabelField::CreateWidget()
|
||||
{
|
||||
QLabel* label = new QLabel(label_text_);
|
||||
|
||||
label->setEnabled(IsEnabled());
|
||||
connect(this, SIGNAL(EnabledChanged(bool)), label, SLOT(setEnabled(bool)));
|
||||
|
||||
return label;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
#ifndef LABELFIELD_H
|
||||
#define LABELFIELD_H
|
||||
|
||||
#include "effectfield.h"
|
||||
|
||||
class LabelField : public EffectField
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
LabelField(EffectRow* parent, const QString& string);
|
||||
|
||||
virtual QWidget* CreateWidget() override;
|
||||
private:
|
||||
QString label_text_;
|
||||
};
|
||||
|
||||
#endif // LABELFIELD_H
|
||||
@@ -1,40 +0,0 @@
|
||||
#include "stringfield.h"
|
||||
|
||||
#include <QtMath>
|
||||
|
||||
#include "ui/texteditex.h"
|
||||
#include "io/config.h"
|
||||
|
||||
StringField::StringField(EffectRow* parent, const QString& id) :
|
||||
EffectField(parent, id, EFFECT_FIELD_STRING)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QString StringField::GetStringAt(double timecode)
|
||||
{
|
||||
return GetValueAt(timecode).toString();
|
||||
}
|
||||
|
||||
QWidget *StringField::CreateWidget()
|
||||
{
|
||||
TextEditEx* text_edit = new TextEditEx();
|
||||
|
||||
text_edit->setEnabled(IsEnabled());
|
||||
text_edit->setUndoRedoEnabled(true);
|
||||
|
||||
// the "2" looks like a magic number, but it's just one pixel on the top and the bottom
|
||||
text_edit->setFixedHeight(qCeil(text_edit->fontMetrics().lineSpacing()*olive::CurrentConfig.effect_textbox_lines
|
||||
+ text_edit->document()->documentMargin()
|
||||
+ text_edit->document()->documentMargin() + 2));
|
||||
|
||||
connect(text_edit, SIGNAL(textModified(const QString&)), this, SLOT(UpdateFromWidget(const QString&)));
|
||||
connect(this, SIGNAL(EnabledChanged(bool)), text_edit, SLOT(setEnabled(bool)));
|
||||
|
||||
return text_edit;
|
||||
}
|
||||
|
||||
void StringField::UpdateFromWidget(const QString &s)
|
||||
{
|
||||
SetValueAt(Now(), s);
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef STRINGFIELD_H
|
||||
#define STRINGFIELD_H
|
||||
|
||||
#include "effectfield.h"
|
||||
|
||||
class StringField : public EffectField
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
StringField(EffectRow* parent, const QString& id);
|
||||
|
||||
QString GetStringAt(double timecode);
|
||||
|
||||
virtual QWidget *CreateWidget() override;
|
||||
private slots:
|
||||
void UpdateFromWidget(const QString& b);
|
||||
};
|
||||
|
||||
#endif // STRINGFIELD_H
|
||||
@@ -1,73 +0,0 @@
|
||||
/***
|
||||
|
||||
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 "effectgizmo.h"
|
||||
|
||||
#include "ui/labelslider.h"
|
||||
#include "effectfields/doublefield.h"
|
||||
#include "project/effect.h"
|
||||
|
||||
EffectGizmo::EffectGizmo(Effect *parent, int type) :
|
||||
QObject(parent),
|
||||
x_field1(nullptr),
|
||||
x_field_multi1(1.0),
|
||||
y_field1(nullptr),
|
||||
y_field_multi1(1.0),
|
||||
x_field2(nullptr),
|
||||
x_field_multi2(1.0),
|
||||
y_field2(nullptr),
|
||||
y_field_multi2(1.0),
|
||||
type(type),
|
||||
cursor(-1)
|
||||
{
|
||||
Q_ASSERT(parent != nullptr);
|
||||
|
||||
int point_count = (type == GIZMO_TYPE_POLY) ? 4 : 1;
|
||||
world_pos.resize(point_count);
|
||||
screen_pos.resize(point_count);
|
||||
|
||||
color = Qt::white;
|
||||
}
|
||||
|
||||
void EffectGizmo::set_previous_value() {
|
||||
// TODO address this
|
||||
/*
|
||||
if (x_field1 != nullptr) static_cast<LabelSlider*>(x_field1->ui_element)->set_previous_value();
|
||||
if (y_field1 != nullptr) static_cast<LabelSlider*>(y_field1->ui_element)->set_previous_value();
|
||||
if (x_field2 != nullptr) static_cast<LabelSlider*>(x_field2->ui_element)->set_previous_value();
|
||||
if (y_field2 != nullptr) static_cast<LabelSlider*>(y_field2->ui_element)->set_previous_value();
|
||||
*/
|
||||
}
|
||||
|
||||
int EffectGizmo::get_point_count() {
|
||||
return world_pos.size();
|
||||
}
|
||||
|
||||
int EffectGizmo::get_type() {
|
||||
return type;
|
||||
}
|
||||
|
||||
int EffectGizmo::get_cursor() {
|
||||
return cursor;
|
||||
}
|
||||
|
||||
void EffectGizmo::set_cursor(int c) {
|
||||
cursor = c;
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef EFFECTGIZMO_H
|
||||
#define EFFECTGIZMO_H
|
||||
|
||||
enum GizmoType {
|
||||
GIZMO_TYPE_DOT,
|
||||
GIZMO_TYPE_POLY,
|
||||
GIZMO_TYPE_TARGET
|
||||
};
|
||||
|
||||
#define GIZMO_DOT_SIZE 2.5
|
||||
#define GIZMO_TARGET_SIZE 5.0
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QRect>
|
||||
#include <QPoint>
|
||||
#include <QVector>
|
||||
#include <QColor>
|
||||
|
||||
class DoubleField;
|
||||
class Effect;
|
||||
|
||||
class EffectGizmo : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
EffectGizmo(Effect* parent, int type);
|
||||
|
||||
QVector<QPoint> world_pos;
|
||||
QVector<QPoint> screen_pos;
|
||||
|
||||
DoubleField* x_field1;
|
||||
double x_field_multi1;
|
||||
DoubleField* y_field1;
|
||||
double y_field_multi1;
|
||||
DoubleField* x_field2;
|
||||
double x_field_multi2;
|
||||
DoubleField* y_field2;
|
||||
double y_field_multi2;
|
||||
|
||||
void set_previous_value();
|
||||
|
||||
QColor color;
|
||||
int get_point_count();
|
||||
|
||||
int get_type();
|
||||
|
||||
int get_cursor();
|
||||
void set_cursor(int c);
|
||||
private:
|
||||
int type;
|
||||
int cursor;
|
||||
};
|
||||
|
||||
#endif // EFFECTGIZMO_H
|
||||
@@ -1,271 +0,0 @@
|
||||
/***
|
||||
|
||||
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 "effectloaders.h"
|
||||
|
||||
#include "project/effect.h"
|
||||
#include "project/transition.h"
|
||||
#include "io/path.h"
|
||||
#include "panels/panels.h"
|
||||
#include "panels/effectcontrols.h"
|
||||
#include "io/crossplatformlib.h"
|
||||
#include "io/config.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#ifndef NOFREI0R
|
||||
#include <frei0r.h>
|
||||
typedef void (*f0rGetPluginInfo)(f0r_plugin_info_t* info);
|
||||
#endif
|
||||
|
||||
void load_internal_effects() {
|
||||
if (!olive::CurrentRuntimeConfig.shaders_are_enabled) qWarning() << "Shaders are disabled, some effects may be nonfunctional";
|
||||
|
||||
EffectMeta em;
|
||||
|
||||
// load internal effects
|
||||
em.path = ":/internalshaders";
|
||||
|
||||
em.type = EFFECT_TYPE_EFFECT;
|
||||
em.subtype = EFFECT_TYPE_AUDIO;
|
||||
|
||||
em.name = "Volume";
|
||||
em.internal = EFFECT_INTERNAL_VOLUME;
|
||||
effects.append(em);
|
||||
|
||||
em.name = "Pan";
|
||||
em.internal = EFFECT_INTERNAL_PAN;
|
||||
effects.append(em);
|
||||
|
||||
#ifndef NOVST
|
||||
em.name = "VST Plugin 2.x";
|
||||
em.internal = EFFECT_INTERNAL_VST;
|
||||
effects.append(em);
|
||||
#endif
|
||||
|
||||
em.name = "Tone";
|
||||
em.internal = EFFECT_INTERNAL_TONE;
|
||||
effects.append(em);
|
||||
|
||||
em.name = "Noise";
|
||||
em.internal = EFFECT_INTERNAL_NOISE;
|
||||
effects.append(em);
|
||||
|
||||
em.name = "Fill Left/Right";
|
||||
em.internal = EFFECT_INTERNAL_FILLLEFTRIGHT;
|
||||
effects.append(em);
|
||||
|
||||
em.subtype = EFFECT_TYPE_VIDEO;
|
||||
|
||||
em.name = "Transform";
|
||||
em.category = "Distort";
|
||||
em.internal = EFFECT_INTERNAL_TRANSFORM;
|
||||
effects.append(em);
|
||||
|
||||
em.name = "Corner Pin";
|
||||
em.internal = EFFECT_INTERNAL_CORNERPIN;
|
||||
effects.append(em);
|
||||
|
||||
/*em.name = "Mask";
|
||||
em.internal = EFFECT_INTERNAL_MASK;
|
||||
effects.append(em);*/
|
||||
|
||||
em.name = "Shake";
|
||||
em.internal = EFFECT_INTERNAL_SHAKE;
|
||||
effects.append(em);
|
||||
|
||||
em.name = "Text";
|
||||
em.category = "Render";
|
||||
em.internal = EFFECT_INTERNAL_TEXT;
|
||||
effects.append(em);
|
||||
|
||||
em.name = "Timecode";
|
||||
em.internal = EFFECT_INTERNAL_TIMECODE;
|
||||
effects.append(em);
|
||||
|
||||
em.name = "Solid";
|
||||
em.internal = EFFECT_INTERNAL_SOLID;
|
||||
effects.append(em);
|
||||
|
||||
// internal transitions
|
||||
em.type = EFFECT_TYPE_TRANSITION;
|
||||
em.category = "";
|
||||
|
||||
em.name = "Cross Dissolve";
|
||||
em.internal = TRANSITION_INTERNAL_CROSSDISSOLVE;
|
||||
effects.append(em);
|
||||
|
||||
em.subtype = EFFECT_TYPE_AUDIO;
|
||||
|
||||
em.name = "Linear Fade";
|
||||
em.internal = TRANSITION_INTERNAL_LINEARFADE;
|
||||
effects.append(em);
|
||||
|
||||
em.name = "Exponential Fade";
|
||||
em.internal = TRANSITION_INTERNAL_EXPONENTIALFADE;
|
||||
effects.append(em);
|
||||
|
||||
em.name = "Logarithmic Fade";
|
||||
em.internal = TRANSITION_INTERNAL_LOGARITHMICFADE;
|
||||
effects.append(em);
|
||||
}
|
||||
|
||||
void load_shader_effects() {
|
||||
QList<QString> effects_paths = get_effects_paths();
|
||||
|
||||
for (int h=0;h<effects_paths.size();h++) {
|
||||
const QString& effects_path = effects_paths.at(h);
|
||||
QDir effects_dir(effects_path);
|
||||
if (effects_dir.exists()) {
|
||||
QList<QString> entries = effects_dir.entryList(QStringList("*.xml"), QDir::Files);
|
||||
for (int i=0;i<entries.size();i++) {
|
||||
QFile file(effects_path + "/" + entries.at(i));
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qCritical() << "Could not open" << entries.at(i);
|
||||
return;
|
||||
}
|
||||
|
||||
QXmlStreamReader reader(&file);
|
||||
while (!reader.atEnd()) {
|
||||
if (reader.name() == "effect") {
|
||||
QString effect_name = "";
|
||||
QString effect_cat = "";
|
||||
const QXmlStreamAttributes attr = reader.attributes();
|
||||
for (int j=0;j<attr.size();j++) {
|
||||
if (attr.at(j).name() == "name") {
|
||||
effect_name = attr.at(j).value().toString();
|
||||
} else if (attr.at(j).name() == "category") {
|
||||
effect_cat = attr.at(j).value().toString();
|
||||
}
|
||||
}
|
||||
if (!effect_name.isEmpty()) {
|
||||
EffectMeta em;
|
||||
em.type = EFFECT_TYPE_EFFECT;
|
||||
em.subtype = EFFECT_TYPE_VIDEO;
|
||||
em.name = effect_name;
|
||||
em.category = effect_cat;
|
||||
em.filename = file.fileName();
|
||||
em.path = effects_path;
|
||||
em.internal = -1;
|
||||
effects.append(em);
|
||||
} else {
|
||||
qCritical() << "Invalid effect found in" << entries.at(i);
|
||||
}
|
||||
break;
|
||||
}
|
||||
reader.readNext();
|
||||
}
|
||||
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void init_effects() {
|
||||
EffectInit* init_thread = new EffectInit();
|
||||
QObject::connect(init_thread, SIGNAL(finished()), init_thread, SLOT(deleteLater()));
|
||||
init_thread->start();
|
||||
}
|
||||
|
||||
#ifndef NOFREI0R
|
||||
void load_frei0r_effects_worker(const QString& dir, EffectMeta& em, QVector<QString>& loaded_names) {
|
||||
QDir search_dir(dir);
|
||||
if (search_dir.exists()) {
|
||||
QList<QString> entry_list = search_dir.entryList(LibFilter(), QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot);
|
||||
for (int j=0;j<entry_list.size();j++) {
|
||||
QString entry_path = search_dir.filePath(entry_list.at(j));
|
||||
if (QFileInfo(entry_path).isDir()) {
|
||||
load_frei0r_effects_worker(entry_path, em, loaded_names);
|
||||
} else {
|
||||
ModulePtr effect = LibLoad(entry_path);
|
||||
if (effect != nullptr) {
|
||||
f0rGetPluginInfo get_info_func = reinterpret_cast<f0rGetPluginInfo>(LibAddress(effect, "f0r_get_plugin_info"));
|
||||
if (get_info_func != nullptr) {
|
||||
f0r_plugin_info_t info;
|
||||
get_info_func(&info);
|
||||
|
||||
if (!loaded_names.contains(info.name)
|
||||
&& info.plugin_type == F0R_PLUGIN_TYPE_FILTER
|
||||
&& info.color_model == F0R_COLOR_MODEL_RGBA8888) {
|
||||
em.name = info.name;
|
||||
em.path = dir;
|
||||
em.filename = entry_list.at(j);
|
||||
em.tooltip = QString("%1\n%2\n%3\n%4").arg(em.name, info.author, info.explanation, em.filename);
|
||||
|
||||
loaded_names.append(em.name);
|
||||
|
||||
effects.append(em);
|
||||
}
|
||||
// qDebug() << "Found:" << info.name << "by" << info.author;
|
||||
}
|
||||
LibClose(effect);
|
||||
}
|
||||
// qDebug() << search_dir.filePath(entry_list.at(j));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void load_frei0r_effects() {
|
||||
QList<QString> effect_dirs = get_effects_paths();
|
||||
|
||||
// add defined paths for frei0r plugins on unix
|
||||
#if defined(__APPLE__) || defined(__linux__) || defined(__HAIKU__)
|
||||
effect_dirs.prepend("/usr/lib/frei0r-1");
|
||||
effect_dirs.prepend("/usr/local/lib/frei0r-1");
|
||||
effect_dirs.prepend(QDir::homePath() + "/.frei0r-1/lib");
|
||||
#endif
|
||||
|
||||
QString env_path(qgetenv("FREI0R_PATH"));
|
||||
if (!env_path.isEmpty()) effect_dirs.append(env_path);
|
||||
|
||||
QVector<QString> loaded_names;
|
||||
|
||||
// search for paths
|
||||
EffectMeta em;
|
||||
em.category = "Frei0r";
|
||||
em.type = EFFECT_TYPE_EFFECT;
|
||||
em.subtype = EFFECT_TYPE_VIDEO;
|
||||
em.internal = EFFECT_INTERNAL_FREI0R;
|
||||
|
||||
for (int i=0;i<effect_dirs.size();i++) {
|
||||
load_frei0r_effects_worker(effect_dirs.at(i), em, loaded_names);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
EffectInit::EffectInit() {
|
||||
panel_effect_controls->effects_loaded.lock();
|
||||
}
|
||||
|
||||
void EffectInit::run() {
|
||||
qInfo() << "Initializing effects...";
|
||||
load_internal_effects();
|
||||
load_shader_effects();
|
||||
#ifndef NOFREI0R
|
||||
load_frei0r_effects();
|
||||
#endif
|
||||
panel_effect_controls->effects_loaded.unlock();
|
||||
qInfo() << "Finished initializing effects";
|
||||
}
|
||||
@@ -1,248 +0,0 @@
|
||||
/***
|
||||
|
||||
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 "effectrow.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QPushButton>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "project/undo.h"
|
||||
#include "project/clip.h"
|
||||
#include "project/sequence.h"
|
||||
#include "panels/panels.h"
|
||||
#include "panels/effectcontrols.h"
|
||||
#include "panels/viewer.h"
|
||||
#include "panels/grapheditor.h"
|
||||
#include "effect.h"
|
||||
#include "ui/viewerwidget.h"
|
||||
#include "ui/keyframenavigator.h"
|
||||
#include "ui/clickablelabel.h"
|
||||
|
||||
EffectRow::EffectRow(Effect *parent, const QString &n, bool savable, bool keyframable) :
|
||||
QObject(parent),
|
||||
name_(n),
|
||||
keyframable_(keyframable),
|
||||
keyframing_(false),
|
||||
savable_(savable)
|
||||
{
|
||||
Q_ASSERT(parent != nullptr);
|
||||
|
||||
parent->AddRow(this);
|
||||
}
|
||||
|
||||
void EffectRow::AddField(EffectField *field)
|
||||
{
|
||||
field->setParent(this);
|
||||
fields_.append(field);
|
||||
}
|
||||
|
||||
bool EffectRow::IsKeyframing() {
|
||||
return keyframing_;
|
||||
}
|
||||
|
||||
void EffectRow::SetKeyframingInternal(bool b) {
|
||||
if (GetParentEffect()->meta->type != EFFECT_TYPE_TRANSITION) {
|
||||
keyframing_ = b;
|
||||
emit KeyframingSetChanged(keyframing_);
|
||||
}
|
||||
}
|
||||
|
||||
bool EffectRow::IsSavable()
|
||||
{
|
||||
return savable_;
|
||||
}
|
||||
|
||||
void EffectRow::SetKeyframingEnabled(bool enabled) {
|
||||
if (enabled) {
|
||||
|
||||
ComboAction* ca = new ComboAction();
|
||||
|
||||
// Enable keyframing setting on this row
|
||||
ca->append(new SetIsKeyframing(this, true));
|
||||
|
||||
// Prepare each field's data to start keyframing
|
||||
for (int i=0;i<FieldCount();i++) {
|
||||
Field(i)->PrepareDataForKeyframing(true, ca);
|
||||
}
|
||||
|
||||
olive::UndoStack.push(ca);
|
||||
|
||||
} else {
|
||||
|
||||
// Confirm with the user whether they really want to disable keyframing
|
||||
if (QMessageBox::question(panel_effect_controls,
|
||||
tr("Disable Keyframes"),
|
||||
tr("Disabling keyframes will delete all current keyframes. "
|
||||
"Are you sure you want to do this?"),
|
||||
QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) {
|
||||
|
||||
ComboAction* ca = new ComboAction();
|
||||
|
||||
// Prepare each field's data to stop keyframing
|
||||
for (int i=0;i<FieldCount();i++) {
|
||||
Field(i)->PrepareDataForKeyframing(false, ca);
|
||||
}
|
||||
|
||||
// Disable keyframing setting on this row
|
||||
ca->append(new SetIsKeyframing(this, false));
|
||||
|
||||
olive::UndoStack.push(ca);
|
||||
panel_effect_controls->update_keyframes();
|
||||
|
||||
} else {
|
||||
|
||||
|
||||
SetKeyframingInternal(true);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EffectRow::GoToPreviousKeyframe() {
|
||||
long key = LONG_MIN;
|
||||
Clip* c = GetParentEffect()->parent_clip;
|
||||
long sequence_playhead = c->sequence->playhead;
|
||||
|
||||
// Used to convert clip frame number to sequence frame number
|
||||
long time_adjustment = c->timeline_in() - c->clip_in();
|
||||
|
||||
// Loop through all of this row's fields
|
||||
for (int i=0;i<FieldCount();i++) {
|
||||
|
||||
EffectField* f = Field(i);
|
||||
|
||||
// Loop through all of this field's keyframes for a keyframe EARLIER than the playhead
|
||||
for (int j=0;j<f->keyframes.size();j++) {
|
||||
long comp = f->keyframes.at(j).time + time_adjustment;
|
||||
|
||||
// Get the closest keyframe
|
||||
if (comp < sequence_playhead) {
|
||||
key = qMax(comp, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we found a keyframe less than the playhead, jump to it
|
||||
if (key != LONG_MIN) panel_sequence_viewer->seek(key);
|
||||
}
|
||||
|
||||
void EffectRow::ToggleKeyframe() {
|
||||
Clip* c = GetParentEffect()->parent_clip;
|
||||
long sequence_playhead = c->sequence->playhead;
|
||||
|
||||
// Used to convert clip frame number to sequence frame number
|
||||
long time_adjustment = c->timeline_in() - c->clip_in();
|
||||
|
||||
QVector<EffectField*> key_fields;
|
||||
QVector<int> key_field_index;
|
||||
|
||||
// See if any keyframes on any fields are at the current time
|
||||
for (int j=0;j<FieldCount();j++) {
|
||||
EffectField* f = Field(j);
|
||||
for (int i=0;i<f->keyframes.size();i++) {
|
||||
long comp = f->keyframes.at(i).time + time_adjustment;
|
||||
|
||||
if (comp == sequence_playhead) {
|
||||
|
||||
// Cache the keyframes if they are at the current time
|
||||
key_fields.append(f);
|
||||
key_field_index.append(i);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ComboAction* ca = new ComboAction();
|
||||
|
||||
if (key_fields.isEmpty()) {
|
||||
|
||||
// If we didn't find any current keyframes, create one for each field
|
||||
SetKeyframeOnAllFields(ca);
|
||||
|
||||
} else {
|
||||
|
||||
// If we DID find keyframes at this time, delete them
|
||||
for (int i=0;i<key_fields.size();i++) {
|
||||
ca->append(new KeyframeDelete(key_fields.at(i), key_field_index.at(i)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
olive::UndoStack.push(ca);
|
||||
update_ui(false);
|
||||
}
|
||||
|
||||
void EffectRow::GoToNextKeyframe() {
|
||||
long key = LONG_MAX;
|
||||
Clip* c = GetParentEffect()->parent_clip;
|
||||
for (int i=0;i<FieldCount();i++) {
|
||||
EffectField* f = Field(i);
|
||||
for (int j=0;j<f->keyframes.size();j++) {
|
||||
long comp = f->keyframes.at(j).time - c->clip_in() + c->timeline_in();
|
||||
if (comp > olive::ActiveSequence->playhead) {
|
||||
key = qMin(comp, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (key != LONG_MAX) panel_sequence_viewer->seek(key);
|
||||
}
|
||||
|
||||
void EffectRow::FocusRow() {
|
||||
panel_graph_editor->set_row(this);
|
||||
}
|
||||
|
||||
void EffectRow::SetKeyframeOnAllFields(ComboAction* ca) {
|
||||
for (int i=0;i<FieldCount();i++) {
|
||||
EffectField* field = Field(i);
|
||||
field->SetValueAt(field->Now(), field->GetValueAt(field->Now()));
|
||||
}
|
||||
|
||||
panel_effect_controls->update_keyframes();
|
||||
}
|
||||
|
||||
void EffectRow::delete_keyframe_at_time(ComboAction* ca, long time) {
|
||||
for (int j=0;j<FieldCount();j++) {
|
||||
EffectField* f = Field(j);
|
||||
for (int i=0;i<f->keyframes.size();i++) {
|
||||
if (f->keyframes.at(i).time == time) {
|
||||
ca->append(new KeyframeDelete(f, i));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Effect *EffectRow::GetParentEffect()
|
||||
{
|
||||
return static_cast<Effect*>(parent());
|
||||
}
|
||||
|
||||
const QString &EffectRow::name() {
|
||||
return name_;
|
||||
}
|
||||
|
||||
EffectField* EffectRow::Field(int i) {
|
||||
return fields_.at(i);
|
||||
}
|
||||
|
||||
int EffectRow::FieldCount() {
|
||||
return fields_.size();
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef EFFECTROW_H
|
||||
#define EFFECTROW_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QVector>
|
||||
|
||||
class Effect;
|
||||
class QGridLayout;
|
||||
class EffectField;
|
||||
class QLabel;
|
||||
class QPushButton;
|
||||
class ComboAction;
|
||||
class QHBoxLayout;
|
||||
class KeyframeNavigator;
|
||||
class ClickableLabel;
|
||||
|
||||
#include "effectfields.h"
|
||||
|
||||
class EffectRow : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
EffectRow(Effect* parent, const QString& n, bool savable = true, bool keyframable = true);
|
||||
|
||||
void AddField(EffectField* Field);
|
||||
|
||||
EffectField* Field(int i);
|
||||
int FieldCount();
|
||||
void SetKeyframeOnAllFields(ComboAction *ca);
|
||||
void delete_keyframe_at_time(ComboAction *ca, long time);
|
||||
|
||||
Effect* GetParentEffect();
|
||||
|
||||
const QString& name();
|
||||
|
||||
bool IsKeyframing();
|
||||
void SetKeyframingInternal(bool);
|
||||
|
||||
bool IsSavable();
|
||||
public slots:
|
||||
void GoToPreviousKeyframe();
|
||||
void ToggleKeyframe();
|
||||
void GoToNextKeyframe();
|
||||
void FocusRow();
|
||||
signals:
|
||||
void KeyframingSetChanged(bool);
|
||||
private slots:
|
||||
void SetKeyframingEnabled(bool);
|
||||
private:
|
||||
QString name_;
|
||||
|
||||
bool keyframable_;
|
||||
bool keyframing_;
|
||||
bool savable_;
|
||||
|
||||
QVector<EffectField*> fields_;
|
||||
};
|
||||
|
||||
#endif // EFFECTROW_H
|
||||
+36
-36
@@ -23,9 +23,9 @@
|
||||
#include <QDebug>
|
||||
#include <QtMath>
|
||||
#include <QPainter>
|
||||
#include "io/previewgenerator.h"
|
||||
|
||||
#include "project/clip.h"
|
||||
#include "project/previewgenerator.h"
|
||||
#include "timeline/clip.h"
|
||||
|
||||
Footage::Footage() {
|
||||
ready = (false);
|
||||
@@ -38,56 +38,56 @@ Footage::Footage() {
|
||||
proxy = (false);
|
||||
start_number = 0;
|
||||
|
||||
ready_lock.lock();
|
||||
ready_lock.lock();
|
||||
}
|
||||
|
||||
Footage::~Footage() {
|
||||
reset();
|
||||
reset();
|
||||
}
|
||||
|
||||
void Footage::reset() {
|
||||
if (preview_gen != nullptr) {
|
||||
preview_gen->cancel();
|
||||
}
|
||||
video_tracks.clear();
|
||||
audio_tracks.clear();
|
||||
ready = false;
|
||||
if (preview_gen != nullptr) {
|
||||
preview_gen->cancel();
|
||||
}
|
||||
video_tracks.clear();
|
||||
audio_tracks.clear();
|
||||
ready = false;
|
||||
}
|
||||
|
||||
long Footage::get_length_in_frames(double frame_rate) {
|
||||
if (length >= 0) {
|
||||
return qFloor((double(length) / double(AV_TIME_BASE)) * frame_rate / speed);
|
||||
}
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
FootageStream* Footage::get_stream_from_file_index(bool video, int index) {
|
||||
if (video) {
|
||||
for (int i=0;i<video_tracks.size();i++) {
|
||||
if (video_tracks.at(i).file_index == index) {
|
||||
return &video_tracks[i];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i=0;i<audio_tracks.size();i++) {
|
||||
if (audio_tracks.at(i).file_index == index) {
|
||||
return &audio_tracks[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
if (video) {
|
||||
for (int i=0;i<video_tracks.size();i++) {
|
||||
if (video_tracks.at(i).file_index == index) {
|
||||
return &video_tracks[i];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i=0;i<audio_tracks.size();i++) {
|
||||
if (audio_tracks.at(i).file_index == index) {
|
||||
return &audio_tracks[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void FootageStream::make_square_thumb() {
|
||||
// generate square version for QListView?
|
||||
int square_size = qMax(video_preview.width(), video_preview.height());
|
||||
QPixmap pixmap(square_size, square_size);
|
||||
pixmap.fill(Qt::transparent);
|
||||
QPainter p(&pixmap);
|
||||
int diff = (video_preview.width() - video_preview.height())>>1;
|
||||
int sqx = (diff < 0) ? -diff : 0;
|
||||
int sqy = (diff > 0) ? diff : 0;
|
||||
p.drawImage(sqx, sqy, video_preview);
|
||||
p.end();
|
||||
video_preview_square = QIcon(pixmap);
|
||||
// generate square version for QListView?
|
||||
int square_size = qMax(video_preview.width(), video_preview.height());
|
||||
QPixmap pixmap(square_size, square_size);
|
||||
pixmap.fill(Qt::transparent);
|
||||
QPainter p(&pixmap);
|
||||
int diff = (video_preview.width() - video_preview.height())>>1;
|
||||
int sqx = (diff < 0) ? -diff : 0;
|
||||
int sqy = (diff > 0) ? diff : 0;
|
||||
p.drawImage(sqx, sqy, video_preview);
|
||||
p.end();
|
||||
video_preview_square = QIcon(pixmap);
|
||||
}
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ extern "C" {
|
||||
#include <QPixmap>
|
||||
#include <QIcon>
|
||||
|
||||
#include "project/marker.h"
|
||||
#include "timeline/marker.h"
|
||||
|
||||
enum VideoInterlacingMode {
|
||||
VIDEO_PROGRESSIVE,
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
/***
|
||||
|
||||
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 "keyframe.h"
|
||||
|
||||
#include <QVector>
|
||||
|
||||
#include "effectfields.h"
|
||||
#include "undo.h"
|
||||
#include "panels/panels.h"
|
||||
|
||||
EffectKeyframe::EffectKeyframe() {
|
||||
pre_handle_x = -40;
|
||||
pre_handle_y = 0;
|
||||
post_handle_x = 40;
|
||||
post_handle_y = 0;
|
||||
}
|
||||
|
||||
void delete_keyframes(QVector<EffectField *>& selected_key_fields, QVector<int> &selected_keys) {
|
||||
QVector<EffectField*> fields;
|
||||
QVector<int> key_indices;
|
||||
|
||||
for (int i=0;i<selected_keys.size();i++) {
|
||||
bool added = false;
|
||||
for (int j=0;j<key_indices.size();j++) {
|
||||
if (key_indices.at(j) < selected_keys.at(i)) {
|
||||
key_indices.insert(j, selected_keys.at(i));
|
||||
fields.insert(j, selected_key_fields.at(i));
|
||||
added = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!added) {
|
||||
key_indices.append(selected_keys.at(i));
|
||||
fields.append(selected_key_fields.at(i));
|
||||
}
|
||||
}
|
||||
|
||||
if (fields.size() > 0) {
|
||||
ComboAction* ca = new ComboAction();
|
||||
for (int i=0;i<key_indices.size();i++) {
|
||||
ca->append(new KeyframeDelete(fields.at(i), key_indices.at(i)));
|
||||
}
|
||||
olive::UndoStack.push(ca);
|
||||
selected_keys.clear();
|
||||
selected_key_fields.clear();
|
||||
update_ui(false);
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef KEYFRAME_H
|
||||
#define KEYFRAME_H
|
||||
|
||||
#include <QVariant>
|
||||
|
||||
class EffectField;
|
||||
|
||||
class EffectKeyframe {
|
||||
public:
|
||||
EffectKeyframe();
|
||||
|
||||
long time;
|
||||
int type;
|
||||
QVariant data;
|
||||
|
||||
// only for bezier type
|
||||
double pre_handle_x;
|
||||
double pre_handle_y;
|
||||
double post_handle_x;
|
||||
double post_handle_y;
|
||||
};
|
||||
|
||||
void delete_keyframes(QVector<EffectField *> &selected_key_fields, QVector<int> &selected_keys);
|
||||
|
||||
#endif // KEYFRAME_H
|
||||
@@ -0,0 +1,781 @@
|
||||
/***
|
||||
|
||||
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 "loadthread.h"
|
||||
|
||||
#include "global/global.h"
|
||||
|
||||
#include "ui/mainwindow.h"
|
||||
|
||||
#include "panels/panels.h"
|
||||
|
||||
#include "project/projectelements.h"
|
||||
|
||||
#include "global/config.h"
|
||||
#include "rendering/renderfunctions.h"
|
||||
#include "project/previewgenerator.h"
|
||||
#include "effects/internal/voideffect.h"
|
||||
#include "global/debug.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QTreeWidgetItem>
|
||||
|
||||
LoadThread::LoadThread(const QString& filename, bool autorecovery, bool clear) :
|
||||
filename_(filename),
|
||||
autorecovery_(autorecovery),
|
||||
clear_(clear),
|
||||
cancelled_(false)
|
||||
{
|
||||
connect(this, SIGNAL(finished()), this, SLOT(deleteLater()));
|
||||
|
||||
connect(this, SIGNAL(success()), this, SLOT(success_func()), Qt::QueuedConnection);
|
||||
|
||||
connect(this, SIGNAL(error()), this, SLOT(error_func()), Qt::QueuedConnection);
|
||||
|
||||
connect(this,
|
||||
SIGNAL(start_question(const QString&, const QString &, int)),
|
||||
this,
|
||||
SLOT(question_func(const QString &, const QString &, int)),
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void LoadThread::load_effect(QXmlStreamReader& stream, Clip* c) {
|
||||
QString tag = stream.name().toString();
|
||||
|
||||
// variables to store effect metadata in
|
||||
int effect_id = -1;
|
||||
QString effect_name;
|
||||
bool effect_enabled = true;
|
||||
long effect_length = -1;
|
||||
|
||||
// loop through attributes for effect metadata
|
||||
for (int j=0;j<stream.attributes().size();j++) {
|
||||
const QXmlStreamAttribute& attr = stream.attributes().at(j);
|
||||
if (attr.name() == "id") {
|
||||
effect_id = attr.value().toInt();
|
||||
} else if (attr.name() == "enabled") {
|
||||
effect_enabled = (attr.value() == "1");
|
||||
} else if (attr.name() == "name") {
|
||||
effect_name = attr.value().toString();
|
||||
} else if (attr.name() == "length") {
|
||||
effect_length = attr.value().toLong();
|
||||
} else if (attr.name() == "shared") {
|
||||
// if a transition has this tag, it's sharing a transition with another clip so we don't have to do any processing
|
||||
|
||||
Clip* sharing_clip = nullptr;
|
||||
|
||||
// Find the clip with the ID referenced in the transition
|
||||
int clip_id = attr.value().toInt();
|
||||
for (int i=0;i<c->sequence->clips.size();i++) {
|
||||
Clip* test_clip = c->sequence->clips.at(i).get();
|
||||
if (test_clip->load_id == clip_id) {
|
||||
sharing_clip = test_clip;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (sharing_clip == nullptr) {
|
||||
qWarning() << "Failed to link shared transition. Project may be corrupt.";
|
||||
} else if (tag == "opening") {
|
||||
c->opening_transition = (sharing_clip->closing_transition);
|
||||
|
||||
// since this is the opened clip, switch secondaries and primaries
|
||||
c->opening_transition->secondary_clip = c->opening_transition->parent_clip;
|
||||
c->opening_transition->parent_clip = c;
|
||||
c->opening_transition->refresh();
|
||||
} else if (tag == "closing") {
|
||||
c->closing_transition = (sharing_clip->opening_transition);
|
||||
|
||||
// since this is the closed clip, make this clip the secondary
|
||||
c->opening_transition->secondary_clip = c;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Effect loading occurs in another thread, and while it's usually very quick, just for safety we wait here
|
||||
// for all the effects to finish loading
|
||||
panel_effect_controls->effects_loaded.lock();
|
||||
|
||||
const EffectMeta* meta = nullptr;
|
||||
|
||||
// find effect with this name
|
||||
if (!effect_name.isEmpty()) {
|
||||
meta = get_meta_from_name(effect_name);
|
||||
}
|
||||
|
||||
panel_effect_controls->effects_loaded.unlock();
|
||||
|
||||
int type;
|
||||
if (tag == "opening") {
|
||||
type = kTransitionOpening;
|
||||
} else if (tag == "closing") {
|
||||
type = kTransitionClosing;
|
||||
} else {
|
||||
type = kTransitionNone;
|
||||
}
|
||||
|
||||
// effect construction
|
||||
if (cancelled_) return;
|
||||
if (type == kTransitionNone) {
|
||||
if (meta == nullptr) {
|
||||
// create void effect
|
||||
EffectPtr ve(new VoidEffect(c, effect_name));
|
||||
ve->SetEnabled(effect_enabled);
|
||||
ve->load(stream);
|
||||
|
||||
ve->moveToThread(QApplication::instance()->thread());
|
||||
|
||||
c->effects.append(ve);
|
||||
} else {
|
||||
EffectPtr e(Effect::Create(c, meta));
|
||||
e->SetEnabled(effect_enabled);
|
||||
e->load(stream);
|
||||
|
||||
e->moveToThread(QApplication::instance()->thread());
|
||||
|
||||
c->effects.append(e);
|
||||
}
|
||||
} else {
|
||||
TransitionPtr t = Transition::Create(c, nullptr, meta);
|
||||
if (effect_length > -1) t->set_length(effect_length);
|
||||
t->SetEnabled(effect_enabled);
|
||||
t->load(stream);
|
||||
|
||||
t->moveToThread(QApplication::instance()->thread());
|
||||
|
||||
if (type == kTransitionOpening) {
|
||||
c->opening_transition = t;
|
||||
} else {
|
||||
c->closing_transition = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LoadThread::read_next(QXmlStreamReader &stream) {
|
||||
stream.readNext();
|
||||
update_current_element_count(stream);
|
||||
}
|
||||
|
||||
void LoadThread::read_next_start_element(QXmlStreamReader &stream) {
|
||||
stream.readNextStartElement();
|
||||
update_current_element_count(stream);
|
||||
}
|
||||
|
||||
void LoadThread::update_current_element_count(QXmlStreamReader &stream) {
|
||||
if (is_element(stream)) {
|
||||
current_element_count++;
|
||||
report_progress((current_element_count * 100) / total_element_count);
|
||||
}
|
||||
}
|
||||
|
||||
void LoadThread::show_message(const QString& title, const QString& body, int buttons)
|
||||
{
|
||||
emit start_question(
|
||||
title,
|
||||
body,
|
||||
buttons
|
||||
);
|
||||
waitCond.wait(&mutex);
|
||||
}
|
||||
|
||||
bool LoadThread::is_element(QXmlStreamReader &stream) {
|
||||
return stream.isStartElement()
|
||||
&& (stream.name() == "folder"
|
||||
|| stream.name() == "footage"
|
||||
|| stream.name() == "sequence"
|
||||
|| stream.name() == "clip"
|
||||
|| stream.name() == "effect");
|
||||
}
|
||||
|
||||
bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) {
|
||||
f.seek(0);
|
||||
stream.setDevice(stream.device());
|
||||
|
||||
QString root_search;
|
||||
QString child_search;
|
||||
|
||||
switch (type) {
|
||||
case LOAD_TYPE_VERSION:
|
||||
root_search = "version";
|
||||
break;
|
||||
case LOAD_TYPE_URL:
|
||||
root_search = "url";
|
||||
break;
|
||||
case MEDIA_TYPE_FOLDER:
|
||||
root_search = "folders";
|
||||
child_search = "folder";
|
||||
break;
|
||||
case MEDIA_TYPE_FOOTAGE:
|
||||
root_search = "media";
|
||||
child_search = "footage";
|
||||
break;
|
||||
case MEDIA_TYPE_SEQUENCE:
|
||||
root_search = "sequences";
|
||||
child_search = "sequence";
|
||||
break;
|
||||
}
|
||||
|
||||
show_err = true;
|
||||
|
||||
int proj_version = olive::kSaveVersion;
|
||||
|
||||
while (!stream.atEnd() && !cancelled_) {
|
||||
read_next_start_element(stream);
|
||||
if (stream.name() == root_search) {
|
||||
if (type == LOAD_TYPE_VERSION) {
|
||||
proj_version = stream.readElementText().toInt();
|
||||
if (proj_version < olive::kMinimumSaveVersion || proj_version > olive::kSaveVersion) {
|
||||
show_message(
|
||||
tr("Version Mismatch"),
|
||||
tr("This project was saved in a different version of Olive and may not be fully compatible with this version. Would you like to attempt loading it anyway?"),
|
||||
QMessageBox::Yes | QMessageBox::No
|
||||
);
|
||||
if (question_btn == QMessageBox::No) {
|
||||
show_err = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (type == LOAD_TYPE_URL) {
|
||||
internal_proj_url = stream.readElementText();
|
||||
internal_proj_dir = QFileInfo(internal_proj_url).absoluteDir();
|
||||
} else {
|
||||
while (!cancelled_ && !stream.atEnd() && !(stream.name() == root_search && stream.isEndElement())) {
|
||||
read_next(stream);
|
||||
if (stream.name() == child_search && stream.isStartElement()) {
|
||||
switch (type) {
|
||||
case MEDIA_TYPE_FOLDER:
|
||||
{
|
||||
MediaPtr folder = panel_project->create_folder_internal(nullptr);
|
||||
folder->temp_id2 = 0;
|
||||
for (int j=0;j<stream.attributes().size();j++) {
|
||||
const QXmlStreamAttribute& attr = stream.attributes().at(j);
|
||||
if (attr.name() == "id") {
|
||||
folder->temp_id = attr.value().toInt();
|
||||
} else if (attr.name() == "name") {
|
||||
folder->set_name(attr.value().toString());
|
||||
} else if (attr.name() == "parent") {
|
||||
folder->temp_id2 = attr.value().toInt();
|
||||
}
|
||||
}
|
||||
loaded_folders.append(folder);
|
||||
}
|
||||
break;
|
||||
case MEDIA_TYPE_FOOTAGE:
|
||||
{
|
||||
int folder = 0;
|
||||
|
||||
MediaPtr item = std::make_shared<Media>();
|
||||
FootagePtr f(new Footage());
|
||||
|
||||
f->using_inout = false;
|
||||
|
||||
for (int j=0;j<stream.attributes().size();j++) {
|
||||
const QXmlStreamAttribute& attr = stream.attributes().at(j);
|
||||
if (attr.name() == "id") {
|
||||
f->save_id = attr.value().toInt();
|
||||
} else if (attr.name() == "folder") {
|
||||
folder = attr.value().toInt();
|
||||
} else if (attr.name() == "name") {
|
||||
f->name = attr.value().toString();
|
||||
} else if (attr.name() == "url") {
|
||||
f->url = attr.value().toString();
|
||||
|
||||
if (!QFileInfo::exists(f->url)) { // if path is not absolute
|
||||
// tries to locate file using a file path relative to the project's current folder
|
||||
QString proj_dir_test = proj_dir.absoluteFilePath(f->url);
|
||||
|
||||
// tries to locate file using a file path relative to the folder the project was saved in
|
||||
// (unaffected by moving the project file)
|
||||
QString internal_proj_dir_test = internal_proj_dir.absoluteFilePath(f->url);
|
||||
|
||||
// tries to locate file using the file name directly in the project's current folder
|
||||
QString proj_dir_direct_test = proj_dir.filePath(QFileInfo(f->url).fileName());
|
||||
|
||||
if (QFileInfo::exists(proj_dir_test)) {
|
||||
|
||||
f->url = proj_dir_test;
|
||||
qInfo() << "Matched" << attr.value().toString() << "relative to project's current directory";
|
||||
|
||||
} else if (QFileInfo::exists(internal_proj_dir_test)) {
|
||||
|
||||
f->url = internal_proj_dir_test;
|
||||
qInfo() << "Matched" << attr.value().toString() << "relative to project's internal directory";
|
||||
|
||||
} else if (QFileInfo::exists(proj_dir_direct_test)) {
|
||||
|
||||
f->url = proj_dir_direct_test;
|
||||
qInfo() << "Matched" << attr.value().toString() << "directly to project's current directory";
|
||||
|
||||
} else if (f->url.contains('%')) {
|
||||
|
||||
// hack for image sequences (qt won't be able to find the URL with %, but ffmpeg may)
|
||||
f->url = internal_proj_dir_test;
|
||||
qInfo() << "Guess image sequence" << attr.value().toString() << "path to project's internal directory";
|
||||
|
||||
} else {
|
||||
|
||||
qInfo() << "Failed to match" << attr.value().toString() << "to file";
|
||||
|
||||
}
|
||||
} else {
|
||||
f->url = QFileInfo(f->url).absoluteFilePath();
|
||||
qInfo() << "Matched" << attr.value().toString() << "with absolute path";
|
||||
}
|
||||
} else if (attr.name() == "duration") {
|
||||
f->length = attr.value().toLongLong();
|
||||
} else if (attr.name() == "using_inout") {
|
||||
f->using_inout = (attr.value() == "1");
|
||||
} else if (attr.name() == "in") {
|
||||
f->in = attr.value().toLong();
|
||||
} else if (attr.name() == "out") {
|
||||
f->out = attr.value().toLong();
|
||||
} else if (attr.name() == "speed") {
|
||||
f->speed = attr.value().toDouble();
|
||||
} else if (attr.name() == "alphapremul") {
|
||||
f->alpha_is_premultiplied = (attr.value() == "1");
|
||||
} else if (attr.name() == "proxy") {
|
||||
f->proxy = (attr.value() == "1");
|
||||
} else if (attr.name() == "proxypath") {
|
||||
f->proxy_path = attr.value().toString();
|
||||
} else if (attr.name() == "startnumber") {
|
||||
f->start_number = attr.value().toInt();
|
||||
}
|
||||
}
|
||||
|
||||
while (!cancelled_ && !(stream.name() == child_search && stream.isEndElement()) && !stream.atEnd()) {
|
||||
read_next_start_element(stream);
|
||||
if (stream.name() == "marker" && stream.isStartElement()) {
|
||||
Marker m;
|
||||
for (int j=0;j<stream.attributes().size();j++) {
|
||||
const QXmlStreamAttribute& attr = stream.attributes().at(j);
|
||||
if (attr.name() == "frame") {
|
||||
m.frame = attr.value().toLong();
|
||||
} else if (attr.name() == "name") {
|
||||
m.name = attr.value().toString();
|
||||
}
|
||||
}
|
||||
f->markers.append(m);
|
||||
}
|
||||
}
|
||||
|
||||
item->set_footage(f);
|
||||
|
||||
olive::project_model.appendChild(find_loaded_folder_by_id(folder), item);
|
||||
|
||||
// analyze media to see if it's the same
|
||||
loaded_media_items.append(item.get());
|
||||
}
|
||||
break;
|
||||
case MEDIA_TYPE_SEQUENCE:
|
||||
{
|
||||
Media* parent = nullptr;
|
||||
SequencePtr s = std::make_shared<Sequence>();
|
||||
|
||||
// load attributes about sequence
|
||||
for (int j=0;j<stream.attributes().size();j++) {
|
||||
const QXmlStreamAttribute& attr = stream.attributes().at(j);
|
||||
if (attr.name() == "name") {
|
||||
s->name = attr.value().toString();
|
||||
} else if (attr.name() == "folder") {
|
||||
int folder = attr.value().toInt();
|
||||
if (folder > 0) parent = find_loaded_folder_by_id(folder);
|
||||
} else if (attr.name() == "id") {
|
||||
s->save_id = attr.value().toInt();
|
||||
} else if (attr.name() == "width") {
|
||||
s->width = attr.value().toInt();
|
||||
} else if (attr.name() == "height") {
|
||||
s->height = attr.value().toInt();
|
||||
} else if (attr.name() == "framerate") {
|
||||
s->frame_rate = attr.value().toDouble();
|
||||
} else if (attr.name() == "afreq") {
|
||||
s->audio_frequency = attr.value().toInt();
|
||||
} else if (attr.name() == "alayout") {
|
||||
s->audio_layout = attr.value().toInt();
|
||||
} else if (attr.name() == "open") {
|
||||
open_seq = s;
|
||||
} else if (attr.name() == "workarea") {
|
||||
s->using_workarea = (attr.value() == "1");
|
||||
} else if (attr.name() == "workareaIn") {
|
||||
s->workarea_in = attr.value().toLong();
|
||||
} else if (attr.name() == "workareaOut") {
|
||||
s->workarea_out = attr.value().toLong();
|
||||
}
|
||||
}
|
||||
|
||||
// load all clips and clip information
|
||||
while (!cancelled_ && !(stream.name() == child_search && stream.isEndElement()) && !stream.atEnd()) {
|
||||
read_next_start_element(stream);
|
||||
if (stream.name() == "marker" && stream.isStartElement()) {
|
||||
Marker m;
|
||||
for (int j=0;j<stream.attributes().size();j++) {
|
||||
const QXmlStreamAttribute& attr = stream.attributes().at(j);
|
||||
if (attr.name() == "frame") {
|
||||
m.frame = attr.value().toLong();
|
||||
} else if (attr.name() == "name") {
|
||||
m.name = attr.value().toString();
|
||||
}
|
||||
}
|
||||
s->markers.append(m);
|
||||
} else if (stream.name() == "clip" && stream.isStartElement()) {
|
||||
int media_type = -1;
|
||||
int media_id = -1;
|
||||
int stream_id = -1;
|
||||
|
||||
ClipPtr c = std::make_shared<Clip>(s.get());
|
||||
|
||||
QColor clip_color;
|
||||
ClipSpeed speed_info = c->speed();
|
||||
|
||||
for (int j=0;j<stream.attributes().size();j++) {
|
||||
const QXmlStreamAttribute& attr = stream.attributes().at(j);
|
||||
if (attr.name() == "name") {
|
||||
c->set_name(attr.value().toString());
|
||||
} else if (attr.name() == "enabled") {
|
||||
c->set_enabled(attr.value() == "1");
|
||||
} else if (attr.name() == "id") {
|
||||
c->load_id = attr.value().toInt();
|
||||
} else if (attr.name() == "clipin") {
|
||||
c->set_clip_in(attr.value().toLong());
|
||||
} else if (attr.name() == "in") {
|
||||
c->set_timeline_in(attr.value().toLong());
|
||||
} else if (attr.name() == "out") {
|
||||
c->set_timeline_out(attr.value().toLong());
|
||||
} else if (attr.name() == "track") {
|
||||
c->set_track(attr.value().toInt());
|
||||
} else if (attr.name() == "r") {
|
||||
clip_color.setRed(attr.value().toInt());
|
||||
} else if (attr.name() == "g") {
|
||||
clip_color.setGreen(attr.value().toInt());
|
||||
} else if (attr.name() == "b") {
|
||||
clip_color.setBlue(attr.value().toInt());
|
||||
} else if (attr.name() == "autoscale") {
|
||||
c->set_autoscaled(attr.value() == "1");
|
||||
} else if (attr.name() == "media") {
|
||||
media_type = MEDIA_TYPE_FOOTAGE;
|
||||
media_id = attr.value().toInt();
|
||||
} else if (attr.name() == "stream") {
|
||||
stream_id = attr.value().toInt();
|
||||
} else if (attr.name() == "speed") {
|
||||
speed_info.value = attr.value().toDouble();
|
||||
} else if (attr.name() == "maintainpitch") {
|
||||
speed_info.maintain_audio_pitch = (attr.value() == "1");
|
||||
} else if (attr.name() == "reverse") {
|
||||
c->set_reversed(attr.value() == "1");
|
||||
} else if (attr.name() == "sequence") {
|
||||
media_type = MEDIA_TYPE_SEQUENCE;
|
||||
|
||||
// since we haven't finished loading sequences, we defer linking this until later
|
||||
c->set_media(nullptr, attr.value().toInt());
|
||||
loaded_clips.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
c->set_color(clip_color);
|
||||
c->set_speed(speed_info);
|
||||
|
||||
// set media and media stream
|
||||
switch (media_type) {
|
||||
case MEDIA_TYPE_FOOTAGE:
|
||||
if (media_id >= 0 && stream_id >= 0) {
|
||||
for (int j=0;j<loaded_media_items.size();j++) {
|
||||
Footage* m = loaded_media_items.at(j)->to_footage();
|
||||
if (m->save_id == media_id) {
|
||||
c->set_media(loaded_media_items.at(j), stream_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// load links and effects
|
||||
while (!cancelled_ && !(stream.name() == "clip" && stream.isEndElement()) && !stream.atEnd()) {
|
||||
read_next(stream);
|
||||
if (stream.isStartElement()) {
|
||||
if (stream.name() == "linked") {
|
||||
while (!cancelled_ && !(stream.name() == "linked" && stream.isEndElement()) && !stream.atEnd()) {
|
||||
read_next(stream);
|
||||
if (stream.name() == "link" && stream.isStartElement()) {
|
||||
for (int k=0;k<stream.attributes().size();k++) {
|
||||
const QXmlStreamAttribute& link_attr = stream.attributes().at(k);
|
||||
if (link_attr.name() == "id") {
|
||||
c->linked.append(link_attr.value().toInt());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cancelled_) return false;
|
||||
} else if (stream.isStartElement()
|
||||
&& (stream.name() == "effect"
|
||||
|| stream.name() == "opening"
|
||||
|| stream.name() == "closing")) {
|
||||
load_effect(stream, c.get());
|
||||
} else if (stream.name() == "marker" && stream.isStartElement()) {
|
||||
Marker m;
|
||||
for (int j=0;j<stream.attributes().size();j++) {
|
||||
const QXmlStreamAttribute& attr = stream.attributes().at(j);
|
||||
if (attr.name() == "frame") {
|
||||
m.frame = attr.value().toLong();
|
||||
} else if (attr.name() == "name") {
|
||||
m.name = attr.value().toString();
|
||||
}
|
||||
}
|
||||
c->get_markers().append(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cancelled_) return false;
|
||||
|
||||
s->clips.append(c);
|
||||
}
|
||||
}
|
||||
if (cancelled_) return false;
|
||||
|
||||
// correct links, clip IDs, transitions
|
||||
for (int i=0;i<s->clips.size();i++) {
|
||||
// correct links
|
||||
Clip* correct_clip = s->clips.at(i).get();
|
||||
for (int j=0;j<correct_clip->linked.size();j++) {
|
||||
bool found = false;
|
||||
for (int k=0;k<s->clips.size();k++) {
|
||||
if (s->clips.at(k)->load_id == correct_clip->linked.at(j)) {
|
||||
correct_clip->linked[j] = k;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
correct_clip->linked.removeAt(j);
|
||||
j--;
|
||||
|
||||
show_message(
|
||||
tr("Invalid Clip Link"),
|
||||
tr("This project contains an invalid clip link. It may be corrupt. Would you like to continue loading it?"),
|
||||
QMessageBox::Yes | QMessageBox::No
|
||||
);
|
||||
|
||||
if (question_btn == QMessageBox::No) {
|
||||
s.reset();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MediaPtr m = panel_project->create_sequence_internal(nullptr, s, false, parent);
|
||||
|
||||
loaded_sequences.append(m.get());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cancelled_) return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return !cancelled_;
|
||||
}
|
||||
|
||||
Media* LoadThread::find_loaded_folder_by_id(int id) {
|
||||
if (id == 0) return nullptr;
|
||||
for (int j=0;j<loaded_folders.size();j++) {
|
||||
Media* parent_item = loaded_folders.at(j).get();
|
||||
if (parent_item->temp_id == id) {
|
||||
return parent_item;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void LoadThread::run() {
|
||||
mutex.lock();
|
||||
|
||||
QFile file(filename_);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qCritical() << "Could not open file";
|
||||
return;
|
||||
}
|
||||
|
||||
/* set up directories to search for media
|
||||
* most of the time, these will be the same but in
|
||||
* case the project file has moved without the footage,
|
||||
* we check both
|
||||
*/
|
||||
proj_dir = QFileInfo(filename_).absoluteDir();
|
||||
internal_proj_dir = QFileInfo(filename_).absoluteDir();
|
||||
internal_proj_url = filename_;
|
||||
|
||||
QXmlStreamReader stream(&file);
|
||||
|
||||
bool cont = false;
|
||||
error_str.clear();
|
||||
show_err = true;
|
||||
|
||||
open_seq = nullptr;
|
||||
|
||||
// get "element" count
|
||||
current_element_count = 0;
|
||||
total_element_count = 0;
|
||||
while (!cancelled_ && !stream.atEnd()) {
|
||||
stream.readNextStartElement();
|
||||
if (is_element(stream)) {
|
||||
total_element_count++;
|
||||
}
|
||||
}
|
||||
cont = !cancelled_;
|
||||
|
||||
// find project file version
|
||||
if (cont) {
|
||||
cont = load_worker(file, stream, LOAD_TYPE_VERSION);
|
||||
}
|
||||
|
||||
// find project's internal URL
|
||||
if (cont) {
|
||||
cont = load_worker(file, stream, LOAD_TYPE_URL);
|
||||
}
|
||||
|
||||
// load folders first
|
||||
if (cont) {
|
||||
cont = load_worker(file, stream, MEDIA_TYPE_FOLDER);
|
||||
}
|
||||
|
||||
// load media
|
||||
if (cont) {
|
||||
// since folders loaded correctly, organize them appropriately
|
||||
for (int i=0;i<loaded_folders.size();i++) {
|
||||
MediaPtr folder = loaded_folders.at(i);
|
||||
int parent = folder->temp_id2;
|
||||
olive::project_model.appendChild(find_loaded_folder_by_id(parent), folder);
|
||||
}
|
||||
|
||||
cont = load_worker(file, stream, MEDIA_TYPE_FOOTAGE);
|
||||
}
|
||||
|
||||
// load sequences
|
||||
if (cont) {
|
||||
cont = load_worker(file, stream, MEDIA_TYPE_SEQUENCE);
|
||||
}
|
||||
|
||||
if (!cancelled_) {
|
||||
if (!cont) {
|
||||
xml_error = false;
|
||||
if (show_err) emit error();
|
||||
} else if (stream.hasError()) {
|
||||
error_str = tr("%1 - Line: %2 Col: %3").arg(stream.errorString(), QString::number(stream.lineNumber()), QString::number(stream.columnNumber()));
|
||||
xml_error = true;
|
||||
emit error();
|
||||
cont = false;
|
||||
} else {
|
||||
// attach nested sequence clips to their sequences
|
||||
for (int i=0;i<loaded_clips.size();i++) {
|
||||
for (int j=0;j<loaded_sequences.size();j++) {
|
||||
if (loaded_clips.at(i)->media() == nullptr
|
||||
&& loaded_clips.at(i)->media_stream_index() == loaded_sequences.at(j)->to_sequence()->save_id) {
|
||||
loaded_clips.at(i)->set_media(loaded_sequences.at(j), loaded_clips.at(i)->media_stream_index());
|
||||
loaded_clips.at(i)->refresh();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cont) {
|
||||
emit success(); // run in main thread
|
||||
|
||||
for (int i=0;i<loaded_media_items.size();i++) {
|
||||
PreviewGenerator::AnalyzeMedia(loaded_media_items.at(i));
|
||||
}
|
||||
} else {
|
||||
if (error_str.isEmpty()) {
|
||||
error_str = tr("User aborted loading");
|
||||
}
|
||||
|
||||
emit error();
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
void LoadThread::cancel() {
|
||||
waitCond.wakeAll();
|
||||
cancelled_ = true;
|
||||
}
|
||||
|
||||
void LoadThread::question_func(const QString &title, const QString &text, int buttons) {
|
||||
mutex.lock();
|
||||
question_btn = QMessageBox::warning(
|
||||
olive::MainWindow,
|
||||
title,
|
||||
text,
|
||||
static_cast<enum QMessageBox::StandardButton>(buttons));
|
||||
mutex.unlock();
|
||||
waitCond.wakeAll();
|
||||
}
|
||||
|
||||
void LoadThread::error_func() {
|
||||
if (xml_error) {
|
||||
qCritical() << "Error parsing XML." << error_str;
|
||||
QMessageBox::critical(olive::MainWindow,
|
||||
tr("XML Parsing Error"),
|
||||
tr("Couldn't load '%1'. %2").arg(filename_, error_str),
|
||||
QMessageBox::Ok);
|
||||
} else {
|
||||
QMessageBox::critical(olive::MainWindow,
|
||||
tr("Project Load Error"),
|
||||
tr("Error loading project: %1").arg(error_str),
|
||||
QMessageBox::Ok);
|
||||
}
|
||||
}
|
||||
|
||||
void LoadThread::success_func() {
|
||||
if (autorecovery_) {
|
||||
QString orig_filename = internal_proj_url;
|
||||
int insert_index = internal_proj_url.lastIndexOf(".ove", -1, Qt::CaseInsensitive);
|
||||
if (insert_index == -1) insert_index = internal_proj_url.length();
|
||||
int counter = 1;
|
||||
while (QFileInfo::exists(orig_filename)) {
|
||||
orig_filename = internal_proj_url;
|
||||
QString recover_text = "recovered";
|
||||
if (counter > 1) {
|
||||
recover_text += " " + QString::number(counter);
|
||||
}
|
||||
orig_filename.insert(insert_index, " (" + recover_text + ")");
|
||||
counter++;
|
||||
}
|
||||
|
||||
if (clear_) {
|
||||
olive::Global->update_project_filename(orig_filename);
|
||||
}
|
||||
} else {
|
||||
panel_project->add_recent_project(filename_);
|
||||
}
|
||||
|
||||
olive::MainWindow->setWindowModified(autorecovery_ || !clear_);
|
||||
if (open_seq != nullptr) {
|
||||
olive::Global->set_sequence(open_seq);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef LOADTHREAD_H
|
||||
#define LOADTHREAD_H
|
||||
|
||||
#include <QThread>
|
||||
#include <QDir>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QMutex>
|
||||
#include <QWaitCondition>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "project/projectelements.h"
|
||||
#include "timeline/clip.h"
|
||||
|
||||
class LoadThread : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
LoadThread(const QString& filename, bool autorecovery, bool clear);
|
||||
void run();
|
||||
void cancel();
|
||||
signals:
|
||||
void start_question(const QString &title, const QString &text, int buttons);
|
||||
void success();
|
||||
void error();
|
||||
void report_progress(int p);
|
||||
private slots:
|
||||
void question_func(const QString &title, const QString &text, int buttons);
|
||||
void error_func();
|
||||
void success_func();
|
||||
private:
|
||||
bool autorecovery_;
|
||||
bool clear_;
|
||||
QString filename_;
|
||||
|
||||
bool load_worker(QFile& f, QXmlStreamReader& stream, int type);
|
||||
void load_effect(QXmlStreamReader& stream, Clip* c);
|
||||
|
||||
void read_next(QXmlStreamReader& stream);
|
||||
void read_next_start_element(QXmlStreamReader& stream);
|
||||
void update_current_element_count(QXmlStreamReader& stream);
|
||||
|
||||
void show_message(const QString& title, const QString& body, int buttons);
|
||||
|
||||
SequencePtr open_seq;
|
||||
QVector<Media*> loaded_media_items;
|
||||
QDir proj_dir;
|
||||
QDir internal_proj_dir;
|
||||
QString internal_proj_url;
|
||||
bool show_err;
|
||||
QString error_str;
|
||||
|
||||
bool is_element(QXmlStreamReader& stream);
|
||||
|
||||
QVector<MediaPtr> loaded_folders;
|
||||
QVector<ClipPtr> loaded_clips;
|
||||
QVector<Media*> loaded_sequences;
|
||||
Media* find_loaded_folder_by_id(int id);
|
||||
|
||||
int current_element_count;
|
||||
int total_element_count;
|
||||
|
||||
QMutex mutex;
|
||||
QWaitCondition waitCond;
|
||||
|
||||
bool cancelled_;
|
||||
bool xml_error;
|
||||
|
||||
QMessageBox::StandardButton question_btn;
|
||||
};
|
||||
|
||||
#endif // LOADTHREAD_H
|
||||
@@ -1,127 +0,0 @@
|
||||
/***
|
||||
|
||||
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 "marker.h"
|
||||
|
||||
#include "io/config.h"
|
||||
#include "project/undo.h"
|
||||
#include "mainwindow.h"
|
||||
#include "project/sequence.h"
|
||||
#include "project/clip.h"
|
||||
#include "panels/panels.h"
|
||||
#include "panels/viewer.h"
|
||||
|
||||
#include <QInputDialog>
|
||||
#include <QCoreApplication>
|
||||
|
||||
void draw_marker(QPainter &p, int x, int y, int bottom, bool selected) {
|
||||
const QPoint points[5] = {
|
||||
QPoint(x, bottom),
|
||||
QPoint(x + MARKER_SIZE, bottom - MARKER_SIZE),
|
||||
QPoint(x + MARKER_SIZE, y),
|
||||
QPoint(x - MARKER_SIZE, y),
|
||||
QPoint(x - MARKER_SIZE, bottom - MARKER_SIZE)
|
||||
};
|
||||
p.setPen(Qt::black);
|
||||
if (selected) {
|
||||
p.setBrush(QColor(208, 255, 208));
|
||||
} else {
|
||||
p.setBrush(QColor(128, 224, 128));
|
||||
}
|
||||
p.drawPolygon(points, 5);
|
||||
}
|
||||
|
||||
void set_marker_internal(Sequence* seq, const QVector<int>& clips) {
|
||||
// if clips is empty, the marker is being added to the sequence
|
||||
|
||||
// add_marker is used to determine whether we're adding a marker, depending on whether the user input a marker name
|
||||
// however if (config.set_name_with_marker) is true, we don't need a marker name so we just add
|
||||
bool add_marker = !olive::CurrentConfig.set_name_with_marker;
|
||||
|
||||
QString marker_name;
|
||||
|
||||
// if (config.set_name_with_marker) is false (set above), ask for a marker name
|
||||
if (!add_marker) {
|
||||
QInputDialog d(olive::MainWindow);
|
||||
d.setWindowTitle(QCoreApplication::translate("Marker", "Set Marker"));
|
||||
d.setLabelText(clips.size() > 0
|
||||
? QCoreApplication::translate("Marker", "Set clip marker name:")
|
||||
: QCoreApplication::translate("Marker", "Set sequence marker name:"));
|
||||
d.setInputMode(QInputDialog::TextInput);
|
||||
add_marker = (d.exec() == QDialog::Accepted);
|
||||
marker_name = d.textValue();
|
||||
}
|
||||
|
||||
// if we've decided to add a marker
|
||||
if (add_marker) {
|
||||
|
||||
ComboAction* ca = new ComboAction();
|
||||
|
||||
if (clips.size() > 0) {
|
||||
|
||||
// add a marker action for each clip
|
||||
foreach (int i, clips) {
|
||||
ClipPtr c = seq->clips.at(i);
|
||||
ca->append(new AddMarkerAction(&c->get_markers(),
|
||||
seq->playhead - c->timeline_in() + c->clip_in(),
|
||||
marker_name));
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// if no clips are selected, we're adding a marker to the sequence
|
||||
|
||||
// kind of hacky, we get the correct marker structure from the viewer panel object that the sequence is attached to
|
||||
if (seq == panel_footage_viewer->seq.get()) {
|
||||
|
||||
// get correct marker reference from footage viewer
|
||||
ca->append(new AddMarkerAction(panel_footage_viewer->marker_ref, seq->playhead, marker_name));
|
||||
|
||||
} else if (seq == panel_sequence_viewer->seq.get()) {
|
||||
|
||||
// get correct marker reference from sequence viewer
|
||||
ca->append(new AddMarkerAction(panel_sequence_viewer->marker_ref, seq->playhead, marker_name));
|
||||
|
||||
} else {
|
||||
|
||||
// fallback to using markers from sequence provided
|
||||
ca->append(new AddMarkerAction(&seq->markers, seq->playhead, marker_name));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// push action
|
||||
olive::UndoStack.push(ca);
|
||||
|
||||
// redraw UI for new markers
|
||||
update_ui(false);
|
||||
panel_footage_viewer->update_viewer();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void set_marker_internal(Sequence *seq) {
|
||||
// create empty clip array
|
||||
QVector<int> clips;
|
||||
|
||||
set_marker_internal(seq, clips);
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef MARKER_H
|
||||
#define MARKER_H
|
||||
|
||||
#define MARKER_SIZE 4
|
||||
|
||||
#include <QString>
|
||||
#include <QPainter>
|
||||
#include <memory>
|
||||
|
||||
class Sequence;
|
||||
using SequencePtr = std::shared_ptr<Sequence>;
|
||||
|
||||
struct Marker {
|
||||
long frame;
|
||||
QString name;
|
||||
};
|
||||
|
||||
void draw_marker(QPainter& p, int x, int y, int bottom, bool selected);
|
||||
|
||||
void set_marker_internal(Sequence *seq, const QVector<int>& clips);
|
||||
void set_marker_internal(Sequence* seq);
|
||||
|
||||
#endif // MARKER_H
|
||||
+13
-14
@@ -20,25 +20,24 @@
|
||||
|
||||
#include "media.h"
|
||||
|
||||
#include "footage.h"
|
||||
#include "sequence.h"
|
||||
#include "undo.h"
|
||||
#include "io/config.h"
|
||||
#include "panels/viewer.h"
|
||||
#include "panels/project.h"
|
||||
#include "ui/icons.h"
|
||||
#include "projectmodel.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QtMath>
|
||||
|
||||
#include "debug.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
}
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QtMath>
|
||||
|
||||
#include "footage.h"
|
||||
#include "timeline/sequence.h"
|
||||
#include "undo/undo.h"
|
||||
#include "global/config.h"
|
||||
#include "panels/viewer.h"
|
||||
#include "panels/project.h"
|
||||
#include "ui/icons.h"
|
||||
#include "projectmodel.h"
|
||||
#include "global/debug.h"
|
||||
|
||||
QString get_interlacing_name(int interlacing) {
|
||||
switch (interlacing) {
|
||||
case VIDEO_PROGRESSIVE: return QCoreApplication::translate("InterlacingName", "None (Progressive)");
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@
|
||||
#include <QVariant>
|
||||
#include <QIcon>
|
||||
|
||||
#include "project/marker.h"
|
||||
#include "timeline/marker.h"
|
||||
#include "project/footage.h"
|
||||
|
||||
enum MediaType {
|
||||
|
||||
@@ -0,0 +1,630 @@
|
||||
/***
|
||||
|
||||
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 "previewgenerator.h"
|
||||
|
||||
#include "ui/mediaiconservice.h"
|
||||
#include "project/media.h"
|
||||
#include "project/footage.h"
|
||||
#include "panels/viewer.h"
|
||||
#include "panels/project.h"
|
||||
#include "global/config.h"
|
||||
#include "global/path.h"
|
||||
#include "global/debug.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QPixmap>
|
||||
#include <QtMath>
|
||||
#include <QTreeWidgetItem>
|
||||
#include <QSemaphore>
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
|
||||
QSemaphore sem(5); // only 5 preview generators can run at one time
|
||||
|
||||
PreviewGenerator::PreviewGenerator(Media* i) :
|
||||
QThread(nullptr)
|
||||
{
|
||||
fmt_ctx_ = (nullptr);
|
||||
media_ = (i);
|
||||
retrieve_duration_ = (false);
|
||||
contains_still_image_ = (false);
|
||||
cancelled_ = (false);
|
||||
footage_ = media_->to_footage();
|
||||
|
||||
footage_->preview_gen = this;
|
||||
|
||||
data_dir_ = QDir(get_data_dir().filePath("previews"));
|
||||
if (!data_dir_.exists()) {
|
||||
data_dir_.mkpath(".");
|
||||
}
|
||||
|
||||
connect(this, SIGNAL(finished()), this, SLOT(deleteLater()));
|
||||
|
||||
// set up throbber animation
|
||||
olive::media_icon_service->SetMediaIcon(media_, ICON_TYPE_LOADING);
|
||||
|
||||
start(QThread::LowPriority);
|
||||
}
|
||||
|
||||
void PreviewGenerator::parse_media() {
|
||||
// detect video/audio streams in file
|
||||
for (int i=0;i<int(fmt_ctx_->nb_streams);i++) {
|
||||
// Find the decoder for the video stream
|
||||
if (avcodec_find_decoder(fmt_ctx_->streams[i]->codecpar->codec_id) == nullptr) {
|
||||
qCritical() << "Unsupported codec in stream" << i << "of file" << footage_->name;
|
||||
} else {
|
||||
FootageStream ms;
|
||||
ms.preview_done = false;
|
||||
ms.file_index = i;
|
||||
ms.enabled = true;
|
||||
ms.infinite_length = false;
|
||||
|
||||
bool append = false;
|
||||
|
||||
if (fmt_ctx_->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO
|
||||
&& fmt_ctx_->streams[i]->codecpar->width > 0
|
||||
&& fmt_ctx_->streams[i]->codecpar->height > 0) {
|
||||
|
||||
// heuristic to determine if video is a still image (if it is, we treat it differently in the playback/render process)
|
||||
if (fmt_ctx_->streams[i]->avg_frame_rate.den == 0
|
||||
&& fmt_ctx_->streams[i]->codecpar->codec_id != AV_CODEC_ID_DNXHD) { // silly hack but this is the only scenario i've seen this
|
||||
if (footage_->url.contains('%')) {
|
||||
// must be an image sequence
|
||||
ms.video_frame_rate = 25;
|
||||
} else {
|
||||
ms.infinite_length = true;
|
||||
contains_still_image_ = true;
|
||||
ms.video_frame_rate = 0;
|
||||
}
|
||||
|
||||
} else {
|
||||
// using ffmpeg's built-in heuristic
|
||||
ms.video_frame_rate = av_q2d(av_guess_frame_rate(fmt_ctx_, fmt_ctx_->streams[i], nullptr));
|
||||
}
|
||||
|
||||
ms.video_width = fmt_ctx_->streams[i]->codecpar->width;
|
||||
ms.video_height = fmt_ctx_->streams[i]->codecpar->height;
|
||||
|
||||
// default value, we get the true value later in generate_waveform()
|
||||
ms.video_auto_interlacing = VIDEO_PROGRESSIVE;
|
||||
ms.video_interlacing = VIDEO_PROGRESSIVE;
|
||||
|
||||
append = true;
|
||||
} else if (fmt_ctx_->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
|
||||
ms.audio_channels = fmt_ctx_->streams[i]->codecpar->channels;
|
||||
ms.audio_layout = int(fmt_ctx_->streams[i]->codecpar->channel_layout);
|
||||
ms.audio_frequency = fmt_ctx_->streams[i]->codecpar->sample_rate;
|
||||
|
||||
append = true;
|
||||
}
|
||||
|
||||
if (append) {
|
||||
QVector<FootageStream>& stream_list = (fmt_ctx_->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) ?
|
||||
footage_->audio_tracks : footage_->video_tracks;
|
||||
|
||||
for (int j=0;j<stream_list.size();j++) {
|
||||
if (stream_list.at(j).file_index == i) {
|
||||
stream_list[j] = ms;
|
||||
append = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (append) stream_list.append(ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
footage_->length = fmt_ctx_->duration;
|
||||
|
||||
if (fmt_ctx_->duration == INT64_MIN) {
|
||||
retrieve_duration_ = true;
|
||||
} else {
|
||||
finalize_media();
|
||||
}
|
||||
}
|
||||
|
||||
bool PreviewGenerator::retrieve_preview(const QString& hash) {
|
||||
// returns true if generate_waveform must be run, false if we got all previews from cached files
|
||||
if (retrieve_duration_) {
|
||||
//dout << "[NOTE] " << media->name << "needs to retrieve duration";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool found = true;
|
||||
for (int i=0;i<footage_->video_tracks.size();i++) {
|
||||
FootageStream& ms = footage_->video_tracks[i];
|
||||
QString thumb_path = get_thumbnail_path(hash, ms);
|
||||
QFile f(thumb_path);
|
||||
if (f.exists() && ms.video_preview.load(thumb_path)) {
|
||||
//dout << "loaded thumb" << ms->file_index << "from" << thumb_path;
|
||||
ms.make_square_thumb();
|
||||
ms.preview_done = true;
|
||||
} else {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (int i=0;i<footage_->audio_tracks.size();i++) {
|
||||
FootageStream& ms = footage_->audio_tracks[i];
|
||||
QString waveform_path = get_waveform_path(hash, ms);
|
||||
QFile f(waveform_path);
|
||||
if (f.exists()) {
|
||||
//dout << "loaded wave" << ms->file_index << "from" << waveform_path;
|
||||
f.open(QFile::ReadOnly);
|
||||
QByteArray data = f.readAll();
|
||||
ms.audio_preview.resize(data.size());
|
||||
for (int j=0;j<data.size();j++) {
|
||||
// faster way?
|
||||
ms.audio_preview[j] = data.at(j);
|
||||
}
|
||||
ms.preview_done = true;
|
||||
f.close();
|
||||
} else {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
for (int i=0;i<footage_->video_tracks.size();i++) {
|
||||
FootageStream& ms = footage_->video_tracks[i];
|
||||
ms.preview_done = false;
|
||||
}
|
||||
for (int i=0;i<footage_->audio_tracks.size();i++) {
|
||||
FootageStream& ms = footage_->audio_tracks[i];
|
||||
ms.audio_preview.clear();
|
||||
ms.preview_done = false;
|
||||
}
|
||||
}
|
||||
return !found;
|
||||
}
|
||||
|
||||
void PreviewGenerator::finalize_media() {
|
||||
if (!cancelled_) {
|
||||
bool footage_is_ready = true;
|
||||
|
||||
if (footage_->video_tracks.isEmpty() && footage_->audio_tracks.isEmpty()) {
|
||||
// ERROR
|
||||
footage_is_ready = false;
|
||||
invalidate_media(tr("Failed to find any valid video/audio streams"));
|
||||
} else if (!footage_->video_tracks.isEmpty() && !contains_still_image_) {
|
||||
// VIDEO
|
||||
olive::media_icon_service->SetMediaIcon(media_, ICON_TYPE_VIDEO);
|
||||
} else if (!footage_->audio_tracks.isEmpty()) {
|
||||
// AUDIO
|
||||
olive::media_icon_service->SetMediaIcon(media_, ICON_TYPE_AUDIO);
|
||||
} else {
|
||||
// IMAGE
|
||||
olive::media_icon_service->SetMediaIcon(media_, ICON_TYPE_IMAGE);
|
||||
}
|
||||
|
||||
if (footage_is_ready) {
|
||||
footage_->ready_lock.unlock();
|
||||
footage_->ready = true;
|
||||
media_->update_tooltip();
|
||||
}
|
||||
|
||||
if (olive::ActiveSequence != nullptr) {
|
||||
olive::ActiveSequence->RefreshClips(media_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PreviewGenerator::invalidate_media(const QString &error_msg)
|
||||
{
|
||||
media_->update_tooltip(error_msg);
|
||||
olive::media_icon_service->SetMediaIcon(media_, ICON_TYPE_ERROR);
|
||||
footage_->invalid = true;
|
||||
footage_->ready_lock.unlock();
|
||||
}
|
||||
|
||||
void PreviewGenerator::generate_waveform() {
|
||||
SwsContext* sws_ctx;
|
||||
SwrContext* swr_ctx;
|
||||
AVFrame* temp_frame = av_frame_alloc();
|
||||
|
||||
// stores codec contexts for format's streams
|
||||
AVCodecContext** codec_ctx = new AVCodecContext* [fmt_ctx_->nb_streams];
|
||||
|
||||
// stores media lengths while scanning in case the format has no duration metadata
|
||||
int64_t* media_lengths = new int64_t[fmt_ctx_->nb_streams]{0};
|
||||
|
||||
// stores samples while scanning before they get sent to preview file
|
||||
qint16*** waveform_cache_data = new qint16** [fmt_ctx_->nb_streams];
|
||||
int waveform_cache_count = 0;
|
||||
|
||||
// defaults to false, sets to true if we find a valid stream to make a preview of
|
||||
bool create_previews = false;
|
||||
|
||||
for (unsigned int i=0;i<fmt_ctx_->nb_streams;i++) {
|
||||
|
||||
// default to nullptr values for easier memory management later
|
||||
codec_ctx[i] = nullptr;
|
||||
waveform_cache_data[i] = nullptr;
|
||||
|
||||
// we only generate previews for video and audio
|
||||
// and only if the thumbnail and waveform sizes are > 0
|
||||
if ((fmt_ctx_->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && olive::CurrentConfig.thumbnail_resolution > 0)
|
||||
|| (fmt_ctx_->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && olive::CurrentConfig.waveform_resolution > 0)) {
|
||||
AVCodec* codec = avcodec_find_decoder(fmt_ctx_->streams[i]->codecpar->codec_id);
|
||||
if (codec != nullptr) {
|
||||
|
||||
// alloc the context and load the params into it
|
||||
codec_ctx[i] = avcodec_alloc_context3(codec);
|
||||
avcodec_parameters_to_context(codec_ctx[i], fmt_ctx_->streams[i]->codecpar);
|
||||
|
||||
// open the decoder
|
||||
avcodec_open2(codec_ctx[i], codec, nullptr);
|
||||
|
||||
// audio specific functions
|
||||
if (fmt_ctx_->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
|
||||
|
||||
// allocate sample cache for this stream
|
||||
waveform_cache_data[i] = new qint16* [fmt_ctx_->streams[i]->codecpar->channels];
|
||||
|
||||
// each channel gets a min and a max value so we allocate two ints for each one
|
||||
for (int j=0;j<fmt_ctx_->streams[i]->codecpar->channels;j++) {
|
||||
waveform_cache_data[i][j] = new qint16[2];
|
||||
}
|
||||
|
||||
// if codec context has no defined channel layout, guess it from the channel count
|
||||
if (codec_ctx[i]->channel_layout == 0) {
|
||||
codec_ctx[i]->channel_layout = av_get_default_channel_layout(fmt_ctx_->streams[i]->codecpar->channels);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// enable next step of process
|
||||
create_previews = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (create_previews) {
|
||||
// TODO may be unnecessary - doesn't av_read_frame allocate a packet itself?
|
||||
AVPacket* packet = av_packet_alloc();
|
||||
|
||||
bool done = true;
|
||||
|
||||
bool end_of_file = false;
|
||||
|
||||
// get the ball rolling
|
||||
do {
|
||||
av_read_frame(fmt_ctx_, packet);
|
||||
} while (codec_ctx[packet->stream_index] == nullptr);
|
||||
avcodec_send_packet(codec_ctx[packet->stream_index], packet);
|
||||
|
||||
while (!end_of_file) {
|
||||
while (codec_ctx[packet->stream_index] == nullptr || avcodec_receive_frame(codec_ctx[packet->stream_index], temp_frame) == AVERROR(EAGAIN)) {
|
||||
av_packet_unref(packet);
|
||||
int read_ret = av_read_frame(fmt_ctx_, packet);
|
||||
|
||||
if (read_ret < 0) {
|
||||
end_of_file = true;
|
||||
if (read_ret != AVERROR_EOF) qCritical() << "Failed to read packet for preview generation" << read_ret;
|
||||
break;
|
||||
}
|
||||
if (codec_ctx[packet->stream_index] != nullptr) {
|
||||
int send_ret = avcodec_send_packet(codec_ctx[packet->stream_index], packet);
|
||||
if (send_ret < 0 && send_ret != AVERROR(EAGAIN)) {
|
||||
qCritical() << "Failed to send packet for preview generation - aborting" << send_ret;
|
||||
end_of_file = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!end_of_file) {
|
||||
FootageStream* s = footage_->get_stream_from_file_index(fmt_ctx_->streams[packet->stream_index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO, packet->stream_index);
|
||||
if (s != nullptr) {
|
||||
if (fmt_ctx_->streams[packet->stream_index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||
if (!s->preview_done) {
|
||||
int dstH = olive::CurrentConfig.thumbnail_resolution;
|
||||
int dstW = qRound(dstH * (float(temp_frame->width)/float(temp_frame->height)));
|
||||
|
||||
sws_ctx = sws_getContext(
|
||||
temp_frame->width,
|
||||
temp_frame->height,
|
||||
static_cast<AVPixelFormat>(temp_frame->format),
|
||||
dstW,
|
||||
dstH,
|
||||
static_cast<AVPixelFormat>(AV_PIX_FMT_RGBA),
|
||||
SWS_FAST_BILINEAR,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr
|
||||
);
|
||||
|
||||
int linesize[AV_NUM_DATA_POINTERS];
|
||||
linesize[0] = dstW*4;
|
||||
|
||||
s->video_preview = QImage(dstW, dstH, QImage::Format_RGBA8888);
|
||||
uint8_t* data = s->video_preview.bits();
|
||||
|
||||
sws_scale(sws_ctx,
|
||||
temp_frame->data,
|
||||
temp_frame->linesize,
|
||||
0,
|
||||
temp_frame->height,
|
||||
&data,
|
||||
linesize);
|
||||
|
||||
s->make_square_thumb();
|
||||
|
||||
// is video interlaced?
|
||||
s->video_auto_interlacing = (temp_frame->interlaced_frame) ? ((temp_frame->top_field_first) ? VIDEO_TOP_FIELD_FIRST : VIDEO_BOTTOM_FIELD_FIRST) : VIDEO_PROGRESSIVE;
|
||||
s->video_interlacing = s->video_auto_interlacing;
|
||||
|
||||
s->preview_done = true;
|
||||
|
||||
sws_freeContext(sws_ctx);
|
||||
|
||||
if (!retrieve_duration_) {
|
||||
avcodec_close(codec_ctx[packet->stream_index]);
|
||||
codec_ctx[packet->stream_index] = nullptr;
|
||||
}
|
||||
}
|
||||
media_lengths[packet->stream_index]++;
|
||||
} else if (fmt_ctx_->streams[packet->stream_index]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
|
||||
AVFrame* swr_frame = av_frame_alloc();
|
||||
swr_frame->channel_layout = temp_frame->channel_layout;
|
||||
swr_frame->sample_rate = temp_frame->sample_rate;
|
||||
swr_frame->format = AV_SAMPLE_FMT_S16P;
|
||||
|
||||
swr_ctx = swr_alloc_set_opts(
|
||||
nullptr,
|
||||
temp_frame->channel_layout,
|
||||
static_cast<AVSampleFormat>(swr_frame->format),
|
||||
temp_frame->sample_rate,
|
||||
temp_frame->channel_layout,
|
||||
static_cast<AVSampleFormat>(temp_frame->format),
|
||||
temp_frame->sample_rate,
|
||||
0,
|
||||
nullptr
|
||||
);
|
||||
|
||||
swr_init(swr_ctx);
|
||||
|
||||
swr_convert_frame(swr_ctx, swr_frame, temp_frame);
|
||||
|
||||
// `config.waveform_resolution` determines how many samples per second are stored in waveform.
|
||||
// `sample_rate` is samples per second, so `interval` is how many samples are averaged in
|
||||
// each "point" of the waveform
|
||||
int interval = qFloor((temp_frame->sample_rate/olive::CurrentConfig.waveform_resolution)/4)*4;
|
||||
|
||||
// get the amount of bytes in an audio sample
|
||||
int sample_size = av_get_bytes_per_sample(static_cast<AVSampleFormat>(swr_frame->format));
|
||||
|
||||
// total amount of data in this frame
|
||||
int nb_bytes = swr_frame->nb_samples * sample_size;
|
||||
|
||||
// loop through entire frame
|
||||
for (int i=0;i<nb_bytes;i+=sample_size) {
|
||||
|
||||
// check if we've hit sample threshold
|
||||
if (waveform_cache_count == interval) {
|
||||
|
||||
// if so, we dump our cached values into the preview and reset them
|
||||
// for the next interval
|
||||
for (int j=0;j<swr_frame->channels;j++) {
|
||||
qint16& min = waveform_cache_data[packet->stream_index][j][0];
|
||||
qint16& max = waveform_cache_data[packet->stream_index][j][1];
|
||||
|
||||
s->audio_preview.append(min >> 8);
|
||||
s->audio_preview.append(max >> 8);
|
||||
}
|
||||
|
||||
waveform_cache_count = 0;
|
||||
}
|
||||
|
||||
// standard processing for each channel of information
|
||||
for (int j=0;j<swr_frame->channels;j++) {
|
||||
qint16& min = waveform_cache_data[packet->stream_index][j][0];
|
||||
qint16& max = waveform_cache_data[packet->stream_index][j][1];
|
||||
|
||||
// if we're starting over, reset cache to zero
|
||||
if (waveform_cache_count == 0) {
|
||||
min = 0;
|
||||
max = 0;
|
||||
}
|
||||
|
||||
// store most minimum and most maximum samples of this interval
|
||||
qint16 sample = qint16((swr_frame->data[j][i+1] << 8) | swr_frame->data[j][i]);
|
||||
min = qMin(min, sample);
|
||||
max = qMax(max, sample);
|
||||
}
|
||||
|
||||
waveform_cache_count++;
|
||||
|
||||
if (cancelled_) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
swr_free(&swr_ctx);
|
||||
av_frame_free(&swr_frame);
|
||||
|
||||
if (cancelled_) {
|
||||
end_of_file = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if we've got all our previews
|
||||
if (retrieve_duration_) {
|
||||
done = false;
|
||||
} else if (footage_->audio_tracks.size() == 0) {
|
||||
done = true;
|
||||
for (int i=0;i<footage_->video_tracks.size();i++) {
|
||||
if (!footage_->video_tracks.at(i).preview_done) {
|
||||
done = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (done) {
|
||||
end_of_file = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
av_packet_unref(packet);
|
||||
}
|
||||
}
|
||||
|
||||
av_frame_free(&temp_frame);
|
||||
av_packet_free(&packet);
|
||||
|
||||
for (unsigned int i=0;i<fmt_ctx_->nb_streams;i++) {
|
||||
if (waveform_cache_data[i] != nullptr) {
|
||||
for (int j=0;j<codec_ctx[i]->channels;j++) {
|
||||
delete [] waveform_cache_data[i][j];
|
||||
}
|
||||
delete [] waveform_cache_data[i];
|
||||
}
|
||||
|
||||
if (codec_ctx[i] != nullptr) {
|
||||
avcodec_close(codec_ctx[i]);
|
||||
avcodec_free_context(&codec_ctx[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// by this point, we'll have made all audio waveform previews
|
||||
for (int i=0;i<footage_->audio_tracks.size();i++) {
|
||||
footage_->audio_tracks[i].preview_done = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (retrieve_duration_) {
|
||||
footage_->length = 0;
|
||||
unsigned int maximum_stream = 0;
|
||||
for (unsigned int i=0;i<fmt_ctx_->nb_streams;i++) {
|
||||
if (media_lengths[i] > media_lengths[maximum_stream]) {
|
||||
maximum_stream = i;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: length is currently retrieved as a frame count rather than a timestamp
|
||||
footage_->length = qRound(double(media_lengths[maximum_stream]) / av_q2d(fmt_ctx_->streams[maximum_stream]->avg_frame_rate) * AV_TIME_BASE);
|
||||
|
||||
finalize_media();
|
||||
}
|
||||
|
||||
delete [] waveform_cache_data;
|
||||
delete [] media_lengths;
|
||||
delete [] codec_ctx;
|
||||
}
|
||||
|
||||
QString PreviewGenerator::get_thumbnail_path(const QString& hash, const FootageStream& ms) {
|
||||
return data_dir_.filePath(QString("%1t%2").arg(hash, QString::number(ms.file_index)));
|
||||
}
|
||||
|
||||
QString PreviewGenerator::get_waveform_path(const QString& hash, const FootageStream& ms) {
|
||||
return data_dir_.filePath(QString("%1w%2").arg(hash, QString::number(ms.file_index)));
|
||||
}
|
||||
|
||||
void PreviewGenerator::run() {
|
||||
Q_ASSERT(footage_ != nullptr);
|
||||
Q_ASSERT(media_ != nullptr);
|
||||
|
||||
const QString url = footage_->url;
|
||||
QByteArray ba = url.toUtf8();
|
||||
char* filename = new char[ba.size()+1];
|
||||
strcpy(filename, ba.data());
|
||||
|
||||
QString errorStr;
|
||||
bool error = false;
|
||||
|
||||
AVDictionary* format_opts = nullptr;
|
||||
|
||||
// for image sequences that don't start at 0, set the index where it does start
|
||||
if (footage_->start_number > 0) {
|
||||
av_dict_set(&format_opts, "start_number", QString::number(footage_->start_number).toUtf8(), 0);
|
||||
}
|
||||
|
||||
int errCode = avformat_open_input(&fmt_ctx_, filename, nullptr, &format_opts);
|
||||
if(errCode != 0) {
|
||||
char err[1024];
|
||||
av_strerror(errCode, err, 1024);
|
||||
errorStr = tr("Could not open file - %1").arg(err);
|
||||
error = true;
|
||||
} else {
|
||||
errCode = avformat_find_stream_info(fmt_ctx_, nullptr);
|
||||
if (errCode < 0) {
|
||||
char err[1024];
|
||||
av_strerror(errCode, err, 1024);
|
||||
errorStr = tr("Could not find stream information - %1").arg(err);
|
||||
error = true;
|
||||
} else {
|
||||
av_dump_format(fmt_ctx_, 0, filename, 0);
|
||||
parse_media();
|
||||
|
||||
// see if we already have data for this
|
||||
QString hash = get_file_hash(footage_->url);
|
||||
|
||||
if (retrieve_preview(hash)) {
|
||||
sem.acquire();
|
||||
|
||||
if (!cancelled_) {
|
||||
generate_waveform();
|
||||
|
||||
if (!cancelled_) {
|
||||
// save preview to file
|
||||
for (int i=0;i<footage_->video_tracks.size();i++) {
|
||||
FootageStream& ms = footage_->video_tracks[i];
|
||||
ms.video_preview.save(get_thumbnail_path(hash, ms), "PNG");
|
||||
//dout << "saved" << ms->file_index << "thumbnail to" << get_thumbnail_path(hash, ms);
|
||||
}
|
||||
for (int i=0;i<footage_->audio_tracks.size();i++) {
|
||||
FootageStream& ms = footage_->audio_tracks[i];
|
||||
QFile f(get_waveform_path(hash, ms));
|
||||
f.open(QFile::WriteOnly);
|
||||
f.write(ms.audio_preview.constData(), ms.audio_preview.size());
|
||||
f.close();
|
||||
//dout << "saved" << ms->file_index << "waveform to" << get_waveform_path(hash, ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sem.release();
|
||||
}
|
||||
}
|
||||
avformat_close_input(&fmt_ctx_);
|
||||
}
|
||||
|
||||
if (!cancelled_) {
|
||||
if (error) {
|
||||
invalidate_media(errorStr);
|
||||
}
|
||||
}
|
||||
|
||||
delete [] filename;
|
||||
footage_->preview_gen = nullptr;
|
||||
}
|
||||
|
||||
void PreviewGenerator::cancel() {
|
||||
cancelled_ = true;
|
||||
wait();
|
||||
}
|
||||
|
||||
void PreviewGenerator::AnalyzeMedia(Media *m)
|
||||
{
|
||||
// PreviewGenerator's constructor starts the thread, sets a reference of itself as the media's generator,
|
||||
// and connects its thread completion to its own deletion, therefore handling its own memory. Nothing else needs to
|
||||
// be done.
|
||||
new PreviewGenerator(m);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef PREVIEWGENERATOR_H
|
||||
#define PREVIEWGENERATOR_H
|
||||
|
||||
#include <QThread>
|
||||
#include <QSemaphore>
|
||||
#include <QDir>
|
||||
|
||||
#include "project/footage.h"
|
||||
#include "project/media.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libswscale/swscale.h>
|
||||
#include <libswresample/swresample.h>
|
||||
}
|
||||
|
||||
class PreviewGenerator : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
PreviewGenerator(Media*);
|
||||
void run();
|
||||
void cancel();
|
||||
|
||||
static void AnalyzeMedia(Media*);
|
||||
private:
|
||||
void parse_media();
|
||||
bool retrieve_preview(const QString &hash);
|
||||
void generate_waveform();
|
||||
void finalize_media();
|
||||
void invalidate_media(const QString& error_msg);
|
||||
QString get_thumbnail_path(const QString &hash, const FootageStream &ms);
|
||||
QString get_waveform_path(const QString& hash, const FootageStream &ms);
|
||||
|
||||
AVFormatContext* fmt_ctx_;
|
||||
Media* media_;
|
||||
Footage* footage_;
|
||||
bool retrieve_duration_;
|
||||
bool contains_still_image_;
|
||||
bool cancelled_;
|
||||
QDir data_dir_;
|
||||
};
|
||||
|
||||
#endif // PREVIEWGENERATOR_H
|
||||
@@ -30,12 +30,5 @@
|
||||
// includes elements the user can use in a project
|
||||
#include "media.h"
|
||||
#include "footage.h"
|
||||
#include "sequence.h"
|
||||
|
||||
// includes elements the user can use in a sequence
|
||||
#include "clip.h"
|
||||
#include "transition.h"
|
||||
#include "marker.h"
|
||||
#include "effect.h"
|
||||
|
||||
#endif // PROJECTELEMENTS_H
|
||||
|
||||
+11
-11
@@ -21,21 +21,21 @@
|
||||
#include "projectfilter.h"
|
||||
|
||||
#include "project/media.h"
|
||||
#include "project/sequence.h"
|
||||
#include "timeline/sequence.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
ProjectFilter::ProjectFilter(QObject *parent) :
|
||||
QSortFilterProxyModel(parent),
|
||||
show_sequences(true)
|
||||
QSortFilterProxyModel(parent),
|
||||
show_sequences(true)
|
||||
{}
|
||||
|
||||
bool ProjectFilter::get_show_sequences() {
|
||||
return show_sequences;
|
||||
return show_sequences;
|
||||
}
|
||||
|
||||
void ProjectFilter::set_show_sequences(bool b) {
|
||||
show_sequences = b;
|
||||
show_sequences = b;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
@@ -50,11 +50,11 @@ bool ProjectFilter::filterAcceptsRow(int source_row, const QModelIndex &source_p
|
||||
Media* media = static_cast<Media*>(index.internalPointer());
|
||||
|
||||
// hide sequences if show_sequences is false
|
||||
if (!show_sequences) {
|
||||
if (media != nullptr && media->get_type() == MEDIA_TYPE_SEQUENCE) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!show_sequences) {
|
||||
if (media != nullptr && media->get_type() == MEDIA_TYPE_SEQUENCE) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// filter by search filter string
|
||||
if (!search_filter.isEmpty()) {
|
||||
@@ -80,5 +80,5 @@ bool ProjectFilter::filterAcceptsRow(int source_row, const QModelIndex &source_p
|
||||
}
|
||||
}
|
||||
|
||||
return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
|
||||
return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#include "panels/viewer.h"
|
||||
#include "ui/viewerwidget.h"
|
||||
#include "project/media.h"
|
||||
#include "debug.h"
|
||||
#include "global/debug.h"
|
||||
|
||||
ProjectModel olive::project_model;
|
||||
|
||||
|
||||
@@ -0,0 +1,420 @@
|
||||
/***
|
||||
|
||||
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 "proxygenerator.h"
|
||||
|
||||
#include "global/path.h"
|
||||
#include "project/previewgenerator.h"
|
||||
#include "ui/mainwindow.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QtMath>
|
||||
#include <QStatusBar>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
extern "C" {
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libswscale/swscale.h>
|
||||
}
|
||||
|
||||
enum AVCodecID temp_enc_codec = AV_CODEC_ID_PRORES;
|
||||
|
||||
ProxyGenerator::ProxyGenerator() : cancelled(false) {}
|
||||
|
||||
void ProxyGenerator::transcode(const ProxyInfo& info) {
|
||||
Footage* footage = info.media->to_footage();
|
||||
|
||||
// set progress to 0
|
||||
current_progress = 0.0;
|
||||
|
||||
// for image sequences that don't start at 0, set the index where it does start
|
||||
AVDictionary* format_opts = nullptr;
|
||||
if (footage->start_number > 0) {
|
||||
av_dict_set(&format_opts, "start_number", QString::number(footage->start_number).toUtf8(), 0);
|
||||
}
|
||||
|
||||
// open input file
|
||||
AVFormatContext* input_fmt_ctx = nullptr;
|
||||
avformat_open_input(&input_fmt_ctx, footage->url.toUtf8(), nullptr, &format_opts);
|
||||
|
||||
// open output file
|
||||
AVFormatContext* output_fmt_ctx = nullptr;
|
||||
avformat_alloc_output_context2(&output_fmt_ctx, nullptr, nullptr, info.path.toUtf8());
|
||||
|
||||
// open output file writing handle
|
||||
avio_open(&output_fmt_ctx->pb, info.path.toUtf8(), AVIO_FLAG_WRITE);
|
||||
|
||||
// get stream info from input file
|
||||
avformat_find_stream_info(input_fmt_ctx, nullptr);
|
||||
|
||||
// create array of input decoders
|
||||
QVector<AVCodecContext*> input_streams;
|
||||
input_streams.resize(input_fmt_ctx->nb_streams);
|
||||
input_streams.fill(nullptr);
|
||||
|
||||
// create array of output encoders
|
||||
QVector<AVCodecContext*> output_streams;
|
||||
output_streams.resize(input_fmt_ctx->nb_streams);
|
||||
output_streams.fill(nullptr);
|
||||
|
||||
// create array of swscale contexts for pixel format conversion
|
||||
QVector<SwsContext*> sws_contexts;
|
||||
sws_contexts.resize(input_fmt_ctx->nb_streams);
|
||||
sws_contexts.fill(nullptr);
|
||||
|
||||
// loop through file to find compatible video streams
|
||||
for (int i=0;i<int(input_fmt_ctx->nb_streams);i++) {
|
||||
AVStream* in_stream = input_fmt_ctx->streams[i];
|
||||
|
||||
// create new stream in output
|
||||
AVStream* out_stream = avformat_new_stream(output_fmt_ctx, nullptr);
|
||||
out_stream->id = in_stream->id;
|
||||
|
||||
// find decoder for this codec
|
||||
AVCodec* dec_codec = avcodec_find_decoder(in_stream->codecpar->codec_id);
|
||||
|
||||
// find encoder for chosen proxy type
|
||||
AVCodec* enc_codec = avcodec_find_encoder(temp_enc_codec);
|
||||
|
||||
// we only transcode video streams, others we just passthrough
|
||||
if (in_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && dec_codec != nullptr) {
|
||||
|
||||
// allocate decoding context for this stream
|
||||
AVCodecContext* dec_ctx = avcodec_alloc_context3(dec_codec);
|
||||
|
||||
// copy parameters from stream to decoding context
|
||||
avcodec_parameters_to_context(dec_ctx, in_stream->codecpar);
|
||||
|
||||
// open decoder
|
||||
avcodec_open2(dec_ctx, dec_codec, nullptr);
|
||||
|
||||
// store decoding context in array
|
||||
input_streams[i] = dec_ctx;
|
||||
|
||||
// retrieve more information about this stream
|
||||
av_dump_format(input_fmt_ctx, i, footage->url.toUtf8(), 0);
|
||||
|
||||
// allocate encoding context for this stream
|
||||
AVCodecContext* enc_ctx = avcodec_alloc_context3(enc_codec);
|
||||
|
||||
// copy properties from decoding context to encoding context
|
||||
enc_ctx->codec_id = temp_enc_codec;
|
||||
enc_ctx->codec_type = AVMEDIA_TYPE_VIDEO;
|
||||
enc_ctx->width = qFloor(dec_ctx->width*info.size_multiplier);
|
||||
enc_ctx->height = qFloor(dec_ctx->height*info.size_multiplier);
|
||||
enc_ctx->sample_aspect_ratio = dec_ctx->sample_aspect_ratio;
|
||||
enc_ctx->pix_fmt = enc_codec->pix_fmts[0];
|
||||
enc_ctx->framerate = dec_ctx->framerate;
|
||||
enc_ctx->time_base = in_stream->time_base;
|
||||
out_stream->time_base = in_stream->time_base;
|
||||
|
||||
// if format uses global headers, add flag to enc_ctx
|
||||
if (output_fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) {
|
||||
enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
|
||||
}
|
||||
|
||||
// set encoder options (mostly just multithreading)
|
||||
AVDictionary* opts = nullptr;
|
||||
av_dict_set(&opts, "threads", "auto", 0);
|
||||
|
||||
// open encoder
|
||||
avcodec_open2(enc_ctx, enc_codec, &opts);
|
||||
|
||||
// copy parameters from encoding context to stream
|
||||
avcodec_parameters_from_context(out_stream->codecpar, enc_ctx);
|
||||
|
||||
// store encoding context in array
|
||||
output_streams[i] = enc_ctx;
|
||||
|
||||
// create swscontext for this stream
|
||||
SwsContext* sws_ctx = sws_getContext(
|
||||
in_stream->codecpar->width,
|
||||
in_stream->codecpar->height,
|
||||
static_cast<enum AVPixelFormat>(in_stream->codecpar->format),
|
||||
enc_ctx->width,
|
||||
enc_ctx->height,
|
||||
enc_ctx->pix_fmt,
|
||||
0,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr
|
||||
);
|
||||
|
||||
sws_contexts[i] = sws_ctx;
|
||||
} else {
|
||||
avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar);
|
||||
}
|
||||
}
|
||||
|
||||
// write video header
|
||||
avformat_write_header(output_fmt_ctx, nullptr);
|
||||
|
||||
// packet that av_read_frame will dump file packets into
|
||||
AVPacket packet;
|
||||
av_init_packet(&packet);
|
||||
|
||||
// frame that decoder will decode into
|
||||
AVFrame* dec_frame = av_frame_alloc();
|
||||
|
||||
// main transcoding loop
|
||||
while (!skip) {
|
||||
|
||||
// cache stream index
|
||||
int stream_index = packet.stream_index;
|
||||
|
||||
// retrieve frame from decoder (this will clear the last frame so we don't have to do that)
|
||||
int read_ret = -1;
|
||||
int recfr_ret = -1;
|
||||
do {
|
||||
// read from input file
|
||||
read_ret = av_read_frame(input_fmt_ctx, &packet);
|
||||
|
||||
// handle errors
|
||||
if (read_ret < 0) {
|
||||
|
||||
// AVERROR_EOF means we've simply reached the end of the file, otherwise this is an error
|
||||
if (read_ret != AVERROR_EOF) {
|
||||
qWarning() << "Proxy generation for file" << footage->url << "ended prematurely";
|
||||
}
|
||||
|
||||
// either way, we shall abort reading
|
||||
break;
|
||||
}
|
||||
|
||||
stream_index = packet.stream_index;
|
||||
|
||||
// determine whether this frame is from a stream we're transcoding
|
||||
if (input_streams.at(stream_index) == nullptr) {
|
||||
// if we didn't allocate a decoder for this earlier, we just pass it through
|
||||
|
||||
av_packet_rescale_ts(&packet, input_fmt_ctx->streams[stream_index]->time_base, output_fmt_ctx->streams[stream_index]->time_base);
|
||||
|
||||
// write packet to output
|
||||
av_interleaved_write_frame(output_fmt_ctx, &packet);
|
||||
|
||||
} else {
|
||||
// we're going to transcode this packet.
|
||||
|
||||
// send packet to decoder
|
||||
avcodec_send_packet(input_streams.at(stream_index), &packet);
|
||||
|
||||
// use timestamp and stream duration to create a rough estimation of the progress through this file
|
||||
current_progress = qCeil((double(packet.pts)/double(input_fmt_ctx->streams[packet.stream_index]->duration))*100);
|
||||
|
||||
}
|
||||
|
||||
// free packet allocated by av_read_frame
|
||||
av_packet_unref(&packet);
|
||||
} while ((recfr_ret = avcodec_receive_frame(input_streams.at(packet.stream_index), dec_frame)) == AVERROR(EAGAIN) && !skip);
|
||||
|
||||
// error/eof handling - cancel while loop
|
||||
if (read_ret < 0 || skip) {
|
||||
break;
|
||||
}
|
||||
|
||||
// free packet as we're about to use it for encoding
|
||||
av_packet_unref(&packet);
|
||||
|
||||
// rescale input frame timestamp to output timestamp
|
||||
dec_frame->pts = av_rescale_q(dec_frame->pts,
|
||||
input_fmt_ctx->streams[stream_index]->time_base,
|
||||
output_fmt_ctx->streams[stream_index]->time_base);
|
||||
|
||||
// determine if the pix_fmt, width, and/or height is different, so if we need to convert
|
||||
bool convert_pix_fmt = (output_streams.at(stream_index)->pix_fmt != input_streams.at(stream_index)->pix_fmt
|
||||
|| output_streams.at(stream_index)->width != input_streams.at(stream_index)->width
|
||||
|| output_streams.at(stream_index)->height != input_streams.at(stream_index)->height);
|
||||
|
||||
// create reference to the frame to be sent to the encoder
|
||||
AVFrame* enc_frame = dec_frame;
|
||||
|
||||
if (convert_pix_fmt) {
|
||||
// create sws frame for pixel format conversion
|
||||
enc_frame = av_frame_alloc();
|
||||
enc_frame->width = output_streams.at(stream_index)->width;
|
||||
enc_frame->height = output_streams.at(stream_index)->height;
|
||||
enc_frame->format = output_streams.at(stream_index)->pix_fmt;
|
||||
av_frame_get_buffer(enc_frame, 0);
|
||||
|
||||
// convert pixel format to format expected by the encoder
|
||||
sws_scale(sws_contexts.at(stream_index), dec_frame->data, dec_frame->linesize, 0, dec_frame->height, enc_frame->data, enc_frame->linesize);
|
||||
|
||||
// set same pts as dec_frame
|
||||
enc_frame->pts = dec_frame->pts;
|
||||
}
|
||||
|
||||
// send frame to encoder
|
||||
avcodec_send_frame(output_streams.at(stream_index), enc_frame);
|
||||
|
||||
if (convert_pix_fmt) {
|
||||
// free sws frame since we made one before
|
||||
av_frame_free(&enc_frame);
|
||||
}
|
||||
|
||||
// return value for packet receiving
|
||||
int recret;
|
||||
|
||||
// loop through receiving packets
|
||||
while ((recret = avcodec_receive_packet(output_streams.at(stream_index), &packet)) >= 0 && !skip) {
|
||||
|
||||
// set packet stream index to current stream index
|
||||
packet.stream_index = stream_index;
|
||||
|
||||
// write frame to file
|
||||
av_interleaved_write_frame(output_fmt_ctx, &packet);
|
||||
|
||||
// unref old packet
|
||||
av_packet_unref(&packet);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// free dec_frame
|
||||
av_frame_free(&dec_frame);
|
||||
|
||||
// write video trailer
|
||||
av_write_trailer(output_fmt_ctx);
|
||||
|
||||
// free stream contexts
|
||||
for (int i=0;i<int(input_fmt_ctx->nb_streams);i++) {
|
||||
if (input_streams[i] != nullptr) {
|
||||
// free swscale contexts
|
||||
sws_freeContext(sws_contexts[i]);
|
||||
|
||||
// free input decoding context
|
||||
avcodec_close(input_streams[i]);
|
||||
avcodec_free_context(&input_streams[i]);
|
||||
|
||||
// free output encoding context
|
||||
avcodec_close(output_streams[i]);
|
||||
avcodec_free_context(&output_streams[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// close output file handle
|
||||
avio_closep(&output_fmt_ctx->pb);
|
||||
|
||||
// close output file
|
||||
avformat_free_context(output_fmt_ctx);
|
||||
|
||||
// close input file
|
||||
avformat_close_input(&input_fmt_ctx);
|
||||
|
||||
// set footage to use newly generated proxy
|
||||
footage->proxy = true;
|
||||
footage->proxy_path = info.path;
|
||||
|
||||
qInfo() << "Finished creating proxy for" << footage->url;
|
||||
QMetaObject::invokeMethod(olive::MainWindow->statusBar(),
|
||||
"showMessage",
|
||||
Qt::QueuedConnection,
|
||||
Q_ARG(QString, tr("Finished generating proxy for \"%1\"").arg(footage->url)));
|
||||
}
|
||||
|
||||
// main proxy generating loop
|
||||
void ProxyGenerator::run() {
|
||||
// mutex used for thread safe signalling
|
||||
mutex.lock();
|
||||
|
||||
while (!cancelled) {
|
||||
// wait for queue() to be called
|
||||
waitCond.wait(&mutex);
|
||||
|
||||
// quit thread if cancel() was called
|
||||
if (cancelled) break;
|
||||
|
||||
// loop through queue until the queue is empty
|
||||
while (proxy_queue.size() > 0) {
|
||||
|
||||
// grab proxy info
|
||||
const ProxyInfo& info = proxy_queue.first();
|
||||
|
||||
// create directory for info
|
||||
QFileInfo(info.path).dir().mkpath(".");
|
||||
|
||||
// set skip to false
|
||||
skip = false;
|
||||
|
||||
// transcode proxy
|
||||
transcode(info);
|
||||
|
||||
// we're finished with this proxy, remove it
|
||||
proxy_queue.removeFirst();
|
||||
|
||||
// quit loop if cancel() was called
|
||||
if (cancelled) break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
// called to add footage to generate proxies for
|
||||
void ProxyGenerator::queue(const ProxyInfo &info) {
|
||||
// remove any queued proxies with the same footage
|
||||
if (!proxy_queue.isEmpty()
|
||||
&& proxy_queue.first().media == info.media) {
|
||||
// if the thread is currently processing a proxy with the same footage, abort it
|
||||
skip = true;
|
||||
}
|
||||
|
||||
// scan through the rest of the queue for another proxy with the same footage (start with 1 since we already processed first())
|
||||
for (int i=1;i<proxy_queue.size();i++) {
|
||||
if (proxy_queue.at(i).media == info.media) {
|
||||
// found a duplicate, assume the one we're queuing now overrides and delete it
|
||||
proxy_queue.removeAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
// add proxy info to queue
|
||||
proxy_queue.append(info);
|
||||
|
||||
// wake proxy thread loop if sleeping
|
||||
waitCond.wakeAll();
|
||||
}
|
||||
|
||||
// to be called from another thread to terminate the proxy generator thread and free it
|
||||
void ProxyGenerator::cancel() {
|
||||
// signal to thread to cancel
|
||||
cancelled = true;
|
||||
skip = true;
|
||||
|
||||
// if signal is sleeping, wake it to cancel correctly
|
||||
waitCond.wakeAll();
|
||||
|
||||
// wait for thread to finish
|
||||
wait();
|
||||
}
|
||||
|
||||
double ProxyGenerator::get_proxy_progress(Media* m) {
|
||||
if (proxy_queue.first().media == m) {
|
||||
return current_progress;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// proxy generator is a global omnipotent entity
|
||||
ProxyGenerator olive::proxy_generator;
|
||||
@@ -0,0 +1,72 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef PROXYGENERATOR_H
|
||||
#define PROXYGENERATOR_H
|
||||
|
||||
#include <QThread>
|
||||
#include <QVector>
|
||||
#include <QMutex>
|
||||
#include <QWaitCondition>
|
||||
|
||||
#include "project/media.h"
|
||||
|
||||
struct ProxyInfo {
|
||||
Media* media;
|
||||
double size_multiplier;
|
||||
int codec_type;
|
||||
QString path;
|
||||
};
|
||||
|
||||
class ProxyGenerator : public QThread {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ProxyGenerator();
|
||||
void run();
|
||||
void queue(const ProxyInfo& info);
|
||||
void cancel();
|
||||
double get_proxy_progress(Media *f);
|
||||
private:
|
||||
// queue of footage to process proxies for
|
||||
QVector<ProxyInfo> proxy_queue;
|
||||
|
||||
// threading objects
|
||||
QWaitCondition waitCond;
|
||||
QMutex mutex;
|
||||
|
||||
// set to true if you want to permanently close ProxyGenerator
|
||||
bool cancelled;
|
||||
|
||||
// set to true if you want to abort the footage currently being processed
|
||||
bool skip;
|
||||
|
||||
// stores progress in percent of proxy currently being processed
|
||||
double current_progress;
|
||||
|
||||
// function that performs the actual transcode
|
||||
void transcode(const ProxyInfo& info);
|
||||
};
|
||||
|
||||
namespace olive {
|
||||
// proxy generator is a global omnipotent entity
|
||||
extern ProxyGenerator proxy_generator;
|
||||
}
|
||||
|
||||
#endif // PROXYGENERATOR_H
|
||||
@@ -1,195 +0,0 @@
|
||||
/***
|
||||
|
||||
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 "sequence.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include "panels/panels.h"
|
||||
|
||||
#include "debug.h"
|
||||
|
||||
Sequence::Sequence() {
|
||||
playhead = 0;
|
||||
using_workarea = false;
|
||||
workarea_in = 0;
|
||||
workarea_out = 0;
|
||||
wrapper_sequence = false;
|
||||
}
|
||||
|
||||
Sequence::~Sequence() {}
|
||||
|
||||
SequencePtr Sequence::copy() {
|
||||
SequencePtr s = std::make_shared<Sequence>();
|
||||
s->name = QCoreApplication::translate("Sequence", "%1 (copy)").arg(name);
|
||||
s->width = width;
|
||||
s->height = height;
|
||||
s->frame_rate = frame_rate;
|
||||
s->audio_frequency = audio_frequency;
|
||||
s->audio_layout = audio_layout;
|
||||
|
||||
// deep copy all of the sequence's clips
|
||||
s->clips.resize(clips.size());
|
||||
for (int i=0;i<clips.size();i++) {
|
||||
ClipPtr c = clips.at(i);
|
||||
if (c == nullptr) {
|
||||
s->clips[i] = nullptr;
|
||||
} else {
|
||||
ClipPtr copy = c->copy(s.get());
|
||||
copy->linked = c->linked;
|
||||
s->clips[i] = copy;
|
||||
}
|
||||
}
|
||||
|
||||
// copy all of the sequence's markers
|
||||
s->markers = markers;
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
long Sequence::getEndFrame() {
|
||||
long end = 0;
|
||||
for (int j=0;j<clips.size();j++) {
|
||||
ClipPtr c = clips.at(j);
|
||||
if (c != nullptr && c->timeline_out() > end) {
|
||||
end = c->timeline_out();
|
||||
}
|
||||
}
|
||||
return end;
|
||||
}
|
||||
|
||||
void Sequence::RefreshClips(Media *m) {
|
||||
for (int i=0;i<clips.size();i++) {
|
||||
ClipPtr c = clips.at(i);
|
||||
|
||||
if (c != nullptr
|
||||
&& (m == nullptr || c->media() == m)) {
|
||||
c->Close(true);
|
||||
c->refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QVector<Clip *> Sequence::SelectedClips()
|
||||
{
|
||||
QVector<Clip*> selected_clips;
|
||||
|
||||
for (int i=0;i<clips.size();i++) {
|
||||
Clip* c = clips.at(i).get();
|
||||
if (c != nullptr && IsClipSelected(c, true)) {
|
||||
selected_clips.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
return selected_clips;
|
||||
}
|
||||
|
||||
QVector<int> Sequence::SelectedClipIndexes()
|
||||
{
|
||||
QVector<int> selected_clips;
|
||||
|
||||
for (int i=0;i<clips.size();i++) {
|
||||
Clip* c = clips.at(i).get();
|
||||
if (c != nullptr && IsClipSelected(c, true)) {
|
||||
selected_clips.append(i);
|
||||
}
|
||||
}
|
||||
|
||||
return selected_clips;
|
||||
}
|
||||
|
||||
Effect *Sequence::GetSelectedGizmo()
|
||||
{
|
||||
Effect* gizmo_ptr = nullptr;
|
||||
|
||||
for (int i=0;i<clips.size();i++) {
|
||||
Clip* c = clips.at(i).get();
|
||||
if (c != nullptr
|
||||
&& c->IsActiveAt(playhead)
|
||||
&& IsClipSelected(c, true)) {
|
||||
// This clip is selected and currently active - we'll use this for gizmos
|
||||
|
||||
if (!c->effects.isEmpty()) {
|
||||
|
||||
// find which effect has gizmos selected, or default to the first gizmo effect we find if there is
|
||||
// none selected
|
||||
|
||||
for (int j=0;j<c->effects.size();j++) {
|
||||
Effect* e = c->effects.at(j).get();
|
||||
|
||||
// retrieve gizmo data from effect
|
||||
if (e->are_gizmos_enabled()) {
|
||||
if (gizmo_ptr == nullptr) {
|
||||
gizmo_ptr = e;
|
||||
}
|
||||
if (panel_effect_controls->IsEffectSelected(e)) {
|
||||
gizmo_ptr = e;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (gizmo_ptr != nullptr) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return gizmo_ptr;
|
||||
}
|
||||
|
||||
bool Sequence::IsClipSelected(int clip_index, bool containing)
|
||||
{
|
||||
return IsClipSelected(clips.at(clip_index).get(), containing);
|
||||
}
|
||||
|
||||
bool Sequence::IsClipSelected(Clip *clip, bool containing)
|
||||
{
|
||||
for (int i=0;i<selections.size();i++) {
|
||||
const Selection& s = selections.at(i);
|
||||
if (clip->track() == s.track && ((clip->timeline_in() >= s.in && clip->timeline_out() <= s.out && containing)
|
||||
|| (!containing && !(clip->timeline_in() < s.in && clip->timeline_out() < s.in)
|
||||
&& !(clip->timeline_in() > s.in && clip->timeline_out() > s.in)))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Sequence::getTrackLimits(int* video_tracks, int* audio_tracks) {
|
||||
int vt = 0;
|
||||
int at = 0;
|
||||
for (int j=0;j<clips.size();j++) {
|
||||
ClipPtr c = clips.at(j);
|
||||
if (c != nullptr) {
|
||||
if (c->track() < 0 && c->track() < vt) { // video clip
|
||||
vt = c->track();
|
||||
} else if (c->track() > at) {
|
||||
at = c->track();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (video_tracks != nullptr) *video_tracks = vt;
|
||||
if (audio_tracks != nullptr) *audio_tracks = at;
|
||||
}
|
||||
|
||||
// static variable for the currently active sequence
|
||||
SequencePtr olive::ActiveSequence = nullptr;
|
||||
@@ -1,77 +0,0 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef SEQUENCE_H
|
||||
#define SEQUENCE_H
|
||||
|
||||
#include <memory>
|
||||
#include <QVector>
|
||||
|
||||
#include "project/clip.h"
|
||||
#include "project/marker.h"
|
||||
#include "project/transition.h"
|
||||
#include "project/selection.h"
|
||||
|
||||
class Sequence {
|
||||
public:
|
||||
Sequence();
|
||||
~Sequence();
|
||||
SequencePtr copy();
|
||||
QString name;
|
||||
void getTrackLimits(int* video_tracks, int* audio_tracks);
|
||||
long getEndFrame();
|
||||
int width;
|
||||
int height;
|
||||
double frame_rate;
|
||||
int audio_frequency;
|
||||
int audio_layout;
|
||||
|
||||
void RefreshClips(Media* m = nullptr);
|
||||
QVector<Clip*> SelectedClips();
|
||||
QVector<int> SelectedClipIndexes();
|
||||
|
||||
Effect* GetSelectedGizmo();
|
||||
|
||||
bool IsClipSelected(int clip_index, bool containing);
|
||||
bool IsClipSelected(Clip* clip, bool containing);
|
||||
|
||||
QVector<Selection> selections;
|
||||
long playhead;
|
||||
|
||||
bool using_workarea;
|
||||
long workarea_in;
|
||||
long workarea_out;
|
||||
|
||||
bool wrapper_sequence;
|
||||
|
||||
int save_id;
|
||||
|
||||
QVector<Marker> markers;
|
||||
QVector<ClipPtr> clips;
|
||||
};
|
||||
|
||||
using SequencePtr = std::shared_ptr<Sequence>;
|
||||
|
||||
// static variable for the currently active sequence
|
||||
namespace olive {
|
||||
extern SequencePtr ActiveSequence;
|
||||
}
|
||||
|
||||
#endif // SEQUENCE_H
|
||||
@@ -23,19 +23,19 @@
|
||||
#include "ui/menuhelper.h"
|
||||
#include "panels/panels.h"
|
||||
#include "project/media.h"
|
||||
#include "project/undo.h"
|
||||
#include "undo/undo.h"
|
||||
#include "rendering/renderfunctions.h"
|
||||
#include "panels/timeline.h"
|
||||
#include "panels/project.h"
|
||||
#include "project/footage.h"
|
||||
#include "panels/viewer.h"
|
||||
#include "project/projectfilter.h"
|
||||
#include "project/sequence.h"
|
||||
#include "io/config.h"
|
||||
#include "timeline/sequence.h"
|
||||
#include "global/config.h"
|
||||
#include "dialogs/proxydialog.h"
|
||||
#include "ui/viewerwidget.h"
|
||||
#include "io/proxygenerator.h"
|
||||
#include "mainwindow.h"
|
||||
#include "project/proxygenerator.h"
|
||||
#include "ui/mainwindow.h"
|
||||
|
||||
#include <QProcess>
|
||||
#include <QMenu>
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
/***
|
||||
|
||||
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 "transition.h"
|
||||
|
||||
#include "mainwindow.h"
|
||||
#include "clip.h"
|
||||
#include "sequence.h"
|
||||
#include "debug.h"
|
||||
|
||||
#include "io/clipboard.h"
|
||||
|
||||
#include "effects/internal/crossdissolvetransition.h"
|
||||
#include "effects/internal/linearfadetransition.h"
|
||||
#include "effects/internal/exponentialfadetransition.h"
|
||||
#include "effects/internal/logarithmicfadetransition.h"
|
||||
#include "effects/internal/cubetransition.h"
|
||||
|
||||
#include "ui/labelslider.h"
|
||||
|
||||
#include "panels/panels.h"
|
||||
#include "panels/timeline.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QCoreApplication>
|
||||
|
||||
Transition::Transition(Clip *c, Clip *s, const EffectMeta* em) :
|
||||
Effect(c, em),
|
||||
secondary_clip(s)
|
||||
{
|
||||
EffectRow* length_row = new EffectRow(this, tr("Length"), false, false);
|
||||
length_field = new DoubleField(length_row, "length");
|
||||
length_field->SetDefault(30);
|
||||
length_field->SetMinimum(0);
|
||||
length_field->SetDisplayType(LabelSlider::LABELSLIDER_FRAMENUMBER);
|
||||
length_field->SetFrameRate(parent_clip->sequence == nullptr ?
|
||||
parent_clip->cached_frame_rate() : parent_clip->sequence->frame_rate);
|
||||
connect(length_field, SIGNAL(changed()), this, SLOT(set_length_from_slider()));
|
||||
}
|
||||
|
||||
TransitionPtr Transition::copy(Clip *c, Clip *s) {
|
||||
return Transition::Create(c, s, meta, get_true_length());
|
||||
}
|
||||
|
||||
void Transition::save(QXmlStreamWriter &stream) {
|
||||
stream.writeAttribute("length", QString::number(get_true_length()));
|
||||
Effect::save(stream);
|
||||
}
|
||||
|
||||
void Transition::set_length(int l) {
|
||||
length_field->SetValueAt(0, l);
|
||||
}
|
||||
|
||||
int Transition::get_true_length() {
|
||||
return length_field->GetValueAt(0).toInt();
|
||||
}
|
||||
|
||||
int Transition::get_length() {
|
||||
if (secondary_clip != nullptr) {
|
||||
return get_true_length() * 2;
|
||||
}
|
||||
return get_true_length();
|
||||
}
|
||||
|
||||
Clip* Transition::get_opened_clip() {
|
||||
if (parent_clip->opening_transition.get() == this) {
|
||||
return parent_clip;
|
||||
} else if (secondary_clip != nullptr && secondary_clip->opening_transition.get() == this) {
|
||||
return secondary_clip;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Clip* Transition::get_closed_clip() {
|
||||
if (parent_clip->closing_transition.get() == this) {
|
||||
return parent_clip;
|
||||
} else if (secondary_clip != nullptr && secondary_clip->closing_transition.get() == this) {
|
||||
return secondary_clip;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Transition::set_length_from_slider() {
|
||||
update_ui(false);
|
||||
}
|
||||
|
||||
TransitionPtr Transition::CreateFromMeta(Clip* c, Clip* s, const EffectMeta* em) {
|
||||
if (!em->filename.isEmpty()) {
|
||||
// load effect from file
|
||||
return TransitionPtr(new Transition(c, s, em));
|
||||
} else if (em->internal >= 0 && em->internal < TRANSITION_INTERNAL_COUNT) {
|
||||
// must be an internal effect
|
||||
switch (em->internal) {
|
||||
case TRANSITION_INTERNAL_CROSSDISSOLVE: return TransitionPtr(new CrossDissolveTransition(c, s, em));
|
||||
case TRANSITION_INTERNAL_LINEARFADE: return TransitionPtr(new LinearFadeTransition(c, s, em));
|
||||
case TRANSITION_INTERNAL_EXPONENTIALFADE: return TransitionPtr(new ExponentialFadeTransition(c, s, em));
|
||||
case TRANSITION_INTERNAL_LOGARITHMICFADE: return TransitionPtr(new LogarithmicFadeTransition(c, s, em));
|
||||
//case TRANSITION_INTERNAL_CUBE: return TransitionPtr(new CubeTransition(c, s, em));
|
||||
}
|
||||
} else {
|
||||
qCritical() << "Invalid transition data";
|
||||
QMessageBox::critical(olive::MainWindow,
|
||||
QCoreApplication::translate("transition", "Invalid transition"),
|
||||
QCoreApplication::translate("transition", "No candidate for transition '%1'. This transition may be corrupt. Try reinstalling it or Olive.").arg(em->name)
|
||||
);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TransitionPtr Transition::Create(Clip* c, Clip* s, const EffectMeta* em, long length) {
|
||||
TransitionPtr t(CreateFromMeta(c, s, em));
|
||||
if (t != nullptr) {
|
||||
if (length > 0) {
|
||||
t->set_length(length);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef TRANSITION_H
|
||||
#define TRANSITION_H
|
||||
|
||||
#include "effect.h"
|
||||
|
||||
enum TransitionType {
|
||||
kTransitionNone,
|
||||
kTransitionOpening,
|
||||
kTransitionClosing
|
||||
};
|
||||
|
||||
enum TransitionInternal {
|
||||
TRANSITION_INTERNAL_CROSSDISSOLVE,
|
||||
TRANSITION_INTERNAL_LINEARFADE,
|
||||
TRANSITION_INTERNAL_EXPONENTIALFADE,
|
||||
TRANSITION_INTERNAL_LOGARITHMICFADE,
|
||||
//TRANSITION_INTERNAL_CUBE,
|
||||
TRANSITION_INTERNAL_COUNT
|
||||
};
|
||||
|
||||
class Transition;
|
||||
using TransitionPtr = std::shared_ptr<Transition>;
|
||||
|
||||
class Transition : public Effect {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Transition(Clip* c, Clip* s, const EffectMeta* em);
|
||||
virtual TransitionPtr copy(Clip* c, Clip* s);
|
||||
Clip* secondary_clip;
|
||||
|
||||
virtual void save(QXmlStreamWriter& stream) override;
|
||||
|
||||
void set_length(int l);
|
||||
int get_true_length();
|
||||
int get_length();
|
||||
|
||||
Clip* get_opened_clip();
|
||||
Clip* get_closed_clip();
|
||||
|
||||
static TransitionPtr Create(Clip* c, Clip* s, const EffectMeta* em, long length = 0);
|
||||
static TransitionPtr CreateFromMeta(Clip *c, Clip *s, const EffectMeta* em);
|
||||
private slots:
|
||||
void set_length_from_slider();
|
||||
private:
|
||||
DoubleField* length_field;
|
||||
};
|
||||
|
||||
#endif // TRANSITION_H
|
||||
-1176
File diff suppressed because it is too large
Load Diff
-622
@@ -1,622 +0,0 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef UNDO_H
|
||||
#define UNDO_H
|
||||
|
||||
#include "project/projectelements.h"
|
||||
#include "project/selection.h"
|
||||
#include "project/effectfields/effectfield.h"
|
||||
|
||||
#include "ui/labelslider.h"
|
||||
#include "ui/sourcetable.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QUndoStack>
|
||||
#include <QUndoCommand>
|
||||
#include <QVector>
|
||||
#include <QVariant>
|
||||
#include <QModelIndex>
|
||||
|
||||
namespace olive {
|
||||
extern QUndoStack UndoStack;
|
||||
}
|
||||
|
||||
class OliveAction : public QUndoCommand {
|
||||
public:
|
||||
OliveAction(bool iset_window_modified = true);
|
||||
virtual ~OliveAction() override;
|
||||
|
||||
virtual void undo() override;
|
||||
virtual void redo() override;
|
||||
|
||||
virtual void doUndo() = 0;
|
||||
virtual void doRedo() = 0;
|
||||
private:
|
||||
/**
|
||||
* @brief Setting whether to change the windowModified state of MainWindow
|
||||
*/
|
||||
bool set_window_modified;
|
||||
|
||||
/**
|
||||
* @brief Cache previous window modified value to return to if the user undoes this action
|
||||
*/
|
||||
bool old_window_modified;
|
||||
};
|
||||
|
||||
class MoveClipAction : public OliveAction {
|
||||
public:
|
||||
MoveClipAction(Clip* c, long iin, long iout, long iclip_in, int itrack, bool irelative);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
Clip* clip;
|
||||
|
||||
long old_in;
|
||||
long old_out;
|
||||
long old_clip_in;
|
||||
int old_track;
|
||||
|
||||
long new_in;
|
||||
long new_out;
|
||||
long new_clip_in;
|
||||
int new_track;
|
||||
|
||||
bool relative;
|
||||
};
|
||||
|
||||
class RippleAction : public OliveAction {
|
||||
public:
|
||||
RippleAction(Sequence* is, long ipoint, long ilength, const QVector<int>& iignore);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
Sequence* s;
|
||||
long point;
|
||||
long length;
|
||||
QVector<int> ignore;
|
||||
ComboAction* ca;
|
||||
};
|
||||
|
||||
class DeleteClipAction : public OliveAction {
|
||||
public:
|
||||
DeleteClipAction(Sequence* s, int clip);
|
||||
virtual ~DeleteClipAction() override;
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
Sequence* seq;
|
||||
ClipPtr ref;
|
||||
int index;
|
||||
|
||||
int opening_transition;
|
||||
int closing_transition;
|
||||
|
||||
QVector<int> linkClipIndex;
|
||||
QVector<int> linkLinkIndex;
|
||||
};
|
||||
|
||||
class ChangeSequenceAction : public OliveAction {
|
||||
public:
|
||||
ChangeSequenceAction(SequencePtr s);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
SequencePtr old_sequence;
|
||||
SequencePtr new_sequence;
|
||||
};
|
||||
|
||||
class AddEffectCommand : public OliveAction {
|
||||
public:
|
||||
AddEffectCommand(Clip* c, EffectPtr e, const EffectMeta* m, int insert_pos = -1);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
Clip* clip;
|
||||
const EffectMeta* meta;
|
||||
EffectPtr ref;
|
||||
int pos;
|
||||
bool done;
|
||||
};
|
||||
|
||||
class AddTransitionCommand : public OliveAction {
|
||||
public:
|
||||
AddTransitionCommand(Clip* iopen, Clip* iclose, TransitionPtr copy, const EffectMeta* itransition, int ilength);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
Clip* open_;
|
||||
Clip* close_;
|
||||
TransitionPtr transition_to_copy_;
|
||||
const EffectMeta* transition_meta_;
|
||||
int length_;
|
||||
TransitionPtr old_open_transition_;
|
||||
TransitionPtr old_close_transition_;
|
||||
TransitionPtr new_transition_ref_;
|
||||
};
|
||||
|
||||
class ModifyTransitionCommand : public OliveAction {
|
||||
public:
|
||||
ModifyTransitionCommand(TransitionPtr t, long ilength);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
TransitionPtr transition_ref_;
|
||||
long new_length_;
|
||||
long old_length_;
|
||||
};
|
||||
|
||||
class DeleteTransitionCommand : public OliveAction {
|
||||
public:
|
||||
DeleteTransitionCommand(TransitionPtr t);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
TransitionPtr transition_ref_;
|
||||
Clip* opened_clip_;
|
||||
Clip* closed_clip_;
|
||||
};
|
||||
|
||||
class SetTimelineInOutCommand : public OliveAction {
|
||||
public:
|
||||
SetTimelineInOutCommand(Sequence* s, bool enabled, long in, long out);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
Sequence* seq;
|
||||
|
||||
bool old_enabled;
|
||||
long old_in;
|
||||
long old_out;
|
||||
|
||||
bool new_enabled;
|
||||
long new_in;
|
||||
long new_out;
|
||||
};
|
||||
|
||||
class AddMediaCommand : public OliveAction {
|
||||
public:
|
||||
AddMediaCommand(MediaPtr iitem, Media* iparent);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
MediaPtr item;
|
||||
Media* parent;
|
||||
};
|
||||
|
||||
class DeleteMediaCommand : public OliveAction {
|
||||
public:
|
||||
DeleteMediaCommand(MediaPtr i);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
MediaPtr item;
|
||||
Media* parent;
|
||||
};
|
||||
|
||||
class AddClipCommand : public OliveAction {
|
||||
public:
|
||||
AddClipCommand(Sequence* s, QVector<ClipPtr>& add);
|
||||
virtual ~AddClipCommand() override;
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
Sequence* seq;
|
||||
QVector<ClipPtr> clips;
|
||||
int link_offset_;
|
||||
};
|
||||
|
||||
class LinkCommand : public OliveAction {
|
||||
public:
|
||||
LinkCommand();
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
Sequence* s;
|
||||
QVector<int> clips;
|
||||
bool link;
|
||||
private:
|
||||
QVector< QVector<int> > old_links;
|
||||
};
|
||||
|
||||
class CheckboxCommand : public OliveAction {
|
||||
public:
|
||||
CheckboxCommand(QCheckBox* b);
|
||||
virtual ~CheckboxCommand() override;
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
QCheckBox* box;
|
||||
bool checked;
|
||||
bool done;
|
||||
};
|
||||
|
||||
class ReplaceMediaCommand : public OliveAction {
|
||||
public:
|
||||
ReplaceMediaCommand(MediaPtr, QString);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
MediaPtr item;
|
||||
QString old_filename;
|
||||
QString new_filename;
|
||||
void replace(QString& filename);
|
||||
};
|
||||
|
||||
class ReplaceClipMediaCommand : public OliveAction {
|
||||
public:
|
||||
ReplaceClipMediaCommand(Media *, Media *, bool);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
QVector<ClipPtr> clips;
|
||||
private:
|
||||
Media* old_media;
|
||||
Media* new_media;
|
||||
bool preserve_clip_ins;
|
||||
QVector<int> old_clip_ins;
|
||||
void replace(bool undo);
|
||||
};
|
||||
|
||||
class EffectDeleteCommand : public OliveAction {
|
||||
public:
|
||||
EffectDeleteCommand(Effect* e);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
Effect* effect_;
|
||||
EffectPtr deleted_obj_;
|
||||
Clip* parent_clip_;
|
||||
int index_;
|
||||
};
|
||||
|
||||
class MediaMove : public OliveAction {
|
||||
public:
|
||||
MediaMove();
|
||||
QVector<MediaPtr> items;
|
||||
Media* to;
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
QVector<Media*> froms;
|
||||
};
|
||||
|
||||
class MediaRename : public OliveAction {
|
||||
public:
|
||||
MediaRename(Media* iitem, QString to);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
Media* item;
|
||||
QString from;
|
||||
QString to;
|
||||
};
|
||||
|
||||
class KeyframeDelete : public OliveAction {
|
||||
public:
|
||||
KeyframeDelete(EffectField* ifield, int iindex);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
EffectField* field;
|
||||
int index;
|
||||
bool done;
|
||||
EffectKeyframe deleted_key;
|
||||
};
|
||||
|
||||
// a more modern version of the above, could probably replace it
|
||||
// assumes the keyframe already exists
|
||||
class KeyframeFieldSet : public OliveAction {
|
||||
public:
|
||||
KeyframeFieldSet(EffectField* ifield, int ii);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
EffectField* field;
|
||||
int index;
|
||||
EffectKeyframe key;
|
||||
bool done;
|
||||
};
|
||||
|
||||
class EffectFieldUndo : public OliveAction {
|
||||
public:
|
||||
EffectFieldUndo(EffectField* field, const QVariant &old_data, const QVariant new_data);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
EffectField* field;
|
||||
QVariant old_val;
|
||||
QVariant new_val;
|
||||
bool done;
|
||||
};
|
||||
|
||||
enum SetClipPropertyType {
|
||||
kSetClipPropertyAutoscale,
|
||||
kSetClipPropertyReversed,
|
||||
kSetClipPropertyMaintainAudioPitch,
|
||||
kSetClipPropertyEnabled
|
||||
};
|
||||
|
||||
class SetClipProperty : public OliveAction {
|
||||
public:
|
||||
SetClipProperty(SetClipPropertyType type);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
void AddSetting(QVector<Clip *> clips, bool setting);
|
||||
void AddSetting(Clip *c, bool setting);
|
||||
private:
|
||||
SetClipPropertyType type_;
|
||||
QVector<Clip*> clips_;
|
||||
QVector<bool> setting_;
|
||||
QVector<bool> old_setting_;
|
||||
void MainLoop(bool undo);
|
||||
};
|
||||
|
||||
class AddMarkerAction : public OliveAction {
|
||||
public:
|
||||
AddMarkerAction(QVector<Marker>* m, long t, QString n);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
QVector<Marker>* active_array;
|
||||
long time;
|
||||
QString name;
|
||||
QString old_name;
|
||||
int index;
|
||||
};
|
||||
|
||||
class MoveMarkerAction : public OliveAction {
|
||||
public:
|
||||
MoveMarkerAction(Marker* m, long o, long n);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
Marker* marker;
|
||||
long old_time;
|
||||
long new_time;
|
||||
};
|
||||
|
||||
class DeleteMarkerAction : public OliveAction {
|
||||
public:
|
||||
DeleteMarkerAction(QVector<Marker>* m);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
QVector<int> markers;
|
||||
private:
|
||||
QVector<Marker>* active_array;
|
||||
QVector<Marker> copies;
|
||||
bool sorted;
|
||||
};
|
||||
|
||||
class SetSpeedAction : public OliveAction {
|
||||
public:
|
||||
SetSpeedAction(Clip* c, double speed);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
Clip* clip;
|
||||
double old_speed;
|
||||
double new_speed;
|
||||
};
|
||||
|
||||
class SetBool : public OliveAction {
|
||||
public:
|
||||
SetBool(bool* b, bool setting);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
bool* boolean;
|
||||
bool old_setting;
|
||||
bool new_setting;
|
||||
};
|
||||
|
||||
class SetSelectionsCommand : public OliveAction {
|
||||
public:
|
||||
SetSelectionsCommand(Sequence* s);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
QVector<Selection> old_data;
|
||||
QVector<Selection> new_data;
|
||||
private:
|
||||
Sequence* seq;
|
||||
bool done;
|
||||
};
|
||||
|
||||
class EditSequenceCommand : public OliveAction {
|
||||
public:
|
||||
EditSequenceCommand(Media *i, SequencePtr s);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
void update();
|
||||
|
||||
QString name;
|
||||
int width;
|
||||
int height;
|
||||
double frame_rate;
|
||||
int audio_frequency;
|
||||
int audio_layout;
|
||||
private:
|
||||
Media* item;
|
||||
SequencePtr seq;
|
||||
|
||||
QString old_name;
|
||||
int old_width;
|
||||
int old_height;
|
||||
double old_frame_rate;
|
||||
int old_audio_frequency;
|
||||
int old_audio_layout;
|
||||
};
|
||||
|
||||
class SetInt : public OliveAction {
|
||||
public:
|
||||
SetInt(int* pointer, int new_value);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
int* p;
|
||||
int oldval;
|
||||
int newval;
|
||||
};
|
||||
|
||||
class SetLong : public OliveAction {
|
||||
public:
|
||||
SetLong(long* pointer, long old_value, long new_value);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
long* p;
|
||||
long oldval;
|
||||
long newval;
|
||||
};
|
||||
|
||||
class SetDouble : public OliveAction {
|
||||
public:
|
||||
SetDouble(double* pointer, double old_value, double new_value);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
double* p;
|
||||
double oldval;
|
||||
double newval;
|
||||
};
|
||||
|
||||
class SetString : public OliveAction {
|
||||
public:
|
||||
SetString(QString* pointer, QString new_value);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
QString* p;
|
||||
QString oldval;
|
||||
QString newval;
|
||||
};
|
||||
|
||||
class CloseAllClipsCommand : public OliveAction {
|
||||
public:
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
};
|
||||
|
||||
class UpdateFootageTooltip : public OliveAction {
|
||||
public:
|
||||
UpdateFootageTooltip(Media* i);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
Media* item;
|
||||
};
|
||||
|
||||
class MoveEffectCommand : public OliveAction {
|
||||
public:
|
||||
MoveEffectCommand();
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
Clip* clip;
|
||||
int from;
|
||||
int to;
|
||||
};
|
||||
|
||||
class RemoveClipsFromClipboard : public OliveAction {
|
||||
public:
|
||||
RemoveClipsFromClipboard(int index);
|
||||
virtual ~RemoveClipsFromClipboard() override;
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
int pos;
|
||||
ClipPtr clip;
|
||||
bool done;
|
||||
};
|
||||
|
||||
class RenameClipCommand : public OliveAction {
|
||||
public:
|
||||
RenameClipCommand(Clip* clip, QString new_name);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
QString old_name_;
|
||||
QString new_name_;
|
||||
Clip* clip_;
|
||||
};
|
||||
|
||||
class SetPointer : public OliveAction {
|
||||
public:
|
||||
SetPointer(void** pointer, void* data);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
bool old_changed;
|
||||
void** p;
|
||||
void* new_data;
|
||||
void* old_data;
|
||||
};
|
||||
|
||||
class ReloadEffectsCommand : public OliveAction {
|
||||
public:
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
};
|
||||
|
||||
class SetQVariant : public OliveAction {
|
||||
public:
|
||||
SetQVariant(QVariant* itarget, const QVariant& iold, const QVariant& inew);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
QVariant* target;
|
||||
QVariant old_val;
|
||||
QVariant new_val;
|
||||
};
|
||||
|
||||
class SetIsKeyframing : public OliveAction {
|
||||
public:
|
||||
SetIsKeyframing(EffectRow* irow, bool ib);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
EffectRow* row;
|
||||
bool b;
|
||||
};
|
||||
|
||||
class RefreshClips : public OliveAction {
|
||||
public:
|
||||
RefreshClips(Media* m);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
Media* media;
|
||||
};
|
||||
|
||||
class UpdateViewer : public OliveAction {
|
||||
public:
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
};
|
||||
|
||||
class SetEffectData : public OliveAction {
|
||||
public:
|
||||
SetEffectData(Effect* e, const QByteArray &s);
|
||||
virtual void doUndo() override;
|
||||
virtual void doRedo() override;
|
||||
private:
|
||||
Effect* effect;
|
||||
QByteArray data;
|
||||
QByteArray old_data;
|
||||
};
|
||||
|
||||
#endif // UNDO_H
|
||||
Reference in New Issue
Block a user