File indexing completed on 2024-05-12 05:10:07

0001 /***************************************************************************
0002     Copyright (C) 2006-2009 Robby Stephenson <robby@periapsis.org>
0003  ***************************************************************************/
0004 
0005 /***************************************************************************
0006  *                                                                         *
0007  *   This program is free software; you can redistribute it and/or         *
0008  *   modify it under the terms of the GNU General Public License as        *
0009  *   published by the Free Software Foundation; either version 2 of        *
0010  *   the License or (at your option) version 3 or any later version        *
0011  *   accepted by the membership of KDE e.V. (or its successor approved     *
0012  *   by the membership of KDE e.V.), which shall act as a proxy            *
0013  *   defined in Section 14 of version 3 of the license.                    *
0014  *                                                                         *
0015  *   This program is distributed in the hope that it will be useful,       *
0016  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0017  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0018  *   GNU General Public License for more details.                          *
0019  *                                                                         *
0020  *   You should have received a copy of the GNU General Public License     *
0021  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
0022  *                                                                         *
0023  ***************************************************************************/
0024 
0025 // The information about the AMC file format was taken from the source code for
0026 // GCfilms, (GPL) (c) 2005 Tian
0027 // Monotheka, (GPL) (c) 2004, 2005 Michael Dominic K.
0028 //                      2005 Aurelien Mino
0029 
0030 #include "amcimporter.h"
0031 #include "../fieldformat.h"
0032 #include "../collections/videocollection.h"
0033 #include "../images/imagefactory.h"
0034 #include "../tellico_debug.h"
0035 
0036 #include <QImage>
0037 #include <QByteArray>
0038 #include <QApplication>
0039 
0040 #include <limits>
0041 #include <algorithm>
0042 
0043 #define AMC_FILE_ID " AMC_X.Y Ant Movie Catalog 3.5.x   www.buypin.com    www.antp.be "
0044 
0045 namespace {
0046   static const quint32 AMC_MAX_STRING_SIZE = 128 * 1024;
0047 }
0048 
0049 using Tellico::Import::AMCImporter;
0050 
0051 AMCImporter::AMCImporter(const QUrl& url_) : DataImporter(url_), m_cancelled(false), m_failed(false), m_majVersion(0), m_minVersion(0) {
0052 }
0053 
0054 AMCImporter::~AMCImporter() {
0055 }
0056 
0057 bool AMCImporter::canImport(int type) const {
0058   return type == Data::Collection::Video;
0059 }
0060 
0061 Tellico::Data::CollPtr AMCImporter::collection() {
0062   if(m_coll) {
0063     return m_coll;
0064   }
0065 
0066   if(!fileRef().open()) {
0067     return Data::CollPtr();
0068   }
0069 
0070   QIODevice* f = fileRef().file();
0071   m_ds.setDevice(f);
0072   // AMC is always little-endian? can't confirm
0073   m_ds.setByteOrder(QDataStream::LittleEndian);
0074   emit signalTotalSteps(this, f->size());
0075 
0076   const uint l = sizeof(AMC_FILE_ID)-1;
0077   QVector<char> buffer(l+1);
0078   m_ds.readRawData(buffer.data(), l);
0079   QString version = QString::fromLocal8Bit(buffer.data(), l);
0080   static const QRegularExpression versionRx(QLatin1String(".+AMC_(\\d+)\\.(\\d+).+"));
0081   QRegularExpressionMatch versionMatch = versionRx.match(version);
0082   if(!versionMatch.hasMatch()) {
0083     myDebug() << "no file id match";
0084     return Data::CollPtr();
0085   }
0086 
0087   m_coll = new Data::VideoCollection(true);
0088 
0089   m_majVersion = versionMatch.captured(1).toInt();
0090   m_minVersion = versionMatch.captured(2).toInt();
0091 //  myDebug() << m_majVersion << "-" << m_minVersion;
0092 
0093   readString(); // name
0094   readString(); // email
0095   if(m_majVersion <= 3 && m_minVersion < 5) {
0096     readString(); // icq
0097   }
0098   readString(); // webpage
0099   readString(); // description
0100 
0101   const bool showProgress = options() & ImportProgress;
0102 
0103   while(!m_cancelled && !m_failed && !f->atEnd()) {
0104     readEntry();
0105     if(showProgress) {
0106       emit signalProgress(this, f->pos());
0107       qApp->processEvents();
0108     }
0109   }
0110 
0111   return m_coll;
0112 }
0113 
0114 bool AMCImporter::readBool() {
0115   quint8 b;
0116   m_ds >> b;
0117   return b;
0118 }
0119 
0120 quint32 AMCImporter::readInt() {
0121   if(m_failed) {
0122     return 0;
0123   }
0124   quint32 i;
0125   m_ds >> i;
0126   if(i >= std::numeric_limits<uint>::max()) {
0127     i = 0;
0128   }
0129   return i;
0130 }
0131 
0132 QString AMCImporter::readString() {
0133   if(m_failed) {
0134     return QString();
0135   }
0136   // The serialization format is a length specifier first, then l bytes of data
0137   quint32 l = readInt();
0138   if(l == 0) {
0139     return QString();
0140   }
0141   if(l > AMC_MAX_STRING_SIZE) {
0142     myDebug() << "string is too long:" << l;
0143     m_failed = true;
0144     return QString();
0145   }
0146   QVector<char> buffer(l+1);
0147   m_ds.readRawData(buffer.data(), l);
0148   QString s = QString::fromLocal8Bit(buffer.data(), l);
0149 //  myDebug() << "string: " << s;
0150   return s;
0151 }
0152 
0153 QString AMCImporter::readImage(const QString& format_) {
0154   if(m_failed) {
0155     return QString();
0156   }
0157   quint32 l = readInt();
0158   if(l == 0) {
0159     return QString();
0160   }
0161   if(l > AMC_MAX_STRING_SIZE) {
0162     myDebug() << "string is too long:" << l;
0163     m_failed = true;
0164     return QString();
0165   }
0166   QVector<char> buffer(l+1);
0167   m_ds.readRawData(buffer.data(), l);
0168   QByteArray bytes;
0169   bytes.reserve(l);
0170   std::copy(buffer.data(), buffer.data() + l, bytes.begin());
0171   QImage img = QImage::fromData(bytes);
0172   if(img.isNull()) {
0173     static uint count = 0;
0174     ++count;
0175     if(count < 5) {
0176       myDebug() << "AMCImporter::readImage() - null image, expected" << format_ << "from" << l << "bytes";
0177     } else if(count == 6) {
0178       myDebug() << "AMCImporter::readImage() - skipping further errors for null images";
0179     }
0180     return QString();
0181   }
0182   QString newFormat;
0183   if(format_ == QLatin1String(".jpg")) {
0184     newFormat = QStringLiteral("JPEG");
0185   } else if(format_ == QLatin1String(".gif")) {
0186     newFormat = QStringLiteral("GIF");
0187   } else {
0188     newFormat = QStringLiteral("PNG");
0189   }
0190   return ImageFactory::addImage(img, newFormat);
0191 }
0192 
0193 void AMCImporter::readEntry() {
0194   Data::EntryPtr e(new Data::Entry(m_coll));
0195 
0196   quint32 id = readInt();
0197   if(id > 0) {
0198     e->setId(id);
0199   }
0200   readInt(); // add date
0201 
0202   quint32 rating = readInt();
0203   if(m_majVersion >= 3 && m_minVersion >= 5) {
0204     rating /= 10;
0205   }
0206   e->setField(QStringLiteral("rating"), QString::number(rating));
0207   quint32 year = readInt();
0208   if(year > 0) {
0209     e->setField(QStringLiteral("year"), QString::number(year));
0210   }
0211   quint32 time = readInt();
0212   if(time > 0) {
0213     e->setField(QStringLiteral("running-time"), QString::number(time));
0214   }
0215 
0216   readInt(); // video bitrate
0217   readInt(); // audio bitrate
0218   readInt(); // number of files
0219   readBool(); // checked
0220   readString(); // media label
0221   e->setField(QStringLiteral("medium"), readString());
0222   readString(); // source
0223   readString(); // borrower
0224   QString s = readString(); // title
0225   if(!s.isEmpty()) {
0226     e->setField(QStringLiteral("title"), s);
0227   }
0228   QString s2 = readString(); // translated title
0229   if(s.isEmpty()) {
0230     e->setField(QStringLiteral("title"), s2);
0231   }
0232 
0233   e->setField(QStringLiteral("director"), readString());
0234   s = readString();
0235   static const QRegularExpression roleRx(QLatin1String("(.+?) \\(([^(]+)\\)"));
0236   QRegularExpressionMatch roleMatch = roleRx.match(s);
0237   if(roleMatch.hasMatch()) {
0238     QString role = roleMatch.captured(2).toLower();
0239     if(role == QLatin1String("story") || role == QLatin1String("written by")) {
0240       e->setField(QStringLiteral("writer"), roleMatch.captured(1));
0241     } else {
0242       e->setField(QStringLiteral("producer"), s);
0243     }
0244   } else {
0245     e->setField(QStringLiteral("producer"), s);
0246   }
0247   e->setField(QStringLiteral("nationality"), readString());
0248   e->setField(QStringLiteral("genre"), readString().replace(QLatin1String(", "), FieldFormat::delimiterString()));
0249 
0250   e->setField(QStringLiteral("cast"), parseCast(readString()).join(FieldFormat::rowDelimiterString()));
0251 
0252   readString(); // url
0253   e->setField(QStringLiteral("plot"), readString());
0254   e->setField(QStringLiteral("comments"), readString());
0255   s = readString(); // video format
0256   static const QRegularExpression regionRx(QLatin1String("Region \\d"));
0257   QRegularExpressionMatch regionMatch = regionRx.match(s);
0258   if(regionMatch.hasMatch()) {
0259     e->setField(QStringLiteral("region"), regionMatch.captured());
0260   }
0261   e->setField(QStringLiteral("audio-track"), readString()); // audio format
0262   readString(); // resolution
0263   readString(); // frame rate
0264   e->setField(QStringLiteral("language"), readString()); // audio language
0265   e->setField(QStringLiteral("subtitle"), readString()); // subtitle
0266   readString(); // file size
0267   s = readString(); // picture extension
0268   s = readImage(s); // picture
0269   if(!s.isEmpty()) {
0270     e->setField(QStringLiteral("cover"), s);
0271   }
0272 
0273   m_coll->addEntries(e);
0274 }
0275 
0276 QStringList AMCImporter::parseCast(const QString& text_) {
0277   QStringList cast;
0278   int nPar = 0;
0279   static const QRegularExpression castRx(QLatin1String("[,()]"));
0280   QRegularExpressionMatch castMatch = castRx.match(text_);
0281   QString person, role;
0282   int oldPos = 0;
0283   for(int pos = castMatch.capturedStart(); pos > -1; pos = castMatch.capturedStart()) {
0284     if(text_.at(pos) == QLatin1Char(',') && nPar%2 == 0) {
0285       // we're done with this one
0286       person += text_.mid(oldPos, pos-oldPos).trimmed();
0287       QString all = person;
0288       if(!role.isEmpty()) {
0289         if(role.startsWith(QLatin1String("as "))) {
0290           role = role.mid(3);
0291         }
0292         all += FieldFormat::columnDelimiterString() + role;
0293       }
0294       cast << all;
0295       person.clear();
0296       role.clear();
0297       oldPos = pos+1; // add one to go past comma
0298     } else if(text_.at(pos) == QLatin1Char('(')) {
0299       if(nPar == 0) {
0300         person = text_.mid(oldPos, pos-oldPos).trimmed();
0301         oldPos = pos+1; // add one to go past parenthesis
0302       }
0303       ++nPar;
0304     } else if(text_.at(pos) == QLatin1Char(')')) {
0305       --nPar;
0306       if(nPar == 0) {
0307         role = text_.mid(oldPos, pos-oldPos).trimmed();
0308         oldPos = pos+1; // add one to go past parenthesis
0309       }
0310     }
0311     castMatch = castRx.match(text_, pos+1);
0312   }
0313   // grab the last one
0314   if(nPar%2 == 0) {
0315     int pos = text_.length();
0316     person += text_.mid(oldPos, pos-oldPos).trimmed();
0317     QString all = person;
0318     if(!role.isEmpty()) {
0319       if(role.startsWith(QLatin1String("as "))) {
0320         role = role.mid(3);
0321       }
0322       all += FieldFormat::columnDelimiterString() + role;
0323     }
0324     cast << all;
0325   }
0326   return cast;
0327 }
0328 
0329 void AMCImporter::slotCancel() {
0330   m_cancelled = true;
0331 }