diff --git a/dialogs/loaddialog.cpp b/dialogs/loaddialog.cpp index 923d5a8c3..310e668ca 100644 --- a/dialogs/loaddialog.cpp +++ b/dialogs/loaddialog.cpp @@ -31,48 +31,50 @@ #include "ui/sourcetable.h" #include "mainwindow.h" -LoadDialog::LoadDialog(QWidget *parent, bool autorecovery) : QDialog(parent) { - setWindowTitle(tr("Loading...")); - setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); +LoadDialog::LoadDialog(QWidget *parent, const QString& fn, bool autorecovery, bool clear) : + QDialog(parent) +{ + setWindowTitle(tr("Loading...")); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - QVBoxLayout* layout = new QVBoxLayout(this); + QVBoxLayout* layout = new QVBoxLayout(this); - layout->addWidget(new QLabel(tr("Loading '%1'...").arg(olive::ActiveProjectFilename.mid(olive::ActiveProjectFilename.lastIndexOf('/')+1)), this)); + layout->addWidget(new QLabel(tr("Loading '%1'...").arg(olive::ActiveProjectFilename.mid(olive::ActiveProjectFilename.lastIndexOf('/')+1)), this)); - bar = new QProgressBar(this); - bar->setValue(0); - layout->addWidget(bar); + bar = new QProgressBar(this); + bar->setValue(0); + layout->addWidget(bar); - cancel_button = new QPushButton(tr("Cancel"), this); - connect(cancel_button, SIGNAL(clicked(bool)), this, SLOT(cancel())); + cancel_button = new QPushButton(tr("Cancel"), this); + connect(cancel_button, SIGNAL(clicked(bool)), this, SLOT(cancel())); - hboxLayout = new QHBoxLayout(); - hboxLayout->addStretch(); - hboxLayout->addWidget(cancel_button); - hboxLayout->addStretch(); + hboxLayout = new QHBoxLayout(); + hboxLayout->addStretch(); + hboxLayout->addWidget(cancel_button); + hboxLayout->addStretch(); - layout->addLayout(hboxLayout); + layout->addLayout(hboxLayout); - update(); + update(); - lt = new LoadThread(autorecovery); - QObject::connect(lt, SIGNAL(success()), this, SLOT(thread_done())); - QObject::connect(lt, SIGNAL(error()), this, SLOT(die())); - QObject::connect(lt, SIGNAL(report_progress(int)), bar, SLOT(setValue(int))); - lt->start(); + lt = new LoadThread(fn, autorecovery, clear); + QObject::connect(lt, SIGNAL(success()), this, SLOT(thread_done())); + QObject::connect(lt, SIGNAL(error()), this, SLOT(die())); + QObject::connect(lt, SIGNAL(report_progress(int)), bar, SLOT(setValue(int))); + lt->start(); } void LoadDialog::cancel() { - lt->cancel(); - lt->wait(); - die(); + lt->cancel(); + lt->wait(); + die(); } void LoadDialog::die() { - olive::Global->new_project(); - reject(); + olive::Global->new_project(); + reject(); } void LoadDialog::thread_done() { - accept(); + accept(); } diff --git a/dialogs/loaddialog.h b/dialogs/loaddialog.h index e75bc289d..285a58448 100644 --- a/dialogs/loaddialog.h +++ b/dialogs/loaddialog.h @@ -30,18 +30,18 @@ class LoadDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - LoadDialog(QWidget* parent, bool autorecovery); + LoadDialog(QWidget* parent, const QString& filename, bool autorecovery, bool clear); private slots: - void cancel(); - void die(); - void thread_done(); + void cancel(); + void die(); + void thread_done(); private: - QProgressBar* bar; - QPushButton* cancel_button; - QHBoxLayout* hboxLayout; - LoadThread* lt; + QProgressBar* bar; + QPushButton* cancel_button; + QHBoxLayout* hboxLayout; + LoadThread* lt; }; #endif // LOADDIALOG_H diff --git a/io/loadthread.cpp b/io/loadthread.cpp index 8070eb990..8130fdd18 100644 --- a/io/loadthread.cpp +++ b/io/loadthread.cpp @@ -37,7 +37,12 @@ #include #include -LoadThread::LoadThread(bool a) : autorecovery(a), cancelled(false) { +LoadThread::LoadThread(const QString& filename, bool autorecovery, bool clear) : + filename_(filename), + autorecovery_(autorecovery), + clear_(clear), + 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())); @@ -45,7 +50,10 @@ LoadThread::LoadThread(bool a) : autorecovery(a), cancelled(false) { SIGNAL(start_create_effect_ui(QXmlStreamReader*, Clip*, int, const QString*, const EffectMeta*, long, bool)), this, SLOT(create_effect_ui(QXmlStreamReader*, Clip*, 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(start_question(const QString&, const QString &, int)), + this, + SLOT(question_func(const QString &, const QString &, int))); } void LoadThread::load_effect(QXmlStreamReader& stream, Clip* c) { @@ -174,7 +182,7 @@ bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) { int proj_version = olive::kSaveVersion; - while (!stream.atEnd() && !cancelled) { + while (!stream.atEnd() && !cancelled_) { read_next_start_element(stream); if (stream.name() == root_search) { if (type == LOAD_TYPE_VERSION) { @@ -195,7 +203,7 @@ bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) { internal_proj_url = stream.readElementText(); internal_proj_dir = QFileInfo(internal_proj_url).absoluteDir(); } else { - while (!cancelled && !stream.atEnd() && !(stream.name() == root_search && stream.isEndElement())) { + while (!cancelled_ && !stream.atEnd() && !(stream.name() == root_search && stream.isEndElement())) { read_next(stream); if (stream.name() == child_search && stream.isStartElement()) { switch (type) { @@ -298,7 +306,7 @@ bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) { } } - while (!cancelled && !(stream.name() == child_search && stream.isEndElement()) && !stream.atEnd()) { + while (!cancelled_ && !(stream.name() == child_search && stream.isEndElement()) && !stream.atEnd()) { read_next_start_element(stream); if (stream.name() == "marker" && stream.isStartElement()) { Marker m; @@ -363,7 +371,7 @@ bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) { } // load all clips and clip information - while (!cancelled && !(stream.name() == child_search && stream.isEndElement()) && !stream.atEnd()) { + while (!cancelled_ && !(stream.name() == child_search && stream.isEndElement()) && !stream.atEnd()) { read_next_start_element(stream); if (stream.name() == "marker" && stream.isStartElement()) { Marker m; @@ -448,11 +456,11 @@ bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) { } // load links and effects - while (!cancelled && !(stream.name() == "clip" && stream.isEndElement()) && !stream.atEnd()) { + 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()) { + while (!cancelled_ && !(stream.name() == "linked" && stream.isEndElement()) && !stream.atEnd()) { read_next(stream); if (stream.name() == "link" && stream.isStartElement()) { for (int k=0;kclips.append(c); } } - if (cancelled) return false; + if (cancelled_) return false; // correct links, clip IDs, transitions for (int i=0;iclips.size();i++) { @@ -530,12 +538,12 @@ bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) { } } } - if (cancelled) return false; + if (cancelled_) return false; } break; } } - return !cancelled; + return !cancelled_; } Media* LoadThread::find_loaded_folder_by_id(int id) { @@ -552,7 +560,7 @@ Media* LoadThread::find_loaded_folder_by_id(int id) { void LoadThread::run() { mutex.lock(); - QFile file(olive::ActiveProjectFilename); + QFile file(filename_); if (!file.open(QIODevice::ReadOnly)) { qCritical() << "Could not open file"; return; @@ -563,9 +571,9 @@ void LoadThread::run() { * 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; + proj_dir = QFileInfo(filename_).absoluteDir(); + internal_proj_dir = QFileInfo(filename_).absoluteDir(); + internal_proj_url = filename_; QXmlStreamReader stream(&file); @@ -583,13 +591,13 @@ void LoadThread::run() { // get "element" count current_element_count = 0; total_element_count = 0; - while (!cancelled && !stream.atEnd()) { + while (!cancelled_ && !stream.atEnd()) { stream.readNextStartElement(); if (is_element(stream)) { total_element_count++; } } - cont = !cancelled; + cont = !cancelled_; // find project file version if (cont) { @@ -627,7 +635,7 @@ void LoadThread::run() { cont = load_worker(file, stream, MEDIA_TYPE_SEQUENCE); } - if (!cancelled) { + if (!cancelled_) { if (!cont) { xml_error = false; if (show_err) emit error(); @@ -672,7 +680,7 @@ void LoadThread::run() { void LoadThread::cancel() { waitCond.wakeAll(); - cancelled = true; + cancelled_ = true; } void LoadThread::question_func(const QString &title, const QString &text, int buttons) { @@ -691,7 +699,7 @@ void LoadThread::error_func() { 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), + tr("Couldn't load '%1'. %2").arg(filename_, error_str), QMessageBox::Ok); } else { QMessageBox::critical(olive::MainWindow, @@ -702,7 +710,7 @@ void LoadThread::error_func() { } void LoadThread::success_func() { - if (autorecovery) { + 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(); @@ -717,12 +725,14 @@ void LoadThread::success_func() { counter++; } - olive::Global->update_project_filename(orig_filename); + if (clear_) { + olive::Global->update_project_filename(orig_filename); + } } else { - panel_project->add_recent_project(olive::ActiveProjectFilename); + panel_project->add_recent_project(filename_); } - olive::MainWindow->setWindowModified(autorecovery); + olive::MainWindow->setWindowModified(autorecovery_ || !clear_); if (open_seq != nullptr) { olive::Global->set_sequence(open_seq); } @@ -762,7 +772,7 @@ void LoadThread::create_effect_ui( // lock mutex - ensures the load thread is suspended while this happens mutex.lock(); - if (cancelled) return; + if (cancelled_) return; if (type == kTransitionNone) { if (meta == nullptr) { // create void effect diff --git a/io/loadthread.h b/io/loadthread.h index 28d9e00fb..14b92e7b2 100644 --- a/io/loadthread.h +++ b/io/loadthread.h @@ -34,7 +34,7 @@ class LoadThread : public QThread { Q_OBJECT public: - LoadThread(bool a); + LoadThread(const QString& filename, bool autorecovery, bool clear); void run(); void cancel(); signals: @@ -61,7 +61,9 @@ private slots: long effect_length, bool effect_enabled); private: - bool autorecovery; + bool autorecovery_; + bool clear_; + QString filename_; bool load_worker(QFile& f, QXmlStreamReader& stream, int type); void load_effect(QXmlStreamReader& stream, Clip* c); @@ -91,7 +93,7 @@ private: QMutex mutex; QWaitCondition waitCond; - bool cancelled; + bool cancelled_; bool xml_error; QMessageBox::StandardButton question_btn; diff --git a/oliveglobal.cpp b/oliveglobal.cpp index a62ab05cc..7317e20e6 100644 --- a/oliveglobal.cpp +++ b/oliveglobal.cpp @@ -293,7 +293,7 @@ void OliveGlobal::set_sequence(SequencePtr s) void OliveGlobal::open_project_worker(const QString& fn, bool autorecovery) { update_project_filename(fn); - panel_project->load_project(autorecovery); + panel_project->load_project(fn, autorecovery, true); olive::UndoStack.clear(); } diff --git a/panels/project.cpp b/panels/project.cpp index 1458ba4f0..891b2f31d 100644 --- a/panels/project.cpp +++ b/panels/project.cpp @@ -655,9 +655,12 @@ void Project::delete_selected_media() { void Project::process_file_list(QStringList& files, bool recursive, Media* replace, Media* parent) { bool imported = false; + // retrieve the array of image formats from the user's configuration + QStringList image_sequence_formats = olive::CurrentConfig.img_seq_formats.split("|"); + + // a cache of image sequence formatted URLS to assist the user in importing image sequences QVector image_sequence_urls; QVector image_sequence_importassequence; - QStringList image_sequence_formats = olive::CurrentConfig.img_seq_formats.split("|"); if (!recursive) last_imported_media.clear(); @@ -665,8 +668,12 @@ void Project::process_file_list(QStringList& files, bool recursive, Media* repla ComboAction* ca = nullptr; if (create_undo_action) ca = new ComboAction(); + // Loop through received files for (int i=0;i file.lastIndexOf('/')) { - // image_sequence_formats - found = false; - QString ext = file.mid(lastcharindex+1); - for (int j=0;j file.lastIndexOf('/')) { - if (is_img_sequence) { - // get the URL that we would pass to FFmpeg to force it to read the image as a sequence - QString new_filename = file.left(digit_test) + "%" + QString::number(digit_count) + "d" + file.mid(lastcharindex); + QString ext = file.mid(lastcharindex+1); - // add image sequence url to a vector in case the user imported several files that - // we're interpreting as a possible sequence - found = false; - for (int i=0;i -1) { + + // We've already processed an image with the same formatting + + // Check if the last time we saw this formatting, the user chose to import as a sequence + if (image_sequence_importassequence.at(does_url_cache_already_contain_this)) { + + // If so, no need to import this file too, so we signal to the rest of the function to skip this file + skip = true; + + } + + // If not, we can fall-through to the next step which is importing normally } else { - image_sequence_importassequence.append(false); + + // If we're here, we've never seen a file with this formatting before, so we'll ask whether to import + // as a sequence or not + + // Add this file formatting file to the URL cache + image_sequence_urls.append(new_filename); + + // This does look like an image sequence, let's ask the user if it'll indeed be an image sequence + if (QMessageBox::question(this, + tr("Image sequence detected"), + tr("The file '%1' appears to be part of an image sequence. " + "Would you like to import it as such?").arg(file), + QMessageBox::Yes | QMessageBox::No, + QMessageBox::Yes) == QMessageBox::Yes) { + + // Proceed to the next step of this with the formatted filename + file = new_filename; + + // Cache the user's answer alongside the image_sequence_urls value - in this case, YES, this will be an + // image sequence + image_sequence_importassequence.append(true); + + + // FFmpeg needs to know what file number to start at in the sequence. In case the image sequence doesn't + // start at a zero, we'll loop decreasing the number until it doesn't exist anymore + QString test_filename_format = QString("%1%2%3").arg(file.left(digit_test), "%1", file.mid(lastcharindex)); + int test_file_number = file_number; + do { + test_file_number--; + } while (QFileInfo::exists(test_filename_format.arg(QString("%1").arg(test_file_number, digit_count, 10, QChar('0'))))); + + // set the image sequence's start number to the last that existed + start_number = test_file_number + 1; + + } else { + + // Cache the user's response to the image sequence question - i.e. none of the files imported with this + // formatting should be imported as an image sequence + image_sequence_importassequence.append(false); + + } + } + + } + + } + + // If we're not skipping this file, let's import it + if (!skip) { + Media* item; + FootagePtr m; + + if (replace != nullptr) { + item = replace; + } else { + item = new Media(parent); + } + + m = FootagePtr(new Footage()); + + m->using_inout = false; + m->url = file; + m->name = get_file_name_from_path(files.at(i)); + m->start_number = start_number; + + item->set_footage(m); + + last_imported_media.append(item); + + if (replace == nullptr) { + if (create_undo_action) { + ca->append(new AddMediaCommand(item, parent)); + } else { + parent->appendChild(item); } } + + imported = true; } + } - if (!skip) { - Media* item; - FootagePtr m; - if (replace != nullptr) { - item = replace; - } else { - item = new Media(parent); - } - - m = FootagePtr(new Footage()); - - m->using_inout = false; - m->url = file; - m->name = get_file_name_from_path(files.at(i)); - m->start_number = start_number; - - item->set_footage(m); - - last_imported_media.append(item); - - if (replace == nullptr) { - if (create_undo_action) { - ca->append(new AddMediaCommand(item, parent)); - } else { - parent->appendChild(item); - } - } - - imported = true; - } } } if (create_undo_action) { @@ -966,10 +1034,16 @@ void Project::new_project() { olive::MainWindow->setWindowModified(false); } -void Project::load_project(bool autorecovery) { - new_project(); +void Project::load_project(const QString& filename, bool autorecovery, bool clear) { - LoadDialog ld(this, autorecovery); + // Normally, the user will be closing the previous project to load a new one, but just in case the user + // is importing a new project + + if (clear) { + new_project(); + } + + LoadDialog ld(this, filename, autorecovery, clear); ld.exec(); } diff --git a/panels/project.h b/panels/project.h index db9aeb46b..4b1414f76 100644 --- a/panels/project.h +++ b/panels/project.h @@ -67,7 +67,7 @@ public: void add_recent_project(QString url); void new_project(); - void load_project(bool autorecovery); + void load_project(const QString &filename, bool autorecovery, bool clear); void save_project(bool autorecovery); Media* create_folder_internal(QString name);