From 12835c3c01d39c996a27e4cff18f9f7d13e64d91 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Wed, 5 Dec 2018 13:14:02 +1100 Subject: [PATCH 01/40] changed default CRF for h264 --- dialogs/exportdialog.cpp | 4 ++-- io/exportthread.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dialogs/exportdialog.cpp b/dialogs/exportdialog.cpp index b8fec27b4..ca5a8a52a 100644 --- a/dialogs/exportdialog.cpp +++ b/dialogs/exportdialog.cpp @@ -552,9 +552,9 @@ void ExportDialog::on_compressionTypeCombobox_currentIndexChanged(int) { break; case COMPRESSION_TYPE_CFR: ui->videoBitrateLabel->setText("Quality (CRF):"); - ui->videobitrateSpinbox->setValue(23); + ui->videobitrateSpinbox->setValue(36); ui->videobitrateSpinbox->setMaximum(51); - ui->videobitrateSpinbox->setToolTip("Quality Factor:\n\n0 = lossless\n17-18 = visually lossless (compressed, but unnoticeable)\n23 = default, high quality\n51 = lowest quality possible"); + ui->videobitrateSpinbox->setToolTip("Quality Factor:\n\n0 = lossless\n17-18 = visually lossless (compressed, but unnoticeable)\n23 = high quality\n51 = lowest quality possible"); break; case COMPRESSION_TYPE_TARGETSIZE: ui->videoBitrateLabel->setText("Target File Size (MB):"); diff --git a/io/exportthread.cpp b/io/exportthread.cpp index 4024214db..8482db33f 100644 --- a/io/exportthread.cpp +++ b/io/exportthread.cpp @@ -122,9 +122,9 @@ bool ExportThread::setupVideo() { if (vcodec_ctx->codec_id == AV_CODEC_ID_H264) { /*char buffer[50]; - itoa(vcodec_ctx, buffer, 10);*/ + itoa(vcodec_ctx, buffer, 10);*/ -// av_opt_set(vcodec_ctx->priv_data, "preset", "slow", AV_OPT_SEARCH_CHILDREN); + //av_opt_set(vcodec_ctx->priv_data, "preset", "fast", AV_OPT_SEARCH_CHILDREN); switch (video_compression_type) { case COMPRESSION_TYPE_CFR: From a5761861d4d42bebd54352f17b0c5ca3010fc759 Mon Sep 17 00:00:00 2001 From: Olive Team Date: Wed, 5 Dec 2018 22:02:46 +1100 Subject: [PATCH 02/40] added fallback if no default audio device was returned --- playback/audio.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/playback/audio.cpp b/playback/audio.cpp index d07098383..591dc59f6 100644 --- a/playback/audio.cpp +++ b/playback/audio.cpp @@ -53,6 +53,17 @@ void init_audio(Sequence* s) { audio_format.setSampleType(QAudioFormat::SignedInt); QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice()); + QList devs = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput); + dout << "[INFO] Found the following audio devices:"; + for (int i=0;i 0) { + dout << "[WARNING] Default audio returned NULL, attempting to use first device found..."; + info = devs.at(0); + } + dout << "[INFO] Using audio device" << info.deviceName(); + if (!info.isFormatSupported(audio_format)) { qWarning() << "[WARNING] Audio format is not supported by backend, using nearest"; audio_format = info.nearestFormat(audio_format); From 0e2632e578dc29cacf884f8d233eda83083da703 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Wed, 5 Dec 2018 22:34:53 +1100 Subject: [PATCH 03/40] added export ETA --- dialogs/exportdialog.cpp | 13 ++++++++++--- dialogs/exportdialog.h | 2 +- dialogs/exportdialog.ui | 7 +++++-- io/exportthread.cpp | 26 ++++++++++++++++++++------ io/exportthread.h | 2 +- 5 files changed, 37 insertions(+), 13 deletions(-) diff --git a/dialogs/exportdialog.cpp b/dialogs/exportdialog.cpp index ca5a8a52a..0ff349cc1 100644 --- a/dialogs/exportdialog.cpp +++ b/dialogs/exportdialog.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "debug.h" #include "panels/panels.h" @@ -474,7 +475,7 @@ void ExportDialog::on_pushButton_clicked() { connect(et, SIGNAL(finished()), et, SLOT(deleteLater())); connect(et, SIGNAL(finished()), this, SLOT(render_thread_finished())); - connect(et, SIGNAL(progress_changed(int)), this, SLOT(update_progress_bar(int))); + connect(et, SIGNAL(progress_changed(int, qint64)), this, SLOT(update_progress_bar(int, qint64))); closeActiveClips(sequence, true); @@ -517,8 +518,14 @@ void ExportDialog::on_pushButton_clicked() { } } -void ExportDialog::update_progress_bar(int value) { - ui->progressBar->setValue(value); +void ExportDialog::update_progress_bar(int value, qint64 remaining_ms) { + // convert ms to H:MM:SS + int seconds = qFloor(remaining_ms*0.001)%60; + int minutes = qFloor(remaining_ms/60000)%60; + int hours = qFloor(remaining_ms/3600000); + ui->progressBar->setFormat("%p% (ETA: " + QString::number(hours) + ":" + QString::number(minutes).rightJustified(2, '0') + ":" + QString::number(seconds).rightJustified(2, '0') + ")"); + + ui->progressBar->setValue(value); } void ExportDialog::on_renderCancel_clicked() { diff --git a/dialogs/exportdialog.h b/dialogs/exportdialog.h index 7505d1b1a..07585b6f9 100644 --- a/dialogs/exportdialog.h +++ b/dialogs/exportdialog.h @@ -25,7 +25,7 @@ private slots: void on_pushButton_clicked(); - void update_progress_bar(int value); + void update_progress_bar(int value, qint64 remaining_ms); void on_renderCancel_clicked(); diff --git a/dialogs/exportdialog.ui b/dialogs/exportdialog.ui index 3133f2766..4a9207014 100644 --- a/dialogs/exportdialog.ui +++ b/dialogs/exportdialog.ui @@ -6,8 +6,8 @@ 0 0 - 358 - 470 + 443 + 562 @@ -227,6 +227,9 @@ 0 + + %p% (ETA: 0:00:00) + diff --git a/io/exportthread.cpp b/io/exportthread.cpp index 8482db33f..320543529 100644 --- a/io/exportthread.cpp +++ b/io/exportthread.cpp @@ -125,6 +125,7 @@ bool ExportThread::setupVideo() { itoa(vcodec_ctx, buffer, 10);*/ //av_opt_set(vcodec_ctx->priv_data, "preset", "fast", AV_OPT_SEARCH_CHILDREN); + av_opt_set(vcodec_ctx->priv_data, "x264opts", "opencl", AV_OPT_SEARCH_CHILDREN); switch (video_compression_type) { case COMPRESSION_TYPE_CFR: @@ -344,8 +345,12 @@ void ExportThread::run() { panel_sequence_viewer->viewer_widget->default_fbo = &fbo; long file_audio_samples = 0; + qint64 start_time, frame_time, avg_time, eta, total_time = 0; + long remaining_frames, frame_count = 1; while (sequence->playhead < end_frame && continueEncode) { + start_time = QDateTime::currentMSecsSinceEpoch(); + panel_sequence_viewer->viewer_widget->paintGL(); double timecode_secs = (double) (sequence->playhead-start_frame) / sequence->frame_rate; @@ -357,10 +362,8 @@ void ExportThread::run() { sws_scale(sws_ctx, video_frame->data, video_frame->linesize, 0, video_frame->height, sws_frame->data, sws_frame->linesize); sws_frame->pts = round(timecode_secs/av_q2d(video_stream->time_base)); - // send to encoder -// dout << "starting encode of video frame" << sequence->playhead; - if (!encode(fmt_ctx, vcodec_ctx, sws_frame, &video_pkt, video_stream)) continueEncode = false; -// dout << "completed encode of video frame" << sequence->playhead; + // send to encoder + if (!encode(fmt_ctx, vcodec_ctx, sws_frame, &video_pkt, video_stream)) continueEncode = false; } if (audio_enabled) { // do we need to encode more audio samples? @@ -389,8 +392,19 @@ void ExportThread::run() { file_audio_samples += swr_frame->nb_samples; } } - emit progress_changed(((double) (sequence->playhead-start_frame) / (double) (end_frame-start_frame)) * 100); + + // encoding stats + frame_time = (QDateTime::currentMSecsSinceEpoch()-start_time); + total_time += frame_time; + remaining_frames = (end_frame-sequence->playhead); + avg_time = (total_time/frame_count); + eta = (remaining_frames*avg_time); + +// dout << "[INFO] Encoded frame" << sequence->playhead << "- took" << frame_time << "ms (avg:" << avg_time << "ms, remaining:" << remaining_frames << ", ETA:" << eta << ")"; + + emit progress_changed(qRound(((double) (sequence->playhead-start_frame) / (double) (end_frame-start_frame)) * 100), eta); sequence->playhead++; + frame_count++; } panel_sequence_viewer->viewer_widget->default_fbo = NULL; @@ -425,7 +439,7 @@ void ExportThread::run() { continueEncode = false; } - emit progress_changed(100); + emit progress_changed(100, 0); } avio_closep(&fmt_ctx->pb); diff --git a/io/exportthread.h b/io/exportthread.h index cd36fc7a9..dfc48c442 100644 --- a/io/exportthread.h +++ b/io/exportthread.h @@ -44,7 +44,7 @@ public: bool continueEncode; signals: - void progress_changed(int value); + void progress_changed(int value, qint64 remaining_ms); private: bool encode(AVFormatContext* ofmt_ctx, AVCodecContext* codec_ctx, AVFrame* frame, AVPacket* packet, AVStream* stream); bool setupVideo(); From f21742c2f3c38d43921ea2155a3d968fbac7c47d Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sat, 8 Dec 2018 16:37:34 +1100 Subject: [PATCH 04/40] fixed blurs causing black frames and added backend for stabilizer --- effects/boxblur.frag | 2 +- effects/directionalblur.frag | 5 ++-- effects/gaussianblur.frag | 2 +- playback/cacher.cpp | 57 ++++++++++++------------------------ project/clip.h | 3 +- ui/viewerwidget.cpp | 2 +- 6 files changed, 26 insertions(+), 45 deletions(-) diff --git a/effects/boxblur.frag b/effects/boxblur.frag index 2ecbd56a4..3683a469e 100644 --- a/effects/boxblur.frag +++ b/effects/boxblur.frag @@ -8,7 +8,7 @@ uniform bool horiz_blur; uniform bool vert_blur; void main(void) { - float rad = floor(radius); + float rad = ceil(radius); float x_rad = (horiz_blur) ? rad : 0.5; float y_rad = (vert_blur) ? rad : 0.5; vec2 texCoord = gl_FragCoord.xy/resolution; diff --git a/effects/directionalblur.frag b/effects/directionalblur.frag index 13bee421c..1d549ed6d 100644 --- a/effects/directionalblur.frag +++ b/effects/directionalblur.frag @@ -11,12 +11,13 @@ uniform vec2 resolution; void main(void) { if (length > 0.0) { + float ceillen = ceil(length); float radians = (angle*M_PI)/180.0; - float divider = 1.0 / length; + float divider = 1.0 / ceillen; float sin_angle = sin(radians); float cos_angle = cos(radians); - for (float i=-length+0.5;i<=length;i+=2.0) { + for (float i=-ceillen+0.5;i<=ceillen;i+=2.0) { float y = sin_angle * i; float x = cos_angle * i; gl_FragColor += texture2D(image, vec2(gl_FragCoord.x+x, gl_FragCoord.y+y)/resolution)*(divider); diff --git a/effects/gaussianblur.frag b/effects/gaussianblur.frag index ac90c0563..f2d76caf2 100644 --- a/effects/gaussianblur.frag +++ b/effects/gaussianblur.frag @@ -24,7 +24,7 @@ void main(void) { if (radius == 0.0 || sigma == 0.0 || (!horiz_blur && !vert_blur)) { gl_FragColor = texture2D(image, gl_FragCoord.xy/resolution); } else { - float rad = floor(radius); + float rad = ceil(radius); float x_rad = horiz_blur ? rad : 0.5; float y_rad = vert_blur ? rad : 0.5; diff --git a/playback/cacher.cpp b/playback/cacher.cpp index 814624595..6e2f78395 100644 --- a/playback/cacher.cpp +++ b/playback/cacher.cpp @@ -456,9 +456,7 @@ void cache_video_worker(Clip* c, long playhead) { MediaStream* 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) { // abort - return; - } + if (c->multithreaded && c->cacher->interrupt) return; // abort AVFrame* send_frame = c->frame; read_ret = (c->use_existing_frame) ? 0 : retrieve_next_frame(c, send_frame); @@ -476,11 +474,11 @@ void cache_video_worker(Clip* c, long playhead) { dout << "skipped adding a frame to the queue - fpts:" << send_frame->pts << "target:" << target_pts; }*/ - if (send_it) { + if (send_it) { if ((send_ret = av_buffersrc_add_frame_flags(c->buffersrc_ctx, send_frame, AV_BUFFERSRC_FLAG_KEEP_REF)) < 0) { dout << "[ERROR] Failed to add frame to buffer source." << send_ret; break; - } + } } av_frame_unref(c->frame); @@ -693,27 +691,7 @@ void open_clip_worker(Clip* clip) { } char filter_args[512]; - if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { - /* SKIP_TYPE_SEEK is used if a video is playing at a speed so fast - * that it is quicker to seek to the next frame than to just play - * up to it (e.g. 2000% speed would require playing and skipping - * 20 frames per frame and it many cases it would be quicker to - * seek to it and cache in memory instead. - * - * TODO there could probably be a better heuristic than - * (speed >= 5) for using seek mode. Experiment with the value - * but also in the future perhaps we could implement a system - * of testing how long it takes to seek vs how long it takes to - * decode a frame and compare them to choose with method. - */ - clip->skip_type = (clip->speed < 5) ? SKIP_TYPE_DISCARD : SKIP_TYPE_SEEK; - - // create memory cache for video (deprecated) - // clip->cache_size = (ms->infinite_length) ? 1 : ceil(av_q2d(clip->stream->avg_frame_rate)/4); // cache is half a second in total - - // if (clip->skip_type == SKIP_TYPE_SEEK) clip->cache_size *= 2; - // if (ms->video_interlacing != VIDEO_PROGRESSIVE) clip->cache_size *= 2; - + 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, @@ -727,39 +705,42 @@ void open_clip_worker(Clip* clip) { avfilter_graph_create_filter(&clip->buffersrc_ctx, avfilter_get_by_name("buffer"), "in", filter_args, NULL, clip->filter_graph); avfilter_graph_create_filter(&clip->buffersink_ctx, avfilter_get_by_name("buffersink"), "out", NULL, NULL, clip->filter_graph); - enum AVPixelFormat pix_fmts[] = { static_cast(dest_format), AV_PIX_FMT_NONE }; - if (av_opt_set_int_list(clip->buffersink_ctx, "pix_fmts", pix_fmts, AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN) < 0) { - dout << "[ERROR] Could not set output pixel format"; - } + /*enum AVPixelFormat sinkpix_fmts[] = { static_cast(dest_format), AV_PIX_FMT_NONE }; + if (av_opt_set_int_list(clip->buffersink_ctx, "pix_fmts", sinkpix_fmts, AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN) < 0) { + dout << "[ERROR] Could not set output pixel format"; + }*/ AVFilterContext* last_filter = clip->buffersrc_ctx; if (ms->video_interlacing != VIDEO_PROGRESSIVE) { AVFilterContext* yadif_filter; char yadif_args[100]; - snprintf(yadif_args, sizeof(yadif_args), "mode=3:parity=%d", ((ms->video_interlacing == VIDEO_TOP_FIELD_FIRST) ? 0 : 1)); // try mode 1 + snprintf(yadif_args, sizeof(yadif_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", yadif_args, NULL, clip->filter_graph); avfilter_link(last_filter, 0, yadif_filter, 0); last_filter = yadif_filter; } - /* stabilization code one day - if (false) { + /* stabilization code one day */ + bool stabilize = false; + if (stabilize) { AVFilterContext* stab_filter; - int stab_ret = avfilter_graph_create_filter(&stab_filter, avfilter_get_by_name("vidstabtransform"), "vidstab", "input=C\\:/Users/Matt/Desktop/samples/transforms.trf", NULL, clip->filter_graph); + int stab_ret = avfilter_graph_create_filter(&stab_filter, avfilter_get_by_name("vidstabtransform"), "vidstab", "input=/media/matt/Home/samples/transforms.trf", NULL, clip->filter_graph); if (stab_ret < 0) { char err[100]; av_strerror(stab_ret, err, sizeof(err)); - dout << "stab ret:" << stab_ret << err; } else { - dout << "link 1:" << avfilter_link(last_filter, 0, stab_filter, 0); + avfilter_link(last_filter, 0, stab_filter, 0); last_filter = stab_filter; } } - */ - avfilter_link(last_filter, 0, clip->buffersink_ctx, 0); + AVFilterContext* format_conv; + avfilter_graph_create_filter(&format_conv, avfilter_get_by_name("format"), "fmt", "pix_fmts=rgba", NULL, clip->filter_graph); + avfilter_link(last_filter, 0, format_conv, 0); + + avfilter_link(format_conv, 0, clip->buffersink_ctx, 0); avfilter_graph_config(clip->filter_graph, NULL); } else if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { diff --git a/project/clip.h b/project/clip.h index ea0fc6e85..8f6659b0f 100644 --- a/project/clip.h +++ b/project/clip.h @@ -96,8 +96,7 @@ struct Clip bool pkt_written; bool open; bool finished_opening; - bool replaced; - int skip_type; + bool replaced; bool ignore_reverse; // caching functions diff --git a/ui/viewerwidget.cpp b/ui/viewerwidget.cpp index ecfa1be82..fa1e62ce2 100644 --- a/ui/viewerwidget.cpp +++ b/ui/viewerwidget.cpp @@ -52,7 +52,7 @@ ViewerWidget::ViewerWidget(QWidget *parent) : format.setDepthBufferSize(24); setFormat(format); - // error handler - retries after 500ms if we couldn't get the entire image + // error handler - retries after 50ms if we couldn't get the entire image retry_timer.setInterval(50); connect(&retry_timer, SIGNAL(timeout()), this, SLOT(retry())); From 576d3f00984ad57d87c30daf6731d795f7262720 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sat, 8 Dec 2018 20:42:41 +1100 Subject: [PATCH 05/40] fixed several memory leaks and image sequences not matching when loading --- io/media.cpp | 2 +- io/media.h | 2 +- panels/project.cpp | 6 +++++- playback/cacher.cpp | 17 ++++++++++------- project/clip.cpp | 4 +++- project/clip.h | 2 ++ project/effect.cpp | 5 +++++ project/effectrow.cpp | 2 +- project/effectrow.h | 2 ++ ui/collapsiblewidget.cpp | 2 +- ui/collapsiblewidget.h | 1 + ui/labelslider.cpp | 6 +++--- 12 files changed, 35 insertions(+), 16 deletions(-) diff --git a/io/media.cpp b/io/media.cpp index f9cc4d8bc..8dd4674a9 100644 --- a/io/media.cpp +++ b/io/media.cpp @@ -10,7 +10,7 @@ extern "C" { #include "project/clip.h" -Media::Media() : ready(false), preview_gen(NULL), invalid(false) { +Media::Media() : ready(false), preview_gen(NULL), invalid(false), in(0), out(0) { ready_lock.lock(); } diff --git a/io/media.h b/io/media.h index 9d1312f06..35e65e4aa 100644 --- a/io/media.h +++ b/io/media.h @@ -45,7 +45,7 @@ struct Media { Media(); ~Media(); - QString url; + QString url; QString name; int64_t length; QVector video_tracks; diff --git a/panels/project.cpp b/panels/project.cpp index 1b124b08d..3e220d458 100644 --- a/panels/project.cpp +++ b/panels/project.cpp @@ -602,7 +602,7 @@ void Project::process_file_list(bool recursive, QStringList& files, QTreeWidgetI } m->using_inout = false; - m->url = file; + m->url = file; m->name = get_file_name_from_path(files.at(i)); // generate waveform/thumbnail in another thread @@ -1004,6 +1004,10 @@ bool Project::load_worker(QFile& f, QXmlStreamReader& stream, int type) { } else if (QFileInfo::exists(internal_proj_dir_test)) { // if path is relative to the last directory the project was saved in m->url = internal_proj_dir_test; dout << "[INFO] Matched" << attr.value().toString() << "relative to project's internal directory"; + } else if (m->url.contains('%')) { + // hack for image sequences (qt won't be able to find the URL with %, but ffmpeg may) + m->url = proj_dir_test; + dout << "[INFO] Guess image sequence" << attr.value().toString() << "path to project's current directory"; } else { dout << "[INFO] Failed to match" << attr.value().toString() << "to file"; } diff --git a/playback/cacher.cpp b/playback/cacher.cpp index 6e2f78395..0c18f5fc4 100644 --- a/playback/cacher.cpp +++ b/playback/cacher.cpp @@ -665,22 +665,22 @@ void open_clip_worker(Clip* clip) { clip->max_queue_size = (ms->infinite_length) ? 1 : qCeil(ms->video_frame_rate*0.5); if (ms->video_interlacing != VIDEO_PROGRESSIVE) clip->max_queue_size *= 2; - AVDictionary* opts = NULL; + clip->opts = NULL; // optimized decoding settings - if (clip->stream->codecpar->codec_id != AV_CODEC_ID_PNG && + if (clip->stream->codecpar->codec_id != AV_CODEC_ID_PNG && clip->stream->codecpar->codec_id != AV_CODEC_ID_APNG && clip->stream->codecpar->codec_id != AV_CODEC_ID_TIFF && clip->stream->codecpar->codec_id != AV_CODEC_ID_PSD) { - av_dict_set(&opts, "threads", "auto", 0); + av_dict_set(&clip->opts, "threads", "auto", 0); } if (clip->stream->codecpar->codec_id == AV_CODEC_ID_H264) { - av_dict_set(&opts, "tune", "fastdecode", 0); - av_dict_set(&opts, "tune", "zerolatency", 0); - } + 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, &opts) < 0) { + if (avcodec_open2(clip->codecCtx, clip->codec, &clip->opts) < 0) { dout << "[ERROR] Could not open codec"; } @@ -888,6 +888,9 @@ void close_clip_worker(Clip* clip) { avcodec_close(clip->codecCtx); avcodec_free_context(&clip->codecCtx); + + av_dict_free(&clip->opts); + avformat_close_input(&clip->formatCtx); } diff --git a/project/clip.cpp b/project/clip.cpp index 08bf01299..e4934fcad 100644 --- a/project/clip.cpp +++ b/project/clip.cpp @@ -35,7 +35,8 @@ Clip::Clip(Sequence* s) : use_existing_frame(false), filter_graph(NULL), fbo(NULL), - texture(NULL) + texture(NULL), + opts(NULL) { pkt = av_packet_alloc(); reset(); @@ -88,6 +89,7 @@ void Clip::reset() { codec = NULL; codecCtx = NULL; texture = NULL; + last_invalid_ts = -1; } void Clip::reset_audio() { diff --git a/project/clip.h b/project/clip.h index 8f6659b0f..300659e31 100644 --- a/project/clip.h +++ b/project/clip.h @@ -27,6 +27,7 @@ struct SwsContext; struct SwrContext; struct AVFilterGraph; struct AVFilterContext; +struct AVDictionary; class QOpenGLTexture; struct Clip @@ -87,6 +88,7 @@ struct Clip AVCodecContext* codecCtx; AVPacket* pkt; AVFrame* frame; + AVDictionary* opts; long calculated_length; // temporary variables diff --git a/project/effect.cpp b/project/effect.cpp index 73c661167..8255631ce 100644 --- a/project/effect.cpp +++ b/project/effect.cpp @@ -431,9 +431,14 @@ Effect::~Effect() { close(); } + delete container; + for (int i=0;isetSpacing(0); key_controls->setMargin(0); key_controls->addStretch(); diff --git a/project/effectrow.h b/project/effectrow.h index ec4e2025c..ab89c23f6 100644 --- a/project/effectrow.h +++ b/project/effectrow.h @@ -11,6 +11,7 @@ class QLabel; class KeyframeDelete; class QPushButton; class ComboAction; +class QHBoxLayout; class EffectRow : public QObject { Q_OBJECT @@ -45,6 +46,7 @@ private: int ui_row; QVector fields; + QHBoxLayout* key_controls; QPushButton* keyframe_enable; QPushButton* left_key_nav; QPushButton* key_addremove; diff --git a/ui/collapsiblewidget.cpp b/ui/collapsiblewidget.cpp index f9622b03a..a4284b80b 100644 --- a/ui/collapsiblewidget.cpp +++ b/ui/collapsiblewidget.cpp @@ -22,7 +22,7 @@ CollapsibleWidget::CollapsibleWidget(QWidget* parent) : QWidget(parent) { title_bar = new CollapsibleWidgetHeader(); title_bar->setFocusPolicy(Qt::ClickFocus); title_bar->setAutoFillBackground(true); - QHBoxLayout* title_bar_layout = new QHBoxLayout(); + title_bar_layout = new QHBoxLayout(); title_bar_layout->setMargin(5); title_bar->setLayout(title_bar_layout); enabled_check = new CheckboxEx(); diff --git a/ui/collapsiblewidget.h b/ui/collapsiblewidget.h index d21c38945..b944c208a 100644 --- a/ui/collapsiblewidget.h +++ b/ui/collapsiblewidget.h @@ -41,6 +41,7 @@ private: QVBoxLayout* layout; QPushButton* collapse_button; QFrame* line; + QHBoxLayout* title_bar_layout; signals: void deselect_others(QWidget*); diff --git a/ui/labelslider.cpp b/ui/labelslider.cpp index ab0158f74..357a891db 100644 --- a/ui/labelslider.cpp +++ b/ui/labelslider.cpp @@ -19,10 +19,10 @@ LabelSlider::LabelSlider(QWidget* parent) : QLabel(parent) { setStyleSheet("QLabel{color:#ffc000;text-decoration:underline;}QLabel:disabled{color:#808080;}"); setCursor(Qt::SizeHorCursor); internal_value = -1; + set = false; + display_type = LABELSLIDER_NORMAL; + set_default_value(0); - set_value(0, false); - set = false; - display_type = LABELSLIDER_NORMAL; } void LabelSlider::set_frame_rate(double d) { From 6c3197aac60b9b242da6f8997c0a1bd3948c1e58 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sat, 8 Dec 2018 21:00:36 +1100 Subject: [PATCH 06/40] ensured renders can be cancelled --- dialogs/exportdialog.cpp | 1 + panels/viewer.h | 2 +- ui/viewerwidget.cpp | 2 ++ ui/viewerwidget.h | 2 ++ 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/dialogs/exportdialog.cpp b/dialogs/exportdialog.cpp index 0ff349cc1..737e92cc0 100644 --- a/dialogs/exportdialog.cpp +++ b/dialogs/exportdialog.cpp @@ -529,6 +529,7 @@ void ExportDialog::update_progress_bar(int value, qint64 remaining_ms) { } void ExportDialog::on_renderCancel_clicked() { + panel_sequence_viewer->viewer_widget->force_quit = true; et->continueEncode = false; cancelled = true; } diff --git a/panels/viewer.h b/panels/viewer.h index d7b5f1d53..547efdf7b 100644 --- a/panels/viewer.h +++ b/panels/viewer.h @@ -57,7 +57,7 @@ public: bool is_recording_cued(); long recording_start; long recording_end; - int recording_track; + int recording_track; void reset_all_audio(); void assert_audio_device(); diff --git a/ui/viewerwidget.cpp b/ui/viewerwidget.cpp index fa1e62ce2..d0730a725 100644 --- a/ui/viewerwidget.cpp +++ b/ui/viewerwidget.cpp @@ -736,6 +736,7 @@ GLuint ViewerWidget::compose_sequence(QVector& nests, bool render_audio) void ViewerWidget::paintGL() { drawn_gizmos = false; + force_quit = false; if (viewer->seq != NULL) { gizmos = NULL; @@ -784,6 +785,7 @@ void ViewerWidget::paintGL() { p.drawLine(playhead_x, 0, playhead_x, height()); } + if (force_quit) break; if (texture_failed) { if (rendering) { dout << "[INFO] Texture failed - looping"; diff --git a/ui/viewerwidget.h b/ui/viewerwidget.h index 55f68a654..1b7abc7d5 100644 --- a/ui/viewerwidget.h +++ b/ui/viewerwidget.h @@ -33,6 +33,8 @@ public: bool waveform; Clip* waveform_clip; MediaStream* waveform_ms; + + bool force_quit; public slots: void delete_function(); protected: From 7f08925385fd963bb3da6ea7f226b026578c3a75 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sun, 9 Dec 2018 01:25:22 +1100 Subject: [PATCH 07/40] began work on scrollable headers on viewer panels --- mainwindow.cpp | 14 +++++++++++--- panels/viewer.cpp | 21 ++++++++++++++++++--- panels/viewer.h | 3 +++ panels/viewer.ui | 7 +++++++ ui/timelineheader.cpp | 6 +++++- ui/timelineheader.h | 1 + 6 files changed, 45 insertions(+), 7 deletions(-) diff --git a/mainwindow.cpp b/mainwindow.cpp index b2a3005df..2354caf01 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -311,7 +311,11 @@ void MainWindow::on_actionZoom_In_triggered() panel_timeline->set_zoom(true); } else if (panel_effect_controls->keyframe_focus()) { panel_effect_controls->set_zoom(true); - } + } else if (panel_footage_viewer->is_focused()) { + panel_footage_viewer->set_zoom(true); + } else if (panel_sequence_viewer->is_focused()) { + panel_sequence_viewer->set_zoom(true); + } } void MainWindow::on_actionZoom_out_triggered() @@ -320,7 +324,11 @@ void MainWindow::on_actionZoom_out_triggered() panel_timeline->set_zoom(false); } else if (panel_effect_controls->keyframe_focus()) { panel_effect_controls->set_zoom(false); - } + } else if (panel_footage_viewer->is_focused()) { + panel_footage_viewer->set_zoom(false); + } else if (panel_sequence_viewer->is_focused()) { + panel_sequence_viewer->set_zoom(false); + } } void MainWindow::on_actionExport_triggered() @@ -582,7 +590,7 @@ void MainWindow::on_actionEdit_Tool_triggered() void MainWindow::on_actionToggle_Snapping_triggered() { - if (panel_timeline->focused()) panel_timeline->ui->snappingButton->click(); + if (panel_timeline->focused() || panel_effect_controls->keyframe_focus()) panel_timeline->ui->snappingButton->click(); } void MainWindow::on_actionPointer_Tool_triggered() diff --git a/panels/viewer.cpp b/panels/viewer.cpp index 9e2fccde9..5b13fe699 100644 --- a/panels/viewer.cpp +++ b/panels/viewer.cpp @@ -37,7 +37,8 @@ Viewer::Viewer(QWidget *parent) : ui(new Ui::Viewer), created_sequence(false), cue_recording_internal(false), - panel_name("Viewer: ") + panel_name("Viewer: "), + minimum_zoom(1.0) { ui->setupUi(this); ui->headers->viewer = this; @@ -59,6 +60,7 @@ Viewer::Viewer(QWidget *parent) : connect(&playback_updater, SIGNAL(timeout()), this, SLOT(timer_update())); connect(&recording_flasher, SIGNAL(timeout()), this, SLOT(recording_flasher_update())); + connect(ui->horizontalScrollBar, SIGNAL(valueChanged(int)), ui->headers, SLOT(set_scroll(int))); update_playhead_timecode(0); update_end_timecode(); @@ -380,7 +382,12 @@ void Viewer::update_end_timecode() { void Viewer::update_header_zoom() { if (seq != NULL) { long sequenceEndFrame = seq->getEndFrame(); - ui->headers->update_zoom((sequenceEndFrame > 0) ? ((double) ui->headers->width() / (double) sequenceEndFrame) : 1); + if (cached_end_frame != sequenceEndFrame) { + minimum_zoom = (sequenceEndFrame > 0) ? ((double) ui->headers->width() / (double) sequenceEndFrame) : 1; + ui->headers->update_zoom(qMax(ui->headers->get_zoom(), minimum_zoom)); + } else { + ui->headers->update(); + } } } @@ -411,7 +418,15 @@ void Viewer::set_in_point() { } void Viewer::set_out_point() { - ui->headers->set_out_point(seq->playhead); + ui->headers->set_out_point(seq->playhead); +} + +void Viewer::set_zoom(bool in) { + if (in) { + ui->headers->update_zoom(ui->headers->get_zoom()*2); + } else { + ui->headers->update_zoom(qMax(minimum_zoom, ui->headers->get_zoom()*0.5)); + } } void Viewer::set_media(int type, void* media) { diff --git a/panels/viewer.h b/panels/viewer.h index 547efdf7b..a46c24c66 100644 --- a/panels/viewer.h +++ b/panels/viewer.h @@ -36,6 +36,7 @@ public: void clear_inout_point(); void set_in_point(); void set_out_point(); + void set_zoom(bool in); // playback functions void go_to_start(); @@ -86,7 +87,9 @@ private: void set_sequence(bool main, Sequence* s); bool main_sequence; bool created_sequence; + long cached_end_frame; QString panel_name; + double minimum_zoom; bool cue_recording_internal; QTimer recording_flasher; diff --git a/panels/viewer.ui b/panels/viewer.ui index 111812803..648765042 100644 --- a/panels/viewer.ui +++ b/panels/viewer.ui @@ -64,6 +64,13 @@ + + + + Qt::Horizontal + + + diff --git a/ui/timelineheader.cpp b/ui/timelineheader.cpp index 00094fac2..c67c8d39f 100644 --- a/ui/timelineheader.cpp +++ b/ui/timelineheader.cpp @@ -255,7 +255,11 @@ void TimelineHeader::update_parents() { void TimelineHeader::update_zoom(double z) { zoom = z; - update(); + update(); +} + +double TimelineHeader::get_zoom() { + return zoom; } void TimelineHeader::delete_markers() { diff --git a/ui/timelineheader.h b/ui/timelineheader.h index 68cb38573..979b3fd38 100644 --- a/ui/timelineheader.h +++ b/ui/timelineheader.h @@ -20,6 +20,7 @@ public: void show_text(bool enable); void update_zoom(double z); + double get_zoom(); void delete_markers(); public slots: From 5d89afb639b0124cd23dd941f16f8a87376ff1d0 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sun, 9 Dec 2018 01:39:36 +1100 Subject: [PATCH 08/40] finished scrollable viewer playheads --- panels/timeline.cpp | 16 +++------------- panels/timeline.h | 3 +-- panels/viewer.cpp | 3 +++ panels/viewer.ui | 9 +++++++++ ui/timelineheader.cpp | 16 +++++++++++++++- ui/timelineheader.h | 5 ++++- 6 files changed, 35 insertions(+), 17 deletions(-) diff --git a/panels/timeline.cpp b/panels/timeline.cpp index e9279206f..7726a08e8 100644 --- a/panels/timeline.cpp +++ b/panels/timeline.cpp @@ -417,16 +417,6 @@ bool Timeline::focused() { return (sequence != NULL && (ui->headers->hasFocus() || ui->video_area->hasFocus() || ui->audio_area->hasFocus())); } -bool Timeline::center_scroll_to_playhead() { - // returns true is the scroll was changed, false if not - int target_scroll = qMin(ui->horizontalScrollBar->maximum(), qMax(0, getScreenPointFromFrame(zoom, sequence->playhead)-(ui->editAreas->width()>>1))); - if (target_scroll == ui->horizontalScrollBar->value()) { - return false; - } - ui->horizontalScrollBar->setValue(target_scroll); - return true; -} - void Timeline::repaint_timeline() { bool draw = true; @@ -442,7 +432,7 @@ void Timeline::repaint_timeline() { draw = false; } } else if (config.autoscroll == AUTOSCROLL_SMOOTH_SCROLL) { - if (center_scroll_to_playhead()) { + if (center_scroll_to_playhead(ui->horizontalScrollBar, zoom, sequence->playhead)) { draw = false; } } @@ -458,7 +448,7 @@ void Timeline::repaint_timeline() { if (sequence != NULL) { long sequenceEndFrame = sequence->getEndFrame(); - ui->horizontalScrollBar->setMaximum(qMax(0, getScreenPointFromFrame(zoom, sequenceEndFrame) - (ui->editAreas->width()/2))); + ui->headers->set_scrollbar_max(ui->horizontalScrollBar, sequenceEndFrame, (ui->editAreas->width()/2)); if (last_frame != sequence->playhead) { ui->audio_monitor->update(); @@ -579,7 +569,7 @@ void Timeline::set_zoom_value(double v) { repaint_timeline(); // TODO find a way to gradually move towards target_scroll instead of just centering it? - center_scroll_to_playhead(); + center_scroll_to_playhead(ui->horizontalScrollBar, zoom, sequence->playhead); } void Timeline::set_zoom(bool in) { diff --git a/panels/timeline.h b/panels/timeline.h index db73a87da..fddd177b4 100644 --- a/panels/timeline.h +++ b/panels/timeline.h @@ -227,8 +227,7 @@ private: void set_zoom_value(double v); QVector tool_buttons; void decheck_tool_buttons(QObject* sender); - void set_tool(int tool); - bool center_scroll_to_playhead(); + void set_tool(int tool); long last_frame; int scroll; diff --git a/panels/viewer.cpp b/panels/viewer.cpp index 5b13fe699..b92d96935 100644 --- a/panels/viewer.cpp +++ b/panels/viewer.cpp @@ -385,6 +385,7 @@ void Viewer::update_header_zoom() { if (cached_end_frame != sequenceEndFrame) { minimum_zoom = (sequenceEndFrame > 0) ? ((double) ui->headers->width() / (double) sequenceEndFrame) : 1; ui->headers->update_zoom(qMax(ui->headers->get_zoom(), minimum_zoom)); + ui->headers->set_scrollbar_max(ui->horizontalScrollBar, sequenceEndFrame, ui->headers->width()); } else { ui->headers->update(); } @@ -427,6 +428,8 @@ void Viewer::set_zoom(bool in) { } else { ui->headers->update_zoom(qMax(minimum_zoom, ui->headers->get_zoom()*0.5)); } + ui->headers->set_scrollbar_max(ui->horizontalScrollBar, seq->getEndFrame(), ui->headers->width()); + center_scroll_to_playhead(ui->horizontalScrollBar, ui->headers->get_zoom(), seq->playhead); } void Viewer::set_media(int type, void* media) { diff --git a/panels/viewer.ui b/panels/viewer.ui index 648765042..0a7613183 100644 --- a/panels/viewer.ui +++ b/panels/viewer.ui @@ -66,6 +66,15 @@ + + 0 + + + 20 + + + 1826 + Qt::Horizontal diff --git a/ui/timelineheader.cpp b/ui/timelineheader.cpp index c67c8d39f..d39976bbd 100644 --- a/ui/timelineheader.cpp +++ b/ui/timelineheader.cpp @@ -10,7 +10,7 @@ #include #include -#include +#include #include #define CLICK_RANGE 5 @@ -19,6 +19,16 @@ #define SUBLINE_MIN_PADDING 50 // TODO play with this #define MARKER_SIZE 4 +bool center_scroll_to_playhead(QScrollBar* bar, double zoom, long playhead) { + // returns true is the scroll was changed, false if not + int target_scroll = qMin(bar->maximum(), qMax(0, getScreenPointFromFrame(zoom, playhead)-(bar->width()>>1))); + if (target_scroll == bar->value()) { + return false; + } + bar->setValue(target_scroll); + return true; +} + TimelineHeader::TimelineHeader(QWidget *parent) : QWidget(parent), snapping(true), @@ -87,6 +97,10 @@ void TimelineHeader::set_out_point(long new_out) { update_parents(); } +void TimelineHeader::set_scrollbar_max(QScrollBar* bar, long sequence_end_frame, int offset) { + bar->setMaximum(qMax(0, getScreenPointFromFrame(zoom, sequence_end_frame) - offset)); +} + void TimelineHeader::show_text(bool enable) { text_enabled = enable; if (enable) { diff --git a/ui/timelineheader.h b/ui/timelineheader.h index 979b3fd38..88adc3151 100644 --- a/ui/timelineheader.h +++ b/ui/timelineheader.h @@ -3,8 +3,10 @@ #include #include -class QScrollArea; class Viewer; +class QScrollBar; + +bool center_scroll_to_playhead(QScrollBar* bar, double zoom, long playhead); class TimelineHeader : public QWidget { @@ -22,6 +24,7 @@ public: void update_zoom(double z); double get_zoom(); void delete_markers(); + void set_scrollbar_max(QScrollBar* bar, long sequence_end_frame, int offset); public slots: void set_scroll(int); From e623b8640e0734c0170cad5611f60308a18fed54 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sun, 9 Dec 2018 02:15:53 +1100 Subject: [PATCH 09/40] integrated waveform with scrollable playhead --- panels/viewer.cpp | 8 +++++++- ui/viewercontainer.cpp | 41 ++++++++++++++++++++++++----------------- ui/viewercontainer.h | 3 ++- ui/viewerwidget.cpp | 27 ++++++++++++++++++--------- ui/viewerwidget.h | 3 +++ 5 files changed, 54 insertions(+), 28 deletions(-) diff --git a/panels/viewer.cpp b/panels/viewer.cpp index b92d96935..49b144b42 100644 --- a/panels/viewer.cpp +++ b/panels/viewer.cpp @@ -61,6 +61,7 @@ Viewer::Viewer(QWidget *parent) : connect(&playback_updater, SIGNAL(timeout()), this, SLOT(timer_update())); connect(&recording_flasher, SIGNAL(timeout()), this, SLOT(recording_flasher_update())); connect(ui->horizontalScrollBar, SIGNAL(valueChanged(int)), ui->headers, SLOT(set_scroll(int))); + connect(ui->horizontalScrollBar, SIGNAL(valueChanged(int)), ui->openGLWidget, SLOT(set_waveform_scroll(int))); update_playhead_timecode(0); update_end_timecode(); @@ -386,6 +387,7 @@ void Viewer::update_header_zoom() { minimum_zoom = (sequenceEndFrame > 0) ? ((double) ui->headers->width() / (double) sequenceEndFrame) : 1; ui->headers->update_zoom(qMax(ui->headers->get_zoom(), minimum_zoom)); ui->headers->set_scrollbar_max(ui->horizontalScrollBar, sequenceEndFrame, ui->headers->width()); + ui->openGLWidget->waveform_zoom = ui->headers->get_zoom(); } else { ui->headers->update(); } @@ -401,8 +403,8 @@ void Viewer::update_parents() { } void Viewer::update_viewer() { + update_header_zoom(); viewer_widget->update(); - update_header_zoom(); if (seq != NULL) update_playhead_timecode(seq->playhead); update_end_timecode(); } @@ -428,6 +430,10 @@ void Viewer::set_zoom(bool in) { } else { ui->headers->update_zoom(qMax(minimum_zoom, ui->headers->get_zoom()*0.5)); } + if (ui->openGLWidget->waveform) { + ui->openGLWidget->waveform_zoom = ui->headers->get_zoom(); + ui->openGLWidget->update(); + } ui->headers->set_scrollbar_max(ui->horizontalScrollBar, seq->getEndFrame(), ui->headers->width()); center_scroll_to_playhead(ui->horizontalScrollBar, ui->headers->get_zoom(), seq->playhead); } diff --git a/ui/viewercontainer.cpp b/ui/viewercontainer.cpp index cd3e7cbfd..82b203bba 100644 --- a/ui/viewercontainer.cpp +++ b/ui/viewercontainer.cpp @@ -3,6 +3,8 @@ #include #include +#include "viewerwidget.h" + // enforces aspect ratio ViewerContainer::ViewerContainer(QWidget *parent) : QWidget(parent), @@ -11,26 +13,31 @@ ViewerContainer::ViewerContainer(QWidget *parent) : {} void ViewerContainer::adjust() { - if (child != NULL) { - QSize widget_size = size(); - int widget_x = 0; - int widget_y = 0; - int widget_width = widget_size.width(); - int widget_height = widget_size.height(); - float widget_ar = (float) widget_width /(float) widget_height; + if (child != NULL) { + if (child->waveform) { + child->move(0, 0); + child->resize(size()); + } else { + QSize widget_size = size(); + int widget_x = 0; + int widget_y = 0; + int widget_width = widget_size.width(); + int widget_height = widget_size.height(); + float widget_ar = (float) widget_width /(float) widget_height; - bool widget_is_larger_than_sequence = widget_ar > aspect_ratio; + bool widget_is_larger_than_sequence = widget_ar > aspect_ratio; - if (widget_is_larger_than_sequence) { - widget_width = widget_height * aspect_ratio; - widget_x = (widget_size.width() / 2) - (widget_width / 2); - } else { - widget_height = widget_width / aspect_ratio; - widget_y = (widget_size.height() / 2) - (widget_height / 2); - } + if (widget_is_larger_than_sequence) { + widget_width = widget_height * aspect_ratio; + widget_x = (widget_size.width() / 2) - (widget_width / 2); + } else { + widget_height = widget_width / aspect_ratio; + widget_y = (widget_size.height() / 2) - (widget_height / 2); + } - child->move(widget_x, widget_y); - child->resize(widget_width, widget_height); + child->move(widget_x, widget_y); + child->resize(widget_width, widget_height); + } } } diff --git a/ui/viewercontainer.h b/ui/viewercontainer.h index 2d2bdd0b6..c33a65ac6 100644 --- a/ui/viewercontainer.h +++ b/ui/viewercontainer.h @@ -2,6 +2,7 @@ #define VIEWERCONTAINER_H #include +class ViewerWidget; class ViewerContainer : public QWidget { @@ -9,7 +10,7 @@ class ViewerContainer : public QWidget public: explicit ViewerContainer(QWidget *parent = 0); float aspect_ratio; - QWidget* child; + ViewerWidget* child; void adjust(); protected: diff --git a/ui/viewerwidget.cpp b/ui/viewerwidget.cpp index d0730a725..53c3c47ee 100644 --- a/ui/viewerwidget.cpp +++ b/ui/viewerwidget.cpp @@ -43,7 +43,9 @@ ViewerWidget::ViewerWidget(QWidget *parent) : default_fbo(NULL), waveform(false), dragging(false), - selected_gizmo(NULL) + selected_gizmo(NULL), + waveform_zoom(1.0), + waveform_scroll(0) { setMouseTracking(true); setFocusPolicy(Qt::ClickFocus); @@ -69,6 +71,13 @@ void ViewerWidget::delete_function() { } } +void ViewerWidget::set_waveform_scroll(int s) { + if (waveform) { + waveform_scroll = s; + update(); + } +} + void ViewerWidget::show_context_menu() { QMenu menu(this); @@ -141,7 +150,7 @@ void ViewerWidget::paintEvent(QPaintEvent *e) { } void ViewerWidget::seek_from_click(int x) { - viewer->seek(getFrameFromScreenPoint((double) width() / (double) waveform_clip->timeline_out, x)); + viewer->seek(getFrameFromScreenPoint(waveform_zoom, x+waveform_scroll)); } double get_timecode(Clip* c, long playhead) { @@ -765,23 +774,23 @@ void ViewerWidget::paintGL() { compose_sequence(nests, render_audio); if (waveform) { - double waveform_zoom = (double) waveform_ms->audio_preview.size() / (double) width(); - double timeline_zoom = (double) width() / (double) waveform_clip->timeline_out; - QPainter p(this); if (viewer->seq->using_workarea) { - int in_x = getScreenPointFromFrame(timeline_zoom, viewer->seq->workarea_in); - int out_x = getScreenPointFromFrame(timeline_zoom, viewer->seq->workarea_out); + int in_x = getScreenPointFromFrame(waveform_zoom, viewer->seq->workarea_in) - waveform_scroll; + int out_x = getScreenPointFromFrame(waveform_zoom, viewer->seq->workarea_out) - waveform_scroll; p.fillRect(QRect(in_x, 0, out_x - in_x, height()), QColor(255, 255, 255, 64)); p.setPen(Qt::white); p.drawLine(in_x, 0, in_x, height()); p.drawLine(out_x, 0, out_x, height()); } + QRect wr = rect(); + wr.setX(wr.x() - waveform_scroll); + p.setPen(Qt::green); - draw_waveform(waveform_clip, waveform_ms, waveform_clip->timeline_out, &p, rect(), 0, width(), waveform_zoom); + draw_waveform(waveform_clip, waveform_ms, waveform_clip->timeline_out, &p, wr, waveform_scroll, width()+waveform_scroll, waveform_zoom); p.setPen(Qt::red); - int playhead_x = getScreenPointFromFrame(timeline_zoom, viewer->seq->playhead); + int playhead_x = getScreenPointFromFrame(waveform_zoom, viewer->seq->playhead) - waveform_scroll; p.drawLine(playhead_x, 0, playhead_x, height()); } diff --git a/ui/viewerwidget.h b/ui/viewerwidget.h index 1b7abc7d5..d7b9bb859 100644 --- a/ui/viewerwidget.h +++ b/ui/viewerwidget.h @@ -33,10 +33,13 @@ public: bool waveform; Clip* waveform_clip; MediaStream* waveform_ms; + double waveform_zoom; + int waveform_scroll; bool force_quit; public slots: void delete_function(); + void set_waveform_scroll(int s); protected: void paintEvent(QPaintEvent *e); // void resizeGL(int w, int h); From 25e4bd9d8d3b87eb486e06e6e4af992b142831ca Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sun, 9 Dec 2018 15:44:17 +1100 Subject: [PATCH 10/40] added routines for alpha channel editing --- olive.pro | 392 +++++++++++++++++++++--------------------- playback/playback.cpp | 29 +++- playback/playback.h | 1 + project/effect.cpp | 25 +-- project/effect.h | 4 +- project/effectgizmo.h | 2 + ui/viewerwidget.cpp | 22 ++- 7 files changed, 263 insertions(+), 212 deletions(-) diff --git a/olive.pro b/olive.pro index 52a855dab..78d642345 100644 --- a/olive.pro +++ b/olive.pro @@ -1,195 +1,197 @@ -#------------------------------------------------- -# -# Project created by QtCreator 2018-05-11T10:31:59 -# -#------------------------------------------------- - -QT += core gui multimedia opengl - -greaterThan(QT_MAJOR_VERSION, 4): QT += widgets - -TARGET = Olive -TEMPLATE = app - -# The following define makes your compiler emit warnings if you use -# any feature of Qt which has been marked as deprecated (the exact warnings -# depend on your compiler). Please consult the documentation of the -# deprecated API in order to know how to port your code away from it. -DEFINES += QT_DEPRECATED_WARNINGS - -# You can also make your code fail to compile if you use deprecated APIs. -# In order to do so, uncomment the following line. -# You can also select to disable deprecated APIs only up to a certain version of Qt. -#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 - - -SOURCES += \ - main.cpp \ - mainwindow.cpp \ - panels/project.cpp \ - panels/effectcontrols.cpp \ - panels/viewer.cpp \ - panels/timeline.cpp \ - ui/sourcetable.cpp \ - dialogs/aboutdialog.cpp \ - ui/timelinewidget.cpp \ - io/media.cpp \ - project/sequence.cpp \ - project/clip.cpp \ - playback/playback.cpp \ - playback/audio.cpp \ - io/config.cpp \ - dialogs/newsequencedialog.cpp \ - ui/viewerwidget.cpp \ - ui/viewercontainer.cpp \ - dialogs/exportdialog.cpp \ - ui/collapsiblewidget.cpp \ - panels/panels.cpp \ - playback/cacher.cpp \ - io/exportthread.cpp \ - ui/timelineheader.cpp \ - io/previewgenerator.cpp \ - ui/labelslider.cpp \ - dialogs/preferencesdialog.cpp \ - ui/audiomonitor.cpp \ - project/undo.cpp \ - ui/scrollarea.cpp \ - ui/comboboxex.cpp \ - ui/colorbutton.cpp \ - dialogs/replaceclipmediadialog.cpp \ - ui/fontcombobox.cpp \ - ui/checkboxex.cpp \ - ui/keyframeview.cpp \ - ui/texteditex.cpp \ - dialogs/demonotice.cpp \ - project/marker.cpp \ - dialogs/speeddialog.cpp \ - dialogs/mediapropertiesdialog.cpp \ - io/crc32.cpp \ - dialogs/loaddialog.cpp \ - debug.cpp \ - io/path.cpp \ - effects/internal/linearfadetransition.cpp \ - effects/internal/transformeffect.cpp \ - effects/internal/solideffect.cpp \ - effects/internal/texteffect.cpp \ - effects/internal/timecodeeffect.cpp \ - effects/internal/audionoiseeffect.cpp \ - effects/internal/paneffect.cpp \ - effects/internal/toneeffect.cpp \ - effects/internal/volumeeffect.cpp \ - effects/internal/crossdissolvetransition.cpp \ - effects/internal/shakeeffect.cpp \ - effects/internal/exponentialfadetransition.cpp \ - effects/internal/logarithmicfadetransition.cpp \ - effects/internal/cornerpineffect.cpp \ - io/math.cpp \ - io/qpainterwrapper.cpp \ - project/effect.cpp \ - project/transition.cpp \ - project/effectrow.cpp \ - project/effectfield.cpp \ - effects/internal/cubetransition.cpp \ - project/effectgizmo.cpp \ - io/clipboard.cpp - -HEADERS += \ - mainwindow.h \ - panels/project.h \ - panels/effectcontrols.h \ - panels/viewer.h \ - panels/timeline.h \ - ui/sourcetable.h \ - dialogs/aboutdialog.h \ - ui/timelinewidget.h \ - io/media.h \ - project/sequence.h \ - project/clip.h \ - playback/playback.h \ - playback/audio.h \ - io/config.h \ - dialogs/newsequencedialog.h \ - ui/viewerwidget.h \ - ui/viewercontainer.h \ - dialogs/exportdialog.h \ - ui/collapsiblewidget.h \ - panels/panels.h \ - playback/cacher.h \ - io/exportthread.h \ - ui/timelinetools.h \ - ui/timelineheader.h \ - io/previewgenerator.h \ - ui/labelslider.h \ - dialogs/preferencesdialog.h \ - ui/audiomonitor.h \ - project/undo.h \ - ui/scrollarea.h \ - ui/comboboxex.h \ - ui/colorbutton.h \ - dialogs/replaceclipmediadialog.h \ - ui/fontcombobox.h \ - ui/checkboxex.h \ - ui/keyframeview.h \ - ui/texteditex.h \ - dialogs/demonotice.h \ - project/marker.h \ - project/selection.h \ - dialogs/speeddialog.h \ - dialogs/mediapropertiesdialog.h \ - io/crc32.h \ - dialogs/loaddialog.h \ - debug.h \ - io/path.h \ - effects/internal/transformeffect.h \ - effects/internal/solideffect.h \ - effects/internal/texteffect.h \ - effects/internal/timecodeeffect.h \ - effects/internal/audionoiseeffect.h \ - effects/internal/paneffect.h \ - effects/internal/toneeffect.h \ - effects/internal/volumeeffect.h \ - effects/internal/shakeeffect.h \ - effects/internal/linearfadetransition.h \ - effects/internal/crossdissolvetransition.h \ - effects/internal/exponentialfadetransition.h \ - effects/internal/logarithmicfadetransition.h \ - effects/internal/cornerpineffect.h \ - io/math.h \ - io/qpainterwrapper.h \ - project/effect.h \ - project/transition.h \ - project/effectrow.h \ - project/effectfield.h \ - effects/internal/cubetransition.h \ - project/effectgizmo.h \ - io/clipboard.h - -FORMS += \ - mainwindow.ui \ - panels/project.ui \ - panels/effectcontrols.ui \ - panels/viewer.ui \ - panels/timeline.ui \ - dialogs/aboutdialog.ui \ - dialogs/newsequencedialog.ui \ - dialogs/exportdialog.ui \ - dialogs/preferencesdialog.ui \ - dialogs/demonotice.ui - -win32 { - RC_FILE = icons/resources.rc - LIBS += -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample -lopengl32 -} - -mac { - LIBS += -L/usr/local/lib -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample - ICON = icons/olive.icns - INCLUDEPATH = /usr/local/include -} - -linux { - LIBS += -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample -} - -RESOURCES += \ - icons/icons.qrc +#------------------------------------------------- +# +# Project created by QtCreator 2018-05-11T10:31:59 +# +#------------------------------------------------- + +QT += core gui multimedia opengl + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +TARGET = Olive +TEMPLATE = app + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + + +SOURCES += \ + main.cpp \ + mainwindow.cpp \ + panels/project.cpp \ + panels/effectcontrols.cpp \ + panels/viewer.cpp \ + panels/timeline.cpp \ + ui/sourcetable.cpp \ + dialogs/aboutdialog.cpp \ + ui/timelinewidget.cpp \ + io/media.cpp \ + project/sequence.cpp \ + project/clip.cpp \ + playback/playback.cpp \ + playback/audio.cpp \ + io/config.cpp \ + dialogs/newsequencedialog.cpp \ + ui/viewerwidget.cpp \ + ui/viewercontainer.cpp \ + dialogs/exportdialog.cpp \ + ui/collapsiblewidget.cpp \ + panels/panels.cpp \ + playback/cacher.cpp \ + io/exportthread.cpp \ + ui/timelineheader.cpp \ + io/previewgenerator.cpp \ + ui/labelslider.cpp \ + dialogs/preferencesdialog.cpp \ + ui/audiomonitor.cpp \ + project/undo.cpp \ + ui/scrollarea.cpp \ + ui/comboboxex.cpp \ + ui/colorbutton.cpp \ + dialogs/replaceclipmediadialog.cpp \ + ui/fontcombobox.cpp \ + ui/checkboxex.cpp \ + ui/keyframeview.cpp \ + ui/texteditex.cpp \ + dialogs/demonotice.cpp \ + project/marker.cpp \ + dialogs/speeddialog.cpp \ + dialogs/mediapropertiesdialog.cpp \ + io/crc32.cpp \ + dialogs/loaddialog.cpp \ + debug.cpp \ + io/path.cpp \ + effects/internal/linearfadetransition.cpp \ + effects/internal/transformeffect.cpp \ + effects/internal/solideffect.cpp \ + effects/internal/texteffect.cpp \ + effects/internal/timecodeeffect.cpp \ + effects/internal/audionoiseeffect.cpp \ + effects/internal/paneffect.cpp \ + effects/internal/toneeffect.cpp \ + effects/internal/volumeeffect.cpp \ + effects/internal/crossdissolvetransition.cpp \ + effects/internal/shakeeffect.cpp \ + effects/internal/exponentialfadetransition.cpp \ + effects/internal/logarithmicfadetransition.cpp \ + effects/internal/cornerpineffect.cpp \ + io/math.cpp \ + io/qpainterwrapper.cpp \ + project/effect.cpp \ + project/transition.cpp \ + project/effectrow.cpp \ + project/effectfield.cpp \ + effects/internal/cubetransition.cpp \ + project/effectgizmo.cpp \ + io/clipboard.cpp \ + effects/internal/maskeffect.cpp + +HEADERS += \ + mainwindow.h \ + panels/project.h \ + panels/effectcontrols.h \ + panels/viewer.h \ + panels/timeline.h \ + ui/sourcetable.h \ + dialogs/aboutdialog.h \ + ui/timelinewidget.h \ + io/media.h \ + project/sequence.h \ + project/clip.h \ + playback/playback.h \ + playback/audio.h \ + io/config.h \ + dialogs/newsequencedialog.h \ + ui/viewerwidget.h \ + ui/viewercontainer.h \ + dialogs/exportdialog.h \ + ui/collapsiblewidget.h \ + panels/panels.h \ + playback/cacher.h \ + io/exportthread.h \ + ui/timelinetools.h \ + ui/timelineheader.h \ + io/previewgenerator.h \ + ui/labelslider.h \ + dialogs/preferencesdialog.h \ + ui/audiomonitor.h \ + project/undo.h \ + ui/scrollarea.h \ + ui/comboboxex.h \ + ui/colorbutton.h \ + dialogs/replaceclipmediadialog.h \ + ui/fontcombobox.h \ + ui/checkboxex.h \ + ui/keyframeview.h \ + ui/texteditex.h \ + dialogs/demonotice.h \ + project/marker.h \ + project/selection.h \ + dialogs/speeddialog.h \ + dialogs/mediapropertiesdialog.h \ + io/crc32.h \ + dialogs/loaddialog.h \ + debug.h \ + io/path.h \ + effects/internal/transformeffect.h \ + effects/internal/solideffect.h \ + effects/internal/texteffect.h \ + effects/internal/timecodeeffect.h \ + effects/internal/audionoiseeffect.h \ + effects/internal/paneffect.h \ + effects/internal/toneeffect.h \ + effects/internal/volumeeffect.h \ + effects/internal/shakeeffect.h \ + effects/internal/linearfadetransition.h \ + effects/internal/crossdissolvetransition.h \ + effects/internal/exponentialfadetransition.h \ + effects/internal/logarithmicfadetransition.h \ + effects/internal/cornerpineffect.h \ + io/math.h \ + io/qpainterwrapper.h \ + project/effect.h \ + project/transition.h \ + project/effectrow.h \ + project/effectfield.h \ + effects/internal/cubetransition.h \ + project/effectgizmo.h \ + io/clipboard.h \ + effects/internal/maskeffect.h + +FORMS += \ + mainwindow.ui \ + panels/project.ui \ + panels/effectcontrols.ui \ + panels/viewer.ui \ + panels/timeline.ui \ + dialogs/aboutdialog.ui \ + dialogs/newsequencedialog.ui \ + dialogs/exportdialog.ui \ + dialogs/preferencesdialog.ui \ + dialogs/demonotice.ui + +win32 { + RC_FILE = icons/resources.rc + LIBS += -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample -lopengl32 +} + +mac { + LIBS += -L/usr/local/lib -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample + ICON = icons/olive.icns + INCLUDEPATH = /usr/local/include +} + +linux { + LIBS += -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample +} + +RESOURCES += \ + icons/icons.qrc diff --git a/playback/playback.cpp b/playback/playback.cpp index 638a4ec43..c8295e0ba 100644 --- a/playback/playback.cpp +++ b/playback/playback.cpp @@ -111,6 +111,10 @@ void cache_clip(Clip* clip, long playhead, bool reset, bool scrubbing, QVectorget_timeline_in_with_transition()+c->get_clip_in_with_transition())/(double)c->sequence->frame_rate); +} + void get_clip_frame(Clip* c, long playhead) { if (c->finished_opening) { MediaStream* ms = static_cast(c->media)->get_stream_from_file_index(c->track < 0, c->media_stream); @@ -220,8 +224,29 @@ void get_clip_frame(Clip* c, long playhead) { if (target_frame != NULL) { // add gate if this is the same frame - glPixelStorei(GL_UNPACK_ROW_LENGTH, target_frame->linesize[0]/4); - c->texture->setData(0, QOpenGLTexture::RGBA, QOpenGLTexture::UInt8, target_frame->data[0]); + glPixelStorei(GL_UNPACK_ROW_LENGTH, target_frame->linesize[0]/4); + + bool copied = false; + uint8_t* data = target_frame->data[0]; + int frame_size; + + for (int i=0;ieffects.size();i++) { + Effect* e = c->effects.at(i); + if (e->enable_image) { + if (!copied) { + frame_size = target_frame->linesize[0]*target_frame->height; + data = new uint8_t[frame_size]; + memcpy(data, target_frame->data[0], frame_size); + copied = true; + } + e->process_image(get_timecode(c, playhead), data, frame_size); + } + } + + c->texture->setData(0, QOpenGLTexture::RGBA, QOpenGLTexture::UInt8, data); + + if (copied) delete [] data; + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); } diff --git a/playback/playback.h b/playback/playback.h index af1d742f6..ba7062a2a 100644 --- a/playback/playback.h +++ b/playback/playback.h @@ -20,6 +20,7 @@ void cache_video_worker(Clip* c, long playhead); void handle_media(Sequence* sequence, long playhead, bool multithreaded); void reset_cache(Clip* c, long target_frame); void get_clip_frame(Clip* c, long playhead); +double get_timecode(Clip* c, long playhead); long playhead_to_clip_frame(Clip* c, long playhead); double playhead_to_clip_seconds(Clip* c, long playhead); diff --git a/project/effect.cpp b/project/effect.cpp index 8255631ce..7bee62a95 100644 --- a/project/effect.cpp +++ b/project/effect.cpp @@ -27,6 +27,7 @@ #include "effects/internal/paneffect.h" #include "effects/internal/shakeeffect.h" #include "effects/internal/cornerpineffect.h" +#include "effects/internal/maskeffect.h" #include #include @@ -59,6 +60,7 @@ Effect* create_effect(Clip* c, const EffectMeta* em) { case EFFECT_INTERNAL_TONE: return new ToneEffect(c, em); case EFFECT_INTERNAL_SHAKE: return new ShakeEffect(c, em); case EFFECT_INTERNAL_CORNERPIN: return new CornerPinEffect(c, em); + case EFFECT_INTERNAL_MASK: return new MaskEffect(c, em); } } else { dout << "[ERROR] Invalid effect data"; @@ -107,8 +109,15 @@ void load_internal_effects() { effects.append(em); em.name = "Corner Pin"; - em.category = "Distort"; em.internal = EFFECT_INTERNAL_CORNERPIN; + effects.append(em); + + em.name = "Mask"; + em.internal = EFFECT_INTERNAL_MASK; + effects.append(em); + + em.name = "Shake"; + em.internal = EFFECT_INTERNAL_SHAKE; effects.append(em); em.name = "Text"; @@ -117,20 +126,13 @@ void load_internal_effects() { effects.append(em); em.name = "Timecode"; - em.category = "Render"; em.internal = EFFECT_INTERNAL_TIMECODE; effects.append(em); - em.name = "Solid"; - em.category = "Render"; + em.name = "Solid"; em.internal = EFFECT_INTERNAL_SOLID; effects.append(em); - em.name = "Shake"; - em.category = "Distort"; - em.internal = EFFECT_INTERNAL_SHAKE; - effects.append(em); - // internal transitions em.type = EFFECT_TYPE_TRANSITION; em.category = ""; @@ -230,6 +232,7 @@ Effect::Effect(Clip* c, const EffectMeta *em) : enable_shader(false), enable_coords(false), enable_superimpose(false), + enable_image(false), glslProgram(NULL), texture(NULL), isOpen(false), @@ -767,9 +770,11 @@ void Effect::startEffect() { void Effect::endEffect() { if (bound) glslProgram->release(); - bound = false; + bound = false; } +void Effect::process_image(double, uint8_t *, int) {} + Effect* Effect::copy(Clip* c) { Effect* copy = create_effect(c, meta); copy->set_enabled(is_enabled()); diff --git a/project/effect.h b/project/effect.h index 4f15a4123..08fcc13b4 100644 --- a/project/effect.h +++ b/project/effect.h @@ -62,7 +62,7 @@ extern QMutex effects_loaded; #define EFFECT_INTERNAL_TONE 6 #define EFFECT_INTERNAL_SHAKE 7 #define EFFECT_INTERNAL_TIMECODE 8 - +#define EFFECT_INTERNAL_MASK 9 #define EFFECT_INTERNAL_CORNERPIN 12 @@ -147,12 +147,14 @@ public: bool enable_shader; bool enable_coords; bool enable_superimpose; + bool enable_image; int getIterations(); void setIterations(int i); const char* ffmpeg_filter; + virtual void process_image(double timecode, uint8_t* data, int size); virtual void process_shader(double timecode, GLTextureCoords&); virtual void process_coords(double timecode, GLTextureCoords& coords, int data); virtual GLuint process_superimpose(double timecode); diff --git a/project/effectgizmo.h b/project/effectgizmo.h index befe4bcd3..ec10f4b09 100644 --- a/project/effectgizmo.h +++ b/project/effectgizmo.h @@ -3,8 +3,10 @@ #define GIZMO_TYPE_DOT 0 #define GIZMO_TYPE_POLY 1 +#define GIZMO_TYPE_TARGET 2 #define GIZMO_DOT_SIZE 2.5F +#define GIZMO_TARGET_SIZE 5.0F #include #include diff --git a/ui/viewerwidget.cpp b/ui/viewerwidget.cpp index 53c3c47ee..81e22ae7e 100644 --- a/ui/viewerwidget.cpp +++ b/ui/viewerwidget.cpp @@ -153,15 +153,12 @@ void ViewerWidget::seek_from_click(int x) { viewer->seek(getFrameFromScreenPoint(waveform_zoom, x+waveform_scroll)); } -double get_timecode(Clip* c, long playhead) { - return ((double)(playhead-c->get_timeline_in_with_transition()+c->get_clip_in_with_transition())/(double)c->sequence->frame_rate); -} - EffectGizmo* ViewerWidget::get_gizmo_from_mouse(int x, int y) { if (gizmos != NULL) { double multiplier = (double) viewer->seq->width / (double) width(); QPoint mouse_pos(qRound(x*multiplier), qRound(y*multiplier)); int dot_size = 2 * qRound(GIZMO_DOT_SIZE * multiplier); + int target_size = 2 * qRound(GIZMO_TARGET_SIZE * multiplier); for (int i=0;igizmo_count();i++) { EffectGizmo* g = gizmos->gizmo(i); @@ -179,6 +176,14 @@ EffectGizmo* ViewerWidget::get_gizmo_from_mouse(int x, int y) { return g; } break; + case GIZMO_TYPE_TARGET: + if (mouse_pos.x() > g->screen_pos[0].x() - target_size + && mouse_pos.y() > g->screen_pos[0].y() - target_size + && mouse_pos.x() < g->screen_pos[0].x() + target_size + && mouse_pos.y() < g->screen_pos[0].y() + target_size) { + return g; + } + break; } } @@ -813,6 +818,7 @@ void ViewerWidget::paintGL() { glGetFloatv(GL_CURRENT_COLOR, color); float dot_size = GIZMO_DOT_SIZE / width() * viewer->seq->width; + float target_size = GIZMO_TARGET_SIZE / width() * viewer->seq->width; glPushMatrix(); glLoadIdentity(); @@ -840,6 +846,14 @@ void ViewerWidget::paintGL() { glVertex3f(g->screen_pos[0].x(), g->screen_pos[0].y(), gizmo_z); glEnd(); break; + case GIZMO_TYPE_DOT: // draw target + glBegin(GL_QUADS); + glVertex3f(g->screen_pos[0].x()-target_size, g->screen_pos[0].y()-target_size, gizmo_z); + glVertex3f(g->screen_pos[0].x()+target_size, g->screen_pos[0].y()-target_size, gizmo_z); + glVertex3f(g->screen_pos[0].x()+target_size, g->screen_pos[0].y()+target_size, gizmo_z); + glVertex3f(g->screen_pos[0].x()-target_size, g->screen_pos[0].y()+target_size, gizmo_z); + glEnd(); + break; } } glPopMatrix(); From 3e57e06542774ed6758e89951d86765f4f55d028 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sun, 9 Dec 2018 15:56:47 +1100 Subject: [PATCH 11/40] added gizmo for anchor point --- effects/internal/transformeffect.cpp | 5 +++++ effects/internal/transformeffect.h | 1 + ui/viewerwidget.cpp | 17 +++++++++++++++-- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/effects/internal/transformeffect.cpp b/effects/internal/transformeffect.cpp index b83aef23f..5ece40f36 100644 --- a/effects/internal/transformeffect.cpp +++ b/effects/internal/transformeffect.cpp @@ -98,6 +98,11 @@ TransformEffect::TransformEffect(Clip* c, const EffectMeta* em) : Effect(c, em) right_center_gizmo->set_cursor(Qt::SizeHorCursor); right_center_gizmo->x_field = scale_x; + anchor_gizmo = add_gizmo(GIZMO_TYPE_TARGET); + anchor_gizmo->set_cursor(Qt::SizeAllCursor); + anchor_gizmo->x_field = anchor_x_box; + anchor_gizmo->y_field = anchor_y_box; + rotate_gizmo = add_gizmo(GIZMO_TYPE_DOT); rotate_gizmo->color = Qt::green; rotate_gizmo->set_cursor(Qt::SizeAllCursor); diff --git a/effects/internal/transformeffect.h b/effects/internal/transformeffect.h index 7fc635ad2..c198ffb0a 100644 --- a/effects/internal/transformeffect.h +++ b/effects/internal/transformeffect.h @@ -33,6 +33,7 @@ private: EffectGizmo* bottom_right_gizmo; EffectGizmo* left_center_gizmo; EffectGizmo* right_center_gizmo; + EffectGizmo* anchor_gizmo; EffectGizmo* rotate_gizmo; EffectGizmo* rect_gizmo; diff --git a/ui/viewerwidget.cpp b/ui/viewerwidget.cpp index 81e22ae7e..3fcc8dd4d 100644 --- a/ui/viewerwidget.cpp +++ b/ui/viewerwidget.cpp @@ -846,12 +846,25 @@ void ViewerWidget::paintGL() { glVertex3f(g->screen_pos[0].x(), g->screen_pos[0].y(), gizmo_z); glEnd(); break; - case GIZMO_TYPE_DOT: // draw target - glBegin(GL_QUADS); + case GIZMO_TYPE_TARGET: // draw target + glBegin(GL_LINES); glVertex3f(g->screen_pos[0].x()-target_size, g->screen_pos[0].y()-target_size, gizmo_z); glVertex3f(g->screen_pos[0].x()+target_size, g->screen_pos[0].y()-target_size, gizmo_z); + + glVertex3f(g->screen_pos[0].x()+target_size, g->screen_pos[0].y()-target_size, gizmo_z); + glVertex3f(g->screen_pos[0].x()+target_size, g->screen_pos[0].y()+target_size, gizmo_z); + glVertex3f(g->screen_pos[0].x()+target_size, g->screen_pos[0].y()+target_size, gizmo_z); glVertex3f(g->screen_pos[0].x()-target_size, g->screen_pos[0].y()+target_size, gizmo_z); + + glVertex3f(g->screen_pos[0].x()-target_size, g->screen_pos[0].y()+target_size, gizmo_z); + glVertex3f(g->screen_pos[0].x()-target_size, g->screen_pos[0].y()-target_size, gizmo_z); + + glVertex3f(g->screen_pos[0].x()-target_size, g->screen_pos[0].y(), gizmo_z); + glVertex3f(g->screen_pos[0].x()+target_size, g->screen_pos[0].y(), gizmo_z); + + glVertex3f(g->screen_pos[0].x(), g->screen_pos[0].y()-target_size, gizmo_z); + glVertex3f(g->screen_pos[0].x(), g->screen_pos[0].y()+target_size, gizmo_z); glEnd(); break; } From ac1cb9d14fc1e4d41cccfc13605596798fe71adb Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 13 Dec 2018 14:29:09 +1100 Subject: [PATCH 12/40] massive rewrite of project data system --- dialogs/loaddialog.cpp | 23 +- dialogs/loaddialog.h | 11 +- dialogs/mediapropertiesdialog.cpp | 30 +- dialogs/mediapropertiesdialog.h | 9 +- dialogs/newsequencedialog.h | 4 +- dialogs/replaceclipmediadialog.cpp | 69 +- dialogs/replaceclipmediadialog.h | 13 +- dialogs/speeddialog.cpp | 17 +- effects/internal/transformeffect.cpp | 2 +- icons/error.png | Bin 0 -> 11806 bytes icons/icons.qrc | 1 + io/loadthread.cpp | 639 ++++++++++++++ io/loadthread.h | 43 + io/previewgenerator.cpp | 116 +-- io/previewgenerator.h | 15 +- mainwindow.cpp | 13 +- olive.pro | 14 +- panels/panels.cpp | 2 + panels/project.cpp | 1183 +++++--------------------- panels/project.h | 79 +- panels/project.ui | 40 +- panels/timeline.cpp | 36 +- panels/timeline.h | 10 +- panels/viewer.cpp | 154 ++-- panels/viewer.h | 4 +- playback/cacher.cpp | 795 +++++++++-------- playback/playback.cpp | 91 +- playback/playback.h | 1 + project/clip.cpp | 105 ++- project/clip.h | 10 +- io/media.cpp => project/footage.cpp | 15 +- io/media.h => project/footage.h | 26 +- project/media.cpp | 312 +++++++ project/media.h | 67 ++ project/projectmodel.cpp | 146 ++++ project/projectmodel.h | 39 + project/undo.cpp | 166 ++-- project/undo.h | 67 +- ui/sourcetable.cpp | 140 +-- ui/sourcetable.h | 13 +- ui/timelinewidget.cpp | 72 +- ui/timelinewidget.h | 4 +- ui/viewerwidget.cpp | 120 ++- ui/viewerwidget.h | 4 +- 44 files changed, 2562 insertions(+), 2158 deletions(-) create mode 100644 icons/error.png create mode 100644 io/loadthread.cpp create mode 100644 io/loadthread.h rename io/media.cpp => project/footage.cpp (67%) rename io/media.h => project/footage.h (66%) create mode 100644 project/media.cpp create mode 100644 project/media.h create mode 100644 project/projectmodel.cpp create mode 100644 project/projectmodel.h diff --git a/dialogs/loaddialog.cpp b/dialogs/loaddialog.cpp index 47a641810..3a065241e 100644 --- a/dialogs/loaddialog.cpp +++ b/dialogs/loaddialog.cpp @@ -6,25 +6,40 @@ #include #include -LoadDialog::LoadDialog(QWidget *parent) : QDialog(parent) { +#include "panels/panels.h" +#include "panels/project.h" +#include "io/loadthread.h" +#include "playback/playback.h" +#include "ui/sourcetable.h" + +LoadDialog::LoadDialog(QWidget *parent, bool autorecovery) : QDialog(parent) { setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); QVBoxLayout* layout = new QVBoxLayout(); setLayout(layout); - layout->addWidget(new QLabel("Loading ''...")); + layout->addWidget(new QLabel("Loading '" + project_url.mid(project_url.lastIndexOf('/')+1) + "'...")); bar = new QProgressBar(); bar->setValue(50); layout->addWidget(bar); - QPushButton* cancel_button = new QPushButton("Cancel"); + cancel_button = new QPushButton("Cancel"); connect(cancel_button, SIGNAL(clicked(bool)), this, SLOT(reject())); - QHBoxLayout* hboxLayout = new QHBoxLayout(); + hboxLayout = new QHBoxLayout(); hboxLayout->addStretch(); hboxLayout->addWidget(cancel_button); hboxLayout->addStretch(); layout->addLayout(hboxLayout); + + LoadThread* lt = new LoadThread(this, autorecovery); + QObject::connect(lt, SIGNAL(success()), this, SLOT(thread_done())); + lt->start(); +} + +void LoadDialog::thread_done() { +// project_model.update_data(); + close(); } diff --git a/dialogs/loaddialog.h b/dialogs/loaddialog.h index 7f6b0a77f..2f646fc91 100644 --- a/dialogs/loaddialog.h +++ b/dialogs/loaddialog.h @@ -4,13 +4,22 @@ #include class QProgressBar; +class Sequence; +class Media; +class Footage; +class QHBoxLayout; class LoadDialog : public QDialog { + Q_OBJECT public: - LoadDialog(QWidget* parent = 0); + LoadDialog(QWidget* parent, bool autorecovery); +private slots: + void thread_done(); private: QProgressBar* bar; + QPushButton* cancel_button; + QHBoxLayout* hboxLayout; }; #endif // LOADDIALOG_H diff --git a/dialogs/mediapropertiesdialog.cpp b/dialogs/mediapropertiesdialog.cpp index 946b83395..6f4fae51e 100644 --- a/dialogs/mediapropertiesdialog.cpp +++ b/dialogs/mediapropertiesdialog.cpp @@ -7,34 +7,35 @@ #include #include -#include "io/media.h" +#include "project/footage.h" +#include "project/media.h" #include "panels/project.h" #include "project/undo.h" -MediaPropertiesDialog::MediaPropertiesDialog(QWidget *parent, QTreeWidgetItem *i, Media *m) : +MediaPropertiesDialog::MediaPropertiesDialog(QWidget *parent, Media *i) : QDialog(parent), - item(i), - media(m) + item(i) { setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); QGridLayout* grid = new QGridLayout(); setLayout(grid); - if (m->video_tracks.size() > 0) { + Footage* f = item->to_footage(); + if (f->video_tracks.size() > 0) { interlacing_box = new QComboBox(); - interlacing_box->addItem("Auto (" + get_interlacing_name(media->video_tracks.at(0)->video_auto_interlacing) + ")"); + interlacing_box->addItem("Auto (" + get_interlacing_name(f->video_tracks.at(0)->video_auto_interlacing) + ")"); interlacing_box->addItem(get_interlacing_name(VIDEO_PROGRESSIVE)); interlacing_box->addItem(get_interlacing_name(VIDEO_TOP_FIELD_FIRST)); interlacing_box->addItem(get_interlacing_name(VIDEO_BOTTOM_FIELD_FIRST)); - interlacing_box->setCurrentIndex((media->video_tracks.at(0)->video_auto_interlacing == media->video_tracks.at(0)->video_interlacing) ? 0 : media->video_tracks.at(0)->video_interlacing + 1); + interlacing_box->setCurrentIndex((f->video_tracks.at(0)->video_auto_interlacing == f->video_tracks.at(0)->video_interlacing) ? 0 : f->video_tracks.at(0)->video_interlacing + 1); grid->addWidget(new QLabel("Interlacing:"), 0, 0); grid->addWidget(interlacing_box, 0, 1); } - name_box = new QLineEdit(m->name); + name_box = new QLineEdit(item->get_name()); grid->addWidget(new QLabel("Name:"), 1, 0); grid->addWidget(name_box, 1, 1); @@ -50,22 +51,19 @@ void MediaPropertiesDialog::accept() { ComboAction* ca = new ComboAction(); //set interlacing + Footage* f = item->to_footage(); if (interlacing_box->currentIndex() > 0) { - ca->append(new SetInt(&media->video_tracks.at(0)->video_interlacing, interlacing_box->currentIndex() - 1)); + ca->append(new SetInt(&f->video_tracks.at(0)->video_interlacing, interlacing_box->currentIndex() - 1)); } else { - ca->append(new SetInt(&media->video_tracks.at(0)->video_interlacing, media->video_tracks.at(0)->video_auto_interlacing)); + ca->append(new SetInt(&f->video_tracks.at(0)->video_interlacing, f->video_tracks.at(0)->video_auto_interlacing)); } //set name - MediaRename* mr = new MediaRename(); - mr->from = media->name; - mr->item = item; - mr->to = name_box->text(); - item->setText(0, name_box->text()); + MediaRename* mr = new MediaRename(item, name_box->text()); ca->append(mr); ca->appendPost(new CloseAllClipsCommand()); - ca->appendPost(new UpdateFootageTooltip(item, media)); + ca->appendPost(new UpdateFootageTooltip(item)); undo_stack.push(ca); diff --git a/dialogs/mediapropertiesdialog.h b/dialogs/mediapropertiesdialog.h index de5c5f92b..13f96c3d1 100644 --- a/dialogs/mediapropertiesdialog.h +++ b/dialogs/mediapropertiesdialog.h @@ -3,20 +3,19 @@ #include -struct Media; +struct Footage; class QComboBox; class QLineEdit; -class QTreeWidgetItem; +class Media; class MediaPropertiesDialog : public QDialog { Q_OBJECT public: - MediaPropertiesDialog(QWidget *parent, QTreeWidgetItem* i, Media *m); + MediaPropertiesDialog(QWidget *parent, Media* i); private: QComboBox* interlacing_box; QLineEdit* name_box; - QTreeWidgetItem* item; - Media* media; + Media* item; private slots: void accept(); }; diff --git a/dialogs/newsequencedialog.h b/dialogs/newsequencedialog.h index af219bb83..d6b9b2a2d 100644 --- a/dialogs/newsequencedialog.h +++ b/dialogs/newsequencedialog.h @@ -4,7 +4,7 @@ #include class Project; -class QTreeWidgetItem; +class Media; struct Sequence; namespace Ui { @@ -19,7 +19,7 @@ public: explicit NewSequenceDialog(QWidget *parent = 0); ~NewSequenceDialog(); Sequence* existing_sequence; - QTreeWidgetItem* existing_item; + Media* existing_item; void set_sequence_name(const QString& s); protected: diff --git a/dialogs/replaceclipmediadialog.cpp b/dialogs/replaceclipmediadialog.cpp index 45092104f..e8732164f 100644 --- a/dialogs/replaceclipmediadialog.cpp +++ b/dialogs/replaceclipmediadialog.cpp @@ -8,17 +8,18 @@ #include "project/clip.h" #include "playback/playback.h" #include "playback/cacher.h" -#include "io/media.h" +#include "project/footage.h" #include "project/undo.h" +#include "project/media.h" #include -#include +#include #include #include #include #include -ReplaceClipMediaDialog::ReplaceClipMediaDialog(QWidget *parent, SourceTable *table, QTreeWidgetItem *old_media) : +ReplaceClipMediaDialog::ReplaceClipMediaDialog(QWidget *parent, SourceTable *table, Media *old_media) : QDialog(parent), source_table(table), media(old_media) @@ -29,12 +30,11 @@ ReplaceClipMediaDialog::ReplaceClipMediaDialog(QWidget *parent, SourceTable *tab layout->addWidget(new QLabel("Select which media you want to replace this media's clips with:")); - tree = new QTreeWidget(); - tree->setHeaderHidden(true); + tree = new QTreeView(); layout->addWidget(tree); - use_same_media_in_points = new QCheckBox("Keep the same media in points"); + use_same_media_in_points = new QCheckBox("Keep the same media in-points"); use_same_media_in_points->setChecked(true); layout->addWidget(use_same_media_in_points); @@ -56,36 +56,34 @@ ReplaceClipMediaDialog::ReplaceClipMediaDialog(QWidget *parent, SourceTable *tab setLayout(layout); - copy_tree(NULL, NULL); + tree->setModel(&project_model); + + //copy_tree(NULL, NULL); } void ReplaceClipMediaDialog::replace() { - if (tree->selectedItems().size() != 1) { + QModelIndexList selected_items = tree->selectionModel()->selectedRows(); + if (selected_items.size() != 1) { QMessageBox::critical(this, "No media selected", "Please select a media to replace with or click 'Cancel'.", QMessageBox::Ok); } else { - QTreeWidgetItem* selected_item = tree->selectedItems().at(0); - QTreeWidgetItem* new_item = reinterpret_cast(selected_item->data(0, Qt::UserRole + 1).value()); + Media* new_item = static_cast(selected_items.at(0).internalPointer()); if (media == new_item) { QMessageBox::critical(this, "Same media selected", "You selected the same media that you're replacing. Please select a different one or click 'Cancel'.", QMessageBox::Ok); - } else if (get_type_from_tree(new_item) == MEDIA_TYPE_FOLDER) { + } else if (new_item->get_type() == MEDIA_TYPE_FOLDER) { QMessageBox::critical(this, "Folder selected", "You cannot replace footage with a folder.", QMessageBox::Ok); } else { - if (get_type_from_tree(new_item) == MEDIA_TYPE_SEQUENCE && sequence == get_sequence_from_tree(new_item)) { + if (new_item->get_type() == MEDIA_TYPE_SEQUENCE && sequence == new_item->to_sequence()) { QMessageBox::critical(this, "Active sequence selected", "You cannot insert a sequence into itself.", QMessageBox::Ok); - } else { - void* old_media = get_media_from_tree(media); - + } else { ReplaceClipMediaCommand* rcmc = new ReplaceClipMediaCommand( - old_media, - get_media_from_tree(new_item), - get_type_from_tree(media), - get_type_from_tree(new_item), + media, + new_item, use_same_media_in_points->isChecked() ); for (int i=0;iclips.size();i++) { Clip* c = sequence->clips.at(i); - if (c != NULL && c->media == old_media) { + if (c != NULL && c->media == media) { rcmc->clips.append(c); } } @@ -98,34 +96,3 @@ void ReplaceClipMediaDialog::replace() { } } } - -void ReplaceClipMediaDialog::copy_tree(QTreeWidgetItem* parent, QTreeWidgetItem* target) { - QVector items; - - if (parent == NULL) { - for (int i=0;itopLevelItemCount();i++) { - items.append(source_table->topLevelItem(i)); - } - } else { - for (int i=0;ichildCount();i++) { - items.append(parent->child(i)); - } - } - - for (int i=0;isetText(0, original->text(0)); - item->setData(0, Qt::UserRole + 1, reinterpret_cast(original)); - - if (target == NULL) { - tree->addTopLevelItem(item); - } else { - target->addChild(item); - } - - if (original->childCount() > 0) { - copy_tree(original, item); - } - } -} diff --git a/dialogs/replaceclipmediadialog.h b/dialogs/replaceclipmediadialog.h index 8bd60e3cd..15498336b 100644 --- a/dialogs/replaceclipmediadialog.h +++ b/dialogs/replaceclipmediadialog.h @@ -4,22 +4,21 @@ #include class SourceTable; -class QTreeWidget; -class QTreeWidgetItem; +class QTreeView; +class Media; class QCheckBox; class ReplaceClipMediaDialog : public QDialog { Q_OBJECT public: - ReplaceClipMediaDialog(QWidget* parent, SourceTable* table, QTreeWidgetItem *old_media); + ReplaceClipMediaDialog(QWidget* parent, SourceTable* table, Media *old_media); private slots: void replace(); private: SourceTable* source_table; - QTreeWidget* tree; - QTreeWidgetItem* media; - QCheckBox* use_same_media_in_points; - void copy_tree(QTreeWidgetItem* parent, QTreeWidgetItem *target); + QTreeView* tree; + Media* media; + QCheckBox* use_same_media_in_points; }; #endif // REPLACECLIPMEDIADIALOG_H diff --git a/dialogs/speeddialog.cpp b/dialogs/speeddialog.cpp index 6713bad1b..0efcb45d1 100644 --- a/dialogs/speeddialog.cpp +++ b/dialogs/speeddialog.cpp @@ -10,12 +10,13 @@ #include "ui/labelslider.h" #include "project/clip.h" #include "project/sequence.h" -#include "io/media.h" +#include "project/footage.h" #include "playback/playback.h" #include "panels/panels.h" #include "panels/timeline.h" #include "project/undo.h" #include "project/effect.h" +#include "project/media.h" SpeedDialog::SpeedDialog(QWidget *parent) : QDialog(parent) { QVBoxLayout* main_layout = new QVBoxLayout(); @@ -83,14 +84,12 @@ void SpeedDialog::run() { clip_percent = c->speed; if (c->track < 0) { bool process_video = true; - if (c->media_type == MEDIA_TYPE_FOOTAGE) { - Media* m = static_cast(c->media); - if (m != NULL) { - MediaStream* ms = m->get_stream_from_file_index(true, c->media_stream); - if (ms != NULL && ms->infinite_length) { - process_video = false; - } - } + if (c->media != NULL && c->media->get_type() == MEDIA_TYPE_FOOTAGE) { + Footage* m = c->media->to_footage(); + FootageStream* ms = m->get_stream_from_file_index(true, c->media_stream); + if (ms != NULL && ms->infinite_length) { + process_video = false; + } } if (process_video) { diff --git a/effects/internal/transformeffect.cpp b/effects/internal/transformeffect.cpp index b83aef23f..4d5e4b3b2 100644 --- a/effects/internal/transformeffect.cpp +++ b/effects/internal/transformeffect.cpp @@ -12,7 +12,7 @@ #include "ui/collapsiblewidget.h" #include "project/clip.h" #include "project/sequence.h" -#include "io/media.h" +#include "project/footage.h" #include "io/math.h" #include "ui/labelslider.h" #include "ui/comboboxex.h" diff --git a/icons/error.png b/icons/error.png new file mode 100644 index 0000000000000000000000000000000000000000..34eb2de38fe4820ea0e4b779544b96fa2d042852 GIT binary patch literal 11806 zcmWk!1yoeu6Mh@O(j7`IOGtwtT}uhlA}J|IiL`X>5(-F*q=b|p(nt!sAc6=;mk8`n zx|eSF^FQa^H|N~*-pri2bLM{Eof~VQr$J6~mjnO+@<*C#hPM*+{{bVseScSc)_*IA zJvGgJ0DzSC{{sYMe_*H@@Bqbj^toYn|{@D1_ zZ<&j)9T&Y%UGqJv>yH=5wVUtF%zV?K2--uh_Px{9MU~sL&sfW>x!yb{0Sh!Ly;F6ou4OPzY4=k{p*U<#J~^@Fa~#$Iy&aV2`@CH5Ttydr3ZPCv0V1da&^W13Rs4GfjDLkm z+FN+Z<3uOq;e(hw@Q4WVL9RA*v+d<@Juh60AURz<=JSDF8`pxvav+=$y_bol1?vCA(XB|y(&jhAp+Tn z%;=~J$?iZEj?HQ@M1RBn5>8)QmN!2#nB133q69ZHsWF12tE18~-u)ANyLBXfV14hL zK86i6@*T)q!YtxwFJzJc^J?x@?p9*uo9o>lo!03CPOQR)%`hBc2O&-@G=xQsSqp7! z@-LYCu}J>1;tKMRmP!FuNEFKp1^Qx(iV>43lbs94bUj|xqu)HM~{)Rm1!X+UwF>G+02H~rNwnD7lG*aXp zY~n93h8N$TdWpa+1X{vunNb)ZWvy@2m~OrWr@eRM0RC>3T? zJdZ0VO#o@lV;kujwxR}4ED4*V7bM|$#bChmqd#uTio~|!p?~p>A2565rKQi{4spj= z(=n#;4IFAkahK6P;)`+4;W}qRT@BBIoO@W(> zHw%|RdA`35qph`SwLyxzPK4+%YgTH9R zrNVmFEC!`iOB9ESM7RUEET=Cz4ke{gt;U;n5n?qQuD+zL`AZxoog359 zw+NPFqXIM``gxy_8Db@bqu!Jspc6WqB2VyM<;d0g9#9A|HUnwY|2~)z*Pd+4x4%Hz z>@c6h$lR<6AgA5om4r@IBe|(-LV)|dLUTQ^Ds*vmFe}QS79E(hOtaNokZryz0txd& zujT?(kQxPXY(Kb->`qYawRPx~A#rksgc+7L*d5bj1aLVd#B}JWy@PHM;!__jq*jk1 zwOQ$C?t#UhcaEdp6=b_y1(M+SkNmJo>{wIet3a2ERSlL^i|~}?YPIC1zZ$WIVkdK5 zh)A3Sh))^ohq(J*O$rjgb7X8Hh$RMwwDU~mML_wtikPu`sqSoRtPjIr*rQhL82LBS z5N13=g4g0m0CM?4>Y3Rh4*tnf8Qlg)XgU~d7N^+|;SUU5 z%2Ln4a9(l(a6Woag8<5lm_<(L>NRvCZ~t`Qxt<1UN-sF$M26$+mc%&4_S;9dkn(>N-@S{-bzx*C)0`*@-s!vHg0%b}~?0 zM90^dor7z?*!yVQQRo|zX)WA2OFECW9+nu6KCYHj|AH89Gv8QjKZdxWldwpd_buwk&N+NBYuVnWj3H3 zigYC6W{FoiOD5ul`%*srA$6#p>De#yFggc|FzfkzhcnmpRnrvta#S@A&qN%+XFf&T zp{7Oz3K3r>i3gk2H$}i|O(I@6zVR@jB^vob_3r?9VRbE|y<){x6Ps$hw*EE!hKCcG zL6T{PVy%-F{X#vm3i_sn{Vvb@${2BW@WL@aPi`%k8Re?{>UI53dtlkN?aP9Cgtb;B;C#~VnMdqgb7?HrzNoogyVsZfp>_rhpW!Pg0gryBZRUS- zE-uW0yNhT|97q!xx)FG+ph9!r6V^xy_k(D?eZ8>9LAr>IZ(IG5lAxxRBlTsD>I7&2;Bi4W#{PhKW z=mh`91ixAdxY`hHrizc0HnAVG-wJ%m0BS}om7`W;h)-8j(o~^aT!l_iR2N7nUliiw zV^AsA1Q<8?#$$KmKcBM@=70;|9ezvYe1b3m*zjSf;JM<{Y$&wAq};Z^ zt{NRR*Y!-kB?(X~TSJc7uiJeV#{L5#WTRsmK>>87&CkVv>ny3Q>5rEYF1vaH*n6PA zFeDeUB~IS_%~jq9cyfcj+nM5ug%FF4W%ocAxO8>(ep?Ny#f0=Ih1&9ZqS|iJ<ma*cTmbz{q7kA1XuiD66^nHfp_h$p;38>|ZBT%F4&z9N+%2)i5UNwbRl! zO+&zYU$WDp)5^_`)l*J6KW*bnxMXKp*ibmZh&3VzEw%xA&s_CQY;Jae1B)WO$+enL zH0|tlI5Hpg(B0umXNQtpI?{@hl4fS-_dX6qUr4!kH|Dr^-3ALlx_MGDFB}OEw4Wl> zsCdDd1T;OrG9L)f?teExSI5doC?(B2gxoIa`NM}b+od_8b%FJFXR$!fZyBNk%#~2o z?x$Xd1YJ7cyA}kZT97Y*Q+3~~hwxW7*hl~|?L(^B{I4bW8rzf5?0w=MucueaC_zSH_ubs-c8#G%uO#JCArxG%)b>a5-KP76&fw+aoG9-eOV(E-;XS{Lpdr1rxOGil`+y{ z2pPHeVUWdq)CStISRnn7>b3LN*-Q^fufHAGL7sCB4X=sHCO@j_@?}7%=J=w(X(=4<}BIP=^;5HB_Chm-d0H(a!{5KUsg*_zMQBl zJ-T5;1}}-b_PzG_hhFlR<&B!qRWnrSx&NFc9Fe$~9hR8MYHdnvR=Xso8R+7^SlW+q zIt$b2rF@iSiA!4^8d`KNLcgnIAnfV!9NccQH_~>UERvLs6MB7H6&m&WyLeStdSj0S zCsx~fCY8h@to-L=Ysv7m+)Q^m`Cdc0d(&Ug_>S+si%9kg<|(tUdi`mD?B8T4kGzVW62;#Ul1%aMx=bW zm$$UA3>LElsi%PRt!(d|`p;+aU1Tu^y`r;FyfDAjJ}gfB0XH<*(y6qZHs2#D79k|( zha{uqY|w-sWu8t)=|UgEl3EBlNmc)_!h&tUz9AoqFAHKB?JALZMNZpxnk^jl8P(3| zLlZa`LYiKfuS?VVMW*Z5)Uu$)5v*lf<@1}Wwje^KmSJWoei|}Lfj$R^C3$~b>Xpm8 zSf&|vR^z$_&QnR^Z{*BW>#o$U$kax+3YTt0X3s!nUYQ0tz?JR(O?8@gWZM2e>BLSe zw3AiLeJU%X7jaErE`2sLXi`j2Km6qOuI^a~JqTgnOeYSDgUb08b4$U-37oXBNomU> z!hX*&BbpH-%c!A8HG#bp7PfLzj=22MAX~e2+L36MetMhWvVux2!gM-SpYA1 zcEeOGFCxC?V|;Fjaw zC)BK8D$44>=!DytwC59W)~3OHci7da3#EJT-B7>3VR8>mV7G>n6yq<-kGH>k!B+9TnqXkrUTW3DKem?NH`CbV zD!$Y#Zx9(GU+RWFbT9EEN~YnYr9C|qYO?)K5}A+aG&U{q-t(ivPAS_7z(lJLE?wj% zm|1^Rwe9vyvtM>@=m=3zLc*_Cv3#FiX&O|jFoCB#;iSg_$jo09EbgdLemwP^-gjap zz%LagfNw}}3*VQsBSg(YV%1$njK0<)U~fHsO18eg^FraXz5R5ZD`PiaG-R8C;VWsj ztEAlWTy*qT$b$T2HPQ8H(u6rJe0|PoiY!qFyXdww;Lxmzwd~9?{6(4h5^JA&(_vJp zM0zS#-D0WCqN*87Lj3|D`2Ka$`hvXCaa`Z1UZ+^7Z+pJjGn7RF?#zGyH55 z^U&e@YcAS5ciDRr6@qKLDzV{Lz66bp0S&VoW|l@uPGv5p@HPC!W$5obFyAKEa&Q{B zj55ssztk$R`Vj8P3)6z*(h5>ET8m(lpZ-gQ$$hhOteh8EV=@LBy0|F4d%js4_L7WQ z(yi;$ZZo7v;KPPPL^uw>U2Fv19cCuSoq82a81B|GMvLX|A|PUQsH#K`gt+RdJD)?H zDgFlJ`FQm78DqFkt`gaphDm?Z+}gTxkZ2B9Z)>}3jD5qAd3?NWKO-h)eeX8>W%fPv>Ad=f_zC(PnGIf9krnP7VNPa- z$x5cLZ9Eut^G{18XrvjDlh+Ik8d|nCJam_q9BJ}>r z!2x!pNWr4Ripg7-wJmH{3~9xnI2W1r>FC3LiwWeYb<>pC-vEMI?DcwnvLkhmxQps^ z=*9>=M}ns8-GZBW^0K_u_)nNPvRHmlmBnM*WwdwaNJKWh5Gt#^b|Sw_k;vYgV&NX> zlY&Kg^k0t@VgLJ15<lZ?4|?@ktR>`u9VNTYg+|F2L|-uku5=M%-%z& z2b6{huZvwxz4DvQTCLvbG+~-)DixP1!&CJ}wlG*bzMV3DMQlm@If(6J;hn$PS)KEn z)o+v}jxHbzC+POz_dl9uH$wJ{!8f?hiFa3IuKMQf%OpRaDrp*01jQ@{xvOgQ{K}h% z2Z*gkkx z&8|ZfeZ+s*Luln*pmTTa&xVkPc9Vm=9p6OvW4e#5>bK=(fBkJRRW;SZ$fQ!cRt9M|L)=ba@Y>D^yl#51vUY#a@3X%>|>lX1#&zi zn|Qzna(pa&Y~oR&hSD5*DbYoBFOr;MT-iu;iu+|M}u!ekv5|`BNW`IM1#VuZFpv zcpjucoah6YSf!bIqD`%;35#0C-~A#)J1rWzYfDy1??a>;Lw(h+{N_d6)D1|6zaSDB zkq`bHJ*#rF+TU4y_ciyzyEB8PlDA8R?!V?;`_{Q0qqYS5TOP&Rg)7xP-Mzt6GR?I5 zZrh_q9uG@Q6@)G(#3Zpo3eugkD*xl>-dUStKAv=>&b1XL=x6{k8ZFj%u@P^i6|>|j zOjii??sT}A&(`5{Wx8r0x4^FG&$;XEju^JvJ{_=8j%ngTzWft)zHlw{l#|mrii4p@ zbKAL;G0#)mvV7A{HC@zh1tLo-RJee)gmp*S zCxbkRjkB4g;PKwx+JlY@QNKSfm=iIigB`fY=Hl+Zk;e_+4SW~z1m!O|2DCl}fB7NM zmz=7_9{hVEqFr*6Bd%22_%d$1;LD3caL29y{{L{F{Isp(fHfWi!b&A=bVGS=c6L#F zUMHWIjN!_4f7{1)wgu1j9}NFdbo`@A$J%H0w^plU-Y|Xlf+VDW-#_%E&4U+DOQWM_ z*FPww`UOs0RxqskQh*84YJSQ$=6kb)H2B5I_U{zAZq-qDd}dWT;q_es;U zUx+G4va*$?89S7D=;Z`bBa&AexwgayZaAG2X2AASZ^`<2Z%@tU^~L?`$)=9~WEJZbG2fhpnn;GEt*jJY6hm1FfaYXZd^|N73hfjVmMIOR$2t(XX=QjhYit~uV}2I&LxklWe$tRH-ipNwy^Y2FiD48h-Mjl{6N7X|DD-xn&|)#0@8m zsJT;NaI;t`+HJ{XH+kYUesbUSdAQImLqmsOnd0VyCkE!9@^xV#)s-bE)X{~+j2KZjSjVDUILK(U8PnZl9mc?IJvNScpgKR|UKH7>>9<|rh?snAa(S-h#Z^9)VNYlLcaQLIDt4hS+$=}F7OV3AtY-W{M z27M9@e#=yc(l7C|yq3UIL*{wV={HU>YiAUo@x52ixQ8Wn7gXWSCJls!h4j77YSavi zOoP%rxFxPCYXN*Hd%%v2I{4irn+*7d)CL)X$88L;zMPNOy)}XsvKS^Rcv@a2WOsg{ zeAJ4FXkiTr;xjchm*P<=j+C!IpMr+hhAA}iM8>SGKbMiQIKMJ_F>7inIP8jF)uiXf z%%mp1p3>E^y`T(_sr?GRiDoofeEqly4s=nCjMF9B1Pc{Hh)hD6=2r&Inw%&%KIK!y z#{;UWanR7vzJy%M{+3fH<-&nf-Sd3f&O8V(@+3FmlN|g8{J25Ne_=uF1o$Ph<;@lI z|FWomgClY!y7mYwdaeYMos$c4s7j{U8>cJ1-l(Rk0uhVK5dZaK_B@{X++^#_JHr=$ z*iL7=d^P&tjm&Av{F{-j$jmqp-Dz;_8Y3&gn%U)p!nIhsQ}g?sV)tTos>78~r0y*Q zoUSfsZ{KbN7JbZ?)c;^G^j&mavFGqXX;sJF|2C_uubU;^p?*71NjF1&Te^au`{Zjn zAQec*V<7#eet(x1o-NtmtKizZoA1|$(FKqF*BWK4(x*a+xIMkvm)~mtkFdd{tuiNa z!M&ov!_P$Mk7#JZNp~9N!ZBvP;I#32{QqKV%* zRn)b{L1ciL2=sL{wr2IC-x?K#+d`sqiKZ=9O2BU*OrG$vNJ9>DaJ3Y^M@Aeo$~9eU zzpRP7z*dJ>E-iey=PZf-p+4OA4#k673cj#VXQO!K%qmZ4TOaNN56Y~lMMSiZX|O28 zie5gNqIjl$LijVAqv0>IPL~A*;;7MsePIMgsvucpqc{bW!4w@SS4A51rx(ZNt8d@t zhMs>JOqW;aX!SFVl{?*XqTX4+cz-UIdRy2cb4v$?|NCBW7HGiaH%Be#Xscm};8H1Y z<>$l4QtdywZG=obyP^g3v%jXV;0?X!YtM;v-7U+&=2^;YkdV8Lr&Vt(!aSFZGf3wY|IRB?DA!T;f|&CCe^lsD&AA>2$FM*(s`QicAkRNhqk_#%n%Z5;m|-3j3$2Ej;Zz!3z6o=T zd(}r`Xvw(}JB`Yv3ju79e-kvMilKT65|lz3zE(S2G@2QiuutC+)pgT&$|FZ5C|F|S_S~!V<_H~5{ayW3+cP@Y_?hFFGggqtT?fv&|_9G&k zKAyLxQJ5R9;;?D*vg0PqXg@vl>#WPBH$jLa~QVG|f9qKtAr9CwDie?8ART6wRp%e@OjVo8rgq!4&xv%YN3X z8!Ey}il;{`oke6)1z(iJx48W}&9&vTOMr!*m(ik+cYC)Q-IC4wF|%&(^=1^qZ^Cf? zm?!C&n&kBZ?QMPAUg&u|r-tXtS;=m5E!!SRrTqAW2L6%xcCBMZhSUZTPHb&Yi7uRmQ6uY%Li3t>+TnJafzQ?lV z?4AAHXC^mbDlI(H6hLCfMfa`-{w08!)I}%R?<`b5+*6=D)luJVa6Eo`cX(0>4u$51 z`mN?2tiAjvD0g!how_Cme6VmK-SjKz=Z???w+ExOdVkAmOL)X5@U?{R_a*AvSQ_Cl z*XMDO`RWm7No~T=4Uh1)yR+#+8^Je=N{05@&u{!O{ZJ+Dt88cw>)lxYzObB>QD_oC z0ckEobQd}LA1msj!iK&VRP~s+Gcj+@nrP@KZ~|GM z$3b?i$iW9%-!8>GC(W^(Nv|SWqoWg-F_+%=CIs%_kLsLK6dJSdSX7o;{?2mxT${CP zV+OFwm4X$8FO-v^L8%$Om8hX9be_kTIaHMmX~3Cjq<&mxFcgo`t#P6&5RTGn@$GB# z{@#{y4hOnr`AAqb7kqKvjFgc;*{L^Ki+e^OnS%ZGxNtcvg!4*% z7Vm>vO|K@dfulS~ZS(J`z3{2bmWeW`&jUWk9=dAtyz!+4h~)tsCGjbrq!k^ylsy=)S zrXgA|6RkczL*Bwxxzh_3!j8oAn?Xp&d*TkgtBo(s#?kGysiB~(1sat1P|Z3Fd=cw0gST+2+>iybEvXsvyWO{v{M{tU<&uS1kE5x2tonKER@u&!(=e+qjD^K)HeN$9At;4IV|;@gV`J6r`TpUIO69+-!X^&3EP zV9)uqd8o`O0GezL9Fv=t7@5a%}A7NYze=!|W6ubQ|kyGJY zZVV6cw4RJQD+jcgaP~vrM>;(*QSf`?XaugC2K5^}r#NdOV*V=6;S5dits1oIj&<4< zzaP1U3eN}J_0K{VSADS=8{Yq}^UI2W2#sU1mo-yg;-&?uw|Q)hz!`1A>Ei^n6q5H5 znm!rYJQr;G0yERleg@0uysl>rWZa&)eOftICPPo!yH_{shv*92egGY{A*Bo^9XBF? zUoiFy2;zGU5!mGN{HRS_44Nc?Cj2lJY(geB z7}6zwe2e9F;&}U0IdQ5bi3pM_h)bAQMYDS@c;tz1Qd*`jaLg5fn_Cdw+Vm@9Z?RHw z2D&ex>7+HB{-!&^qg(+G!8Z2_^ssMo?}IoaIM7Zl1VdVP`0g3J8Wdmp0cp^|46SO~ z()U^7ddWgK&r(#6A*2BdhEYfje?I(#bQn+6X)BTZJG!7K&DMh93*mC1s!F<`yCDImRH^(g1Z`ZGPIQ>qs zJdk>4c4h`(71!1`mxCzKZ)G_vMM6`qXL%K5PQ*cf*FZ|*zSpv@PEro@AUNO2@%rkc zBu*cCD`EJuMaxzz;;3OZfCVXt-Xv}o6%ad_b_x&_YOn&ELR~^J84?F;o;Lk^CuzzZ zX%jh@^9Tr$$n*38hdC#HDl?3DIO8Ys<;S3qIY`*=9M?9kd`)V!!Y*By5k8~%tuP}X z;6`h5C|q}O8K$Q;MO&kZT~4EBBI##^tq|HB|Lrg13JM$>;;xG-i4dvu*#Z_$FEXj& zyl{YWzj>)%o*lSW2c&|r;)Eej#;Qq2K)IUc?W|{;1=Xci54mGOI%zx2&W&GZ;dodk zU#B=UBpO9Knu6&1$dhLb*Fx_aM0HKW7r!A)hL)y}kM0xYWdzEcQXrz_e>Eu%6$b%+ zrl<^^=<44Wyb^1-8&2p4s4N=}mjev@Fhp1)*QI6|U(;r2|U|~222`4 zrp&_fHQN=>k2+1ICSJK=2gr>YNDX=<X<%20r6axn zffZApSD-hy;)z-29y!2ptoM3X}w=^s3$l@oz9&sO1$ z&C$J3f5KA`1AY%X1dW|2zN}!3hJln-RrSEqZ<$TPa=6iwcVNxZJgp2^ZZS?{TE7D4 z2~v5YHT5bWF>NGo;U{Mp&*|jNU324(pWYBj9GadJKQuXYxd6qJQwB`i3;mT}+qB1? ztd}X35WY-tO&BEMar>|5r}JE^j!c8dLrY`?uRghv)QAMlv^ti`VStOow8z#I#yW#) ziianYL?~pb0`Be96=QGG8)my}5*~)1MC1+KqyyHAR~Hg12b&D1S31`+`1MzmkMJ4Z z)!iF_(SBj~9j+J>I_2=5Ph2(cP^3ROIs;Qe@8J4>Vd~R&;UO6d{$?13rXgbNBWm;A zjP>)Miw3#!lmTCs1&#j`ZwaHohnkGNsXgb~Z?Y3mhCo_Mh6mzI6Sp)d)N3L>HPO~s zIT3#{(Cg$$BI|zy>7zbkq{i}uEqOvx2>jCia$&t^aG+0 zNr^NF4|g}xF?sd!LN)6d^>Nm_=#(`T;+LE_5g3(Gp?AjeYJCu?x^iXyzFl5w)b9C0 z7XJCv9%N&`Q`Du{Cg4}t(a}YmBF`NoerdO5 z7fyHwc3KQoRL7RosqslsIzJv=_>PbeGt!eqglDNne0UjS)B6UHhoji{(we#uM^)wu zE3+3Y3_}8;<~X@Xr_WQ`gK-W$kUKK9+K4ls9C;e1WNiK;94*vxBh{I%InLVY^cOUI zlpZRpnSaFQ%C^J-C=ufI!OkU57t9Mx>IE_GLmPX@p%+J=#Srx9>e4gUucP92>U@S< zWpCuhO5|N1s~7cidENoBXJ84R8?lxHOW*qPH6E$I+ui+@7^iVFXV8 z7i_)^SnDcCO6kAGY}tx#dJdqjNwAy%8zG+Si&&Er$fL=9isea*@Z0JZ68Ut@iT6J) z<@Z|pnu=X+=vNb*=s9N^a#KtRa#3%Ck{(4@P{dj`z6n zAn^P>^a+ipBIrm^Loj z>&+B3o`U#;9@fP$l^@#O-t7h+sq3j#sn|yR4`Tvu7XSbN literal 0 HcmV?d00001 diff --git a/icons/icons.qrc b/icons/icons.qrc index 5f495df07..711fdcdc0 100644 --- a/icons/icons.qrc +++ b/icons/icons.qrc @@ -48,5 +48,6 @@ record-disabled.png transition-tool.png transition-tool-disabled.png + error.png diff --git a/io/loadthread.cpp b/io/loadthread.cpp new file mode 100644 index 000000000..196a88d2c --- /dev/null +++ b/io/loadthread.cpp @@ -0,0 +1,639 @@ +#include "loadthread.h" + +#include "mainwindow.h" +#include "panels/panels.h" +#include "panels/project.h" +#include "project/footage.h" +#include "io/config.h" +#include "project/clip.h" +#include "project/sequence.h" +#include "project/transition.h" +#include "project/effect.h" +#include "playback/playback.h" +#include "ui_project.h" +#include "io/previewgenerator.h" +#include "dialogs/loaddialog.h" +#include "project/media.h" +#include "debug.h" + +#include +#include +#include + +struct TransitionData { + int id; + QString name; + long length; + Clip* otc; + Clip* ctc; +}; + +LoadThread::LoadThread(LoadDialog* l, bool a) : ld(l), autorecovery(a) { + connect(this, SIGNAL(finished()), this, SLOT(deleteLater())); + connect(this, SIGNAL(success()), this, SLOT(success_func())); +} + +const EffectMeta* get_meta_from_name(const QString& name, int type) { + for (int j=0;jtrack < 0) ? "Transform" : "Volume"; break; + case 1: effect_name = (c->track < 0) ? "Shake" : "Pan"; break; + case 2: effect_name = (c->track < 0) ? "Text" : "Noise"; break; + case 3: effect_name = (c->track < 0) ? "Solid" : "Tone"; break; + case 4: effect_name = "Invert"; break; + case 5: effect_name = "Chroma Key"; break; + case 6: effect_name = "Gaussian Blur"; break; + case 7: effect_name = "Crop"; break; + case 8: effect_name = "Flip"; break; + case 9: effect_name = "Box Blur"; break; + case 10: effect_name = "Wave"; break; + case 11: effect_name = "Temperature"; break; + } + } + + // wait for effects to be loaded + effects_loaded.lock(); + + const EffectMeta* meta = NULL; + + // find effect with this name + if (!effect_name.isEmpty()) { + meta = get_meta_from_name(effect_name, (c->track < 0) ? EFFECT_TYPE_VIDEO : EFFECT_TYPE_AUDIO); + } + + effects_loaded.unlock(); + + if (meta == NULL) { + dout << "[WARNING] An effect used by this project is missing. It was not loaded."; + } else { + QString tag = stream.name().toString(); + + if (tag == "opening" || tag == "closing") { + // TODO replace NULL/s with something else + + int transition_index = create_transition(c, NULL, meta); + Transition* t = c->sequence->transitions.at(transition_index); + if (effect_length > -1) t->set_length(effect_length); + t->set_enabled(effect_enabled); + t->load(stream); + + if (tag == "opening") { + c->opening_transition = transition_index; + } else { + c->closing_transition = transition_index; + } + } else { + Effect* e = create_effect(c, meta); + e->set_enabled(effect_enabled); + e->load(stream); + + c->effects.append(e); + } + } +} + +bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) { + f.seek(0); + stream.setDevice(stream.device()); + + QString root_search; + QString child_search; + + switch (type) { + case LOAD_TYPE_VERSION: + root_search = "version"; + break; + case LOAD_TYPE_URL: + root_search = "url"; + break; + case MEDIA_TYPE_FOLDER: + root_search = "folders"; + child_search = "folder"; + break; + case MEDIA_TYPE_FOOTAGE: + root_search = "media"; + child_search = "footage"; + break; + case MEDIA_TYPE_SEQUENCE: + root_search = "sequences"; + child_search = "sequence"; + break; + } + + show_err = true; + + while (!stream.atEnd()) { + stream.readNextStartElement(); + if (stream.name() == root_search) { + if (type == LOAD_TYPE_VERSION) { + int proj_version = stream.readElementText().toInt(); + if (proj_version < MIN_SAVE_VERSION && proj_version > SAVE_VERSION) { + if (QMessageBox::warning(mainWindow, "Version Mismatch", "This project was saved in a different version of Olive and may not be fully compatible with this version. Would you like to attempt loading it anyway?", QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) { + show_err = false; + return false; + } + } + } else if (type == LOAD_TYPE_URL) { + internal_proj_url = stream.readElementText(); + internal_proj_dir = QFileInfo(internal_proj_url).absoluteDir(); + } else { + while (!(stream.name() == root_search && stream.isEndElement())) { + stream.readNext(); + if (stream.name() == child_search && stream.isStartElement()) { + switch (type) { + case MEDIA_TYPE_FOLDER: + { + Media* folder = panel_project->new_folder(0); + folder->temp_id2 = 0; + for (int j=0;jtemp_id = attr.value().toInt(); + } else if (attr.name() == "name") { + folder->set_name(attr.value().toString()); + } else if (attr.name() == "parent") { + folder->temp_id2 = attr.value().toInt(); + } + } + loaded_folders.append(folder); + } + break; + case MEDIA_TYPE_FOOTAGE: + { + int folder = 0; + + Media* item = new Media(0); + Footage* m = new Footage(); + + m->using_inout = false; + + for (int j=0;jsave_id = attr.value().toInt(); + } else if (attr.name() == "folder") { + folder = attr.value().toInt(); + } else if (attr.name() == "name") { + m->name = attr.value().toString(); + } else if (attr.name() == "url") { + m->url = attr.value().toString(); + + if (!QFileInfo::exists(m->url)) { // if path is not absolute + QString proj_dir_test = proj_dir.absoluteFilePath(m->url); + QString internal_proj_dir_test = internal_proj_dir.absoluteFilePath(m->url); + + if (QFileInfo::exists(proj_dir_test)) { // if path is relative to the project's current dir + m->url = proj_dir_test; + dout << "[INFO] Matched" << attr.value().toString() << "relative to project's current directory"; + } else if (QFileInfo::exists(internal_proj_dir_test)) { // if path is relative to the last directory the project was saved in + m->url = internal_proj_dir_test; + dout << "[INFO] Matched" << attr.value().toString() << "relative to project's internal directory"; + } else if (m->url.contains('%')) { + // hack for image sequences (qt won't be able to find the URL with %, but ffmpeg may) + m->url = proj_dir_test; + dout << "[INFO] Guess image sequence" << attr.value().toString() << "path to project's current directory"; + } else { + dout << "[INFO] Failed to match" << attr.value().toString() << "to file"; + } + } else { + dout << "[INFO] Matched" << attr.value().toString() << "with absolute path"; + } + } else if (attr.name() == "duration") { + m->length = attr.value().toLongLong(); + } else if (attr.name() == "using_inout") { + m->using_inout = (attr.value() == "1"); + } else if (attr.name() == "in") { + m->in = attr.value().toLong(); + } else if (attr.name() == "out") { + m->out = attr.value().toLong(); + } + } + + item->set_footage(m); + + if (folder == 0) { + project_model.addTopLevelItem(item); + } else { + find_loaded_folder_by_id(folder)->appendChild(item); + } + + // analyze media to see if it's the same + loaded_media_items.append(item); + } + break; + case MEDIA_TYPE_SEQUENCE: + { + Media* parent = NULL; + Sequence* s = new Sequence(); + + // load attributes about sequence + for (int j=0;jname = attr.value().toString(); + } else if (attr.name() == "folder") { + int folder = attr.value().toInt(); + if (folder > 0) parent = find_loaded_folder_by_id(folder); + } else if (attr.name() == "id") { + s->save_id = attr.value().toInt(); + } else if (attr.name() == "width") { + s->width = attr.value().toInt(); + } else if (attr.name() == "height") { + s->height = attr.value().toInt(); + } else if (attr.name() == "framerate") { + s->frame_rate = attr.value().toDouble(); + } else if (attr.name() == "afreq") { + s->audio_frequency = attr.value().toInt(); + } else if (attr.name() == "alayout") { + s->audio_layout = attr.value().toInt(); + } else if (attr.name() == "open") { + open_seq = s; + } else if (attr.name() == "workarea") { + s->using_workarea = (attr.value() == "1"); + } else if (attr.name() == "workareaIn") { + s->workarea_in = attr.value().toLong(); + } else if (attr.name() == "workareaOut") { + s->workarea_out = attr.value().toLong(); + } + } + + QVector transition_data; + + // load all clips and clip information + while (!(stream.name() == child_search && stream.isEndElement()) && !stream.atEnd()) { + stream.readNextStartElement(); + if (stream.name() == "marker" && stream.isStartElement()) { + Marker m; + for (int j=0;jmarkers.append(m); + } else if (stream.name() == "transition" && stream.isStartElement()) { + TransitionData td; + td.otc = NULL; + td.ctc = NULL; + for (int j=0;jautoscale = false; + + c->media = NULL; + + for (int j=0;jname = attr.value().toString(); + } else if (attr.name() == "enabled") { + c->enabled = (attr.value() == "1"); + } else if (attr.name() == "id") { + c->load_id = attr.value().toInt(); + } else if (attr.name() == "clipin") { + c->clip_in = attr.value().toLong(); + } else if (attr.name() == "in") { + c->timeline_in = attr.value().toLong(); + } else if (attr.name() == "out") { + c->timeline_out = attr.value().toLong(); + } else if (attr.name() == "track") { + c->track = attr.value().toInt(); + } else if (attr.name() == "r") { + c->color_r = attr.value().toInt(); + } else if (attr.name() == "g") { + c->color_g = attr.value().toInt(); + } else if (attr.name() == "b") { + c->color_b = attr.value().toInt(); + } else if (attr.name() == "autoscale") { + c->autoscale = (attr.value() == "1"); + } else if (attr.name() == "media") { + media_type = MEDIA_TYPE_FOOTAGE; + media_id = attr.value().toInt(); + } else if (attr.name() == "stream") { + stream_id = attr.value().toInt(); + } else if (attr.name() == "speed") { + c->speed = attr.value().toDouble(); + } else if (attr.name() == "maintainpitch") { + c->maintain_audio_pitch = (attr.value() == "1"); + } else if (attr.name() == "reverse") { + c->reverse = (attr.value() == "1"); + } else if (attr.name() == "opening") { + c->opening_transition = attr.value().toInt(); + } else if (attr.name() == "closing") { + c->closing_transition = attr.value().toInt(); + } else if (attr.name() == "sequence") { + media_type = MEDIA_TYPE_SEQUENCE; + + // since we haven't finished loading sequences, we defer linking this until later + c->media = NULL; + c->media_stream = attr.value().toInt(); + loaded_clips.append(c); + } + } + + // set media and media stream + switch (media_type) { + case MEDIA_TYPE_FOOTAGE: + if (media_id >= 0) { + for (int j=0;jto_footage(); + if (m->save_id == media_id) { + c->media = loaded_media_items.at(j); + c->media_stream = stream_id; + break; + } + } + } + break; + } + + // load links and effects + while (!(stream.name() == "clip" && stream.isEndElement()) && !stream.atEnd()) { + stream.readNext(); + if (stream.isStartElement()) { + if (stream.name() == "linked") { + while (!(stream.name() == "linked" && stream.isEndElement()) && !stream.atEnd()) { + stream.readNext(); + if (stream.name() == "link" && stream.isStartElement()) { + for (int k=0;klinked.append(link_attr.value().toInt()); + break; + } + } + } + } + } else if (stream.isStartElement() && (stream.name() == "effect" || stream.name() == "opening" || stream.name() == "closing")) { + // "opening" and "closing" are backwards compatibility code + load_effect(stream, c); + } + } + } + + s->clips.append(c); + } + } + + // correct links, clip IDs, transitions + for (int i=0;iclips.size();i++) { + // correct links + Clip* correct_clip = s->clips.at(i); + for (int j=0;jlinked.size();j++) { + bool found = false; + for (int k=0;kclips.size();k++) { + if (s->clips.at(k)->load_id == correct_clip->linked.at(j)) { + correct_clip->linked[j] = k; + found = true; + break; + } + } + if (!found) { + correct_clip->linked.removeAt(j); + j--; + if (QMessageBox::warning(mainWindow, "Invalid Clip Link", "This project contains an invalid clip link. It may be corrupt. Would you like to continue loading it?", QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) { + delete s; + return false; + } + } + } + + // re-link clips to transitions + if (correct_clip->opening_transition > -1) { + for (int j=0;jopening_transition) { + transition_data[j].otc = correct_clip; + } + } + } + if (correct_clip->closing_transition > -1) { + for (int j=0;jclosing_transition) { + transition_data[j].ctc = correct_clip; + } + } + } + } + + // create transitions + for (int i=0;itrack < 0) ? EFFECT_TYPE_VIDEO : EFFECT_TYPE_AUDIO); + if (meta == NULL) { + dout << "[WARNING] Failed to link transition with name:" << td.name; + if (td.otc != NULL) td.otc->opening_transition = -1; + if (td.ctc != NULL) td.ctc->closing_transition = -1; + } else { + int transition_index = create_transition(primary, secondary, meta); + primary->sequence->transitions.at(transition_index)->set_length(td.length); + if (td.otc != NULL) td.otc->opening_transition = transition_index; + if (td.ctc != NULL) td.ctc->closing_transition = transition_index; + } + } + } + + Media* m = panel_project->new_sequence(NULL, s, false, parent); + + loaded_sequences.append(m); + } + break; + } + } + } + } + break; + } + } + return true; +} + +Media* LoadThread::find_loaded_folder_by_id(int id) { + for (int j=0;jtemp_id == id) { + return parent_item; + } + } + return NULL; +} + +void LoadThread::run() { + QFile file(project_url); + if (!file.open(QIODevice::ReadOnly)) { + dout << "[ERROR] Could not open file"; + return; + } + + /* set up directories to search for media + * most of the time, these will be the same but in + * case the project file has moved without the footage, + * we check both + */ + proj_dir = QFileInfo(project_url).absoluteDir(); + internal_proj_dir = QFileInfo(project_url).absoluteDir(); + internal_proj_url = project_url; + + QXmlStreamReader stream(&file); + + bool cont = false; + error_str.clear(); + show_err = true; + + // temp variables for loading (unnecessary?) + open_seq = NULL; + loaded_folders.clear(); + loaded_media_items.clear(); + loaded_clips.clear(); + loaded_sequences.clear(); + + // get "element" count + int element_count = 0; + while (!stream.atEnd()) { + stream.readNextStartElement(); + if (stream.name() == "folder" + || stream.name() == "footage" + || stream.name() == "sequence" + || stream.name() == "clip" + || stream.name() == "effect") { + element_count++; + } + } + + // find project file version + cont = load_worker(file, stream, LOAD_TYPE_VERSION); + + // find project's internal URL + cont = load_worker(file, stream, LOAD_TYPE_URL); + if (autorecovery) { + QString orig_filename = internal_proj_url; + int insert_index = internal_proj_url.lastIndexOf(".ove", -1, Qt::CaseInsensitive); + if (insert_index == -1) insert_index = internal_proj_url.length(); + int counter = 1; + while (QFileInfo::exists(orig_filename)) { + orig_filename = internal_proj_url; + QString recover_text = "recovered"; + if (counter > 1) { + recover_text += " " + QString::number(counter); + } + orig_filename.insert(insert_index, " (" + recover_text + ")"); + counter++; + } + mainWindow->updateTitle(orig_filename); + } + + // load folders first + if (cont) { + cont = load_worker(file, stream, MEDIA_TYPE_FOLDER); + } + + // load media + if (cont) { + // since folders loaded correctly, organize them appropriately + for (int i=0;itemp_id2; + if (parent > 0) { + find_loaded_folder_by_id(parent)->appendChild(folder); + } else { + project_model.addTopLevelItem(folder); + } + } + + cont = load_worker(file, stream, MEDIA_TYPE_FOOTAGE); + } + + // load sequences + if (cont) { + cont = load_worker(file, stream, MEDIA_TYPE_SEQUENCE); + } + + // attach nested sequence clips to their sequences + for (int i=0;imedia == NULL && loaded_clips.at(i)->media_stream == loaded_sequences.at(j)->to_sequence()->save_id) { + loaded_clips.at(i)->media = loaded_sequences.at(j); + loaded_clips.at(i)->refresh(); + break; + } + } + } + + if (!cont) { + if (show_err) QMessageBox::critical(mainWindow, "Project Load Error", "Error loading project: " + error_str, QMessageBox::Ok); + } else if (stream.hasError()) { + dout << "[ERROR] Error parsing XML." << stream.errorString(); + QMessageBox::critical(mainWindow, "XML Parsing Error", "Couldn't load '" + project_url + "'. " + stream.errorString(), QMessageBox::Ok); + cont = false; + } + + if (cont) { + mainWindow->setWindowModified(autorecovery); + + emit success(); // run in main thread + + for (int i=0;istart_preview_generator(loaded_media_items.at(i), true); + } + } else { + panel_project->new_project(); + } + + panel_project->add_recent_project(project_url); + + file.close(); +} + +void LoadThread::success_func() { + if (open_seq != NULL) set_sequence(open_seq); + update_ui(false); +} diff --git a/io/loadthread.h b/io/loadthread.h new file mode 100644 index 000000000..77bd0ca5f --- /dev/null +++ b/io/loadthread.h @@ -0,0 +1,43 @@ +#ifndef LOADTHREAD_H +#define LOADTHREAD_H + +#include +#include +#include + +class Media; +class Footage; +class Clip; +class Sequence; +class LoadDialog; + +class LoadThread : public QThread +{ + Q_OBJECT +public: + LoadThread(LoadDialog* l, bool a); + void run(); +signals: + void success(); +private slots: + void success_func(); +private: + LoadDialog* ld; + bool autorecovery; + + bool load_worker(QFile& f, QXmlStreamReader& stream, int type); + Sequence* open_seq; + QVector loaded_media_items; + QDir proj_dir; + QDir internal_proj_dir; + QString internal_proj_url; + bool show_err; + QString error_str; + + QVector loaded_folders; + QVector loaded_clips; + QVector loaded_sequences; + Media* find_loaded_folder_by_id(int id); +}; + +#endif // LOADTHREAD_H diff --git a/io/previewgenerator.cpp b/io/previewgenerator.cpp index 7b8934fdd..83fc426e6 100644 --- a/io/previewgenerator.cpp +++ b/io/previewgenerator.cpp @@ -1,6 +1,7 @@ #include "previewgenerator.h" -#include "media.h" +#include "project/media.h" +#include "project/footage.h" #include "panels/viewer.h" #include "panels/project.h" #include "io/config.h" @@ -28,11 +29,11 @@ extern "C" { QSemaphore sem(5); // only 5 preview generators can run at one time -PreviewGenerator::PreviewGenerator(QTreeWidgetItem* i, Media* m, bool r) : +PreviewGenerator::PreviewGenerator(Media* i, Footage* m, bool r) : QThread(0), fmt_ctx(NULL), - item(i), - media(m), + media(i), + footage(m), retrieve_duration(false), contains_still_image(false), replace(r), @@ -52,12 +53,12 @@ void PreviewGenerator::parse_media() { for (int i=0;i<(int)fmt_ctx->nb_streams;i++) { // Find the decoder for the video stream if (avcodec_find_decoder(fmt_ctx->streams[i]->codecpar->codec_id) == NULL) { - dout << "[ERROR] Unsupported codec in stream" << i << "of file" << media->name; + dout << "[ERROR] Unsupported codec in stream" << i << "of file" << footage->name; } else { - MediaStream* ms = media->get_stream_from_file_index(fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO, i); + FootageStream* ms = footage->get_stream_from_file_index(fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO, i); bool append = false; if (ms == NULL) { - ms = new MediaStream(); + ms = new FootageStream(); ms->preview_done = false; ms->file_index = i; append = true; @@ -93,18 +94,18 @@ void PreviewGenerator::parse_media() { ms->video_auto_interlacing = VIDEO_PROGRESSIVE; ms->video_interlacing = VIDEO_PROGRESSIVE; - if (append) media->video_tracks.append(ms); + if (append) footage->video_tracks.append(ms); } else if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { ms->audio_channels = fmt_ctx->streams[i]->codecpar->channels; ms->audio_layout = fmt_ctx->streams[i]->codecpar->channel_layout; ms->audio_frequency = fmt_ctx->streams[i]->codecpar->sample_rate; - if (append) media->audio_tracks.append(ms); + if (append) footage->audio_tracks.append(ms); } else if (append) { delete ms; } } } - media->length = fmt_ctx->duration; + footage->length = fmt_ctx->duration; if (fmt_ctx->duration == INT64_MIN) { retrieve_duration = true; @@ -116,28 +117,29 @@ void PreviewGenerator::parse_media() { bool PreviewGenerator::retrieve_preview(const QString& hash) { // returns true if generate_waveform must be run, false if we got all previews from cached files if (retrieve_duration) { + //dout << "[NOTE] " << media->name << "needs to retrieve duration"; return true; } bool found = true; - for (int i=0;ivideo_tracks.size();i++) { - MediaStream* ms = media->video_tracks.at(i); + for (int i=0;ivideo_tracks.size();i++) { + FootageStream* ms = footage->video_tracks.at(i); QString thumb_path = get_thumbnail_path(hash, ms); QFile f(thumb_path); if (f.exists() && ms->video_preview.load(thumb_path)) { - //dout << "loaded thumb" << ms->file_index << "from" << thumb_path; + dout << "loaded thumb" << ms->file_index << "from" << thumb_path; ms->preview_done = true; } else { found = false; break; } } - for (int i=0;iaudio_tracks.size();i++) { - MediaStream* ms = media->audio_tracks.at(i); + for (int i=0;iaudio_tracks.size();i++) { + FootageStream* ms = footage->audio_tracks.at(i); QString waveform_path = get_waveform_path(hash, ms); QFile f(waveform_path); if (f.exists()) { - //dout << "loaded wave" << ms->file_index << "from" << waveform_path; + dout << "loaded wave" << ms->file_index << "from" << waveform_path; f.open(QFile::ReadOnly); QByteArray data = f.readAll(); ms->audio_preview.resize(data.size()); @@ -153,12 +155,12 @@ bool PreviewGenerator::retrieve_preview(const QString& hash) { } } if (!found) { - for (int i=0;ivideo_tracks.size();i++) { - MediaStream* ms = media->video_tracks.at(i); + for (int i=0;ivideo_tracks.size();i++) { + FootageStream* ms = footage->video_tracks.at(i); ms->preview_done = false; } - for (int i=0;iaudio_tracks.size();i++) { - MediaStream* ms = media->audio_tracks.at(i); + for (int i=0;iaudio_tracks.size();i++) { + FootageStream* ms = footage->audio_tracks.at(i); ms->audio_preview.clear(); ms->preview_done = false; } @@ -167,11 +169,11 @@ bool PreviewGenerator::retrieve_preview(const QString& hash) { } void PreviewGenerator::finalize_media() { - media->ready_lock.unlock(); - media->ready = true; + footage->ready_lock.unlock(); + footage->ready = true; if (!cancelled) { - if (media->video_tracks.size() == 0) { + if (footage->video_tracks.size() == 0) { emit set_icon(ICON_TYPE_AUDIO, replace); } else if (contains_still_image) { emit set_icon(ICON_TYPE_IMAGE, replace); @@ -179,7 +181,7 @@ void PreviewGenerator::finalize_media() { emit set_icon(ICON_TYPE_VIDEO, replace); } - if (!contains_still_image || media->audio_tracks.size() > 0) { + /*if (!contains_still_image || media->audio_tracks.size() > 0) { double frame_rate = 30; if (!contains_still_image && media->video_tracks.size() > 0) frame_rate = media->video_tracks.at(0)->video_frame_rate; item->setText(1, frame_to_timecode(media->get_length_in_frames(frame_rate), config.timecode_view, frame_rate)); @@ -189,7 +191,7 @@ void PreviewGenerator::finalize_media() { } else { item->setText(2, QString::number(media->audio_tracks.at(0)->audio_frequency) + " Hz"); } - } + }*/ } } @@ -229,6 +231,9 @@ void PreviewGenerator::generate_waveform() { while (codec_ctx[packet->stream_index] == NULL || avcodec_receive_frame(codec_ctx[packet->stream_index], temp_frame) == AVERROR(EAGAIN)) { av_packet_unref(packet); int read_ret = av_read_frame(fmt_ctx, packet); + + //dout << "read frame for" << footage->name << footage->url << read_ret << "retrieve_duration:" << retrieve_duration << "eof:" << end_of_file << "packet pts:" << packet->pts; + if (read_ret < 0) { end_of_file = true; if (read_ret != AVERROR_EOF) dout << "[ERROR] Failed to read packet for preview generation" << read_ret; @@ -244,7 +249,7 @@ void PreviewGenerator::generate_waveform() { } } if (!end_of_file) { - MediaStream* s = media->get_stream_from_file_index(fmt_ctx->streams[packet->stream_index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO, packet->stream_index); + FootageStream* s = footage->get_stream_from_file_index(fmt_ctx->streams[packet->stream_index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO, packet->stream_index); if (s != NULL) { if (fmt_ctx->streams[packet->stream_index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { if (!s->preview_done) { @@ -349,10 +354,10 @@ void PreviewGenerator::generate_waveform() { // check if we've got all our previews if (retrieve_duration) { done = false; - } else if (media->audio_tracks.size() == 0) { + } else if (footage->audio_tracks.size() == 0) { done = true; - for (int i=0;ivideo_tracks.size();i++) { - if (!media->video_tracks.at(i)->preview_done) { + for (int i=0;ivideo_tracks.size();i++) { + if (!footage->video_tracks.at(i)->preview_done) { done = false; break; } @@ -365,8 +370,8 @@ void PreviewGenerator::generate_waveform() { av_packet_unref(packet); } } - for (int i=0;iaudio_tracks.size();i++) { - media->audio_tracks.at(i)->preview_done = true; + for (int i=0;iaudio_tracks.size();i++) { + footage->audio_tracks.at(i)->preview_done = true; } av_frame_free(&temp_frame); av_packet_free(&packet); @@ -376,33 +381,33 @@ void PreviewGenerator::generate_waveform() { } } if (retrieve_duration) { - media->length = 0; + footage->length = 0; int maximum_stream = 0; for (unsigned int i=0;inb_streams;i++) { if (media_lengths[i] > media_lengths[maximum_stream]) { maximum_stream = i; } } - media->length = (double) media_lengths[maximum_stream] / av_q2d(fmt_ctx->streams[maximum_stream]->avg_frame_rate) * AV_TIME_BASE; // TODO redo with PTS + footage->length = (double) media_lengths[maximum_stream] / av_q2d(fmt_ctx->streams[maximum_stream]->avg_frame_rate) * AV_TIME_BASE; // TODO redo with PTS finalize_media(); } delete [] media_lengths; delete [] codec_ctx; } -QString PreviewGenerator::get_thumbnail_path(const QString& hash, MediaStream* ms) { +QString PreviewGenerator::get_thumbnail_path(const QString& hash, FootageStream* ms) { return data_path + "/" + hash + "t" + QString::number(ms->file_index); } -QString PreviewGenerator::get_waveform_path(const QString& hash, MediaStream* ms) { +QString PreviewGenerator::get_waveform_path(const QString& hash, FootageStream* ms) { return data_path + "/" + hash + "w" + QString::number(ms->file_index); } void PreviewGenerator::run() { + Q_ASSERT(footage != NULL); Q_ASSERT(media != NULL); - Q_ASSERT(item != NULL); - QByteArray ba = media->url.toLatin1(); + QByteArray ba = footage->url.toLatin1(); char* filename = new char[ba.size()+1]; strcpy(filename, ba.data()); @@ -423,48 +428,51 @@ void PreviewGenerator::run() { error = true; } else { av_dump_format(fmt_ctx, 0, filename, 0); - parse_media(); - sem.acquire(); + parse_media(); // see if we already have data for this - QFileInfo file_info(media->url); - QString cache_file = media->url + QString::number(file_info.lastModified().toMSecsSinceEpoch()); + QFileInfo file_info(footage->url); + QString cache_file = footage->url.mid(footage->url.lastIndexOf('/')+1) + QString::number(file_info.size()) + QString::number(file_info.lastModified().toMSecsSinceEpoch()); + dout << "using hash" << cache_file; QString hash = QCryptographicHash::hash(cache_file.toLatin1(), QCryptographicHash::Md5).toHex(); if (retrieve_preview(hash)) { + sem.acquire(); + generate_waveform(); // save preview to file - for (int i=0;ivideo_tracks.size();i++) { - MediaStream* ms = media->video_tracks.at(i); + for (int i=0;ivideo_tracks.size();i++) { + FootageStream* ms = footage->video_tracks.at(i); ms->video_preview.save(get_thumbnail_path(hash, ms), "PNG"); + dout << "saved" << ms->file_index << "thumbnail to" << get_thumbnail_path(hash, ms); } - for (int i=0;iaudio_tracks.size();i++) { - MediaStream* ms = media->audio_tracks.at(i); + for (int i=0;iaudio_tracks.size();i++) { + FootageStream* ms = footage->audio_tracks.at(i); QFile f(get_waveform_path(hash, ms)); f.open(QFile::WriteOnly); f.write(ms->audio_preview.constData(), ms->audio_preview.size()); f.close(); - //dout << "saved" << ms->file_index << "waveform to" << get_waveform_path(hash, ms); + dout << "saved" << ms->file_index << "waveform to" << get_waveform_path(hash, ms); } - } - sem.release(); + sem.release(); + } } avformat_close_input(&fmt_ctx); } - if (error) { - update_footage_tooltip(item, media, errorStr); + if (error) { + media->update_tooltip(errorStr); emit set_icon(ICON_TYPE_ERROR, replace); - media->invalid = true; - media->ready_lock.unlock(); + footage->invalid = true; + footage->ready_lock.unlock(); } else { - update_footage_tooltip(item, media); + media->update_tooltip(); } delete [] filename; - media->preview_gen = NULL; + footage->preview_gen = NULL; } void PreviewGenerator::cancel() { diff --git a/io/previewgenerator.h b/io/previewgenerator.h index b1dd512f5..2f1f86c7d 100644 --- a/io/previewgenerator.h +++ b/io/previewgenerator.h @@ -2,22 +2,23 @@ #define PREVIEWGENERATOR_H #include +#include #define ICON_TYPE_VIDEO 0 #define ICON_TYPE_AUDIO 1 #define ICON_TYPE_IMAGE 2 #define ICON_TYPE_ERROR 3 -struct Media; -struct MediaStream; +struct Footage; +struct FootageStream; struct AVFormatContext; -class QTreeWidgetItem; +class Media; class PreviewGenerator : public QThread { Q_OBJECT public: - PreviewGenerator(QTreeWidgetItem*, Media*, bool); + PreviewGenerator(Media*, Footage*, bool); void run(); void cancel(); signals: @@ -28,15 +29,15 @@ private: void generate_waveform(); void finalize_media(); AVFormatContext* fmt_ctx; - QTreeWidgetItem* item; Media* media; + Footage* footage; bool retrieve_duration; bool contains_still_image; bool replace; bool cancelled; QString data_path; - QString get_thumbnail_path(const QString &hash, MediaStream* ms); - QString get_waveform_path(const QString& hash, MediaStream* ms); + QString get_thumbnail_path(const QString &hash, FootageStream* ms); + QString get_waveform_path(const QString& hash, FootageStream* ms); }; #endif // PREVIEWGENERATOR_H diff --git a/mainwindow.cpp b/mainwindow.cpp index b2a3005df..e404b7d67 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -3,11 +3,12 @@ #include "io/config.h" #include "io/path.h" -#include "io/media.h" +#include "project/footage.h" #include "project/sequence.h" #include "project/clip.h" #include "project/undo.h" +#include "project/media.h" #include "ui/sourcetable.h" #include "ui/viewerwidget.h" @@ -222,9 +223,6 @@ MainWindow::MainWindow(QWidget *parent) : } MainWindow::~MainWindow() { - panel_sequence_viewer->viewer_widget->delete_function(); - panel_footage_viewer->viewer_widget->delete_function(); - set_sequence(NULL); QString data_dir = get_data_path(); @@ -977,12 +975,11 @@ void MainWindow::on_actionNest_triggered() { } // add sequence to project - panel_project->new_sequence(ca, s, false, NULL); + Media* m = panel_project->new_sequence(ca, s, false, NULL); // add nested sequence to active sequence - QVector media_list = {s}; - QVector type_list = {MEDIA_TYPE_SEQUENCE}; - panel_timeline->create_ghosts_from_media(sequence, earliest_point, media_list, type_list); + QVector media_list = {m}; + panel_timeline->create_ghosts_from_media(sequence, earliest_point, media_list); panel_timeline->add_clips_from_ghosts(ca, sequence); undo_stack.push(ca); diff --git a/olive.pro b/olive.pro index 52a855dab..897f652a5 100644 --- a/olive.pro +++ b/olive.pro @@ -33,7 +33,6 @@ SOURCES += \ ui/sourcetable.cpp \ dialogs/aboutdialog.cpp \ ui/timelinewidget.cpp \ - io/media.cpp \ project/sequence.cpp \ project/clip.cpp \ playback/playback.cpp \ @@ -91,7 +90,11 @@ SOURCES += \ project/effectfield.cpp \ effects/internal/cubetransition.cpp \ project/effectgizmo.cpp \ - io/clipboard.cpp + io/clipboard.cpp \ + io/loadthread.cpp \ + project/footage.cpp \ + project/media.cpp \ + project/projectmodel.cpp HEADERS += \ mainwindow.h \ @@ -102,7 +105,6 @@ HEADERS += \ ui/sourcetable.h \ dialogs/aboutdialog.h \ ui/timelinewidget.h \ - io/media.h \ project/sequence.h \ project/clip.h \ playback/playback.h \ @@ -162,7 +164,11 @@ HEADERS += \ project/effectfield.h \ effects/internal/cubetransition.h \ project/effectgizmo.h \ - io/clipboard.h + io/clipboard.h \ + io/loadthread.h \ + project/footage.h \ + project/media.h \ + project/projectmodel.h FORMS += \ mainwindow.ui \ diff --git a/panels/panels.cpp b/panels/panels.cpp index 94f568470..17916c24c 100644 --- a/panels/panels.cpp +++ b/panels/panels.cpp @@ -3,6 +3,7 @@ #include "timeline.h" #include "effectcontrols.h" #include "viewer.h" +#include "project.h" #include "project/sequence.h" #include "project/clip.h" #include "project/transition.h" @@ -100,6 +101,7 @@ void update_ui(bool modified) { if (modified) { update_effect_controls(); } + project_model.update_data(); panel_effect_controls->update_keyframes(); panel_timeline->repaint_timeline(); panel_sequence_viewer->update_viewer(); diff --git a/panels/project.cpp b/panels/project.cpp index 3e220d458..5349240ab 100644 --- a/panels/project.cpp +++ b/panels/project.cpp @@ -1,6 +1,6 @@ -#include "project.h" +#include "project.h" #include "ui_project.h" -#include "io/media.h" +#include "project/footage.h" #include "panels/panels.h" #include "panels/timeline.h" @@ -22,6 +22,7 @@ #include "dialogs/mediapropertiesdialog.h" #include "dialogs/loaddialog.h" #include "io/clipboard.h" +#include "project/media.h" #include "debug.h" #include @@ -43,6 +44,8 @@ extern "C" { #define MAXIMUM_RECENT_PROJECTS 10 +ProjectModel project_model; + QString autorecovery_filename; QString project_url = ""; QStringList recent_projects; @@ -53,8 +56,13 @@ Project::Project(QWidget *parent) : ui(new Ui::Project) { ui->setupUi(this); - source_table = ui->treeWidget; - connect(ui->treeWidget, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(rename_media(QTreeWidgetItem*,int))); + source_table = ui->treeView; + + source_table->setModel(&project_model); + connect(source_table, SIGNAL(itemChanged(Media*,int)), this, SLOT(rename_media(Media*,int))); + + /*Media* m = new Media(project_model.get_root()); + project_model.get_root()->appendChild(m);*/ } Project::~Project() { @@ -74,8 +82,8 @@ QString Project::get_next_sequence_name(QString start) { name += "0"; } name += QString::number(n); - for (int i=0;itreeWidget->topLevelItemCount();i++) { - if (QString::compare(ui->treeWidget->topLevelItem(i)->text(0), name, Qt::CaseInsensitive) == 0) { + for (int i=0;iget_name(), name, Qt::CaseInsensitive) == 0) { found = true; n++; break; @@ -85,7 +93,7 @@ QString Project::get_next_sequence_name(QString start) { return name; } -Sequence* create_sequence_from_media(QVector& media_list, QVector& type_list) { +Sequence* create_sequence_from_media(QVector& media_list) { Sequence* s = new Sequence(); s->name = panel_project->get_next_sequence_name(); @@ -100,14 +108,15 @@ Sequence* create_sequence_from_media(QVector& media_list, QVector& t bool got_video_values = false; bool got_audio_values = false; for (int i=0;iget_type()) { case MEDIA_TYPE_FOOTAGE: { - Media* m = static_cast(media_list.at(i)); + Footage* m = media->to_footage(); if (m->ready) { if (!got_video_values) { for (int j=0;jvideo_tracks.size();j++) { - MediaStream* ms = m->video_tracks.at(j); + FootageStream* ms = m->video_tracks.at(j); s->width = ms->video_width; s->height = ms->video_height; if (ms->video_frame_rate != 0) { @@ -123,7 +132,7 @@ Sequence* create_sequence_from_media(QVector& media_list, QVector& t } if (!got_audio_values) { for (int j=0;jaudio_tracks.size();j++) { - MediaStream* ms = m->audio_tracks.at(j); + FootageStream* ms = m->audio_tracks.at(j); s->audio_frequency = ms->audio_frequency; got_audio_values = true; break; @@ -134,7 +143,7 @@ Sequence* create_sequence_from_media(QVector& media_list, QVector& t break; case MEDIA_TYPE_SEQUENCE: { - Sequence* seq = static_cast(media_list.at(i)); + Sequence* seq = media->to_sequence(); s->width = seq->width; s->height = seq->height; s->frame_rate = seq->frame_rate; @@ -152,23 +161,22 @@ Sequence* create_sequence_from_media(QVector& media_list, QVector& t return s; } -void Project::rename_media(QTreeWidgetItem* item, int column) { - int type = get_type_from_tree(item); - QString n = item->text(column); - switch (type) { - case MEDIA_TYPE_FOOTAGE: get_footage_from_tree(item)->name = n; break; - case MEDIA_TYPE_SEQUENCE: get_sequence_from_tree(item)->name = n; break; +void Project::rename_media(Media* item, int column) { + QString n = "rename function"; + switch (item->get_type()) { + case MEDIA_TYPE_FOOTAGE: item->to_footage()->name = n; break; + case MEDIA_TYPE_SEQUENCE: item->to_sequence()->name = n; break; } } void Project::duplicate_selected() { - QList items = ui->treeWidget->selectedItems(); + QModelIndexList items = source_table->selectionModel()->selectedRows(); bool duped = false; ComboAction* ca = new ComboAction(); for (int j=0;jcopy(), false, i->parent()); + Media* i = static_cast(items.at(j).internalPointer()); + if (i->get_type() == MEDIA_TYPE_SEQUENCE) { + new_sequence(ca, i->to_sequence()->copy(), false, static_cast(items.at(j).parent().internalPointer())); duped = true; } } @@ -180,18 +188,18 @@ void Project::duplicate_selected() { } void Project::replace_selected_file() { - QList selected_items = ui->treeWidget->selectedItems(); + QModelIndexList selected_items = source_table->selectionModel()->selectedRows(); if (selected_items.size() == 1) { - QTreeWidgetItem* item = selected_items.at(0); - if (get_type_from_tree(item) == MEDIA_TYPE_FOOTAGE) { + Media* item = static_cast(selected_items.at(0).internalPointer()); + if (item->get_type() == MEDIA_TYPE_FOOTAGE) { replace_media(item, 0); } } } -void Project::replace_media(QTreeWidgetItem* item, QString filename) { +void Project::replace_media(Media* item, QString filename) { if (filename.isEmpty()) { - filename = QFileDialog::getOpenFileName(this, "Replace '" + item->text(0) + "'", "", "All Files (*)"); + filename = QFileDialog::getOpenFileName(this, "Replace '" + item->get_name() + "'", "", "All Files (*)"); } if (!filename.isEmpty()) { ReplaceMediaCommand* rmc = new ReplaceMediaCommand(item, filename); @@ -202,45 +210,46 @@ void Project::replace_media(QTreeWidgetItem* item, QString filename) { void Project::replace_clip_media() { if (sequence == NULL) { QMessageBox::critical(this, "No active sequence", "No sequence is active, please open the sequence you want to replace clips from.", QMessageBox::Ok); - } else if (ui->treeWidget->selectedItems().size() == 1) { - QTreeWidgetItem* item = ui->treeWidget->selectedItems().at(0); - if (get_type_from_tree(item) == MEDIA_TYPE_SEQUENCE && sequence == get_sequence_from_tree(item)) { - QMessageBox::critical(this, "Active sequence selected", "You cannot insert a sequence into itself, so no clips of this media would be in this sequence.", QMessageBox::Ok); - } else { - ReplaceClipMediaDialog dialog(this, ui->treeWidget, item); - dialog.exec(); - } + } else { + QModelIndexList selected_items = source_table->selectionModel()->selectedRows(); + if (selected_items.size() == 1) { + Media* item = static_cast(selected_items.at(0).internalPointer()); + if (item->get_type() == MEDIA_TYPE_SEQUENCE && sequence == item->to_sequence()) { + QMessageBox::critical(this, "Active sequence selected", "You cannot insert a sequence into itself, so no clips of this media would be in this sequence.", QMessageBox::Ok); + } else { + ReplaceClipMediaDialog dialog(this, source_table, item); + dialog.exec(); + } + } } } void Project::open_properties() { - if (ui->treeWidget->selectedItems().size() == 1) { - QTreeWidgetItem* item = ui->treeWidget->selectedItems().at(0); - switch (get_type_from_tree(item)) { + QModelIndexList selected_items = source_table->selectionModel()->selectedRows(); + if (selected_items.size() == 1) { + Media* item = static_cast(selected_items.at(0).internalPointer()); + switch (item->get_type()) { case MEDIA_TYPE_FOOTAGE: { - MediaPropertiesDialog mpd(this, item, get_footage_from_tree(item)); + MediaPropertiesDialog mpd(this, item); mpd.exec(); } break; case MEDIA_TYPE_SEQUENCE: { NewSequenceDialog nsd(this); - Sequence* s = get_sequence_from_tree(item); + Sequence* s = item->to_sequence(); nsd.existing_sequence = s; - nsd.existing_item = item; + nsd.existing_item = item; nsd.exec(); } break; default: { // fall back to renaming - QString new_name = QInputDialog::getText(this, "Rename '" + item->text(0) + "'", "Enter new name:", QLineEdit::Normal, item->text(0)); - if (!new_name.isEmpty()) { - MediaRename* mr = new MediaRename(); - mr->from = item->text(0); - mr->item = item; - mr->to = new_name; + QString new_name = QInputDialog::getText(this, "Rename '" + item->get_name() + "'", "Enter new name:", QLineEdit::Normal, item->get_name()); + if (!new_name.isEmpty()) { + MediaRename* mr = new MediaRename(item, new_name); undo_stack.push(mr); } } @@ -248,70 +257,55 @@ void Project::open_properties() { } } -void Project::new_sequence(ComboAction *ca, Sequence *s, bool open, QTreeWidgetItem* parent) { - QTreeWidgetItem* item = new_item(); - item->setText(0, s->name); - set_sequence_of_tree(item, s); +Media* Project::new_sequence(ComboAction *ca, Sequence *s, bool open, Media* parent) { + if (parent == NULL) parent = project_model.get_root(); + Media* item = new Media(parent); + item->set_sequence(s); if (ca != NULL) { ca->append(new NewSequenceCommand(item, parent)); if (open) ca->append(new ChangeSequenceAction(s)); } else { if (parent == NULL) { - ui->treeWidget->addTopLevelItem(item); + project_model.addTopLevelItem(item); } else { - parent->addChild(item); + parent->appendChild(item); } if (open) set_sequence(s); } -} - -void Project::start_preview_generator(QTreeWidgetItem* item, Media* media, bool replacing) { - // set up throbber animation - MediaThrobber* throbber = new MediaThrobber(item); - item->setData(0, Qt::UserRole + 5, reinterpret_cast(throbber)); - - PreviewGenerator* pg = new PreviewGenerator(item, media, replacing); - media->preview_gen = pg; - connect(pg, SIGNAL(set_icon(int, bool)), throbber, SLOT(stop(int, bool))); - pg->start(QThread::LowPriority); + return item; } QString Project::get_file_name_from_path(const QString& path) { return path.mid(path.lastIndexOf('/')+1); } -QTreeWidgetItem* Project::new_item() { - QTreeWidgetItem* item = new QTreeWidgetItem(); - item->setData(0, Qt::UserRole + 5, 0); - item->setFlags(item->flags() | Qt::ItemIsEditable); +/*Media* Project::new_item() { + Media* item = new Media(0); + //item->setFlags(item->flags() | Qt::ItemIsEditable); return item; -} +}*/ bool Project::is_focused() { - return ui->treeWidget->hasFocus(); + return source_table->hasFocus(); } -QTreeWidgetItem* Project::new_folder(QString name) { - QTreeWidgetItem* item = new_item(); - item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); - item->setText(0, (name.isEmpty()) ? "New Folder" : name); - item->setIcon(0, QIcon(":/icons/folder.png")); - set_item_to_folder(item); +Media* Project::new_folder(QString name) { + Media* item = new Media(0); + item->set_folder(); return item; } -void Project::get_all_media_from_table(QList items, QList& list, int search_type) { +void Project::get_all_media_from_table(QList items, QList& list, int search_type) { for (int i=0;i children; + Media* item = items.at(i); + if (item->get_type() == MEDIA_TYPE_FOLDER) { + QList children; for (int j=0;jchildCount();j++) { children.append(item->child(j)); } get_all_media_from_table(children, list, search_type); - } else if (search_type == type || search_type == -1) { + } else if (search_type == item->get_type() || search_type == -1) { list.append(item); } } @@ -333,7 +327,11 @@ bool delete_clips_in_clipboard_with_media(ComboAction* ca, Media* m) { void Project::delete_selected_media() { ComboAction* ca = new ComboAction(); - QList items = ui->treeWidget->selectedItems(); + QModelIndexList selected_items = source_table->selectionModel()->selectedRows(); + QList items; + for (int i=0;i(selected_items.at(i).internalPointer())); + } bool remove = true; bool redraw = false; @@ -342,25 +340,25 @@ void Project::delete_selected_media() { source_table->setSortingEnabled(true); // check if media is in use - QVector parents; - QList sequence_items; - QList all_top_level_items; - for (int i=0;itreeWidget->topLevelItemCount();i++) { - all_top_level_items.append(ui->treeWidget->topLevelItem(i)); + QVector parents; + QList sequence_items; + QList all_top_level_items; + for (int i=0;i 0) { - QList media_items; + QList media_items; get_all_media_from_table(items, media_items, MEDIA_TYPE_FOOTAGE); for (int i=0;ito_footage(); bool confirm_delete = false; for (int j=0;jto_sequence(); for (int k=0;kclips.size();k++) { Clip* c = s->clips.at(k); - if (c != NULL && c->media == media) { + if (c != NULL && c->media == item) { if (!confirm_delete) { // we found a reference, so we know we'll need to ask if the user wants to delete it QMessageBox confirm(this); @@ -377,13 +375,13 @@ void Project::delete_selected_media() { redraw = true; } else if (confirm.clickedButton() == skip_button) { // remove media item and any folders containing it from the remove list - QTreeWidgetItem* parent = item; + Media* parent = item; while (parent != NULL) { parents.append(parent); // re-add item's siblings for (int m=0;mchildCount();m++) { - QTreeWidgetItem* child = parent->child(m); + Media* child = parent->child(m); bool found = false; for (int n=0;nparent(); + parent = parent->parentItem(); } j = sequence_items.size(); @@ -417,7 +415,7 @@ void Project::delete_selected_media() { } } if (confirm_delete) { - delete_clips_in_clipboard_with_media(ca, media); + delete_clips_in_clipboard_with_media(ca, item); } } @@ -436,27 +434,27 @@ void Project::delete_selected_media() { } for (int i=0;iappend(new DeleteMediaCommand(items.at(i))); + ca->append(new DeleteMediaCommand(selected_items.at(i))); - if (get_type_from_tree(items.at(i)) == MEDIA_TYPE_SEQUENCE) { + if (items.at(i)->get_type() == MEDIA_TYPE_SEQUENCE) { redraw = true; - Sequence* s = get_sequence_from_tree(items.at(i)); + Sequence* s = items.at(i)->to_sequence(); if (s == sequence) { ca->append(new ChangeSequenceAction(NULL)); } if (s == panel_footage_viewer->seq) { - panel_footage_viewer->set_media(MEDIA_TYPE_SEQUENCE, NULL); + panel_footage_viewer->set_media(NULL); } - } else if (get_type_from_tree(items.at(i)) == MEDIA_TYPE_FOOTAGE) { + } else if (items.at(i)->get_type() == MEDIA_TYPE_FOOTAGE) { if (panel_footage_viewer->seq != NULL) { for (int j=0;jseq->clips.size();j++) { Clip* c = panel_footage_viewer->seq->clips.at(j); if (c != NULL) { - if (c->media == get_media_from_tree(items.at(i))) { - panel_footage_viewer->set_media(MEDIA_TYPE_SEQUENCE, NULL); + if (c->media == items.at(i)->to_object()) { + panel_footage_viewer->set_media(NULL); } break; } @@ -475,7 +473,20 @@ void Project::delete_selected_media() { } } -void Project::process_file_list(bool recursive, QStringList& files, QTreeWidgetItem* parent, QTreeWidgetItem* replace) { +void Project::start_preview_generator(Media* item, bool replacing) { + // set up throbber animation + MediaThrobber* throbber = new MediaThrobber(item); + throbber->moveToThread(QApplication::instance()->thread()); + item->throbber = throbber; + QMetaObject::invokeMethod(throbber, "start", Qt::QueuedConnection); + + PreviewGenerator* pg = new PreviewGenerator(item, item->to_footage(), replacing); + item->to_footage()->preview_gen = pg; + connect(pg, SIGNAL(set_icon(int, bool)), throbber, SLOT(stop(int, bool))); + pg->start(QThread::LowPriority); +} + +void Project::process_file_list(bool recursive, QStringList& files, Media* parent, Media* replace) { bool imported = false; QVector image_sequence_urls; @@ -491,7 +502,7 @@ void Project::process_file_list(bool recursive, QStringList& files, QTreeWidgetI for (int i=0;iappend(new AddMediaCommand(folder, parent)); } else { - parent->addChild(folder); + parent->appendChild(folder); } imported = true; @@ -589,34 +600,34 @@ void Project::process_file_list(bool recursive, QStringList& files, QTreeWidgetI } if (!skip) { - QTreeWidgetItem* item; - Media* m; + Media* item; + Footage* m; if (replace != NULL) { item = replace; - m = get_footage_from_tree(replace); + m = replace->to_footage(); m->reset(); } else { - item = new_item(); - m = new Media(); + item = new Media(parent); + m = new Footage(); } m->using_inout = false; m->url = file; m->name = get_file_name_from_path(files.at(i)); - // generate waveform/thumbnail in another thread - start_preview_generator(item, m, replace != NULL); + item->set_footage(m); - set_footage_of_tree(item, m); + // generate waveform/thumbnail in another thread + start_preview_generator(item, replace != NULL); - last_imported_media.append(m); + last_imported_media.append(item); if (replace == NULL) { if (create_undo_action) { ca->append(new AddMediaCommand(item, parent)); } else { - parent->addChild(item); + parent->appendChild(item); } } @@ -633,32 +644,33 @@ void Project::process_file_list(bool recursive, QStringList& files, QTreeWidgetI } } -QTreeWidgetItem* Project::get_selected_folder() { +Media* Project::get_selected_folder() { // if one item is selected and it's a folder, return it - QList selected_items = panel_project->source_table->selectedItems(); - if (selected_items.size() == 1 && get_type_from_tree(selected_items.at(0)) == MEDIA_TYPE_FOLDER) { - return selected_items.at(0); + QModelIndexList selected_items = source_table->selectionModel()->selectedRows(); + if (selected_items.size() == 1) { + Media* m = static_cast(selected_items.at(0).internalPointer()); + if (m->get_type() == MEDIA_TYPE_FOLDER) return m; } return NULL; } -bool Project::reveal_media(void *media, QTreeWidgetItem* parent) { - int count = (parent == NULL) ? ui->treeWidget->topLevelItemCount() : parent->childCount(); +bool Project::reveal_media(void *media, QModelIndex parent) { + for (int i=0;i(item.internalPointer()); - for (int i=0;itreeWidget->topLevelItem(i) : parent->child(i); - if (get_type_from_tree(item) == MEDIA_TYPE_FOLDER) { + if (m->get_type() == MEDIA_TYPE_FOLDER) { if (reveal_media(media, item)) return true; - } else if (get_media_from_tree(item) == media) { + } else if (m->to_object() == media) { // expand all folders leading to this media - QTreeWidgetItem* hierarchy = item->parent(); - while (hierarchy != NULL) { - hierarchy->setExpanded(true); - hierarchy = hierarchy->parent(); + QModelIndex hierarchy = item.parent(); + while (hierarchy.isValid()) { + source_table->setExpanded(hierarchy, true); + hierarchy = hierarchy.parent(); } - // select item - item->setSelected(true); + // select item + source_table->selectionModel()->select(item, QItemSelectionModel::Select); return true; } @@ -677,88 +689,18 @@ void Project::import_dialog() { } } -void set_item_to_folder(QTreeWidgetItem* item) { - item->setData(0, Qt::UserRole + 1, MEDIA_TYPE_FOLDER); -} - -void* get_media_from_tree(QTreeWidgetItem* item) { - return reinterpret_cast(item->data(0, Qt::UserRole + 2).value()); - /*int type = get_type_from_tree(item); - switch (type) { - case MEDIA_TYPE_FOOTAGE: return get_footage_from_tree(item); - case MEDIA_TYPE_SEQUENCE: return get_sequence_from_tree(item); - default: dout << "[ERROR] Invalid media type when retrieving media"; - } - return NULL;*/ -} - -Media* get_footage_from_tree(QTreeWidgetItem* item) { - return reinterpret_cast(item->data(0, Qt::UserRole + 2).value()); -} - -void set_footage_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* get_sequence_from_tree(QTreeWidgetItem* item) { - return reinterpret_cast(item->data(0, Qt::UserRole + 2).value()); -} - -QString get_channel_layout_name(int channels, int layout) { - switch (channels) { - case 0: return "Invalid"; break; - case 1: return "Mono"; break; - case 2: return "Stereo"; break; - default: { - char buf[50]; - av_get_channel_layout_string(buf, sizeof(buf), channels, layout); - return QString(buf); - } - } -} - -void set_sequence_of_tree(QTreeWidgetItem* item, Sequence* s) { - item->setData(0, Qt::UserRole + 1, MEDIA_TYPE_SEQUENCE); - item->setData(0, Qt::UserRole + 2, QVariant::fromValue(reinterpret_cast(s))); - item->setToolTip(0, "Name: " + s->name - + "\nVideo Dimensions: " + QString::number(s->width) + "x" + QString::number(s->height) - + "\nFrame Rate: " + QString::number(s->frame_rate) - + "\nAudio Frequency: " + QString::number(s->audio_frequency) - + "\nAudio Layout: " + get_channel_layout_name(av_get_channel_layout_nb_channels(s->audio_layout), s->audio_layout)); - item->setIcon(0, QIcon(":/icons/sequence.png")); -} - -int get_type_from_tree(QTreeWidgetItem* item) { - return item->data(0, Qt::UserRole + 1).toInt(); -} - -void Project::delete_media(QTreeWidgetItem* item) { - int type = get_type_from_tree(item); - void* media = get_media_from_tree(item); - switch (type) { - case MEDIA_TYPE_FOOTAGE: - delete static_cast(media); - break; - case MEDIA_TYPE_SEQUENCE: - delete static_cast(media); - break; - } -} - void Project::delete_clips_using_selected_media() { if (sequence == NULL) { QMessageBox::critical(this, "No active sequence", "No sequence is active, please open the sequence you want to delete clips from.", QMessageBox::Ok); } else { ComboAction* ca = new ComboAction(); bool deleted = false; - QList items = source_table->selectedItems(); + QModelIndexList items = source_table->selectionModel()->selectedRows(); for (int i=0;iclips.size();i++) { Clip* c = sequence->clips.at(i); if (c != NULL) { for (int j=0;j(items.at(j).internalPointer()); if (c->media == m) { ca->append(new DeleteClipAction(sequence, i)); deleted = true; @@ -767,7 +709,7 @@ void Project::delete_clips_using_selected_media() { } } for (int j=0;j(items.at(j).internalPointer()); if (delete_clips_in_clipboard_with_media(ca, m)) deleted = true; } if (deleted) { @@ -784,18 +726,14 @@ void Project::clear() { panel_effect_controls->clear_effects(true); // delete sequences first because it's important to close all the clips before deleting the media - QVector sequences = list_all_project_sequences(); + QVector sequences = list_all_project_sequences(); for (int i=0;ito_sequence(); + sequences.at(i)->set_sequence(NULL); } // delete everything else - while (ui->treeWidget->topLevelItemCount() > 0) { - QTreeWidgetItem* item = ui->treeWidget->topLevelItem(0); - if (get_type_from_tree(item) != MEDIA_TYPE_SEQUENCE) delete_media(item); // already deleted - if (item->data(0, Qt::UserRole + 5) != 0) delete reinterpret_cast(item->data(0, Qt::UserRole + 5).value()); - delete item; - } + project_model.clear(); } void Project::new_project() { @@ -805,664 +743,53 @@ void Project::new_project() { mainWindow->setWindowModified(false); } -QTreeWidgetItem* Project::find_loaded_folder_by_id(int id) { - for (int j=0;jdata(0, Qt::UserRole + 3).toInt() == id) { - return parent_item; - } - } - return NULL; -} - -const EffectMeta* get_meta_from_name(const QString& name, int type) { - for (int j=0;jtrack < 0) ? "Transform" : "Volume"; break; - case 1: effect_name = (c->track < 0) ? "Shake" : "Pan"; break; - case 2: effect_name = (c->track < 0) ? "Text" : "Noise"; break; - case 3: effect_name = (c->track < 0) ? "Solid" : "Tone"; break; - case 4: effect_name = "Invert"; break; - case 5: effect_name = "Chroma Key"; break; - case 6: effect_name = "Gaussian Blur"; break; - case 7: effect_name = "Crop"; break; - case 8: effect_name = "Flip"; break; - case 9: effect_name = "Box Blur"; break; - case 10: effect_name = "Wave"; break; - case 11: effect_name = "Temperature"; break; - } - } - - // wait for effects to be loaded - effects_loaded.lock(); - - const EffectMeta* meta = NULL; - - // find effect with this name - if (!effect_name.isEmpty()) { - meta = get_meta_from_name(effect_name, (c->track < 0) ? EFFECT_TYPE_VIDEO : EFFECT_TYPE_AUDIO); - } - - effects_loaded.unlock(); - - if (meta == NULL) { - dout << "[WARNING] An effect used by this project is missing. It was not loaded."; - } else { - QString tag = stream.name().toString(); - - if (tag == "opening" || tag == "closing") { - // TODO replace NULL/s with something else - - int transition_index = create_transition(c, NULL, meta); - Transition* t = c->sequence->transitions.at(transition_index); - if (effect_length > -1) t->set_length(effect_length); - t->set_enabled(effect_enabled); - t->load(stream); - - if (tag == "opening") { - c->opening_transition = transition_index; - } else { - c->closing_transition = transition_index; - } - } else { - Effect* e = create_effect(c, meta); - e->set_enabled(effect_enabled); - e->load(stream); - - c->effects.append(e); - } - } -} - -struct TransitionData { - int id; - QString name; - long length; - Clip* otc; - Clip* ctc; -}; - -bool Project::load_worker(QFile& f, QXmlStreamReader& stream, int type) { - f.seek(0); - stream.setDevice(stream.device()); - - QString root_search; - QString child_search; - - switch (type) { - case LOAD_TYPE_VERSION: - root_search = "version"; - break; - case LOAD_TYPE_URL: - root_search = "url"; - break; - case MEDIA_TYPE_FOLDER: - root_search = "folders"; - child_search = "folder"; - break; - case MEDIA_TYPE_FOOTAGE: - root_search = "media"; - child_search = "footage"; - break; - case MEDIA_TYPE_SEQUENCE: - root_search = "sequences"; - child_search = "sequence"; - break; - } - - show_err = true; - - while (!stream.atEnd()) { - stream.readNextStartElement(); - if (stream.name() == root_search) { - if (type == LOAD_TYPE_VERSION) { - int proj_version = stream.readElementText().toInt(); - if (proj_version < MIN_SAVE_VERSION && proj_version > SAVE_VERSION) { - if (QMessageBox::warning(this, "Version Mismatch", "This project was saved in a different version of Olive and may not be fully compatible with this version. Would you like to attempt loading it anyway?", QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) { - show_err = false; - return false; - } - } - } else if (type == LOAD_TYPE_URL) { - internal_proj_url = stream.readElementText(); - internal_proj_dir = QFileInfo(internal_proj_url).absoluteDir(); - } else { - while (!(stream.name() == root_search && stream.isEndElement())) { - stream.readNext(); - if (stream.name() == child_search && stream.isStartElement()) { - switch (type) { - case MEDIA_TYPE_FOLDER: - { - QTreeWidgetItem* folder = new_folder(0); - for (int j=0;jsetData(0, Qt::UserRole + 3, attr.value().toInt()); - } else if (attr.name() == "name") { - folder->setText(0, attr.value().toString()); - } else if (attr.name() == "parent") { - folder->setData(0, Qt::UserRole + 4, attr.value().toInt()); - } - } - loaded_folders.append(folder); - } - break; - case MEDIA_TYPE_FOOTAGE: - { - int folder = 0; - - QTreeWidgetItem* item = new_item(); - Media* m = new Media(); - - m->using_inout = false; - - for (int j=0;jsave_id = attr.value().toInt(); - } else if (attr.name() == "folder") { - folder = attr.value().toInt(); - } else if (attr.name() == "name") { - m->name = attr.value().toString(); - } else if (attr.name() == "url") { - m->url = attr.value().toString(); - - if (!QFileInfo::exists(m->url)) { // if path is not absolute - QString proj_dir_test = proj_dir.absoluteFilePath(m->url); - QString internal_proj_dir_test = internal_proj_dir.absoluteFilePath(m->url); - - if (QFileInfo::exists(proj_dir_test)) { // if path is relative to the project's current dir - m->url = proj_dir_test; - dout << "[INFO] Matched" << attr.value().toString() << "relative to project's current directory"; - } else if (QFileInfo::exists(internal_proj_dir_test)) { // if path is relative to the last directory the project was saved in - m->url = internal_proj_dir_test; - dout << "[INFO] Matched" << attr.value().toString() << "relative to project's internal directory"; - } else if (m->url.contains('%')) { - // hack for image sequences (qt won't be able to find the URL with %, but ffmpeg may) - m->url = proj_dir_test; - dout << "[INFO] Guess image sequence" << attr.value().toString() << "path to project's current directory"; - } else { - dout << "[INFO] Failed to match" << attr.value().toString() << "to file"; - } - } else { - dout << "[INFO] Matched" << attr.value().toString() << "with absolute path"; - } - } else if (attr.name() == "duration") { - m->length = attr.value().toLongLong(); - } else if (attr.name() == "using_inout") { - m->using_inout = (attr.value() == "1"); - } else if (attr.name() == "in") { - m->in = attr.value().toLong(); - } else if (attr.name() == "out") { - m->out = attr.value().toLong(); - } - } - - set_footage_of_tree(item, m); - - if (folder == 0) { - ui->treeWidget->addTopLevelItem(item); - } else { - find_loaded_folder_by_id(folder)->addChild(item); - } - - // analyze media to see if it's the same - loaded_media.append(m); - loaded_media_items.append(item); - } - break; - case MEDIA_TYPE_SEQUENCE: - { - QTreeWidgetItem* parent = NULL; - Sequence* s = new Sequence(); - - // load attributes about sequence - for (int j=0;jname = attr.value().toString(); - } else if (attr.name() == "folder") { - int folder = attr.value().toInt(); - if (folder > 0) parent = find_loaded_folder_by_id(folder); - } else if (attr.name() == "id") { - s->save_id = attr.value().toInt(); - } else if (attr.name() == "width") { - s->width = attr.value().toInt(); - } else if (attr.name() == "height") { - s->height = attr.value().toInt(); - } else if (attr.name() == "framerate") { - s->frame_rate = attr.value().toDouble(); - } else if (attr.name() == "afreq") { - s->audio_frequency = attr.value().toInt(); - } else if (attr.name() == "alayout") { - s->audio_layout = attr.value().toInt(); - } else if (attr.name() == "open") { - open_seq = s; - } else if (attr.name() == "workarea") { - s->using_workarea = (attr.value() == "1"); - } else if (attr.name() == "workareaIn") { - s->workarea_in = attr.value().toLong(); - } else if (attr.name() == "workareaOut") { - s->workarea_out = attr.value().toLong(); - } - } - - QVector transition_data; - - // load all clips and clip information - while (!(stream.name() == child_search && stream.isEndElement()) && !stream.atEnd()) { - stream.readNextStartElement(); - if (stream.name() == "marker" && stream.isStartElement()) { - Marker m; - for (int j=0;jmarkers.append(m); - } else if (stream.name() == "transition" && stream.isStartElement()) { - TransitionData td; - td.otc = NULL; - td.ctc = NULL; - for (int j=0;jautoscale = false; - - c->media = NULL; - for (int j=0;jname = attr.value().toString(); - } else if (attr.name() == "enabled") { - c->enabled = (attr.value() == "1"); - } else if (attr.name() == "id") { - c->load_id = attr.value().toInt(); - } else if (attr.name() == "clipin") { - c->clip_in = attr.value().toLong(); - } else if (attr.name() == "in") { - c->timeline_in = attr.value().toLong(); - } else if (attr.name() == "out") { - c->timeline_out = attr.value().toLong(); - } else if (attr.name() == "track") { - c->track = attr.value().toInt(); - } else if (attr.name() == "r") { - c->color_r = attr.value().toInt(); - } else if (attr.name() == "g") { - c->color_g = attr.value().toInt(); - } else if (attr.name() == "b") { - c->color_b = attr.value().toInt(); - } else if (attr.name() == "autoscale") { - c->autoscale = (attr.value() == "1"); - } else if (attr.name() == "type") { - c->media_type = attr.value().toInt(); - } else if (attr.name() == "media") { - c->media_type = MEDIA_TYPE_FOOTAGE; - media_id = attr.value().toInt(); - } else if (attr.name() == "stream") { - stream_id = attr.value().toInt(); - } else if (attr.name() == "speed") { - c->speed = attr.value().toDouble(); - } else if (attr.name() == "maintainpitch") { - c->maintain_audio_pitch = (attr.value() == "1"); - } else if (attr.name() == "reverse") { - c->reverse = (attr.value() == "1"); - } else if (attr.name() == "opening") { - c->opening_transition = attr.value().toInt(); - } else if (attr.name() == "closing") { - c->closing_transition = attr.value().toInt(); - } else if (attr.name() == "sequence") { - c->media_type = MEDIA_TYPE_SEQUENCE; - - // since we haven't finished loading sequences, we defer linking this until later - c->media = NULL; - c->media_stream = attr.value().toInt(); - loaded_clips.append(c); - } - } - - // set media and media stream - switch (c->media_type) { - case MEDIA_TYPE_FOOTAGE: - if (media_id == 0) { - c->media = NULL; - } else { - for (int j=0;jsave_id == media_id) { - c->media = m; - c->media_stream = stream_id; - break; - } - } - } - break; - } - - // load links and effects - while (!(stream.name() == "clip" && stream.isEndElement()) && !stream.atEnd()) { - stream.readNext(); - if (stream.isStartElement()) { - if (stream.name() == "linked") { - while (!(stream.name() == "linked" && stream.isEndElement()) && !stream.atEnd()) { - stream.readNext(); - if (stream.name() == "link" && stream.isStartElement()) { - for (int k=0;klinked.append(link_attr.value().toInt()); - break; - } - } - } - } - } else if (stream.isStartElement() && (stream.name() == "effect" || stream.name() == "opening" || stream.name() == "closing")) { - // "opening" and "closing" are backwards compatibility code - load_effect(stream, c); - } - } - } - - s->clips.append(c); - } - } - - // correct links, clip IDs, transitions - for (int i=0;iclips.size();i++) { - // correct links - Clip* correct_clip = s->clips.at(i); - for (int j=0;jlinked.size();j++) { - bool found = false; - for (int k=0;kclips.size();k++) { - if (s->clips.at(k)->load_id == correct_clip->linked.at(j)) { - correct_clip->linked[j] = k; - found = true; - break; - } - } - if (!found) { - correct_clip->linked.removeAt(j); - j--; - if (QMessageBox::warning(this, "Invalid Clip Link", "This project contains an invalid clip link. It may be corrupt. Would you like to continue loading it?", QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) { - delete s; - return false; - } - } - } - - // re-link clips to transitions - if (correct_clip->opening_transition > -1) { - for (int j=0;jopening_transition) { - transition_data[j].otc = correct_clip; - } - } - } - if (correct_clip->closing_transition > -1) { - for (int j=0;jclosing_transition) { - transition_data[j].ctc = correct_clip; - } - } - } - } - - // create transitions - for (int i=0;itrack < 0) ? EFFECT_TYPE_VIDEO : EFFECT_TYPE_AUDIO); - if (meta == NULL) { - dout << "[WARNING] Failed to link transition with name:" << td.name; - if (td.otc != NULL) td.otc->opening_transition = -1; - if (td.ctc != NULL) td.ctc->closing_transition = -1; - } else { - int transition_index = create_transition(primary, secondary, meta); - primary->sequence->transitions.at(transition_index)->set_length(td.length); - if (td.otc != NULL) td.otc->opening_transition = transition_index; - if (td.ctc != NULL) td.ctc->closing_transition = transition_index; - } - } - } - - new_sequence(NULL, s, false, parent); - - loaded_sequences.append(s); - } - break; - } - } - } - } - break; - } - } - return true; -} - void Project::load_project(bool autorecovery) { new_project(); - /*LoadDialog ld; - ld.exec();*/ - - QFile file(project_url); - if (!file.open(QIODevice::ReadOnly)) { - dout << "[ERROR] Could not open file"; - return; - } - - /* set up directories to search for media - * most of the time, these will be the same but in - * case the project file has moved without the footage, - * we check both - */ - proj_dir = QFileInfo(project_url).absoluteDir(); - internal_proj_dir = QFileInfo(project_url).absoluteDir(); - internal_proj_url = project_url; - - QXmlStreamReader stream(&file); - - bool cont = false; - error_str.clear(); - show_err = true; - - // temp variables for loading - loaded_folders.clear(); - loaded_media.clear(); - loaded_media_items.clear(); - loaded_clips.clear(); - loaded_sequences.clear(); - open_seq = NULL; - - // get "element" count - int element_count = 0; - while (!stream.atEnd()) { - stream.readNextStartElement(); - if (stream.name() == "folder" - || stream.name() == "footage" - || stream.name() == "sequence" - || stream.name() == "clip" - || stream.name() == "effect") { - element_count++; - } - } - - // find project file version - cont = load_worker(file, stream, LOAD_TYPE_VERSION); - - // find project's internal URL - cont = load_worker(file, stream, LOAD_TYPE_URL); - if (autorecovery) { - QString orig_filename = internal_proj_url; - int insert_index = internal_proj_url.lastIndexOf(".ove", -1, Qt::CaseInsensitive); - if (insert_index == -1) insert_index = internal_proj_url.length(); - int counter = 1; - while (QFileInfo::exists(orig_filename)) { - orig_filename = internal_proj_url; - QString recover_text = "recovered"; - if (counter > 1) { - recover_text += " " + QString::number(counter); - } - orig_filename.insert(insert_index, " (" + recover_text + ")"); - counter++; - } - mainWindow->updateTitle(orig_filename); - } - - // load folders first - if (cont) { - cont = load_worker(file, stream, MEDIA_TYPE_FOLDER); - } - - // load media - if (cont) { - // since folders loaded correctly, organize them appropriately - for (int i=0;idata(0, Qt::UserRole + 4).toInt(); - if (parent > 0) { - find_loaded_folder_by_id(parent)->addChild(folder); - } else { - ui->treeWidget->addTopLevelItem(folder); - } - } - - cont = load_worker(file, stream, MEDIA_TYPE_FOOTAGE); - } - - // load sequences - if (cont) { - cont = load_worker(file, stream, MEDIA_TYPE_SEQUENCE); - } - - // attach nested sequence clips to their sequences - for (int i=0;imedia_stream == loaded_sequences.at(j)->save_id) { - loaded_clips.at(i)->media = loaded_sequences.at(j); - loaded_clips.at(i)->refresh(); - break; - } - } - } - - if (!cont) { - if (show_err) QMessageBox::critical(this, "Project Load Error", "Error loading project: " + error_str, QMessageBox::Ok); - } else if (stream.hasError()) { - dout << "[ERROR] Error parsing XML." << stream.errorString(); - QMessageBox::critical(this, "XML Parsing Error", "Couldn't load '" + project_url + "'. " + stream.errorString(), QMessageBox::Ok); - cont = false; - } - - if (cont) { - if (open_seq != NULL) set_sequence(open_seq); - - update_ui(false); - mainWindow->setWindowModified(autorecovery); - - for (int i=0;itreeWidget->topLevelItemCount() : parent->childCount(); - for (int i=0;itreeWidget->topLevelItem(i) : parent->child(i); - int item_type = get_type_from_tree(item); +void Project::save_folder(QXmlStreamWriter& stream, int type, bool set_ids_only, const QModelIndex& parent) { + bool root = (!parent.parent().isValid()); + for (int i=0;i(item.internalPointer()); - if (type == item_type) { - if (item_type == MEDIA_TYPE_FOLDER) { + if (type == m->get_type()) { + if (m->get_type() == MEDIA_TYPE_FOLDER) { if (set_ids_only) { - item->setData(0, Qt::UserRole + 3, folder_id); // saves a temporary ID for matching in the project file + m->temp_id = folder_id; // saves a temporary ID for matching in the project file folder_id++; } else { // if we're saving folders, save the folder stream.writeStartElement("folder"); - stream.writeAttribute("name", item->text(0)); - stream.writeAttribute("id", QString::number(item->data(0, Qt::UserRole + 3).toInt())); - if (item->parent() == NULL) { + stream.writeAttribute("name", m->get_name()); + stream.writeAttribute("id", QString::number(m->temp_id)); + if (!item.parent().isValid()) { stream.writeAttribute("parent", "0"); } else { - stream.writeAttribute("parent", QString::number(item->parent()->data(0, Qt::UserRole + 3).toInt())); + stream.writeAttribute("parent", QString::number(static_cast(item.parent().internalPointer())->temp_id)); } stream.writeEndElement(); } // save_folder(stream, item, type, set_ids_only); } else { - int folder = root ? 0 : parent->data(0, Qt::UserRole + 3).toInt(); + int folder = root ? 0 : static_cast(parent.internalPointer())->temp_id; if (type == MEDIA_TYPE_FOOTAGE) { - Media* m = get_footage_from_tree(item); - m->save_id = media_id; + Footage* f = m->to_footage(); + f->save_id = media_id; stream.writeStartElement("footage"); stream.writeAttribute("id", QString::number(media_id)); stream.writeAttribute("folder", QString::number(folder)); - stream.writeAttribute("name", m->name); - stream.writeAttribute("url", proj_dir.relativeFilePath(m->url)); - stream.writeAttribute("duration", QString::number(m->length)); - stream.writeAttribute("using_inout", QString::number(m->using_inout)); - stream.writeAttribute("in", QString::number(m->in)); - stream.writeAttribute("out", QString::number(m->out)); - for (int j=0;jvideo_tracks.size();j++) { - MediaStream* ms = m->video_tracks.at(j); + stream.writeAttribute("name", f->name); + stream.writeAttribute("url", proj_dir.relativeFilePath(f->url)); + stream.writeAttribute("duration", QString::number(f->length)); + stream.writeAttribute("using_inout", QString::number(f->using_inout)); + stream.writeAttribute("in", QString::number(f->in)); + stream.writeAttribute("out", QString::number(f->out)); + for (int j=0;jvideo_tracks.size();j++) { + FootageStream* ms = f->video_tracks.at(j); stream.writeStartElement("video"); stream.writeAttribute("id", QString::number(ms->file_index)); stream.writeAttribute("width", QString::number(ms->video_width)); @@ -1471,8 +798,8 @@ void Project::save_folder(QXmlStreamWriter& stream, QTreeWidgetItem* parent, int stream.writeAttribute("infinite", QString::number(ms->infinite_length)); stream.writeEndElement(); } - for (int j=0;jaudio_tracks.size();j++) { - MediaStream* ms = m->audio_tracks.at(j); + for (int j=0;jaudio_tracks.size();j++) { + FootageStream* ms = f->audio_tracks.at(j); stream.writeStartElement("audio"); stream.writeAttribute("id", QString::number(ms->file_index)); stream.writeAttribute("channels", QString::number(ms->audio_channels)); @@ -1483,7 +810,7 @@ void Project::save_folder(QXmlStreamWriter& stream, QTreeWidgetItem* parent, int stream.writeEndElement(); media_id++; } else if (type == MEDIA_TYPE_SEQUENCE) { - Sequence* s = get_sequence_from_tree(item); + Sequence* s = m->to_sequence(); if (set_ids_only) { s->save_id = sequence_id; sequence_id++; @@ -1538,14 +865,14 @@ void Project::save_folder(QXmlStreamWriter& stream, QTreeWidgetItem* parent, int stream.writeAttribute("maintainpitch", QString::number(c->maintain_audio_pitch)); stream.writeAttribute("reverse", QString::number(c->reverse)); - stream.writeAttribute("type", QString::number(c->media_type)); - switch (c->media_type) { + stream.writeAttribute("type", QString::number(c->media->get_type())); + switch (c->media->get_type()) { case MEDIA_TYPE_FOOTAGE: - stream.writeAttribute("media", QString::number(static_cast(c->media)->save_id)); + stream.writeAttribute("media", QString::number(c->media->to_footage()->save_id)); stream.writeAttribute("stream", QString::number(c->media_stream)); break; case MEDIA_TYPE_SEQUENCE: - stream.writeAttribute("sequence", QString::number(static_cast(c->media)->save_id)); + stream.writeAttribute("sequence", QString::number(c->media->to_sequence()->save_id)); break; } @@ -1578,8 +905,8 @@ void Project::save_folder(QXmlStreamWriter& stream, QTreeWidgetItem* parent, int } } - if (item_type == MEDIA_TYPE_FOLDER) { - save_folder(stream, item, type, set_ids_only); + if (m->get_type() == MEDIA_TYPE_FOLDER) { + save_folder(stream, type, set_ids_only, item); } } } @@ -1606,20 +933,20 @@ void Project::save_project(bool autorecovery) { stream.writeTextElement("url", project_url); proj_dir = QFileInfo(project_url).absoluteDir(); - save_folder(stream, NULL, MEDIA_TYPE_FOLDER, true); + save_folder(stream, MEDIA_TYPE_FOLDER, true); stream.writeStartElement("folders"); // folders - save_folder(stream, NULL, MEDIA_TYPE_FOLDER, false); + save_folder(stream, MEDIA_TYPE_FOLDER, false); stream.writeEndElement(); // folders stream.writeStartElement("media"); // media - save_folder(stream, NULL, MEDIA_TYPE_FOOTAGE, false); + save_folder(stream, MEDIA_TYPE_FOOTAGE, false); stream.writeEndElement(); // media - save_folder(stream, NULL, MEDIA_TYPE_SEQUENCE, true); + save_folder(stream, MEDIA_TYPE_SEQUENCE, true); stream.writeStartElement("sequences"); // sequences - save_folder(stream, NULL, MEDIA_TYPE_SEQUENCE, false); + save_folder(stream, MEDIA_TYPE_SEQUENCE, false); stream.writeEndElement();// sequences stream.writeEndElement(); // project @@ -1674,20 +1001,23 @@ void Project::add_recent_project(QString url) { save_recent_projects(); } -void Project::list_all_sequences_worker(QVector* list, QTreeWidgetItem* parent) { - int len = (parent == NULL) ? ui->treeWidget->topLevelItemCount() : parent->childCount(); +void Project::list_all_sequences_worker(QVector* list, Media* parent) { + int len = (parent == NULL) ? project_model.topLevelItemCount() : parent->childCount(); for (int i=0;itreeWidget->topLevelItem(i) : parent->child(i); - if (get_type_from_tree(item) == MEDIA_TYPE_SEQUENCE) { - list->append(get_sequence_from_tree(item)); - } else if (get_type_from_tree(item) == MEDIA_TYPE_FOLDER) { + Media* item = (parent == NULL) ? project_model.topLevelItem(i) : parent->child(i); + switch (item->get_type()) { + case MEDIA_TYPE_SEQUENCE: + list->append(item); + break; + case MEDIA_TYPE_FOLDER: list_all_sequences_worker(list, item); + break; } } } -QVector Project::list_all_project_sequences() { - QVector list; +QVector Project::list_all_project_sequences() { + QVector list; list_all_sequences_worker(&list, NULL); return list; } @@ -1695,36 +1025,43 @@ QVector Project::list_all_project_sequences() { #define THROBBER_LIMIT 20 #define THROBBER_SIZE 50 -MediaThrobber::MediaThrobber(QTreeWidgetItem *i) : pixmap(":/icons/throbber.png"), animation(0), item(i) { +MediaThrobber::MediaThrobber(Media *i) : pixmap(":/icons/throbber.png"), animation(0), item(i), animator(NULL) {} + +void MediaThrobber::start() { // set up throbber animation_update(); - animator.setInterval(20); - connect(&animator, SIGNAL(timeout()), this, SLOT(animation_update())); - animator.start(); + animator = new QTimer(this); + animator->setInterval(20); + connect(animator, SIGNAL(timeout()), this, SLOT(animation_update())); + animator->start(); } void MediaThrobber::animation_update() { if (animation == THROBBER_LIMIT) { animation = 0; } - item->setIcon(0, QIcon(pixmap.copy(THROBBER_SIZE*animation, 0, THROBBER_SIZE, THROBBER_SIZE))); + item->set_icon(QIcon(pixmap.copy(THROBBER_SIZE*animation, 0, THROBBER_SIZE, THROBBER_SIZE))); + project_model.update_data(); animation++; } void MediaThrobber::stop(int icon_type, bool replace) { - animator.stop(); + if (animator != NULL) { + animator->stop(); + delete animator; + } switch (icon_type) { - case ICON_TYPE_VIDEO: item->setIcon(0, QIcon(":/icons/videosource.png")); break; - case ICON_TYPE_AUDIO: item->setIcon(0, QIcon(":/icons/audiosource.png")); break; - case ICON_TYPE_IMAGE: item->setIcon(0, QIcon(":/icons/imagesource.png")); break; - case ICON_TYPE_ERROR: item->setIcon(0, QIcon::fromTheme("dialog-error")); break; + case ICON_TYPE_VIDEO: item->set_icon(QIcon(":/icons/videosource.png")); break; + case ICON_TYPE_AUDIO: item->set_icon(QIcon(":/icons/audiosource.png")); break; + case ICON_TYPE_IMAGE: item->set_icon(QIcon(":/icons/imagesource.png")); break; + case ICON_TYPE_ERROR: item->set_icon(QIcon(":/icons/error.png")); break; } // refresh all clips - QVector sequences = panel_project->list_all_project_sequences(); + QVector sequences = panel_project->list_all_project_sequences(); for (int i=0;ito_sequence(); for (int j=0;jclips.size();j++) { Clip* c = s->clips.at(j); if (c != NULL) { @@ -1737,82 +1074,6 @@ void MediaThrobber::stop(int icon_type, bool replace) { update_ui(replace); panel_project->source_table->viewport()->update(); - item->setData(0, Qt::UserRole + 5, 0); + item->throbber = NULL; deleteLater(); } - -QString get_interlacing_name(int interlacing) { - switch (interlacing) { - case VIDEO_PROGRESSIVE: return "None (Progressive)"; - case VIDEO_TOP_FIELD_FIRST: return "Top Field First"; - case VIDEO_BOTTOM_FIELD_FIRST: return "Bottom Field First"; - default: return "Invalid"; - } -} - -void update_footage_tooltip(QTreeWidgetItem *item, Media *media, QString error) { - QString tooltip = "Name: " + media->name + "\nFilename: " + media->url + "\n"; - - if (error.isEmpty()) { - if (media->video_tracks.size() > 0) { - tooltip += "Video Dimensions: "; - for (int i=0;ivideo_tracks.size();i++) { - if (i > 0) { - tooltip += ", "; - } - tooltip += QString::number(media->video_tracks.at(i)->video_width) + "x" + QString::number(media->video_tracks.at(i)->video_height); - } - tooltip += "\n"; - - if (!media->video_tracks.at(0)->infinite_length) { - tooltip += "Frame Rate: "; - for (int i=0;ivideo_tracks.size();i++) { - if (i > 0) { - tooltip += ", "; - } - if (media->video_tracks.at(i)->video_interlacing == VIDEO_PROGRESSIVE) { - tooltip += QString::number(media->video_tracks.at(i)->video_frame_rate); - } else { - tooltip += QString::number(media->video_tracks.at(i)->video_frame_rate * 2); - tooltip += " fields (" + QString::number(media->video_tracks.at(i)->video_frame_rate) + " frames)"; - } - } - tooltip += "\n"; - } - - tooltip += "Interlacing: "; - for (int i=0;ivideo_tracks.size();i++) { - if (i > 0) { - tooltip += ", "; - } - tooltip += get_interlacing_name(media->video_tracks.at(i)->video_interlacing); - } - } - - if (media->audio_tracks.size() > 0) { - tooltip += "\n"; - - tooltip += "Audio Frequency: "; - for (int i=0;iaudio_tracks.size();i++) { - if (i > 0) { - tooltip += ", "; - } - tooltip += QString::number(media->audio_tracks.at(i)->audio_frequency); - } - tooltip += "\n"; - - tooltip += "Audio Channels: "; - for (int i=0;iaudio_tracks.size();i++) { - if (i > 0) { - tooltip += ", "; - } - tooltip += get_channel_layout_name(media->audio_tracks.at(i)->audio_channels, media->audio_tracks.at(i)->audio_layout); - } - // tooltip += "\n"; - } - } else { - tooltip = error; - } - - item->setToolTip(0, tooltip); -} diff --git a/panels/project.h b/panels/project.h index 64c112d75..feac5f90c 100644 --- a/panels/project.h +++ b/panels/project.h @@ -6,13 +6,15 @@ #include #include -struct Media; +#include "project/projectmodel.h" + +struct Footage; struct Sequence; struct Clip; class Timeline; class Viewer; class SourceTable; -class QTreeWidgetItem; +class Media; class QXmlStreamWriter; class QXmlStreamReader; class QFile; @@ -30,18 +32,20 @@ extern QString project_url; extern QStringList recent_projects; extern QString recent_proj_file; -int get_type_from_tree(QTreeWidgetItem* item); +extern ProjectModel project_model; + +/*int get_type_from_tree(Media *item); void* get_media_from_tree(QTreeWidgetItem* item); -Media* get_footage_from_tree(QTreeWidgetItem* item); -void set_footage_of_tree(QTreeWidgetItem* item, Media* media); +Footage* get_footage_from_tree(QTreeWidgetItem* item); +void set_footage_of_tree(QTreeWidgetItem* item, Footage* 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 update_footage_tooltip(QTreeWidgetItem* item, Media* media, QString error = 0); +void set_item_to_folder(QTreeWidgetItem* item);*/ +//void update_footage_tooltip(Media* item, Footage* media, QString error = 0); -Sequence* create_sequence_from_media(QVector& media_list, QVector& type_list); +Sequence* create_sequence_from_media(QVector &media_list); -QString get_channel_layout_name(int channels, int layout); +QString get_channel_layout_name(int channels, uint64_t layout); QString get_interlacing_name(int interlacing); class Project : public QDockWidget @@ -53,27 +57,33 @@ public: ~Project(); bool is_focused(); void clear(); - void new_sequence(ComboAction *ca, Sequence* s, bool open, QTreeWidgetItem* parent); - QString get_next_sequence_name(QString start = 0); - void delete_media(QTreeWidgetItem* item); - void process_file_list(bool recursive, QStringList& files, QTreeWidgetItem *parent, QTreeWidgetItem* replace); - void replace_media(QTreeWidgetItem* item, QString filename); - QTreeWidgetItem* get_selected_folder(); - bool reveal_media(void* media, QTreeWidgetItem *parent = 0); + Media* new_sequence(ComboAction *ca, Sequence* s, bool open, Media* parent); + QString get_next_sequence_name(QString start = 0); + void process_file_list(bool recursive, QStringList& files, Media *parent, Media* replace); + void replace_media(Media* item, QString filename); + Media* get_selected_folder(); + bool reveal_media(void* media, QModelIndex parent = QModelIndex()); + void add_recent_project(QString url); void new_project(); void load_project(bool autorecovery); void save_project(bool autorecovery); - QTreeWidgetItem* new_folder(QString name); + Media* new_folder(QString name); void save_recent_projects(); - QVector list_all_project_sequences(); + QVector list_all_project_sequences(); SourceTable* source_table; - QVector last_imported_media; + QVector last_imported_media; + + //Media *new_item(); + + void start_preview_generator(Media* item, bool replacing); + + Ui::Project *ui; public slots: void import_dialog(); void delete_selected_media(); @@ -83,48 +93,33 @@ public slots: void replace_clip_media(); void open_properties(); private: - Ui::Project *ui; - QTreeWidgetItem* new_item(); - bool load_worker(QFile& f, QXmlStreamReader& stream, int type); - void save_folder(QXmlStreamWriter& stream, QTreeWidgetItem* parent, int type, bool set_ids_only); - bool show_err; - QString error_str; + void save_folder(QXmlStreamWriter& stream, int type, bool set_ids_only, const QModelIndex &parent = QModelIndex()); int folder_id; int media_id; int sequence_id; - Sequence* open_seq; - QVector loaded_folders; - QVector loaded_media; - QVector loaded_media_items; - QVector loaded_clips; - QVector loaded_sequences; - QTreeWidgetItem* find_loaded_folder_by_id(int id); - void add_recent_project(QString url); - void get_all_media_from_table(QList items, QList& list, int type); - void start_preview_generator(QTreeWidgetItem* item, Media* media, bool replacing); - void list_all_sequences_worker(QVector* list, QTreeWidgetItem* parent); + void get_all_media_from_table(QList items, QList &list, int type); + void list_all_sequences_worker(QVector *list, Media* parent); QString get_file_name_from_path(const QString &path); QDir proj_dir; - QDir internal_proj_dir; - QString internal_proj_url; private slots: - void rename_media(QTreeWidgetItem* item, int column); + void rename_media(Media* item, int column); void clear_recent_projects(); }; class MediaThrobber : public QObject { Q_OBJECT public: - MediaThrobber(QTreeWidgetItem*); + MediaThrobber(Media*); public slots: + void start(); void stop(int, bool replace); private slots: void animation_update(); private: QPixmap pixmap; int animation; - QTreeWidgetItem* item; - QTimer animator; + Media* item; + QTimer* animator; }; #endif // PROJECT_H diff --git a/panels/project.ui b/panels/project.ui index cd064d9b0..fbf5d7094 100644 --- a/panels/project.ui +++ b/panels/project.ui @@ -34,55 +34,19 @@ 0 - + true QAbstractItemView::NoEditTriggers - - false - QAbstractItemView::DragDrop QAbstractItemView::ExtendedSelection - - QAbstractItemView::SelectRows - - - true - - - true - - - false - - - false - - - true - - - - Name - - - - - Duration - - - - - Rate - - @@ -91,7 +55,7 @@ SourceTable - QTreeWidget + QTreeView
ui/sourcetable.h
diff --git a/panels/timeline.cpp b/panels/timeline.cpp index e9279206f..ba02ffefe 100644 --- a/panels/timeline.cpp +++ b/panels/timeline.cpp @@ -14,10 +14,11 @@ #include "playback/playback.h" #include "ui_viewer.h" #include "project/undo.h" +#include "project/media.h" #include "io/config.h" #include "project/effect.h" #include "project/transition.h" -#include "io/media.h" +#include "project/footage.h" #include "io/clipboard.h" #include "debug.h" @@ -160,23 +161,24 @@ void Timeline::toggle_show_all() { } } -void Timeline::create_ghosts_from_media(Sequence* seq, long entry_point, QVector& media_list, QVector& type_list) { +void Timeline::create_ghosts_from_media(Sequence* seq, long entry_point, QVector& media_list) { video_ghosts = false; audio_ghosts = false; for (int i=0;iget_type()) { case MEDIA_TYPE_FOOTAGE: - m = static_cast(media_list.at(i)); + m = medium->to_footage(); media = m; can_import = m->ready; if (m->using_inout) { @@ -187,7 +189,7 @@ void Timeline::create_ghosts_from_media(Sequence* seq, long entry_point, QVector } break; case MEDIA_TYPE_SEQUENCE: - s = static_cast(media_list.at(i)); + s = medium->to_sequence(); sequence_length = s->getEndFrame(); if (seq != NULL) sequence_length = refactor_frame_number(sequence_length, s->frame_rate, seq->frame_rate); media = s; @@ -203,15 +205,14 @@ void Timeline::create_ghosts_from_media(Sequence* seq, long entry_point, QVector if (can_import) { Ghost g; - g.clip = -1; - g.media_type = type_list.at(i); + g.clip = -1; g.trimming = false; g.old_clip_in = g.clip_in = default_clip_in; - g.media = media; + g.media = medium; g.in = entry_point; g.transition = NULL; - switch (type_list.at(i)) { + switch (medium->get_type()) { case MEDIA_TYPE_FOOTAGE: // is video source a still image? if (m->video_tracks.size() > 0 && m->video_tracks[0]->infinite_length && m->audio_tracks.size() == 0) { @@ -274,15 +275,14 @@ void Timeline::add_clips_from_ghosts(ComboAction* ca, Sequence* s) { earliest_point = qMin(earliest_point, g.in); Clip* c = new Clip(s); - c->media = g.media; - c->media_type = g.media_type; + c->media = g.media; c->media_stream = g.media_stream; c->timeline_in = g.in; c->timeline_out = g.out; c->clip_in = g.clip_in; c->track = g.track; - if (c->media_type == MEDIA_TYPE_FOOTAGE) { - Media* m = static_cast(c->media); + if (c->media->get_type() == MEDIA_TYPE_FOOTAGE) { + Footage* m = c->media->to_footage(); if (m->video_tracks.size() == 0) { // audio only (greenish) c->color_r = 128; @@ -300,13 +300,13 @@ void Timeline::add_clips_from_ghosts(ComboAction* ca, Sequence* s) { c->color_b = 192; } c->name = m->name; - } else if (c->media_type == MEDIA_TYPE_SEQUENCE) { + } else if (c->media->get_type() == MEDIA_TYPE_SEQUENCE) { // sequence (red?ish?) c->color_r = 192; c->color_g = 128; c->color_b = 128; - Sequence* media = static_cast(c->media); + Sequence* media = c->media->to_sequence(); c->name = media->name; } c->recalculateMaxLength(); diff --git a/panels/timeline.h b/panels/timeline.h index db73a87da..0bbd50d00 100644 --- a/panels/timeline.h +++ b/panels/timeline.h @@ -22,12 +22,13 @@ class SourceTable; class ViewerWidget; class ComboAction; class Effect; +class Media; class Transition; struct EffectMeta; struct Sequence; struct Clip; -struct Media; -struct MediaStream; +struct Footage; +struct FootageStream; long refactor_frame_number(long framenumber, double source_frame_rate, double target_frame_rate); int getScreenPointFromFrame(double zoom, long frame); @@ -49,8 +50,7 @@ struct Ghost { long old_clip_in; // importing variables - void* media; - int media_type; + Media* media; int media_stream; // other variables @@ -102,7 +102,7 @@ public: void next_cut(); void toggle_show_all(); - void create_ghosts_from_media(Sequence *seq, long entry_point, QVector &media_list, QVector &type_list); + void create_ghosts_from_media(Sequence *seq, long entry_point, QVector &media_list); void add_clips_from_ghosts(ComboAction *ca, Sequence *s); int getTimelineScreenPointFromFrame(long frame); diff --git a/panels/viewer.cpp b/panels/viewer.cpp index 9e2fccde9..346835fbc 100644 --- a/panels/viewer.cpp +++ b/panels/viewer.cpp @@ -8,7 +8,8 @@ #include "project/clip.h" #include "panels/panels.h" #include "io/config.h" -#include "io/media.h" +#include "project/footage.h" +#include "project/media.h" #include "project/undo.h" #include "ui/audiomonitor.h" #include "ui_timeline.h" @@ -33,6 +34,7 @@ Viewer::Viewer(QWidget *parent) : QDockWidget(parent), playing(false), just_played(false), + media(NULL), seq(NULL), ui(new Ui::Viewer), created_sequence(false), @@ -46,7 +48,7 @@ Viewer::Viewer(QWidget *parent) : ui->glViewerPane->child = ui->openGLWidget; ui->openGLWidget->viewer = this; viewer_widget = ui->openGLWidget; - set_media(MEDIA_TYPE_SEQUENCE, NULL); + set_media(NULL); ui->currentTimecode->setEnabled(false); ui->currentTimecode->set_minimum_value(0); @@ -344,23 +346,23 @@ void Viewer::pause() { // add it to the sequence Clip* c = new Clip(seq); - Media* m = panel_project->last_imported_media.at(0); + Media* m = panel_project->last_imported_media.at(0); + Footage* f = m->to_footage(); - m->ready_lock.lock(); + f->ready_lock.lock(); - c->media = m; // latest media - c->media_type = MEDIA_TYPE_FOOTAGE; + c->media = m; // latest media c->media_stream = 0; c->timeline_in = recording_start; - c->timeline_out = m->get_length_in_frames(seq->frame_rate); + c->timeline_out = f->get_length_in_frames(seq->frame_rate); c->clip_in = 0; c->track = recording_track; c->color_r = 128; c->color_g = 192; c->color_b = 128; - c->name = m->name; + c->name = m->get_name(); - m->ready_lock.unlock(); + f->ready_lock.unlock(); QVector add_clips; add_clips.append(c); @@ -414,83 +416,83 @@ void Viewer::set_out_point() { ui->headers->set_out_point(seq->playhead); } -void Viewer::set_media(int type, void* media) { +void Viewer::set_media(Media* m) { main_sequence = false; - clean_created_seq(); - switch (type) { - case MEDIA_TYPE_FOOTAGE: - { - Media* footage = static_cast(media); + media = m; + clean_created_seq(); + if (media != NULL) { + switch (media->get_type()) { + case MEDIA_TYPE_FOOTAGE: + { + Footage* footage = media->to_footage(); - seq = new Sequence(); - created_sequence = true; - seq->wrapper_sequence = true; - seq->name = footage->name; + seq = new Sequence(); + created_sequence = true; + seq->wrapper_sequence = true; + seq->name = footage->name; - seq->using_workarea = footage->using_inout; - if (footage->using_inout) { - seq->workarea_in = footage->in; - seq->workarea_out = footage->out; - } + seq->using_workarea = footage->using_inout; + if (footage->using_inout) { + seq->workarea_in = footage->in; + seq->workarea_out = footage->out; + } - seq->frame_rate = 30; + seq->frame_rate = 30; - if (footage->video_tracks.size() > 0) { - MediaStream* video_stream = footage->video_tracks.at(0); - seq->width = video_stream->video_width; - seq->height = video_stream->video_height; - if (video_stream->video_frame_rate > 0 && !video_stream->infinite_length) seq->frame_rate = video_stream->video_frame_rate; + if (footage->video_tracks.size() > 0) { + FootageStream* video_stream = footage->video_tracks.at(0); + seq->width = video_stream->video_width; + seq->height = video_stream->video_height; + if (video_stream->video_frame_rate > 0 && !video_stream->infinite_length) seq->frame_rate = video_stream->video_frame_rate; - Clip* c = new Clip(seq); - c->media = footage; - c->media_type = type; - c->media_stream = video_stream->file_index; - c->timeline_in = 0; - c->timeline_out = footage->get_length_in_frames(seq->frame_rate); - if (c->timeline_out <= 0) c->timeline_out = 150; - c->track = -1; - c->clip_in = 0; - c->recalculateMaxLength(); - seq->clips.append(c); - } else { - seq->width = 1920; - seq->height = 1080; - } + Clip* c = new Clip(seq); + c->media = media; + c->media_stream = video_stream->file_index; + c->timeline_in = 0; + c->timeline_out = footage->get_length_in_frames(seq->frame_rate); + if (c->timeline_out <= 0) c->timeline_out = 150; + c->track = -1; + c->clip_in = 0; + c->recalculateMaxLength(); + seq->clips.append(c); + } else { + seq->width = 1920; + seq->height = 1080; + } - if (footage->audio_tracks.size() > 0) { - MediaStream* audio_stream = footage->audio_tracks.at(0); - seq->audio_frequency = audio_stream->audio_frequency; + if (footage->audio_tracks.size() > 0) { + FootageStream* audio_stream = footage->audio_tracks.at(0); + seq->audio_frequency = audio_stream->audio_frequency; - Clip* c = new Clip(seq); - c->media = footage; - c->media_type = type; - c->media_stream = audio_stream->file_index; - c->timeline_in = 0; - c->timeline_out = footage->get_length_in_frames(seq->frame_rate); - c->track = 0; - c->clip_in = 0; - c->recalculateMaxLength(); - seq->clips.append(c); + Clip* c = new Clip(seq); + c->media = media; + c->media_stream = audio_stream->file_index; + c->timeline_in = 0; + c->timeline_out = footage->get_length_in_frames(seq->frame_rate); + c->track = 0; + c->clip_in = 0; + c->recalculateMaxLength(); + seq->clips.append(c); - if (footage->video_tracks.size() == 0) { - viewer_widget->waveform = true; - viewer_widget->waveform_clip = c; - viewer_widget->waveform_ms = audio_stream; - viewer_widget->update(); - } - } else { - seq->audio_frequency = 48000; - } + if (footage->video_tracks.size() == 0) { + viewer_widget->waveform = true; + viewer_widget->waveform_clip = c; + viewer_widget->waveform_ms = audio_stream; + viewer_widget->update(); + } + } else { + seq->audio_frequency = 48000; + } - seq->audio_layout = AV_CH_LAYOUT_STEREO; - - set_sequence(false, seq); - } - break; - case MEDIA_TYPE_SEQUENCE: - set_sequence(false, static_cast(media)); - break; - } + seq->audio_layout = AV_CH_LAYOUT_STEREO; + } + break; + case MEDIA_TYPE_SEQUENCE: + seq = media->to_sequence(); + break; + } + } + set_sequence(false, seq); } void Viewer::on_pushButton_clicked() { diff --git a/panels/viewer.h b/panels/viewer.h index 547efdf7b..3bd461ff9 100644 --- a/panels/viewer.h +++ b/panels/viewer.h @@ -6,6 +6,7 @@ class Timeline; class ViewerWidget; +class Media; struct Sequence; namespace Ui { @@ -26,7 +27,7 @@ public: bool is_focused(); void set_main_sequence(); - void set_media(int type, void* media); + void set_media(Media *m); void compose(); void set_playpause_icon(bool play); void update_playhead_timecode(long p); @@ -65,6 +66,7 @@ public: ViewerWidget* viewer_widget; + Media* media; Sequence* seq; Ui::Viewer *ui; diff --git a/playback/cacher.cpp b/playback/cacher.cpp index 0c18f5fc4..ff6fd9fd3 100644 --- a/playback/cacher.cpp +++ b/playback/cacher.cpp @@ -3,7 +3,7 @@ #include "project/clip.h" #include "project/sequence.h" #include "project/transition.h" -#include "io/media.h" +#include "project/footage.h" #include "playback/audio.h" #include "playback/playback.h" #include "project/effect.h" @@ -12,6 +12,7 @@ #include "playback/audio.h" #include "panels/panels.h" #include "panels/viewer.h" +#include "project/media.h" #include "debug.h" extern "C" { @@ -49,7 +50,7 @@ void apply_audio_effects(Clip* c, double timecode_start, AVFrame* frame, int nb_ if (e->is_enabled()) e->process_audio(timecode_start, timecode_end, frame->data[0], nb_bytes, 2); } if (c->get_opening_transition() != NULL) { - if (c->media_type == MEDIA_TYPE_FOOTAGE) { + if (c->media != NULL && 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) { @@ -61,7 +62,7 @@ void apply_audio_effects(Clip* c, double timecode_start, AVFrame* frame, int nb_ } } if (c->get_closing_transition() != NULL) { - if (c->media_type == MEDIA_TYPE_FOOTAGE) { + if (c->media != NULL && 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; @@ -114,250 +115,246 @@ void cache_audio_worker(Clip* c, bool scrubbing, QVector& nests) { AVFrame* frame; int nb_bytes = INT_MAX; - switch (c->media_type) { - case MEDIA_TYPE_FOOTAGE: - { - double timebase = av_q2d(c->stream->time_base); + if (c->media == NULL) { + 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; + 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 (c->reverse && !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 (c->reverse && !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) { - dout << "[ERROR] 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) { + dout << "[ERROR] 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 (!c->reverse) { - c->reached_end = true; - } else { - } - } else { - dout << "[WARNING] Raw audio frame data could not be retrieved." << ret; - c->reached_end = true; - } - break; - } - } + // TODO revise usage of reached_end in audio + if (!c->reverse) { + c->reached_end = true; + } else { + } + } else { + dout << "[WARNING] Raw audio frame data could not be retrieved." << ret; + c->reached_end = true; + } + break; + } + } - if (ret < 0) { - if (ret != AVERROR_EOF) { - dout << "[ERROR] Could not pull from filtergraph"; - c->reached_end = true; - break; - } else { + if (ret < 0) { + if (ret != AVERROR_EOF) { + dout << "[ERROR] 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 (!c->reverse) break; - } - } + if (!c->reverse) break; + } + } - if (c->reverse) { - if (loop > 1) { - AVFrame* rev_frame = c->queue.at(1); - if (ret != AVERROR_EOF) { - if (loop == 2) { + if (c->reverse) { + 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) { + if (cutoff > 0) { #ifdef AUDIOWARNINGS 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 rev_frame->nb_samples = qRound64(static_cast(c->reverse_target - rev_frame->pts) / c->stream->codecpar->sample_rate * (audio_output->format().sampleRate() / c->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); + 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)*audio_output->format().sampleRate()); - c->frame_sample_index = nb_samples * 4; + 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 (c->reverse) c->frame_sample_index = nb_bytes - c->frame_sample_index; + if (c->reverse) 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 (c->reverse) frame = c->queue.at(1); + if (c->reverse) 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 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, audio_output->format().sampleRate()) + audio_ibuffer_timecode + ((double)c->get_clip_in_with_transition()/c->sequence->frame_rate) - ((double)timeline_in/last_fr), frame, nb_bytes, nests); - } - } - break; - case MEDIA_TYPE_TONE: - 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; - } - } - break; - default: // shouldn't ever get here - dout << "[ERROR] Tried to cache a non-footage/tone clip"; - return; + } + } else { + // shouldn't ever get here + dout << "[ERROR] Tried to cache a non-footage/tone clip"; + return; } // mix audio into internal buffer @@ -452,8 +449,8 @@ void cache_video_worker(Clip* c, long playhead) { while (true) { AVFrame* frame = av_frame_alloc(); - Media* media = static_cast(c->media); - MediaStream* ms = media->get_stream_from_file_index(true, c->media_stream); + Footage* media = c->media->to_footage(); + 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 @@ -539,85 +536,83 @@ void cache_video_worker(Clip* c, long playhead) { 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 - switch (c->media_type) { - case MEDIA_TYPE_FOOTAGE: - { - MediaStream* ms = static_cast(c->media)->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_clear(); + if (c->media == NULL) { + 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 { + 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_clear(); - // seeks to nearest keyframe (target_frame represents internal clip frame) + // 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))); + 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) { - dout << "[WARNING] Seeking terminated prematurely"; - break; + av_frame_unref(c->frame); + int ret = retrieve_next_frame(c, c->frame); + if (ret < 0) { + dout << "[WARNING] 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; + 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) - 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)); if (c->reverse) { - c->reverse_target = timestamp; - timestamp -= av_q2d(av_inv_q(c->stream->time_base)); + 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; - } - } - } - break; - case MEDIA_TYPE_TONE: - c->reached_end = false; - c->audio_target_frame = target_frame; - c->frame_sample_index = -1; - c->frame->pts = 0; - break; - } + c->audio_target_frame = target_frame; + c->frame_sample_index = -1; + c->audio_just_reset = true; + } + } + } } Cacher::Cacher(Clip* c) : clip(c) {} @@ -625,85 +620,97 @@ Cacher::Cacher(Clip* c) : clip(c) {} AVSampleFormat sample_format = AV_SAMPLE_FMT_S16; void open_clip_worker(Clip* clip) { - switch (clip->media_type) { - case MEDIA_TYPE_FOOTAGE: - { - // opens file resource for FFmpeg and prepares Clip struct for playback - Media* m = static_cast(clip->media); - QByteArray ba = m->url.toUtf8(); - const char* filename = ba.constData(); - MediaStream* ms = m->get_stream_from_file_index(clip->track < 0, clip->media_stream); + if (clip->media == NULL) { + 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 = audio_output->format().sampleRate(); + clip->frame->nb_samples = 2048; + av_frame_make_writable(clip->frame); + if (av_frame_get_buffer(clip->frame, 0)) { + dout << "[ERROR] 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 + Footage* m = clip->media->to_footage(); + QByteArray ba = m->url.toUtf8(); + const char* filename = ba.constData(); + FootageStream* ms = m->get_stream_from_file_index(clip->track < 0, clip->media_stream); - int errCode = avformat_open_input( - &clip->formatCtx, - filename, - NULL, - NULL - ); - if (errCode != 0) { - char err[1024]; - av_strerror(errCode, err, 1024); - dout << "[ERROR] Could not open" << filename << "-" << err; - return; - } + int errCode = avformat_open_input( + &clip->formatCtx, + filename, + NULL, + NULL + ); + if (errCode != 0) { + char err[1024]; + av_strerror(errCode, err, 1024); + dout << "[ERROR] Could not open" << filename << "-" << err; + return; + } - errCode = avformat_find_stream_info(clip->formatCtx, NULL); - if (errCode < 0) { - char err[1024]; - av_strerror(errCode, err, 1024); - dout << "[ERROR] Could not open" << filename << "-" << err; - return; - } + errCode = avformat_find_stream_info(clip->formatCtx, NULL); + if (errCode < 0) { + char err[1024]; + av_strerror(errCode, err, 1024); + dout << "[ERROR] 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); - clip->max_queue_size = (ms->infinite_length) ? 1 : qCeil(ms->video_frame_rate*0.5); - if (ms->video_interlacing != VIDEO_PROGRESSIVE) clip->max_queue_size *= 2; + clip->max_queue_size = (ms->infinite_length) ? 1 : qCeil(ms->video_frame_rate*0.5); + if (ms->video_interlacing != VIDEO_PROGRESSIVE) clip->max_queue_size *= 2; clip->opts = NULL; - // optimized decoding settings + // optimized decoding settings if (clip->stream->codecpar->codec_id != AV_CODEC_ID_PNG && - clip->stream->codecpar->codec_id != AV_CODEC_ID_APNG && - clip->stream->codecpar->codec_id != AV_CODEC_ID_TIFF && - clip->stream->codecpar->codec_id != AV_CODEC_ID_PSD) { + clip->stream->codecpar->codec_id != AV_CODEC_ID_APNG && + clip->stream->codecpar->codec_id != AV_CODEC_ID_TIFF && + clip->stream->codecpar->codec_id != AV_CODEC_ID_PSD) { av_dict_set(&clip->opts, "threads", "auto", 0); - } - if (clip->stream->codecpar->codec_id == AV_CODEC_ID_H264) { + } + 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 + // Open codec if (avcodec_open2(clip->codecCtx, clip->codec, &clip->opts) < 0) { - dout << "[ERROR] Could not open codec"; - } + dout << "[ERROR] Could not open codec"; + } - // allocate filtergraph - clip->filter_graph = avfilter_graph_alloc(); - if (clip->filter_graph == NULL) { - dout << "[ERROR] Could not create filtergraph"; - } - char filter_args[512]; + // allocate filtergraph + clip->filter_graph = avfilter_graph_alloc(); + if (clip->filter_graph == NULL) { + dout << "[ERROR] 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 - ); + 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, NULL, clip->filter_graph); - avfilter_graph_create_filter(&clip->buffersink_ctx, avfilter_get_by_name("buffersink"), "out", NULL, NULL, clip->filter_graph); + avfilter_graph_create_filter(&clip->buffersrc_ctx, avfilter_get_by_name("buffer"), "in", filter_args, NULL, clip->filter_graph); + avfilter_graph_create_filter(&clip->buffersink_ctx, avfilter_get_by_name("buffersink"), "out", NULL, NULL, clip->filter_graph); /*enum AVPixelFormat sinkpix_fmts[] = { static_cast(dest_format), AV_PIX_FMT_NONE }; if (av_opt_set_int_list(clip->buffersink_ctx, "pix_fmts", sinkpix_fmts, AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN) < 0) { @@ -713,14 +720,14 @@ void open_clip_worker(Clip* clip) { AVFilterContext* last_filter = clip->buffersrc_ctx; if (ms->video_interlacing != VIDEO_PROGRESSIVE) { - AVFilterContext* yadif_filter; - char yadif_args[100]; + AVFilterContext* yadif_filter; + char yadif_args[100]; snprintf(yadif_args, sizeof(yadif_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", yadif_args, NULL, clip->filter_graph); + avfilter_graph_create_filter(&yadif_filter, avfilter_get_by_name("yadif"), "yadif", yadif_args, NULL, clip->filter_graph); avfilter_link(last_filter, 0, yadif_filter, 0); last_filter = yadif_filter; - } + } /* stabilization code one day */ bool stabilize = false; @@ -743,110 +750,95 @@ void open_clip_worker(Clip* clip) { avfilter_link(format_conv, 0, clip->buffersink_ctx, 0); avfilter_graph_config(clip->filter_graph, NULL); - } 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); + } 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) { - AVFrame* reverse_frame = av_frame_alloc(); + // set up cache + clip->queue.append(av_frame_alloc()); + if (clip->reverse) { + AVFrame* reverse_frame = av_frame_alloc(); - reverse_frame->format = sample_format; + reverse_frame->format = sample_format; reverse_frame->nb_samples = audio_output->format().sampleRate()*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->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, NULL, clip->filter_graph); - avfilter_graph_create_filter(&clip->buffersink_ctx, avfilter_get_by_name("abuffersink"), "out", NULL, NULL, clip->filter_graph); + avfilter_graph_create_filter(&clip->buffersrc_ctx, avfilter_get_by_name("abuffer"), "in", filter_args, NULL, clip->filter_graph); + avfilter_graph_create_filter(&clip->buffersink_ctx, avfilter_get_by_name("abuffersink"), "out", NULL, NULL, 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) { - dout << "[ERROR] 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) { + dout << "[ERROR] 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) { - dout << "[ERROR] 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) { + dout << "[ERROR] Could not set output sample format"; + } - int target_sample_rate = audio_output->format().sampleRate(); + int target_sample_rate = audio_output->format().sampleRate(); - if (qFuzzyCompare(clip->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(clip->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 (clip->speed != 1.0) { - double base = (clip->speed > 1.0) ? 2.0 : 0.5; + if (clip->speed != 1.0) { + double base = (clip->speed > 1.0) ? 2.0 : 0.5; - double speedlog = log(clip->speed) / log(base); - int whole2 = qFloor(speedlog); - speedlog -= whole2; + double speedlog = log(clip->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 = NULL; - avfilter_graph_create_filter(&last_filter, avfilter_get_by_name("atempo"), "atempo", speed_param, NULL, clip->filter_graph); - avfilter_link(previous_filter, 0, last_filter, 0); - } + snprintf(speed_param, sizeof(speed_param), "%f", qPow(base, speedlog)); + last_filter = NULL; + avfilter_graph_create_filter(&last_filter, avfilter_get_by_name("atempo"), "atempo", speed_param, NULL, 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 / clip->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 / clip->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) { - dout << "[ERROR] 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) { + dout << "[ERROR] Could not set output sample rates"; + } - avfilter_graph_config(clip->filter_graph, NULL); + avfilter_graph_config(clip->filter_graph, NULL); - clip->audio_reset = true; - } + clip->audio_reset = true; + } - clip->frame = av_frame_alloc(); - } - break; - case MEDIA_TYPE_TONE: - 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 = audio_output->format().sampleRate(); - clip->frame->nb_samples = 2048; - av_frame_make_writable(clip->frame); - if (av_frame_get_buffer(clip->frame, 0)) { - dout << "[ERROR] Could not allocate buffer for tone clip"; - } - clip->audio_reset = true; - break; - } + clip->frame = av_frame_alloc(); + } for (int i=0;ieffects.size();i++) { clip->effects.at(i)->open(); @@ -864,24 +856,23 @@ void cache_clip_worker(Clip* clip, long playhead, bool reset, bool scrubbing, QV clip->audio_reset = false; } - switch (clip->media_type) { - case MEDIA_TYPE_FOOTAGE: + if (clip->media == NULL) { + if (clip->track >= 0) { + cache_audio_worker(clip, scrubbing, nests); + } + } 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); } - break; - case MEDIA_TYPE_TONE: - cache_audio_worker(clip, scrubbing, nests); - break; } } void close_clip_worker(Clip* clip) { clip->finished_opening = false; - if (clip->media_type == MEDIA_TYPE_FOOTAGE) { + if (clip->media != NULL && clip->media->get_type() == MEDIA_TYPE_FOOTAGE) { clip->queue_clear(); avfilter_graph_free(&clip->filter_graph); diff --git a/playback/playback.cpp b/playback/playback.cpp index 638a4ec43..73c3ceb91 100644 --- a/playback/playback.cpp +++ b/playback/playback.cpp @@ -2,7 +2,7 @@ #include "project/clip.h" #include "project/sequence.h" -#include "io/media.h" +#include "project/footage.h" #include "playback/audio.h" #include "playback/cacher.h" #include "panels/panels.h" @@ -10,6 +10,7 @@ #include "panels/viewer.h" #include "project/effect.h" #include "panels/effectcontrols.h" +#include "project/media.h" #include "debug.h" extern "C" { @@ -32,36 +33,34 @@ extern "C" { bool texture_failed = false; bool rendering = false; -void open_clip(Clip* clip, bool multithreaded) { - switch (clip->media_type) { - case MEDIA_TYPE_FOOTAGE: - case MEDIA_TYPE_TONE: - clip->multithreaded = multithreaded; - if (multithreaded) { - if (clip->open_lock.tryLock()) { - // maybe keep cacher instance in memory while clip exists for performance? - clip->cacher = new Cacher(clip); - QObject::connect(clip->cacher, SIGNAL(finished()), clip->cacher, SLOT(deleteLater())); - clip->cacher->start((clip->track < 0) ? QThread::NormalPriority : QThread::TimeCriticalPriority); - } - } else { - clip->finished_opening = false; - clip->open = true; +bool clip_uses_cacher(Clip* clip) { + return (clip->media == NULL && clip->track >= 0) || (clip->media != NULL && clip->media->get_type() == MEDIA_TYPE_FOOTAGE); +} - open_clip_worker(clip); - } - break; - case MEDIA_TYPE_SEQUENCE: - case MEDIA_TYPE_SOLID: - clip->open = true; - break; - } +void open_clip(Clip* clip, bool multithreaded) { + if (clip_uses_cacher(clip)) { + clip->multithreaded = multithreaded; + if (multithreaded) { + if (clip->open_lock.tryLock()) { + // maybe keep cacher instance in memory while clip exists for performance? + clip->cacher = new Cacher(clip); + QObject::connect(clip->cacher, SIGNAL(finished()), clip->cacher, SLOT(deleteLater())); + clip->cacher->start((clip->track < 0) ? QThread::NormalPriority : QThread::TimeCriticalPriority); + } + } else { + clip->finished_opening = false; + clip->open = true; + + open_clip_worker(clip); + } + } else { + clip->open = true; + } } void close_clip(Clip* clip) { // destroy opengl texture in main thread - if (clip->texture != NULL) { - clip->texture->destroy(); + if (clip->texture != NULL) { delete clip->texture; clip->texture = NULL; } @@ -77,26 +76,22 @@ void close_clip(Clip* clip) { clip->fbo = NULL; } - switch (clip->media_type) { - case MEDIA_TYPE_FOOTAGE: - case MEDIA_TYPE_TONE: - if (clip->multithreaded) { - clip->cacher->caching = false; - clip->can_cache.wakeAll(); - } else { - close_clip_worker(clip); - } - break; - case MEDIA_TYPE_SEQUENCE: - closeActiveClips(static_cast(clip->media), false); - case MEDIA_TYPE_SOLID: - clip->open = false; - break; - } + if (clip_uses_cacher(clip)) { + if (clip->multithreaded) { + clip->cacher->caching = false; + clip->can_cache.wakeAll(); + } else { + close_clip_worker(clip); + } + } else { + if (clip->media != NULL && clip->media->get_type() == MEDIA_TYPE_SEQUENCE) + closeActiveClips(clip->media->to_sequence(), false); + clip->open = false; + } } void cache_clip(Clip* clip, long playhead, bool reset, bool scrubbing, QVector& nests) { - if (clip->media_type == MEDIA_TYPE_FOOTAGE || clip->media_type == MEDIA_TYPE_TONE) { + if (clip_uses_cacher(clip)) { if (clip->multithreaded) { clip->cacher->playhead = playhead; clip->cacher->reset = reset; @@ -113,7 +108,7 @@ void cache_clip(Clip* clip, long playhead, bool reset, bool scrubbing, QVectorfinished_opening) { - MediaStream* ms = static_cast(c->media)->get_stream_from_file_index(c->track < 0, c->media_stream); + FootageStream* ms = c->media->to_footage()->get_stream_from_file_index(c->track < 0, c->media_stream); int64_t target_pts = qMax(static_cast(0), playhead_to_timestamp(c, playhead)); int64_t second_pts = qRound64(av_q2d(av_inv_q(c->stream->time_base))); @@ -318,11 +313,11 @@ void closeActiveClips(Sequence *s, bool wait) { if (s != NULL) { for (int i=0;iclips.size();i++) { Clip* c = s->clips.at(i); - if (c != NULL) { - if (c->media_type == MEDIA_TYPE_SEQUENCE) { - closeActiveClips(static_cast(c->media), wait); + if (c != NULL) { + if (c->media != NULL && c->media->get_type() == MEDIA_TYPE_SEQUENCE) { + closeActiveClips(c->media->to_sequence(), wait); close_clip(c); - } else if (c->media_type == MEDIA_TYPE_FOOTAGE && c->open) { + } else if (clip_uses_cacher(c) && c->open) { close_clip(c); if (c->multithreaded && wait) c->cacher->wait(); } diff --git a/playback/playback.h b/playback/playback.h index af1d742f6..ed5f19900 100644 --- a/playback/playback.h +++ b/playback/playback.h @@ -12,6 +12,7 @@ struct AVFrame; extern bool texture_failed; extern bool rendering; +bool clip_uses_cacher(Clip* clip); void open_clip(Clip* clip, bool multithreaded); void cache_clip(Clip* clip, long playhead, bool reset, bool scrubbing, QVector &nests); void close_clip(Clip* clip); diff --git a/project/clip.cpp b/project/clip.cpp index e4934fcad..90a0fb48b 100644 --- a/project/clip.cpp +++ b/project/clip.cpp @@ -2,13 +2,14 @@ #include "project/effect.h" #include "project/transition.h" -#include "io/media.h" +#include "project/footage.h" #include "io/config.h" #include "playback/playback.h" #include "playback/cacher.h" #include "panels/project.h" #include "project/sequence.h" #include "panels/timeline.h" +#include "project/media.h" #include "undo.h" extern "C" { @@ -35,7 +36,6 @@ Clip::Clip(Sequence* s) : use_existing_frame(false), filter_graph(NULL), fbo(NULL), - texture(NULL), opts(NULL) { pkt = av_packet_alloc(); @@ -55,7 +55,6 @@ Clip* Clip::copy(Sequence* s) { copy->color_g = color_g; copy->color_b = color_b; copy->media = media; - copy->media_type = media_type; copy->media_stream = media_stream; copy->autoscale = autoscale; copy->speed = speed; @@ -93,29 +92,23 @@ void Clip::reset() { } void Clip::reset_audio() { - switch (media_type) { - case MEDIA_TYPE_FOOTAGE: - case MEDIA_TYPE_TONE: + if (media == NULL || media->get_type() == MEDIA_TYPE_FOOTAGE) { audio_reset = true; - frame_sample_index = -1; - audio_buffer_write = 0; - break; - case MEDIA_TYPE_SEQUENCE: - { - Sequence* nested_sequence = static_cast(media); + frame_sample_index = -1; + audio_buffer_write = 0; + } else if (media->get_type() == MEDIA_TYPE_SEQUENCE) { + Sequence* nested_sequence = media->to_sequence(); for (int i=0;iclips.size();i++) { Clip* c = nested_sequence->clips.at(i); if (c != NULL) c->reset_audio(); } } - break; - } } void Clip::refresh() { // validates media if it was replaced - if (replaced && media_type == MEDIA_TYPE_FOOTAGE) { - Media* m = static_cast(media); + if (replaced && media != NULL && media->get_type() == MEDIA_TYPE_FOOTAGE) { + Footage* m = media->to_footage(); media_stream = (track < 0) ? m->video_tracks.at(0)->file_index : m->audio_tracks.at(0)->file_index; } replaced = false; @@ -166,7 +159,7 @@ Clip::~Clip() { close_clip(this); // make sure clip has closed before clip is destroyed - if (multithreaded && media_type == MEDIA_TYPE_FOOTAGE) { + if (multithreaded && media != NULL && media->get_type() == MEDIA_TYPE_FOOTAGE) { cacher->wait(); } } @@ -210,36 +203,44 @@ long Clip::getLength() { return timeline_out - timeline_in; } +double Clip::getMediaFrameRate() { + Q_ASSERT(track < 0); + if (media != NULL) { + double rate = media->get_frame_rate(media_stream); + if (!qIsNaN(rate)) return rate; + } + if (sequence != NULL) return sequence->frame_rate; +} + void Clip::recalculateMaxLength() { if (sequence != NULL) { double fr = this->sequence->frame_rate; fr /= speed; - switch (media_type) { - case MEDIA_TYPE_FOOTAGE: - { - Media* m = static_cast(media); - MediaStream* ms = m->get_stream_from_file_index(track < 0, media_stream); - if (ms != NULL && ms->infinite_length) { - calculated_length = LONG_MAX; - } else { - calculated_length = m->get_length_in_frames(fr); - } - } - break; - case MEDIA_TYPE_SEQUENCE: - { - Sequence* s = static_cast(media); - calculated_length = refactor_frame_number(s->getEndFrame(), s->frame_rate, fr); - } - break; - /*case MEDIA_TYPE_SOLID: - case MEDIA_TYPE_TONE:*/ - default: - calculated_length = LONG_MAX; - break; - } + calculated_length = LONG_MAX; + + if (media != NULL) { + switch (media->get_type()) { + case MEDIA_TYPE_FOOTAGE: + { + Footage* m = media->to_footage(); + FootageStream* ms = m->get_stream_from_file_index(track < 0, media_stream); + if (ms != NULL && ms->infinite_length) { + calculated_length = LONG_MAX; + } else { + calculated_length = m->get_length_in_frames(fr); + } + } + break; + case MEDIA_TYPE_SEQUENCE: + { + Sequence* s = media->to_sequence(); + calculated_length = refactor_frame_number(s->getEndFrame(), s->frame_rate, fr); + } + break; + } + } } } @@ -247,28 +248,18 @@ long Clip::getMaximumLength() { return calculated_length; } -double Clip::getMediaFrameRate() { - Q_ASSERT(track < 0); - switch (media_type) { - case MEDIA_TYPE_FOOTAGE: return static_cast(media)->get_stream_from_file_index(track < 0, media_stream)->video_frame_rate; - case MEDIA_TYPE_SEQUENCE: return static_cast(media)->frame_rate; - } - if (sequence != NULL) return sequence->frame_rate; - return qSNaN(); -} - int Clip::getWidth() { if (media == NULL && sequence != NULL) return sequence->width; - switch (media_type) { + switch (media->get_type()) { case MEDIA_TYPE_FOOTAGE: { - MediaStream* ms = static_cast(media)->get_stream_from_file_index(track < 0, media_stream); + FootageStream* ms = media->to_footage()->get_stream_from_file_index(track < 0, media_stream); if (ms != NULL) return ms->video_width; if (sequence != NULL) return sequence->width; } case MEDIA_TYPE_SEQUENCE: { - Sequence* s = static_cast(media); + Sequence* s = media->to_sequence(); return s->width; } } @@ -277,16 +268,16 @@ int Clip::getWidth() { int Clip::getHeight() { if (media == NULL && sequence != NULL) return sequence->height; - switch (media_type) { + switch (media->get_type()) { case MEDIA_TYPE_FOOTAGE: { - MediaStream* ms = static_cast(media)->get_stream_from_file_index(track < 0, media_stream); + FootageStream* ms = media->to_footage()->get_stream_from_file_index(track < 0, media_stream); if (ms != NULL) return ms->video_height; if (sequence != NULL) return sequence->height; } case MEDIA_TYPE_SEQUENCE: { - Sequence* s = static_cast(media); + Sequence* s = media->to_sequence(); return s->height; } } diff --git a/project/clip.h b/project/clip.h index 300659e31..6e2ffc75a 100644 --- a/project/clip.h +++ b/project/clip.h @@ -13,9 +13,10 @@ class Effect; class Transition; class QOpenGLFramebufferObject; class ComboAction; +class Media; struct Sequence; -struct Media; -struct MediaStream; +struct Footage; +struct FootageStream; struct AVFormatContext; struct AVStream; @@ -42,9 +43,9 @@ struct Clip long get_timeline_in_with_transition(); long get_timeline_out_with_transition(); long getLength(); + double getMediaFrameRate(); long getMaximumLength(); void recalculateMaxLength(); - double getMediaFrameRate(); int getWidth(); int getHeight(); void refactor_frame_rate(ComboAction* ca, double multiplier, bool change_timeline_points); @@ -64,8 +65,7 @@ struct Clip quint8 color_r; quint8 color_g; quint8 color_b; - void* media; // attached media - int media_type; + Media* media; int media_stream; double speed; double cached_fr; diff --git a/io/media.cpp b/project/footage.cpp similarity index 67% rename from io/media.cpp rename to project/footage.cpp index 8dd4674a9..6dc841eb7 100644 --- a/io/media.cpp +++ b/project/footage.cpp @@ -1,4 +1,4 @@ -#include "media.h" +#include "footage.h" #include #include @@ -10,15 +10,15 @@ extern "C" { #include "project/clip.h" -Media::Media() : ready(false), preview_gen(NULL), invalid(false), in(0), out(0) { +Footage::Footage() : ready(false), preview_gen(NULL), invalid(false), in(0), out(0) { ready_lock.lock(); } -Media::~Media() { +Footage::~Footage() { reset(); } -void Media::reset() { +void Footage::reset() { if (preview_gen != NULL) { preview_gen->cancel(); preview_gen->wait(); @@ -34,11 +34,12 @@ void Media::reset() { ready = false; } -long Media::get_length_in_frames(double frame_rate) { - return qFloor(((double) length / (double) AV_TIME_BASE) * frame_rate); +long Footage::get_length_in_frames(double frame_rate) { + if (length >= 0) return qFloor(((double) length / (double) AV_TIME_BASE) * frame_rate); + return 0; } -MediaStream* Media::get_stream_from_file_index(bool video, int index) { +FootageStream* Footage::get_stream_from_file_index(bool video, int index) { if (video) { for (int i=0;ifile_index == index) { diff --git a/io/media.h b/project/footage.h similarity index 66% rename from io/media.h rename to project/footage.h index 35e65e4aa..55442e3ed 100644 --- a/io/media.h +++ b/project/footage.h @@ -1,5 +1,5 @@ -#ifndef MEDIA_H -#define MEDIA_H +#ifndef FOOTAGE_H +#define FOOTAGE_H #include #include @@ -8,12 +8,6 @@ #include #include -#define MEDIA_TYPE_FOOTAGE 0 -#define MEDIA_TYPE_SEQUENCE 1 -#define MEDIA_TYPE_FOLDER 2 -#define MEDIA_TYPE_SOLID 3 -#define MEDIA_TYPE_TONE 4 - #define VIDEO_PROGRESSIVE 0 #define VIDEO_TOP_FIELD_FIRST 1 #define VIDEO_BOTTOM_FIELD_FIRST 2 @@ -23,7 +17,7 @@ struct Clip; class PreviewGenerator; class MediaThrobber; -struct MediaStream { +struct FootageStream { int file_index; int video_width; int video_height; @@ -41,15 +35,15 @@ struct MediaStream { QVector audio_preview; }; -struct Media { - Media(); - ~Media(); +struct Footage { + Footage(); + ~Footage(); QString url; QString name; int64_t length; - QVector video_tracks; - QVector audio_tracks; + QVector video_tracks; + QVector audio_tracks; int save_id; bool ready; bool invalid; @@ -62,8 +56,8 @@ struct Media { long out; long get_length_in_frames(double frame_rate); - MediaStream* get_stream_from_file_index(bool video, int index); + FootageStream* get_stream_from_file_index(bool video, int index); void reset(); }; -#endif // MEDIA_H +#endif // FOOTAGE_H diff --git a/project/media.cpp b/project/media.cpp new file mode 100644 index 000000000..d3906f9ff --- /dev/null +++ b/project/media.cpp @@ -0,0 +1,312 @@ +#include "media.h" + +#include "footage.h" +#include "sequence.h" +#include "undo.h" +#include "io/config.h" +#include "panels/viewer.h" + +#include "debug.h" + +extern "C" { + #include + #include +} + +QString get_interlacing_name(int interlacing) { + switch (interlacing) { + case VIDEO_PROGRESSIVE: return "None (Progressive)"; + case VIDEO_TOP_FIELD_FIRST: return "Top Field First"; + case VIDEO_BOTTOM_FIELD_FIRST: return "Bottom Field First"; + default: return "Invalid"; + } +} + +QString get_channel_layout_name(int channels, uint64_t layout) { + switch (channels) { + case 0: return "Invalid"; break; + case 1: return "Mono"; break; + case 2: return "Stereo"; break; + default: { + char buf[50]; + av_get_channel_layout_string(buf, sizeof(buf), channels, layout); + return QString(buf); + } + } +} + +Media::Media(Media* iparent) : + parent(iparent), + throbber(NULL), + root(false), + type(-1) +{} + +Media::~Media() { + switch (get_type()) { + case MEDIA_TYPE_FOOTAGE: delete to_footage(); break; + case MEDIA_TYPE_SEQUENCE: if (object != NULL) delete to_sequence(); break; + } + if (throbber != NULL) delete throbber; + qDeleteAll(children); +} + +Footage *Media::to_footage() { + return static_cast(object); +} + +Sequence *Media::to_sequence() { + return static_cast(object); +} + +void Media::set_footage(Footage *f) { + type = MEDIA_TYPE_FOOTAGE; + object = f; +} + +void Media::set_sequence(Sequence *s) { + set_icon(QIcon(":/icons/sequence.png")); + type = MEDIA_TYPE_SEQUENCE; + object = s; + if (s != NULL) update_tooltip(); +} + +void Media::set_folder() { + if (folder_name.isEmpty()) folder_name = "New Folder"; + set_icon(QIcon(":/icons/folder.png")); + type = MEDIA_TYPE_FOLDER; + object = NULL; +} + +void Media::set_icon(const QIcon &ico) { + icon = ico; +} + +void Media::set_parent(Media *p) { + parent = p; +} + +void Media::update_tooltip(const QString& error) { + switch (type) { + case MEDIA_TYPE_FOOTAGE: + { + Footage* f = to_footage(); + tooltip = "Name: " + f->name + "\nFilename: " + f->url + "\n"; + + if (error.isEmpty()) { + if (f->video_tracks.size() > 0) { + tooltip += "Video Dimensions: "; + for (int i=0;ivideo_tracks.size();i++) { + if (i > 0) { + tooltip += ", "; + } + tooltip += QString::number(f->video_tracks.at(i)->video_width) + "x" + QString::number(f->video_tracks.at(i)->video_height); + } + tooltip += "\n"; + + if (!f->video_tracks.at(0)->infinite_length) { + tooltip += "Frame Rate: "; + for (int i=0;ivideo_tracks.size();i++) { + if (i > 0) { + tooltip += ", "; + } + if (f->video_tracks.at(i)->video_interlacing == VIDEO_PROGRESSIVE) { + tooltip += QString::number(f->video_tracks.at(i)->video_frame_rate); + } else { + tooltip += QString::number(f->video_tracks.at(i)->video_frame_rate * 2); + tooltip += " fields (" + QString::number(f->video_tracks.at(i)->video_frame_rate) + " frames)"; + } + } + tooltip += "\n"; + } + + tooltip += "Interlacing: "; + for (int i=0;ivideo_tracks.size();i++) { + if (i > 0) { + tooltip += ", "; + } + tooltip += get_interlacing_name(f->video_tracks.at(i)->video_interlacing); + } + } + + if (f->audio_tracks.size() > 0) { + tooltip += "\n"; + + tooltip += "Audio Frequency: "; + for (int i=0;iaudio_tracks.size();i++) { + if (i > 0) { + tooltip += ", "; + } + tooltip += QString::number(f->audio_tracks.at(i)->audio_frequency); + } + tooltip += "\n"; + + tooltip += "Audio Channels: "; + for (int i=0;iaudio_tracks.size();i++) { + if (i > 0) { + tooltip += ", "; + } + tooltip += get_channel_layout_name(f->audio_tracks.at(i)->audio_channels, f->audio_tracks.at(i)->audio_layout); + } + // tooltip += "\n"; + } + } else { + tooltip += error; + } + } + break; + case MEDIA_TYPE_SEQUENCE: + { + Sequence* s = to_sequence(); + tooltip = "Name: " + s->name + + "\nVideo Dimensions: " + QString::number(s->width) + "x" + QString::number(s->height) + + "\nFrame Rate: " + QString::number(s->frame_rate) + + "\nAudio Frequency: " + QString::number(s->audio_frequency) + + "\nAudio Layout: " + get_channel_layout_name(av_get_channel_layout_nb_channels(s->audio_layout), s->audio_layout); + } + break; + } + +} + +void *Media::to_object() { + return object; +} + +int Media::get_type() { + return type; +} + +const QString &Media::get_name() { + switch (type) { + case MEDIA_TYPE_FOOTAGE: return to_footage()->name; + case MEDIA_TYPE_SEQUENCE: return to_sequence()->name; + case MEDIA_TYPE_FOLDER: return folder_name; + } +} + +void Media::set_name(const QString &n) { + switch (type) { + case MEDIA_TYPE_FOOTAGE: to_footage()->name = n; break; + case MEDIA_TYPE_SEQUENCE: to_sequence()->name = n; break; + case MEDIA_TYPE_FOLDER: folder_name = n; break; + } +} + +double Media::get_frame_rate(int stream) { + switch (get_type()) { + case MEDIA_TYPE_FOOTAGE: return to_footage()->get_stream_from_file_index(true, stream)->video_frame_rate; + case MEDIA_TYPE_SEQUENCE: return to_sequence()->frame_rate; + } + return qSNaN(); +} + +int Media::get_sampling_rate(int stream) { + switch (get_type()) { + case MEDIA_TYPE_FOOTAGE: return to_footage()->get_stream_from_file_index(false, stream)->audio_frequency; + case MEDIA_TYPE_SEQUENCE: return to_sequence()->audio_frequency; + } + return 0; +} + +void Media::appendChild(Media *child) { + child->set_parent(this); + children.append(child); +} + +bool Media::setData(int col, const QVariant &value) { + if (col == 0) { + QString n = value.toString(); + if (!n.isEmpty() && get_name() != n) { + undo_stack.push(new MediaRename(this, value.toString())); + return true; + } + } + return false; +} + +Media *Media::child(int row) { + return children.value(row); +} + +int Media::childCount() const { + return children.count(); +} + +int Media::columnCount() const { + return 3; +} + +QVariant Media::data(int column, int role) { + switch (role) { + case Qt::DecorationRole: + if (column == 0) { + return icon; + } + break; + case Qt::DisplayRole: + switch (column) { + case 0: return (root) ? "Name" : get_name(); + case 1: + if (root) return "Duration"; + if (get_type() == MEDIA_TYPE_SEQUENCE) { + Sequence* s = to_sequence(); + return frame_to_timecode(s->getEndFrame(), config.timecode_view, s->frame_rate); + } + if (get_type() == MEDIA_TYPE_FOOTAGE) { + Footage* f = to_footage(); + double r = 30; + + if (f->video_tracks.size() > 0) r = f->video_tracks.at(0)->video_frame_rate; + if (!qIsNull(r)) { + long len = f->get_length_in_frames(r); + if (len > 0) return frame_to_timecode(len, config.timecode_view, r); + } + } + break; + case 2: + if (root) return "Rate"; + if (get_type() == MEDIA_TYPE_SEQUENCE) return QString::number(get_frame_rate()) + " FPS"; + if (get_type() == MEDIA_TYPE_FOOTAGE) { + Footage* f = to_footage(); + if (f->video_tracks.size() > 0) { + double r = get_frame_rate(); + if (!qIsNull(r)) return QString::number(get_frame_rate()) + " FPS"; + } else if (f->audio_tracks.size() > 0) { + return QString::number(get_sampling_rate()) + " Hz"; + } + } + break; + } + break; + case Qt::ToolTipRole: + return tooltip; + } + return QVariant(); +} + +int Media::row() const { + if (parent) { + return parent->children.indexOf(const_cast(this)); + } + return 0; +} + +Media *Media::parentItem() { + return parent; +} + +Media *Media::takeChild(int i) { + Media* ref = children.at(i); + children.removeAt(i); + return ref; +} + +void Media::removeChild(Media* m) { + for (int i=0;i +#include + +#define MEDIA_TYPE_FOOTAGE 0 +#define MEDIA_TYPE_SEQUENCE 1 +#define MEDIA_TYPE_FOLDER 2 +#define MEDIA_TYPE_SOLID 3 +#define MEDIA_TYPE_TONE 4 + +class Footage; +class MediaThrobber; +struct Sequence; +#include + +class Media +{ +public: + Media(Media* iparent); + ~Media(); + Footage *to_footage(); + Sequence* to_sequence(); + void set_footage(Footage* f); + void set_sequence(Sequence* s); + void set_folder(); + void set_icon(const QIcon &ico); + void set_parent(Media* p); + void update_tooltip(const QString& error = 0); + void *to_object(); + int get_type(); + const QString& get_name(); + void set_name(const QString& n); + MediaThrobber* throbber; + + double get_frame_rate(int stream = 0); + int get_sampling_rate(int stream = 0); + + // item functions + void appendChild(Media *child); + bool setData(int col, const QVariant &value); + Media *child(int row); + int childCount() const; + int columnCount() const; + QVariant data(int column, int role); + int row() const; + Media *parentItem(); + Media* takeChild(int i); + void removeChild(Media* m); + + bool root; + int temp_id; + int temp_id2; +private: + int type; + void* object; + + // item functions + QList children; + Media* parent; + QString folder_name; + QString tooltip; + QIcon icon; +}; + +#endif // MEDIA_H diff --git a/project/projectmodel.cpp b/project/projectmodel.cpp new file mode 100644 index 000000000..34e0c5ee7 --- /dev/null +++ b/project/projectmodel.cpp @@ -0,0 +1,146 @@ +#include "projectmodel.h" + +#include "panels/panels.h" +#include "panels/viewer.h" +#include "ui/viewerwidget.h" +#include "project/media.h" +#include "debug.h" + +ProjectModel::ProjectModel(QObject *parent) : QAbstractItemModel(parent), root_item(NULL) { + clear(); +} + +ProjectModel::~ProjectModel() { + destroy_root(); +} + +void ProjectModel::destroy_root() { + if (panel_sequence_viewer != NULL) panel_sequence_viewer->viewer_widget->delete_function(); + if (panel_footage_viewer != NULL) panel_footage_viewer->viewer_widget->delete_function(); + + if (root_item != NULL) delete root_item; +} + +void ProjectModel::clear() { + destroy_root(); + root_item = new Media(0); + root_item->root = true; + update_data(); +} + +Media *ProjectModel::get_root() { + return root_item; +} + +QVariant ProjectModel::data(const QModelIndex &index, int role) const { + if (!index.isValid()) + return QVariant(); + + return static_cast(index.internalPointer())->data(index.column(), role); +} + +Qt::ItemFlags ProjectModel::flags(const QModelIndex &index) const { + if (!index.isValid()) + return Qt::ItemIsDropEnabled; + + return QAbstractItemModel::flags(index) | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable; +} + +QVariant ProjectModel::headerData(int section, Qt::Orientation orientation, int role) const { + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) + return root_item->data(section, role); + + return QVariant(); +} + +QModelIndex ProjectModel::index(int row, int column, const QModelIndex &parent) const { + if (!hasIndex(row, column, parent)) + return QModelIndex(); + + Media *parentItem; + + if (!parent.isValid()) + parentItem = root_item; + else + parentItem = static_cast(parent.internalPointer()); + + Media *childItem = parentItem->child(row); + if (childItem) + return createIndex(row, column, childItem); + else + return QModelIndex(); +} + +QModelIndex ProjectModel::parent(const QModelIndex &index) const { + if (!index.isValid()) + return QModelIndex(); + + Media *childItem = static_cast(index.internalPointer()); + Media *parentItem = childItem->parentItem(); + + if (parentItem == root_item) + return QModelIndex(); + + return createIndex(parentItem->row(), 0, parentItem); +} + +bool ProjectModel::setData(const QModelIndex &index, const QVariant &value, int role) { + if (role != Qt::EditRole) + return false; + + Media *item = static_cast(index.internalPointer()); + bool result = item->setData(index.column(), value); + + if (result) + emit dataChanged(index, index); + + return result; +} + +int ProjectModel::rowCount(const QModelIndex &parent) const { + Media *parentItem; + if (parent.column() > 0) + return 0; + + if (!parent.isValid()) { + parentItem = root_item; + } else { + parentItem = static_cast(parent.internalPointer()); + } + + return parentItem->childCount(); +} + +int ProjectModel::columnCount(const QModelIndex &parent) const { + if (parent.isValid()) + return static_cast(parent.internalPointer())->columnCount(); + else + return root_item->columnCount(); +} + +int ProjectModel::topLevelItemCount() { + return root_item->childCount(); +} + +Media *ProjectModel::topLevelItem(int i) { + return root_item->child(i); +} + +void ProjectModel::addTopLevelItem(Media *m) { + root_item->appendChild(m); +} + +Media *ProjectModel::takeTopLevelItem(int i) { + Media* ref = root_item->child(i); + root_item->takeChild(i); + return ref; +} + +void ProjectModel::removeTopLevelItem(Media *m) { + root_item->removeChild(m); +} + +void ProjectModel::update_data() { +// emit dataChanged(); + emit layoutChanged(); +} diff --git a/project/projectmodel.h b/project/projectmodel.h new file mode 100644 index 000000000..16c40c13c --- /dev/null +++ b/project/projectmodel.h @@ -0,0 +1,39 @@ +#ifndef PROJECTMODEL_H +#define PROJECTMODEL_H + +#include + +class Media; + +class ProjectModel : public QAbstractItemModel +{ + Q_OBJECT +public: + ProjectModel(QObject* parent = 0); + ~ProjectModel() override; + + void destroy_root(); + void clear(); + Media* get_root(); + QVariant data(const QModelIndex &index, int role) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const override; + QModelIndex index(int row, int column, + const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &index) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + int topLevelItemCount(); + Media* topLevelItem(int i); + void addTopLevelItem(Media*); + Media* takeTopLevelItem(int i); + void removeTopLevelItem(Media* m); + void update_data(); + +private: + Media* root_item; +}; + +#endif // PROJECTMODEL_H diff --git a/project/undo.cpp b/project/undo.cpp index 9010404fd..ace334589 100644 --- a/project/undo.cpp +++ b/project/undo.cpp @@ -15,13 +15,14 @@ #include "ui/sourcetable.h" #include "project/effect.h" #include "project/transition.h" -#include "io/media.h" +#include "project/footage.h" #include "playback/cacher.h" #include "ui/labelslider.h" #include "ui/viewerwidget.h" #include "project/marker.h" #include "mainwindow.h" #include "io/clipboard.h" +#include "project/media.h" #include "debug.h" QUndoStack undo_stack; @@ -169,7 +170,7 @@ void SetTimelineInOutCommand::undo() { // footage viewer functions if (seq->wrapper_sequence) { - Media* m = static_cast(seq->clips.at(0)->media); + Footage* m = seq->clips.at(0)->media->to_footage(); m->using_inout = old_enabled; m->in = old_in; m->out = old_out; @@ -189,7 +190,7 @@ void SetTimelineInOutCommand::redo() { // footage viewer functions if (seq->wrapper_sequence) { - Media* m = static_cast(seq->clips.at(0)->media); + Footage* m = seq->clips.at(0)->media->to_footage(); m->using_inout = new_enabled; m->in = new_in; m->out = new_out; @@ -340,7 +341,7 @@ void DeleteTransitionCommand::redo() { mainWindow->setWindowModified(true); } -NewSequenceCommand::NewSequenceCommand(QTreeWidgetItem *s, QTreeWidgetItem* iparent) : +NewSequenceCommand::NewSequenceCommand(Media *s, Media* iparent) : seq(s), parent(iparent), done(false), @@ -353,7 +354,7 @@ NewSequenceCommand::~NewSequenceCommand() { void NewSequenceCommand::undo() { if (parent == NULL) { - panel_project->source_table->takeTopLevelItem(panel_project->source_table->indexOfTopLevelItem(seq)); + project_model.removeTopLevelItem(seq); } else { parent->removeChild(seq); } @@ -363,45 +364,38 @@ void NewSequenceCommand::undo() { void NewSequenceCommand::redo() { if (parent == NULL) { - panel_project->source_table->addTopLevelItem(seq); + project_model.addTopLevelItem(seq); } else { - parent->addChild(seq); + parent->appendChild(seq); } done = true; mainWindow->setWindowModified(true); } -AddMediaCommand::AddMediaCommand(QTreeWidgetItem* iitem, QTreeWidgetItem* iparent) : +AddMediaCommand::AddMediaCommand(Media* iitem, Media* iparent) : item(iitem), parent(iparent), done(false), old_project_changed(mainWindow->isWindowModified()) -{} +{ + if (parent == NULL) parent = project_model.get_root(); +} AddMediaCommand::~AddMediaCommand() { if (!done) { - panel_project->delete_media(item); - if (item->data(0, Qt::UserRole + 5) != 0) delete reinterpret_cast(item->data(0, Qt::UserRole + 5).value()); delete item; } } void AddMediaCommand::undo() { - if (parent == NULL) { - panel_project->source_table->takeTopLevelItem(panel_project->source_table->indexOfTopLevelItem(item)); - } else { - parent->removeChild(item); - } + parent->removeChild(item); done = false; mainWindow->setWindowModified(old_project_changed); } void AddMediaCommand::redo() { - if (parent == NULL) { - panel_project->source_table->addTopLevelItem(item); - } else { - parent->addChild(item); - } + parent->appendChild(item); + project_model.update_data(); /* Here we force the source_table to sort itself. * @@ -419,32 +413,33 @@ void AddMediaCommand::redo() { * I guess I'm "supposed" to use a Model–view–viewmodel instead, * which I'll probably have to switch to soon anyway. So perhaps * this will be a non-issue soon. - */ + panel_project->source_table->setSortingEnabled(false); - panel_project->source_table->setSortingEnabled(true); + panel_project->source_table->setSortingEnabled(true);*/ done = true; mainWindow->setWindowModified(true); } -DeleteMediaCommand::DeleteMediaCommand(QTreeWidgetItem* i) : +DeleteMediaCommand::DeleteMediaCommand(const QModelIndex& i) : item(i), + parent(i.parent()), old_project_changed(mainWindow->isWindowModified()) {} DeleteMediaCommand::~DeleteMediaCommand() { if (done) { - panel_project->delete_media(item); - if (item->data(0, Qt::UserRole + 5) != 0) delete reinterpret_cast(item->data(0, Qt::UserRole + 5).value()); - delete item; + Media* m = static_cast(item.internalPointer()); + delete m; } } void DeleteMediaCommand::undo() { - if (parent == NULL) { - panel_project->source_table->addTopLevelItem(item); + Media* m = static_cast(item.internalPointer()); + if (!parent.isValid()) { + project_model.addTopLevelItem(m); } else { - parent->addChild(item); + static_cast(parent.internalPointer())->appendChild(m); } mainWindow->setWindowModified(old_project_changed); @@ -452,14 +447,14 @@ void DeleteMediaCommand::undo() { } void DeleteMediaCommand::redo() { - parent = item->parent(); - - if (parent == NULL) { - panel_project->source_table->takeTopLevelItem(panel_project->source_table->indexOfTopLevelItem(item)); + Media* m = static_cast(item.internalPointer()); + if (!parent.isValid()) { + project_model.removeTopLevelItem(m); } else { - parent->removeChild(item); + static_cast(parent.internalPointer())->removeChild(m); } + project_model.update_data(); mainWindow->setWindowModified(true); done = true; } @@ -601,25 +596,24 @@ void CheckboxCommand::redo() { mainWindow->setWindowModified(true); } -ReplaceMediaCommand::ReplaceMediaCommand(QTreeWidgetItem* i, QString s) : +ReplaceMediaCommand::ReplaceMediaCommand(Media* i, QString s) : item(i), new_filename(s), old_project_changed(mainWindow->isWindowModified()) { - media = get_footage_from_tree(item); - old_filename = media->url; + old_filename = item->to_footage()->url; } void ReplaceMediaCommand::replace(QString& filename) { // close any clips currently using this media - QVector all_sequences = panel_project->list_all_project_sequences(); + QVector all_sequences = panel_project->list_all_project_sequences(); for (int i=0;ito_sequence(); for (int j=0;jclips.size();j++) { Clip* c = s->clips.at(j); - if (c != NULL && c->media == media && c->open) { + if (c != NULL && c->media == item && c->open) { close_clip(c); - if (c->media_type == MEDIA_TYPE_FOOTAGE) c->cacher->wait(); + if (c->media != NULL && c->media->get_type() == MEDIA_TYPE_FOOTAGE) c->cacher->wait(); c->replaced = true; } } @@ -643,11 +637,9 @@ void ReplaceMediaCommand::redo() { mainWindow->setWindowModified(true); } -ReplaceClipMediaCommand::ReplaceClipMediaCommand(void *a, void *b, int c, int d, bool e) : +ReplaceClipMediaCommand::ReplaceClipMediaCommand(Media *a, Media *b, bool e) : old_media(a), - new_media(b), - old_type(c), - new_type(d), + new_media(b), preserve_clip_ins(e), old_project_changed(mainWindow->isWindowModified()) {} @@ -661,7 +653,7 @@ void ReplaceClipMediaCommand::replace(bool undo) { Clip* c = clips.at(i); if (c->open) { close_clip(c); - if (c->media_type == MEDIA_TYPE_FOOTAGE) c->cacher->wait(); + if (c->media != NULL && c->media->get_type() == MEDIA_TYPE_FOOTAGE) c->cacher->wait(); } if (undo) { @@ -669,16 +661,14 @@ void ReplaceClipMediaCommand::replace(bool undo) { c->clip_in = old_clip_ins.at(i); } - c->media = old_media; - c->media_type = old_type; + c->media = old_media; } else { if (!preserve_clip_ins) { old_clip_ins.append(c->clip_in); c->clip_in = 0; } - c->media = new_media; - c->media_type = new_type; + c->media = new_media; } c->replaced = true; @@ -695,6 +685,7 @@ void ReplaceClipMediaCommand::undo() { void ReplaceClipMediaCommand::redo() { replace(false); + update_ui(true); mainWindow->setWindowModified(true); } @@ -737,61 +728,40 @@ MediaMove::MediaMove(SourceTable *s) : table(s), old_project_changed(mainWindow- void MediaMove::undo() { for (int i=0;itakeTopLevelItem(table->indexOfTopLevelItem(items.at(i))); - } else { - to->removeChild(items.at(i)); - } - } - for (int i=0;iaddTopLevelItem(items.at(i)); - } else { - froms.at(i)->addChild(items.at(i)); - } - } + to->removeChild(items.at(i)); + froms.at(i)->appendChild(items.at(i)); + } mainWindow->setWindowModified(old_project_changed); } void MediaMove::redo() { + if (to == NULL) to = project_model.get_root(); froms.resize(items.size()); for (int i=0;iparent(); + Media* parent = items.at(i)->parentItem(); froms[i] = parent; - if (parent == NULL) { - table->takeTopLevelItem(table->indexOfTopLevelItem(items.at(i))); - } else { - parent->removeChild(items.at(i)); - } - if (to == NULL) { - table->addTopLevelItem(items.at(i)); - } else { - to->addChild(items.at(i)); - } - } - for (int i=0;iaddTopLevelItem(items.at(i)); - } else { - to->addChild(items.at(i)); - } - } + parent->removeChild(items.at(i)); + to->appendChild(items.at(i)); + } + project_model.update_data(); mainWindow->setWindowModified(true); } -MediaRename::MediaRename() : done(true), old_project_changed(mainWindow->isWindowModified()) {} +MediaRename::MediaRename(Media* iitem, QString ito) : + item(iitem), + from(iitem->get_name()), + to(ito), + old_project_changed(mainWindow->isWindowModified()) +{} void MediaRename::undo() { - item->setText(0, from); - done = false; - mainWindow->setWindowModified(old_project_changed); + item->set_name(from); + mainWindow->setWindowModified(old_project_changed); } void MediaRename::redo() { - if (!done) { - item->setText(0, to); - } - mainWindow->setWindowModified(true); + item->set_name(to); + mainWindow->setWindowModified(true); } KeyframeMove::KeyframeMove() : old_project_changed(mainWindow->isWindowModified()) {} @@ -1138,7 +1108,7 @@ void SetEnableCommand::redo() { mainWindow->setWindowModified(true); } -EditSequenceCommand::EditSequenceCommand(QTreeWidgetItem* i, Sequence *s) : +EditSequenceCommand::EditSequenceCommand(Media* i, Sequence *s) : item(i), seq(s), old_project_changed(mainWindow->isWindowModified()), @@ -1175,11 +1145,8 @@ void EditSequenceCommand::redo() { } void EditSequenceCommand::update() { - // update name - item->setText(0, seq->name); - // update tooltip - set_sequence_of_tree(item, seq); + item->set_sequence(seq); for (int i=0;iclips.size();i++) { // TODO shift in/out/clipin points to match new frame rate @@ -1237,9 +1204,8 @@ void CloseAllClipsCommand::redo() { closeActiveClips(sequence, true); } -UpdateFootageTooltip::UpdateFootageTooltip(QTreeWidgetItem *i, Media *m) : - item(i), - media(m) +UpdateFootageTooltip::UpdateFootageTooltip(Media *i) : + item(i) {} void UpdateFootageTooltip::undo() { @@ -1247,7 +1213,7 @@ void UpdateFootageTooltip::undo() { } void UpdateFootageTooltip::redo() { - update_footage_tooltip(item, media); + item->update_tooltip(); } MoveEffectCommand::MoveEffectCommand() : diff --git a/project/undo.h b/project/undo.h index 57084a4c7..46ba95af3 100644 --- a/project/undo.h +++ b/project/undo.h @@ -1,7 +1,7 @@ #ifndef UNDO_H #define UNDO_H -class QTreeWidgetItem; +class Media; class QCheckBox; class LabelSlider; class Effect; @@ -12,7 +12,7 @@ class Transition; class EffectGizmo; struct Clip; struct Sequence; -struct Media; +struct Footage; struct EffectMeta; #include "project/marker.h" @@ -166,39 +166,39 @@ private: class NewSequenceCommand : public QUndoCommand { public: - NewSequenceCommand(QTreeWidgetItem *s, QTreeWidgetItem* iparent); + NewSequenceCommand(Media *s, Media* iparent); ~NewSequenceCommand(); void undo(); void redo(); private: - QTreeWidgetItem* seq; - QTreeWidgetItem* parent; + Media* seq; + Media* parent; bool done; bool old_project_changed; }; class AddMediaCommand : public QUndoCommand { public: - AddMediaCommand(QTreeWidgetItem* iitem, QTreeWidgetItem* iparent); + AddMediaCommand(Media* iitem, Media* iparent); ~AddMediaCommand(); void undo(); void redo(); private: - QTreeWidgetItem* item; - QTreeWidgetItem* parent; + Media* item; + Media* parent; bool done; bool old_project_changed; }; class DeleteMediaCommand : public QUndoCommand { public: - DeleteMediaCommand(QTreeWidgetItem* i); + DeleteMediaCommand(const QModelIndex& i); ~DeleteMediaCommand(); void undo(); void redo(); private: - QTreeWidgetItem* item; - QTreeWidgetItem* parent; + const QModelIndex& item; + const QModelIndex& parent; bool old_project_changed; bool done; }; @@ -258,29 +258,26 @@ private: class ReplaceMediaCommand : public QUndoCommand { public: - ReplaceMediaCommand(QTreeWidgetItem*, QString); + ReplaceMediaCommand(Media*, QString); void undo(); void redo(); private: - QTreeWidgetItem *item; + Media *item; QString old_filename; QString new_filename; - bool old_project_changed; - Media* media; + bool old_project_changed; void replace(QString& filename); }; class ReplaceClipMediaCommand : public QUndoCommand { public: - ReplaceClipMediaCommand(void*, void*, int, int, bool); + ReplaceClipMediaCommand(Media *, Media *, bool); void undo(); void redo(); QVector clips; private: - void* old_media; - void* new_media; - int old_type; - int new_type; + Media* old_media; + Media* new_media; bool preserve_clip_ins; bool old_project_changed; QVector old_clip_ins; @@ -304,27 +301,26 @@ private: class MediaMove : public QUndoCommand { public: MediaMove(SourceTable* s); - QVector items; - QTreeWidgetItem* to; + QVector items; + Media* to; void undo(); void redo(); private: - QVector froms; + QVector froms; SourceTable* table; bool old_project_changed; }; class MediaRename : public QUndoCommand { public: - MediaRename(); - QTreeWidgetItem* item; - QString from; - QString to; - void undo(); - void redo(); + MediaRename(Media* iitem, QString to); + void undo(); + void redo(); private: - bool done; - bool old_project_changed; + bool old_project_changed; + Media* item; + QString from; + QString to; }; class KeyframeMove : public QUndoCommand { @@ -486,7 +482,7 @@ private: class EditSequenceCommand : public QUndoCommand { public: - EditSequenceCommand(QTreeWidgetItem *i, Sequence* s); + EditSequenceCommand(Media *i, Sequence* s); void undo(); void redo(); void update(); @@ -498,7 +494,7 @@ public: int audio_frequency; int audio_layout; private: - QTreeWidgetItem* item; + Media* item; Sequence* seq; bool old_project_changed; @@ -542,12 +538,11 @@ public: class UpdateFootageTooltip : public QUndoCommand { public: - UpdateFootageTooltip(QTreeWidgetItem* i, Media* m); + UpdateFootageTooltip(Media* i); void undo(); void redo(); private: - QTreeWidgetItem* item; - Media* media; + Media* item; }; class MoveEffectCommand : public QUndoCommand { diff --git a/ui/sourcetable.cpp b/ui/sourcetable.cpp index 8e11cfe3f..a3cb85627 100644 --- a/ui/sourcetable.cpp +++ b/ui/sourcetable.cpp @@ -1,7 +1,7 @@ #include "sourcetable.h" #include "panels/project.h" -#include "io/media.h" +#include "project/footage.h" #include "panels/timeline.h" #include "panels/viewer.h" #include "panels/panels.h" @@ -10,6 +10,8 @@ #include "project/sequence.h" #include "mainwindow.h" #include "io/config.h" +#include "project/media.h" +#include "debug.h" #include #include @@ -22,16 +24,16 @@ #include #include -SourceTable::SourceTable(QWidget* parent) : QTreeWidget(parent) { +SourceTable::SourceTable(QWidget* parent) : QTreeView(parent) { editing_item = NULL; setSortingEnabled(true); sortByColumn(0, Qt::AscendingOrder); rename_timer.setInterval(1000); 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())); + connect(this, SIGNAL(clicked(const QModelIndex&)), this, SLOT(item_click(const QModelIndex&))); + //connect(this, SIGNAL(itemChanged(Media*,int)), this, SLOT(item_renamed(Media*))); + connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu())); } void SourceTable::show_context_menu() { @@ -43,10 +45,14 @@ void SourceTable::show_context_menu() { QAction* new_folder_action = menu.addAction("New Folder..."); connect(new_folder_action, SIGNAL(triggered(bool)), mainWindow, SLOT(on_actionFolder_triggered())); - if (selectedItems().size() > 0) { - if (selectedItems().size() == 1) { + QModelIndexList selected_items = selectionModel()->selectedRows(); + + if (selected_items.size() > 0) { + Media* m = static_cast(selected_items.at(0).internalPointer()); + + if (selected_items.size() == 1) { // replace footage - int type = get_type_from_tree(selectedItems().at(0)); + int type = m->get_type(); if (type == MEDIA_TYPE_FOOTAGE) { QAction* replace_action = menu.addAction("Replace/Relink Media"); connect(replace_action, SIGNAL(triggered(bool)), panel_project, SLOT(replace_selected_file())); @@ -69,11 +75,11 @@ void SourceTable::show_context_menu() { // duplicate item bool all_sequences = true; bool all_footage = true; - for (int i=0;iget_type() != MEDIA_TYPE_SEQUENCE) { all_sequences = false; } - if (get_type_from_tree(selectedItems().at(i)) != MEDIA_TYPE_FOOTAGE) { + if (m->get_type() != MEDIA_TYPE_FOOTAGE) { all_footage = false; } } @@ -99,7 +105,7 @@ void SourceTable::show_context_menu() { QAction* delete_action = menu.addAction("Delete"); connect(delete_action, SIGNAL(triggered(bool)), panel_project, SLOT(delete_selected_media())); - if (selectedItems().size() == 1) { + if (selected_items.size() == 1) { QAction* properties_action = menu.addAction("Properties..."); connect(properties_action, SIGNAL(triggered(bool)), panel_project, SLOT(open_properties())); } @@ -109,20 +115,19 @@ void SourceTable::show_context_menu() { } void SourceTable::create_seq_from_selected() { - if (!selectedItems().isEmpty()) { - QVector media_list; - QVector type_list; - for (int i=0;iselectedRows(); + + if (!selected_items.isEmpty()) { + QVector media_list; + for (int i=0;i(selected_items.at(i).internalPointer())); } ComboAction* ca = new ComboAction(); - Sequence* s = create_sequence_from_media(media_list, type_list); + Sequence* s = create_sequence_from_media(media_list); // add clips to it - panel_timeline->create_ghosts_from_media(s, 0, media_list, type_list); + panel_timeline->create_ghosts_from_media(s, 0, media_list); panel_timeline->add_clips_from_ghosts(ca, s); panel_project->new_sequence(ca, s, true, NULL); @@ -131,7 +136,8 @@ void SourceTable::create_seq_from_selected() { } void SourceTable::reveal_in_browser() { - Media* m = get_footage_from_tree(selectedItems().at(0)); + QModelIndexList selected_items = selectionModel()->selectedRows(); + Footage* m = static_cast(selected_items.at(0).internalPointer()); #if defined(Q_OS_WIN) QStringList args; @@ -153,12 +159,9 @@ void SourceTable::reveal_in_browser() { #endif } -void SourceTable::item_renamed(QTreeWidgetItem* item) { +void SourceTable::item_renamed(Media* item) { if (editing_item == item) { - MediaRename* mr = new MediaRename(); - mr->from = editing_item_name; - mr->item = editing_item; - mr->to = editing_item->text(0); + MediaRename* mr = new MediaRename(item, "idk"); undo_stack.push(mr); editing_item = NULL; } @@ -171,37 +174,41 @@ void SourceTable::stop_rename_timer() { void SourceTable::rename_interval() { stop_rename_timer(); if (hasFocus() && editing_item != NULL) { - editing_item_name = editing_item->text(0); - editItem(editing_item, 0); + edit(editing_index); + //editItem(editing_item, 0); } } -void SourceTable::item_click(QTreeWidgetItem* item, int column) { - if (column == 0 && selectedItems().size() == 1) { - if (editing_item == item) { +void SourceTable::item_click(const QModelIndex& index) { + if (selectionModel()->selectedRows().size() == 1 && index.column() == 0) { + Media* m = static_cast(index.internalPointer()); + if (editing_item == m) { rename_timer.start(); + } else { + editing_item = m; + editing_index = index; } - editing_item = item; } } void SourceTable::mousePressEvent(QMouseEvent* event) { stop_rename_timer(); - QTreeWidget::mousePressEvent(event); + QTreeView::mousePressEvent(event); } -void SourceTable::mouseDoubleClickEvent(QMouseEvent* ) { +void SourceTable::mouseDoubleClickEvent(QMouseEvent* e) { stop_rename_timer(); - if (selectedItems().count() == 0) { + QModelIndexList selected_items = selectionModel()->selectedRows(); + if (selected_items.size() == 0) { panel_project->import_dialog(); - } else if (selectedItems().count() == 1) { - QTreeWidgetItem* item = selectedItems().at(0); - switch (get_type_from_tree(item)) { + } else if (selected_items.size() == 1) { + Media* item = static_cast(selected_items.at(0).internalPointer()); + switch (item->get_type()) { case MEDIA_TYPE_FOOTAGE: - panel_footage_viewer->set_media(get_type_from_tree(item), get_media_from_tree(item)); + panel_footage_viewer->set_media(item); panel_footage_viewer->setFocus(); break; case MEDIA_TYPE_SEQUENCE: - undo_stack.push(new ChangeSequenceAction(get_sequence_from_tree(item))); + undo_stack.push(new ChangeSequenceAction(item->to_sequence())); break; } } @@ -211,7 +218,7 @@ void SourceTable::dragEnterEvent(QDragEnterEvent *event) { if (event->mimeData()->hasUrls()) { event->acceptProposedAction(); } else { - QTreeWidget::dragEnterEvent(event); + QTreeView::dragEnterEvent(event); } } @@ -219,13 +226,14 @@ void SourceTable::dragMoveEvent(QDragMoveEvent *event) { if (event->mimeData()->hasUrls()) { event->acceptProposedAction(); } else { - QTreeWidget::dragMoveEvent(event); + QTreeView::dragMoveEvent(event); } } void SourceTable::dropEvent(QDropEvent* event) { const QMimeData* mimeData = event->mimeData(); - QTreeWidgetItem* drop_item = itemAt(event->pos()); + const QModelIndex& drop_item = indexAt(event->pos()); + Media* m = static_cast(drop_item.internalPointer()); if (mimeData->hasUrls()) { // drag files in from outside QList urls = mimeData->urls(); @@ -236,25 +244,25 @@ void SourceTable::dropEvent(QDropEvent* event) { } bool replace = false; if (urls.size() == 1 - && drop_item != NULL - && get_type_from_tree(drop_item) == MEDIA_TYPE_FOOTAGE + && drop_item.isValid() + && m->get_type() == MEDIA_TYPE_FOOTAGE && !QFileInfo(paths.at(0)).isDir() && config.drop_on_media_to_replace - && QMessageBox::question(this, "Replace Media", "You dropped a file onto '" + drop_item->text(0) + "'. Would you like to replace it with the dropped file?", QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) { + && QMessageBox::question(this, "Replace Media", "You dropped a file onto '" + m->get_name() + "'. Would you like to replace it with the dropped file?", QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) { replace = true; - panel_project->replace_media(drop_item, paths.at(0)); + panel_project->replace_media(m, paths.at(0)); } if (!replace) { - QTreeWidgetItem* parent = NULL; - if (drop_item != NULL) { - if (get_type_from_tree(drop_item) == MEDIA_TYPE_FOLDER) { - parent = drop_item; + QModelIndex parent; + if (drop_item.isValid()) { + if (m->get_type() == MEDIA_TYPE_FOLDER) { + parent = drop_item; } else { - parent = drop_item->parent(); + parent = drop_item.parent(); } } - if (parent != NULL) parent->setExpanded(true); - panel_project->process_file_list(false, paths, parent, NULL); + if (parent.isValid()) setExpanded(parent, true); + panel_project->process_file_list(false, paths, static_cast(parent.internalPointer()), NULL); } } event->acceptProposedAction(); @@ -263,24 +271,26 @@ void SourceTable::dropEvent(QDropEvent* event) { // dragging files within project // if we dragged to the root OR dragged to a folder - if (drop_item == NULL || (drop_item != NULL && get_type_from_tree(drop_item) == MEDIA_TYPE_FOLDER)) { - QVector move_items; - QList selected_items = selectedItems(); + if (!drop_item.isValid() || (drop_item.isValid() && m->get_type() == MEDIA_TYPE_FOLDER)) { + QVector move_items; + QModelIndexList selected_items = selectionModel()->selectedRows(); for (int i=0;iparent() != drop_item && s != drop_item) { + const QModelIndex& item = selected_items.at(i); + const QModelIndex& parent = item.parent(); + Media* s = static_cast(item.internalPointer()); + if (parent != drop_item && item != drop_item) { bool ignore = false; - if (s->parent() != NULL) { + if (parent.isValid()) { // if child belongs to a selected parent, assume the user is just moving the parent and ignore the child - QTreeWidgetItem* par = s->parent(); - while (par != NULL && !ignore) { + QModelIndex par = parent; + while (par.isValid() && !ignore) { for (int j=0;jparent(); + par = par.parent(); } } if (!ignore) { @@ -290,7 +300,7 @@ void SourceTable::dropEvent(QDropEvent* event) { } if (move_items.size() > 0) { MediaMove* mm = new MediaMove(this); - mm->to = drop_item; + mm->to = m; mm->items = move_items; undo_stack.push(mm); } diff --git a/ui/sourcetable.h b/ui/sourcetable.h index 77b9a590b..136586ba6 100644 --- a/ui/sourcetable.h +++ b/ui/sourcetable.h @@ -1,13 +1,14 @@ #ifndef SOURCETABLE_H #define SOURCETABLE_H -#include +#include #include #include class Project; +class Media; -class SourceTable : public QTreeWidget +class SourceTable : public QTreeView { Q_OBJECT public: @@ -20,13 +21,13 @@ protected: void dropEvent(QDropEvent *event); private: QTimer rename_timer; - QTreeWidgetItem* editing_item; - QString editing_item_name; + Media* editing_item; + QModelIndex editing_index; private slots: void rename_interval(); - void item_click(QTreeWidgetItem* item, int column); + void item_click(const QModelIndex& index); void stop_rename_timer(); - void item_renamed(QTreeWidgetItem *item); + void item_renamed(Media *item); void show_context_menu(); void create_seq_from_selected(); void reveal_in_browser(); diff --git a/ui/timelinewidget.cpp b/ui/timelinewidget.cpp index a34c84259..a6902bf84 100644 --- a/ui/timelinewidget.cpp +++ b/ui/timelinewidget.cpp @@ -8,7 +8,7 @@ #include "project/clip.h" #include "panels/project.h" #include "panels/timeline.h" -#include "io/media.h" +#include "project/footage.h" #include "ui/sourcetable.h" #include "panels/effectcontrols.h" #include "panels/viewer.h" @@ -16,6 +16,7 @@ #include "ui_timeline.h" #include "mainwindow.h" #include "ui/viewerwidget.h" +#include "project/media.h" #include "debug.h" #include "project/effect.h" @@ -238,17 +239,14 @@ bool same_sign(int a, int b) { void TimelineWidget::dragEnterEvent(QDragEnterEvent *event) { bool import_init = false; - QVector media_list; - QVector type_list; + QVector media_list; panel_timeline->importing_files = false; if (event->source() == panel_project->source_table) { - QList items = panel_project->source_table->selectedItems(); - media_list.resize(items.size()); - type_list.resize(items.size()); - for (int i=0;isource_table->selectionModel()->selectedRows(); + media_list.resize(items.size()); + for (int i=0;i(items.at(i).internalPointer()); } import_init = true; } @@ -256,13 +254,7 @@ void TimelineWidget::dragEnterEvent(QDragEnterEvent *event) { if (event->source() == panel_footage_viewer->viewer_widget) { Sequence* proposed_seq = panel_footage_viewer->seq; if (proposed_seq != sequence) { // don't allow nesting the same sequence - if (proposed_seq->wrapper_sequence) { - type_list.append(MEDIA_TYPE_FOOTAGE); - media_list.append(proposed_seq->clips.at(0)->media); - } else { - type_list.append(MEDIA_TYPE_SEQUENCE); - media_list.append(proposed_seq); - } + media_list.append(panel_footage_viewer->media); import_init = true; } } @@ -281,12 +273,12 @@ void TimelineWidget::dragEnterEvent(QDragEnterEvent *event) { for (int i=0;ilast_imported_media.size();i++) { // waits for media to have a duration // TODO would be much nicer if this was multithreaded - panel_project->last_imported_media.at(i)->ready_lock.lock(); - panel_project->last_imported_media.at(i)->ready_lock.unlock(); + Footage* f = panel_project->last_imported_media.at(i)->to_footage(); + f->ready_lock.lock(); + f->ready_lock.unlock(); - if (panel_project->last_imported_media.at(i)->ready) { - media_list.append(panel_project->last_imported_media.at(i)); - type_list.append(MEDIA_TYPE_FOOTAGE); + if (f->ready) { + media_list.append(panel_project->last_imported_media.at(i)); } } @@ -309,7 +301,7 @@ void TimelineWidget::dragEnterEvent(QDragEnterEvent *event) { // if no sequence, we're going to create a new one using the clips as a reference entry_point = 0; - self_created_sequence = create_sequence_from_media(media_list, type_list); + self_created_sequence = create_sequence_from_media(media_list); seq = self_created_sequence; } else { entry_point = panel_timeline->getTimelineFrameFromScreenPoint(event->pos().x()); @@ -317,7 +309,7 @@ void TimelineWidget::dragEnterEvent(QDragEnterEvent *event) { panel_timeline->drag_track_start = (bottom_align) ? -1 : 0; } - panel_timeline->create_ghosts_from_media(seq, entry_point, media_list, type_list); + panel_timeline->create_ghosts_from_media(seq, entry_point, media_list); panel_timeline->importing = true; } @@ -773,7 +765,7 @@ void TimelineWidget::mouseReleaseEvent(QMouseEvent *event) { if (c->track < 0) { // default video effects (before custom effects) c->effects.append(create_effect(c, get_internal_meta(EFFECT_INTERNAL_TRANSFORM, EFFECT_TYPE_EFFECT))); - c->media_type = MEDIA_TYPE_SOLID; + //c->media_type = MEDIA_TYPE_SOLID; } switch (panel_timeline->creating_object) { @@ -807,7 +799,7 @@ void TimelineWidget::mouseReleaseEvent(QMouseEvent *event) { // default audio effects (after custom effects) c->effects.append(create_effect(c, get_internal_meta(EFFECT_INTERNAL_VOLUME, EFFECT_TYPE_EFFECT))); c->effects.append(create_effect(c, get_internal_meta(EFFECT_INTERNAL_PAN, EFFECT_TYPE_EFFECT))); - c->media_type = MEDIA_TYPE_TONE; + //c->media_type = MEDIA_TYPE_TONE; } push_undo = true; @@ -1227,17 +1219,17 @@ void TimelineWidget::update_ghosts(const QPoint& mouse_pos, bool lock_frame) { Clip* c = NULL; if (g.clip != -1) c = sequence->clips.at(g.clip); - MediaStream* ms = NULL; - if (g.clip != -1 && c->media_type == MEDIA_TYPE_FOOTAGE) { - ms = static_cast(c->media)->get_stream_from_file_index(c->track < 0, c->media_stream); + FootageStream* ms = NULL; + if (g.clip != -1 && c->media->get_type() == MEDIA_TYPE_FOOTAGE) { + ms = c->media->to_footage()->get_stream_from_file_index(c->track < 0, c->media_stream); } // validate ghosts for trimming if (panel_timeline->creating) { // i feel like we might need something here but we haven't so far? } else if (panel_timeline->tool == TIMELINE_TOOL_SLIP) { - if (c->media_type == MEDIA_TYPE_SEQUENCE - || (c->media_type == MEDIA_TYPE_FOOTAGE && !static_cast(c->media)->get_stream_from_file_index(c->track < 0, c->media_stream)->infinite_length)) { + if (c->media->get_type() == MEDIA_TYPE_SEQUENCE + || (ms != NULL && !ms->infinite_length)) { // prevent slip moving a clip below 0 clip_in validator = g.old_clip_in - frame_diff; if (validator < 0) frame_diff += validator; @@ -1257,7 +1249,7 @@ void TimelineWidget::update_ghosts(const QPoint& mouse_pos, bool lock_frame) { if (validator < 0) frame_diff -= validator; // prevent clip_in from going below 0 - if (c->media_type == MEDIA_TYPE_SEQUENCE + if (c->media->get_type() == MEDIA_TYPE_SEQUENCE || (ms != NULL && !ms->infinite_length)) { validator = g.old_clip_in + frame_diff; if (validator < 0) frame_diff -= validator; @@ -1268,7 +1260,7 @@ void TimelineWidget::update_ghosts(const QPoint& mouse_pos, bool lock_frame) { if (validator < 1) frame_diff += (1 - validator); // prevent clip length exceeding media length - if (c->media_type == MEDIA_TYPE_SEQUENCE + if (c->media->get_type() == MEDIA_TYPE_SEQUENCE || (ms != NULL && !ms->infinite_length)) { validator = g.old_clip_in + g.ghost_length + frame_diff; if (validator > g.media_length) frame_diff -= validator - g.media_length; @@ -1349,14 +1341,14 @@ void TimelineWidget::update_ghosts(const QPoint& mouse_pos, bool lock_frame) { if (validator > 0) frame_diff -= validator; } else { // prevent clip_in from going below 0 - if (c->media_type == MEDIA_TYPE_SEQUENCE + if (c->media->get_type() == MEDIA_TYPE_SEQUENCE || (ms != NULL && !ms->infinite_length)) { validator = g.old_clip_in + frame_diff; if (validator < 0) frame_diff -= validator; } // prevent clip length exceeding media length - if (c->media_type == MEDIA_TYPE_SEQUENCE + if (c->media->get_type() == MEDIA_TYPE_SEQUENCE || (ms != NULL && !ms->infinite_length)) { validator = g.old_clip_in + g.ghost_length + frame_diff; if (validator > g.media_length) frame_diff -= validator - g.media_length; @@ -2049,7 +2041,7 @@ void TimelineWidget::mouseMoveEvent(QMouseEvent *event) { g.in = g.old_in = g.out = g.old_out = (panel_timeline->transition_tool_type == TA_OPENING_TRANSITION) ? c->timeline_in : c->timeline_out; g.track = c->track; g.clip = panel_timeline->transition_tool_pre_clip; - g.media_type = panel_timeline->transition_tool_type; + g.media_stream = panel_timeline->transition_tool_type; g.trimming = false; panel_timeline->ghosts.append(g); @@ -2097,7 +2089,7 @@ int color_brightness(int r, int g, int b) { return (0.2126*r + 0.7152*g + 0.0722*b); } -void draw_waveform(Clip* clip, MediaStream* ms, long media_length, QPainter *p, const QRect& clip_rect, int waveform_start, int waveform_limit, double zoom) { +void draw_waveform(Clip* clip, FootageStream* ms, long media_length, QPainter *p, const QRect& clip_rect, int waveform_start, int waveform_limit, double zoom) { int divider = ms->audio_channels*2; int channel_height = clip_rect.height()/ms->audio_channels; @@ -2225,11 +2217,11 @@ void TimelineWidget::paintEvent(QPaintEvent*) { int thumb_x = clip_rect.x() + 1; - if (clip->media_type == MEDIA_TYPE_FOOTAGE) { + if (clip->media != NULL && clip->media->get_type() == MEDIA_TYPE_FOOTAGE) { bool draw_checkerboard = false; QRect checkerboard_rect(clip_rect); - Media* m = static_cast(clip->media); - MediaStream* ms = m->get_stream_from_file_index(clip->track < 0, clip->media_stream); + Footage* m = clip->media->to_footage(); + FootageStream* ms = m->get_stream_from_file_index(clip->track < 0, clip->media_stream); if (ms == NULL) { draw_checkerboard = true; } else if (ms->preview_done) { @@ -2641,5 +2633,5 @@ void TimelineWidget::setScroll(int s) { } void TimelineWidget::reveal_media() { - panel_project->reveal_media(rc_reveal_media); + panel_project->reveal_media(rc_reveal_media); } diff --git a/ui/timelinewidget.h b/ui/timelinewidget.h index 07c6b6248..0af743afb 100644 --- a/ui/timelinewidget.h +++ b/ui/timelinewidget.h @@ -13,7 +13,7 @@ struct Sequence; struct Clip; -struct MediaStream; +struct FootageStream; class Timeline; class TimelineAction; class QScrollBar; @@ -21,7 +21,7 @@ class SetSelectionsCommand; class QPainter; bool same_sign(int a, int b); -void draw_waveform(Clip* clip, MediaStream* ms, long media_length, QPainter* p, const QRect& clip_rect, int waveform_start, int waveform_limit, double zoom); +void draw_waveform(Clip* clip, FootageStream* ms, long media_length, QPainter* p, const QRect& clip_rect, int waveform_start, int waveform_limit, double zoom); class TimelineWidget : public QWidget { Q_OBJECT diff --git a/ui/viewerwidget.cpp b/ui/viewerwidget.cpp index d0730a725..5048d0fac 100644 --- a/ui/viewerwidget.cpp +++ b/ui/viewerwidget.cpp @@ -10,7 +10,7 @@ #include "project/transition.h" #include "playback/playback.h" #include "playback/audio.h" -#include "io/media.h" +#include "project/footage.h" #include "ui_timeline.h" #include "playback/cacher.h" #include "io/config.h" @@ -18,6 +18,7 @@ #include "io/math.h" #include "ui/collapsiblewidget.h" #include "project/undo.h" +#include "project/media.h" #include #include @@ -126,7 +127,7 @@ void ViewerWidget::initializeGL() { connect(context(), SIGNAL(aboutToBeDestroyed()), this, SLOT(delete_function()), Qt::DirectConnection); - retry_timer.start(); + retry_timer.start(); } //void ViewerWidget::resizeGL(int w, int h) @@ -378,7 +379,7 @@ GLuint ViewerWidget::compose_sequence(QVector& nests, bool render_audio) if (!nests.isEmpty()) { for (int i=0;i(nests.at(i)->media); + s = nests.at(i)->media->to_sequence(); playhead += nests.at(i)->clip_in - nests.at(i)->get_timeline_in_with_transition(); playhead = refactor_frame_number(playhead, nests.at(i)->sequence->frame_rate, s->frame_rate); } @@ -402,13 +403,11 @@ GLuint ViewerWidget::compose_sequence(QVector& nests, bool render_audio) if (!(!nests.isEmpty() && !same_sign(c->track, nests.last()->track))) { bool clip_is_active = false; - switch (c->media_type) { - case MEDIA_TYPE_FOOTAGE: - { - Media* m = static_cast(c->media); + if (c->media != NULL && c->media->get_type() == MEDIA_TYPE_FOOTAGE) { + Footage* m = c->media->to_footage(); if (!m->invalid && !(c->track >= 0 && !is_audio_device_set())) { if (m->ready) { - MediaStream* ms = m->get_stream_from_file_index(c->track < 0, c->media_stream); + FootageStream* ms = m->get_stream_from_file_index(c->track < 0, c->media_stream); if (ms != NULL && is_clip_active(c, playhead)) { // if thread is already working, we don't want to touch this, // but we also don't want to hang the UI thread @@ -421,23 +420,18 @@ GLuint ViewerWidget::compose_sequence(QVector& nests, bool render_audio) close_clip(c); } } else { - dout << "[WARNING] Media was not ready, retrying..."; + //dout << "[WARNING] Media '" + m->name + "' was not ready, retrying..."; texture_failed = true; } } - } - break; - case MEDIA_TYPE_SEQUENCE: - case MEDIA_TYPE_SOLID: - case MEDIA_TYPE_TONE: - if (is_clip_active(c, playhead)) { - if (!c->open) open_clip(c, !rendering); - clip_is_active = true; - } else if (c->open) { - close_clip(c); - } - break; - } + } else { + if (is_clip_active(c, playhead)) { + if (!c->open) open_clip(c, !rendering); + clip_is_active = true; + } else if (c->open) { + close_clip(c); + } + } if (clip_is_active) { bool added = false; for (int j=0;j& nests, bool render_audio) Clip* c = current_clips.at(i); - if (c->media_type == MEDIA_TYPE_FOOTAGE && !c->finished_opening) { + if (c->media != NULL && c->media->get_type() == MEDIA_TYPE_FOOTAGE && !c->finished_opening) { dout << "[WARNING] Tried to display clip" << i << "but it's closed"; texture_failed = true; } else { @@ -478,23 +472,28 @@ GLuint ViewerWidget::compose_sequence(QVector& nests, bool render_audio) int video_width = c->getWidth(); int video_height = c->getHeight(); - if (c->media_type == MEDIA_TYPE_FOOTAGE) { - // set up opengl texture - if (c->texture == NULL) { - 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); - } - get_clip_frame(c, playhead); - textureID = c->texture->textureId(); - } else if (c->media_type == MEDIA_TYPE_SEQUENCE) { - textureID = -1; - } + if (c->media != NULL) { + switch (c->media->get_type()) { + case MEDIA_TYPE_FOOTAGE: + // set up opengl texture + if (c->texture == NULL) { + 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); + } + get_clip_frame(c, playhead); + textureID = c->texture->textureId(); + break; + case MEDIA_TYPE_SEQUENCE: + textureID = -1; + break; + } + } - if (textureID == 0 && c->media_type != MEDIA_TYPE_SOLID) { + if (textureID == 0 && c->media != NULL) { dout << "[WARNING] Texture hasn't been created yet"; texture_failed = true; } else if (playhead >= c->get_timeline_in_with_transition()) { @@ -519,21 +518,22 @@ GLuint ViewerWidget::compose_sequence(QVector& nests, bool render_audio) glViewport(0, 0, video_width, video_height); - // for nested sequences - if (c->media_type == MEDIA_TYPE_SEQUENCE) { - nests.append(c); - textureID = compose_sequence(nests, render_audio); - nests.removeLast(); - fbo_switcher = true; - } + GLuint composite_texture; - GLuint composite_texture; - if (c->media_type == MEDIA_TYPE_SOLID) { + if (c->media == NULL) { c->fbo[fbo_switcher]->bind(); glClear(GL_COLOR_BUFFER_BIT); c->fbo[fbo_switcher]->release(); composite_texture = c->fbo[fbo_switcher]->texture(); } else { + // for nested sequences + if (c->media->get_type()== MEDIA_TYPE_SEQUENCE) { + nests.append(c); + textureID = compose_sequence(nests, render_audio); + nests.removeLast(); + fbo_switcher = true; + } + composite_texture = draw_clip(c->fbo[fbo_switcher], textureID, true); } @@ -686,21 +686,17 @@ GLuint ViewerWidget::compose_sequence(QVector& nests, bool render_audio) } } else { if (render_audio || (config.enable_audio_scrubbing && audio_scrub)) { - switch (c->media_type) { - case MEDIA_TYPE_FOOTAGE: - case MEDIA_TYPE_TONE: - if (c->lock.tryLock()) { + if (c->media != NULL && c->media->get_type() == MEDIA_TYPE_SEQUENCE) { + nests.append(c); + compose_sequence(nests, render_audio); + nests.removeLast(); + } else { + if (c->lock.tryLock()) { // clip is not caching, start caching audio - cache_clip(c, playhead, c->audio_reset, !render_audio, nests); - c->lock.unlock(); - } - break; - case MEDIA_TYPE_SEQUENCE: - nests.append(c); - compose_sequence(nests, render_audio); - nests.removeLast(); - break; - } + cache_clip(c, playhead, c->audio_reset, !render_audio, nests); + c->lock.unlock(); + } + } } // visually update all the keyframe values diff --git a/ui/viewerwidget.h b/ui/viewerwidget.h index 1b7abc7d5..388582106 100644 --- a/ui/viewerwidget.h +++ b/ui/viewerwidget.h @@ -12,7 +12,7 @@ class Viewer; struct Clip; -struct MediaStream; +struct FootageStream; class QOpenGLFramebufferObject; class Effect; class EffectGizmo; @@ -32,7 +32,7 @@ public: bool waveform; Clip* waveform_clip; - MediaStream* waveform_ms; + FootageStream* waveform_ms; bool force_quit; public slots: From b3fb7d37afa5ad04db5414fe0bd2fb7ac833980b Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 13 Dec 2018 14:57:56 +1100 Subject: [PATCH 13/40] fixed compile issues --- dialogs/loaddialog.cpp | 2 ++ mainwindow.cpp | 5 +++++ olive.pro | 16 ++++++++++------ panels/project.cpp | 10 +--------- panels/project.h | 1 - project/effect.cpp | 4 +--- project/projectmodel.cpp | 9 +++++++-- 7 files changed, 26 insertions(+), 21 deletions(-) diff --git a/dialogs/loaddialog.cpp b/dialogs/loaddialog.cpp index 3a065241e..8e8522102 100644 --- a/dialogs/loaddialog.cpp +++ b/dialogs/loaddialog.cpp @@ -34,6 +34,8 @@ LoadDialog::LoadDialog(QWidget *parent, bool autorecovery) : QDialog(parent) { layout->addLayout(hboxLayout); + update(); + LoadThread* lt = new LoadThread(this, autorecovery); QObject::connect(lt, SIGNAL(success()), this, SLOT(thread_done())); lt->start(); diff --git a/mainwindow.cpp b/mainwindow.cpp index 0e6e2f552..ce8bcbd43 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -250,10 +250,15 @@ MainWindow::~MainWindow() { delete ui; delete panel_sequence_viewer; + panel_sequence_viewer = NULL; delete panel_footage_viewer; + panel_footage_viewer = NULL; delete panel_project; + panel_project = NULL; delete panel_effect_controls; + panel_effect_controls = NULL; delete panel_timeline; + panel_timeline = NULL; close_debug(); } diff --git a/olive.pro b/olive.pro index b9cb61096..aabbbdaa3 100644 --- a/olive.pro +++ b/olive.pro @@ -33,7 +33,8 @@ SOURCES += \ ui/sourcetable.cpp \ dialogs/aboutdialog.cpp \ ui/timelinewidget.cpp \ - io/media.cpp \ + project/media.cpp \ + project/footage.cpp \ project/sequence.cpp \ project/clip.cpp \ playback/playback.cpp \ @@ -66,6 +67,8 @@ SOURCES += \ dialogs/speeddialog.cpp \ dialogs/mediapropertiesdialog.cpp \ io/crc32.cpp \ + project/projectmodel.cpp \ + io/loadthread.cpp \ dialogs/loaddialog.cpp \ debug.cpp \ io/path.cpp \ @@ -91,8 +94,7 @@ SOURCES += \ project/effectfield.cpp \ effects/internal/cubetransition.cpp \ project/effectgizmo.cpp \ - io/clipboard.cpp \ - effects/internal/maskeffect.cpp + io/clipboard.cpp HEADERS += \ mainwindow.h \ @@ -103,7 +105,8 @@ HEADERS += \ ui/sourcetable.h \ dialogs/aboutdialog.h \ ui/timelinewidget.h \ - io/media.h \ + project/media.h \ + project/footage.h \ project/sequence.h \ project/clip.h \ playback/playback.h \ @@ -138,6 +141,8 @@ HEADERS += \ dialogs/speeddialog.h \ dialogs/mediapropertiesdialog.h \ io/crc32.h \ + project/projectmodel.h \ + io/loadthread.h \ dialogs/loaddialog.h \ debug.h \ io/path.h \ @@ -163,8 +168,7 @@ HEADERS += \ project/effectfield.h \ effects/internal/cubetransition.h \ project/effectgizmo.h \ - io/clipboard.h \ - effects/internal/maskeffect.h + io/clipboard.h FORMS += \ mainwindow.ui \ diff --git a/panels/project.cpp b/panels/project.cpp index 5349240ab..c22c07d31 100644 --- a/panels/project.cpp +++ b/panels/project.cpp @@ -59,7 +59,6 @@ Project::Project(QWidget *parent) : source_table = ui->treeView; source_table->setModel(&project_model); - connect(source_table, SIGNAL(itemChanged(Media*,int)), this, SLOT(rename_media(Media*,int))); /*Media* m = new Media(project_model.get_root()); project_model.get_root()->appendChild(m);*/ @@ -161,14 +160,6 @@ Sequence* create_sequence_from_media(QVector& media_list) { return s; } -void Project::rename_media(Media* item, int column) { - QString n = "rename function"; - switch (item->get_type()) { - case MEDIA_TYPE_FOOTAGE: item->to_footage()->name = n; break; - case MEDIA_TYPE_SEQUENCE: item->to_sequence()->name = n; break; - } -} - void Project::duplicate_selected() { QModelIndexList items = source_table->selectionModel()->selectedRows(); bool duped = false; @@ -739,6 +730,7 @@ void Project::clear() { void Project::new_project() { // clear existing project set_sequence(NULL); + panel_footage_viewer->set_media(NULL); clear(); mainWindow->setWindowModified(false); } diff --git a/panels/project.h b/panels/project.h index feac5f90c..4f392a808 100644 --- a/panels/project.h +++ b/panels/project.h @@ -102,7 +102,6 @@ private: QString get_file_name_from_path(const QString &path); QDir proj_dir; private slots: - void rename_media(Media* item, int column); void clear_recent_projects(); }; diff --git a/project/effect.cpp b/project/effect.cpp index 7bee62a95..3358e4822 100644 --- a/project/effect.cpp +++ b/project/effect.cpp @@ -27,7 +27,6 @@ #include "effects/internal/paneffect.h" #include "effects/internal/shakeeffect.h" #include "effects/internal/cornerpineffect.h" -#include "effects/internal/maskeffect.h" #include #include @@ -59,8 +58,7 @@ Effect* create_effect(Clip* c, const EffectMeta* em) { case EFFECT_INTERNAL_PAN: return new PanEffect(c, em); case EFFECT_INTERNAL_TONE: return new ToneEffect(c, em); case EFFECT_INTERNAL_SHAKE: return new ShakeEffect(c, em); - case EFFECT_INTERNAL_CORNERPIN: return new CornerPinEffect(c, em); - case EFFECT_INTERNAL_MASK: return new MaskEffect(c, em); + case EFFECT_INTERNAL_CORNERPIN: return new CornerPinEffect(c, em); } } else { dout << "[ERROR] Invalid effect data"; diff --git a/project/projectmodel.cpp b/project/projectmodel.cpp index 34e0c5ee7..f5991db26 100644 --- a/project/projectmodel.cpp +++ b/project/projectmodel.cpp @@ -18,14 +18,19 @@ void ProjectModel::destroy_root() { if (panel_sequence_viewer != NULL) panel_sequence_viewer->viewer_widget->delete_function(); if (panel_footage_viewer != NULL) panel_footage_viewer->viewer_widget->delete_function(); - if (root_item != NULL) delete root_item; + if (root_item != NULL) { + beginRemoveRows(QModelIndex(), 0, root_item->childCount()); + delete root_item; + endRemoveRows(); + } } void ProjectModel::clear() { destroy_root(); + beginResetModel(); root_item = new Media(0); root_item->root = true; - update_data(); + endResetModel(); } Media *ProjectModel::get_root() { From 1c696df6e8030ccd2c93e9c9853fd985f545ac53 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 13 Dec 2018 15:02:04 +1100 Subject: [PATCH 14/40] fixed source tree not updating with new project --- panels/project.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/panels/project.cpp b/panels/project.cpp index c22c07d31..74798c1e9 100644 --- a/panels/project.cpp +++ b/panels/project.cpp @@ -732,6 +732,7 @@ void Project::new_project() { set_sequence(NULL); panel_footage_viewer->set_media(NULL); clear(); + panel_project->source_table->update(); mainWindow->setWindowModified(false); } From 76f1fa69fe388081a4da8eb6a9ebdbb9b67897a1 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 13 Dec 2018 15:34:53 +1100 Subject: [PATCH 15/40] fixed drag issue introduced in qt 5.12 --- ui/timelinewidget.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ui/timelinewidget.cpp b/ui/timelinewidget.cpp index a6902bf84..ab3e45d4c 100644 --- a/ui/timelinewidget.cpp +++ b/ui/timelinewidget.cpp @@ -291,8 +291,8 @@ void TimelineWidget::dragEnterEvent(QDragEnterEvent *event) { } } - if (import_init) { - event->accept(); + if (import_init) { + event->acceptProposedAction(); long entry_point; Sequence* seq = sequence; @@ -312,11 +312,12 @@ void TimelineWidget::dragEnterEvent(QDragEnterEvent *event) { panel_timeline->create_ghosts_from_media(seq, entry_point, media_list); panel_timeline->importing = true; - } + } } void TimelineWidget::dragMoveEvent(QDragMoveEvent *event) { if (sequence != NULL && panel_timeline->importing) { + event->acceptProposedAction(); QPoint pos = event->pos(); update_ghosts(pos, event->keyboardModifiers() & Qt::ShiftModifier); panel_timeline->move_insert = ((event->keyboardModifiers() & Qt::ControlModifier) && (panel_timeline->tool == TIMELINE_TOOL_POINTER || panel_timeline->importing)); @@ -456,7 +457,7 @@ void insert_clips(ComboAction* ca) { void TimelineWidget::dropEvent(QDropEvent* event) { if (panel_timeline->importing && panel_timeline->ghosts.size() > 0) { - event->accept(); + event->acceptProposedAction(); ComboAction* ca = new ComboAction(); From 94db640327389d96e5e1a3352cdf09bab63d2716 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 14 Dec 2018 14:45:42 +1100 Subject: [PATCH 16/40] reimplemented sorting and improved view-model --- io/loadthread.cpp | 13 ++----- panels/panels.cpp | 3 +- panels/project.cpp | 76 +++++++++++++++++++------------------ panels/project.h | 16 +++----- panels/viewer.cpp | 2 +- project/media.cpp | 15 ++------ project/media.h | 3 +- project/projectmodel.cpp | 53 ++++++++++++++++++-------- project/projectmodel.h | 15 ++++---- project/undo.cpp | 82 ++++++++++------------------------------ project/undo.h | 7 ++-- ui/sourcetable.cpp | 38 +++++++++---------- ui/sourcetable.h | 1 + ui/timelinewidget.cpp | 21 ++++++---- 14 files changed, 156 insertions(+), 189 deletions(-) diff --git a/io/loadthread.cpp b/io/loadthread.cpp index 196a88d2c..d35d3e791 100644 --- a/io/loadthread.cpp +++ b/io/loadthread.cpp @@ -238,11 +238,7 @@ bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) { item->set_footage(m); - if (folder == 0) { - project_model.addTopLevelItem(item); - } else { - find_loaded_folder_by_id(folder)->appendChild(item); - } + project_model.appendChild(find_loaded_folder_by_id(folder), item); // analyze media to see if it's the same loaded_media_items.append(item); @@ -498,6 +494,7 @@ bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) { } Media* LoadThread::find_loaded_folder_by_id(int id) { + if (id == 0) return NULL; for (int j=0;jtemp_id == id) { @@ -582,11 +579,7 @@ void LoadThread::run() { for (int i=0;itemp_id2; - if (parent > 0) { - find_loaded_folder_by_id(parent)->appendChild(folder); - } else { - project_model.addTopLevelItem(folder); - } + project_model.appendChild(find_loaded_folder_by_id(parent), folder); } cont = load_worker(file, stream, MEDIA_TYPE_FOOTAGE); diff --git a/panels/panels.cpp b/panels/panels.cpp index 17916c24c..6f6396b9b 100644 --- a/panels/panels.cpp +++ b/panels/panels.cpp @@ -100,8 +100,7 @@ void update_effect_controls() { void update_ui(bool modified) { if (modified) { update_effect_controls(); - } - project_model.update_data(); + } panel_effect_controls->update_keyframes(); panel_timeline->repaint_timeline(); panel_sequence_viewer->update_viewer(); diff --git a/panels/project.cpp b/panels/project.cpp index 74798c1e9..d4a66e715 100644 --- a/panels/project.cpp +++ b/panels/project.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -57,8 +58,12 @@ Project::Project(QWidget *parent) : { ui->setupUi(this); source_table = ui->treeView; + source_table->project_parent = this; - source_table->setModel(&project_model); + sorter = new QSortFilterProxyModel(this); + sorter->setSourceModel(&project_model); + + source_table->setModel(sorter); /*Media* m = new Media(project_model.get_root()); project_model.get_root()->appendChild(m);*/ @@ -81,8 +86,8 @@ QString Project::get_next_sequence_name(QString start) { name += "0"; } name += QString::number(n); - for (int i=0;iget_name(), name, Qt::CaseInsensitive) == 0) { + for (int i=0;iget_name(), name, Qt::CaseInsensitive) == 0) { found = true; n++; break; @@ -165,9 +170,9 @@ void Project::duplicate_selected() { bool duped = false; ComboAction* ca = new ComboAction(); for (int j=0;j(items.at(j).internalPointer()); + Media* i = item_to_media(items.at(j)); if (i->get_type() == MEDIA_TYPE_SEQUENCE) { - new_sequence(ca, i->to_sequence()->copy(), false, static_cast(items.at(j).parent().internalPointer())); + new_sequence(ca, i->to_sequence()->copy(), false, item_to_media(items.at(j).parent())); duped = true; } } @@ -181,7 +186,7 @@ void Project::duplicate_selected() { void Project::replace_selected_file() { QModelIndexList selected_items = source_table->selectionModel()->selectedRows(); if (selected_items.size() == 1) { - Media* item = static_cast(selected_items.at(0).internalPointer()); + Media* item = item_to_media(selected_items.at(0)); if (item->get_type() == MEDIA_TYPE_FOOTAGE) { replace_media(item, 0); } @@ -204,7 +209,7 @@ void Project::replace_clip_media() { } else { QModelIndexList selected_items = source_table->selectionModel()->selectedRows(); if (selected_items.size() == 1) { - Media* item = static_cast(selected_items.at(0).internalPointer()); + Media* item = item_to_media(selected_items.at(0)); if (item->get_type() == MEDIA_TYPE_SEQUENCE && sequence == item->to_sequence()) { QMessageBox::critical(this, "Active sequence selected", "You cannot insert a sequence into itself, so no clips of this media would be in this sequence.", QMessageBox::Ok); } else { @@ -218,7 +223,7 @@ void Project::replace_clip_media() { void Project::open_properties() { QModelIndexList selected_items = source_table->selectionModel()->selectedRows(); if (selected_items.size() == 1) { - Media* item = static_cast(selected_items.at(0).internalPointer()); + Media* item = item_to_media(selected_items.at(0)); switch (item->get_type()) { case MEDIA_TYPE_FOOTAGE: { @@ -257,11 +262,7 @@ Media* Project::new_sequence(ComboAction *ca, Sequence *s, bool open, Media* par ca->append(new NewSequenceCommand(item, parent)); if (open) ca->append(new ChangeSequenceAction(s)); } else { - if (parent == NULL) { - project_model.addTopLevelItem(item); - } else { - parent->appendChild(item); - } + project_model.appendChild(NULL, item); if (open) set_sequence(s); } return item; @@ -287,6 +288,11 @@ Media* Project::new_folder(QString name) { return item; } +Media *Project::item_to_media(const QModelIndex &index) { + return static_cast(sorter->mapToSource(index).internalPointer()); +// return static_cast(index.internalPointer()); +} + void Project::get_all_media_from_table(QList items, QList& list, int search_type) { for (int i=0;iselectionModel()->selectedRows(); QList items; for (int i=0;i(selected_items.at(i).internalPointer())); + items.append(item_to_media(selected_items.at(i))); } bool remove = true; bool redraw = false; @@ -334,8 +340,8 @@ void Project::delete_selected_media() { QVector parents; QList sequence_items; QList all_top_level_items; - for (int i=0;i 0) { @@ -425,7 +431,7 @@ void Project::delete_selected_media() { } for (int i=0;iappend(new DeleteMediaCommand(selected_items.at(i))); + ca->append(new DeleteMediaCommand(items.at(i))); if (items.at(i)->get_type() == MEDIA_TYPE_SEQUENCE) { redraw = true; @@ -477,7 +483,7 @@ void Project::start_preview_generator(Media* item, bool replacing) { pg->start(QThread::LowPriority); } -void Project::process_file_list(bool recursive, QStringList& files, Media* parent, Media* replace) { +void Project::process_file_list(QStringList& files, bool recursive, Media* replace, Media* parent) { bool imported = false; QVector image_sequence_urls; @@ -505,12 +511,12 @@ void Project::process_file_list(bool recursive, QStringList& files, Media* paren subdir_filenames.append(subdir_files.at(j).filePath()); } - process_file_list(true, subdir_filenames, folder, NULL); + process_file_list(subdir_filenames, true, NULL, folder); - if (create_undo_action) { + if (create_undo_action) { ca->append(new AddMediaCommand(folder, parent)); - } else { - parent->appendChild(folder); + } else { + project_model.appendChild(parent, folder); } imported = true; @@ -618,7 +624,7 @@ void Project::process_file_list(bool recursive, QStringList& files, Media* paren if (create_undo_action) { ca->append(new AddMediaCommand(item, parent)); } else { - parent->appendChild(item); + project_model.appendChild(parent, item); } } @@ -639,16 +645,16 @@ Media* Project::get_selected_folder() { // if one item is selected and it's a folder, return it QModelIndexList selected_items = source_table->selectionModel()->selectedRows(); if (selected_items.size() == 1) { - Media* m = static_cast(selected_items.at(0).internalPointer()); + Media* m = item_to_media(selected_items.at(0)); if (m->get_type() == MEDIA_TYPE_FOLDER) return m; } - return NULL; + return NULL; } bool Project::reveal_media(void *media, QModelIndex parent) { for (int i=0;i(item.internalPointer()); + Media* m = project_model.getItem(item); if (m->get_type() == MEDIA_TYPE_FOLDER) { if (reveal_media(media, item)) return true; @@ -676,7 +682,7 @@ void Project::import_dialog() { if (fd.exec()) { QStringList files = fd.selectedFiles(); - process_file_list(false, files, get_selected_folder(), NULL); + process_file_list(files, false, NULL, get_selected_folder()); } } @@ -691,7 +697,7 @@ void Project::delete_clips_using_selected_media() { Clip* c = sequence->clips.at(i); if (c != NULL) { for (int j=0;j(items.at(j).internalPointer()); + Media* m = item_to_media(items.at(j)); if (c->media == m) { ca->append(new DeleteClipAction(sequence, i)); deleted = true; @@ -700,7 +706,7 @@ void Project::delete_clips_using_selected_media() { } } for (int j=0;j(items.at(j).internalPointer()); + Media* m = item_to_media(items.at(j)); if (delete_clips_in_clipboard_with_media(ca, m)) deleted = true; } if (deleted) { @@ -747,7 +753,7 @@ void Project::save_folder(QXmlStreamWriter& stream, int type, bool set_ids_only, bool root = (!parent.parent().isValid()); for (int i=0;i(item.internalPointer()); + Media* m = project_model.getItem(item); if (type == m->get_type()) { if (m->get_type() == MEDIA_TYPE_FOLDER) { @@ -762,13 +768,13 @@ void Project::save_folder(QXmlStreamWriter& stream, int type, bool set_ids_only, if (!item.parent().isValid()) { stream.writeAttribute("parent", "0"); } else { - stream.writeAttribute("parent", QString::number(static_cast(item.parent().internalPointer())->temp_id)); + stream.writeAttribute("parent", QString::number(project_model.getItem(item.parent())->temp_id)); } stream.writeEndElement(); } // save_folder(stream, item, type, set_ids_only); } else { - int folder = root ? 0 : static_cast(parent.internalPointer())->temp_id; + int folder = root ? 0 : project_model.getItem(parent)->temp_id; if (type == MEDIA_TYPE_FOOTAGE) { Footage* f = m->to_footage(); f->save_id = media_id; @@ -995,9 +1001,8 @@ void Project::add_recent_project(QString url) { } void Project::list_all_sequences_worker(QVector* list, Media* parent) { - int len = (parent == NULL) ? project_model.topLevelItemCount() : parent->childCount(); - for (int i=0;ichild(i); + for (int i=0;iget_type()) { case MEDIA_TYPE_SEQUENCE: list->append(item); @@ -1034,7 +1039,6 @@ void MediaThrobber::animation_update() { animation = 0; } item->set_icon(QIcon(pixmap.copy(THROBBER_SIZE*animation, 0, THROBBER_SIZE, THROBBER_SIZE))); - project_model.update_data(); animation++; } diff --git a/panels/project.h b/panels/project.h index 4f392a808..7e6ec8229 100644 --- a/panels/project.h +++ b/panels/project.h @@ -18,6 +18,7 @@ class Media; class QXmlStreamWriter; class QXmlStreamReader; class QFile; +class QSortFilterProxyModel; class ComboAction; #define LOAD_TYPE_VERSION 69 @@ -34,15 +35,6 @@ extern QString recent_proj_file; extern ProjectModel project_model; -/*int get_type_from_tree(Media *item); -void* get_media_from_tree(QTreeWidgetItem* item); -Footage* get_footage_from_tree(QTreeWidgetItem* item); -void set_footage_of_tree(QTreeWidgetItem* item, Footage* 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 update_footage_tooltip(Media* item, Footage* media, QString error = 0); - Sequence* create_sequence_from_media(QVector &media_list); QString get_channel_layout_name(int channels, uint64_t layout); @@ -59,9 +51,9 @@ public: void clear(); Media* new_sequence(ComboAction *ca, Sequence* s, bool open, Media* parent); QString get_next_sequence_name(QString start = 0); - void process_file_list(bool recursive, QStringList& files, Media *parent, Media* replace); + void process_file_list(QStringList& files, bool recursive = false, Media* replace = NULL, Media *parent = NULL); void replace_media(Media* item, QString filename); - Media* get_selected_folder(); + Media *get_selected_folder(); bool reveal_media(void* media, QModelIndex parent = QModelIndex()); void add_recent_project(QString url); @@ -70,12 +62,14 @@ public: void save_project(bool autorecovery); Media* new_folder(QString name); + Media* item_to_media(const QModelIndex& index); void save_recent_projects(); QVector list_all_project_sequences(); SourceTable* source_table; + QSortFilterProxyModel* sorter; QVector last_imported_media; diff --git a/panels/viewer.cpp b/panels/viewer.cpp index f70b8ecc4..e04655367 100644 --- a/panels/viewer.cpp +++ b/panels/viewer.cpp @@ -345,7 +345,7 @@ void Viewer::pause() { // import audio QStringList file_list; file_list.append(get_recorded_audio_filename()); - panel_project->process_file_list(false, file_list, NULL, NULL); + panel_project->process_file_list(file_list); // add it to the sequence Clip* c = new Clip(seq); diff --git a/project/media.cpp b/project/media.cpp index d3906f9ff..b97d09a11 100644 --- a/project/media.cpp +++ b/project/media.cpp @@ -5,6 +5,8 @@ #include "undo.h" #include "io/config.h" #include "panels/viewer.h" +#include "panels/project.h" +#include "projectmodel.h" #include "debug.h" @@ -296,17 +298,6 @@ Media *Media::parentItem() { return parent; } -Media *Media::takeChild(int i) { - Media* ref = children.at(i); +void Media::removeChild(int i) { children.removeAt(i); - return ref; -} - -void Media::removeChild(Media* m) { - for (int i=0;icolumnCount(); } -int ProjectModel::topLevelItemCount() { - return root_item->childCount(); +Media *ProjectModel::getItem(const QModelIndex &index) const { + if (index.isValid()) { + Media *item = static_cast(index.internalPointer()); + if (item) + return item; + } + return root_item; } -Media *ProjectModel::topLevelItem(int i) { - return root_item->child(i); +void ProjectModel::appendChild(Media *parent, Media *child) { + if (parent == NULL) parent = root_item; + beginInsertRows(parent == root_item ? QModelIndex() : createIndex(parent->row(), 0, parent), parent->childCount(), parent->childCount()); + parent->appendChild(child); + endInsertRows(); } -void ProjectModel::addTopLevelItem(Media *m) { - root_item->appendChild(m); +void ProjectModel::moveChild(Media *child, Media *to) { + if (to == NULL) to = root_item; + Media* from = child->parentItem(); + beginMoveRows( + from == root_item ? QModelIndex() : createIndex(from->row(), 0, from), + child->row(), + child->row(), + to == root_item ? QModelIndex() : createIndex(to->row(), 0, to), + to->childCount() + ); + from->removeChild(child->row()); + to->appendChild(child); + endMoveRows(); } -Media *ProjectModel::takeTopLevelItem(int i) { - Media* ref = root_item->child(i); - root_item->takeChild(i); - return ref; +void ProjectModel::removeChild(Media* parent, Media* m) { + if (parent == NULL) parent = root_item; + beginRemoveRows(parent == root_item ? QModelIndex() : createIndex(parent->row(), 0, parent), m->row(), m->row()); + parent->removeChild(m->row()); + endRemoveRows(); } -void ProjectModel::removeTopLevelItem(Media *m) { - root_item->removeChild(m); +Media* ProjectModel::child(int i, Media* parent) { + if (parent == NULL) parent = root_item; + return parent->child(i); } -void ProjectModel::update_data() { -// emit dataChanged(); - emit layoutChanged(); +int ProjectModel::childCount(Media *parent) { + if (parent == NULL) parent = root_item; + return parent->childCount(); } diff --git a/project/projectmodel.h b/project/projectmodel.h index 16c40c13c..e5d6b9a87 100644 --- a/project/projectmodel.h +++ b/project/projectmodel.h @@ -1,4 +1,4 @@ -#ifndef PROJECTMODEL_H +#ifndef PROJECTMODEL_H #define PROJECTMODEL_H #include @@ -25,12 +25,13 @@ public: bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; - int topLevelItemCount(); - Media* topLevelItem(int i); - void addTopLevelItem(Media*); - Media* takeTopLevelItem(int i); - void removeTopLevelItem(Media* m); - void update_data(); + Media *getItem(const QModelIndex &index) const; + + void appendChild(Media* parent, Media* child); + void moveChild(Media *child, Media *to); + void removeChild(Media *parent, Media* m); + Media *child(int i, Media* parent = NULL); + int childCount(Media* parent = NULL); private: Media* root_item; diff --git a/project/undo.cpp b/project/undo.cpp index ace334589..d691ca278 100644 --- a/project/undo.cpp +++ b/project/undo.cpp @@ -346,40 +346,34 @@ NewSequenceCommand::NewSequenceCommand(Media *s, Media* iparent) : parent(iparent), done(false), old_project_changed(mainWindow->isWindowModified()) -{} +{ + if (parent == NULL) parent = project_model.get_root(); +} NewSequenceCommand::~NewSequenceCommand() { if (!done) delete seq; } void NewSequenceCommand::undo() { - if (parent == NULL) { - project_model.removeTopLevelItem(seq); - } else { - parent->removeChild(seq); - } + project_model.removeChild(parent, seq); + done = false; mainWindow->setWindowModified(old_project_changed); } void NewSequenceCommand::redo() { - if (parent == NULL) { - project_model.addTopLevelItem(seq); - } else { - parent->appendChild(seq); - } + project_model.appendChild(parent, seq); + done = true; mainWindow->setWindowModified(true); } -AddMediaCommand::AddMediaCommand(Media* iitem, Media* iparent) : +AddMediaCommand::AddMediaCommand(Media* iitem, Media *iparent) : item(iitem), parent(iparent), done(false), old_project_changed(mainWindow->isWindowModified()) -{ - if (parent == NULL) parent = project_model.get_root(); -} +{} AddMediaCommand::~AddMediaCommand() { if (!done) { @@ -388,73 +382,40 @@ AddMediaCommand::~AddMediaCommand() { } void AddMediaCommand::undo() { - parent->removeChild(item); + project_model.removeChild(parent, item); done = false; mainWindow->setWindowModified(old_project_changed); } void AddMediaCommand::redo() { - parent->appendChild(item); - project_model.update_data(); - - /* Here we force the source_table to sort itself. - * - * For some reason, sometimes when you add items to the QTreeWidget, - * (usually upon first import) they appear at the bottom, regardless - * of where they should be placed alphabetically. Then when this - * function is "undone", and it tries to remove this item from the - * QTreeWidget, it immediately sorts and then removes THE WRONG ONE. - * If this happens to be a sequence, the sequence data doesn't save - * and is then lost forever (outside of autorecoveries). - * - * The following 2 lines seem to force the source_table to re-sort - * correctly and therefore works around this problem. But holy shit. - * - * I guess I'm "supposed" to use a Model–view–viewmodel instead, - * which I'll probably have to switch to soon anyway. So perhaps - * this will be a non-issue soon. - - panel_project->source_table->setSortingEnabled(false); - panel_project->source_table->setSortingEnabled(true);*/ + project_model.appendChild(parent, item); done = true; mainWindow->setWindowModified(true); } -DeleteMediaCommand::DeleteMediaCommand(const QModelIndex& i) : +DeleteMediaCommand::DeleteMediaCommand(Media* i) : item(i), - parent(i.parent()), + parent(i->parentItem()), old_project_changed(mainWindow->isWindowModified()) {} DeleteMediaCommand::~DeleteMediaCommand() { - if (done) { - Media* m = static_cast(item.internalPointer()); - delete m; + if (done) { + delete item; } } void DeleteMediaCommand::undo() { - Media* m = static_cast(item.internalPointer()); - if (!parent.isValid()) { - project_model.addTopLevelItem(m); - } else { - static_cast(parent.internalPointer())->appendChild(m); - } + project_model.appendChild(parent, item); mainWindow->setWindowModified(old_project_changed); done = false; } void DeleteMediaCommand::redo() { - Media* m = static_cast(item.internalPointer()); - if (!parent.isValid()) { - project_model.removeTopLevelItem(m); - } else { - static_cast(parent.internalPointer())->removeChild(m); - } + project_model.removeChild(parent, item); - project_model.update_data(); mainWindow->setWindowModified(true); done = true; } @@ -622,7 +583,7 @@ void ReplaceMediaCommand::replace(QString& filename) { // replace media QStringList files; files.append(filename); - panel_project->process_file_list(false, files, NULL, item); + panel_project->process_file_list(files, false, item, NULL); } void ReplaceMediaCommand::undo() { @@ -728,8 +689,7 @@ MediaMove::MediaMove(SourceTable *s) : table(s), old_project_changed(mainWindow- void MediaMove::undo() { for (int i=0;iremoveChild(items.at(i)); - froms.at(i)->appendChild(items.at(i)); + project_model.moveChild(items.at(i), froms.at(i)); } mainWindow->setWindowModified(old_project_changed); } @@ -740,10 +700,8 @@ void MediaMove::redo() { for (int i=0;iparentItem(); froms[i] = parent; - parent->removeChild(items.at(i)); - to->appendChild(items.at(i)); + project_model.moveChild(items.at(i), to); } - project_model.update_data(); mainWindow->setWindowModified(true); } diff --git a/project/undo.h b/project/undo.h index 46ba95af3..2d1462440 100644 --- a/project/undo.h +++ b/project/undo.h @@ -22,6 +22,7 @@ struct EffectMeta; #include #include #include +#include extern QUndoStack undo_stack; @@ -192,13 +193,13 @@ private: class DeleteMediaCommand : public QUndoCommand { public: - DeleteMediaCommand(const QModelIndex& i); + DeleteMediaCommand(Media *i); ~DeleteMediaCommand(); void undo(); void redo(); private: - const QModelIndex& item; - const QModelIndex& parent; + Media* item; + Media* parent; bool old_project_changed; bool done; }; diff --git a/ui/sourcetable.cpp b/ui/sourcetable.cpp index a3cb85627..aa835f55d 100644 --- a/ui/sourcetable.cpp +++ b/ui/sourcetable.cpp @@ -32,7 +32,6 @@ SourceTable::SourceTable(QWidget* parent) : QTreeView(parent) { setContextMenuPolicy(Qt::CustomContextMenu); connect(&rename_timer, SIGNAL(timeout()), this, SLOT(rename_interval())); connect(this, SIGNAL(clicked(const QModelIndex&)), this, SLOT(item_click(const QModelIndex&))); - //connect(this, SIGNAL(itemChanged(Media*,int)), this, SLOT(item_renamed(Media*))); connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu())); } @@ -40,7 +39,7 @@ void SourceTable::show_context_menu() { QMenu menu(this); QAction* import_action = menu.addAction("Import..."); - connect(import_action, SIGNAL(triggered(bool)), panel_project, SLOT(import_dialog())); + connect(import_action, SIGNAL(triggered(bool)), project_parent, SLOT(import_dialog())); QAction* new_folder_action = menu.addAction("New Folder..."); connect(new_folder_action, SIGNAL(triggered(bool)), mainWindow, SLOT(on_actionFolder_triggered())); @@ -48,14 +47,14 @@ void SourceTable::show_context_menu() { QModelIndexList selected_items = selectionModel()->selectedRows(); if (selected_items.size() > 0) { - Media* m = static_cast(selected_items.at(0).internalPointer()); + Media* m = project_parent->item_to_media(selected_items.at(0)); if (selected_items.size() == 1) { // replace footage int type = m->get_type(); if (type == MEDIA_TYPE_FOOTAGE) { QAction* replace_action = menu.addAction("Replace/Relink Media"); - connect(replace_action, SIGNAL(triggered(bool)), panel_project, SLOT(replace_selected_file())); + connect(replace_action, SIGNAL(triggered(bool)), project_parent, SLOT(replace_selected_file())); #if defined(Q_OS_WIN) QAction* reveal_in_explorer = menu.addAction("Reveal in Explorer"); @@ -68,7 +67,7 @@ void SourceTable::show_context_menu() { } if (type != MEDIA_TYPE_FOLDER) { QAction* replace_clip_media = menu.addAction("Replace Clips Using This Media"); - connect(replace_clip_media, SIGNAL(triggered(bool)), panel_project, SLOT(replace_clip_media())); + connect(replace_clip_media, SIGNAL(triggered(bool)), project_parent, SLOT(replace_clip_media())); } } @@ -92,22 +91,22 @@ void SourceTable::show_context_menu() { if (all_sequences) { // ONLY sequences are selected QAction* duplicate_action = menu.addAction("Duplicate"); - connect(duplicate_action, SIGNAL(triggered(bool)), panel_project, SLOT(duplicate_selected())); + connect(duplicate_action, SIGNAL(triggered(bool)), project_parent, SLOT(duplicate_selected())); } // ONLY footage is selected if (all_footage) { QAction* delete_footage_from_sequences = menu.addAction("Delete All Clips Using This Media"); - connect(delete_footage_from_sequences, SIGNAL(triggered(bool)), panel_project, SLOT(delete_clips_using_selected_media())); + connect(delete_footage_from_sequences, SIGNAL(triggered(bool)), project_parent, SLOT(delete_clips_using_selected_media())); } // delete media QAction* delete_action = menu.addAction("Delete"); - connect(delete_action, SIGNAL(triggered(bool)), panel_project, SLOT(delete_selected_media())); + connect(delete_action, SIGNAL(triggered(bool)), project_parent, SLOT(delete_selected_media())); if (selected_items.size() == 1) { QAction* properties_action = menu.addAction("Properties..."); - connect(properties_action, SIGNAL(triggered(bool)), panel_project, SLOT(open_properties())); + connect(properties_action, SIGNAL(triggered(bool)), project_parent, SLOT(open_properties())); } } @@ -120,7 +119,7 @@ void SourceTable::create_seq_from_selected() { if (!selected_items.isEmpty()) { QVector media_list; for (int i=0;i(selected_items.at(i).internalPointer())); + media_list.append(project_parent->item_to_media(selected_items.at(i))); } ComboAction* ca = new ComboAction(); @@ -130,14 +129,15 @@ void SourceTable::create_seq_from_selected() { panel_timeline->create_ghosts_from_media(s, 0, media_list); panel_timeline->add_clips_from_ghosts(ca, s); - panel_project->new_sequence(ca, s, true, NULL); + project_parent->new_sequence(ca, s, true, NULL); undo_stack.push(ca); } } void SourceTable::reveal_in_browser() { QModelIndexList selected_items = selectionModel()->selectedRows(); - Footage* m = static_cast(selected_items.at(0).internalPointer()); + Media* media = project_parent->item_to_media(selected_items.at(0)); + Footage* m = media->to_footage(); #if defined(Q_OS_WIN) QStringList args; @@ -180,7 +180,7 @@ void SourceTable::rename_interval() { } void SourceTable::item_click(const QModelIndex& index) { if (selectionModel()->selectedRows().size() == 1 && index.column() == 0) { - Media* m = static_cast(index.internalPointer()); + Media* m = project_parent->item_to_media(index); if (editing_item == m) { rename_timer.start(); } else { @@ -199,9 +199,9 @@ void SourceTable::mouseDoubleClickEvent(QMouseEvent* e) { stop_rename_timer(); QModelIndexList selected_items = selectionModel()->selectedRows(); if (selected_items.size() == 0) { - panel_project->import_dialog(); + project_parent->import_dialog(); } else if (selected_items.size() == 1) { - Media* item = static_cast(selected_items.at(0).internalPointer()); + Media* item = project_parent->item_to_media(selected_items.at(0)); switch (item->get_type()) { case MEDIA_TYPE_FOOTAGE: panel_footage_viewer->set_media(item); @@ -233,7 +233,7 @@ void SourceTable::dragMoveEvent(QDragMoveEvent *event) { void SourceTable::dropEvent(QDropEvent* event) { const QMimeData* mimeData = event->mimeData(); const QModelIndex& drop_item = indexAt(event->pos()); - Media* m = static_cast(drop_item.internalPointer()); + Media* m = project_parent->item_to_media(drop_item); if (mimeData->hasUrls()) { // drag files in from outside QList urls = mimeData->urls(); @@ -250,7 +250,7 @@ void SourceTable::dropEvent(QDropEvent* event) { && config.drop_on_media_to_replace && QMessageBox::question(this, "Replace Media", "You dropped a file onto '" + m->get_name() + "'. Would you like to replace it with the dropped file?", QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) { replace = true; - panel_project->replace_media(m, paths.at(0)); + project_parent->replace_media(m, paths.at(0)); } if (!replace) { QModelIndex parent; @@ -262,7 +262,7 @@ void SourceTable::dropEvent(QDropEvent* event) { } } if (parent.isValid()) setExpanded(parent, true); - panel_project->process_file_list(false, paths, static_cast(parent.internalPointer()), NULL); + project_parent->process_file_list(paths, false, NULL, panel_project->item_to_media(parent)); } } event->acceptProposedAction(); @@ -277,7 +277,7 @@ void SourceTable::dropEvent(QDropEvent* event) { for (int i=0;i(item.internalPointer()); + Media* s = project_parent->item_to_media(item); if (parent != drop_item && item != drop_item) { bool ignore = false; if (parent.isValid()) { diff --git a/ui/sourcetable.h b/ui/sourcetable.h index 136586ba6..c2a2637a7 100644 --- a/ui/sourcetable.h +++ b/ui/sourcetable.h @@ -13,6 +13,7 @@ class SourceTable : public QTreeView Q_OBJECT public: SourceTable(QWidget* parent = 0); + Project* project_parent; protected: void mousePressEvent(QMouseEvent*); void mouseDoubleClickEvent(QMouseEvent *event); diff --git a/ui/timelinewidget.cpp b/ui/timelinewidget.cpp index ab3e45d4c..aab15f9e4 100644 --- a/ui/timelinewidget.cpp +++ b/ui/timelinewidget.cpp @@ -246,7 +246,7 @@ void TimelineWidget::dragEnterEvent(QDragEnterEvent *event) { QModelIndexList items = panel_project->source_table->selectionModel()->selectedRows(); media_list.resize(items.size()); for (int i=0;i(items.at(i).internalPointer()); + media_list[i] = panel_project->item_to_media(items.at(i)); } import_init = true; } @@ -268,7 +268,7 @@ void TimelineWidget::dragEnterEvent(QDragEnterEvent *event) { file_list.append(urls.at(i).toLocalFile()); } - panel_project->process_file_list(false, file_list, NULL, NULL); + panel_project->process_file_list(file_list); for (int i=0;ilast_imported_media.size();i++) { // waits for media to have a duration @@ -316,12 +316,15 @@ void TimelineWidget::dragEnterEvent(QDragEnterEvent *event) { } void TimelineWidget::dragMoveEvent(QDragMoveEvent *event) { - if (sequence != NULL && panel_timeline->importing) { + if (panel_timeline->importing) { event->acceptProposedAction(); - QPoint pos = event->pos(); - update_ghosts(pos, event->keyboardModifiers() & Qt::ShiftModifier); - panel_timeline->move_insert = ((event->keyboardModifiers() & Qt::ControlModifier) && (panel_timeline->tool == TIMELINE_TOOL_POINTER || panel_timeline->importing)); - update_ui(false); + + if (sequence != NULL) { + QPoint pos = event->pos(); + update_ghosts(pos, event->keyboardModifiers() & Qt::ShiftModifier); + panel_timeline->move_insert = ((event->keyboardModifiers() & Qt::ControlModifier) && (panel_timeline->tool == TIMELINE_TOOL_POINTER || panel_timeline->importing)); + update_ui(false); + } } } @@ -343,7 +346,8 @@ void TimelineWidget::wheelEvent(QWheelEvent *event) { } } -void TimelineWidget::dragLeaveEvent(QDragLeaveEvent*) { +void TimelineWidget::dragLeaveEvent(QDragLeaveEvent* event) { + event->accept(); if (panel_timeline->importing) { if (panel_timeline->importing_files) { undo_stack.undo(); @@ -355,6 +359,7 @@ void TimelineWidget::dragLeaveEvent(QDragLeaveEvent*) { } if (self_created_sequence != NULL) { delete self_created_sequence; + self_created_sequence = NULL; } } From e85d771af0b23cb83bab0c31ab6abff92c25d76a Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 14 Dec 2018 21:22:54 +1100 Subject: [PATCH 17/40] addressed #164 --- dialogs/exportdialog.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/dialogs/exportdialog.cpp b/dialogs/exportdialog.cpp index 737e92cc0..772c35823 100644 --- a/dialogs/exportdialog.cpp +++ b/dialogs/exportdialog.cpp @@ -325,6 +325,7 @@ void ExportDialog::render_thread_finished() { prep_ui_for_render(false); panel_sequence_viewer->viewer_widget->makeCurrent(); panel_sequence_viewer->viewer_widget->initializeGL(); + if (ui->progressBar->value() == 100) close(); } void ExportDialog::prep_ui_for_render(bool r) { From 6b8168dc6a7900ac4df7b2beffafa351c7d49e6c Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 14 Dec 2018 22:19:52 +1100 Subject: [PATCH 18/40] export dialog updates ui when done --- dialogs/exportdialog.cpp | 4 +- io/loadthread.cpp | 4 +- project/effect.cpp | 425 ++++++++++++++++++++------------------- project/effect.h | 1 + 4 files changed, 224 insertions(+), 210 deletions(-) diff --git a/dialogs/exportdialog.cpp b/dialogs/exportdialog.cpp index 772c35823..20b010913 100644 --- a/dialogs/exportdialog.cpp +++ b/dialogs/exportdialog.cpp @@ -313,8 +313,7 @@ void ExportDialog::on_formatCombobox_currentIndexChanged(int index) ui->audioGroupbox->setEnabled(audio_enabled); } -void ExportDialog::on_pushButton_2_clicked() -{ +void ExportDialog::on_pushButton_2_clicked() { close(); } @@ -325,6 +324,7 @@ void ExportDialog::render_thread_finished() { prep_ui_for_render(false); panel_sequence_viewer->viewer_widget->makeCurrent(); panel_sequence_viewer->viewer_widget->initializeGL(); + update_ui(false); if (ui->progressBar->value() == 100) close(); } diff --git a/io/loadthread.cpp b/io/loadthread.cpp index d35d3e791..68340bac5 100644 --- a/io/loadthread.cpp +++ b/io/loadthread.cpp @@ -1,4 +1,4 @@ -#include "loadthread.h" +#include "loadthread.h" #include "mainwindow.h" #include "panels/panels.h" @@ -114,6 +114,8 @@ void load_effect(QXmlStreamReader& stream, Clip* c) { e->set_enabled(effect_enabled); e->load(stream); + e->moveToThread(QApplication::instance()->thread()); + c->effects.append(e); } } diff --git a/project/effect.cpp b/project/effect.cpp index 3358e4822..376419fa4 100644 --- a/project/effect.cpp +++ b/project/effect.cpp @@ -38,33 +38,40 @@ #include #include #include +#include QVector effects; QMutex effects_loaded; Effect* create_effect(Clip* c, const EffectMeta* em) { + Effect* e = NULL; if (!em->filename.isEmpty()) { // load effect from file - return new Effect(c, em); - } else if (em->internal >= 0 && em->internal < EFFECT_INTERNAL_COUNT) { + e = new Effect(c, em); + } else { // must be an internal effect switch (em->internal) { - case EFFECT_INTERNAL_TRANSFORM: return new TransformEffect(c, em); - case EFFECT_INTERNAL_TEXT: return new TextEffect(c, em); - case EFFECT_INTERNAL_TIMECODE: return new TimecodeEffect(c, em); - case EFFECT_INTERNAL_SOLID: return new SolidEffect(c, em); - case EFFECT_INTERNAL_NOISE: return new AudioNoiseEffect(c, em); - case EFFECT_INTERNAL_VOLUME: return new VolumeEffect(c, em); - case EFFECT_INTERNAL_PAN: return new PanEffect(c, em); - case EFFECT_INTERNAL_TONE: return new ToneEffect(c, em); - case EFFECT_INTERNAL_SHAKE: return new ShakeEffect(c, em); - case EFFECT_INTERNAL_CORNERPIN: return new CornerPinEffect(c, em); + case EFFECT_INTERNAL_TRANSFORM: e = new TransformEffect(c, em); break; + case EFFECT_INTERNAL_TEXT: e = new TextEffect(c, em); break; + case EFFECT_INTERNAL_TIMECODE: e = new TimecodeEffect(c, em); break; + case EFFECT_INTERNAL_SOLID: e = new SolidEffect(c, em); break; + case EFFECT_INTERNAL_NOISE: e = new AudioNoiseEffect(c, em); break; + case EFFECT_INTERNAL_VOLUME: e = new VolumeEffect(c, em); break; + case EFFECT_INTERNAL_PAN: e = new PanEffect(c, em); break; + case EFFECT_INTERNAL_TONE: e = new ToneEffect(c, em); break; + case EFFECT_INTERNAL_SHAKE: e = new ShakeEffect(c, em); break; + case EFFECT_INTERNAL_CORNERPIN: e = new CornerPinEffect(c, em); break; } - } else { - dout << "[ERROR] Invalid effect data"; - QMessageBox::critical(mainWindow, "Invalid effect", "No candidate for effect '" + em->name + "'. This effect may be corrupt. Try reinstalling it or Olive."); - } - return NULL; + } + if (e == NULL) { + dout << "[ERROR] Invalid effect data"; + QMessageBox::critical(mainWindow, "Invalid effect", "No candidate for effect '" + em->name + "'. This effect may be corrupt. Try reinstalling it or Olive."); + } else { + // make sure we're in the main thread + e->moveToThread(QApplication::instance()->thread()); + e->init_ui(); + } + return e; } const EffectMeta* get_internal_meta(int internal_id, int type) { @@ -157,8 +164,10 @@ void load_internal_effects() { void load_shader_effects() { QString effects_path = get_effects_dir(); QDir effects_dir(effects_path); + if (effects_dir.exists()) { QList entries = effects_dir.entryList(QStringList("*.xml"), QDir::Files); + for (int i=0;ienabled_check, SIGNAL(clicked(bool)), this, SLOT(field_changed())); - ui = new QWidget(); - ui_layout = new QGridLayout(); - ui_layout->setSpacing(4); - ui->setLayout(ui_layout); - container->setContents(ui); - - connect(container->title_bar, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu(const QPoint&))); - - // set up UI from effect file - container->setText(em->name); - - if (!em->filename.isEmpty()) { - QFile effect_file(get_effects_dir() + "/" + em->filename); - if (effect_file.open(QFile::ReadOnly)) { - QXmlStreamReader reader(&effect_file); - - while (!reader.atEnd()) { - if (reader.name() == "row" && reader.isStartElement()) { - QString row_name; - const QXmlStreamAttributes& attributes = reader.attributes(); - for (int i=0;ifilename << "- ID cannot be empty."; - } else if (type > -1) { - EffectField* field = row->add_field(type, id); - connect(field, SIGNAL(changed()), this, SLOT(field_changed())); - switch (type) { - case EFFECT_FIELD_DOUBLE: - for (int i=0;iset_double_default_value(attr.value().toDouble()); - } else if (attr.name() == "min") { - field->set_double_minimum_value(attr.value().toDouble()); - } else if (attr.name() == "max") { - field->set_double_maximum_value(attr.value().toDouble()); - } - } - break; - case EFFECT_FIELD_COLOR: - { - QColor color; - for (int i=0;iset_color_value(color); - } - break; - case EFFECT_FIELD_STRING: - for (int i=0;iset_string_value(attr.value().toString()); - } - } - break; - case EFFECT_FIELD_BOOL: - for (int i=0;iset_bool_value(attr.value() == "1"); - } - } - break; - case EFFECT_FIELD_COMBO: - { - int combo_index = 0; - for (int i=0;iadd_combo_item(reader.text().toString(), 0); - } - } - field->set_combo_index(combo_index); - } - break; - case EFFECT_FIELD_FONT: - for (int i=0;iset_font_name(attr.value().toString()); - } - } - break; - } - } - } - } - } - } else if (reader.name() == "shader" && reader.isStartElement()) { - enable_shader = true; - const QXmlStreamAttributes& attributes = reader.attributes(); - for (int i=0;ifilename; - enable_superimpose = false; - } - break; - } - } - } - reader.readNext(); - } - - effect_file.close(); - } else { - dout << "[ERROR] Failed to open effect file" << em->filename; - } - } -} +{} Effect::~Effect() { if (isOpen) { @@ -442,6 +262,197 @@ Effect::~Effect() { } } +void Effect::init_ui() { + // set up base UI + container = new CollapsibleWidget(); + connect(container->enabled_check, SIGNAL(clicked(bool)), this, SLOT(field_changed())); + ui = new QWidget(container); + ui_layout = new QGridLayout(ui); + ui_layout->setSpacing(4); + ui->setLayout(ui_layout); + container->setContents(ui); + + connect(container->title_bar, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu(const QPoint&))); + + // set up UI from effect file + container->setText(meta->name); + + if (!meta->filename.isEmpty()) { + QFile effect_file(get_effects_dir() + "/" + meta->filename); + if (effect_file.open(QFile::ReadOnly)) { + QXmlStreamReader reader(&effect_file); + + while (!reader.atEnd()) { + if (reader.name() == "row" && reader.isStartElement()) { + QString row_name; + const QXmlStreamAttributes& attributes = reader.attributes(); + for (int i=0;ifilename << "- ID cannot be empty."; + } else if (type > -1) { + EffectField* field = row->add_field(type, id); + connect(field, SIGNAL(changed()), this, SLOT(field_changed())); + switch (type) { + case EFFECT_FIELD_DOUBLE: + for (int i=0;iset_double_default_value(attr.value().toDouble()); + } else if (attr.name() == "min") { + field->set_double_minimum_value(attr.value().toDouble()); + } else if (attr.name() == "max") { + field->set_double_maximum_value(attr.value().toDouble()); + } + } + break; + case EFFECT_FIELD_COLOR: + { + QColor color; + for (int i=0;iset_color_value(color); + } + break; + case EFFECT_FIELD_STRING: + for (int i=0;iset_string_value(attr.value().toString()); + } + } + break; + case EFFECT_FIELD_BOOL: + for (int i=0;iset_bool_value(attr.value() == "1"); + } + } + break; + case EFFECT_FIELD_COMBO: + { + int combo_index = 0; + for (int i=0;iadd_combo_item(reader.text().toString(), 0); + } + } + field->set_combo_index(combo_index); + } + break; + case EFFECT_FIELD_FONT: + for (int i=0;iset_font_name(attr.value().toString()); + } + } + break; + } + } + } + } + } + } else if (reader.name() == "shader" && reader.isStartElement()) { + enable_shader = true; + const QXmlStreamAttributes& attributes = reader.attributes(); + for (int i=0;ifilename; + enable_superimpose = false; + } + break; + } + } + } + reader.readNext(); + } + + effect_file.close(); + } else { + dout << "[ERROR] Failed to open effect file" << meta->filename; + } + } +} + void Effect::copy_field_keyframes(Effect* e) { for (int i=0;i Date: Fri, 14 Dec 2018 22:25:23 +1100 Subject: [PATCH 19/40] reverted some files erroneously added in previous commit --- io/loadthread.cpp | 4 +- project/effect.cpp | 425 ++++++++++++++++++++++----------------------- project/effect.h | 1 - 3 files changed, 208 insertions(+), 222 deletions(-) diff --git a/io/loadthread.cpp b/io/loadthread.cpp index 68340bac5..d35d3e791 100644 --- a/io/loadthread.cpp +++ b/io/loadthread.cpp @@ -1,4 +1,4 @@ -#include "loadthread.h" +#include "loadthread.h" #include "mainwindow.h" #include "panels/panels.h" @@ -114,8 +114,6 @@ void load_effect(QXmlStreamReader& stream, Clip* c) { e->set_enabled(effect_enabled); e->load(stream); - e->moveToThread(QApplication::instance()->thread()); - c->effects.append(e); } } diff --git a/project/effect.cpp b/project/effect.cpp index 376419fa4..3358e4822 100644 --- a/project/effect.cpp +++ b/project/effect.cpp @@ -38,40 +38,33 @@ #include #include #include -#include QVector effects; QMutex effects_loaded; Effect* create_effect(Clip* c, const EffectMeta* em) { - Effect* e = NULL; if (!em->filename.isEmpty()) { // load effect from file - e = new Effect(c, em); - } else { + return new Effect(c, em); + } else if (em->internal >= 0 && em->internal < EFFECT_INTERNAL_COUNT) { // must be an internal effect switch (em->internal) { - case EFFECT_INTERNAL_TRANSFORM: e = new TransformEffect(c, em); break; - case EFFECT_INTERNAL_TEXT: e = new TextEffect(c, em); break; - case EFFECT_INTERNAL_TIMECODE: e = new TimecodeEffect(c, em); break; - case EFFECT_INTERNAL_SOLID: e = new SolidEffect(c, em); break; - case EFFECT_INTERNAL_NOISE: e = new AudioNoiseEffect(c, em); break; - case EFFECT_INTERNAL_VOLUME: e = new VolumeEffect(c, em); break; - case EFFECT_INTERNAL_PAN: e = new PanEffect(c, em); break; - case EFFECT_INTERNAL_TONE: e = new ToneEffect(c, em); break; - case EFFECT_INTERNAL_SHAKE: e = new ShakeEffect(c, em); break; - case EFFECT_INTERNAL_CORNERPIN: e = new CornerPinEffect(c, em); break; + case EFFECT_INTERNAL_TRANSFORM: return new TransformEffect(c, em); + case EFFECT_INTERNAL_TEXT: return new TextEffect(c, em); + case EFFECT_INTERNAL_TIMECODE: return new TimecodeEffect(c, em); + case EFFECT_INTERNAL_SOLID: return new SolidEffect(c, em); + case EFFECT_INTERNAL_NOISE: return new AudioNoiseEffect(c, em); + case EFFECT_INTERNAL_VOLUME: return new VolumeEffect(c, em); + case EFFECT_INTERNAL_PAN: return new PanEffect(c, em); + case EFFECT_INTERNAL_TONE: return new ToneEffect(c, em); + case EFFECT_INTERNAL_SHAKE: return new ShakeEffect(c, em); + case EFFECT_INTERNAL_CORNERPIN: return new CornerPinEffect(c, em); } - } - if (e == NULL) { - dout << "[ERROR] Invalid effect data"; - QMessageBox::critical(mainWindow, "Invalid effect", "No candidate for effect '" + em->name + "'. This effect may be corrupt. Try reinstalling it or Olive."); - } else { - // make sure we're in the main thread - e->moveToThread(QApplication::instance()->thread()); - e->init_ui(); - } - return e; + } else { + dout << "[ERROR] Invalid effect data"; + QMessageBox::critical(mainWindow, "Invalid effect", "No candidate for effect '" + em->name + "'. This effect may be corrupt. Try reinstalling it or Olive."); + } + return NULL; } const EffectMeta* get_internal_meta(int internal_id, int type) { @@ -164,10 +157,8 @@ void load_internal_effects() { void load_shader_effects() { QString effects_path = get_effects_dir(); QDir effects_dir(effects_path); - if (effects_dir.exists()) { QList entries = effects_dir.entryList(QStringList("*.xml"), QDir::Files); - for (int i=0;ienabled_check, SIGNAL(clicked(bool)), this, SLOT(field_changed())); + ui = new QWidget(); + ui_layout = new QGridLayout(); + ui_layout->setSpacing(4); + ui->setLayout(ui_layout); + container->setContents(ui); + + connect(container->title_bar, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu(const QPoint&))); + + // set up UI from effect file + container->setText(em->name); + + if (!em->filename.isEmpty()) { + QFile effect_file(get_effects_dir() + "/" + em->filename); + if (effect_file.open(QFile::ReadOnly)) { + QXmlStreamReader reader(&effect_file); + + while (!reader.atEnd()) { + if (reader.name() == "row" && reader.isStartElement()) { + QString row_name; + const QXmlStreamAttributes& attributes = reader.attributes(); + for (int i=0;ifilename << "- ID cannot be empty."; + } else if (type > -1) { + EffectField* field = row->add_field(type, id); + connect(field, SIGNAL(changed()), this, SLOT(field_changed())); + switch (type) { + case EFFECT_FIELD_DOUBLE: + for (int i=0;iset_double_default_value(attr.value().toDouble()); + } else if (attr.name() == "min") { + field->set_double_minimum_value(attr.value().toDouble()); + } else if (attr.name() == "max") { + field->set_double_maximum_value(attr.value().toDouble()); + } + } + break; + case EFFECT_FIELD_COLOR: + { + QColor color; + for (int i=0;iset_color_value(color); + } + break; + case EFFECT_FIELD_STRING: + for (int i=0;iset_string_value(attr.value().toString()); + } + } + break; + case EFFECT_FIELD_BOOL: + for (int i=0;iset_bool_value(attr.value() == "1"); + } + } + break; + case EFFECT_FIELD_COMBO: + { + int combo_index = 0; + for (int i=0;iadd_combo_item(reader.text().toString(), 0); + } + } + field->set_combo_index(combo_index); + } + break; + case EFFECT_FIELD_FONT: + for (int i=0;iset_font_name(attr.value().toString()); + } + } + break; + } + } + } + } + } + } else if (reader.name() == "shader" && reader.isStartElement()) { + enable_shader = true; + const QXmlStreamAttributes& attributes = reader.attributes(); + for (int i=0;ifilename; + enable_superimpose = false; + } + break; + } + } + } + reader.readNext(); + } + + effect_file.close(); + } else { + dout << "[ERROR] Failed to open effect file" << em->filename; + } + } +} Effect::~Effect() { if (isOpen) { @@ -262,197 +442,6 @@ Effect::~Effect() { } } -void Effect::init_ui() { - // set up base UI - container = new CollapsibleWidget(); - connect(container->enabled_check, SIGNAL(clicked(bool)), this, SLOT(field_changed())); - ui = new QWidget(container); - ui_layout = new QGridLayout(ui); - ui_layout->setSpacing(4); - ui->setLayout(ui_layout); - container->setContents(ui); - - connect(container->title_bar, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu(const QPoint&))); - - // set up UI from effect file - container->setText(meta->name); - - if (!meta->filename.isEmpty()) { - QFile effect_file(get_effects_dir() + "/" + meta->filename); - if (effect_file.open(QFile::ReadOnly)) { - QXmlStreamReader reader(&effect_file); - - while (!reader.atEnd()) { - if (reader.name() == "row" && reader.isStartElement()) { - QString row_name; - const QXmlStreamAttributes& attributes = reader.attributes(); - for (int i=0;ifilename << "- ID cannot be empty."; - } else if (type > -1) { - EffectField* field = row->add_field(type, id); - connect(field, SIGNAL(changed()), this, SLOT(field_changed())); - switch (type) { - case EFFECT_FIELD_DOUBLE: - for (int i=0;iset_double_default_value(attr.value().toDouble()); - } else if (attr.name() == "min") { - field->set_double_minimum_value(attr.value().toDouble()); - } else if (attr.name() == "max") { - field->set_double_maximum_value(attr.value().toDouble()); - } - } - break; - case EFFECT_FIELD_COLOR: - { - QColor color; - for (int i=0;iset_color_value(color); - } - break; - case EFFECT_FIELD_STRING: - for (int i=0;iset_string_value(attr.value().toString()); - } - } - break; - case EFFECT_FIELD_BOOL: - for (int i=0;iset_bool_value(attr.value() == "1"); - } - } - break; - case EFFECT_FIELD_COMBO: - { - int combo_index = 0; - for (int i=0;iadd_combo_item(reader.text().toString(), 0); - } - } - field->set_combo_index(combo_index); - } - break; - case EFFECT_FIELD_FONT: - for (int i=0;iset_font_name(attr.value().toString()); - } - } - break; - } - } - } - } - } - } else if (reader.name() == "shader" && reader.isStartElement()) { - enable_shader = true; - const QXmlStreamAttributes& attributes = reader.attributes(); - for (int i=0;ifilename; - enable_superimpose = false; - } - break; - } - } - } - reader.readNext(); - } - - effect_file.close(); - } else { - dout << "[ERROR] Failed to open effect file" << meta->filename; - } - } -} - void Effect::copy_field_keyframes(Effect* e) { for (int i=0;i Date: Sat, 15 Dec 2018 00:09:14 +1100 Subject: [PATCH 20/40] vastly improved multithreaded loading and annchor point gizmo --- dialogs/loaddialog.cpp | 18 ++- dialogs/loaddialog.h | 3 + effects/internal/cornerpineffect.cpp | 16 +-- effects/internal/transformeffect.cpp | 66 ++++----- io/loadthread.cpp | 202 +++++++++++++++++++-------- io/loadthread.h | 23 +++ io/previewgenerator.cpp | 10 +- mainwindow.cpp | 27 ++-- mainwindow.h | 3 +- panels/project.cpp | 15 +- project/effect.cpp | 20 ++- project/effectgizmo.cpp | 18 ++- project/effectgizmo.h | 12 +- project/projectmodel.cpp | 11 +- project/projectmodel.h | 1 + 15 files changed, 292 insertions(+), 153 deletions(-) diff --git a/dialogs/loaddialog.cpp b/dialogs/loaddialog.cpp index 8e8522102..1d25a9fa8 100644 --- a/dialogs/loaddialog.cpp +++ b/dialogs/loaddialog.cpp @@ -11,6 +11,7 @@ #include "io/loadthread.h" #include "playback/playback.h" #include "ui/sourcetable.h" +#include "mainwindow.h" LoadDialog::LoadDialog(QWidget *parent, bool autorecovery) : QDialog(parent) { setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); @@ -21,11 +22,11 @@ LoadDialog::LoadDialog(QWidget *parent, bool autorecovery) : QDialog(parent) { layout->addWidget(new QLabel("Loading '" + project_url.mid(project_url.lastIndexOf('/')+1) + "'...")); bar = new QProgressBar(); - bar->setValue(50); + bar->setValue(0); layout->addWidget(bar); cancel_button = new QPushButton("Cancel"); - connect(cancel_button, SIGNAL(clicked(bool)), this, SLOT(reject())); + connect(cancel_button, SIGNAL(clicked(bool)), this, SLOT(cancel())); hboxLayout = new QHBoxLayout(); hboxLayout->addStretch(); @@ -36,12 +37,19 @@ LoadDialog::LoadDialog(QWidget *parent, bool autorecovery) : QDialog(parent) { update(); - LoadThread* lt = new LoadThread(this, autorecovery); + lt = new LoadThread(this, autorecovery); QObject::connect(lt, SIGNAL(success()), this, SLOT(thread_done())); + QObject::connect(lt, SIGNAL(report_progress(int)), bar, SLOT(setValue(int))); lt->start(); } +void LoadDialog::cancel() { + lt->cancel(); + lt->wait(); + mainWindow->new_project(); + reject(); +} + void LoadDialog::thread_done() { -// project_model.update_data(); - close(); + accept(); } diff --git a/dialogs/loaddialog.h b/dialogs/loaddialog.h index 2f646fc91..c9099c66d 100644 --- a/dialogs/loaddialog.h +++ b/dialogs/loaddialog.h @@ -8,6 +8,7 @@ class Sequence; class Media; class Footage; class QHBoxLayout; +class LoadThread; class LoadDialog : public QDialog { @@ -15,11 +16,13 @@ class LoadDialog : public QDialog public: LoadDialog(QWidget* parent, bool autorecovery); private slots: + void cancel(); void thread_done(); private: QProgressBar* bar; QPushButton* cancel_button; QHBoxLayout* hboxLayout; + LoadThread* lt; }; #endif // LOADDIALOG_H diff --git a/effects/internal/cornerpineffect.cpp b/effects/internal/cornerpineffect.cpp index cea4e3d3a..96069b0bf 100644 --- a/effects/internal/cornerpineffect.cpp +++ b/effects/internal/cornerpineffect.cpp @@ -28,20 +28,20 @@ CornerPinEffect::CornerPinEffect(Clip *c, const EffectMeta *em) : Effect(c, em) perspective->set_bool_value(true); top_left_gizmo = add_gizmo(GIZMO_TYPE_DOT); - top_left_gizmo->x_field = top_left_x; - top_left_gizmo->y_field = top_left_y; + top_left_gizmo->x_field1 = top_left_x; + top_left_gizmo->y_field1 = top_left_y; top_right_gizmo = add_gizmo(GIZMO_TYPE_DOT); - top_right_gizmo->x_field = top_right_x; - top_right_gizmo->y_field = top_right_y; + top_right_gizmo->x_field1 = top_right_x; + top_right_gizmo->y_field1 = top_right_y; bottom_left_gizmo = add_gizmo(GIZMO_TYPE_DOT); - bottom_left_gizmo->x_field = bottom_left_x; - bottom_left_gizmo->y_field = bottom_left_y; + bottom_left_gizmo->x_field1 = bottom_left_x; + bottom_left_gizmo->y_field1 = bottom_left_y; bottom_right_gizmo = add_gizmo(GIZMO_TYPE_DOT); - bottom_right_gizmo->x_field = bottom_right_x; - bottom_right_gizmo->y_field = bottom_right_y; + bottom_right_gizmo->x_field1 = bottom_right_x; + bottom_right_gizmo->y_field1 = bottom_right_y; vertPath = "cornerpin.vert"; fragPath = "cornerpin.frag"; diff --git a/effects/internal/transformeffect.cpp b/effects/internal/transformeffect.cpp index da03ea718..42b54497c 100644 --- a/effects/internal/transformeffect.cpp +++ b/effects/internal/transformeffect.cpp @@ -68,49 +68,51 @@ TransformEffect::TransformEffect(Clip* c, const EffectMeta* em) : Effect(c, em) // set up gizmos top_left_gizmo = add_gizmo(GIZMO_TYPE_DOT); top_left_gizmo->set_cursor(Qt::SizeFDiagCursor); - top_left_gizmo->x_field = scale_x; + top_left_gizmo->x_field1 = scale_x; top_center_gizmo = add_gizmo(GIZMO_TYPE_DOT); top_center_gizmo->set_cursor(Qt::SizeVerCursor); - top_center_gizmo->y_field = scale_x; + top_center_gizmo->y_field1 = scale_x; top_right_gizmo = add_gizmo(GIZMO_TYPE_DOT); top_right_gizmo->set_cursor(Qt::SizeBDiagCursor); - top_right_gizmo->x_field = scale_x; + top_right_gizmo->x_field1 = scale_x; bottom_left_gizmo = add_gizmo(GIZMO_TYPE_DOT); bottom_left_gizmo->set_cursor(Qt::SizeBDiagCursor); - bottom_left_gizmo->x_field = scale_x; + bottom_left_gizmo->x_field1 = scale_x; bottom_center_gizmo = add_gizmo(GIZMO_TYPE_DOT); bottom_center_gizmo->set_cursor(Qt::SizeVerCursor); - bottom_center_gizmo->y_field = scale_x; + bottom_center_gizmo->y_field1 = scale_x; bottom_right_gizmo = add_gizmo(GIZMO_TYPE_DOT); bottom_right_gizmo->set_cursor(Qt::SizeFDiagCursor); - bottom_right_gizmo->x_field = scale_x; + bottom_right_gizmo->x_field1 = scale_x; left_center_gizmo = add_gizmo(GIZMO_TYPE_DOT); left_center_gizmo->set_cursor(Qt::SizeHorCursor); - left_center_gizmo->x_field = scale_x; + left_center_gizmo->x_field1 = scale_x; right_center_gizmo = add_gizmo(GIZMO_TYPE_DOT); right_center_gizmo->set_cursor(Qt::SizeHorCursor); - right_center_gizmo->x_field = scale_x; + right_center_gizmo->x_field1 = scale_x; anchor_gizmo = add_gizmo(GIZMO_TYPE_TARGET); anchor_gizmo->set_cursor(Qt::SizeAllCursor); - anchor_gizmo->x_field = anchor_x_box; - anchor_gizmo->y_field = anchor_y_box; + anchor_gizmo->x_field1 = anchor_x_box; + anchor_gizmo->y_field1 = anchor_y_box; + anchor_gizmo->x_field2 = position_x; + anchor_gizmo->y_field2 = position_y; rotate_gizmo = add_gizmo(GIZMO_TYPE_DOT); rotate_gizmo->color = Qt::green; rotate_gizmo->set_cursor(Qt::SizeAllCursor); - rotate_gizmo->x_field = rotation; + rotate_gizmo->x_field1 = rotation; rect_gizmo = add_gizmo(GIZMO_TYPE_POLY); - rect_gizmo->x_field = position_x; - rect_gizmo->y_field = position_y; + rect_gizmo->x_field1 = position_x; + rect_gizmo->y_field1 = position_y; connect(uniform_scale_field, SIGNAL(toggled(bool)), this, SLOT(toggle_uniform_scale(bool))); @@ -142,31 +144,31 @@ void TransformEffect::refresh() { double x_percent_multipler = 200.0 / parent_clip->sequence->width; double y_percent_multipler = 200.0 / parent_clip->sequence->height; - top_left_gizmo->x_field_multi = -x_percent_multipler; - top_left_gizmo->y_field_multi = -y_percent_multipler; - top_center_gizmo->y_field_multi = -y_percent_multipler; - top_right_gizmo->x_field_multi = x_percent_multipler; - top_right_gizmo->y_field_multi = -y_percent_multipler; - bottom_left_gizmo->x_field_multi = -x_percent_multipler; - bottom_left_gizmo->y_field_multi = y_percent_multipler; - bottom_center_gizmo->y_field_multi = y_percent_multipler; - bottom_right_gizmo->x_field_multi = x_percent_multipler; - bottom_right_gizmo->y_field_multi = y_percent_multipler; - left_center_gizmo->x_field_multi = -x_percent_multipler; - right_center_gizmo->x_field_multi = x_percent_multipler; - rotate_gizmo->x_field_multi = x_percent_multipler; + top_left_gizmo->x_field_multi1 = -x_percent_multipler; + top_left_gizmo->y_field_multi1 = -y_percent_multipler; + top_center_gizmo->y_field_multi1 = -y_percent_multipler; + top_right_gizmo->x_field_multi1 = x_percent_multipler; + top_right_gizmo->y_field_multi1 = -y_percent_multipler; + bottom_left_gizmo->x_field_multi1 = -x_percent_multipler; + bottom_left_gizmo->y_field_multi1 = y_percent_multipler; + bottom_center_gizmo->y_field_multi1 = y_percent_multipler; + bottom_right_gizmo->x_field_multi1 = x_percent_multipler; + bottom_right_gizmo->y_field_multi1 = y_percent_multipler; + left_center_gizmo->x_field_multi1 = -x_percent_multipler; + right_center_gizmo->x_field_multi1 = x_percent_multipler; + rotate_gizmo->x_field_multi1 = x_percent_multipler; } } void TransformEffect::toggle_uniform_scale(bool enabled) { scale_y->set_enabled(!enabled); - top_center_gizmo->y_field = enabled ? scale_x : scale_y; - bottom_center_gizmo->y_field = enabled ? scale_x : scale_y; - top_left_gizmo->y_field = enabled ? NULL : scale_y; - top_right_gizmo->y_field = enabled ? NULL : scale_y; - bottom_left_gizmo->y_field = enabled ? NULL : scale_y; - bottom_right_gizmo->y_field = enabled ? NULL : scale_y; + top_center_gizmo->y_field1 = enabled ? scale_x : scale_y; + bottom_center_gizmo->y_field1 = enabled ? scale_x : scale_y; + top_left_gizmo->y_field1 = enabled ? NULL : scale_y; + top_right_gizmo->y_field1 = enabled ? NULL : scale_y; + bottom_left_gizmo->y_field1 = enabled ? NULL : scale_y; + bottom_right_gizmo->y_field1 = enabled ? NULL : scale_y; } void TransformEffect::process_coords(double timecode, GLTextureCoords& coords, int data) { diff --git a/io/loadthread.cpp b/io/loadthread.cpp index d35d3e791..90c10ac4f 100644 --- a/io/loadthread.cpp +++ b/io/loadthread.cpp @@ -1,4 +1,4 @@ -#include "loadthread.h" +#include "loadthread.h" #include "mainwindow.h" #include "panels/panels.h" @@ -28,9 +28,10 @@ struct TransitionData { Clip* ctc; }; -LoadThread::LoadThread(LoadDialog* l, bool a) : ld(l), autorecovery(a) { +LoadThread::LoadThread(LoadDialog* l, bool a) : ld(l), autorecovery(a), cancelled(false) { connect(this, SIGNAL(finished()), this, SLOT(deleteLater())); connect(this, SIGNAL(success()), this, SLOT(success_func())); + connect(this, SIGNAL(start_create_effect_ui(QXmlStreamReader*, Clip*, int, const EffectMeta*, long, bool)), this, SLOT(create_effect_ui(QXmlStreamReader*, Clip*, int, const EffectMeta*, long, bool))); } const EffectMeta* get_meta_from_name(const QString& name, int type) { @@ -42,7 +43,7 @@ const EffectMeta* get_meta_from_name(const QString& name, int type) { return NULL; } -void load_effect(QXmlStreamReader& stream, Clip* c) { +void LoadThread::load_effect(QXmlStreamReader& stream, Clip* c) { int effect_id = -1; QString effect_name; bool effect_enabled = true; @@ -95,30 +96,47 @@ void load_effect(QXmlStreamReader& stream, Clip* c) { } else { QString tag = stream.name().toString(); - if (tag == "opening" || tag == "closing") { - // TODO replace NULL/s with something else - - int transition_index = create_transition(c, NULL, meta); - Transition* t = c->sequence->transitions.at(transition_index); - if (effect_length > -1) t->set_length(effect_length); - t->set_enabled(effect_enabled); - t->load(stream); - - if (tag == "opening") { - c->opening_transition = transition_index; - } else { - c->closing_transition = transition_index; - } + int type; + if (tag == "opening") { + type = TA_OPENING_TRANSITION; + } else if (tag == "closing") { + type = TA_CLOSING_TRANSITION; } else { - Effect* e = create_effect(c, meta); - e->set_enabled(effect_enabled); - e->load(stream); - - c->effects.append(e); + type = TA_NO_TRANSITION; } + + emit start_create_effect_ui(&stream, c, type, meta, effect_length, effect_enabled); + + waitCond.wait(&mutex); } } +void LoadThread::read_next(QXmlStreamReader &stream) { + stream.readNext(); + update_current_element_count(stream); +} + +void LoadThread::read_next_start_element(QXmlStreamReader &stream) { + stream.readNextStartElement(); + update_current_element_count(stream); +} + +void LoadThread::update_current_element_count(QXmlStreamReader &stream) { + if (is_element(stream)) { + current_element_count++; + report_progress((current_element_count * 100) / total_element_count); + } +} + +bool LoadThread::is_element(QXmlStreamReader &stream) { + return stream.isStartElement() + && (stream.name() == "folder" + || stream.name() == "footage" + || stream.name() == "sequence" + || stream.name() == "clip" + || stream.name() == "effect"); +} + bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) { f.seek(0); stream.setDevice(stream.device()); @@ -149,8 +167,8 @@ bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) { show_err = true; - while (!stream.atEnd()) { - stream.readNextStartElement(); + while (!stream.atEnd() && !cancelled) { + read_next_start_element(stream); if (stream.name() == root_search) { if (type == LOAD_TYPE_VERSION) { int proj_version = stream.readElementText().toInt(); @@ -164,8 +182,8 @@ bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) { internal_proj_url = stream.readElementText(); internal_proj_dir = QFileInfo(internal_proj_url).absoluteDir(); } else { - while (!(stream.name() == root_search && stream.isEndElement())) { - stream.readNext(); + while (!cancelled && !(stream.name() == root_search && stream.isEndElement())) { + read_next(stream); if (stream.name() == child_search && stream.isStartElement()) { switch (type) { case MEDIA_TYPE_FOLDER: @@ -283,8 +301,8 @@ bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) { QVector transition_data; // load all clips and clip information - while (!(stream.name() == child_search && stream.isEndElement()) && !stream.atEnd()) { - stream.readNextStartElement(); + while (!cancelled && !(stream.name() == child_search && stream.isEndElement()) && !stream.atEnd()) { + read_next_start_element(stream); if (stream.name() == "marker" && stream.isStartElement()) { Marker m; for (int j=0;jclips.append(c); } } + if (cancelled) return false; // correct links, clip IDs, transitions for (int i=0;iclips.size();i++) { @@ -486,11 +507,12 @@ bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) { } } } + if (cancelled) return false; } break; } } - return true; + return !cancelled; } Media* LoadThread::find_loaded_folder_by_id(int id) { @@ -505,6 +527,8 @@ Media* LoadThread::find_loaded_folder_by_id(int id) { } void LoadThread::run() { + mutex.lock(); + QFile file(project_url); if (!file.open(QIODevice::ReadOnly)) { dout << "[ERROR] Could not open file"; @@ -534,17 +558,15 @@ void LoadThread::run() { loaded_sequences.clear(); // get "element" count - int element_count = 0; - while (!stream.atEnd()) { + current_element_count = 0; + total_element_count = 0; + while (!cancelled && !stream.atEnd()) { stream.readNextStartElement(); - if (stream.name() == "folder" - || stream.name() == "footage" - || stream.name() == "sequence" - || stream.name() == "clip" - || stream.name() == "effect") { - element_count++; + if (is_element(stream)) { + total_element_count++; } } + cont = !cancelled; // find project file version cont = load_worker(file, stream, LOAD_TYPE_VERSION); @@ -590,43 +612,105 @@ void LoadThread::run() { cont = load_worker(file, stream, MEDIA_TYPE_SEQUENCE); } - // attach nested sequence clips to their sequences - for (int i=0;imedia == NULL && loaded_clips.at(i)->media_stream == loaded_sequences.at(j)->to_sequence()->save_id) { - loaded_clips.at(i)->media = loaded_sequences.at(j); - loaded_clips.at(i)->refresh(); - break; + if (!cancelled) { + if (!cont) { + if (show_err) QMessageBox::critical(mainWindow, "Project Load Error", "Error loading project: " + error_str, QMessageBox::Ok); + } else if (stream.hasError()) { + dout << "[ERROR] Error parsing XML." << stream.errorString(); + QMessageBox::critical(mainWindow, "XML Parsing Error", "Couldn't load '" + project_url + "'. " + stream.errorString(), QMessageBox::Ok); + cont = false; + } else { + // attach nested sequence clips to their sequences + for (int i=0;imedia == NULL && loaded_clips.at(i)->media_stream == loaded_sequences.at(j)->to_sequence()->save_id) { + loaded_clips.at(i)->media = loaded_sequences.at(j); + loaded_clips.at(i)->refresh(); + break; + } + } } } } - if (!cont) { - if (show_err) QMessageBox::critical(mainWindow, "Project Load Error", "Error loading project: " + error_str, QMessageBox::Ok); - } else if (stream.hasError()) { - dout << "[ERROR] Error parsing XML." << stream.errorString(); - QMessageBox::critical(mainWindow, "XML Parsing Error", "Couldn't load '" + project_url + "'. " + stream.errorString(), QMessageBox::Ok); - cont = false; - } - if (cont) { mainWindow->setWindowModified(autorecovery); + panel_project->add_recent_project(project_url); + emit success(); // run in main thread for (int i=0;istart_preview_generator(loaded_media_items.at(i), true); } } else { - panel_project->new_project(); + mainWindow->new_project(); } - panel_project->add_recent_project(project_url); - file.close(); + + mutex.unlock(); +} + +void LoadThread::cancel() { + waitCond.wakeAll(); + cancelled = true; } void LoadThread::success_func() { if (open_seq != NULL) set_sequence(open_seq); update_ui(false); } + +void LoadThread::create_effect_ui( + QXmlStreamReader* stream, + Clip* c, + int type, + const EffectMeta* meta, + long effect_length, + bool effect_enabled) +{ + /* This is extremely hacky - prepare yourself. + * + * When moving project loading to a separate thread, it was soon discovered + * that effects wouldn't load correctly anymore. They were actually still + * "functional", but there were no controls appearing in EffectControls. + * + * Turns out since Effect creates its UI in its constructor, the UI was + * created in this thread rather than the main GUI thread, which is a big + * no-no. Unfortunately the design of Effect does not separate UI and data, + * so having the UI set up was integral to creating annd loading the effect. + * + * Therefore, rather than rewrite the class (I just rewrote QTreeWidget to + * QTreeView with a custom model/item so I'm exhausted), for + * quick-n-dirty-ness, I made LoadThread offload the effect creation to the + * main thread (and since the effect loads data from the same XML stream, + * the LoadThread has to wait for the effect to finish before it can + * continue. + * + * Sorry. I'll fix it one day. + */ + + if (cancelled) return; + if (type == TA_NO_TRANSITION) { + Effect* e = create_effect(c, meta); + e->set_enabled(effect_enabled); + e->load(*stream); + + c->effects.append(e); + } else { + int transition_index = create_transition(c, NULL, meta); + Transition* t = c->sequence->transitions.at(transition_index); + if (effect_length > -1) t->set_length(effect_length); + t->set_enabled(effect_enabled); + t->load(*stream); + + if (type == TA_OPENING_TRANSITION) { + c->opening_transition = transition_index; + } else { + c->closing_transition = transition_index; + } + } + waitCond.wakeAll(); + +} diff --git a/io/loadthread.h b/io/loadthread.h index 77bd0ca5f..b41123775 100644 --- a/io/loadthread.h +++ b/io/loadthread.h @@ -4,12 +4,15 @@ #include #include #include +#include +#include class Media; class Footage; class Clip; class Sequence; class LoadDialog; +class EffectMeta; class LoadThread : public QThread { @@ -17,15 +20,25 @@ class LoadThread : public QThread public: LoadThread(LoadDialog* l, bool a); void run(); + void cancel(); signals: void success(); + void start_create_effect_ui(QXmlStreamReader* stream, Clip* c, int type, const EffectMeta* meta, long effect_length, bool effect_enabled); + void report_progress(int p); private slots: void success_func(); + void create_effect_ui(QXmlStreamReader* stream, Clip* c, int type, const EffectMeta* meta, long effect_length, bool effect_enabled); private: LoadDialog* ld; bool autorecovery; bool load_worker(QFile& f, QXmlStreamReader& stream, int type); + void load_effect(QXmlStreamReader& stream, Clip* c); + + void read_next(QXmlStreamReader& stream); + void read_next_start_element(QXmlStreamReader& stream); + void update_current_element_count(QXmlStreamReader& stream); + Sequence* open_seq; QVector loaded_media_items; QDir proj_dir; @@ -34,10 +47,20 @@ private: bool show_err; QString error_str; + bool is_element(QXmlStreamReader& stream); + QVector loaded_folders; QVector loaded_clips; QVector loaded_sequences; Media* find_loaded_folder_by_id(int id); + + int current_element_count; + int total_element_count; + + QMutex mutex; + QWaitCondition waitCond; + + bool cancelled; }; #endif // LOADTHREAD_H diff --git a/io/previewgenerator.cpp b/io/previewgenerator.cpp index 83fc426e6..5b9cc097b 100644 --- a/io/previewgenerator.cpp +++ b/io/previewgenerator.cpp @@ -127,7 +127,7 @@ bool PreviewGenerator::retrieve_preview(const QString& hash) { QString thumb_path = get_thumbnail_path(hash, ms); QFile f(thumb_path); if (f.exists() && ms->video_preview.load(thumb_path)) { - dout << "loaded thumb" << ms->file_index << "from" << thumb_path; + //dout << "loaded thumb" << ms->file_index << "from" << thumb_path; ms->preview_done = true; } else { found = false; @@ -139,7 +139,7 @@ bool PreviewGenerator::retrieve_preview(const QString& hash) { QString waveform_path = get_waveform_path(hash, ms); QFile f(waveform_path); if (f.exists()) { - dout << "loaded wave" << ms->file_index << "from" << waveform_path; + //dout << "loaded wave" << ms->file_index << "from" << waveform_path; f.open(QFile::ReadOnly); QByteArray data = f.readAll(); ms->audio_preview.resize(data.size()); @@ -433,7 +433,7 @@ void PreviewGenerator::run() { // see if we already have data for this QFileInfo file_info(footage->url); QString cache_file = footage->url.mid(footage->url.lastIndexOf('/')+1) + QString::number(file_info.size()) + QString::number(file_info.lastModified().toMSecsSinceEpoch()); - dout << "using hash" << cache_file; + //dout << "using hash" << cache_file; QString hash = QCryptographicHash::hash(cache_file.toLatin1(), QCryptographicHash::Md5).toHex(); if (retrieve_preview(hash)) { @@ -445,7 +445,7 @@ void PreviewGenerator::run() { for (int i=0;ivideo_tracks.size();i++) { FootageStream* ms = footage->video_tracks.at(i); ms->video_preview.save(get_thumbnail_path(hash, ms), "PNG"); - dout << "saved" << ms->file_index << "thumbnail to" << get_thumbnail_path(hash, ms); + //dout << "saved" << ms->file_index << "thumbnail to" << get_thumbnail_path(hash, ms); } for (int i=0;iaudio_tracks.size();i++) { FootageStream* ms = footage->audio_tracks.at(i); @@ -453,7 +453,7 @@ void PreviewGenerator::run() { f.open(QFile::WriteOnly); f.write(ms->audio_preview.constData(), ms->audio_preview.size()); f.close(); - dout << "saved" << ms->file_index << "waveform to" << get_waveform_path(hash, ms); + //dout << "saved" << ms->file_index << "waveform to" << get_waveform_path(hash, ms); } sem.release(); diff --git a/mainwindow.cpp b/mainwindow.cpp index ce8bcbd43..3b38c9dee 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -220,6 +220,7 @@ MainWindow::MainWindow(QWidget *parent) : connect(ui->actionCu_t, SIGNAL(triggered(bool)), this, SLOT(cut())); connect(ui->actionCop_y, SIGNAL(triggered(bool)), this, SLOT(copy())); connect(ui->action_Paste, SIGNAL(triggered(bool)), this, SLOT(paste())); + connect(ui->actionProject, SIGNAL(triggered(bool)), this, SLOT(new_project())); } MainWindow::~MainWindow() { @@ -418,7 +419,19 @@ void MainWindow::copy() { void MainWindow::paste() { if ((panel_timeline->focused() || panel_effect_controls->is_focused()) && sequence != NULL) { panel_timeline->paste(false); - } + } +} + +void MainWindow::new_project() { + if (can_close_project()) { + panel_effect_controls->clear_effects(true); + undo_stack.clear(); + project_url.clear(); + panel_project->new_project(); + updateTitle(""); + update_ui(false); + panel_project->source_table->update(); + } } void MainWindow::on_actionSplit_at_Playhead_triggered() @@ -516,18 +529,6 @@ void MainWindow::on_action_Open_Project_triggered() } } -void MainWindow::on_actionProject_triggered() -{ - if (can_close_project()) { - panel_effect_controls->clear_effects(true); - undo_stack.clear(); - project_url.clear(); - panel_project->new_project(); - updateTitle(""); - update_ui(false); - } -} - void MainWindow::on_actionSave_Project_As_triggered() { save_project_as(); diff --git a/mainwindow.h b/mainwindow.h index 18e2fd455..c2dd7bfb7 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -28,6 +28,7 @@ public slots: void cut(); void copy(); void paste(); + void new_project(); void autorecover_interval(); void on_actionNest_triggered(); @@ -70,8 +71,6 @@ private slots: void on_action_Open_Project_triggered(); - void on_actionProject_triggered(); - void on_actionSave_Project_As_triggered(); void on_actionDeselect_All_triggered(); diff --git a/panels/project.cpp b/panels/project.cpp index d4a66e715..6b77d01a0 100644 --- a/panels/project.cpp +++ b/panels/project.cpp @@ -62,11 +62,7 @@ Project::Project(QWidget *parent) : sorter = new QSortFilterProxyModel(this); sorter->setSourceModel(&project_model); - source_table->setModel(sorter); - - /*Media* m = new Media(project_model.get_root()); - project_model.get_root()->appendChild(m);*/ } Project::~Project() { @@ -738,7 +734,6 @@ void Project::new_project() { set_sequence(NULL); panel_footage_viewer->set_media(NULL); clear(); - panel_project->source_table->update(); mainWindow->setWindowModified(false); } @@ -1038,7 +1033,7 @@ void MediaThrobber::animation_update() { if (animation == THROBBER_LIMIT) { animation = 0; } - item->set_icon(QIcon(pixmap.copy(THROBBER_SIZE*animation, 0, THROBBER_SIZE, THROBBER_SIZE))); + project_model.set_icon(item, QIcon(pixmap.copy(THROBBER_SIZE*animation, 0, THROBBER_SIZE, THROBBER_SIZE))); animation++; } @@ -1049,10 +1044,10 @@ void MediaThrobber::stop(int icon_type, bool replace) { } switch (icon_type) { - case ICON_TYPE_VIDEO: item->set_icon(QIcon(":/icons/videosource.png")); break; - case ICON_TYPE_AUDIO: item->set_icon(QIcon(":/icons/audiosource.png")); break; - case ICON_TYPE_IMAGE: item->set_icon(QIcon(":/icons/imagesource.png")); break; - case ICON_TYPE_ERROR: item->set_icon(QIcon(":/icons/error.png")); break; + case ICON_TYPE_VIDEO: project_model.set_icon(item, QIcon(":/icons/videosource.png")); break; + case ICON_TYPE_AUDIO: project_model.set_icon(item, QIcon(":/icons/audiosource.png")); break; + case ICON_TYPE_IMAGE: project_model.set_icon(item, QIcon(":/icons/imagesource.png")); break; + case ICON_TYPE_ERROR: project_model.set_icon(item, QIcon(":/icons/error.png")); break; } // refresh all clips diff --git a/project/effect.cpp b/project/effect.cpp index 3358e4822..7d9d25e0f 100644 --- a/project/effect.cpp +++ b/project/effect.cpp @@ -868,13 +868,21 @@ void Effect::gizmo_move(EffectGizmo* gizmo, int x_movement, int y_movement, doub if (gizmos.at(i) == gizmo) { ComboAction* ca = NULL; if (done) ca = new ComboAction(); - if (gizmo->x_field != NULL) { - gizmo->x_field->set_double_value(gizmo->x_field->get_double_value(timecode) + x_movement*gizmo->x_field_multi); - gizmo->x_field->make_key_from_change(ca); + if (gizmo->x_field1 != NULL) { + gizmo->x_field1->set_double_value(gizmo->x_field1->get_double_value(timecode) + x_movement*gizmo->x_field_multi1); + gizmo->x_field1->make_key_from_change(ca); } - if (gizmo->y_field != NULL) { - gizmo->y_field->set_double_value(gizmo->y_field->get_double_value(timecode) + y_movement*gizmo->y_field_multi); - gizmo->y_field->make_key_from_change(ca); + if (gizmo->y_field1 != NULL) { + gizmo->y_field1->set_double_value(gizmo->y_field1->get_double_value(timecode) + y_movement*gizmo->y_field_multi1); + gizmo->y_field1->make_key_from_change(ca); + } + if (gizmo->x_field2 != NULL) { + gizmo->x_field2->set_double_value(gizmo->x_field2->get_double_value(timecode) + x_movement*gizmo->x_field_multi2); + gizmo->x_field2->make_key_from_change(ca); + } + if (gizmo->y_field2 != NULL) { + gizmo->y_field2->set_double_value(gizmo->y_field2->get_double_value(timecode) + y_movement*gizmo->y_field_multi2); + gizmo->y_field2->make_key_from_change(ca); } if (done) undo_stack.push(ca); break; diff --git a/project/effectgizmo.cpp b/project/effectgizmo.cpp index 0eecbac8f..65bf0cc31 100644 --- a/project/effectgizmo.cpp +++ b/project/effectgizmo.cpp @@ -4,10 +4,14 @@ #include "effectfield.h" EffectGizmo::EffectGizmo(int type) : - x_field(NULL), - x_field_multi(1.0), - y_field(NULL), - y_field_multi(1.0), + x_field1(NULL), + x_field_multi1(1.0), + y_field1(NULL), + y_field_multi1(1.0), + x_field2(NULL), + x_field_multi2(1.0), + y_field2(NULL), + y_field_multi2(1.0), type(type), cursor(-1) { @@ -19,8 +23,10 @@ EffectGizmo::EffectGizmo(int type) : } void EffectGizmo::set_previous_value() { - if (x_field != NULL) static_cast(x_field->ui_element)->set_previous_value(); - if (y_field != NULL) static_cast(y_field->ui_element)->set_previous_value(); + if (x_field1 != NULL) static_cast(x_field1->ui_element)->set_previous_value(); + if (y_field1 != NULL) static_cast(y_field1->ui_element)->set_previous_value(); + if (x_field2 != NULL) static_cast(x_field2->ui_element)->set_previous_value(); + if (y_field2 != NULL) static_cast(y_field2->ui_element)->set_previous_value(); } int EffectGizmo::get_point_count() { diff --git a/project/effectgizmo.h b/project/effectgizmo.h index ec10f4b09..127e0ed14 100644 --- a/project/effectgizmo.h +++ b/project/effectgizmo.h @@ -24,10 +24,14 @@ public: QVector world_pos; QVector screen_pos; - EffectField* x_field; - double x_field_multi; - EffectField* y_field; - double y_field_multi; + EffectField* x_field1; + double x_field_multi1; + EffectField* y_field1; + double y_field_multi1; + EffectField* x_field2; + double x_field_multi2; + EffectField* y_field2; + double y_field_multi2; void set_previous_value(); diff --git a/project/projectmodel.cpp b/project/projectmodel.cpp index 358c443a7..74b284cb6 100644 --- a/project/projectmodel.cpp +++ b/project/projectmodel.cpp @@ -19,15 +19,13 @@ void ProjectModel::destroy_root() { if (panel_footage_viewer != NULL) panel_footage_viewer->viewer_widget->delete_function(); if (root_item != NULL) { - beginRemoveRows(QModelIndex(), 0, root_item->childCount()); delete root_item; - endRemoveRows(); } } void ProjectModel::clear() { - destroy_root(); beginResetModel(); + destroy_root(); root_item = new Media(0); root_item->root = true; endResetModel(); @@ -132,6 +130,13 @@ Media *ProjectModel::getItem(const QModelIndex &index) const { return root_item; } +void ProjectModel::set_icon(Media* m, const QIcon &ico) { + QModelIndex index = createIndex(m->row(), 0, m); + m->set_icon(ico); + emit dataChanged(index, index); + +} + void ProjectModel::appendChild(Media *parent, Media *child) { if (parent == NULL) parent = root_item; beginInsertRows(parent == root_item ? QModelIndex() : createIndex(parent->row(), 0, parent), parent->childCount(), parent->childCount()); diff --git a/project/projectmodel.h b/project/projectmodel.h index e5d6b9a87..42709b848 100644 --- a/project/projectmodel.h +++ b/project/projectmodel.h @@ -32,6 +32,7 @@ public: void removeChild(Media *parent, Media* m); Media *child(int i, Media* parent = NULL); int childCount(Media* parent = NULL); + void set_icon(Media* m, const QIcon &ico); private: Media* root_item; From a9b8908988e44a29fab6cd910d65e18d357e7e5b Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Mon, 17 Dec 2018 13:51:50 +1100 Subject: [PATCH 21/40] switched to global audio rate, support for QT_SCALE_FACTOR --- dialogs/stabilizerdialog.cpp | 20 ++++++++++ dialogs/stabilizerdialog.h | 20 ++++++++++ io/config.cpp | 7 +++- io/config.h | 1 + mainwindow.cpp | 4 +- olive.pro | 6 ++- panels/viewer.cpp | 13 ------- panels/viewer.h | 3 +- playback/audio.cpp | 75 ++++++++++++++++++------------------ playback/audio.h | 2 +- playback/playback.cpp | 2 +- project/media.h | 2 - ui/timelinewidget.cpp | 13 ++++++- ui/timelinewidget.h | 1 + ui/viewerwidget.cpp | 13 ++++++- 15 files changed, 119 insertions(+), 63 deletions(-) create mode 100644 dialogs/stabilizerdialog.cpp create mode 100644 dialogs/stabilizerdialog.h diff --git a/dialogs/stabilizerdialog.cpp b/dialogs/stabilizerdialog.cpp new file mode 100644 index 000000000..935f26f6a --- /dev/null +++ b/dialogs/stabilizerdialog.cpp @@ -0,0 +1,20 @@ +#include "stabilizerdialog.h" + +#include +#include +#include + +StabilizerDialog::StabilizerDialog(QWidget *parent) : QDialog(parent) { + layout = new QVBoxLayout(this); + setLayout(layout); + + enable_stab = new QCheckBox(this); + enable_stab->setText("Enable Stabilizer"); + layout->addWidget(enable_stab); + + buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + layout->addWidget(buttons); + + connect(buttons, SIGNAL(accepted()), this, SLOT(accept())); + connect(buttons, SIGNAL(rejected()), this, SLOT(reject())); +} diff --git a/dialogs/stabilizerdialog.h b/dialogs/stabilizerdialog.h new file mode 100644 index 000000000..0f276b343 --- /dev/null +++ b/dialogs/stabilizerdialog.h @@ -0,0 +1,20 @@ +#ifndef STABILIZERDIALOG_H +#define STABILIZERDIALOG_H + +class QVBoxLayout; +class QCheckBox; +class QDialogButtonBox; + +#include + +class StabilizerDialog : public QDialog +{ +public: + StabilizerDialog(QWidget* parent = 0); +private: + QVBoxLayout* layout; + QCheckBox* enable_stab; + QDialogButtonBox* buttons; +}; + +#endif // STABILIZERDIALOG_H diff --git a/io/config.cpp b/io/config.cpp index 02ab90cdf..4f6980c0b 100644 --- a/io/config.cpp +++ b/io/config.cpp @@ -29,7 +29,8 @@ Config::Config() enable_seek_to_import(false), enable_audio_scrubbing(true), drop_on_media_to_replace(true), - autoscroll(AUTOSCROLL_PAGE_SCROLL) + autoscroll(AUTOSCROLL_PAGE_SCROLL), + audio_rate(48000) {} void Config::load(QString path) { @@ -103,6 +104,9 @@ void Config::load(QString path) { } else if (stream.name() == "Autoscroll") { stream.readNext(); autoscroll = stream.text().toInt(); + } else if (stream.name() == "AudioRate") { + stream.readNext(); + audio_rate = stream.text().toInt(); } } } @@ -148,6 +152,7 @@ void Config::save(QString path) { stream.writeTextElement("AudioScrubbing", QString::number(enable_audio_scrubbing)); stream.writeTextElement("DropFileOnMediaToReplace", QString::number(drop_on_media_to_replace)); stream.writeTextElement("Autoscroll", QString::number(autoscroll)); + stream.writeTextElement("AudioRate", QString::number(audio_rate)); stream.writeEndElement(); // configuration stream.writeEndDocument(); // doc diff --git a/io/config.h b/io/config.h index ea0745650..5b303e641 100644 --- a/io/config.h +++ b/io/config.h @@ -41,6 +41,7 @@ struct Config { bool enable_audio_scrubbing; bool drop_on_media_to_replace; int autoscroll; + int audio_rate; void load(QString path); void save(QString path); diff --git a/mainwindow.cpp b/mainwindow.cpp index 3b38c9dee..cf3abec0e 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -213,7 +213,9 @@ MainWindow::MainWindow(QWidget *parent) : config_dir = data_dir + "/config.xml"; config.load(config_dir); } - } + } + + init_audio(); connect(ui->action_Undo, SIGNAL(triggered(bool)), this, SLOT(undo())); connect(ui->action_Redo, SIGNAL(triggered(bool)), this, SLOT(redo())); diff --git a/olive.pro b/olive.pro index aabbbdaa3..16fa1bfcf 100644 --- a/olive.pro +++ b/olive.pro @@ -94,7 +94,8 @@ SOURCES += \ project/effectfield.cpp \ effects/internal/cubetransition.cpp \ project/effectgizmo.cpp \ - io/clipboard.cpp + io/clipboard.cpp \ + dialogs/stabilizerdialog.cpp HEADERS += \ mainwindow.h \ @@ -168,7 +169,8 @@ HEADERS += \ project/effectfield.h \ effects/internal/cubetransition.h \ project/effectgizmo.h \ - io/clipboard.h + io/clipboard.h \ + dialogs/stabilizerdialog.h FORMS += \ mainwindow.ui \ diff --git a/panels/viewer.cpp b/panels/viewer.cpp index e04655367..6cfc2dc86 100644 --- a/panels/viewer.cpp +++ b/panels/viewer.cpp @@ -102,15 +102,6 @@ void Viewer::reset_all_audio() { clear_audio_ibuffer(); } -void Viewer::assert_audio_device() { - if (is_audio_device_set() && - (audio_output->format().sampleRate() != seq->audio_frequency - || audio_output->format().channelCount() != av_get_channel_layout_nb_channels(seq->audio_layout))) { - closeActiveClips(seq, true); - init_audio(seq); - } -} - long timecode_to_frame(const QString& s, int view, double frame_rate) { QList list = s.split(QRegExp("[:;]")); @@ -242,7 +233,6 @@ void Viewer::seek(long p) { seq->playhead = p; update_parents(); reset_all_audio(); - assert_audio_device(); audio_scrub = true; } @@ -307,7 +297,6 @@ void Viewer::play() { if (seq != NULL) { reset_all_audio(); - assert_audio_device(); if (is_recording_cued() && !start_recording()) { dout << "[ERROR] Failed to record audio"; return; @@ -590,8 +579,6 @@ void Viewer::set_sequence(bool main, Sequence *s) { bool null_sequence = (seq == NULL); - init_audio(seq); - ui->headers->setEnabled(!null_sequence); ui->currentTimecode->setEnabled(!null_sequence); ui->openGLWidget->setEnabled(!null_sequence); diff --git a/panels/viewer.h b/panels/viewer.h index a7e1d84aa..81457674c 100644 --- a/panels/viewer.h +++ b/panels/viewer.h @@ -61,8 +61,7 @@ public: long recording_end; int recording_track; - void reset_all_audio(); - void assert_audio_device(); + void reset_all_audio(); void update_parents(); ViewerWidget* viewer_widget; diff --git a/playback/audio.cpp b/playback/audio.cpp index 591dc59f6..8d04cedce 100644 --- a/playback/audio.cpp +++ b/playback/audio.cpp @@ -40,52 +40,51 @@ bool is_audio_device_set() { return audio_device_set; } -void init_audio(Sequence* s) { +void init_audio() { stop_audio(); - if (s != NULL) { - QAudioFormat audio_format; - audio_format.setSampleRate(s->audio_frequency); - audio_format.setChannelCount(av_get_channel_layout_nb_channels(s->audio_layout)); - audio_format.setSampleSize(16); - audio_format.setCodec("audio/pcm"); - audio_format.setByteOrder(QAudioFormat::LittleEndian); - audio_format.setSampleType(QAudioFormat::SignedInt); + QAudioFormat audio_format; + audio_format.setSampleRate(config.audio_rate); + audio_format.setChannelCount(2); + audio_format.setSampleSize(16); + audio_format.setCodec("audio/pcm"); + audio_format.setByteOrder(QAudioFormat::LittleEndian); + audio_format.setSampleType(QAudioFormat::SignedInt); - QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice()); - QList devs = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput); - dout << "[INFO] Found the following audio devices:"; - for (int i=0;i 0) { - dout << "[WARNING] Default audio returned NULL, attempting to use first device found..."; - info = devs.at(0); - } - dout << "[INFO] Using audio device" << info.deviceName(); + QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice()); + QList devs = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput); + dout << "[INFO] Found the following audio devices:"; + for (int i=0;i 0) { + dout << "[WARNING] Default audio returned NULL, attempting to use first device found..."; + info = devs.at(0); + } + dout << "[INFO] Using audio device" << info.deviceName(); - if (!info.isFormatSupported(audio_format)) { - qWarning() << "[WARNING] Audio format is not supported by backend, using nearest"; - audio_format = info.nearestFormat(audio_format); - } + if (!info.isFormatSupported(audio_format)) { + qWarning() << "[WARNING] Audio format is not supported by backend, using nearest"; + audio_format = info.nearestFormat(audio_format); + } - audio_output = new QAudioOutput(info, audio_format); - audio_output->setNotifyInterval(5); + audio_output = new QAudioOutput(info, audio_format); + audio_output->moveToThread(QApplication::instance()->thread()); + audio_output->setNotifyInterval(5); - // connect - audio_io_device = audio_output->start(); - if (audio_io_device == NULL) { - dout << "[WARNING] Received NULL audio device. No compatible audio output was found."; - } else { - audio_device_set = true; + // connect + audio_io_device = audio_output->start(); + if (audio_io_device == NULL) { + dout << "[WARNING] Received NULL audio device. No compatible audio output was found."; + } else { + audio_device_set = true; - // start sender thread - audio_thread = new AudioSenderThread(); - QObject::connect(audio_output, SIGNAL(notify()), audio_thread, SLOT(notifyReceiver())); - audio_thread->start(QThread::TimeCriticalPriority); + // start sender thread + audio_thread = new AudioSenderThread(); + QObject::connect(audio_output, SIGNAL(notify()), audio_thread, SLOT(notifyReceiver())); + audio_thread->start(QThread::TimeCriticalPriority); - clear_audio_ibuffer(); - } + clear_audio_ibuffer(); } } diff --git a/playback/audio.h b/playback/audio.h index 96fc460cb..795c6ba01 100644 --- a/playback/audio.h +++ b/playback/audio.h @@ -45,7 +45,7 @@ void clear_audio_ibuffer(); bool is_audio_device_set(); -void init_audio(Sequence *s); +void init_audio(); void stop_audio(); int get_buffer_offset_from_frame(double framerate, long frame); diff --git a/playback/playback.cpp b/playback/playback.cpp index d24a03c8b..95d421161 100644 --- a/playback/playback.cpp +++ b/playback/playback.cpp @@ -193,7 +193,7 @@ void get_clip_frame(Clip* c, long playhead) { #ifdef GCF_DEBUG dout << "GCF ==> RESET" << target_pts << "(" << target_frame->pts << "-" << target_frame->pts+target_frame->pkt_duration << ")"; #endif -// target_frame = NULL; + target_frame = NULL; reset = true; c->last_invalid_ts = target_pts; } else { diff --git a/project/media.h b/project/media.h index 7dea09620..11ede6673 100644 --- a/project/media.h +++ b/project/media.h @@ -7,8 +7,6 @@ #define MEDIA_TYPE_FOOTAGE 0 #define MEDIA_TYPE_SEQUENCE 1 #define MEDIA_TYPE_FOLDER 2 -#define MEDIA_TYPE_SOLID 3 -#define MEDIA_TYPE_TONE 4 class Footage; class MediaThrobber; diff --git a/ui/timelinewidget.cpp b/ui/timelinewidget.cpp index aab15f9e4..5e3a318d7 100644 --- a/ui/timelinewidget.cpp +++ b/ui/timelinewidget.cpp @@ -16,6 +16,7 @@ #include "ui_timeline.h" #include "mainwindow.h" #include "ui/viewerwidget.h" +#include "dialogs/stabilizerdialog.h" #include "project/media.h" #include "debug.h" @@ -150,6 +151,11 @@ void TimelineWidget::show_context_menu(const QPoint& pos) { QAction* nestAction = menu.addAction("&Nest"); connect(nestAction, SIGNAL(triggered(bool)), mainWindow, SLOT(on_actionNest_triggered())); + if (selected_clips.size() == 1 && selected_clips.at(0)->media != NULL && selected_clips.at(0)->media->get_type() == MEDIA_TYPE_FOOTAGE) { + QAction* stabilizerAction = menu.addAction("S&tabilizer"); + connect(stabilizerAction, SIGNAL(triggered(bool)), this, SLOT(show_stabilizer_diag())); + } + // set autoscale arbitrarily to the first selected clip autoscaleAction->setChecked(selected_clips.at(0)->autoscale); @@ -229,7 +235,12 @@ void TimelineWidget::rename_clip() { undo_stack.push(rcc); update_ui(true); } - } + } +} + +void TimelineWidget::show_stabilizer_diag() { + StabilizerDialog sd; + sd.exec(); } bool same_sign(int a, int b) { diff --git a/ui/timelinewidget.h b/ui/timelinewidget.h index 0af743afb..2ba01cab4 100644 --- a/ui/timelinewidget.h +++ b/ui/timelinewidget.h @@ -85,6 +85,7 @@ private slots: void toggle_autoscale(); void tooltip_timer_timeout(); void rename_clip(); + void show_stabilizer_diag(); }; #endif // TIMELINEWIDGET_H diff --git a/ui/viewerwidget.cpp b/ui/viewerwidget.cpp index cf51ee2eb..5f51c3811 100644 --- a/ui/viewerwidget.cpp +++ b/ui/viewerwidget.cpp @@ -32,6 +32,7 @@ #include #include #include +#include extern "C" { #include @@ -618,7 +619,17 @@ GLuint ViewerWidget::compose_sequence(QVector& nests, bool render_audio) } else if (rendering) { glViewport(0, 0, s->width, s->height); } else { - glViewport(0, 0, width(), height()); + int widget_width = width(); + int widget_height = height(); + + QString qt_scale_factor = QString(qgetenv("QT_SCALE_FACTOR")); + if (!qt_scale_factor.isEmpty()) { + double scale = qt_scale_factor.toDouble(); + widget_width *= scale; + widget_height *= scale; + } + + glViewport(0, 0, widget_width, widget_height); } glBindTexture(GL_TEXTURE_2D, composite_texture); From 302310ab7bd1319e92f3955015f4322e1f18126b Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Mon, 17 Dec 2018 21:12:51 +1100 Subject: [PATCH 22/40] added setting for fast seeking --- dialogs/preferencesdialog.cpp | 3 +++ dialogs/preferencesdialog.ui | 34 +++++++++++++++++++++++++++++++++- io/config.cpp | 7 ++++++- io/config.h | 1 + playback/playback.cpp | 3 ++- 5 files changed, 45 insertions(+), 3 deletions(-) diff --git a/dialogs/preferencesdialog.cpp b/dialogs/preferencesdialog.cpp index ce869cb12..81bd53211 100644 --- a/dialogs/preferencesdialog.cpp +++ b/dialogs/preferencesdialog.cpp @@ -22,6 +22,8 @@ PreferencesDialog::PreferencesDialog(QWidget *parent) : { ui->setupUi(this); + ui->accurateSeekButton->setChecked(!config.fast_seeking); + ui->fastSeekButton->setChecked(config.fast_seeking); ui->recordingComboBox->setCurrentIndex(config.recording_mode - 1); ui->imgSeqFormatEdit->setText(config.img_seq_formats); } @@ -77,4 +79,5 @@ void PreferencesDialog::setup_kbd_shortcuts(QMenuBar* menubar) { void PreferencesDialog::on_buttonBox_accepted() { config.recording_mode = ui->recordingComboBox->currentIndex() + 1; config.img_seq_formats = ui->imgSeqFormatEdit->text(); + config.fast_seeking = ui->fastSeekButton->isChecked(); } diff --git a/dialogs/preferencesdialog.ui b/dialogs/preferencesdialog.ui index 54344dd23..814d3b4aa 100644 --- a/dialogs/preferencesdialog.ui +++ b/dialogs/preferencesdialog.ui @@ -17,7 +17,7 @@ - 0 + 2 @@ -62,6 +62,38 @@ Behavior + + + Playback + + + + + + Seeking + + + + + + Accurate Seeking +Always show the correct frame (visual may pause briefly as correct frame is retrieved) + + + + + + + Fast Seeking +Seek quickly (may briefly show inaccurate frames when seeking - doesn't affect playback/export) + + + + + + + + Keyboard diff --git a/io/config.cpp b/io/config.cpp index 4f6980c0b..31d9fddfd 100644 --- a/io/config.cpp +++ b/io/config.cpp @@ -30,7 +30,8 @@ Config::Config() enable_audio_scrubbing(true), drop_on_media_to_replace(true), autoscroll(AUTOSCROLL_PAGE_SCROLL), - audio_rate(48000) + audio_rate(48000), + fast_seeking(false) {} void Config::load(QString path) { @@ -107,6 +108,9 @@ void Config::load(QString path) { } else if (stream.name() == "AudioRate") { stream.readNext(); audio_rate = stream.text().toInt(); + } else if (stream.name() == "FastSeeking") { + stream.readNext(); + fast_seeking = (stream.text() == "1"); } } } @@ -153,6 +157,7 @@ void Config::save(QString path) { stream.writeTextElement("DropFileOnMediaToReplace", QString::number(drop_on_media_to_replace)); stream.writeTextElement("Autoscroll", QString::number(autoscroll)); stream.writeTextElement("AudioRate", QString::number(audio_rate)); + stream.writeTextElement("FastSeeking", QString::number(fast_seeking)); stream.writeEndElement(); // configuration stream.writeEndDocument(); // doc diff --git a/io/config.h b/io/config.h index 5b303e641..5bcb10447 100644 --- a/io/config.h +++ b/io/config.h @@ -42,6 +42,7 @@ struct Config { bool drop_on_media_to_replace; int autoscroll; int audio_rate; + bool fast_seeking; void load(QString path); void save(QString path); diff --git a/playback/playback.cpp b/playback/playback.cpp index 95d421161..cfc99bc1b 100644 --- a/playback/playback.cpp +++ b/playback/playback.cpp @@ -11,6 +11,7 @@ #include "project/effect.h" #include "panels/effectcontrols.h" #include "project/media.h" +#include "io/config.h" #include "debug.h" extern "C" { @@ -193,7 +194,7 @@ void get_clip_frame(Clip* c, long playhead) { #ifdef GCF_DEBUG dout << "GCF ==> RESET" << target_pts << "(" << target_frame->pts << "-" << target_frame->pts+target_frame->pkt_duration << ")"; #endif - target_frame = NULL; + if (!config.fast_seeking) target_frame = NULL; reset = true; c->last_invalid_ts = target_pts; } else { From 467d271d5c4d6a5d31304526c54d6b4d100d2194 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Mon, 17 Dec 2018 21:53:42 +1100 Subject: [PATCH 23/40] added setting for focus on hover for #161 --- io/config.cpp | 7 ++++++- io/config.h | 1 + mainwindow.cpp | 45 ++++++++++++++++++++++++++------------------- mainwindow.h | 2 ++ mainwindow.ui | 9 +++++++++ panels/panels.cpp | 32 ++++++++++++++++++++++++++++++++ panels/panels.h | 2 ++ panels/viewer.cpp | 22 ++++++++++++---------- 8 files changed, 90 insertions(+), 30 deletions(-) diff --git a/io/config.cpp b/io/config.cpp index 31d9fddfd..bd0882f68 100644 --- a/io/config.cpp +++ b/io/config.cpp @@ -31,7 +31,8 @@ Config::Config() drop_on_media_to_replace(true), autoscroll(AUTOSCROLL_PAGE_SCROLL), audio_rate(48000), - fast_seeking(false) + fast_seeking(false), + hover_focus(false) {} void Config::load(QString path) { @@ -111,6 +112,9 @@ void Config::load(QString path) { } else if (stream.name() == "FastSeeking") { stream.readNext(); fast_seeking = (stream.text() == "1"); + } else if (stream.name() == "HoverFocus") { + stream.readNext(); + hover_focus = (stream.text() == "1"); } } } @@ -158,6 +162,7 @@ void Config::save(QString path) { stream.writeTextElement("Autoscroll", QString::number(autoscroll)); stream.writeTextElement("AudioRate", QString::number(audio_rate)); stream.writeTextElement("FastSeeking", QString::number(fast_seeking)); + stream.writeTextElement("HoverFocus", QString::number(hover_focus)); stream.writeEndElement(); // configuration stream.writeEndDocument(); // doc diff --git a/io/config.h b/io/config.h index 5bcb10447..4092204ff 100644 --- a/io/config.h +++ b/io/config.h @@ -43,6 +43,7 @@ struct Config { int autoscroll; int audio_rate; bool fast_seeking; + bool hover_focus; void load(QString path); void save(QString path); diff --git a/mainwindow.cpp b/mainwindow.cpp index cf3abec0e..7987b1e51 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -311,34 +311,33 @@ void MainWindow::on_actionSequence_triggered() nsd.exec(); } -void MainWindow::on_actionZoom_In_triggered() -{ - if (panel_timeline->focused()) { +void MainWindow::on_actionZoom_In_triggered() { + QDockWidget* focused_panel = get_focused_panel(); + if (focused_panel == panel_timeline) { panel_timeline->set_zoom(true); - } else if (panel_effect_controls->keyframe_focus()) { + } else if (focused_panel == panel_effect_controls) { panel_effect_controls->set_zoom(true); - } else if (panel_footage_viewer->is_focused()) { + } else if (focused_panel == panel_footage_viewer) { panel_footage_viewer->set_zoom(true); - } else if (panel_sequence_viewer->is_focused()) { + } else if (focused_panel == panel_sequence_viewer) { panel_sequence_viewer->set_zoom(true); } } -void MainWindow::on_actionZoom_out_triggered() -{ - if (panel_timeline->focused()) { +void MainWindow::on_actionZoom_out_triggered() { + QDockWidget* focused_panel = get_focused_panel(); + if (focused_panel == panel_timeline) { panel_timeline->set_zoom(false); - } else if (panel_effect_controls->keyframe_focus()) { + } else if (focused_panel == panel_effect_controls) { panel_effect_controls->set_zoom(false); - } else if (panel_footage_viewer->is_focused()) { + } else if (focused_panel == panel_footage_viewer) { panel_footage_viewer->set_zoom(false); - } else if (panel_sequence_viewer->is_focused()) { + } else if (focused_panel == panel_sequence_viewer) { panel_sequence_viewer->set_zoom(false); } } -void MainWindow::on_actionExport_triggered() -{ +void MainWindow::on_actionExport_triggered() { if (sequence == NULL) { QMessageBox::information(this, "No active sequence", "Please open the sequence you wish to export.", QMessageBox::Ok); } else { @@ -400,9 +399,10 @@ void MainWindow::openSpeedDialog() { void MainWindow::cut() { if (sequence != NULL) { - if (panel_timeline->focused()) { + QDockWidget* focused_panel = get_focused_panel(); + if (panel_timeline == focused_panel) { panel_timeline->copy(true); - } else if (panel_effect_controls->is_focused()) { + } else if (panel_effect_controls == focused_panel) { panel_effect_controls->copy(true); } } @@ -410,16 +410,18 @@ void MainWindow::cut() { void MainWindow::copy() { if (sequence != NULL) { - if (panel_timeline->focused()) { + QDockWidget* focused_panel = get_focused_panel(); + if (panel_timeline == focused_panel) { panel_timeline->copy(false); - } else if (panel_effect_controls->is_focused()) { + } else if (panel_effect_controls == focused_panel) { panel_effect_controls->copy(false); } } } void MainWindow::paste() { - if ((panel_timeline->focused() || panel_effect_controls->is_focused()) && sequence != NULL) { + QDockWidget* focused_panel = get_focused_panel(); + if ((panel_timeline == focused_panel || panel_effect_controls == focused_panel) && sequence != NULL) { panel_timeline->paste(false); } } @@ -708,6 +710,7 @@ void MainWindow::toolMenu_About_To_Be_Shown() { ui->actionEnable_Seek_to_Import->setChecked(config.enable_seek_to_import); ui->actionAudio_Scrubbing->setChecked(config.enable_audio_scrubbing); ui->actionEnable_Drop_on_Media_to_Replace->setChecked(config.drop_on_media_to_replace); + ui->actionEnable_Hover_Focus->setChecked(config.hover_focus); ui->actionNo_autoscroll->setChecked(config.autoscroll == AUTOSCROLL_NO_SCROLL); ui->actionPage_Autoscroll->setChecked(config.autoscroll == AUTOSCROLL_PAGE_SCROLL); @@ -1039,3 +1042,7 @@ void MainWindow::on_actionMilliseconds_triggered() { config.timecode_view = TIMECODE_MILLISECONDS; update_ui(false); } + +void MainWindow::on_actionEnable_Hover_Focus_triggered() { + config.hover_focus = !config.hover_focus; +} diff --git a/mainwindow.h b/mainwindow.h index c2dd7bfb7..5908ccd6b 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -211,6 +211,8 @@ private slots: void on_actionMilliseconds_triggered(); + void on_actionEnable_Hover_Focus_triggered(); + private: Ui::MainWindow *ui; void setup_layout(bool reset); diff --git a/mainwindow.ui b/mainwindow.ui index b444a2a49..9e18008f4 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -187,6 +187,7 @@ + @@ -925,6 +926,14 @@ Milliseconds + + + true + + + Enable Hover Focus for Zooming + + diff --git a/panels/panels.cpp b/panels/panels.cpp index 6f6396b9b..4efe6de21 100644 --- a/panels/panels.cpp +++ b/panels/panels.cpp @@ -7,6 +7,7 @@ #include "project/sequence.h" #include "project/clip.h" #include "project/transition.h" +#include "io/config.h" #include "debug.h" Project* panel_project = 0; @@ -105,3 +106,34 @@ void update_ui(bool modified) { panel_timeline->repaint_timeline(); panel_sequence_viewer->update_viewer(); } + +QDockWidget *get_focused_panel() { + QDockWidget* w = NULL; + if (config.hover_focus) { + if (panel_project->rect().contains(panel_project->mapFromGlobal(QCursor::pos()))) { + w = panel_project; + } else if (panel_effect_controls->rect().contains(panel_effect_controls->mapFromGlobal(QCursor::pos()))) { + w = panel_effect_controls; + } else if (panel_sequence_viewer->rect().contains(panel_sequence_viewer->mapFromGlobal(QCursor::pos()))) { + w = panel_sequence_viewer; + } else if (panel_footage_viewer->rect().contains(panel_footage_viewer->mapFromGlobal(QCursor::pos()))) { + w = panel_footage_viewer; + } else if (panel_timeline->rect().contains(panel_timeline->mapFromGlobal(QCursor::pos()))) { + w = panel_timeline; + } + } + if (w == NULL) { + if (panel_project->is_focused()) { + w = panel_project; + } else if (panel_effect_controls->keyframe_focus()) { + w = panel_effect_controls; + } else if (panel_sequence_viewer->is_focused()) { + w = panel_sequence_viewer; + } else if (panel_footage_viewer->is_focused()) { + w = panel_footage_viewer; + } else if (panel_timeline->focused()) { + w = panel_timeline; + } + } + return w; +} diff --git a/panels/panels.h b/panels/panels.h index 1be614a5d..042a652d8 100644 --- a/panels/panels.h +++ b/panels/panels.h @@ -5,6 +5,7 @@ class Project; class EffectControls; class Viewer; class Timeline; +class QDockWidget; extern Project* panel_project; extern EffectControls* panel_effect_controls; @@ -13,5 +14,6 @@ extern Viewer* panel_footage_viewer; extern Timeline* panel_timeline; void update_ui(bool modified); +QDockWidget* get_focused_panel(); #endif // PANELS_H diff --git a/panels/viewer.cpp b/panels/viewer.cpp index 6cfc2dc86..755da67a8 100644 --- a/panels/viewer.cpp +++ b/panels/viewer.cpp @@ -416,17 +416,19 @@ void Viewer::set_out_point() { } void Viewer::set_zoom(bool in) { - if (in) { - ui->headers->update_zoom(ui->headers->get_zoom()*2); - } else { - ui->headers->update_zoom(qMax(minimum_zoom, ui->headers->get_zoom()*0.5)); + if (seq != NULL) { + if (in) { + ui->headers->update_zoom(ui->headers->get_zoom()*2); + } else { + ui->headers->update_zoom(qMax(minimum_zoom, ui->headers->get_zoom()*0.5)); + } + if (ui->openGLWidget->waveform) { + ui->openGLWidget->waveform_zoom = ui->headers->get_zoom(); + ui->openGLWidget->update(); + } + ui->headers->set_scrollbar_max(ui->horizontalScrollBar, seq->getEndFrame(), ui->headers->width()); + center_scroll_to_playhead(ui->horizontalScrollBar, ui->headers->get_zoom(), seq->playhead); } - if (ui->openGLWidget->waveform) { - ui->openGLWidget->waveform_zoom = ui->headers->get_zoom(); - ui->openGLWidget->update(); - } - ui->headers->set_scrollbar_max(ui->horizontalScrollBar, seq->getEndFrame(), ui->headers->width()); - center_scroll_to_playhead(ui->horizontalScrollBar, ui->headers->get_zoom(), seq->playhead); } void Viewer::set_media(Media* m) { From d3c8e7379bdc0c24ff2a330d9cadc5f54bf8ab35 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 18 Dec 2018 02:03:40 +1100 Subject: [PATCH 24/40] fixed msvc issues and added zoom combobox --- dialogs/loaddialog.h | 4 +- io/loadthread.cpp | 3 +- io/loadthread.h | 6 +-- panels/viewer.ui | 93 ++++++++++++++++++++++++++++++++++++++++ project/projectmodel.cpp | 3 +- 5 files changed, 101 insertions(+), 8 deletions(-) diff --git a/dialogs/loaddialog.h b/dialogs/loaddialog.h index c9099c66d..b5f2cdf4f 100644 --- a/dialogs/loaddialog.h +++ b/dialogs/loaddialog.h @@ -4,9 +4,9 @@ #include class QProgressBar; -class Sequence; +struct Sequence; class Media; -class Footage; +struct Footage; class QHBoxLayout; class LoadThread; diff --git a/io/loadthread.cpp b/io/loadthread.cpp index 90c10ac4f..8a65d1496 100644 --- a/io/loadthread.cpp +++ b/io/loadthread.cpp @@ -634,8 +634,6 @@ void LoadThread::run() { } if (cont) { - mainWindow->setWindowModified(autorecovery); - panel_project->add_recent_project(project_url); emit success(); // run in main thread @@ -658,6 +656,7 @@ void LoadThread::cancel() { } void LoadThread::success_func() { + mainWindow->setWindowModified(autorecovery); if (open_seq != NULL) set_sequence(open_seq); update_ui(false); } diff --git a/io/loadthread.h b/io/loadthread.h index b41123775..522e2867d 100644 --- a/io/loadthread.h +++ b/io/loadthread.h @@ -8,9 +8,9 @@ #include class Media; -class Footage; -class Clip; -class Sequence; +struct Footage; +struct Clip; +struct Sequence; class LoadDialog; class EffectMeta; diff --git a/panels/viewer.ui b/panels/viewer.ui index 0a7613183..738c52458 100644 --- a/panels/viewer.ui +++ b/panels/viewer.ui @@ -80,6 +80,99 @@ + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 260 + 20 + + + + + + + + + Fit + + + + + 10% + + + + + 25% + + + + + 50% + + + + + 75% + + + + + 100% + + + + + 150% + + + + + 200% + + + + + 400% + + + + + + + + Qt::Horizontal + + + + 259 + 20 + + + + + + + diff --git a/project/projectmodel.cpp b/project/projectmodel.cpp index 74b284cb6..6452b623d 100644 --- a/project/projectmodel.cpp +++ b/project/projectmodel.cpp @@ -7,7 +7,8 @@ #include "debug.h" ProjectModel::ProjectModel(QObject *parent) : QAbstractItemModel(parent), root_item(NULL) { - clear(); + root_item = new Media(0); + root_item->root = true; } ProjectModel::~ProjectModel() { From 04274637427726c0148df5cb0199a673a23fed7c Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 18 Dec 2018 11:18:55 +1100 Subject: [PATCH 25/40] added viewer zoom functionality --- panels/viewer.cpp | 39 ++++++++++++++++++++---------- panels/viewer.h | 1 + panels/viewer.ui | 17 +------------ project/media.h | 2 +- ui/viewercontainer.cpp | 55 +++++++++++++++++++++++++++++++++--------- ui/viewercontainer.h | 15 +++++++++--- 6 files changed, 84 insertions(+), 45 deletions(-) diff --git a/panels/viewer.cpp b/panels/viewer.cpp index 755da67a8..667780ea3 100644 --- a/panels/viewer.cpp +++ b/panels/viewer.cpp @@ -14,6 +14,7 @@ #include "ui/audiomonitor.h" #include "ui_timeline.h" #include "playback/playback.h" +#include "ui/viewerwidget.h" #include "debug.h" #define FRAMES_IN_ONE_MINUTE 1798 // 1800 - 2 @@ -46,9 +47,9 @@ Viewer::Viewer(QWidget *parent) : ui->headers->viewer = this; ui->headers->snapping = false; ui->headers->show_text(false); - ui->glViewerPane->child = ui->openGLWidget; - ui->openGLWidget->viewer = this; - viewer_widget = ui->openGLWidget; + ui->glViewerPane->viewer = this; + viewer_widget = ui->glViewerPane->child; + viewer_widget->viewer = this; set_media(NULL); ui->currentTimecode->setEnabled(false); @@ -63,7 +64,8 @@ Viewer::Viewer(QWidget *parent) : connect(&playback_updater, SIGNAL(timeout()), this, SLOT(timer_update())); connect(&recording_flasher, SIGNAL(timeout()), this, SLOT(recording_flasher_update())); connect(ui->horizontalScrollBar, SIGNAL(valueChanged(int)), ui->headers, SLOT(set_scroll(int))); - connect(ui->horizontalScrollBar, SIGNAL(valueChanged(int)), ui->openGLWidget, SLOT(set_waveform_scroll(int))); + connect(ui->horizontalScrollBar, SIGNAL(valueChanged(int)), viewer_widget, SLOT(set_waveform_scroll(int))); + connect(ui->zoomComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(zoom_update(int))); update_playhead_timecode(0); update_end_timecode(); @@ -75,7 +77,7 @@ Viewer::~Viewer() { bool Viewer::is_focused() { return ui->headers->hasFocus() - || ui->openGLWidget->hasFocus() + || viewer_widget->hasFocus() || ui->pushButton->hasFocus() || ui->pushButton_2->hasFocus() || ui->pushButton_3->hasFocus() @@ -378,7 +380,7 @@ void Viewer::update_header_zoom() { minimum_zoom = (sequenceEndFrame > 0) ? ((double) ui->headers->width() / (double) sequenceEndFrame) : 1; ui->headers->update_zoom(qMax(ui->headers->get_zoom(), minimum_zoom)); ui->headers->set_scrollbar_max(ui->horizontalScrollBar, sequenceEndFrame, ui->headers->width()); - ui->openGLWidget->waveform_zoom = ui->headers->get_zoom(); + viewer_widget->waveform_zoom = ui->headers->get_zoom(); } else { ui->headers->update(); } @@ -422,9 +424,9 @@ void Viewer::set_zoom(bool in) { } else { ui->headers->update_zoom(qMax(minimum_zoom, ui->headers->get_zoom()*0.5)); } - if (ui->openGLWidget->waveform) { - ui->openGLWidget->waveform_zoom = ui->headers->get_zoom(); - ui->openGLWidget->update(); + if (viewer_widget->waveform) { + viewer_widget->waveform_zoom = ui->headers->get_zoom(); + viewer_widget->update(); } ui->headers->set_scrollbar_max(ui->horizontalScrollBar, seq->getEndFrame(), ui->headers->width()); center_scroll_to_playhead(ui->horizontalScrollBar, ui->headers->get_zoom(), seq->playhead); @@ -555,7 +557,19 @@ void Viewer::recording_flasher_update() { ui->pushButton_3->setStyleSheet("background: red;"); } else { ui->pushButton_3->setStyleSheet(QString()); - } + } +} + +void Viewer::zoom_update(int i) { + if (i == 0) { + ui->glViewerPane->fit = true; + } else { + ui->glViewerPane->fit = false; + QString pc = ui->zoomComboBox->itemText(i); + pc = pc.left(pc.length() - 1); + ui->glViewerPane->zoom = pc.toDouble()*0.01; + } + ui->glViewerPane->adjust(); } void Viewer::clean_created_seq() { @@ -583,8 +597,8 @@ void Viewer::set_sequence(bool main, Sequence *s) { ui->headers->setEnabled(!null_sequence); ui->currentTimecode->setEnabled(!null_sequence); - ui->openGLWidget->setEnabled(!null_sequence); - ui->openGLWidget->setVisible(!null_sequence); + viewer_widget->setEnabled(!null_sequence); + viewer_widget->setVisible(!null_sequence); ui->pushButton->setEnabled(!null_sequence); ui->pushButton_2->setEnabled(!null_sequence); ui->pushButton_3->setEnabled(!null_sequence); @@ -599,7 +613,6 @@ void Viewer::set_sequence(bool main, Sequence *s) { update_playhead_timecode(seq->playhead); update_end_timecode(); - ui->glViewerPane->aspect_ratio = (float) seq->width / (float) seq->height; ui->glViewerPane->adjust(); setWindowTitle(panel_name + seq->name); diff --git a/panels/viewer.h b/panels/viewer.h index 81457674c..449f884e2 100644 --- a/panels/viewer.h +++ b/panels/viewer.h @@ -83,6 +83,7 @@ private slots: void update_playhead(); void timer_update(); void recording_flasher_update(); + void zoom_update(int i); private: void clean_created_seq(); void set_sequence(bool main, Sequence* s); diff --git a/panels/viewer.ui b/panels/viewer.ui index 738c52458..acb55f077 100644 --- a/panels/viewer.ui +++ b/panels/viewer.ui @@ -49,16 +49,6 @@ 0 - - - - 110 - 50 - 300 - 200 - - - @@ -109,7 +99,7 @@ - + Fit @@ -332,11 +322,6 @@ - - ViewerWidget - QOpenGLWidget -
ui/viewerwidget.h
-
ViewerContainer QWidget diff --git a/project/media.h b/project/media.h index 11ede6673..b917d0530 100644 --- a/project/media.h +++ b/project/media.h @@ -8,7 +8,7 @@ #define MEDIA_TYPE_SEQUENCE 1 #define MEDIA_TYPE_FOLDER 2 -class Footage; +struct Footage; class MediaThrobber; struct Sequence; #include diff --git a/ui/viewercontainer.cpp b/ui/viewercontainer.cpp index 82b203bba..39f9fa66f 100644 --- a/ui/viewercontainer.cpp +++ b/ui/viewercontainer.cpp @@ -2,43 +2,74 @@ #include #include +#include +#include #include "viewerwidget.h" +#include "panels/viewer.h" +#include "project/sequence.h" +#include "debug.h" // enforces aspect ratio ViewerContainer::ViewerContainer(QWidget *parent) : - QWidget(parent), - aspect_ratio(1), + QScrollArea(parent), + fit(true), child(NULL) -{} +{ + setFrameShadow(QFrame::Plain); + setFrameShape(QFrame::NoFrame); + + area = new QWidget(this); + area->move(0, 0); + setWidget(area); + + child = new ViewerWidget(area); +} + +ViewerContainer::~ViewerContainer() { + delete area; +} void ViewerContainer::adjust() { - if (child != NULL) { + if (viewer->seq != NULL) { if (child->waveform) { child->move(0, 0); child->resize(size()); - } else { - QSize widget_size = size(); + } else if (fit) { + double aspect_ratio = double(viewer->seq->width)/double(viewer->seq->height); + int widget_x = 0; int widget_y = 0; - int widget_width = widget_size.width(); - int widget_height = widget_size.height(); + int widget_width = width(); + int widget_height = height(); float widget_ar = (float) widget_width /(float) widget_height; - bool widget_is_larger_than_sequence = widget_ar > aspect_ratio; + bool widget_is_wider_than_sequence = widget_ar > aspect_ratio; - if (widget_is_larger_than_sequence) { + if (widget_is_wider_than_sequence) { widget_width = widget_height * aspect_ratio; - widget_x = (widget_size.width() / 2) - (widget_width / 2); + widget_x = (width() / 2) - (widget_width / 2); } else { widget_height = widget_width / aspect_ratio; - widget_y = (widget_size.height() / 2) - (widget_height / 2); + widget_y = (height() / 2) - (widget_height / 2); } child->move(widget_x, widget_y); child->resize(widget_width, widget_height); + } else { + int zoomed_width = double(viewer->seq->width)*zoom; + int zoomed_height = double(viewer->seq->height)*zoom; + int zoomed_x = 0; + int zoomed_y = 0; + + if (zoomed_width < width()) zoomed_x = (width()>>1)-(zoomed_width>>1); + if (zoomed_height < height()) zoomed_y = (height()>>1)-(zoomed_height>>1); + + child->move(zoomed_x, zoomed_y); + child->resize(zoomed_width, zoomed_height); } } + area->resize(qMax(width(), child->width()), qMax(height(), child->height())); } void ViewerContainer::resizeEvent(QResizeEvent *event) { diff --git a/ui/viewercontainer.h b/ui/viewercontainer.h index c33a65ac6..6b07b9365 100644 --- a/ui/viewercontainer.h +++ b/ui/viewercontainer.h @@ -1,15 +1,21 @@ #ifndef VIEWERCONTAINER_H #define VIEWERCONTAINER_H -#include +#include +class Viewer; class ViewerWidget; -class ViewerContainer : public QWidget +class ViewerContainer : public QScrollArea { Q_OBJECT public: explicit ViewerContainer(QWidget *parent = 0); - float aspect_ratio; + ~ViewerContainer(); + + bool fit; + double zoom; + + Viewer* viewer; ViewerWidget* child; void adjust(); @@ -19,6 +25,9 @@ protected: signals: public slots: + +private: + QWidget* area; }; #endif // VIEWERCONTAINER_H From 26c3534048226bcb9e2d4b7e72b3e00c3548fc8f Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 18 Dec 2018 11:38:13 +1100 Subject: [PATCH 26/40] implemented middle click drag on zoomed viewer --- ui/viewercontainer.cpp | 19 +++++++++++++++++++ ui/viewercontainer.h | 7 +++++++ ui/viewerwidget.cpp | 7 ++++++- ui/viewerwidget.h | 2 ++ 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/ui/viewercontainer.cpp b/ui/viewercontainer.cpp index 39f9fa66f..be24c84bf 100644 --- a/ui/viewercontainer.cpp +++ b/ui/viewercontainer.cpp @@ -24,12 +24,31 @@ ViewerContainer::ViewerContainer(QWidget *parent) : setWidget(area); child = new ViewerWidget(area); + child->container = this; } ViewerContainer::~ViewerContainer() { delete area; } +void ViewerContainer::dragScrollPress(const QPoint &p) { + drag_start_x = p.x(); + drag_start_y = p.y(); + horiz_start = horizontalScrollBar()->value(); + vert_start = verticalScrollBar()->value(); +} + +void ViewerContainer::dragScrollMove(const QPoint &p) { + int true_x = p.x() + (horiz_start - horizontalScrollBar()->value()); + int true_y = p.y() + (vert_start - verticalScrollBar()->value()); + + horizontalScrollBar()->setValue(horizontalScrollBar()->value() + (drag_start_x - true_x)); + verticalScrollBar()->setValue(verticalScrollBar()->value() + (drag_start_y - true_y)); + + drag_start_x = true_x; + drag_start_y = true_y; +} + void ViewerContainer::adjust() { if (viewer->seq != NULL) { if (child->waveform) { diff --git a/ui/viewercontainer.h b/ui/viewercontainer.h index 6b07b9365..921e2b9aa 100644 --- a/ui/viewercontainer.h +++ b/ui/viewercontainer.h @@ -15,6 +15,9 @@ public: bool fit; double zoom; + void dragScrollPress(const QPoint&); + void dragScrollMove(const QPoint&); + Viewer* viewer; ViewerWidget* child; void adjust(); @@ -28,6 +31,10 @@ public slots: private: QWidget* area; + int drag_start_x; + int drag_start_y; + int horiz_start; + int vert_start; }; #endif // VIEWERCONTAINER_H diff --git a/ui/viewerwidget.cpp b/ui/viewerwidget.cpp index 5f51c3811..071c428e3 100644 --- a/ui/viewerwidget.cpp +++ b/ui/viewerwidget.cpp @@ -19,6 +19,7 @@ #include "ui/collapsiblewidget.h" #include "project/undo.h" #include "project/media.h" +#include "ui/viewercontainer.h" #include #include @@ -215,6 +216,8 @@ void ViewerWidget::move_gizmos(QMouseEvent *event, bool done) { void ViewerWidget::mousePressEvent(QMouseEvent* event) { if (waveform) { seek_from_click(event->x()); + } else if (event->buttons() & Qt::MiddleButton) { + container->dragScrollPress(event->pos()); } else { drag_start_x = event->pos().x(); drag_start_y = event->pos().y(); @@ -232,9 +235,11 @@ void ViewerWidget::mousePressEvent(QMouseEvent* event) { } void ViewerWidget::mouseMoveEvent(QMouseEvent* event) { - if (dragging) { + if (dragging) { if (waveform) { seek_from_click(event->x()); + } else if (event->buttons() & Qt::MiddleButton) { + container->dragScrollMove(event->pos()); } else if (gizmos == NULL) { QDrag* drag = new QDrag(this); QMimeData* mimeData = new QMimeData; diff --git a/ui/viewerwidget.h b/ui/viewerwidget.h index 965e86f3d..8d9899151 100644 --- a/ui/viewerwidget.h +++ b/ui/viewerwidget.h @@ -16,6 +16,7 @@ struct FootageStream; class QOpenGLFramebufferObject; class Effect; class EffectGizmo; +class ViewerContainer; struct GLTextureCoords; class ViewerWidget : public QOpenGLWidget, QOpenGLFunctions @@ -27,6 +28,7 @@ public: void paintGL(); void initializeGL(); Viewer* viewer; + ViewerContainer* container; QOpenGLFramebufferObject* default_fbo; From 27c4520e58fce5dabdac7f0e6d5bccf2ff322dce Mon Sep 17 00:00:00 2001 From: alcomposer Date: Tue, 18 Dec 2018 19:52:33 +1030 Subject: [PATCH 27/40] Add right click menu to clear I/O points when they exist in timelineheader --- mainwindow.h | 3 +-- ui/timelineheader.cpp | 17 +++++++++++++++++ ui/timelineheader.h | 1 + 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/mainwindow.h b/mainwindow.h index 5908ccd6b..fafdfe1b1 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -31,6 +31,7 @@ public slots: void new_project(); void autorecover_interval(); void on_actionNest_triggered(); + void on_actionClear_In_Out_triggered(); protected: void closeEvent(QCloseEvent *); @@ -157,8 +158,6 @@ private slots: void on_actionSet_Out_Point_triggered(); - void on_actionClear_In_Out_triggered(); - void on_actionDelete_In_Out_triggered(); void on_actionRipple_Delete_In_Out_triggered(); diff --git a/ui/timelineheader.cpp b/ui/timelineheader.cpp index d39976bbd..329bac195 100644 --- a/ui/timelineheader.cpp +++ b/ui/timelineheader.cpp @@ -1,5 +1,6 @@ #include "timelineheader.h" +#include "mainwindow.h" #include "panels/panels.h" #include "panels/timeline.h" #include "project/sequence.h" @@ -12,6 +13,8 @@ #include #include #include +#include +#include #define CLICK_RANGE 5 #define PLAYHEAD_SIZE 6 @@ -45,6 +48,9 @@ TimelineHeader::TimelineHeader(QWidget *parent) : setMouseTracking(true); setFocusPolicy(Qt::ClickFocus); show_text(true); + + setContextMenuPolicy(Qt::CustomContextMenu); + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(show_context_menu(const QPoint &))); } void TimelineHeader::set_scroll(int s) { @@ -394,3 +400,14 @@ void TimelineHeader::paintEvent(QPaintEvent*) { p.fillPath(path, Qt::red); } } + +void TimelineHeader::show_context_menu(const QPoint &pos) +{ + QMenu contextMenu(tr("Context menu"), this); + + QAction clear_in_out("Clear In/Out Points", this); + if (!viewer->seq->using_workarea) clear_in_out.setVisible(false); + connect(&clear_in_out, SIGNAL(triggered()), mainWindow, SLOT(on_actionClear_In_Out_triggered())); + contextMenu.addAction(&clear_in_out); + contextMenu.exec(mapToGlobal(pos)); +} diff --git a/ui/timelineheader.h b/ui/timelineheader.h index 88adc3151..4bced42af 100644 --- a/ui/timelineheader.h +++ b/ui/timelineheader.h @@ -29,6 +29,7 @@ public: public slots: void set_scroll(int); void set_visible_in(long i); + void show_context_menu(const QPoint &pos); protected: void paintEvent(QPaintEvent*); From 4de524e7faa684305d9fd02549e454fd7b3c811e Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 20 Dec 2018 15:09:28 +1100 Subject: [PATCH 28/40] added full screen mode #171 --- mainwindow.cpp | 11 +++++++++++ mainwindow.h | 1 + mainwindow.ui | 13 +++++++++++++ 3 files changed, 25 insertions(+) diff --git a/mainwindow.cpp b/mainwindow.cpp index 7987b1e51..b317aa017 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -223,6 +223,7 @@ MainWindow::MainWindow(QWidget *parent) : connect(ui->actionCop_y, SIGNAL(triggered(bool)), this, SLOT(copy())); connect(ui->action_Paste, SIGNAL(triggered(bool)), this, SLOT(paste())); connect(ui->actionProject, SIGNAL(triggered(bool)), this, SLOT(new_project())); + connect(ui->actionFull_Screen, SIGNAL(triggered(bool)), this, SLOT(toggle_full_screen())); } MainWindow::~MainWindow() { @@ -677,6 +678,8 @@ void MainWindow::viewMenu_About_To_Be_Shown() { ui->action4_3->setChecked(config.show_title_safe_area && config.use_custom_title_safe_ratio && config.custom_title_safe_ratio == 4.0/3.0); ui->action16_9->setChecked(config.show_title_safe_area && config.use_custom_title_safe_ratio && config.custom_title_safe_ratio == 16.0/9.0); ui->actionCustom->setChecked(config.show_title_safe_area && config.use_custom_title_safe_ratio && !ui->action4_3->isChecked() && !ui->action16_9->isChecked()); + + ui->actionFull_Screen->setChecked(windowState() == Qt::WindowFullScreen); } void MainWindow::on_actionFrames_triggered() @@ -826,6 +829,14 @@ void MainWindow::on_actionClear_In_Out_triggered() { } } +void MainWindow::toggle_full_screen() { + if (windowState() == Qt::WindowFullScreen) { + setWindowState(Qt::WindowMaximized); + } else { + setWindowState(Qt::WindowFullScreen); + } +} + void MainWindow::on_actionDelete_In_Out_triggered() { if (panel_timeline->focused()) { diff --git a/mainwindow.h b/mainwindow.h index fafdfe1b1..048c30a7b 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -32,6 +32,7 @@ public slots: void autorecover_interval(); void on_actionNest_triggered(); void on_actionClear_In_Out_triggered(); + void toggle_full_screen(); protected: void closeEvent(QCloseEvent *); diff --git a/mainwindow.ui b/mainwindow.ui index 9e18008f4..61cc110c8 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -147,6 +147,8 @@ + + @@ -934,6 +936,17 @@ Enable Hover Focus for Zooming + + + true + + + Full Screen + + + F11 + + From 9797a3bf4b75104486eccef559b7b0d089c816c1 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 20 Dec 2018 23:20:00 +1100 Subject: [PATCH 29/40] fixed a bunch of multithreaded loading issues and #174 --- dialogs/loaddialog.cpp | 9 ++++-- dialogs/loaddialog.h | 1 + io/clipboard.cpp | 1 + io/loadthread.cpp | 60 +++++++++++++++++++++++---------------- io/loadthread.h | 5 +++- mainwindow.cpp | 6 ++-- panels/effectcontrols.cpp | 2 ++ panels/panels.cpp | 2 +- panels/project.cpp | 22 +++++++------- playback/playback.cpp | 5 ++-- project/clip.cpp | 1 + project/effect.cpp | 5 ++-- project/media.cpp | 4 +-- ui/collapsiblewidget.cpp | 4 ++- ui/timelinewidget.cpp | 4 +-- ui/viewerwidget.cpp | 2 +- 16 files changed, 82 insertions(+), 51 deletions(-) diff --git a/dialogs/loaddialog.cpp b/dialogs/loaddialog.cpp index 1d25a9fa8..706b1193a 100644 --- a/dialogs/loaddialog.cpp +++ b/dialogs/loaddialog.cpp @@ -39,6 +39,7 @@ LoadDialog::LoadDialog(QWidget *parent, bool autorecovery) : QDialog(parent) { lt = new LoadThread(this, autorecovery); QObject::connect(lt, SIGNAL(success()), this, SLOT(thread_done())); + QObject::connect(lt, SIGNAL(error()), this, SLOT(die())); QObject::connect(lt, SIGNAL(report_progress(int)), bar, SLOT(setValue(int))); lt->start(); } @@ -46,8 +47,12 @@ LoadDialog::LoadDialog(QWidget *parent, bool autorecovery) : QDialog(parent) { void LoadDialog::cancel() { lt->cancel(); lt->wait(); - mainWindow->new_project(); - reject(); + die(); +} + +void LoadDialog::die() { + mainWindow->new_project(); + reject(); } void LoadDialog::thread_done() { diff --git a/dialogs/loaddialog.h b/dialogs/loaddialog.h index b5f2cdf4f..736acfaef 100644 --- a/dialogs/loaddialog.h +++ b/dialogs/loaddialog.h @@ -17,6 +17,7 @@ public: LoadDialog(QWidget* parent, bool autorecovery); private slots: void cancel(); + void die(); void thread_done(); private: QProgressBar* bar; diff --git a/io/clipboard.cpp b/io/clipboard.cpp index bca0912df..b02761d5e 100644 --- a/io/clipboard.cpp +++ b/io/clipboard.cpp @@ -1,6 +1,7 @@ #include "clipboard.h" #include "project/clip.h" +#include "project/effect.h" int clipboard_type = CLIPBOARD_TYPE_CLIP; QVector clipboard; diff --git a/io/loadthread.cpp b/io/loadthread.cpp index 8a65d1496..001d29669 100644 --- a/io/loadthread.cpp +++ b/io/loadthread.cpp @@ -31,6 +31,7 @@ struct TransitionData { LoadThread::LoadThread(LoadDialog* l, bool a) : ld(l), autorecovery(a), cancelled(false) { connect(this, SIGNAL(finished()), this, SLOT(deleteLater())); connect(this, SIGNAL(success()), this, SLOT(success_func())); + connect(this, SIGNAL(error()), this, SLOT(error_func())); connect(this, SIGNAL(start_create_effect_ui(QXmlStreamReader*, Clip*, int, const EffectMeta*, long, bool)), this, SLOT(create_effect_ui(QXmlStreamReader*, Clip*, int, const EffectMeta*, long, bool))); } @@ -572,23 +573,7 @@ void LoadThread::run() { cont = load_worker(file, stream, LOAD_TYPE_VERSION); // find project's internal URL - cont = load_worker(file, stream, LOAD_TYPE_URL); - if (autorecovery) { - QString orig_filename = internal_proj_url; - int insert_index = internal_proj_url.lastIndexOf(".ove", -1, Qt::CaseInsensitive); - if (insert_index == -1) insert_index = internal_proj_url.length(); - int counter = 1; - while (QFileInfo::exists(orig_filename)) { - orig_filename = internal_proj_url; - QString recover_text = "recovered"; - if (counter > 1) { - recover_text += " " + QString::number(counter); - } - orig_filename.insert(insert_index, " (" + recover_text + ")"); - counter++; - } - mainWindow->updateTitle(orig_filename); - } + cont = load_worker(file, stream, LOAD_TYPE_URL); // load folders first if (cont) { @@ -614,11 +599,14 @@ void LoadThread::run() { if (!cancelled) { if (!cont) { - if (show_err) QMessageBox::critical(mainWindow, "Project Load Error", "Error loading project: " + error_str, QMessageBox::Ok); + xml_error = false; + if (show_err) emit error(); } else if (stream.hasError()) { - dout << "[ERROR] Error parsing XML." << stream.errorString(); - QMessageBox::critical(mainWindow, "XML Parsing Error", "Couldn't load '" + project_url + "'. " + stream.errorString(), QMessageBox::Ok); + error_str = stream.errorString(); + xml_error = true; + emit error(); cont = false; + } else { // attach nested sequence clips to their sequences for (int i=0;istart_preview_generator(loaded_media_items.at(i), true); } - } else { - mainWindow->new_project(); - } + } file.close(); @@ -652,10 +638,36 @@ void LoadThread::run() { void LoadThread::cancel() { waitCond.wakeAll(); - cancelled = true; + cancelled = true; +} + +void LoadThread::error_func() { + if (xml_error) { + dout << "[ERROR] Error parsing XML." << error_str; + QMessageBox::critical(mainWindow, "XML Parsing Error", "Couldn't load '" + project_url + "'. " + error_str, QMessageBox::Ok); + } else { + QMessageBox::critical(mainWindow, "Project Load Error", "Error loading project: " + error_str, QMessageBox::Ok); + } } void LoadThread::success_func() { + if (autorecovery) { + QString orig_filename = internal_proj_url; + int insert_index = internal_proj_url.lastIndexOf(".ove", -1, Qt::CaseInsensitive); + if (insert_index == -1) insert_index = internal_proj_url.length(); + int counter = 1; + while (QFileInfo::exists(orig_filename)) { + orig_filename = internal_proj_url; + QString recover_text = "recovered"; + if (counter > 1) { + recover_text += " " + QString::number(counter); + } + orig_filename.insert(insert_index, " (" + recover_text + ")"); + counter++; + } + mainWindow->updateTitle(orig_filename); + } + mainWindow->setWindowModified(autorecovery); if (open_seq != NULL) set_sequence(open_seq); update_ui(false); diff --git a/io/loadthread.h b/io/loadthread.h index 522e2867d..d0b86f5f9 100644 --- a/io/loadthread.h +++ b/io/loadthread.h @@ -12,7 +12,7 @@ struct Footage; struct Clip; struct Sequence; class LoadDialog; -class EffectMeta; +struct EffectMeta; class LoadThread : public QThread { @@ -23,9 +23,11 @@ public: void cancel(); signals: void success(); + void error(); void start_create_effect_ui(QXmlStreamReader* stream, Clip* c, int type, const EffectMeta* meta, long effect_length, bool effect_enabled); void report_progress(int p); private slots: + void error_func(); void success_func(); void create_effect_ui(QXmlStreamReader* stream, Clip* c, int type, const EffectMeta* meta, long effect_length, bool effect_enabled); private: @@ -61,6 +63,7 @@ private: QWaitCondition waitCond; bool cancelled; + bool xml_error; }; #endif // LOADTHREAD_H diff --git a/mainwindow.cpp b/mainwindow.cpp index b317aa017..2efb33e42 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -227,6 +227,9 @@ MainWindow::MainWindow(QWidget *parent) : } MainWindow::~MainWindow() { + panel_sequence_viewer->viewer_widget->delete_function(); + panel_footage_viewer->viewer_widget->delete_function(); + set_sequence(NULL); QString data_dir = get_data_path(); @@ -439,8 +442,7 @@ void MainWindow::new_project() { } } -void MainWindow::on_actionSplit_at_Playhead_triggered() -{ +void MainWindow::on_actionSplit_at_Playhead_triggered() { if (panel_timeline->focused()) { panel_timeline->split_at_playhead(); } diff --git a/panels/effectcontrols.cpp b/panels/effectcontrols.cpp index c31f46e13..3fa48359c 100644 --- a/panels/effectcontrols.cpp +++ b/panels/effectcontrols.cpp @@ -201,6 +201,8 @@ void EffectControls::show_effect_menu(int type, int subtype) { void EffectControls::clear_effects(bool clear_cache) { // clear existing clips + deselect_all_effects(NULL); + QVBoxLayout* video_layout = static_cast(ui->video_effect_area->layout()); QVBoxLayout* audio_layout = static_cast(ui->audio_effect_area->layout()); QLayoutItem* item; diff --git a/panels/panels.cpp b/panels/panels.cpp index 4efe6de21..dd8206ccc 100644 --- a/panels/panels.cpp +++ b/panels/panels.cpp @@ -125,7 +125,7 @@ QDockWidget *get_focused_panel() { if (w == NULL) { if (panel_project->is_focused()) { w = panel_project; - } else if (panel_effect_controls->keyframe_focus()) { + } else if (panel_effect_controls->keyframe_focus() || panel_effect_controls->is_focused()) { w = panel_effect_controls; } else if (panel_sequence_viewer->is_focused()) { w = panel_sequence_viewer; diff --git a/panels/project.cpp b/panels/project.cpp index 6b77d01a0..280fab531 100644 --- a/panels/project.cpp +++ b/panels/project.cpp @@ -859,16 +859,18 @@ void Project::save_folder(QXmlStreamWriter& stream, int type, bool set_ids_only, stream.writeAttribute("maintainpitch", QString::number(c->maintain_audio_pitch)); stream.writeAttribute("reverse", QString::number(c->reverse)); - stream.writeAttribute("type", QString::number(c->media->get_type())); - switch (c->media->get_type()) { - case MEDIA_TYPE_FOOTAGE: - stream.writeAttribute("media", QString::number(c->media->to_footage()->save_id)); - stream.writeAttribute("stream", QString::number(c->media_stream)); - break; - case MEDIA_TYPE_SEQUENCE: - stream.writeAttribute("sequence", QString::number(c->media->to_sequence()->save_id)); - break; - } + if (c->media != NULL) { + stream.writeAttribute("type", QString::number(c->media->get_type())); + switch (c->media->get_type()) { + case MEDIA_TYPE_FOOTAGE: + stream.writeAttribute("media", QString::number(c->media->to_footage()->save_id)); + stream.writeAttribute("stream", QString::number(c->media_stream)); + break; + case MEDIA_TYPE_SEQUENCE: + stream.writeAttribute("sequence", QString::number(c->media->to_sequence()->save_id)); + break; + } + } stream.writeStartElement("linked"); // linked for (int k=0;klinked.size();k++) { diff --git a/playback/playback.cpp b/playback/playback.cpp index cfc99bc1b..12361233d 100644 --- a/playback/playback.cpp +++ b/playback/playback.cpp @@ -87,7 +87,8 @@ void close_clip(Clip* clip) { } else { if (clip->media != NULL && clip->media->get_type() == MEDIA_TYPE_SEQUENCE) closeActiveClips(clip->media->to_sequence(), false); - clip->open = false; + + clip->open = false; } } @@ -342,7 +343,7 @@ void closeActiveClips(Sequence *s, bool wait) { if (c != NULL) { if (c->media != NULL && c->media->get_type() == MEDIA_TYPE_SEQUENCE) { closeActiveClips(c->media->to_sequence(), wait); - close_clip(c); + if (c->open) close_clip(c); } else if (clip_uses_cacher(c) && c->open) { close_clip(c); if (c->multithreaded && wait) c->cacher->wait(); diff --git a/project/clip.cpp b/project/clip.cpp index 90a0fb48b..df0927544 100644 --- a/project/clip.cpp +++ b/project/clip.cpp @@ -210,6 +210,7 @@ double Clip::getMediaFrameRate() { if (!qIsNaN(rate)) return rate; } if (sequence != NULL) return sequence->frame_rate; + return qSNaN(); } void Clip::recalculateMaxLength() { diff --git a/project/effect.cpp b/project/effect.cpp index 7d9d25e0f..9940e8b43 100644 --- a/project/effect.cpp +++ b/project/effect.cpp @@ -432,7 +432,7 @@ Effect::~Effect() { close(); } - delete container; + delete container; for (int i=0;idestroy(); delete texture; texture = NULL; } diff --git a/project/media.cpp b/project/media.cpp index b97d09a11..696a0f34b 100644 --- a/project/media.cpp +++ b/project/media.cpp @@ -183,8 +183,8 @@ const QString &Media::get_name() { switch (type) { case MEDIA_TYPE_FOOTAGE: return to_footage()->name; case MEDIA_TYPE_SEQUENCE: return to_sequence()->name; - case MEDIA_TYPE_FOLDER: return folder_name; - } + default: return folder_name; + } } void Media::set_name(const QString &n) { diff --git a/ui/collapsiblewidget.cpp b/ui/collapsiblewidget.cpp index a4284b80b..910cf0ef2 100644 --- a/ui/collapsiblewidget.cpp +++ b/ui/collapsiblewidget.cpp @@ -12,6 +12,8 @@ #include #include +#include "debug.h" + CollapsibleWidget::CollapsibleWidget(QWidget* parent) : QWidget(parent) { selected = false; @@ -48,7 +50,7 @@ void CollapsibleWidget::header_click(bool s, bool deselect) { selected = s; title_bar->selected = s; if (s) { - QPalette p = palette(); + QPalette p = title_bar->palette(); p.setColor(QPalette::Background, QColor(255, 255, 255, 64)); title_bar->setPalette(p); } else { diff --git a/ui/timelinewidget.cpp b/ui/timelinewidget.cpp index 5e3a318d7..719b1ca51 100644 --- a/ui/timelinewidget.cpp +++ b/ui/timelinewidget.cpp @@ -1237,7 +1237,7 @@ void TimelineWidget::update_ghosts(const QPoint& mouse_pos, bool lock_frame) { if (g.clip != -1) c = sequence->clips.at(g.clip); FootageStream* ms = NULL; - if (g.clip != -1 && c->media->get_type() == MEDIA_TYPE_FOOTAGE) { + if (g.clip != -1 && c->media != NULL && c->media->get_type() == MEDIA_TYPE_FOOTAGE) { ms = c->media->to_footage()->get_stream_from_file_index(c->track < 0, c->media_stream); } @@ -1277,7 +1277,7 @@ void TimelineWidget::update_ghosts(const QPoint& mouse_pos, bool lock_frame) { if (validator < 1) frame_diff += (1 - validator); // prevent clip length exceeding media length - if (c->media->get_type() == MEDIA_TYPE_SEQUENCE + if ((c->media != NULL && c->media->get_type() == MEDIA_TYPE_SEQUENCE) || (ms != NULL && !ms->infinite_length)) { validator = g.old_clip_in + g.ghost_length + frame_diff; if (validator > g.media_length) frame_diff -= validator - g.media_length; diff --git a/ui/viewerwidget.cpp b/ui/viewerwidget.cpp index 071c428e3..0a7732832 100644 --- a/ui/viewerwidget.cpp +++ b/ui/viewerwidget.cpp @@ -67,7 +67,7 @@ ViewerWidget::ViewerWidget(QWidget *parent) : void ViewerWidget::delete_function() { // destroy all textures as well - if (viewer->seq != NULL) { + if (viewer->seq != NULL) { makeCurrent(); closeActiveClips(viewer->seq, true); doneCurrent(); From ef3ff6f0c4333c9dfd01c62fa295105738066ba5 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 21 Dec 2018 00:17:12 +1100 Subject: [PATCH 30/40] changed latin encoding to utf8 for #176 --- debug.cpp | 2 +- io/exportthread.cpp | 4 ++-- io/previewgenerator.cpp | 4 ++-- project/effect.cpp | 8 ++++---- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/debug.cpp b/debug.cpp index 6645250d7..0c63079ef 100644 --- a/debug.cpp +++ b/debug.cpp @@ -14,7 +14,7 @@ void setup_debug() { debug_file.setFileName(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/debug_log"); if (debug_file.open(QFile::WriteOnly)) { QString debug_intro = "Olive Session " + QString::number(QDateTime::currentMSecsSinceEpoch()); - debug_file.write(debug_intro.toLatin1()); + debug_file.write(debug_intro.toUtf8()); } else { debug_out = QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC).debug(); } diff --git a/io/exportthread.cpp b/io/exportthread.cpp index 320543529..52e6e6999 100644 --- a/io/exportthread.cpp +++ b/io/exportthread.cpp @@ -129,7 +129,7 @@ bool ExportThread::setupVideo() { switch (video_compression_type) { case COMPRESSION_TYPE_CFR: - av_opt_set(vcodec_ctx->priv_data, "crf", QString::number(static_cast(video_bitrate)).toLatin1(), AV_OPT_SEARCH_CHILDREN); + av_opt_set(vcodec_ctx->priv_data, "crf", QString::number(static_cast(video_bitrate)).toUtf8(), AV_OPT_SEARCH_CHILDREN); break; } } @@ -317,7 +317,7 @@ void ExportThread::run() { } // copy filename - QByteArray ba = filename.toLatin1(); + QByteArray ba = filename.toUtf8(); c_filename = new char[ba.size()+1]; strcpy(c_filename, ba.data()); diff --git a/io/previewgenerator.cpp b/io/previewgenerator.cpp index 5b9cc097b..696c7987a 100644 --- a/io/previewgenerator.cpp +++ b/io/previewgenerator.cpp @@ -407,7 +407,7 @@ void PreviewGenerator::run() { Q_ASSERT(footage != NULL); Q_ASSERT(media != NULL); - QByteArray ba = footage->url.toLatin1(); + QByteArray ba = footage->url.toUtf8(); char* filename = new char[ba.size()+1]; strcpy(filename, ba.data()); @@ -434,7 +434,7 @@ void PreviewGenerator::run() { QFileInfo file_info(footage->url); QString cache_file = footage->url.mid(footage->url.lastIndexOf('/')+1) + QString::number(file_info.size()) + QString::number(file_info.lastModified().toMSecsSinceEpoch()); //dout << "using hash" << cache_file; - QString hash = QCryptographicHash::hash(cache_file.toLatin1(), QCryptographicHash::Md5).toHex(); + QString hash = QCryptographicHash::hash(cache_file.toUtf8(), QCryptographicHash::Md5).toHex(); if (retrieve_preview(hash)) { sem.acquire(); diff --git a/project/effect.cpp b/project/effect.cpp index 9940e8b43..153987319 100644 --- a/project/effect.cpp +++ b/project/effect.cpp @@ -791,17 +791,17 @@ void Effect::process_shader(double timecode, GLTextureCoords&) { if (!field->id.isEmpty()) { switch (field->type) { case EFFECT_FIELD_DOUBLE: - glslProgram->setUniformValue(field->id.toLatin1().constData(), (GLfloat) field->get_double_value(timecode)); + glslProgram->setUniformValue(field->id.toUtf8().constData(), (GLfloat) field->get_double_value(timecode)); break; case EFFECT_FIELD_COLOR: - glslProgram->setUniformValue(field->id.toLatin1().constData(), field->get_color_value(timecode).redF(), field->get_color_value(timecode).greenF(), field->get_color_value(timecode).blueF()); + glslProgram->setUniformValue(field->id.toUtf8().constData(), field->get_color_value(timecode).redF(), field->get_color_value(timecode).greenF(), field->get_color_value(timecode).blueF()); break; case EFFECT_FIELD_STRING: break; // can you even send a string to a uniform value? case EFFECT_FIELD_BOOL: - glslProgram->setUniformValue(field->id.toLatin1().constData(), field->get_bool_value(timecode)); + glslProgram->setUniformValue(field->id.toUtf8().constData(), field->get_bool_value(timecode)); break; case EFFECT_FIELD_COMBO: - glslProgram->setUniformValue(field->id.toLatin1().constData(), field->get_combo_index(timecode)); + glslProgram->setUniformValue(field->id.toUtf8().constData(), field->get_combo_index(timecode)); break; case EFFECT_FIELD_FONT: break; // can you even send a string to a uniform value? } From c65e2557b56fda50230359f027e54399d2792219 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 21 Dec 2018 01:03:40 +1100 Subject: [PATCH 31/40] removed project ui, fixed some project panel crashes --- olive.pro | 405 +++++++++++++++++++++--------------------- panels/project.cpp | 35 +++- panels/project.h | 4 +- panels/project.ui | 64 ------- project/media.cpp | 28 ++- project/media.h | 4 +- ui/timelineheader.cpp | 5 +- 7 files changed, 256 insertions(+), 289 deletions(-) delete mode 100644 panels/project.ui diff --git a/olive.pro b/olive.pro index 16fa1bfcf..56f3bfe89 100644 --- a/olive.pro +++ b/olive.pro @@ -1,203 +1,202 @@ -#------------------------------------------------- -# -# Project created by QtCreator 2018-05-11T10:31:59 -# -#------------------------------------------------- - -QT += core gui multimedia opengl - -greaterThan(QT_MAJOR_VERSION, 4): QT += widgets - -TARGET = Olive -TEMPLATE = app - -# The following define makes your compiler emit warnings if you use -# any feature of Qt which has been marked as deprecated (the exact warnings -# depend on your compiler). Please consult the documentation of the -# deprecated API in order to know how to port your code away from it. -DEFINES += QT_DEPRECATED_WARNINGS - -# You can also make your code fail to compile if you use deprecated APIs. -# In order to do so, uncomment the following line. -# You can also select to disable deprecated APIs only up to a certain version of Qt. -#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 - - -SOURCES += \ - main.cpp \ - mainwindow.cpp \ - panels/project.cpp \ - panels/effectcontrols.cpp \ - panels/viewer.cpp \ - panels/timeline.cpp \ - ui/sourcetable.cpp \ - dialogs/aboutdialog.cpp \ - ui/timelinewidget.cpp \ - project/media.cpp \ - project/footage.cpp \ - project/sequence.cpp \ - project/clip.cpp \ - playback/playback.cpp \ - playback/audio.cpp \ - io/config.cpp \ - dialogs/newsequencedialog.cpp \ - ui/viewerwidget.cpp \ - ui/viewercontainer.cpp \ - dialogs/exportdialog.cpp \ - ui/collapsiblewidget.cpp \ - panels/panels.cpp \ - playback/cacher.cpp \ - io/exportthread.cpp \ - ui/timelineheader.cpp \ - io/previewgenerator.cpp \ - ui/labelslider.cpp \ - dialogs/preferencesdialog.cpp \ - ui/audiomonitor.cpp \ - project/undo.cpp \ - ui/scrollarea.cpp \ - ui/comboboxex.cpp \ - ui/colorbutton.cpp \ - dialogs/replaceclipmediadialog.cpp \ - ui/fontcombobox.cpp \ - ui/checkboxex.cpp \ - ui/keyframeview.cpp \ - ui/texteditex.cpp \ - dialogs/demonotice.cpp \ - project/marker.cpp \ - dialogs/speeddialog.cpp \ - dialogs/mediapropertiesdialog.cpp \ - io/crc32.cpp \ - project/projectmodel.cpp \ - io/loadthread.cpp \ - dialogs/loaddialog.cpp \ - debug.cpp \ - io/path.cpp \ - effects/internal/linearfadetransition.cpp \ - effects/internal/transformeffect.cpp \ - effects/internal/solideffect.cpp \ - effects/internal/texteffect.cpp \ - effects/internal/timecodeeffect.cpp \ - effects/internal/audionoiseeffect.cpp \ - effects/internal/paneffect.cpp \ - effects/internal/toneeffect.cpp \ - effects/internal/volumeeffect.cpp \ - effects/internal/crossdissolvetransition.cpp \ - effects/internal/shakeeffect.cpp \ - effects/internal/exponentialfadetransition.cpp \ - effects/internal/logarithmicfadetransition.cpp \ - effects/internal/cornerpineffect.cpp \ - io/math.cpp \ - io/qpainterwrapper.cpp \ - project/effect.cpp \ - project/transition.cpp \ - project/effectrow.cpp \ - project/effectfield.cpp \ - effects/internal/cubetransition.cpp \ - project/effectgizmo.cpp \ - io/clipboard.cpp \ - dialogs/stabilizerdialog.cpp - -HEADERS += \ - mainwindow.h \ - panels/project.h \ - panels/effectcontrols.h \ - panels/viewer.h \ - panels/timeline.h \ - ui/sourcetable.h \ - dialogs/aboutdialog.h \ - ui/timelinewidget.h \ - project/media.h \ - project/footage.h \ - project/sequence.h \ - project/clip.h \ - playback/playback.h \ - playback/audio.h \ - io/config.h \ - dialogs/newsequencedialog.h \ - ui/viewerwidget.h \ - ui/viewercontainer.h \ - dialogs/exportdialog.h \ - ui/collapsiblewidget.h \ - panels/panels.h \ - playback/cacher.h \ - io/exportthread.h \ - ui/timelinetools.h \ - ui/timelineheader.h \ - io/previewgenerator.h \ - ui/labelslider.h \ - dialogs/preferencesdialog.h \ - ui/audiomonitor.h \ - project/undo.h \ - ui/scrollarea.h \ - ui/comboboxex.h \ - ui/colorbutton.h \ - dialogs/replaceclipmediadialog.h \ - ui/fontcombobox.h \ - ui/checkboxex.h \ - ui/keyframeview.h \ - ui/texteditex.h \ - dialogs/demonotice.h \ - project/marker.h \ - project/selection.h \ - dialogs/speeddialog.h \ - dialogs/mediapropertiesdialog.h \ - io/crc32.h \ - project/projectmodel.h \ - io/loadthread.h \ - dialogs/loaddialog.h \ - debug.h \ - io/path.h \ - effects/internal/transformeffect.h \ - effects/internal/solideffect.h \ - effects/internal/texteffect.h \ - effects/internal/timecodeeffect.h \ - effects/internal/audionoiseeffect.h \ - effects/internal/paneffect.h \ - effects/internal/toneeffect.h \ - effects/internal/volumeeffect.h \ - effects/internal/shakeeffect.h \ - effects/internal/linearfadetransition.h \ - effects/internal/crossdissolvetransition.h \ - effects/internal/exponentialfadetransition.h \ - effects/internal/logarithmicfadetransition.h \ - effects/internal/cornerpineffect.h \ - io/math.h \ - io/qpainterwrapper.h \ - project/effect.h \ - project/transition.h \ - project/effectrow.h \ - project/effectfield.h \ - effects/internal/cubetransition.h \ - project/effectgizmo.h \ - io/clipboard.h \ - dialogs/stabilizerdialog.h - -FORMS += \ - mainwindow.ui \ - panels/project.ui \ - panels/effectcontrols.ui \ - panels/viewer.ui \ - panels/timeline.ui \ - dialogs/aboutdialog.ui \ - dialogs/newsequencedialog.ui \ - dialogs/exportdialog.ui \ - dialogs/preferencesdialog.ui \ - dialogs/demonotice.ui - -win32 { - RC_FILE = icons/resources.rc - LIBS += -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample -lopengl32 -} - -mac { - LIBS += -L/usr/local/lib -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample - ICON = icons/olive.icns - INCLUDEPATH = /usr/local/include -} - -linux { - LIBS += -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample -} - -RESOURCES += \ - icons/icons.qrc +#------------------------------------------------- +# +# Project created by QtCreator 2018-05-11T10:31:59 +# +#------------------------------------------------- + +QT += core gui multimedia opengl + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +TARGET = Olive +TEMPLATE = app + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + + +SOURCES += \ + main.cpp \ + mainwindow.cpp \ + panels/project.cpp \ + panels/effectcontrols.cpp \ + panels/viewer.cpp \ + panels/timeline.cpp \ + ui/sourcetable.cpp \ + dialogs/aboutdialog.cpp \ + ui/timelinewidget.cpp \ + project/media.cpp \ + project/footage.cpp \ + project/sequence.cpp \ + project/clip.cpp \ + playback/playback.cpp \ + playback/audio.cpp \ + io/config.cpp \ + dialogs/newsequencedialog.cpp \ + ui/viewerwidget.cpp \ + ui/viewercontainer.cpp \ + dialogs/exportdialog.cpp \ + ui/collapsiblewidget.cpp \ + panels/panels.cpp \ + playback/cacher.cpp \ + io/exportthread.cpp \ + ui/timelineheader.cpp \ + io/previewgenerator.cpp \ + ui/labelslider.cpp \ + dialogs/preferencesdialog.cpp \ + ui/audiomonitor.cpp \ + project/undo.cpp \ + ui/scrollarea.cpp \ + ui/comboboxex.cpp \ + ui/colorbutton.cpp \ + dialogs/replaceclipmediadialog.cpp \ + ui/fontcombobox.cpp \ + ui/checkboxex.cpp \ + ui/keyframeview.cpp \ + ui/texteditex.cpp \ + dialogs/demonotice.cpp \ + project/marker.cpp \ + dialogs/speeddialog.cpp \ + dialogs/mediapropertiesdialog.cpp \ + io/crc32.cpp \ + project/projectmodel.cpp \ + io/loadthread.cpp \ + dialogs/loaddialog.cpp \ + debug.cpp \ + io/path.cpp \ + effects/internal/linearfadetransition.cpp \ + effects/internal/transformeffect.cpp \ + effects/internal/solideffect.cpp \ + effects/internal/texteffect.cpp \ + effects/internal/timecodeeffect.cpp \ + effects/internal/audionoiseeffect.cpp \ + effects/internal/paneffect.cpp \ + effects/internal/toneeffect.cpp \ + effects/internal/volumeeffect.cpp \ + effects/internal/crossdissolvetransition.cpp \ + effects/internal/shakeeffect.cpp \ + effects/internal/exponentialfadetransition.cpp \ + effects/internal/logarithmicfadetransition.cpp \ + effects/internal/cornerpineffect.cpp \ + io/math.cpp \ + io/qpainterwrapper.cpp \ + project/effect.cpp \ + project/transition.cpp \ + project/effectrow.cpp \ + project/effectfield.cpp \ + effects/internal/cubetransition.cpp \ + project/effectgizmo.cpp \ + io/clipboard.cpp \ + dialogs/stabilizerdialog.cpp + +HEADERS += \ + mainwindow.h \ + panels/project.h \ + panels/effectcontrols.h \ + panels/viewer.h \ + panels/timeline.h \ + ui/sourcetable.h \ + dialogs/aboutdialog.h \ + ui/timelinewidget.h \ + project/media.h \ + project/footage.h \ + project/sequence.h \ + project/clip.h \ + playback/playback.h \ + playback/audio.h \ + io/config.h \ + dialogs/newsequencedialog.h \ + ui/viewerwidget.h \ + ui/viewercontainer.h \ + dialogs/exportdialog.h \ + ui/collapsiblewidget.h \ + panels/panels.h \ + playback/cacher.h \ + io/exportthread.h \ + ui/timelinetools.h \ + ui/timelineheader.h \ + io/previewgenerator.h \ + ui/labelslider.h \ + dialogs/preferencesdialog.h \ + ui/audiomonitor.h \ + project/undo.h \ + ui/scrollarea.h \ + ui/comboboxex.h \ + ui/colorbutton.h \ + dialogs/replaceclipmediadialog.h \ + ui/fontcombobox.h \ + ui/checkboxex.h \ + ui/keyframeview.h \ + ui/texteditex.h \ + dialogs/demonotice.h \ + project/marker.h \ + project/selection.h \ + dialogs/speeddialog.h \ + dialogs/mediapropertiesdialog.h \ + io/crc32.h \ + project/projectmodel.h \ + io/loadthread.h \ + dialogs/loaddialog.h \ + debug.h \ + io/path.h \ + effects/internal/transformeffect.h \ + effects/internal/solideffect.h \ + effects/internal/texteffect.h \ + effects/internal/timecodeeffect.h \ + effects/internal/audionoiseeffect.h \ + effects/internal/paneffect.h \ + effects/internal/toneeffect.h \ + effects/internal/volumeeffect.h \ + effects/internal/shakeeffect.h \ + effects/internal/linearfadetransition.h \ + effects/internal/crossdissolvetransition.h \ + effects/internal/exponentialfadetransition.h \ + effects/internal/logarithmicfadetransition.h \ + effects/internal/cornerpineffect.h \ + io/math.h \ + io/qpainterwrapper.h \ + project/effect.h \ + project/transition.h \ + project/effectrow.h \ + project/effectfield.h \ + effects/internal/cubetransition.h \ + project/effectgizmo.h \ + io/clipboard.h \ + dialogs/stabilizerdialog.h + +FORMS += \ + mainwindow.ui \ + panels/effectcontrols.ui \ + panels/viewer.ui \ + panels/timeline.ui \ + dialogs/aboutdialog.ui \ + dialogs/newsequencedialog.ui \ + dialogs/exportdialog.ui \ + dialogs/preferencesdialog.ui \ + dialogs/demonotice.ui + +win32 { + RC_FILE = icons/resources.rc + LIBS += -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample -lopengl32 +} + +mac { + LIBS += -L/usr/local/lib -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample + ICON = icons/olive.icns + INCLUDEPATH = /usr/local/include +} + +linux { + LIBS += -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample +} + +RESOURCES += \ + icons/icons.qrc diff --git a/panels/project.cpp b/panels/project.cpp index 280fab531..5f4c3cec7 100644 --- a/panels/project.cpp +++ b/panels/project.cpp @@ -37,6 +37,8 @@ #include #include #include +#include +#include extern "C" { #include @@ -53,20 +55,43 @@ QStringList recent_projects; QString recent_proj_file; Project::Project(QWidget *parent) : - QDockWidget(parent), - ui(new Ui::Project) + QDockWidget(parent) { - ui->setupUi(this); - source_table = ui->treeView; + setObjectName("Project"); + resize(504, 371); + QSizePolicy sizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); + sizePolicy.setHorizontalStretch(0); + sizePolicy.setVerticalStretch(0); + sizePolicy.setHeightForWidth(this->sizePolicy().hasHeightForWidth()); + setSizePolicy(sizePolicy); + + QWidget* dockWidgetContents = new QWidget(); + QVBoxLayout* verticalLayout = new QVBoxLayout(dockWidgetContents); + verticalLayout->setContentsMargins(0, 0, 0, 0); + + setWidget(dockWidgetContents); + + source_table = new SourceTable(dockWidgetContents); source_table->project_parent = this; + source_table->setAcceptDrops(true); + source_table->setEditTriggers(QAbstractItemView::NoEditTriggers); + source_table->setDragDropMode(QAbstractItemView::DragDrop); + source_table->setSelectionMode(QAbstractItemView::ExtendedSelection); + verticalLayout->addWidget(source_table); + //setWidget(source_table); + + //layout->addWidget(source_table); sorter = new QSortFilterProxyModel(this); sorter->setSourceModel(&project_model); source_table->setModel(sorter); + + //retranslateUi(Project); + setWindowTitle(QApplication::translate("Project", "Project", nullptr)); } Project::~Project() { - delete ui; + delete sorter; } QString Project::get_next_sequence_name(QString start) { diff --git a/panels/project.h b/panels/project.h index 7e6ec8229..563ae5ac0 100644 --- a/panels/project.h +++ b/panels/project.h @@ -75,9 +75,7 @@ public: //Media *new_item(); - void start_preview_generator(Media* item, bool replacing); - - Ui::Project *ui; + void start_preview_generator(Media* item, bool replacing); public slots: void import_dialog(); void delete_selected_media(); diff --git a/panels/project.ui b/panels/project.ui deleted file mode 100644 index fbf5d7094..000000000 --- a/panels/project.ui +++ /dev/null @@ -1,64 +0,0 @@ - - - Project - - - - 0 - 0 - 504 - 371 - - - - - 0 - 0 - - - - Project - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - true - - - QAbstractItemView::NoEditTriggers - - - QAbstractItemView::DragDrop - - - QAbstractItemView::ExtendedSelection - - - - - - - - - SourceTable - QTreeView -
ui/sourcetable.h
-
-
- - -
diff --git a/project/media.cpp b/project/media.cpp index 696a0f34b..7f190effe 100644 --- a/project/media.cpp +++ b/project/media.cpp @@ -197,15 +197,25 @@ void Media::set_name(const QString &n) { double Media::get_frame_rate(int stream) { switch (get_type()) { - case MEDIA_TYPE_FOOTAGE: return to_footage()->get_stream_from_file_index(true, stream)->video_frame_rate; + case MEDIA_TYPE_FOOTAGE: + { + Footage* f = to_footage(); + if (stream < 0) return f->video_tracks.at(0)->video_frame_rate; + return f->get_stream_from_file_index(true, stream)->video_frame_rate; + } case MEDIA_TYPE_SEQUENCE: return to_sequence()->frame_rate; } - return qSNaN(); + return NULL; } int Media::get_sampling_rate(int stream) { switch (get_type()) { - case MEDIA_TYPE_FOOTAGE: return to_footage()->get_stream_from_file_index(false, stream)->audio_frequency; + case MEDIA_TYPE_FOOTAGE: + { + Footage* f = to_footage(); + if (stream < 0) return f->audio_tracks.at(0)->audio_frequency; + return to_footage()->get_stream_from_file_index(false, stream)->audio_frequency; + } case MEDIA_TYPE_SEQUENCE: return to_sequence()->audio_frequency; } return 0; @@ -259,11 +269,11 @@ QVariant Media::data(int column, int role) { Footage* f = to_footage(); double r = 30; - if (f->video_tracks.size() > 0) r = f->video_tracks.at(0)->video_frame_rate; - if (!qIsNull(r)) { + if (f->video_tracks.size() > 0 && !qIsNull(f->video_tracks.at(0)->video_frame_rate)) r = f->video_tracks.at(0)->video_frame_rate; + //if (!qIsNull(r)) { long len = f->get_length_in_frames(r); if (len > 0) return frame_to_timecode(len, config.timecode_view, r); - } + //} } break; case 2: @@ -271,9 +281,9 @@ QVariant Media::data(int column, int role) { if (get_type() == MEDIA_TYPE_SEQUENCE) return QString::number(get_frame_rate()) + " FPS"; if (get_type() == MEDIA_TYPE_FOOTAGE) { Footage* f = to_footage(); - if (f->video_tracks.size() > 0) { - double r = get_frame_rate(); - if (!qIsNull(r)) return QString::number(get_frame_rate()) + " FPS"; + double r; + if (f->video_tracks.size() > 0 && !qIsNull(r = get_frame_rate())) { + return QString::number(get_frame_rate()) + " FPS"; } else if (f->audio_tracks.size() > 0) { return QString::number(get_sampling_rate()) + " Hz"; } diff --git a/project/media.h b/project/media.h index b917d0530..3c04de933 100644 --- a/project/media.h +++ b/project/media.h @@ -32,8 +32,8 @@ public: void set_name(const QString& n); MediaThrobber* throbber; - double get_frame_rate(int stream = 0); - int get_sampling_rate(int stream = 0); + double get_frame_rate(int stream = -1); + int get_sampling_rate(int stream = -1); // item functions void appendChild(Media *child); diff --git a/ui/timelineheader.cpp b/ui/timelineheader.cpp index 329bac195..53cddecab 100644 --- a/ui/timelineheader.cpp +++ b/ui/timelineheader.cpp @@ -401,12 +401,11 @@ void TimelineHeader::paintEvent(QPaintEvent*) { } } -void TimelineHeader::show_context_menu(const QPoint &pos) -{ +void TimelineHeader::show_context_menu(const QPoint &pos) { QMenu contextMenu(tr("Context menu"), this); QAction clear_in_out("Clear In/Out Points", this); - if (!viewer->seq->using_workarea) clear_in_out.setVisible(false); + if (!viewer->seq->using_workarea) clear_in_out.setEnabled(false); connect(&clear_in_out, SIGNAL(triggered()), mainWindow, SLOT(on_actionClear_In_Out_triggered())); contextMenu.addAction(&clear_in_out); contextMenu.exec(mapToGlobal(pos)); From c62d4803fbdcdf05bad55ca3c5b38beabba65bb2 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 21 Dec 2018 10:34:42 +1100 Subject: [PATCH 32/40] added replace function for pasting effects --- panels/timeline.cpp | 56 +++++++++++++++++++++++++++++++++++++++++++-- project/undo.cpp | 15 +++++++++--- project/undo.h | 3 ++- 3 files changed, 68 insertions(+), 6 deletions(-) diff --git a/panels/timeline.cpp b/panels/timeline.cpp index a992e8573..0cb5ac181 100644 --- a/panels/timeline.cpp +++ b/panels/timeline.cpp @@ -31,6 +31,7 @@ #include #include #include +#include long refactor_frame_number(long framenumber, double source_frame_rate, double target_frame_rate) { return qRound(((double)framenumber/source_frame_rate)*target_frame_rate); @@ -965,14 +966,65 @@ void Timeline::paste(bool insert) { } else if (clipboard_type == CLIPBOARD_TYPE_EFFECT) { ComboAction* ca = new ComboAction(); bool push = false; + + bool replace = false; + bool skip = false; + bool ask_conflict = true; + for (int i=0;iclips.size();i++) { Clip* c = sequence->clips.at(i); if (c != NULL && is_clip_selected(c, true)) { for (int j=0;j(clipboard.at(j)); if ((c->track < 0) == (e->meta->subtype == EFFECT_TYPE_VIDEO)) { - ca->append(new AddEffectCommand(c, e->copy(c), NULL)); - push = true; + int found = -1; + if (ask_conflict) { + replace = false; + skip = false; + } + for (int k=0;keffects.size();k++) { + if (c->effects.at(k)->meta == e->meta) { + found = k; + break; + } + } + if (found >= 0 && ask_conflict) { + QMessageBox box(this); + box.setWindowTitle("Effect already exists"); + box.setText("Clip '" + c->name + "' already contains a '" + e->meta->name + "' effect. Would you like to replace it with the pasted one or add it as a separate effect?"); + box.setIcon(QMessageBox::Icon::Question); + + box.addButton("Add", QMessageBox::YesRole); + QPushButton* replace_button = box.addButton("Replace", QMessageBox::NoRole); + QPushButton* skip_button = box.addButton("Skip", QMessageBox::RejectRole); + + QCheckBox* future_box = new QCheckBox("Do this for all conflicts found"); + box.setCheckBox(future_box); + + box.exec(); + + if (box.clickedButton() == replace_button) { + replace = true; + } else if (box.clickedButton() == skip_button) { + skip = true; + } + ask_conflict = !future_box->isChecked(); + } + + if (found >= 0 && skip) { + // do nothing + } else if (found >= 0 && replace) { + EffectDeleteCommand* delcom = new EffectDeleteCommand(); + delcom->clips.append(c); + delcom->fx.append(found); + ca->append(delcom); + + ca->append(new AddEffectCommand(c, e->copy(c), NULL, found)); + push = true; + } else { + ca->append(new AddEffectCommand(c, e->copy(c), NULL)); + push = true; + } } } } diff --git a/project/undo.cpp b/project/undo.cpp index d691ca278..17fe258d7 100644 --- a/project/undo.cpp +++ b/project/undo.cpp @@ -199,10 +199,11 @@ void SetTimelineInOutCommand::redo() { mainWindow->setWindowModified(true); } -AddEffectCommand::AddEffectCommand(Clip* c, Effect* e, const EffectMeta *m) : +AddEffectCommand::AddEffectCommand(Clip* c, Effect* e, const EffectMeta *m, int insert_pos) : clip(c), meta(m), ref(e), + pos(insert_pos), done(false), old_project_changed(mainWindow->isWindowModified()) {} @@ -213,7 +214,11 @@ AddEffectCommand::~AddEffectCommand() { void AddEffectCommand::undo() { clip->effects.last()->close(); - clip->effects.removeLast(); + if (pos < 0) { + clip->effects.removeLast(); + } else { + clip->effects.removeAt(pos); + } done = false; mainWindow->setWindowModified(old_project_changed); } @@ -222,7 +227,11 @@ void AddEffectCommand::redo() { if (ref == NULL) { ref = create_effect(clip, meta); } - clip->effects.append(ref); + if (pos < 0) { + clip->effects.append(ref); + } else { + clip->effects.insert(pos, ref); + } done = true; mainWindow->setWindowModified(true); } diff --git a/project/undo.h b/project/undo.h index 2d1462440..173163dc4 100644 --- a/project/undo.h +++ b/project/undo.h @@ -89,7 +89,7 @@ private: class AddEffectCommand : public QUndoCommand { public: - AddEffectCommand(Clip* c, Effect *e, const EffectMeta* m); + AddEffectCommand(Clip* c, Effect *e, const EffectMeta* m, int insert_pos = -1); ~AddEffectCommand(); void undo(); void redo(); @@ -97,6 +97,7 @@ private: Clip* clip; const EffectMeta* meta; Effect* ref; + int pos; bool done; bool old_project_changed; }; From e669949ec6ad7d99077e0a35932e93073ec1fd95 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 21 Dec 2018 11:35:53 +1100 Subject: [PATCH 33/40] progress on stabilization dialog --- dialogs/stabilizerdialog.cpp | 83 ++++++++++++++++++++++++++++++++++++ dialogs/stabilizerdialog.h | 17 ++++++++ ui/timelinewidget.cpp | 14 +++++- 3 files changed, 113 insertions(+), 1 deletion(-) diff --git a/dialogs/stabilizerdialog.cpp b/dialogs/stabilizerdialog.cpp index 935f26f6a..5e0738d80 100644 --- a/dialogs/stabilizerdialog.cpp +++ b/dialogs/stabilizerdialog.cpp @@ -3,8 +3,14 @@ #include #include #include +#include +#include + +#include "ui/labelslider.h" StabilizerDialog::StabilizerDialog(QWidget *parent) : QDialog(parent) { + setWindowTitle("Stabilizer"); + layout = new QVBoxLayout(this); setLayout(layout); @@ -12,9 +18,86 @@ StabilizerDialog::StabilizerDialog(QWidget *parent) : QDialog(parent) { enable_stab->setText("Enable Stabilizer"); layout->addWidget(enable_stab); + analysis = new QGroupBox("Analysis", this); + layout->addWidget(analysis); + + analysis_layout = new QGridLayout(analysis); + analysis->setLayout(analysis_layout); + + analysis_layout->addWidget(new QLabel("Shakiness:"), 0, 0); + + shakiness_slider = new LabelSlider(); + shakiness_slider->set_minimum_value(1); + shakiness_slider->set_default_value(5); + shakiness_slider->set_maximum_value(10); + analysis_layout->addWidget(shakiness_slider, 0, 1); + + analysis_layout->addWidget(new QLabel("Accuracy:"), 1, 0); + + accuracy_slider = new LabelSlider(); + accuracy_slider->set_minimum_value(1); + accuracy_slider->set_default_value(15); + accuracy_slider->set_maximum_value(15); + analysis_layout->addWidget(accuracy_slider, 1, 1); + + analysis_layout->addWidget(new QLabel("Step Size:"), 2, 0); + + stepsize_slider = new LabelSlider(); + stepsize_slider->set_minimum_value(1); + stepsize_slider->set_default_value(6); + analysis_layout->addWidget(stepsize_slider, 2, 1); + + analysis_layout->addWidget(new QLabel("Minimum Contrast:"), 3, 0); + + mincontrast_slider = new LabelSlider(); + mincontrast_slider->set_minimum_value(0); + mincontrast_slider->set_default_value(0.3); + mincontrast_slider->set_maximum_value(1); + analysis_layout->addWidget(mincontrast_slider, 3, 1); + + /*analysis_layout->addWidget(new QLabel("Tripod Mode:"), 4, 0); + + tripod_mode_box = new QCheckBox(); + analysis_layout->addWidget(tripod_mode_box, 4, 1);*/ + + stabilization = new QGroupBox("Stabilization", this); + layout->addWidget(stabilization); + + stabilization_layout = new QGridLayout(); + stabilization->setLayout(stabilization_layout); + + stabilization_layout->addWidget(new QLabel("Smoothing:"), 0, 0); + + smoothing_slider = new LabelSlider(); + smoothing_slider->set_minimum_value(0); + smoothing_slider->set_default_value(10); + stabilization_layout->addWidget(smoothing_slider, 0, 1); + + stabilization_layout->addWidget(new QLabel("Gaussian Motion:"), 1, 0); + + gaussian_motion = new QCheckBox(); + gaussian_motion->setChecked(true); + stabilization_layout->addWidget(gaussian_motion, 1, 1); + + stabilization_layout->addWidget(new QLabel("Maximum Movement:"), 2, 0); + stabilization_layout->addWidget(new QLabel("Maximum Rotation:"), 3, 0); + stabilization_layout->addWidget(new QLabel("Crop:"), 4, 0); + stabilization_layout->addWidget(new QLabel("Zoom Behavior:"), 5, 0); + stabilization_layout->addWidget(new QLabel("Zoom Speed:"), 6, 0); + stabilization_layout->addWidget(new QLabel("Interpolation Quality:"), 7, 0); + buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); layout->addWidget(buttons); connect(buttons, SIGNAL(accepted()), this, SLOT(accept())); connect(buttons, SIGNAL(rejected()), this, SLOT(reject())); + + connect(enable_stab, SIGNAL(toggled(bool)), this, SLOT(set_all_enabled(bool))); + + set_all_enabled(false); +} + +void StabilizerDialog::set_all_enabled(bool e) { + analysis->setEnabled(e); + stabilization->setEnabled(e); } diff --git a/dialogs/stabilizerdialog.h b/dialogs/stabilizerdialog.h index 0f276b343..9e5a5a339 100644 --- a/dialogs/stabilizerdialog.h +++ b/dialogs/stabilizerdialog.h @@ -4,17 +4,34 @@ class QVBoxLayout; class QCheckBox; class QDialogButtonBox; +class QGroupBox; +class QGridLayout; +class LabelSlider; #include class StabilizerDialog : public QDialog { + Q_OBJECT public: StabilizerDialog(QWidget* parent = 0); +private slots: + void set_all_enabled(bool e); private: QVBoxLayout* layout; QCheckBox* enable_stab; QDialogButtonBox* buttons; + QGroupBox* analysis; + QGridLayout* analysis_layout; + LabelSlider* shakiness_slider; + LabelSlider* accuracy_slider; + LabelSlider* stepsize_slider; + LabelSlider* mincontrast_slider; + QCheckBox* tripod_mode_box; + QGroupBox* stabilization; + QGridLayout* stabilization_layout; + LabelSlider* smoothing_slider; + QCheckBox* gaussian_motion; }; #endif // STABILIZERDIALOG_H diff --git a/ui/timelinewidget.cpp b/ui/timelinewidget.cpp index 719b1ca51..ba4d42fcb 100644 --- a/ui/timelinewidget.cpp +++ b/ui/timelinewidget.cpp @@ -151,7 +151,19 @@ void TimelineWidget::show_context_menu(const QPoint& pos) { QAction* nestAction = menu.addAction("&Nest"); connect(nestAction, SIGNAL(triggered(bool)), mainWindow, SLOT(on_actionNest_triggered())); - if (selected_clips.size() == 1 && selected_clips.at(0)->media != NULL && selected_clips.at(0)->media->get_type() == MEDIA_TYPE_FOOTAGE) { + // stabilizer option + int video_clip_count = 0; + bool all_video_is_footage = true; + for (int i=0;itrack < 0) { + video_clip_count++; + if (selected_clips.at(i)->media == NULL + || selected_clips.at(i)->media->get_type() != MEDIA_TYPE_FOOTAGE) { + all_video_is_footage = false; + } + } + } + if (video_clip_count == 1 && all_video_is_footage) { QAction* stabilizerAction = menu.addAction("S&tabilizer"); connect(stabilizerAction, SIGNAL(triggered(bool)), this, SLOT(show_stabilizer_diag())); } From 1ed857483dada4e9ea479657f37a0244453b3ddc Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 21 Dec 2018 21:56:07 +1100 Subject: [PATCH 34/40] workaround for qt not returning to maximized when exiting full screen #171 --- mainwindow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/mainwindow.cpp b/mainwindow.cpp index 2efb33e42..c4a4441b1 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -833,6 +833,7 @@ void MainWindow::on_actionClear_In_Out_triggered() { void MainWindow::toggle_full_screen() { if (windowState() == Qt::WindowFullScreen) { + setWindowState(Qt::WindowNoState); // seems to be necessary for it to return to Maximized correctly on Linux setWindowState(Qt::WindowMaximized); } else { setWindowState(Qt::WindowFullScreen); From fb36c79eeadb987d3143d69dcdfcff7b9781268e Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 21 Dec 2018 22:05:33 +1100 Subject: [PATCH 35/40] restore current blendfunc after using default --- ui/viewerwidget.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/ui/viewerwidget.cpp b/ui/viewerwidget.cpp index 0a7732832..f044afc24 100644 --- a/ui/viewerwidget.cpp +++ b/ui/viewerwidget.cpp @@ -342,6 +342,14 @@ GLuint ViewerWidget::draw_clip(QOpenGLFramebufferObject* fbo, GLuint texture, bo fbo->bind(); if (clear) glClear(GL_COLOR_BUFFER_BIT); + + // get current blend mode + GLint src_rgb, src_alpha, dst_rgb, dst_alpha; + glGetIntegerv(GL_BLEND_SRC_RGB, &src_rgb); + glGetIntegerv(GL_BLEND_SRC_ALPHA, &src_alpha); + glGetIntegerv(GL_BLEND_DST_RGB, &dst_rgb); + glGetIntegerv(GL_BLEND_DST_ALPHA, &dst_alpha); + GL_DEFAULT_BLEND glBindTexture(GL_TEXTURE_2D, texture); @@ -358,7 +366,11 @@ GLuint ViewerWidget::draw_clip(QOpenGLFramebufferObject* fbo, GLuint texture, bo glBindTexture(GL_TEXTURE_2D, 0); fbo->release(); - if (default_fbo != NULL) default_fbo->bind(); + + // restore previous blendFunc + glBlendFuncSeparate(src_rgb, dst_rgb, src_alpha, dst_alpha); + + if (default_fbo != NULL) default_fbo->bind(); glPopMatrix(); return fbo->texture(); From e08df8df4dd8c61535bac944ae3506e18c4b8360 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 21 Dec 2018 23:05:32 +1100 Subject: [PATCH 36/40] removed orphaned references to project UI file --- io/loadthread.cpp | 21 ++++++++++++++------- io/loadthread.h | 3 +++ panels/project.cpp | 4 +++- project/media.cpp | 7 +++---- 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/io/loadthread.cpp b/io/loadthread.cpp index 001d29669..b01c9a14c 100644 --- a/io/loadthread.cpp +++ b/io/loadthread.cpp @@ -10,7 +10,6 @@ #include "project/transition.h" #include "project/effect.h" #include "playback/playback.h" -#include "ui_project.h" #include "io/previewgenerator.h" #include "dialogs/loaddialog.h" #include "project/media.h" @@ -32,6 +31,7 @@ LoadThread::LoadThread(LoadDialog* l, bool a) : ld(l), autorecovery(a), cancelle connect(this, SIGNAL(finished()), this, SLOT(deleteLater())); connect(this, SIGNAL(success()), this, SLOT(success_func())); connect(this, SIGNAL(error()), this, SLOT(error_func())); + connect(this, SIGNAL(start_create_dual_transition(const TransitionData*,Clip*,Clip*,const EffectMeta*)), this, SLOT(create_dual_transition(const TransitionData*,Clip*,Clip*,const EffectMeta*))); connect(this, SIGNAL(start_create_effect_ui(QXmlStreamReader*, Clip*, int, const EffectMeta*, long, bool)), this, SLOT(create_effect_ui(QXmlStreamReader*, Clip*, int, const EffectMeta*, long, bool))); } @@ -491,11 +491,10 @@ bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) { dout << "[WARNING] Failed to link transition with name:" << td.name; if (td.otc != NULL) td.otc->opening_transition = -1; if (td.ctc != NULL) td.ctc->closing_transition = -1; - } else { - int transition_index = create_transition(primary, secondary, meta); - primary->sequence->transitions.at(transition_index)->set_length(td.length); - if (td.otc != NULL) td.otc->opening_transition = transition_index; - if (td.ctc != NULL) td.ctc->closing_transition = transition_index; + } else { + emit start_create_dual_transition(&td, primary, secondary, meta); + + waitCond.wait(&mutex); } } } @@ -722,6 +721,14 @@ void LoadThread::create_effect_ui( c->closing_transition = transition_index; } } - waitCond.wakeAll(); + waitCond.wakeAll(); +} + +void LoadThread::create_dual_transition(const TransitionData* td, Clip* primary, Clip* secondary, const EffectMeta* meta) { + int transition_index = create_transition(primary, secondary, meta); + primary->sequence->transitions.at(transition_index)->set_length(td->length); + if (td->otc != NULL) td->otc->opening_transition = transition_index; + if (td->ctc != NULL) td->ctc->closing_transition = transition_index; + waitCond.wakeAll(); } diff --git a/io/loadthread.h b/io/loadthread.h index d0b86f5f9..572e7b8d4 100644 --- a/io/loadthread.h +++ b/io/loadthread.h @@ -12,6 +12,7 @@ struct Footage; struct Clip; struct Sequence; class LoadDialog; +class TransitionData; struct EffectMeta; class LoadThread : public QThread @@ -25,11 +26,13 @@ signals: void success(); void error(); void start_create_effect_ui(QXmlStreamReader* stream, Clip* c, int type, const EffectMeta* meta, long effect_length, bool effect_enabled); + void start_create_dual_transition(const TransitionData* td, Clip* primary, Clip* secondary, const EffectMeta* meta); void report_progress(int p); private slots: void error_func(); void success_func(); void create_effect_ui(QXmlStreamReader* stream, Clip* c, int type, const EffectMeta* meta, long effect_length, bool effect_enabled); + void create_dual_transition(const TransitionData* td, Clip* primary, Clip* secondary, const EffectMeta* meta); private: LoadDialog* ld; bool autorecovery; diff --git a/panels/project.cpp b/panels/project.cpp index 5f4c3cec7..dc391ae0b 100644 --- a/panels/project.cpp +++ b/panels/project.cpp @@ -1,5 +1,4 @@ #include "project.h" -#include "ui_project.h" #include "project/footage.h" #include "panels/panels.h" @@ -23,8 +22,10 @@ #include "dialogs/loaddialog.h" #include "io/clipboard.h" #include "project/media.h" +#include "ui/sourcetable.h" #include "debug.h" +#include #include #include #include @@ -39,6 +40,7 @@ #include #include #include +#include extern "C" { #include diff --git a/project/media.cpp b/project/media.cpp index 7f190effe..bb01dc72e 100644 --- a/project/media.cpp +++ b/project/media.cpp @@ -270,10 +270,9 @@ QVariant Media::data(int column, int role) { double r = 30; if (f->video_tracks.size() > 0 && !qIsNull(f->video_tracks.at(0)->video_frame_rate)) r = f->video_tracks.at(0)->video_frame_rate; - //if (!qIsNull(r)) { - long len = f->get_length_in_frames(r); - if (len > 0) return frame_to_timecode(len, config.timecode_view, r); - //} + + long len = f->get_length_in_frames(r); + if (len > 0) return frame_to_timecode(len, config.timecode_view, r); } break; case 2: From 74d343522d3b09864d76c66d88caec4afc25d66b Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sat, 22 Dec 2018 08:53:39 +1100 Subject: [PATCH 37/40] fixed blur issues on intel gpus --- effects/boxblur.frag | 4 +++- effects/directionalblur.frag | 4 +++- effects/gaussianblur.frag | 4 +++- effects/radialblur.frag | 6 +++++- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/effects/boxblur.frag b/effects/boxblur.frag index 3683a469e..626cc1f7e 100644 --- a/effects/boxblur.frag +++ b/effects/boxblur.frag @@ -18,10 +18,12 @@ void main(void) { float divider = 1.0; if (horiz_blur) divider /= rad; if (vert_blur) divider /= rad; + vec4 color = vec4(0.0); for (float x=-x_rad+0.5;x<=x_rad;x+=2.0) { for (float y=-y_rad+0.5;y<=y_rad;y+=2.0) { - gl_FragColor += texture2D(image, (vec2(gl_FragCoord.x+x, gl_FragCoord.y+y))/resolution)*(divider); + color += texture2D(image, (vec2(gl_FragCoord.x+x, gl_FragCoord.y+y))/resolution)*(divider); } } + gl_FragColor = color; } } \ No newline at end of file diff --git a/effects/directionalblur.frag b/effects/directionalblur.frag index 1d549ed6d..e04d350ed 100644 --- a/effects/directionalblur.frag +++ b/effects/directionalblur.frag @@ -17,11 +17,13 @@ void main(void) { float sin_angle = sin(radians); float cos_angle = cos(radians); + vec4 color = vec4(0.0); for (float i=-ceillen+0.5;i<=ceillen;i+=2.0) { float y = sin_angle * i; float x = cos_angle * i; - gl_FragColor += texture2D(image, vec2(gl_FragCoord.x+x, gl_FragCoord.y+y)/resolution)*(divider); + color += texture2D(image, vec2(gl_FragCoord.x+x, gl_FragCoord.y+y)/resolution)*(divider); } + gl_FragColor = color; } else { gl_FragColor = texture2D(image, gl_FragCoord.xy/resolution); } diff --git a/effects/gaussianblur.frag b/effects/gaussianblur.frag index f2d76caf2..b5dd4e8c9 100644 --- a/effects/gaussianblur.frag +++ b/effects/gaussianblur.frag @@ -36,11 +36,13 @@ void main(void) { } } + vec4 color = vec4(0.0); for (float x=-x_rad+0.5;x<=x_rad;x+=2.0) { for (float y=-y_rad+0.5;y<=y_rad;y+=2.0) { float weight = (gaussian2(x, y, sigma)/sum); - gl_FragColor += texture2D(image, (vec2(gl_FragCoord.x+x, gl_FragCoord.y+y))/resolution)*(weight); + color += texture2D(image, (vec2(gl_FragCoord.x+x, gl_FragCoord.y+y))/resolution)*(weight); } } + gl_FragColor = color; } } \ No newline at end of file diff --git a/effects/radialblur.frag b/effects/radialblur.frag index 2101480f2..e32cfb662 100644 --- a/effects/radialblur.frag +++ b/effects/radialblur.frag @@ -22,11 +22,15 @@ void main(void) { float limit = ceil(radius * multiplier); float divider = 1.0 / limit; + + vec4 color = vec4(0.0); + for (float i=-limit+0.5;i<=limit;i+=2.0) { float y = sin_angle * i; float x = cos_angle * i; - gl_FragColor += texture2D(image, vec2(gl_FragCoord.x+x, gl_FragCoord.y+y)/resolution)*(divider); + color += texture2D(image, vec2(gl_FragCoord.x+x, gl_FragCoord.y+y)/resolution)*(divider); } + gl_FragColor = color; } else { gl_FragColor = texture2D(image, gl_FragCoord.xy/resolution); } From 90d9c46a93b20d43ca5c74b5e09d845f72a16294 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sat, 22 Dec 2018 08:57:51 +1100 Subject: [PATCH 38/40] moved load function to main thread --- io/loadthread.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/io/loadthread.cpp b/io/loadthread.cpp index b01c9a14c..5fa67ac85 100644 --- a/io/loadthread.cpp +++ b/io/loadthread.cpp @@ -621,8 +621,6 @@ void LoadThread::run() { } if (cont) { - panel_project->add_recent_project(project_url); - emit success(); // run in main thread for (int i=0;iupdateTitle(orig_filename); - } + } else { + panel_project->add_recent_project(project_url); + } mainWindow->setWindowModified(autorecovery); if (open_seq != NULL) set_sequence(open_seq); From 81f6b5d5493f1305e2c358cf78365939ab4c6a74 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sat, 22 Dec 2018 15:20:39 +1100 Subject: [PATCH 39/40] enabled autochoosing opengl pixel format from source --- io/avtogl.cpp | 19 +++++++++++++++++++ io/avtogl.h | 9 +++++++++ mainwindow.cpp | 1 + olive.pro | 6 ++++-- playback/cacher.cpp | 25 ++++++++++++++++--------- playback/playback.cpp | 8 +++++--- project/clip.h | 1 + ui/viewerwidget.cpp | 5 +++-- 8 files changed, 58 insertions(+), 16 deletions(-) create mode 100644 io/avtogl.cpp create mode 100644 io/avtogl.h diff --git a/io/avtogl.cpp b/io/avtogl.cpp new file mode 100644 index 000000000..895612c6b --- /dev/null +++ b/io/avtogl.cpp @@ -0,0 +1,19 @@ +#include "avtogl.h" + +extern "C" { + #include +} + +enum QOpenGLTexture::PixelFormat get_gl_pix_fmt_from_av(int format) { + switch (format) { + case AV_PIX_FMT_RGB24: return QOpenGLTexture::RGB; + } + return QOpenGLTexture::RGBA; +} + +enum QOpenGLTexture::TextureFormat get_gl_tex_fmt_from_av(int format) { + switch (format) { + case AV_PIX_FMT_RGB24: return QOpenGLTexture::RGB8_UNorm; + } + return QOpenGLTexture::RGBA8_UNorm; +} diff --git a/io/avtogl.h b/io/avtogl.h new file mode 100644 index 000000000..b2bfd1e34 --- /dev/null +++ b/io/avtogl.h @@ -0,0 +1,9 @@ +#ifndef AVTOGL_H +#define AVTOGL_H + +#include + +enum QOpenGLTexture::PixelFormat get_gl_pix_fmt_from_av(int format); +enum QOpenGLTexture::TextureFormat get_gl_tex_fmt_from_av(int format); + +#endif // AVTOGL_H diff --git a/mainwindow.cpp b/mainwindow.cpp index c4a4441b1..6cf750313 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -227,6 +227,7 @@ MainWindow::MainWindow(QWidget *parent) : } MainWindow::~MainWindow() { + panel_effect_controls->clear_effects(true); panel_sequence_viewer->viewer_widget->delete_function(); panel_footage_viewer->viewer_widget->delete_function(); diff --git a/olive.pro b/olive.pro index 56f3bfe89..000bc854d 100644 --- a/olive.pro +++ b/olive.pro @@ -95,7 +95,8 @@ SOURCES += \ effects/internal/cubetransition.cpp \ project/effectgizmo.cpp \ io/clipboard.cpp \ - dialogs/stabilizerdialog.cpp + dialogs/stabilizerdialog.cpp \ + io/avtogl.cpp HEADERS += \ mainwindow.h \ @@ -170,7 +171,8 @@ HEADERS += \ effects/internal/cubetransition.h \ project/effectgizmo.h \ io/clipboard.h \ - dialogs/stabilizerdialog.h + dialogs/stabilizerdialog.h \ + io/avtogl.h FORMS += \ mainwindow.ui \ diff --git a/playback/cacher.cpp b/playback/cacher.cpp index ff6fd9fd3..ae3ecc683 100644 --- a/playback/cacher.cpp +++ b/playback/cacher.cpp @@ -24,6 +24,7 @@ extern "C" { #include #include #include + #include } #include @@ -34,7 +35,7 @@ extern "C" { // temp debug shit //#define AUDIOWARNINGS -int dest_format = AV_PIX_FMT_RGBA; +//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); @@ -712,11 +713,6 @@ void open_clip_worker(Clip* clip) { avfilter_graph_create_filter(&clip->buffersrc_ctx, avfilter_get_by_name("buffer"), "in", filter_args, NULL, clip->filter_graph); avfilter_graph_create_filter(&clip->buffersink_ctx, avfilter_get_by_name("buffersink"), "out", NULL, NULL, clip->filter_graph); - /*enum AVPixelFormat sinkpix_fmts[] = { static_cast(dest_format), AV_PIX_FMT_NONE }; - if (av_opt_set_int_list(clip->buffersink_ctx, "pix_fmts", sinkpix_fmts, AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN) < 0) { - dout << "[ERROR] Could not set output pixel format"; - }*/ - AVFilterContext* last_filter = clip->buffersrc_ctx; if (ms->video_interlacing != VIDEO_PROGRESSIVE) { @@ -729,7 +725,7 @@ void open_clip_worker(Clip* clip) { last_filter = yadif_filter; } - /* stabilization code one day */ + /* stabilization code */ bool stabilize = false; if (stabilize) { AVFilterContext* stab_filter; @@ -741,10 +737,21 @@ void open_clip_worker(Clip* clip) { avfilter_link(last_filter, 0, stab_filter, 0); last_filter = stab_filter; } - } + } + + enum AVPixelFormat valid_pix_fmts[] = { + AV_PIX_FMT_RGB24, + AV_PIX_FMT_RGBA, + AV_PIX_FMT_NONE + }; + + clip->pix_fmt = avcodec_find_best_pix_fmt_of_list(valid_pix_fmts, static_cast(clip->stream->codecpar->format), 1, NULL); + const char* chosen_format = av_get_pix_fmt_name(static_cast(clip->pix_fmt)); + char format_args[100]; + snprintf(format_args, sizeof(format_args), "pix_fmts=%s", chosen_format); AVFilterContext* format_conv; - avfilter_graph_create_filter(&format_conv, avfilter_get_by_name("format"), "fmt", "pix_fmts=rgba", NULL, clip->filter_graph); + avfilter_graph_create_filter(&format_conv, avfilter_get_by_name("format"), "fmt", format_args, NULL, clip->filter_graph); avfilter_link(last_filter, 0, format_conv, 0); avfilter_link(format_conv, 0, clip->buffersink_ctx, 0); diff --git a/playback/playback.cpp b/playback/playback.cpp index 12361233d..13e71bac7 100644 --- a/playback/playback.cpp +++ b/playback/playback.cpp @@ -12,10 +12,12 @@ #include "panels/effectcontrols.h" #include "project/media.h" #include "io/config.h" +#include "io/avtogl.h" #include "debug.h" extern "C" { #include + #include #include #include #include @@ -220,8 +222,8 @@ void get_clip_frame(Clip* c, long playhead) { } if (target_frame != NULL) { - // add gate if this is the same frame - glPixelStorei(GL_UNPACK_ROW_LENGTH, target_frame->linesize[0]/4); + int nb_components = av_pix_fmt_desc_get(static_cast(c->pix_fmt))->nb_components; + glPixelStorei(GL_UNPACK_ROW_LENGTH, target_frame->linesize[0]/nb_components); bool copied = false; uint8_t* data = target_frame->data[0]; @@ -240,7 +242,7 @@ void get_clip_frame(Clip* c, long playhead) { } } - c->texture->setData(0, QOpenGLTexture::RGBA, QOpenGLTexture::UInt8, data); + c->texture->setData(0, get_gl_pix_fmt_from_av(c->pix_fmt), QOpenGLTexture::UInt8, data); if (copied) delete [] data; diff --git a/project/clip.h b/project/clip.h index 6e2ffc75a..69e757d5f 100644 --- a/project/clip.h +++ b/project/clip.h @@ -100,6 +100,7 @@ struct Clip bool finished_opening; bool replaced; bool ignore_reverse; + int pix_fmt; // caching functions bool use_existing_frame; diff --git a/ui/viewerwidget.cpp b/ui/viewerwidget.cpp index f044afc24..0c80622cd 100644 --- a/ui/viewerwidget.cpp +++ b/ui/viewerwidget.cpp @@ -20,6 +20,7 @@ #include "project/undo.h" #include "project/media.h" #include "ui/viewercontainer.h" +#include "io/avtogl.h" #include #include @@ -511,10 +512,10 @@ GLuint ViewerWidget::compose_sequence(QVector& nests, bool render_audio) if (c->texture == NULL) { 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->setFormat(get_gl_tex_fmt_from_av(c->pix_fmt)); c->texture->setMipLevels(c->texture->maximumMipLevels()); c->texture->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear); - c->texture->allocateStorage(QOpenGLTexture::RGBA, QOpenGLTexture::UInt8); + c->texture->allocateStorage(get_gl_pix_fmt_from_av(c->pix_fmt), QOpenGLTexture::UInt8); } get_clip_frame(c, playhead); textureID = c->texture->textureId(); From 5bb579e9d1b900d8cc8ae973a7e689aca5a1882c Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sun, 23 Dec 2018 09:22:43 +1100 Subject: [PATCH 40/40] added environment variable for effects path --- io/path.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/io/path.cpp b/io/path.cpp index 533acc110..fe8a813f0 100644 --- a/io/path.cpp +++ b/io/path.cpp @@ -8,6 +8,8 @@ QString real_app_dir; QString get_effects_dir() { + QString env_path(qgetenv("OLIVE_EFFECTS_PATH")); + if (!env_path.isEmpty()) return env_path; return get_app_dir() + "/effects"; }