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 }