/* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "newmoviewizard.h" #include "smtreeitem.h" #include "smglobals.h" #include "mappingtablemodel.h" #include "delegates.h" #include "helper.h" #include "pictureviewer2.h" #include "archivemodel.h" #include "archivecontroller.h" #include "archiveview.h" NewMovieWizard::NewMovieWizard(QWidget *parent) : QWizard(parent){ mInfoPage = new MovieInfoPage; mActorPage = new MovieMappingPage("Actors"); mGenrePage = new MovieMappingPage("Genres"); mMetadataPage = new MovieMetadataPage; addPage(mInfoPage); addPage(mActorPage); addPage(mGenrePage); addPage(mMetadataPage); setOption(QWizard::IndependentPages, true); } void NewMovieWizard::reject(){ mInfoPage->saveData(); QWizard::reject(); } void NewMovieWizard::accept(){ QSqlDatabase db = QSqlDatabase::database("treedb"); db.open(); db.transaction(); QString seriesName = field("title").toString().toLower().trimmed(); QSqlQuery seriesIdQ(db); seriesIdQ.prepare("SELECT iseries_id FROM series WHERE tseries_name = :value"); seriesIdQ.bindValue(":value", seriesName); //see if we already have a series with this name int seriesId = 0; seriesIdQ.exec(); while(seriesIdQ.next()){ seriesId = seriesIdQ.value(0).toInt(); } //we don't have an id if(!seriesId){ QSqlQuery insertSeriesQ(db); insertSeriesQ.prepare("INSERT INTO series (tseries_name) VALUES(:name)"); insertSeriesQ.bindValue(":name", seriesName); if(insertSeriesQ.exec()){ seriesIdQ.bindValue(":value", seriesName); seriesIdQ.exec(); while(seriesIdQ.next()){ seriesId = seriesIdQ.value(0).toInt(); } }else{ QMessageBox::critical(this, tr("Error"), tr("Failed to create series!")); db.rollback(); return; } } //now we have a series id, handle seriespart int partNo = field("seriesNo").toInt(); QString subtitle = field("subtitle").toString(); QSqlQuery insertPartQ(db); insertPartQ.prepare("INSERT INTO seriesparts (iseries_id, iseriespart, tsubtitle) VALUES(:sid, :pid, :tsub)"); insertPartQ.bindValue(":sid", seriesId); insertPartQ.bindValue(":pid", partNo); insertPartQ.bindValue(":tsub", subtitle); int seriesPartId = 0; if(!insertPartQ.exec()){ QMessageBox::critical(this, tr("Error"), tr("Hmm, seems we already have that part of the series... Bailing out!")); db.rollback(); return; } QSqlQuery partNoQ(db); partNoQ.prepare("SELECT iseriesparts_id FROM seriesparts WHERE iseries_id = :sid AND iseriespart = :pid AND tsubtitle = :tsub"); partNoQ.bindValue(":sid", seriesId); partNoQ.bindValue(":pid", partNo); partNoQ.bindValue(":tsub", subtitle); if(!partNoQ.exec()){ db.rollback(); return; } while(partNoQ.next()){ seriesPartId = partNoQ.value(0).toInt(); } //since we're in a transaction, we have to have a valid seriespart //handle files MovieInfoPage *movieInfoPage = qobject_cast(page(0)); WizardTreeModel *wizardModel = movieInfoPage->model(); QSqlQuery insertFilesQ(db); insertFilesQ.prepare("INSERT INTO files (iseriespart_id, tfilename, cmd5sum, bisize, sifiletype, sifileno, siquality, cpicsize, iduration) VALUES(:ipid, :tfn, :md5, :size, :ft, :fno, :qual, :psize, :dur)"); QSqlQuery insertOriginQ(db); insertOriginQ.prepare("INSERT INTO files_origin (ifiles_id, tname, cmd5sum, bisize, ibitrate) VALUES(:oid, :oname, :omd5, :osize, :obitrate)"); QHash md5Sums; for(int i = 0; i < wizardModel->rowCount(QModelIndex()); ++i){ QModelIndex curIdx = wizardModel->index(i, 0, QModelIndex()); QList fData = wizardModel->fileData(curIdx); QString fullPath = fData.value(WizardTreeModel::FullPath).toString(); QFileInfo fi(fullPath); qint64 size = fi.size(); QString md5 = Helper::md5Sum(fullPath); md5Sums.insert(fullPath, md5); qint64 secs = 0; QString picSize; int type = fData.value(WizardTreeModel::FileType).toInt(); QVariant quality; QString oName, oMD5; qint64 oSize, oldBitrate; bool hasOrigin = false; if(type == FT_MOVIE){ QVariantMap m = Helper::ffmpegData(fullPath); secs = m.value("duration").toDouble(); quality = field("quality").toInt(); //check for origin QModelIndex oIdx = curIdx.child(0, 0); if(oIdx.isValid()){ QList oData = wizardModel->fileData(oIdx); QString oFullPath = oData.value(WizardTreeModel::FullPath).toString(); QFileInfo oFi(oFullPath); oName = oFi.fileName(); oSize = oFi.size(); oMD5 = Helper::md5Sum(oFullPath); QVariantMap oldFfmpeg = Helper::ffmpegData(oFullPath); oldBitrate = oldFfmpeg.value("bit_rate").toLongLong(); oldBitrate /= 1000; hasOrigin = true; } }else{ QPixmap pix(fullPath); picSize = QString("%1x%2").arg(QString::number(pix.width())).arg(QString::number(pix.height())); } insertFilesQ.bindValue(":ipid", seriesPartId); insertFilesQ.bindValue(":tfn", fi.fileName()); insertFilesQ.bindValue(":md5", md5); insertFilesQ.bindValue(":size", size); insertFilesQ.bindValue(":ft", type); insertFilesQ.bindValue(":fno", fData.value(WizardTreeModel::FilePart)); insertFilesQ.bindValue(":qual", quality); insertFilesQ.bindValue(":psize", picSize); insertFilesQ.bindValue(":dur", secs); if(insertFilesQ.exec()){ if(hasOrigin){ int curFileId = -1; QSqlQuery curval("SELECT currval('files_ifiles_id__seq')", db); while(curval.next()){ curFileId= curval.value(0).toInt(); } insertOriginQ.bindValue(":oid", curFileId); insertOriginQ.bindValue(":oname", oName); insertOriginQ.bindValue(":omd5", oMD5); insertOriginQ.bindValue(":osize", oSize); insertOriginQ.bindValue(":obitrate", oldBitrate); if(!insertOriginQ.exec()){ QMessageBox::critical(this, tr("Error"), tr("Failed to insert old!")); db.rollback(); return; } } }else{ QMessageBox::critical(this, tr("Error"), tr("Craptastic... Something went wrong archiving files.")); db.rollback(); return; } } //files have landed //handle actors MovieMappingPage *actorPage = qobject_cast(page(1)); QStringList actors = actorPage->widget()->items(); QSqlQuery actorIdQ(db); actorIdQ.prepare("SELECT iactors_id FROM actors WHERE tactorname = :name"); QSqlQuery insertActorQ(db); insertActorQ.prepare("INSERT INTO actors (tactorname) VALUES(:name)"); QSqlQuery actorMapQ(db); actorMapQ.prepare("INSERT INTO seriesparts_actormap (iseriesparts_id, iactors_id) VALUES(:pid, :aid)"); foreach(QString a, actors){ a = a.toLower().trimmed(); int actorId = 0; actorIdQ.bindValue(":name", a); actorIdQ.exec(); while(actorIdQ.next()){ actorId = actorIdQ.value(0).toInt(); } if(!actorId){ insertActorQ.bindValue(":name", a); insertActorQ.exec(); actorIdQ.bindValue(":name", a); actorIdQ.exec(); while(actorIdQ.next()){ actorId = actorIdQ.value(0).toInt(); } } actorMapQ.bindValue(":pid", seriesPartId); actorMapQ.bindValue(":aid", actorId); if(!actorMapQ.exec()){ QMessageBox::critical(this, tr("Error"), tr("Failed to handle actors.")); db.rollback(); return; } } //actors are in place! //handle genres MovieMappingPage *genrePage = qobject_cast(page(2)); QStringList genres = genrePage->widget()->items(); QSqlQuery genreIdQ(db); genreIdQ.prepare("SELECT igenres_id FROM genres WHERE tgenrename = :name"); QSqlQuery insertGenreQ(db); insertGenreQ.prepare("INSERT INTO genres (tgenrename) VALUES(:name)"); QSqlQuery genreMapQ(db); genreMapQ.prepare("INSERT INTO seriesparts_genremap (iseriesparts_id, igenres_id) VALUES(:pid, :gid)"); foreach(QString g, genres){ g = g.toLower().trimmed(); int genreId = 0; genreIdQ.bindValue(":name", g); genreIdQ.exec(); while(genreIdQ.next()){ genreId = genreIdQ.value(0).toInt(); } if(!genreId){ insertGenreQ.bindValue(":name", g); insertGenreQ.exec(); genreIdQ.bindValue(":name", g); genreIdQ.exec(); while(genreIdQ.next()){ genreId = genreIdQ.value(0).toInt(); } } genreMapQ.bindValue(":pid", seriesPartId); genreMapQ.bindValue(":gid", genreId); if(!genreMapQ.exec()){ QMessageBox::critical(this, tr("Error"), tr("Failed to handle genres.")); db.rollback(); return; } } //genres done! //now handle metadata QSqlQuery insertMetadataQ(db); insertMetadataQ.prepare("INSERT INTO metadata (iseriespart_id, sireleaseyear, tsourcemedium, tsubject, treleasegroup, tencoderopts, tcomment, sipasses) VALUES(:pid, :rely, :source, :sub, :group, :encopts, :comment, :passes)"); MovieMetadataPage *metadataPage = qobject_cast(page(3)); QList metadata = metadataPage->widget()->metadata(); insertMetadataQ.bindValue(":pid", seriesPartId); insertMetadataQ.bindValue(":rely", metadata.at(ArchiveModel::ReleaseYear)); insertMetadataQ.bindValue(":source", metadata.at(ArchiveModel::Source)); insertMetadataQ.bindValue(":sub", metadata.at(ArchiveModel::Subject)); insertMetadataQ.bindValue(":group", metadata.at(ArchiveModel::ReleaseGroup)); insertMetadataQ.bindValue(":encopts", metadata.at(ArchiveModel::EncoderOpts)); insertMetadataQ.bindValue(":comment", metadata.at(ArchiveModel::Comment)); insertMetadataQ.bindValue(":passes", metadata.at(ArchiveModel::Passes)); if(!insertMetadataQ.exec()){ QMessageBox::critical(this, tr("Error"), tr("Failed to insert metadata.")); db.rollback(); return; } //we're still here, good //now actually move the files for(int i = 0; i < wizardModel->rowCount(QModelIndex()); ++i){ QModelIndex curIdx = wizardModel->index(i, 0, QModelIndex()); QList fData = wizardModel->fileData(curIdx); QString fullPath = fData.value(WizardTreeModel::FullPath).toString(); QString md5 = md5Sums.value(fullPath); Helper::moveToArchive(fullPath, md5); } db.commit(); mInfoPage->saveData(); QWizard::accept(); } MovieInfoPage::MovieInfoPage(QWidget *parent) : QWizardPage(parent), mCurSeriesno(0), mCurQuality(8){ setupGui(); } void MovieInfoPage::setupGui(){ setTitle(tr("Collect files for movie")); setSubTitle(tr("Select files by clicking the \"Add files...\" button. After adding files select one by one and set the appropriate file type. The series no is the number the movie has in the series: 14 in case of e.g. rogue adventures 14. The part number only has to be set if the movie is split in several parts.")); setPixmap(QWizard::LogoPixmap, QPixmap(":/shemov.png")); //files model setup QStringList modelHeaders = QStringList() << tr("File name") << tr("Size") << tr("File Type") << tr("No.") << tr("Full path"); mFileModel = new WizardTreeModel(modelHeaders, this); //files view mFileView = new SmTreeView; mProxy = new QSortFilterProxyModel(this); mProxy->setSourceModel(mFileModel); mFileView->setModel(mProxy); mFileView->setItemDelegateForColumn(WizardTreeModel::FileType, new FileTypeDelegate(mFileView)); mFileView->setItemDelegateForColumn(WizardTreeModel::FileSize, new SizeDelegate(mFileView)); mFileView->setItemDelegateForColumn(WizardTreeModel::FilePart, new FileNoDelegate(mFileView)); mFileView->setSortingEnabled(true); mFileView->header()->moveSection(1, 3); mFileView->setSelectionMode(QAbstractItemView::ExtendedSelection); //add + remove files QHBoxLayout *fileButtonLayout = new QHBoxLayout; fileButtonLayout->addStretch(); mExtractTitle = new QPushButton(tr("Title")); fileButtonLayout->addWidget(mExtractTitle); connect(mExtractTitle, SIGNAL(clicked()), this, SLOT(extractTitle())); mAddOld = new QPushButton(tr("Add Old...")); fileButtonLayout->addWidget(mAddOld); connect(mAddOld, SIGNAL(clicked()), this, SLOT(addOld())); mAddFile = new QPushButton(tr("Add files...")); fileButtonLayout->addWidget(mAddFile); connect(mAddFile, SIGNAL(clicked()), this, SLOT(addFiles())); mRemoveFile = new QPushButton(tr("Remove from list")); fileButtonLayout->addWidget(mRemoveFile); connect(mRemoveFile, SIGNAL(clicked()), this, SLOT(removeFile())); //movie name + subtitle QFormLayout *movieTitleLayout = new QFormLayout; mTitle = new QLineEdit; mSubtitle = new QLineEdit; movieTitleLayout->addRow(tr("Movie &title"), mTitle); movieTitleLayout->addRow(tr("Movie &subtitle"), mSubtitle); mSeriesCompleter = new QCompleter(this); mSeriesCompleterModel = new QStringListModel(this); mSeriesCompleter->setModel(mSeriesCompleterModel); mTitle->setCompleter(mSeriesCompleter); //series, part + quality QHBoxLayout *numberLayout = new QHBoxLayout; numberLayout->addStretch(); QLabel *l3 = new QLabel(tr("&Series no.")); mSeriesNo = new QSpinBox; l3->setBuddy(mSeriesNo); mSeriesNo->setMinimum(0); numberLayout->addWidget(l3); numberLayout->addWidget(mSeriesNo); QLabel *l5 = new QLabel(tr("&Quality")); mQuality = new QSpinBox; l5->setBuddy(mQuality); mQuality->setMinimum(1); mQuality->setMaximum(10); numberLayout->addWidget(l5); numberLayout->addWidget(mQuality); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(mFileView); mainLayout->addLayout(fileButtonLayout); mainLayout->addLayout(movieTitleLayout); mainLayout->addLayout(numberLayout); setLayout(mainLayout); initCompleters(); //expose data registerField("title*", mTitle); registerField("subtitle", mSubtitle); registerField("seriesNo", mSeriesNo); registerField("quality", mQuality); } void MovieInfoPage::initializePage(){ initCompleters(); QSettings s; bool clearPage = s.value("ui/clearnewmoviewizard").toBool(); if(clearPage == false){ restoreData(); } mFileModel->clear(); } void MovieInfoPage::addFile(const QString &file){ QFileInfo fi(file); if(fi.exists()){ if(mFileModel->find(file, WizardTreeModel::FullPath).isValid()){ return; } QList itemData; itemData << fi.fileName() << fi.size() << QVariant() << QVariant() << fi.absoluteFilePath(); QString mimeType = Helper::mimeType(fi.absoluteFilePath()); if(mimeType.startsWith("video")){ itemData[WizardTreeModel::FileType] = WizardTreeModel::Movie; }else{ QString baseName = fi.completeBaseName(); QRegExp reFront = QRegExp("front"); reFront.setCaseSensitivity(Qt::CaseInsensitive); QRegExp reBack = QRegExp("back"); reBack.setCaseSensitivity(Qt::CaseInsensitive); if(baseName.endsWith('f') || (reFront.indexIn(baseName) != -1)){ itemData[WizardTreeModel::FileType] = WizardTreeModel::FrontCover; }else if(baseName.endsWith('b') || (reBack.indexIn(baseName) != -1)){ itemData[WizardTreeModel::FileType] = WizardTreeModel::BackCover; }else{ itemData[WizardTreeModel::FileType] = WizardTreeModel::GeneralCover; } } mFileModel->appendRow(itemData, mFileModel->rootIndex()); } mFileView->resizeColumnToContents(0); mFileView->resizeColumnToContents(1); mFileView->resizeColumnToContents(2); } void MovieInfoPage::selectFirst(){ QModelIndex fIdx = mFileModel->rootIndex().child(0, 0); if(fIdx.isValid()){ mFileView->selectionModel()->select(fIdx, QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect); } } void MovieInfoPage::saveData(){ mCurQuality = mQuality->value(); mCurSeriesno = mSeriesNo->value(); mCurTitle = mTitle->text(); mCurSubtitle = mSubtitle->text(); } void MovieInfoPage::restoreData(){ mQuality->setValue(mCurQuality); mSeriesNo->setValue(mCurSeriesno); mTitle->setText(mCurTitle); mSubtitle->setText(mCurSubtitle); } void MovieInfoPage::initCompleters(){ QSqlDatabase db = QSqlDatabase::database("treedb"); db.open(); QStringList series; QSqlQuery seriesQ("SELECT tseries_name FROM series", db); while(seriesQ.next()){ series << seriesQ.value(0).toString(); } mSeriesCompleterModel->setStringList(series); } void MovieInfoPage::extractTitle(){ QModelIndexList curIdxList = mFileView->selectionModel()->selectedRows(); if(!curIdxList.isEmpty()){ QString fp = curIdxList.at(0).data(WizardTreeModel::FullPathRole).toString(); QJsonDocument jDoc = Helper::streamData(fp); if(!jDoc.isNull()){ QJsonObject jObj1 = jDoc.object().value("format").toObject(); QJsonObject jObj2 = jObj1.value("tags").toObject(); QString title = jObj2.value("title").toString().toLower(); mSubtitle->setText(title); } } } void MovieInfoPage::addOld(){ QFileDialog *oldFileDlg = new QFileDialog(this, tr("Select source"), mCurrentDir); int retval = oldFileDlg->exec(); if(retval != QDialog::Accepted || oldFileDlg->selectedFiles().isEmpty()){ oldFileDlg->deleteLater(); return; } QStringList files = oldFileDlg->selectedFiles(); QString oldFile = files.first(); QFileInfo fi(oldFile); qint64 oldSize = fi.size(); QString fullPath = fi.absoluteFilePath(); QString fn = fi.fileName(); int filetype = FT_ORIGIN; //prepare item data QList itemData; itemData << fn << oldSize << filetype << QVariant() << fullPath; QModelIndexList curIdxList = mFileView->selectionModel()->selectedRows(); if(!curIdxList.isEmpty()){ QModelIndex realIdx = mProxy->mapToSource(curIdxList[0]); mFileModel->appendRow(itemData, realIdx); mFileView->expandAll(); } oldFileDlg->deleteLater(); } void MovieInfoPage::addFiles(){ QSettings s; QString startDir = s.value("paths/addfilespath", QDir::homePath()).toString(); QStringList files = QFileDialog::getOpenFileNames(this, tr("Select files"), startDir); if(files.isEmpty()){ return; } foreach(QString f, files){ addFile(f); } QFileInfo fi(files.at(0)); s.setValue("paths/addfilespath", fi.absolutePath()); } void MovieInfoPage::removeFile(){ QModelIndexList selected = mFileView->selectionModel()->selectedRows(); QList curSel; foreach(QModelIndex idx, selected){ curSel << QPersistentModelIndex(idx); } if(selected.isEmpty()){ return; } foreach(QPersistentModelIndex pi, curSel){ QModelIndex cur = mProxy->mapToSource(pi); if(cur.isValid()){ mFileModel->removeRow(cur.row(), cur.parent()); } } } MovieMappingPage::MovieMappingPage(const QString &table, QWidget *parent) : QWizardPage(parent), mTable(table){ QString title = QString(tr("Edit %1")).arg(table); QString subTitle = QString(tr("Edit %1 by adding them from the text field below")).arg(table); setTitle(title); setSubTitle(subTitle); mWidget = new MappingEditorWidget(table, true); QHBoxLayout *mainLayout = new QHBoxLayout; mainLayout->addWidget(mWidget); setLayout(mainLayout); } void MovieMappingPage::initializePage(){ ArchiveController *c = SmGlobals::instance()->archiveController(); if(mTable.toLower() == "actors"){ mWidget->fillCompleter(c->archiveTreeModel()->allActors()); mWidget->setDecorationItem(SmGlobals::instance()->iconFor("actor")); }else if(mTable.toLower() == "genres"){ mWidget->fillCompleter(c->archiveTreeModel()->allGenres()); mWidget->setDecorationItem(SmGlobals::instance()->iconFor("genre")); } QSettings s; bool clearPage = s.value("ui/clearnewmoviewizard").toBool(); if(clearPage){ mWidget->clear(); } } MovieMetadataPage::MovieMetadataPage(QWidget *parent) : QWizardPage(parent){ setTitle(tr("Edit movie metadata")); setSubTitle(tr("Set the movie metadata here, as far as known")); setupGui(); } void MovieMetadataPage::setupGui(){ mWidget = new MetadataEditorWidget; QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(mWidget); setLayout(mainLayout); } void MovieMetadataPage::initializePage(){ QSettings s; bool clearPage = s.value("ui/clearnewmoviewizard").toBool(); if(clearPage){ QList curMetadata; for(int i = 0; i < ArchiveModel::MetadataNumFields; ++i){ curMetadata << QVariant(); } curMetadata[ArchiveModel::ReleaseYear] = QDate::currentDate().year(); curMetadata[ArchiveModel::Source] = "torrent"; curMetadata[ArchiveModel::ReleaseGroup] = "unknown"; curMetadata[ArchiveModel::Added] = QDate::currentDate(); mWidget->setMetadata(curMetadata); } } WizardTreeModel::WizardTreeModel(QStringList &headers, QObject *parent) : SmTreeModel(headers, parent){ mFiletypeMap = SmGlobals::instance()->filetypeMap(); } Qt::ItemFlags WizardTreeModel::flags(const QModelIndex &index) const{ if(index.column() == FileType || index.column() == FilePart){ return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; } return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } QList WizardTreeModel::fileData(const QModelIndex &idx) const{ SmTreeItem *item = itemAt(idx); QList retval; for(int i = 0; i < item->columnCount(); ++i){ retval << item->data(i); } return retval; } void WizardTreeModel::clear(){ SmTreeItem *rootItem = new SmTreeItem(NumFields); setRoot(rootItem); } QVariant WizardTreeModel::data(const QModelIndex &index, int role) const{ SmTreeItem *item = static_cast(index.internalPointer()); if(role == Qt::TextAlignmentRole){ if(index.column() == FileSize){ return Qt::AlignRight; } return Qt::AlignLeft; } if(role == FileNameRole){ return item->data(FileName); } if(role == FileSizeRole){ return item->data(FileSize); } if(role == FileTypeRole){ return item->data(FileType); } if(role == FullPathRole){ return item->data(FullPath); } if(role == FilePartRole){ return item->data(FilePart); } return SmTreeModel::data(index, role); } bool WizardTreeModel::setData(const QModelIndex &index, const QVariant &value, int role){ if(role == Qt::EditRole){ SmTreeItem *item = itemAt(index); if(index.column() == FileType){ QVariant realVal = mFiletypeMap.key(value.toString()); item->setData(index.column(), realVal); }else{ item->setData(index.column(), value); } emit dataChanged(index, index); return true; } return false; }