From dc8d4728fc2fe5b7a174a013f1e6d50c19d38482 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 15 Jun 2018 20:17:05 -0700 Subject: [PATCH] thumbnail/waveform generation --- dialogs/newsequencedialog.cpp | 6 +- effects/effects.h | 4 +- effects/paneffect.cpp | 14 +- effects/volumeeffect.cpp | 8 +- io/exportthread.cpp | 4 +- io/media.h | 5 +- io/previewgenerator.cpp | 175 ++++++++- io/previewgenerator.h | 9 +- mainwindow.cpp | 56 ++- mainwindow.h | 12 + mainwindow.ui | 60 +++- olive.pro.user | 654 +++++++++++++++++----------------- panels/effectcontrols.cpp | 41 ++- panels/effectcontrols.h | 3 +- panels/project.cpp | 20 +- panels/timeline.cpp | 3 +- panels/timeline.ui | 3 + panels/viewer.cpp | 24 +- panels/viewer.ui | 14 +- playback/cacher.cpp | 8 +- project/clip.h | 8 +- project/effect.h | 2 +- ui/sourcetable.h | 2 +- ui/timelineheader.h | 2 +- ui/timelinewidget.cpp | 72 +++- ui/timelinewidget.h | 3 +- ui/viewercontainer.h | 2 +- ui/viewerwidget.cpp | 108 +++--- ui/viewerwidget.h | 6 +- 29 files changed, 849 insertions(+), 479 deletions(-) diff --git a/dialogs/newsequencedialog.cpp b/dialogs/newsequencedialog.cpp index 91663a651..a3e19b681 100644 --- a/dialogs/newsequencedialog.cpp +++ b/dialogs/newsequencedialog.cpp @@ -21,13 +21,15 @@ NewSequenceDialog::NewSequenceDialog(QWidget *parent) : ui->frame_rate_combobox->addItem("10 FPS", 10.0f); ui->frame_rate_combobox->addItem("12.5 FPS", 12.5f); ui->frame_rate_combobox->addItem("15 FPS", 15.0f); - ui->frame_rate_combobox->addItem("25 FPS", 25.0f); + ui->frame_rate_combobox->addItem("23.976 FPS", 23.976f); + ui->frame_rate_combobox->addItem("24 FPS", 24.0f); + ui->frame_rate_combobox->addItem("25 FPS", 25.0f); ui->frame_rate_combobox->addItem("29.97 FPS", 29.97f); ui->frame_rate_combobox->addItem("30 FPS", (float) 30.0f); ui->frame_rate_combobox->addItem("50 FPS", (float) 50.0f); ui->frame_rate_combobox->addItem("59.94 FPS", (float) 59.94f); ui->frame_rate_combobox->addItem("60 FPS", (float) 60.0f); - ui->frame_rate_combobox->setCurrentIndex(4); + ui->frame_rate_combobox->setCurrentIndex(6); ui->audio_frequency_combobox->addItem("22050 Hz", 22050); ui->audio_frequency_combobox->addItem("24000 Hz", 24000); diff --git a/effects/effects.h b/effects/effects.h index fbd8f51e7..247cf5bb6 100644 --- a/effects/effects.h +++ b/effects/effects.h @@ -51,7 +51,7 @@ private: class VolumeEffect : public Effect { public: VolumeEffect(Clip* c); - void process_audio(uint8_t* samples, int nb_bytes); + void process_audio(quint8* samples, int nb_bytes); Effect* copy(Clip* c); void load(QXmlStreamReader* stream); void save(QXmlStreamWriter* stream); @@ -62,7 +62,7 @@ public: class PanEffect : public Effect { public: PanEffect(Clip* c); - void process_audio(uint8_t* samples, int nb_bytes); + void process_audio(quint8* samples, int nb_bytes); Effect* copy(Clip* c); void load(QXmlStreamReader* stream); void save(QXmlStreamWriter* stream); diff --git a/effects/paneffect.cpp b/effects/paneffect.cpp index 3d957a2f3..d18427825 100644 --- a/effects/paneffect.cpp +++ b/effects/paneffect.cpp @@ -45,10 +45,10 @@ void PanEffect::save(QXmlStreamWriter *stream) { stream->writeTextElement("pan", QString::number(pan_val->value())); } -void PanEffect::process_audio(uint8_t *samples, int nb_bytes) { +void PanEffect::process_audio(quint8 *samples, int nb_bytes) { for (int i=0;ivalue()*0.01; if (val < 0) { @@ -59,9 +59,9 @@ void PanEffect::process_audio(uint8_t *samples, int nb_bytes) { left_sample *= (1-val); } - samples[i+3] = (uint8_t) (right_sample >> 8); - samples[i+2] = (uint8_t) right_sample; - samples[i+1] = (uint8_t) (left_sample >> 8); - samples[i] = (uint8_t) left_sample; + samples[i+3] = (quint8) (right_sample >> 8); + samples[i+2] = (quint8) right_sample; + samples[i+1] = (quint8) (left_sample >> 8); + samples[i] = (quint8) left_sample; } } diff --git a/effects/volumeeffect.cpp b/effects/volumeeffect.cpp index 6826eb0f3..0c01b6a65 100644 --- a/effects/volumeeffect.cpp +++ b/effects/volumeeffect.cpp @@ -45,11 +45,11 @@ void VolumeEffect::save(QXmlStreamWriter *stream) { stream->writeTextElement("volume", QString::number(volume_val->value())); } -void VolumeEffect::process_audio(uint8_t *samples, int nb_bytes) { +void VolumeEffect::process_audio(quint8 *samples, int nb_bytes) { for (int i=0;ivalue()*0.01; - samples[i+1] = (uint8_t) (full_sample >> 8); - samples[i] = (uint8_t) full_sample; + samples[i+1] = (quint8) (full_sample >> 8); + samples[i] = (quint8) full_sample; } } diff --git a/io/exportthread.cpp b/io/exportthread.cpp index 5fbe4a7eb..a2cb4cf8f 100644 --- a/io/exportthread.cpp +++ b/io/exportthread.cpp @@ -21,7 +21,7 @@ extern "C" { #include #include #include -#include +#include #include bool encode(AVFormatContext* fmt_ctx, AVCodecContext* codec_ctx, AVFrame* frame, AVPacket* packet, AVStream* stream) { @@ -277,7 +277,7 @@ void ExportThread::run() { long file_audio_samples = 0; while (panel_timeline->playhead < end && !fail) { - panel_viewer->viewer_widget->paintGL(); + panel_viewer->viewer_widget->update(); double timecode_secs = (double) panel_timeline->playhead / sequence->frame_rate; if (video_enabled) { diff --git a/io/media.h b/io/media.h index e10680bd2..bf7f179d6 100644 --- a/io/media.h +++ b/io/media.h @@ -6,6 +6,7 @@ #include #include #include +#include struct Sequence; @@ -16,8 +17,10 @@ struct MediaStream { bool infinite_length; // preview thumbnail/waveform - QPixmap* preview; + bool preview_done; + QImage preview; QMutex preview_lock; + int preview_audio_index; }; struct Media diff --git a/io/previewgenerator.cpp b/io/previewgenerator.cpp index c8fe936c0..651f14a5b 100644 --- a/io/previewgenerator.cpp +++ b/io/previewgenerator.cpp @@ -1,10 +1,181 @@ #include "previewgenerator.h" -PreviewGenerator::PreviewGenerator(QObject* parent) : QThread(parent) -{ +#include "media.h" +#include +#include +#include +#include + +extern "C" { +#include +#include +#include } +PreviewGenerator::PreviewGenerator(QObject* parent) : QThread(parent) { + media = NULL; + fmt_ctx = NULL; +} + +MediaStream* PreviewGenerator::get_stream_from_file_index(int index) { + for (int i=0;ivideo_tracks.size();i++) { + if (media->video_tracks.at(i)->file_index == index) { + return media->video_tracks.at(i); + } + } + for (int i=0;iaudio_tracks.size();i++) { + if (media->audio_tracks.at(i)->file_index == index) { + return media->audio_tracks.at(i); + } + } + return NULL; +} + +#define WAVEFORM_RESOLUTION 256 + void PreviewGenerator::run() { + if (media == NULL) { + qDebug() << "[ERROR] No media was set for preview generation"; + } else { + if (media->is_sequence) { + qDebug() << "[ERROR] Cannot run preview generation on a sequence"; + } else { + SwsContext* sws_ctx; + SwrContext* swr_ctx; + AVFrame* temp_frame = av_frame_alloc(); + AVCodecContext* codec_ctx[fmt_ctx->nb_streams]; + for (unsigned int i=0;inb_streams;i++) { + AVCodec* codec = avcodec_find_decoder(fmt_ctx->streams[i]->codecpar->codec_id); + codec_ctx[i] = avcodec_alloc_context3(codec); + avcodec_parameters_to_context(codec_ctx[i], fmt_ctx->streams[i]->codecpar); + avcodec_open2(codec_ctx[i], codec, NULL); + if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { + // initiate qimage + MediaStream* ms = get_stream_from_file_index(i); + ms->preview = QImage(fmt_ctx->streams[i]->duration * av_q2d(fmt_ctx->streams[i]->time_base) * WAVEFORM_RESOLUTION, 255, QImage::Format_RGBA8888); + ms->preview_audio_index = 0; + ms->preview.fill(Qt::transparent); + } + } + AVPacket packet; + bool done = true; + while (av_read_frame(fmt_ctx, &packet) >= 0) { + MediaStream* s = get_stream_from_file_index(packet.stream_index); + if (s != NULL && !s->preview_done) { + if (avcodec_send_packet(codec_ctx[packet.stream_index], &packet) >= 0) { + if (avcodec_receive_frame(codec_ctx[packet.stream_index], temp_frame) >= 0) { + if (fmt_ctx->streams[packet.stream_index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + int dstH = 120; + int dstW = dstH * ((float)temp_frame->width/(float)temp_frame->height); + uint8_t* data = new uint8_t[dstW*dstH*3]; + + sws_ctx = sws_getContext( + temp_frame->width, + temp_frame->height, + static_cast(temp_frame->format), + dstW, + dstH, + static_cast(AV_PIX_FMT_RGB24), + SWS_FAST_BILINEAR, + NULL, + NULL, + NULL + ); + + int linesize[AV_NUM_DATA_POINTERS]; + linesize[0] = dstW*3; + sws_scale(sws_ctx, temp_frame->data, temp_frame->linesize, 0, temp_frame->height, &data, linesize); + + s->preview = QImage(data, dstW, dstH, linesize[0], QImage::Format_RGB888); + +// delete [] data; + + s->preview_done = true; + s->preview_lock.unlock(); + + sws_freeContext(sws_ctx); + } else if (fmt_ctx->streams[packet.stream_index]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { + int interval = qFloor((temp_frame->sample_rate/WAVEFORM_RESOLUTION)/4)*4; + swr_ctx = swr_alloc_set_opts( + NULL, + temp_frame->channel_layout, + AV_SAMPLE_FMT_S16, + temp_frame->sample_rate, + temp_frame->channel_layout, + static_cast(temp_frame->format), + temp_frame->sample_rate, + 0, + NULL + ); + + swr_init(swr_ctx); + + AVFrame* swr_frame = av_frame_alloc(); + swr_frame->channel_layout = temp_frame->channel_layout; + swr_frame->sample_rate = temp_frame->sample_rate; + swr_frame->format = AV_SAMPLE_FMT_S16; + + swr_convert_frame(swr_ctx, swr_frame, temp_frame); + + int channel_height = s->preview.height()/swr_frame->channels; + + QPainter p; + p.begin(&s->preview); + p.setPen(QColor(80, 80, 80)); + for (int i=0;inb_samples;i+=interval) { + for (int j=0;jchannels;j++) { + qint16 sample = ((swr_frame->data[0][i+1] << 8) | swr_frame->data[0][i]); + sample /= 256; + sample /= swr_frame->channels; + int mid = channel_height*j+(channel_height/2); + p.drawLine(s->preview_audio_index, mid, s->preview_audio_index, mid+sample); + i += 2; + } + s->preview_audio_index++; + } + p.end(); + + swr_free(&swr_ctx); + av_frame_free(&swr_frame); + } + } else { + qDebug() << "[ERROR] Failed to retrieve frame"; + } + } else { + qDebug() << "[ERROR] Failed to send packet"; + } + } + + av_packet_unref(&packet); + + // check if we've got all our previews + if (media->audio_tracks.size() == 0) { + done = true; + for (int i=0;ivideo_tracks.size();i++) { + if (!media->video_tracks.at(i)->preview_done) { + done = false; + break; + } + } + if (done) { + break; + } + } + } + for (int i=0;iaudio_tracks.size();i++) { + media->audio_tracks.at(i)->preview_done = true; + media->audio_tracks.at(i)->preview_lock.unlock(); + } + av_frame_free(&temp_frame); + for (unsigned int i=0;inb_streams;i++) { + avcodec_close(codec_ctx[i]); + } + } + } + + if (fmt_ctx != NULL) { + avformat_close_input(&fmt_ctx); + } } diff --git a/io/previewgenerator.h b/io/previewgenerator.h index e13447443..7d7d23717 100644 --- a/io/previewgenerator.h +++ b/io/previewgenerator.h @@ -3,11 +3,18 @@ #include +struct Media; +struct MediaStream; +struct AVFormatContext; + class PreviewGenerator : public QThread { public: PreviewGenerator(QObject* parent = 0); - void run() override; + void run(); + MediaStream* get_stream_from_file_index(int index); + Media* media; + AVFormatContext* fmt_ctx; }; #endif // PREVIEWGENERATOR_H diff --git a/mainwindow.cpp b/mainwindow.cpp index bfce6970e..86722328f 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -21,11 +21,6 @@ #define OLIVE_FILE_FILTER "Olive Project (*.ove)" void MainWindow::setup_layout() { - panel_project = new Project(this); - panel_effect_controls = new EffectControls(this); - panel_viewer = new Viewer(this); - panel_timeline = new Timeline(this); - addDockWidget(Qt::TopDockWidgetArea, panel_project); addDockWidget(Qt::TopDockWidgetArea, panel_effect_controls); addDockWidget(Qt::TopDockWidgetArea, panel_viewer); @@ -69,12 +64,23 @@ MainWindow::MainWindow(QWidget *parent) : setWindowTitle("Olive (May 2018 | Pre-Alpha)"); statusBar()->showMessage("Welcome to Olive::Qt"); + // TODO maybe replace these with non-pointers later on? + panel_project = new Project(this); + panel_effect_controls = new EffectControls(this); + panel_viewer = new Viewer(this); + panel_timeline = new Timeline(this); + setup_layout(); } MainWindow::~MainWindow() { delete ui; + + delete panel_project; + delete panel_effect_controls; + delete panel_viewer; + delete panel_timeline; } void MainWindow::on_action_Import_triggered() @@ -272,3 +278,43 @@ void MainWindow::on_actionDeselect_All_triggered() panel_timeline->deselect(); } } + +void MainWindow::on_actionGo_to_start_triggered() +{ + if (panel_timeline->focused() || panel_viewer->hasFocus()) { + panel_timeline->go_to_start(); + } +} + +void MainWindow::on_actionReset_to_default_layout_triggered() +{ + setup_layout(); +} + +void MainWindow::on_actionPrevious_Frame_triggered() +{ + if (panel_timeline->focused() || panel_viewer->hasFocus()) { + panel_timeline->previous_frame(); + } +} + +void MainWindow::on_actionNext_Frame_triggered() +{ + if (panel_timeline->focused() || panel_viewer->hasFocus()) { + panel_timeline->next_frame(); + } +} + +void MainWindow::on_actionGo_to_End_triggered() +{ + if (panel_timeline->focused() || panel_viewer->hasFocus()) { + panel_timeline->go_to_end(); + } +} + +void MainWindow::on_actionPlay_Pause_triggered() +{ + if (panel_timeline->focused() || panel_viewer->hasFocus()) { + panel_timeline->toggle_play(); + } +} diff --git a/mainwindow.h b/mainwindow.h index 557c34de1..6e220f0d9 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -73,6 +73,18 @@ private slots: void on_actionDeselect_All_triggered(); + void on_actionGo_to_start_triggered(); + + void on_actionReset_to_default_layout_triggered(); + + void on_actionPrevious_Frame_triggered(); + + void on_actionNext_Frame_triggered(); + + void on_actionGo_to_End_triggered(); + + void on_actionPlay_Pause_triggered(); + private: Ui::MainWindow *ui; void setup_layout(); diff --git a/mainwindow.ui b/mainwindow.ui index 704b9bc30..176ca7439 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -24,7 +24,7 @@ 0 0 653 - 17 + 34 @@ -76,6 +76,8 @@ + + @@ -91,9 +93,20 @@ + + + Playback + + + + + + + + @@ -303,6 +316,51 @@ Deselect All + + + Go to Start + + + Home + + + + + Reset to default layout + + + + + Previous Frame + + + Left + + + + + Play/Pause + + + Space + + + + + Next Frame + + + Right + + + + + Go to End + + + End + + diff --git a/olive.pro.user b/olive.pro.user index d57c157b8..720492dac 100644 --- a/olive.pro.user +++ b/olive.pro.user @@ -1,318 +1,336 @@ - - - - - - EnvironmentId - {3af06612-75ce-48d7-b444-7dc372f1566d} - - - ProjectExplorer.Project.ActiveTarget - 0 - - - ProjectExplorer.Project.EditorSettings - - true - false - true - - Cpp - - CppGlobal - - - - QmlJS - - QmlJSGlobal - - - 2 - UTF-8 - false - 4 - false - 80 - true - true - 1 - true - false - 0 - true - true - 0 - 8 - true - 1 - true - true - true - false - - - - ProjectExplorer.Project.PluginSettings - - - - ProjectExplorer.Project.Target.0 - - Desktop Qt 5.10.1 MSVC2017 64bit - Desktop Qt 5.10.1 MSVC2017 64bit - qt.qt5.5101.win64_msvc2017_64_kit - 0 - 0 - 0 - - C:/Users/Matt/Documents/temp - - - true - qmake - - QtProjectManager.QMakeBuildStep - true - - false - false - false - - - true - Make - - Qt4ProjectManager.MakeStep - - false - - - - 2 - Build - - ProjectExplorer.BuildSteps.Build - - - - true - Make - - Qt4ProjectManager.MakeStep - - true - clean - - - 1 - Clean - - ProjectExplorer.BuildSteps.Clean - - 2 - false - - Debug - Debug - Qt4ProjectManager.Qt4BuildConfiguration - 2 - true - - - //neptune/build-olive-qt-Desktop_Qt_5_10_1_MSVC2017_64bit-Release - - - true - qmake - - QtProjectManager.QMakeBuildStep - false - - false - false - false - - - true - Make - - Qt4ProjectManager.MakeStep - - false - - - - 2 - Build - - ProjectExplorer.BuildSteps.Build - - - - true - Make - - Qt4ProjectManager.MakeStep - - true - clean - - - 1 - Clean - - ProjectExplorer.BuildSteps.Clean - - 2 - false - - Release - Release - Qt4ProjectManager.Qt4BuildConfiguration - 0 - true - - - //neptune/build-olive-qt-Desktop_Qt_5_10_1_MSVC2017_64bit-Profile - - - true - qmake - - QtProjectManager.QMakeBuildStep - true - - false - true - false - - - true - Make - - Qt4ProjectManager.MakeStep - - false - - - - 2 - Build - - ProjectExplorer.BuildSteps.Build - - - - true - Make - - Qt4ProjectManager.MakeStep - - true - clean - - - 1 - Clean - - ProjectExplorer.BuildSteps.Clean - - 2 - false - - Profile - Profile - Qt4ProjectManager.Qt4BuildConfiguration - 0 - true - - 3 - - - 0 - Deploy - - ProjectExplorer.BuildSteps.Deploy - - 1 - Deploy Configuration - - ProjectExplorer.DefaultDeployConfiguration - - 1 - - - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - olive - - Qt4ProjectManager.Qt4RunConfiguration:C:/Users/Matt/Desktop/olive/olive.pro - true - - olive.pro - false - - C:/Users/Matt/Documents/temp - 3768 - false - true - false - false - true - - 1 - - - - ProjectExplorer.Project.TargetCount - 1 - - - ProjectExplorer.Project.Updater.FileVersion - 18 - - - Version - 18 - - + + + + + + EnvironmentId + {be57d5b1-e5ae-4026-9f48-bb6e01f50d4e} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + false + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 80 + true + true + 1 + true + false + 0 + true + true + 0 + 8 + true + 1 + true + true + true + false + + + + ProjectExplorer.Project.PluginSettings + + + + ProjectExplorer.Project.Target.0 + + Desktop Qt 5.5.1 GCC 64bit + Desktop Qt 5.5.1 GCC 64bit + qt.55.gcc_64_kit + 0 + 0 + 0 + + /home/matt/Documents/build-olive-Desktop_Qt_5_5_1_GCC_64bit-Debug + + + true + qmake + + QtProjectManager.QMakeBuildStep + true + + false + false + false + + + true + Make + + Qt4ProjectManager.MakeStep + + -w + -r + + false + + + + 2 + Build + + ProjectExplorer.BuildSteps.Build + + + + true + Make + + Qt4ProjectManager.MakeStep + + -w + -r + + true + clean + + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Debug + Debug + Qt4ProjectManager.Qt4BuildConfiguration + 2 + true + + + /home/matt/build-olive-Desktop_Qt_5_5_1_GCC_64bit-Release + + + true + qmake + + QtProjectManager.QMakeBuildStep + false + + false + false + false + + + true + Make + + Qt4ProjectManager.MakeStep + + -w + -r + + false + + + + 2 + Build + + ProjectExplorer.BuildSteps.Build + + + + true + Make + + Qt4ProjectManager.MakeStep + + -w + -r + + true + clean + + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Release + Release + Qt4ProjectManager.Qt4BuildConfiguration + 0 + true + + + /home/matt/build-olive-Desktop_Qt_5_5_1_GCC_64bit-Profile + + + true + qmake + + QtProjectManager.QMakeBuildStep + true + + false + true + false + + + true + Make + + Qt4ProjectManager.MakeStep + + -w + -r + + false + + + + 2 + Build + + ProjectExplorer.BuildSteps.Build + + + + true + Make + + Qt4ProjectManager.MakeStep + + -w + -r + + true + clean + + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Profile + Profile + Qt4ProjectManager.Qt4BuildConfiguration + 0 + true + + 3 + + + 0 + Deploy + + ProjectExplorer.BuildSteps.Deploy + + 1 + Deploy Configuration + + ProjectExplorer.DefaultDeployConfiguration + + 1 + + + false + false + 1000 + + true + + false + false + false + false + true + 0.01 + 10 + true + 1 + 25 + + 1 + true + false + true + valgrind + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + 2 + + olive + + Qt4ProjectManager.Qt4RunConfiguration:/home/matt/olive/olive.pro + true + + olive.pro + false + + /home/matt/Documents/build-olive-Desktop_Qt_5_5_1_GCC_64bit-Debug + 3768 + false + true + false + false + true + + 1 + + + + ProjectExplorer.Project.TargetCount + 1 + + + ProjectExplorer.Project.Updater.FileVersion + 18 + + + Version + 18 + + diff --git a/panels/effectcontrols.cpp b/panels/effectcontrols.cpp index 503a150ac..b606a81b4 100644 --- a/panels/effectcontrols.cpp +++ b/panels/effectcontrols.cpp @@ -20,23 +20,7 @@ EffectControls::EffectControls(QWidget *parent) : init_effects(); clip = NULL; - set_clip(NULL); - - effects_menu = new QMenu(this); - for (int i=0;isetText(video_effect_names.at(i)); - action->setData(i); - effects_menu->addAction(action); - } - effects_menu->addSeparator(); - for (int i=0;isetText(audio_effect_names.at(i)); - action->setData(i); - effects_menu->addAction(action); - } - connect(effects_menu, SIGNAL(triggered(QAction*)), this, SLOT(menu_select(QAction*))); + set_clip(NULL); } void EffectControls::menu_select(QAction* q) { @@ -51,7 +35,28 @@ EffectControls::~EffectControls() void EffectControls::on_pushButton_clicked() { - effects_menu->exec(QCursor::pos()); + int lim; + QVector* effect_names; + if (clip->track < 0) { + lim = VIDEO_EFFECT_COUNT; + effect_names = &video_effect_names; + } else { + lim = AUDIO_EFFECT_COUNT; + effect_names = &audio_effect_names; + } + + QMenu effects_menu(this); + + for (int i=0;isetText(effect_names->at(i)); + action->setData(i); + effects_menu.addAction(action); + } + + connect(&effects_menu, SIGNAL(triggered(QAction*)), this, SLOT(menu_select(QAction*))); + + effects_menu.exec(QCursor::pos()); } void EffectControls::set_clip(Clip* c) { diff --git a/panels/effectcontrols.h b/panels/effectcontrols.h index a84750d47..cb33cc987 100644 --- a/panels/effectcontrols.h +++ b/panels/effectcontrols.h @@ -25,8 +25,7 @@ private slots: private: Ui::EffectControls *ui; - Clip* clip; - QMenu* effects_menu; + Clip* clip; }; #endif // EFFECTCONTROLS_H diff --git a/panels/project.cpp b/panels/project.cpp index 0f2d0e5ae..59de614b4 100644 --- a/panels/project.cpp +++ b/panels/project.cpp @@ -7,6 +7,10 @@ #include "panels/viewer.h" #include "playback/playback.h" #include "effects/effects.h" +#include "panels/timeline.h" +#include "project/sequence.h" +#include "project/effect.h" +#include "io/previewgenerator.h" #include #include @@ -17,10 +21,6 @@ #include #include -#include "panels/timeline.h" -#include "project/sequence.h" -#include "project/effect.h" - extern "C" { #include #include @@ -76,6 +76,8 @@ void Project::new_sequence(Sequence *s) { source_table = ui->treeWidget; project_changed = true; + + set_sequence(s); } Media* Project::import_file(QString file) { @@ -110,6 +112,8 @@ Media* Project::import_file(QString file) { qDebug() << "[ERROR] Unsupported codec in stream %d.\n"; } else { MediaStream* ms = new MediaStream(); + ms->preview_done = false; + ms->preview_lock.lock(); ms->file_index = i; if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { qDebug() << "[WARNING] INFINITE_LENGTH calculation is inaccurate in this build\n"; @@ -143,7 +147,13 @@ Media* Project::import_file(QString file) { project_changed = true; } } - avformat_close_input(&pFormatCtx); + + PreviewGenerator* pg = new PreviewGenerator(); + pg->fmt_ctx = pFormatCtx; // cleaned up in PG + pg->media = m; + connect(pg, SIGNAL(finished()), pg, SLOT(deleteLater())); + pg->run(); + delete [] filename; return m; } diff --git a/panels/timeline.cpp b/panels/timeline.cpp index 5d4fe19c7..687633d48 100644 --- a/panels/timeline.cpp +++ b/panels/timeline.cpp @@ -14,6 +14,7 @@ #include "playback/playback.h" #include +#include Timeline::Timeline(QWidget *parent) : QDockWidget(parent), @@ -120,7 +121,7 @@ void Timeline::update_sequence() { } else { setWindowTitle("Timeline: " + sequence->name); redraw_all_clips(); - playback_updater.setInterval(floor(1000 / sequence->frame_rate)); + playback_updater.setInterval(qFloor(1000 / sequence->frame_rate)); } } diff --git a/panels/timeline.ui b/panels/timeline.ui index 0b9e68c5d..08afe0588 100644 --- a/panels/timeline.ui +++ b/panels/timeline.ui @@ -340,6 +340,9 @@ 15 + + Qt::ClickFocus + diff --git a/panels/viewer.cpp b/panels/viewer.cpp index 654fd8b79..89800d9e3 100644 --- a/panels/viewer.cpp +++ b/panels/viewer.cpp @@ -18,7 +18,7 @@ Viewer::Viewer(QWidget *parent) : { ui->setupUi(this); ui->glViewerPane->child = ui->openGLWidget; - viewer_widget = ui->openGLWidget; + viewer_widget = ui->openGLWidget; update_sequence(); } @@ -31,22 +31,22 @@ Viewer::~Viewer() void Viewer::update_sequence() { bool null_sequence = (sequence == NULL); - ui->openGLWidget->setEnabled(!null_sequence); - ui->openGLWidget->setVisible(!null_sequence); - ui->pushButton->setEnabled(!null_sequence); - ui->pushButton_2->setEnabled(!null_sequence); - ui->pushButton_3->setEnabled(!null_sequence); - ui->pushButton_4->setEnabled(!null_sequence); - ui->pushButton_5->setEnabled(!null_sequence); + ui->openGLWidget->setEnabled(!null_sequence); + ui->openGLWidget->setVisible(!null_sequence); + ui->pushButton->setEnabled(!null_sequence); + ui->pushButton_2->setEnabled(!null_sequence); + ui->pushButton_3->setEnabled(!null_sequence); + ui->pushButton_4->setEnabled(!null_sequence); + ui->pushButton_5->setEnabled(!null_sequence); init_audio(); - if (!null_sequence) { - ui->glViewerPane->aspect_ratio = (float) sequence->width / (float) sequence->height; + if (!null_sequence) { + ui->glViewerPane->aspect_ratio = (float) sequence->width / (float) sequence->height; ui->glViewerPane->adjust(); - } + } - update(); + update(); } void Viewer::on_pushButton_clicked() diff --git a/panels/viewer.ui b/panels/viewer.ui index 3ad67cbc0..73b45a1b1 100644 --- a/panels/viewer.ui +++ b/panels/viewer.ui @@ -49,18 +49,12 @@ - 130 - 60 - 301 - 221 + 110 + 50 + 300 + 200 - - - 16777215 - 16777215 - - diff --git a/playback/cacher.cpp b/playback/cacher.cpp index 4c11912a1..fe8d4d91a 100644 --- a/playback/cacher.cpp +++ b/playback/cacher.cpp @@ -44,13 +44,15 @@ void cache_audio_worker(Clip* c) { c->effects.at(j)->process_audio(frame->data[0], nb_bytes); } - if (c->audio_buffer_write == 0) c->audio_buffer_write = audio_ibuffer_read + 1024; + if (c->audio_buffer_write == 0) c->audio_buffer_write = (((int)(audio_ibuffer_read/2))*2) + 1024; + int half_buffer = (audio_ibuffer_size/2); while (c->frame_sample_index < nb_bytes) { - if (c->audio_buffer_write >= audio_ibuffer_read+(audio_ibuffer_size/2)) { + if (c->audio_buffer_write >= audio_ibuffer_read+half_buffer) { written = max_write; break; } else { audio_ibuffer[c->audio_buffer_write%audio_ibuffer_size] += frame->data[0][c->frame_sample_index]; + c->audio_buffer_write++; c->frame_sample_index++; } @@ -293,6 +295,7 @@ void cache_clip_worker(Clip* clip, long playhead, bool write_A, bool write_B, bo } void close_clip_worker(Clip* clip) { + cc_lock.lock(); // closes ffmpeg file handle and frees any memory used for caching if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { sws_freeContext(clip->sws_ctx); @@ -314,7 +317,6 @@ void close_clip_worker(Clip* clip) { av_frame_free(&clip->frame); // remove clip from current_clips - cc_lock.lock(); bool found = false; for (int i=0;i #include #include +#include TimelineWidget::TimelineWidget(QWidget *parent) : QWidget(parent) { bottom_align = false; setMouseTracking(true); - track_height = 40; + track_height = 80; setAcceptDrops(true); } @@ -54,13 +55,13 @@ void TimelineWidget::dragEnterEvent(QDragEnterEvent *event) { g.clip_in = 0; for (int j=0;jaudio_tracks.size();j++) { g.track = j; - g.media_stream = m->audio_tracks[j]; + g.media_stream = m->audio_tracks.at(j); ignore_infinite_length = true; panel_timeline->ghosts.append(g); } for (int j=0;jvideo_tracks.size();j++) { g.track = -1-j; - g.media_stream = m->video_tracks[j]; + g.media_stream = m->video_tracks.at(j); if (m->video_tracks[j]->infinite_length && !ignore_infinite_length) g.out = g.in + 100; panel_timeline->ghosts.append(g); } @@ -150,6 +151,10 @@ void TimelineWidget::dropEvent(QDropEvent* event) { } } +void TimelineWidget::mouseDoubleClickEvent(QMouseEvent *event) { + +} + void TimelineWidget::mousePressEvent(QMouseEvent *event) { if (panel_timeline->tool == TIMELINE_TOOL_EDIT || panel_timeline->tool == TIMELINE_TOOL_RAZOR) { panel_timeline->drag_frame_start = panel_timeline->cursor_frame; @@ -200,6 +205,7 @@ void TimelineWidget::mousePressEvent(QMouseEvent *event) { case TIMELINE_TOOL_EDIT: panel_timeline->seek(panel_timeline->drag_frame_start); panel_timeline->selecting = true; + break; case TIMELINE_TOOL_RAZOR: { @@ -609,7 +615,8 @@ void TimelineWidget::mouseMoveEvent(QMouseEvent *event) { panel_timeline->seek(qMin(panel_timeline->drag_frame_start, panel_timeline->cursor_frame)); } else if (panel_timeline->moving_init) { if (panel_timeline->moving_proc) { - update_ghosts((QPoint&) event->pos()); + QPoint pos = event->pos(); + update_ghosts(pos); } else { // set up movement // create ghosts @@ -702,26 +709,34 @@ void TimelineWidget::mouseMoveEvent(QMouseEvent *event) { QPoint pos = event->pos(); int lim = 5; - int mouse_track = getTrackFromScreenPoint(pos.y()); + int mouse_track = getTrackFromScreenPoint(pos.y()); long mouse_frame_lower = panel_timeline->getFrameFromScreenPoint(pos.x()-lim)-1; long mouse_frame_upper = panel_timeline->getFrameFromScreenPoint(pos.x()+lim)+1; - bool found = false; + bool found = false; + int closeness = INT_MAX; for (int i=0;iclip_count();i++) { Clip* c = sequence->get_clip(i); if (c->track == mouse_track) { if (c->timeline_in > mouse_frame_lower && c->timeline_in < mouse_frame_upper) { - panel_timeline->trim_target = i; - panel_timeline->trim_in = true; - found = true; - break; - } else if (c->timeline_out > mouse_frame_lower && c->timeline_out < mouse_frame_upper) { - panel_timeline->trim_target = i; - panel_timeline->trim_in = false; - found = true; - break; + int nc = abs(c->timeline_in + 1 - panel_timeline->cursor_frame); + if (nc < closeness) { + panel_timeline->trim_target = i; + panel_timeline->trim_in = true; + closeness = nc; + found = true; + } + } + if (c->timeline_out > mouse_frame_lower && c->timeline_out < mouse_frame_upper) { + int nc = abs(c->timeline_out - 1 - panel_timeline->cursor_frame); + if (nc < closeness) { + panel_timeline->trim_target = i; + panel_timeline->trim_in = false; + closeness = nc; + found = true; + } } } - } + } if (found) { setCursor(Qt::SizeHorCursor); } else { @@ -765,6 +780,29 @@ void TimelineWidget::redraw_clips() { QRect clip_rect(panel_timeline->getScreenPointFromFrame(clip->timeline_in), getScreenPointFromTrack(clip->track), clip->getLength() * panel_timeline->zoom, track_height); clip_painter.fillRect(clip_rect, QColor(clip->color_r, clip->color_g, clip->color_b)); + + // draw thumbnail/waveform + if (clip->media_stream->preview_lock.tryLock()) { + if (clip->media_stream->preview_done) { + if (clip->track < 0) { + int thumb_y = clip_painter.fontMetrics().height()+CLIP_TEXT_PADDING+CLIP_TEXT_PADDING; + int thumb_height = clip_rect.height()-thumb_y; + if (thumb_height > thumb_y) { // at small clip heights, don't even draw it + QRect thumb_rect(clip_rect.x(), clip_rect.y()+thumb_y, (thumb_height*((float)clip->media_stream->preview.width()/(float)clip->media_stream->preview.height())), thumb_height); + clip_painter.drawImage(thumb_rect, clip->media_stream->preview); + } + } else { + long length = clip->media->get_length_in_frames(clip->sequence->frame_rate); + int waveform_x = ((float)clip->clip_in/(float)length) * clip->media_stream->preview.width(); + int waveform_width = (((float)clip->getLength()/(float)length) * clip->media_stream->preview.width()); + qDebug() << waveform_x << waveform_width; + QRect source(waveform_x, 0, waveform_width, clip->media_stream->preview.height()); + clip_painter.drawImage(clip_rect, clip->media_stream->preview, source); + } + } + clip->media_stream->preview_lock.unlock(); + } + clip_painter.setPen(Qt::white); clip_painter.drawLine(clip_rect.bottomLeft(), clip_rect.topLeft()); clip_painter.drawLine(clip_rect.topLeft(), clip_rect.topRight()); @@ -883,7 +921,7 @@ int TimelineWidget::getTrackFromScreenPoint(int y) { } int temp_track_height = track_height; if (show_track_lines) temp_track_height--; - return (int)floor((float) (y)/ (float) temp_track_height); + return (int)qFloor((float) (y)/ (float) temp_track_height); } int TimelineWidget::getScreenPointFromTrack(int track) { diff --git a/ui/timelinewidget.h b/ui/timelinewidget.h index 168bbb624..92cd55b5f 100644 --- a/ui/timelinewidget.h +++ b/ui/timelinewidget.h @@ -15,7 +15,7 @@ class TimelineWidget : public QWidget { Q_OBJECT public: - explicit TimelineWidget(QWidget *parent = nullptr); + explicit TimelineWidget(QWidget *parent = 0); bool bottom_align; @@ -24,6 +24,7 @@ protected: void paintEvent(QPaintEvent*) override; void resizeEvent(QResizeEvent*) override; + void mouseDoubleClickEvent(QMouseEvent *event); void mousePressEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; diff --git a/ui/viewercontainer.h b/ui/viewercontainer.h index 136d3c479..516d82bff 100644 --- a/ui/viewercontainer.h +++ b/ui/viewercontainer.h @@ -7,7 +7,7 @@ class ViewerContainer : public QWidget { Q_OBJECT public: - explicit ViewerContainer(QWidget *parent = nullptr); + explicit ViewerContainer(QWidget *parent = 0); float aspect_ratio; QWidget* child; void adjust(); diff --git a/ui/viewerwidget.cpp b/ui/viewerwidget.cpp index b3bf99388..d12324db8 100644 --- a/ui/viewerwidget.cpp +++ b/ui/viewerwidget.cpp @@ -35,13 +35,13 @@ void ViewerWidget::retry() { } void ViewerWidget::initializeGL() { - initializeOpenGLFunctions(); + initializeOpenGLFunctions(); - glClearColor(0, 0, 0, 1); - glMatrixMode(GL_PROJECTION); - glEnable(GL_TEXTURE_2D); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glClearColor(0, 0, 0, 1); + glMatrixMode(GL_PROJECTION); + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } //void ViewerWidget::resizeGL(int w, int h) @@ -54,63 +54,63 @@ void ViewerWidget::paintEvent(QPaintEvent *e) { void ViewerWidget::paintGL() { - if (multithreaded) retry_timer.stop(); + if (multithreaded) retry_timer.stop(); - glClear(GL_COLOR_BUFFER_BIT); + glClear(GL_COLOR_BUFFER_BIT); - long playhead = panel_timeline->playhead; + long playhead = panel_timeline->playhead; handle_media(sequence, playhead, multithreaded); - texture_failed = false; + texture_failed = false; bool render_audio = (panel_timeline->playing || force_audio); - cc_lock.lock(); + cc_lock.lock(); - for (int i=0;iopen) { - qDebug() << "[WARNING] Tried to display clip" << i << "but it's closed"; - texture_failed = true; - } else if (is_clip_active(c, playhead)) { - if (c->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { - // start preparing cache - get_clip_frame(c, playhead); + if (!c->open) { + qDebug() << "[WARNING] Tried to display clip" << i << "but it's closed"; + texture_failed = true; + } else if (is_clip_active(c, playhead)) { + if (c->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + // start preparing cache + get_clip_frame(c, playhead); - if (c->texture == NULL) { - qDebug() << "[WARNING] Texture hasn't been created yet"; - texture_failed = true; - } else if (playhead >= c->timeline_in) { - glLoadIdentity(); - int half_width = c->sequence->width/2; - int half_height = c->sequence->height/2; - glOrtho(-half_width, half_width, half_height,- half_height, -1, 1); + if (c->texture == NULL) { + qDebug() << "[WARNING] Texture hasn't been created yet"; + texture_failed = true; + } else if (playhead >= c->timeline_in) { + glLoadIdentity(); + int half_width = c->sequence->width/2; + int half_height = c->sequence->height/2; + glOrtho(-half_width, half_width, half_height,- half_height, -1, 1); int anchor_x = c->media_stream->video_width/2; int anchor_y = c->media_stream->video_height/2; - // perform all transform effects + // perform all transform effects for (int j=0;jeffects.size();j++) { c->effects.at(j)->process_gl(&anchor_x, &anchor_y); - } + } - int anchor_right = c->media_stream->video_width - anchor_x; - int anchor_bottom = c->media_stream->video_height - anchor_y; + int anchor_right = c->media_stream->video_width - anchor_x; + int anchor_bottom = c->media_stream->video_height - anchor_y; - c->texture->bind(); + c->texture->bind(); - glBegin(GL_QUADS); - glTexCoord2f(0.0, 0.0); - glVertex2f(-anchor_x, -anchor_y); - glTexCoord2f(1.0, 0.0); - glVertex2f(anchor_right, -anchor_y); - glTexCoord2f(1.0, 1.0); - glVertex2f(anchor_right, anchor_bottom); - glTexCoord2f(0.0, 1.0); - glVertex2f(-anchor_x, anchor_bottom); - glEnd(); + glBegin(GL_QUADS); + glTexCoord2f(0.0, 0.0); + glVertex2f(-anchor_x, -anchor_y); + glTexCoord2f(1.0, 0.0); + glVertex2f(anchor_right, -anchor_y); + glTexCoord2f(1.0, 1.0); + glVertex2f(anchor_right, anchor_bottom); + glTexCoord2f(0.0, 1.0); + glVertex2f(-anchor_x, anchor_bottom); + glEnd(); - c->texture->release(); + c->texture->release(); } } else if (render_audio && c->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && @@ -119,8 +119,8 @@ void ViewerWidget::paintGL() // clip is not caching, start caching audio c->lock.unlock(); cache_clip(c, playhead, false, false, c->reset_audio); - } - } + } + } } if (panel_timeline->playing) { @@ -137,13 +137,13 @@ void ViewerWidget::paintGL() } } - cc_lock.unlock(); + cc_lock.unlock(); - if (texture_failed) { - if (multithreaded) { - retry_timer.start(); - } else { - paintGL(); - } - } + if (texture_failed) { + if (multithreaded) { + retry_timer.start(); + } else { + paintGL(); + } + } } diff --git a/ui/viewerwidget.h b/ui/viewerwidget.h index 2fdaba4d8..079e6d397 100644 --- a/ui/viewerwidget.h +++ b/ui/viewerwidget.h @@ -14,15 +14,15 @@ class ViewerWidget : public QOpenGLWidget, public QOpenGLFunctions Q_OBJECT public: ViewerWidget(QWidget *parent = 0); - void initializeGL(); -// void resizeGL(int w, int h); - void paintGL(); bool multithreaded; bool force_audio; bool enable_paint; protected: void paintEvent(QPaintEvent *e) override; + void initializeGL() override; +// void resizeGL(int w, int h); + void paintGL() override; private: QTimer retry_timer; private slots: