diff --git a/dialogs/exportdialog.cpp b/dialogs/exportdialog.cpp index 5457138ad..d8ee35825 100644 --- a/dialogs/exportdialog.cpp +++ b/dialogs/exportdialog.cpp @@ -302,6 +302,16 @@ void ExportDialog::on_pushButton_2_clicked() close(); } +void ExportDialog::render_thread_finished() { + prep_ui_for_render(false); +} + +void ExportDialog::prep_ui_for_render(bool rendering) { + ui->pushButton->setEnabled(!rendering); + ui->pushButton_2->setEnabled(!rendering); + ui->renderCancel->setEnabled(rendering); +} + void ExportDialog::on_pushButton_clicked() { QString ext; @@ -399,15 +409,20 @@ void ExportDialog::on_pushButton_clicked() } QString filename = QFileDialog::getSaveFileName(this, "Export Media", "", format_strings[ui->formatCombobox->currentIndex()] + " (*." + ext + ")"); if (!filename.isEmpty()) { - ExportThread* et = new ExportThread(); + et = new ExportThread(); et->surface.create(); 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))); + panel_viewer->viewer_widget->enable_paint = false; panel_viewer->viewer_widget->context()->doneCurrent(); panel_viewer->viewer_widget->context()->moveToThread(et); + panel_viewer->viewer_widget->force_audio = true; + + prep_ui_for_render(true); et->filename = filename; et->video_enabled = ui->videoGroupbox->isChecked(); @@ -421,6 +436,8 @@ void ExportDialog::on_pushButton_clicked() et->audio_sampling_rate = 48000; et->audio_bitrate = ui->audiobitrateSpinbox->value(); + et->ed = this; + et->start(); } } @@ -435,3 +452,8 @@ void ExportDialog::set_defaults(Sequence* s) { void ExportDialog::update_progress_bar(int value) { ui->progressBar->setValue(value); } + +void ExportDialog::on_renderCancel_clicked() +{ + et->fail = true; +} diff --git a/dialogs/exportdialog.h b/dialogs/exportdialog.h index fcd0449db..db8241bee 100644 --- a/dialogs/exportdialog.h +++ b/dialogs/exportdialog.h @@ -8,6 +8,7 @@ class ExportDialog; } struct Sequence; +class ExportThread; class ExportDialog : public QDialog { @@ -27,12 +28,19 @@ private slots: void update_progress_bar(int value); + void on_renderCancel_clicked(); + + void render_thread_finished(); + private: Ui::ExportDialog *ui; QVector format_strings; QVector format_vcodecs; QVector format_acodecs; + + ExportThread* et; + void prep_ui_for_render(bool rendering); }; #endif // EXPORTDIALOG_H diff --git a/dialogs/exportdialog.ui b/dialogs/exportdialog.ui index 7bcc52716..ce1b53b70 100644 --- a/dialogs/exportdialog.ui +++ b/dialogs/exportdialog.ui @@ -172,14 +172,34 @@ - - - false - - - 0 - - + + + + + false + + + 0 + + + + + + + false + + + + 20 + 16777215 + + + + x + + + + diff --git a/effects/effects.cpp b/effects/effects.cpp index 594c308c6..3d3aaa72d 100644 --- a/effects/effects.cpp +++ b/effects/effects.cpp @@ -1,6 +1,9 @@ #include "effects/effects.h" +#include "project/clip.h" + #include +#include QVector video_effect_names; QVector audio_effect_names; @@ -14,3 +17,18 @@ void init_effects() { audio_effect_names[AUDIO_VOLUME_EFFECT] = "Volume"; audio_effect_names[AUDIO_PAN_EFFECT] = "Pan"; } + +Effect* create_effect(int effect_id, Clip* c) { + if (c->track < 0) { + switch (effect_id) { + case VIDEO_TRANSFORM_EFFECT: return new TransformEffect(c); + } + } else { + switch (effect_id) { + case AUDIO_VOLUME_EFFECT: return new VolumeEffect(c); + case AUDIO_PAN_EFFECT: return new PanEffect(c); + } + } + qDebug() << "[ERROR] Invalid effect ID"; + return NULL; +} diff --git a/effects/effects.h b/effects/effects.h index e061cba16..73a0b7a56 100644 --- a/effects/effects.h +++ b/effects/effects.h @@ -20,14 +20,16 @@ enum AudioEffects { extern QVector video_effect_names; extern QVector audio_effect_names; void init_effects(); +Effect* create_effect(int effect_id, Clip* c); // video effects class TransformEffect : public Effect { Q_OBJECT public: - TransformEffect(Clip* c); + TransformEffect(Clip* c); void process_gl(int* anchor_x, int* anchor_y); -private: + Effect* copy(); + QSpinBox* position_x; QSpinBox* position_y; QSpinBox* scale_x; @@ -44,17 +46,19 @@ public slots: // audio effects class VolumeEffect : public Effect { public: - VolumeEffect(Clip* c); - void process_audio(uint8_t* samples, int nb_bytes); -private: + VolumeEffect(Clip* c); + void process_audio(uint8_t* samples, int nb_bytes); + Effect* copy(); + QSpinBox* volume_val; }; class PanEffect : public Effect { public: - PanEffect(Clip* c); - void process_audio(uint8_t* samples, int nb_bytes); -private: + PanEffect(Clip* c); + void process_audio(uint8_t* samples, int nb_bytes); + Effect* copy(); + QSpinBox* pan_val; }; diff --git a/effects/paneffect.cpp b/effects/paneffect.cpp index c10e35a64..34ffe1f2b 100644 --- a/effects/paneffect.cpp +++ b/effects/paneffect.cpp @@ -28,6 +28,12 @@ PanEffect::PanEffect(Clip* c) : Effect(c) { pan_val->setValue(0); } +Effect* PanEffect::copy() { + PanEffect* p = new PanEffect(parent_clip); + p->pan_val->setValue(pan_val->value()); + return p; +} + void PanEffect::process_audio(uint8_t *samples, int nb_bytes) { for (int i=0;isetContents(ui); // set defaults - position_x->setValue(c->sequence->width/2); - position_y->setValue(c->sequence->height/2); + position_x->setValue(c->sequence->width/2); + position_y->setValue(c->sequence->height/2); scale_x->setValue(100); scale_y->setValue(100); scale_y->setEnabled(false); @@ -94,6 +94,20 @@ TransformEffect::TransformEffect(Clip* c) : Effect(c) { connect(uniform_scale_box, SIGNAL(toggled(bool)), this, SLOT(field_changed())); } +Effect* TransformEffect::copy() { + TransformEffect* t = new TransformEffect(parent_clip); + t->position_x->setValue(position_x->value()); + t->position_y->setValue(position_y->value()); + t->scale_x->setValue(scale_x->value()); + t->scale_y->setValue(scale_y->value()); + t->uniform_scale_box->setChecked(uniform_scale_box->isChecked()); + t->rotation->setValue(rotation->value()); + t->anchor_x_box->setValue(anchor_x_box->value()); + t->anchor_y_box->setValue(anchor_y_box->value()); + t->opacity->setValue(opacity->value()); + return t; +} + void TransformEffect::toggle_uniform_scale(bool enabled) { scale_y->setEnabled(!enabled); } diff --git a/effects/volumeeffect.cpp b/effects/volumeeffect.cpp index b29de4884..0650d55a1 100644 --- a/effects/volumeeffect.cpp +++ b/effects/volumeeffect.cpp @@ -28,6 +28,12 @@ VolumeEffect::VolumeEffect(Clip* c) : Effect(c) { volume_val->setValue(100); } +Effect* VolumeEffect::copy() { + VolumeEffect* v = new VolumeEffect(parent_clip); + v->volume_val->setValue(volume_val->value()); + return v; +} + void VolumeEffect::process_audio(uint8_t *samples, int nb_bytes) { for (int i=0;i @@ -72,17 +74,21 @@ void ExportThread::run() { AVCodec* vcodec; AVCodecContext* vcodec_ctx; AVFrame* video_frame; + AVFrame* sws_frame; SwsContext* sws_ctx = NULL; -// AVStream* audio_stream; -// AVCodec* acodec; -// AVFrame* audio_frame; -// AVCodecContext* acodec_ctx; + uint8_t* flip_buffer; + AVStream* audio_stream; + AVCodec* acodec; + AVFrame* audio_frame; + AVFrame* swr_frame; + AVCodecContext* acodec_ctx; AVPacket video_pkt; -// AVPacket audio_pkt; -// SwrContext* swr_ctx = NULL; + AVPacket audio_pkt; + SwrContext* swr_ctx = NULL; + int aframe_bytes; int ret; - bool fail = false; + fail = false; if (video_enabled) { // initialize array contain opengl data @@ -150,6 +156,13 @@ void ExportThread::run() { NULL, NULL ); + + sws_frame = av_frame_alloc(); + sws_frame->format = AV_PIX_FMT_YUV420P; + sws_frame->width = video_frame->width; + sws_frame->height = video_frame->height; + av_frame_get_buffer(sws_frame, 0); + flip_buffer = new uint8_t[video_frame->width * video_frame->height * 4]; } } } @@ -157,80 +170,90 @@ void ExportThread::run() { } } -// if (audio_enabled && !fail) { -// audio_stream = avformat_new_stream(fmt_ctx, NULL); -// if (!audio_stream) { -// qDebug() << "[ERROR] Could not allocate output streams"; -// fail = true; -// } else { -// acodec = avcodec_find_encoder((enum AVCodecID) audio_codec); -// if (!acodec) { -// qDebug() << "[ERROR] Could not find audio encoder"; -// fail = true; -// } else { -// acodec_ctx = avcodec_alloc_context3(acodec); -// if (!acodec_ctx) { -// qDebug() << "[ERROR] Could not find allocate audio encoding context"; -// fail = true; -// } else { -// acodec_ctx->sample_rate = audio_sampling_rate; -// acodec_ctx->channel_layout = AV_CH_LAYOUT_STEREO; -// acodec_ctx->channels = av_get_channel_layout_nb_channels(acodec_ctx->channel_layout); -// acodec_ctx->sample_fmt = acodec->sample_fmts[0]; -// acodec_ctx->time_base = {1, audio_sampling_rate}; -// acodec_ctx->bit_rate = audio_bitrate * 1000; + if (audio_enabled && !fail) { + audio_stream = avformat_new_stream(fmt_ctx, NULL); + if (!audio_stream) { + qDebug() << "[ERROR] Could not allocate output streams"; + fail = true; + } else { + acodec = avcodec_find_encoder((enum AVCodecID) audio_codec); + if (!acodec) { + qDebug() << "[ERROR] Could not find audio encoder"; + fail = true; + } else { + acodec_ctx = avcodec_alloc_context3(acodec); + if (!acodec_ctx) { + qDebug() << "[ERROR] Could not find allocate audio encoding context"; + fail = true; + } else { + acodec_ctx->sample_rate = audio_sampling_rate; + acodec_ctx->channel_layout = AV_CH_LAYOUT_STEREO; // change this to support surround/mono sound in the future (this is what the user sets the output audio to) + acodec_ctx->channels = av_get_channel_layout_nb_channels(acodec_ctx->channel_layout); + acodec_ctx->sample_fmt = acodec->sample_fmts[0]; + acodec_ctx->time_base = {1, audio_sampling_rate}; + acodec_ctx->bit_rate = audio_bitrate * 1000; -// ret = avcodec_open2(acodec_ctx, acodec, NULL); -// if (ret < 0) { -// qDebug() << "[ERROR] Could not open output audio encoder." << ret; -// fail = true; -// } else { -// ret = avcodec_parameters_from_context(audio_stream->codecpar, acodec_ctx); -// if (ret < 0) { -// qDebug() << "[ERROR] Could not copy audio encoder parameters to output stream." << ret; -// fail = true; -// } else { -// if (fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) { -// acodec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; -// } + ret = avcodec_open2(acodec_ctx, acodec, NULL); + if (ret < 0) { + qDebug() << "[ERROR] Could not open output audio encoder." << ret; + fail = true; + } else { + ret = avcodec_parameters_from_context(audio_stream->codecpar, acodec_ctx); + if (ret < 0) { + qDebug() << "[ERROR] Could not copy audio encoder parameters to output stream." << ret; + fail = true; + } else { + if (fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) { + acodec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + } -// audio_stream->time_base = acodec_ctx->time_base; + audio_stream->time_base = acodec_ctx->time_base; -// // init audio resampler context -// swr_ctx = swr_alloc_set_opts( -// NULL, -// acodec_ctx->channel_layout, -// acodec_ctx->sample_fmt, -// acodec_ctx->sample_rate, -// sequence->audio_layout, -// AV_SAMPLE_FMT_S16, -// sequence->audio_frequency, -// 0, -// NULL -// ); -// swr_init(swr_ctx); + // init audio resampler context + swr_ctx = swr_alloc_set_opts( + NULL, + acodec_ctx->channel_layout, + acodec_ctx->sample_fmt, + acodec_ctx->sample_rate, + sequence->audio_layout, + AV_SAMPLE_FMT_S16, + sequence->audio_frequency, + 0, + NULL + ); + swr_init(swr_ctx); -// // initialize raw audio frame -// audio_frame = av_frame_alloc(); -// audio_frame->nb_samples = acodec_ctx->frame_size; -// if (audio_frame->nb_samples == 0) audio_frame->nb_samples = 2048; // should probably be way smaller? -// audio_frame->format = acodec_ctx->sample_fmt; -// audio_frame->channels = acodec_ctx->channels; -// audio_frame->channel_layout = acodec_ctx->channel_layout; -// ret = av_frame_get_buffer(audio_frame, 0); -// if (ret < 0) { -// qDebug() << "[ERROR] Could not allocate audio buffer." << ret; -// } -// // av_samples_alloc(&audio_frame->data[0], NULL, av_get_channel_layout_nb_channels(audio_frame->channel_layout), audio_frame->nb_samples, acodec_ctx->sample_fmt, 0); -// av_frame_make_writable(audio_frame); + // initialize raw audio frame + audio_frame = av_frame_alloc(); + audio_frame->sample_rate = sequence->audio_frequency; + audio_frame->nb_samples = acodec_ctx->frame_size; + if (audio_frame->nb_samples == 0) audio_frame->nb_samples = 2048; // should possibly be smaller? + audio_frame->format = AV_SAMPLE_FMT_S16; + audio_frame->channel_layout = AV_CH_LAYOUT_STEREO; // change this to support surround/mono sound in the future (this is whatever format they're held in the internal buffer) + audio_frame->channels = av_get_channel_layout_nb_channels(audio_frame->channel_layout); + ret = av_frame_get_buffer(audio_frame, 0); + if (ret < 0) { + qDebug() << "[ERROR] Could not allocate audio buffer." << ret; + } + // av_samples_alloc(&audio_frame->data[0], NULL, av_get_channel_layout_nb_channels(audio_frame->channel_layout), audio_frame->nb_samples, acodec_ctx->sample_fmt, 0); + av_frame_make_writable(audio_frame); + aframe_bytes = av_samples_get_buffer_size(NULL, audio_frame->channels, audio_frame->nb_samples, static_cast(audio_frame->format), 0); -// av_init_packet(&audio_pkt); -// } -// } -// } -// } -// } -// } + // init converted audio frame + swr_frame = av_frame_alloc(); + swr_frame->channel_layout = acodec_ctx->channel_layout; + swr_frame->channels = acodec_ctx->channels; + swr_frame->sample_rate = acodec_ctx->sample_rate; + swr_frame->format = acodec_ctx->sample_fmt; + av_frame_make_writable(swr_frame); + + av_init_packet(&audio_pkt); + } + } + } + } + } + } if (!fail) { av_dump_format(fmt_ctx, 0, c_filename, 1); @@ -257,14 +280,10 @@ void ExportThread::run() { fbo.bind(); QOpenGLPaintDevice fbo_dev(video_width, video_height); QPainter painter(&fbo_dev); - painter.beginNativePainting(); + painter.beginNativePainting(); - AVFrame* sws_frame = av_frame_alloc(); - sws_frame->format = AV_PIX_FMT_YUV420P; - sws_frame->width = video_frame->width; - sws_frame->height = video_frame->height; - av_frame_get_buffer(sws_frame, 0); - uint8_t* flip_buffer = new uint8_t[video_frame->width * video_frame->height * 4]; + int written_frame_audio_bytes = 0; + long file_audio_samples = 0; while (panel_timeline->playhead < end && !fail) { panel_viewer->viewer_widget->paintGL(); @@ -289,6 +308,27 @@ void ExportThread::run() { // send to encoder if (!encode(fmt_ctx, vcodec_ctx, sws_frame, &video_pkt, video_stream)) fail = true; } + if (audio_enabled && !fail) { + // do we need to encode more audio samples? + if (file_audio_samples <= (timecode_secs*audio_sampling_rate)) { + uint8_t* cache = (reading_audio_cache_A) ? audio_cache_A : audio_cache_B; + int copylen = qMin(aframe_bytes-written_frame_audio_bytes, audio_cache_size-audio_bytes_written); + memcpy(audio_frame->data[0]+written_frame_audio_bytes, cache+audio_bytes_written, copylen); + written_frame_audio_bytes += copylen; + audio_bytes_written += copylen; + if (written_frame_audio_bytes == aframe_bytes) { + // convert to export sample format + swr_convert_frame(swr_ctx, swr_frame, audio_frame); + swr_frame->pts = file_audio_samples; + + // send to encoder + if (!encode(fmt_ctx, acodec_ctx, swr_frame, &audio_pkt, audio_stream)) fail = true; + + file_audio_samples += swr_frame->nb_samples; + written_frame_audio_bytes = 0; + } + } + } emit progress_changed(((float) panel_timeline->playhead / (float) end) * 100); panel_timeline->playhead++; } @@ -297,16 +337,14 @@ void ExportThread::run() { painter.endNativePainting(); fbo.release(); - av_frame_free(&sws_frame); - if (!fail) { // flush remaining packets if (video_enabled) { if (!encode(fmt_ctx, vcodec_ctx, NULL, &video_pkt, video_stream)) fail = true; } - // if (audio_enabled) { - // if (!encode(fmt_ctx, acodec_ctx, NULL, &audio_pkt, audio_stream)) return false; - // } + if (audio_enabled) { + if (!encode(fmt_ctx, acodec_ctx, NULL, &audio_pkt, audio_stream)) fail = true; + } } if (!fail) { @@ -321,16 +359,24 @@ void ExportThread::run() { } av_packet_unref(&video_pkt); + av_packet_unref(&audio_pkt); av_frame_free(&video_frame); + av_frame_free(&audio_frame); avcodec_free_context(&vcodec_ctx); -// avcodec_free_context(&acodec_ctx); + avcodec_free_context(&acodec_ctx); avio_closep(&fmt_ctx->pb); avformat_free_context(fmt_ctx); // qDebug() << "Render took: %ih %im %is\n", render_time/3600000, render_time/60000, render_time/1000; - if (sws_ctx != NULL) sws_freeContext(sws_ctx); -// if (swr_ctx != NULL) swr_free(&swr_ctx); + if (sws_ctx != NULL) { + sws_freeContext(sws_ctx); + av_frame_free(&sws_frame); + } + if (swr_ctx != NULL) { + swr_free(&swr_ctx); + av_frame_free(&swr_frame); + } } } @@ -339,4 +385,6 @@ void ExportThread::run() { panel_viewer->viewer_widget->context()->doneCurrent(); panel_viewer->viewer_widget->context()->moveToThread(qApp->thread()); panel_viewer->viewer_widget->multithreaded = true; + panel_viewer->viewer_widget->force_audio = false; + panel_viewer->viewer_widget->enable_paint = true; } diff --git a/io/exportthread.h b/io/exportthread.h index c0d76365b..32692e831 100644 --- a/io/exportthread.h +++ b/io/exportthread.h @@ -4,6 +4,8 @@ #include #include +class ExportDialog; + class ExportThread : public QThread { Q_OBJECT public: @@ -23,8 +25,12 @@ public: int audio_bitrate; QOffscreenSurface surface; + + ExportDialog* ed; + + bool fail; signals: - void progress_changed(int value); + void progress_changed(int value); }; #endif // EXPORTTHREAD_H diff --git a/mainwindow.cpp b/mainwindow.cpp index 7c7e19c37..4d6234e4b 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -36,7 +36,7 @@ MainWindow::MainWindow(QWidget *parent) : { // set up style? - qApp->setStyle(QStyleFactory::create("Fusion")); + qApp->setStyle(QStyleFactory::create("Fusion")); QPalette darkPalette; darkPalette.setColor(QPalette::Window, QColor(53,53,53)); @@ -54,9 +54,7 @@ MainWindow::MainWindow(QWidget *parent) : darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218)); darkPalette.setColor(QPalette::HighlightedText, Qt::black); - - - qApp->setPalette(darkPalette); + qApp->setPalette(darkPalette); // end style diff --git a/olive.pro.user b/olive.pro.user index f6349a675..de193c163 100644 --- a/olive.pro.user +++ b/olive.pro.user @@ -1,6 +1,6 @@ - + EnvironmentId diff --git a/panels/effectcontrols.cpp b/panels/effectcontrols.cpp index ac5134bff..503a150ac 100644 --- a/panels/effectcontrols.cpp +++ b/panels/effectcontrols.cpp @@ -21,6 +21,27 @@ EffectControls::EffectControls(QWidget *parent) : 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*))); +} + +void EffectControls::menu_select(QAction* q) { + clip->effects.append(create_effect(q->data().toInt(), clip)); + set_clip(clip); } EffectControls::~EffectControls() @@ -30,29 +51,21 @@ EffectControls::~EffectControls() void EffectControls::on_pushButton_clicked() { - QMenu effects_menu(this); - for (int i=0;iexec(QCursor::pos()); } void EffectControls::set_clip(Clip* c) { // clear ui->scrollAreaWidgetContents if (clip != NULL) { for (int i=0;ieffects.size();i++) { - clip->effects.at(i)->container->setParent(NULL); + clip->effects.at(i)->container->setParent(NULL); } } ui->pushButton->setEnabled(c != NULL); if (c != NULL) { for (int i=0;ieffects.size();i++) { - static_cast(ui->scrollAreaWidgetContents->layout())->insertWidget(i, c->effects.at(i)->container); + static_cast(ui->scrollAreaWidgetContents->layout())->insertWidget(i, c->effects.at(i)->container); } } clip = c; diff --git a/panels/effectcontrols.h b/panels/effectcontrols.h index c536a8144..a84750d47 100644 --- a/panels/effectcontrols.h +++ b/panels/effectcontrols.h @@ -4,6 +4,7 @@ #include struct Clip; +class QMenu; namespace Ui { class EffectControls; @@ -20,10 +21,12 @@ public: private slots: void on_pushButton_clicked(); + void menu_select(QAction* q); private: Ui::EffectControls *ui; Clip* clip; + QMenu* effects_menu; }; #endif // EFFECTCONTROLS_H diff --git a/panels/timeline.cpp b/panels/timeline.cpp index 67c15e76b..bee7a2ab3 100644 --- a/panels/timeline.cpp +++ b/panels/timeline.cpp @@ -71,6 +71,7 @@ void Timeline::seek(long p) { c.frame_sample_index = 0; } clear_cache(true, true); + audio_bytes_written = 0; switch_audio_cache = true; reading_audio_cache_A = false; @@ -146,7 +147,7 @@ void Timeline::repaint_timeline() { } ui->video_area->update(); ui->audio_area->update(); - if (last_frame != playhead) { + if (last_frame != playhead) { panel_viewer->viewer_widget->update(); last_frame = playhead; } @@ -319,3 +320,10 @@ void Timeline::on_toolSlipButton_toggled(bool checked) tool = TIMELINE_TOOL_SLIP; } } + +void Timeline::on_snappingButton_toggled(bool checked) +{ + snapping = checked; + + qDebug() << "clips vector size:" << sizeof(Clip) * sequence->clip_count(); +} diff --git a/panels/timeline.h b/panels/timeline.h index dbe8ff40b..76a7124c6 100644 --- a/panels/timeline.h +++ b/panels/timeline.h @@ -137,6 +137,8 @@ private slots: void on_toolSlipButton_toggled(bool checked); + void on_snappingButton_toggled(bool checked); + private: Ui::Timeline *ui; QVector tool_buttons; diff --git a/playback/audio.cpp b/playback/audio.cpp index 963df89ff..33fc9a820 100644 --- a/playback/audio.cpp +++ b/playback/audio.cpp @@ -18,6 +18,7 @@ uint8_t* audio_cache_B = NULL; int audio_cache_size = 0; bool switch_audio_cache = true; bool reading_audio_cache_A = false; +int audio_bytes_written = 0; void init_audio(Sequence* s) { if (audio_cache_A != NULL) { diff --git a/playback/audio.h b/playback/audio.h index 812666cb8..7296872a9 100644 --- a/playback/audio.h +++ b/playback/audio.h @@ -11,6 +11,7 @@ extern QIODevice* audio_io_device; extern uint8_t* audio_cache_A; extern uint8_t* audio_cache_B; extern int audio_cache_size; +extern int audio_bytes_written; extern bool switch_audio_cache; extern bool reading_audio_cache_A; void init_audio(Sequence* s); diff --git a/playback/cacher.cpp b/playback/cacher.cpp index a342cabc8..866fe1a87 100644 --- a/playback/cacher.cpp +++ b/playback/cacher.cpp @@ -34,7 +34,7 @@ void cache_audio_worker(Clip* c, bool write_A) { int limit = std::min(c->frame_sample_index + audio_cache_size - bytes_written, nb_bytes); // perform all audio effects for (unsigned int j=0;jeffects.size();j++) { - c->effects[j]->process_audio(frame->data[0], limit); + c->effects.at(j)->process_audio(frame->data[0], limit); } // mix audio into cache for (int i=c->frame_sample_index;icopy()); + } } void Clip::init() { @@ -68,9 +72,9 @@ Clip::~Clip() { open_lock.lock(); open_lock.unlock(); - for (int i=0;i effects; + QList effects; // QVector linkedClips; // media handling diff --git a/project/effect.cpp b/project/effect.cpp index 25c222020..cacad5fee 100644 --- a/project/effect.cpp +++ b/project/effect.cpp @@ -14,5 +14,12 @@ void Effect::field_changed() { panel_viewer->viewer_widget->update(); } +Effect* Effect::copy() { + Effect* e = new Effect(parent_clip); + name = e->name; + type = e->type; + return e; +} + void Effect::process_gl(int* anchor_x, int* anchor_y) {} void Effect::process_audio(uint8_t* samples, int nb_bytes) {} diff --git a/project/effect.h b/project/effect.h index 443ac5591..d1c99b746 100644 --- a/project/effect.h +++ b/project/effect.h @@ -14,12 +14,14 @@ class Effect : public QObject { Q_OBJECT public: - Effect(Clip* c); + Effect(Clip* c); // default constructor int type; QString name; CollapsibleWidget* container; QWidget* ui; - Clip* parent_clip; + Clip* parent_clip; + + virtual Effect* copy(); virtual void process_gl(int* anchor_x, int* anchor_y); virtual void process_audio(uint8_t* samples, int nb_bytes); diff --git a/ui/timelinewidget.cpp b/ui/timelinewidget.cpp index 6b0a820f9..03ece55ad 100644 --- a/ui/timelinewidget.cpp +++ b/ui/timelinewidget.cpp @@ -109,16 +109,17 @@ void TimelineWidget::dropEvent(QDropEvent* event) { if (c.track < 0) { // add default video effects - c.effects.append(new TransformEffect(&c)); + c.effects.append(create_effect(VIDEO_TRANSFORM_EFFECT, &c)); } else { // add default audio effects - c.effects.append(new VolumeEffect(&c)); - c.effects.append(new PanEffect(&c)); + c.effects.append(create_effect(AUDIO_VOLUME_EFFECT, &c)); + c.effects.append(create_effect(AUDIO_PAN_EFFECT, &c)); } } panel_timeline->ghosts.clear(); panel_timeline->importing = false; + panel_timeline->snapped = false; panel_timeline->redraw_all_clips(); } @@ -303,13 +304,46 @@ void TimelineWidget::init_ghosts() { } } +bool subvalidate_snapping(Ghost& g, long* frame_diff, long snap_point) { + int snap_range = 15; + long in_validator = g.old_in + *frame_diff - snap_point; + long out_validator = g.old_out + *frame_diff - snap_point; + + if (in_validator > -snap_range && in_validator < snap_range) { + *frame_diff -= in_validator; + panel_timeline->snap_point = snap_point; + panel_timeline->snapped = true; + return true; + } else if (out_validator > -snap_range && out_validator < snap_range) { + *frame_diff -= out_validator; + panel_timeline->snap_point = snap_point; + panel_timeline->snapped = true; + return true; + } + return false; +} + +void validate_snapping(Ghost& g, long* frame_diff) { + long validator; + panel_timeline->snapped = false; + if (panel_timeline->snapping) { + if (!subvalidate_snapping(g, frame_diff, panel_timeline->playhead)) { + for (int j=0;jsequence->clip_count();j++) { + Clip& c = panel_timeline->sequence->get_clip(j); + if (!subvalidate_snapping(g, frame_diff, c.timeline_in)) { + subvalidate_snapping(g, frame_diff, c.timeline_out); + } + if (panel_timeline->snapped) break; + } + } + } +} + void TimelineWidget::update_ghosts(QPoint& mouse_pos) { int mouse_track = getTrackFromScreenPoint(mouse_pos.y()); long frame_diff = getFrameFromScreenPoint(mouse_pos.x(), false) - panel_timeline->drag_frame_start; int track_diff = mouse_track - panel_timeline->drag_track_start; - int snap_range = 15; - long validator; if (panel_timeline->trim_target > -1) { // trim ops @@ -378,6 +412,8 @@ void TimelineWidget::update_ghosts(QPoint& mouse_pos) { } } } + + validate_snapping(g, &frame_diff); } // resize ghosts @@ -422,46 +458,7 @@ void TimelineWidget::update_ghosts(QPoint& mouse_pos) { } } - // shitty hacky code but works wonders - panel_timeline->snapped = false; - if (panel_timeline->snapping) { - for (int j=0;jsequence->clip_count();j++) { - Clip& c = panel_timeline->sequence->get_clip(j); - - panel_timeline->snapped = false; - for (int k=0;k<4;k++) { - switch (k) { - case 0: - // snap in to timeline_in - validator = g.old_in + frame_diff - c.timeline_in; - panel_timeline->snap_point = c.timeline_in; - break; - case 1: - // snap in to timeline out - validator = g.old_in + frame_diff - c.timeline_out; - panel_timeline->snap_point = c.timeline_out; - break; - case 2: - // snap out to timeline_in - validator = g.old_out + frame_diff - c.timeline_in; - panel_timeline->snap_point = c.timeline_in; - break; - case 3: - // snap out to timeline_out - validator = g.old_out + frame_diff - c.timeline_out; - panel_timeline->snap_point = c.timeline_out; - break; - } - - if (validator > -snap_range && validator < snap_range) { - frame_diff -= validator; - panel_timeline->snapped = true; - break; - } - } - if (panel_timeline->snapped) break; - } - } + validate_snapping(g, &frame_diff); } // move ghosts @@ -756,6 +753,15 @@ void TimelineWidget::paintEvent(QPaintEvent*) { } } + // Draw playhead + p.setPen(Qt::red); + int playhead_x = getScreenPointFromFrame(panel_timeline->playhead); + p.drawLine(playhead_x, rect().top(), playhead_x, rect().bottom()); + + p.setPen(QColor(0, 0, 0, 64)); + int edge_y = (bottom_align) ? rect().height()-1 : 0; + p.drawLine(0, edge_y, rect().width(), edge_y); + // draw snap point if (panel_timeline->snapping && panel_timeline->snapped) { p.setPen(Qt::white); @@ -774,16 +780,7 @@ void TimelineWidget::paintEvent(QPaintEvent*) { p.setPen(Qt::gray); p.drawLine(cursor_x, cursor_y, cursor_x, cursor_y + track_height); } - } - - // Draw playhead - p.setPen(Qt::red); - int playhead_x = getScreenPointFromFrame(panel_timeline->playhead); - p.drawLine(playhead_x, rect().top(), playhead_x, rect().bottom()); - - p.setPen(QColor(0, 0, 0, 64)); - int edge_y = (bottom_align) ? rect().height()-1 : 0; - p.drawLine(0, edge_y, rect().width(), edge_y); + } } } diff --git a/ui/viewerwidget.cpp b/ui/viewerwidget.cpp index 5ea1f6d87..f894a0847 100644 --- a/ui/viewerwidget.cpp +++ b/ui/viewerwidget.cpp @@ -18,6 +18,10 @@ extern "C" { ViewerWidget::ViewerWidget(QWidget *parent) : QOpenGLWidget(parent) { multithreaded = true; + enable_paint = true; + force_audio = false; + + audio_bytes_written = 0; QSurfaceFormat format; format.setDepthBufferSize(24); @@ -46,7 +50,9 @@ void ViewerWidget::initializeGL() { //{ //} -int audio_bytes_written = 0; +void ViewerWidget::paintEvent(QPaintEvent *e) { + if (enable_paint) QOpenGLWidget::paintEvent(e); +} void ViewerWidget::paintGL() { @@ -59,14 +65,19 @@ void ViewerWidget::paintGL() handle_media(panel_viewer->sequence, playhead, multithreaded); texture_failed = false; - bool render_audio = (panel_timeline->playing); + bool render_audio = (panel_timeline->playing || force_audio); - if (render_audio && switch_audio_cache) { - reading_audio_cache_A = !reading_audio_cache_A; - audio_bytes_written = 0; - clear_cache(!reading_audio_cache_A, reading_audio_cache_A); + // switch_cache + if (render_audio && audio_bytes_written == audio_cache_size) { + switch_audio_cache = true; + + reading_audio_cache_A = !reading_audio_cache_A; + audio_bytes_written = 0; + clear_cache(!reading_audio_cache_A, reading_audio_cache_A); } + qDebug() << audio_bytes_written << "/" << audio_cache_size; + cc_lock.lock(); for (int i=0;ieffects.size();j++) { - c->effects.at(j)->process_gl(&anchor_x, &anchor_y); + c->effects.at(j)->process_gl(&anchor_x, &anchor_y); } int anchor_right = c->media_stream->video_width - anchor_x; @@ -118,7 +129,7 @@ void ViewerWidget::paintGL() render_audio && !c->reached_end && (switch_audio_cache || c->reset_audio)) { - // TODO doesn't new clips appearing in the timeline (they'll ALWAYS end up in the other cache with a 1/8 sec delay) + // TODO doesn't affect new clips appearing in the timeline (they'll ALWAYS end up in the other cache with a 1/8 sec delay) // cache audio if (c->lock.tryLock()) { @@ -136,13 +147,8 @@ void ViewerWidget::paintGL() if (audio_cache_A != NULL && audio_bytes_written < audio_cache_size) { // send cached/buffered audio to QIODevice/QAudioOutput uint8_t* cache = reading_audio_cache_A ? audio_cache_A : audio_cache_B; - audio_bytes_written += audio_io_device->write((const char*) cache+audio_bytes_written, audio_cache_size-audio_bytes_written); - } - - if (audio_bytes_written == audio_cache_size) { - // switch_cache - switch_audio_cache = true; - } + if (panel_timeline->playing) audio_bytes_written += audio_io_device->write((const char*) cache+audio_bytes_written, audio_cache_size-audio_bytes_written); + } } cc_lock.unlock(); diff --git a/ui/viewerwidget.h b/ui/viewerwidget.h index 33000d84c..2fdaba4d8 100644 --- a/ui/viewerwidget.h +++ b/ui/viewerwidget.h @@ -18,7 +18,11 @@ public: // void resizeGL(int w, int h); void paintGL(); - bool multithreaded; + bool multithreaded; + bool force_audio; + bool enable_paint; +protected: + void paintEvent(QPaintEvent *e) override; private: QTimer retry_timer; private slots: