diff --git a/dialogs/proxydialog.cpp b/dialogs/proxydialog.cpp index 1592ad904..5d7b47469 100644 --- a/dialogs/proxydialog.cpp +++ b/dialogs/proxydialog.cpp @@ -108,6 +108,9 @@ void ProxyDialog::accept() { // all proxy info checks out, queue it with the proxy generator for (int i=0;iproxy = true; + info_list.at(i).footage->proxy_path.clear(); + proxy_generator.queue(info_list.at(i)); } diff --git a/io/config.h b/io/config.h index 5e8c61f27..c01ee0ee3 100644 --- a/io/config.h +++ b/io/config.h @@ -3,7 +3,7 @@ #include -#define SAVE_VERSION 190120 // YYMMDD +#define SAVE_VERSION 190201 // YYMMDD #define MIN_SAVE_VERSION 190104 // lowest compatible project version #define TIMECODE_DROP 0 diff --git a/io/loadthread.cpp b/io/loadthread.cpp index adfc3a510..068ed901a 100644 --- a/io/loadthread.cpp +++ b/io/loadthread.cpp @@ -250,6 +250,10 @@ bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) { m->speed = attr.value().toDouble(); } else if (attr.name() == "alphapremul") { m->alpha_is_premultiplied = (attr.value() == "1"); + } else if (attr.name() == "proxy") { + m->proxy = (attr.value() == "1"); + } else if (attr.name() == "proxypath") { + m->proxy_path = attr.value().toString(); } } diff --git a/io/proxygenerator.cpp b/io/proxygenerator.cpp index ca42efdd9..9f54b56a5 100644 --- a/io/proxygenerator.cpp +++ b/io/proxygenerator.cpp @@ -2,10 +2,12 @@ #include "project/footage.h" #include "io/path.h" +#include "mainwindow.h" #include #include #include +#include #include @@ -19,7 +21,10 @@ enum AVCodecID temp_enc_codec = AV_CODEC_ID_PRORES; ProxyGenerator::ProxyGenerator() : cancelled(false) {} -void transcode(const ProxyInfo& info) { +void ProxyGenerator::transcode(const ProxyInfo& info) { + // set progress to 0 + current_progress = 0.0; + // open input file AVFormatContext* input_fmt_ctx = nullptr; avformat_open_input(&input_fmt_ctx, info.footage->url.toUtf8(), nullptr, nullptr); @@ -145,7 +150,7 @@ void transcode(const ProxyInfo& info) { AVFrame* dec_frame = av_frame_alloc(); // main transcoding loop - while (true) { + while (!skip) { // cache stream index int stream_index = packet.stream_index; @@ -186,28 +191,30 @@ void transcode(const ProxyInfo& info) { // send packet to decoder avcodec_send_packet(input_streams.at(stream_index), &packet); + // use timestamp and stream duration to create a rough estimation of the progress through this file + current_progress = qCeil((double(packet.pts)/double(input_fmt_ctx->streams[packet.stream_index]->duration))*100); + } // free packet allocated by av_read_frame av_packet_unref(&packet); - } while ((recfr_ret = avcodec_receive_frame(input_streams.at(packet.stream_index), dec_frame)) == AVERROR(EAGAIN)); + } while ((recfr_ret = avcodec_receive_frame(input_streams.at(packet.stream_index), dec_frame)) == AVERROR(EAGAIN) && !skip); // error/eof handling - cancel while loop - if (read_ret < 0) { + if (read_ret < 0 || skip) { break; } - // - // SWSCALE IF NECESSARY - // - // free packet as we're about to use it for encoding av_packet_unref(&packet); + // rescale input frame timestamp to output timestamp av_rescale_q(dec_frame->pts, input_fmt_ctx->streams[stream_index]->time_base, output_fmt_ctx->streams[stream_index]->time_base); + // determine if the pix_fmt is different, so if we need to convert bool convert_pix_fmt = (output_streams.at(stream_index)->pix_fmt != input_streams.at(stream_index)->pix_fmt); + // create reference to the frame to be sent to the encoder AVFrame* enc_frame = dec_frame; if (convert_pix_fmt) { @@ -233,14 +240,21 @@ void transcode(const ProxyInfo& info) { av_frame_free(&enc_frame); } + // return value for packet receiving int recret; - while ((recret = avcodec_receive_packet(output_streams.at(stream_index), &packet)) >= 0) { + // loop through receiving packets + while ((recret = avcodec_receive_packet(output_streams.at(stream_index), &packet)) >= 0 && !skip) { + + // set packet stream index to current stream index packet.stream_index = stream_index; + // write frame to file av_interleaved_write_frame(output_fmt_ctx, &packet); + // unref old packet av_packet_unref(&packet); + } } @@ -276,7 +290,13 @@ void transcode(const ProxyInfo& info) { // close input file avformat_close_input(&input_fmt_ctx); + // set footage to use newly generated proxy + info.footage->proxy = true; + info.footage->proxy_path = info.path; + qInfo() << "Finished creating proxy for" << info.footage->url; + mainWindow->statusBar()->showMessage(tr("Finished generating proxy for \"%1\"").arg(info.footage->url)); + } // main proxy generating loop @@ -300,6 +320,9 @@ void ProxyGenerator::run() { // create directory for info QFileInfo(info.path).dir().mkpath("."); + // set skip to false + skip = false; + // transcode proxy transcode(info); @@ -317,6 +340,22 @@ void ProxyGenerator::run() { // called to add footage to generate proxies for void ProxyGenerator::queue(const ProxyInfo &info) { + // remove any queued proxies with the same footage + if (!proxy_queue.isEmpty() + && proxy_queue.first().footage == info.footage) { + // if the thread is currently processing a proxy with the same footage, abort it + skip = true; + } + + // scan through the rest of the queue for another proxy with the same footage (start with 1 since we already processed first()) + for (int i=1;i proxy_queue; + + // threading objects QWaitCondition waitCond; QMutex mutex; + + // set to true if you want to permanently close ProxyGenerator bool cancelled; + + // set to true if you want to abort the footage currently being processed + bool skip; + + // stores progress in percent of proxy currently being processed + double current_progress; + + // function that performs the actual transcode + void transcode(const ProxyInfo& info); }; // proxy generator is a global omnipotent entity diff --git a/panels/project.cpp b/panels/project.cpp index 4ec6ae81a..0c543e1da 100644 --- a/panels/project.cpp +++ b/panels/project.cpp @@ -954,6 +954,10 @@ void Project::save_folder(QXmlStreamWriter& stream, int type, bool set_ids_only, stream.writeAttribute("out", QString::number(f->out)); stream.writeAttribute("speed", QString::number(f->speed)); stream.writeAttribute("alphapremul", QString::number(f->alpha_is_premultiplied)); + + stream.writeAttribute("proxy", QString::number(f->proxy)); + stream.writeAttribute("proxypath", f->proxy_path); + for (int j=0;jvideo_tracks.size();j++) { const FootageStream& ms = f->video_tracks.at(j); stream.writeStartElement("video"); diff --git a/playback/cacher.cpp b/playback/cacher.cpp index ae08fbb69..0f29b290b 100644 --- a/playback/cacher.cpp +++ b/playback/cacher.cpp @@ -673,7 +673,19 @@ void open_clip_worker(Clip* clip) { } 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(); + + // byte array for retriving raw bytes from QString URL + QByteArray ba; + + // do we have a proxy? + if (m->proxy + && !m->proxy_path.isEmpty() + && QFileInfo::exists(m->proxy_path)) { + ba = m->proxy_path.toUtf8(); + } else { + ba = m->url.toUtf8(); + } + const char* filename = ba.constData(); const FootageStream* ms = m->get_stream_from_file_index(clip->track < 0, clip->media_stream); diff --git a/playback/playback.cpp b/playback/playback.cpp index 744723c4e..ce0f1d2d6 100644 --- a/playback/playback.cpp +++ b/playback/playback.cpp @@ -13,6 +13,7 @@ #include "project/media.h" #include "io/config.h" #include "io/avtogl.h" +#include "io/proxygenerator.h" #include "debug.h" extern "C" { @@ -28,6 +29,8 @@ extern "C" { #include #include #include +#include +#include #ifdef QT_DEBUG //#define GCF_DEBUG @@ -272,14 +275,55 @@ void get_clip_frame(Clip* c, long playhead, bool& texture_failed) { uint8_t* data_buffer_1 = target_frame->data[0]; uint8_t* data_buffer_2 = nullptr; - size_t frame_size; + size_t frame_size = size_t(target_frame->linesize[0])*size_t(target_frame->height); + + // if proxy is currently being generated, show an on-screen message of its progress + if (c->media->to_footage()->proxy + && c->media->to_footage()->proxy_path.isEmpty()) { + // create buffers to draw on + data_buffer_1 = new uint8_t[frame_size]; + data_buffer_2 = new uint8_t[frame_size]; + + memcpy(data_buffer_1, target_frame->data[0], frame_size); + + // wrap data in a QImage for painting + QImage img(data_buffer_1, target_frame->width, target_frame->height, QImage::Format_RGBA8888); + + // create QPainter process + QPainter p(&img); + + // set font color to white + p.setPen(Qt::white); + + // set font size relative to frame size (divided by 12) + QFont overlay_font = p.font(); + overlay_font.setPixelSize(target_frame->height/12); + p.setFont(overlay_font); + + // generate overlay text + QString proxy_overlay_text = QCoreApplication::translate("Playback", "Generating Proxy: %1%").arg(proxy_generator.get_proxy_progress(c->media->to_footage())); + + int text_height = p.fontMetrics().descent() + p.fontMetrics().height(); + + // draw semi-transparent black background + p.fillRect(QRect(0, + target_frame->height-text_height, + p.fontMetrics().width(proxy_overlay_text), + text_height), + QColor(0, 0, 0, 128) + ); + + + // draw text + p.drawText(0, + target_frame->height-p.fontMetrics().descent(), + proxy_overlay_text); + } for (int i=0;ieffects.size();i++) { Effect* e = c->effects.at(i); if (e->enable_image && e->is_enabled()) { if (data_buffer_1 == target_frame->data[0]) { - frame_size = size_t(target_frame->linesize[0])*size_t(target_frame->height); - data_buffer_1 = new uint8_t[frame_size]; data_buffer_2 = new uint8_t[frame_size]; diff --git a/project/footage.cpp b/project/footage.cpp index c5cdc94e9..82ecc5915 100644 --- a/project/footage.cpp +++ b/project/footage.cpp @@ -18,7 +18,8 @@ Footage::Footage() : in(0), out(0), speed(1.0), - alpha_is_premultiplied(false) + alpha_is_premultiplied(false), + proxy(false) { ready_lock.lock(); } diff --git a/project/footage.h b/project/footage.h index 56ae6cace..6e5219078 100644 --- a/project/footage.h +++ b/project/footage.h @@ -56,6 +56,10 @@ struct Footage { double speed; bool alpha_is_premultiplied; + // proxy config + bool proxy; + QString proxy_path; + PreviewGenerator* preview_gen; QMutex ready_lock; diff --git a/project/sourcescommon.cpp b/project/sourcescommon.cpp index 957eb5b7a..b856fbcd1 100644 --- a/project/sourcescommon.cpp +++ b/project/sourcescommon.cpp @@ -3,13 +3,17 @@ #include "panels/panels.h" #include "project/media.h" #include "project/undo.h" +#include "playback/playback.h" #include "panels/timeline.h" #include "panels/project.h" #include "project/footage.h" #include "panels/viewer.h" #include "project/projectfilter.h" +#include "project/sequence.h" #include "io/config.h" #include "dialogs/proxydialog.h" +#include "ui/viewerwidget.h" +#include "io/proxygenerator.h" #include "mainwindow.h" #include @@ -19,6 +23,8 @@ #include #include +#include + SourcesCommon::SourcesCommon(Project* parent) : editing_item(nullptr), project_parent(parent) @@ -76,11 +82,11 @@ void SourcesCommon::show_context_menu(QWidget* parent, const QModelIndexList& it connect(show_sequences, SIGNAL(triggered(bool)), panel_project->sorter, SLOT(set_show_sequences(bool))); if (items.size() > 0) { - Media* m = project_parent->item_to_media(items.at(0)); - if (items.size() == 1) { + Media* first_media = project_parent->item_to_media(items.at(0)); + // replace footage - int type = m->get_type(); + int type = first_media->get_type(); if (type == MEDIA_TYPE_FOOTAGE) { QAction* replace_action = menu.addAction(tr("Replace/Relink Media")); QObject::connect(replace_action, SIGNAL(triggered(bool)), project_parent, SLOT(replace_selected_file())); @@ -100,11 +106,13 @@ void SourcesCommon::show_context_menu(QWidget* parent, const QModelIndexList& it } } - // duplicate item + // analyze selected footage types bool all_sequences = true; bool all_footage = true; + cached_selected_footage.clear(); for (int i=0;iitem_to_media(items.at(i)); if (m->get_type() != MEDIA_TYPE_SEQUENCE) { all_sequences = false; } @@ -132,9 +140,52 @@ void SourcesCommon::show_context_menu(QWidget* parent, const QModelIndexList& it QObject::connect(delete_footage_from_sequences, SIGNAL(triggered(bool)), project_parent, SLOT(delete_clips_using_selected_media())); QMenu* proxies = menu.addMenu(tr("Proxy")); - proxies->addAction(tr("Create Proxy"), this, SLOT(open_create_proxy_dialog())); -// proxies->addAction(tr("Modify Proxy")); -// proxies->addAction(tr("Restore Original")); + + // special case if one footage item is selected and its proxy is currently being generated + if (cached_selected_footage.size() == 1 + && cached_selected_footage.at(0)->proxy + && cached_selected_footage.at(0)->proxy_path.isEmpty()) { + QAction* action = proxies->addAction(tr("Generating proxy: %1% complete").arg(proxy_generator.get_proxy_progress(cached_selected_footage.at(0)))); + action->setEnabled(false); + } else { + // determine whether any selected footage has or doesn't have proxies + bool footage_without_proxies_exists = false; + bool footage_with_proxies_exists = false; + + for (int i=0;iproxy) { + footage_with_proxies_exists = true; + } else { + footage_without_proxies_exists = true; + } + } + + // if footage was selected WITHOUT proxies + if (footage_without_proxies_exists) { + QString create_proxy_text; + + if (footage_with_proxies_exists) { + // some of the footage already has proxies, so we use a different string + create_proxy_text = tr("Create/Modify Proxy"); + } else { + // none of the footage has proxies + create_proxy_text = tr("Create Proxy"); + } + + proxies->addAction(create_proxy_text, this, SLOT(open_create_proxy_dialog())); + } + + // if footage was selected WITH proxies + if (footage_with_proxies_exists) { + + if (!footage_without_proxies_exists) { + // if all the footage has proxies, we didn't make a "Create/Modify" above, so we create one here (but only "modify") + proxies->addAction(tr("Modify Proxy"), this, SLOT(open_create_proxy_dialog())); + } + + proxies->addAction(tr("Restore Original"), this, SLOT(clear_proxies_from_selected())); + } + } } // delete media @@ -308,3 +359,38 @@ void SourcesCommon::open_create_proxy_dialog() { ProxyDialog pd(mainWindow, cached_selected_footage); pd.exec(); } + +void SourcesCommon::clear_proxies_from_selected() { + QList delete_list; + + for (int i=0;iproxy && !f->proxy_path.isEmpty()) { + if (QFileInfo::exists(f->proxy_path)) { + if (QMessageBox::question(mainWindow, + tr("Delete proxy"), + tr("Would you like to delete the proxy file \"%1\" as well?").arg(f->proxy_path), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + delete_list.append(f->proxy_path); + } + } + } + + f->proxy = false; + f->proxy_path.clear(); + } + + if (sequence != nullptr) { + // close all clips so we can delete any proxies requested to be deleted + closeActiveClips(sequence); + + // delete proxies requested to be deleted + for (int i=0;iviewer_widget->frame_update(); + } +} diff --git a/project/sourcescommon.h b/project/sourcescommon.h index 3c15e5ae2..1eb816129 100644 --- a/project/sourcescommon.h +++ b/project/sourcescommon.h @@ -30,7 +30,10 @@ private slots: void reveal_in_browser(); void rename_interval(); void item_renamed(Media *item); + + // proxy functions void open_create_proxy_dialog(); + void clear_proxies_from_selected(); private: Media* editing_item; QModelIndex editing_index; diff --git a/ui/renderthread.cpp b/ui/renderthread.cpp index a8ef4fee5..b85a3d349 100644 --- a/ui/renderthread.cpp +++ b/ui/renderthread.cpp @@ -58,7 +58,6 @@ void RenderThread::run() { ctx->functions()->glGenFramebuffers(1, &back_buffer_2); } - // gen texture if (front_texture == 0 || tex_width != seq->width || tex_height != seq->height) { // cache texture size