From ac1cb9d14fc1e4d41cccfc13605596798fe71adb Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 13 Dec 2018 14:29:09 +1100 Subject: [PATCH] massive rewrite of project data system --- dialogs/loaddialog.cpp | 23 +- dialogs/loaddialog.h | 11 +- dialogs/mediapropertiesdialog.cpp | 30 +- dialogs/mediapropertiesdialog.h | 9 +- dialogs/newsequencedialog.h | 4 +- dialogs/replaceclipmediadialog.cpp | 69 +- dialogs/replaceclipmediadialog.h | 13 +- dialogs/speeddialog.cpp | 17 +- effects/internal/transformeffect.cpp | 2 +- icons/error.png | Bin 0 -> 11806 bytes icons/icons.qrc | 1 + io/loadthread.cpp | 639 ++++++++++++++ io/loadthread.h | 43 + io/previewgenerator.cpp | 116 +-- io/previewgenerator.h | 15 +- mainwindow.cpp | 13 +- olive.pro | 14 +- panels/panels.cpp | 2 + panels/project.cpp | 1183 +++++--------------------- panels/project.h | 79 +- panels/project.ui | 40 +- panels/timeline.cpp | 36 +- panels/timeline.h | 10 +- panels/viewer.cpp | 154 ++-- panels/viewer.h | 4 +- playback/cacher.cpp | 795 +++++++++-------- playback/playback.cpp | 91 +- playback/playback.h | 1 + project/clip.cpp | 105 ++- project/clip.h | 10 +- io/media.cpp => project/footage.cpp | 15 +- io/media.h => project/footage.h | 26 +- project/media.cpp | 312 +++++++ project/media.h | 67 ++ project/projectmodel.cpp | 146 ++++ project/projectmodel.h | 39 + project/undo.cpp | 166 ++-- project/undo.h | 67 +- ui/sourcetable.cpp | 140 +-- ui/sourcetable.h | 13 +- ui/timelinewidget.cpp | 72 +- ui/timelinewidget.h | 4 +- ui/viewerwidget.cpp | 120 ++- ui/viewerwidget.h | 4 +- 44 files changed, 2562 insertions(+), 2158 deletions(-) create mode 100644 icons/error.png create mode 100644 io/loadthread.cpp create mode 100644 io/loadthread.h rename io/media.cpp => project/footage.cpp (67%) rename io/media.h => project/footage.h (66%) create mode 100644 project/media.cpp create mode 100644 project/media.h create mode 100644 project/projectmodel.cpp create mode 100644 project/projectmodel.h diff --git a/dialogs/loaddialog.cpp b/dialogs/loaddialog.cpp index 47a641810..3a065241e 100644 --- a/dialogs/loaddialog.cpp +++ b/dialogs/loaddialog.cpp @@ -6,25 +6,40 @@ #include #include -LoadDialog::LoadDialog(QWidget *parent) : QDialog(parent) { +#include "panels/panels.h" +#include "panels/project.h" +#include "io/loadthread.h" +#include "playback/playback.h" +#include "ui/sourcetable.h" + +LoadDialog::LoadDialog(QWidget *parent, bool autorecovery) : QDialog(parent) { setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); QVBoxLayout* layout = new QVBoxLayout(); setLayout(layout); - layout->addWidget(new QLabel("Loading ''...")); + layout->addWidget(new QLabel("Loading '" + project_url.mid(project_url.lastIndexOf('/')+1) + "'...")); bar = new QProgressBar(); bar->setValue(50); layout->addWidget(bar); - QPushButton* cancel_button = new QPushButton("Cancel"); + cancel_button = new QPushButton("Cancel"); connect(cancel_button, SIGNAL(clicked(bool)), this, SLOT(reject())); - QHBoxLayout* hboxLayout = new QHBoxLayout(); + hboxLayout = new QHBoxLayout(); hboxLayout->addStretch(); hboxLayout->addWidget(cancel_button); hboxLayout->addStretch(); layout->addLayout(hboxLayout); + + LoadThread* lt = new LoadThread(this, autorecovery); + QObject::connect(lt, SIGNAL(success()), this, SLOT(thread_done())); + lt->start(); +} + +void LoadDialog::thread_done() { +// project_model.update_data(); + close(); } diff --git a/dialogs/loaddialog.h b/dialogs/loaddialog.h index 7f6b0a77f..2f646fc91 100644 --- a/dialogs/loaddialog.h +++ b/dialogs/loaddialog.h @@ -4,13 +4,22 @@ #include class QProgressBar; +class Sequence; +class Media; +class Footage; +class QHBoxLayout; class LoadDialog : public QDialog { + Q_OBJECT public: - LoadDialog(QWidget* parent = 0); + LoadDialog(QWidget* parent, bool autorecovery); +private slots: + void thread_done(); private: QProgressBar* bar; + QPushButton* cancel_button; + QHBoxLayout* hboxLayout; }; #endif // LOADDIALOG_H diff --git a/dialogs/mediapropertiesdialog.cpp b/dialogs/mediapropertiesdialog.cpp index 946b83395..6f4fae51e 100644 --- a/dialogs/mediapropertiesdialog.cpp +++ b/dialogs/mediapropertiesdialog.cpp @@ -7,34 +7,35 @@ #include #include -#include "io/media.h" +#include "project/footage.h" +#include "project/media.h" #include "panels/project.h" #include "project/undo.h" -MediaPropertiesDialog::MediaPropertiesDialog(QWidget *parent, QTreeWidgetItem *i, Media *m) : +MediaPropertiesDialog::MediaPropertiesDialog(QWidget *parent, Media *i) : QDialog(parent), - item(i), - media(m) + item(i) { setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); QGridLayout* grid = new QGridLayout(); setLayout(grid); - if (m->video_tracks.size() > 0) { + Footage* f = item->to_footage(); + if (f->video_tracks.size() > 0) { interlacing_box = new QComboBox(); - interlacing_box->addItem("Auto (" + get_interlacing_name(media->video_tracks.at(0)->video_auto_interlacing) + ")"); + interlacing_box->addItem("Auto (" + get_interlacing_name(f->video_tracks.at(0)->video_auto_interlacing) + ")"); interlacing_box->addItem(get_interlacing_name(VIDEO_PROGRESSIVE)); interlacing_box->addItem(get_interlacing_name(VIDEO_TOP_FIELD_FIRST)); interlacing_box->addItem(get_interlacing_name(VIDEO_BOTTOM_FIELD_FIRST)); - interlacing_box->setCurrentIndex((media->video_tracks.at(0)->video_auto_interlacing == media->video_tracks.at(0)->video_interlacing) ? 0 : media->video_tracks.at(0)->video_interlacing + 1); + interlacing_box->setCurrentIndex((f->video_tracks.at(0)->video_auto_interlacing == f->video_tracks.at(0)->video_interlacing) ? 0 : f->video_tracks.at(0)->video_interlacing + 1); grid->addWidget(new QLabel("Interlacing:"), 0, 0); grid->addWidget(interlacing_box, 0, 1); } - name_box = new QLineEdit(m->name); + name_box = new QLineEdit(item->get_name()); grid->addWidget(new QLabel("Name:"), 1, 0); grid->addWidget(name_box, 1, 1); @@ -50,22 +51,19 @@ void MediaPropertiesDialog::accept() { ComboAction* ca = new ComboAction(); //set interlacing + Footage* f = item->to_footage(); if (interlacing_box->currentIndex() > 0) { - ca->append(new SetInt(&media->video_tracks.at(0)->video_interlacing, interlacing_box->currentIndex() - 1)); + ca->append(new SetInt(&f->video_tracks.at(0)->video_interlacing, interlacing_box->currentIndex() - 1)); } else { - ca->append(new SetInt(&media->video_tracks.at(0)->video_interlacing, media->video_tracks.at(0)->video_auto_interlacing)); + ca->append(new SetInt(&f->video_tracks.at(0)->video_interlacing, f->video_tracks.at(0)->video_auto_interlacing)); } //set name - MediaRename* mr = new MediaRename(); - mr->from = media->name; - mr->item = item; - mr->to = name_box->text(); - item->setText(0, name_box->text()); + MediaRename* mr = new MediaRename(item, name_box->text()); ca->append(mr); ca->appendPost(new CloseAllClipsCommand()); - ca->appendPost(new UpdateFootageTooltip(item, media)); + ca->appendPost(new UpdateFootageTooltip(item)); undo_stack.push(ca); diff --git a/dialogs/mediapropertiesdialog.h b/dialogs/mediapropertiesdialog.h index de5c5f92b..13f96c3d1 100644 --- a/dialogs/mediapropertiesdialog.h +++ b/dialogs/mediapropertiesdialog.h @@ -3,20 +3,19 @@ #include -struct Media; +struct Footage; class QComboBox; class QLineEdit; -class QTreeWidgetItem; +class Media; class MediaPropertiesDialog : public QDialog { Q_OBJECT public: - MediaPropertiesDialog(QWidget *parent, QTreeWidgetItem* i, Media *m); + MediaPropertiesDialog(QWidget *parent, Media* i); private: QComboBox* interlacing_box; QLineEdit* name_box; - QTreeWidgetItem* item; - Media* media; + Media* item; private slots: void accept(); }; diff --git a/dialogs/newsequencedialog.h b/dialogs/newsequencedialog.h index af219bb83..d6b9b2a2d 100644 --- a/dialogs/newsequencedialog.h +++ b/dialogs/newsequencedialog.h @@ -4,7 +4,7 @@ #include class Project; -class QTreeWidgetItem; +class Media; struct Sequence; namespace Ui { @@ -19,7 +19,7 @@ public: explicit NewSequenceDialog(QWidget *parent = 0); ~NewSequenceDialog(); Sequence* existing_sequence; - QTreeWidgetItem* existing_item; + Media* existing_item; void set_sequence_name(const QString& s); protected: diff --git a/dialogs/replaceclipmediadialog.cpp b/dialogs/replaceclipmediadialog.cpp index 45092104f..e8732164f 100644 --- a/dialogs/replaceclipmediadialog.cpp +++ b/dialogs/replaceclipmediadialog.cpp @@ -8,17 +8,18 @@ #include "project/clip.h" #include "playback/playback.h" #include "playback/cacher.h" -#include "io/media.h" +#include "project/footage.h" #include "project/undo.h" +#include "project/media.h" #include -#include +#include #include #include #include #include -ReplaceClipMediaDialog::ReplaceClipMediaDialog(QWidget *parent, SourceTable *table, QTreeWidgetItem *old_media) : +ReplaceClipMediaDialog::ReplaceClipMediaDialog(QWidget *parent, SourceTable *table, Media *old_media) : QDialog(parent), source_table(table), media(old_media) @@ -29,12 +30,11 @@ ReplaceClipMediaDialog::ReplaceClipMediaDialog(QWidget *parent, SourceTable *tab layout->addWidget(new QLabel("Select which media you want to replace this media's clips with:")); - tree = new QTreeWidget(); - tree->setHeaderHidden(true); + tree = new QTreeView(); layout->addWidget(tree); - use_same_media_in_points = new QCheckBox("Keep the same media in points"); + use_same_media_in_points = new QCheckBox("Keep the same media in-points"); use_same_media_in_points->setChecked(true); layout->addWidget(use_same_media_in_points); @@ -56,36 +56,34 @@ ReplaceClipMediaDialog::ReplaceClipMediaDialog(QWidget *parent, SourceTable *tab setLayout(layout); - copy_tree(NULL, NULL); + tree->setModel(&project_model); + + //copy_tree(NULL, NULL); } void ReplaceClipMediaDialog::replace() { - if (tree->selectedItems().size() != 1) { + QModelIndexList selected_items = tree->selectionModel()->selectedRows(); + if (selected_items.size() != 1) { QMessageBox::critical(this, "No media selected", "Please select a media to replace with or click 'Cancel'.", QMessageBox::Ok); } else { - QTreeWidgetItem* selected_item = tree->selectedItems().at(0); - QTreeWidgetItem* new_item = reinterpret_cast(selected_item->data(0, Qt::UserRole + 1).value()); + Media* new_item = static_cast(selected_items.at(0).internalPointer()); if (media == new_item) { QMessageBox::critical(this, "Same media selected", "You selected the same media that you're replacing. Please select a different one or click 'Cancel'.", QMessageBox::Ok); - } else if (get_type_from_tree(new_item) == MEDIA_TYPE_FOLDER) { + } else if (new_item->get_type() == MEDIA_TYPE_FOLDER) { QMessageBox::critical(this, "Folder selected", "You cannot replace footage with a folder.", QMessageBox::Ok); } else { - if (get_type_from_tree(new_item) == MEDIA_TYPE_SEQUENCE && sequence == get_sequence_from_tree(new_item)) { + if (new_item->get_type() == MEDIA_TYPE_SEQUENCE && sequence == new_item->to_sequence()) { QMessageBox::critical(this, "Active sequence selected", "You cannot insert a sequence into itself.", QMessageBox::Ok); - } else { - void* old_media = get_media_from_tree(media); - + } else { ReplaceClipMediaCommand* rcmc = new ReplaceClipMediaCommand( - old_media, - get_media_from_tree(new_item), - get_type_from_tree(media), - get_type_from_tree(new_item), + media, + new_item, use_same_media_in_points->isChecked() ); for (int i=0;iclips.size();i++) { Clip* c = sequence->clips.at(i); - if (c != NULL && c->media == old_media) { + if (c != NULL && c->media == media) { rcmc->clips.append(c); } } @@ -98,34 +96,3 @@ void ReplaceClipMediaDialog::replace() { } } } - -void ReplaceClipMediaDialog::copy_tree(QTreeWidgetItem* parent, QTreeWidgetItem* target) { - QVector items; - - if (parent == NULL) { - for (int i=0;itopLevelItemCount();i++) { - items.append(source_table->topLevelItem(i)); - } - } else { - for (int i=0;ichildCount();i++) { - items.append(parent->child(i)); - } - } - - for (int i=0;isetText(0, original->text(0)); - item->setData(0, Qt::UserRole + 1, reinterpret_cast(original)); - - if (target == NULL) { - tree->addTopLevelItem(item); - } else { - target->addChild(item); - } - - if (original->childCount() > 0) { - copy_tree(original, item); - } - } -} diff --git a/dialogs/replaceclipmediadialog.h b/dialogs/replaceclipmediadialog.h index 8bd60e3cd..15498336b 100644 --- a/dialogs/replaceclipmediadialog.h +++ b/dialogs/replaceclipmediadialog.h @@ -4,22 +4,21 @@ #include class SourceTable; -class QTreeWidget; -class QTreeWidgetItem; +class QTreeView; +class Media; class QCheckBox; class ReplaceClipMediaDialog : public QDialog { Q_OBJECT public: - ReplaceClipMediaDialog(QWidget* parent, SourceTable* table, QTreeWidgetItem *old_media); + ReplaceClipMediaDialog(QWidget* parent, SourceTable* table, Media *old_media); private slots: void replace(); private: SourceTable* source_table; - QTreeWidget* tree; - QTreeWidgetItem* media; - QCheckBox* use_same_media_in_points; - void copy_tree(QTreeWidgetItem* parent, QTreeWidgetItem *target); + QTreeView* tree; + Media* media; + QCheckBox* use_same_media_in_points; }; #endif // REPLACECLIPMEDIADIALOG_H diff --git a/dialogs/speeddialog.cpp b/dialogs/speeddialog.cpp index 6713bad1b..0efcb45d1 100644 --- a/dialogs/speeddialog.cpp +++ b/dialogs/speeddialog.cpp @@ -10,12 +10,13 @@ #include "ui/labelslider.h" #include "project/clip.h" #include "project/sequence.h" -#include "io/media.h" +#include "project/footage.h" #include "playback/playback.h" #include "panels/panels.h" #include "panels/timeline.h" #include "project/undo.h" #include "project/effect.h" +#include "project/media.h" SpeedDialog::SpeedDialog(QWidget *parent) : QDialog(parent) { QVBoxLayout* main_layout = new QVBoxLayout(); @@ -83,14 +84,12 @@ void SpeedDialog::run() { clip_percent = c->speed; if (c->track < 0) { bool process_video = true; - if (c->media_type == MEDIA_TYPE_FOOTAGE) { - Media* m = static_cast(c->media); - if (m != NULL) { - MediaStream* ms = m->get_stream_from_file_index(true, c->media_stream); - if (ms != NULL && ms->infinite_length) { - process_video = false; - } - } + if (c->media != NULL && c->media->get_type() == MEDIA_TYPE_FOOTAGE) { + Footage* m = c->media->to_footage(); + FootageStream* ms = m->get_stream_from_file_index(true, c->media_stream); + if (ms != NULL && ms->infinite_length) { + process_video = false; + } } if (process_video) { diff --git a/effects/internal/transformeffect.cpp b/effects/internal/transformeffect.cpp index b83aef23f..4d5e4b3b2 100644 --- a/effects/internal/transformeffect.cpp +++ b/effects/internal/transformeffect.cpp @@ -12,7 +12,7 @@ #include "ui/collapsiblewidget.h" #include "project/clip.h" #include "project/sequence.h" -#include "io/media.h" +#include "project/footage.h" #include "io/math.h" #include "ui/labelslider.h" #include "ui/comboboxex.h" diff --git a/icons/error.png b/icons/error.png new file mode 100644 index 0000000000000000000000000000000000000000..34eb2de38fe4820ea0e4b779544b96fa2d042852 GIT binary patch literal 11806 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 literal 0 HcmV?d00001 diff --git a/icons/icons.qrc b/icons/icons.qrc index 5f495df07..711fdcdc0 100644 --- a/icons/icons.qrc +++ b/icons/icons.qrc @@ -48,5 +48,6 @@ record-disabled.png transition-tool.png transition-tool-disabled.png + error.png diff --git a/io/loadthread.cpp b/io/loadthread.cpp new file mode 100644 index 000000000..196a88d2c --- /dev/null +++ b/io/loadthread.cpp @@ -0,0 +1,639 @@ +#include "loadthread.h" + +#include "mainwindow.h" +#include "panels/panels.h" +#include "panels/project.h" +#include "project/footage.h" +#include "io/config.h" +#include "project/clip.h" +#include "project/sequence.h" +#include "project/transition.h" +#include "project/effect.h" +#include "playback/playback.h" +#include "ui_project.h" +#include "io/previewgenerator.h" +#include "dialogs/loaddialog.h" +#include "project/media.h" +#include "debug.h" + +#include +#include +#include + +struct TransitionData { + int id; + QString name; + long length; + Clip* otc; + Clip* ctc; +}; + +LoadThread::LoadThread(LoadDialog* l, bool a) : ld(l), autorecovery(a) { + connect(this, SIGNAL(finished()), this, SLOT(deleteLater())); + connect(this, SIGNAL(success()), this, SLOT(success_func())); +} + +const EffectMeta* get_meta_from_name(const QString& name, int type) { + for (int j=0;jtrack < 0) ? "Transform" : "Volume"; break; + case 1: effect_name = (c->track < 0) ? "Shake" : "Pan"; break; + case 2: effect_name = (c->track < 0) ? "Text" : "Noise"; break; + case 3: effect_name = (c->track < 0) ? "Solid" : "Tone"; break; + case 4: effect_name = "Invert"; break; + case 5: effect_name = "Chroma Key"; break; + case 6: effect_name = "Gaussian Blur"; break; + case 7: effect_name = "Crop"; break; + case 8: effect_name = "Flip"; break; + case 9: effect_name = "Box Blur"; break; + case 10: effect_name = "Wave"; break; + case 11: effect_name = "Temperature"; break; + } + } + + // wait for effects to be loaded + effects_loaded.lock(); + + const EffectMeta* meta = NULL; + + // find effect with this name + if (!effect_name.isEmpty()) { + meta = get_meta_from_name(effect_name, (c->track < 0) ? EFFECT_TYPE_VIDEO : EFFECT_TYPE_AUDIO); + } + + effects_loaded.unlock(); + + if (meta == NULL) { + dout << "[WARNING] An effect used by this project is missing. It was not loaded."; + } else { + QString tag = stream.name().toString(); + + if (tag == "opening" || tag == "closing") { + // TODO replace NULL/s with something else + + int transition_index = create_transition(c, NULL, meta); + Transition* t = c->sequence->transitions.at(transition_index); + if (effect_length > -1) t->set_length(effect_length); + t->set_enabled(effect_enabled); + t->load(stream); + + if (tag == "opening") { + c->opening_transition = transition_index; + } else { + c->closing_transition = transition_index; + } + } else { + Effect* e = create_effect(c, meta); + e->set_enabled(effect_enabled); + e->load(stream); + + c->effects.append(e); + } + } +} + +bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) { + f.seek(0); + stream.setDevice(stream.device()); + + QString root_search; + QString child_search; + + switch (type) { + case LOAD_TYPE_VERSION: + root_search = "version"; + break; + case LOAD_TYPE_URL: + root_search = "url"; + break; + case MEDIA_TYPE_FOLDER: + root_search = "folders"; + child_search = "folder"; + break; + case MEDIA_TYPE_FOOTAGE: + root_search = "media"; + child_search = "footage"; + break; + case MEDIA_TYPE_SEQUENCE: + root_search = "sequences"; + child_search = "sequence"; + break; + } + + show_err = true; + + while (!stream.atEnd()) { + stream.readNextStartElement(); + if (stream.name() == root_search) { + if (type == LOAD_TYPE_VERSION) { + int proj_version = stream.readElementText().toInt(); + if (proj_version < MIN_SAVE_VERSION && proj_version > SAVE_VERSION) { + if (QMessageBox::warning(mainWindow, "Version Mismatch", "This project was saved in a different version of Olive and may not be fully compatible with this version. Would you like to attempt loading it anyway?", QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) { + show_err = false; + return false; + } + } + } else if (type == LOAD_TYPE_URL) { + internal_proj_url = stream.readElementText(); + internal_proj_dir = QFileInfo(internal_proj_url).absoluteDir(); + } else { + while (!(stream.name() == root_search && stream.isEndElement())) { + stream.readNext(); + if (stream.name() == child_search && stream.isStartElement()) { + switch (type) { + case MEDIA_TYPE_FOLDER: + { + Media* folder = panel_project->new_folder(0); + folder->temp_id2 = 0; + for (int j=0;jtemp_id = attr.value().toInt(); + } else if (attr.name() == "name") { + folder->set_name(attr.value().toString()); + } else if (attr.name() == "parent") { + folder->temp_id2 = attr.value().toInt(); + } + } + loaded_folders.append(folder); + } + break; + case MEDIA_TYPE_FOOTAGE: + { + int folder = 0; + + Media* item = new Media(0); + Footage* m = new Footage(); + + m->using_inout = false; + + for (int j=0;jsave_id = attr.value().toInt(); + } else if (attr.name() == "folder") { + folder = attr.value().toInt(); + } else if (attr.name() == "name") { + m->name = attr.value().toString(); + } else if (attr.name() == "url") { + m->url = attr.value().toString(); + + if (!QFileInfo::exists(m->url)) { // if path is not absolute + QString proj_dir_test = proj_dir.absoluteFilePath(m->url); + QString internal_proj_dir_test = internal_proj_dir.absoluteFilePath(m->url); + + if (QFileInfo::exists(proj_dir_test)) { // if path is relative to the project's current dir + m->url = proj_dir_test; + dout << "[INFO] Matched" << attr.value().toString() << "relative to project's current directory"; + } else if (QFileInfo::exists(internal_proj_dir_test)) { // if path is relative to the last directory the project was saved in + m->url = internal_proj_dir_test; + dout << "[INFO] Matched" << attr.value().toString() << "relative to project's internal directory"; + } else if (m->url.contains('%')) { + // hack for image sequences (qt won't be able to find the URL with %, but ffmpeg may) + m->url = proj_dir_test; + dout << "[INFO] Guess image sequence" << attr.value().toString() << "path to project's current directory"; + } else { + dout << "[INFO] Failed to match" << attr.value().toString() << "to file"; + } + } else { + dout << "[INFO] Matched" << attr.value().toString() << "with absolute path"; + } + } else if (attr.name() == "duration") { + m->length = attr.value().toLongLong(); + } else if (attr.name() == "using_inout") { + m->using_inout = (attr.value() == "1"); + } else if (attr.name() == "in") { + m->in = attr.value().toLong(); + } else if (attr.name() == "out") { + m->out = attr.value().toLong(); + } + } + + item->set_footage(m); + + if (folder == 0) { + project_model.addTopLevelItem(item); + } else { + find_loaded_folder_by_id(folder)->appendChild(item); + } + + // analyze media to see if it's the same + loaded_media_items.append(item); + } + break; + case MEDIA_TYPE_SEQUENCE: + { + Media* parent = NULL; + Sequence* s = new Sequence(); + + // load attributes about sequence + for (int j=0;jname = attr.value().toString(); + } else if (attr.name() == "folder") { + int folder = attr.value().toInt(); + if (folder > 0) parent = find_loaded_folder_by_id(folder); + } else if (attr.name() == "id") { + s->save_id = attr.value().toInt(); + } else if (attr.name() == "width") { + s->width = attr.value().toInt(); + } else if (attr.name() == "height") { + s->height = attr.value().toInt(); + } else if (attr.name() == "framerate") { + s->frame_rate = attr.value().toDouble(); + } else if (attr.name() == "afreq") { + s->audio_frequency = attr.value().toInt(); + } else if (attr.name() == "alayout") { + s->audio_layout = attr.value().toInt(); + } else if (attr.name() == "open") { + open_seq = s; + } else if (attr.name() == "workarea") { + s->using_workarea = (attr.value() == "1"); + } else if (attr.name() == "workareaIn") { + s->workarea_in = attr.value().toLong(); + } else if (attr.name() == "workareaOut") { + s->workarea_out = attr.value().toLong(); + } + } + + QVector transition_data; + + // load all clips and clip information + while (!(stream.name() == child_search && stream.isEndElement()) && !stream.atEnd()) { + stream.readNextStartElement(); + if (stream.name() == "marker" && stream.isStartElement()) { + Marker m; + for (int j=0;jmarkers.append(m); + } else if (stream.name() == "transition" && stream.isStartElement()) { + TransitionData td; + td.otc = NULL; + td.ctc = NULL; + for (int j=0;jautoscale = false; + + c->media = NULL; + + for (int j=0;jname = attr.value().toString(); + } else if (attr.name() == "enabled") { + c->enabled = (attr.value() == "1"); + } else if (attr.name() == "id") { + c->load_id = attr.value().toInt(); + } else if (attr.name() == "clipin") { + c->clip_in = attr.value().toLong(); + } else if (attr.name() == "in") { + c->timeline_in = attr.value().toLong(); + } else if (attr.name() == "out") { + c->timeline_out = attr.value().toLong(); + } else if (attr.name() == "track") { + c->track = attr.value().toInt(); + } else if (attr.name() == "r") { + c->color_r = attr.value().toInt(); + } else if (attr.name() == "g") { + c->color_g = attr.value().toInt(); + } else if (attr.name() == "b") { + c->color_b = attr.value().toInt(); + } else if (attr.name() == "autoscale") { + c->autoscale = (attr.value() == "1"); + } else if (attr.name() == "media") { + media_type = MEDIA_TYPE_FOOTAGE; + media_id = attr.value().toInt(); + } else if (attr.name() == "stream") { + stream_id = attr.value().toInt(); + } else if (attr.name() == "speed") { + c->speed = attr.value().toDouble(); + } else if (attr.name() == "maintainpitch") { + c->maintain_audio_pitch = (attr.value() == "1"); + } else if (attr.name() == "reverse") { + c->reverse = (attr.value() == "1"); + } else if (attr.name() == "opening") { + c->opening_transition = attr.value().toInt(); + } else if (attr.name() == "closing") { + c->closing_transition = attr.value().toInt(); + } else if (attr.name() == "sequence") { + media_type = MEDIA_TYPE_SEQUENCE; + + // since we haven't finished loading sequences, we defer linking this until later + c->media = NULL; + c->media_stream = attr.value().toInt(); + loaded_clips.append(c); + } + } + + // set media and media stream + switch (media_type) { + case MEDIA_TYPE_FOOTAGE: + if (media_id >= 0) { + for (int j=0;jto_footage(); + if (m->save_id == media_id) { + c->media = loaded_media_items.at(j); + c->media_stream = stream_id; + break; + } + } + } + break; + } + + // load links and effects + while (!(stream.name() == "clip" && stream.isEndElement()) && !stream.atEnd()) { + stream.readNext(); + if (stream.isStartElement()) { + if (stream.name() == "linked") { + while (!(stream.name() == "linked" && stream.isEndElement()) && !stream.atEnd()) { + stream.readNext(); + if (stream.name() == "link" && stream.isStartElement()) { + for (int k=0;klinked.append(link_attr.value().toInt()); + break; + } + } + } + } + } else if (stream.isStartElement() && (stream.name() == "effect" || stream.name() == "opening" || stream.name() == "closing")) { + // "opening" and "closing" are backwards compatibility code + load_effect(stream, c); + } + } + } + + s->clips.append(c); + } + } + + // correct links, clip IDs, transitions + for (int i=0;iclips.size();i++) { + // correct links + Clip* correct_clip = s->clips.at(i); + for (int j=0;jlinked.size();j++) { + bool found = false; + for (int k=0;kclips.size();k++) { + if (s->clips.at(k)->load_id == correct_clip->linked.at(j)) { + correct_clip->linked[j] = k; + found = true; + break; + } + } + if (!found) { + correct_clip->linked.removeAt(j); + j--; + if (QMessageBox::warning(mainWindow, "Invalid Clip Link", "This project contains an invalid clip link. It may be corrupt. Would you like to continue loading it?", QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) { + delete s; + return false; + } + } + } + + // re-link clips to transitions + if (correct_clip->opening_transition > -1) { + for (int j=0;jopening_transition) { + transition_data[j].otc = correct_clip; + } + } + } + if (correct_clip->closing_transition > -1) { + for (int j=0;jclosing_transition) { + transition_data[j].ctc = correct_clip; + } + } + } + } + + // create transitions + for (int i=0;itrack < 0) ? EFFECT_TYPE_VIDEO : EFFECT_TYPE_AUDIO); + if (meta == NULL) { + dout << "[WARNING] Failed to link transition with name:" << td.name; + if (td.otc != NULL) td.otc->opening_transition = -1; + if (td.ctc != NULL) td.ctc->closing_transition = -1; + } else { + int transition_index = create_transition(primary, secondary, meta); + primary->sequence->transitions.at(transition_index)->set_length(td.length); + if (td.otc != NULL) td.otc->opening_transition = transition_index; + if (td.ctc != NULL) td.ctc->closing_transition = transition_index; + } + } + } + + Media* m = panel_project->new_sequence(NULL, s, false, parent); + + loaded_sequences.append(m); + } + break; + } + } + } + } + break; + } + } + return true; +} + +Media* LoadThread::find_loaded_folder_by_id(int id) { + for (int j=0;jtemp_id == id) { + return parent_item; + } + } + return NULL; +} + +void LoadThread::run() { + QFile file(project_url); + if (!file.open(QIODevice::ReadOnly)) { + dout << "[ERROR] Could not open file"; + return; + } + + /* set up directories to search for media + * most of the time, these will be the same but in + * case the project file has moved without the footage, + * we check both + */ + proj_dir = QFileInfo(project_url).absoluteDir(); + internal_proj_dir = QFileInfo(project_url).absoluteDir(); + internal_proj_url = project_url; + + QXmlStreamReader stream(&file); + + bool cont = false; + error_str.clear(); + show_err = true; + + // temp variables for loading (unnecessary?) + open_seq = NULL; + loaded_folders.clear(); + loaded_media_items.clear(); + loaded_clips.clear(); + loaded_sequences.clear(); + + // get "element" count + int element_count = 0; + while (!stream.atEnd()) { + stream.readNextStartElement(); + if (stream.name() == "folder" + || stream.name() == "footage" + || stream.name() == "sequence" + || stream.name() == "clip" + || stream.name() == "effect") { + element_count++; + } + } + + // find project file version + cont = load_worker(file, stream, LOAD_TYPE_VERSION); + + // find project's internal URL + cont = load_worker(file, stream, LOAD_TYPE_URL); + if (autorecovery) { + QString orig_filename = internal_proj_url; + int insert_index = internal_proj_url.lastIndexOf(".ove", -1, Qt::CaseInsensitive); + if (insert_index == -1) insert_index = internal_proj_url.length(); + int counter = 1; + while (QFileInfo::exists(orig_filename)) { + orig_filename = internal_proj_url; + QString recover_text = "recovered"; + if (counter > 1) { + recover_text += " " + QString::number(counter); + } + orig_filename.insert(insert_index, " (" + recover_text + ")"); + counter++; + } + mainWindow->updateTitle(orig_filename); + } + + // load folders first + if (cont) { + cont = load_worker(file, stream, MEDIA_TYPE_FOLDER); + } + + // load media + if (cont) { + // since folders loaded correctly, organize them appropriately + for (int i=0;itemp_id2; + if (parent > 0) { + find_loaded_folder_by_id(parent)->appendChild(folder); + } else { + project_model.addTopLevelItem(folder); + } + } + + cont = load_worker(file, stream, MEDIA_TYPE_FOOTAGE); + } + + // load sequences + if (cont) { + cont = load_worker(file, stream, MEDIA_TYPE_SEQUENCE); + } + + // attach nested sequence clips to their sequences + for (int i=0;imedia == NULL && loaded_clips.at(i)->media_stream == loaded_sequences.at(j)->to_sequence()->save_id) { + loaded_clips.at(i)->media = loaded_sequences.at(j); + loaded_clips.at(i)->refresh(); + break; + } + } + } + + if (!cont) { + if (show_err) QMessageBox::critical(mainWindow, "Project Load Error", "Error loading project: " + error_str, QMessageBox::Ok); + } else if (stream.hasError()) { + dout << "[ERROR] Error parsing XML." << stream.errorString(); + QMessageBox::critical(mainWindow, "XML Parsing Error", "Couldn't load '" + project_url + "'. " + stream.errorString(), QMessageBox::Ok); + cont = false; + } + + if (cont) { + mainWindow->setWindowModified(autorecovery); + + emit success(); // run in main thread + + for (int i=0;istart_preview_generator(loaded_media_items.at(i), true); + } + } else { + panel_project->new_project(); + } + + panel_project->add_recent_project(project_url); + + file.close(); +} + +void LoadThread::success_func() { + if (open_seq != NULL) set_sequence(open_seq); + update_ui(false); +} diff --git a/io/loadthread.h b/io/loadthread.h new file mode 100644 index 000000000..77bd0ca5f --- /dev/null +++ b/io/loadthread.h @@ -0,0 +1,43 @@ +#ifndef LOADTHREAD_H +#define LOADTHREAD_H + +#include +#include +#include + +class Media; +class Footage; +class Clip; +class Sequence; +class LoadDialog; + +class LoadThread : public QThread +{ + Q_OBJECT +public: + LoadThread(LoadDialog* l, bool a); + void run(); +signals: + void success(); +private slots: + void success_func(); +private: + LoadDialog* ld; + bool autorecovery; + + bool load_worker(QFile& f, QXmlStreamReader& stream, int type); + Sequence* open_seq; + QVector loaded_media_items; + QDir proj_dir; + QDir internal_proj_dir; + QString internal_proj_url; + bool show_err; + QString error_str; + + QVector loaded_folders; + QVector loaded_clips; + QVector loaded_sequences; + Media* find_loaded_folder_by_id(int id); +}; + +#endif // LOADTHREAD_H diff --git a/io/previewgenerator.cpp b/io/previewgenerator.cpp index 7b8934fdd..83fc426e6 100644 --- a/io/previewgenerator.cpp +++ b/io/previewgenerator.cpp @@ -1,6 +1,7 @@ #include "previewgenerator.h" -#include "media.h" +#include "project/media.h" +#include "project/footage.h" #include "panels/viewer.h" #include "panels/project.h" #include "io/config.h" @@ -28,11 +29,11 @@ extern "C" { QSemaphore sem(5); // only 5 preview generators can run at one time -PreviewGenerator::PreviewGenerator(QTreeWidgetItem* i, Media* m, bool r) : +PreviewGenerator::PreviewGenerator(Media* i, Footage* m, bool r) : QThread(0), fmt_ctx(NULL), - item(i), - media(m), + media(i), + footage(m), retrieve_duration(false), contains_still_image(false), replace(r), @@ -52,12 +53,12 @@ void PreviewGenerator::parse_media() { for (int i=0;i<(int)fmt_ctx->nb_streams;i++) { // Find the decoder for the video stream if (avcodec_find_decoder(fmt_ctx->streams[i]->codecpar->codec_id) == NULL) { - dout << "[ERROR] Unsupported codec in stream" << i << "of file" << media->name; + dout << "[ERROR] Unsupported codec in stream" << i << "of file" << footage->name; } else { - MediaStream* ms = media->get_stream_from_file_index(fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO, i); + FootageStream* ms = footage->get_stream_from_file_index(fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO, i); bool append = false; if (ms == NULL) { - ms = new MediaStream(); + ms = new FootageStream(); ms->preview_done = false; ms->file_index = i; append = true; @@ -93,18 +94,18 @@ void PreviewGenerator::parse_media() { ms->video_auto_interlacing = VIDEO_PROGRESSIVE; ms->video_interlacing = VIDEO_PROGRESSIVE; - if (append) media->video_tracks.append(ms); + if (append) footage->video_tracks.append(ms); } else if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { ms->audio_channels = fmt_ctx->streams[i]->codecpar->channels; ms->audio_layout = fmt_ctx->streams[i]->codecpar->channel_layout; ms->audio_frequency = fmt_ctx->streams[i]->codecpar->sample_rate; - if (append) media->audio_tracks.append(ms); + if (append) footage->audio_tracks.append(ms); } else if (append) { delete ms; } } } - media->length = fmt_ctx->duration; + footage->length = fmt_ctx->duration; if (fmt_ctx->duration == INT64_MIN) { retrieve_duration = true; @@ -116,28 +117,29 @@ void PreviewGenerator::parse_media() { bool PreviewGenerator::retrieve_preview(const QString& hash) { // returns true if generate_waveform must be run, false if we got all previews from cached files if (retrieve_duration) { + //dout << "[NOTE] " << media->name << "needs to retrieve duration"; return true; } bool found = true; - for (int i=0;ivideo_tracks.size();i++) { - MediaStream* ms = media->video_tracks.at(i); + for (int i=0;ivideo_tracks.size();i++) { + FootageStream* ms = footage->video_tracks.at(i); QString thumb_path = get_thumbnail_path(hash, ms); QFile f(thumb_path); if (f.exists() && ms->video_preview.load(thumb_path)) { - //dout << "loaded thumb" << ms->file_index << "from" << thumb_path; + dout << "loaded thumb" << ms->file_index << "from" << thumb_path; ms->preview_done = true; } else { found = false; break; } } - for (int i=0;iaudio_tracks.size();i++) { - MediaStream* ms = media->audio_tracks.at(i); + for (int i=0;iaudio_tracks.size();i++) { + FootageStream* ms = footage->audio_tracks.at(i); QString waveform_path = get_waveform_path(hash, ms); QFile f(waveform_path); if (f.exists()) { - //dout << "loaded wave" << ms->file_index << "from" << waveform_path; + dout << "loaded wave" << ms->file_index << "from" << waveform_path; f.open(QFile::ReadOnly); QByteArray data = f.readAll(); ms->audio_preview.resize(data.size()); @@ -153,12 +155,12 @@ bool PreviewGenerator::retrieve_preview(const QString& hash) { } } if (!found) { - for (int i=0;ivideo_tracks.size();i++) { - MediaStream* ms = media->video_tracks.at(i); + for (int i=0;ivideo_tracks.size();i++) { + FootageStream* ms = footage->video_tracks.at(i); ms->preview_done = false; } - for (int i=0;iaudio_tracks.size();i++) { - MediaStream* ms = media->audio_tracks.at(i); + for (int i=0;iaudio_tracks.size();i++) { + FootageStream* ms = footage->audio_tracks.at(i); ms->audio_preview.clear(); ms->preview_done = false; } @@ -167,11 +169,11 @@ bool PreviewGenerator::retrieve_preview(const QString& hash) { } void PreviewGenerator::finalize_media() { - media->ready_lock.unlock(); - media->ready = true; + footage->ready_lock.unlock(); + footage->ready = true; if (!cancelled) { - if (media->video_tracks.size() == 0) { + if (footage->video_tracks.size() == 0) { emit set_icon(ICON_TYPE_AUDIO, replace); } else if (contains_still_image) { emit set_icon(ICON_TYPE_IMAGE, replace); @@ -179,7 +181,7 @@ void PreviewGenerator::finalize_media() { emit set_icon(ICON_TYPE_VIDEO, replace); } - if (!contains_still_image || media->audio_tracks.size() > 0) { + /*if (!contains_still_image || media->audio_tracks.size() > 0) { double frame_rate = 30; if (!contains_still_image && media->video_tracks.size() > 0) frame_rate = media->video_tracks.at(0)->video_frame_rate; item->setText(1, frame_to_timecode(media->get_length_in_frames(frame_rate), config.timecode_view, frame_rate)); @@ -189,7 +191,7 @@ void PreviewGenerator::finalize_media() { } else { item->setText(2, QString::number(media->audio_tracks.at(0)->audio_frequency) + " Hz"); } - } + }*/ } } @@ -229,6 +231,9 @@ void PreviewGenerator::generate_waveform() { while (codec_ctx[packet->stream_index] == NULL || avcodec_receive_frame(codec_ctx[packet->stream_index], temp_frame) == AVERROR(EAGAIN)) { av_packet_unref(packet); int read_ret = av_read_frame(fmt_ctx, packet); + + //dout << "read frame for" << footage->name << footage->url << read_ret << "retrieve_duration:" << retrieve_duration << "eof:" << end_of_file << "packet pts:" << packet->pts; + if (read_ret < 0) { end_of_file = true; if (read_ret != AVERROR_EOF) dout << "[ERROR] Failed to read packet for preview generation" << read_ret; @@ -244,7 +249,7 @@ void PreviewGenerator::generate_waveform() { } } if (!end_of_file) { - MediaStream* s = media->get_stream_from_file_index(fmt_ctx->streams[packet->stream_index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO, packet->stream_index); + FootageStream* s = footage->get_stream_from_file_index(fmt_ctx->streams[packet->stream_index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO, packet->stream_index); if (s != NULL) { if (fmt_ctx->streams[packet->stream_index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { if (!s->preview_done) { @@ -349,10 +354,10 @@ void PreviewGenerator::generate_waveform() { // check if we've got all our previews if (retrieve_duration) { done = false; - } else if (media->audio_tracks.size() == 0) { + } else if (footage->audio_tracks.size() == 0) { done = true; - for (int i=0;ivideo_tracks.size();i++) { - if (!media->video_tracks.at(i)->preview_done) { + for (int i=0;ivideo_tracks.size();i++) { + if (!footage->video_tracks.at(i)->preview_done) { done = false; break; } @@ -365,8 +370,8 @@ void PreviewGenerator::generate_waveform() { av_packet_unref(packet); } } - for (int i=0;iaudio_tracks.size();i++) { - media->audio_tracks.at(i)->preview_done = true; + for (int i=0;iaudio_tracks.size();i++) { + footage->audio_tracks.at(i)->preview_done = true; } av_frame_free(&temp_frame); av_packet_free(&packet); @@ -376,33 +381,33 @@ void PreviewGenerator::generate_waveform() { } } if (retrieve_duration) { - media->length = 0; + footage->length = 0; int maximum_stream = 0; for (unsigned int i=0;inb_streams;i++) { if (media_lengths[i] > media_lengths[maximum_stream]) { maximum_stream = i; } } - media->length = (double) media_lengths[maximum_stream] / av_q2d(fmt_ctx->streams[maximum_stream]->avg_frame_rate) * AV_TIME_BASE; // TODO redo with PTS + footage->length = (double) media_lengths[maximum_stream] / av_q2d(fmt_ctx->streams[maximum_stream]->avg_frame_rate) * AV_TIME_BASE; // TODO redo with PTS finalize_media(); } delete [] media_lengths; delete [] codec_ctx; } -QString PreviewGenerator::get_thumbnail_path(const QString& hash, MediaStream* ms) { +QString PreviewGenerator::get_thumbnail_path(const QString& hash, FootageStream* ms) { return data_path + "/" + hash + "t" + QString::number(ms->file_index); } -QString PreviewGenerator::get_waveform_path(const QString& hash, MediaStream* ms) { +QString PreviewGenerator::get_waveform_path(const QString& hash, FootageStream* ms) { return data_path + "/" + hash + "w" + QString::number(ms->file_index); } void PreviewGenerator::run() { + Q_ASSERT(footage != NULL); Q_ASSERT(media != NULL); - Q_ASSERT(item != NULL); - QByteArray ba = media->url.toLatin1(); + QByteArray ba = footage->url.toLatin1(); char* filename = new char[ba.size()+1]; strcpy(filename, ba.data()); @@ -423,48 +428,51 @@ void PreviewGenerator::run() { error = true; } else { av_dump_format(fmt_ctx, 0, filename, 0); - parse_media(); - sem.acquire(); + parse_media(); // see if we already have data for this - QFileInfo file_info(media->url); - QString cache_file = media->url + QString::number(file_info.lastModified().toMSecsSinceEpoch()); + QFileInfo file_info(footage->url); + QString cache_file = footage->url.mid(footage->url.lastIndexOf('/')+1) + QString::number(file_info.size()) + QString::number(file_info.lastModified().toMSecsSinceEpoch()); + dout << "using hash" << cache_file; QString hash = QCryptographicHash::hash(cache_file.toLatin1(), QCryptographicHash::Md5).toHex(); if (retrieve_preview(hash)) { + sem.acquire(); + generate_waveform(); // save preview to file - for (int i=0;ivideo_tracks.size();i++) { - MediaStream* ms = media->video_tracks.at(i); + for (int i=0;ivideo_tracks.size();i++) { + FootageStream* ms = footage->video_tracks.at(i); ms->video_preview.save(get_thumbnail_path(hash, ms), "PNG"); + dout << "saved" << ms->file_index << "thumbnail to" << get_thumbnail_path(hash, ms); } - for (int i=0;iaudio_tracks.size();i++) { - MediaStream* ms = media->audio_tracks.at(i); + for (int i=0;iaudio_tracks.size();i++) { + FootageStream* ms = footage->audio_tracks.at(i); QFile f(get_waveform_path(hash, ms)); f.open(QFile::WriteOnly); f.write(ms->audio_preview.constData(), ms->audio_preview.size()); f.close(); - //dout << "saved" << ms->file_index << "waveform to" << get_waveform_path(hash, ms); + dout << "saved" << ms->file_index << "waveform to" << get_waveform_path(hash, ms); } - } - sem.release(); + sem.release(); + } } avformat_close_input(&fmt_ctx); } - if (error) { - update_footage_tooltip(item, media, errorStr); + if (error) { + media->update_tooltip(errorStr); emit set_icon(ICON_TYPE_ERROR, replace); - media->invalid = true; - media->ready_lock.unlock(); + footage->invalid = true; + footage->ready_lock.unlock(); } else { - update_footage_tooltip(item, media); + media->update_tooltip(); } delete [] filename; - media->preview_gen = NULL; + footage->preview_gen = NULL; } void PreviewGenerator::cancel() { diff --git a/io/previewgenerator.h b/io/previewgenerator.h index b1dd512f5..2f1f86c7d 100644 --- a/io/previewgenerator.h +++ b/io/previewgenerator.h @@ -2,22 +2,23 @@ #define PREVIEWGENERATOR_H #include +#include #define ICON_TYPE_VIDEO 0 #define ICON_TYPE_AUDIO 1 #define ICON_TYPE_IMAGE 2 #define ICON_TYPE_ERROR 3 -struct Media; -struct MediaStream; +struct Footage; +struct FootageStream; struct AVFormatContext; -class QTreeWidgetItem; +class Media; class PreviewGenerator : public QThread { Q_OBJECT public: - PreviewGenerator(QTreeWidgetItem*, Media*, bool); + PreviewGenerator(Media*, Footage*, bool); void run(); void cancel(); signals: @@ -28,15 +29,15 @@ private: void generate_waveform(); void finalize_media(); AVFormatContext* fmt_ctx; - QTreeWidgetItem* item; Media* media; + Footage* footage; bool retrieve_duration; bool contains_still_image; bool replace; bool cancelled; QString data_path; - QString get_thumbnail_path(const QString &hash, MediaStream* ms); - QString get_waveform_path(const QString& hash, MediaStream* ms); + QString get_thumbnail_path(const QString &hash, FootageStream* ms); + QString get_waveform_path(const QString& hash, FootageStream* ms); }; #endif // PREVIEWGENERATOR_H diff --git a/mainwindow.cpp b/mainwindow.cpp index b2a3005df..e404b7d67 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -3,11 +3,12 @@ #include "io/config.h" #include "io/path.h" -#include "io/media.h" +#include "project/footage.h" #include "project/sequence.h" #include "project/clip.h" #include "project/undo.h" +#include "project/media.h" #include "ui/sourcetable.h" #include "ui/viewerwidget.h" @@ -222,9 +223,6 @@ MainWindow::MainWindow(QWidget *parent) : } MainWindow::~MainWindow() { - panel_sequence_viewer->viewer_widget->delete_function(); - panel_footage_viewer->viewer_widget->delete_function(); - set_sequence(NULL); QString data_dir = get_data_path(); @@ -977,12 +975,11 @@ void MainWindow::on_actionNest_triggered() { } // add sequence to project - panel_project->new_sequence(ca, s, false, NULL); + Media* m = panel_project->new_sequence(ca, s, false, NULL); // add nested sequence to active sequence - QVector media_list = {s}; - QVector type_list = {MEDIA_TYPE_SEQUENCE}; - panel_timeline->create_ghosts_from_media(sequence, earliest_point, media_list, type_list); + QVector media_list = {m}; + panel_timeline->create_ghosts_from_media(sequence, earliest_point, media_list); panel_timeline->add_clips_from_ghosts(ca, sequence); undo_stack.push(ca); diff --git a/olive.pro b/olive.pro index 52a855dab..897f652a5 100644 --- a/olive.pro +++ b/olive.pro @@ -33,7 +33,6 @@ SOURCES += \ ui/sourcetable.cpp \ dialogs/aboutdialog.cpp \ ui/timelinewidget.cpp \ - io/media.cpp \ project/sequence.cpp \ project/clip.cpp \ playback/playback.cpp \ @@ -91,7 +90,11 @@ SOURCES += \ project/effectfield.cpp \ effects/internal/cubetransition.cpp \ project/effectgizmo.cpp \ - io/clipboard.cpp + io/clipboard.cpp \ + io/loadthread.cpp \ + project/footage.cpp \ + project/media.cpp \ + project/projectmodel.cpp HEADERS += \ mainwindow.h \ @@ -102,7 +105,6 @@ HEADERS += \ ui/sourcetable.h \ dialogs/aboutdialog.h \ ui/timelinewidget.h \ - io/media.h \ project/sequence.h \ project/clip.h \ playback/playback.h \ @@ -162,7 +164,11 @@ HEADERS += \ project/effectfield.h \ effects/internal/cubetransition.h \ project/effectgizmo.h \ - io/clipboard.h + io/clipboard.h \ + io/loadthread.h \ + project/footage.h \ + project/media.h \ + project/projectmodel.h FORMS += \ mainwindow.ui \ diff --git a/panels/panels.cpp b/panels/panels.cpp index 94f568470..17916c24c 100644 --- a/panels/panels.cpp +++ b/panels/panels.cpp @@ -3,6 +3,7 @@ #include "timeline.h" #include "effectcontrols.h" #include "viewer.h" +#include "project.h" #include "project/sequence.h" #include "project/clip.h" #include "project/transition.h" @@ -100,6 +101,7 @@ void update_ui(bool modified) { if (modified) { update_effect_controls(); } + project_model.update_data(); panel_effect_controls->update_keyframes(); panel_timeline->repaint_timeline(); panel_sequence_viewer->update_viewer(); diff --git a/panels/project.cpp b/panels/project.cpp index 3e220d458..5349240ab 100644 --- a/panels/project.cpp +++ b/panels/project.cpp @@ -1,6 +1,6 @@ -#include "project.h" +#include "project.h" #include "ui_project.h" -#include "io/media.h" +#include "project/footage.h" #include "panels/panels.h" #include "panels/timeline.h" @@ -22,6 +22,7 @@ #include "dialogs/mediapropertiesdialog.h" #include "dialogs/loaddialog.h" #include "io/clipboard.h" +#include "project/media.h" #include "debug.h" #include @@ -43,6 +44,8 @@ extern "C" { #define MAXIMUM_RECENT_PROJECTS 10 +ProjectModel project_model; + QString autorecovery_filename; QString project_url = ""; QStringList recent_projects; @@ -53,8 +56,13 @@ Project::Project(QWidget *parent) : ui(new Ui::Project) { ui->setupUi(this); - source_table = ui->treeWidget; - connect(ui->treeWidget, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(rename_media(QTreeWidgetItem*,int))); + source_table = ui->treeView; + + source_table->setModel(&project_model); + connect(source_table, SIGNAL(itemChanged(Media*,int)), this, SLOT(rename_media(Media*,int))); + + /*Media* m = new Media(project_model.get_root()); + project_model.get_root()->appendChild(m);*/ } Project::~Project() { @@ -74,8 +82,8 @@ QString Project::get_next_sequence_name(QString start) { name += "0"; } name += QString::number(n); - for (int i=0;itreeWidget->topLevelItemCount();i++) { - if (QString::compare(ui->treeWidget->topLevelItem(i)->text(0), name, Qt::CaseInsensitive) == 0) { + for (int i=0;iget_name(), name, Qt::CaseInsensitive) == 0) { found = true; n++; break; @@ -85,7 +93,7 @@ QString Project::get_next_sequence_name(QString start) { return name; } -Sequence* create_sequence_from_media(QVector& media_list, QVector& type_list) { +Sequence* create_sequence_from_media(QVector& media_list) { Sequence* s = new Sequence(); s->name = panel_project->get_next_sequence_name(); @@ -100,14 +108,15 @@ Sequence* create_sequence_from_media(QVector& media_list, QVector& t bool got_video_values = false; bool got_audio_values = false; for (int i=0;iget_type()) { case MEDIA_TYPE_FOOTAGE: { - Media* m = static_cast(media_list.at(i)); + Footage* m = media->to_footage(); if (m->ready) { if (!got_video_values) { for (int j=0;jvideo_tracks.size();j++) { - MediaStream* ms = m->video_tracks.at(j); + FootageStream* ms = m->video_tracks.at(j); s->width = ms->video_width; s->height = ms->video_height; if (ms->video_frame_rate != 0) { @@ -123,7 +132,7 @@ Sequence* create_sequence_from_media(QVector& media_list, QVector& t } if (!got_audio_values) { for (int j=0;jaudio_tracks.size();j++) { - MediaStream* ms = m->audio_tracks.at(j); + FootageStream* ms = m->audio_tracks.at(j); s->audio_frequency = ms->audio_frequency; got_audio_values = true; break; @@ -134,7 +143,7 @@ Sequence* create_sequence_from_media(QVector& media_list, QVector& t break; case MEDIA_TYPE_SEQUENCE: { - Sequence* seq = static_cast(media_list.at(i)); + Sequence* seq = media->to_sequence(); s->width = seq->width; s->height = seq->height; s->frame_rate = seq->frame_rate; @@ -152,23 +161,22 @@ Sequence* create_sequence_from_media(QVector& media_list, QVector& t return s; } -void Project::rename_media(QTreeWidgetItem* item, int column) { - int type = get_type_from_tree(item); - QString n = item->text(column); - switch (type) { - case MEDIA_TYPE_FOOTAGE: get_footage_from_tree(item)->name = n; break; - case MEDIA_TYPE_SEQUENCE: get_sequence_from_tree(item)->name = n; break; +void Project::rename_media(Media* item, int column) { + QString n = "rename function"; + switch (item->get_type()) { + case MEDIA_TYPE_FOOTAGE: item->to_footage()->name = n; break; + case MEDIA_TYPE_SEQUENCE: item->to_sequence()->name = n; break; } } void Project::duplicate_selected() { - QList items = ui->treeWidget->selectedItems(); + QModelIndexList items = source_table->selectionModel()->selectedRows(); bool duped = false; ComboAction* ca = new ComboAction(); for (int j=0;jcopy(), false, i->parent()); + Media* i = static_cast(items.at(j).internalPointer()); + if (i->get_type() == MEDIA_TYPE_SEQUENCE) { + new_sequence(ca, i->to_sequence()->copy(), false, static_cast(items.at(j).parent().internalPointer())); duped = true; } } @@ -180,18 +188,18 @@ void Project::duplicate_selected() { } void Project::replace_selected_file() { - QList selected_items = ui->treeWidget->selectedItems(); + QModelIndexList selected_items = source_table->selectionModel()->selectedRows(); if (selected_items.size() == 1) { - QTreeWidgetItem* item = selected_items.at(0); - if (get_type_from_tree(item) == MEDIA_TYPE_FOOTAGE) { + Media* item = static_cast(selected_items.at(0).internalPointer()); + if (item->get_type() == MEDIA_TYPE_FOOTAGE) { replace_media(item, 0); } } } -void Project::replace_media(QTreeWidgetItem* item, QString filename) { +void Project::replace_media(Media* item, QString filename) { if (filename.isEmpty()) { - filename = QFileDialog::getOpenFileName(this, "Replace '" + item->text(0) + "'", "", "All Files (*)"); + filename = QFileDialog::getOpenFileName(this, "Replace '" + item->get_name() + "'", "", "All Files (*)"); } if (!filename.isEmpty()) { ReplaceMediaCommand* rmc = new ReplaceMediaCommand(item, filename); @@ -202,45 +210,46 @@ void Project::replace_media(QTreeWidgetItem* item, QString filename) { void Project::replace_clip_media() { if (sequence == NULL) { QMessageBox::critical(this, "No active sequence", "No sequence is active, please open the sequence you want to replace clips from.", QMessageBox::Ok); - } else if (ui->treeWidget->selectedItems().size() == 1) { - QTreeWidgetItem* item = ui->treeWidget->selectedItems().at(0); - if (get_type_from_tree(item) == MEDIA_TYPE_SEQUENCE && sequence == get_sequence_from_tree(item)) { - QMessageBox::critical(this, "Active sequence selected", "You cannot insert a sequence into itself, so no clips of this media would be in this sequence.", QMessageBox::Ok); - } else { - ReplaceClipMediaDialog dialog(this, ui->treeWidget, item); - dialog.exec(); - } + } else { + QModelIndexList selected_items = source_table->selectionModel()->selectedRows(); + if (selected_items.size() == 1) { + Media* item = static_cast(selected_items.at(0).internalPointer()); + if (item->get_type() == MEDIA_TYPE_SEQUENCE && sequence == item->to_sequence()) { + QMessageBox::critical(this, "Active sequence selected", "You cannot insert a sequence into itself, so no clips of this media would be in this sequence.", QMessageBox::Ok); + } else { + ReplaceClipMediaDialog dialog(this, source_table, item); + dialog.exec(); + } + } } } void Project::open_properties() { - if (ui->treeWidget->selectedItems().size() == 1) { - QTreeWidgetItem* item = ui->treeWidget->selectedItems().at(0); - switch (get_type_from_tree(item)) { + QModelIndexList selected_items = source_table->selectionModel()->selectedRows(); + if (selected_items.size() == 1) { + Media* item = static_cast(selected_items.at(0).internalPointer()); + switch (item->get_type()) { case MEDIA_TYPE_FOOTAGE: { - MediaPropertiesDialog mpd(this, item, get_footage_from_tree(item)); + MediaPropertiesDialog mpd(this, item); mpd.exec(); } break; case MEDIA_TYPE_SEQUENCE: { NewSequenceDialog nsd(this); - Sequence* s = get_sequence_from_tree(item); + Sequence* s = item->to_sequence(); nsd.existing_sequence = s; - nsd.existing_item = item; + nsd.existing_item = item; nsd.exec(); } break; default: { // fall back to renaming - QString new_name = QInputDialog::getText(this, "Rename '" + item->text(0) + "'", "Enter new name:", QLineEdit::Normal, item->text(0)); - if (!new_name.isEmpty()) { - MediaRename* mr = new MediaRename(); - mr->from = item->text(0); - mr->item = item; - mr->to = new_name; + QString new_name = QInputDialog::getText(this, "Rename '" + item->get_name() + "'", "Enter new name:", QLineEdit::Normal, item->get_name()); + if (!new_name.isEmpty()) { + MediaRename* mr = new MediaRename(item, new_name); undo_stack.push(mr); } } @@ -248,70 +257,55 @@ void Project::open_properties() { } } -void Project::new_sequence(ComboAction *ca, Sequence *s, bool open, QTreeWidgetItem* parent) { - QTreeWidgetItem* item = new_item(); - item->setText(0, s->name); - set_sequence_of_tree(item, s); +Media* Project::new_sequence(ComboAction *ca, Sequence *s, bool open, Media* parent) { + if (parent == NULL) parent = project_model.get_root(); + Media* item = new Media(parent); + item->set_sequence(s); if (ca != NULL) { ca->append(new NewSequenceCommand(item, parent)); if (open) ca->append(new ChangeSequenceAction(s)); } else { if (parent == NULL) { - ui->treeWidget->addTopLevelItem(item); + project_model.addTopLevelItem(item); } else { - parent->addChild(item); + parent->appendChild(item); } if (open) set_sequence(s); } -} - -void Project::start_preview_generator(QTreeWidgetItem* item, Media* media, bool replacing) { - // set up throbber animation - MediaThrobber* throbber = new MediaThrobber(item); - item->setData(0, Qt::UserRole + 5, reinterpret_cast(throbber)); - - PreviewGenerator* pg = new PreviewGenerator(item, media, replacing); - media->preview_gen = pg; - connect(pg, SIGNAL(set_icon(int, bool)), throbber, SLOT(stop(int, bool))); - pg->start(QThread::LowPriority); + return item; } QString Project::get_file_name_from_path(const QString& path) { return path.mid(path.lastIndexOf('/')+1); } -QTreeWidgetItem* Project::new_item() { - QTreeWidgetItem* item = new QTreeWidgetItem(); - item->setData(0, Qt::UserRole + 5, 0); - item->setFlags(item->flags() | Qt::ItemIsEditable); +/*Media* Project::new_item() { + Media* item = new Media(0); + //item->setFlags(item->flags() | Qt::ItemIsEditable); return item; -} +}*/ bool Project::is_focused() { - return ui->treeWidget->hasFocus(); + return source_table->hasFocus(); } -QTreeWidgetItem* Project::new_folder(QString name) { - QTreeWidgetItem* item = new_item(); - item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); - item->setText(0, (name.isEmpty()) ? "New Folder" : name); - item->setIcon(0, QIcon(":/icons/folder.png")); - set_item_to_folder(item); +Media* Project::new_folder(QString name) { + Media* item = new Media(0); + item->set_folder(); return item; } -void Project::get_all_media_from_table(QList items, QList& list, int search_type) { +void Project::get_all_media_from_table(QList items, QList& list, int search_type) { for (int i=0;i children; + Media* item = items.at(i); + if (item->get_type() == MEDIA_TYPE_FOLDER) { + QList children; for (int j=0;jchildCount();j++) { children.append(item->child(j)); } get_all_media_from_table(children, list, search_type); - } else if (search_type == type || search_type == -1) { + } else if (search_type == item->get_type() || search_type == -1) { list.append(item); } } @@ -333,7 +327,11 @@ bool delete_clips_in_clipboard_with_media(ComboAction* ca, Media* m) { void Project::delete_selected_media() { ComboAction* ca = new ComboAction(); - QList items = ui->treeWidget->selectedItems(); + QModelIndexList selected_items = source_table->selectionModel()->selectedRows(); + QList items; + for (int i=0;i(selected_items.at(i).internalPointer())); + } bool remove = true; bool redraw = false; @@ -342,25 +340,25 @@ void Project::delete_selected_media() { source_table->setSortingEnabled(true); // check if media is in use - QVector parents; - QList sequence_items; - QList all_top_level_items; - for (int i=0;itreeWidget->topLevelItemCount();i++) { - all_top_level_items.append(ui->treeWidget->topLevelItem(i)); + QVector parents; + QList sequence_items; + QList all_top_level_items; + for (int i=0;i 0) { - QList media_items; + QList media_items; get_all_media_from_table(items, media_items, MEDIA_TYPE_FOOTAGE); for (int i=0;ito_footage(); bool confirm_delete = false; for (int j=0;jto_sequence(); for (int k=0;kclips.size();k++) { Clip* c = s->clips.at(k); - if (c != NULL && c->media == media) { + if (c != NULL && c->media == item) { if (!confirm_delete) { // we found a reference, so we know we'll need to ask if the user wants to delete it QMessageBox confirm(this); @@ -377,13 +375,13 @@ void Project::delete_selected_media() { redraw = true; } else if (confirm.clickedButton() == skip_button) { // remove media item and any folders containing it from the remove list - QTreeWidgetItem* parent = item; + Media* parent = item; while (parent != NULL) { parents.append(parent); // re-add item's siblings for (int m=0;mchildCount();m++) { - QTreeWidgetItem* child = parent->child(m); + Media* child = parent->child(m); bool found = false; for (int n=0;nparent(); + parent = parent->parentItem(); } j = sequence_items.size(); @@ -417,7 +415,7 @@ void Project::delete_selected_media() { } } if (confirm_delete) { - delete_clips_in_clipboard_with_media(ca, media); + delete_clips_in_clipboard_with_media(ca, item); } } @@ -436,27 +434,27 @@ void Project::delete_selected_media() { } for (int i=0;iappend(new DeleteMediaCommand(items.at(i))); + ca->append(new DeleteMediaCommand(selected_items.at(i))); - if (get_type_from_tree(items.at(i)) == MEDIA_TYPE_SEQUENCE) { + if (items.at(i)->get_type() == MEDIA_TYPE_SEQUENCE) { redraw = true; - Sequence* s = get_sequence_from_tree(items.at(i)); + Sequence* s = items.at(i)->to_sequence(); if (s == sequence) { ca->append(new ChangeSequenceAction(NULL)); } if (s == panel_footage_viewer->seq) { - panel_footage_viewer->set_media(MEDIA_TYPE_SEQUENCE, NULL); + panel_footage_viewer->set_media(NULL); } - } else if (get_type_from_tree(items.at(i)) == MEDIA_TYPE_FOOTAGE) { + } else if (items.at(i)->get_type() == MEDIA_TYPE_FOOTAGE) { if (panel_footage_viewer->seq != NULL) { for (int j=0;jseq->clips.size();j++) { Clip* c = panel_footage_viewer->seq->clips.at(j); if (c != NULL) { - if (c->media == get_media_from_tree(items.at(i))) { - panel_footage_viewer->set_media(MEDIA_TYPE_SEQUENCE, NULL); + if (c->media == items.at(i)->to_object()) { + panel_footage_viewer->set_media(NULL); } break; } @@ -475,7 +473,20 @@ void Project::delete_selected_media() { } } -void Project::process_file_list(bool recursive, QStringList& files, QTreeWidgetItem* parent, QTreeWidgetItem* replace) { +void Project::start_preview_generator(Media* item, bool replacing) { + // set up throbber animation + MediaThrobber* throbber = new MediaThrobber(item); + throbber->moveToThread(QApplication::instance()->thread()); + item->throbber = throbber; + QMetaObject::invokeMethod(throbber, "start", Qt::QueuedConnection); + + PreviewGenerator* pg = new PreviewGenerator(item, item->to_footage(), replacing); + item->to_footage()->preview_gen = pg; + connect(pg, SIGNAL(set_icon(int, bool)), throbber, SLOT(stop(int, bool))); + pg->start(QThread::LowPriority); +} + +void Project::process_file_list(bool recursive, QStringList& files, Media* parent, Media* replace) { bool imported = false; QVector image_sequence_urls; @@ -491,7 +502,7 @@ void Project::process_file_list(bool recursive, QStringList& files, QTreeWidgetI for (int i=0;iappend(new AddMediaCommand(folder, parent)); } else { - parent->addChild(folder); + parent->appendChild(folder); } imported = true; @@ -589,34 +600,34 @@ void Project::process_file_list(bool recursive, QStringList& files, QTreeWidgetI } if (!skip) { - QTreeWidgetItem* item; - Media* m; + Media* item; + Footage* m; if (replace != NULL) { item = replace; - m = get_footage_from_tree(replace); + m = replace->to_footage(); m->reset(); } else { - item = new_item(); - m = new Media(); + item = new Media(parent); + m = new Footage(); } m->using_inout = false; m->url = file; m->name = get_file_name_from_path(files.at(i)); - // generate waveform/thumbnail in another thread - start_preview_generator(item, m, replace != NULL); + item->set_footage(m); - set_footage_of_tree(item, m); + // generate waveform/thumbnail in another thread + start_preview_generator(item, replace != NULL); - last_imported_media.append(m); + last_imported_media.append(item); if (replace == NULL) { if (create_undo_action) { ca->append(new AddMediaCommand(item, parent)); } else { - parent->addChild(item); + parent->appendChild(item); } } @@ -633,32 +644,33 @@ void Project::process_file_list(bool recursive, QStringList& files, QTreeWidgetI } } -QTreeWidgetItem* Project::get_selected_folder() { +Media* Project::get_selected_folder() { // if one item is selected and it's a folder, return it - QList selected_items = panel_project->source_table->selectedItems(); - if (selected_items.size() == 1 && get_type_from_tree(selected_items.at(0)) == MEDIA_TYPE_FOLDER) { - return selected_items.at(0); + QModelIndexList selected_items = source_table->selectionModel()->selectedRows(); + if (selected_items.size() == 1) { + Media* m = static_cast(selected_items.at(0).internalPointer()); + if (m->get_type() == MEDIA_TYPE_FOLDER) return m; } return NULL; } -bool Project::reveal_media(void *media, QTreeWidgetItem* parent) { - int count = (parent == NULL) ? ui->treeWidget->topLevelItemCount() : parent->childCount(); +bool Project::reveal_media(void *media, QModelIndex parent) { + for (int i=0;i(item.internalPointer()); - for (int i=0;itreeWidget->topLevelItem(i) : parent->child(i); - if (get_type_from_tree(item) == MEDIA_TYPE_FOLDER) { + if (m->get_type() == MEDIA_TYPE_FOLDER) { if (reveal_media(media, item)) return true; - } else if (get_media_from_tree(item) == media) { + } else if (m->to_object() == media) { // expand all folders leading to this media - QTreeWidgetItem* hierarchy = item->parent(); - while (hierarchy != NULL) { - hierarchy->setExpanded(true); - hierarchy = hierarchy->parent(); + QModelIndex hierarchy = item.parent(); + while (hierarchy.isValid()) { + source_table->setExpanded(hierarchy, true); + hierarchy = hierarchy.parent(); } - // select item - item->setSelected(true); + // select item + source_table->selectionModel()->select(item, QItemSelectionModel::Select); return true; } @@ -677,88 +689,18 @@ void Project::import_dialog() { } } -void set_item_to_folder(QTreeWidgetItem* item) { - item->setData(0, Qt::UserRole + 1, MEDIA_TYPE_FOLDER); -} - -void* get_media_from_tree(QTreeWidgetItem* item) { - return reinterpret_cast(item->data(0, Qt::UserRole + 2).value()); - /*int type = get_type_from_tree(item); - switch (type) { - case MEDIA_TYPE_FOOTAGE: return get_footage_from_tree(item); - case MEDIA_TYPE_SEQUENCE: return get_sequence_from_tree(item); - default: dout << "[ERROR] Invalid media type when retrieving media"; - } - return NULL;*/ -} - -Media* get_footage_from_tree(QTreeWidgetItem* item) { - return reinterpret_cast(item->data(0, Qt::UserRole + 2).value()); -} - -void set_footage_of_tree(QTreeWidgetItem* item, Media* media) { - item->setText(0, media->name); - item->setData(0, Qt::UserRole + 1, MEDIA_TYPE_FOOTAGE); - item->setData(0, Qt::UserRole + 2, QVariant::fromValue(reinterpret_cast(media))); -} - -Sequence* get_sequence_from_tree(QTreeWidgetItem* item) { - return reinterpret_cast(item->data(0, Qt::UserRole + 2).value()); -} - -QString get_channel_layout_name(int channels, int layout) { - switch (channels) { - case 0: return "Invalid"; break; - case 1: return "Mono"; break; - case 2: return "Stereo"; break; - default: { - char buf[50]; - av_get_channel_layout_string(buf, sizeof(buf), channels, layout); - return QString(buf); - } - } -} - -void set_sequence_of_tree(QTreeWidgetItem* item, Sequence* s) { - item->setData(0, Qt::UserRole + 1, MEDIA_TYPE_SEQUENCE); - item->setData(0, Qt::UserRole + 2, QVariant::fromValue(reinterpret_cast(s))); - item->setToolTip(0, "Name: " + s->name - + "\nVideo Dimensions: " + QString::number(s->width) + "x" + QString::number(s->height) - + "\nFrame Rate: " + QString::number(s->frame_rate) - + "\nAudio Frequency: " + QString::number(s->audio_frequency) - + "\nAudio Layout: " + get_channel_layout_name(av_get_channel_layout_nb_channels(s->audio_layout), s->audio_layout)); - item->setIcon(0, QIcon(":/icons/sequence.png")); -} - -int get_type_from_tree(QTreeWidgetItem* item) { - return item->data(0, Qt::UserRole + 1).toInt(); -} - -void Project::delete_media(QTreeWidgetItem* item) { - int type = get_type_from_tree(item); - void* media = get_media_from_tree(item); - switch (type) { - case MEDIA_TYPE_FOOTAGE: - delete static_cast(media); - break; - case MEDIA_TYPE_SEQUENCE: - delete static_cast(media); - break; - } -} - void Project::delete_clips_using_selected_media() { if (sequence == NULL) { QMessageBox::critical(this, "No active sequence", "No sequence is active, please open the sequence you want to delete clips from.", QMessageBox::Ok); } else { ComboAction* ca = new ComboAction(); bool deleted = false; - QList items = source_table->selectedItems(); + QModelIndexList items = source_table->selectionModel()->selectedRows(); for (int i=0;iclips.size();i++) { Clip* c = sequence->clips.at(i); if (c != NULL) { for (int j=0;j(items.at(j).internalPointer()); if (c->media == m) { ca->append(new DeleteClipAction(sequence, i)); deleted = true; @@ -767,7 +709,7 @@ void Project::delete_clips_using_selected_media() { } } for (int j=0;j(items.at(j).internalPointer()); if (delete_clips_in_clipboard_with_media(ca, m)) deleted = true; } if (deleted) { @@ -784,18 +726,14 @@ void Project::clear() { panel_effect_controls->clear_effects(true); // delete sequences first because it's important to close all the clips before deleting the media - QVector sequences = list_all_project_sequences(); + QVector sequences = list_all_project_sequences(); for (int i=0;ito_sequence(); + sequences.at(i)->set_sequence(NULL); } // delete everything else - while (ui->treeWidget->topLevelItemCount() > 0) { - QTreeWidgetItem* item = ui->treeWidget->topLevelItem(0); - if (get_type_from_tree(item) != MEDIA_TYPE_SEQUENCE) delete_media(item); // already deleted - if (item->data(0, Qt::UserRole + 5) != 0) delete reinterpret_cast(item->data(0, Qt::UserRole + 5).value()); - delete item; - } + project_model.clear(); } void Project::new_project() { @@ -805,664 +743,53 @@ void Project::new_project() { mainWindow->setWindowModified(false); } -QTreeWidgetItem* Project::find_loaded_folder_by_id(int id) { - for (int j=0;jdata(0, Qt::UserRole + 3).toInt() == id) { - return parent_item; - } - } - return NULL; -} - -const EffectMeta* get_meta_from_name(const QString& name, int type) { - for (int j=0;jtrack < 0) ? "Transform" : "Volume"; break; - case 1: effect_name = (c->track < 0) ? "Shake" : "Pan"; break; - case 2: effect_name = (c->track < 0) ? "Text" : "Noise"; break; - case 3: effect_name = (c->track < 0) ? "Solid" : "Tone"; break; - case 4: effect_name = "Invert"; break; - case 5: effect_name = "Chroma Key"; break; - case 6: effect_name = "Gaussian Blur"; break; - case 7: effect_name = "Crop"; break; - case 8: effect_name = "Flip"; break; - case 9: effect_name = "Box Blur"; break; - case 10: effect_name = "Wave"; break; - case 11: effect_name = "Temperature"; break; - } - } - - // wait for effects to be loaded - effects_loaded.lock(); - - const EffectMeta* meta = NULL; - - // find effect with this name - if (!effect_name.isEmpty()) { - meta = get_meta_from_name(effect_name, (c->track < 0) ? EFFECT_TYPE_VIDEO : EFFECT_TYPE_AUDIO); - } - - effects_loaded.unlock(); - - if (meta == NULL) { - dout << "[WARNING] An effect used by this project is missing. It was not loaded."; - } else { - QString tag = stream.name().toString(); - - if (tag == "opening" || tag == "closing") { - // TODO replace NULL/s with something else - - int transition_index = create_transition(c, NULL, meta); - Transition* t = c->sequence->transitions.at(transition_index); - if (effect_length > -1) t->set_length(effect_length); - t->set_enabled(effect_enabled); - t->load(stream); - - if (tag == "opening") { - c->opening_transition = transition_index; - } else { - c->closing_transition = transition_index; - } - } else { - Effect* e = create_effect(c, meta); - e->set_enabled(effect_enabled); - e->load(stream); - - c->effects.append(e); - } - } -} - -struct TransitionData { - int id; - QString name; - long length; - Clip* otc; - Clip* ctc; -}; - -bool Project::load_worker(QFile& f, QXmlStreamReader& stream, int type) { - f.seek(0); - stream.setDevice(stream.device()); - - QString root_search; - QString child_search; - - switch (type) { - case LOAD_TYPE_VERSION: - root_search = "version"; - break; - case LOAD_TYPE_URL: - root_search = "url"; - break; - case MEDIA_TYPE_FOLDER: - root_search = "folders"; - child_search = "folder"; - break; - case MEDIA_TYPE_FOOTAGE: - root_search = "media"; - child_search = "footage"; - break; - case MEDIA_TYPE_SEQUENCE: - root_search = "sequences"; - child_search = "sequence"; - break; - } - - show_err = true; - - while (!stream.atEnd()) { - stream.readNextStartElement(); - if (stream.name() == root_search) { - if (type == LOAD_TYPE_VERSION) { - int proj_version = stream.readElementText().toInt(); - if (proj_version < MIN_SAVE_VERSION && proj_version > SAVE_VERSION) { - if (QMessageBox::warning(this, "Version Mismatch", "This project was saved in a different version of Olive and may not be fully compatible with this version. Would you like to attempt loading it anyway?", QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) { - show_err = false; - return false; - } - } - } else if (type == LOAD_TYPE_URL) { - internal_proj_url = stream.readElementText(); - internal_proj_dir = QFileInfo(internal_proj_url).absoluteDir(); - } else { - while (!(stream.name() == root_search && stream.isEndElement())) { - stream.readNext(); - if (stream.name() == child_search && stream.isStartElement()) { - switch (type) { - case MEDIA_TYPE_FOLDER: - { - QTreeWidgetItem* folder = new_folder(0); - for (int j=0;jsetData(0, Qt::UserRole + 3, attr.value().toInt()); - } else if (attr.name() == "name") { - folder->setText(0, attr.value().toString()); - } else if (attr.name() == "parent") { - folder->setData(0, Qt::UserRole + 4, attr.value().toInt()); - } - } - loaded_folders.append(folder); - } - break; - case MEDIA_TYPE_FOOTAGE: - { - int folder = 0; - - QTreeWidgetItem* item = new_item(); - Media* m = new Media(); - - m->using_inout = false; - - for (int j=0;jsave_id = attr.value().toInt(); - } else if (attr.name() == "folder") { - folder = attr.value().toInt(); - } else if (attr.name() == "name") { - m->name = attr.value().toString(); - } else if (attr.name() == "url") { - m->url = attr.value().toString(); - - if (!QFileInfo::exists(m->url)) { // if path is not absolute - QString proj_dir_test = proj_dir.absoluteFilePath(m->url); - QString internal_proj_dir_test = internal_proj_dir.absoluteFilePath(m->url); - - if (QFileInfo::exists(proj_dir_test)) { // if path is relative to the project's current dir - m->url = proj_dir_test; - dout << "[INFO] Matched" << attr.value().toString() << "relative to project's current directory"; - } else if (QFileInfo::exists(internal_proj_dir_test)) { // if path is relative to the last directory the project was saved in - m->url = internal_proj_dir_test; - dout << "[INFO] Matched" << attr.value().toString() << "relative to project's internal directory"; - } else if (m->url.contains('%')) { - // hack for image sequences (qt won't be able to find the URL with %, but ffmpeg may) - m->url = proj_dir_test; - dout << "[INFO] Guess image sequence" << attr.value().toString() << "path to project's current directory"; - } else { - dout << "[INFO] Failed to match" << attr.value().toString() << "to file"; - } - } else { - dout << "[INFO] Matched" << attr.value().toString() << "with absolute path"; - } - } else if (attr.name() == "duration") { - m->length = attr.value().toLongLong(); - } else if (attr.name() == "using_inout") { - m->using_inout = (attr.value() == "1"); - } else if (attr.name() == "in") { - m->in = attr.value().toLong(); - } else if (attr.name() == "out") { - m->out = attr.value().toLong(); - } - } - - set_footage_of_tree(item, m); - - if (folder == 0) { - ui->treeWidget->addTopLevelItem(item); - } else { - find_loaded_folder_by_id(folder)->addChild(item); - } - - // analyze media to see if it's the same - loaded_media.append(m); - loaded_media_items.append(item); - } - break; - case MEDIA_TYPE_SEQUENCE: - { - QTreeWidgetItem* parent = NULL; - Sequence* s = new Sequence(); - - // load attributes about sequence - for (int j=0;jname = attr.value().toString(); - } else if (attr.name() == "folder") { - int folder = attr.value().toInt(); - if (folder > 0) parent = find_loaded_folder_by_id(folder); - } else if (attr.name() == "id") { - s->save_id = attr.value().toInt(); - } else if (attr.name() == "width") { - s->width = attr.value().toInt(); - } else if (attr.name() == "height") { - s->height = attr.value().toInt(); - } else if (attr.name() == "framerate") { - s->frame_rate = attr.value().toDouble(); - } else if (attr.name() == "afreq") { - s->audio_frequency = attr.value().toInt(); - } else if (attr.name() == "alayout") { - s->audio_layout = attr.value().toInt(); - } else if (attr.name() == "open") { - open_seq = s; - } else if (attr.name() == "workarea") { - s->using_workarea = (attr.value() == "1"); - } else if (attr.name() == "workareaIn") { - s->workarea_in = attr.value().toLong(); - } else if (attr.name() == "workareaOut") { - s->workarea_out = attr.value().toLong(); - } - } - - QVector transition_data; - - // load all clips and clip information - while (!(stream.name() == child_search && stream.isEndElement()) && !stream.atEnd()) { - stream.readNextStartElement(); - if (stream.name() == "marker" && stream.isStartElement()) { - Marker m; - for (int j=0;jmarkers.append(m); - } else if (stream.name() == "transition" && stream.isStartElement()) { - TransitionData td; - td.otc = NULL; - td.ctc = NULL; - for (int j=0;jautoscale = false; - - c->media = NULL; - for (int j=0;jname = attr.value().toString(); - } else if (attr.name() == "enabled") { - c->enabled = (attr.value() == "1"); - } else if (attr.name() == "id") { - c->load_id = attr.value().toInt(); - } else if (attr.name() == "clipin") { - c->clip_in = attr.value().toLong(); - } else if (attr.name() == "in") { - c->timeline_in = attr.value().toLong(); - } else if (attr.name() == "out") { - c->timeline_out = attr.value().toLong(); - } else if (attr.name() == "track") { - c->track = attr.value().toInt(); - } else if (attr.name() == "r") { - c->color_r = attr.value().toInt(); - } else if (attr.name() == "g") { - c->color_g = attr.value().toInt(); - } else if (attr.name() == "b") { - c->color_b = attr.value().toInt(); - } else if (attr.name() == "autoscale") { - c->autoscale = (attr.value() == "1"); - } else if (attr.name() == "type") { - c->media_type = attr.value().toInt(); - } else if (attr.name() == "media") { - c->media_type = MEDIA_TYPE_FOOTAGE; - media_id = attr.value().toInt(); - } else if (attr.name() == "stream") { - stream_id = attr.value().toInt(); - } else if (attr.name() == "speed") { - c->speed = attr.value().toDouble(); - } else if (attr.name() == "maintainpitch") { - c->maintain_audio_pitch = (attr.value() == "1"); - } else if (attr.name() == "reverse") { - c->reverse = (attr.value() == "1"); - } else if (attr.name() == "opening") { - c->opening_transition = attr.value().toInt(); - } else if (attr.name() == "closing") { - c->closing_transition = attr.value().toInt(); - } else if (attr.name() == "sequence") { - c->media_type = MEDIA_TYPE_SEQUENCE; - - // since we haven't finished loading sequences, we defer linking this until later - c->media = NULL; - c->media_stream = attr.value().toInt(); - loaded_clips.append(c); - } - } - - // set media and media stream - switch (c->media_type) { - case MEDIA_TYPE_FOOTAGE: - if (media_id == 0) { - c->media = NULL; - } else { - for (int j=0;jsave_id == media_id) { - c->media = m; - c->media_stream = stream_id; - break; - } - } - } - break; - } - - // load links and effects - while (!(stream.name() == "clip" && stream.isEndElement()) && !stream.atEnd()) { - stream.readNext(); - if (stream.isStartElement()) { - if (stream.name() == "linked") { - while (!(stream.name() == "linked" && stream.isEndElement()) && !stream.atEnd()) { - stream.readNext(); - if (stream.name() == "link" && stream.isStartElement()) { - for (int k=0;klinked.append(link_attr.value().toInt()); - break; - } - } - } - } - } else if (stream.isStartElement() && (stream.name() == "effect" || stream.name() == "opening" || stream.name() == "closing")) { - // "opening" and "closing" are backwards compatibility code - load_effect(stream, c); - } - } - } - - s->clips.append(c); - } - } - - // correct links, clip IDs, transitions - for (int i=0;iclips.size();i++) { - // correct links - Clip* correct_clip = s->clips.at(i); - for (int j=0;jlinked.size();j++) { - bool found = false; - for (int k=0;kclips.size();k++) { - if (s->clips.at(k)->load_id == correct_clip->linked.at(j)) { - correct_clip->linked[j] = k; - found = true; - break; - } - } - if (!found) { - correct_clip->linked.removeAt(j); - j--; - if (QMessageBox::warning(this, "Invalid Clip Link", "This project contains an invalid clip link. It may be corrupt. Would you like to continue loading it?", QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) { - delete s; - return false; - } - } - } - - // re-link clips to transitions - if (correct_clip->opening_transition > -1) { - for (int j=0;jopening_transition) { - transition_data[j].otc = correct_clip; - } - } - } - if (correct_clip->closing_transition > -1) { - for (int j=0;jclosing_transition) { - transition_data[j].ctc = correct_clip; - } - } - } - } - - // create transitions - for (int i=0;itrack < 0) ? EFFECT_TYPE_VIDEO : EFFECT_TYPE_AUDIO); - if (meta == NULL) { - dout << "[WARNING] Failed to link transition with name:" << td.name; - if (td.otc != NULL) td.otc->opening_transition = -1; - if (td.ctc != NULL) td.ctc->closing_transition = -1; - } else { - int transition_index = create_transition(primary, secondary, meta); - primary->sequence->transitions.at(transition_index)->set_length(td.length); - if (td.otc != NULL) td.otc->opening_transition = transition_index; - if (td.ctc != NULL) td.ctc->closing_transition = transition_index; - } - } - } - - new_sequence(NULL, s, false, parent); - - loaded_sequences.append(s); - } - break; - } - } - } - } - break; - } - } - return true; -} - void Project::load_project(bool autorecovery) { new_project(); - /*LoadDialog ld; - ld.exec();*/ - - QFile file(project_url); - if (!file.open(QIODevice::ReadOnly)) { - dout << "[ERROR] Could not open file"; - return; - } - - /* set up directories to search for media - * most of the time, these will be the same but in - * case the project file has moved without the footage, - * we check both - */ - proj_dir = QFileInfo(project_url).absoluteDir(); - internal_proj_dir = QFileInfo(project_url).absoluteDir(); - internal_proj_url = project_url; - - QXmlStreamReader stream(&file); - - bool cont = false; - error_str.clear(); - show_err = true; - - // temp variables for loading - loaded_folders.clear(); - loaded_media.clear(); - loaded_media_items.clear(); - loaded_clips.clear(); - loaded_sequences.clear(); - open_seq = NULL; - - // get "element" count - int element_count = 0; - while (!stream.atEnd()) { - stream.readNextStartElement(); - if (stream.name() == "folder" - || stream.name() == "footage" - || stream.name() == "sequence" - || stream.name() == "clip" - || stream.name() == "effect") { - element_count++; - } - } - - // find project file version - cont = load_worker(file, stream, LOAD_TYPE_VERSION); - - // find project's internal URL - cont = load_worker(file, stream, LOAD_TYPE_URL); - if (autorecovery) { - QString orig_filename = internal_proj_url; - int insert_index = internal_proj_url.lastIndexOf(".ove", -1, Qt::CaseInsensitive); - if (insert_index == -1) insert_index = internal_proj_url.length(); - int counter = 1; - while (QFileInfo::exists(orig_filename)) { - orig_filename = internal_proj_url; - QString recover_text = "recovered"; - if (counter > 1) { - recover_text += " " + QString::number(counter); - } - orig_filename.insert(insert_index, " (" + recover_text + ")"); - counter++; - } - mainWindow->updateTitle(orig_filename); - } - - // load folders first - if (cont) { - cont = load_worker(file, stream, MEDIA_TYPE_FOLDER); - } - - // load media - if (cont) { - // since folders loaded correctly, organize them appropriately - for (int i=0;idata(0, Qt::UserRole + 4).toInt(); - if (parent > 0) { - find_loaded_folder_by_id(parent)->addChild(folder); - } else { - ui->treeWidget->addTopLevelItem(folder); - } - } - - cont = load_worker(file, stream, MEDIA_TYPE_FOOTAGE); - } - - // load sequences - if (cont) { - cont = load_worker(file, stream, MEDIA_TYPE_SEQUENCE); - } - - // attach nested sequence clips to their sequences - for (int i=0;imedia_stream == loaded_sequences.at(j)->save_id) { - loaded_clips.at(i)->media = loaded_sequences.at(j); - loaded_clips.at(i)->refresh(); - break; - } - } - } - - if (!cont) { - if (show_err) QMessageBox::critical(this, "Project Load Error", "Error loading project: " + error_str, QMessageBox::Ok); - } else if (stream.hasError()) { - dout << "[ERROR] Error parsing XML." << stream.errorString(); - QMessageBox::critical(this, "XML Parsing Error", "Couldn't load '" + project_url + "'. " + stream.errorString(), QMessageBox::Ok); - cont = false; - } - - if (cont) { - if (open_seq != NULL) set_sequence(open_seq); - - update_ui(false); - mainWindow->setWindowModified(autorecovery); - - for (int i=0;itreeWidget->topLevelItemCount() : parent->childCount(); - for (int i=0;itreeWidget->topLevelItem(i) : parent->child(i); - int item_type = get_type_from_tree(item); +void Project::save_folder(QXmlStreamWriter& stream, int type, bool set_ids_only, const QModelIndex& parent) { + bool root = (!parent.parent().isValid()); + for (int i=0;i(item.internalPointer()); - if (type == item_type) { - if (item_type == MEDIA_TYPE_FOLDER) { + if (type == m->get_type()) { + if (m->get_type() == MEDIA_TYPE_FOLDER) { if (set_ids_only) { - item->setData(0, Qt::UserRole + 3, folder_id); // saves a temporary ID for matching in the project file + m->temp_id = folder_id; // saves a temporary ID for matching in the project file folder_id++; } else { // if we're saving folders, save the folder stream.writeStartElement("folder"); - stream.writeAttribute("name", item->text(0)); - stream.writeAttribute("id", QString::number(item->data(0, Qt::UserRole + 3).toInt())); - if (item->parent() == NULL) { + stream.writeAttribute("name", m->get_name()); + stream.writeAttribute("id", QString::number(m->temp_id)); + if (!item.parent().isValid()) { stream.writeAttribute("parent", "0"); } else { - stream.writeAttribute("parent", QString::number(item->parent()->data(0, Qt::UserRole + 3).toInt())); + stream.writeAttribute("parent", QString::number(static_cast(item.parent().internalPointer())->temp_id)); } stream.writeEndElement(); } // save_folder(stream, item, type, set_ids_only); } else { - int folder = root ? 0 : parent->data(0, Qt::UserRole + 3).toInt(); + int folder = root ? 0 : static_cast(parent.internalPointer())->temp_id; if (type == MEDIA_TYPE_FOOTAGE) { - Media* m = get_footage_from_tree(item); - m->save_id = media_id; + Footage* f = m->to_footage(); + f->save_id = media_id; stream.writeStartElement("footage"); stream.writeAttribute("id", QString::number(media_id)); stream.writeAttribute("folder", QString::number(folder)); - stream.writeAttribute("name", m->name); - stream.writeAttribute("url", proj_dir.relativeFilePath(m->url)); - stream.writeAttribute("duration", QString::number(m->length)); - stream.writeAttribute("using_inout", QString::number(m->using_inout)); - stream.writeAttribute("in", QString::number(m->in)); - stream.writeAttribute("out", QString::number(m->out)); - for (int j=0;jvideo_tracks.size();j++) { - MediaStream* ms = m->video_tracks.at(j); + stream.writeAttribute("name", f->name); + stream.writeAttribute("url", proj_dir.relativeFilePath(f->url)); + stream.writeAttribute("duration", QString::number(f->length)); + stream.writeAttribute("using_inout", QString::number(f->using_inout)); + stream.writeAttribute("in", QString::number(f->in)); + stream.writeAttribute("out", QString::number(f->out)); + for (int j=0;jvideo_tracks.size();j++) { + FootageStream* ms = f->video_tracks.at(j); stream.writeStartElement("video"); stream.writeAttribute("id", QString::number(ms->file_index)); stream.writeAttribute("width", QString::number(ms->video_width)); @@ -1471,8 +798,8 @@ void Project::save_folder(QXmlStreamWriter& stream, QTreeWidgetItem* parent, int stream.writeAttribute("infinite", QString::number(ms->infinite_length)); stream.writeEndElement(); } - for (int j=0;jaudio_tracks.size();j++) { - MediaStream* ms = m->audio_tracks.at(j); + for (int j=0;jaudio_tracks.size();j++) { + FootageStream* ms = f->audio_tracks.at(j); stream.writeStartElement("audio"); stream.writeAttribute("id", QString::number(ms->file_index)); stream.writeAttribute("channels", QString::number(ms->audio_channels)); @@ -1483,7 +810,7 @@ void Project::save_folder(QXmlStreamWriter& stream, QTreeWidgetItem* parent, int stream.writeEndElement(); media_id++; } else if (type == MEDIA_TYPE_SEQUENCE) { - Sequence* s = get_sequence_from_tree(item); + Sequence* s = m->to_sequence(); if (set_ids_only) { s->save_id = sequence_id; sequence_id++; @@ -1538,14 +865,14 @@ void Project::save_folder(QXmlStreamWriter& stream, QTreeWidgetItem* parent, int stream.writeAttribute("maintainpitch", QString::number(c->maintain_audio_pitch)); stream.writeAttribute("reverse", QString::number(c->reverse)); - stream.writeAttribute("type", QString::number(c->media_type)); - switch (c->media_type) { + stream.writeAttribute("type", QString::number(c->media->get_type())); + switch (c->media->get_type()) { case MEDIA_TYPE_FOOTAGE: - stream.writeAttribute("media", QString::number(static_cast(c->media)->save_id)); + stream.writeAttribute("media", QString::number(c->media->to_footage()->save_id)); stream.writeAttribute("stream", QString::number(c->media_stream)); break; case MEDIA_TYPE_SEQUENCE: - stream.writeAttribute("sequence", QString::number(static_cast(c->media)->save_id)); + stream.writeAttribute("sequence", QString::number(c->media->to_sequence()->save_id)); break; } @@ -1578,8 +905,8 @@ void Project::save_folder(QXmlStreamWriter& stream, QTreeWidgetItem* parent, int } } - if (item_type == MEDIA_TYPE_FOLDER) { - save_folder(stream, item, type, set_ids_only); + if (m->get_type() == MEDIA_TYPE_FOLDER) { + save_folder(stream, type, set_ids_only, item); } } } @@ -1606,20 +933,20 @@ void Project::save_project(bool autorecovery) { stream.writeTextElement("url", project_url); proj_dir = QFileInfo(project_url).absoluteDir(); - save_folder(stream, NULL, MEDIA_TYPE_FOLDER, true); + save_folder(stream, MEDIA_TYPE_FOLDER, true); stream.writeStartElement("folders"); // folders - save_folder(stream, NULL, MEDIA_TYPE_FOLDER, false); + save_folder(stream, MEDIA_TYPE_FOLDER, false); stream.writeEndElement(); // folders stream.writeStartElement("media"); // media - save_folder(stream, NULL, MEDIA_TYPE_FOOTAGE, false); + save_folder(stream, MEDIA_TYPE_FOOTAGE, false); stream.writeEndElement(); // media - save_folder(stream, NULL, MEDIA_TYPE_SEQUENCE, true); + save_folder(stream, MEDIA_TYPE_SEQUENCE, true); stream.writeStartElement("sequences"); // sequences - save_folder(stream, NULL, MEDIA_TYPE_SEQUENCE, false); + save_folder(stream, MEDIA_TYPE_SEQUENCE, false); stream.writeEndElement();// sequences stream.writeEndElement(); // project @@ -1674,20 +1001,23 @@ void Project::add_recent_project(QString url) { save_recent_projects(); } -void Project::list_all_sequences_worker(QVector* list, QTreeWidgetItem* parent) { - int len = (parent == NULL) ? ui->treeWidget->topLevelItemCount() : parent->childCount(); +void Project::list_all_sequences_worker(QVector* list, Media* parent) { + int len = (parent == NULL) ? project_model.topLevelItemCount() : parent->childCount(); for (int i=0;itreeWidget->topLevelItem(i) : parent->child(i); - if (get_type_from_tree(item) == MEDIA_TYPE_SEQUENCE) { - list->append(get_sequence_from_tree(item)); - } else if (get_type_from_tree(item) == MEDIA_TYPE_FOLDER) { + Media* item = (parent == NULL) ? project_model.topLevelItem(i) : parent->child(i); + switch (item->get_type()) { + case MEDIA_TYPE_SEQUENCE: + list->append(item); + break; + case MEDIA_TYPE_FOLDER: list_all_sequences_worker(list, item); + break; } } } -QVector Project::list_all_project_sequences() { - QVector list; +QVector Project::list_all_project_sequences() { + QVector list; list_all_sequences_worker(&list, NULL); return list; } @@ -1695,36 +1025,43 @@ QVector Project::list_all_project_sequences() { #define THROBBER_LIMIT 20 #define THROBBER_SIZE 50 -MediaThrobber::MediaThrobber(QTreeWidgetItem *i) : pixmap(":/icons/throbber.png"), animation(0), item(i) { +MediaThrobber::MediaThrobber(Media *i) : pixmap(":/icons/throbber.png"), animation(0), item(i), animator(NULL) {} + +void MediaThrobber::start() { // set up throbber animation_update(); - animator.setInterval(20); - connect(&animator, SIGNAL(timeout()), this, SLOT(animation_update())); - animator.start(); + animator = new QTimer(this); + animator->setInterval(20); + connect(animator, SIGNAL(timeout()), this, SLOT(animation_update())); + animator->start(); } void MediaThrobber::animation_update() { if (animation == THROBBER_LIMIT) { animation = 0; } - item->setIcon(0, QIcon(pixmap.copy(THROBBER_SIZE*animation, 0, THROBBER_SIZE, THROBBER_SIZE))); + item->set_icon(QIcon(pixmap.copy(THROBBER_SIZE*animation, 0, THROBBER_SIZE, THROBBER_SIZE))); + project_model.update_data(); animation++; } void MediaThrobber::stop(int icon_type, bool replace) { - animator.stop(); + if (animator != NULL) { + animator->stop(); + delete animator; + } switch (icon_type) { - case ICON_TYPE_VIDEO: item->setIcon(0, QIcon(":/icons/videosource.png")); break; - case ICON_TYPE_AUDIO: item->setIcon(0, QIcon(":/icons/audiosource.png")); break; - case ICON_TYPE_IMAGE: item->setIcon(0, QIcon(":/icons/imagesource.png")); break; - case ICON_TYPE_ERROR: item->setIcon(0, QIcon::fromTheme("dialog-error")); break; + case ICON_TYPE_VIDEO: item->set_icon(QIcon(":/icons/videosource.png")); break; + case ICON_TYPE_AUDIO: item->set_icon(QIcon(":/icons/audiosource.png")); break; + case ICON_TYPE_IMAGE: item->set_icon(QIcon(":/icons/imagesource.png")); break; + case ICON_TYPE_ERROR: item->set_icon(QIcon(":/icons/error.png")); break; } // refresh all clips - QVector sequences = panel_project->list_all_project_sequences(); + QVector sequences = panel_project->list_all_project_sequences(); for (int i=0;ito_sequence(); for (int j=0;jclips.size();j++) { Clip* c = s->clips.at(j); if (c != NULL) { @@ -1737,82 +1074,6 @@ void MediaThrobber::stop(int icon_type, bool replace) { update_ui(replace); panel_project->source_table->viewport()->update(); - item->setData(0, Qt::UserRole + 5, 0); + item->throbber = NULL; deleteLater(); } - -QString get_interlacing_name(int interlacing) { - switch (interlacing) { - case VIDEO_PROGRESSIVE: return "None (Progressive)"; - case VIDEO_TOP_FIELD_FIRST: return "Top Field First"; - case VIDEO_BOTTOM_FIELD_FIRST: return "Bottom Field First"; - default: return "Invalid"; - } -} - -void update_footage_tooltip(QTreeWidgetItem *item, Media *media, QString error) { - QString tooltip = "Name: " + media->name + "\nFilename: " + media->url + "\n"; - - if (error.isEmpty()) { - if (media->video_tracks.size() > 0) { - tooltip += "Video Dimensions: "; - for (int i=0;ivideo_tracks.size();i++) { - if (i > 0) { - tooltip += ", "; - } - tooltip += QString::number(media->video_tracks.at(i)->video_width) + "x" + QString::number(media->video_tracks.at(i)->video_height); - } - tooltip += "\n"; - - if (!media->video_tracks.at(0)->infinite_length) { - tooltip += "Frame Rate: "; - for (int i=0;ivideo_tracks.size();i++) { - if (i > 0) { - tooltip += ", "; - } - if (media->video_tracks.at(i)->video_interlacing == VIDEO_PROGRESSIVE) { - tooltip += QString::number(media->video_tracks.at(i)->video_frame_rate); - } else { - tooltip += QString::number(media->video_tracks.at(i)->video_frame_rate * 2); - tooltip += " fields (" + QString::number(media->video_tracks.at(i)->video_frame_rate) + " frames)"; - } - } - tooltip += "\n"; - } - - tooltip += "Interlacing: "; - for (int i=0;ivideo_tracks.size();i++) { - if (i > 0) { - tooltip += ", "; - } - tooltip += get_interlacing_name(media->video_tracks.at(i)->video_interlacing); - } - } - - if (media->audio_tracks.size() > 0) { - tooltip += "\n"; - - tooltip += "Audio Frequency: "; - for (int i=0;iaudio_tracks.size();i++) { - if (i > 0) { - tooltip += ", "; - } - tooltip += QString::number(media->audio_tracks.at(i)->audio_frequency); - } - tooltip += "\n"; - - tooltip += "Audio Channels: "; - for (int i=0;iaudio_tracks.size();i++) { - if (i > 0) { - tooltip += ", "; - } - tooltip += get_channel_layout_name(media->audio_tracks.at(i)->audio_channels, media->audio_tracks.at(i)->audio_layout); - } - // tooltip += "\n"; - } - } else { - tooltip = error; - } - - item->setToolTip(0, tooltip); -} diff --git a/panels/project.h b/panels/project.h index 64c112d75..feac5f90c 100644 --- a/panels/project.h +++ b/panels/project.h @@ -6,13 +6,15 @@ #include #include -struct Media; +#include "project/projectmodel.h" + +struct Footage; struct Sequence; struct Clip; class Timeline; class Viewer; class SourceTable; -class QTreeWidgetItem; +class Media; class QXmlStreamWriter; class QXmlStreamReader; class QFile; @@ -30,18 +32,20 @@ extern QString project_url; extern QStringList recent_projects; extern QString recent_proj_file; -int get_type_from_tree(QTreeWidgetItem* item); +extern ProjectModel project_model; + +/*int get_type_from_tree(Media *item); void* get_media_from_tree(QTreeWidgetItem* item); -Media* get_footage_from_tree(QTreeWidgetItem* item); -void set_footage_of_tree(QTreeWidgetItem* item, Media* media); +Footage* get_footage_from_tree(QTreeWidgetItem* item); +void set_footage_of_tree(QTreeWidgetItem* item, Footage* media); Sequence* get_sequence_from_tree(QTreeWidgetItem* item); void set_sequence_of_tree(QTreeWidgetItem* item, Sequence* sequence); -void set_item_to_folder(QTreeWidgetItem* item); -void update_footage_tooltip(QTreeWidgetItem* item, Media* media, QString error = 0); +void set_item_to_folder(QTreeWidgetItem* item);*/ +//void update_footage_tooltip(Media* item, Footage* media, QString error = 0); -Sequence* create_sequence_from_media(QVector& media_list, QVector& type_list); +Sequence* create_sequence_from_media(QVector &media_list); -QString get_channel_layout_name(int channels, int layout); +QString get_channel_layout_name(int channels, uint64_t layout); QString get_interlacing_name(int interlacing); class Project : public QDockWidget @@ -53,27 +57,33 @@ public: ~Project(); bool is_focused(); void clear(); - void new_sequence(ComboAction *ca, Sequence* s, bool open, QTreeWidgetItem* parent); - QString get_next_sequence_name(QString start = 0); - void delete_media(QTreeWidgetItem* item); - void process_file_list(bool recursive, QStringList& files, QTreeWidgetItem *parent, QTreeWidgetItem* replace); - void replace_media(QTreeWidgetItem* item, QString filename); - QTreeWidgetItem* get_selected_folder(); - bool reveal_media(void* media, QTreeWidgetItem *parent = 0); + Media* new_sequence(ComboAction *ca, Sequence* s, bool open, Media* parent); + QString get_next_sequence_name(QString start = 0); + void process_file_list(bool recursive, QStringList& files, Media *parent, Media* replace); + void replace_media(Media* item, QString filename); + Media* get_selected_folder(); + bool reveal_media(void* media, QModelIndex parent = QModelIndex()); + void add_recent_project(QString url); void new_project(); void load_project(bool autorecovery); void save_project(bool autorecovery); - QTreeWidgetItem* new_folder(QString name); + Media* new_folder(QString name); void save_recent_projects(); - QVector list_all_project_sequences(); + QVector list_all_project_sequences(); SourceTable* source_table; - QVector last_imported_media; + QVector last_imported_media; + + //Media *new_item(); + + void start_preview_generator(Media* item, bool replacing); + + Ui::Project *ui; public slots: void import_dialog(); void delete_selected_media(); @@ -83,48 +93,33 @@ public slots: void replace_clip_media(); void open_properties(); private: - Ui::Project *ui; - QTreeWidgetItem* new_item(); - bool load_worker(QFile& f, QXmlStreamReader& stream, int type); - void save_folder(QXmlStreamWriter& stream, QTreeWidgetItem* parent, int type, bool set_ids_only); - bool show_err; - QString error_str; + void save_folder(QXmlStreamWriter& stream, int type, bool set_ids_only, const QModelIndex &parent = QModelIndex()); int folder_id; int media_id; int sequence_id; - Sequence* open_seq; - QVector loaded_folders; - QVector loaded_media; - QVector loaded_media_items; - QVector loaded_clips; - QVector loaded_sequences; - QTreeWidgetItem* find_loaded_folder_by_id(int id); - void add_recent_project(QString url); - void get_all_media_from_table(QList items, QList& list, int type); - void start_preview_generator(QTreeWidgetItem* item, Media* media, bool replacing); - void list_all_sequences_worker(QVector* list, QTreeWidgetItem* parent); + void get_all_media_from_table(QList items, QList &list, int type); + void list_all_sequences_worker(QVector *list, Media* parent); QString get_file_name_from_path(const QString &path); QDir proj_dir; - QDir internal_proj_dir; - QString internal_proj_url; private slots: - void rename_media(QTreeWidgetItem* item, int column); + void rename_media(Media* item, int column); void clear_recent_projects(); }; class MediaThrobber : public QObject { Q_OBJECT public: - MediaThrobber(QTreeWidgetItem*); + MediaThrobber(Media*); public slots: + void start(); void stop(int, bool replace); private slots: void animation_update(); private: QPixmap pixmap; int animation; - QTreeWidgetItem* item; - QTimer animator; + Media* item; + QTimer* animator; }; #endif // PROJECT_H diff --git a/panels/project.ui b/panels/project.ui index cd064d9b0..fbf5d7094 100644 --- a/panels/project.ui +++ b/panels/project.ui @@ -34,55 +34,19 @@ 0 - + true QAbstractItemView::NoEditTriggers - - false - QAbstractItemView::DragDrop QAbstractItemView::ExtendedSelection - - QAbstractItemView::SelectRows - - - true - - - true - - - false - - - false - - - true - - - - Name - - - - - Duration - - - - - Rate - - @@ -91,7 +55,7 @@ SourceTable - QTreeWidget + QTreeView
ui/sourcetable.h
diff --git a/panels/timeline.cpp b/panels/timeline.cpp index e9279206f..ba02ffefe 100644 --- a/panels/timeline.cpp +++ b/panels/timeline.cpp @@ -14,10 +14,11 @@ #include "playback/playback.h" #include "ui_viewer.h" #include "project/undo.h" +#include "project/media.h" #include "io/config.h" #include "project/effect.h" #include "project/transition.h" -#include "io/media.h" +#include "project/footage.h" #include "io/clipboard.h" #include "debug.h" @@ -160,23 +161,24 @@ void Timeline::toggle_show_all() { } } -void Timeline::create_ghosts_from_media(Sequence* seq, long entry_point, QVector& media_list, QVector& type_list) { +void Timeline::create_ghosts_from_media(Sequence* seq, long entry_point, QVector& media_list) { video_ghosts = false; audio_ghosts = false; for (int i=0;iget_type()) { case MEDIA_TYPE_FOOTAGE: - m = static_cast(media_list.at(i)); + m = medium->to_footage(); media = m; can_import = m->ready; if (m->using_inout) { @@ -187,7 +189,7 @@ void Timeline::create_ghosts_from_media(Sequence* seq, long entry_point, QVector } break; case MEDIA_TYPE_SEQUENCE: - s = static_cast(media_list.at(i)); + s = medium->to_sequence(); sequence_length = s->getEndFrame(); if (seq != NULL) sequence_length = refactor_frame_number(sequence_length, s->frame_rate, seq->frame_rate); media = s; @@ -203,15 +205,14 @@ void Timeline::create_ghosts_from_media(Sequence* seq, long entry_point, QVector if (can_import) { Ghost g; - g.clip = -1; - g.media_type = type_list.at(i); + g.clip = -1; g.trimming = false; g.old_clip_in = g.clip_in = default_clip_in; - g.media = media; + g.media = medium; g.in = entry_point; g.transition = NULL; - switch (type_list.at(i)) { + switch (medium->get_type()) { case MEDIA_TYPE_FOOTAGE: // is video source a still image? if (m->video_tracks.size() > 0 && m->video_tracks[0]->infinite_length && m->audio_tracks.size() == 0) { @@ -274,15 +275,14 @@ void Timeline::add_clips_from_ghosts(ComboAction* ca, Sequence* s) { earliest_point = qMin(earliest_point, g.in); Clip* c = new Clip(s); - c->media = g.media; - c->media_type = g.media_type; + c->media = g.media; c->media_stream = g.media_stream; c->timeline_in = g.in; c->timeline_out = g.out; c->clip_in = g.clip_in; c->track = g.track; - if (c->media_type == MEDIA_TYPE_FOOTAGE) { - Media* m = static_cast(c->media); + if (c->media->get_type() == MEDIA_TYPE_FOOTAGE) { + Footage* m = c->media->to_footage(); if (m->video_tracks.size() == 0) { // audio only (greenish) c->color_r = 128; @@ -300,13 +300,13 @@ void Timeline::add_clips_from_ghosts(ComboAction* ca, Sequence* s) { c->color_b = 192; } c->name = m->name; - } else if (c->media_type == MEDIA_TYPE_SEQUENCE) { + } else if (c->media->get_type() == MEDIA_TYPE_SEQUENCE) { // sequence (red?ish?) c->color_r = 192; c->color_g = 128; c->color_b = 128; - Sequence* media = static_cast(c->media); + Sequence* media = c->media->to_sequence(); c->name = media->name; } c->recalculateMaxLength(); diff --git a/panels/timeline.h b/panels/timeline.h index db73a87da..0bbd50d00 100644 --- a/panels/timeline.h +++ b/panels/timeline.h @@ -22,12 +22,13 @@ class SourceTable; class ViewerWidget; class ComboAction; class Effect; +class Media; class Transition; struct EffectMeta; struct Sequence; struct Clip; -struct Media; -struct MediaStream; +struct Footage; +struct FootageStream; long refactor_frame_number(long framenumber, double source_frame_rate, double target_frame_rate); int getScreenPointFromFrame(double zoom, long frame); @@ -49,8 +50,7 @@ struct Ghost { long old_clip_in; // importing variables - void* media; - int media_type; + Media* media; int media_stream; // other variables @@ -102,7 +102,7 @@ public: void next_cut(); void toggle_show_all(); - void create_ghosts_from_media(Sequence *seq, long entry_point, QVector &media_list, QVector &type_list); + void create_ghosts_from_media(Sequence *seq, long entry_point, QVector &media_list); void add_clips_from_ghosts(ComboAction *ca, Sequence *s); int getTimelineScreenPointFromFrame(long frame); diff --git a/panels/viewer.cpp b/panels/viewer.cpp index 9e2fccde9..346835fbc 100644 --- a/panels/viewer.cpp +++ b/panels/viewer.cpp @@ -8,7 +8,8 @@ #include "project/clip.h" #include "panels/panels.h" #include "io/config.h" -#include "io/media.h" +#include "project/footage.h" +#include "project/media.h" #include "project/undo.h" #include "ui/audiomonitor.h" #include "ui_timeline.h" @@ -33,6 +34,7 @@ Viewer::Viewer(QWidget *parent) : QDockWidget(parent), playing(false), just_played(false), + media(NULL), seq(NULL), ui(new Ui::Viewer), created_sequence(false), @@ -46,7 +48,7 @@ Viewer::Viewer(QWidget *parent) : ui->glViewerPane->child = ui->openGLWidget; ui->openGLWidget->viewer = this; viewer_widget = ui->openGLWidget; - set_media(MEDIA_TYPE_SEQUENCE, NULL); + set_media(NULL); ui->currentTimecode->setEnabled(false); ui->currentTimecode->set_minimum_value(0); @@ -344,23 +346,23 @@ void Viewer::pause() { // add it to the sequence Clip* c = new Clip(seq); - Media* m = panel_project->last_imported_media.at(0); + Media* m = panel_project->last_imported_media.at(0); + Footage* f = m->to_footage(); - m->ready_lock.lock(); + f->ready_lock.lock(); - c->media = m; // latest media - c->media_type = MEDIA_TYPE_FOOTAGE; + c->media = m; // latest media c->media_stream = 0; c->timeline_in = recording_start; - c->timeline_out = m->get_length_in_frames(seq->frame_rate); + c->timeline_out = f->get_length_in_frames(seq->frame_rate); c->clip_in = 0; c->track = recording_track; c->color_r = 128; c->color_g = 192; c->color_b = 128; - c->name = m->name; + c->name = m->get_name(); - m->ready_lock.unlock(); + f->ready_lock.unlock(); QVector add_clips; add_clips.append(c); @@ -414,83 +416,83 @@ void Viewer::set_out_point() { ui->headers->set_out_point(seq->playhead); } -void Viewer::set_media(int type, void* media) { +void Viewer::set_media(Media* m) { main_sequence = false; - clean_created_seq(); - switch (type) { - case MEDIA_TYPE_FOOTAGE: - { - Media* footage = static_cast(media); + media = m; + clean_created_seq(); + if (media != NULL) { + switch (media->get_type()) { + case MEDIA_TYPE_FOOTAGE: + { + Footage* footage = media->to_footage(); - seq = new Sequence(); - created_sequence = true; - seq->wrapper_sequence = true; - seq->name = footage->name; + seq = new Sequence(); + created_sequence = true; + seq->wrapper_sequence = true; + seq->name = footage->name; - seq->using_workarea = footage->using_inout; - if (footage->using_inout) { - seq->workarea_in = footage->in; - seq->workarea_out = footage->out; - } + seq->using_workarea = footage->using_inout; + if (footage->using_inout) { + seq->workarea_in = footage->in; + seq->workarea_out = footage->out; + } - seq->frame_rate = 30; + seq->frame_rate = 30; - if (footage->video_tracks.size() > 0) { - MediaStream* video_stream = footage->video_tracks.at(0); - seq->width = video_stream->video_width; - seq->height = video_stream->video_height; - if (video_stream->video_frame_rate > 0 && !video_stream->infinite_length) seq->frame_rate = video_stream->video_frame_rate; + if (footage->video_tracks.size() > 0) { + FootageStream* video_stream = footage->video_tracks.at(0); + seq->width = video_stream->video_width; + seq->height = video_stream->video_height; + if (video_stream->video_frame_rate > 0 && !video_stream->infinite_length) seq->frame_rate = video_stream->video_frame_rate; - Clip* c = new Clip(seq); - c->media = footage; - c->media_type = type; - c->media_stream = video_stream->file_index; - c->timeline_in = 0; - c->timeline_out = footage->get_length_in_frames(seq->frame_rate); - if (c->timeline_out <= 0) c->timeline_out = 150; - c->track = -1; - c->clip_in = 0; - c->recalculateMaxLength(); - seq->clips.append(c); - } else { - seq->width = 1920; - seq->height = 1080; - } + Clip* c = new Clip(seq); + c->media = media; + c->media_stream = video_stream->file_index; + c->timeline_in = 0; + c->timeline_out = footage->get_length_in_frames(seq->frame_rate); + if (c->timeline_out <= 0) c->timeline_out = 150; + c->track = -1; + c->clip_in = 0; + c->recalculateMaxLength(); + seq->clips.append(c); + } else { + seq->width = 1920; + seq->height = 1080; + } - if (footage->audio_tracks.size() > 0) { - MediaStream* audio_stream = footage->audio_tracks.at(0); - seq->audio_frequency = audio_stream->audio_frequency; + if (footage->audio_tracks.size() > 0) { + FootageStream* audio_stream = footage->audio_tracks.at(0); + seq->audio_frequency = audio_stream->audio_frequency; - Clip* c = new Clip(seq); - c->media = footage; - c->media_type = type; - c->media_stream = audio_stream->file_index; - c->timeline_in = 0; - c->timeline_out = footage->get_length_in_frames(seq->frame_rate); - c->track = 0; - c->clip_in = 0; - c->recalculateMaxLength(); - seq->clips.append(c); + Clip* c = new Clip(seq); + c->media = media; + c->media_stream = audio_stream->file_index; + c->timeline_in = 0; + c->timeline_out = footage->get_length_in_frames(seq->frame_rate); + c->track = 0; + c->clip_in = 0; + c->recalculateMaxLength(); + seq->clips.append(c); - if (footage->video_tracks.size() == 0) { - viewer_widget->waveform = true; - viewer_widget->waveform_clip = c; - viewer_widget->waveform_ms = audio_stream; - viewer_widget->update(); - } - } else { - seq->audio_frequency = 48000; - } + if (footage->video_tracks.size() == 0) { + viewer_widget->waveform = true; + viewer_widget->waveform_clip = c; + viewer_widget->waveform_ms = audio_stream; + viewer_widget->update(); + } + } else { + seq->audio_frequency = 48000; + } - seq->audio_layout = AV_CH_LAYOUT_STEREO; - - set_sequence(false, seq); - } - break; - case MEDIA_TYPE_SEQUENCE: - set_sequence(false, static_cast(media)); - break; - } + seq->audio_layout = AV_CH_LAYOUT_STEREO; + } + break; + case MEDIA_TYPE_SEQUENCE: + seq = media->to_sequence(); + break; + } + } + set_sequence(false, seq); } void Viewer::on_pushButton_clicked() { diff --git a/panels/viewer.h b/panels/viewer.h index 547efdf7b..3bd461ff9 100644 --- a/panels/viewer.h +++ b/panels/viewer.h @@ -6,6 +6,7 @@ class Timeline; class ViewerWidget; +class Media; struct Sequence; namespace Ui { @@ -26,7 +27,7 @@ public: bool is_focused(); void set_main_sequence(); - void set_media(int type, void* media); + void set_media(Media *m); void compose(); void set_playpause_icon(bool play); void update_playhead_timecode(long p); @@ -65,6 +66,7 @@ public: ViewerWidget* viewer_widget; + Media* media; Sequence* seq; Ui::Viewer *ui; diff --git a/playback/cacher.cpp b/playback/cacher.cpp index 0c18f5fc4..ff6fd9fd3 100644 --- a/playback/cacher.cpp +++ b/playback/cacher.cpp @@ -3,7 +3,7 @@ #include "project/clip.h" #include "project/sequence.h" #include "project/transition.h" -#include "io/media.h" +#include "project/footage.h" #include "playback/audio.h" #include "playback/playback.h" #include "project/effect.h" @@ -12,6 +12,7 @@ #include "playback/audio.h" #include "panels/panels.h" #include "panels/viewer.h" +#include "project/media.h" #include "debug.h" extern "C" { @@ -49,7 +50,7 @@ void apply_audio_effects(Clip* c, double timecode_start, AVFrame* frame, int nb_ if (e->is_enabled()) e->process_audio(timecode_start, timecode_end, frame->data[0], nb_bytes, 2); } if (c->get_opening_transition() != NULL) { - if (c->media_type == MEDIA_TYPE_FOOTAGE) { + if (c->media != NULL && c->media->get_type() == MEDIA_TYPE_FOOTAGE) { double transition_start = (c->get_clip_in_with_transition() / c->sequence->frame_rate); double transition_end = (c->get_clip_in_with_transition() + c->get_opening_transition()->get_length()) / c->sequence->frame_rate; if (timecode_end < transition_end) { @@ -61,7 +62,7 @@ void apply_audio_effects(Clip* c, double timecode_start, AVFrame* frame, int nb_ } } if (c->get_closing_transition() != NULL) { - if (c->media_type == MEDIA_TYPE_FOOTAGE) { + if (c->media != NULL && c->media->get_type() == MEDIA_TYPE_FOOTAGE) { long length_with_transitions = c->get_timeline_out_with_transition() - c->get_timeline_in_with_transition(); double transition_start = (c->get_clip_in_with_transition() + length_with_transitions - c->get_closing_transition()->get_length()) / c->sequence->frame_rate; double transition_end = (c->get_clip_in_with_transition() + length_with_transitions) / c->sequence->frame_rate; @@ -114,250 +115,246 @@ void cache_audio_worker(Clip* c, bool scrubbing, QVector& nests) { AVFrame* frame; int nb_bytes = INT_MAX; - switch (c->media_type) { - case MEDIA_TYPE_FOOTAGE: - { - double timebase = av_q2d(c->stream->time_base); + if (c->media == NULL) { + frame = c->frame; + nb_bytes = frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels; + while ((c->frame_sample_index == -1 || c->frame_sample_index >= nb_bytes) && nb_bytes > 0) { + // create "new frame" + memset(c->frame->data[0], 0, nb_bytes); + apply_audio_effects(c, bytes_to_seconds(frame->pts, frame->channels, frame->sample_rate), frame, nb_bytes, nests); + c->frame->pts += nb_bytes; + c->frame_sample_index = 0; + if (c->audio_buffer_write == 0) { + c->audio_buffer_write = get_buffer_offset_from_frame(last_fr, qMax(timeline_in, target_frame)); + } + int offset = audio_ibuffer_read - c->audio_buffer_write; + if (offset > 0) { + c->audio_buffer_write += offset; + c->frame_sample_index += offset; + } + } + } else if (c->media->get_type() == MEDIA_TYPE_FOOTAGE) { + double timebase = av_q2d(c->stream->time_base); - frame = c->queue.at(0); + frame = c->queue.at(0); // retrieve frame - bool new_frame = false; - while ((c->frame_sample_index == -1 || c->frame_sample_index >= nb_bytes) && nb_bytes > 0) { - // no more audio left in frame, get a new one - if (!c->reached_end) { - int loop = 0; + bool new_frame = false; + while ((c->frame_sample_index == -1 || c->frame_sample_index >= nb_bytes) && nb_bytes > 0) { + // no more audio left in frame, get a new one + if (!c->reached_end) { + int loop = 0; - if (c->reverse && !c->audio_just_reset) { - avcodec_flush_buffers(c->codecCtx); - c->reached_end = false; - int64_t backtrack_seek = qMax(c->reverse_target - static_cast(av_q2d(av_inv_q(c->stream->time_base))), static_cast(0)); - av_seek_frame(c->formatCtx, c->stream->index, backtrack_seek, AVSEEK_FLAG_BACKWARD); + if (c->reverse && !c->audio_just_reset) { + avcodec_flush_buffers(c->codecCtx); + c->reached_end = false; + int64_t backtrack_seek = qMax(c->reverse_target - static_cast(av_q2d(av_inv_q(c->stream->time_base))), static_cast(0)); + av_seek_frame(c->formatCtx, c->stream->index, backtrack_seek, AVSEEK_FLAG_BACKWARD); #ifdef AUDIOWARNINGS - if (backtrack_seek == 0) { - dout << "backtracked to 0"; - } + if (backtrack_seek == 0) { + dout << "backtracked to 0"; + } #endif - } + } - do { - av_frame_unref(frame); + do { + av_frame_unref(frame); - int ret; + int ret; - while ((ret = av_buffersink_get_frame(c->buffersink_ctx, frame)) == AVERROR(EAGAIN)) { - ret = retrieve_next_frame(c, c->frame); - if (ret >= 0) { - if ((ret = av_buffersrc_add_frame_flags(c->buffersrc_ctx, c->frame, AV_BUFFERSRC_FLAG_KEEP_REF)) < 0) { - dout << "[ERROR] Could not feed filtergraph -" << ret; - break; - } - } else { - if (ret == AVERROR_EOF) { + while ((ret = av_buffersink_get_frame(c->buffersink_ctx, frame)) == AVERROR(EAGAIN)) { + ret = retrieve_next_frame(c, c->frame); + if (ret >= 0) { + if ((ret = av_buffersrc_add_frame_flags(c->buffersrc_ctx, c->frame, AV_BUFFERSRC_FLAG_KEEP_REF)) < 0) { + dout << "[ERROR] Could not feed filtergraph -" << ret; + break; + } + } else { + if (ret == AVERROR_EOF) { #ifdef AUDIOWARNINGS - dout << "reached EOF while reading"; + dout << "reached EOF while reading"; #endif - // TODO revise usage of reached_end in audio - if (!c->reverse) { - c->reached_end = true; - } else { - } - } else { - dout << "[WARNING] Raw audio frame data could not be retrieved." << ret; - c->reached_end = true; - } - break; - } - } + // TODO revise usage of reached_end in audio + if (!c->reverse) { + c->reached_end = true; + } else { + } + } else { + dout << "[WARNING] Raw audio frame data could not be retrieved." << ret; + c->reached_end = true; + } + break; + } + } - if (ret < 0) { - if (ret != AVERROR_EOF) { - dout << "[ERROR] Could not pull from filtergraph"; - c->reached_end = true; - break; - } else { + if (ret < 0) { + if (ret != AVERROR_EOF) { + dout << "[ERROR] Could not pull from filtergraph"; + c->reached_end = true; + break; + } else { #ifdef AUDIOWARNINGS - dout << "reached EOF while pulling from filtergraph"; + dout << "reached EOF while pulling from filtergraph"; #endif - if (!c->reverse) break; - } - } + if (!c->reverse) break; + } + } - if (c->reverse) { - if (loop > 1) { - AVFrame* rev_frame = c->queue.at(1); - if (ret != AVERROR_EOF) { - if (loop == 2) { + if (c->reverse) { + if (loop > 1) { + AVFrame* rev_frame = c->queue.at(1); + if (ret != AVERROR_EOF) { + if (loop == 2) { #ifdef AUDIOWARNINGS - dout << "starting rev_frame"; + dout << "starting rev_frame"; #endif - rev_frame->nb_samples = 0; - rev_frame->pts = c->frame->pkt_pts; - } - int offset = rev_frame->nb_samples * av_get_bytes_per_sample(static_cast(rev_frame->format)) * rev_frame->channels; + rev_frame->nb_samples = 0; + rev_frame->pts = c->frame->pkt_pts; + } + int offset = rev_frame->nb_samples * av_get_bytes_per_sample(static_cast(rev_frame->format)) * rev_frame->channels; #ifdef AUDIOWARNINGS - dout << "offset 1:" << offset; - dout << "retrieved samples:" << frame->nb_samples << "size:" << (frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels); + dout << "offset 1:" << offset; + dout << "retrieved samples:" << frame->nb_samples << "size:" << (frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels); #endif - memcpy( - rev_frame->data[0]+offset, - frame->data[0], - (frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels) - ); + memcpy( + rev_frame->data[0]+offset, + frame->data[0], + (frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels) + ); #ifdef AUDIOWARNINGS - dout << "pts:" << c->frame->pts << "dur:" << c->frame->pkt_duration << "rev_target:" << c->reverse_target << "offset:" << offset << "limit:" << rev_frame->linesize[0]; + dout << "pts:" << c->frame->pts << "dur:" << c->frame->pkt_duration << "rev_target:" << c->reverse_target << "offset:" << offset << "limit:" << rev_frame->linesize[0]; #endif - } + } - rev_frame->nb_samples += frame->nb_samples; + rev_frame->nb_samples += frame->nb_samples; - if ((c->frame->pts >= c->reverse_target) || (ret == AVERROR_EOF)) { + if ((c->frame->pts >= c->reverse_target) || (ret == AVERROR_EOF)) { /* #ifdef AUDIOWARNINGS - dout << "time for the end of rev cache" << rev_frame->nb_samples << c->rev_target << c->frame->pts << c->frame->pkt_duration << c->frame->nb_samples; - dout << "diff:" << (c->frame->pkt_pts + c->frame->pkt_duration) - c->rev_target; + dout << "time for the end of rev cache" << rev_frame->nb_samples << c->rev_target << c->frame->pts << c->frame->pkt_duration << c->frame->nb_samples; + dout << "diff:" << (c->frame->pkt_pts + c->frame->pkt_duration) - c->rev_target; #endif int cutoff = qRound64((((c->frame->pkt_pts + c->frame->pkt_duration) - c->reverse_target) * timebase) * audio_output->format().sampleRate()); - if (cutoff > 0) { + if (cutoff > 0) { #ifdef AUDIOWARNINGS dout << "cut off" << cutoff << "samples (rate:" << audio_output->format().sampleRate() << ")"; #endif - rev_frame->nb_samples -= cutoff; - } + rev_frame->nb_samples -= cutoff; + } */ #ifdef AUDIOWARNINGS - dout << "pre cutoff deets::: rev_frame.pts:" << rev_frame->pts << "rev_frame.nb_samples" << rev_frame->nb_samples << "rev_target:" << c->reverse_target; + dout << "pre cutoff deets::: rev_frame.pts:" << rev_frame->pts << "rev_frame.nb_samples" << rev_frame->nb_samples << "rev_target:" << c->reverse_target; #endif rev_frame->nb_samples = qRound64(static_cast(c->reverse_target - rev_frame->pts) / c->stream->codecpar->sample_rate * (audio_output->format().sampleRate() / c->speed)); #ifdef AUDIOWARNINGS - dout << "post cutoff deets::" << rev_frame->nb_samples; + dout << "post cutoff deets::" << rev_frame->nb_samples; #endif - int frame_size = rev_frame->nb_samples * rev_frame->channels * av_get_bytes_per_sample(static_cast(rev_frame->format)); - int half_frame_size = frame_size >> 1; + int frame_size = rev_frame->nb_samples * rev_frame->channels * av_get_bytes_per_sample(static_cast(rev_frame->format)); + int half_frame_size = frame_size >> 1; - int sample_size = rev_frame->channels*av_get_bytes_per_sample(static_cast(rev_frame->format)); - char* temp_chars = new char[sample_size]; - for (int i=0;idata[0][i+j]; - } - for (int j=0;jdata[0][i+j] = rev_frame->data[0][frame_size-i-sample_size+j]; - } - for (int j=0;jdata[0][frame_size-i-sample_size+j] = temp_chars[j]; - } - } - delete [] temp_chars; + int sample_size = rev_frame->channels*av_get_bytes_per_sample(static_cast(rev_frame->format)); + char* temp_chars = new char[sample_size]; + for (int i=0;idata[0][i+j]; + } + for (int j=0;jdata[0][i+j] = rev_frame->data[0][frame_size-i-sample_size+j]; + } + for (int j=0;jdata[0][frame_size-i-sample_size+j] = temp_chars[j]; + } + } + delete [] temp_chars; - c->reverse_target = rev_frame->pts; - frame = rev_frame; - break; - } - } + c->reverse_target = rev_frame->pts; + frame = rev_frame; + break; + } + } - loop++; + loop++; #ifdef AUDIOWARNINGS - dout << "loop" << loop; + dout << "loop" << loop; #endif - } else { - frame->pts = c->frame->pts; - break; - } - } while (true); - } else { - // if there is no more data in the file, we flush the remainder out of swresample - break; + } else { + frame->pts = c->frame->pts; + break; + } + } while (true); + } else { + // if there is no more data in the file, we flush the remainder out of swresample + break; } - new_frame = true; + new_frame = true; - if (c->frame_sample_index < 0) { - c->frame_sample_index = 0; - } else { - c->frame_sample_index -= nb_bytes; - } + if (c->frame_sample_index < 0) { + c->frame_sample_index = 0; + } else { + c->frame_sample_index -= nb_bytes; + } - nb_bytes = frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels; + nb_bytes = frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels; if (c->audio_just_reset) { // get precise sample offset for the elected clip_in from this audio frame - double target_sts = playhead_to_clip_seconds(c, c->audio_target_frame); - double frame_sts = ((frame->pts - c->stream->start_time) * timebase); + double target_sts = playhead_to_clip_seconds(c, c->audio_target_frame); + double frame_sts = ((frame->pts - c->stream->start_time) * timebase); int nb_samples = qRound64((target_sts - frame_sts)*audio_output->format().sampleRate()); - c->frame_sample_index = nb_samples * 4; + c->frame_sample_index = nb_samples * 4; #ifdef AUDIOWARNINGS - dout << "fsts:" << frame_sts << "tsts:" << target_sts << "nbs:" << nb_samples << "nbb:" << nb_bytes << "rev_targetToSec:" << (c->reverse_target * timebase); - dout << "fsi-calc:" << c->frame_sample_index; + dout << "fsts:" << frame_sts << "tsts:" << target_sts << "nbs:" << nb_samples << "nbb:" << nb_bytes << "rev_targetToSec:" << (c->reverse_target * timebase); + dout << "fsi-calc:" << c->frame_sample_index; #endif - if (c->reverse) c->frame_sample_index = nb_bytes - c->frame_sample_index; + if (c->reverse) c->frame_sample_index = nb_bytes - c->frame_sample_index; c->audio_just_reset = false; - } + } #ifdef AUDIOWARNINGS - dout << "fsi-post-post:" << c->frame_sample_index; + dout << "fsi-post-post:" << c->frame_sample_index; #endif - if (c->audio_buffer_write == 0) { - c->audio_buffer_write = get_buffer_offset_from_frame(last_fr, qMax(timeline_in, target_frame)); + if (c->audio_buffer_write == 0) { + c->audio_buffer_write = get_buffer_offset_from_frame(last_fr, qMax(timeline_in, target_frame)); - if (frame_skip > 0) { - int target = get_buffer_offset_from_frame(last_fr, qMax(timeline_in + frame_skip, target_frame)); - c->frame_sample_index += (target - c->audio_buffer_write); - c->audio_buffer_write = target; - } - } + if (frame_skip > 0) { + int target = get_buffer_offset_from_frame(last_fr, qMax(timeline_in + frame_skip, target_frame)); + c->frame_sample_index += (target - c->audio_buffer_write); + c->audio_buffer_write = target; + } + } - int offset = audio_ibuffer_read - c->audio_buffer_write; - if (offset > 0) { - c->audio_buffer_write += offset; - c->frame_sample_index += offset; - } + int offset = audio_ibuffer_read - c->audio_buffer_write; + if (offset > 0) { + c->audio_buffer_write += offset; + c->frame_sample_index += offset; + } - // try to correct negative fsi - if (c->frame_sample_index < 0) { - c->audio_buffer_write -= c->frame_sample_index; - c->frame_sample_index = 0; - } - } + // try to correct negative fsi + if (c->frame_sample_index < 0) { + c->audio_buffer_write -= c->frame_sample_index; + c->frame_sample_index = 0; + } + } - if (c->reverse) frame = c->queue.at(1); + if (c->reverse) frame = c->queue.at(1); #ifdef AUDIOWARNINGS - dout << "j" << c->frame_sample_index << nb_bytes; + dout << "j" << c->frame_sample_index << nb_bytes; #endif - // apply any audio effects to the data - if (nb_bytes == INT_MAX) nb_bytes = frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels; - if (new_frame) { + // apply any audio effects to the data + if (nb_bytes == INT_MAX) nb_bytes = frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels; + if (new_frame) { apply_audio_effects(c, bytes_to_seconds(c->audio_buffer_write, 2, audio_output->format().sampleRate()) + audio_ibuffer_timecode + ((double)c->get_clip_in_with_transition()/c->sequence->frame_rate) - ((double)timeline_in/last_fr), frame, nb_bytes, nests); - } - } - break; - case MEDIA_TYPE_TONE: - frame = c->frame; - nb_bytes = frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels; - while ((c->frame_sample_index == -1 || c->frame_sample_index >= nb_bytes) && nb_bytes > 0) { - // create "new frame" - memset(c->frame->data[0], 0, nb_bytes); - apply_audio_effects(c, bytes_to_seconds(frame->pts, frame->channels, frame->sample_rate), frame, nb_bytes, nests); - c->frame->pts += nb_bytes; - c->frame_sample_index = 0; - if (c->audio_buffer_write == 0) { - c->audio_buffer_write = get_buffer_offset_from_frame(last_fr, qMax(timeline_in, target_frame)); - } - int offset = audio_ibuffer_read - c->audio_buffer_write; - if (offset > 0) { - c->audio_buffer_write += offset; - c->frame_sample_index += offset; - } - } - break; - default: // shouldn't ever get here - dout << "[ERROR] Tried to cache a non-footage/tone clip"; - return; + } + } else { + // shouldn't ever get here + dout << "[ERROR] Tried to cache a non-footage/tone clip"; + return; } // mix audio into internal buffer @@ -452,8 +449,8 @@ void cache_video_worker(Clip* c, long playhead) { while (true) { AVFrame* frame = av_frame_alloc(); - Media* media = static_cast(c->media); - MediaStream* ms = media->get_stream_from_file_index(true, c->media_stream); + Footage* media = c->media->to_footage(); + FootageStream* ms = media->get_stream_from_file_index(true, c->media_stream); while ((retr_ret = av_buffersink_get_frame(c->buffersink_ctx, frame)) == AVERROR(EAGAIN)) { if (c->multithreaded && c->cacher->interrupt) return; // abort @@ -539,85 +536,83 @@ void cache_video_worker(Clip* c, long playhead) { void reset_cache(Clip* c, long target_frame) { // if we seek to a whole other place in the timeline, we'll need to reset the cache with new values - switch (c->media_type) { - case MEDIA_TYPE_FOOTAGE: - { - MediaStream* ms = static_cast(c->media)->get_stream_from_file_index(c->track < 0, c->media_stream); - if (ms->infinite_length) { - /*avcodec_flush_buffers(c->codecCtx); - av_seek_frame(c->formatCtx, ms->file_index, 0, AVSEEK_FLAG_BACKWARD);*/ - c->use_existing_frame = false; - } else { - if (c->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { - // clear current queue - c->queue_clear(); + if (c->media == NULL) { + if (c->track >= 0) { + // tone clip + c->reached_end = false; + c->audio_target_frame = target_frame; + c->frame_sample_index = -1; + c->frame->pts = 0; + } + } else { + FootageStream* ms = c->media->to_footage()->get_stream_from_file_index(c->track < 0, c->media_stream); + if (ms->infinite_length) { + /*avcodec_flush_buffers(c->codecCtx); + av_seek_frame(c->formatCtx, ms->file_index, 0, AVSEEK_FLAG_BACKWARD);*/ + c->use_existing_frame = false; + } else { + if (c->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + // clear current queue + c->queue_clear(); - // seeks to nearest keyframe (target_frame represents internal clip frame) + // seeks to nearest keyframe (target_frame represents internal clip frame) int64_t target_ts = seconds_to_timestamp(c, playhead_to_clip_seconds(c, target_frame)); - int64_t seek_ts = target_ts; - int64_t timebase_half_second = qRound64(av_q2d(av_inv_q(c->stream->time_base))); + int64_t seek_ts = target_ts; + int64_t timebase_half_second = qRound64(av_q2d(av_inv_q(c->stream->time_base))); if (c->reverse) seek_ts -= timebase_half_second; - while (true) { - // flush ffmpeg codecs - avcodec_flush_buffers(c->codecCtx); - c->reached_end = false; + while (true) { + // flush ffmpeg codecs + avcodec_flush_buffers(c->codecCtx); + c->reached_end = false; - if (seek_ts > 0) { - av_seek_frame(c->formatCtx, ms->file_index, seek_ts, AVSEEK_FLAG_BACKWARD); + if (seek_ts > 0) { + av_seek_frame(c->formatCtx, ms->file_index, seek_ts, AVSEEK_FLAG_BACKWARD); - av_frame_unref(c->frame); - int ret = retrieve_next_frame(c, c->frame); - if (ret < 0) { - dout << "[WARNING] Seeking terminated prematurely"; - break; + av_frame_unref(c->frame); + int ret = retrieve_next_frame(c, c->frame); + if (ret < 0) { + dout << "[WARNING] Seeking terminated prematurely"; + break; } - if (c->frame->pts <= target_ts) { - c->use_existing_frame = true; - break; - } else { - seek_ts -= timebase_half_second; - } - } else { - av_frame_unref(c->frame); - av_seek_frame(c->formatCtx, ms->file_index, 0, AVSEEK_FLAG_BACKWARD); - c->use_existing_frame = false; - break; - } - } - } else if (c->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { - // flush ffmpeg codecs - avcodec_flush_buffers(c->codecCtx); - c->reached_end = false; + if (c->frame->pts <= target_ts) { + c->use_existing_frame = true; + break; + } else { + seek_ts -= timebase_half_second; + } + } else { + av_frame_unref(c->frame); + av_seek_frame(c->formatCtx, ms->file_index, 0, AVSEEK_FLAG_BACKWARD); + c->use_existing_frame = false; + break; + } + } + } else if (c->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { + // flush ffmpeg codecs + avcodec_flush_buffers(c->codecCtx); + c->reached_end = false; - // seek (target_frame represents timeline timecode in frames, not clip timecode) + // seek (target_frame represents timeline timecode in frames, not clip timecode) - int64_t timestamp = seconds_to_timestamp(c, playhead_to_clip_seconds(c, target_frame)); + int64_t timestamp = seconds_to_timestamp(c, playhead_to_clip_seconds(c, target_frame)); if (c->reverse) { - c->reverse_target = timestamp; - timestamp -= av_q2d(av_inv_q(c->stream->time_base)); + c->reverse_target = timestamp; + timestamp -= av_q2d(av_inv_q(c->stream->time_base)); #ifdef AUDIOWARNINGS - dout << "seeking to" << timestamp << "(originally" << c->reverse_target << ")"; - } else { - dout << "reset called; seeking to" << timestamp; + dout << "seeking to" << timestamp << "(originally" << c->reverse_target << ")"; + } else { + dout << "reset called; seeking to" << timestamp; #endif - } + } av_seek_frame(c->formatCtx, ms->file_index, timestamp, AVSEEK_FLAG_BACKWARD); - c->audio_target_frame = target_frame; - c->frame_sample_index = -1; - c->audio_just_reset = true; - } - } - } - break; - case MEDIA_TYPE_TONE: - c->reached_end = false; - c->audio_target_frame = target_frame; - c->frame_sample_index = -1; - c->frame->pts = 0; - break; - } + c->audio_target_frame = target_frame; + c->frame_sample_index = -1; + c->audio_just_reset = true; + } + } + } } Cacher::Cacher(Clip* c) : clip(c) {} @@ -625,85 +620,97 @@ Cacher::Cacher(Clip* c) : clip(c) {} AVSampleFormat sample_format = AV_SAMPLE_FMT_S16; void open_clip_worker(Clip* clip) { - switch (clip->media_type) { - case MEDIA_TYPE_FOOTAGE: - { - // opens file resource for FFmpeg and prepares Clip struct for playback - Media* m = static_cast(clip->media); - QByteArray ba = m->url.toUtf8(); - const char* filename = ba.constData(); - MediaStream* ms = m->get_stream_from_file_index(clip->track < 0, clip->media_stream); + if (clip->media == NULL) { + if (clip->track >= 0) { + clip->frame = av_frame_alloc(); + clip->frame->format = sample_format; + clip->frame->channel_layout = clip->sequence->audio_layout; + clip->frame->channels = av_get_channel_layout_nb_channels(clip->frame->channel_layout); + clip->frame->sample_rate = audio_output->format().sampleRate(); + clip->frame->nb_samples = 2048; + av_frame_make_writable(clip->frame); + if (av_frame_get_buffer(clip->frame, 0)) { + dout << "[ERROR] Could not allocate buffer for tone clip"; + } + clip->audio_reset = true; + } + } else if (clip->media->get_type() == MEDIA_TYPE_FOOTAGE) { + // opens file resource for FFmpeg and prepares Clip struct for playback + Footage* m = clip->media->to_footage(); + QByteArray ba = m->url.toUtf8(); + const char* filename = ba.constData(); + FootageStream* ms = m->get_stream_from_file_index(clip->track < 0, clip->media_stream); - int errCode = avformat_open_input( - &clip->formatCtx, - filename, - NULL, - NULL - ); - if (errCode != 0) { - char err[1024]; - av_strerror(errCode, err, 1024); - dout << "[ERROR] Could not open" << filename << "-" << err; - return; - } + int errCode = avformat_open_input( + &clip->formatCtx, + filename, + NULL, + NULL + ); + if (errCode != 0) { + char err[1024]; + av_strerror(errCode, err, 1024); + dout << "[ERROR] Could not open" << filename << "-" << err; + return; + } - errCode = avformat_find_stream_info(clip->formatCtx, NULL); - if (errCode < 0) { - char err[1024]; - av_strerror(errCode, err, 1024); - dout << "[ERROR] Could not open" << filename << "-" << err; - return; - } + errCode = avformat_find_stream_info(clip->formatCtx, NULL); + if (errCode < 0) { + char err[1024]; + av_strerror(errCode, err, 1024); + dout << "[ERROR] Could not open" << filename << "-" << err; + return; + } - av_dump_format(clip->formatCtx, 0, filename, 0); + av_dump_format(clip->formatCtx, 0, filename, 0); - clip->stream = clip->formatCtx->streams[ms->file_index]; - clip->codec = avcodec_find_decoder(clip->stream->codecpar->codec_id); - clip->codecCtx = avcodec_alloc_context3(clip->codec); - avcodec_parameters_to_context(clip->codecCtx, clip->stream->codecpar); + clip->stream = clip->formatCtx->streams[ms->file_index]; + clip->codec = avcodec_find_decoder(clip->stream->codecpar->codec_id); + clip->codecCtx = avcodec_alloc_context3(clip->codec); + avcodec_parameters_to_context(clip->codecCtx, clip->stream->codecpar); - clip->max_queue_size = (ms->infinite_length) ? 1 : qCeil(ms->video_frame_rate*0.5); - if (ms->video_interlacing != VIDEO_PROGRESSIVE) clip->max_queue_size *= 2; + clip->max_queue_size = (ms->infinite_length) ? 1 : qCeil(ms->video_frame_rate*0.5); + if (ms->video_interlacing != VIDEO_PROGRESSIVE) clip->max_queue_size *= 2; clip->opts = NULL; - // optimized decoding settings + // optimized decoding settings if (clip->stream->codecpar->codec_id != AV_CODEC_ID_PNG && - clip->stream->codecpar->codec_id != AV_CODEC_ID_APNG && - clip->stream->codecpar->codec_id != AV_CODEC_ID_TIFF && - clip->stream->codecpar->codec_id != AV_CODEC_ID_PSD) { + clip->stream->codecpar->codec_id != AV_CODEC_ID_APNG && + clip->stream->codecpar->codec_id != AV_CODEC_ID_TIFF && + clip->stream->codecpar->codec_id != AV_CODEC_ID_PSD) { av_dict_set(&clip->opts, "threads", "auto", 0); - } - if (clip->stream->codecpar->codec_id == AV_CODEC_ID_H264) { + } + if (clip->stream->codecpar->codec_id == AV_CODEC_ID_H264) { av_dict_set(&clip->opts, "tune", "fastdecode", 0); av_dict_set(&clip->opts, "tune", "zerolatency", 0); } - // Open codec + // Open codec if (avcodec_open2(clip->codecCtx, clip->codec, &clip->opts) < 0) { - dout << "[ERROR] Could not open codec"; - } + dout << "[ERROR] Could not open codec"; + } - // allocate filtergraph - clip->filter_graph = avfilter_graph_alloc(); - if (clip->filter_graph == NULL) { - dout << "[ERROR] Could not create filtergraph"; - } - char filter_args[512]; + // allocate filtergraph + clip->filter_graph = avfilter_graph_alloc(); + if (clip->filter_graph == NULL) { + dout << "[ERROR] Could not create filtergraph"; + } + char filter_args[512]; if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { - snprintf(filter_args, sizeof(filter_args), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", - clip->stream->codecpar->width, - clip->stream->codecpar->height, - clip->stream->codecpar->format, - clip->stream->time_base.num, - clip->stream->time_base.den, - clip->stream->codecpar->sample_aspect_ratio.num, - clip->stream->codecpar->sample_aspect_ratio.den - ); + snprintf(filter_args, sizeof(filter_args), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", + clip->stream->codecpar->width, + clip->stream->codecpar->height, + clip->stream->codecpar->format, + clip->stream->time_base.num, + clip->stream->time_base.den, + clip->stream->codecpar->sample_aspect_ratio.num, + clip->stream->codecpar->sample_aspect_ratio.den + ); - avfilter_graph_create_filter(&clip->buffersrc_ctx, avfilter_get_by_name("buffer"), "in", filter_args, NULL, clip->filter_graph); - avfilter_graph_create_filter(&clip->buffersink_ctx, avfilter_get_by_name("buffersink"), "out", NULL, NULL, clip->filter_graph); + avfilter_graph_create_filter(&clip->buffersrc_ctx, avfilter_get_by_name("buffer"), "in", filter_args, NULL, clip->filter_graph); + avfilter_graph_create_filter(&clip->buffersink_ctx, avfilter_get_by_name("buffersink"), "out", NULL, NULL, clip->filter_graph); /*enum AVPixelFormat sinkpix_fmts[] = { static_cast(dest_format), AV_PIX_FMT_NONE }; if (av_opt_set_int_list(clip->buffersink_ctx, "pix_fmts", sinkpix_fmts, AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN) < 0) { @@ -713,14 +720,14 @@ void open_clip_worker(Clip* clip) { AVFilterContext* last_filter = clip->buffersrc_ctx; if (ms->video_interlacing != VIDEO_PROGRESSIVE) { - AVFilterContext* yadif_filter; - char yadif_args[100]; + AVFilterContext* yadif_filter; + char yadif_args[100]; snprintf(yadif_args, sizeof(yadif_args), "mode=3:parity=%d", ((ms->video_interlacing == VIDEO_TOP_FIELD_FIRST) ? 0 : 1)); // there's a CUDA version if we start using nvdec/nvenc - avfilter_graph_create_filter(&yadif_filter, avfilter_get_by_name("yadif"), "yadif", yadif_args, NULL, clip->filter_graph); + avfilter_graph_create_filter(&yadif_filter, avfilter_get_by_name("yadif"), "yadif", yadif_args, NULL, clip->filter_graph); avfilter_link(last_filter, 0, yadif_filter, 0); last_filter = yadif_filter; - } + } /* stabilization code one day */ bool stabilize = false; @@ -743,110 +750,95 @@ void open_clip_worker(Clip* clip) { avfilter_link(format_conv, 0, clip->buffersink_ctx, 0); avfilter_graph_config(clip->filter_graph, NULL); - } else if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { - if (clip->codecCtx->channel_layout == 0) clip->codecCtx->channel_layout = av_get_default_channel_layout(clip->stream->codecpar->channels); + } else if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { + if (clip->codecCtx->channel_layout == 0) clip->codecCtx->channel_layout = av_get_default_channel_layout(clip->stream->codecpar->channels); - // set up cache - clip->queue.append(av_frame_alloc()); - if (clip->reverse) { - AVFrame* reverse_frame = av_frame_alloc(); + // set up cache + clip->queue.append(av_frame_alloc()); + if (clip->reverse) { + AVFrame* reverse_frame = av_frame_alloc(); - reverse_frame->format = sample_format; + reverse_frame->format = sample_format; reverse_frame->nb_samples = audio_output->format().sampleRate()*2; - reverse_frame->channel_layout = clip->sequence->audio_layout; - reverse_frame->channels = av_get_channel_layout_nb_channels(clip->sequence->audio_layout); - av_frame_get_buffer(reverse_frame, 0); + reverse_frame->channel_layout = clip->sequence->audio_layout; + reverse_frame->channels = av_get_channel_layout_nb_channels(clip->sequence->audio_layout); + av_frame_get_buffer(reverse_frame, 0); - clip->queue.append(reverse_frame); - } + clip->queue.append(reverse_frame); + } - snprintf(filter_args, sizeof(filter_args), "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%" PRIx64, - clip->stream->time_base.num, - clip->stream->time_base.den, - clip->stream->codecpar->sample_rate, - av_get_sample_fmt_name(clip->codecCtx->sample_fmt), - clip->codecCtx->channel_layout - ); + snprintf(filter_args, sizeof(filter_args), "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%" PRIx64, + clip->stream->time_base.num, + clip->stream->time_base.den, + clip->stream->codecpar->sample_rate, + av_get_sample_fmt_name(clip->codecCtx->sample_fmt), + clip->codecCtx->channel_layout + ); - avfilter_graph_create_filter(&clip->buffersrc_ctx, avfilter_get_by_name("abuffer"), "in", filter_args, NULL, clip->filter_graph); - avfilter_graph_create_filter(&clip->buffersink_ctx, avfilter_get_by_name("abuffersink"), "out", NULL, NULL, clip->filter_graph); + avfilter_graph_create_filter(&clip->buffersrc_ctx, avfilter_get_by_name("abuffer"), "in", filter_args, NULL, clip->filter_graph); + avfilter_graph_create_filter(&clip->buffersink_ctx, avfilter_get_by_name("abuffersink"), "out", NULL, NULL, clip->filter_graph); - enum AVSampleFormat sample_fmts[] = { sample_format, static_cast(-1) }; - if (av_opt_set_int_list(clip->buffersink_ctx, "sample_fmts", sample_fmts, -1, AV_OPT_SEARCH_CHILDREN) < 0) { - dout << "[ERROR] Could not set output sample format"; - } + enum AVSampleFormat sample_fmts[] = { sample_format, static_cast(-1) }; + if (av_opt_set_int_list(clip->buffersink_ctx, "sample_fmts", sample_fmts, -1, AV_OPT_SEARCH_CHILDREN) < 0) { + dout << "[ERROR] Could not set output sample format"; + } - int64_t channel_layouts[] = { AV_CH_LAYOUT_STEREO, static_cast(-1) }; - if (av_opt_set_int_list(clip->buffersink_ctx, "channel_layouts", channel_layouts, -1, AV_OPT_SEARCH_CHILDREN) < 0) { - dout << "[ERROR] Could not set output sample format"; - } + int64_t channel_layouts[] = { AV_CH_LAYOUT_STEREO, static_cast(-1) }; + if (av_opt_set_int_list(clip->buffersink_ctx, "channel_layouts", channel_layouts, -1, AV_OPT_SEARCH_CHILDREN) < 0) { + dout << "[ERROR] Could not set output sample format"; + } - int target_sample_rate = audio_output->format().sampleRate(); + int target_sample_rate = audio_output->format().sampleRate(); - if (qFuzzyCompare(clip->speed, 1.0)) { - avfilter_link(clip->buffersrc_ctx, 0, clip->buffersink_ctx, 0); - } else if (clip->maintain_audio_pitch) { - AVFilterContext* previous_filter = clip->buffersrc_ctx; - AVFilterContext* last_filter = clip->buffersrc_ctx; + if (qFuzzyCompare(clip->speed, 1.0)) { + avfilter_link(clip->buffersrc_ctx, 0, clip->buffersink_ctx, 0); + } else if (clip->maintain_audio_pitch) { + AVFilterContext* previous_filter = clip->buffersrc_ctx; + AVFilterContext* last_filter = clip->buffersrc_ctx; - char speed_param[10]; + char speed_param[10]; - if (clip->speed != 1.0) { - double base = (clip->speed > 1.0) ? 2.0 : 0.5; + if (clip->speed != 1.0) { + double base = (clip->speed > 1.0) ? 2.0 : 0.5; - double speedlog = log(clip->speed) / log(base); - int whole2 = qFloor(speedlog); - speedlog -= whole2; + double speedlog = log(clip->speed) / log(base); + int whole2 = qFloor(speedlog); + speedlog -= whole2; - if (whole2 > 0) { - snprintf(speed_param, sizeof(speed_param), "%f", base); - for (int i=0;ifilter_graph); - avfilter_link(previous_filter, 0, tempo_filter, 0); - previous_filter = tempo_filter; - } - } + if (whole2 > 0) { + snprintf(speed_param, sizeof(speed_param), "%f", base); + for (int i=0;ifilter_graph); + avfilter_link(previous_filter, 0, tempo_filter, 0); + previous_filter = tempo_filter; + } + } - snprintf(speed_param, sizeof(speed_param), "%f", qPow(base, speedlog)); - last_filter = NULL; - avfilter_graph_create_filter(&last_filter, avfilter_get_by_name("atempo"), "atempo", speed_param, NULL, clip->filter_graph); - avfilter_link(previous_filter, 0, last_filter, 0); - } + snprintf(speed_param, sizeof(speed_param), "%f", qPow(base, speedlog)); + last_filter = NULL; + avfilter_graph_create_filter(&last_filter, avfilter_get_by_name("atempo"), "atempo", speed_param, NULL, clip->filter_graph); + avfilter_link(previous_filter, 0, last_filter, 0); + } - avfilter_link(last_filter, 0, clip->buffersink_ctx, 0); - } else { - target_sample_rate = qRound64(target_sample_rate / clip->speed); - avfilter_link(clip->buffersrc_ctx, 0, clip->buffersink_ctx, 0); - } + avfilter_link(last_filter, 0, clip->buffersink_ctx, 0); + } else { + target_sample_rate = qRound64(target_sample_rate / clip->speed); + avfilter_link(clip->buffersrc_ctx, 0, clip->buffersink_ctx, 0); + } - int sample_rates[] = { target_sample_rate, 0 }; - if (av_opt_set_int_list(clip->buffersink_ctx, "sample_rates", sample_rates, 0, AV_OPT_SEARCH_CHILDREN) < 0) { - dout << "[ERROR] Could not set output sample rates"; - } + int sample_rates[] = { target_sample_rate, 0 }; + if (av_opt_set_int_list(clip->buffersink_ctx, "sample_rates", sample_rates, 0, AV_OPT_SEARCH_CHILDREN) < 0) { + dout << "[ERROR] Could not set output sample rates"; + } - avfilter_graph_config(clip->filter_graph, NULL); + avfilter_graph_config(clip->filter_graph, NULL); - clip->audio_reset = true; - } + clip->audio_reset = true; + } - clip->frame = av_frame_alloc(); - } - break; - case MEDIA_TYPE_TONE: - clip->frame = av_frame_alloc(); - clip->frame->format = sample_format; - clip->frame->channel_layout = clip->sequence->audio_layout; - clip->frame->channels = av_get_channel_layout_nb_channels(clip->frame->channel_layout); - clip->frame->sample_rate = audio_output->format().sampleRate(); - clip->frame->nb_samples = 2048; - av_frame_make_writable(clip->frame); - if (av_frame_get_buffer(clip->frame, 0)) { - dout << "[ERROR] Could not allocate buffer for tone clip"; - } - clip->audio_reset = true; - break; - } + clip->frame = av_frame_alloc(); + } for (int i=0;ieffects.size();i++) { clip->effects.at(i)->open(); @@ -864,24 +856,23 @@ void cache_clip_worker(Clip* clip, long playhead, bool reset, bool scrubbing, QV clip->audio_reset = false; } - switch (clip->media_type) { - case MEDIA_TYPE_FOOTAGE: + if (clip->media == NULL) { + if (clip->track >= 0) { + cache_audio_worker(clip, scrubbing, nests); + } + } else if (clip->media->get_type() == MEDIA_TYPE_FOOTAGE) { if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { cache_video_worker(clip, playhead); } else if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { cache_audio_worker(clip, scrubbing, nests); } - break; - case MEDIA_TYPE_TONE: - cache_audio_worker(clip, scrubbing, nests); - break; } } void close_clip_worker(Clip* clip) { clip->finished_opening = false; - if (clip->media_type == MEDIA_TYPE_FOOTAGE) { + if (clip->media != NULL && clip->media->get_type() == MEDIA_TYPE_FOOTAGE) { clip->queue_clear(); avfilter_graph_free(&clip->filter_graph); diff --git a/playback/playback.cpp b/playback/playback.cpp index 638a4ec43..73c3ceb91 100644 --- a/playback/playback.cpp +++ b/playback/playback.cpp @@ -2,7 +2,7 @@ #include "project/clip.h" #include "project/sequence.h" -#include "io/media.h" +#include "project/footage.h" #include "playback/audio.h" #include "playback/cacher.h" #include "panels/panels.h" @@ -10,6 +10,7 @@ #include "panels/viewer.h" #include "project/effect.h" #include "panels/effectcontrols.h" +#include "project/media.h" #include "debug.h" extern "C" { @@ -32,36 +33,34 @@ extern "C" { bool texture_failed = false; bool rendering = false; -void open_clip(Clip* clip, bool multithreaded) { - switch (clip->media_type) { - case MEDIA_TYPE_FOOTAGE: - case MEDIA_TYPE_TONE: - clip->multithreaded = multithreaded; - if (multithreaded) { - if (clip->open_lock.tryLock()) { - // maybe keep cacher instance in memory while clip exists for performance? - clip->cacher = new Cacher(clip); - QObject::connect(clip->cacher, SIGNAL(finished()), clip->cacher, SLOT(deleteLater())); - clip->cacher->start((clip->track < 0) ? QThread::NormalPriority : QThread::TimeCriticalPriority); - } - } else { - clip->finished_opening = false; - clip->open = true; +bool clip_uses_cacher(Clip* clip) { + return (clip->media == NULL && clip->track >= 0) || (clip->media != NULL && clip->media->get_type() == MEDIA_TYPE_FOOTAGE); +} - open_clip_worker(clip); - } - break; - case MEDIA_TYPE_SEQUENCE: - case MEDIA_TYPE_SOLID: - clip->open = true; - break; - } +void open_clip(Clip* clip, bool multithreaded) { + if (clip_uses_cacher(clip)) { + clip->multithreaded = multithreaded; + if (multithreaded) { + if (clip->open_lock.tryLock()) { + // maybe keep cacher instance in memory while clip exists for performance? + clip->cacher = new Cacher(clip); + QObject::connect(clip->cacher, SIGNAL(finished()), clip->cacher, SLOT(deleteLater())); + clip->cacher->start((clip->track < 0) ? QThread::NormalPriority : QThread::TimeCriticalPriority); + } + } else { + clip->finished_opening = false; + clip->open = true; + + open_clip_worker(clip); + } + } else { + clip->open = true; + } } void close_clip(Clip* clip) { // destroy opengl texture in main thread - if (clip->texture != NULL) { - clip->texture->destroy(); + if (clip->texture != NULL) { delete clip->texture; clip->texture = NULL; } @@ -77,26 +76,22 @@ void close_clip(Clip* clip) { clip->fbo = NULL; } - switch (clip->media_type) { - case MEDIA_TYPE_FOOTAGE: - case MEDIA_TYPE_TONE: - if (clip->multithreaded) { - clip->cacher->caching = false; - clip->can_cache.wakeAll(); - } else { - close_clip_worker(clip); - } - break; - case MEDIA_TYPE_SEQUENCE: - closeActiveClips(static_cast(clip->media), false); - case MEDIA_TYPE_SOLID: - clip->open = false; - break; - } + if (clip_uses_cacher(clip)) { + if (clip->multithreaded) { + clip->cacher->caching = false; + clip->can_cache.wakeAll(); + } else { + close_clip_worker(clip); + } + } else { + if (clip->media != NULL && clip->media->get_type() == MEDIA_TYPE_SEQUENCE) + closeActiveClips(clip->media->to_sequence(), false); + clip->open = false; + } } void cache_clip(Clip* clip, long playhead, bool reset, bool scrubbing, QVector& nests) { - if (clip->media_type == MEDIA_TYPE_FOOTAGE || clip->media_type == MEDIA_TYPE_TONE) { + if (clip_uses_cacher(clip)) { if (clip->multithreaded) { clip->cacher->playhead = playhead; clip->cacher->reset = reset; @@ -113,7 +108,7 @@ void cache_clip(Clip* clip, long playhead, bool reset, bool scrubbing, QVectorfinished_opening) { - MediaStream* ms = static_cast(c->media)->get_stream_from_file_index(c->track < 0, c->media_stream); + FootageStream* ms = c->media->to_footage()->get_stream_from_file_index(c->track < 0, c->media_stream); int64_t target_pts = qMax(static_cast(0), playhead_to_timestamp(c, playhead)); int64_t second_pts = qRound64(av_q2d(av_inv_q(c->stream->time_base))); @@ -318,11 +313,11 @@ void closeActiveClips(Sequence *s, bool wait) { if (s != NULL) { for (int i=0;iclips.size();i++) { Clip* c = s->clips.at(i); - if (c != NULL) { - if (c->media_type == MEDIA_TYPE_SEQUENCE) { - closeActiveClips(static_cast(c->media), wait); + if (c != NULL) { + if (c->media != NULL && c->media->get_type() == MEDIA_TYPE_SEQUENCE) { + closeActiveClips(c->media->to_sequence(), wait); close_clip(c); - } else if (c->media_type == MEDIA_TYPE_FOOTAGE && c->open) { + } else if (clip_uses_cacher(c) && c->open) { close_clip(c); if (c->multithreaded && wait) c->cacher->wait(); } diff --git a/playback/playback.h b/playback/playback.h index af1d742f6..ed5f19900 100644 --- a/playback/playback.h +++ b/playback/playback.h @@ -12,6 +12,7 @@ struct AVFrame; extern bool texture_failed; extern bool rendering; +bool clip_uses_cacher(Clip* clip); void open_clip(Clip* clip, bool multithreaded); void cache_clip(Clip* clip, long playhead, bool reset, bool scrubbing, QVector &nests); void close_clip(Clip* clip); diff --git a/project/clip.cpp b/project/clip.cpp index e4934fcad..90a0fb48b 100644 --- a/project/clip.cpp +++ b/project/clip.cpp @@ -2,13 +2,14 @@ #include "project/effect.h" #include "project/transition.h" -#include "io/media.h" +#include "project/footage.h" #include "io/config.h" #include "playback/playback.h" #include "playback/cacher.h" #include "panels/project.h" #include "project/sequence.h" #include "panels/timeline.h" +#include "project/media.h" #include "undo.h" extern "C" { @@ -35,7 +36,6 @@ Clip::Clip(Sequence* s) : use_existing_frame(false), filter_graph(NULL), fbo(NULL), - texture(NULL), opts(NULL) { pkt = av_packet_alloc(); @@ -55,7 +55,6 @@ Clip* Clip::copy(Sequence* s) { copy->color_g = color_g; copy->color_b = color_b; copy->media = media; - copy->media_type = media_type; copy->media_stream = media_stream; copy->autoscale = autoscale; copy->speed = speed; @@ -93,29 +92,23 @@ void Clip::reset() { } void Clip::reset_audio() { - switch (media_type) { - case MEDIA_TYPE_FOOTAGE: - case MEDIA_TYPE_TONE: + if (media == NULL || media->get_type() == MEDIA_TYPE_FOOTAGE) { audio_reset = true; - frame_sample_index = -1; - audio_buffer_write = 0; - break; - case MEDIA_TYPE_SEQUENCE: - { - Sequence* nested_sequence = static_cast(media); + frame_sample_index = -1; + audio_buffer_write = 0; + } else if (media->get_type() == MEDIA_TYPE_SEQUENCE) { + Sequence* nested_sequence = media->to_sequence(); for (int i=0;iclips.size();i++) { Clip* c = nested_sequence->clips.at(i); if (c != NULL) c->reset_audio(); } } - break; - } } void Clip::refresh() { // validates media if it was replaced - if (replaced && media_type == MEDIA_TYPE_FOOTAGE) { - Media* m = static_cast(media); + if (replaced && media != NULL && media->get_type() == MEDIA_TYPE_FOOTAGE) { + Footage* m = media->to_footage(); media_stream = (track < 0) ? m->video_tracks.at(0)->file_index : m->audio_tracks.at(0)->file_index; } replaced = false; @@ -166,7 +159,7 @@ Clip::~Clip() { close_clip(this); // make sure clip has closed before clip is destroyed - if (multithreaded && media_type == MEDIA_TYPE_FOOTAGE) { + if (multithreaded && media != NULL && media->get_type() == MEDIA_TYPE_FOOTAGE) { cacher->wait(); } } @@ -210,36 +203,44 @@ long Clip::getLength() { return timeline_out - timeline_in; } +double Clip::getMediaFrameRate() { + Q_ASSERT(track < 0); + if (media != NULL) { + double rate = media->get_frame_rate(media_stream); + if (!qIsNaN(rate)) return rate; + } + if (sequence != NULL) return sequence->frame_rate; +} + void Clip::recalculateMaxLength() { if (sequence != NULL) { double fr = this->sequence->frame_rate; fr /= speed; - switch (media_type) { - case MEDIA_TYPE_FOOTAGE: - { - Media* m = static_cast(media); - MediaStream* ms = m->get_stream_from_file_index(track < 0, media_stream); - if (ms != NULL && ms->infinite_length) { - calculated_length = LONG_MAX; - } else { - calculated_length = m->get_length_in_frames(fr); - } - } - break; - case MEDIA_TYPE_SEQUENCE: - { - Sequence* s = static_cast(media); - calculated_length = refactor_frame_number(s->getEndFrame(), s->frame_rate, fr); - } - break; - /*case MEDIA_TYPE_SOLID: - case MEDIA_TYPE_TONE:*/ - default: - calculated_length = LONG_MAX; - break; - } + calculated_length = LONG_MAX; + + if (media != NULL) { + switch (media->get_type()) { + case MEDIA_TYPE_FOOTAGE: + { + Footage* m = media->to_footage(); + FootageStream* ms = m->get_stream_from_file_index(track < 0, media_stream); + if (ms != NULL && ms->infinite_length) { + calculated_length = LONG_MAX; + } else { + calculated_length = m->get_length_in_frames(fr); + } + } + break; + case MEDIA_TYPE_SEQUENCE: + { + Sequence* s = media->to_sequence(); + calculated_length = refactor_frame_number(s->getEndFrame(), s->frame_rate, fr); + } + break; + } + } } } @@ -247,28 +248,18 @@ long Clip::getMaximumLength() { return calculated_length; } -double Clip::getMediaFrameRate() { - Q_ASSERT(track < 0); - switch (media_type) { - case MEDIA_TYPE_FOOTAGE: return static_cast(media)->get_stream_from_file_index(track < 0, media_stream)->video_frame_rate; - case MEDIA_TYPE_SEQUENCE: return static_cast(media)->frame_rate; - } - if (sequence != NULL) return sequence->frame_rate; - return qSNaN(); -} - int Clip::getWidth() { if (media == NULL && sequence != NULL) return sequence->width; - switch (media_type) { + switch (media->get_type()) { case MEDIA_TYPE_FOOTAGE: { - MediaStream* ms = static_cast(media)->get_stream_from_file_index(track < 0, media_stream); + FootageStream* ms = media->to_footage()->get_stream_from_file_index(track < 0, media_stream); if (ms != NULL) return ms->video_width; if (sequence != NULL) return sequence->width; } case MEDIA_TYPE_SEQUENCE: { - Sequence* s = static_cast(media); + Sequence* s = media->to_sequence(); return s->width; } } @@ -277,16 +268,16 @@ int Clip::getWidth() { int Clip::getHeight() { if (media == NULL && sequence != NULL) return sequence->height; - switch (media_type) { + switch (media->get_type()) { case MEDIA_TYPE_FOOTAGE: { - MediaStream* ms = static_cast(media)->get_stream_from_file_index(track < 0, media_stream); + FootageStream* ms = media->to_footage()->get_stream_from_file_index(track < 0, media_stream); if (ms != NULL) return ms->video_height; if (sequence != NULL) return sequence->height; } case MEDIA_TYPE_SEQUENCE: { - Sequence* s = static_cast(media); + Sequence* s = media->to_sequence(); return s->height; } } diff --git a/project/clip.h b/project/clip.h index 300659e31..6e2ffc75a 100644 --- a/project/clip.h +++ b/project/clip.h @@ -13,9 +13,10 @@ class Effect; class Transition; class QOpenGLFramebufferObject; class ComboAction; +class Media; struct Sequence; -struct Media; -struct MediaStream; +struct Footage; +struct FootageStream; struct AVFormatContext; struct AVStream; @@ -42,9 +43,9 @@ struct Clip long get_timeline_in_with_transition(); long get_timeline_out_with_transition(); long getLength(); + double getMediaFrameRate(); long getMaximumLength(); void recalculateMaxLength(); - double getMediaFrameRate(); int getWidth(); int getHeight(); void refactor_frame_rate(ComboAction* ca, double multiplier, bool change_timeline_points); @@ -64,8 +65,7 @@ struct Clip quint8 color_r; quint8 color_g; quint8 color_b; - void* media; // attached media - int media_type; + Media* media; int media_stream; double speed; double cached_fr; diff --git a/io/media.cpp b/project/footage.cpp similarity index 67% rename from io/media.cpp rename to project/footage.cpp index 8dd4674a9..6dc841eb7 100644 --- a/io/media.cpp +++ b/project/footage.cpp @@ -1,4 +1,4 @@ -#include "media.h" +#include "footage.h" #include #include @@ -10,15 +10,15 @@ extern "C" { #include "project/clip.h" -Media::Media() : ready(false), preview_gen(NULL), invalid(false), in(0), out(0) { +Footage::Footage() : ready(false), preview_gen(NULL), invalid(false), in(0), out(0) { ready_lock.lock(); } -Media::~Media() { +Footage::~Footage() { reset(); } -void Media::reset() { +void Footage::reset() { if (preview_gen != NULL) { preview_gen->cancel(); preview_gen->wait(); @@ -34,11 +34,12 @@ void Media::reset() { ready = false; } -long Media::get_length_in_frames(double frame_rate) { - return qFloor(((double) length / (double) AV_TIME_BASE) * frame_rate); +long Footage::get_length_in_frames(double frame_rate) { + if (length >= 0) return qFloor(((double) length / (double) AV_TIME_BASE) * frame_rate); + return 0; } -MediaStream* Media::get_stream_from_file_index(bool video, int index) { +FootageStream* Footage::get_stream_from_file_index(bool video, int index) { if (video) { for (int i=0;ifile_index == index) { diff --git a/io/media.h b/project/footage.h similarity index 66% rename from io/media.h rename to project/footage.h index 35e65e4aa..55442e3ed 100644 --- a/io/media.h +++ b/project/footage.h @@ -1,5 +1,5 @@ -#ifndef MEDIA_H -#define MEDIA_H +#ifndef FOOTAGE_H +#define FOOTAGE_H #include #include @@ -8,12 +8,6 @@ #include #include -#define MEDIA_TYPE_FOOTAGE 0 -#define MEDIA_TYPE_SEQUENCE 1 -#define MEDIA_TYPE_FOLDER 2 -#define MEDIA_TYPE_SOLID 3 -#define MEDIA_TYPE_TONE 4 - #define VIDEO_PROGRESSIVE 0 #define VIDEO_TOP_FIELD_FIRST 1 #define VIDEO_BOTTOM_FIELD_FIRST 2 @@ -23,7 +17,7 @@ struct Clip; class PreviewGenerator; class MediaThrobber; -struct MediaStream { +struct FootageStream { int file_index; int video_width; int video_height; @@ -41,15 +35,15 @@ struct MediaStream { QVector audio_preview; }; -struct Media { - Media(); - ~Media(); +struct Footage { + Footage(); + ~Footage(); QString url; QString name; int64_t length; - QVector video_tracks; - QVector audio_tracks; + QVector video_tracks; + QVector audio_tracks; int save_id; bool ready; bool invalid; @@ -62,8 +56,8 @@ struct Media { long out; long get_length_in_frames(double frame_rate); - MediaStream* get_stream_from_file_index(bool video, int index); + FootageStream* get_stream_from_file_index(bool video, int index); void reset(); }; -#endif // MEDIA_H +#endif // FOOTAGE_H diff --git a/project/media.cpp b/project/media.cpp new file mode 100644 index 000000000..d3906f9ff --- /dev/null +++ b/project/media.cpp @@ -0,0 +1,312 @@ +#include "media.h" + +#include "footage.h" +#include "sequence.h" +#include "undo.h" +#include "io/config.h" +#include "panels/viewer.h" + +#include "debug.h" + +extern "C" { + #include + #include +} + +QString get_interlacing_name(int interlacing) { + switch (interlacing) { + case VIDEO_PROGRESSIVE: return "None (Progressive)"; + case VIDEO_TOP_FIELD_FIRST: return "Top Field First"; + case VIDEO_BOTTOM_FIELD_FIRST: return "Bottom Field First"; + default: return "Invalid"; + } +} + +QString get_channel_layout_name(int channels, uint64_t layout) { + switch (channels) { + case 0: return "Invalid"; break; + case 1: return "Mono"; break; + case 2: return "Stereo"; break; + default: { + char buf[50]; + av_get_channel_layout_string(buf, sizeof(buf), channels, layout); + return QString(buf); + } + } +} + +Media::Media(Media* iparent) : + parent(iparent), + throbber(NULL), + root(false), + type(-1) +{} + +Media::~Media() { + switch (get_type()) { + case MEDIA_TYPE_FOOTAGE: delete to_footage(); break; + case MEDIA_TYPE_SEQUENCE: if (object != NULL) delete to_sequence(); break; + } + if (throbber != NULL) delete throbber; + qDeleteAll(children); +} + +Footage *Media::to_footage() { + return static_cast(object); +} + +Sequence *Media::to_sequence() { + return static_cast(object); +} + +void Media::set_footage(Footage *f) { + type = MEDIA_TYPE_FOOTAGE; + object = f; +} + +void Media::set_sequence(Sequence *s) { + set_icon(QIcon(":/icons/sequence.png")); + type = MEDIA_TYPE_SEQUENCE; + object = s; + if (s != NULL) update_tooltip(); +} + +void Media::set_folder() { + if (folder_name.isEmpty()) folder_name = "New Folder"; + set_icon(QIcon(":/icons/folder.png")); + type = MEDIA_TYPE_FOLDER; + object = NULL; +} + +void Media::set_icon(const QIcon &ico) { + icon = ico; +} + +void Media::set_parent(Media *p) { + parent = p; +} + +void Media::update_tooltip(const QString& error) { + switch (type) { + case MEDIA_TYPE_FOOTAGE: + { + Footage* f = to_footage(); + tooltip = "Name: " + f->name + "\nFilename: " + f->url + "\n"; + + if (error.isEmpty()) { + if (f->video_tracks.size() > 0) { + tooltip += "Video Dimensions: "; + for (int i=0;ivideo_tracks.size();i++) { + if (i > 0) { + tooltip += ", "; + } + tooltip += QString::number(f->video_tracks.at(i)->video_width) + "x" + QString::number(f->video_tracks.at(i)->video_height); + } + tooltip += "\n"; + + if (!f->video_tracks.at(0)->infinite_length) { + tooltip += "Frame Rate: "; + for (int i=0;ivideo_tracks.size();i++) { + if (i > 0) { + tooltip += ", "; + } + if (f->video_tracks.at(i)->video_interlacing == VIDEO_PROGRESSIVE) { + tooltip += QString::number(f->video_tracks.at(i)->video_frame_rate); + } else { + tooltip += QString::number(f->video_tracks.at(i)->video_frame_rate * 2); + tooltip += " fields (" + QString::number(f->video_tracks.at(i)->video_frame_rate) + " frames)"; + } + } + tooltip += "\n"; + } + + tooltip += "Interlacing: "; + for (int i=0;ivideo_tracks.size();i++) { + if (i > 0) { + tooltip += ", "; + } + tooltip += get_interlacing_name(f->video_tracks.at(i)->video_interlacing); + } + } + + if (f->audio_tracks.size() > 0) { + tooltip += "\n"; + + tooltip += "Audio Frequency: "; + for (int i=0;iaudio_tracks.size();i++) { + if (i > 0) { + tooltip += ", "; + } + tooltip += QString::number(f->audio_tracks.at(i)->audio_frequency); + } + tooltip += "\n"; + + tooltip += "Audio Channels: "; + for (int i=0;iaudio_tracks.size();i++) { + if (i > 0) { + tooltip += ", "; + } + tooltip += get_channel_layout_name(f->audio_tracks.at(i)->audio_channels, f->audio_tracks.at(i)->audio_layout); + } + // tooltip += "\n"; + } + } else { + tooltip += error; + } + } + break; + case MEDIA_TYPE_SEQUENCE: + { + Sequence* s = to_sequence(); + tooltip = "Name: " + s->name + + "\nVideo Dimensions: " + QString::number(s->width) + "x" + QString::number(s->height) + + "\nFrame Rate: " + QString::number(s->frame_rate) + + "\nAudio Frequency: " + QString::number(s->audio_frequency) + + "\nAudio Layout: " + get_channel_layout_name(av_get_channel_layout_nb_channels(s->audio_layout), s->audio_layout); + } + break; + } + +} + +void *Media::to_object() { + return object; +} + +int Media::get_type() { + return type; +} + +const QString &Media::get_name() { + switch (type) { + case MEDIA_TYPE_FOOTAGE: return to_footage()->name; + case MEDIA_TYPE_SEQUENCE: return to_sequence()->name; + case MEDIA_TYPE_FOLDER: return folder_name; + } +} + +void Media::set_name(const QString &n) { + switch (type) { + case MEDIA_TYPE_FOOTAGE: to_footage()->name = n; break; + case MEDIA_TYPE_SEQUENCE: to_sequence()->name = n; break; + case MEDIA_TYPE_FOLDER: folder_name = n; break; + } +} + +double Media::get_frame_rate(int stream) { + switch (get_type()) { + case MEDIA_TYPE_FOOTAGE: return to_footage()->get_stream_from_file_index(true, stream)->video_frame_rate; + case MEDIA_TYPE_SEQUENCE: return to_sequence()->frame_rate; + } + return qSNaN(); +} + +int Media::get_sampling_rate(int stream) { + switch (get_type()) { + case MEDIA_TYPE_FOOTAGE: return to_footage()->get_stream_from_file_index(false, stream)->audio_frequency; + case MEDIA_TYPE_SEQUENCE: return to_sequence()->audio_frequency; + } + return 0; +} + +void Media::appendChild(Media *child) { + child->set_parent(this); + children.append(child); +} + +bool Media::setData(int col, const QVariant &value) { + if (col == 0) { + QString n = value.toString(); + if (!n.isEmpty() && get_name() != n) { + undo_stack.push(new MediaRename(this, value.toString())); + return true; + } + } + return false; +} + +Media *Media::child(int row) { + return children.value(row); +} + +int Media::childCount() const { + return children.count(); +} + +int Media::columnCount() const { + return 3; +} + +QVariant Media::data(int column, int role) { + switch (role) { + case Qt::DecorationRole: + if (column == 0) { + return icon; + } + break; + case Qt::DisplayRole: + switch (column) { + case 0: return (root) ? "Name" : get_name(); + case 1: + if (root) return "Duration"; + if (get_type() == MEDIA_TYPE_SEQUENCE) { + Sequence* s = to_sequence(); + return frame_to_timecode(s->getEndFrame(), config.timecode_view, s->frame_rate); + } + if (get_type() == MEDIA_TYPE_FOOTAGE) { + Footage* f = to_footage(); + double r = 30; + + if (f->video_tracks.size() > 0) r = f->video_tracks.at(0)->video_frame_rate; + if (!qIsNull(r)) { + long len = f->get_length_in_frames(r); + if (len > 0) return frame_to_timecode(len, config.timecode_view, r); + } + } + break; + case 2: + if (root) return "Rate"; + if (get_type() == MEDIA_TYPE_SEQUENCE) return QString::number(get_frame_rate()) + " FPS"; + if (get_type() == MEDIA_TYPE_FOOTAGE) { + Footage* f = to_footage(); + if (f->video_tracks.size() > 0) { + double r = get_frame_rate(); + if (!qIsNull(r)) return QString::number(get_frame_rate()) + " FPS"; + } else if (f->audio_tracks.size() > 0) { + return QString::number(get_sampling_rate()) + " Hz"; + } + } + break; + } + break; + case Qt::ToolTipRole: + return tooltip; + } + return QVariant(); +} + +int Media::row() const { + if (parent) { + return parent->children.indexOf(const_cast(this)); + } + return 0; +} + +Media *Media::parentItem() { + return parent; +} + +Media *Media::takeChild(int i) { + Media* ref = children.at(i); + children.removeAt(i); + return ref; +} + +void Media::removeChild(Media* m) { + for (int i=0;i +#include + +#define MEDIA_TYPE_FOOTAGE 0 +#define MEDIA_TYPE_SEQUENCE 1 +#define MEDIA_TYPE_FOLDER 2 +#define MEDIA_TYPE_SOLID 3 +#define MEDIA_TYPE_TONE 4 + +class Footage; +class MediaThrobber; +struct Sequence; +#include + +class Media +{ +public: + Media(Media* iparent); + ~Media(); + Footage *to_footage(); + Sequence* to_sequence(); + void set_footage(Footage* f); + void set_sequence(Sequence* s); + void set_folder(); + void set_icon(const QIcon &ico); + void set_parent(Media* p); + void update_tooltip(const QString& error = 0); + void *to_object(); + int get_type(); + const QString& get_name(); + void set_name(const QString& n); + MediaThrobber* throbber; + + double get_frame_rate(int stream = 0); + int get_sampling_rate(int stream = 0); + + // item functions + void appendChild(Media *child); + bool setData(int col, const QVariant &value); + Media *child(int row); + int childCount() const; + int columnCount() const; + QVariant data(int column, int role); + int row() const; + Media *parentItem(); + Media* takeChild(int i); + void removeChild(Media* m); + + bool root; + int temp_id; + int temp_id2; +private: + int type; + void* object; + + // item functions + QList children; + Media* parent; + QString folder_name; + QString tooltip; + QIcon icon; +}; + +#endif // MEDIA_H diff --git a/project/projectmodel.cpp b/project/projectmodel.cpp new file mode 100644 index 000000000..34e0c5ee7 --- /dev/null +++ b/project/projectmodel.cpp @@ -0,0 +1,146 @@ +#include "projectmodel.h" + +#include "panels/panels.h" +#include "panels/viewer.h" +#include "ui/viewerwidget.h" +#include "project/media.h" +#include "debug.h" + +ProjectModel::ProjectModel(QObject *parent) : QAbstractItemModel(parent), root_item(NULL) { + clear(); +} + +ProjectModel::~ProjectModel() { + destroy_root(); +} + +void ProjectModel::destroy_root() { + if (panel_sequence_viewer != NULL) panel_sequence_viewer->viewer_widget->delete_function(); + if (panel_footage_viewer != NULL) panel_footage_viewer->viewer_widget->delete_function(); + + if (root_item != NULL) delete root_item; +} + +void ProjectModel::clear() { + destroy_root(); + root_item = new Media(0); + root_item->root = true; + update_data(); +} + +Media *ProjectModel::get_root() { + return root_item; +} + +QVariant ProjectModel::data(const QModelIndex &index, int role) const { + if (!index.isValid()) + return QVariant(); + + return static_cast(index.internalPointer())->data(index.column(), role); +} + +Qt::ItemFlags ProjectModel::flags(const QModelIndex &index) const { + if (!index.isValid()) + return Qt::ItemIsDropEnabled; + + return QAbstractItemModel::flags(index) | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable; +} + +QVariant ProjectModel::headerData(int section, Qt::Orientation orientation, int role) const { + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) + return root_item->data(section, role); + + return QVariant(); +} + +QModelIndex ProjectModel::index(int row, int column, const QModelIndex &parent) const { + if (!hasIndex(row, column, parent)) + return QModelIndex(); + + Media *parentItem; + + if (!parent.isValid()) + parentItem = root_item; + else + parentItem = static_cast(parent.internalPointer()); + + Media *childItem = parentItem->child(row); + if (childItem) + return createIndex(row, column, childItem); + else + return QModelIndex(); +} + +QModelIndex ProjectModel::parent(const QModelIndex &index) const { + if (!index.isValid()) + return QModelIndex(); + + Media *childItem = static_cast(index.internalPointer()); + Media *parentItem = childItem->parentItem(); + + if (parentItem == root_item) + return QModelIndex(); + + return createIndex(parentItem->row(), 0, parentItem); +} + +bool ProjectModel::setData(const QModelIndex &index, const QVariant &value, int role) { + if (role != Qt::EditRole) + return false; + + Media *item = static_cast(index.internalPointer()); + bool result = item->setData(index.column(), value); + + if (result) + emit dataChanged(index, index); + + return result; +} + +int ProjectModel::rowCount(const QModelIndex &parent) const { + Media *parentItem; + if (parent.column() > 0) + return 0; + + if (!parent.isValid()) { + parentItem = root_item; + } else { + parentItem = static_cast(parent.internalPointer()); + } + + return parentItem->childCount(); +} + +int ProjectModel::columnCount(const QModelIndex &parent) const { + if (parent.isValid()) + return static_cast(parent.internalPointer())->columnCount(); + else + return root_item->columnCount(); +} + +int ProjectModel::topLevelItemCount() { + return root_item->childCount(); +} + +Media *ProjectModel::topLevelItem(int i) { + return root_item->child(i); +} + +void ProjectModel::addTopLevelItem(Media *m) { + root_item->appendChild(m); +} + +Media *ProjectModel::takeTopLevelItem(int i) { + Media* ref = root_item->child(i); + root_item->takeChild(i); + return ref; +} + +void ProjectModel::removeTopLevelItem(Media *m) { + root_item->removeChild(m); +} + +void ProjectModel::update_data() { +// emit dataChanged(); + emit layoutChanged(); +} diff --git a/project/projectmodel.h b/project/projectmodel.h new file mode 100644 index 000000000..16c40c13c --- /dev/null +++ b/project/projectmodel.h @@ -0,0 +1,39 @@ +#ifndef PROJECTMODEL_H +#define PROJECTMODEL_H + +#include + +class Media; + +class ProjectModel : public QAbstractItemModel +{ + Q_OBJECT +public: + ProjectModel(QObject* parent = 0); + ~ProjectModel() override; + + void destroy_root(); + void clear(); + Media* get_root(); + QVariant data(const QModelIndex &index, int role) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const override; + QModelIndex index(int row, int column, + const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &index) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + int topLevelItemCount(); + Media* topLevelItem(int i); + void addTopLevelItem(Media*); + Media* takeTopLevelItem(int i); + void removeTopLevelItem(Media* m); + void update_data(); + +private: + Media* root_item; +}; + +#endif // PROJECTMODEL_H diff --git a/project/undo.cpp b/project/undo.cpp index 9010404fd..ace334589 100644 --- a/project/undo.cpp +++ b/project/undo.cpp @@ -15,13 +15,14 @@ #include "ui/sourcetable.h" #include "project/effect.h" #include "project/transition.h" -#include "io/media.h" +#include "project/footage.h" #include "playback/cacher.h" #include "ui/labelslider.h" #include "ui/viewerwidget.h" #include "project/marker.h" #include "mainwindow.h" #include "io/clipboard.h" +#include "project/media.h" #include "debug.h" QUndoStack undo_stack; @@ -169,7 +170,7 @@ void SetTimelineInOutCommand::undo() { // footage viewer functions if (seq->wrapper_sequence) { - Media* m = static_cast(seq->clips.at(0)->media); + Footage* m = seq->clips.at(0)->media->to_footage(); m->using_inout = old_enabled; m->in = old_in; m->out = old_out; @@ -189,7 +190,7 @@ void SetTimelineInOutCommand::redo() { // footage viewer functions if (seq->wrapper_sequence) { - Media* m = static_cast(seq->clips.at(0)->media); + Footage* m = seq->clips.at(0)->media->to_footage(); m->using_inout = new_enabled; m->in = new_in; m->out = new_out; @@ -340,7 +341,7 @@ void DeleteTransitionCommand::redo() { mainWindow->setWindowModified(true); } -NewSequenceCommand::NewSequenceCommand(QTreeWidgetItem *s, QTreeWidgetItem* iparent) : +NewSequenceCommand::NewSequenceCommand(Media *s, Media* iparent) : seq(s), parent(iparent), done(false), @@ -353,7 +354,7 @@ NewSequenceCommand::~NewSequenceCommand() { void NewSequenceCommand::undo() { if (parent == NULL) { - panel_project->source_table->takeTopLevelItem(panel_project->source_table->indexOfTopLevelItem(seq)); + project_model.removeTopLevelItem(seq); } else { parent->removeChild(seq); } @@ -363,45 +364,38 @@ void NewSequenceCommand::undo() { void NewSequenceCommand::redo() { if (parent == NULL) { - panel_project->source_table->addTopLevelItem(seq); + project_model.addTopLevelItem(seq); } else { - parent->addChild(seq); + parent->appendChild(seq); } done = true; mainWindow->setWindowModified(true); } -AddMediaCommand::AddMediaCommand(QTreeWidgetItem* iitem, QTreeWidgetItem* iparent) : +AddMediaCommand::AddMediaCommand(Media* iitem, Media* iparent) : item(iitem), parent(iparent), done(false), old_project_changed(mainWindow->isWindowModified()) -{} +{ + if (parent == NULL) parent = project_model.get_root(); +} AddMediaCommand::~AddMediaCommand() { if (!done) { - panel_project->delete_media(item); - if (item->data(0, Qt::UserRole + 5) != 0) delete reinterpret_cast(item->data(0, Qt::UserRole + 5).value()); delete item; } } void AddMediaCommand::undo() { - if (parent == NULL) { - panel_project->source_table->takeTopLevelItem(panel_project->source_table->indexOfTopLevelItem(item)); - } else { - parent->removeChild(item); - } + parent->removeChild(item); done = false; mainWindow->setWindowModified(old_project_changed); } void AddMediaCommand::redo() { - if (parent == NULL) { - panel_project->source_table->addTopLevelItem(item); - } else { - parent->addChild(item); - } + parent->appendChild(item); + project_model.update_data(); /* Here we force the source_table to sort itself. * @@ -419,32 +413,33 @@ void AddMediaCommand::redo() { * I guess I'm "supposed" to use a Model–view–viewmodel instead, * which I'll probably have to switch to soon anyway. So perhaps * this will be a non-issue soon. - */ + panel_project->source_table->setSortingEnabled(false); - panel_project->source_table->setSortingEnabled(true); + panel_project->source_table->setSortingEnabled(true);*/ done = true; mainWindow->setWindowModified(true); } -DeleteMediaCommand::DeleteMediaCommand(QTreeWidgetItem* i) : +DeleteMediaCommand::DeleteMediaCommand(const QModelIndex& i) : item(i), + parent(i.parent()), old_project_changed(mainWindow->isWindowModified()) {} DeleteMediaCommand::~DeleteMediaCommand() { if (done) { - panel_project->delete_media(item); - if (item->data(0, Qt::UserRole + 5) != 0) delete reinterpret_cast(item->data(0, Qt::UserRole + 5).value()); - delete item; + Media* m = static_cast(item.internalPointer()); + delete m; } } void DeleteMediaCommand::undo() { - if (parent == NULL) { - panel_project->source_table->addTopLevelItem(item); + Media* m = static_cast(item.internalPointer()); + if (!parent.isValid()) { + project_model.addTopLevelItem(m); } else { - parent->addChild(item); + static_cast(parent.internalPointer())->appendChild(m); } mainWindow->setWindowModified(old_project_changed); @@ -452,14 +447,14 @@ void DeleteMediaCommand::undo() { } void DeleteMediaCommand::redo() { - parent = item->parent(); - - if (parent == NULL) { - panel_project->source_table->takeTopLevelItem(panel_project->source_table->indexOfTopLevelItem(item)); + Media* m = static_cast(item.internalPointer()); + if (!parent.isValid()) { + project_model.removeTopLevelItem(m); } else { - parent->removeChild(item); + static_cast(parent.internalPointer())->removeChild(m); } + project_model.update_data(); mainWindow->setWindowModified(true); done = true; } @@ -601,25 +596,24 @@ void CheckboxCommand::redo() { mainWindow->setWindowModified(true); } -ReplaceMediaCommand::ReplaceMediaCommand(QTreeWidgetItem* i, QString s) : +ReplaceMediaCommand::ReplaceMediaCommand(Media* i, QString s) : item(i), new_filename(s), old_project_changed(mainWindow->isWindowModified()) { - media = get_footage_from_tree(item); - old_filename = media->url; + old_filename = item->to_footage()->url; } void ReplaceMediaCommand::replace(QString& filename) { // close any clips currently using this media - QVector all_sequences = panel_project->list_all_project_sequences(); + QVector all_sequences = panel_project->list_all_project_sequences(); for (int i=0;ito_sequence(); for (int j=0;jclips.size();j++) { Clip* c = s->clips.at(j); - if (c != NULL && c->media == media && c->open) { + if (c != NULL && c->media == item && c->open) { close_clip(c); - if (c->media_type == MEDIA_TYPE_FOOTAGE) c->cacher->wait(); + if (c->media != NULL && c->media->get_type() == MEDIA_TYPE_FOOTAGE) c->cacher->wait(); c->replaced = true; } } @@ -643,11 +637,9 @@ void ReplaceMediaCommand::redo() { mainWindow->setWindowModified(true); } -ReplaceClipMediaCommand::ReplaceClipMediaCommand(void *a, void *b, int c, int d, bool e) : +ReplaceClipMediaCommand::ReplaceClipMediaCommand(Media *a, Media *b, bool e) : old_media(a), - new_media(b), - old_type(c), - new_type(d), + new_media(b), preserve_clip_ins(e), old_project_changed(mainWindow->isWindowModified()) {} @@ -661,7 +653,7 @@ void ReplaceClipMediaCommand::replace(bool undo) { Clip* c = clips.at(i); if (c->open) { close_clip(c); - if (c->media_type == MEDIA_TYPE_FOOTAGE) c->cacher->wait(); + if (c->media != NULL && c->media->get_type() == MEDIA_TYPE_FOOTAGE) c->cacher->wait(); } if (undo) { @@ -669,16 +661,14 @@ void ReplaceClipMediaCommand::replace(bool undo) { c->clip_in = old_clip_ins.at(i); } - c->media = old_media; - c->media_type = old_type; + c->media = old_media; } else { if (!preserve_clip_ins) { old_clip_ins.append(c->clip_in); c->clip_in = 0; } - c->media = new_media; - c->media_type = new_type; + c->media = new_media; } c->replaced = true; @@ -695,6 +685,7 @@ void ReplaceClipMediaCommand::undo() { void ReplaceClipMediaCommand::redo() { replace(false); + update_ui(true); mainWindow->setWindowModified(true); } @@ -737,61 +728,40 @@ MediaMove::MediaMove(SourceTable *s) : table(s), old_project_changed(mainWindow- void MediaMove::undo() { for (int i=0;itakeTopLevelItem(table->indexOfTopLevelItem(items.at(i))); - } else { - to->removeChild(items.at(i)); - } - } - for (int i=0;iaddTopLevelItem(items.at(i)); - } else { - froms.at(i)->addChild(items.at(i)); - } - } + to->removeChild(items.at(i)); + froms.at(i)->appendChild(items.at(i)); + } mainWindow->setWindowModified(old_project_changed); } void MediaMove::redo() { + if (to == NULL) to = project_model.get_root(); froms.resize(items.size()); for (int i=0;iparent(); + Media* parent = items.at(i)->parentItem(); froms[i] = parent; - if (parent == NULL) { - table->takeTopLevelItem(table->indexOfTopLevelItem(items.at(i))); - } else { - parent->removeChild(items.at(i)); - } - if (to == NULL) { - table->addTopLevelItem(items.at(i)); - } else { - to->addChild(items.at(i)); - } - } - for (int i=0;iaddTopLevelItem(items.at(i)); - } else { - to->addChild(items.at(i)); - } - } + parent->removeChild(items.at(i)); + to->appendChild(items.at(i)); + } + project_model.update_data(); mainWindow->setWindowModified(true); } -MediaRename::MediaRename() : done(true), old_project_changed(mainWindow->isWindowModified()) {} +MediaRename::MediaRename(Media* iitem, QString ito) : + item(iitem), + from(iitem->get_name()), + to(ito), + old_project_changed(mainWindow->isWindowModified()) +{} void MediaRename::undo() { - item->setText(0, from); - done = false; - mainWindow->setWindowModified(old_project_changed); + item->set_name(from); + mainWindow->setWindowModified(old_project_changed); } void MediaRename::redo() { - if (!done) { - item->setText(0, to); - } - mainWindow->setWindowModified(true); + item->set_name(to); + mainWindow->setWindowModified(true); } KeyframeMove::KeyframeMove() : old_project_changed(mainWindow->isWindowModified()) {} @@ -1138,7 +1108,7 @@ void SetEnableCommand::redo() { mainWindow->setWindowModified(true); } -EditSequenceCommand::EditSequenceCommand(QTreeWidgetItem* i, Sequence *s) : +EditSequenceCommand::EditSequenceCommand(Media* i, Sequence *s) : item(i), seq(s), old_project_changed(mainWindow->isWindowModified()), @@ -1175,11 +1145,8 @@ void EditSequenceCommand::redo() { } void EditSequenceCommand::update() { - // update name - item->setText(0, seq->name); - // update tooltip - set_sequence_of_tree(item, seq); + item->set_sequence(seq); for (int i=0;iclips.size();i++) { // TODO shift in/out/clipin points to match new frame rate @@ -1237,9 +1204,8 @@ void CloseAllClipsCommand::redo() { closeActiveClips(sequence, true); } -UpdateFootageTooltip::UpdateFootageTooltip(QTreeWidgetItem *i, Media *m) : - item(i), - media(m) +UpdateFootageTooltip::UpdateFootageTooltip(Media *i) : + item(i) {} void UpdateFootageTooltip::undo() { @@ -1247,7 +1213,7 @@ void UpdateFootageTooltip::undo() { } void UpdateFootageTooltip::redo() { - update_footage_tooltip(item, media); + item->update_tooltip(); } MoveEffectCommand::MoveEffectCommand() : diff --git a/project/undo.h b/project/undo.h index 57084a4c7..46ba95af3 100644 --- a/project/undo.h +++ b/project/undo.h @@ -1,7 +1,7 @@ #ifndef UNDO_H #define UNDO_H -class QTreeWidgetItem; +class Media; class QCheckBox; class LabelSlider; class Effect; @@ -12,7 +12,7 @@ class Transition; class EffectGizmo; struct Clip; struct Sequence; -struct Media; +struct Footage; struct EffectMeta; #include "project/marker.h" @@ -166,39 +166,39 @@ private: class NewSequenceCommand : public QUndoCommand { public: - NewSequenceCommand(QTreeWidgetItem *s, QTreeWidgetItem* iparent); + NewSequenceCommand(Media *s, Media* iparent); ~NewSequenceCommand(); void undo(); void redo(); private: - QTreeWidgetItem* seq; - QTreeWidgetItem* parent; + Media* seq; + Media* parent; bool done; bool old_project_changed; }; class AddMediaCommand : public QUndoCommand { public: - AddMediaCommand(QTreeWidgetItem* iitem, QTreeWidgetItem* iparent); + AddMediaCommand(Media* iitem, Media* iparent); ~AddMediaCommand(); void undo(); void redo(); private: - QTreeWidgetItem* item; - QTreeWidgetItem* parent; + Media* item; + Media* parent; bool done; bool old_project_changed; }; class DeleteMediaCommand : public QUndoCommand { public: - DeleteMediaCommand(QTreeWidgetItem* i); + DeleteMediaCommand(const QModelIndex& i); ~DeleteMediaCommand(); void undo(); void redo(); private: - QTreeWidgetItem* item; - QTreeWidgetItem* parent; + const QModelIndex& item; + const QModelIndex& parent; bool old_project_changed; bool done; }; @@ -258,29 +258,26 @@ private: class ReplaceMediaCommand : public QUndoCommand { public: - ReplaceMediaCommand(QTreeWidgetItem*, QString); + ReplaceMediaCommand(Media*, QString); void undo(); void redo(); private: - QTreeWidgetItem *item; + Media *item; QString old_filename; QString new_filename; - bool old_project_changed; - Media* media; + bool old_project_changed; void replace(QString& filename); }; class ReplaceClipMediaCommand : public QUndoCommand { public: - ReplaceClipMediaCommand(void*, void*, int, int, bool); + ReplaceClipMediaCommand(Media *, Media *, bool); void undo(); void redo(); QVector clips; private: - void* old_media; - void* new_media; - int old_type; - int new_type; + Media* old_media; + Media* new_media; bool preserve_clip_ins; bool old_project_changed; QVector old_clip_ins; @@ -304,27 +301,26 @@ private: class MediaMove : public QUndoCommand { public: MediaMove(SourceTable* s); - QVector items; - QTreeWidgetItem* to; + QVector items; + Media* to; void undo(); void redo(); private: - QVector froms; + QVector froms; SourceTable* table; bool old_project_changed; }; class MediaRename : public QUndoCommand { public: - MediaRename(); - QTreeWidgetItem* item; - QString from; - QString to; - void undo(); - void redo(); + MediaRename(Media* iitem, QString to); + void undo(); + void redo(); private: - bool done; - bool old_project_changed; + bool old_project_changed; + Media* item; + QString from; + QString to; }; class KeyframeMove : public QUndoCommand { @@ -486,7 +482,7 @@ private: class EditSequenceCommand : public QUndoCommand { public: - EditSequenceCommand(QTreeWidgetItem *i, Sequence* s); + EditSequenceCommand(Media *i, Sequence* s); void undo(); void redo(); void update(); @@ -498,7 +494,7 @@ public: int audio_frequency; int audio_layout; private: - QTreeWidgetItem* item; + Media* item; Sequence* seq; bool old_project_changed; @@ -542,12 +538,11 @@ public: class UpdateFootageTooltip : public QUndoCommand { public: - UpdateFootageTooltip(QTreeWidgetItem* i, Media* m); + UpdateFootageTooltip(Media* i); void undo(); void redo(); private: - QTreeWidgetItem* item; - Media* media; + Media* item; }; class MoveEffectCommand : public QUndoCommand { diff --git a/ui/sourcetable.cpp b/ui/sourcetable.cpp index 8e11cfe3f..a3cb85627 100644 --- a/ui/sourcetable.cpp +++ b/ui/sourcetable.cpp @@ -1,7 +1,7 @@ #include "sourcetable.h" #include "panels/project.h" -#include "io/media.h" +#include "project/footage.h" #include "panels/timeline.h" #include "panels/viewer.h" #include "panels/panels.h" @@ -10,6 +10,8 @@ #include "project/sequence.h" #include "mainwindow.h" #include "io/config.h" +#include "project/media.h" +#include "debug.h" #include #include @@ -22,16 +24,16 @@ #include #include -SourceTable::SourceTable(QWidget* parent) : QTreeWidget(parent) { +SourceTable::SourceTable(QWidget* parent) : QTreeView(parent) { editing_item = NULL; setSortingEnabled(true); sortByColumn(0, Qt::AscendingOrder); rename_timer.setInterval(1000); setContextMenuPolicy(Qt::CustomContextMenu); connect(&rename_timer, SIGNAL(timeout()), this, SLOT(rename_interval())); - connect(this, SIGNAL(itemClicked(QTreeWidgetItem*,int)), this, SLOT(item_click(QTreeWidgetItem*,int))); - connect(this, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(item_renamed(QTreeWidgetItem*))); - connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu())); + connect(this, SIGNAL(clicked(const QModelIndex&)), this, SLOT(item_click(const QModelIndex&))); + //connect(this, SIGNAL(itemChanged(Media*,int)), this, SLOT(item_renamed(Media*))); + connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu())); } void SourceTable::show_context_menu() { @@ -43,10 +45,14 @@ void SourceTable::show_context_menu() { QAction* new_folder_action = menu.addAction("New Folder..."); connect(new_folder_action, SIGNAL(triggered(bool)), mainWindow, SLOT(on_actionFolder_triggered())); - if (selectedItems().size() > 0) { - if (selectedItems().size() == 1) { + QModelIndexList selected_items = selectionModel()->selectedRows(); + + if (selected_items.size() > 0) { + Media* m = static_cast(selected_items.at(0).internalPointer()); + + if (selected_items.size() == 1) { // replace footage - int type = get_type_from_tree(selectedItems().at(0)); + int type = m->get_type(); if (type == MEDIA_TYPE_FOOTAGE) { QAction* replace_action = menu.addAction("Replace/Relink Media"); connect(replace_action, SIGNAL(triggered(bool)), panel_project, SLOT(replace_selected_file())); @@ -69,11 +75,11 @@ void SourceTable::show_context_menu() { // duplicate item bool all_sequences = true; bool all_footage = true; - for (int i=0;iget_type() != MEDIA_TYPE_SEQUENCE) { all_sequences = false; } - if (get_type_from_tree(selectedItems().at(i)) != MEDIA_TYPE_FOOTAGE) { + if (m->get_type() != MEDIA_TYPE_FOOTAGE) { all_footage = false; } } @@ -99,7 +105,7 @@ void SourceTable::show_context_menu() { QAction* delete_action = menu.addAction("Delete"); connect(delete_action, SIGNAL(triggered(bool)), panel_project, SLOT(delete_selected_media())); - if (selectedItems().size() == 1) { + if (selected_items.size() == 1) { QAction* properties_action = menu.addAction("Properties..."); connect(properties_action, SIGNAL(triggered(bool)), panel_project, SLOT(open_properties())); } @@ -109,20 +115,19 @@ void SourceTable::show_context_menu() { } void SourceTable::create_seq_from_selected() { - if (!selectedItems().isEmpty()) { - QVector media_list; - QVector type_list; - for (int i=0;iselectedRows(); + + if (!selected_items.isEmpty()) { + QVector media_list; + for (int i=0;i(selected_items.at(i).internalPointer())); } ComboAction* ca = new ComboAction(); - Sequence* s = create_sequence_from_media(media_list, type_list); + Sequence* s = create_sequence_from_media(media_list); // add clips to it - panel_timeline->create_ghosts_from_media(s, 0, media_list, type_list); + panel_timeline->create_ghosts_from_media(s, 0, media_list); panel_timeline->add_clips_from_ghosts(ca, s); panel_project->new_sequence(ca, s, true, NULL); @@ -131,7 +136,8 @@ void SourceTable::create_seq_from_selected() { } void SourceTable::reveal_in_browser() { - Media* m = get_footage_from_tree(selectedItems().at(0)); + QModelIndexList selected_items = selectionModel()->selectedRows(); + Footage* m = static_cast(selected_items.at(0).internalPointer()); #if defined(Q_OS_WIN) QStringList args; @@ -153,12 +159,9 @@ void SourceTable::reveal_in_browser() { #endif } -void SourceTable::item_renamed(QTreeWidgetItem* item) { +void SourceTable::item_renamed(Media* item) { if (editing_item == item) { - MediaRename* mr = new MediaRename(); - mr->from = editing_item_name; - mr->item = editing_item; - mr->to = editing_item->text(0); + MediaRename* mr = new MediaRename(item, "idk"); undo_stack.push(mr); editing_item = NULL; } @@ -171,37 +174,41 @@ void SourceTable::stop_rename_timer() { void SourceTable::rename_interval() { stop_rename_timer(); if (hasFocus() && editing_item != NULL) { - editing_item_name = editing_item->text(0); - editItem(editing_item, 0); + edit(editing_index); + //editItem(editing_item, 0); } } -void SourceTable::item_click(QTreeWidgetItem* item, int column) { - if (column == 0 && selectedItems().size() == 1) { - if (editing_item == item) { +void SourceTable::item_click(const QModelIndex& index) { + if (selectionModel()->selectedRows().size() == 1 && index.column() == 0) { + Media* m = static_cast(index.internalPointer()); + if (editing_item == m) { rename_timer.start(); + } else { + editing_item = m; + editing_index = index; } - editing_item = item; } } void SourceTable::mousePressEvent(QMouseEvent* event) { stop_rename_timer(); - QTreeWidget::mousePressEvent(event); + QTreeView::mousePressEvent(event); } -void SourceTable::mouseDoubleClickEvent(QMouseEvent* ) { +void SourceTable::mouseDoubleClickEvent(QMouseEvent* e) { stop_rename_timer(); - if (selectedItems().count() == 0) { + QModelIndexList selected_items = selectionModel()->selectedRows(); + if (selected_items.size() == 0) { panel_project->import_dialog(); - } else if (selectedItems().count() == 1) { - QTreeWidgetItem* item = selectedItems().at(0); - switch (get_type_from_tree(item)) { + } else if (selected_items.size() == 1) { + Media* item = static_cast(selected_items.at(0).internalPointer()); + switch (item->get_type()) { case MEDIA_TYPE_FOOTAGE: - panel_footage_viewer->set_media(get_type_from_tree(item), get_media_from_tree(item)); + panel_footage_viewer->set_media(item); panel_footage_viewer->setFocus(); break; case MEDIA_TYPE_SEQUENCE: - undo_stack.push(new ChangeSequenceAction(get_sequence_from_tree(item))); + undo_stack.push(new ChangeSequenceAction(item->to_sequence())); break; } } @@ -211,7 +218,7 @@ void SourceTable::dragEnterEvent(QDragEnterEvent *event) { if (event->mimeData()->hasUrls()) { event->acceptProposedAction(); } else { - QTreeWidget::dragEnterEvent(event); + QTreeView::dragEnterEvent(event); } } @@ -219,13 +226,14 @@ void SourceTable::dragMoveEvent(QDragMoveEvent *event) { if (event->mimeData()->hasUrls()) { event->acceptProposedAction(); } else { - QTreeWidget::dragMoveEvent(event); + QTreeView::dragMoveEvent(event); } } void SourceTable::dropEvent(QDropEvent* event) { const QMimeData* mimeData = event->mimeData(); - QTreeWidgetItem* drop_item = itemAt(event->pos()); + const QModelIndex& drop_item = indexAt(event->pos()); + Media* m = static_cast(drop_item.internalPointer()); if (mimeData->hasUrls()) { // drag files in from outside QList urls = mimeData->urls(); @@ -236,25 +244,25 @@ void SourceTable::dropEvent(QDropEvent* event) { } bool replace = false; if (urls.size() == 1 - && drop_item != NULL - && get_type_from_tree(drop_item) == MEDIA_TYPE_FOOTAGE + && drop_item.isValid() + && m->get_type() == MEDIA_TYPE_FOOTAGE && !QFileInfo(paths.at(0)).isDir() && config.drop_on_media_to_replace - && QMessageBox::question(this, "Replace Media", "You dropped a file onto '" + drop_item->text(0) + "'. Would you like to replace it with the dropped file?", QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) { + && QMessageBox::question(this, "Replace Media", "You dropped a file onto '" + m->get_name() + "'. Would you like to replace it with the dropped file?", QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) { replace = true; - panel_project->replace_media(drop_item, paths.at(0)); + panel_project->replace_media(m, paths.at(0)); } if (!replace) { - QTreeWidgetItem* parent = NULL; - if (drop_item != NULL) { - if (get_type_from_tree(drop_item) == MEDIA_TYPE_FOLDER) { - parent = drop_item; + QModelIndex parent; + if (drop_item.isValid()) { + if (m->get_type() == MEDIA_TYPE_FOLDER) { + parent = drop_item; } else { - parent = drop_item->parent(); + parent = drop_item.parent(); } } - if (parent != NULL) parent->setExpanded(true); - panel_project->process_file_list(false, paths, parent, NULL); + if (parent.isValid()) setExpanded(parent, true); + panel_project->process_file_list(false, paths, static_cast(parent.internalPointer()), NULL); } } event->acceptProposedAction(); @@ -263,24 +271,26 @@ void SourceTable::dropEvent(QDropEvent* event) { // dragging files within project // if we dragged to the root OR dragged to a folder - if (drop_item == NULL || (drop_item != NULL && get_type_from_tree(drop_item) == MEDIA_TYPE_FOLDER)) { - QVector move_items; - QList selected_items = selectedItems(); + if (!drop_item.isValid() || (drop_item.isValid() && m->get_type() == MEDIA_TYPE_FOLDER)) { + QVector move_items; + QModelIndexList selected_items = selectionModel()->selectedRows(); for (int i=0;iparent() != drop_item && s != drop_item) { + const QModelIndex& item = selected_items.at(i); + const QModelIndex& parent = item.parent(); + Media* s = static_cast(item.internalPointer()); + if (parent != drop_item && item != drop_item) { bool ignore = false; - if (s->parent() != NULL) { + if (parent.isValid()) { // if child belongs to a selected parent, assume the user is just moving the parent and ignore the child - QTreeWidgetItem* par = s->parent(); - while (par != NULL && !ignore) { + QModelIndex par = parent; + while (par.isValid() && !ignore) { for (int j=0;jparent(); + par = par.parent(); } } if (!ignore) { @@ -290,7 +300,7 @@ void SourceTable::dropEvent(QDropEvent* event) { } if (move_items.size() > 0) { MediaMove* mm = new MediaMove(this); - mm->to = drop_item; + mm->to = m; mm->items = move_items; undo_stack.push(mm); } diff --git a/ui/sourcetable.h b/ui/sourcetable.h index 77b9a590b..136586ba6 100644 --- a/ui/sourcetable.h +++ b/ui/sourcetable.h @@ -1,13 +1,14 @@ #ifndef SOURCETABLE_H #define SOURCETABLE_H -#include +#include #include #include class Project; +class Media; -class SourceTable : public QTreeWidget +class SourceTable : public QTreeView { Q_OBJECT public: @@ -20,13 +21,13 @@ protected: void dropEvent(QDropEvent *event); private: QTimer rename_timer; - QTreeWidgetItem* editing_item; - QString editing_item_name; + Media* editing_item; + QModelIndex editing_index; private slots: void rename_interval(); - void item_click(QTreeWidgetItem* item, int column); + void item_click(const QModelIndex& index); void stop_rename_timer(); - void item_renamed(QTreeWidgetItem *item); + void item_renamed(Media *item); void show_context_menu(); void create_seq_from_selected(); void reveal_in_browser(); diff --git a/ui/timelinewidget.cpp b/ui/timelinewidget.cpp index a34c84259..a6902bf84 100644 --- a/ui/timelinewidget.cpp +++ b/ui/timelinewidget.cpp @@ -8,7 +8,7 @@ #include "project/clip.h" #include "panels/project.h" #include "panels/timeline.h" -#include "io/media.h" +#include "project/footage.h" #include "ui/sourcetable.h" #include "panels/effectcontrols.h" #include "panels/viewer.h" @@ -16,6 +16,7 @@ #include "ui_timeline.h" #include "mainwindow.h" #include "ui/viewerwidget.h" +#include "project/media.h" #include "debug.h" #include "project/effect.h" @@ -238,17 +239,14 @@ bool same_sign(int a, int b) { void TimelineWidget::dragEnterEvent(QDragEnterEvent *event) { bool import_init = false; - QVector media_list; - QVector type_list; + QVector media_list; panel_timeline->importing_files = false; if (event->source() == panel_project->source_table) { - QList items = panel_project->source_table->selectedItems(); - media_list.resize(items.size()); - type_list.resize(items.size()); - for (int i=0;isource_table->selectionModel()->selectedRows(); + media_list.resize(items.size()); + for (int i=0;i(items.at(i).internalPointer()); } import_init = true; } @@ -256,13 +254,7 @@ void TimelineWidget::dragEnterEvent(QDragEnterEvent *event) { if (event->source() == panel_footage_viewer->viewer_widget) { Sequence* proposed_seq = panel_footage_viewer->seq; if (proposed_seq != sequence) { // don't allow nesting the same sequence - if (proposed_seq->wrapper_sequence) { - type_list.append(MEDIA_TYPE_FOOTAGE); - media_list.append(proposed_seq->clips.at(0)->media); - } else { - type_list.append(MEDIA_TYPE_SEQUENCE); - media_list.append(proposed_seq); - } + media_list.append(panel_footage_viewer->media); import_init = true; } } @@ -281,12 +273,12 @@ void TimelineWidget::dragEnterEvent(QDragEnterEvent *event) { for (int i=0;ilast_imported_media.size();i++) { // waits for media to have a duration // TODO would be much nicer if this was multithreaded - panel_project->last_imported_media.at(i)->ready_lock.lock(); - panel_project->last_imported_media.at(i)->ready_lock.unlock(); + Footage* f = panel_project->last_imported_media.at(i)->to_footage(); + f->ready_lock.lock(); + f->ready_lock.unlock(); - if (panel_project->last_imported_media.at(i)->ready) { - media_list.append(panel_project->last_imported_media.at(i)); - type_list.append(MEDIA_TYPE_FOOTAGE); + if (f->ready) { + media_list.append(panel_project->last_imported_media.at(i)); } } @@ -309,7 +301,7 @@ void TimelineWidget::dragEnterEvent(QDragEnterEvent *event) { // if no sequence, we're going to create a new one using the clips as a reference entry_point = 0; - self_created_sequence = create_sequence_from_media(media_list, type_list); + self_created_sequence = create_sequence_from_media(media_list); seq = self_created_sequence; } else { entry_point = panel_timeline->getTimelineFrameFromScreenPoint(event->pos().x()); @@ -317,7 +309,7 @@ void TimelineWidget::dragEnterEvent(QDragEnterEvent *event) { panel_timeline->drag_track_start = (bottom_align) ? -1 : 0; } - panel_timeline->create_ghosts_from_media(seq, entry_point, media_list, type_list); + panel_timeline->create_ghosts_from_media(seq, entry_point, media_list); panel_timeline->importing = true; } @@ -773,7 +765,7 @@ void TimelineWidget::mouseReleaseEvent(QMouseEvent *event) { if (c->track < 0) { // default video effects (before custom effects) c->effects.append(create_effect(c, get_internal_meta(EFFECT_INTERNAL_TRANSFORM, EFFECT_TYPE_EFFECT))); - c->media_type = MEDIA_TYPE_SOLID; + //c->media_type = MEDIA_TYPE_SOLID; } switch (panel_timeline->creating_object) { @@ -807,7 +799,7 @@ void TimelineWidget::mouseReleaseEvent(QMouseEvent *event) { // default audio effects (after custom effects) c->effects.append(create_effect(c, get_internal_meta(EFFECT_INTERNAL_VOLUME, EFFECT_TYPE_EFFECT))); c->effects.append(create_effect(c, get_internal_meta(EFFECT_INTERNAL_PAN, EFFECT_TYPE_EFFECT))); - c->media_type = MEDIA_TYPE_TONE; + //c->media_type = MEDIA_TYPE_TONE; } push_undo = true; @@ -1227,17 +1219,17 @@ void TimelineWidget::update_ghosts(const QPoint& mouse_pos, bool lock_frame) { Clip* c = NULL; if (g.clip != -1) c = sequence->clips.at(g.clip); - MediaStream* ms = NULL; - if (g.clip != -1 && c->media_type == MEDIA_TYPE_FOOTAGE) { - ms = static_cast(c->media)->get_stream_from_file_index(c->track < 0, c->media_stream); + FootageStream* ms = NULL; + if (g.clip != -1 && c->media->get_type() == MEDIA_TYPE_FOOTAGE) { + ms = c->media->to_footage()->get_stream_from_file_index(c->track < 0, c->media_stream); } // validate ghosts for trimming if (panel_timeline->creating) { // i feel like we might need something here but we haven't so far? } else if (panel_timeline->tool == TIMELINE_TOOL_SLIP) { - if (c->media_type == MEDIA_TYPE_SEQUENCE - || (c->media_type == MEDIA_TYPE_FOOTAGE && !static_cast(c->media)->get_stream_from_file_index(c->track < 0, c->media_stream)->infinite_length)) { + if (c->media->get_type() == MEDIA_TYPE_SEQUENCE + || (ms != NULL && !ms->infinite_length)) { // prevent slip moving a clip below 0 clip_in validator = g.old_clip_in - frame_diff; if (validator < 0) frame_diff += validator; @@ -1257,7 +1249,7 @@ void TimelineWidget::update_ghosts(const QPoint& mouse_pos, bool lock_frame) { if (validator < 0) frame_diff -= validator; // prevent clip_in from going below 0 - if (c->media_type == MEDIA_TYPE_SEQUENCE + if (c->media->get_type() == MEDIA_TYPE_SEQUENCE || (ms != NULL && !ms->infinite_length)) { validator = g.old_clip_in + frame_diff; if (validator < 0) frame_diff -= validator; @@ -1268,7 +1260,7 @@ void TimelineWidget::update_ghosts(const QPoint& mouse_pos, bool lock_frame) { if (validator < 1) frame_diff += (1 - validator); // prevent clip length exceeding media length - if (c->media_type == MEDIA_TYPE_SEQUENCE + if (c->media->get_type() == MEDIA_TYPE_SEQUENCE || (ms != NULL && !ms->infinite_length)) { validator = g.old_clip_in + g.ghost_length + frame_diff; if (validator > g.media_length) frame_diff -= validator - g.media_length; @@ -1349,14 +1341,14 @@ void TimelineWidget::update_ghosts(const QPoint& mouse_pos, bool lock_frame) { if (validator > 0) frame_diff -= validator; } else { // prevent clip_in from going below 0 - if (c->media_type == MEDIA_TYPE_SEQUENCE + if (c->media->get_type() == MEDIA_TYPE_SEQUENCE || (ms != NULL && !ms->infinite_length)) { validator = g.old_clip_in + frame_diff; if (validator < 0) frame_diff -= validator; } // prevent clip length exceeding media length - if (c->media_type == MEDIA_TYPE_SEQUENCE + if (c->media->get_type() == MEDIA_TYPE_SEQUENCE || (ms != NULL && !ms->infinite_length)) { validator = g.old_clip_in + g.ghost_length + frame_diff; if (validator > g.media_length) frame_diff -= validator - g.media_length; @@ -2049,7 +2041,7 @@ void TimelineWidget::mouseMoveEvent(QMouseEvent *event) { g.in = g.old_in = g.out = g.old_out = (panel_timeline->transition_tool_type == TA_OPENING_TRANSITION) ? c->timeline_in : c->timeline_out; g.track = c->track; g.clip = panel_timeline->transition_tool_pre_clip; - g.media_type = panel_timeline->transition_tool_type; + g.media_stream = panel_timeline->transition_tool_type; g.trimming = false; panel_timeline->ghosts.append(g); @@ -2097,7 +2089,7 @@ int color_brightness(int r, int g, int b) { return (0.2126*r + 0.7152*g + 0.0722*b); } -void draw_waveform(Clip* clip, MediaStream* ms, long media_length, QPainter *p, const QRect& clip_rect, int waveform_start, int waveform_limit, double zoom) { +void draw_waveform(Clip* clip, FootageStream* ms, long media_length, QPainter *p, const QRect& clip_rect, int waveform_start, int waveform_limit, double zoom) { int divider = ms->audio_channels*2; int channel_height = clip_rect.height()/ms->audio_channels; @@ -2225,11 +2217,11 @@ void TimelineWidget::paintEvent(QPaintEvent*) { int thumb_x = clip_rect.x() + 1; - if (clip->media_type == MEDIA_TYPE_FOOTAGE) { + if (clip->media != NULL && clip->media->get_type() == MEDIA_TYPE_FOOTAGE) { bool draw_checkerboard = false; QRect checkerboard_rect(clip_rect); - Media* m = static_cast(clip->media); - MediaStream* ms = m->get_stream_from_file_index(clip->track < 0, clip->media_stream); + Footage* m = clip->media->to_footage(); + FootageStream* ms = m->get_stream_from_file_index(clip->track < 0, clip->media_stream); if (ms == NULL) { draw_checkerboard = true; } else if (ms->preview_done) { @@ -2641,5 +2633,5 @@ void TimelineWidget::setScroll(int s) { } void TimelineWidget::reveal_media() { - panel_project->reveal_media(rc_reveal_media); + panel_project->reveal_media(rc_reveal_media); } diff --git a/ui/timelinewidget.h b/ui/timelinewidget.h index 07c6b6248..0af743afb 100644 --- a/ui/timelinewidget.h +++ b/ui/timelinewidget.h @@ -13,7 +13,7 @@ struct Sequence; struct Clip; -struct MediaStream; +struct FootageStream; class Timeline; class TimelineAction; class QScrollBar; @@ -21,7 +21,7 @@ class SetSelectionsCommand; class QPainter; bool same_sign(int a, int b); -void draw_waveform(Clip* clip, MediaStream* ms, long media_length, QPainter* p, const QRect& clip_rect, int waveform_start, int waveform_limit, double zoom); +void draw_waveform(Clip* clip, FootageStream* ms, long media_length, QPainter* p, const QRect& clip_rect, int waveform_start, int waveform_limit, double zoom); class TimelineWidget : public QWidget { Q_OBJECT diff --git a/ui/viewerwidget.cpp b/ui/viewerwidget.cpp index d0730a725..5048d0fac 100644 --- a/ui/viewerwidget.cpp +++ b/ui/viewerwidget.cpp @@ -10,7 +10,7 @@ #include "project/transition.h" #include "playback/playback.h" #include "playback/audio.h" -#include "io/media.h" +#include "project/footage.h" #include "ui_timeline.h" #include "playback/cacher.h" #include "io/config.h" @@ -18,6 +18,7 @@ #include "io/math.h" #include "ui/collapsiblewidget.h" #include "project/undo.h" +#include "project/media.h" #include #include @@ -126,7 +127,7 @@ void ViewerWidget::initializeGL() { connect(context(), SIGNAL(aboutToBeDestroyed()), this, SLOT(delete_function()), Qt::DirectConnection); - retry_timer.start(); + retry_timer.start(); } //void ViewerWidget::resizeGL(int w, int h) @@ -378,7 +379,7 @@ GLuint ViewerWidget::compose_sequence(QVector& nests, bool render_audio) if (!nests.isEmpty()) { for (int i=0;i(nests.at(i)->media); + s = nests.at(i)->media->to_sequence(); playhead += nests.at(i)->clip_in - nests.at(i)->get_timeline_in_with_transition(); playhead = refactor_frame_number(playhead, nests.at(i)->sequence->frame_rate, s->frame_rate); } @@ -402,13 +403,11 @@ GLuint ViewerWidget::compose_sequence(QVector& nests, bool render_audio) if (!(!nests.isEmpty() && !same_sign(c->track, nests.last()->track))) { bool clip_is_active = false; - switch (c->media_type) { - case MEDIA_TYPE_FOOTAGE: - { - Media* m = static_cast(c->media); + if (c->media != NULL && c->media->get_type() == MEDIA_TYPE_FOOTAGE) { + Footage* m = c->media->to_footage(); if (!m->invalid && !(c->track >= 0 && !is_audio_device_set())) { if (m->ready) { - MediaStream* ms = m->get_stream_from_file_index(c->track < 0, c->media_stream); + FootageStream* ms = m->get_stream_from_file_index(c->track < 0, c->media_stream); if (ms != NULL && is_clip_active(c, playhead)) { // if thread is already working, we don't want to touch this, // but we also don't want to hang the UI thread @@ -421,23 +420,18 @@ GLuint ViewerWidget::compose_sequence(QVector& nests, bool render_audio) close_clip(c); } } else { - dout << "[WARNING] Media was not ready, retrying..."; + //dout << "[WARNING] Media '" + m->name + "' was not ready, retrying..."; texture_failed = true; } } - } - break; - case MEDIA_TYPE_SEQUENCE: - case MEDIA_TYPE_SOLID: - case MEDIA_TYPE_TONE: - if (is_clip_active(c, playhead)) { - if (!c->open) open_clip(c, !rendering); - clip_is_active = true; - } else if (c->open) { - close_clip(c); - } - break; - } + } else { + if (is_clip_active(c, playhead)) { + if (!c->open) open_clip(c, !rendering); + clip_is_active = true; + } else if (c->open) { + close_clip(c); + } + } if (clip_is_active) { bool added = false; for (int j=0;j& nests, bool render_audio) Clip* c = current_clips.at(i); - if (c->media_type == MEDIA_TYPE_FOOTAGE && !c->finished_opening) { + if (c->media != NULL && c->media->get_type() == MEDIA_TYPE_FOOTAGE && !c->finished_opening) { dout << "[WARNING] Tried to display clip" << i << "but it's closed"; texture_failed = true; } else { @@ -478,23 +472,28 @@ GLuint ViewerWidget::compose_sequence(QVector& nests, bool render_audio) int video_width = c->getWidth(); int video_height = c->getHeight(); - if (c->media_type == MEDIA_TYPE_FOOTAGE) { - // set up opengl texture - if (c->texture == NULL) { - c->texture = new QOpenGLTexture(QOpenGLTexture::Target2D); - c->texture->setSize(c->stream->codecpar->width, c->stream->codecpar->height); - c->texture->setFormat(QOpenGLTexture::RGBA8_UNorm); - c->texture->setMipLevels(c->texture->maximumMipLevels()); - c->texture->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear); - c->texture->allocateStorage(QOpenGLTexture::RGBA, QOpenGLTexture::UInt8); - } - get_clip_frame(c, playhead); - textureID = c->texture->textureId(); - } else if (c->media_type == MEDIA_TYPE_SEQUENCE) { - textureID = -1; - } + if (c->media != NULL) { + switch (c->media->get_type()) { + case MEDIA_TYPE_FOOTAGE: + // set up opengl texture + if (c->texture == NULL) { + c->texture = new QOpenGLTexture(QOpenGLTexture::Target2D); + c->texture->setSize(c->stream->codecpar->width, c->stream->codecpar->height); + c->texture->setFormat(QOpenGLTexture::RGBA8_UNorm); + c->texture->setMipLevels(c->texture->maximumMipLevels()); + c->texture->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear); + c->texture->allocateStorage(QOpenGLTexture::RGBA, QOpenGLTexture::UInt8); + } + get_clip_frame(c, playhead); + textureID = c->texture->textureId(); + break; + case MEDIA_TYPE_SEQUENCE: + textureID = -1; + break; + } + } - if (textureID == 0 && c->media_type != MEDIA_TYPE_SOLID) { + if (textureID == 0 && c->media != NULL) { dout << "[WARNING] Texture hasn't been created yet"; texture_failed = true; } else if (playhead >= c->get_timeline_in_with_transition()) { @@ -519,21 +518,22 @@ GLuint ViewerWidget::compose_sequence(QVector& nests, bool render_audio) glViewport(0, 0, video_width, video_height); - // for nested sequences - if (c->media_type == MEDIA_TYPE_SEQUENCE) { - nests.append(c); - textureID = compose_sequence(nests, render_audio); - nests.removeLast(); - fbo_switcher = true; - } + GLuint composite_texture; - GLuint composite_texture; - if (c->media_type == MEDIA_TYPE_SOLID) { + if (c->media == NULL) { c->fbo[fbo_switcher]->bind(); glClear(GL_COLOR_BUFFER_BIT); c->fbo[fbo_switcher]->release(); composite_texture = c->fbo[fbo_switcher]->texture(); } else { + // for nested sequences + if (c->media->get_type()== MEDIA_TYPE_SEQUENCE) { + nests.append(c); + textureID = compose_sequence(nests, render_audio); + nests.removeLast(); + fbo_switcher = true; + } + composite_texture = draw_clip(c->fbo[fbo_switcher], textureID, true); } @@ -686,21 +686,17 @@ GLuint ViewerWidget::compose_sequence(QVector& nests, bool render_audio) } } else { if (render_audio || (config.enable_audio_scrubbing && audio_scrub)) { - switch (c->media_type) { - case MEDIA_TYPE_FOOTAGE: - case MEDIA_TYPE_TONE: - if (c->lock.tryLock()) { + if (c->media != NULL && c->media->get_type() == MEDIA_TYPE_SEQUENCE) { + nests.append(c); + compose_sequence(nests, render_audio); + nests.removeLast(); + } else { + if (c->lock.tryLock()) { // clip is not caching, start caching audio - cache_clip(c, playhead, c->audio_reset, !render_audio, nests); - c->lock.unlock(); - } - break; - case MEDIA_TYPE_SEQUENCE: - nests.append(c); - compose_sequence(nests, render_audio); - nests.removeLast(); - break; - } + cache_clip(c, playhead, c->audio_reset, !render_audio, nests); + c->lock.unlock(); + } + } } // visually update all the keyframe values diff --git a/ui/viewerwidget.h b/ui/viewerwidget.h index 1b7abc7d5..388582106 100644 --- a/ui/viewerwidget.h +++ b/ui/viewerwidget.h @@ -12,7 +12,7 @@ class Viewer; struct Clip; -struct MediaStream; +struct FootageStream; class QOpenGLFramebufferObject; class Effect; class EffectGizmo; @@ -32,7 +32,7 @@ public: bool waveform; Clip* waveform_clip; - MediaStream* waveform_ms; + FootageStream* waveform_ms; bool force_quit; public slots: