From a1619ce8ceb7ccc1fdc8a6aacc01c491b6035ef4 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 19 Feb 2019 01:16:37 -0800 Subject: [PATCH] transition rewrite mostly done --- debug.cpp | 15 +- panels/project.cpp | 9 +- panels/timeline.cpp | 92 ++- playback/cacher.cpp | 1534 ++++++++++++++++++++-------------------- playback/playback.cpp | 5 + project/clip.cpp | 9 - project/clip.h | 1 + project/undo.cpp | 62 +- project/undo.h | 2 +- ui/renderfunctions.cpp | 1090 ++++++++++++++-------------- ui/timelinewidget.cpp | 482 ++++++++++--- ui/timelinewidget.h | 2 + 12 files changed, 1792 insertions(+), 1511 deletions(-) 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;