diff --git a/io/media.cpp b/io/media.cpp index 3280986ee..5c0da7d43 100644 --- a/io/media.cpp +++ b/io/media.cpp @@ -4,6 +4,21 @@ extern "C" { #include } +Media::Media() { + +} + +Media::~Media() { + for (int i=0;i #include #include +#include struct Sequence; @@ -13,16 +14,23 @@ struct MediaStream { int video_width; int video_height; bool infinite_length; + + // preview thumbnail/waveform + QPixmap* preview; + QMutex preview_lock; }; struct Media { + Media(); + ~Media(); + QString url; QString name; bool is_sequence; int64_t length; - QVector video_tracks; - QVector audio_tracks; + QVector video_tracks; + QVector audio_tracks; Sequence* sequence; int save_id; long get_length_in_frames(float frame_rate); diff --git a/io/previewgenerator.cpp b/io/previewgenerator.cpp new file mode 100644 index 000000000..843196fbb --- /dev/null +++ b/io/previewgenerator.cpp @@ -0,0 +1,10 @@ +#include "previewgenerator.h" + +PreviewGenerator::PreviewGenerator(QWidget* parent) : QThread(parent) +{ + +} + +void PreviewGenerator::run() { + +} diff --git a/io/previewgenerator.h b/io/previewgenerator.h new file mode 100644 index 000000000..e3c256a15 --- /dev/null +++ b/io/previewgenerator.h @@ -0,0 +1,13 @@ +#ifndef PREVIEWGENERATOR_H +#define PREVIEWGENERATOR_H + +#include + +class PreviewGenerator : public QThread +{ +public: + PreviewGenerator(QWidget* parent = 0); + void run() override; +}; + +#endif // PREVIEWGENERATOR_H diff --git a/mainwindow.cpp b/mainwindow.cpp index 01e888feb..bfce6970e 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -265,3 +265,10 @@ void MainWindow::on_actionSave_Project_As_triggered() { save_project_as(); } + +void MainWindow::on_actionDeselect_All_triggered() +{ + if (panel_timeline->focused()) { + panel_timeline->deselect(); + } +} diff --git a/mainwindow.h b/mainwindow.h index c0239def1..557c34de1 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -71,6 +71,8 @@ private slots: void on_actionSave_Project_As_triggered(); + void on_actionDeselect_All_triggered(); + private: Ui::MainWindow *ui; void setup_layout(); diff --git a/mainwindow.ui b/mainwindow.ui index 2fb9381fa..704b9bc30 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -64,6 +64,7 @@ + @@ -297,6 +298,11 @@ Ctrl+K + + + Deselect All + + diff --git a/olive.pro b/olive.pro index cde6760c0..63afc3e49 100644 --- a/olive.pro +++ b/olive.pro @@ -52,7 +52,8 @@ SOURCES += \ effects/effects.cpp \ playback/cacher.cpp \ io/exportthread.cpp \ - ui/timelineheader.cpp + ui/timelineheader.cpp \ + io/previewgenerator.cpp HEADERS += \ mainwindow.h \ @@ -80,7 +81,8 @@ HEADERS += \ playback/cacher.h \ io/exportthread.h \ ui/timelinetools.h \ - ui/timelineheader.h + ui/timelineheader.h \ + io/previewgenerator.h FORMS += \ mainwindow.ui \ diff --git a/olive.pro.user b/olive.pro.user index 861203364..25e111309 100644 --- a/olive.pro.user +++ b/olive.pro.user @@ -1,6 +1,6 @@ - + EnvironmentId diff --git a/panels/project.cpp b/panels/project.cpp index 4d2f8225a..0f2d0e5ae 100644 --- a/panels/project.cpp +++ b/panels/project.cpp @@ -109,15 +109,19 @@ Media* Project::import_file(QString file) { if (avcodec_find_decoder(pFormatCtx->streams[i]->codecpar->codec_id) == NULL) { qDebug() << "[ERROR] Unsupported codec in stream %d.\n"; } else { + MediaStream* ms = new MediaStream(); + ms->file_index = i; if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { qDebug() << "[WARNING] INFINITE_LENGTH calculation is inaccurate in this build\n"; // TODO BETTER infinite length calculator bool infinite_length = (pFormatCtx->streams[i]->nb_frames == 0); // bool infinite_length = false; - - m->video_tracks.append({i, pFormatCtx->streams[i]->codecpar->width, pFormatCtx->streams[i]->codecpar->height, infinite_length}); + ms->video_width = pFormatCtx->streams[i]->codecpar->width; + ms->video_height = pFormatCtx->streams[i]->codecpar->height; + ms->infinite_length = infinite_length; + m->video_tracks.append(ms); } else if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { - m->audio_tracks.append({i, 0, 0, false}); + m->audio_tracks.append(ms); } } } @@ -355,16 +359,16 @@ void Project::load_project() { int stream_index = stream.text().toInt(); bool found = false; for (int i=0;imedia->video_tracks.size();i++) { - if (temp_clip->media->video_tracks.at(i).file_index == stream_index) { - temp_clip->media_stream = &temp_clip->media->video_tracks[i]; + if (temp_clip->media->video_tracks.at(i)->file_index == stream_index) { + temp_clip->media_stream = temp_clip->media->video_tracks[i]; found = true; break; } } if (!found) { for (int i=0;imedia->audio_tracks.size();i++) { - if (temp_clip->media->audio_tracks.at(i).file_index == stream_index) { - temp_clip->media_stream = &temp_clip->media->audio_tracks[i]; + if (temp_clip->media->audio_tracks.at(i)->file_index == stream_index) { + temp_clip->media_stream = temp_clip->media->audio_tracks[i]; found = true; break; } diff --git a/panels/timeline.cpp b/panels/timeline.cpp index abbf2eb54..c7c3415c4 100644 --- a/panels/timeline.cpp +++ b/panels/timeline.cpp @@ -183,14 +183,14 @@ void Timeline::delete_selection(bool ripple_delete) { long ripple_point = selections.at(0).in; long ripple_length = selections.at(0).out - selections.at(0).in; - for (int i=0;idelete_area(s.in, s.out, s.track); - if (ripple_delete) { - if (ripple_point > s.in) ripple_point = s.in; - if (ripple_length > s.out - s.in) ripple_length = s.out - s.in; - } - } + 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(selections); selections.clear(); if (ripple_delete) { @@ -293,8 +293,7 @@ void Timeline::on_pushButton_5_clicked() zoom_out(); } -bool Timeline::is_clip_selected(int clip_index) { - Clip* clip = sequence->get_clip(clip_index); +bool Timeline::is_clip_selected(Clip* clip) { for (int i=0;itrack == s.track && clip->timeline_in >= s.in && clip->timeline_out <= s.out) { @@ -336,6 +335,86 @@ void Timeline::on_snappingButton_toggled(bool checked) snapping = checked; } +void Timeline::split_clip_and_relink(Clip* pre_clip, long frame, bool relink) { + Clip* post = sequence->split_clip(pre_clip, frame); + + // if alt is not down, split clips links too + if (relink) { + QVector splits; + splits.append(post); + + // find linked clips of old clip + for (int i=0;ilinked.size();i++) { + Clip* link = pre_clip->linked.at(i); + if (!panel_timeline->is_clip_selected(link)) { + Clip* s = sequence->split_clip(link, frame); + if (s != NULL) splits.append(s); + } + } + + // re-link all new clips + for (int i=0;ilinked.append(cc); + } + } + } + } +} + +void Timeline::delete_areas_and_relink(QVector areas) { + QVector pre_clips; + QVector post_clips; + + for (int i=0;iclip_count();j++) { + Clip* c = sequence->get_clip(j); + if (c->track == s.track && !c->undeletable) { + if (c->timeline_in >= s.in && c->timeline_out <= s.out) { + // clips falls entirely within deletion area + sequence->delete_clip(j); + j--; + } else if (c->timeline_in < s.in && c->timeline_out > s.out) { + // middle of clip is within deletion area + + // duplicate clip + Clip* post = c->copy(); + + c->timeline_out = s.in; + post->timeline_in = s.out; + post->clip_in = c->clip_in + c->getLength() + (s.out - s.in); + + pre_clips.append(c); + post_clips.append(post); + + sequence->add_clip(post); + } else if (c->timeline_in < s.in && c->timeline_out > s.in) { + // only out point is in deletion area + c->timeline_out = s.in; + } else if (c->timeline_in < s.out && c->timeline_out > s.out) { + // only in point is in deletion area + c->clip_in += s.out - c->timeline_in; + c->timeline_in = s.out; + } + } + } + } + for (int i=0;ilinked.size();k++) { + if (c->linked.at(k) == pre_clips.at(j)) { + post_clips.at(i)->linked.append(post_clips.at(j)); + } + } + } + } +} + void Timeline::copy(bool del) { bool cleared = false; bool copied = false; @@ -388,12 +467,18 @@ void Timeline::copy(bool del) { void Timeline::paste() { if (clip_clipboard.size() > 0) { + QVector delete_areas; + for (int i=0;itimeline_in + playhead, c->timeline_out + playhead, c->track}); + } + delete_areas_and_relink(delete_areas); for (int i=0;idelete_area(c->timeline_in + playhead, c->timeline_out + playhead, c->track); Clip* cc = c->copy(); cc->timeline_in += playhead; cc->timeline_out += playhead; + cc->sequence = sequence; sequence->add_clip(cc); } redraw_all_clips(); @@ -402,16 +487,47 @@ void Timeline::paste() { bool Timeline::split_selection() { bool split = false; + + // temporary relinking vectors + QVector pre_splits; + QVector post_splits; + + // find clips within selection and split for (int j=0;jclip_count();j++) { for (int i=0;iget_clip(j)->track) { - sequence->split_clip(j, s.in); - sequence->split_clip(j, s.out); + Clip* clip = sequence->get_clip(j); + if (s.track == clip->track) { + Clip* post_a = sequence->split_clip(clip, s.in); + Clip* post_b = sequence->split_clip(clip, s.out); + + if (post_a != NULL) { + pre_splits.append(clip); + post_splits.append(post_a); + } + if (post_b != NULL) { + pre_splits.append(clip); + post_splits.append(post_b); + } + split = true; } } } + + // relink after splitting + for (int i=0;ilinked.size();k++) { + if (pre->linked.at(k) == pre_splits.at(j)) { + post_splits.at(i)->linked.append(post_splits.at(j)); + } + } + } + } + return split; } @@ -421,8 +537,10 @@ void Timeline::split_at_playhead() { if (selections.size() > 0) { // see if whole clips are selected for (int j=0;jclip_count();j++) { - if (is_clip_selected(j)) { - sequence->split_clip(j, playhead); + Clip* clip = sequence->get_clip(j); + if (is_clip_selected(clip)) { + // should this relink? + sequence->split_clip(clip, playhead); split_selected = true; } } @@ -436,7 +554,8 @@ void Timeline::split_at_playhead() { // if nothing was selected or no selections fell within playhead, simply split at playhead if (!split_selected) { for (int j=0;jclip_count();j++) { - sequence->split_clip(j, playhead); + // always relinks + split_clip_and_relink(sequence->get_clip(j), playhead, true); split_selected = true; } } @@ -465,6 +584,11 @@ void Timeline::snap_to_clip(long* l) { } } +void Timeline::deselect() { + selections.clear(); + repaint_timeline(); +} + long Timeline::getFrameFromScreenPoint(int x) { long f = round((float) x / zoom); if (f < 0) { diff --git a/panels/timeline.h b/panels/timeline.h index 5c6a82b81..13d0785b2 100644 --- a/panels/timeline.h +++ b/panels/timeline.h @@ -65,8 +65,11 @@ public: void redo(); void copy(bool del); void paste(); + void deselect(); bool split_selection(); void split_at_playhead(); + void split_clip_and_relink(Clip* clip, long frame, bool relink); + void delete_areas_and_relink(QVector areas); void update_sequence(); int getScreenPointFromFrame(long frame); @@ -107,7 +110,7 @@ public: // selecting functions bool selecting; QVector selections; - bool is_clip_selected(int clip_index); + bool is_clip_selected(Clip* clip); void delete_selection(bool ripple); void select_all(); diff --git a/project/clip.h b/project/clip.h index 013709fa6..f8418333e 100644 --- a/project/clip.h +++ b/project/clip.h @@ -20,6 +20,7 @@ struct AVPacket; struct SwsContext; struct SwrContext; class QOpenGLTexture; +class QPixmap; struct ClipCache { AVFrame** frames; @@ -56,7 +57,7 @@ struct Clip // other variables (should be "duplicated" in copy()) QList effects; -// QVector linkedClips; + QVector linked; // media handling AVFormatContext* formatCtx; diff --git a/project/sequence.cpp b/project/sequence.cpp index 2d4048dfc..32b76c1aa 100644 --- a/project/sequence.cpp +++ b/project/sequence.cpp @@ -29,14 +29,15 @@ Clip* Sequence::get_clip(int i) { void Sequence::delete_clip(int i) { // remove any potential link references to clip -// for (size_t j=0;jlinkedClips.size();k++) { -// if (c->linkedClips[k] == clips[i]) { -// c->linkedClips.erase(c->linkedClips.begin()+k); -// } -// } -// } + for (int j=0;jlinked.size();k++) { + if (c->linked[k] == clips[i]) { + c->linked.removeAt(k); + break; + } + } + } // finally remove from vector delete clips.at(i); @@ -69,40 +70,11 @@ void Sequence::get_track_limits(int* video_tracks, int* audio_tracks) { if (audio_tracks != NULL) *audio_tracks = at; } -void Sequence::delete_area(long in, long out, int track) { - for (int i=0;itrack == track && !c->undeletable) { - if (c->timeline_in >= in && c->timeline_out <= out) { - // clips falls entirely within deletion area - delete_clip(i); - i--; - } else if (c->timeline_in < in && c->timeline_out > out) { - // middle of clip is within deletion area +//void Sequence::delete_area(long in, long out, int track) { - // duplicate clip - Clip* pre = get_clip(i); - Clip* post = pre->copy(); +//} - pre->timeline_out = in; - post->timeline_in = out; - post->clip_in = pre->clip_in + pre->getLength() + (out - in); - - add_clip(post); - } else if (c->timeline_in < in && c->timeline_out > in) { - // only out point is in deletion area - c->timeline_out = in; - } else if (c->timeline_in < out && c->timeline_out > out) { - // only in point is in deletion area - c->clip_in += out - c->timeline_in; - c->timeline_in = out; - } - } - } -} - -void Sequence::split_clip(int i, long frame) { - Clip* pre = get_clip(i); +Clip* Sequence::split_clip(Clip* pre, long frame) { if (pre->timeline_in < frame && pre->timeline_out > frame) { // guard against attempts to split at in/out points Clip* post = pre->copy(); @@ -111,7 +83,10 @@ void Sequence::split_clip(int i, long frame) { post->clip_in = pre->clip_in + pre->getLength(); add_clip(post); + + return post; } + return NULL; } void Sequence::undo_add_current() { diff --git a/project/sequence.h b/project/sequence.h index da9c3f7dd..bf9b4e1fc 100644 --- a/project/sequence.h +++ b/project/sequence.h @@ -16,8 +16,8 @@ public: int clip_count(); Clip* get_clip(int i); void delete_clip(int i); - void delete_area(long in, long out, int track); - void split_clip(int i, long frame); +// void delete_area(long in, long out, int track); + Clip* split_clip(Clip* pre, long frame); void get_track_limits(int* video_tracks, int* audio_tracks); long getEndFrame(); int width; diff --git a/ui/timelinewidget.cpp b/ui/timelinewidget.cpp index df0eecba7..02332f606 100644 --- a/ui/timelinewidget.cpp +++ b/ui/timelinewidget.cpp @@ -54,14 +54,14 @@ void TimelineWidget::dragEnterEvent(QDragEnterEvent *event) { g.clip_in = 0; for (int j=0;jaudio_tracks.size();j++) { g.track = j; - g.media_stream = &m->audio_tracks[j]; + g.media_stream = m->audio_tracks[j]; ignore_infinite_length = true; panel_timeline->ghosts.append(g); } for (int j=0;jvideo_tracks.size();j++) { g.track = -1-j; - g.media_stream = &m->video_tracks[j]; - if (m->video_tracks[j].infinite_length && !ignore_infinite_length) g.out = g.in + 100; + g.media_stream = m->video_tracks[j]; + if (m->video_tracks[j]->infinite_length && !ignore_infinite_length) g.out = g.in + 100; panel_timeline->ghosts.append(g); } entry_point += duration; @@ -91,10 +91,19 @@ void TimelineWidget::dropEvent(QDropEvent* event) { if (panel_timeline->importing) { event->accept(); - for (int i=0;ighosts.size();i++) { - const Ghost& g = panel_timeline->ghosts.at(i); + QVector added_clips; - sequence->delete_area(g.in, g.out, g.track); + // delete areas before adding + QVector delete_areas; + for (int i=0;ighosts.size();i++) { + const Ghost& g = panel_timeline->ghosts.at(i); + delete_areas.append({g.in, g.out, g.track}); + } + panel_timeline->delete_areas_and_relink(delete_areas); + + // add clips + for (int i=0;ighosts.size();i++) { + const Ghost& g = panel_timeline->ghosts.at(i); Clip* c = new Clip(); c->media = g.media; @@ -119,8 +128,20 @@ void TimelineWidget::dropEvent(QDropEvent* event) { } sequence->add_clip(c); + added_clips.append(c); } + // link clips from the same media + for (int i=0;imedia == cc->media) { + c->linked.append(cc); + } + } + } + panel_timeline->ghosts.clear(); panel_timeline->importing = false; panel_timeline->snapped = false; @@ -148,7 +169,7 @@ void TimelineWidget::mousePressEvent(QMouseEvent *event) { case TIMELINE_TOOL_SLIP: { if (clip_index >= 0) { - if (panel_timeline->is_clip_selected(clip_index)) { + if (panel_timeline->is_clip_selected(sequence->get_clip(clip_index))) { // TODO if shift is down, deselect it } else { // if "shift" is not down @@ -158,6 +179,16 @@ void TimelineWidget::mousePressEvent(QMouseEvent *event) { Clip* clip = sequence->get_clip(clip_index); panel_timeline->selections.append({clip->timeline_in, clip->timeline_out, clip->track}); + + // if alt is not down, select links + if (!(event->modifiers() & Qt::AltModifier)) { + for (int i=0;ilinked.size();i++) { + Clip* link = clip->linked.at(i); + if (!panel_timeline->is_clip_selected(link)) { + panel_timeline->selections.append({link->timeline_in, link->timeline_out, link->track}); + } + } + } } panel_timeline->moving_init = true; } else { @@ -173,7 +204,7 @@ void TimelineWidget::mousePressEvent(QMouseEvent *event) { case TIMELINE_TOOL_RAZOR: { if (clip_index >= 0) { - sequence->split_clip(clip_index, panel_timeline->drag_frame_start); + panel_timeline->split_clip_and_relink(sequence->get_clip(clip_index), panel_timeline->drag_frame_start, !(event->modifiers() & Qt::AltModifier)); } panel_timeline->splitting = true; panel_timeline->redraw_all_clips(); @@ -186,19 +217,24 @@ void TimelineWidget::mouseReleaseEvent(QMouseEvent *event) { if (panel_timeline->moving_proc) { if (event->modifiers() & Qt::AltModifier) { // if holding alt, duplicate rather than move // duplicate clips + QVector copy_clips; + QVector delete_areas; for (int i=0;ighosts.size();i++) { - Ghost& g = panel_timeline->ghosts[i]; + const Ghost& g = panel_timeline->ghosts.at(i); + Clip* c = g.clip->copy(); c->timeline_in = g.in; c->timeline_out = g.out; c->track = g.track; - // step 2 - delete anything that exists in area that clip is moving to - sequence->delete_area(g.in, g.out, g.track); - - sequence->add_clip(c); + copy_clips.append(c); } + panel_timeline->delete_areas_and_relink(delete_areas); + for (int i=0;iadd_clip(copy_clips.at(i)); + } } else { // move clips // TODO can we do this better than 3 consecutive for loops? @@ -206,14 +242,20 @@ void TimelineWidget::mouseReleaseEvent(QMouseEvent *event) { // step 1 - set clips that are moving to "undeletable" (to avoid step 2 deleting any part of them) panel_timeline->ghosts[i].clip->undeletable = true; } - for (int i=0;ighosts.size();i++) { - Ghost& g = panel_timeline->ghosts[i]; + // step 2 - delete areas + QVector delete_areas; + for (int i=0;ighosts.size();i++) { + const Ghost& g = panel_timeline->ghosts.at(i); - if (panel_timeline->tool == TIMELINE_TOOL_POINTER) { - // step 2 - delete anything that exists in area that clip is moving to - // note: ripples are non-destructive so this is pointer-tool exclusive - sequence->delete_area(g.in, g.out, g.track); - } + if (panel_timeline->tool == TIMELINE_TOOL_POINTER) { + // step 2 - delete anything that exists in area that clip is moving to + // note: ripples are non-destructive so this is pointer-tool exclusive + delete_areas.append({g.in, g.out, g.track}); + } + } + panel_timeline->delete_areas_and_relink(delete_areas); + for (int i=0;ighosts.size();i++) { + Ghost& g = panel_timeline->ghosts[i]; // step 3 - move clips g.clip->timeline_in = g.in; @@ -271,7 +313,7 @@ void TimelineWidget::mouseReleaseEvent(QMouseEvent *event) { bool single_select = false; int selected_clip = 0; for (int i=0;iclip_count();i++) { - if (panel_timeline->is_clip_selected(i)) { + if (panel_timeline->is_clip_selected(sequence->get_clip(i))) { if (!single_select) { // found ONE selected clip selected_clip = i; @@ -528,7 +570,6 @@ void TimelineWidget::update_ghosts(QPoint& mouse_pos) { Ghost& g = panel_timeline->ghosts[i]; g.clip_in = g.old_clip_in - frame_diff; - qDebug() << "slipping ghost" << i << "to" << g.clip_in; } } } @@ -573,8 +614,8 @@ void TimelineWidget::mouseMoveEvent(QMouseEvent *event) { // set up movement // create ghosts for (int i=0;iclip_count();i++) { - if (panel_timeline->is_clip_selected(i)) { - Clip* c = sequence->get_clip(i); + Clip* c = sequence->get_clip(i); + if (panel_timeline->is_clip_selected(c)) { panel_timeline->ghosts.append({c, c->timeline_in, c->timeline_out, c->track, c->clip_in}); } } @@ -648,8 +689,9 @@ void TimelineWidget::mouseMoveEvent(QMouseEvent *event) { int track = panel_timeline->cursor_track; bool repaint = false; for (int i=0;iclip_count();i++) { - if (sequence->get_clip(i)->track == track) { - sequence->split_clip(i, panel_timeline->drag_frame_start); + Clip* clip = sequence->get_clip(i); + if (clip->track == track) { + panel_timeline->split_clip_and_relink(clip, panel_timeline->drag_frame_start, !(event->modifiers() & Qt::AltModifier)); repaint = true; } } @@ -726,7 +768,7 @@ void TimelineWidget::redraw_clips() { clip_painter.setPen(Qt::white); clip_painter.drawLine(clip_rect.bottomLeft(), clip_rect.topLeft()); clip_painter.drawLine(clip_rect.topLeft(), clip_rect.topRight()); - clip_painter.setPen(QColor(0, 0, 0, 128)); + clip_painter.setPen(Qt::gray); clip_painter.drawLine(clip_rect.bottomLeft(), clip_rect.bottomRight()); clip_painter.drawLine(clip_rect.bottomRight(), clip_rect.topRight());