File indexing completed on 2024-05-12 16:46:26
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 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 format = QStringLiteral("PNG"); 0183 if(format_ == QLatin1String(".jpg")) { 0184 format = QStringLiteral("JPEG"); 0185 } else if(format_ == QLatin1String(".gif")) { 0186 format = QStringLiteral("GIF"); 0187 } 0188 return ImageFactory::addImage(img, format); 0189 } 0190 0191 void AMCImporter::readEntry() { 0192 Data::EntryPtr e(new Data::Entry(m_coll)); 0193 0194 quint32 id = readInt(); 0195 if(id > 0) { 0196 e->setId(id); 0197 } 0198 readInt(); // add date 0199 0200 quint32 rating = readInt(); 0201 if(m_majVersion >= 3 && m_minVersion >= 5) { 0202 rating /= 10; 0203 } 0204 e->setField(QStringLiteral("rating"), QString::number(rating)); 0205 quint32 year = readInt(); 0206 if(year > 0) { 0207 e->setField(QStringLiteral("year"), QString::number(year)); 0208 } 0209 quint32 time = readInt(); 0210 if(time > 0) { 0211 e->setField(QStringLiteral("running-time"), QString::number(time)); 0212 } 0213 0214 readInt(); // video bitrate 0215 readInt(); // audio bitrate 0216 readInt(); // number of files 0217 readBool(); // checked 0218 readString(); // media label 0219 e->setField(QStringLiteral("medium"), readString()); 0220 readString(); // source 0221 readString(); // borrower 0222 QString s = readString(); // title 0223 if(!s.isEmpty()) { 0224 e->setField(QStringLiteral("title"), s); 0225 } 0226 QString s2 = readString(); // translated title 0227 if(s.isEmpty()) { 0228 e->setField(QStringLiteral("title"), s2); 0229 } 0230 0231 e->setField(QStringLiteral("director"), readString()); 0232 s = readString(); 0233 QRegularExpression roleRx(QLatin1String("(.+?) \\(([^(]+)\\)")); 0234 QRegularExpressionMatch roleMatch = roleRx.match(s); 0235 if(roleMatch.hasMatch()) { 0236 QString role = roleMatch.captured(2).toLower(); 0237 if(role == QLatin1String("story") || role == QLatin1String("written by")) { 0238 e->setField(QStringLiteral("writer"), roleMatch.captured(1)); 0239 } else { 0240 e->setField(QStringLiteral("producer"), s); 0241 } 0242 } else { 0243 e->setField(QStringLiteral("producer"), s); 0244 } 0245 e->setField(QStringLiteral("nationality"), readString()); 0246 e->setField(QStringLiteral("genre"), readString().replace(QLatin1String(", "), FieldFormat::delimiterString())); 0247 0248 e->setField(QStringLiteral("cast"), parseCast(readString()).join(FieldFormat::rowDelimiterString())); 0249 0250 readString(); // url 0251 e->setField(QStringLiteral("plot"), readString()); 0252 e->setField(QStringLiteral("comments"), readString()); 0253 s = readString(); // video format 0254 QRegularExpression regionRx(QLatin1String("Region \\d")); 0255 QRegularExpressionMatch regionMatch = regionRx.match(s); 0256 if(regionMatch.hasMatch()) { 0257 e->setField(QStringLiteral("region"), regionMatch.captured()); 0258 } 0259 e->setField(QStringLiteral("audio-track"), readString()); // audio format 0260 readString(); // resolution 0261 readString(); // frame rate 0262 e->setField(QStringLiteral("language"), readString()); // audio language 0263 e->setField(QStringLiteral("subtitle"), readString()); // subtitle 0264 readString(); // file size 0265 s = readString(); // picture extension 0266 s = readImage(s); // picture 0267 if(!s.isEmpty()) { 0268 e->setField(QStringLiteral("cover"), s); 0269 } 0270 0271 m_coll->addEntries(e); 0272 } 0273 0274 QStringList AMCImporter::parseCast(const QString& text_) { 0275 QStringList cast; 0276 int nPar = 0; 0277 QRegularExpression castRx(QLatin1String("[,()]")); 0278 QRegularExpressionMatch castMatch = castRx.match(text_); 0279 QString person, role; 0280 int oldPos = 0; 0281 for(int pos = castMatch.capturedStart(); pos > -1; pos = castMatch.capturedStart()) { 0282 if(text_.at(pos) == QLatin1Char(',') && nPar%2 == 0) { 0283 // we're done with this one 0284 person += text_.mid(oldPos, pos-oldPos).trimmed(); 0285 QString all = person; 0286 if(!role.isEmpty()) { 0287 if(role.startsWith(QLatin1String("as "))) { 0288 role = role.mid(3); 0289 } 0290 all += FieldFormat::columnDelimiterString() + role; 0291 } 0292 cast << all; 0293 person.clear(); 0294 role.clear(); 0295 oldPos = pos+1; // add one to go past comma 0296 } else if(text_.at(pos) == QLatin1Char('(')) { 0297 if(nPar == 0) { 0298 person = text_.mid(oldPos, pos-oldPos).trimmed(); 0299 oldPos = pos+1; // add one to go past parenthesis 0300 } 0301 ++nPar; 0302 } else if(text_.at(pos) == QLatin1Char(')')) { 0303 --nPar; 0304 if(nPar == 0) { 0305 role = text_.mid(oldPos, pos-oldPos).trimmed(); 0306 oldPos = pos+1; // add one to go past parenthesis 0307 } 0308 } 0309 castMatch = castRx.match(text_, pos+1); 0310 } 0311 // grab the last one 0312 if(nPar%2 == 0) { 0313 int pos = text_.length(); 0314 person += text_.mid(oldPos, pos-oldPos).trimmed(); 0315 QString all = person; 0316 if(!role.isEmpty()) { 0317 if(role.startsWith(QLatin1String("as "))) { 0318 role = role.mid(3); 0319 } 0320 all += FieldFormat::columnDelimiterString() + role; 0321 } 0322 cast << all; 0323 } 0324 return cast; 0325 } 0326 0327 void AMCImporter::slotCancel() { 0328 m_cancelled = true; 0329 }