#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); e->moveToThread(QApplication::instance()->thread()); 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); project_model.appendChild(find_loaded_folder_by_id(folder), item); // analyze media to see if it's the same loaded_media_items.append(item); } break; case MEDIA_TYPE_SEQUENCE: { Media* parent = NULL; Sequence* s = new Sequence(); // load attributes about sequence for (int j=0;jname = attr.value().toString(); } else if (attr.name() == "folder") { int folder = attr.value().toInt(); if (folder > 0) parent = find_loaded_folder_by_id(folder); } else if (attr.name() == "id") { s->save_id = attr.value().toInt(); } else if (attr.name() == "width") { s->width = attr.value().toInt(); } else if (attr.name() == "height") { s->height = attr.value().toInt(); } else if (attr.name() == "framerate") { s->frame_rate = attr.value().toDouble(); } else if (attr.name() == "afreq") { s->audio_frequency = attr.value().toInt(); } else if (attr.name() == "alayout") { s->audio_layout = attr.value().toInt(); } else if (attr.name() == "open") { open_seq = s; } else if (attr.name() == "workarea") { s->using_workarea = (attr.value() == "1"); } else if (attr.name() == "workareaIn") { s->workarea_in = attr.value().toLong(); } else if (attr.name() == "workareaOut") { s->workarea_out = attr.value().toLong(); } } QVector transition_data; // load all clips and clip information while (!(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) { if (id == 0) return NULL; 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; project_model.appendChild(find_loaded_folder_by_id(parent), folder); } cont = load_worker(file, stream, MEDIA_TYPE_FOOTAGE); } // load sequences if (cont) { cont = load_worker(file, stream, MEDIA_TYPE_SEQUENCE); } // 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); }