#include "videotranscoder.hpp" #include #include #include #include #include #include #include Q_LOGGING_CATEGORY(transcodeLog, "ZShell.Internal.wallpaper") namespace ZShell::wallpaper { VideoTranscoder::VideoTranscoder(QObject* parent) : QObject(parent) {} bool VideoTranscoder::isHardwareCodec(const QString& codec) { static const QStringList hardware{ "av1", "hevc", "h264", "vp9", "vp8", "vc1", "mpeg2", "mpeg2video", "mpeg4", "mjpeg", }; return hardware.contains(codec); } QString VideoTranscoder::codecName(const QString& path) const { QProcess probe; probe.start( "ffprobe", { "-v", "error", "-select_streams", "v:0", "-show_entries", "stream=codec_name", "-of", "default=nw=1:nk=1", path, }); if (!probe.waitForFinished(2000) || probe.exitCode() != 0) { qWarning(transcodeLog) << "WallpaperService: ffprobe failed for" << path << "(exit" << probe.exitCode() << ")"; return {}; } return QString::fromUtf8(probe.readAllStandardOutput()).trimmed().toLower(); } QString VideoTranscoder::cacheDir() const { return QDir::homePath() + "/.cache/zshell/wallpaper-videos"; } QString VideoTranscoder::transcodeTarget(const QString& path) const { const QFileInfo info(path); const QByteArray key = path.toUtf8() + '|' + QByteArray::number(info.size()) + '|' + QByteArray::number(info.lastModified().toMSecsSinceEpoch()); const QString hash = QString::fromUtf8( QCryptographicHash::hash(key, QCryptographicHash::Sha1).toHex()); return cacheDir() + '/' + hash + ".av1.mkv"; } bool VideoTranscoder::needsTranscode(const QString& path) const { if (path.startsWith(cacheDir())) return false; const QString codec = codecName(path); if (codec.isEmpty()) return false; return !isHardwareCodec(codec); } void VideoTranscoder::start(const QString& src, const QString& dst) { QDir().mkpath(cacheDir()); if (m_process) { m_process->kill(); m_process->deleteLater(); m_process = nullptr; } m_process = new QProcess(this); auto* proc = m_process; connect(proc, &QProcess::readyReadStandardError, proc, [proc]() { qWarning(transcodeLog).noquote() << "ffmpeg:" << QString::fromUtf8(proc->readAllStandardError()); }); connect( proc, &QProcess::finished, this, [this, proc, dst](int code, QProcess::ExitStatus status) { const bool ok = code == 0 && status == QProcess::NormalExit && QFile::exists(dst); qWarning(transcodeLog) << "WallpaperService: transcode finished, exit" << code << "status" << int(status) << "exists" << QFile::exists(dst) << "->" << dst; if (proc == m_process) m_process = nullptr; proc->deleteLater(); emit transcoded(dst, ok); }); qWarning(transcodeLog) << "WallpaperService: transcoding" << src << "->" << dst; proc->setProgram("nice"); proc->setArguments({ "-n", "19", "ffmpeg", "-hide_banner", "-loglevel", "error", "-y", "-threads", "2", "-i", src, "-map", "0:v:0", "-vf", "scale=in_range=pc:out_range=tv:out_color_matrix=bt709", "-colorspace", "bt709", "-color_primaries", "bt709", "-color_trc", "bt709", "-color_range", "tv", "-c:v", "libsvtav1", "-preset", "6", "-crf", "20", "-pix_fmt", "yuv420p10le", "-an", dst, }); proc->start(); } } // namespace ZShell::wallpaper