diff --git a/debug.cpp b/debug.cpp
index f949f07ac..9589ee5e4 100644
--- a/debug.cpp
+++ b/debug.cpp
@@ -55,31 +55,36 @@ void debug_message_handler(QtMsgType type, const QMessageLogContext &context, co
QByteArray localMsg = msg.toLocal8Bit();
switch (type) {
case QtDebugMsg:
- fprintf(stderr, "[DEBUG] %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
+// fprintf(stderr, "[DEBUG] %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
+ fprintf(stderr, "[DEBUG] %s\n", localMsg.constData());
if (debug_file.isOpen()) debug_stream << QString("[DEBUG] %1 (%2:%3, %4)\n").arg(localMsg.constData(), context.file, QString::number(context.line), context.function);
debug_info.append(QString("[DEBUG] %1 (%2:%3, %4)
").arg(localMsg.constData(), context.file, QString::number(context.line), context.function));
fflush(stderr);
break;
case QtInfoMsg:
- fprintf(stderr, "[INFO] %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
+// fprintf(stderr, "[INFO] %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
+ fprintf(stderr, "[INFO] %s\n", localMsg.constData());
if (debug_file.isOpen()) debug_stream << QString("[INFO] %1 (%2:%3, %4)\n").arg(localMsg.constData(), context.file, QString::number(context.line), context.function);
debug_info.append(QString("[INFO] %1 (%2:%3, %4)
").arg(localMsg.constData(), context.file, QString::number(context.line), context.function));
fflush(stderr);
break;
case QtWarningMsg:
- fprintf(stderr, "[WARNING] %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
+// fprintf(stderr, "[WARNING] %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
+ fprintf(stderr, "[WARNING] %s\n", localMsg.constData());
if (debug_file.isOpen()) debug_stream << QString("[WARNING] %1 (%2:%3, %4)\n").arg(localMsg.constData(), context.file, QString::number(context.line), context.function);
debug_info.append(QString("[WARNING] %1 (%2:%3, %4)
").arg(localMsg.constData(), context.file, QString::number(context.line), context.function));
fflush(stderr);
break;
case QtCriticalMsg:
- fprintf(stderr, "[ERROR] %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
+// fprintf(stderr, "[ERROR] %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
+ fprintf(stderr, "[ERROR] %s\n", localMsg.constData());
if (debug_file.isOpen()) debug_stream << QString("[ERROR] %1 (%2:%3, %4)\n").arg(localMsg.constData(), context.file, QString::number(context.line), context.function);
debug_info.append(QString("[ERROR] %1 (%2:%3, %4)
").arg(localMsg.constData(), context.file, QString::number(context.line), context.function));
fflush(stderr);
break;
case QtFatalMsg:
- fprintf(stderr, "[FATAL] %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
+// fprintf(stderr, "[FATAL] %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
+ fprintf(stderr, "[FATAL] %s\n", localMsg.constData());
if (debug_file.isOpen()) debug_stream << QString("[FATAL] %1 (%2:%3, %4)\n").arg(localMsg.constData(), context.file, QString::number(context.line), context.function);
debug_info.append(QString("[FATAL] %1 (%2:%3, %4)
").arg(localMsg.constData(), context.file, QString::number(context.line), context.function));
fflush(stderr);
diff --git a/panels/project.cpp b/panels/project.cpp
index 3f41708f0..ba6311e8c 100644
--- a/panels/project.cpp
+++ b/panels/project.cpp
@@ -1087,6 +1087,9 @@ void Project::save_folder(QXmlStreamWriter& stream, int type, bool set_ids_only,
stream.writeAttribute("workareaOut", QString::number(s->workarea_out));
/*
+ QVector transition_save_cache;
+ QVector transition_clip_save_cache;
+ QVector transition_type_save_cache;
for (int j=0;jtransitions.size();j++) {
TransitionPtr t = s->transitions.at(j);
if (t != nullptr) {
@@ -1097,7 +1100,7 @@ void Project::save_folder(QXmlStreamWriter& stream, int type, bool set_ids_only,
stream.writeEndElement(); // transition
}
}
- */
+ */
for (int j=0;jclips.size();j++) {
const ClipPtr& c = s->clips.at(j);
@@ -1110,10 +1113,6 @@ void Project::save_folder(QXmlStreamWriter& stream, int type, bool set_ids_only,
stream.writeAttribute("in", QString::number(c->timeline_in));
stream.writeAttribute("out", QString::number(c->timeline_out));
stream.writeAttribute("track", QString::number(c->track));
- /*
- stream.writeAttribute("opening", QString::number(c->opening_transition));
- stream.writeAttribute("closing", QString::number(c->closing_transition));
- */
stream.writeAttribute("r", QString::number(c->color_r));
stream.writeAttribute("g", QString::number(c->color_g));
diff --git a/panels/timeline.cpp b/panels/timeline.cpp
index 2ded033b8..227dd50b2 100644
--- a/panels/timeline.cpp
+++ b/panels/timeline.cpp
@@ -845,57 +845,73 @@ ClipPtr Timeline::split_clip(ComboAction* ca, bool transitions, int p, long fram
ClipPtr Timeline::split_clip(ComboAction* ca, bool transitions, int p, long frame, long post_in) {
ClipPtr pre = olive::ActiveSequence->clips.at(p);
- if (pre != nullptr && pre->timeline_in < frame && pre->timeline_out > frame) { // guard against attempts to split at in/out points
- bool splitting_closing_dual_transition = false;
+ if (pre != nullptr) {
- if (transitions
- && pre->get_closing_transition() != nullptr
- && pre->get_closing_transition()->secondary_clip != nullptr) {
- splitting_closing_dual_transition = true;
- }
+ if (pre->timeline_in < frame && pre->timeline_out > frame) {
+ // duplicate clip without duplicating its transitions, we'll restore them later
- ClipPtr post = ClipPtr(pre->copy(olive::ActiveSequence, transitions && !splitting_closing_dual_transition));
+ ClipPtr post = ClipPtr(pre->copy(olive::ActiveSequence, false));
- long new_clip_length = frame - pre->timeline_in;
+ long new_clip_length = frame - pre->timeline_in;
- post->timeline_in = post_in;
- post->clip_in = pre->clip_in + (post->timeline_in - pre->timeline_in);
+ post->timeline_in = post_in;
+ post->clip_in = pre->clip_in + (post->timeline_in - pre->timeline_in);
- move_clip(ca, pre, pre->timeline_in, frame, pre->clip_in, pre->track);
+ move_clip(ca, pre, pre->timeline_in, frame, pre->clip_in, pre->track, false);
- if (pre->get_opening_transition() != nullptr) {
- // if (frame < pre->timeline_in + pre->get_opening_transition()->length && pre->get_opening_transition()->secondary_clip != nullptr) {
- // separate shared transition
- // ca->append(new SetPointer((void**) &pre->get_opening_transition()->secondary_clip, nullptr));
- // pre->get_opening_transition()->secondary_clip->closing_transition = pre->get_opening_transition()->copy(pre->get_opening_transition()->secondary_clip, nullptr);
- // }
+ if (transitions) {
- if (pre->get_opening_transition()->get_true_length() > new_clip_length) {
- ca->append(new ModifyTransitionCommand(pre->get_opening_transition(), new_clip_length));
- }
- }
- if (pre->get_closing_transition() != nullptr) {
- if (splitting_closing_dual_transition) {
- // just move closing transition to post clip
+ // check if this clip has a closing transition
+ if (pre->closing_transition != nullptr) {
- // WORKAROUND
- ca->append(new DeleteTransitionCommand(pre->closing_transition));
- } else {
- ca->append(new DeleteTransitionCommand(pre->closing_transition));
+ // if so, move closing transition to the post clip
+ post->closing_transition = pre->closing_transition;
- if (post->get_closing_transition() != nullptr) {
- if (pre->get_closing_transition()->secondary_clip == nullptr) {
- post->get_closing_transition()->set_length(qMin(long(post->get_closing_transition()->get_true_length()), post->getLength()));
+ // and set the original clip's closing transition to nothing
+ ca->append(new SetPointer(reinterpret_cast(&pre->closing_transition), nullptr));
+
+ // and set the transition's reference to the post clip
+ if (post->closing_transition->parent_clip == pre) {
+ ca->append(new SetPointer(reinterpret_cast(&post->closing_transition->parent_clip), post.get()));
+ }
+ if (post->closing_transition->secondary_clip == pre) {
+ ca->append(new SetPointer(reinterpret_cast(&post->closing_transition->secondary_clip), post.get()));
}
- if (post->get_closing_transition()->get_length() > post->getLength()) {
- post->get_closing_transition()->set_length(post->getLength());
+ // and make sure it's at the correct size to the closing clip
+ if (post->closing_transition != nullptr && post->closing_transition->get_true_length() > post->getLength()) {
+ ca->append(new ModifyTransitionCommand(post->closing_transition, post->getLength()));
+ post->closing_transition->set_length(post->getLength());
}
+
+ }
+
+ // we're keeping the opening clip, so ensure that's a correct size too
+ if (pre->opening_transition != nullptr && pre->opening_transition->get_true_length() > new_clip_length) {
+ ca->append(new ModifyTransitionCommand(pre->opening_transition, new_clip_length));
}
}
+
+ return post;
+
+ } else if (frame == pre->timeline_in
+ && pre->opening_transition != nullptr
+ && pre->opening_transition->secondary_clip != nullptr) {
+ // special case for shared transitions to split it into two
+
+ // set transition to single-clip mode
+ ca->append(new SetPointer(reinterpret_cast(&pre->opening_transition->secondary_clip), nullptr));
+
+ // clone transition for other clip
+ ca->append(new AddTransitionCommand(nullptr,
+ pre->opening_transition->secondary_clip,
+ pre->opening_transition,
+ nullptr,
+ 0)
+ );
+
}
- return post;
}
return nullptr;
}
@@ -915,11 +931,12 @@ bool Timeline::split_clip_and_relink(ComboAction *ca, int clip, long frame, bool
ClipPtr post = split_clip(ca, true, clip, frame);
- // if alt is not down, split clips links too
if (post == nullptr) {
return false;
} else {
post_clips.append(post);
+
+ // if alt is not down, split clips links too
if (relink) {
pre_clips.append(clip);
@@ -1410,7 +1427,8 @@ bool Timeline::split_selection(ComboAction* ca) {
const Selection& s = olive::ActiveSequence->selections.at(i);
if (s.track == clip->track) {
ClipPtr post_b = split_clip(ca, true, j, s.out);
- ClipPtr post_a = split_clip(ca, post_b == nullptr, j, s.in);
+ ClipPtr post_a = split_clip(ca, true, j, s.in);
+
pre_splits.append(j);
post_splits.append(post_a);
secondary_post_splits.append(post_b);
diff --git a/playback/cacher.cpp b/playback/cacher.cpp
index 86dc423b3..f974c8788 100644
--- a/playback/cacher.cpp
+++ b/playback/cacher.cpp
@@ -37,15 +37,15 @@
#include "debug.h"
extern "C" {
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
}
#include
@@ -59,616 +59,616 @@ extern "C" {
//int dest_format = AV_PIX_FMT_RGBA;
double bytes_to_seconds(int nb_bytes, int nb_channels, int sample_rate) {
- return ((double) (nb_bytes >> 1) / nb_channels / sample_rate);
+ return ((double) (nb_bytes >> 1) / nb_channels / sample_rate);
}
void apply_audio_effects(ClipPtr c, double timecode_start, AVFrame* frame, int nb_bytes, QVector nests) {
- // perform all audio effects
- double timecode_end;
- timecode_end = timecode_start + bytes_to_seconds(nb_bytes, frame->channels, frame->sample_rate);
+ // perform all audio effects
+ double timecode_end;
+ timecode_end = timecode_start + bytes_to_seconds(nb_bytes, frame->channels, frame->sample_rate);
- for (int j=0;jeffects.size();j++) {
- EffectPtr e = c->effects.at(j);
- if (e->is_enabled()) e->process_audio(timecode_start, timecode_end, frame->data[0], nb_bytes, 2);
- }
- if (c->get_opening_transition() != nullptr) {
- if (c->media != nullptr && c->media->get_type() == MEDIA_TYPE_FOOTAGE) {
- double transition_start = (c->get_clip_in_with_transition() / c->sequence->frame_rate);
- double transition_end = (c->get_clip_in_with_transition() + c->get_opening_transition()->get_length()) / c->sequence->frame_rate;
- if (timecode_end < transition_end) {
- double adjustment = transition_end - transition_start;
- double adjusted_range_start = (timecode_start - transition_start) / adjustment;
- double adjusted_range_end = (timecode_end - transition_start) / adjustment;
- c->get_opening_transition()->process_audio(adjusted_range_start, adjusted_range_end, frame->data[0], nb_bytes, kTransitionOpening);
- }
- }
- }
- if (c->get_closing_transition() != nullptr) {
- if (c->media != nullptr && c->media->get_type() == MEDIA_TYPE_FOOTAGE) {
- long length_with_transitions = c->get_timeline_out_with_transition() - c->get_timeline_in_with_transition();
- double transition_start = (c->get_clip_in_with_transition() + length_with_transitions - c->get_closing_transition()->get_length()) / c->sequence->frame_rate;
- double transition_end = (c->get_clip_in_with_transition() + length_with_transitions) / c->sequence->frame_rate;
- if (timecode_start > transition_start) {
- double adjustment = transition_end - transition_start;
- double adjusted_range_start = (timecode_start - transition_start) / adjustment;
- double adjusted_range_end = (timecode_end - transition_start) / adjustment;
- c->get_closing_transition()->process_audio(adjusted_range_start, adjusted_range_end, frame->data[0], nb_bytes, kTransitionClosing);
- }
- }
- }
+ for (int j=0;jeffects.size();j++) {
+ EffectPtr e = c->effects.at(j);
+ if (e->is_enabled()) e->process_audio(timecode_start, timecode_end, frame->data[0], nb_bytes, 2);
+ }
+ if (c->get_opening_transition() != nullptr) {
+ if (c->media != nullptr && c->media->get_type() == MEDIA_TYPE_FOOTAGE) {
+ double transition_start = (c->get_clip_in_with_transition() / c->sequence->frame_rate);
+ double transition_end = (c->get_clip_in_with_transition() + c->get_opening_transition()->get_length()) / c->sequence->frame_rate;
+ if (timecode_end < transition_end) {
+ double adjustment = transition_end - transition_start;
+ double adjusted_range_start = (timecode_start - transition_start) / adjustment;
+ double adjusted_range_end = (timecode_end - transition_start) / adjustment;
+ c->get_opening_transition()->process_audio(adjusted_range_start, adjusted_range_end, frame->data[0], nb_bytes, kTransitionOpening);
+ }
+ }
+ }
+ if (c->get_closing_transition() != nullptr) {
+ if (c->media != nullptr && c->media->get_type() == MEDIA_TYPE_FOOTAGE) {
+ long length_with_transitions = c->get_timeline_out_with_transition() - c->get_timeline_in_with_transition();
+ double transition_start = (c->get_clip_in_with_transition() + length_with_transitions - c->get_closing_transition()->get_length()) / c->sequence->frame_rate;
+ double transition_end = (c->get_clip_in_with_transition() + length_with_transitions) / c->sequence->frame_rate;
+ if (timecode_start > transition_start) {
+ double adjustment = transition_end - transition_start;
+ double adjusted_range_start = (timecode_start - transition_start) / adjustment;
+ double adjusted_range_end = (timecode_end - transition_start) / adjustment;
+ c->get_closing_transition()->process_audio(adjusted_range_start, adjusted_range_end, frame->data[0], nb_bytes, kTransitionClosing);
+ }
+ }
+ }
- if (!nests.isEmpty()) {
- ClipPtr next_nest = nests.last();
- nests.removeLast();
- apply_audio_effects(next_nest, timecode_start + (((double)c->get_timeline_in_with_transition()-c->get_clip_in_with_transition())/c->sequence->frame_rate), frame, nb_bytes, nests);
- }
+ if (!nests.isEmpty()) {
+ ClipPtr next_nest = nests.last();
+ nests.removeLast();
+ apply_audio_effects(next_nest, timecode_start + (((double)c->get_timeline_in_with_transition()-c->get_clip_in_with_transition())/c->sequence->frame_rate), frame, nb_bytes, nests);
+ }
}
#define AUDIO_BUFFER_PADDING 2048
void cache_audio_worker(ClipPtr c, bool scrubbing, QVector& nests, int playback_speed) {
- long timeline_in = c->get_timeline_in_with_transition();
- long timeline_out = c->get_timeline_out_with_transition();
- long target_frame = c->audio_target_frame;
+ long timeline_in = c->get_timeline_in_with_transition();
+ long timeline_out = c->get_timeline_out_with_transition();
+ long target_frame = c->audio_target_frame;
- bool temp_reverse = (playback_speed < 0);
- bool reverse_audio = (c->reverse != temp_reverse);
+ bool temp_reverse = (playback_speed < 0);
+ bool reverse_audio = (c->reverse != temp_reverse);
- long frame_skip = 0;
- double last_fr = c->sequence->frame_rate;
- if (!nests.isEmpty()) {
- for (int i=nests.size()-1;i>=0;i--) {
- timeline_in = refactor_frame_number(timeline_in, last_fr, nests.at(i)->sequence->frame_rate) + nests.at(i)->get_timeline_in_with_transition() - nests.at(i)->get_clip_in_with_transition();
- timeline_out = refactor_frame_number(timeline_out, last_fr, nests.at(i)->sequence->frame_rate) + nests.at(i)->get_timeline_in_with_transition() - nests.at(i)->get_clip_in_with_transition();
- target_frame = refactor_frame_number(target_frame, last_fr, nests.at(i)->sequence->frame_rate) + nests.at(i)->get_timeline_in_with_transition() - nests.at(i)->get_clip_in_with_transition();
+ long frame_skip = 0;
+ double last_fr = c->sequence->frame_rate;
+ if (!nests.isEmpty()) {
+ for (int i=nests.size()-1;i>=0;i--) {
+ timeline_in = refactor_frame_number(timeline_in, last_fr, nests.at(i)->sequence->frame_rate) + nests.at(i)->get_timeline_in_with_transition() - nests.at(i)->get_clip_in_with_transition();
+ timeline_out = refactor_frame_number(timeline_out, last_fr, nests.at(i)->sequence->frame_rate) + nests.at(i)->get_timeline_in_with_transition() - nests.at(i)->get_clip_in_with_transition();
+ target_frame = refactor_frame_number(target_frame, last_fr, nests.at(i)->sequence->frame_rate) + nests.at(i)->get_timeline_in_with_transition() - nests.at(i)->get_clip_in_with_transition();
- timeline_out = qMin(timeline_out, nests.at(i)->get_timeline_out_with_transition());
+ timeline_out = qMin(timeline_out, nests.at(i)->get_timeline_out_with_transition());
- frame_skip = refactor_frame_number(frame_skip, last_fr, nests.at(i)->sequence->frame_rate);
+ frame_skip = refactor_frame_number(frame_skip, last_fr, nests.at(i)->sequence->frame_rate);
- long validator = nests.at(i)->get_timeline_in_with_transition() - timeline_in;
- if (validator > 0) {
- frame_skip += validator;
- //timeline_in = nests.at(i)->get_timeline_in_with_transition();
- }
+ long validator = nests.at(i)->get_timeline_in_with_transition() - timeline_in;
+ if (validator > 0) {
+ frame_skip += validator;
+ //timeline_in = nests.at(i)->get_timeline_in_with_transition();
+ }
- last_fr = nests.at(i)->sequence->frame_rate;
- }
- }
+ last_fr = nests.at(i)->sequence->frame_rate;
+ }
+ }
- if (temp_reverse) {
- long seq_end = olive::ActiveSequence->getEndFrame();
- timeline_in = seq_end - timeline_in;
- timeline_out = seq_end - timeline_out;
- target_frame = seq_end - target_frame;
+ if (temp_reverse) {
+ long seq_end = olive::ActiveSequence->getEndFrame();
+ timeline_in = seq_end - timeline_in;
+ timeline_out = seq_end - timeline_out;
+ target_frame = seq_end - target_frame;
- long temp = timeline_in;
- timeline_in = timeline_out;
- timeline_out = temp;
- }
+ long temp = timeline_in;
+ timeline_in = timeline_out;
+ timeline_out = temp;
+ }
- while (true) {
- AVFrame* frame;
- int nb_bytes = INT_MAX;
+ while (true) {
+ AVFrame* frame;
+ int nb_bytes = INT_MAX;
- if (c->media == nullptr) {
- frame = c->frame;
- nb_bytes = frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels;
- while ((c->frame_sample_index == -1 || c->frame_sample_index >= nb_bytes) && nb_bytes > 0) {
- // create "new frame"
- memset(c->frame->data[0], 0, nb_bytes);
- apply_audio_effects(c, bytes_to_seconds(frame->pts, frame->channels, frame->sample_rate), frame, nb_bytes, nests);
- c->frame->pts += nb_bytes;
- c->frame_sample_index = 0;
- if (c->audio_buffer_write == 0) {
- c->audio_buffer_write = get_buffer_offset_from_frame(last_fr, qMax(timeline_in, target_frame));
- }
- int offset = audio_ibuffer_read - c->audio_buffer_write;
- if (offset > 0) {
- c->audio_buffer_write += offset;
- c->frame_sample_index += offset;
- }
- }
- } else if (c->media->get_type() == MEDIA_TYPE_FOOTAGE) {
- double timebase = av_q2d(c->stream->time_base);
+ if (c->media == nullptr) {
+ frame = c->frame;
+ nb_bytes = frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels;
+ while ((c->frame_sample_index == -1 || c->frame_sample_index >= nb_bytes) && nb_bytes > 0) {
+ // create "new frame"
+ memset(c->frame->data[0], 0, nb_bytes);
+ apply_audio_effects(c, bytes_to_seconds(frame->pts, frame->channels, frame->sample_rate), frame, nb_bytes, nests);
+ c->frame->pts += nb_bytes;
+ c->frame_sample_index = 0;
+ if (c->audio_buffer_write == 0) {
+ c->audio_buffer_write = get_buffer_offset_from_frame(last_fr, qMax(timeline_in, target_frame));
+ }
+ int offset = audio_ibuffer_read - c->audio_buffer_write;
+ if (offset > 0) {
+ c->audio_buffer_write += offset;
+ c->frame_sample_index += offset;
+ }
+ }
+ } else if (c->media->get_type() == MEDIA_TYPE_FOOTAGE) {
+ double timebase = av_q2d(c->stream->time_base);
- frame = c->queue.at(0);
+ frame = c->queue.at(0);
- // retrieve frame
- bool new_frame = false;
- while ((c->frame_sample_index == -1 || c->frame_sample_index >= nb_bytes) && nb_bytes > 0) {
- // no more audio left in frame, get a new one
- if (!c->reached_end) {
- int loop = 0;
+ // retrieve frame
+ bool new_frame = false;
+ while ((c->frame_sample_index == -1 || c->frame_sample_index >= nb_bytes) && nb_bytes > 0) {
+ // no more audio left in frame, get a new one
+ if (!c->reached_end) {
+ int loop = 0;
- if (reverse_audio && !c->audio_just_reset) {
- avcodec_flush_buffers(c->codecCtx);
- c->reached_end = false;
- int64_t backtrack_seek = qMax(c->reverse_target - static_cast(av_q2d(av_inv_q(c->stream->time_base))), static_cast(0));
- av_seek_frame(c->formatCtx, c->stream->index, backtrack_seek, AVSEEK_FLAG_BACKWARD);
+ if (reverse_audio && !c->audio_just_reset) {
+ avcodec_flush_buffers(c->codecCtx);
+ c->reached_end = false;
+ int64_t backtrack_seek = qMax(c->reverse_target - static_cast(av_q2d(av_inv_q(c->stream->time_base))), static_cast(0));
+ av_seek_frame(c->formatCtx, c->stream->index, backtrack_seek, AVSEEK_FLAG_BACKWARD);
#ifdef AUDIOWARNINGS
- if (backtrack_seek == 0) {
- dout << "backtracked to 0";
- }
+ if (backtrack_seek == 0) {
+ dout << "backtracked to 0";
+ }
#endif
- }
+ }
- do {
- av_frame_unref(frame);
+ do {
+ av_frame_unref(frame);
- int ret;
+ int ret;
- while ((ret = av_buffersink_get_frame(c->buffersink_ctx, frame)) == AVERROR(EAGAIN)) {
- ret = retrieve_next_frame(c, c->frame);
- if (ret >= 0) {
- if ((ret = av_buffersrc_add_frame_flags(c->buffersrc_ctx, c->frame, AV_BUFFERSRC_FLAG_KEEP_REF)) < 0) {
- qCritical() << "Could not feed filtergraph -" << ret;
- break;
- }
- } else {
- if (ret == AVERROR_EOF) {
+ while ((ret = av_buffersink_get_frame(c->buffersink_ctx, frame)) == AVERROR(EAGAIN)) {
+ ret = retrieve_next_frame(c, c->frame);
+ if (ret >= 0) {
+ if ((ret = av_buffersrc_add_frame_flags(c->buffersrc_ctx, c->frame, AV_BUFFERSRC_FLAG_KEEP_REF)) < 0) {
+ qCritical() << "Could not feed filtergraph -" << ret;
+ break;
+ }
+ } else {
+ if (ret == AVERROR_EOF) {
#ifdef AUDIOWARNINGS
- dout << "reached EOF while reading";
+ dout << "reached EOF while reading";
#endif
- // TODO revise usage of reached_end in audio
- if (!reverse_audio) {
- c->reached_end = true;
- } else {
- }
- } else {
- qWarning() << "Raw audio frame data could not be retrieved." << ret;
- c->reached_end = true;
- }
- break;
- }
- }
+ // TODO revise usage of reached_end in audio
+ if (!reverse_audio) {
+ c->reached_end = true;
+ } else {
+ }
+ } else {
+ qWarning() << "Raw audio frame data could not be retrieved." << ret;
+ c->reached_end = true;
+ }
+ break;
+ }
+ }
- if (ret < 0) {
- if (ret != AVERROR_EOF) {
- qCritical() << "Could not pull from filtergraph";
- c->reached_end = true;
- break;
- } else {
+ if (ret < 0) {
+ if (ret != AVERROR_EOF) {
+ qCritical() << "Could not pull from filtergraph";
+ c->reached_end = true;
+ break;
+ } else {
#ifdef AUDIOWARNINGS
- dout << "reached EOF while pulling from filtergraph";
+ dout << "reached EOF while pulling from filtergraph";
#endif
- if (!reverse_audio) break;
- }
- }
+ if (!reverse_audio) break;
+ }
+ }
- if (reverse_audio) {
- if (loop > 1) {
- AVFrame* rev_frame = c->queue.at(1);
- if (ret != AVERROR_EOF) {
- if (loop == 2) {
+ if (reverse_audio) {
+ if (loop > 1) {
+ AVFrame* rev_frame = c->queue.at(1);
+ if (ret != AVERROR_EOF) {
+ if (loop == 2) {
#ifdef AUDIOWARNINGS
- dout << "starting rev_frame";
+ dout << "starting rev_frame";
#endif
- rev_frame->nb_samples = 0;
- rev_frame->pts = c->frame->pkt_pts;
- }
- int offset = rev_frame->nb_samples * av_get_bytes_per_sample(static_cast(rev_frame->format)) * rev_frame->channels;
+ rev_frame->nb_samples = 0;
+ rev_frame->pts = c->frame->pkt_pts;
+ }
+ int offset = rev_frame->nb_samples * av_get_bytes_per_sample(static_cast(rev_frame->format)) * rev_frame->channels;
#ifdef AUDIOWARNINGS
- dout << "offset 1:" << offset;
- dout << "retrieved samples:" << frame->nb_samples << "size:" << (frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels);
+ dout << "offset 1:" << offset;
+ dout << "retrieved samples:" << frame->nb_samples << "size:" << (frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels);
#endif
- memcpy(
- rev_frame->data[0]+offset,
- frame->data[0],
- (frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels)
- );
+ memcpy(
+ rev_frame->data[0]+offset,
+ frame->data[0],
+ (frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels)
+ );
#ifdef AUDIOWARNINGS
- dout << "pts:" << c->frame->pts << "dur:" << c->frame->pkt_duration << "rev_target:" << c->reverse_target << "offset:" << offset << "limit:" << rev_frame->linesize[0];
+ dout << "pts:" << c->frame->pts << "dur:" << c->frame->pkt_duration << "rev_target:" << c->reverse_target << "offset:" << offset << "limit:" << rev_frame->linesize[0];
#endif
- }
+ }
- rev_frame->nb_samples += frame->nb_samples;
+ rev_frame->nb_samples += frame->nb_samples;
- if ((c->frame->pts >= c->reverse_target) || (ret == AVERROR_EOF)) {
-/*
+ if ((c->frame->pts >= c->reverse_target) || (ret == AVERROR_EOF)) {
+ /*
#ifdef AUDIOWARNINGS
- dout << "time for the end of rev cache" << rev_frame->nb_samples << c->rev_target << c->frame->pts << c->frame->pkt_duration << c->frame->nb_samples;
- dout << "diff:" << (c->frame->pkt_pts + c->frame->pkt_duration) - c->rev_target;
+ dout << "time for the end of rev cache" << rev_frame->nb_samples << c->rev_target << c->frame->pts << c->frame->pkt_duration << c->frame->nb_samples;
+ dout << "diff:" << (c->frame->pkt_pts + c->frame->pkt_duration) - c->rev_target;
#endif
- int cutoff = qRound64((((c->frame->pkt_pts + c->frame->pkt_duration) - c->reverse_target) * timebase) * audio_output->format().sampleRate());
- if (cutoff > 0) {
+ int cutoff = qRound64((((c->frame->pkt_pts + c->frame->pkt_duration) - c->reverse_target) * timebase) * audio_output->format().sampleRate());
+ if (cutoff > 0) {
#ifdef AUDIOWARNINGS
- dout << "cut off" << cutoff << "samples (rate:" << audio_output->format().sampleRate() << ")";
+ dout << "cut off" << cutoff << "samples (rate:" << audio_output->format().sampleRate() << ")";
#endif
- rev_frame->nb_samples -= cutoff;
- }
+ rev_frame->nb_samples -= cutoff;
+ }
*/
#ifdef AUDIOWARNINGS
- dout << "pre cutoff deets::: rev_frame.pts:" << rev_frame->pts << "rev_frame.nb_samples" << rev_frame->nb_samples << "rev_target:" << c->reverse_target;
+ dout << "pre cutoff deets::: rev_frame.pts:" << rev_frame->pts << "rev_frame.nb_samples" << rev_frame->nb_samples << "rev_target:" << c->reverse_target;
#endif
- double playback_speed = c->speed * c->media->to_footage()->speed;
- rev_frame->nb_samples = qRound64(static_cast(c->reverse_target - rev_frame->pts) / c->stream->codecpar->sample_rate * (current_audio_freq() / playback_speed));
+ double playback_speed = c->speed * c->media->to_footage()->speed;
+ rev_frame->nb_samples = qRound64(static_cast(c->reverse_target - rev_frame->pts) / c->stream->codecpar->sample_rate * (current_audio_freq() / playback_speed));
#ifdef AUDIOWARNINGS
- dout << "post cutoff deets::" << rev_frame->nb_samples;
+ dout << "post cutoff deets::" << rev_frame->nb_samples;
#endif
- int frame_size = rev_frame->nb_samples * rev_frame->channels * av_get_bytes_per_sample(static_cast(rev_frame->format));
- int half_frame_size = frame_size >> 1;
+ int frame_size = rev_frame->nb_samples * rev_frame->channels * av_get_bytes_per_sample(static_cast(rev_frame->format));
+ int half_frame_size = frame_size >> 1;
- int sample_size = rev_frame->channels*av_get_bytes_per_sample(static_cast(rev_frame->format));
- char* temp_chars = new char[sample_size];
- for (int i=0;idata[0][i+j];
- }
- for (int j=0;jdata[0][i+j] = rev_frame->data[0][frame_size-i-sample_size+j];
- }
- for (int j=0;jdata[0][frame_size-i-sample_size+j] = temp_chars[j];
- }
- }
- delete [] temp_chars;
+ int sample_size = rev_frame->channels*av_get_bytes_per_sample(static_cast(rev_frame->format));
+ char* temp_chars = new char[sample_size];
+ for (int i=0;idata[0][i+j];
+ }
+ for (int j=0;jdata[0][i+j] = rev_frame->data[0][frame_size-i-sample_size+j];
+ }
+ for (int j=0;jdata[0][frame_size-i-sample_size+j] = temp_chars[j];
+ }
+ }
+ delete [] temp_chars;
- c->reverse_target = rev_frame->pts;
- frame = rev_frame;
- break;
- }
- }
+ c->reverse_target = rev_frame->pts;
+ frame = rev_frame;
+ break;
+ }
+ }
- loop++;
+ loop++;
#ifdef AUDIOWARNINGS
- dout << "loop" << loop;
+ dout << "loop" << loop;
#endif
- } else {
- frame->pts = c->frame->pts;
- break;
- }
- } while (true);
- } else {
- // if there is no more data in the file, we flush the remainder out of swresample
- break;
- }
+ } else {
+ frame->pts = c->frame->pts;
+ break;
+ }
+ } while (true);
+ } else {
+ // if there is no more data in the file, we flush the remainder out of swresample
+ break;
+ }
- new_frame = true;
+ new_frame = true;
- if (c->frame_sample_index < 0) {
- c->frame_sample_index = 0;
- } else {
- c->frame_sample_index -= nb_bytes;
- }
+ if (c->frame_sample_index < 0) {
+ c->frame_sample_index = 0;
+ } else {
+ c->frame_sample_index -= nb_bytes;
+ }
- nb_bytes = frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels;
+ nb_bytes = frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels;
- if (c->audio_just_reset) {
- // get precise sample offset for the elected clip_in from this audio frame
- double target_sts = playhead_to_clip_seconds(c, c->audio_target_frame);
- double frame_sts = ((frame->pts - c->stream->start_time) * timebase);
- int nb_samples = qRound64((target_sts - frame_sts)*current_audio_freq());
- c->frame_sample_index = nb_samples * 4;
+ if (c->audio_just_reset) {
+ // get precise sample offset for the elected clip_in from this audio frame
+ double target_sts = playhead_to_clip_seconds(c, c->audio_target_frame);
+ double frame_sts = ((frame->pts - c->stream->start_time) * timebase);
+ int nb_samples = qRound64((target_sts - frame_sts)*current_audio_freq());
+ c->frame_sample_index = nb_samples * 4;
#ifdef AUDIOWARNINGS
- dout << "fsts:" << frame_sts << "tsts:" << target_sts << "nbs:" << nb_samples << "nbb:" << nb_bytes << "rev_targetToSec:" << (c->reverse_target * timebase);
- dout << "fsi-calc:" << c->frame_sample_index;
+ dout << "fsts:" << frame_sts << "tsts:" << target_sts << "nbs:" << nb_samples << "nbb:" << nb_bytes << "rev_targetToSec:" << (c->reverse_target * timebase);
+ dout << "fsi-calc:" << c->frame_sample_index;
#endif
- if (reverse_audio) c->frame_sample_index = nb_bytes - c->frame_sample_index;
- c->audio_just_reset = false;
- }
+ if (reverse_audio) c->frame_sample_index = nb_bytes - c->frame_sample_index;
+ c->audio_just_reset = false;
+ }
#ifdef AUDIOWARNINGS
- dout << "fsi-post-post:" << c->frame_sample_index;
+ dout << "fsi-post-post:" << c->frame_sample_index;
#endif
- if (c->audio_buffer_write == 0) {
- c->audio_buffer_write = get_buffer_offset_from_frame(last_fr, qMax(timeline_in, target_frame));
+ if (c->audio_buffer_write == 0) {
+ c->audio_buffer_write = get_buffer_offset_from_frame(last_fr, qMax(timeline_in, target_frame));
- if (frame_skip > 0) {
- int target = get_buffer_offset_from_frame(last_fr, qMax(timeline_in + frame_skip, target_frame));
- c->frame_sample_index += (target - c->audio_buffer_write);
- c->audio_buffer_write = target;
- }
- }
+ if (frame_skip > 0) {
+ int target = get_buffer_offset_from_frame(last_fr, qMax(timeline_in + frame_skip, target_frame));
+ c->frame_sample_index += (target - c->audio_buffer_write);
+ c->audio_buffer_write = target;
+ }
+ }
- int offset = audio_ibuffer_read - c->audio_buffer_write;
- if (offset > 0) {
- c->audio_buffer_write += offset;
- c->frame_sample_index += offset;
- }
+ int offset = audio_ibuffer_read - c->audio_buffer_write;
+ if (offset > 0) {
+ c->audio_buffer_write += offset;
+ c->frame_sample_index += offset;
+ }
- // try to correct negative fsi
- if (c->frame_sample_index < 0) {
- c->audio_buffer_write -= c->frame_sample_index;
- c->frame_sample_index = 0;
- }
- }
+ // try to correct negative fsi
+ if (c->frame_sample_index < 0) {
+ c->audio_buffer_write -= c->frame_sample_index;
+ c->frame_sample_index = 0;
+ }
+ }
- if (reverse_audio) frame = c->queue.at(1);
+ if (reverse_audio) frame = c->queue.at(1);
#ifdef AUDIOWARNINGS
- dout << "j" << c->frame_sample_index << nb_bytes;
+ dout << "j" << c->frame_sample_index << nb_bytes;
#endif
- // apply any audio effects to the data
- if (nb_bytes == INT_MAX) nb_bytes = frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels;
- if (new_frame) {
- apply_audio_effects(c, bytes_to_seconds(c->audio_buffer_write, 2, current_audio_freq()) + audio_ibuffer_timecode + ((double)c->get_clip_in_with_transition()/c->sequence->frame_rate) - ((double)timeline_in/last_fr), frame, nb_bytes, nests);
- }
- } else {
- // shouldn't ever get here
- qCritical() << "Tried to cache a non-footage/tone clip";
- return;
- }
+ // apply any audio effects to the data
+ if (nb_bytes == INT_MAX) nb_bytes = frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels;
+ if (new_frame) {
+ apply_audio_effects(c, bytes_to_seconds(c->audio_buffer_write, 2, current_audio_freq()) + audio_ibuffer_timecode + ((double)c->get_clip_in_with_transition()/c->sequence->frame_rate) - ((double)timeline_in/last_fr), frame, nb_bytes, nests);
+ }
+ } else {
+ // shouldn't ever get here
+ qCritical() << "Tried to cache a non-footage/tone clip";
+ return;
+ }
- // mix audio into internal buffer
- if (frame->nb_samples == 0) {
- break;
- } else {
- qint64 buffer_timeline_out = get_buffer_offset_from_frame(c->sequence->frame_rate, timeline_out);
+ // mix audio into internal buffer
+ if (frame->nb_samples == 0) {
+ break;
+ } else {
+ qint64 buffer_timeline_out = get_buffer_offset_from_frame(c->sequence->frame_rate, timeline_out);
- audio_write_lock.lock();
+ audio_write_lock.lock();
- int sample_skip = 4*qMax(0, qAbs(playback_speed)-1);
- int sample_byte_size = av_get_bytes_per_sample(static_cast(frame->format));
+ int sample_skip = 4*qMax(0, qAbs(playback_speed)-1);
+ int sample_byte_size = av_get_bytes_per_sample(static_cast(frame->format));
- while (c->frame_sample_index < nb_bytes
- && c->audio_buffer_write < audio_ibuffer_read+(audio_ibuffer_size>>1)
- && c->audio_buffer_write < buffer_timeline_out) {
- for (int i=0;ichannels;i++) {
- int upper_byte_index = (c->audio_buffer_write+1)%audio_ibuffer_size;
- int lower_byte_index = (c->audio_buffer_write)%audio_ibuffer_size;
- qint16 old_sample = static_cast((audio_ibuffer[upper_byte_index] & 0xFF) << 8 | (audio_ibuffer[lower_byte_index] & 0xFF));
- qint16 new_sample = static_cast((frame->data[0][c->frame_sample_index+1] & 0xFF) << 8 | (frame->data[0][c->frame_sample_index] & 0xFF));
- qint16 mixed_sample = mix_audio_sample(old_sample, new_sample);
+ while (c->frame_sample_index < nb_bytes
+ && c->audio_buffer_write < audio_ibuffer_read+(audio_ibuffer_size>>1)
+ && c->audio_buffer_write < buffer_timeline_out) {
+ for (int i=0;ichannels;i++) {
+ int upper_byte_index = (c->audio_buffer_write+1)%audio_ibuffer_size;
+ int lower_byte_index = (c->audio_buffer_write)%audio_ibuffer_size;
+ qint16 old_sample = static_cast((audio_ibuffer[upper_byte_index] & 0xFF) << 8 | (audio_ibuffer[lower_byte_index] & 0xFF));
+ qint16 new_sample = static_cast((frame->data[0][c->frame_sample_index+1] & 0xFF) << 8 | (frame->data[0][c->frame_sample_index] & 0xFF));
+ qint16 mixed_sample = mix_audio_sample(old_sample, new_sample);
- audio_ibuffer[upper_byte_index] = quint8((mixed_sample >> 8) & 0xFF);
- audio_ibuffer[lower_byte_index] = quint8(mixed_sample & 0xFF);
+ audio_ibuffer[upper_byte_index] = quint8((mixed_sample >> 8) & 0xFF);
+ audio_ibuffer[lower_byte_index] = quint8(mixed_sample & 0xFF);
- c->audio_buffer_write+=sample_byte_size;
- c->frame_sample_index+=sample_byte_size;
- }
+ c->audio_buffer_write+=sample_byte_size;
+ c->frame_sample_index+=sample_byte_size;
+ }
- c->frame_sample_index += sample_skip;
+ c->frame_sample_index += sample_skip;
- if (c->audio_reset) break;
- }
+ if (c->audio_reset) break;
+ }
#ifdef AUDIOWARNINGS
- if (c->audio_buffer_write >= buffer_timeline_out) dout << "timeline out at fsi" << c->frame_sample_index << "of frame ts" << c->frame->pts;
+ if (c->audio_buffer_write >= buffer_timeline_out) dout << "timeline out at fsi" << c->frame_sample_index << "of frame ts" << c->frame->pts;
#endif
- audio_write_lock.unlock();
+ audio_write_lock.unlock();
- if (c->audio_reset) return;
+ if (c->audio_reset) return;
- if (scrubbing) {
- if (audio_thread != nullptr) audio_thread->notifyReceiver();
- }
+ if (scrubbing) {
+ if (audio_thread != nullptr) audio_thread->notifyReceiver();
+ }
- if (c->frame_sample_index >= nb_bytes) {
- c->frame_sample_index = -1;
- } else {
- // assume we have no more data to send
- break;
- }
+ if (c->frame_sample_index >= nb_bytes) {
+ c->frame_sample_index = -1;
+ } else {
+ // assume we have no more data to send
+ break;
+ }
-// dout << "ended" << c->frame_sample_index << nb_bytes;
- }
- if (c->reached_end) {
- frame->nb_samples = 0;
- }
- if (scrubbing) {
- break;
- }
- }
+ // dout << "ended" << c->frame_sample_index << nb_bytes;
+ }
+ if (c->reached_end) {
+ frame->nb_samples = 0;
+ }
+ if (scrubbing) {
+ break;
+ }
+ }
- QMetaObject::invokeMethod(panel_footage_viewer, "play_wake", Qt::QueuedConnection);
- QMetaObject::invokeMethod(panel_sequence_viewer, "play_wake", Qt::QueuedConnection);
+ QMetaObject::invokeMethod(panel_footage_viewer, "play_wake", Qt::QueuedConnection);
+ QMetaObject::invokeMethod(panel_sequence_viewer, "play_wake", Qt::QueuedConnection);
}
void cache_video_worker(ClipPtr c, long playhead) {
- int read_ret, send_ret, retr_ret;
+ int read_ret, send_ret, retr_ret;
- int64_t target_pts = seconds_to_timestamp(c, playhead_to_clip_seconds(c, playhead));
+ int64_t target_pts = seconds_to_timestamp(c, playhead_to_clip_seconds(c, playhead));
- int limit = c->max_queue_size;
- if (c->ignore_reverse) {
- // waiting for one frame
- limit = c->queue.size() + 1;
- } else if (c->reverse) {
- limit *= 2;
- }
+ int limit = c->max_queue_size;
+ if (c->ignore_reverse) {
+ // waiting for one frame
+ limit = c->queue.size() + 1;
+ } else if (c->reverse) {
+ limit *= 2;
+ }
- if (c->queue.size() < limit) {
- bool reverse = (c->reverse && !c->ignore_reverse);
- c->ignore_reverse = false;
+ if (c->queue.size() < limit) {
+ bool reverse = (c->reverse && !c->ignore_reverse);
+ c->ignore_reverse = false;
- int64_t smallest_pts = INT64_MAX;
- if (reverse && c->queue.size() > 0) {
- int64_t quarter_sec = qRound64(av_q2d(av_inv_q(c->stream->time_base))) >> 2;
- for (int i=0;iqueue.size();i++) {
- smallest_pts = qMin(smallest_pts, c->queue.at(i)->pts);
- }
- avcodec_flush_buffers(c->codecCtx);
- c->reached_end = false;
- int64_t seek_ts = qMax(static_cast(0), smallest_pts - quarter_sec);
- av_seek_frame(c->formatCtx, c->stream->index, seek_ts, AVSEEK_FLAG_BACKWARD);
- } else {
- smallest_pts = target_pts;
- }
+ int64_t smallest_pts = INT64_MAX;
+ if (reverse && c->queue.size() > 0) {
+ int64_t quarter_sec = qRound64(av_q2d(av_inv_q(c->stream->time_base))) >> 2;
+ for (int i=0;iqueue.size();i++) {
+ smallest_pts = qMin(smallest_pts, c->queue.at(i)->pts);
+ }
+ avcodec_flush_buffers(c->codecCtx);
+ c->reached_end = false;
+ int64_t seek_ts = qMax(static_cast(0), smallest_pts - quarter_sec);
+ av_seek_frame(c->formatCtx, c->stream->index, seek_ts, AVSEEK_FLAG_BACKWARD);
+ } else {
+ smallest_pts = target_pts;
+ }
- if (c->multithreaded && c->cacher->interrupt) { // ignore interrupts for now
- c->cacher->interrupt = false;
- }
+ if (c->multithreaded && c->cacher->interrupt) { // ignore interrupts for now
+ c->cacher->interrupt = false;
+ }
- while (true) {
- AVFrame* frame = av_frame_alloc();
+ while (true) {
+ AVFrame* frame = av_frame_alloc();
- FootagePtr media = c->media->to_footage();
- const FootageStream* ms = media->get_stream_from_file_index(true, c->media_stream);
+ FootagePtr media = c->media->to_footage();
+ const FootageStream* ms = media->get_stream_from_file_index(true, c->media_stream);
- while ((retr_ret = av_buffersink_get_frame(c->buffersink_ctx, frame)) == AVERROR(EAGAIN)) {
- if (c->multithreaded && c->cacher->interrupt) return; // abort
+ while ((retr_ret = av_buffersink_get_frame(c->buffersink_ctx, frame)) == AVERROR(EAGAIN)) {
+ if (c->multithreaded && c->cacher->interrupt) return; // abort
- AVFrame* send_frame = c->frame;
-// qint64 time = QDateTime::currentMSecsSinceEpoch();
- read_ret = (c->use_existing_frame) ? 0 : retrieve_next_frame(c, send_frame);
-// dout << QDateTime::currentMSecsSinceEpoch() - time;
- c->use_existing_frame = false;
- if (read_ret >= 0) {
- bool send_it = true;
+ AVFrame* send_frame = c->frame;
+ // qint64 time = QDateTime::currentMSecsSinceEpoch();
+ read_ret = (c->use_existing_frame) ? 0 : retrieve_next_frame(c, send_frame);
+ // dout << QDateTime::currentMSecsSinceEpoch() - time;
+ c->use_existing_frame = false;
+ if (read_ret >= 0) {
+ bool send_it = true;
- /*if (reverse) {
- send_it = true;
- } else if (send_frame->pts > target_pts - eighth_second) {
- send_it = true;
- } else if (media->get_stream_from_file_index(true, c->media_stream)->infinite_length) {
- send_it = true;
- } else {
- dout << "skipped adding a frame to the queue - fpts:" << send_frame->pts << "target:" << target_pts;
- }*/
+ /*if (reverse) {
+ send_it = true;
+ } else if (send_frame->pts > target_pts - eighth_second) {
+ send_it = true;
+ } else if (media->get_stream_from_file_index(true, c->media_stream)->infinite_length) {
+ send_it = true;
+ } else {
+ dout << "skipped adding a frame to the queue - fpts:" << send_frame->pts << "target:" << target_pts;
+ }*/
- if (send_it) {
- if ((send_ret = av_buffersrc_add_frame_flags(c->buffersrc_ctx, send_frame, AV_BUFFERSRC_FLAG_KEEP_REF)) < 0) {
- qCritical() << "Failed to add frame to buffer source." << send_ret;
- break;
- }
- }
+ if (send_it) {
+ if ((send_ret = av_buffersrc_add_frame_flags(c->buffersrc_ctx, send_frame, AV_BUFFERSRC_FLAG_KEEP_REF)) < 0) {
+ qCritical() << "Failed to add frame to buffer source." << send_ret;
+ break;
+ }
+ }
- av_frame_unref(c->frame);
- } else {
- if (read_ret == AVERROR_EOF) {
- c->reached_end = true;
- } else {
- qCritical() << "Failed to read frame." << read_ret;
- }
- break;
- }
- }
+ av_frame_unref(c->frame);
+ } else {
+ if (read_ret == AVERROR_EOF) {
+ c->reached_end = true;
+ } else {
+ qCritical() << "Failed to read frame." << read_ret;
+ }
+ break;
+ }
+ }
- if (retr_ret < 0) {
- if (retr_ret == AVERROR_EOF) {
- c->reached_end = true;
- } else {
- qCritical() << "Failed to retrieve frame from buffersink." << retr_ret;
- }
- av_frame_free(&frame);
- break;
- } else {
- if (reverse && ((smallest_pts == target_pts && frame->pts >= smallest_pts) || (smallest_pts != target_pts && frame->pts > smallest_pts))) {
- av_frame_free(&frame);
- break;
- } else {
- // thread-safety while adding frame to the queue
- c->queue_lock.lock();
- c->queue.append(frame);
+ if (retr_ret < 0) {
+ if (retr_ret == AVERROR_EOF) {
+ c->reached_end = true;
+ } else {
+ qCritical() << "Failed to retrieve frame from buffersink." << retr_ret;
+ }
+ av_frame_free(&frame);
+ break;
+ } else {
+ if (reverse && ((smallest_pts == target_pts && frame->pts >= smallest_pts) || (smallest_pts != target_pts && frame->pts > smallest_pts))) {
+ av_frame_free(&frame);
+ break;
+ } else {
+ // thread-safety while adding frame to the queue
+ c->queue_lock.lock();
+ c->queue.append(frame);
- if (!ms->infinite_length && !reverse && c->queue.size() == limit) {
- // see if we got the frame we needed (used for speed ups primarily)
- bool found = false;
- for (int i=0;iqueue.size();i++) {
- if (c->queue.at(i)->pts >= target_pts) {
- found = true;
- break;
- }
- }
- if (found) {
- c->queue_lock.unlock();
- break;
- } else {
- // remove earliest frame and loop to store another
- c->queue_remove_earliest();
- }
- }
- c->queue_lock.unlock();
- }
- }
+ if (!ms->infinite_length && !reverse && c->queue.size() == limit) {
+ // see if we got the frame we needed (used for speed ups primarily)
+ bool found = false;
+ for (int i=0;iqueue.size();i++) {
+ if (c->queue.at(i)->pts >= target_pts) {
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ c->queue_lock.unlock();
+ break;
+ } else {
+ // remove earliest frame and loop to store another
+ c->queue_remove_earliest();
+ }
+ }
+ c->queue_lock.unlock();
+ }
+ }
- if (c->multithreaded && c->cacher->interrupt) { // abort
- return;
- }
- }
- }
+ if (c->multithreaded && c->cacher->interrupt) { // abort
+ return;
+ }
+ }
+ }
}
void reset_cache(ClipPtr c, long target_frame, int playback_speed) {
- // if we seek to a whole other place in the timeline, we'll need to reset the cache with new values
- if (c->media == nullptr) {
- if (c->track >= 0) {
- // tone clip
- c->reached_end = false;
- c->audio_target_frame = target_frame;
- c->frame_sample_index = -1;
- c->frame->pts = 0;
- }
- } else {
- const FootageStream* ms = c->media->to_footage()->get_stream_from_file_index(c->track < 0, c->media_stream);
- if (ms->infinite_length) {
- /*avcodec_flush_buffers(c->codecCtx);
- av_seek_frame(c->formatCtx, ms->file_index, 0, AVSEEK_FLAG_BACKWARD);*/
- c->use_existing_frame = false;
- } else {
- if (c->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
- // clear current queue
- c->queue_lock.lock();
- c->queue_clear();
- c->queue_lock.unlock();
+ // if we seek to a whole other place in the timeline, we'll need to reset the cache with new values
+ if (c->media == nullptr) {
+ if (c->track >= 0) {
+ // tone clip
+ c->reached_end = false;
+ c->audio_target_frame = target_frame;
+ c->frame_sample_index = -1;
+ c->frame->pts = 0;
+ }
+ } else {
+ const FootageStream* ms = c->media->to_footage()->get_stream_from_file_index(c->track < 0, c->media_stream);
+ if (ms->infinite_length) {
+ /*avcodec_flush_buffers(c->codecCtx);
+ av_seek_frame(c->formatCtx, ms->file_index, 0, AVSEEK_FLAG_BACKWARD);*/
+ c->use_existing_frame = false;
+ } else {
+ if (c->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
+ // clear current queue
+ c->queue_lock.lock();
+ c->queue_clear();
+ c->queue_lock.unlock();
- // seeks to nearest keyframe (target_frame represents internal clip frame)
- int64_t target_ts = seconds_to_timestamp(c, playhead_to_clip_seconds(c, target_frame));
- int64_t seek_ts = target_ts;
- int64_t timebase_half_second = qRound64(av_q2d(av_inv_q(c->stream->time_base)));
- if (c->reverse) seek_ts -= timebase_half_second;
+ // seeks to nearest keyframe (target_frame represents internal clip frame)
+ int64_t target_ts = seconds_to_timestamp(c, playhead_to_clip_seconds(c, target_frame));
+ int64_t seek_ts = target_ts;
+ int64_t timebase_half_second = qRound64(av_q2d(av_inv_q(c->stream->time_base)));
+ if (c->reverse) seek_ts -= timebase_half_second;
- while (true) {
- // flush ffmpeg codecs
- avcodec_flush_buffers(c->codecCtx);
- c->reached_end = false;
+ while (true) {
+ // flush ffmpeg codecs
+ avcodec_flush_buffers(c->codecCtx);
+ c->reached_end = false;
- if (seek_ts > 0) {
- av_seek_frame(c->formatCtx, ms->file_index, seek_ts, AVSEEK_FLAG_BACKWARD);
+ if (seek_ts > 0) {
+ av_seek_frame(c->formatCtx, ms->file_index, seek_ts, AVSEEK_FLAG_BACKWARD);
- av_frame_unref(c->frame);
- int ret = retrieve_next_frame(c, c->frame);
- if (ret < 0) {
- qWarning() << "Seeking terminated prematurely";
- break;
- }
- if (c->frame->pts <= target_ts) {
- c->use_existing_frame = true;
- break;
- } else {
- seek_ts -= timebase_half_second;
- }
- } else {
- av_frame_unref(c->frame);
- av_seek_frame(c->formatCtx, ms->file_index, 0, AVSEEK_FLAG_BACKWARD);
- c->use_existing_frame = false;
- break;
- }
- }
- } else if (c->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
- // flush ffmpeg codecs
- avcodec_flush_buffers(c->codecCtx);
- c->reached_end = false;
+ av_frame_unref(c->frame);
+ int ret = retrieve_next_frame(c, c->frame);
+ if (ret < 0) {
+ qWarning() << "Seeking terminated prematurely";
+ break;
+ }
+ if (c->frame->pts <= target_ts) {
+ c->use_existing_frame = true;
+ break;
+ } else {
+ seek_ts -= timebase_half_second;
+ }
+ } else {
+ av_frame_unref(c->frame);
+ av_seek_frame(c->formatCtx, ms->file_index, 0, AVSEEK_FLAG_BACKWARD);
+ c->use_existing_frame = false;
+ break;
+ }
+ }
+ } else if (c->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
+ // flush ffmpeg codecs
+ avcodec_flush_buffers(c->codecCtx);
+ c->reached_end = false;
- // seek (target_frame represents timeline timecode in frames, not clip timecode)
+ // seek (target_frame represents timeline timecode in frames, not clip timecode)
-// bool reverse = (c->reverse != temp_reverse);
+ // bool reverse = (c->reverse != temp_reverse);
- int64_t timestamp = seconds_to_timestamp(c, playhead_to_clip_seconds(c, target_frame));
+ int64_t timestamp = seconds_to_timestamp(c, playhead_to_clip_seconds(c, target_frame));
- bool temp_reverse = (playback_speed < 0);
- if (c->reverse != temp_reverse) {
- c->reverse_target = timestamp;
- timestamp -= av_q2d(av_inv_q(c->stream->time_base));
+ bool temp_reverse = (playback_speed < 0);
+ if (c->reverse != temp_reverse) {
+ c->reverse_target = timestamp;
+ timestamp -= av_q2d(av_inv_q(c->stream->time_base));
#ifdef AUDIOWARNINGS
- dout << "seeking to" << timestamp << "(originally" << c->reverse_target << ")";
- } else {
- dout << "reset called; seeking to" << timestamp;
+ dout << "seeking to" << timestamp << "(originally" << c->reverse_target << ")";
+ } else {
+ dout << "reset called; seeking to" << timestamp;
#endif
- }
- av_seek_frame(c->formatCtx, ms->file_index, timestamp, AVSEEK_FLAG_BACKWARD);
- c->audio_target_frame = target_frame;
- c->frame_sample_index = -1;
- c->audio_just_reset = true;
- }
- }
- }
+ }
+ av_seek_frame(c->formatCtx, ms->file_index, timestamp, AVSEEK_FLAG_BACKWARD);
+ c->audio_target_frame = target_frame;
+ c->frame_sample_index = -1;
+ c->audio_just_reset = true;
+ }
+ }
+ }
}
Cacher::Cacher(ClipPtr c) : clip(c) {}
@@ -676,350 +676,350 @@ Cacher::Cacher(ClipPtr c) : clip(c) {}
AVSampleFormat sample_format = AV_SAMPLE_FMT_S16;
void open_clip_worker(ClipPtr clip) {
- qint64 time_start = QDateTime::currentMSecsSinceEpoch();
+ qint64 time_start = QDateTime::currentMSecsSinceEpoch();
- if (clip->media == nullptr) {
- if (clip->track >= 0) {
- clip->frame = av_frame_alloc();
- clip->frame->format = sample_format;
- clip->frame->channel_layout = clip->sequence->audio_layout;
- clip->frame->channels = av_get_channel_layout_nb_channels(clip->frame->channel_layout);
- clip->frame->sample_rate = current_audio_freq();
- clip->frame->nb_samples = 2048;
- av_frame_make_writable(clip->frame);
- if (av_frame_get_buffer(clip->frame, 0)) {
- qCritical() << "Could not allocate buffer for tone clip";
- }
- clip->audio_reset = true;
- }
- } else if (clip->media->get_type() == MEDIA_TYPE_FOOTAGE) {
- // opens file resource for FFmpeg and prepares Clip struct for playback
- FootagePtr m = clip->media->to_footage();
+ if (clip->media == nullptr) {
+ if (clip->track >= 0) {
+ clip->frame = av_frame_alloc();
+ clip->frame->format = sample_format;
+ clip->frame->channel_layout = clip->sequence->audio_layout;
+ clip->frame->channels = av_get_channel_layout_nb_channels(clip->frame->channel_layout);
+ clip->frame->sample_rate = current_audio_freq();
+ clip->frame->nb_samples = 2048;
+ av_frame_make_writable(clip->frame);
+ if (av_frame_get_buffer(clip->frame, 0)) {
+ qCritical() << "Could not allocate buffer for tone clip";
+ }
+ clip->audio_reset = true;
+ }
+ } else if (clip->media->get_type() == MEDIA_TYPE_FOOTAGE) {
+ // opens file resource for FFmpeg and prepares Clip struct for playback
+ FootagePtr m = clip->media->to_footage();
- // byte array for retriving raw bytes from QString URL
- QByteArray ba;
+ // byte array for retriving raw bytes from QString URL
+ QByteArray ba;
- // do we have a proxy?
- if (m->proxy
- && !m->proxy_path.isEmpty()
- && QFileInfo::exists(m->proxy_path)) {
- ba = m->proxy_path.toUtf8();
- } else {
- ba = m->url.toUtf8();
- }
+ // do we have a proxy?
+ if (m->proxy
+ && !m->proxy_path.isEmpty()
+ && QFileInfo::exists(m->proxy_path)) {
+ ba = m->proxy_path.toUtf8();
+ } else {
+ ba = m->url.toUtf8();
+ }
- const char* filename = ba.constData();
- const FootageStream* ms = m->get_stream_from_file_index(clip->track < 0, clip->media_stream);
+ const char* filename = ba.constData();
+ const FootageStream* ms = m->get_stream_from_file_index(clip->track < 0, clip->media_stream);
- int errCode = avformat_open_input(
- &clip->formatCtx,
- filename,
- nullptr,
- nullptr
- );
- if (errCode != 0) {
- char err[1024];
- av_strerror(errCode, err, 1024);
- qCritical() << "Could not open" << filename << "-" << err;
- return;
- }
+ int errCode = avformat_open_input(
+ &clip->formatCtx,
+ filename,
+ nullptr,
+ nullptr
+ );
+ if (errCode != 0) {
+ char err[1024];
+ av_strerror(errCode, err, 1024);
+ qCritical() << "Could not open" << filename << "-" << err;
+ return;
+ }
- errCode = avformat_find_stream_info(clip->formatCtx, nullptr);
- if (errCode < 0) {
- char err[1024];
- av_strerror(errCode, err, 1024);
- qCritical() << "Could not open" << filename << "-" << err;
- return;
- }
+ errCode = avformat_find_stream_info(clip->formatCtx, nullptr);
+ if (errCode < 0) {
+ char err[1024];
+ av_strerror(errCode, err, 1024);
+ qCritical() << "Could not open" << filename << "-" << err;
+ return;
+ }
- av_dump_format(clip->formatCtx, 0, filename, 0);
+ av_dump_format(clip->formatCtx, 0, filename, 0);
- clip->stream = clip->formatCtx->streams[ms->file_index];
- clip->codec = avcodec_find_decoder(clip->stream->codecpar->codec_id);
- clip->codecCtx = avcodec_alloc_context3(clip->codec);
- avcodec_parameters_to_context(clip->codecCtx, clip->stream->codecpar);
+ clip->stream = clip->formatCtx->streams[ms->file_index];
+ clip->codec = avcodec_find_decoder(clip->stream->codecpar->codec_id);
+ clip->codecCtx = avcodec_alloc_context3(clip->codec);
+ avcodec_parameters_to_context(clip->codecCtx, clip->stream->codecpar);
- if (ms->infinite_length) {
- clip->max_queue_size = 1;
- } else {
- clip->max_queue_size = 0;
- if (olive::CurrentConfig.upcoming_queue_type == FRAME_QUEUE_TYPE_FRAMES) {
- clip->max_queue_size += qCeil(olive::CurrentConfig.upcoming_queue_size);
- } else {
- clip->max_queue_size += qCeil(ms->video_frame_rate * m->speed * olive::CurrentConfig.upcoming_queue_size);
- }
- if (olive::CurrentConfig.previous_queue_type == FRAME_QUEUE_TYPE_FRAMES) {
- clip->max_queue_size += qCeil(olive::CurrentConfig.previous_queue_size);
- } else {
- clip->max_queue_size += qCeil(ms->video_frame_rate * m->speed * olive::CurrentConfig.previous_queue_size);
- }
- }
+ if (ms->infinite_length) {
+ clip->max_queue_size = 1;
+ } else {
+ clip->max_queue_size = 0;
+ if (olive::CurrentConfig.upcoming_queue_type == FRAME_QUEUE_TYPE_FRAMES) {
+ clip->max_queue_size += qCeil(olive::CurrentConfig.upcoming_queue_size);
+ } else {
+ clip->max_queue_size += qCeil(ms->video_frame_rate * m->speed * olive::CurrentConfig.upcoming_queue_size);
+ }
+ if (olive::CurrentConfig.previous_queue_type == FRAME_QUEUE_TYPE_FRAMES) {
+ clip->max_queue_size += qCeil(olive::CurrentConfig.previous_queue_size);
+ } else {
+ clip->max_queue_size += qCeil(ms->video_frame_rate * m->speed * olive::CurrentConfig.previous_queue_size);
+ }
+ }
- if (ms->video_interlacing != VIDEO_PROGRESSIVE) clip->max_queue_size *= 2;
+ if (ms->video_interlacing != VIDEO_PROGRESSIVE) clip->max_queue_size *= 2;
- clip->opts = nullptr;
+ clip->opts = nullptr;
- // enable multithreading on decoding
- av_dict_set(&clip->opts, "threads", "auto", 0);
+ // enable multithreading on decoding
+ av_dict_set(&clip->opts, "threads", "auto", 0);
- // enable extra optimization code on h264 (not even sure if they help)
- if (clip->stream->codecpar->codec_id == AV_CODEC_ID_H264) {
- av_dict_set(&clip->opts, "tune", "fastdecode", 0);
- av_dict_set(&clip->opts, "tune", "zerolatency", 0);
- }
+ // enable extra optimization code on h264 (not even sure if they help)
+ if (clip->stream->codecpar->codec_id == AV_CODEC_ID_H264) {
+ av_dict_set(&clip->opts, "tune", "fastdecode", 0);
+ av_dict_set(&clip->opts, "tune", "zerolatency", 0);
+ }
- // Open codec
- if (avcodec_open2(clip->codecCtx, clip->codec, &clip->opts) < 0) {
- qCritical() << "Could not open codec";
- }
+ // Open codec
+ if (avcodec_open2(clip->codecCtx, clip->codec, &clip->opts) < 0) {
+ qCritical() << "Could not open codec";
+ }
- // allocate filtergraph
- clip->filter_graph = avfilter_graph_alloc();
- if (clip->filter_graph == nullptr) {
- qCritical() << "Could not create filtergraph";
- }
- char filter_args[512];
+ // allocate filtergraph
+ clip->filter_graph = avfilter_graph_alloc();
+ if (clip->filter_graph == nullptr) {
+ qCritical() << "Could not create filtergraph";
+ }
+ char filter_args[512];
- if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
- snprintf(filter_args, sizeof(filter_args), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
- clip->stream->codecpar->width,
- clip->stream->codecpar->height,
- clip->stream->codecpar->format,
- clip->stream->time_base.num,
- clip->stream->time_base.den,
- clip->stream->codecpar->sample_aspect_ratio.num,
- clip->stream->codecpar->sample_aspect_ratio.den
- );
+ if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
+ snprintf(filter_args, sizeof(filter_args), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
+ clip->stream->codecpar->width,
+ clip->stream->codecpar->height,
+ clip->stream->codecpar->format,
+ clip->stream->time_base.num,
+ clip->stream->time_base.den,
+ clip->stream->codecpar->sample_aspect_ratio.num,
+ clip->stream->codecpar->sample_aspect_ratio.den
+ );
- avfilter_graph_create_filter(&clip->buffersrc_ctx, avfilter_get_by_name("buffer"), "in", filter_args, nullptr, clip->filter_graph);
- avfilter_graph_create_filter(&clip->buffersink_ctx, avfilter_get_by_name("buffersink"), "out", nullptr, nullptr, clip->filter_graph);
+ avfilter_graph_create_filter(&clip->buffersrc_ctx, avfilter_get_by_name("buffer"), "in", filter_args, nullptr, clip->filter_graph);
+ avfilter_graph_create_filter(&clip->buffersink_ctx, avfilter_get_by_name("buffersink"), "out", nullptr, nullptr, clip->filter_graph);
- AVFilterContext* last_filter = clip->buffersrc_ctx;
+ AVFilterContext* last_filter = clip->buffersrc_ctx;
- char filter_args[100];
+ char filter_args[100];
- if (ms->video_interlacing != VIDEO_PROGRESSIVE) {
- AVFilterContext* yadif_filter;
- snprintf(filter_args, sizeof(filter_args), "mode=3:parity=%d", ((ms->video_interlacing == VIDEO_TOP_FIELD_FIRST) ? 0 : 1)); // there's a CUDA version if we start using nvdec/nvenc
- avfilter_graph_create_filter(&yadif_filter, avfilter_get_by_name("yadif"), "yadif", filter_args, nullptr, clip->filter_graph);
+ if (ms->video_interlacing != VIDEO_PROGRESSIVE) {
+ AVFilterContext* yadif_filter;
+ snprintf(filter_args, sizeof(filter_args), "mode=3:parity=%d", ((ms->video_interlacing == VIDEO_TOP_FIELD_FIRST) ? 0 : 1)); // there's a CUDA version if we start using nvdec/nvenc
+ avfilter_graph_create_filter(&yadif_filter, avfilter_get_by_name("yadif"), "yadif", filter_args, nullptr, clip->filter_graph);
- avfilter_link(last_filter, 0, yadif_filter, 0);
- last_filter = yadif_filter;
- }
+ avfilter_link(last_filter, 0, yadif_filter, 0);
+ last_filter = yadif_filter;
+ }
- // ffmpeg premultiplier
- /*
- if (!clip->media->to_footage()->alpha_is_premultiplied) {
- AVFilterContext* premultiply_filter;
- snprintf(filter_args, sizeof(filter_args), "inplace=1");
- avfilter_graph_create_filter(&premultiply_filter, avfilter_get_by_name("premultiply"), "premultiply", filter_args, nullptr, clip->filter_graph);
+ // ffmpeg premultiplier
+ /*
+ if (!clip->media->to_footage()->alpha_is_premultiplied) {
+ AVFilterContext* premultiply_filter;
+ snprintf(filter_args, sizeof(filter_args), "inplace=1");
+ avfilter_graph_create_filter(&premultiply_filter, avfilter_get_by_name("premultiply"), "premultiply", filter_args, nullptr, clip->filter_graph);
- avfilter_link(last_filter, 0, premultiply_filter, 0);
- last_filter = premultiply_filter;
- }
- */
+ avfilter_link(last_filter, 0, premultiply_filter, 0);
+ last_filter = premultiply_filter;
+ }
+ */
- /* stabilization code */
- /*bool stabilize = false;
- if (stabilize) {
- AVFilterContext* stab_filter;
- int stab_ret = avfilter_graph_create_filter(&stab_filter, avfilter_get_by_name("vidstabtransform"), "vidstab", "input=/media/matt/Home/samples/transforms.trf", nullptr, clip->filter_graph);
+ /* stabilization code */
+ /*bool stabilize = false;
+ if (stabilize) {
+ AVFilterContext* stab_filter;
+ int stab_ret = avfilter_graph_create_filter(&stab_filter, avfilter_get_by_name("vidstabtransform"), "vidstab", "input=/media/matt/Home/samples/transforms.trf", nullptr, clip->filter_graph);
- if (stab_ret < 0) {
- char err[100];
- av_strerror(stab_ret, err, sizeof(err));
- } else {
- avfilter_link(last_filter, 0, stab_filter, 0);
- last_filter = stab_filter;
- }
- }*/
+ if (stab_ret < 0) {
+ char err[100];
+ av_strerror(stab_ret, err, sizeof(err));
+ } else {
+ avfilter_link(last_filter, 0, stab_filter, 0);
+ last_filter = stab_filter;
+ }
+ }*/
- clip->pix_fmt = AV_PIX_FMT_RGBA;
- const char* chosen_format = av_get_pix_fmt_name(static_cast(clip->pix_fmt));
- snprintf(filter_args, sizeof(filter_args), "pix_fmts=%s", chosen_format);
+ clip->pix_fmt = AV_PIX_FMT_RGBA;
+ const char* chosen_format = av_get_pix_fmt_name(static_cast(clip->pix_fmt));
+ snprintf(filter_args, sizeof(filter_args), "pix_fmts=%s", chosen_format);
- AVFilterContext* format_conv;
- avfilter_graph_create_filter(&format_conv, avfilter_get_by_name("format"), "fmt", filter_args, nullptr, clip->filter_graph);
- avfilter_link(last_filter, 0, format_conv, 0);
+ AVFilterContext* format_conv;
+ avfilter_graph_create_filter(&format_conv, avfilter_get_by_name("format"), "fmt", filter_args, nullptr, clip->filter_graph);
+ avfilter_link(last_filter, 0, format_conv, 0);
- avfilter_link(format_conv, 0, clip->buffersink_ctx, 0);
+ avfilter_link(format_conv, 0, clip->buffersink_ctx, 0);
- avfilter_graph_config(clip->filter_graph, nullptr);
- } else if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
- if (clip->codecCtx->channel_layout == 0) clip->codecCtx->channel_layout = av_get_default_channel_layout(clip->stream->codecpar->channels);
+ avfilter_graph_config(clip->filter_graph, nullptr);
+ } else if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
+ if (clip->codecCtx->channel_layout == 0) clip->codecCtx->channel_layout = av_get_default_channel_layout(clip->stream->codecpar->channels);
- // set up cache
- clip->queue.append(av_frame_alloc());
-// if (clip->reverse) {
- if (true) {
- AVFrame* reverse_frame = av_frame_alloc();
+ // set up cache
+ clip->queue.append(av_frame_alloc());
+ // if (clip->reverse) {
+ if (true) {
+ AVFrame* reverse_frame = av_frame_alloc();
- reverse_frame->format = sample_format;
- reverse_frame->nb_samples = current_audio_freq()*2;
- reverse_frame->channel_layout = clip->sequence->audio_layout;
- reverse_frame->channels = av_get_channel_layout_nb_channels(clip->sequence->audio_layout);
- av_frame_get_buffer(reverse_frame, 0);
+ reverse_frame->format = sample_format;
+ reverse_frame->nb_samples = current_audio_freq()*2;
+ reverse_frame->channel_layout = clip->sequence->audio_layout;
+ reverse_frame->channels = av_get_channel_layout_nb_channels(clip->sequence->audio_layout);
+ av_frame_get_buffer(reverse_frame, 0);
- clip->queue.append(reverse_frame);
- }
+ clip->queue.append(reverse_frame);
+ }
- snprintf(filter_args, sizeof(filter_args), "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%" PRIx64,
- clip->stream->time_base.num,
- clip->stream->time_base.den,
- clip->stream->codecpar->sample_rate,
- av_get_sample_fmt_name(clip->codecCtx->sample_fmt),
- clip->codecCtx->channel_layout
- );
+ snprintf(filter_args, sizeof(filter_args), "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%" PRIx64,
+ clip->stream->time_base.num,
+ clip->stream->time_base.den,
+ clip->stream->codecpar->sample_rate,
+ av_get_sample_fmt_name(clip->codecCtx->sample_fmt),
+ clip->codecCtx->channel_layout
+ );
- avfilter_graph_create_filter(&clip->buffersrc_ctx, avfilter_get_by_name("abuffer"), "in", filter_args, nullptr, clip->filter_graph);
- avfilter_graph_create_filter(&clip->buffersink_ctx, avfilter_get_by_name("abuffersink"), "out", nullptr, nullptr, clip->filter_graph);
+ avfilter_graph_create_filter(&clip->buffersrc_ctx, avfilter_get_by_name("abuffer"), "in", filter_args, nullptr, clip->filter_graph);
+ avfilter_graph_create_filter(&clip->buffersink_ctx, avfilter_get_by_name("abuffersink"), "out", nullptr, nullptr, clip->filter_graph);
- enum AVSampleFormat sample_fmts[] = { sample_format, static_cast(-1) };
- if (av_opt_set_int_list(clip->buffersink_ctx, "sample_fmts", sample_fmts, -1, AV_OPT_SEARCH_CHILDREN) < 0) {
- qCritical() << "Could not set output sample format";
- }
+ enum AVSampleFormat sample_fmts[] = { sample_format, static_cast(-1) };
+ if (av_opt_set_int_list(clip->buffersink_ctx, "sample_fmts", sample_fmts, -1, AV_OPT_SEARCH_CHILDREN) < 0) {
+ qCritical() << "Could not set output sample format";
+ }
- int64_t channel_layouts[] = { AV_CH_LAYOUT_STEREO, static_cast(-1) };
- if (av_opt_set_int_list(clip->buffersink_ctx, "channel_layouts", channel_layouts, -1, AV_OPT_SEARCH_CHILDREN) < 0) {
- qCritical() << "Could not set output sample format";
- }
+ int64_t channel_layouts[] = { AV_CH_LAYOUT_STEREO, static_cast(-1) };
+ if (av_opt_set_int_list(clip->buffersink_ctx, "channel_layouts", channel_layouts, -1, AV_OPT_SEARCH_CHILDREN) < 0) {
+ qCritical() << "Could not set output sample format";
+ }
- int target_sample_rate = current_audio_freq();
+ int target_sample_rate = current_audio_freq();
- double playback_speed = clip->speed * m->speed;
+ double playback_speed = clip->speed * m->speed;
- if (qFuzzyCompare(playback_speed, 1.0)) {
- avfilter_link(clip->buffersrc_ctx, 0, clip->buffersink_ctx, 0);
- } else if (clip->maintain_audio_pitch) {
- AVFilterContext* previous_filter = clip->buffersrc_ctx;
- AVFilterContext* last_filter = clip->buffersrc_ctx;
+ if (qFuzzyCompare(playback_speed, 1.0)) {
+ avfilter_link(clip->buffersrc_ctx, 0, clip->buffersink_ctx, 0);
+ } else if (clip->maintain_audio_pitch) {
+ AVFilterContext* previous_filter = clip->buffersrc_ctx;
+ AVFilterContext* last_filter = clip->buffersrc_ctx;
- char speed_param[10];
+ char speed_param[10];
-// if (playback_speed != 1.0) {
- double base = (playback_speed > 1.0) ? 2.0 : 0.5;
+ // if (playback_speed != 1.0) {
+ double base = (playback_speed > 1.0) ? 2.0 : 0.5;
- double speedlog = log(playback_speed) / log(base);
- int whole2 = qFloor(speedlog);
- speedlog -= whole2;
+ double speedlog = log(playback_speed) / log(base);
+ int whole2 = qFloor(speedlog);
+ speedlog -= whole2;
- if (whole2 > 0) {
- snprintf(speed_param, sizeof(speed_param), "%f", base);
- for (int i=0;ifilter_graph);
- avfilter_link(previous_filter, 0, tempo_filter, 0);
- previous_filter = tempo_filter;
- }
- }
+ if (whole2 > 0) {
+ snprintf(speed_param, sizeof(speed_param), "%f", base);
+ for (int i=0;ifilter_graph);
+ avfilter_link(previous_filter, 0, tempo_filter, 0);
+ previous_filter = tempo_filter;
+ }
+ }
- snprintf(speed_param, sizeof(speed_param), "%f", qPow(base, speedlog));
- last_filter = nullptr;
- avfilter_graph_create_filter(&last_filter, avfilter_get_by_name("atempo"), "atempo", speed_param, nullptr, clip->filter_graph);
- avfilter_link(previous_filter, 0, last_filter, 0);
-// }
+ snprintf(speed_param, sizeof(speed_param), "%f", qPow(base, speedlog));
+ last_filter = nullptr;
+ avfilter_graph_create_filter(&last_filter, avfilter_get_by_name("atempo"), "atempo", speed_param, nullptr, clip->filter_graph);
+ avfilter_link(previous_filter, 0, last_filter, 0);
+ // }
- avfilter_link(last_filter, 0, clip->buffersink_ctx, 0);
- } else {
- target_sample_rate = qRound64(target_sample_rate / playback_speed);
- avfilter_link(clip->buffersrc_ctx, 0, clip->buffersink_ctx, 0);
- }
+ avfilter_link(last_filter, 0, clip->buffersink_ctx, 0);
+ } else {
+ target_sample_rate = qRound64(target_sample_rate / playback_speed);
+ avfilter_link(clip->buffersrc_ctx, 0, clip->buffersink_ctx, 0);
+ }
- int sample_rates[] = { target_sample_rate, 0 };
- if (av_opt_set_int_list(clip->buffersink_ctx, "sample_rates", sample_rates, 0, AV_OPT_SEARCH_CHILDREN) < 0) {
- qCritical() << "Could not set output sample rates";
- }
+ int sample_rates[] = { target_sample_rate, 0 };
+ if (av_opt_set_int_list(clip->buffersink_ctx, "sample_rates", sample_rates, 0, AV_OPT_SEARCH_CHILDREN) < 0) {
+ qCritical() << "Could not set output sample rates";
+ }
- avfilter_graph_config(clip->filter_graph, nullptr);
+ avfilter_graph_config(clip->filter_graph, nullptr);
- clip->audio_reset = true;
- }
+ clip->audio_reset = true;
+ }
- clip->frame = av_frame_alloc();
- }
+ clip->frame = av_frame_alloc();
+ }
- for (int i=0;ieffects.size();i++) {
- clip->effects.at(i)->open();
- }
+ for (int i=0;ieffects.size();i++) {
+ clip->effects.at(i)->open();
+ }
- clip->finished_opening = true;
+ clip->finished_opening = true;
- qInfo() << "Clip opened on track" << clip->track << "(took" << (QDateTime::currentMSecsSinceEpoch() - time_start) << "ms)";
+ qInfo() << "Clip opened on track" << clip->track << "(took" << (QDateTime::currentMSecsSinceEpoch() - time_start) << "ms)";
}
void cache_clip_worker(ClipPtr clip, long playhead, bool reset, bool scrubbing, QVector nests, int playback_speed) {
- if (reset) {
- // note: for video, playhead is in "internal clip" frames - for audio, it's the timeline playhead
- reset_cache(clip, playhead, playback_speed);
- clip->audio_reset = false;
- }
+ if (reset) {
+ // note: for video, playhead is in "internal clip" frames - for audio, it's the timeline playhead
+ reset_cache(clip, playhead, playback_speed);
+ clip->audio_reset = false;
+ }
- if (clip->media == nullptr) {
- if (clip->track >= 0) {
- cache_audio_worker(clip, scrubbing, nests, playback_speed);
- }
- } else if (clip->media->get_type() == MEDIA_TYPE_FOOTAGE) {
- if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
- cache_video_worker(clip, playhead);
- } else if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
- cache_audio_worker(clip, scrubbing, nests, playback_speed);
- }
- }
+ if (clip->media == nullptr) {
+ if (clip->track >= 0) {
+ cache_audio_worker(clip, scrubbing, nests, playback_speed);
+ }
+ } else if (clip->media->get_type() == MEDIA_TYPE_FOOTAGE) {
+ if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
+ cache_video_worker(clip, playhead);
+ } else if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
+ cache_audio_worker(clip, scrubbing, nests, playback_speed);
+ }
+ }
}
void close_clip_worker(ClipPtr clip) {
- clip->finished_opening = false;
+ clip->finished_opening = false;
- if (clip->media != nullptr && clip->media->get_type() == MEDIA_TYPE_FOOTAGE) {
- clip->queue_clear();
+ if (clip->media != nullptr && clip->media->get_type() == MEDIA_TYPE_FOOTAGE) {
+ clip->queue_clear();
- avfilter_graph_free(&clip->filter_graph);
+ avfilter_graph_free(&clip->filter_graph);
- avcodec_close(clip->codecCtx);
- avcodec_free_context(&clip->codecCtx);
+ avcodec_close(clip->codecCtx);
+ avcodec_free_context(&clip->codecCtx);
- av_dict_free(&clip->opts);
+ av_dict_free(&clip->opts);
- avformat_close_input(&clip->formatCtx);
- }
+ avformat_close_input(&clip->formatCtx);
+ }
- av_frame_free(&clip->frame);
+ av_frame_free(&clip->frame);
- clip->reset();
+ clip->reset();
- qInfo() << "Clip closed on track" << clip->track;
+ qInfo() << "Clip closed on track" << clip->track;
}
void Cacher::run() {
- // open_lock is used to prevent the clip from being destroyed before the cacher has closed it properly
- clip->lock.lock();
- clip->finished_opening = false;
- clip->open = true;
- caching = true;
- interrupt = false;
- queued = false;
+ // open_lock is used to prevent the clip from being destroyed before the cacher has closed it properly
+ clip->lock.lock();
+ clip->finished_opening = false;
+ clip->open = true;
+ caching = true;
+ interrupt = false;
+ queued = false;
- open_clip_worker(clip);
+ open_clip_worker(clip);
- while (caching) {
- if (!queued) clip->can_cache.wait(&clip->lock);
- queued = false;
- if (!caching) {
- break;
- } else {
- while (true) {
- cache_clip_worker(clip, playhead, reset, scrubbing, nests, playback_speed);
- if (clip->multithreaded && clip->cacher->interrupt && clip->track < 0) {
- clip->cacher->interrupt = false;
- } else {
- break;
- }
- }
- }
- }
+ while (caching) {
+ if (!queued) clip->can_cache.wait(&clip->lock);
+ queued = false;
+ if (!caching) {
+ break;
+ } else {
+ while (true) {
+ cache_clip_worker(clip, playhead, reset, scrubbing, nests, playback_speed);
+ if (clip->multithreaded && clip->cacher->interrupt && clip->track < 0) {
+ clip->cacher->interrupt = false;
+ } else {
+ break;
+ }
+ }
+ }
+ }
- close_clip_worker(clip);
+ close_clip_worker(clip);
- clip->lock.unlock();
- clip->open_lock.unlock();
+ clip->lock.unlock();
+ clip->open_lock.unlock();
}
diff --git a/playback/playback.cpp b/playback/playback.cpp
index e8225625a..000fb1735 100644
--- a/playback/playback.cpp
+++ b/playback/playback.cpp
@@ -85,6 +85,9 @@ void open_clip(ClipPtr clip, bool multithreaded) {
}
void close_clip(ClipPtr clip, bool wait) {
+ // render lock prevents crashes if this function tries to delete a texture while the RenderThread is rendering it
+ clip->render_lock.lock();
+
clip->finished_opening = false;
// destroy opengl texture in main thread
@@ -126,6 +129,8 @@ void close_clip(ClipPtr clip, bool wait) {
clip->open = false;
}
+
+ clip->render_lock.unlock();
}
void cache_clip(ClipPtr clip, long playhead, bool reset, bool scrubbing, QVector& nests, int playback_speed) {
diff --git a/project/clip.cpp b/project/clip.cpp
index 4b0a2e80c..ca333f977 100644
--- a/project/clip.cpp
+++ b/project/clip.cpp
@@ -85,15 +85,6 @@ ClipPtr Clip::copy(SequencePtr s, bool duplicate_transitions) {
copy->cached_fr = (this->sequence == nullptr) ? cached_fr : this->sequence->frame_rate;
- if (duplicate_transitions) {
- if (get_opening_transition() != nullptr && get_opening_transition()->secondary_clip == nullptr) {
- copy->opening_transition = get_opening_transition()->copy(copy, nullptr);
- }
- if (get_closing_transition() != nullptr && get_closing_transition()->secondary_clip == nullptr) {
- copy->closing_transition = get_closing_transition()->copy(copy, nullptr);
- }
- }
-
copy->recalculateMaxLength();
return copy;
diff --git a/project/clip.h b/project/clip.h
index 0644cf43c..873caad46 100644
--- a/project/clip.h
+++ b/project/clip.h
@@ -129,6 +129,7 @@ public:
int max_queue_size;
QVector queue;
QMutex queue_lock;
+ QMutex render_lock;
QMutex lock;
QMutex open_lock;
int64_t last_invalid_ts;
diff --git a/project/undo.cpp b/project/undo.cpp
index 5a0a998f2..205769599 100644
--- a/project/undo.cpp
+++ b/project/undo.cpp
@@ -284,6 +284,7 @@ void AddTransitionCommand::doRedo() {
ModifyTransitionCommand::ModifyTransitionCommand(TransitionPtr t, long ilength) {
transition_ref_ = t;
new_length_ = ilength;
+ old_length_ = transition_ref_->get_true_length();
}
void ModifyTransitionCommand::doUndo() {
@@ -291,7 +292,7 @@ void ModifyTransitionCommand::doUndo() {
}
void ModifyTransitionCommand::doRedo() {
- old_length_ = transition_ref_->get_true_length();
+
transition_ref_->set_length(new_length_);
}
@@ -395,6 +396,7 @@ void DeleteMediaCommand::doRedo() {
}
AddClipCommand::AddClipCommand(SequencePtr s, QVector& add) {
+ link_offset_ = 0;
seq = s;
clips = add;
}
@@ -402,44 +404,48 @@ AddClipCommand::AddClipCommand(SequencePtr s, QVector& add) {
AddClipCommand::~AddClipCommand() {}
void AddClipCommand::doUndo() {
+ // clear effects panel
panel_effect_controls->clear_effects(true);
+
for (int i=0;iclips.last();
- panel_timeline->deselect_area(c->timeline_in, c->timeline_out, c->track);
- undone_clips.prepend(c);
- if (c->open) close_clip(c, true);
+
+ if (c != nullptr) {
+ // un-offset all the clips
+ for (int j=0;jlinked.size();j++) {
+ c->linked[j] -= link_offset_;
+ }
+
+ // deselect the area occupied by this clip
+ panel_timeline->deselect_area(c->timeline_in, c->timeline_out, c->track);
+
+ // if the clip is open, close it
+ if (c->open) {
+ close_clip(c, true);
+ }
+ }
+
+ // remove it from the sequence
seq->clips.removeLast();
}
}
void AddClipCommand::doRedo() {
- if (undone_clips.size() > 0) {
- for (int i=0;iclips.append(undone_clips.at(i));
- }
- undone_clips.clear();
- } else {
- int linkOffset = seq->clips.size();
- for (int i=0;icopy(seq);
- copy->linked.resize(original->linked.size());
- for (int j=0;jlinked.size();j++) {
- copy->linked[j] = original->linked.at(j) + linkOffset;
- }
- if (original->opening_transition != nullptr) {
- copy->opening_transition = original->get_opening_transition()->copy(copy, nullptr);
- }
- if (original->closing_transition != nullptr) {
- copy->closing_transition = original->get_closing_transition()->copy(copy, nullptr);
- }
- seq->clips.append(copy);
- } else {
- seq->clips.append(nullptr);
+ link_offset_ = seq->clips.size();
+ for (int i=0;ilinked.size();j++) {
+ original->linked[j] += link_offset_;
}
+
}
+
+ seq->clips.append(original);
}
}
diff --git a/project/undo.h b/project/undo.h
index 0ec1d1005..44ff615de 100644
--- a/project/undo.h
+++ b/project/undo.h
@@ -236,7 +236,7 @@ public:
private:
SequencePtr seq;
QVector clips;
- QVector undone_clips;
+ int link_offset_;
};
class LinkCommand : public OliveAction {
diff --git a/ui/renderfunctions.cpp b/ui/renderfunctions.cpp
index d1c732f2a..d6bb29abb 100644
--- a/ui/renderfunctions.cpp
+++ b/ui/renderfunctions.cpp
@@ -44,601 +44,607 @@
#include "panels/viewer.h"
extern "C" {
- #include
+#include
}
void full_blit() {
- glPushMatrix();
- glLoadIdentity();
- glOrtho(0, 1, 0, 1, -1, 1);
+ glPushMatrix();
+ glLoadIdentity();
+ glOrtho(0, 1, 0, 1, -1, 1);
- glBegin(GL_QUADS);
- glTexCoord2f(0, 0); // top left
- glVertex2f(0, 0); // top left
- glTexCoord2f(1, 0); // top right
- glVertex2f(1, 0); // top right
- glTexCoord2f(1, 1); // bottom right
- glVertex2f(1, 1); // bottom right
- glTexCoord2f(0, 1); // bottom left
- glVertex2f(0, 1); // bottom left
- glEnd();
+ glBegin(GL_QUADS);
+ glTexCoord2f(0, 0); // top left
+ glVertex2f(0, 0); // top left
+ glTexCoord2f(1, 0); // top right
+ glVertex2f(1, 0); // top right
+ glTexCoord2f(1, 1); // bottom right
+ glVertex2f(1, 1); // bottom right
+ glTexCoord2f(0, 1); // bottom left
+ glVertex2f(0, 1); // bottom left
+ glEnd();
- glPopMatrix();
+ glPopMatrix();
}
void draw_clip(QOpenGLContext* ctx, GLuint fbo, GLuint texture, bool clear) {
- ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
+ ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
- if (clear) {
- glClear(GL_COLOR_BUFFER_BIT);
- }
+ if (clear) {
+ glClear(GL_COLOR_BUFFER_BIT);
+ }
- glBindTexture(GL_TEXTURE_2D, texture);
+ glBindTexture(GL_TEXTURE_2D, texture);
- full_blit();
+ full_blit();
- glBindTexture(GL_TEXTURE_2D, 0);
+ glBindTexture(GL_TEXTURE_2D, 0);
- ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
+ ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}
GLuint draw_clip(QOpenGLFramebufferObject* fbo, GLuint texture, bool clear) {
- fbo->bind();
+ fbo->bind();
- if (clear) {
- glClear(GL_COLOR_BUFFER_BIT);
- }
+ if (clear) {
+ glClear(GL_COLOR_BUFFER_BIT);
+ }
- glBindTexture(GL_TEXTURE_2D, texture);
+ glBindTexture(GL_TEXTURE_2D, texture);
- full_blit();
+ full_blit();
- glBindTexture(GL_TEXTURE_2D, 0);
+ glBindTexture(GL_TEXTURE_2D, 0);
- fbo->release();
+ fbo->release();
- return fbo->texture();
+ return fbo->texture();
}
void process_effect(ClipPtr c,
EffectPtr e,
- double timecode,
- GLTextureCoords& coords,
- GLuint& composite_texture,
- bool& fbo_switcher,
- bool& texture_failed,
- int data) {
- if (e->is_enabled()) {
- if (e->enable_coords) {
- e->process_coords(timecode, coords, data);
- }
- bool can_process_shaders = (e->enable_shader && olive::CurrentRuntimeConfig.shaders_are_enabled);
- if (can_process_shaders || e->enable_superimpose) {
- e->startEffect();
- if (can_process_shaders && e->is_glsl_linked()) {
- for (int i=0;igetIterations();i++) {
- e->process_shader(timecode, coords, i);
- composite_texture = draw_clip(c->fbo[fbo_switcher], composite_texture, true);
- fbo_switcher = !fbo_switcher;
- }
- }
- if (e->enable_superimpose) {
- GLuint superimpose_texture = e->process_superimpose(timecode);
+ double timecode,
+ GLTextureCoords& coords,
+ GLuint& composite_texture,
+ bool& fbo_switcher,
+ bool& texture_failed,
+ int data) {
+ if (e->is_enabled()) {
+ if (e->enable_coords) {
+ e->process_coords(timecode, coords, data);
+ }
+ bool can_process_shaders = (e->enable_shader && olive::CurrentRuntimeConfig.shaders_are_enabled);
+ if (can_process_shaders || e->enable_superimpose) {
+ e->startEffect();
+ if (can_process_shaders && e->is_glsl_linked()) {
+ for (int i=0;igetIterations();i++) {
+ e->process_shader(timecode, coords, i);
+ composite_texture = draw_clip(c->fbo[fbo_switcher], composite_texture, true);
+ fbo_switcher = !fbo_switcher;
+ }
+ }
+ if (e->enable_superimpose) {
+ GLuint superimpose_texture = e->process_superimpose(timecode);
- if (superimpose_texture == 0) {
- qWarning() << "Superimpose texture was nullptr, retrying...";
- texture_failed = true;
- } else if (composite_texture == 0) {
- // if there is no previous texture, just return the superimposes texture
- // UNLESS this is a shader-extended superimpose effect in which case,
- // we'll need to draw it below
- composite_texture = superimpose_texture;
- } else {
- // if the source texture is not already a framebuffer texture,
- // we'll need to make it one before drawing a superimpose effect on it
- if (composite_texture != c->fbo[0]->texture() && composite_texture != c->fbo[1]->texture()) {
- draw_clip(c->fbo[!fbo_switcher], composite_texture, true);
- }
+ if (superimpose_texture == 0) {
+ qWarning() << "Superimpose texture was nullptr, retrying...";
+ texture_failed = true;
+ } else if (composite_texture == 0) {
+ // if there is no previous texture, just return the superimposes texture
+ // UNLESS this is a shader-extended superimpose effect in which case,
+ // we'll need to draw it below
+ composite_texture = superimpose_texture;
+ } else {
+ // if the source texture is not already a framebuffer texture,
+ // we'll need to make it one before drawing a superimpose effect on it
+ if (composite_texture != c->fbo[0]->texture() && composite_texture != c->fbo[1]->texture()) {
+ draw_clip(c->fbo[!fbo_switcher], composite_texture, true);
+ }
- composite_texture = draw_clip(c->fbo[!fbo_switcher], superimpose_texture, false);
- }
- }
- e->endEffect();
- }
- }
+ composite_texture = draw_clip(c->fbo[!fbo_switcher], superimpose_texture, false);
+ }
+ }
+ e->endEffect();
+ }
+ }
}
GLuint compose_sequence(ComposeSequenceParams ¶ms) {
- GLuint final_fbo = params.main_buffer;
+ GLuint final_fbo = params.main_buffer;
- SequencePtr s = params.seq;
- long playhead = s->playhead;
+ SequencePtr s = params.seq;
+ long playhead = s->playhead;
- if (!params.nests.isEmpty()) {
- for (int i=0;imedia->to_sequence();
- playhead += params.nests.at(i)->clip_in - params.nests.at(i)->get_timeline_in_with_transition();
- playhead = refactor_frame_number(playhead, params.nests.at(i)->sequence->frame_rate, s->frame_rate);
- }
+ if (!params.nests.isEmpty()) {
+ for (int i=0;imedia->to_sequence();
+ playhead += params.nests.at(i)->clip_in - params.nests.at(i)->get_timeline_in_with_transition();
+ playhead = refactor_frame_number(playhead, params.nests.at(i)->sequence->frame_rate, s->frame_rate);
+ }
- if (params.video && params.nests.last()->fbo != nullptr) {
- params.nests.last()->fbo[0]->bind();
- glClear(GL_COLOR_BUFFER_BIT);
- final_fbo = params.nests.last()->fbo[0]->handle();
- }
- }
+ if (params.video && params.nests.last()->fbo != nullptr) {
+ params.nests.last()->fbo[0]->bind();
+ glClear(GL_COLOR_BUFFER_BIT);
+ final_fbo = params.nests.last()->fbo[0]->handle();
+ }
+ }
- int audio_track_count = 0;
+ int audio_track_count = 0;
- QVector current_clips;
+ QVector current_clips;
- // loop through clips, find currently active, and sort by track
- for (int i=0;iclips.size();i++) {
+ // loop through clips, find currently active, and sort by track
+ for (int i=0;iclips.size();i++) {
- ClipPtr c = s->clips.at(i);
+ ClipPtr c = s->clips.at(i);
- if (c != nullptr) {
+ if (c != nullptr) {
- // if clip is video and we're processing video
- if ((c->track < 0) == params.video) {
+ // if clip is video and we're processing video
+ if ((c->track < 0) == params.video) {
- bool clip_is_active = false;
+ bool clip_is_active = false;
- // is the clip a "footage" clip?
- if (c->media != nullptr && c->media->get_type() == MEDIA_TYPE_FOOTAGE) {
- FootagePtr m = c->media->to_footage();
+ // is the clip a "footage" clip?
+ if (c->media != nullptr && c->media->get_type() == MEDIA_TYPE_FOOTAGE) {
+ FootagePtr m = c->media->to_footage();
- // does the clip have a valid media source?
- if (!m->invalid && !(c->track >= 0 && !is_audio_device_set())) {
+ // does the clip have a valid media source?
+ if (!m->invalid && !(c->track >= 0 && !is_audio_device_set())) {
- // is the media process and ready?
- if (m->ready) {
- const FootageStream* ms = m->get_stream_from_file_index(c->track < 0, c->media_stream);
+ // is the media process and ready?
+ if (m->ready) {
+ const FootageStream* ms = m->get_stream_from_file_index(c->track < 0, c->media_stream);
- // does the media have a valid media stream source and is it active?
- if (ms != nullptr && is_clip_active(c, playhead)) {
+ // does the media have a valid media stream source and is it active?
+ if (ms != nullptr && is_clip_active(c, playhead)) {
- // open if not open
- if (!c->open) {
- open_clip(c, !params.single_threaded);
- }
-
- clip_is_active = true;
-
- // increment audio track count
- if (c->track >= 0) audio_track_count++;
-
- } else if (c->finished_opening) {
-
- // close the clip if it isn't active anymore
- close_clip(c, false);
-
- }
- } else {
-
- // media wasn't ready, schedule a redraw
- params.texture_failed = true;
-
- }
- }
- } else {
- // if the clip is a nested sequence or null clip, just open it
-
- if (is_clip_active(c, playhead)) {
- if (!c->open) open_clip(c, !params.single_threaded);
- clip_is_active = true;
- } else if (c->finished_opening) {
- close_clip(c, false);
- }
- }
-
- // if the clip is active, added it to "current_clips", sorted by track
- if (clip_is_active) {
- bool added = false;
-
- // track sorting is only necessary for video clips
- // audio clips are mixed equally, so we skip sorting for those
- if (params.video) {
-
- // insertion sort by track
- for (int j=0;jtrack < c->track) {
- current_clips.insert(j, c);
- added = true;
- break;
- }
- }
-
- }
-
- if (!added) {
- current_clips.append(c);
- }
- }
- }
- }
- }
-
- if (params.video) {
- // set default coordinates based on the sequence, with 0 in the direct center
- glPushMatrix();
- glLoadIdentity();
-
- glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
-
- int half_width = s->width/2;
- int half_height = s->height/2;
- glOrtho(-half_width, half_width, -half_height, half_height, -1, 10);
- }
-
- // loop through current clips
- for (int i=0;imedia != nullptr && c->media->get_type() == MEDIA_TYPE_FOOTAGE && !c->finished_opening) {
- qWarning() << "Tried to display clip" << i << "but it's closed";
- params.texture_failed = true;
- } else {
- // if clip is a video clip
- if (c->track < 0) {
- // reset OpenGL to full color
- glColor4f(1.0, 1.0, 1.0, 1.0);
-
- // textureID variable contains texture to be drawn on screen at the end
- GLuint textureID = 0;
-
- // store video source dimensions
- int video_width = c->getWidth();
- int video_height = c->getHeight();
-
- // if media is footage
- if (c->media != nullptr && c->media->get_type() == MEDIA_TYPE_FOOTAGE) {
-
- if (c->texture == nullptr) {
- // opengl texture doesn't exist yet, create it
-
- c->texture = new QOpenGLTexture(QOpenGLTexture::Target2D);
- c->texture->setSize(c->stream->codecpar->width, c->stream->codecpar->height);
- c->texture->setFormat(QOpenGLTexture::RGBA8_UNorm);
- c->texture->setMipLevels(c->texture->maximumMipLevels());
- c->texture->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear);
- c->texture->allocateStorage(QOpenGLTexture::RGBA, QOpenGLTexture::UInt8);
- }
-
- // retrieve video frame from cache and store it in c->texture
- get_clip_frame(c, qMax(playhead, c->timeline_in), params.texture_failed);
-
- // retrieve ID from c->texture
- textureID = c->texture->textureId();
-
- if (textureID == 0) {
- qWarning() << "Failed to create texture";
- return 0;
- }
- }
-
- // prepare framebuffers for backend drawing operations
- if (c->fbo == nullptr) {
- // create 3 fbos for nested sequences, 2 for most clips
- int fbo_count = (c->media != nullptr && c->media->get_type() == MEDIA_TYPE_SEQUENCE) ? 3 : 2;
-
- c->fbo = new QOpenGLFramebufferObject* [size_t(fbo_count)];
-
- for (int j=0;jfbo[j] = new QOpenGLFramebufferObject(video_width, video_height);
- }
- }
-
- // if clip should actually be shown on screen in this frame
- if (playhead >= c->get_timeline_in_with_transition()
- && playhead < c->get_timeline_out_with_transition()) {
- glPushMatrix();
-
- // simple bool for switching between the two framebuffers
- bool fbo_switcher = false;
-
- glViewport(0, 0, video_width, video_height);
-
- if (c->media != nullptr) {
- if (c->media->get_type() == MEDIA_TYPE_SEQUENCE) {
- // for a nested sequence, run this function again on that sequence and retrieve the texture
-
- // add nested sequence to nest list
- params.nests.append(c);
-
- // compose sequence
- textureID = compose_sequence(params);
-
- // remove sequence from nest list
- params.nests.removeLast();
-
- // compose_sequence() would have written to this clip's fbo[0], so we switch to fbo[1]
- fbo_switcher = true;
- } else if (c->media->get_type() == MEDIA_TYPE_FOOTAGE && !c->media->to_footage()->alpha_is_premultiplied) {
- // alpha is not premultiplied, we'll need to premultiply it for the rest of the pipeline
- params.premultiply_program->bind();
-
- textureID = draw_clip(c->fbo[0], textureID, true);
-
- params.premultiply_program->release();
-
- fbo_switcher = true;
- }
- }
-
- // set up default coordinates for drawing the clip
- GLTextureCoords coords;
- coords.grid_size = 1;
- coords.vertexTopLeftX = coords.vertexBottomLeftX = -video_width/2;
- coords.vertexTopLeftY = coords.vertexTopRightY = -video_height/2;
- coords.vertexTopRightX = coords.vertexBottomRightX = video_width/2;
- coords.vertexBottomLeftY = coords.vertexBottomRightY = video_height/2;
- coords.vertexBottomLeftZ = coords.vertexBottomRightZ = coords.vertexTopLeftZ = coords.vertexTopRightZ = 1;
- coords.textureTopLeftY = coords.textureTopRightY = coords.textureTopLeftX = coords.textureBottomLeftX = 0.0;
- coords.textureBottomLeftY = coords.textureBottomRightY = coords.textureTopRightX = coords.textureBottomRightX = 1.0;
- coords.textureTopLeftQ = coords.textureTopRightQ = coords.textureTopLeftQ = coords.textureBottomLeftQ = 1;
- coords.blendmode = BLEND_MODE_NORMAL;
- coords.opacity = 1.0;
-
- // if auto-scale is enabled, auto-scale the clip
- if (c->autoscale && (video_width != s->width && video_height != s->height)) {
- float width_multiplier = float(s->width) / float(video_width);
- float height_multiplier = float(s->height) / float(video_height);
- float scale_multiplier = qMin(width_multiplier, height_multiplier);
- glScalef(scale_multiplier, scale_multiplier, 1);
- }
-
- // == EFFECT CODE START ==
-
- // get current sequence time in seconds (used for effects)
- double timecode = get_timecode(c, playhead);
-
- // set up variables for gizmos later
- EffectPtr first_gizmo_effect = nullptr;
- EffectPtr selected_effect = nullptr;
-
- // run through all of the clip's effects
- for (int j=0;jeffects.size();j++) {
- EffectPtr e = c->effects.at(j);
- process_effect(c, e, timecode, coords, textureID, fbo_switcher, params.texture_failed, kTransitionNone);
-
- // retrieve gizmo data from effect
- if (e->are_gizmos_enabled()) {
- if (first_gizmo_effect == nullptr) first_gizmo_effect = e;
- if (e->container->selected) selected_effect = e;
- }
- }
-
- // using gizmo data, set definitive gizmo
- if (selected_effect != nullptr) {
- (*params.gizmos) = selected_effect;
- } else if (is_clip_selected(c, true)) {
- (*params.gizmos) = first_gizmo_effect;
- }
-
- // if the clip has an opening transition, process that now
- if (c->get_opening_transition() != nullptr) {
- int transition_progress = playhead - c->get_timeline_in_with_transition();
- if (transition_progress < c->get_opening_transition()->get_length()) {
- process_effect(c, c->get_opening_transition(), double(transition_progress)/double(c->get_opening_transition()->get_length()), coords, textureID, fbo_switcher, params.texture_failed, kTransitionOpening);
- }
- }
-
- // if the clip has a closing transition, process that now
- if (c->get_closing_transition() != nullptr) {
- int transition_progress = playhead - (c->get_timeline_out_with_transition() - c->get_closing_transition()->get_length());
- if (transition_progress >= 0 && transition_progress < c->get_closing_transition()->get_length()) {
- process_effect(c, c->get_closing_transition(), double(transition_progress)/double(c->get_closing_transition()->get_length()), coords, textureID, fbo_switcher, params.texture_failed, kTransitionClosing);
- }
- }
-
- // == EFFECT CODE END ==
-
- if (textureID > 0) {
- // set viewport to sequence size
- params.ctx->functions()->glViewport(0, 0, s->width, s->height);
-
-
-
- // == START RENDER CLIP IN CONTEXT OF SEQUENCE ==
-
-
-
- // use clip textures for nested sequences, otherwise use main frame buffers
- GLuint back_buffer_1;
- GLuint backend_tex_1;
- GLuint backend_tex_2;
- if (params.nests.size() > 0) {
- back_buffer_1 = params.nests.last()->fbo[1]->handle();
- backend_tex_1 = params.nests.last()->fbo[1]->texture();
- backend_tex_2 = params.nests.last()->fbo[2]->texture();
- } else {
- back_buffer_1 = params.backend_buffer1;
- backend_tex_1 = params.backend_attachment1;
- backend_tex_2 = params.backend_attachment2;
- }
-
- // render a backbuffer
- params.ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, back_buffer_1);
-
- glClearColor(0.0, 0.0, 0.0, 0.0);
- glClear(GL_COLOR_BUFFER_BIT);
-
- // bind final clip texture
- glBindTexture(GL_TEXTURE_2D, textureID);
-
- // set texture filter to bilinear
- params.ctx->functions()->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- params.ctx->functions()->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-
- // draw clip on screen according to gl coordinates
- glBegin(GL_QUADS);
-
- glTexCoord2f(coords.textureTopLeftX, coords.textureTopLeftY); // top left
- glVertex2f(coords.vertexTopLeftX, coords.vertexTopLeftY); // top left
- glTexCoord2f(coords.textureTopRightX, coords.textureTopRightY); // top right
- glVertex2f(coords.vertexTopRightX, coords.vertexTopRightY); // top right
- glTexCoord2f(coords.textureBottomRightX, coords.textureBottomRightY); // bottom right
- glVertex2f(coords.vertexBottomRightX, coords.vertexBottomRightY); // bottom right
- glTexCoord2f(coords.textureBottomLeftX, coords.textureBottomLeftY); // bottom left
- glVertex2f(coords.vertexBottomLeftX, coords.vertexBottomLeftY); // bottom left
-
- glEnd();
-
- // release final clip texture
- glBindTexture(GL_TEXTURE_2D, 0);
-
- params.ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
-
-
-
- // == END RENDER CLIP IN CONTEXT OF SEQUENCE ==
-
-
-
- //
- //
- // PROCESS POST-SHADERS
- //
- //
-
-
-
- // copy front buffer to back buffer (only if we're using blending modes - which we usually will be)
- if (!olive::CurrentRuntimeConfig.disable_blending) {
- if (params.nests.size() > 0) {
- draw_clip(params.ctx, params.nests.last()->fbo[2]->handle(), params.nests.last()->fbo[0]->texture(), true);
- } else {
- draw_clip(params.ctx, params.backend_buffer2, params.main_attachment, true);
- }
- }
-
-
-
- // == START FINAL DRAW ON SEQUENCE BUFFER ==
-
-
-
- // bind front buffer as draw buffer
- params.ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, final_fbo);
-
- if (olive::CurrentRuntimeConfig.disable_blending) {
- // some GPUs don't like the blending shader, so we provide a pure GL fallback here
-
- params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, backend_tex_1);
-
- glColor4f(coords.opacity, coords.opacity, coords.opacity, coords.opacity);
-
- full_blit();
-
- params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, 0);
- } else {
- // load background texture into texture unit 0
- params.ctx->functions()->glActiveTexture(GL_TEXTURE0 + 0); // Texture unit 0
- params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, backend_tex_2);
-
- // load foreground texture into texture unit 1
- params.ctx->functions()->glActiveTexture(GL_TEXTURE0 + 1); // Texture unit 1
- params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, backend_tex_1);
-
- // bind and configure blending mode shader
- params.blend_mode_program->bind();
- params.blend_mode_program->setUniformValue("blendmode", coords.blendmode);
- params.blend_mode_program->setUniformValue("opacity", coords.opacity);
- params.blend_mode_program->setUniformValue("background", 0);
- params.blend_mode_program->setUniformValue("foreground", 1);
-
- glClear(GL_COLOR_BUFFER_BIT);
-
- full_blit();
-
- // release blend mode shader
- params.blend_mode_program->release();
-
- // unbind texture from texture unit 1
- params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, 0);
-
- // unbind texture from texture unit 0
- params.ctx->functions()->glActiveTexture(GL_TEXTURE0 + 0); // Texture unit 0
- params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, 0);
- }
-
- // unbind framebuffer
- params.ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
-
-
-
- // == END FINAL DRAW ON SEQUENCE BUFFER ==
- }
-
- // prepare gizmos
- if ((*params.gizmos) != nullptr
- && params.nests.isEmpty()
- && ((*params.gizmos) == first_gizmo_effect
- || (*params.gizmos) == selected_effect)) {
- (*params.gizmos)->gizmo_draw(timecode, coords); // set correct gizmo coords
- (*params.gizmos)->gizmo_world_to_screen(); // convert gizmo coords to screen coords
- }
-
- glPopMatrix();
- }
- } else {
- if (c->media != nullptr && c->media->get_type() == MEDIA_TYPE_SEQUENCE) {
- params.nests.append(c);
- compose_sequence(params);
- params.nests.removeLast();
- } else {
- if (c->lock.tryLock()) {
- // Check whether cacher is currently active, if not activate it now
-
- cache_clip(c, playhead, c->audio_reset, (params.viewer != nullptr && !params.viewer->playing), params.nests, params.playback_speed);
- c->lock.unlock();
- }
+ // open if not open
+ if (!c->open) {
+ open_clip(c, !params.single_threaded);
}
- // visually update all the keyframe values
- if (c->sequence == params.seq) { // only if you can currently see them
- double ts = (playhead - c->get_timeline_in_with_transition() + c->get_clip_in_with_transition())/s->frame_rate;
- for (int i=0;ieffects.size();i++) {
- EffectPtr e = c->effects.at(i);
- for (int j=0;jrow_count();j++) {
- EffectRow* r = e->row(j);
- for (int k=0;kfieldCount();k++) {
- r->field(k)->validate_keyframe_data(ts);
- }
- }
- }
- }
- }
- }
- }
+ clip_is_active = true;
- if (audio_track_count == 0 && params.viewer != nullptr) {
- params.viewer->play_wake();
- }
+ // increment audio track count
+ if (c->track >= 0) audio_track_count++;
- if (params.video) {
- glPopMatrix();
- }
+ } else if (c->finished_opening) {
- if (!params.nests.isEmpty() && params.nests.last()->fbo != nullptr) {
- // returns nested clip's texture
- return params.nests.last()->fbo[0]->texture();
- }
+ // close the clip if it isn't active anymore
+ close_clip(c, false);
- return 0;
+ }
+ } else {
+
+ // media wasn't ready, schedule a redraw
+ params.texture_failed = true;
+
+ }
+ }
+ } else {
+ // if the clip is a nested sequence or null clip, just open it
+
+ if (is_clip_active(c, playhead)) {
+ if (!c->open) open_clip(c, !params.single_threaded);
+ clip_is_active = true;
+ } else if (c->finished_opening) {
+ close_clip(c, false);
+ }
+ }
+
+ // if the clip is active, added it to "current_clips", sorted by track
+ if (clip_is_active) {
+ bool added = false;
+
+ // track sorting is only necessary for video clips
+ // audio clips are mixed equally, so we skip sorting for those
+ if (params.video) {
+
+ // insertion sort by track
+ for (int j=0;jtrack < c->track) {
+ current_clips.insert(j, c);
+ added = true;
+ break;
+ }
+ }
+
+ }
+
+ if (!added) {
+ current_clips.append(c);
+ }
+ }
+ }
+ }
+ }
+
+ if (params.video) {
+ // set default coordinates based on the sequence, with 0 in the direct center
+ glPushMatrix();
+ glLoadIdentity();
+
+ glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+
+ int half_width = s->width/2;
+ int half_height = s->height/2;
+ glOrtho(-half_width, half_width, -half_height, half_height, -1, 10);
+ }
+
+ // loop through current clips
+ for (int i=0;imedia != nullptr && c->media->get_type() == MEDIA_TYPE_FOOTAGE && !c->finished_opening) {
+ qWarning() << "Tried to display clip" << i << "but it's closed";
+ params.texture_failed = true;
+ } else {
+
+ c->render_lock.lock();
+
+ // if clip is a video clip
+ if (c->track < 0) {
+ // reset OpenGL to full color
+ glColor4f(1.0, 1.0, 1.0, 1.0);
+
+ // textureID variable contains texture to be drawn on screen at the end
+ GLuint textureID = 0;
+
+ // store video source dimensions
+ int video_width = c->getWidth();
+ int video_height = c->getHeight();
+
+ // if media is footage
+ if (c->media != nullptr && c->media->get_type() == MEDIA_TYPE_FOOTAGE) {
+
+ if (c->texture == nullptr) {
+ // opengl texture doesn't exist yet, create it
+
+ c->texture = new QOpenGLTexture(QOpenGLTexture::Target2D);
+ c->texture->setSize(c->stream->codecpar->width, c->stream->codecpar->height);
+ c->texture->setFormat(QOpenGLTexture::RGBA8_UNorm);
+ c->texture->setMipLevels(c->texture->maximumMipLevels());
+ c->texture->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear);
+ c->texture->allocateStorage(QOpenGLTexture::RGBA, QOpenGLTexture::UInt8);
+ }
+
+ // retrieve video frame from cache and store it in c->texture
+ get_clip_frame(c, qMax(playhead, c->timeline_in), params.texture_failed);
+
+ // retrieve ID from c->texture
+ textureID = c->texture->textureId();
+
+ if (textureID == 0) {
+ qWarning() << "Failed to create texture";
+ return 0;
+ }
+ }
+
+ // prepare framebuffers for backend drawing operations
+ if (c->fbo == nullptr) {
+ // create 3 fbos for nested sequences, 2 for most clips
+ int fbo_count = (c->media != nullptr && c->media->get_type() == MEDIA_TYPE_SEQUENCE) ? 3 : 2;
+
+ c->fbo = new QOpenGLFramebufferObject* [size_t(fbo_count)];
+
+ for (int j=0;jfbo[j] = new QOpenGLFramebufferObject(video_width, video_height);
+ }
+ }
+
+ // if clip should actually be shown on screen in this frame
+ if (playhead >= c->get_timeline_in_with_transition()
+ && playhead < c->get_timeline_out_with_transition()) {
+ glPushMatrix();
+
+ // simple bool for switching between the two framebuffers
+ bool fbo_switcher = false;
+
+ glViewport(0, 0, video_width, video_height);
+
+ if (c->media != nullptr) {
+ if (c->media->get_type() == MEDIA_TYPE_SEQUENCE) {
+ // for a nested sequence, run this function again on that sequence and retrieve the texture
+
+ // add nested sequence to nest list
+ params.nests.append(c);
+
+ // compose sequence
+ textureID = compose_sequence(params);
+
+ // remove sequence from nest list
+ params.nests.removeLast();
+
+ // compose_sequence() would have written to this clip's fbo[0], so we switch to fbo[1]
+ fbo_switcher = true;
+ } else if (c->media->get_type() == MEDIA_TYPE_FOOTAGE && !c->media->to_footage()->alpha_is_premultiplied) {
+ // alpha is not premultiplied, we'll need to premultiply it for the rest of the pipeline
+ params.premultiply_program->bind();
+
+ textureID = draw_clip(c->fbo[0], textureID, true);
+
+ params.premultiply_program->release();
+
+ fbo_switcher = true;
+ }
+ }
+
+ // set up default coordinates for drawing the clip
+ GLTextureCoords coords;
+ coords.grid_size = 1;
+ coords.vertexTopLeftX = coords.vertexBottomLeftX = -video_width/2;
+ coords.vertexTopLeftY = coords.vertexTopRightY = -video_height/2;
+ coords.vertexTopRightX = coords.vertexBottomRightX = video_width/2;
+ coords.vertexBottomLeftY = coords.vertexBottomRightY = video_height/2;
+ coords.vertexBottomLeftZ = coords.vertexBottomRightZ = coords.vertexTopLeftZ = coords.vertexTopRightZ = 1;
+ coords.textureTopLeftY = coords.textureTopRightY = coords.textureTopLeftX = coords.textureBottomLeftX = 0.0;
+ coords.textureBottomLeftY = coords.textureBottomRightY = coords.textureTopRightX = coords.textureBottomRightX = 1.0;
+ coords.textureTopLeftQ = coords.textureTopRightQ = coords.textureTopLeftQ = coords.textureBottomLeftQ = 1;
+ coords.blendmode = BLEND_MODE_NORMAL;
+ coords.opacity = 1.0;
+
+ // if auto-scale is enabled, auto-scale the clip
+ if (c->autoscale && (video_width != s->width && video_height != s->height)) {
+ float width_multiplier = float(s->width) / float(video_width);
+ float height_multiplier = float(s->height) / float(video_height);
+ float scale_multiplier = qMin(width_multiplier, height_multiplier);
+ glScalef(scale_multiplier, scale_multiplier, 1);
+ }
+
+ // == EFFECT CODE START ==
+
+ // get current sequence time in seconds (used for effects)
+ double timecode = get_timecode(c, playhead);
+
+ // set up variables for gizmos later
+ EffectPtr first_gizmo_effect = nullptr;
+ EffectPtr selected_effect = nullptr;
+
+ // run through all of the clip's effects
+ for (int j=0;jeffects.size();j++) {
+ EffectPtr e = c->effects.at(j);
+ process_effect(c, e, timecode, coords, textureID, fbo_switcher, params.texture_failed, kTransitionNone);
+
+ // retrieve gizmo data from effect
+ if (e->are_gizmos_enabled()) {
+ if (first_gizmo_effect == nullptr) first_gizmo_effect = e;
+ if (e->container->selected) selected_effect = e;
+ }
+ }
+
+ // using gizmo data, set definitive gizmo
+ if (selected_effect != nullptr) {
+ (*params.gizmos) = selected_effect;
+ } else if (is_clip_selected(c, true)) {
+ (*params.gizmos) = first_gizmo_effect;
+ }
+
+ // if the clip has an opening transition, process that now
+ if (c->get_opening_transition() != nullptr) {
+ int transition_progress = playhead - c->get_timeline_in_with_transition();
+ if (transition_progress < c->get_opening_transition()->get_length()) {
+ process_effect(c, c->get_opening_transition(), double(transition_progress)/double(c->get_opening_transition()->get_length()), coords, textureID, fbo_switcher, params.texture_failed, kTransitionOpening);
+ }
+ }
+
+ // if the clip has a closing transition, process that now
+ if (c->get_closing_transition() != nullptr) {
+ int transition_progress = playhead - (c->get_timeline_out_with_transition() - c->get_closing_transition()->get_length());
+ if (transition_progress >= 0 && transition_progress < c->get_closing_transition()->get_length()) {
+ process_effect(c, c->get_closing_transition(), double(transition_progress)/double(c->get_closing_transition()->get_length()), coords, textureID, fbo_switcher, params.texture_failed, kTransitionClosing);
+ }
+ }
+
+ // == EFFECT CODE END ==
+
+ if (textureID > 0) {
+ // set viewport to sequence size
+ params.ctx->functions()->glViewport(0, 0, s->width, s->height);
+
+
+
+ // == START RENDER CLIP IN CONTEXT OF SEQUENCE ==
+
+
+
+ // use clip textures for nested sequences, otherwise use main frame buffers
+ GLuint back_buffer_1;
+ GLuint backend_tex_1;
+ GLuint backend_tex_2;
+ if (params.nests.size() > 0) {
+ back_buffer_1 = params.nests.last()->fbo[1]->handle();
+ backend_tex_1 = params.nests.last()->fbo[1]->texture();
+ backend_tex_2 = params.nests.last()->fbo[2]->texture();
+ } else {
+ back_buffer_1 = params.backend_buffer1;
+ backend_tex_1 = params.backend_attachment1;
+ backend_tex_2 = params.backend_attachment2;
+ }
+
+ // render a backbuffer
+ params.ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, back_buffer_1);
+
+ glClearColor(0.0, 0.0, 0.0, 0.0);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ // bind final clip texture
+ glBindTexture(GL_TEXTURE_2D, textureID);
+
+ // set texture filter to bilinear
+ params.ctx->functions()->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ params.ctx->functions()->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+ // draw clip on screen according to gl coordinates
+ glBegin(GL_QUADS);
+
+ glTexCoord2f(coords.textureTopLeftX, coords.textureTopLeftY); // top left
+ glVertex2f(coords.vertexTopLeftX, coords.vertexTopLeftY); // top left
+ glTexCoord2f(coords.textureTopRightX, coords.textureTopRightY); // top right
+ glVertex2f(coords.vertexTopRightX, coords.vertexTopRightY); // top right
+ glTexCoord2f(coords.textureBottomRightX, coords.textureBottomRightY); // bottom right
+ glVertex2f(coords.vertexBottomRightX, coords.vertexBottomRightY); // bottom right
+ glTexCoord2f(coords.textureBottomLeftX, coords.textureBottomLeftY); // bottom left
+ glVertex2f(coords.vertexBottomLeftX, coords.vertexBottomLeftY); // bottom left
+
+ glEnd();
+
+ // release final clip texture
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ params.ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
+
+
+
+ // == END RENDER CLIP IN CONTEXT OF SEQUENCE ==
+
+
+
+ //
+ //
+ // PROCESS POST-SHADERS
+ //
+ //
+
+
+
+ // copy front buffer to back buffer (only if we're using blending modes - which we usually will be)
+ if (!olive::CurrentRuntimeConfig.disable_blending) {
+ if (params.nests.size() > 0) {
+ draw_clip(params.ctx, params.nests.last()->fbo[2]->handle(), params.nests.last()->fbo[0]->texture(), true);
+ } else {
+ draw_clip(params.ctx, params.backend_buffer2, params.main_attachment, true);
+ }
+ }
+
+
+
+ // == START FINAL DRAW ON SEQUENCE BUFFER ==
+
+
+
+ // bind front buffer as draw buffer
+ params.ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, final_fbo);
+
+ if (olive::CurrentRuntimeConfig.disable_blending) {
+ // some GPUs don't like the blending shader, so we provide a pure GL fallback here
+
+ params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, backend_tex_1);
+
+ glColor4f(coords.opacity, coords.opacity, coords.opacity, coords.opacity);
+
+ full_blit();
+
+ params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, 0);
+ } else {
+ // load background texture into texture unit 0
+ params.ctx->functions()->glActiveTexture(GL_TEXTURE0 + 0); // Texture unit 0
+ params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, backend_tex_2);
+
+ // load foreground texture into texture unit 1
+ params.ctx->functions()->glActiveTexture(GL_TEXTURE0 + 1); // Texture unit 1
+ params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, backend_tex_1);
+
+ // bind and configure blending mode shader
+ params.blend_mode_program->bind();
+ params.blend_mode_program->setUniformValue("blendmode", coords.blendmode);
+ params.blend_mode_program->setUniformValue("opacity", coords.opacity);
+ params.blend_mode_program->setUniformValue("background", 0);
+ params.blend_mode_program->setUniformValue("foreground", 1);
+
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ full_blit();
+
+ // release blend mode shader
+ params.blend_mode_program->release();
+
+ // unbind texture from texture unit 1
+ params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, 0);
+
+ // unbind texture from texture unit 0
+ params.ctx->functions()->glActiveTexture(GL_TEXTURE0 + 0); // Texture unit 0
+ params.ctx->functions()->glBindTexture(GL_TEXTURE_2D, 0);
+ }
+
+ // unbind framebuffer
+ params.ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
+
+
+
+ // == END FINAL DRAW ON SEQUENCE BUFFER ==
+ }
+
+ // prepare gizmos
+ if ((*params.gizmos) != nullptr
+ && params.nests.isEmpty()
+ && ((*params.gizmos) == first_gizmo_effect
+ || (*params.gizmos) == selected_effect)) {
+ (*params.gizmos)->gizmo_draw(timecode, coords); // set correct gizmo coords
+ (*params.gizmos)->gizmo_world_to_screen(); // convert gizmo coords to screen coords
+ }
+
+ glPopMatrix();
+ }
+ } else {
+ if (c->media != nullptr && c->media->get_type() == MEDIA_TYPE_SEQUENCE) {
+ params.nests.append(c);
+ compose_sequence(params);
+ params.nests.removeLast();
+ } else {
+ if (c->lock.tryLock()) {
+ // Check whether cacher is currently active, if not activate it now
+
+ cache_clip(c, playhead, c->audio_reset, (params.viewer != nullptr && !params.viewer->playing), params.nests, params.playback_speed);
+ c->lock.unlock();
+ }
+ }
+
+ // visually update all the keyframe values
+ if (c->sequence == params.seq) { // only if you can currently see them
+ double ts = (playhead - c->get_timeline_in_with_transition() + c->get_clip_in_with_transition())/s->frame_rate;
+ for (int i=0;ieffects.size();i++) {
+ EffectPtr e = c->effects.at(i);
+ for (int j=0;jrow_count();j++) {
+ EffectRow* r = e->row(j);
+ for (int k=0;kfieldCount();k++) {
+ r->field(k)->validate_keyframe_data(ts);
+ }
+ }
+ }
+ }
+ }
+
+ c->render_lock.unlock();
+
+ }
+ }
+
+ if (audio_track_count == 0 && params.viewer != nullptr) {
+ params.viewer->play_wake();
+ }
+
+ if (params.video) {
+ glPopMatrix();
+ }
+
+ if (!params.nests.isEmpty() && params.nests.last()->fbo != nullptr) {
+ // returns nested clip's texture
+ return params.nests.last()->fbo[0]->texture();
+ }
+
+ return 0;
}
void compose_audio(Viewer* viewer, SequencePtr seq, int playback_speed) {
- ComposeSequenceParams params;
- params.viewer = viewer;
- params.ctx = nullptr;
- params.seq = seq;
- params.video = false;
- params.gizmos = nullptr;
- params.single_threaded = audio_rendering;
- params.playback_speed = playback_speed;
- params.blend_mode_program = nullptr;
- compose_sequence(params);
+ ComposeSequenceParams params;
+ params.viewer = viewer;
+ params.ctx = nullptr;
+ params.seq = seq;
+ params.video = false;
+ params.gizmos = nullptr;
+ params.single_threaded = audio_rendering;
+ params.playback_speed = playback_speed;
+ params.blend_mode_program = nullptr;
+ compose_sequence(params);
}
diff --git a/ui/timelinewidget.cpp b/ui/timelinewidget.cpp
index 199b3e396..1f7901964 100644
--- a/ui/timelinewidget.cpp
+++ b/ui/timelinewidget.cpp
@@ -848,7 +848,22 @@ void TimelineWidget::mousePressEvent(QMouseEvent *event) {
}
}
-void make_room_for_transition(ComboAction* ca, ClipPtr c, int type, long transition_start, long transition_end, bool delete_old_transitions) {
+void make_room_for_transition(ComboAction* ca,
+ ClipPtr c,
+ int type,
+ long transition_start,
+ long transition_end,
+ bool delete_old_transitions,
+ long timeline_in = -1,
+ long timeline_out = -1) {
+ // it's possible to specify other in/out points for the clip, but default behavior is to use the ones existing
+ if (timeline_in < 0) {
+ timeline_in = c->timeline_in;
+ }
+ if (timeline_out < 0) {
+ timeline_out = c->timeline_out;
+ }
+
// make room for transition
if (type == kTransitionOpening) {
if (delete_old_transitions && c->get_opening_transition() != nullptr) {
@@ -875,6 +890,97 @@ void make_room_for_transition(ComboAction* ca, ClipPtr c, int type, long transit
}
}
+void VerifyTransitionsAfterCreating(ComboAction* ca, ClipPtr open, ClipPtr close, long transition_start, long transition_end) {
+ // in case the user made the transition larger than the clips, we're going to delete everything under
+ // the transition ghost and extend the clips to the transition's coordinates as necessary
+
+ if (open == nullptr && close == nullptr) {
+ qWarning() << "VerifyTransitionsAfterCreating() called with two null clips";
+ return;
+ }
+
+ // determine whether this is a "shared" transition between to clips or not
+ bool shared_transition = (open != nullptr && close != nullptr);
+
+ int track = 0;
+
+ // first we set the clips to "undeletable" so they aren't affected by delete_areas_and_relink()
+ if (open != nullptr) {
+ open->undeletable = true;
+ track = open->track;
+ }
+ if (close != nullptr) {
+ close->undeletable = true;
+ track = close->track;
+ }
+
+ // set the area to delete to the transition's coordinates and clear it
+ QVector areas;
+ Selection s;
+ s.in = transition_start;
+ s.out = transition_end;
+ s.track = track;
+ areas.append(s);
+ panel_timeline->delete_areas_and_relink(ca, areas, false);
+
+ // set the clips back to undeletable now that we're done
+ if (open != nullptr) {
+ open->undeletable = false;
+ }
+ if (close != nullptr) {
+ close->undeletable = false;
+ }
+
+ // loop through both kinds of transition
+ for (int t=kTransitionOpening;t<=kTransitionClosing;t++) {
+
+ ClipPtr clip_ref = (t == kTransitionOpening) ? open : close;
+
+ // if we have an opening transition:
+ if (clip_ref != nullptr) {
+
+ // make_room_for_transition will adjust the opposite transition to make space for this one,
+ // for example if the user makes an opening transition that overlaps the closing transition, it'll resize
+ // or even delete the closing transition if necessary (and vice versa)
+
+ make_room_for_transition(ca, clip_ref, t, transition_start, transition_end, true);
+
+ // check if the transition coordinates require the clip to be resized
+ if (transition_start < clip_ref->timeline_in || transition_end > clip_ref->timeline_out) {
+
+ long new_in, new_out;
+
+ if (t == kTransitionOpening) {
+
+ // if the transition is shared, it doesn't matter if the transition extend beyond the in point since
+ // that'll be "absorbed" by the other clip
+ new_in = (shared_transition) ? open->timeline_in : qMin(transition_start, open->timeline_in);
+
+ new_out = qMax(transition_end, open->timeline_out);
+
+ } else {
+
+ new_in = qMin(transition_start, close->timeline_in);
+
+ // if the transition is shared, it doesn't matter if the transition extend beyond the out point since
+ // that'll be "absorbed" by the other clip
+ new_out = (shared_transition) ? close->timeline_out : qMax(transition_end, close->timeline_out);
+
+ }
+
+
+
+ move_clip(ca,
+ clip_ref,
+ new_in,
+ new_out,
+ clip_ref->clip_in - (clip_ref->timeline_in - new_in),
+ clip_ref->track);
+ }
+ }
+ }
+}
+
void TimelineWidget::mouseReleaseEvent(QMouseEvent *event) {
QToolTip::hideText();
if (olive::ActiveSequence != nullptr) {
@@ -967,6 +1073,10 @@ void TimelineWidget::mouseReleaseEvent(QMouseEvent *event) {
}
}
} else if (panel_timeline->moving_proc) {
+
+ // see if any clips actually moved, otherwise we don't need to do any processing
+ // (perhaps this could be moved further up to cover more actions?)
+
bool process_moving = false;
for (int i=0;ighosts.size();i++) {
@@ -983,54 +1093,70 @@ void TimelineWidget::mouseReleaseEvent(QMouseEvent *event) {
if (process_moving) {
const Ghost& first_ghost = panel_timeline->ghosts.at(0);
- // if we were RIPPLING, move all the clips
+ // start a ripple movement
if (panel_timeline->tool == TIMELINE_TOOL_RIPPLE) {
- long ripple_length, ripple_point;
// ripple_length becomes the length/number of frames we trimmed
- // ripple point becomes the point to ripple (i.e. the point after or before which we move every clip)
- if (panel_timeline->trim_type == TRIM_IN) {
- ripple_length = first_ghost.old_in - first_ghost.in;
- ripple_point = first_ghost.old_in;
+ // ripple_point is the "axis" around which we move all the clips, any clips after it get moved
+ long ripple_length;
+ long ripple_point = LONG_MAX;
+ if (panel_timeline->trim_type == TRIM_IN) {
+
+ // it's assumed that all the ghosts rippled by the same length, so we just take the difference of the
+ // first ghost here
+ ripple_length = first_ghost.old_in - first_ghost.in;
+
+ // for in trimming movements we also move the selections forward (unnecessary for out trimming since
+ // the selected clips more or less stay in the same place)
for (int i=0;iselections.size();i++) {
olive::ActiveSequence->selections[i].in += ripple_length;
olive::ActiveSequence->selections[i].out += ripple_length;
}
} else {
- // if we're trimming an out-point
+
+ // use the out points for length if the user trimmed the out point
ripple_length = first_ghost.old_out - panel_timeline->ghosts.at(0).out;
- ripple_point = first_ghost.old_out;
+
}
+
+ // build a list of "ignore clips" that won't get affected by ripple_clips() below
QVector ignore_clips;
for (int i=0;ighosts.size();i++) {
const Ghost& g = panel_timeline->ghosts.at(i);
- // push rippled clips forward if necessary
+ // for the same reason that we pushed selections forward above, for in trimming,
+ // we push the ghosts forward here
if (panel_timeline->trim_type == TRIM_IN) {
ignore_clips.append(g.clip);
panel_timeline->ghosts[i].in += ripple_length;
panel_timeline->ghosts[i].out += ripple_length;
}
+ // find the earliest ripple point
long comp_point = (panel_timeline->trim_type == TRIM_IN) ? g.old_in : g.old_out;
ripple_point = qMin(ripple_point, comp_point);
}
+
+ // if this was out trimming, flip the direction of the ripple
if (panel_timeline->trim_type == TRIM_OUT) ripple_length = -ripple_length;
+ // finally, ripple everything
ripple_clips(ca, olive::ActiveSequence, ripple_point, ripple_length, ignore_clips);
}
if (panel_timeline->tool == TIMELINE_TOOL_POINTER
&& (event->modifiers() & Qt::AltModifier)
- && panel_timeline->trim_target == -1) { // if holding alt (and not trimming), duplicate rather than move
- // duplicate clips
+ && panel_timeline->trim_target == -1) {
+
+ // if the user was holding alt (and not trimming), we duplicate clips rather than move them
QVector old_clips;
QVector new_clips;
QVector delete_areas;
for (int i=0;ighosts.size();i++) {
const Ghost& g = panel_timeline->ghosts.at(i);
if (g.old_in != g.in || g.old_out != g.out || g.track != g.old_track || g.clip_in != g.old_clip_in) {
+
// create copy of clip
ClipPtr c(olive::ActiveSequence->clips.at(g.clip)->copy(olive::ActiveSequence));
@@ -1046,111 +1172,277 @@ void TimelineWidget::mouseReleaseEvent(QMouseEvent *event) {
old_clips.append(g.clip);
new_clips.append(c);
+
}
}
+
if (new_clips.size() > 0) {
+
+ // delete anything under the new clips
panel_timeline->delete_areas_and_relink(ca, delete_areas, false);
// relink duplicated clips
panel_timeline->relink_clips_using_ids(old_clips, new_clips);
+ // add them
ca->append(new AddClipCommand(olive::ActiveSequence, new_clips));
+
}
+
} else {
- // INSERT if holding ctrl
+
+ // if we're not holding alt, this will just be a move
+
+ // if the user is holding ctrl, perform an insert rather than an overwrite
if (panel_timeline->tool == TIMELINE_TOOL_POINTER && ctrl) {
+
insert_clips(ca);
+
} else if (panel_timeline->tool == TIMELINE_TOOL_POINTER || panel_timeline->tool == TIMELINE_TOOL_SLIDE) {
- // move clips
+
+ // if the user is not holding ctrl, we start standard clip movement
+
+ // delete everything under the new clips
QVector delete_areas;
for (int i=0;ighosts.size();i++) {
// step 1 - set clips that are moving to "undeletable" (to avoid step 2 deleting any part of them)
const Ghost& g = panel_timeline->ghosts.at(i);
+ // set clip to undeletable so it's unaffected by delete_areas_and_relink() below
olive::ActiveSequence->clips.at(g.clip)->undeletable = true;
+
+ // if the user was moving a transition make sure they're undeletable too
if (g.transition != nullptr) {
g.transition->parent_clip->undeletable = true;
- if (g.transition->secondary_clip != nullptr) g.transition->secondary_clip->undeletable = true;
+ if (g.transition->secondary_clip != nullptr) {
+ g.transition->secondary_clip->undeletable = true;
+ }
}
+ // set area to delete
Selection s;
s.in = g.in;
s.out = g.out;
s.track = g.track;
delete_areas.append(s);
}
+
panel_timeline->delete_areas_and_relink(ca, delete_areas, false);
+
+ // clean up, i.e. make everything not undeletable again
for (int i=0;ighosts.size();i++) {
const Ghost& g = panel_timeline->ghosts.at(i);
olive::ActiveSequence->clips.at(g.clip)->undeletable = false;
+
if (g.transition != nullptr) {
g.transition->parent_clip->undeletable = false;
- if (g.transition->secondary_clip != nullptr) g.transition->secondary_clip->undeletable = false;
+ if (g.transition->secondary_clip != nullptr) {
+ g.transition->secondary_clip->undeletable = false;
+ }
}
- }
+ }
}
+
+ // finally, perform actual movement of clips
for (int i=0;ighosts.size();i++) {
Ghost& g = panel_timeline->ghosts[i];
- // step 3 - move clips
ClipPtr c = olive::ActiveSequence->clips.at(g.clip);
- if (g.transition == nullptr) {
- move_clip(ca, c, (g.in - g.old_in), (g.out - g.old_out), (g.clip_in - g.old_clip_in), (g.track - g.old_track), true, true);
- // adjust transitions if we need to
- long new_clip_length = (g.out - g.in);
- if (c->get_opening_transition() != nullptr) {
- long max_open_length = new_clip_length;
- if (c->get_closing_transition() != nullptr && panel_timeline->trim_type == TRIM_OUT) {
- max_open_length -= c->get_closing_transition()->get_true_length();
- }
- if (max_open_length <= 0) {
- ca->append(new DeleteTransitionCommand(c->opening_transition));
- } else if (c->get_opening_transition()->get_true_length() > max_open_length) {
- ca->append(new ModifyTransitionCommand(c->opening_transition, max_open_length));
- }
- }
- if (c->get_closing_transition() != nullptr) {
- long max_open_length = new_clip_length;
- if (c->get_opening_transition() != nullptr && panel_timeline->trim_type == TRIM_IN) {
- max_open_length -= c->get_opening_transition()->get_true_length();
- }
- if (max_open_length <= 0) {
- ca->append(new DeleteTransitionCommand(c->closing_transition));
- } else if (c->get_closing_transition()->get_true_length() > max_open_length) {
- ca->append(new ModifyTransitionCommand(c->closing_transition, max_open_length));
- }
- }
+ if (g.transition == nullptr) {
+
+ // if this was a clip rather than a transition
+
+ move_clip(ca, c, (g.in - g.old_in), (g.out - g.old_out), (g.clip_in - g.old_clip_in), (g.track - g.old_track), false, true);
+
} else {
+
+ // if the user was moving a transition
+
bool is_opening_transition = (g.transition == c->get_opening_transition());
long new_transition_length = g.out - g.in;
if (g.transition->secondary_clip != nullptr) new_transition_length >>= 1;
- ca->append(new ModifyTransitionCommand(is_opening_transition ? c->opening_transition : c->closing_transition, new_transition_length));
+ ca->append(
+ new ModifyTransitionCommand(is_opening_transition ? c->opening_transition : c->closing_transition,
+ new_transition_length)
+ );
long clip_length = c->getLength();
if (g.transition->secondary_clip != nullptr) {
+
+ // if this is a shared transition
if (g.in != g.old_in && g.trim_type == TRIM_NONE) {
long movement = g.in - g.old_in;
- move_clip(ca, g.transition->parent_clip, movement, 0, movement, 0, false, true);
- move_clip(ca, g.transition->secondary_clip, 0, movement, 0, 0, false, true);
+
+ // check if the transition is going to extend the out point (opening clip)
+ long timeline_out_movement = 0;
+ if (g.out > g.transition->parent_clip->timeline_out) {
+ timeline_out_movement = g.out - g.transition->parent_clip->timeline_out;
+ }
+
+ // check if the transition is going to extend the in point (closing clip)
+ long timeline_in_movement = 0;
+ if (g.in < g.transition->secondary_clip->timeline_in) {
+ timeline_in_movement = g.in - g.transition->secondary_clip->timeline_in;
+ }
+
+ move_clip(ca, g.transition->parent_clip, movement, timeline_out_movement, movement, 0, false, true);
+ move_clip(ca, g.transition->secondary_clip, timeline_in_movement, movement, timeline_in_movement, 0, false, true);
+
+ make_room_for_transition(ca, g.transition->parent_clip, kTransitionOpening, g.in, g.out, false);
+ make_room_for_transition(ca, g.transition->secondary_clip, kTransitionClosing, g.in, g.out, false);
+
}
+
} else if (is_opening_transition) {
+
if (g.in != g.old_in) {
// if transition is going to make the clip bigger, make the clip bigger
- move_clip(ca, c, (g.in - g.old_in), 0, (g.clip_in - g.old_clip_in), 0, true, true);
+
+ // check if the transition is going to extend the out point
+ long timeline_out_movement = 0;
+ if (g.out > g.transition->parent_clip->timeline_out) {
+ timeline_out_movement = g.out - g.transition->parent_clip->timeline_out;
+ }
+
+ move_clip(ca, c, (g.in - g.old_in), timeline_out_movement, (g.clip_in - g.old_clip_in), 0, false, true);
clip_length -= (g.in - g.old_in);
}
make_room_for_transition(ca, c, kTransitionOpening, g.in, g.out, false);
+
} else {
+
if (g.out != g.old_out) {
+
+ // check if the transition is going to extend the in point
+ long timeline_in_movement = 0;
+ if (g.in < g.transition->parent_clip->timeline_in) {
+ timeline_in_movement = g.in - g.transition->parent_clip->timeline_in;
+ }
+
// if transition is going to make the clip bigger, make the clip bigger
- move_clip(ca, c, 0, (g.out - g.old_out), 0, 0, true, true);
+ move_clip(ca, c, timeline_in_movement, (g.out - g.old_out), timeline_in_movement, 0, false, true);
clip_length += (g.out - g.old_out);
}
make_room_for_transition(ca, c, kTransitionClosing, g.in, g.out, false);
+
+ }
+ }
+ }
+
+ // time to verify the transitions of moved clips
+ for (int i=0;ighosts.size();i++) {
+ const Ghost& g = panel_timeline->ghosts.at(i);
+
+ // only applies to moving clips, transitions are verified above instead
+ if (g.transition == nullptr) {
+ ClipPtr c = olive::ActiveSequence->clips.at(g.clip);
+
+ long new_clip_length = g.out - g.in;
+
+ // using a for loop between constants to repeat the same steps for the opening and closing transitions
+ for (int t=kTransitionOpening;t<=kTransitionClosing;t++) {
+
+ TransitionPtr transition = (t == kTransitionOpening) ? c->opening_transition : c->closing_transition;
+
+ // check the whether the clip has a transition here
+ if (transition != nullptr) {
+
+ // if the new clip size exceeds the opening transition's length, resize the transition
+ if (new_clip_length < transition->get_true_length()) {
+ ca->append(new ModifyTransitionCommand(transition, new_clip_length));
+ }
+
+ // check if the transition is a shared transition (it'll never have a secondary clip if it isn't)
+ if (transition->secondary_clip != nullptr) {
+
+ // check if the transition's "edge" is going to move
+ if ((t == kTransitionOpening && g.in != g.old_in)
+ || (t == kTransitionClosing && g.out != g.old_out)) {
+
+ // if we're here, this clip shares its opening transition as the closing transition of another
+ // clip (or vice versa), and the in point is moving, so we may have to account for this
+
+ // the other clip sharing this transition may be moving as well, meaning we don't have to do
+ // anything
+
+ bool split = true;
+
+ // loop through ghosts to find out
+
+ // for a shared transition, the secondary_clip will always be the closing transition side and
+ // the parent_clip will always be the opening transition side
+ ClipPtr search_clip = (t == kTransitionOpening)
+ ? transition->secondary_clip : transition->parent_clip;
+
+ for (int j=0;jghosts.size();j++) {
+ const Ghost& other_clip_ghost = panel_timeline->ghosts.at(j);
+
+ if (olive::ActiveSequence->clips.at(other_clip_ghost.clip) == search_clip) {
+
+ // we found the other clip in the current ghosts/selections
+
+ // see if it's destination edge will be equal to this ghost's edge (in which case the
+ // transition doesn't need to change)
+ //
+ // also only do this if j is less than i, because it only needs to happen once and chances are
+ // the other clip already
+
+ bool edges_still_touch;
+ if (t == kTransitionOpening) {
+ edges_still_touch = (other_clip_ghost.out == g.in);
+ } else {
+ edges_still_touch = (other_clip_ghost.in == g.out);
+ }
+
+ if (edges_still_touch || j < i) {
+ split = false;
+ }
+
+ break;
+ }
+ }
+
+ if (split) {
+ // separate shared transition into one transition for each clip
+
+ if (t == kTransitionOpening) {
+
+ // set transition to single-clip mode
+ ca->append(new SetPointer(reinterpret_cast(&transition->secondary_clip),
+ nullptr));
+
+ // create duplicate transition for other clip
+ ca->append(new AddTransitionCommand(nullptr,
+ transition->secondary_clip,
+ transition,
+ nullptr,
+ 0));
+
+ } else {
+
+ // set transition to single-clip mode
+ ca->append(new SetPointer(reinterpret_cast(&transition->secondary_clip),
+ nullptr));
+
+ // that transition will now attach to the other clip, so we duplicate it for this one
+
+ // create duplicate transition for this clip
+ ca->append(new AddTransitionCommand(nullptr,
+ transition->secondary_clip,
+ transition,
+ nullptr,
+ 0));
+
+ }
+ }
+
+ }
+ }
+ }
}
}
}
@@ -1164,10 +1456,11 @@ void TimelineWidget::mouseReleaseEvent(QMouseEvent *event) {
// if the transition is greater than 0 length (if it is 0, we make nothing)
if (g.in != g.out) {
- // get transition length
+ // get transition coordinates on the timeline
long transition_start = qMin(g.in, g.out);
long transition_end = qMax(g.in, g.out);
+ // get clip references from tool's cached data
ClipPtr open = (panel_timeline->transition_tool_open_clip > -1)
? olive::ActiveSequence->clips.at(panel_timeline->transition_tool_open_clip)
: nullptr;
@@ -1176,71 +1469,17 @@ void TimelineWidget::mouseReleaseEvent(QMouseEvent *event) {
? olive::ActiveSequence->clips.at(panel_timeline->transition_tool_close_clip)
: nullptr;
- bool shared_transition = (open != nullptr && close != nullptr);
-
- if (open != nullptr) {
- open->undeletable = true;
- }
- if (close != nullptr) {
- close->undeletable = true;
- }
-
- // delete everything under this new transition
- QVector areas;
- Selection s;
- s.in = transition_start;
- s.out = transition_end;
- s.track = g.track;
- areas.append(s);
- panel_timeline->delete_areas_and_relink(ca, areas, false);
-
- if (open != nullptr) {
- open->undeletable = false;
- }
- if (close != nullptr) {
- close->undeletable = false;
- }
- if (open != nullptr) {
- make_room_for_transition(ca, open, kTransitionOpening, transition_start, transition_end, true);
-
- if (transition_start < open->timeline_in || transition_end > open->timeline_out) {
-// long effective_out = (close != nullptr) ? close->timeline_out : open->timeline_out;
- long new_in = qMin(transition_start, open->timeline_in);
- long new_out = qMax(transition_end, open->timeline_out);
-
- move_clip(ca,
- open,
- new_in,
- new_out,
- open->clip_in - (open->timeline_in - new_in),
- open->track);
- }
- }
-
- if (close != nullptr) {
- make_room_for_transition(ca, close, kTransitionClosing, transition_start, transition_end, true);
-
- if (transition_start < close->timeline_in || transition_end > close->timeline_out) {
-// long effective_in = (open != nullptr) ? open->timeline_in : close->timeline_in;
- long new_in = qMin(transition_start, close->timeline_in);
- long new_out = qMax(transition_end, close->timeline_out);
-
- move_clip(ca,
- close,
- new_in,
- new_out,
- close->clip_in - (close->timeline_in - new_in),
- close->track);
- }
- }
-
+ // if it's shared, the transition length is halved (one half for each clip will result in the full length)
long transition_length = transition_end - transition_start;
- if (shared_transition) {
+ if (open != nullptr && close != nullptr) {
transition_length /= 2;
}
+ VerifyTransitionsAfterCreating(ca, open, close, transition_start, transition_end);
+
+ // finally, add the transition to these clips
ca->append(new AddTransitionCommand(open,
close,
nullptr,
@@ -1393,7 +1632,9 @@ void TimelineWidget::update_ghosts(const QPoint& mouse_pos, bool lock_frame) {
const Ghost& g = panel_timeline->ghosts.at(i);
// snap ghost's in point
- if (panel_timeline->trim_target == -1 || g.trim_type == TRIM_IN) {
+ if ((panel_timeline->tool != TIMELINE_TOOL_TRANSITION && panel_timeline->trim_target == -1)
+ || g.trim_type == TRIM_IN
+ || panel_timeline->transition_tool_open_clip > -1) {
fm = g.old_in + frame_diff;
if (panel_timeline->snap_to_timeline(&fm, true, true, true)) {
frame_diff = fm - g.old_in;
@@ -1402,7 +1643,9 @@ void TimelineWidget::update_ghosts(const QPoint& mouse_pos, bool lock_frame) {
}
// snap ghost's out point
- if (panel_timeline->trim_target == -1 || g.trim_type == TRIM_OUT) {
+ if ((panel_timeline->tool != TIMELINE_TOOL_TRANSITION && panel_timeline->trim_target == -1)
+ || g.trim_type == TRIM_OUT
+ || panel_timeline->transition_tool_close_clip > -1) {
fm = g.old_out + frame_diff;
if (panel_timeline->snap_to_timeline(&fm, true, true, true)) {
frame_diff = fm - g.old_out;
@@ -1411,7 +1654,7 @@ void TimelineWidget::update_ghosts(const QPoint& mouse_pos, bool lock_frame) {
}
// if the ghost is attached to a clip, snap its markers too
- if (panel_timeline->trim_target == -1 && g.clip >= 0) {
+ if (panel_timeline->trim_target == -1 && g.clip >= 0 && panel_timeline->tool != TIMELINE_TOOL_TRANSITION) {
ClipPtr c = olive::ActiveSequence->clips.at(g.clip);
for (int j=0;jget_markers().size();j++) {
long marker_real_time = c->get_markers().at(j).frame + c->timeline_in - c->clip_in;
@@ -1652,7 +1895,8 @@ void TimelineWidget::update_ghosts(const QPoint& mouse_pos, bool lock_frame) {
g.in = g.old_in + frame_diff;
g.out = g.old_out + frame_diff;
- if (g.transition != nullptr && g.transition == olive::ActiveSequence->clips.at(g.clip)->get_opening_transition()) {
+ if (g.transition != nullptr
+ && g.transition == olive::ActiveSequence->clips.at(g.clip)->get_opening_transition()) {
g.clip_in = g.old_clip_in + frame_diff;
}
@@ -1913,12 +2157,11 @@ void TimelineWidget::mouseMoveEvent(QMouseEvent *event) {
g.transition = nullptr;
// check if whole clip is added
- bool add = is_clip_selected(c, true);
+ bool add = false;
- // if a whole clip is not selected, maybe just a transition is
+ // check if a transition is selected (prioritize transition selection)
// (only the pointer tool supports moving transitions)
- if (!add
- && panel_timeline->tool == TIMELINE_TOOL_POINTER
+ if (panel_timeline->tool == TIMELINE_TOOL_POINTER
&& (c->get_opening_transition() != nullptr || c->get_closing_transition() != nullptr)) {
// check if any selections contain a whole transition
@@ -1946,6 +2189,11 @@ void TimelineWidget::mouseMoveEvent(QMouseEvent *event) {
}
+ // if a transition isn't selected, check if the whole clip is
+ if (!add) {
+ add = is_clip_selected(c, true);
+ }
+
if (add) {
if (g.transition != nullptr) {
diff --git a/ui/timelinewidget.h b/ui/timelinewidget.h
index 3e7481618..68983c1ac 100644
--- a/ui/timelinewidget.h
+++ b/ui/timelinewidget.h
@@ -90,6 +90,8 @@ private:
int getScreenPointFromTrack(int track);
int getClipIndexFromCoords(long frame, int track);
+ void VerifyTransitionHelper();
+
bool track_resizing;
int track_target;