diff --git a/dialogs/newsequencedialog.cpp b/dialogs/newsequencedialog.cpp index dda3eab99..915b55305 100644 --- a/dialogs/newsequencedialog.cpp +++ b/dialogs/newsequencedialog.cpp @@ -61,7 +61,7 @@ void NewSequenceDialog::on_buttonBox_accepted() s->audio_frequency = ui->audio_frequency_combobox->currentData().toInt(); s->audio_layout = AV_CH_LAYOUT_STEREO; - panel_project->new_sequence(s, true); + panel_project->new_sequence(s, true, NULL); } void NewSequenceDialog::on_comboBox_currentIndexChanged(int index) diff --git a/mainwindow.cpp b/mainwindow.cpp index 5ccf27529..8d88e22ab 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -262,13 +262,14 @@ void MainWindow::on_action_Paste_triggered() } void MainWindow::autorecover_interval() { - bool old_changed = project_changed; - QString old_filename = project_url; - project_url = autorecovery_filename; - qDebug() << "[INFO] Auto-recovery project saved"; - panel_project->save_project(); - project_url = old_filename; - project_changed = old_changed; + if (!project_changed) { + QString old_filename = project_url; + project_url = autorecovery_filename; + panel_project->save_project(); + qDebug() << "[INFO] Auto-recovery project saved"; + project_url = old_filename; + project_changed = false; + } } bool MainWindow::save_project_as() { diff --git a/olive.pro.user b/olive.pro.user index 46889be05..1e86905fd 100644 --- a/olive.pro.user +++ b/olive.pro.user @@ -1,6 +1,6 @@ - + EnvironmentId diff --git a/panels/project.cpp b/panels/project.cpp index b2f251ec7..c34f9f820 100644 --- a/panels/project.cpp +++ b/panels/project.cpp @@ -80,32 +80,36 @@ void Project::rename_media(QTreeWidgetItem* item, int column) { void Project::duplicate_selected() { QList items = ui->treeWidget->selectedItems(); - for (int i=0;icopy(), false); + for (int j=0;jcopy(), false, i->parent()); } } } -void Project::new_sequence(Sequence *s, bool open) { +void Project::new_sequence(Sequence *s, bool open, QTreeWidgetItem* parent) { QTreeWidgetItem* item = new_item(); item->setText(0, s->name); set_sequence_of_tree(item, s); - ui->treeWidget->addTopLevelItem(item); + if (parent == NULL) { + ui->treeWidget->addTopLevelItem(item); + } else { + parent->addChild(item); + } project_changed = true; if (open) set_sequence(s); } -Media* Project::import_file(QString file) { +QTreeWidgetItem* Project::import_file(QString file) { QByteArray ba = file.toLatin1(); char* filename = new char[ba.size()+1]; strcpy(filename, ba.data()); - Media* m = NULL; + QTreeWidgetItem* item = NULL; AVFormatContext* pFormatCtx = NULL; int errCode = avformat_open_input(&pFormatCtx, filename, NULL, NULL); @@ -122,7 +126,7 @@ Media* Project::import_file(QString file) { } else { av_dump_format(pFormatCtx, 0, filename, 0); - m = new Media(); + Media* m = new Media(); m->url = file; m->name = file.mid(file.lastIndexOf('/')+1); @@ -149,7 +153,7 @@ Media* Project::import_file(QString file) { } m->length = pFormatCtx->duration; - QTreeWidgetItem* item = new_item(); + item = new_item(); if (m->video_tracks.size() == 0) { item->setIcon(0, QIcon(":/icons/audiosource.png")); } else { @@ -159,8 +163,6 @@ Media* Project::import_file(QString file) { item->setText(1, QString::number(m->length)); set_media_of_tree(item, m); - ui->treeWidget->addTopLevelItem(item); - project_changed = true; // generate waveform/thumbnail in another thread @@ -173,7 +175,7 @@ Media* Project::import_file(QString file) { } delete [] filename; - return m; + return item; } QTreeWidgetItem* Project::new_item() { @@ -186,13 +188,14 @@ bool Project::is_focused() { return ui->treeWidget->hasFocus(); } -void Project::new_folder() { +QTreeWidgetItem* Project::new_folder() { QTreeWidgetItem* item = new_item(); item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); item->setText(0, "New Folder"); set_item_to_folder(item); ui->treeWidget->addTopLevelItem(item); project_changed = true; + return item; } void Project::delete_selected_media() { @@ -293,7 +296,7 @@ void Project::process_file_list(QStringList& files) { } } - import_file(file); + ui->treeWidget->addTopLevelItem(import_file(file)); } } @@ -361,28 +364,269 @@ void Project::new_project() { project_changed = false; } -#define LOAD_STATE_IDLE 0 -#define LOAD_STATE_MEDIA 1 -#define LOAD_STATE_FOOTAGE 2 -#define LOAD_STATE_TIMELINE 3 -#define LOAD_STATE_SEQUENCE 4 -#define LOAD_STATE_CLIP 5 -#define LOAD_STATE_CLIP_EFFECTS 6 -#define LOAD_STATE_EFFECT 7 -#define LOAD_STATE_CLIP_LINKS 8 +QTreeWidgetItem* Project::find_loaded_folder_by_id(int id) { + for (int j=0;jdata(0, Qt::UserRole + 3).toInt() == id) { + return parent_item; + } + } +} + +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 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; + } + + while (!stream.atEnd()) { + stream.readNextStartElement(); + if (stream.name() == root_search) { + if (type == LOAD_TYPE_VERSION) { + if (stream.readElementText() != 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) { + error_str = "Incompatible project version."; + return false; + } + } + } 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(); + for (int j=0;jsetData(0, Qt::UserRole + 3, attr.value().toInt()); + } else if (attr.name() == "name") { + folder->setText(0, attr.value().toString()); + } + } + loaded_folders.append(folder); + } + break; + case MEDIA_TYPE_FOOTAGE: + { + QString name; + QString url; + int id; + int folder; + for (int j=0;jname = name; + item->setText(0, name); + m->save_id = id; + if (folder == 0) { + ui->treeWidget->addTopLevelItem(item); + } else { + find_loaded_folder_by_id(folder)->addChild(item); + } + loaded_media.append(m); + } + break; + case MEDIA_TYPE_SEQUENCE: + { + QTreeWidgetItem* parent = NULL; + Sequence* s = new Sequence(); + + // load attributes about stream + 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() == "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().toFloat(); + } else if (attr.name() == "afreq") { + s->audio_frequency = attr.value().toInt(); + } else if (attr.name() == "alayout") { + s->audio_layout = attr.value().toInt(); + } + } + + // load all clips and clip information + while (!(stream.name() == child_search && stream.isEndElement()) && !stream.atEnd()) { + stream.readNextStartElement(); + if (stream.name() == "clip" && stream.isStartElement()) { + // + int media_id, stream_id; + Clip* c = new Clip(); + for (int j=0;jname = attr.value().toString(); + } else if (attr.name() == "id") { + c->load_id = attr.value().toInt(); + } else if (attr.name() == "clipin") { + c->clip_in = attr.value().toInt(); + } else if (attr.name() == "in") { + c->timeline_in = attr.value().toInt(); + } else if (attr.name() == "out") { + c->timeline_out = attr.value().toInt(); + } 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() == "media") { + media_id = attr.value().toInt(); + } else if (attr.name() == "stream") { + stream_id = attr.value().toInt(); + } + } + + // set media and media stream + for (int j=0;jsave_id == media_id) { + c->media = m; + c->media_stream = c->media->get_stream_from_file_index(stream_id); + break; + } + } + + c->sequence = s; + + // load links and effects + while (!(stream.name() == "clip" && stream.isEndElement()) && !stream.atEnd()) { + stream.readNext(); + if (stream.name() == "linked" && stream.isStartElement()) { + 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; + } + } + } + } + } + if (stream.name() == "effects" && stream.isStartElement()) { + while (!(stream.name() == "effects" && stream.isEndElement()) && !stream.atEnd()) { + stream.readNext(); + if (stream.name() == "effect" && stream.isStartElement()) { + int effect_id = -1; + for (int j=0;jload(&stream); + c->effects.append(e); + } + } + } + } + } + + s->add_clip(c); + } + } + + // correct links and clip IDs + for (int i=0;iclip_count();i++) { + // correct links + Clip* correct_clip = s->get_clip(i); + for (int j=0;jlinked.size();j++) { + bool found = false; + for (int k=0;kclip_count();k++) { + if (s->get_clip(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; + } + } + } + } + + new_sequence(s, false, parent); + } + break; + } + } + } + } + break; + } + } + return true; +} void Project::load_project() { new_project(); QFile file(project_url); - if (!file.open(QIODevice::ReadOnly/* | QIODevice::Text*/)) { + if (!file.open(QIODevice::ReadOnly)) { qDebug() << "[ERROR] Could not open file"; return; } QXmlStreamReader stream(&file); + bool cont = false; + error_str.clear(); + // temp variables for loading + loaded_folders.clear(); + loaded_media.clear(); QVector temp_media_list; QString temp_name; QString temp_url; @@ -390,8 +634,25 @@ void Project::load_project() { Sequence* temp_seq; Clip* temp_clip; - int state = LOAD_STATE_IDLE; - bool error = false; + // find project file version + cont = load_worker(file, stream, LOAD_TYPE_VERSION); + + // load folders first + if (cont) { + cont = load_worker(file, stream, MEDIA_TYPE_FOLDER); + } + + // load media + if (cont) { + cont = load_worker(file, stream, MEDIA_TYPE_FOOTAGE); + } + + // load sequences + if (cont) { + cont = load_worker(file, stream, MEDIA_TYPE_SEQUENCE); + } + + /*int state = LOAD_STATE_IDLE; while (!error && !stream.atEnd()) { stream.readNext(); switch (state) { @@ -574,22 +835,114 @@ void Project::load_project() { } break; } - } - if (stream.hasError()) { - qDebug() << "[ERROR] Error parsing XML." << stream.error(); - QMessageBox::critical(this, "XML Parsing Error", "Couldn't load '" + project_url + "'. " + stream.error(), QMessageBox::Ok); - error = true; + }*/ + if (!cont) { + QMessageBox::critical(this, "Project Load Error", "Error loading project: " + error_str, QMessageBox::Ok); + } else if (stream.hasError()) { + qDebug() << "[ERROR] Error parsing XML." << stream.errorString(); + QMessageBox::critical(this, "XML Parsing Error", "Couldn't load '" + project_url + "'. " + stream.errorString(), QMessageBox::Ok); + cont = false; } - if (error) { - new_project(); - } else { + if (cont) { panel_timeline->redraw_all_clips(false); project_changed = false; + } else { + new_project(); + } +} + +void Project::save_folder(QXmlStreamWriter& stream, QTreeWidgetItem* parent, int type) { + bool root = (parent == NULL); + int len = root ? ui->treeWidget->topLevelItemCount() : parent->childCount(); + for (int i=0;itreeWidget->topLevelItem(i) : parent->child(i); + int item_type = get_type_from_tree(item); + if (item_type == MEDIA_TYPE_FOLDER) { + if (type == MEDIA_TYPE_FOLDER) { + // if we're saving folders, save the folder + stream.writeStartElement("folder"); + stream.writeAttribute("name", item->text(0)); + stream.writeAttribute("id", QString::number(folder_id)); + item->setData(0, Qt::UserRole + 3, folder_id); // saves a temporary ID for matching in the project file + stream.writeEndElement(); + folder_id++; + } + save_folder(stream, item, type); + } else if (type == item_type) { + int folder = root ? 0 : parent->data(0, Qt::UserRole + 3).toInt(); + if (type == MEDIA_TYPE_FOOTAGE) { + Media* m = get_media_from_tree(item); + m->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", m->url); + stream.writeEndElement(); + media_id++; + } else if (type == MEDIA_TYPE_SEQUENCE) { + Sequence* s = get_sequence_from_tree(item); + stream.writeStartElement("sequence"); + stream.writeAttribute("folder", QString::number(folder)); + stream.writeAttribute("name", s->name); + stream.writeAttribute("width", QString::number(s->width)); + stream.writeAttribute("height", QString::number(s->height)); + stream.writeAttribute("framerate", QString::number(s->frame_rate)); + stream.writeAttribute("afreq", QString::number(s->audio_frequency)); + stream.writeAttribute("alayout", QString::number(s->audio_layout)); + for (int j=0;jclip_count();j++) { + Clip* c = s->get_clip(j); + if (c != NULL) { + stream.writeStartElement("clip"); // clip + stream.writeAttribute("id", QString::number(j)); + stream.writeAttribute("name", c->name); + stream.writeAttribute("clipin", QString::number(c->clip_in)); + stream.writeAttribute("in", QString::number(c->timeline_in)); + stream.writeAttribute("out", QString::number(c->timeline_out)); + stream.writeAttribute("track", QString::number(c->track)); + if (c->opening_transition != NULL) { + stream.writeAttribute("opening", QString::number(c->opening_transition->id)); + } + if (c->closing_transition != NULL) { + stream.writeAttribute("closing", QString::number(c->closing_transition->id)); + } + stream.writeAttribute("r", QString::number(c->color_r)); + stream.writeAttribute("g", QString::number(c->color_g)); + stream.writeAttribute("b", QString::number(c->color_b)); + stream.writeAttribute("media", QString::number(c->media->save_id)); + stream.writeAttribute("stream", QString::number(c->media_stream->file_index)); + + stream.writeStartElement("linked"); // linked + for (int k=0;klinked.size();k++) { + stream.writeStartElement("link"); // link + stream.writeAttribute("id", QString::number(c->linked.at(k))); + stream.writeEndElement(); // link + } + stream.writeEndElement(); // linked + + stream.writeStartElement("effects"); // effects + for (int k=0;keffects.size();k++) { + stream.writeStartElement("effect"); // effect + Effect* e = c->effects.at(k); + stream.writeAttribute("id", QString::number(e->id)); + e->save(&stream); + stream.writeEndElement(); // effect + } + stream.writeEndElement(); // effects + stream.writeEndElement(); // clip + } + } + stream.writeEndElement(); + } + } } } void Project::save_project() { + folder_id = 1; + media_id = 0; + QFile file(project_url); if (!file.open(QIODevice::WriteOnly/* | QIODevice::Text*/)) { qDebug() << "[ERROR] Could not open file"; @@ -598,90 +951,27 @@ void Project::save_project() { QXmlStreamWriter stream(&file); stream.setAutoFormatting(true); - stream.writeStartDocument(); + stream.writeStartDocument(); // doc - stream.writeStartElement("project"); + stream.writeStartElement("project"); // project - stream.writeTextElement("version", "180601"); + stream.writeTextElement("version", SAVE_VERSION); - int len = ui->treeWidget->topLevelItemCount(); - stream.writeStartElement("media"); - for (int i=0;itreeWidget->topLevelItem(i); - if (get_type_from_tree(item) == MEDIA_TYPE_FOOTAGE) { - Media* m = get_media_from_tree(item); - m->save_id = i; - stream.writeStartElement("footage"); - stream.writeAttribute("id", QString::number(i)); - stream.writeTextElement("name", m->name); - stream.writeTextElement("url", m->url); - stream.writeEndElement(); - } - } - stream.writeEndElement(); + stream.writeStartElement("folders"); // folders + save_folder(stream, NULL, MEDIA_TYPE_FOLDER); + stream.writeEndElement(); // folders - stream.writeStartElement("timeline"); - for (int i=0;itreeWidget->topLevelItem(i); - if (get_type_from_tree(item) == MEDIA_TYPE_SEQUENCE) { - Sequence* s = get_sequence_from_tree(item); - stream.writeStartElement("sequence"); - stream.writeTextElement("name", s->name); - stream.writeTextElement("width", QString::number(s->width)); - stream.writeTextElement("height", QString::number(s->height)); - stream.writeTextElement("framerate", QString::number(s->frame_rate)); - stream.writeTextElement("afreq", QString::number(s->audio_frequency)); - stream.writeTextElement("alayout", QString::number(s->audio_layout)); - stream.writeStartElement("clips"); + stream.writeStartElement("media"); // media + save_folder(stream, NULL, MEDIA_TYPE_FOOTAGE); + stream.writeEndElement(); // media - for (int i=0;iclip_count();i++) { - Clip* c = s->get_clip(i); - if (c != NULL) { - stream.writeStartElement("clip"); - stream.writeTextElement("id", QString::number(i)); - stream.writeTextElement("name", c->name); - stream.writeTextElement("clipin", QString::number(c->clip_in)); - stream.writeTextElement("in", QString::number(c->timeline_in)); - stream.writeTextElement("out", QString::number(c->timeline_out)); - stream.writeTextElement("track", QString::number(c->track)); - if (c->opening_transition != NULL) { - stream.writeTextElement("opening", QString::number(c->opening_transition->id)); - } - if (c->closing_transition != NULL) { - stream.writeTextElement("closing", QString::number(c->closing_transition->id)); - } - stream.writeStartElement("color"); - stream.writeAttribute("r", QString::number(c->color_r)); - stream.writeAttribute("g", QString::number(c->color_g)); - stream.writeAttribute("b", QString::number(c->color_b)); - stream.writeEndElement(); - stream.writeTextElement("media", QString::number(c->media->save_id)); - stream.writeTextElement("stream", QString::number(c->media_stream->file_index)); - stream.writeStartElement("linked"); - for (int j=0;jlinked.size();j++) { - stream.writeTextElement("link", QString::number(c->linked.at(j))); - } - stream.writeEndElement(); - stream.writeStartElement("effects"); - for (int j=0;jeffects.size();j++) { - stream.writeStartElement("effect"); - Effect* e = c->effects.at(j); - stream.writeAttribute("id", QString::number(e->id)); - e->save(&stream); - stream.writeEndElement(); - } - stream.writeEndElement(); - stream.writeEndElement(); - } - } - stream.writeEndElement(); - } - } - stream.writeEndElement(); + stream.writeStartElement("sequences"); // sequences + save_folder(stream, NULL, MEDIA_TYPE_SEQUENCE); + stream.writeEndElement();// sequences - stream.writeEndElement(); + stream.writeEndElement(); // project - stream.writeEndDocument(); + stream.writeEndDocument(); // doc file.close(); diff --git a/panels/project.h b/panels/project.h index 53417db2d..2b31e1684 100644 --- a/panels/project.h +++ b/panels/project.h @@ -10,12 +10,19 @@ class Timeline; class Viewer; class SourceTable; class QTreeWidgetItem; +class QXmlStreamWriter; +class QXmlStreamReader; +class QFile; + +#define SAVE_VERSION "180711" #define MEDIA_TYPE_FOOTAGE 0 #define MEDIA_TYPE_SEQUENCE 1 #define MEDIA_TYPE_FOLDER 2 #define MEDIA_TYPE_SOLID 3 +#define LOAD_TYPE_VERSION 69 + namespace Ui { class Project; } @@ -32,9 +39,9 @@ public: ~Project(); bool is_focused(); void clear(); - Media* import_file(QString url); + QTreeWidgetItem* import_file(QString url); void import_dialog(); - void new_sequence(Sequence* s, bool open); + void new_sequence(Sequence* s, bool open, QTreeWidgetItem* parent); QString get_next_sequence_name(); void delete_media(QTreeWidgetItem* item); void delete_selected_media(); @@ -45,7 +52,7 @@ public: void load_project(); void save_project(); - void new_folder(); + QTreeWidgetItem* new_folder(); int get_type_from_tree(QTreeWidgetItem* item); Media* get_media_from_tree(QTreeWidgetItem* item); @@ -58,6 +65,14 @@ public: 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); + QString error_str; + int folder_id; + int media_id; + QVector loaded_folders; + QVector loaded_media; + QTreeWidgetItem* find_loaded_folder_by_id(int id); private slots: void rename_media(QTreeWidgetItem* item, int column); }; diff --git a/ui/timelinewidget.cpp b/ui/timelinewidget.cpp index a6328792e..862edc953 100644 --- a/ui/timelinewidget.cpp +++ b/ui/timelinewidget.cpp @@ -62,14 +62,17 @@ void TimelineWidget::dragEnterEvent(QDragEnterEvent *event) { QTreeWidgetItem* item = items.at(i); if (panel_project->get_type_from_tree(item) == MEDIA_TYPE_FOOTAGE) { Media* m = panel_project->get_media_from_tree(item); - bool ignore_infinite_length = (m->audio_tracks.size() > 0); Ghost g; g.clip = -1; g.media = m; g.old_clip_in = g.clip_in = 0; g.in = entry_point; - entry_point += m->get_length_in_frames(sequence->frame_rate); - g.out = entry_point; + if (m->video_tracks.size() > 0 && m->video_tracks[0]->infinite_length && m->audio_tracks.size() == 0) { + g.out = g.in + 100; + } else { + g.out = entry_point + m->get_length_in_frames(sequence->frame_rate); + } + entry_point = g.out; g.trimming = false; for (int j=0;jaudio_tracks.size();j++) { g.track = j; @@ -79,7 +82,6 @@ void TimelineWidget::dragEnterEvent(QDragEnterEvent *event) { for (int j=0;jvideo_tracks.size();j++) { g.track = -1-j; g.media_stream = m->video_tracks.at(j); - if (m->video_tracks[j]->infinite_length && !ignore_infinite_length) g.out = g.in + 100; panel_timeline->ghosts.append(g); } }