/* 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 "newmoviewizard.h" #include "smtreeitem.h" #include "seriestreemodel.h" #include "smglobals.h" #include "mappingtablemodel.h" #include "seriesmetadatamodel.h" #include "filestreemodel.h" #include "helper.h" #include "pictureviewer2.h" #include "archivemodel.h" #include "framecache.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::showEvent(QShowEvent *){ Helper::centerWidget(this); } 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{ 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()){ 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(); int quality = field("quality").toInt(); 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)"); QHash md5Sums; for(int i = 0; i < wizardModel->rowCount(QModelIndex()); ++i){ QList fData = wizardModel->fileData(i); QString fullPath = fData.value(WizardTreeModel::FullPath).toString(); QFileInfo fi(fullPath); qint64 size = fi.size(); QString md5 = Helper::md5Sum(fullPath); md5Sums.insert(fullPath, md5); int secs = 0; QString picSize; int type = fData.value(WizardTreeModel::FileType).toInt(); if(type == ArchiveFilesModel::Movie){ QVariant v = Helper::duration(fullPath).value(0); Helper::Duration dur = v.value(); secs = dur.toSeconds(); }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()){ db.rollback(); return; } } //files have landed //handle actors MovieMappingPage *actorPage = static_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()){ db.rollback(); return; } } //actors are in place! //handle genres MovieMappingPage *genrePage = static_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()){ db.rollback(); return; } } //genres done! //now handle metadata bool isEnabled = field("enabled").toBool(); if(isEnabled){ QSqlQuery insertMetadataQ(db); insertMetadataQ.prepare("INSERT INTO metadata (iseriespart_id, sireleaseyear, tsourcemedium, tsubject, treleasegroup, tencoderopts, tcomment, sipasses, dadded) VALUES(:pid, :rely, :source, :sub, :group, :encopts, :comment, :passes, :added)"); insertMetadataQ.bindValue(":pid", seriesPartId); insertMetadataQ.bindValue(":rely", field("year")); insertMetadataQ.bindValue(":source", mMetadataPage->sourceMedium()); insertMetadataQ.bindValue(":sub", field("subject")); insertMetadataQ.bindValue(":group", mMetadataPage->releaseGroup()); insertMetadataQ.bindValue(":encopts", field("encoder")); insertMetadataQ.bindValue(":comment", mMetadataPage->comment()); insertMetadataQ.bindValue(":passes", field("passes")); insertMetadataQ.bindValue(":added", QDate::currentDate()); if(!insertMetadataQ.exec()){ db.rollback(); return; } } //we're still here, good //now actually move the files for(int i = 0; i < wizardModel->rowCount(QModelIndex()); ++i){ QList fData = wizardModel->fileData(i); QString fullPath = fData.value(WizardTreeModel::FullPath).toString(); QString md5 = md5Sums.value(fullPath); Helper::moveToArchive(fullPath, md5); } db.commit(); QWizard::accept(); } MovieInfoPage::MovieInfoPage(QWidget *parent) : QWizardPage(parent){ 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; QSortFilterProxyModel *p = new QSortFilterProxyModel(this); p->setSourceModel(mFileModel); mFileView->setModel(p); 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); //add + remove files QHBoxLayout *fileButtonLayout = new QHBoxLayout; fileButtonLayout->addStretch(); mAddFile = new QPushButton(tr("Add files...")); fileButtonLayout->addWidget(mAddFile); connect(mAddFile, SIGNAL(clicked()), this, SLOT(addFiles())); mRemoveFile = new QPushButton(tr("Remove file")); fileButtonLayout->addWidget(mRemoveFile); connect(mRemoveFile, SIGNAL(clicked()), this, SLOT(removeFile())); //movie name + subtitle QFormLayout *movieTitleLayout = new QFormLayout; mTitle = new QLineEdit; mSubtitle = new QLineEdit; mSubtitle->setEnabled(false); 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(1); 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(); mTitle->clear(); mSeriesNo->setValue(1); mQuality->setValue(8); 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::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::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(); if(selected.isEmpty()){ return; } mFileModel->removeRows(selected.at(0).row(), 1, selected.at(0).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); 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()); }else if(mTable.toLower() == "genres"){ mWidget->fillCompleter(c->archiveTreeModel()->allGenres()); } mWidget->clear(); } MovieMetadataPage::MovieMetadataPage(QWidget *parent) : QWizardPage(parent){ setTitle(tr("Edit movie metadata")); setSubTitle(tr("Set the movie metadata here, as far as known")); mModel = static_cast(SmGlobals::instance()->model("SeriesMetadata")); setupGui(); } void MovieMetadataPage::setupGui(){ QFormLayout *rowLayout = new QFormLayout; mReleaseYear = new QSpinBox; mReleaseYear->setMaximum(3000); mReleaseYear->setMinimum(1900); rowLayout->addRow(tr("Release year"), mReleaseYear); mReleaseGroup = new QComboBox; mPasses = new QSpinBox; rowLayout->addRow(tr("Encoding passes"), mPasses); mReleaseGroup->setEditable(true); rowLayout->addRow(tr("Release group"), mReleaseGroup); mSourceMedium = new QComboBox; mSourceMedium->setEditable(true); rowLayout->addRow(tr("Source medium"), mSourceMedium); mSubject = new QLineEdit; rowLayout->addRow(tr("Usenet subject"), mSubject); mEncoderOpts = new QLineEdit; rowLayout->addRow(tr("Encoder options"), mEncoderOpts); QVBoxLayout *commentLayout = new QVBoxLayout; QLabel *commentLabel = new QLabel(tr("Comment")); commentLayout->addWidget(commentLabel); mComment = new QPlainTextEdit; commentLayout->addWidget(mComment); QHBoxLayout *enabledLayout = new QHBoxLayout; enabledLayout->setAlignment(Qt::AlignRight); mMetadataEnabled = new QCheckBox(tr("Enable metadata")); enabledLayout->addWidget(mMetadataEnabled); connect(mMetadataEnabled, SIGNAL(stateChanged(int)), this, SLOT(toggleMetadata(int))); mWidgets << mReleaseYear << mReleaseGroup << mSourceMedium << mSubject << mEncoderOpts << mComment << mPasses; toggleMetadata(Qt::Unchecked); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addLayout(rowLayout); mainLayout->addLayout(commentLayout); mainLayout->addLayout(enabledLayout); setLayout(mainLayout); //expose data registerField("year", mReleaseYear); registerField("passes", mPasses); registerField("group", mReleaseGroup); registerField("source", mSourceMedium); registerField("subject", mSubject); registerField("encoder", mEncoderOpts); registerField("comment", mComment); registerField("enabled", mMetadataEnabled); } void MovieMetadataPage::initializePage(){ //release groups mReleaseGroup->clear(); QSqlDatabase db = QSqlDatabase::database("treedb"); db.open(); QSqlQuery releaseGroupsQ("SELECT DISTINCT(treleasegroup) FROM metadata ORDER BY treleasegroup ASC", db); QStringList relGroups; while(releaseGroupsQ.next()){ relGroups << releaseGroupsQ.value(0).toString(); } mReleaseGroup->addItems(relGroups); int relg = mReleaseGroup->findText("unknown"); if(relg != -1){ mReleaseGroup->setCurrentIndex(relg); } //source mediums mSourceMedium->clear(); QStringList sourceMediums; QSqlQuery sourceMediumsQ("SELECT DISTINCT(tsourcemedium) FROM metadata ORDER BY tsourcemedium ASC", db); while(sourceMediumsQ.next()){ sourceMediums << sourceMediumsQ.value(0).toString(); } mSourceMedium->addItems(sourceMediums); int uks = mSourceMedium->findText("unknown"); if(uks != -1){ mSourceMedium->setCurrentIndex(uks); } mReleaseYear->setValue(QDate::currentDate().year()); mPasses->setValue(0); mSubject->clear(); mEncoderOpts->clear(); mComment->clear(); } QString MovieMetadataPage::comment() const { return mComment->toPlainText(); } QString MovieMetadataPage::releaseGroup() const { return mReleaseGroup->currentText(); } QString MovieMetadataPage::sourceMedium() const { return mSourceMedium->currentText(); } void MovieMetadataPage::toggleMetadata(int state){ bool isEnabled = (state == Qt::Checked); foreach(QWidget *w, mWidgets){ w->setEnabled(isEnabled); } } 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(int column) const{ if(column >= columnCount(rootIndex())){ return QList(); } SmTreeItem *item = root()->child(column); 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; }