From 6109ed90029af67281c2fef2fb8a7b3614bc5b40 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sun, 22 Jul 2018 17:59:03 +0100 Subject: [PATCH] many fixes and ripple edit corrections --- effects/effects.h | 4 +- effects/shakeeffect.cpp | 16 ++-- effects/transformeffect.cpp | 7 +- mainwindow.cpp | 10 +++ mainwindow.h | 4 + mainwindow.ui | 20 ++++- panels/project.cpp | 20 ++--- panels/project.h | 20 +++-- panels/timeline.cpp | 158 +++++++++++++++++++++++++----------- panels/timeline.h | 7 +- playback/cacher.cpp | 19 +++-- playback/playback.cpp | 11 +-- project/clip.cpp | 8 +- project/clip.h | 6 +- project/sequence.cpp | 2 +- project/undo.cpp | 80 +++++++++++++----- project/undo.h | 8 ++ ui/sourcetable.cpp | 45 +++++++++- ui/sourcetable.h | 1 + ui/timelinewidget.cpp | 118 ++++++++++++++++----------- ui/timelinewidget.h | 3 +- ui/viewerwidget.cpp | 14 ++-- 22 files changed, 400 insertions(+), 181 deletions(-) diff --git a/effects/effects.h b/effects/effects.h index 4a39c2e18..d5049c71d 100644 --- a/effects/effects.h +++ b/effects/effects.h @@ -75,8 +75,8 @@ public: LabelSlider* intensity_val; LabelSlider* rotation_val; LabelSlider* frequency_val; -private slots: - void set_values(); +public slots: + void init(); private: int shake_progress; int shake_limit; diff --git a/effects/shakeeffect.cpp b/effects/shakeeffect.cpp index 629c36cf8..27648768f 100644 --- a/effects/shakeeffect.cpp +++ b/effects/shakeeffect.cpp @@ -33,7 +33,7 @@ ShakeEffect::ShakeEffect(Clip *c) : Effect(c, EFFECT_TYPE_VIDEO, VIDEO_SHAKE_EFF rotation_val->set_value(0); frequency_val->set_value(10); - set_values(); + init(); connect(intensity_val, SIGNAL(valueChanged()), this, SLOT(field_changed())); connect(rotation_val, SIGNAL(valueChanged()), this, SLOT(field_changed())); @@ -43,12 +43,14 @@ ShakeEffect::ShakeEffect(Clip *c) : Effect(c, EFFECT_TYPE_VIDEO, VIDEO_SHAKE_EFF connect(frequency_val, SIGNAL(valueChanged()), this, SLOT(set_values())); } -void ShakeEffect::set_values() { - shake_limit = qRound(parent_clip->sequence->frame_rate / frequency_val->value()); - shake_progress = shake_limit; - next_x = 0; - next_y = 0; - next_rot = 0; +void ShakeEffect::init() { + if (parent_clip->sequence != NULL) { + shake_limit = qRound(parent_clip->sequence->frame_rate / frequency_val->value()); + shake_progress = shake_limit; + next_x = 0; + next_y = 0; + next_rot = 0; + } } Effect* ShakeEffect::copy(Clip* c) { diff --git a/effects/transformeffect.cpp b/effects/transformeffect.cpp index 77dc15145..0f1964c26 100644 --- a/effects/transformeffect.cpp +++ b/effects/transformeffect.cpp @@ -85,15 +85,16 @@ TransformEffect::TransformEffect(Clip* c) : Effect(c, EFFECT_TYPE_VIDEO, VIDEO_T } void TransformEffect::init() { - if (parent_clip->media_stream != NULL) { + MediaStream* ms = parent_clip->media->get_stream_from_file_index(parent_clip->media_stream); + if (ms != NULL && parent_clip->sequence != NULL) { position_x->set_default_value(parent_clip->sequence->width/2); position_y->set_default_value(parent_clip->sequence->height/2); scale_x->set_default_value(100); scale_y->set_default_value(100); scale_y->setEnabled(false); uniform_scale_box->setChecked(true); - default_anchor_x = parent_clip->media_stream->video_width/2; - default_anchor_y = parent_clip->media_stream->video_height/2; + default_anchor_x = ms->video_width/2; + default_anchor_y = ms->video_height/2; anchor_x_box->set_default_value(default_anchor_x); anchor_y_box->set_default_value(default_anchor_y); opacity->set_default_value(100); diff --git a/mainwindow.cpp b/mainwindow.cpp index f0e281546..ed91a4058 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -619,3 +619,13 @@ void MainWindow::on_actionLink_Unlink_triggered() { panel_timeline->toggle_links(); } + +void MainWindow::on_actionRipple_To_In_Point_triggered() +{ + panel_timeline->ripple_to_in_point(true); +} + +void MainWindow::on_actionRipple_to_Out_Point_triggered() +{ + panel_timeline->ripple_to_in_point(false); +} diff --git a/mainwindow.h b/mainwindow.h index bccad6377..62f6d0f1d 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -154,6 +154,10 @@ private slots: void on_actionLink_Unlink_triggered(); + void on_actionRipple_To_In_Point_triggered(); + + void on_actionRipple_to_Out_Point_triggered(); + private: Ui::MainWindow *ui; void setup_layout(); diff --git a/mainwindow.ui b/mainwindow.ui index 8f49bb0ca..1a7cbd7c6 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -74,9 +74,11 @@ - + + + @@ -621,6 +623,22 @@ Ctrl+L + + + Ripple to In Point + + + Q + + + + + Ripple to Out Point + + + W + + diff --git a/panels/project.cpp b/panels/project.cpp index e5f08a559..37774aec3 100644 --- a/panels/project.cpp +++ b/panels/project.cpp @@ -354,30 +354,30 @@ void Project::import_dialog() { process_file_list(files); } -void Project::set_item_to_folder(QTreeWidgetItem* item) { +void set_item_to_folder(QTreeWidgetItem* item) { item->setData(0, Qt::UserRole + 1, MEDIA_TYPE_FOLDER); } -Media* Project::get_media_from_tree(QTreeWidgetItem* item) { +Media* get_media_from_tree(QTreeWidgetItem* item) { return reinterpret_cast(item->data(0, Qt::UserRole + 2).value()); } -void Project::set_media_of_tree(QTreeWidgetItem* item, Media* media) { +void set_media_of_tree(QTreeWidgetItem* item, Media* media) { item->setText(0, media->name); item->setData(0, Qt::UserRole + 1, MEDIA_TYPE_FOOTAGE); item->setData(0, Qt::UserRole + 2, QVariant::fromValue(reinterpret_cast(media))); } -Sequence* Project::get_sequence_from_tree(QTreeWidgetItem* item) { +Sequence* get_sequence_from_tree(QTreeWidgetItem* item) { return reinterpret_cast(item->data(0, Qt::UserRole + 2).value()); } -void Project::set_sequence_of_tree(QTreeWidgetItem* item, Sequence* sequence) { +void set_sequence_of_tree(QTreeWidgetItem* item, Sequence* sequence) { item->setData(0, Qt::UserRole + 1, MEDIA_TYPE_SEQUENCE); item->setData(0, Qt::UserRole + 2, QVariant::fromValue(reinterpret_cast(sequence))); } -int Project::get_type_from_tree(QTreeWidgetItem* item) { +int get_type_from_tree(QTreeWidgetItem* item) { return item->data(0, Qt::UserRole + 1).toInt(); } @@ -588,7 +588,7 @@ bool Project::load_worker(QFile& f, QXmlStreamReader& stream, int type) { if (stream.name() == "clip" && stream.isStartElement()) { // int media_id, stream_id; - Clip* c = new Clip(); + Clip* c = new Clip(s); for (int j=0;jsave_id == media_id) { c->media = m; - c->media_stream = c->media->get_stream_from_file_index(stream_id); + c->media_stream = stream_id; break; } } - c->sequence = s; - // load links and effects while (!(stream.name() == "clip" && stream.isEndElement()) && !stream.atEnd()) { stream.readNext(); @@ -864,7 +862,7 @@ void Project::save_folder(QXmlStreamWriter& stream, QTreeWidgetItem* parent, int stream.writeAttribute("g", QString::number(c->color_g)); stream.writeAttribute("b", QString::number(c->color_b)); stream.writeAttribute("media", QString::number(c->media->save_id)); - stream.writeAttribute("stream", QString::number(c->media_stream->file_index)); + stream.writeAttribute("stream", QString::number(c->media_stream)); stream.writeStartElement("linked"); // linked for (int k=0;klinked.size();k++) { diff --git a/panels/project.h b/panels/project.h index ca96c67ae..95a881c8b 100644 --- a/panels/project.h +++ b/panels/project.h @@ -37,6 +37,13 @@ extern QString project_url; extern QStringList recent_projects; extern QString recent_proj_file; +int get_type_from_tree(QTreeWidgetItem* item); +Media* get_media_from_tree(QTreeWidgetItem* item); +void set_media_of_tree(QTreeWidgetItem* item, Media* media); +Sequence* get_sequence_from_tree(QTreeWidgetItem* item); +void set_sequence_of_tree(QTreeWidgetItem* item, Sequence* sequence); +void set_item_to_folder(QTreeWidgetItem* item); + class Project : public QDockWidget { Q_OBJECT @@ -47,13 +54,10 @@ public: bool is_focused(); void clear(); QTreeWidgetItem* import_file(QString url); - void import_dialog(); void new_sequence(TimelineAction* ta, Sequence* s, bool open, QTreeWidgetItem* parent); QString get_next_sequence_name(); void delete_media(QTreeWidgetItem* item); - void delete_selected_media(); void process_file_list(QStringList& files); - void duplicate_selected(); void new_project(); void load_project(); @@ -61,15 +65,13 @@ public: QTreeWidgetItem* new_folder(); - int get_type_from_tree(QTreeWidgetItem* item); - Media* get_media_from_tree(QTreeWidgetItem* item); - void set_media_of_tree(QTreeWidgetItem* item, Media* media); - Sequence* get_sequence_from_tree(QTreeWidgetItem* item); - void set_sequence_of_tree(QTreeWidgetItem* item, Sequence* sequence); - void set_item_to_folder(QTreeWidgetItem* item); void save_recent_projects(); SourceTable* source_table; +public slots: + void import_dialog(); + void delete_selected_media(); + void duplicate_selected(); private: Ui::Project *ui; QTreeWidgetItem* new_item(); diff --git a/panels/timeline.cpp b/panels/timeline.cpp index c4187f92d..0a86edf1e 100644 --- a/panels/timeline.cpp +++ b/panels/timeline.cpp @@ -285,44 +285,55 @@ void Timeline::delete_selection(bool ripple_delete) { TimelineAction* ta = new TimelineAction(); - long ripple_point = selections.at(0).in; - long ripple_length = selections.at(0).out - selections.at(0).in; - - // retrieve ripple_point and ripple_length from current selection - for (int i=0;i s.in) ripple_point = s.in; - if (ripple_length > s.out - s.in) ripple_length = s.out - s.in; - } - } delete_areas_and_relink(ta, selections); - selections.clear(); - if (ripple_delete) { - long validator; + if (ripple_delete) { + long ripple_point = selections.at(0).in; + long ripple_length = selections.at(0).out - selections.at(0).in; + + // retrieve ripple_point and ripple_length from current selection + for (int i=1;i ignore; + bool can_ripple = true; for (int i=0;iclip_count();i++) { - // check every clip after and see if it'll collide - // NOTE, we could probably re-use the validation code for the ripple tool here for optimization (since it's technically better code I think) Clip* c = sequence->get_clip(i); - if (c != NULL && c->timeline_in >= ripple_point) { - for (int j=0;jclip_count();j++) { - Clip* cc = sequence->get_clip(j); - if (cc != NULL && cc->timeline_in < ripple_point) { - validator = c->timeline_in - ripple_length - cc->timeline_out; - if (validator < 0) ripple_length += validator; - - if (ripple_length <= 0) { - // we've seen all we need to see here (can't ripple so stop looping) - i = j = sequence->clip_count(); - } - } - } - } + if (c->timeline_in < ripple_point && c->timeline_out > ripple_point) { + // conflict detected, but this clip may be getting deleted so let's check + bool deleted = false; + for (int j=0;jtrack + && !(c->timeline_in < s.in && c->timeline_out < s.in) + && !(c->timeline_in > s.out && c->timeline_out > s.out)) { + deleted = true; + break; + } + } + if (!deleted) { + for (int j=0;jclip_count();j++) { + Clip* cc = sequence->get_clip(j); + if (cc->track == c->track + && cc->timeline_in > c->timeline_out + && cc->timeline_in < c->timeline_out + ripple_length) { + ripple_length = cc->timeline_in - c->timeline_out; + } + } + } + } } - if (ripple_length > 0) ripple(ta, ripple_point, -ripple_length); + if (can_ripple) ta->ripple(sequence, ripple_point, -ripple_length, ignore); } + selections.clear(); + undo_stack.push(ta); redraw_all_clips(true); @@ -349,16 +360,7 @@ void Timeline::set_zoom(bool in) { redraw_all_clips(false); } -void Timeline::ripple(TimelineAction* ta, long ripple_point, long ripple_length) { - // ripple all clips around the ripple_point - for (int i=0;iclip_count();i++) { - Clip* c = sequence->get_clip(i); - if (c != NULL && c->timeline_in >= ripple_point) { - ta->increase_timeline_in(sequence, i, ripple_length); - ta->increase_timeline_out(sequence, i, ripple_length); - } - } - +/*void Timeline::ripple(TimelineAction* ta, long ripple_point, long ripple_length) { // ripple the selections for (int i=0;iget_clip(p); if (pre != NULL && pre->timeline_in < frame && pre->timeline_out > frame) { // guard against attempts to split at in/out points - Clip* post = pre->copy(); + Clip* post = pre->copy(sequence); ta->set_timeline_out(sequence, p, frame); post->timeline_in = frame; @@ -545,7 +547,7 @@ void Timeline::delete_areas_and_relink(TimelineAction* ta, QVector& a // middle of clip is within deletion area // duplicate clip - Clip* post = c->copy(); + Clip* post = c->copy(sequence); ta->set_timeline_out(sequence, j, s.in); post->timeline_in = s.out; @@ -585,7 +587,7 @@ void Timeline::copy(bool del) { cleared = true; } - Clip* copied_clip = c->copy(); + Clip* copied_clip = c->copy(NULL); // copy linked IDs (we correct these later in paste()) copied_clip->linked = c->linked; @@ -652,8 +654,7 @@ void Timeline::paste() { Clip* c = clip_clipboard.at(i); // create copy of clip and offset by playhead - Clip* cc = c->copy(); - cc->sequence = sequence; + Clip* cc = c->copy(sequence); cc->timeline_in += playhead; cc->timeline_out += playhead; cc->track = c->track; @@ -696,6 +697,65 @@ void Timeline::paste() { } } +void Timeline::ripple_to_in_point(bool in) { + if (sequence != NULL) { + // get track count + int track_min = 0; + int track_max = 0; + + bool one_frame_mode = false; + + // find closest in point to playhead + long in_point = in ? 0 : LONG_MAX; + for (int i=0;iclip_count();i++) { + Clip* c = sequence->get_clip(i); + track_min = qMin(track_min, c->track); + track_max = qMax(track_max, c->track); + if ((in && c->timeline_in > in_point && c->timeline_in <= playhead) + || (!in && c->timeline_in < in_point && c->timeline_in >= playhead)) { + in_point = c->timeline_in; + if (playhead == in_point) { + one_frame_mode = true; + break; + } + } + if ((in && c->timeline_out > in_point && c->timeline_out <= playhead) + || (!in && c->timeline_out < in_point && c->timeline_out > playhead)) { + in_point = c->timeline_out; + if (playhead == in_point) { + one_frame_mode = true; + break; + } + } + } + + if (one_frame_mode) { + + } else { + // set up deletion areas based on track count + QVector areas; + for (int i=track_min;i<=track_max;i++) { + Selection s; + s.in = qMin(in_point, playhead); + s.out = qMax(in_point, playhead); + s.track = i; + areas.append(s); + } + + // trim and move clips around the in point + QVector ignore; + TimelineAction* ta = new TimelineAction(); + delete_areas_and_relink(ta, areas); + ta->ripple(sequence, in_point, (in) ? (in_point - playhead) : (playhead - in_point), ignore); + undo_stack.push(ta); + + redraw_all_clips(true); + + if (in) seek(in_point); + } + } +} + bool Timeline::split_selection(TimelineAction* ta) { bool split = false; @@ -712,14 +772,14 @@ bool Timeline::split_selection(TimelineAction* ta) { const Selection& s = selections.at(i); if (s.track == clip->track) { if (clip->timeline_in < s.in && clip->timeline_out > s.out) { - Clip* split_A = clip->copy(); + Clip* split_A = clip->copy(sequence); split_A->clip_in += (s.in - clip->timeline_in); split_A->timeline_in = s.in; split_A->timeline_out = s.out; pre_splits.append(j); post_splits.append(split_A); - Clip* split_B = clip->copy(); + Clip* split_B = clip->copy(sequence); split_B->clip_in += (s.out - clip->timeline_in); split_B->timeline_in = s.out; secondary_post_splits.append(split_B); diff --git a/panels/timeline.h b/panels/timeline.h index d65213ace..7310dc753 100644 --- a/panels/timeline.h +++ b/panels/timeline.h @@ -1,4 +1,4 @@ -#ifndef TIMELINE_H +#ifndef TIMELINE_H #define TIMELINE_H #include "ui/timelinetools.h" @@ -32,7 +32,7 @@ struct Ghost { // importing variables Media* media; - MediaStream* media_stream; + int media_stream; // other variables long ghost_length; @@ -84,6 +84,7 @@ public: QVector get_tracks_of_linked_clips(int i); bool has_clip_been_split(int c); void toggle_links(); + void ripple_to_in_point(bool in); int get_snap_range(); int getScreenPointFromFrame(long frame); @@ -169,7 +170,7 @@ public: bool importing; // ripple - void ripple(TimelineAction* ta, long ripple_point, long ripple_length); +// void ripple(TimelineAction* ta, long ripple_point, long ripple_length); Ui::Timeline *ui; public slots: diff --git a/playback/cacher.cpp b/playback/cacher.cpp index a4a5375ba..6ebc170b9 100644 --- a/playback/cacher.cpp +++ b/playback/cacher.cpp @@ -151,7 +151,8 @@ void cache_video_worker(Clip* c, long playhead, ClipCache* cache) { void reset_cache(Clip* c, long target_frame) { // if we seek to a whole other place in the timeline, we'll need to reset the cache with new values - if (c->media_stream->infinite_length) { + MediaStream* ms = c->media->get_stream_from_file_index(c->media_stream); + if (ms->infinite_length) { // if this clip is a still image, we only need one frame if (!c->cache_A.written) { retrieve_next_frame_raw_data(c, c->cache_A.frames[0]); @@ -165,7 +166,7 @@ void reset_cache(Clip* c, long target_frame) { if (c->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { // seeks to nearest keyframe (target_frame represents internal clip frame) - av_seek_frame(c->formatCtx, c->media_stream->file_index, clip_frame_to_seconds(c, target_frame) / timebase, AVSEEK_FLAG_BACKWARD); + av_seek_frame(c->formatCtx, ms->file_index, clip_frame_to_seconds(c, target_frame) / timebase, AVSEEK_FLAG_BACKWARD); // play up to the frame we actually want long retrieved_frame = 0; @@ -184,7 +185,7 @@ void reset_cache(Clip* c, long target_frame) { } else if (c->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { // seek (target_frame represents timeline timecode in frames, not clip timecode) swr_drop_output(c->swr_ctx, swr_get_out_samples(c->swr_ctx, 0)); - av_seek_frame(c->formatCtx, c->media_stream->file_index, playhead_to_seconds(c, target_frame) / timebase, AVSEEK_FLAG_BACKWARD); + av_seek_frame(c->formatCtx, ms->file_index, playhead_to_seconds(c, target_frame) / timebase, AVSEEK_FLAG_BACKWARD); c->audio_target_frame = target_frame; c->need_new_audio_frame = true; c->audio_just_reset = true; @@ -198,6 +199,7 @@ void open_clip_worker(Clip* clip) { // opens file resource for FFmpeg and prepares Clip struct for playback QByteArray ba = clip->media->url.toUtf8(); const char* filename = ba.constData(); + MediaStream* ms = clip->media->get_stream_from_file_index(clip->media_stream); int errCode = avformat_open_input( &clip->formatCtx, @@ -220,7 +222,7 @@ void open_clip_worker(Clip* clip) { av_dump_format(clip->formatCtx, 0, filename, 0); - clip->stream = clip->formatCtx->streams[clip->media_stream->file_index]; + 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); @@ -263,7 +265,7 @@ void open_clip_worker(Clip* clip) { ); // create memory cache for video - if (clip->media_stream->infinite_length) { + if (ms->infinite_length) { clip->cache_size = 1; } else { clip->cache_size = ceil(av_q2d(av_guess_frame_rate(clip->formatCtx, clip->stream, NULL))/4); // cache is half a second in total @@ -284,7 +286,7 @@ void open_clip_worker(Clip* clip) { av_frame_get_buffer(clip->cache_A.frames[i], 0); clip->cache_A.frames[i]->linesize[0] = clip->stream->codecpar->width*4; - if (!clip->media_stream->infinite_length) { + if (!ms->infinite_length) { clip->cache_B.frames[i] = av_frame_alloc(); av_frame_make_writable(clip->cache_B.frames[i]); clip->cache_B.frames[i]->width = clip->stream->codecpar->width; @@ -359,6 +361,7 @@ void cache_clip_worker(Clip* clip, long playhead, bool write_A, bool write_B, bo void close_clip_worker(Clip* clip) { // closes ffmpeg file handle and frees any memory used for caching + MediaStream* ms = clip->media->get_stream_from_file_index(clip->media_stream); if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { sws_freeContext(clip->sws_ctx); } else if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { @@ -371,10 +374,10 @@ void close_clip_worker(Clip* clip) { for (size_t i=0;icache_size;i++) { av_frame_free(&clip->cache_A.frames[i]); - if (!clip->media_stream->infinite_length) av_frame_free(&clip->cache_B.frames[i]); + if (!ms->infinite_length) av_frame_free(&clip->cache_B.frames[i]); } delete [] clip->cache_A.frames; - if (!clip->media_stream->infinite_length) delete [] clip->cache_B.frames; + if (!ms->infinite_length) delete [] clip->cache_B.frames; av_frame_free(&clip->frame); diff --git a/playback/playback.cpp b/playback/playback.cpp index 67df049b2..de7432326 100644 --- a/playback/playback.cpp +++ b/playback/playback.cpp @@ -77,12 +77,13 @@ void get_clip_frame(Clip* c, long playhead) { long clip_time = seconds_to_clip_frame(c, playhead_to_seconds(c, playhead)); // do we need to update the texture? - if ((!c->media_stream->infinite_length && c->texture_frame != clip_time) || - (c->media_stream->infinite_length && c->texture_frame == -1)) { + MediaStream* ms = c->media->get_stream_from_file_index(c->media_stream); + if ((!ms->infinite_length && c->texture_frame != clip_time) || + (ms->infinite_length && c->texture_frame == -1)) { AVFrame* current_frame = NULL; // get frame data - if (c->media_stream->infinite_length) { // if clip is a still frame, we only need one + if (ms->infinite_length) { // if clip is a still frame, we only need one if (c->cache_A.written) { // retrieve cached frame current_frame = c->cache_A.frames[0]; @@ -156,7 +157,7 @@ void get_clip_frame(Clip* c, long playhead) { // set up opengl texture if (c->texture == NULL) { c->texture = new QOpenGLTexture(QOpenGLTexture::Target2D); - c->texture->setSize(c->media_stream->video_width, c->media_stream->video_height); + c->texture->setSize(ms->video_width, ms->video_height); c->texture->setFormat(QOpenGLTexture::RGBA8_UNorm); c->texture->setMipLevels(c->texture->maximumMipLevels()); c->texture->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear); @@ -206,7 +207,7 @@ int retrieve_next_frame(Clip* c, AVFrame* f) { } read_ret = av_read_frame(c->formatCtx, c->pkt); c->pkt_written = true; - } while (read_ret >= 0 && c->pkt->stream_index != c->media_stream->file_index); + } while (read_ret >= 0 && c->pkt->stream_index != c->media_stream); if (read_ret >= 0) { int send_ret = avcodec_send_packet(c->codecCtx, c->pkt); diff --git a/project/clip.cpp b/project/clip.cpp index e074376cd..e435c04a4 100644 --- a/project/clip.cpp +++ b/project/clip.cpp @@ -12,7 +12,7 @@ extern "C" { #include } -Clip::Clip() { +Clip::Clip(Sequence* s) : sequence(s) { reset(); enabled = true; clip_in = 0; @@ -27,8 +27,8 @@ Clip::Clip() { pkt = new AVPacket(); } -Clip* Clip::copy() { - Clip* copy = new Clip(); +Clip* Clip::copy(Sequence* s) { + Clip* copy = new Clip(s); copy->enabled = enabled; copy->name = QString(name); @@ -39,7 +39,7 @@ Clip* Clip::copy() { copy->color_r = color_r; copy->color_g = color_g; copy->color_b = color_b; - copy->sequence = sequence; + copy->sequence = s; copy->media = media; copy->media_stream = media_stream; diff --git a/project/clip.h b/project/clip.h index 13a05c0a7..2fcb247db 100644 --- a/project/clip.h +++ b/project/clip.h @@ -36,9 +36,9 @@ struct ClipCache { struct Clip { - Clip(); + Clip(Sequence* s); ~Clip(); - Clip* copy(); + Clip* copy(Sequence* s); void reset(); bool undeletable; @@ -57,7 +57,7 @@ struct Clip quint8 color_b; long getLength(); Media* media; // attached media - MediaStream* media_stream; + int media_stream; Sequence* sequence; // other variables (should be "duplicated" in copy()) diff --git a/project/sequence.cpp b/project/sequence.cpp index 803f8f130..788a35a1b 100644 --- a/project/sequence.cpp +++ b/project/sequence.cpp @@ -25,7 +25,7 @@ Sequence* Sequence::copy() { for (int i=0;icopy(); + Clip* copy = c->copy(s); copy->linked = c->linked; s->add_clip(copy); } diff --git a/project/undo.cpp b/project/undo.cpp index 655b15207..14656b451 100644 --- a/project/undo.cpp +++ b/project/undo.cpp @@ -24,7 +24,7 @@ QUndoStack undo_stack; #define TA_ADD_CLIP_IN 7 #define TA_ADD_TRACK 8 -TimelineAction::TimelineAction() : done(false), change_seq(false) {} +TimelineAction::TimelineAction() : done(false), change_seq(false), ripple_enabled(false) {} TimelineAction::~TimelineAction() { for (int i=0;iget_sequence_from_tree(new_sequence_items.at(i)); + delete get_sequence_from_tree(new_sequence_items.at(i)); delete new_sequence_items.at(i); } for (int i=0;i& ignore) { + ripple_enabled = true; + ripple_point = point; + ripple_length = length; + ripple_sequence = s; + ripple_ignores = ignore; +} + void TimelineAction::undo() { + // re-add deleted media for (int i=0;iget_clip(rippled_clips.at(i)); + c->timeline_in -= ripple_length; + c->timeline_out -= ripple_length; + } + } + + for (int i=actions.size()-1;i>=0;i--) { switch (actions.at(i)) { case TA_IN: sequences.at(i)->get_clip(clips.at(i))->timeline_in = old_values.at(i); @@ -172,7 +190,7 @@ void TimelineAction::undo() { } } - // add new sequences + // remove added sequences for (int i=0;isource_table->takeTopLevelItem(panel_project->source_table->indexOfTopLevelItem(new_sequence_items.at(i))); @@ -218,6 +236,7 @@ void TimelineAction::redo() { added_indexes.clear(); deleted_media_parents.clear(); removed_link_from_sequence.clear(); + rippled_clips.clear(); for (int i=0;i 0) { + QVector copies; + for (int i=0;icopy(sequence_to_add_clips_to.at(i)); + copy->linked.resize(original->linked.size()); + for (int j=0;jlinked.size();j++) { + copy->linked[j] = original->linked.at(j) + sequence_to_add_clips_to.at(i)->clip_count(); + } + copies.append(copy); + } + for (int i=0;iadd_clip(copies.at(i))); + } + } + for (int i=0;i 0) { - QVector copies; - for (int i=0;icopy(); - copy->linked.resize(original->linked.size()); - for (int j=0;jlinked.size();j++) { - copy->linked[j] = original->linked.at(j) + sequence_to_add_clips_to.at(i)->clip_count(); + // ripple clips - moves all clips after a certain ripple_point by ripple_length + if (ripple_enabled) { + for (int j=0;jclip_count();j++) { + bool found = false; + for (int i=0;iget_clip(j); + if (c != NULL && c->timeline_in >= ripple_point) { + c->timeline_in += ripple_length; + c->timeline_out += ripple_length; + rippled_clips.append(j); + } } - copies.append(copy); - } - for (int i=0;iadd_clip(copies.at(i))); } } @@ -324,8 +365,8 @@ void TimelineAction::redo() { deleted_media_parents.append(item->parent()); // if we're deleting the open sequence, close it - if (panel_project->get_type_from_tree(item) == MEDIA_TYPE_SEQUENCE && - panel_project->get_sequence_from_tree(item) == sequence && + if (get_type_from_tree(item) == MEDIA_TYPE_SEQUENCE && + get_sequence_from_tree(item) == sequence && !change_seq) { old_seq = sequence; new_seq = NULL; @@ -347,6 +388,7 @@ void TimelineAction::redo() { } } + // change current sequence if (change_seq) { old_seq = sequence; set_sequence(new_seq); diff --git a/project/undo.h b/project/undo.h index 4b3ef7de5..7ca08ea76 100644 --- a/project/undo.h +++ b/project/undo.h @@ -30,6 +30,7 @@ public: void delete_clip(Sequence* s, int clip); void add_media(QTreeWidgetItem* item); void delete_media(QTreeWidgetItem* item); + void ripple(Sequence* s, long point, long length, QVector &ignore); void undo(); void redo(); private: @@ -65,6 +66,13 @@ private: QVector media_to_add; + Sequence* ripple_sequence; + bool ripple_enabled; + long ripple_point; + long ripple_length; + QVector rippled_clips; + QVector ripple_ignores; + void new_action(Sequence* s, int action, int clip, long old_val, long new_val); void offset_links(QVector& clips, int offset); }; diff --git a/ui/sourcetable.cpp b/ui/sourcetable.cpp index 5c6ee4e11..c1748a004 100644 --- a/ui/sourcetable.cpp +++ b/ui/sourcetable.cpp @@ -11,15 +11,54 @@ #include #include #include +#include #include SourceTable::SourceTable(QWidget* parent) : QTreeWidget(parent) { editing_item = NULL; sortByColumn(0, Qt::AscendingOrder); rename_timer.setInterval(500); + setContextMenuPolicy(Qt::CustomContextMenu); connect(&rename_timer, SIGNAL(timeout()), this, SLOT(rename_interval())); connect(this, SIGNAL(itemClicked(QTreeWidgetItem*,int)), this, SLOT(item_click(QTreeWidgetItem*,int))); connect(this, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(item_renamed(QTreeWidgetItem*))); + connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu(const QPoint&))); +} + +void SourceTable::show_context_menu(const QPoint& pos) { + QMenu menu(this); + + if (selectedItems().size() == 0) { + QAction* import_action = menu.addAction("Import..."); + connect(import_action, SIGNAL(triggered(bool)), panel_project, SLOT(import_dialog())); + } else { + if (selectedItems().size() == 1) { + // replace footage + if (get_type_from_tree(selectedItems().at(0)) == MEDIA_TYPE_FOOTAGE) { + menu.addAction("Replace/Relink Media"); + } + menu.addAction("Replace Clips Using This Media"); + } + + // duplicate item + bool all_sequences = true; + for (int i=0;iimport_dialog(); } else if (selectedItems().count() == 1) { QTreeWidgetItem* item = selectedItems().at(0); - if (panel_project->get_type_from_tree(item) == MEDIA_TYPE_SEQUENCE) { + if (get_type_from_tree(item) == MEDIA_TYPE_SEQUENCE) { TimelineAction* ta = new TimelineAction(); - ta->change_sequence(panel_project->get_sequence_from_tree(item)); + ta->change_sequence(get_sequence_from_tree(item)); undo_stack.push(ta); } } @@ -105,7 +144,7 @@ void SourceTable::dropEvent(QDropEvent* event) { QVector move_items; QTreeWidgetItem* drop_item = itemAt(event->pos()); // if we dragged to the root OR dragged to a folder - if (drop_item == NULL || (drop_item != NULL && panel_project->get_type_from_tree(drop_item) == MEDIA_TYPE_FOLDER)) { + if (drop_item == NULL || (drop_item != NULL && get_type_from_tree(drop_item) == MEDIA_TYPE_FOLDER)) { QList selected_items = selectedItems(); for (int i=0;iget_type_from_tree(item) == MEDIA_TYPE_FOOTAGE) { - Media* m = panel_project->get_media_from_tree(item); + if (get_type_from_tree(item) == MEDIA_TYPE_FOOTAGE) { + Media* m = get_media_from_tree(item); if (m->ready) { if (!got_video_values) { for (int j=0;jvideo_tracks.size();j++) { @@ -111,8 +111,8 @@ void TimelineWidget::dragEnterEvent(QDragEnterEvent *event) { } for (int i=0;iget_type_from_tree(item) == MEDIA_TYPE_FOOTAGE) { - Media* m = panel_project->get_media_from_tree(item); + if (get_type_from_tree(item) == MEDIA_TYPE_FOOTAGE) { + Media* m = get_media_from_tree(item); if (m->ready) { Ghost g; g.clip = -1; @@ -128,12 +128,12 @@ void TimelineWidget::dragEnterEvent(QDragEnterEvent *event) { g.trimming = false; for (int j=0;jaudio_tracks.size();j++) { g.track = j; - g.media_stream = m->audio_tracks.at(j); + g.media_stream = m->audio_tracks.at(j)->file_index; panel_timeline->ghosts.append(g); } for (int j=0;jvideo_tracks.size();j++) { g.track = -1-j; - g.media_stream = m->video_tracks.at(j); + g.media_stream = m->video_tracks.at(j)->file_index; panel_timeline->ghosts.append(g); } } @@ -191,11 +191,11 @@ void TimelineWidget::dropEvent(QDropEvent* event) { QVector delete_areas; for (int i=0;ighosts.size();i++) { const Ghost& g = panel_timeline->ghosts.at(i); - Selection s; - s.in = g.in; - s.out = g.out; - s.track = g.track; - delete_areas.append(s); + Selection sel; + sel.in = g.in; + sel.out = g.out; + sel.track = g.track; + delete_areas.append(sel); } panel_timeline->delete_areas_and_relink(ta, delete_areas); } @@ -204,7 +204,7 @@ void TimelineWidget::dropEvent(QDropEvent* event) { for (int i=0;ighosts.size();i++) { const Ghost& g = panel_timeline->ghosts.at(i); - Clip* c = new Clip(); + Clip* c = new Clip(s); c->media = g.media; c->media_stream = g.media_stream; c->timeline_in = g.in; @@ -216,9 +216,9 @@ void TimelineWidget::dropEvent(QDropEvent* event) { c->color_g = 192; c->color_b = 128; } else if (c->media->audio_tracks.size() == 0) { - // video only (purpleish) + // video only (orangeish) c->color_r = 192; - c->color_g = 128; + c->color_g = 160; c->color_b = 128; } else { // video and audio (blueish) @@ -226,13 +226,11 @@ void TimelineWidget::dropEvent(QDropEvent* event) { c->color_g = 128; c->color_b = 192; } - c->sequence = sequence; c->track = g.track; c->name = c->media->name; if (c->track < 0) { - // add default video effects - qDebug() << "hello"; + // add default video effects c->effects.append(create_effect(VIDEO_TRANSFORM_EFFECT, c)); } else { // add default audio effects @@ -412,18 +410,22 @@ void TimelineWidget::mouseReleaseEvent(QMouseEvent *event) { ripple_length = first_ghost.old_out - panel_timeline->ghosts.at(0).out; ripple_point = first_ghost.old_out; } + QVector ignore_clips; for (int i=0;ighosts.size();i++) { const Ghost& g = panel_timeline->ghosts.at(i); - long comp_point; + + // push rippled clips forward if necessary if (panel_timeline->trim_in_point) { - comp_point = g.old_in; - } else { - comp_point = g.old_out; + ignore_clips.append(g.clip); + panel_timeline->ghosts[i].in += ripple_length; + panel_timeline->ghosts[i].out += ripple_length; } - if (ripple_point > comp_point) ripple_point = comp_point; + + long comp_point = panel_timeline->trim_in_point ? g.old_in : g.old_out; + ripple_point = qMin(ripple_point, comp_point); } if (!panel_timeline->trim_in_point) ripple_length = -ripple_length; - panel_timeline->ripple(ta, ripple_point, ripple_length); + ta->ripple(sequence, ripple_point, ripple_length, ignore_clips); } if (panel_timeline->tool == TIMELINE_TOOL_POINTER && (event->modifiers() & Qt::AltModifier)) { // if holding alt, duplicate rather than move @@ -435,7 +437,7 @@ void TimelineWidget::mouseReleaseEvent(QMouseEvent *event) { 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 - Clip* c = sequence->get_clip(g.clip)->copy(); + Clip* c = sequence->get_clip(g.clip)->copy(sequence); c->timeline_in = g.in; c->timeline_out = g.out; @@ -674,7 +676,7 @@ void TimelineWidget::update_ghosts(QPoint& mouse_pos) { // validate ghosts for trimming if (panel_timeline->tool == TIMELINE_TOOL_SLIP) { - if (!sequence->get_clip(g.clip)->media_stream->infinite_length) { + if (!c->media->get_stream_from_file_index(c->media_stream)->infinite_length) { // prevent slip moving a clip below 0 clip_in validator = g.old_clip_in - frame_diff; if (validator < 0) frame_diff += validator; @@ -694,7 +696,7 @@ void TimelineWidget::update_ghosts(QPoint& mouse_pos) { if (validator < 0) frame_diff -= validator; // prevent clip_in from going below 0 - if (!c->media_stream->infinite_length) { + if (!c->media->get_stream_from_file_index(c->media_stream)->infinite_length) { validator = g.old_clip_in + frame_diff; if (validator < 0) frame_diff -= validator; } @@ -704,7 +706,7 @@ void TimelineWidget::update_ghosts(QPoint& mouse_pos) { if (validator < 1) frame_diff += (1 - validator); // prevent clip length exceeding media length - if (!c->media_stream->infinite_length) { + if (!c->media->get_stream_from_file_index(c->media_stream)->infinite_length) { validator = g.old_clip_in + g.ghost_length + frame_diff; if (validator > g.media_length) frame_diff -= validator - g.media_length; } @@ -713,10 +715,13 @@ void TimelineWidget::update_ghosts(QPoint& mouse_pos) { // ripple ops if (panel_timeline->tool == TIMELINE_TOOL_RIPPLE) { for (int j=0;jtimeline_in - frame_diff; - if (validator < 0) frame_diff += validator; + + // prevent any rippled clip from going below 0 + if (panel_timeline->trim_in_point) { + validator = post->timeline_in - frame_diff; + if (validator < 0) frame_diff += validator; + } // prevent any post-clips colliding with pre-clips for (int k=0;ksnapped = false; - } + panel_timeline->snapped = (temp_frame_diff == frame_diff); // apply changes to ghosts for (int i=0;ighosts.size();i++) { @@ -1303,37 +1306,60 @@ void TimelineWidget::redraw_clips() { } } - // draw thumbnail/waveform - if (clip->media_stream->preview_done) { + MediaStream* ms = clip->media->get_stream_from_file_index(clip->media_stream); + if (ms == NULL) { + // draw "error lines" if media stream is missing + clip_painter.setPen(QPen(QColor(64, 64, 64), 2)); + int limit = clip_rect.width(); + int clip_height = clip_rect.bottom()-clip_rect.top(); + for (int j=-clip_height;j clip_rect.right()) { + lines_end_y -= (clip_rect.right() - lines_end_x); + lines_end_x = clip_rect.right(); + } + clip_painter.drawLine(lines_start_x, lines_start_y, lines_end_x, lines_end_y); + } + } else if (ms->preview_done) { + // draw thumbnail/waveform if (clip->track < 0) { int thumb_y = clip_painter.fontMetrics().height()+CLIP_TEXT_PADDING+CLIP_TEXT_PADDING; int thumb_height = clip_rect.height()-thumb_y; - int thumb_width = (thumb_height*((float)clip->media_stream->video_preview.width()/(float)clip->media_stream->video_preview.height())); + int thumb_width = (thumb_height*((double)ms->video_preview.width()/(double)ms->video_preview.height())); if (thumb_height > thumb_y && text_rect.width() + CLIP_TEXT_PADDING > thumb_width) { // at small clip heights, don't even draw it QRect thumb_rect(thumb_x, clip_rect.y()+thumb_y, thumb_width, thumb_height); - clip_painter.drawImage(thumb_rect, clip->media_stream->video_preview); + clip_painter.drawImage(thumb_rect, ms->video_preview); } } else if (clip_rect.height() > TRACK_MIN_HEIGHT) { - int divider = clip->media_stream->audio_channels*2; + int divider = ms->audio_channels*2; clip_painter.setPen(QColor(80, 80, 80)); - int channel_height = clip_rect.height()/clip->media_stream->audio_channels; + int channel_height = clip_rect.height()/ms->audio_channels; long media_length = clip->media->get_length_in_frames(clip->sequence->frame_rate); for (int i=0;iclip_in + ((float) i/panel_timeline->zoom))/media_length) * clip->media_stream->audio_preview.size())/divider)*divider; + int waveform_index = qFloor((((clip->clip_in + ((double) i/panel_timeline->zoom))/media_length) * ms->audio_preview.size())/divider)*divider; - for (int j=0;jmedia_stream->audio_channels;j++) { + for (int j=0;jaudio_channels;j++) { int mid = clip_rect.top()+channel_height*j+(channel_height/2); int offset = waveform_index+(j*2); - if (offset >= 0 && offset < clip->media_stream->audio_preview.size()) { - qint8 min = (double)clip->media_stream->audio_preview.at(offset) / 128.0 * (channel_height/2); - qint8 max = (double)clip->media_stream->audio_preview.at(offset+1) / 128.0 * (channel_height/2); - clip_painter.drawLine(clip_rect.left()+i, mid+min, clip_rect.left()+i, mid+max); + qint8 min = (double)ms->audio_preview.at(offset) / 128.0 * (channel_height/2); + qint8 max = (double)ms->audio_preview.at(offset+1) / 128.0 * (channel_height/2); + clip_painter.drawLine(clip_rect.left()+i, mid+min, clip_rect.left()+i, mid+max); + + /*if (offset >= 0 && offset < ms->audio_preview.size()) { + } else { QMessageBox::critical(this, "AAAAAAAAAAAAA", "Here's that terrible, no-good bug again!!! I've stepped around it but plz report the following to Matt:\n\n" + QString::number(offset) + " vs " + QString::number(clip->media_stream->audio_preview.size()), QMessageBox::Ok); break; - } + }*/ } } } diff --git a/ui/timelinewidget.h b/ui/timelinewidget.h index 0d7793f09..debc9506f 100644 --- a/ui/timelinewidget.h +++ b/ui/timelinewidget.h @@ -16,8 +16,7 @@ struct Clip; class Timeline; class TimelineAction; -class TimelineWidget : public QWidget -{ +class TimelineWidget : public QWidget { Q_OBJECT public: explicit TimelineWidget(QWidget *parent = 0); diff --git a/ui/viewerwidget.cpp b/ui/viewerwidget.cpp index 68c92a4b3..732c3ba09 100644 --- a/ui/viewerwidget.cpp +++ b/ui/viewerwidget.cpp @@ -72,7 +72,9 @@ void ViewerWidget::paintGL() { // if clip starts within one second and/or hasn't finished yet if (c != NULL) { - if (c->media->ready && is_clip_active(c, panel_timeline->playhead)) { + if (c->media->ready + && c->media->get_stream_from_file_index(c->media_stream) != NULL + && is_clip_active(c, panel_timeline->playhead)) { // if thread is already working, we don't want to touch this, // but we also don't want to hang the UI thread if (!c->open) { @@ -113,6 +115,8 @@ void ViewerWidget::paintGL() { qDebug() << "[WARNING] Texture hasn't been created yet"; texture_failed = true; } else if (panel_timeline->playhead >= c->timeline_in) { + MediaStream* ms = c->media->get_stream_from_file_index(c->media_stream); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glColor4f(1.0, 1.0, 1.0, 1.0); glLoadIdentity(); @@ -124,8 +128,8 @@ void ViewerWidget::paintGL() { } else { glOrtho(-half_width, half_width, half_height, -half_height, -1, 1); } - int anchor_x = c->media_stream->video_width/2; - int anchor_y = c->media_stream->video_height/2; + int anchor_x = ms->video_width/2; + int anchor_y = ms->video_height/2; // perform all transform effects for (int j=0;jeffects.size();j++) { @@ -145,8 +149,8 @@ void ViewerWidget::paintGL() { } } - int anchor_right = c->media_stream->video_width - anchor_x; - int anchor_bottom = c->media_stream->video_height - anchor_y; + int anchor_right = ms->video_width - anchor_x; + int anchor_bottom = ms->video_height - anchor_y; c->texture->bind();