File indexing completed on 2024-12-01 04:28:35

0001 /*
0002     SPDX-FileCopyrightText: 2013-2021 Meltytech LLC
0003     SPDX-FileCopyrightText: 2021 Jean-Baptiste Mardelle <jb@kdenlive.org>
0004 
0005     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006 */
0007 
0008 #include "audiolevelstask.h"
0009 #include "audio/audioStreamInfo.h"
0010 #include "bin/projectclip.h"
0011 #include "bin/projectitemmodel.h"
0012 #include "core.h"
0013 
0014 #include <KLocalizedString>
0015 #include <KMessageWidget>
0016 #include <QElapsedTimer>
0017 #include <QFile>
0018 #include <QImage>
0019 #include <QList>
0020 #include <QMutex>
0021 #include <QRgb>
0022 #include <QString>
0023 #include <QThreadPool>
0024 #include <QTime>
0025 #include <QVariantList>
0026 
0027 static QList<AudioLevelsTask *> tasksList;
0028 static QMutex tasksListMutex;
0029 
0030 static void deleteQVariantList(QVector<uint8_t> *list)
0031 {
0032     delete list;
0033 }
0034 
0035 AudioLevelsTask::AudioLevelsTask(const ObjectId &owner, QObject *object)
0036     : AbstractTask(owner, AbstractTask::AUDIOTHUMBJOB, object)
0037 {
0038     m_description = i18n("Audio thumbs");
0039 }
0040 
0041 void AudioLevelsTask::start(const ObjectId &owner, QObject *object, bool force)
0042 {
0043     // See if there is already a task for this MLT service and resource.
0044     if (pCore->taskManager.hasPendingJob(owner, AbstractTask::AUDIOTHUMBJOB)) {
0045         qDebug() << "AUDIO LEVELS TASK STARTED TWICE!!!!";
0046         return;
0047     }
0048     AudioLevelsTask *task = new AudioLevelsTask(owner, object);
0049     // Otherwise, start a new audio levels generation thread.
0050     task->m_isForce = force;
0051     pCore->taskManager.startTask(owner.itemId, task);
0052 }
0053 
0054 void AudioLevelsTask::run()
0055 {
0056     AbstractTaskDone whenFinished(m_owner.itemId, this);
0057     if (m_isCanceled || pCore->taskManager.isBlocked()) {
0058         return;
0059     }
0060     QMutexLocker lock(&m_runMutex);
0061     m_running = true;
0062     // 2 channels interleaved of uchar values
0063     auto binClip = pCore->projectItemModel()->getClipByBinID(QString::number(m_owner.itemId));
0064     if (binClip == nullptr) {
0065         // Clip was deleted
0066         return;
0067     }
0068     if (binClip->audioChannels() == 0 || binClip->audioThumbCreated()) {
0069         // nothing to do
0070         return;
0071     }
0072     std::shared_ptr<Mlt::Producer> producer = binClip->originalProducer();
0073     if ((producer == nullptr) || !producer->is_valid()) {
0074         QMetaObject::invokeMethod(pCore.get(), "displayBinMessage", Qt::QueuedConnection,
0075                                   Q_ARG(QString, i18n("Audio thumbs: cannot open file %1", QFileInfo(binClip->url()).fileName())),
0076                                   Q_ARG(int, int(KMessageWidget::Warning)));
0077         return;
0078     }
0079     int lengthInFrames = producer->get_length(); // Multiply this if we want more than 1 sample per frame
0080     if (lengthInFrames == INT_MAX || lengthInFrames == 0) {
0081         // This is a broken file or live feed, don't attempt to generate audio thumbnails
0082         QMetaObject::invokeMethod(pCore.get(), "displayBinMessage", Qt::QueuedConnection,
0083                                   Q_ARG(QString, i18n("Audio thumbs: unknown file length for %1", QFileInfo(binClip->url()).fileName())),
0084                                   Q_ARG(int, int(KMessageWidget::Warning)));
0085         return;
0086     }
0087     QString service = producer->get("mlt_service");
0088     if (service == QLatin1String("avformat-novalidate")) {
0089         service = QStringLiteral("avformat");
0090     } else if (service.startsWith(QLatin1String("xml"))) {
0091         service = QStringLiteral("xml-nogl");
0092     }
0093     const QString res = qstrdup(producer->get("resource"));
0094     producer.reset();
0095     int frequency = binClip->audioInfo()->samplingRate();
0096     frequency = frequency <= 0 ? 48000 : frequency;
0097 
0098     int channels = binClip->audioInfo()->channels();
0099     channels = channels <= 0 ? 2 : channels;
0100 
0101     QMap<int, QString> streams = binClip->audioInfo()->streams();
0102     QMap<int, int> audioChannels = binClip->audioInfo()->streamChannels();
0103     QMapIterator<int, QString> st(streams);
0104     bool audioCreated = false;
0105     int streamIndex = -1;
0106     while (st.hasNext() && !m_isCanceled) {
0107         st.next();
0108         int stream = st.key();
0109         if (audioChannels.contains(stream)) {
0110             channels = audioChannels.value(stream);
0111         }
0112         streamIndex++;
0113         // Generate one thumb per stream
0114         QString cachePath = binClip->getAudioThumbPath(stream);
0115         QVector<uint8_t> mltLevels;
0116         if (!m_isForce && QFile::exists(cachePath)) {
0117             // Audio thumb already exists
0118             QImage image(cachePath);
0119             if (!m_isCanceled && !image.isNull()) {
0120                 // convert cached image
0121                 int n = image.width() * image.height();
0122                 for (int i = 0; n > 1 && i < n; i++) {
0123                     QRgb p = image.pixel(i / channels, i % channels);
0124                     mltLevels << qRed(p);
0125                     mltLevels << qGreen(p);
0126                     mltLevels << qBlue(p);
0127                     mltLevels << qAlpha(p);
0128                 }
0129                 if (mltLevels.size() > 0) {
0130                     QVector<uint8_t> *levelsCopy = new QVector<uint8_t>(mltLevels);
0131                     producer = binClip->originalProducer();
0132                     producer->lock();
0133                     QString key = QString("_kdenlive:audio%1").arg(stream);
0134                     producer->set(key.toUtf8().constData(), levelsCopy, 0, (mlt_destructor)deleteQVariantList);
0135                     producer->unlock();
0136                     producer.reset();
0137                     continue;
0138                 }
0139             }
0140         }
0141 
0142         Mlt::Producer *aProd = new Mlt::Producer(pCore->getProjectProfile(), service.toUtf8().constData(), res.toUtf8().constData());
0143         if (!aProd->is_valid()) {
0144             QMetaObject::invokeMethod(pCore.get(), "displayBinMessage", Qt::QueuedConnection, Q_ARG(QString, i18n("Audio thumbs: cannot open file %1", res)),
0145                                       Q_ARG(int, int(KMessageWidget::Warning)));
0146             delete aProd;
0147             return;
0148         }
0149         aProd->set("video_index", -1);
0150         aProd->set("audio_index", stream);
0151         aProd->set("vstream", -1);
0152         aProd->set("astream", streamIndex);
0153         Mlt::Filter chans(pCore->getProjectProfile(), "audiochannels");
0154         Mlt::Filter converter(pCore->getProjectProfile(), "audioconvert");
0155         Mlt::Filter levels(pCore->getProjectProfile(), "audiolevel");
0156         aProd->attach(chans);
0157         aProd->attach(converter);
0158         aProd->attach(levels);
0159         std::unique_ptr<Mlt::Producer> audioProducer;
0160         audioProducer.reset(aProd);
0161 
0162         double framesPerSecond = audioProducer->get_fps();
0163         mlt_audio_format audioFormat = mlt_audio_s16;
0164         QStringList keys;
0165         keys.reserve(channels);
0166         for (int i = 0; i < channels; i++) {
0167             keys << "meta.media.audio_level." + QString::number(i);
0168         }
0169         uint maxLevel = 1;
0170         QElapsedTimer updateTime;
0171         updateTime.start();
0172         for (int z = 0; z < lengthInFrames && !m_isCanceled; ++z) {
0173             int val = int(100.0 * z / lengthInFrames);
0174             if (m_progress != val) {
0175                 m_progress = val;
0176                 QMetaObject::invokeMethod(m_object, "updateJobProgress");
0177             }
0178             QScopedPointer<Mlt::Frame> mltFrame(audioProducer->get_frame());
0179             if ((mltFrame != nullptr) && mltFrame->is_valid() && (mltFrame->get_int("test_audio") == 0)) {
0180                 int samples = mlt_audio_calculate_frame_samples(float(framesPerSecond), frequency, z);
0181                 mltFrame->get_audio(audioFormat, frequency, channels, samples);
0182                 for (int channel = 0; channel < channels; ++channel) {
0183                     uint lev = 256 * qMin(mltFrame->get_double(keys.at(channel).toUtf8().constData()) * 0.9, 1.0);
0184                     mltLevels << lev;
0185                     // double lev = mltFrame->get_double(keys.at(channel).toUtf8().constData());
0186                     // mltLevels << lev;
0187                     maxLevel = qMax(lev, maxLevel);
0188                 }
0189             } else if (!mltLevels.isEmpty()) {
0190                 for (int channel = 0; channel < channels; channel++) {
0191                     mltLevels << mltLevels.last();
0192                 }
0193             }
0194             // Incrementally update the audio levels every 3 seconds.
0195             if (updateTime.elapsed() > 3000 && !m_isCanceled) {
0196                 updateTime.restart();
0197                 QVector<uint8_t> *levelsCopy = new QVector<uint8_t>(mltLevels);
0198                 producer = binClip->originalProducer();
0199                 producer->lock();
0200                 QString key = QString("_kdenlive:audio%1").arg(stream);
0201                 producer->set(key.toUtf8().constData(), levelsCopy, 0, (mlt_destructor)deleteQVariantList);
0202                 producer->unlock();
0203                 producer.reset();
0204                 QMetaObject::invokeMethod(m_object, "updateAudioThumbnail", Q_ARG(bool, false));
0205             }
0206         }
0207 
0208         /*// Normalize
0209         for (double &v : mltLevels) {
0210             m_audioLevels << uchar(255 * v / maxLevel);
0211         }*/
0212         if (m_isCanceled) {
0213             mltLevels.clear();
0214             m_progress = 100;
0215             QMetaObject::invokeMethod(m_object, "updateJobProgress");
0216         }
0217         if (mltLevels.size() > 0) {
0218             QVector<uint8_t> *levelsCopy = new QVector<uint8_t>(mltLevels);
0219             producer = binClip->originalProducer();
0220             producer->lock();
0221             QString key = QString("_kdenlive:audio%1").arg(stream);
0222             QString key2 = QString("kdenlive:audio_max%1").arg(stream);
0223             producer->set(key2.toUtf8().constData(), int(maxLevel));
0224             producer->set(key.toUtf8().constData(), levelsCopy, 0, (mlt_destructor)deleteQVariantList);
0225             producer->unlock();
0226             producer.reset();
0227             // qDebug()<<"=== FINISHED PRODUCING AUDIO FOR: "<<key<<", SIZE: "<<levelsCopy->size();
0228             m_progress = 100;
0229             QMetaObject::invokeMethod(m_object, "updateJobProgress");
0230             // Put into an image for caching.
0231             int count = mltLevels.size();
0232             QImage image((count + 3) / 4 / channels, channels, QImage::Format_ARGB32);
0233             int n = image.width() * image.height();
0234             for (int i = 0; i < n; i++) {
0235                 QRgb p;
0236                 if ((4 * i + 3) < count) {
0237                     p = qRgba(mltLevels.at(4 * i), mltLevels.at(4 * i + 1), mltLevels.at(4 * i + 2), mltLevels.at(4 * i + 3));
0238                 } else {
0239                     int last = mltLevels.last();
0240                     int r = (4 * i + 0) < count ? mltLevels.at(4 * i + 0) : last;
0241                     int g = (4 * i + 1) < count ? mltLevels.at(4 * i + 1) : last;
0242                     int b = (4 * i + 2) < count ? mltLevels.at(4 * i + 2) : last;
0243                     int a = last;
0244                     p = qRgba(r, g, b, a);
0245                 }
0246                 image.setPixel(i / channels, i % channels, p);
0247             }
0248             image.save(cachePath);
0249             audioCreated = true;
0250             QMetaObject::invokeMethod(m_object, "updateAudioThumbnail", Q_ARG(bool, false));
0251         }
0252     }
0253     if (!audioCreated && !m_isCanceled) {
0254         // Audio was cached, ensure the bin thumbnail is loaded
0255         QMetaObject::invokeMethod(m_object, "updateAudioThumbnail", Q_ARG(bool, true));
0256     }
0257     QMetaObject::invokeMethod(m_object, "updateJobProgress");
0258 }