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/dialogs/exportdialog.cpp b/dialogs/exportdialog.cpp index b8fec27b4..20b010913 100644 --- a/dialogs/exportdialog.cpp +++ b/dialogs/exportdialog.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "debug.h" #include "panels/panels.h" @@ -312,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(); } @@ -324,6 +324,8 @@ 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(); } void ExportDialog::prep_ui_for_render(bool r) { @@ -474,7 +476,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,11 +519,18 @@ 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() { + panel_sequence_viewer->viewer_widget->force_quit = true; et->continueEncode = false; cancelled = true; } @@ -552,9 +561,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/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/dialogs/loaddialog.cpp b/dialogs/loaddialog.cpp index 47a641810..706b1193a 100644 --- a/dialogs/loaddialog.cpp +++ b/dialogs/loaddialog.cpp @@ -6,25 +6,55 @@ #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" +#include "mainwindow.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); + bar->setValue(0); layout->addWidget(bar); - QPushButton* cancel_button = new QPushButton("Cancel"); - connect(cancel_button, SIGNAL(clicked(bool)), this, SLOT(reject())); + cancel_button = new QPushButton("Cancel"); + connect(cancel_button, SIGNAL(clicked(bool)), this, SLOT(cancel())); - QHBoxLayout* hboxLayout = new QHBoxLayout(); + hboxLayout = new QHBoxLayout(); hboxLayout->addStretch(); hboxLayout->addWidget(cancel_button); hboxLayout->addStretch(); layout->addLayout(hboxLayout); + + update(); + + 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(); +} + +void LoadDialog::cancel() { + lt->cancel(); + lt->wait(); + die(); +} + +void LoadDialog::die() { + mainWindow->new_project(); + reject(); +} + +void LoadDialog::thread_done() { + accept(); } diff --git a/dialogs/loaddialog.h b/dialogs/loaddialog.h index 7f6b0a77f..736acfaef 100644 --- a/dialogs/loaddialog.h +++ b/dialogs/loaddialog.h @@ -4,13 +4,26 @@ #include class QProgressBar; +struct Sequence; +class Media; +struct Footage; +class QHBoxLayout; +class LoadThread; class LoadDialog : public QDialog { + Q_OBJECT public: - LoadDialog(QWidget* parent = 0); + LoadDialog(QWidget* parent, bool autorecovery); +private slots: + void cancel(); + void die(); + void thread_done(); private: QProgressBar* bar; + QPushButton* cancel_button; + QHBoxLayout* hboxLayout; + LoadThread* lt; }; #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/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/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/dialogs/stabilizerdialog.cpp b/dialogs/stabilizerdialog.cpp new file mode 100644 index 000000000..5e0738d80 --- /dev/null +++ b/dialogs/stabilizerdialog.cpp @@ -0,0 +1,103 @@ +#include "stabilizerdialog.h" + +#include +#include +#include +#include +#include + +#include "ui/labelslider.h" + +StabilizerDialog::StabilizerDialog(QWidget *parent) : QDialog(parent) { + setWindowTitle("Stabilizer"); + + layout = new QVBoxLayout(this); + setLayout(layout); + + enable_stab = new QCheckBox(this); + 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 new file mode 100644 index 000000000..9e5a5a339 --- /dev/null +++ b/dialogs/stabilizerdialog.h @@ -0,0 +1,37 @@ +#ifndef STABILIZERDIALOG_H +#define STABILIZERDIALOG_H + +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/effects/boxblur.frag b/effects/boxblur.frag index 2ecbd56a4..626cc1f7e 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; @@ -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 13bee421c..e04d350ed 100644 --- a/effects/directionalblur.frag +++ b/effects/directionalblur.frag @@ -11,16 +11,19 @@ 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) { + 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 ac90c0563..b5dd4e8c9 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; @@ -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/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 b83aef23f..42b54497c 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" @@ -68,44 +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_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))); @@ -137,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/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/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); } diff --git a/icons/error.png b/icons/error.png new file mode 100644 index 000000000..34eb2de38 Binary files /dev/null and b/icons/error.png differ 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/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/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/config.cpp b/io/config.cpp index 02ab90cdf..bd0882f68 100644 --- a/io/config.cpp +++ b/io/config.cpp @@ -29,7 +29,10 @@ 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), + fast_seeking(false), + hover_focus(false) {} void Config::load(QString path) { @@ -103,6 +106,15 @@ 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(); + } else if (stream.name() == "FastSeeking") { + stream.readNext(); + fast_seeking = (stream.text() == "1"); + } else if (stream.name() == "HoverFocus") { + stream.readNext(); + hover_focus = (stream.text() == "1"); } } } @@ -148,6 +160,9 @@ 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.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 ea0745650..4092204ff 100644 --- a/io/config.h +++ b/io/config.h @@ -41,6 +41,9 @@ struct Config { bool enable_audio_scrubbing; bool drop_on_media_to_replace; int autoscroll; + int audio_rate; + bool fast_seeking; + bool hover_focus; void load(QString path); void save(QString path); diff --git a/io/exportthread.cpp b/io/exportthread.cpp index 4024214db..52e6e6999 100644 --- a/io/exportthread.cpp +++ b/io/exportthread.cpp @@ -122,13 +122,14 @@ 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); + av_opt_set(vcodec_ctx->priv_data, "x264opts", "opencl", AV_OPT_SEARCH_CHILDREN); 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; } } @@ -316,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()); @@ -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(); diff --git a/io/loadthread.cpp b/io/loadthread.cpp new file mode 100644 index 000000000..5fa67ac85 --- /dev/null +++ b/io/loadthread.cpp @@ -0,0 +1,734 @@ +#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 "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), 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_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))); +} + +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(); + + int type; + if (tag == "opening") { + type = TA_OPENING_TRANSITION; + } else if (tag == "closing") { + type = TA_CLOSING_TRANSITION; + } else { + 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()); + + 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() && !cancelled) { + read_next_start_element(stream); + 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 (!cancelled && !(stream.name() == root_search && stream.isEndElement())) { + read_next(stream); + 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); + + project_model.appendChild(find_loaded_folder_by_id(folder), 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 (!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;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 (!cancelled && !(stream.name() == "clip" && stream.isEndElement()) && !stream.atEnd()) { + read_next(stream); + if (stream.isStartElement()) { + if (stream.name() == "linked") { + while (!cancelled && !(stream.name() == "linked" && stream.isEndElement()) && !stream.atEnd()) { + read_next(stream); + if (stream.name() == "link" && stream.isStartElement()) { + for (int k=0;klinked.append(link_attr.value().toInt()); + break; + } + } + } + } + if (cancelled) return false; + } else if (stream.isStartElement() && (stream.name() == "effect" || stream.name() == "opening" || stream.name() == "closing")) { + // "opening" and "closing" are backwards compatibility code + load_effect(stream, c); + } + } + } + if (cancelled) return false; + + s->clips.append(c); + } + } + if (cancelled) return false; + + // 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 { + emit start_create_dual_transition(&td, primary, secondary, meta); + + waitCond.wait(&mutex); + } + } + } + + Media* m = panel_project->new_sequence(NULL, s, false, parent); + + loaded_sequences.append(m); + } + break; + } + } + } + if (cancelled) return false; + } + break; + } + } + return !cancelled; +} + +Media* LoadThread::find_loaded_folder_by_id(int id) { + if (id == 0) return NULL; + for (int j=0;jtemp_id == id) { + return parent_item; + } + } + return NULL; +} + +void LoadThread::run() { + mutex.lock(); + + 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 + current_element_count = 0; + total_element_count = 0; + while (!cancelled && !stream.atEnd()) { + stream.readNextStartElement(); + if (is_element(stream)) { + total_element_count++; + } + } + cont = !cancelled; + + // 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); + + // 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; + project_model.appendChild(find_loaded_folder_by_id(parent), folder); + } + + cont = load_worker(file, stream, MEDIA_TYPE_FOOTAGE); + } + + // load sequences + if (cont) { + cont = load_worker(file, stream, MEDIA_TYPE_SEQUENCE); + } + + if (!cancelled) { + if (!cont) { + xml_error = false; + if (show_err) emit error(); + } else if (stream.hasError()) { + error_str = stream.errorString(); + xml_error = true; + emit error(); + 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) { + emit success(); // run in main thread + + for (int i=0;istart_preview_generator(loaded_media_items.at(i), true); + } + } + + file.close(); + + mutex.unlock(); +} + +void LoadThread::cancel() { + waitCond.wakeAll(); + 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); + } else { + panel_project->add_recent_project(project_url); + } + + mainWindow->setWindowModified(autorecovery); + 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(); +} + +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 new file mode 100644 index 000000000..572e7b8d4 --- /dev/null +++ b/io/loadthread.h @@ -0,0 +1,72 @@ +#ifndef LOADTHREAD_H +#define LOADTHREAD_H + +#include +#include +#include +#include +#include + +class Media; +struct Footage; +struct Clip; +struct Sequence; +class LoadDialog; +class TransitionData; +struct EffectMeta; + +class LoadThread : public QThread +{ + Q_OBJECT +public: + LoadThread(LoadDialog* l, bool a); + void run(); + 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 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; + + 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; + QDir internal_proj_dir; + QString internal_proj_url; + 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; + bool xml_error; +}; + +#endif // LOADTHREAD_H 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"; } diff --git a/io/previewgenerator.cpp b/io/previewgenerator.cpp index 7b8934fdd..696c7987a 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.toUtf8(); 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()); - QString hash = QCryptographicHash::hash(cache_file.toLatin1(), QCryptographicHash::Md5).toHex(); + 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.toUtf8(), 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..6cf750313 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" @@ -212,18 +213,23 @@ 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())); 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())); + connect(ui->actionFull_Screen, SIGNAL(triggered(bool)), this, SLOT(toggle_full_screen())); } MainWindow::~MainWindow() { - panel_sequence_viewer->viewer_widget->delete_function(); - panel_footage_viewer->viewer_widget->delete_function(); + panel_effect_controls->clear_effects(true); + panel_sequence_viewer->viewer_widget->delete_function(); + panel_footage_viewer->viewer_widget->delete_function(); set_sequence(NULL); @@ -252,10 +258,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(); } @@ -305,26 +316,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 (focused_panel == panel_footage_viewer) { + panel_footage_viewer->set_zoom(true); + } 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 (focused_panel == panel_footage_viewer) { + panel_footage_viewer->set_zoom(false); + } 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 { @@ -386,9 +404,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); } } @@ -396,22 +415,35 @@ 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); - } + } } -void MainWindow::on_actionSplit_at_Playhead_triggered() -{ +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() { if (panel_timeline->focused()) { panel_timeline->split_at_playhead(); } @@ -505,18 +537,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(); @@ -582,7 +602,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() @@ -661,6 +681,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() @@ -694,6 +716,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); @@ -809,6 +832,15 @@ 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); + } +} + void MainWindow::on_actionDelete_In_Out_triggered() { if (panel_timeline->focused()) { @@ -977,12 +1009,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); @@ -1026,3 +1057,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 18e2fd455..048c30a7b 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -28,8 +28,11 @@ public slots: void cut(); void copy(); void paste(); + void new_project(); void autorecover_interval(); void on_actionNest_triggered(); + void on_actionClear_In_Out_triggered(); + void toggle_full_screen(); protected: void closeEvent(QCloseEvent *); @@ -70,8 +73,6 @@ private slots: void on_action_Open_Project_triggered(); - void on_actionProject_triggered(); - void on_actionSave_Project_As_triggered(); void on_actionDeselect_All_triggered(); @@ -158,8 +159,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(); @@ -212,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..61cc110c8 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -147,6 +147,8 @@ + + @@ -187,6 +189,7 @@ + @@ -925,6 +928,25 @@ Milliseconds + + + true + + + Enable Hover Focus for Zooming + + + + + true + + + Full Screen + + + F11 + + diff --git a/olive.pro b/olive.pro index 7265b2b0b..78bd16870 100644 --- a/olive.pro +++ b/olive.pro @@ -1,196 +1,205 @@ -#------------------------------------------------- -# -# 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 { - CONFIG += link_pkgconfig - PKGCONFIG += libavutil libavformat libavcodec libavfilter libswscale libswresample -} - -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 \ + io/avtogl.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 \ + io/avtogl.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 { + CONFIG += link_pkgconfig + PKGCONFIG += libavutil libavformat libavcodec libavfilter libswscale libswresample +} + +RESOURCES += \ + icons/icons.qrc 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 94f568470..dd8206ccc 100644 --- a/panels/panels.cpp +++ b/panels/panels.cpp @@ -3,9 +3,11 @@ #include "timeline.h" #include "effectcontrols.h" #include "viewer.h" +#include "project.h" #include "project/sequence.h" #include "project/clip.h" #include "project/transition.h" +#include "io/config.h" #include "debug.h" Project* panel_project = 0; @@ -99,8 +101,39 @@ void update_effect_controls() { void update_ui(bool modified) { if (modified) { update_effect_controls(); - } + } panel_effect_controls->update_keyframes(); 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() || panel_effect_controls->is_focused()) { + 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/project.cpp b/panels/project.cpp index 1b124b08d..dc391ae0b 100644 --- a/panels/project.cpp +++ b/panels/project.cpp @@ -1,6 +1,5 @@ -#include "project.h" -#include "ui_project.h" -#include "io/media.h" +#include "project.h" +#include "project/footage.h" #include "panels/panels.h" #include "panels/timeline.h" @@ -22,8 +21,11 @@ #include "dialogs/mediapropertiesdialog.h" #include "dialogs/loaddialog.h" #include "io/clipboard.h" +#include "project/media.h" +#include "ui/sourcetable.h" #include "debug.h" +#include #include #include #include @@ -33,8 +35,12 @@ #include #include #include +#include #include #include +#include +#include +#include extern "C" { #include @@ -43,22 +49,51 @@ extern "C" { #define MAXIMUM_RECENT_PROJECTS 10 +ProjectModel project_model; + QString autorecovery_filename; QString project_url = ""; 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->treeWidget; - connect(ui->treeWidget, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(rename_media(QTreeWidgetItem*,int))); + 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) { @@ -74,8 +109,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 +120,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 +135,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 +159,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 +170,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 +188,14 @@ 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::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 = item_to_media(items.at(j)); + if (i->get_type() == MEDIA_TYPE_SEQUENCE) { + new_sequence(ca, i->to_sequence()->copy(), false, item_to_media(items.at(j).parent())); duped = true; } } @@ -180,18 +207,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 = item_to_media(selected_items.at(0)); + 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 +229,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 = 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 { + 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 = item_to_media(selected_items.at(0)); + 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 +276,56 @@ 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); - } else { - parent->addChild(item); - } + project_model.appendChild(NULL, 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) { +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;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 +347,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;isetSortingEnabled(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 +395,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 +435,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); } } @@ -438,25 +456,25 @@ void Project::delete_selected_media() { for (int i=0;iappend(new DeleteMediaCommand(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 +493,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(QStringList& files, bool recursive, Media* replace, Media* parent) { bool imported = false; QVector image_sequence_urls; @@ -491,7 +522,7 @@ void Project::process_file_list(bool recursive, QStringList& files, QTreeWidgetI for (int i=0;iappend(new AddMediaCommand(folder, parent)); - } else { - parent->addChild(folder); + } else { + project_model.appendChild(parent, folder); } imported = true; @@ -589,34 +620,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->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); + project_model.appendChild(parent, item); } } @@ -633,32 +664,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 = 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, QTreeWidgetItem* parent) { - int count = (parent == NULL) ? ui->treeWidget->topLevelItemCount() : parent->childCount(); +bool Project::reveal_media(void *media, QModelIndex parent) { + 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; } @@ -673,92 +705,22 @@ 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()); } } -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;jmedia == m) { ca->append(new DeleteClipAction(sequence, i)); deleted = true; @@ -767,7 +729,7 @@ void Project::delete_clips_using_selected_media() { } } for (int j=0;jclear_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() { // clear existing project set_sequence(NULL); + panel_footage_viewer->set_media(NULL); clear(); 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 { - 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;iget_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(project_model.getItem(item.parent())->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 : project_model.getItem(parent)->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)); @@ -1467,8 +819,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)); @@ -1479,7 +831,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++; @@ -1534,16 +886,18 @@ 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) { - case MEDIA_TYPE_FOOTAGE: - stream.writeAttribute("media", QString::number(static_cast(c->media)->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)); - 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++) { @@ -1574,8 +928,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); } } } @@ -1602,20 +956,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 @@ -1670,20 +1024,22 @@ 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(); - 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) { +void Project::list_all_sequences_worker(QVector* list, Media* parent) { + for (int i=0;iget_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; } @@ -1691,36 +1047,42 @@ 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))); + project_model.set_icon(item, QIcon(pixmap.copy(THROBBER_SIZE*animation, 0, THROBBER_SIZE, THROBBER_SIZE))); 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: 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 - 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) { @@ -1733,82 +1095,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..563ae5ac0 100644 --- a/panels/project.h +++ b/panels/project.h @@ -6,16 +6,19 @@ #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; +class QSortFilterProxyModel; class ComboAction; #define LOAD_TYPE_VERSION 69 @@ -30,18 +33,11 @@ extern QString project_url; extern QStringList recent_projects; extern QString recent_proj_file; -int get_type_from_tree(QTreeWidgetItem* item); -void* get_media_from_tree(QTreeWidgetItem* item); -Media* get_footage_from_tree(QTreeWidgetItem* item); -void set_footage_of_tree(QTreeWidgetItem* item, Media* 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); +extern ProjectModel project_model; -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 +49,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(QStringList& files, bool recursive = false, Media* replace = NULL, Media *parent = NULL); + 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); + Media* item_to_media(const QModelIndex& index); void save_recent_projects(); - QVector list_all_project_sequences(); + QVector list_all_project_sequences(); SourceTable* source_table; + QSortFilterProxyModel* sorter; - QVector last_imported_media; + QVector last_imported_media; + + //Media *new_item(); + + void start_preview_generator(Media* item, bool replacing); public slots: void import_dialog(); void delete_selected_media(); @@ -83,48 +85,32 @@ 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 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 deleted file mode 100644 index cd064d9b0..000000000 --- a/panels/project.ui +++ /dev/null @@ -1,100 +0,0 @@ - - - Project - - - - 0 - 0 - 504 - 371 - - - - - 0 - 0 - - - - Project - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - true - - - QAbstractItemView::NoEditTriggers - - - false - - - QAbstractItemView::DragDrop - - - QAbstractItemView::ExtendedSelection - - - QAbstractItemView::SelectRows - - - true - - - true - - - false - - - false - - - true - - - - Name - - - - - Duration - - - - - Rate - - - - - - - - - - SourceTable - QTreeWidget -
ui/sourcetable.h
-
-
- - -
diff --git a/panels/timeline.cpp b/panels/timeline.cpp index e9279206f..0cb5ac181 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" @@ -30,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); @@ -160,23 +162,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 +190,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 +206,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 +276,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 +301,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(); @@ -417,16 +418,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 +433,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 +449,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 +570,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) { @@ -975,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/panels/timeline.h b/panels/timeline.h index db73a87da..6d4bb4a5a 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); @@ -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 9e2fccde9..667780ea3 100644 --- a/panels/viewer.cpp +++ b/panels/viewer.cpp @@ -8,11 +8,13 @@ #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" #include "playback/playback.h" +#include "ui/viewerwidget.h" #include "debug.h" #define FRAMES_IN_ONE_MINUTE 1798 // 1800 - 2 @@ -33,20 +35,22 @@ Viewer::Viewer(QWidget *parent) : QDockWidget(parent), playing(false), just_played(false), + media(NULL), seq(NULL), 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; ui->headers->snapping = false; ui->headers->show_text(false); - ui->glViewerPane->child = ui->openGLWidget; - ui->openGLWidget->viewer = this; - viewer_widget = ui->openGLWidget; - set_media(MEDIA_TYPE_SEQUENCE, NULL); + ui->glViewerPane->viewer = this; + viewer_widget = ui->glViewerPane->child; + viewer_widget->viewer = this; + set_media(NULL); ui->currentTimecode->setEnabled(false); ui->currentTimecode->set_minimum_value(0); @@ -59,6 +63,9 @@ 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)), 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(); @@ -70,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() @@ -97,15 +104,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("[:;]")); @@ -237,7 +235,6 @@ void Viewer::seek(long p) { seq->playhead = p; update_parents(); reset_all_audio(); - assert_audio_device(); audio_scrub = true; } @@ -302,7 +299,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; @@ -340,27 +336,27 @@ 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); - 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); @@ -380,7 +376,14 @@ 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)); + ui->headers->set_scrollbar_max(ui->horizontalScrollBar, sequenceEndFrame, ui->headers->width()); + viewer_widget->waveform_zoom = ui->headers->get_zoom(); + } else { + ui->headers->update(); + } } } @@ -393,8 +396,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(); } @@ -411,86 +414,102 @@ 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_media(int type, void* media) { +void Viewer::set_zoom(bool in) { + 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 (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); + } +} + +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() { @@ -538,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() { @@ -564,12 +595,10 @@ 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); - 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); @@ -584,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 d7b5f1d53..449f884e2 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); @@ -36,6 +37,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(); @@ -57,14 +59,14 @@ 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(); + void reset_all_audio(); void update_parents(); ViewerWidget* viewer_widget; + Media* media; Sequence* seq; Ui::Viewer *ui; @@ -81,12 +83,15 @@ 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); 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..acb55f077 100644 --- a/panels/viewer.ui +++ b/panels/viewer.ui @@ -49,21 +49,120 @@ 0
- - - - 110 - 50 - 300 - 200 - - - + + + + 0 + + + 20 + + + 1826 + + + Qt::Horizontal + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 260 + 20 + + + + + + + + + Fit + + + + + 10% + + + + + 25% + + + + + 50% + + + + + 75% + + + + + 100% + + + + + 150% + + + + + 200% + + + + + 400% + + + + + + + + Qt::Horizontal + + + + 259 + 20 + + + + + + + @@ -223,11 +322,6 @@ - - ViewerWidget - QOpenGLWidget -
ui/viewerwidget.h
-
ViewerContainer QWidget diff --git a/playback/audio.cpp b/playback/audio.cpp index d07098383..8d04cedce 100644 --- a/playback/audio.cpp +++ b/playback/audio.cpp @@ -40,41 +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()); - if (!info.isFormatSupported(audio_format)) { - qWarning() << "[WARNING] Audio format is not supported by backend, using nearest"; - audio_format = info.nearestFormat(audio_format); - } + 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(); - audio_output = new QAudioOutput(info, audio_format); - audio_output->setNotifyInterval(5); + if (!info.isFormatSupported(audio_format)) { + qWarning() << "[WARNING] Audio format is not supported by backend, using nearest"; + audio_format = info.nearestFormat(audio_format); + } - // 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; + audio_output = new QAudioOutput(info, audio_format); + audio_output->moveToThread(QApplication::instance()->thread()); + audio_output->setNotifyInterval(5); - // start sender thread - audio_thread = new AudioSenderThread(); - QObject::connect(audio_output, SIGNAL(notify()), audio_thread, SLOT(notifyReceiver())); - audio_thread->start(QThread::TimeCriticalPriority); + // 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; - clear_audio_ibuffer(); - } + // 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(); } } 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/cacher.cpp b/playback/cacher.cpp index 814624595..ae3ecc683 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" { @@ -23,6 +24,7 @@ extern "C" { #include #include #include + #include } #include @@ -33,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); @@ -49,7 +51,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 +63,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 +116,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,13 +450,11 @@ 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) { // 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 +472,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); @@ -541,85 +537,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) {} @@ -627,245 +621,231 @@ 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; - AVDictionary* opts = NULL; + clip->opts = NULL; - // 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) { - av_dict_set(&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); - } + // 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) { + av_dict_set(&clip->opts, "threads", "auto", 0); + } + 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 - if (avcodec_open2(clip->codecCtx, clip->codec, &opts) < 0) { - dout << "[ERROR] Could not open codec"; - } + // Open codec + if (avcodec_open2(clip->codecCtx, clip->codec, &clip->opts) < 0) { + 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) { - /* 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; + 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 + ); - // 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; - - 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); - - 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"; - } + 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); 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 - avfilter_graph_create_filter(&yadif_filter, avfilter_get_by_name("yadif"), "yadif", yadif_args, NULL, clip->filter_graph); + 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_link(last_filter, 0, yadif_filter, 0); last_filter = yadif_filter; - } + } - /* stabilization code one day - if (false) { + /* stabilization code */ + 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); + 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", format_args, 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) { - 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(); @@ -883,30 +863,32 @@ 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); avcodec_close(clip->codecCtx); avcodec_free_context(&clip->codecCtx); + + av_dict_free(&clip->opts); + avformat_close_input(&clip->formatCtx); } diff --git a/playback/playback.cpp b/playback/playback.cpp index 638a4ec43..13e71bac7 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,10 +10,14 @@ #include "panels/viewer.h" #include "project/effect.h" #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 @@ -32,36 +36,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 +79,23 @@ 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: + 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; - break; - } + } } 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; @@ -111,9 +110,13 @@ 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); + 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))); @@ -194,7 +197,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 { @@ -219,9 +222,30 @@ 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]); + 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]; + 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, get_gl_pix_fmt_from_av(c->pix_fmt), QOpenGLTexture::UInt8, data); + + if (copied) delete [] data; + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); } @@ -318,11 +342,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); - close_clip(c); - } else if (c->media_type == MEDIA_TYPE_FOOTAGE && c->open) { + if (c != NULL) { + if (c->media != NULL && c->media->get_type() == MEDIA_TYPE_SEQUENCE) { + closeActiveClips(c->media->to_sequence(), wait); + 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/playback/playback.h b/playback/playback.h index af1d742f6..67a18c6d4 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); @@ -20,6 +21,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/clip.cpp b/project/clip.cpp index 08bf01299..df0927544 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,7 @@ Clip::Clip(Sequence* s) : use_existing_frame(false), filter_graph(NULL), fbo(NULL), - texture(NULL) + opts(NULL) { pkt = av_packet_alloc(); reset(); @@ -54,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; @@ -88,32 +88,27 @@ void Clip::reset() { codec = NULL; codecCtx = NULL; texture = NULL; + last_invalid_ts = -1; } 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; @@ -164,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(); } } @@ -208,36 +203,45 @@ 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; + return qSNaN(); +} + 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; + } + } } } @@ -245,28 +249,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; } } @@ -275,16 +269,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 ea0fc6e85..69e757d5f 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; @@ -27,6 +28,7 @@ struct SwsContext; struct SwrContext; struct AVFilterGraph; struct AVFilterContext; +struct AVDictionary; class QOpenGLTexture; struct Clip @@ -41,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); @@ -63,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; @@ -87,6 +88,7 @@ struct Clip AVCodecContext* codecCtx; AVPacket* pkt; AVFrame* frame; + AVDictionary* opts; long calculated_length; // temporary variables @@ -96,9 +98,9 @@ struct Clip bool pkt_written; bool open; bool finished_opening; - bool replaced; - int skip_type; + bool replaced; bool ignore_reverse; + int pix_fmt; // caching functions bool use_existing_frame; diff --git a/project/effect.cpp b/project/effect.cpp index 73c661167..153987319 100644 --- a/project/effect.cpp +++ b/project/effect.cpp @@ -58,7 +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_CORNERPIN: return new CornerPinEffect(c, em); } } else { dout << "[ERROR] Invalid effect data"; @@ -107,8 +107,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 +124,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 +230,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), @@ -431,9 +432,14 @@ Effect::~Effect() { close(); } + delete container; + for (int i=0;irelease(); - 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()); @@ -783,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? } @@ -860,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; @@ -975,7 +991,6 @@ bool Effect::valueHasChanged(double timecode) { void Effect::delete_texture() { if (texture != NULL) { - texture->destroy(); delete texture; texture = NULL; } 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.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 befe4bcd3..127e0ed14 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 @@ -22,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/effectrow.cpp b/project/effectrow.cpp index 653accb5c..3f3ea6224 100644 --- a/project/effectrow.cpp +++ b/project/effectrow.cpp @@ -30,7 +30,7 @@ EffectRow::EffectRow(Effect *parent, bool save, QGridLayout *uilayout, const QSt QSize button_size(20, 20); QSize icon_size(12, 12); - QHBoxLayout* key_controls = new QHBoxLayout(); + key_controls = new QHBoxLayout(); key_controls->setSpacing(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/io/media.cpp b/project/footage.cpp similarity index 67% rename from io/media.cpp rename to project/footage.cpp index f9cc4d8bc..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) { +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 65% rename from io/media.h rename to project/footage.h index 9d1312f06..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 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..bb01dc72e --- /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 "panels/project.h" +#include "projectmodel.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; + default: 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: + { + 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 NULL; +} + +int Media::get_sampling_rate(int stream) { + switch (get_type()) { + 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; +} + +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 && !qIsNull(f->video_tracks.at(0)->video_frame_rate)) r = f->video_tracks.at(0)->video_frame_rate; + + 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(); + 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"; + } + } + 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; +} + +void Media::removeChild(int i) { + children.removeAt(i); +} diff --git a/project/media.h b/project/media.h new file mode 100644 index 000000000..3c04de933 --- /dev/null +++ b/project/media.h @@ -0,0 +1,64 @@ +#ifndef MEDIA_H +#define MEDIA_H + +#include +#include + +#define MEDIA_TYPE_FOOTAGE 0 +#define MEDIA_TYPE_SEQUENCE 1 +#define MEDIA_TYPE_FOLDER 2 + +struct 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 = -1); + int get_sampling_rate(int stream = -1); + + // 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(); + void removeChild(int i); + + 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..6452b623d --- /dev/null +++ b/project/projectmodel.cpp @@ -0,0 +1,178 @@ +#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) { + root_item = new Media(0); + root_item->root = true; +} + +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() { + beginResetModel(); + destroy_root(); + root_item = new Media(0); + root_item->root = true; + endResetModel(); +} + +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(); +} + +Media *ProjectModel::getItem(const QModelIndex &index) const { + if (index.isValid()) { + Media *item = static_cast(index.internalPointer()); + if (item) + return item; + } + 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()); + parent->appendChild(child); + endInsertRows(); +} + +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(); +} + +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(); +} + +Media* ProjectModel::child(int i, Media* parent) { + if (parent == NULL) parent = root_item; + return parent->child(i); +} + +int ProjectModel::childCount(Media *parent) { + if (parent == NULL) parent = root_item; + return parent->childCount(); +} diff --git a/project/projectmodel.h b/project/projectmodel.h new file mode 100644 index 000000000..42709b848 --- /dev/null +++ b/project/projectmodel.h @@ -0,0 +1,41 @@ +#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; + 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); + void set_icon(Media* m, const QIcon &ico); + +private: + Media* root_item; +}; + +#endif // PROJECTMODEL_H diff --git a/project/undo.cpp b/project/undo.cpp index 9010404fd..17fe258d7 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; @@ -198,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()) {} @@ -212,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); } @@ -221,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); } @@ -340,38 +350,34 @@ void DeleteTransitionCommand::redo() { mainWindow->setWindowModified(true); } -NewSequenceCommand::NewSequenceCommand(QTreeWidgetItem *s, QTreeWidgetItem* iparent) : +NewSequenceCommand::NewSequenceCommand(Media *s, Media* iparent) : seq(s), 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) { - panel_project->source_table->takeTopLevelItem(panel_project->source_table->indexOfTopLevelItem(seq)); - } else { - parent->removeChild(seq); - } + project_model.removeChild(parent, seq); + done = false; mainWindow->setWindowModified(old_project_changed); } void NewSequenceCommand::redo() { - if (parent == NULL) { - panel_project->source_table->addTopLevelItem(seq); - } else { - parent->addChild(seq); - } + project_model.appendChild(parent, seq); + done = true; mainWindow->setWindowModified(true); } -AddMediaCommand::AddMediaCommand(QTreeWidgetItem* iitem, QTreeWidgetItem* iparent) : +AddMediaCommand::AddMediaCommand(Media* iitem, Media *iparent) : item(iitem), parent(iparent), done(false), @@ -380,85 +386,44 @@ AddMediaCommand::AddMediaCommand(QTreeWidgetItem* iitem, QTreeWidgetItem* iparen 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); - } + project_model.removeChild(parent, item); done = false; mainWindow->setWindowModified(old_project_changed); } void AddMediaCommand::redo() { - if (parent == NULL) { - panel_project->source_table->addTopLevelItem(item); - } else { - parent->addChild(item); - } - - /* 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(QTreeWidgetItem* i) : +DeleteMediaCommand::DeleteMediaCommand(Media* i) : item(i), + parent(i->parentItem()), 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; + if (done) { + delete item; } } void DeleteMediaCommand::undo() { - if (parent == NULL) { - panel_project->source_table->addTopLevelItem(item); - } else { - parent->addChild(item); - } + project_model.appendChild(parent, item); mainWindow->setWindowModified(old_project_changed); done = false; } void DeleteMediaCommand::redo() { - parent = item->parent(); - - if (parent == NULL) { - panel_project->source_table->takeTopLevelItem(panel_project->source_table->indexOfTopLevelItem(item)); - } else { - parent->removeChild(item); - } + project_model.removeChild(parent, item); mainWindow->setWindowModified(true); done = true; @@ -601,25 +566,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; } } @@ -628,7 +592,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() { @@ -643,11 +607,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 +623,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 +631,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 +655,7 @@ void ReplaceClipMediaCommand::undo() { void ReplaceClipMediaCommand::redo() { replace(false); + update_ui(true); mainWindow->setWindowModified(true); } @@ -737,61 +698,37 @@ 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)); - } - } + project_model.moveChild(items.at(i), froms.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)); - } - } + project_model.moveChild(items.at(i), to); + } 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 +1075,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 +1112,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 +1171,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 +1180,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..173163dc4 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" @@ -22,6 +22,7 @@ struct EffectMeta; #include #include #include +#include extern QUndoStack undo_stack; @@ -88,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(); @@ -96,6 +97,7 @@ private: Clip* clip; const EffectMeta* meta; Effect* ref; + int pos; bool done; bool old_project_changed; }; @@ -166,39 +168,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(Media *i); ~DeleteMediaCommand(); void undo(); void redo(); private: - QTreeWidgetItem* item; - QTreeWidgetItem* parent; + Media* item; + Media* parent; bool old_project_changed; bool done; }; @@ -258,29 +260,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 +303,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 +484,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 +496,7 @@ public: int audio_frequency; int audio_layout; private: - QTreeWidgetItem* item; + Media* item; Sequence* seq; bool old_project_changed; @@ -542,12 +540,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/collapsiblewidget.cpp b/ui/collapsiblewidget.cpp index f9622b03a..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; @@ -22,7 +24,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(); @@ -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/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) { diff --git a/ui/sourcetable.cpp b/ui/sourcetable.cpp index 8e11cfe3f..aa835f55d 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,34 +24,37 @@ #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(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu())); } 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())); - if (selectedItems().size() > 0) { - if (selectedItems().size() == 1) { + QModelIndexList selected_items = selectionModel()->selectedRows(); + + if (selected_items.size() > 0) { + Media* m = project_parent->item_to_media(selected_items.at(0)); + + 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())); + 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"); @@ -62,18 +67,18 @@ 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())); } } // 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; } } @@ -86,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 (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())); + connect(properties_action, SIGNAL(triggered(bool)), project_parent, SLOT(open_properties())); } } @@ -109,29 +114,30 @@ 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;iitem_to_media(selected_items.at(i))); } 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); + project_parent->new_sequence(ca, s, true, NULL); undo_stack.push(ca); } } void SourceTable::reveal_in_browser() { - Media* m = get_footage_from_tree(selectedItems().at(0)); + QModelIndexList selected_items = selectionModel()->selectedRows(); + Media* media = project_parent->item_to_media(selected_items.at(0)); + Footage* m = media->to_footage(); #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 = project_parent->item_to_media(index); + 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) { - panel_project->import_dialog(); - } else if (selectedItems().count() == 1) { - QTreeWidgetItem* item = selectedItems().at(0); - switch (get_type_from_tree(item)) { + QModelIndexList selected_items = selectionModel()->selectedRows(); + if (selected_items.size() == 0) { + project_parent->import_dialog(); + } else if (selected_items.size() == 1) { + Media* item = project_parent->item_to_media(selected_items.at(0)); + 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 = project_parent->item_to_media(drop_item); 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)); + project_parent->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); + project_parent->process_file_list(paths, false, NULL, panel_project->item_to_media(parent)); } } 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 = project_parent->item_to_media(item); + 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..c2a2637a7 100644 --- a/ui/sourcetable.h +++ b/ui/sourcetable.h @@ -1,17 +1,19 @@ #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: SourceTable(QWidget* parent = 0); + Project* project_parent; protected: void mousePressEvent(QMouseEvent*); void mouseDoubleClickEvent(QMouseEvent *event); @@ -20,13 +22,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/timelineheader.cpp b/ui/timelineheader.cpp index 00094fac2..53cddecab 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" @@ -10,8 +11,10 @@ #include #include -#include +#include #include +#include +#include #define CLICK_RANGE 5 #define PLAYHEAD_SIZE 6 @@ -19,6 +22,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), @@ -35,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) { @@ -87,6 +103,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) { @@ -255,7 +275,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() { @@ -376,3 +400,13 @@ 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.setEnabled(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 68cb38573..4bced42af 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 { @@ -20,11 +22,14 @@ public: void show_text(bool enable); 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); void set_visible_in(long i); + void show_context_menu(const QPoint &pos); protected: void paintEvent(QPaintEvent*); diff --git a/ui/timelinewidget.cpp b/ui/timelinewidget.cpp index a34c84259..ba4d42fcb 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,8 @@ #include "ui_timeline.h" #include "mainwindow.h" #include "ui/viewerwidget.h" +#include "dialogs/stabilizerdialog.h" +#include "project/media.h" #include "debug.h" #include "project/effect.h" @@ -149,6 +151,23 @@ void TimelineWidget::show_context_menu(const QPoint& pos) { QAction* nestAction = menu.addAction("&Nest"); connect(nestAction, SIGNAL(triggered(bool)), mainWindow, SLOT(on_actionNest_triggered())); + // 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())); + } + // set autoscale arbitrarily to the first selected clip autoscaleAction->setChecked(selected_clips.at(0)->autoscale); @@ -228,7 +247,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) { @@ -238,17 +262,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;iitem_to_media(items.at(i)); } import_init = true; } @@ -256,13 +277,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; } } @@ -276,17 +291,17 @@ 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 // 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)); } } @@ -299,8 +314,8 @@ void TimelineWidget::dragEnterEvent(QDragEnterEvent *event) { } } - if (import_init) { - event->accept(); + if (import_init) { + event->acceptProposedAction(); long entry_point; Sequence* seq = sequence; @@ -309,7 +324,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,18 +332,22 @@ 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; - } + } } void TimelineWidget::dragMoveEvent(QDragMoveEvent *event) { - if (sequence != NULL && panel_timeline->importing) { - 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 (panel_timeline->importing) { + event->acceptProposedAction(); + + 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); + } } } @@ -350,7 +369,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(); @@ -362,6 +382,7 @@ void TimelineWidget::dragLeaveEvent(QDragLeaveEvent*) { } if (self_created_sequence != NULL) { delete self_created_sequence; + self_created_sequence = NULL; } } @@ -464,7 +485,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(); @@ -773,7 +794,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 +828,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 +1248,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 != NULL && 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 +1278,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 +1289,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 != 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; @@ -1349,14 +1370,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 +2070,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 +2118,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 +2246,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 +2662,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..2ba01cab4 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 @@ -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/viewercontainer.cpp b/ui/viewercontainer.cpp index cd3e7cbfd..be24c84bf 100644 --- a/ui/viewercontainer.cpp +++ b/ui/viewercontainer.cpp @@ -2,36 +2,93 @@ #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); + 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 (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 (viewer->seq != NULL) { + if (child->waveform) { + child->move(0, 0); + child->resize(size()); + } else if (fit) { + double aspect_ratio = double(viewer->seq->width)/double(viewer->seq->height); - bool widget_is_larger_than_sequence = widget_ar > aspect_ratio; + int widget_x = 0; + int widget_y = 0; + int widget_width = width(); + int widget_height = height(); + float widget_ar = (float) widget_width /(float) widget_height; - 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); - } + bool widget_is_wider_than_sequence = widget_ar > aspect_ratio; - child->move(widget_x, widget_y); - child->resize(widget_width, widget_height); + if (widget_is_wider_than_sequence) { + widget_width = widget_height * aspect_ratio; + widget_x = (width() / 2) - (widget_width / 2); + } else { + widget_height = widget_width / aspect_ratio; + 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 2d2bdd0b6..921e2b9aa 100644 --- a/ui/viewercontainer.h +++ b/ui/viewercontainer.h @@ -1,15 +1,25 @@ #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; - QWidget* child; + ~ViewerContainer(); + + bool fit; + double zoom; + + void dragScrollPress(const QPoint&); + void dragScrollMove(const QPoint&); + + Viewer* viewer; + ViewerWidget* child; void adjust(); protected: @@ -18,6 +28,13 @@ protected: signals: 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 ecfa1be82..0c80622cd 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,9 @@ #include "io/math.h" #include "ui/collapsiblewidget.h" #include "project/undo.h" +#include "project/media.h" +#include "ui/viewercontainer.h" +#include "io/avtogl.h" #include #include @@ -31,6 +34,7 @@ #include #include #include +#include extern "C" { #include @@ -43,7 +47,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); @@ -52,7 +58,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())); @@ -62,13 +68,20 @@ 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(); } } +void ViewerWidget::set_waveform_scroll(int s) { + if (waveform) { + waveform_scroll = s; + update(); + } +} + void ViewerWidget::show_context_menu() { QMenu menu(this); @@ -126,7 +139,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) @@ -141,11 +154,7 @@ void ViewerWidget::paintEvent(QPaintEvent *e) { } void ViewerWidget::seek_from_click(int x) { - viewer->seek(getFrameFromScreenPoint((double) width() / (double) waveform_clip->timeline_out, x)); -} - -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); + viewer->seek(getFrameFromScreenPoint(waveform_zoom, x+waveform_scroll)); } EffectGizmo* ViewerWidget::get_gizmo_from_mouse(int x, int y) { @@ -153,6 +162,7 @@ EffectGizmo* ViewerWidget::get_gizmo_from_mouse(int x, int y) { 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); @@ -170,6 +180,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; } } @@ -199,6 +217,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(); @@ -216,9 +236,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; @@ -321,6 +343,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); @@ -337,7 +367,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(); @@ -378,7 +412,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 +436,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 +453,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 +505,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(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(get_gl_pix_fmt_from_av(c->pix_fmt), 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 +551,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); } @@ -604,7 +637,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); @@ -686,21 +729,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 @@ -736,6 +775,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; @@ -764,26 +804,27 @@ 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()); } + if (force_quit) break; if (texture_failed) { if (rendering) { dout << "[INFO] Texture failed - looping"; @@ -802,6 +843,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(); @@ -829,6 +871,27 @@ void ViewerWidget::paintGL() { glVertex3f(g->screen_pos[0].x(), g->screen_pos[0].y(), gizmo_z); glEnd(); break; + 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; } } glPopMatrix(); diff --git a/ui/viewerwidget.h b/ui/viewerwidget.h index 55f68a654..8d9899151 100644 --- a/ui/viewerwidget.h +++ b/ui/viewerwidget.h @@ -12,10 +12,11 @@ class Viewer; struct Clip; -struct MediaStream; +struct FootageStream; class QOpenGLFramebufferObject; class Effect; class EffectGizmo; +class ViewerContainer; struct GLTextureCoords; class ViewerWidget : public QOpenGLWidget, QOpenGLFunctions @@ -27,14 +28,20 @@ public: void paintGL(); void initializeGL(); Viewer* viewer; + ViewerContainer* container; QOpenGLFramebufferObject* default_fbo; bool waveform; Clip* waveform_clip; - MediaStream* waveform_ms; + FootageStream* 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);