/* 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 #include #include #include #include #include #include #include "helper.h" namespace Helper { const QString mimeType(const QString &path){ QString retval; magic_t mc = magic_open(MAGIC_MIME_TYPE); QByteArray name = path.toUtf8(); if(mc){ magic_load(mc, 0); const char* magic_c = magic_file(mc, name.constData()); retval = QString(magic_c); magic_close(mc); } if(retval.toLower().startsWith("application/octet-stream")){ magic_t mc = magic_open(MAGIC_NONE); if(mc){ magic_load(mc, 0); const char* magic_c = magic_file(mc, name.constData()); QString desc(magic_c); magic_close(mc); if(desc.toLower().contains("mpeg sequence") || desc.toLower().contains("microsoft asf") || desc.toLower().contains("matroska")){ retval = "video/"; } } } return retval; } const QString md5Sum(const QString &path){ QFileInfo info(path); if(!info.exists() || !info.isFile()){ return QString(); } QString retval; QCryptographicHash h(QCryptographicHash::Md5); QFile file(path); file.open(QIODevice::ReadOnly); qint64 read = 0; if(info.size() < (5 * 1024 * 1024)){ QByteArray data(4096, '\0'); do { read = file.read(data.data(), 4096); if(read > 0){ h.addData(data.data(), read); } } while (read == 4096); QByteArray res = h.result(); retval = res.toHex().toLower(); }else{ QByteArray data(512, '\0'); int offset = info.size() / 3; file.seek(offset); int numBytes = 512 * 1024; int readBytes = 0; do { read = file.read(data.data(), 512); if(read > 0){ readBytes += read; }else{ return QString(); } h.addData(data.data(), read); } while(readBytes < numBytes); QByteArray res = h.result(); retval = res.toHex().toLower(); } return retval; } const QString moveToArchive(const QString &path, const QString &md5, bool copy){ QFileInfo info(path); if(!info.exists()){ return QString(); } QFileInfo destFile = QFileInfo(createArchivePath(path, md5)); if(destFile.exists()){ destFile = QFileInfo(createArchivePath(path, md5, true)); if(destFile.exists()){ return QString(); } } QFileInfo destDir = QFileInfo(destFile.absolutePath()); if(!destDir.exists()){ QDir::root().mkpath(destFile.absolutePath()); } bool success = false; if(copy){ success = QFile::copy(path, destFile.absoluteFilePath()); }else{ success = QFile::rename(path, destFile.absoluteFilePath()); } if(success){ return destFile.absoluteFilePath(); } return QString(); } bool removeFromArchive(const QString &fileName, const QString &md5){ QString file = createArchivePath(fileName, md5); QFileInfo info(file); if(!info.exists()){ return false; } return QFile::remove(file); } const QString createArchivePath(const QString &path, const QString &md5, bool withMd5){ QSettings s; QString archiveDir = s.value("paths/archivedir").toString(); QFileInfo info(path); QString retval; if(withMd5){ retval = QString("%1/%2/%3/%4_%5.%6").arg(archiveDir).arg(md5[0]).arg(md5[1]).arg(info.completeBaseName()).arg(md5).arg(info.suffix()); }else{ retval = QString("%1/%2/%3/%4").arg(archiveDir).arg(md5[0]).arg(md5[1]).arg(info.fileName()); } return retval; } const QString createUSBPath(const QString &filename, const QString &seriesName, const QString &subtitle, int dvdNo, int seriesNo){ QSettings s; QString usbPath = s.value("paths/usb").toString(); QString seriesDir = seriesName; if(seriesNo > 0){ seriesDir.append(QString(" %1").arg(QString::number(seriesNo))); }else{ seriesDir.append(QString(" - %1").arg(subtitle)); } seriesDir.replace(' ', '.'); QString retval = QString("%1/DVD_%2/%3/%4").arg(usbPath).arg(QString::number(dvdNo)).arg(seriesDir).arg(filename); return retval; } QPair programData(const QString &prefix, const QString &preferred){ QSettings s; QString section = QString("programs_%1").arg(prefix); QHash data = s.value(QString("%1/data").arg(section)).toHash(); if(data.isEmpty()){ return QPair(); } QHash programData; if(!preferred.isEmpty()){ if(data.keys().contains(preferred)){ programData = data.value(preferred).toHash(); return qMakePair(programData.value("path").toString(), programData.value("args").toStringList()); } return QPair(); } QString defaultProg = s.value(QString("%1/default").arg(section)).toString(); if(defaultProg.isEmpty()){ return QPair(); } programData = data.value(defaultProg).toHash(); return qMakePair(programData.value("path").toString(), programData.value("args").toStringList()); } const QString durationFromSecs(qint64 secs){ int minutes = secs / 60; int hours = 0; int seconds = 0; if(minutes > 60){ hours = minutes / 60; minutes -= hours * 60; } seconds = secs - hours * 60 * 60 - minutes * 60; seconds = (seconds > 60) ? 59 : seconds; QByteArray retval(10, '\0'); ::snprintf(retval.data(), 9, "%.2d:%.2d:%.2d", hours, minutes, seconds); return retval; } QVariant bytesFromUnit(QVariant number, const QString unit){ QString u = unit.toLower(); quint64 retval = number.toULongLong(); if(u == "k"){ retval = retval * 1024; }else if(u == "m"){ retval = retval * 1024 * 1024; }else if(u == "g"){ retval = retval * 1024 * 1024 * 1024; } return retval; } const QStringList toStringList(const QList &list){ QStringList retval; foreach(QVariant v, list){ retval << v.toString(); } return retval; } const QString colorToHtml(const QColor &color){ char colString[7]; ::snprintf(colString, 7, "%0x%0x%0x", color.red(), color.green(), color.blue()); return QString::fromLatin1(colString); } void centerWidget(QWidget *widget){ QRect widgetRect = widget->rect(); widgetRect.moveCenter(qApp->desktop()->screenGeometry(widget).center()); widget->move(widgetRect.topLeft()); } QVariantMap ffmpegData(const QString &path){ QSettings s; QString ffProbe = s.value("paths/ffprobe").toString(); QStringList args; args << "-v" << "quiet" << "-print_format" << "json" << "-show_format" << path; QProcess ffproc; ffproc.start(ffProbe, args); if(!ffproc.waitForStarted()){ return QVariantMap(); } ffproc.waitForFinished(); QByteArray ffData = ffproc.readAllStandardOutput(); QJsonDocument jsDoc = QJsonDocument::fromJson(ffData); return jsDoc.object().value("format").toObject().toVariantMap(); } QJsonDocument streamData(const QString &path){ QSettings s; QString ffProbe = s.value("paths/ffprobe").toString(); QStringList args; args << "-v" << "quiet" << "-print_format" << "json" << "-show_format" << "-show_streams" << path; QProcess ffproc; ffproc.start(ffProbe, args); if(!ffproc.waitForStarted()){ return QJsonDocument(); } ffproc.waitForFinished(); QByteArray ffData = ffproc.readAllStandardOutput(); return QJsonDocument::fromJson(ffData); } QPixmap preview(const QString &path){ QVariantMap m = ffmpegData(path); int secs = m.value("duration").toDouble(); int interval = secs / 4; QImage retval(640 * 2 + 40, 480 * 2 + 40, QImage::Format_ARGB32); retval.fill(Qt::transparent); QPainter p(&retval); QImage img1 = snapshot(path, 20); int yOff = (480 - img1.height()) / 2; yOff = yOff < 0 ? 0 : yOff; p.drawImage(0, yOff, img1); QImage img2 = snapshot(path, interval * 2); p.drawImage(680, yOff, img2); QImage img3 = snapshot(path, interval * 3); p.drawImage(0, 520 + yOff, img3); QImage img4 = snapshot(path, secs - 60); p.drawImage(680, 520 + yOff, img4); QPixmap pmretval = QPixmap::fromImage(retval); return pmretval; } QImage snapshot(const QString &path, int where){ QSettings s; QString ffmpeg = s.value("paths/ffmpeg").toString(); QStringList args; QTemporaryFile tf; tf.open(); args << "-y" << "-ss" << QString::number(where) << "-i" << path << "-f" << "image2" << "-vframes" << "1" << tf.fileName(); QProcess ffproc; ffproc.start(ffmpeg, args); if(!ffproc.waitForStarted()){ return QImage(); } if(ffproc.state() == QProcess::Running){ ffproc.waitForFinished(); } QImage retval(tf.fileName()); retval = retval.scaledToWidth(640); Duration dur(where); QFont font("Monospace", 10); QFontMetrics fm(font); int width = fm.width(dur.toString()); int height = fm.height(); QPainter p(&retval); p.setBrush(QBrush(QColor(255, 255, 255, 70))); QRect durRect(640 / 2 - width / 2 - 4, retval.height() - height - 8, width + 4, height + 4); p.drawRect(durRect); p.setPen(Qt::black); p.drawText(durRect, Qt::AlignCenter, dur.toString()); return retval; } QVariant picSize(const QString &path){ Magick::Image img; img.ping(qPrintable(path)); QString retval = QString("%1x%2").arg(QString::number(img.columns())).arg(QString::number(img.rows())); return retval; } PicData convertArchivefileToPng(PicData data){ if(data.at(2) != "image/jpeg"){ return PicData(); } PicData retval; QString newFn = data.at(0).toString(); newFn.replace(QRegularExpression("(jpg|jpeg)$", QRegularExpression::CaseInsensitiveOption), "png"); Magick::Image img; img.read(data.at(3).toByteArray().data()); QTemporaryFile outFile("shemovconvertXXXXXX.png"); if(outFile.open()){ try { img.write(outFile.fileName().toStdString()); } catch(Magick::Exception &e) { return retval; } outFile.rename(newFn); QString newMd5 = md5Sum(outFile.fileName()); QString dest = moveToArchive(outFile.fileName(), newMd5, true); QFileInfo destFi(dest); if(!destFi.exists()){ return PicData(); } QString mt = mimeType(dest); retval = data; retval[0] = newFn; //Filename retval[1] = destFi.size(); //Size retval[2] = mt; //Mime-Type retval[3] = dest; //Full path retval[6] = newMd5; //md5sum QSqlDatabase db = QSqlDatabase::database("treedb"); db.transaction(); int id = data.at(4).toInt(); QSqlQuery q(db); q.prepare("UPDATE pics SET tfilename = :fn, cmd5sum = :md5, isize = :size, tformat = :format WHERE ipicsid = :id"); q.bindValue(":fn", newFn); q.bindValue(":md5", newMd5); q.bindValue(":size", destFi.size()); q.bindValue(":format", mt); q.bindValue(":id", id); if(q.exec()){ QFile::remove(data.at(3).toString()); db.commit(); return retval; }else{ QFile::remove(dest); db.rollback(); } } return PicData(); } Duration::Duration() : mHours(0), mMinutes(0), mSeconds(0) {} Duration::Duration(qint64 seconds){ int sec(0), min(0), h(0); // get hours h = (seconds / 60 / 60); // remaining minutes min = (seconds / 60) % 60; // seconds sec = seconds % 60; mHours = h; mMinutes = min; mSeconds = sec; } Duration::Duration(const QString &dur) : mHours(0), mMinutes(0), mSeconds(0){ QStringList parts = dur.split(':'); if(parts.size() == 3){ mHours = parts.at(0).toInt(); mMinutes = parts.at(1).toInt(); mSeconds = parts.at(2).toInt(); } } Duration Duration::operator+(const Duration &dur) const{ Duration retval; retval.mSeconds = mSeconds + dur.mSeconds; retval.mMinutes = mMinutes + dur.mMinutes + retval.mSeconds / 60; retval.mSeconds %= 60; retval.mHours = mHours + dur.mHours + retval.mMinutes / 60; retval.mMinutes %= 60; return retval; } const QString Duration::toString() const { QString retval = QString("%1:%2:%3").arg(QString::number(mHours), 2, '0').arg(QString::number(mMinutes), 2, '0').arg(QString::number(mSeconds), 2, '0'); return retval; } bool Duration::isNull() const { return mHours == 0 && mMinutes == 0 && mSeconds == 0; } qint64 Duration::toSeconds() const { qint64 retval; retval = mHours * 60 * 60 + mMinutes * 60 + mSeconds; return retval; } }