file/folder restructuring

This commit is contained in:
itsmattkc
2019-03-13 16:55:49 +11:00
parent d84b5a7f1d
commit 1cd5dabc07
228 changed files with 1269 additions and 1557 deletions
-634
View File
@@ -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
View File
@@ -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
+34 -36
View File
@@ -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();
}
+38 -36
View File
@@ -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
-40
View File
@@ -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;
}
-96
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
-261
View File
@@ -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
-14
View File
@@ -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
-38
View File
@@ -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);
}
-24
View File
@@ -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
-36
View File
@@ -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;
}
-29
View File
@@ -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
-39
View File
@@ -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);
}
-22
View File
@@ -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
-38
View File
@@ -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);
}
-31
View File
@@ -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
-91
View File
@@ -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);
}
-53
View File
@@ -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
-416
View File
@@ -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_);
}
-97
View File
@@ -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
-29
View File
@@ -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);
}
-19
View File
@@ -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
-37
View File
@@ -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);
}
-20
View File
@@ -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
-18
View File
@@ -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;
}
-17
View File
@@ -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
-40
View File
@@ -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);
}
-19
View File
@@ -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
-73
View File
@@ -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;
}
-74
View File
@@ -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
-271
View File
@@ -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";
}
-248
View File
@@ -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();
}
-78
View File
@@ -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
View File
@@ -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
View File
@@ -34,7 +34,7 @@ extern "C" {
#include <QPixmap>
#include <QIcon>
#include "project/marker.h"
#include "timeline/marker.h"
enum VideoInterlacingMode {
VIDEO_PROGRESSIVE,
-66
View File
@@ -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);
}
}
-45
View File
@@ -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
+781
View File
@@ -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);
}
}
+91
View File
@@ -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
-127
View File
@@ -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);
}
-43
View File
@@ -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
View File
@@ -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
View File
@@ -25,7 +25,7 @@
#include <QVariant>
#include <QIcon>
#include "project/marker.h"
#include "timeline/marker.h"
#include "project/footage.h"
enum MediaType {
+630
View File
@@ -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);
}
+65
View File
@@ -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
-7
View File
@@ -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
View File
@@ -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);
}
+1 -1
View File
@@ -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;
+420
View File
@@ -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;
+72
View File
@@ -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
-195
View File
@@ -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;
-77
View File
@@ -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
+5 -5
View File
@@ -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>
-136
View File
@@ -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;
}
-68
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
-622
View File
@@ -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