/** * This file is part of the KDE libraries * * Comic Book Thumbnailer for KDE 4 v0.1 * Creates cover page previews for comic-book files (.cbr/z/t). * Copyright (c) 2009 Harsh J * * Some code borrowed from Okular's comicbook generators, * by Tobias Koenig * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // comiccreator.cpp #include "comiccreator.h" #include #include #include #include #include #include #include #include #include #include #include // For KIO-Thumbnail debug outputs #define KIO_THUMB 11371 extern "C" { KDE_EXPORT ThumbCreator *new_creator() { return new ComicCreator; } } ComicCreator::ComicCreator() : m_loop(0) {} bool ComicCreator::create(const QString& path, int width, int height, QImage& img) { Q_UNUSED(width); Q_UNUSED(height); QImage cover; // Detect mime type. const KMimeType::Ptr mime = KMimeType::findByFileContent(path); if (mime->is("application/x-cbz") || mime->is("application/zip")) { // ZIP archive. cover = extractArchiveImage(path, ZIP); } else if (mime->is("application/x-cbt") || mime->is("application/x-gzip") || mime->is("application/x-tar")) { // TAR archive cover = extractArchiveImage(path, TAR); } else if (mime->is("application/x-cbr") || mime->is("application/x-rar")) { // RAR archive. cover = extractRARImage(path); } if (cover.isNull()) { kDebug(KIO_THUMB) << "Error creating the comic book thumbnail."; return false; } // Copy the extracted cover to KIO::ThumbCreator's img reference. img = cover; return true; } void ComicCreator::filterImages(QStringList& entries) { /// Sort case-insensitive, then remove non-image entries. QMap entryMap; Q_FOREACH(const QString& entry, entries) { if (entry.endsWith(QLatin1String(".gif"), Qt::CaseInsensitive) || entry.endsWith(QLatin1String(".jpg"), Qt::CaseInsensitive) || entry.endsWith(QLatin1String(".jpeg"), Qt::CaseInsensitive) || entry.endsWith(QLatin1String(".png"), Qt::CaseInsensitive)) { entryMap.insert(entry.toLower(), entry); } } entries = entryMap.values(); } QImage ComicCreator::extractArchiveImage(const QString& path, const ComicCreator::Type type) { /// Extracts the cover image out of the .cbz or .cbt file. QScopedPointer cArchive; if (type==ZIP) { // Open the ZIP archive. cArchive.reset(new KZip(path)); } else if (type==TAR) { // Open the TAR archive. cArchive.reset(new KTar(path)); } else { // Reject all other types for this method. return QImage(); } // Can our archive be opened? if (!cArchive->open(QIODevice::ReadOnly)) { return QImage(); } // Get the archive's directory. const KArchiveDirectory* cArchiveDir = 0; cArchiveDir = cArchive->directory(); if (!cArchiveDir) { return QImage(); } QStringList entries; // Get and filter the entries from the archive. getArchiveFileList(entries, QString(), cArchiveDir); filterImages(entries); if (entries.isEmpty()) { return QImage(); } // Extract the cover file. const KArchiveFile *coverFile = static_cast (cArchiveDir->entry(entries[0])); if (!coverFile) { return QImage(); } return QImage::fromData(coverFile->data()); } void ComicCreator::getArchiveFileList(QStringList& entries, const QString& prefix, const KArchiveDirectory *dir) { /// Recursively list all files in the ZIP archive into 'entries'. Q_FOREACH (const QString& entry, dir->entries()) { const KArchiveEntry *e = dir->entry(entry); if (e->isDirectory()) { getArchiveFileList(entries, prefix + entry + '/', static_cast(e)); } else if (e->isFile()) { entries.append(prefix + entry); } } } QImage ComicCreator::extractRARImage(const QString& path) { /// Extracts the cover image out of the .cbr file. // Check if unrar is available. Get its path in 'unrarPath'. QString unrar = unrarPath("--version"); if (unrar.isEmpty()) { unrar = unrarPath("-v"); } if (unrar.isEmpty()) { kWarning(KIO_THUMB) << "A suitable version of unrar is not available."; return QImage(); } // Get the files and filter the images out. QStringList entries = getRARFileList(path, unrar); filterImages(entries); if (entries.isEmpty()) { return QImage(); } // Clear previously used data arrays. m_stdOut.clear(); m_stdErr.clear(); // Extract the cover file alone. Use verbose paths. // unrar x -n path/to/archive /path/to/temp KTempDir cUnrarTempDir; startProcess(unrar, QStringList() << "x" << "-n" + entries[0] << path << cUnrarTempDir.name()); // Load cover file data into image. QImage cover; cover.load(cUnrarTempDir.name() + entries[0]); cUnrarTempDir.unlink(); return cover; } QStringList ComicCreator::getRARFileList(const QString& path, const QString& unrarPath) { /// Get a verbose unrar listing so we can extract a single file later. // CMD: unrar vb /path/to/archive QStringList entries; startProcess(unrarPath, QStringList() << "vb" << path); entries = QString::fromLocal8Bit(m_stdOut).split('\n', QString::SkipEmptyParts); return entries; } QString ComicCreator::unrarPath(const QString& versionarg) const { /// Check the standard paths to see if a suitable unrar is available. QString unrar = KStandardDirs::findExe("unrar"); if (unrar.isEmpty()) { unrar = KStandardDirs::findExe("unrar-nonfree"); } if (unrar.isEmpty()) { unrar = KStandardDirs::findExe("rar"); } if (!unrar.isEmpty()) { QProcess proc; proc.start(unrar, QStringList() << versionarg); proc.waitForFinished(-1); const QStringList lines = QString::fromLocal8Bit(proc.readAllStandardOutput()).split ('\n', QString::SkipEmptyParts); if (!lines.isEmpty()) { if (lines.first().startsWith("RAR ") || lines.first().startsWith("UNRAR ")) { return unrar; } } } return QString(); } void ComicCreator::readProcessOut() { /// Read all std::out data and store to the data array. if (!m_process) return; m_stdOut += m_process->readAllStandardOutput(); } void ComicCreator::readProcessErr() { /// Read available std:err data and kill process if there is any. if (!m_process) return; m_stdErr += m_process->readAllStandardError(); if (!m_stdErr.isEmpty()) { m_process->kill(); return; } } void ComicCreator::finishedProcess(int exitCode, QProcess::ExitStatus exitStatus) { /// Run when process finishes. Q_UNUSED(exitCode) if (m_loop) { m_loop->exit(exitStatus == QProcess::CrashExit ? 1 : 0); } } int ComicCreator::startProcess(const QString& processPath, const QStringList& args) { /// Run a process and store std::out, std::err data in their respective buffers. int ret = 0; m_process.reset(new QProcess(this)); m_process->setProcessChannelMode(QProcess::SeparateChannels); connect(m_process.data(), SIGNAL(readyReadStandardOutput()), SLOT(readProcessOut())); connect(m_process.data(), SIGNAL(readyReadStandardError()), SLOT(readProcessErr())); connect(m_process.data(), SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(finishedProcess(int, QProcess::ExitStatus))); m_process->start(processPath, args, QIODevice::ReadWrite | QIODevice::Unbuffered); QEventLoop loop; m_loop = &loop; ret = loop.exec(QEventLoop::WaitForMoreEvents); m_loop = 0; return ret; } ThumbCreator::Flags ComicCreator::flags() const { return DrawFrame; } #include "moc_comiccreator.cpp"