diff --git a/io/config.h b/io/config.h index 8b907779a..ea9614e00 100644 --- a/io/config.h +++ b/io/config.h @@ -23,26 +23,36 @@ #include -#define SAVE_VERSION 190201 // YYMMDD -#define MIN_SAVE_VERSION 190104 // lowest compatible project version +#define SAVE_VERSION 190219 // YYMMDD +#define MIN_SAVE_VERSION 190219 // lowest compatible project version -#define TIMECODE_DROP 0 -#define TIMECODE_NONDROP 1 -#define TIMECODE_FRAMES 2 -#define TIMECODE_MILLISECONDS 3 +enum TimecodeType { + TIMECODE_DROP, + TIMECODE_NONDROP, + TIMECODE_FRAMES, + TIMECODE_MILLISECONDS +}; -#define RECORD_MODE_MONO 1 -#define RECORD_MODE_STEREO 2 +enum RecordingMode { + RECORD_MODE_MONO, + RECORD_MODE_STEREO +}; -#define AUTOSCROLL_NO_SCROLL 0 -#define AUTOSCROLL_PAGE_SCROLL 1 -#define AUTOSCROLL_SMOOTH_SCROLL 2 +enum AutoScrollMode { + AUTOSCROLL_NO_SCROLL, + AUTOSCROLL_PAGE_SCROLL, + AUTOSCROLL_SMOOTH_SCROLL +}; -#define PROJECT_VIEW_TREE 0 -#define PROJECT_VIEW_ICON 1 +enum ProjectView { + PROJECT_VIEW_TREE, + PROJECT_VIEW_ICON +}; -#define FRAME_QUEUE_TYPE_FRAMES 0 -#define FRAME_QUEUE_TYPE_SECONDS 1 +enum FrameQueueType { + FRAME_QUEUE_TYPE_FRAMES, + FRAME_QUEUE_TYPE_SECONDS +}; struct Config { Config(); diff --git a/io/loadthread.cpp b/io/loadthread.cpp index e95034694..42a970673 100644 --- a/io/loadthread.cpp +++ b/io/loadthread.cpp @@ -38,812 +38,746 @@ #include LoadThread::LoadThread(bool a) : autorecovery(a), cancelled(false) { - connect(this, SIGNAL(finished()), this, SLOT(deleteLater())); - connect(this, SIGNAL(success()), this, SLOT(success_func())); - connect(this, SIGNAL(error()), this, SLOT(error_func())); - connect(this, SIGNAL(start_create_dual_transition(const TransitionData*,ClipPtr,ClipPtr,const EffectMeta*)), this, SLOT(create_dual_transition(const TransitionData*,ClipPtr,ClipPtr,const EffectMeta*))); - connect(this, SIGNAL(start_create_effect_ui(QXmlStreamReader*, ClipPtr, int, const QString*, const EffectMeta*, long, bool)), this, SLOT(create_effect_ui(QXmlStreamReader*, ClipPtr, int, const QString*, const EffectMeta*, long, bool))); - connect(this, SIGNAL(start_question(const QString&, const QString &, int)), this, SLOT(question_func(const QString &, const QString &, int))); + connect(this, SIGNAL(finished()), this, SLOT(deleteLater())); + connect(this, SIGNAL(success()), this, SLOT(success_func())); + connect(this, SIGNAL(error()), this, SLOT(error_func())); + connect(this, SIGNAL(start_create_effect_ui(QXmlStreamReader*, ClipPtr, int, const QString*, const EffectMeta*, long, bool)), this, SLOT(create_effect_ui(QXmlStreamReader*, ClipPtr, int, const QString*, const EffectMeta*, long, bool))); + connect(this, SIGNAL(start_question(const QString&, const QString &, int)), this, SLOT(question_func(const QString &, const QString &, int))); } void LoadThread::load_effect(QXmlStreamReader& stream, ClipPtr c) { - int effect_id = -1; - QString effect_name; - bool effect_enabled = true; - long effect_length = -1; - for (int j=0;jeffects_loaded.lock(); + // variables to store effect metadata in + int effect_id = -1; + QString effect_name; + bool effect_enabled = true; + long effect_length = -1; - const EffectMeta* meta = nullptr; + // loop through attributes for effect metadata + for (int j=0;jsequence->clips.at(attr.value().toInt()); + if (tag == "opening") { + c->opening_transition = (sharing_clip->closing_transition); - panel_effect_controls->effects_loaded.unlock(); + // since this is the opened clip, switch secondaries and primaries + c->opening_transition->secondary_clip = c->opening_transition->parent_clip; + c->opening_transition->parent_clip = c; + c->opening_transition->refresh(); + } else if (tag == "closing") { + c->closing_transition = (sharing_clip->opening_transition); - QString tag = stream.name().toString(); + // since this is the closed clip, make this clip the secondary + c->opening_transition->secondary_clip = c; + } + return; + } + } - int type; - if (tag == "opening") { - type = kTransitionOpening; - } else if (tag == "closing") { - type = kTransitionClosing; - } else { - type = kTransitionNone; - } + // Effect loading occurs in another thread, and while it's usually very quick, just for safety we wait here + // for all the effects to finish loading + panel_effect_controls->effects_loaded.lock(); - emit start_create_effect_ui(&stream, c, type, &effect_name, meta, effect_length, effect_enabled); - waitCond.wait(&mutex); + const EffectMeta* meta = nullptr; + + // find effect with this name + if (!effect_name.isEmpty()) { + meta = get_meta_from_name(effect_name); + } + + panel_effect_controls->effects_loaded.unlock(); + + int type; + if (tag == "opening") { + type = kTransitionOpening; + } else if (tag == "closing") { + type = kTransitionClosing; + } else { + type = kTransitionNone; + } + + // effect UI creation has to occur in the main thread, see an explanation in create_effect_ui() + emit start_create_effect_ui(&stream, c, type, &effect_name, meta, effect_length, effect_enabled); + waitCond.wait(&mutex); } void LoadThread::read_next(QXmlStreamReader &stream) { - stream.readNext(); - update_current_element_count(stream); + stream.readNext(); + update_current_element_count(stream); } void LoadThread::read_next_start_element(QXmlStreamReader &stream) { - stream.readNextStartElement(); - update_current_element_count(stream); + stream.readNextStartElement(); + update_current_element_count(stream); } void LoadThread::update_current_element_count(QXmlStreamReader &stream) { - if (is_element(stream)) { - current_element_count++; - report_progress((current_element_count * 100) / total_element_count); - } + if (is_element(stream)) { + current_element_count++; + report_progress((current_element_count * 100) / total_element_count); + } } bool LoadThread::is_element(QXmlStreamReader &stream) { - return stream.isStartElement() - && (stream.name() == "folder" - || stream.name() == "footage" - || stream.name() == "sequence" - || stream.name() == "clip" - || stream.name() == "effect"); + return stream.isStartElement() + && (stream.name() == "folder" + || stream.name() == "footage" + || stream.name() == "sequence" + || stream.name() == "clip" + || stream.name() == "effect"); } bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) { - f.seek(0); - stream.setDevice(stream.device()); + f.seek(0); + stream.setDevice(stream.device()); - QString root_search; - QString child_search; + 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; - } + 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; + show_err = true; - while (!stream.atEnd() && !cancelled) { - read_next_start_element(stream); - if (stream.name() == root_search) { - if (type == LOAD_TYPE_VERSION) { - int proj_version = stream.readElementText().toInt(); - if (proj_version < MIN_SAVE_VERSION || proj_version > SAVE_VERSION) { - emit start_question( - tr("Version Mismatch"), - tr("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 - ); - waitCond.wait(&mutex); - if (question_btn == QMessageBox::No) { - show_err = false; - return false; - } - } - } else if (type == LOAD_TYPE_URL) { - internal_proj_url = stream.readElementText(); - internal_proj_dir = QFileInfo(internal_proj_url).absoluteDir(); - } else { - while (!cancelled && !stream.atEnd() && !(stream.name() == root_search && stream.isEndElement())) { - read_next(stream); - if (stream.name() == child_search && stream.isStartElement()) { - switch (type) { - case MEDIA_TYPE_FOLDER: - { - Media* folder = panel_project->create_folder_internal(nullptr); - 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; + int proj_version = SAVE_VERSION; - Media* item = new Media(0); - FootagePtr f(new Footage()); + while (!stream.atEnd() && !cancelled) { + read_next_start_element(stream); + if (stream.name() == root_search) { + if (type == LOAD_TYPE_VERSION) { + proj_version = stream.readElementText().toInt(); + if (proj_version < MIN_SAVE_VERSION || proj_version > SAVE_VERSION) { + emit start_question( + tr("Version Mismatch"), + tr("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 + ); + waitCond.wait(&mutex); + if (question_btn == QMessageBox::No) { + show_err = false; + return false; + } + } + } else if (type == LOAD_TYPE_URL) { + internal_proj_url = stream.readElementText(); + internal_proj_dir = QFileInfo(internal_proj_url).absoluteDir(); + } else { + while (!cancelled && !stream.atEnd() && !(stream.name() == root_search && stream.isEndElement())) { + read_next(stream); + if (stream.name() == child_search && stream.isStartElement()) { + switch (type) { + case MEDIA_TYPE_FOLDER: + { + Media* folder = panel_project->create_folder_internal(nullptr); + 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; - f->using_inout = false; + Media* item = new Media(nullptr); + FootagePtr f(new Footage()); - for (int j=0;jsave_id = attr.value().toInt(); - } else if (attr.name() == "folder") { - folder = attr.value().toInt(); - } else if (attr.name() == "name") { - f->name = attr.value().toString(); - } else if (attr.name() == "url") { - f->url = attr.value().toString(); + f->using_inout = false; - if (!QFileInfo::exists(f->url)) { // if path is not absolute - // tries to locate file using a file path relative to the project's current folder - QString proj_dir_test = proj_dir.absoluteFilePath(f->url); + for (int j=0;jsave_id = attr.value().toInt(); + } else if (attr.name() == "folder") { + folder = attr.value().toInt(); + } else if (attr.name() == "name") { + f->name = attr.value().toString(); + } else if (attr.name() == "url") { + f->url = attr.value().toString(); - // tries to locate file using a file path relative to the folder the project was saved in - // (unaffected by moving the project file) - QString internal_proj_dir_test = internal_proj_dir.absoluteFilePath(f->url); + if (!QFileInfo::exists(f->url)) { // if path is not absolute + // tries to locate file using a file path relative to the project's current folder + QString proj_dir_test = proj_dir.absoluteFilePath(f->url); - // tries to locate file using the file name directly in the project's current folder - QString proj_dir_direct_test = proj_dir.filePath(QFileInfo(f->url).fileName()); + // tries to locate file using a file path relative to the folder the project was saved in + // (unaffected by moving the project file) + QString internal_proj_dir_test = internal_proj_dir.absoluteFilePath(f->url); - if (QFileInfo::exists(proj_dir_test)) { + // tries to locate file using the file name directly in the project's current folder + QString proj_dir_direct_test = proj_dir.filePath(QFileInfo(f->url).fileName()); - f->url = proj_dir_test; - qInfo() << "Matched" << attr.value().toString() << "relative to project's current directory"; + if (QFileInfo::exists(proj_dir_test)) { - } else if (QFileInfo::exists(internal_proj_dir_test)) { + f->url = proj_dir_test; + qInfo() << "Matched" << attr.value().toString() << "relative to project's current directory"; - f->url = internal_proj_dir_test; - qInfo() << "Matched" << attr.value().toString() << "relative to project's internal directory"; + } else if (QFileInfo::exists(internal_proj_dir_test)) { - } else if (QFileInfo::exists(proj_dir_direct_test)) { + f->url = internal_proj_dir_test; + qInfo() << "Matched" << attr.value().toString() << "relative to project's internal directory"; - f->url = proj_dir_direct_test; - qInfo() << "Matched" << attr.value().toString() << "directly to project's current directory"; + } else if (QFileInfo::exists(proj_dir_direct_test)) { - } else if (f->url.contains('%')) { + f->url = proj_dir_direct_test; + qInfo() << "Matched" << attr.value().toString() << "directly to project's current directory"; - // hack for image sequences (qt won't be able to find the URL with %, but ffmpeg may) - f->url = internal_proj_dir_test; - qInfo() << "Guess image sequence" << attr.value().toString() << "path to project's internal directory"; + } else if (f->url.contains('%')) { - } else { + // hack for image sequences (qt won't be able to find the URL with %, but ffmpeg may) + f->url = internal_proj_dir_test; + qInfo() << "Guess image sequence" << attr.value().toString() << "path to project's internal directory"; - qInfo() << "Failed to match" << attr.value().toString() << "to file"; + } else { - } - } else { - f->url = QFileInfo(f->url).absoluteFilePath(); - qInfo() << "Matched" << attr.value().toString() << "with absolute path"; - } - } else if (attr.name() == "duration") { - f->length = attr.value().toLongLong(); - } else if (attr.name() == "using_inout") { - f->using_inout = (attr.value() == "1"); - } else if (attr.name() == "in") { - f->in = attr.value().toLong(); - } else if (attr.name() == "out") { - f->out = attr.value().toLong(); - } else if (attr.name() == "speed") { - f->speed = attr.value().toDouble(); - } else if (attr.name() == "alphapremul") { - f->alpha_is_premultiplied = (attr.value() == "1"); - } else if (attr.name() == "proxy") { - f->proxy = (attr.value() == "1"); - } else if (attr.name() == "proxypath") { - f->proxy_path = attr.value().toString(); - } - } + qInfo() << "Failed to match" << attr.value().toString() << "to file"; - while (!cancelled && !(stream.name() == child_search && stream.isEndElement()) && !stream.atEnd()) { - read_next_start_element(stream); - if (stream.name() == "marker" && stream.isStartElement()) { - Marker m; - for (int j=0;jmarkers.append(m); - } - } + } + } else { + f->url = QFileInfo(f->url).absoluteFilePath(); + qInfo() << "Matched" << attr.value().toString() << "with absolute path"; + } + } else if (attr.name() == "duration") { + f->length = attr.value().toLongLong(); + } else if (attr.name() == "using_inout") { + f->using_inout = (attr.value() == "1"); + } else if (attr.name() == "in") { + f->in = attr.value().toLong(); + } else if (attr.name() == "out") { + f->out = attr.value().toLong(); + } else if (attr.name() == "speed") { + f->speed = attr.value().toDouble(); + } else if (attr.name() == "alphapremul") { + f->alpha_is_premultiplied = (attr.value() == "1"); + } else if (attr.name() == "proxy") { + f->proxy = (attr.value() == "1"); + } else if (attr.name() == "proxypath") { + f->proxy_path = attr.value().toString(); + } + } - item->set_footage(f); + while (!cancelled && !(stream.name() == child_search && stream.isEndElement()) && !stream.atEnd()) { + read_next_start_element(stream); + if (stream.name() == "marker" && stream.isStartElement()) { + Marker m; + for (int j=0;jmarkers.append(m); + } + } - if (folder == 0) { - olive::project_model.appendChild(nullptr, item); - } else { - find_loaded_folder_by_id(folder)->appendChild(item); - } + item->set_footage(f); - // analyze media to see if it's the same - loaded_media_items.append(item); - } - break; - case MEDIA_TYPE_SEQUENCE: - { - Media* parent = nullptr; - SequencePtr s(new Sequence()); + if (folder == 0) { + olive::project_model.appendChild(nullptr, item); + } else { + find_loaded_folder_by_id(folder)->appendChild(item); + } - // 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(); - } - } + // analyze media to see if it's the same + loaded_media_items.append(item); + } + break; + case MEDIA_TYPE_SEQUENCE: + { + Media* parent = nullptr; + SequencePtr s(new Sequence()); - QVector transition_data; + // 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(); + } + } - // load all clips and clip information - while (!cancelled && !(stream.name() == child_search && stream.isEndElement()) && !stream.atEnd()) { - read_next_start_element(stream); - if (stream.name() == "marker" && stream.isStartElement()) { - Marker m; - for (int j=0;jmarkers.append(m); - } else if (stream.name() == "transition" && stream.isStartElement()) { - TransitionData td; - td.otc = nullptr; - td.ctc = nullptr; - for (int j=0;jmarkers.append(m); + } else if (stream.name() == "clip" && stream.isStartElement()) { + int media_type = -1; + int media_id, stream_id; + ClipPtr c(new Clip(s)); - // backwards compatibility code - c->autoscale = false; - - c->media = nullptr; - - 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") { + 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 = quint8(attr.value().toInt()); - } else if (attr.name() == "g") { + } else if (attr.name() == "g") { c->color_g = quint8(attr.value().toInt()); - } else if (attr.name() == "b") { + } else if (attr.name() == "b") { c->color_b = quint8(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; + } 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() == "sequence") { + media_type = MEDIA_TYPE_SEQUENCE; - // since we haven't finished loading sequences, we defer linking this until later - c->media = nullptr; - c->media_stream = attr.value().toInt(); - loaded_clips.append(c); - } - } + // since we haven't finished loading sequences, we defer linking this until later + c->media = nullptr; + 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; - } + // set media and media stream + switch (media_type) { + case MEDIA_TYPE_FOOTAGE: + if (media_id >= 0) { + for (int j=0;jto_footage(); + if (m->save_id == media_id) { + c->media = loaded_media_items.at(j); + c->media_stream = stream_id; + break; + } + } + } + break; + } - // load links and effects - while (!cancelled && !(stream.name() == "clip" && stream.isEndElement()) && !stream.atEnd()) { - read_next(stream); - if (stream.isStartElement()) { - if (stream.name() == "linked") { - while (!cancelled && !(stream.name() == "linked" && stream.isEndElement()) && !stream.atEnd()) { - read_next(stream); - if (stream.name() == "link" && stream.isStartElement()) { - for (int k=0;klinked.append(link_attr.value().toInt()); - break; - } - } - } - } - if (cancelled) return false; - } else if (stream.isStartElement() - && (stream.name() == "effect" - || stream.name() == "opening" - || stream.name() == "closing")) { - // "opening" and "closing" are backwards compatibility code - load_effect(stream, c); - } else if (stream.name() == "marker" && stream.isStartElement()) { - Marker m; - for (int j=0;jget_markers().append(m); - } - } - } - if (cancelled) return false; + // load links and effects + while (!cancelled && !(stream.name() == "clip" && stream.isEndElement()) && !stream.atEnd()) { + read_next(stream); + if (stream.isStartElement()) { + if (stream.name() == "linked") { + while (!cancelled && !(stream.name() == "linked" && stream.isEndElement()) && !stream.atEnd()) { + read_next(stream); + if (stream.name() == "link" && stream.isStartElement()) { + for (int k=0;klinked.append(link_attr.value().toInt()); + break; + } + } + } + } + if (cancelled) return false; + } else if (stream.isStartElement() + && (stream.name() == "effect" + || stream.name() == "opening" + || stream.name() == "closing")) { + load_effect(stream, c); + } else if (stream.name() == "marker" && stream.isStartElement()) { + Marker m; + for (int j=0;jget_markers().append(m); + } + } + } + if (cancelled) return false; - s->clips.append(c); - } - } - if (cancelled) return false; + s->clips.append(c); + } + } + if (cancelled) return false; - // correct links, clip IDs, transitions - for (int i=0;iclips.size();i++) { - // correct links - ClipPtr 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--; + // correct links, clip IDs, transitions + for (int i=0;iclips.size();i++) { + // correct links + ClipPtr 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--; - emit start_question( - tr("Invalid Clip Link"), - tr("This project contains an invalid clip link. It may be corrupt. Would you like to continue loading it?"), - QMessageBox::Yes | QMessageBox::No - ); - waitCond.wait(&mutex); - if (question_btn == QMessageBox::No) { - s.reset(); - return false; - } - } - } + emit start_question( + tr("Invalid Clip Link"), + tr("This project contains an invalid clip link. It may be corrupt. Would you like to continue loading it?"), + QMessageBox::Yes | QMessageBox::No + ); + waitCond.wait(&mutex); + if (question_btn == QMessageBox::No) { + s.reset(); + 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; - } - } - } - } + Media* m = panel_project->create_sequence_internal(nullptr, s, false, parent); - // create transitions - for (int i=0;iopening_transition = -1; - if (td.ctc != nullptr) td.ctc->closing_transition = -1; - } else { - emit start_create_dual_transition(&td, primary, secondary, meta); - - waitCond.wait(&mutex); - } - } - */ - } - - Media* m = panel_project->create_sequence_internal(nullptr, s, false, parent); - - loaded_sequences.append(m); - } - break; - } - } - } - if (cancelled) return false; - } - break; - } - } - return !cancelled; + loaded_sequences.append(m); + } + break; + } + } + } + if (cancelled) return false; + } + break; + } + } + return !cancelled; } Media* LoadThread::find_loaded_folder_by_id(int id) { - if (id == 0) return nullptr; - for (int j=0;jtemp_id == id) { - return parent_item; - } - } - return nullptr; + if (id == 0) return nullptr; + for (int j=0;jtemp_id == id) { + return parent_item; + } + } + return nullptr; } void LoadThread::run() { - mutex.lock(); + mutex.lock(); - QFile file(olive::ActiveProjectFilename); - if (!file.open(QIODevice::ReadOnly)) { - qCritical() << "Could not open file"; - return; - } + QFile file(olive::ActiveProjectFilename); + if (!file.open(QIODevice::ReadOnly)) { + qCritical() << "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(olive::ActiveProjectFilename).absoluteDir(); - internal_proj_dir = QFileInfo(olive::ActiveProjectFilename).absoluteDir(); - internal_proj_url = olive::ActiveProjectFilename; + /* 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(olive::ActiveProjectFilename).absoluteDir(); + internal_proj_dir = QFileInfo(olive::ActiveProjectFilename).absoluteDir(); + internal_proj_url = olive::ActiveProjectFilename; - QXmlStreamReader stream(&file); + QXmlStreamReader stream(&file); - bool cont = false; - error_str.clear(); - show_err = true; + bool cont = false; + error_str.clear(); + show_err = true; - // temp variables for loading (unnecessary?) - open_seq = nullptr; - loaded_folders.clear(); - loaded_media_items.clear(); - loaded_clips.clear(); - loaded_sequences.clear(); + // temp variables for loading (unnecessary?) + open_seq = nullptr; + loaded_folders.clear(); + loaded_media_items.clear(); + loaded_clips.clear(); + loaded_sequences.clear(); - // get "element" count - current_element_count = 0; - total_element_count = 0; - while (!cancelled && !stream.atEnd()) { - stream.readNextStartElement(); - if (is_element(stream)) { - total_element_count++; - } - } - cont = !cancelled; + // get "element" count + current_element_count = 0; + total_element_count = 0; + while (!cancelled && !stream.atEnd()) { + stream.readNextStartElement(); + if (is_element(stream)) { + total_element_count++; + } + } + cont = !cancelled; - // find project file version - if (cont) { - cont = load_worker(file, stream, LOAD_TYPE_VERSION); - } + // find project file version + if (cont) { + cont = load_worker(file, stream, LOAD_TYPE_VERSION); + } - // find project's internal URL - if (cont) { - cont = load_worker(file, stream, LOAD_TYPE_URL); - } + // find project's internal URL + if (cont) { + cont = load_worker(file, stream, LOAD_TYPE_URL); + } - // load folders first - if (cont) { - cont = load_worker(file, stream, MEDIA_TYPE_FOLDER); - } + // 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 (folder->temp_id2 == 0) { - olive::project_model.appendChild(nullptr, folder); - } else { - find_loaded_folder_by_id(parent)->appendChild(folder); - } - } + // load media + if (cont) { + // since folders loaded correctly, organize them appropriately + for (int i=0;itemp_id2; + if (folder->temp_id2 == 0) { + olive::project_model.appendChild(nullptr, folder); + } else { + find_loaded_folder_by_id(parent)->appendChild(folder); + } + } - cont = load_worker(file, stream, MEDIA_TYPE_FOOTAGE); - } + cont = load_worker(file, stream, MEDIA_TYPE_FOOTAGE); + } - // load sequences - if (cont) { - cont = load_worker(file, stream, MEDIA_TYPE_SEQUENCE); - } + // load sequences + if (cont) { + cont = load_worker(file, stream, MEDIA_TYPE_SEQUENCE); + } - if (!cancelled) { - if (!cont) { - xml_error = false; - if (show_err) emit error(); - } else if (stream.hasError()) { - error_str = tr("%1 - Line: %2 Col: %3").arg(stream.errorString(), QString::number(stream.lineNumber()), QString::number(stream.columnNumber())); - xml_error = true; - emit error(); - cont = false; - } else { - // attach nested sequence clips to their sequences - for (int i=0;imedia == nullptr && loaded_clips.at(i)->media_stream == loaded_sequences.at(j)->to_sequence()->save_id) { - loaded_clips.at(i)->media = loaded_sequences.at(j); - loaded_clips.at(i)->refresh(); - break; - } - } - } - } - } - - if (cont) { - emit success(); // run in main thread - - for (int i=0;istart_preview_generator(loaded_media_items.at(i), true); - } - } else { - if (error_str.isEmpty()) { - error_str = tr("User aborted loading"); + if (!cancelled) { + if (!cont) { + xml_error = false; + if (show_err) emit error(); + } else if (stream.hasError()) { + error_str = tr("%1 - Line: %2 Col: %3").arg(stream.errorString(), QString::number(stream.lineNumber()), QString::number(stream.columnNumber())); + xml_error = true; + emit error(); + cont = false; + } else { + // attach nested sequence clips to their sequences + for (int i=0;imedia == nullptr && 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; + } } + } + } + } - emit error(); - } + if (cont) { + emit success(); // run in main thread - file.close(); + for (int i=0;istart_preview_generator(loaded_media_items.at(i), true); + } + } else { + if (error_str.isEmpty()) { + error_str = tr("User aborted loading"); + } - mutex.unlock(); + emit error(); + } + + file.close(); + + mutex.unlock(); } void LoadThread::cancel() { - waitCond.wakeAll(); - cancelled = true; + waitCond.wakeAll(); + cancelled = true; } void LoadThread::question_func(const QString &title, const QString &text, int buttons) { - mutex.lock(); - question_btn = QMessageBox::warning( - olive::MainWindow, - title, - text, - static_cast(buttons)); - mutex.unlock(); - waitCond.wakeAll(); + mutex.lock(); + question_btn = QMessageBox::warning( + olive::MainWindow, + title, + text, + static_cast(buttons)); + mutex.unlock(); + waitCond.wakeAll(); } void LoadThread::error_func() { - if (xml_error) { - qCritical() << "Error parsing XML." << error_str; - QMessageBox::critical(olive::MainWindow, - tr("XML Parsing Error"), - tr("Couldn't load '%1'. %2").arg(olive::ActiveProjectFilename, error_str), - QMessageBox::Ok); - } else { - QMessageBox::critical(olive::MainWindow, - tr("Project Load Error"), - tr("Error loading project: %1").arg(error_str), - QMessageBox::Ok); - } + if (xml_error) { + qCritical() << "Error parsing XML." << error_str; + QMessageBox::critical(olive::MainWindow, + tr("XML Parsing Error"), + tr("Couldn't load '%1'. %2").arg(olive::ActiveProjectFilename, error_str), + QMessageBox::Ok); + } else { + QMessageBox::critical(olive::MainWindow, + tr("Project Load Error"), + tr("Error loading project: %1").arg(error_str), + QMessageBox::Ok); + } } void LoadThread::success_func() { - if (autorecovery) { - QString orig_filename = internal_proj_url; - int insert_index = internal_proj_url.lastIndexOf(".ove", -1, Qt::CaseInsensitive); - if (insert_index == -1) insert_index = internal_proj_url.length(); - int counter = 1; - while (QFileInfo::exists(orig_filename)) { - orig_filename = internal_proj_url; - QString recover_text = "recovered"; - if (counter > 1) { - recover_text += " " + QString::number(counter); - } - orig_filename.insert(insert_index, " (" + recover_text + ")"); - counter++; - } + 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++; + } - olive::Global->update_project_filename(orig_filename); - } else { - panel_project->add_recent_project(olive::ActiveProjectFilename); - } + olive::Global->update_project_filename(orig_filename); + } else { + panel_project->add_recent_project(olive::ActiveProjectFilename); + } - olive::MainWindow->setWindowModified(autorecovery); - if (open_seq != nullptr) set_sequence(open_seq); - update_ui(false); + olive::MainWindow->setWindowModified(autorecovery); + if (open_seq != nullptr) set_sequence(open_seq); + update_ui(false); } void LoadThread::create_effect_ui( - QXmlStreamReader* stream, - ClipPtr c, - int type, - const QString* effect_name, - const EffectMeta* meta, - long effect_length, - bool effect_enabled) + QXmlStreamReader* stream, + ClipPtr c, + int type, + const QString* effect_name, + const EffectMeta* meta, + long effect_length, + bool effect_enabled) { - /* This is extremely hacky - prepare yourself. - * - * When moving project loading to a separate thread, it was soon discovered - * that effects wouldn't load correctly anymore. They were actually still - * "functional", but there were no controls appearing in EffectControls. - * - * Turns out since Effect creates its UI in its constructor, the UI was - * created in this thread rather than the main GUI thread, which is a big - * no-no. Unfortunately the design of Effect does not separate UI and data, - * so having the UI set up was integral to creating annd loading the effect. - * - * Therefore, rather than rewrite the class (I just rewrote QTreeWidget to - * QTreeView with a custom model/item so I'm exhausted), for - * quick-n-dirty-ness, I made LoadThread offload the effect creation to the - * main thread (and since the effect loads data from the same XML stream, - * the LoadThread has to wait for the effect to finish before it can - * continue. - * - * Sorry. I'll fix it one day. - */ + /* This is extremely hacky - prepare yourself. + * + * When moving project loading to a separate thread, it was soon discovered + * that effects wouldn't load correctly anymore. They were actually still + * "functional", but there were no controls appearing in EffectControls. + * + * Turns out since Effect creates its UI in its constructor, the UI was + * created in this thread rather than the main GUI thread, which is a big + * no-no. Unfortunately the design of Effect does not separate UI and data, + * so having the UI set up was integral to creating annd loading the effect. + * + * Therefore, rather than rewrite the class (I just rewrote QTreeWidget to + * QTreeView with a custom model/item so I'm exhausted), for + * quick-n-dirty-ness, I made LoadThread offload the effect creation to the + * main thread (and since the effect loads data from the same XML stream, + * the LoadThread has to wait for the effect to finish before it can + * continue. + * + * Sorry. I'll fix it one day. + */ - // lock mutex - ensures the load thread is suspended while this happens - mutex.lock(); + // lock mutex - ensures the load thread is suspended while this happens + mutex.lock(); - if (cancelled) return; - if (type == kTransitionNone) { - if (meta == nullptr) { - // create void effect - EffectPtr ve(new VoidEffect(c, *effect_name)); - ve->set_enabled(effect_enabled); - ve->load(*stream); - c->effects.append(ve); - } else { - EffectPtr e(create_effect(c, meta)); - e->set_enabled(effect_enabled); - e->load(*stream); + if (cancelled) return; + if (type == kTransitionNone) { + if (meta == nullptr) { + // create void effect + EffectPtr ve(new VoidEffect(c, *effect_name)); + ve->set_enabled(effect_enabled); + ve->load(*stream); + c->effects.append(ve); + } else { + EffectPtr e(create_effect(c, meta)); + e->set_enabled(effect_enabled); + e->load(*stream); - c->effects.append(e); - } - } else { - /* - int transition_index = create_transition(c, nullptr, meta); - TransitionPtr t = c->sequence->transitions.at(transition_index); - if (effect_length > -1) t->set_length(effect_length); - t->set_enabled(effect_enabled); - t->load(*stream); + c->effects.append(e); + } + } else { + TransitionPtr t = create_transition(c, nullptr, meta); + if (effect_length > -1) t->set_length(effect_length); + t->set_enabled(effect_enabled); + t->load(*stream); - if (type == kTransitionOpening) { - c->opening_transition = transition_index; - } else { - c->closing_transition = transition_index; - } - */ - } + if (type == kTransitionOpening) { + c->opening_transition = t; + } else { + c->closing_transition = t; + } + } - mutex.unlock(); + mutex.unlock(); - waitCond.wakeAll(); -} - -void LoadThread::create_dual_transition(const TransitionData* td, ClipPtr primary, ClipPtr secondary, const EffectMeta* meta) { - // lock mutex - ensures the load thread is suspended while this happens - mutex.lock(); - - /* - int transition_index = create_transition(primary, secondary, meta); - primary->sequence->transitions.at(transition_index)->set_length(td->length); - if (td->otc != nullptr) td->otc->opening_transition = transition_index; - if (td->ctc != nullptr) td->ctc->closing_transition = transition_index; - */ - - mutex.unlock(); - - // resume load thread - waitCond.wakeAll(); + waitCond.wakeAll(); } diff --git a/io/loadthread.h b/io/loadthread.h index 73ea66951..a9d562137 100644 --- a/io/loadthread.h +++ b/io/loadthread.h @@ -30,69 +30,59 @@ #include "project/projectelements.h" -struct TransitionData { - int id; - QString name; - long length; - ClipPtr otc; - ClipPtr ctc; -}; - class LoadThread : public QThread { - Q_OBJECT + Q_OBJECT public: - LoadThread(bool a); - void run(); - void cancel(); + LoadThread(bool a); + void run(); + void cancel(); signals: - void start_question(const QString &title, const QString &text, int buttons); - void success(); - void error(); - void start_create_effect_ui(QXmlStreamReader* stream, ClipPtr c, int type, const QString *effect_name, const EffectMeta* meta, long effect_length, bool effect_enabled); - void start_create_dual_transition(const TransitionData* td, ClipPtr primary, ClipPtr secondary, const EffectMeta* meta); - void report_progress(int p); + void start_question(const QString &title, const QString &text, int buttons); + void success(); + void error(); + void start_create_effect_ui(QXmlStreamReader* stream, ClipPtr c, int type, const QString *effect_name, const EffectMeta* meta, long effect_length, bool effect_enabled); + void report_progress(int p); private slots: - void question_func(const QString &title, const QString &text, int buttons); - void error_func(); - void success_func(); - void create_effect_ui(QXmlStreamReader* stream, ClipPtr c, int type, const QString *effect_name, const EffectMeta* meta, long effect_length, bool effect_enabled); - void create_dual_transition(const TransitionData* td, ClipPtr primary, ClipPtr secondary, const EffectMeta* meta); + void question_func(const QString &title, const QString &text, int buttons); + void error_func(); + void success_func(); + void create_effect_ui(QXmlStreamReader* stream, ClipPtr c, int type, const QString *effect_name, const EffectMeta* meta, long effect_length, bool effect_enabled); private: - bool autorecovery; + bool autorecovery; - bool load_worker(QFile& f, QXmlStreamReader& stream, int type); - void load_effect(QXmlStreamReader& stream, ClipPtr c); + bool load_worker(QFile& f, QXmlStreamReader& stream, int type); + void load_effect(QXmlStreamReader& stream, ClipPtr c); - void read_next(QXmlStreamReader& stream); - void read_next_start_element(QXmlStreamReader& stream); - void update_current_element_count(QXmlStreamReader& stream); + void read_next(QXmlStreamReader& stream); + void read_next_start_element(QXmlStreamReader& stream); + void update_current_element_count(QXmlStreamReader& stream); - SequencePtr open_seq; - QVector loaded_media_items; - QDir proj_dir; - QDir internal_proj_dir; - QString internal_proj_url; - bool show_err; - QString error_str; + SequencePtr open_seq; + QVector loaded_media_items; + QDir proj_dir; + QDir internal_proj_dir; + QString internal_proj_url; + bool show_err; + QString error_str; - bool is_element(QXmlStreamReader& stream); + bool is_element(QXmlStreamReader& stream); - QVector loaded_folders; - QVector loaded_clips; - QVector loaded_sequences; - Media* find_loaded_folder_by_id(int id); + QVector loaded_folders; + QVector loaded_clips; + QVector loaded_sequences; + Media* find_loaded_folder_by_id(int id); - int current_element_count; - int total_element_count; + int current_element_count; + int total_element_count; - QMutex mutex; - QWaitCondition waitCond; + QMutex mutex; + QWaitCondition waitCond; - bool cancelled; - bool xml_error; + bool cancelled; + bool xml_error; - QMessageBox::StandardButton question_btn; + QMessageBox::StandardButton question_btn; }; #endif // LOADTHREAD_H diff --git a/panels/project.cpp b/panels/project.cpp index ba6311e8c..ee316fb7f 100644 --- a/panels/project.cpp +++ b/panels/project.cpp @@ -1086,21 +1086,8 @@ void Project::save_folder(QXmlStreamWriter& stream, int type, bool set_ids_only, stream.writeAttribute("workareaIn", QString::number(s->workarea_in)); stream.writeAttribute("workareaOut", QString::number(s->workarea_out)); - /* QVector transition_save_cache; QVector transition_clip_save_cache; - QVector transition_type_save_cache; - for (int j=0;jtransitions.size();j++) { - TransitionPtr t = s->transitions.at(j); - if (t != nullptr) { - stream.writeStartElement("transition"); - stream.writeAttribute("id", QString::number(j)); - stream.writeAttribute("length", QString::number(t->get_true_length())); - t->save(stream); - stream.writeEndElement(); // transition - } - } - */ for (int j=0;jclips.size();j++) { const ClipPtr& c = s->clips.at(j); @@ -1153,6 +1140,31 @@ void Project::save_folder(QXmlStreamWriter& stream, int type, bool set_ids_only, } stream.writeEndElement(); // linked + // save opening and closing transitions + for (int t=kTransitionOpening;t<=kTransitionClosing;t++) { + TransitionPtr transition = (t == kTransitionOpening) ? c->opening_transition : c->closing_transition; + + if (transition != nullptr) { + stream.writeStartElement((t == kTransitionOpening) ? "opening" : "closing"); + + // check if this is a shared transition and the transition has already been saved + int transition_cache_index = transition_save_cache.indexOf(transition); + + if (transition_cache_index > -1) { + // if so, just save a reference to the other clip + stream.writeAttribute("shared", + QString::number(transition_clip_save_cache.at(transition_cache_index))); + } else { + // otherwise save the whole transition + transition->save(stream); + transition_save_cache.append(transition); + transition_clip_save_cache.append(j); + } + + stream.writeEndElement(); // opening + } + } + for (int k=0;keffects.size();k++) { stream.writeStartElement("effect"); // effect c->effects.at(k)->save(stream); diff --git a/project/transition.cpp b/project/transition.cpp index d4ff4f6e1..3c4028c71 100644 --- a/project/transition.cpp +++ b/project/transition.cpp @@ -59,6 +59,11 @@ TransitionPtr Transition::copy(ClipPtr c, ClipPtr s) { return create_transition(c, s, meta, length); } +void Transition::save(QXmlStreamWriter &stream) { + stream.writeAttribute("length", QString::number(get_true_length())); + Effect::save(stream); +} + void Transition::set_length(long l) { length = l; length_field->set_double_value(l); diff --git a/project/transition.h b/project/transition.h index b8677b119..ec6352398 100644 --- a/project/transition.h +++ b/project/transition.h @@ -51,6 +51,9 @@ public: Transition(ClipPtr c, ClipPtr s, const EffectMeta* em); virtual TransitionPtr copy(ClipPtr c, ClipPtr s); ClipPtr secondary_clip; + + virtual void save(QXmlStreamWriter& stream) override; + void set_length(long l); long get_true_length(); long get_length();