[multimedia/kdenlive/release/26.08] src: Ensure attached picture stream are not incorrectly marked as video

Jean-Baptiste Mardelle <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 9c87390daa9692af34941e2f9617b6da90eea5cb by Jean-Baptiste Mardelle.
Committed on 20/07/2026 at 16:54.
Pushed by mardelle into branch 'release/26.08'.

Ensure attached picture stream are not incorrectly marked as video
Fix disabling/enabling video in clip properties
Limit clip stream to max 99
BUG: 523268
FIXED-IN: 26.08.0

M  +1    -1    src/lib/audio/audioInfo.cpp
M  +2    -2    src/lib/audio/audioStreamInfo.cpp
M  +43   -3    src/mltcontroller/clipcontroller.cpp
M  +2    -0    src/mltcontroller/clipcontroller.h
M  +29   -4    src/mltcontroller/clippropertiescontroller.cpp

https://invent.kde.org/multimedia/kdenlive/-/commit/9c87390daa9692af34941e2f9617b6da90eea5cb

diff --git a/src/lib/audio/audioInfo.cpp b/src/lib/audio/audioInfo.cpp
index ca2c56802a..c513660fad 100644
--- a/src/lib/audio/audioInfo.cpp
+++ b/src/lib/audio/audioInfo.cpp
@@ -16,7 +16,7 @@ AudioInfo::AudioInfo(const std::shared_ptr<Mlt::Producer> &producer)
     // Since we already receive an MLT producer, we do not need to initialize MLT:
     // Mlt::Factory::init(nullptr);
     // Get the number of streams and add the information of each of them if it is an audio stream.
-    int streams = producer->get_int("meta.media.nb_streams");
+    int streams = qMin(99, producer->get_int("meta.media.nb_streams"));
     for (int i = 0; i < streams; ++i) {
         QByteArray propertyName = QStringLiteral("meta.media.%1.stream.type").arg(i).toLocal8Bit();
         const char *streamtype = producer->get(propertyName.data());
diff --git a/src/lib/audio/audioStreamInfo.cpp b/src/lib/audio/audioStreamInfo.cpp
index 7a877ea8c4..9e12211169 100644
--- a/src/lib/audio/audioStreamInfo.cpp
+++ b/src/lib/audio/audioStreamInfo.cpp
@@ -18,7 +18,7 @@ AudioStreamInfo::AudioStreamInfo(const std::shared_ptr<Mlt::Producer> &producer,
     , m_bitRate(0)
 {
     // Fetch audio streams
-    int streams = producer->get_int("meta.media.nb_streams");
+    int streams = qMin(99, producer->get_int("meta.media.nb_streams"));
     if (streams == 0) {
         if (playlist) {
             // Playlist clips do not provide stream info
@@ -198,7 +198,7 @@ void AudioStreamInfo::setAudioIndex(const std::shared_ptr<Mlt::Producer> &produc
 {
     m_audioStreamIndex = ix;
     if (ix > -1) {
-        int streams = producer->get_int("meta.media.nb_streams");
+        int streams = qMin(99, producer->get_int("meta.media.nb_streams"));
         QList<int> audioStreams;
         for (int i = 0; i < streams; ++i) {
             QByteArray propertyName = QStringLiteral("meta.media.%1.stream.type").arg(i).toLocal8Bit();
diff --git a/src/mltcontroller/clipcontroller.cpp b/src/mltcontroller/clipcontroller.cpp
index db0a96a8ec..6407d01518 100644
--- a/src/mltcontroller/clipcontroller.cpp
+++ b/src/mltcontroller/clipcontroller.cpp
@@ -142,7 +142,7 @@ void ClipController::addMasterProducer(const std::shared_ptr<Mlt::Producer> &pro
         QList<int> videoStreams;
         QList<int> audioStreams;
         QList<int> subtitleStreams;
-        int aStreams = m_properties->get_int("meta.media.nb_streams");
+        int aStreams = qMin(99, m_properties->get_int("meta.media.nb_streams"));
         for (int ix = 0; ix < aStreams; ++ix) {
             char property[200];
             snprintf(property, sizeof(property), "meta.media.%d.stream.type", ix);
@@ -285,7 +285,7 @@ void ClipController::getInfoForProducer()
     } else if (m_service == QLatin1String("avformat") || m_service == QLatin1String("avformat-novalidate")) {
         audioIndex = getProducerIntProperty(QStringLiteral("audio_index"));
         m_videoIndex = getProducerIntProperty(QStringLiteral("video_index"));
-        if (m_videoIndex == -1) {
+        if (m_videoIndex == -1 && !hasVideoStreams()) {
             m_clipType = ClipType::Audio;
         } else {
             if (audioIndex == -1) {
@@ -863,6 +863,9 @@ void ClipController::checkAudioVideo()
             m_hasVideo = true;
             break;
         }
+        if (m_masterProducer->property_exists("video_index") && m_masterProducer->get_int("video_index") == -1) {
+            m_hasVideo = false;
+        }
         if (m_clipType == ClipType::Timeline || m_clipType == ClipType::Playlist) {
             if (m_audioInfo == nullptr) {
                 if (m_hasAudio) {
@@ -1224,7 +1227,8 @@ std::shared_ptr<MarkerSortModel> ClipController::getFilteredMarkerModel() const
 bool ClipController::isFullRange() const
 {
     bool full = !qstrcmp(m_masterProducer->get("meta.media.color_range"), "full");
-    for (int i = 0; !full && i < m_masterProducer->get_int("meta.media.nb_streams"); i++) {
+    int streams = qMin(99, m_masterProducer->get_int("meta.media.nb_streams"));
+    for (int i = 0; !full && i < streams; i++) {
         QString key = QStringLiteral("meta.media.%1.stream.type").arg(i);
         QString streamType(m_masterProducer->get(key.toLatin1().constData()));
         if (streamType == "video") {
@@ -1274,3 +1278,39 @@ std::shared_ptr<Mlt::Producer> ClipController::sequenceProducer(const QUuid &)
     QReadLocker lock(&m_producerLock);
     return m_masterProducer;
 }
+
+bool ClipController::hasVideoStreams()
+{
+    QReadLocker lock(&m_producerLock);
+    if (!m_properties) {
+        return 0;
+    }
+    int aStreams = qMin(99, m_properties->get_int("meta.media.nb_streams"));
+    for (int ix = 0; ix < aStreams; ++ix) {
+        char property[200];
+        snprintf(property, sizeof(property), "meta.media.%d.stream.type", ix);
+        QString type = m_properties->get(property);
+        if (type == QLatin1String("video")) {
+            QString key = QStringLiteral("meta.media.%1.codec.name").arg(ix);
+            QString codec_name = m_properties->get(key.toLatin1().constData());
+            if (codec_name == QLatin1String("png")) {
+                // This is a cover image, skip
+                continue;
+            }
+            if (codec_name == QLatin1String("mjpeg")) {
+                key = QStringLiteral("meta.media.%1.stream.frame_rate").arg(ix);
+                QString fps = m_properties->get(key.toLatin1().constData());
+                if (fps.isEmpty()) {
+                    key = QStringLiteral("meta.media.%1.codec.frame_rate").arg(ix);
+                    fps = m_properties->get(key.toLatin1().constData());
+                }
+                if (fps == QLatin1String("90000")) {
+                    // This is a cover image, skip
+                    continue;
+                }
+            }
+            return true;
+        }
+    }
+    return false;
+}
diff --git a/src/mltcontroller/clipcontroller.h b/src/mltcontroller/clipcontroller.h
index 70b49654d1..4a8cd74c9d 100644
--- a/src/mltcontroller/clipcontroller.h
+++ b/src/mltcontroller/clipcontroller.h
@@ -257,4 +257,6 @@ private:
     QDomElement m_effectsToLoad;
     /** @brief Build the audio info object */
     void buildAudioInfo(int audioIndex);
+    /** @brief Check if a producer has at least one video stream */
+    bool hasVideoStreams();
 };
diff --git a/src/mltcontroller/clippropertiescontroller.cpp b/src/mltcontroller/clippropertiescontroller.cpp
index 4469b32076..6bb1ef65a3 100644
--- a/src/mltcontroller/clippropertiescontroller.cpp
+++ b/src/mltcontroller/clippropertiescontroller.cpp
@@ -713,8 +713,10 @@ QWidget *ClipPropertiesController::constructPropertiesPage()
 
         // Video index
         if (!m_videoStreams.isEmpty()) {
-            QString vix = m_properties->get("video_index");
+            const QString vix = m_properties->get("video_index");
             m_originalProperties.insert(QStringLiteral("video_index"), vix);
+            const QString vStream = m_properties->get("vstream");
+            m_originalProperties.insert(QStringLiteral("vstream"), vStream);
             hlay = new QHBoxLayout;
 
             KDualAction *ac = new KDualAction(i18n("Disable video"), i18n("Enable video"), this);
@@ -743,12 +745,16 @@ QWidget *ClipPropertiesController::constructPropertiesPage()
                 int vindx = -1;
                 if (activated) {
                     videoStream->setEnabled(false);
+                    properties.insert(QStringLiteral("video_index"), QStringLiteral("-1"));
+                    properties.insert(QStringLiteral("vstream"), QStringLiteral("-1"));
+                    properties.insert(QStringLiteral("set.test_image"), QStringLiteral("1"));
                 } else {
                     videoStream->setEnabled(true);
                     vindx = videoStream->currentData().toInt();
+                    properties.insert(QStringLiteral("video_index"), QString::number(vindx));
+                    properties.insert(QStringLiteral("vstream"), QString());
+                    properties.insert(QStringLiteral("set.test_image"), QStringLiteral("0"));
                 }
-                properties.insert(QStringLiteral("video_index"), QString::number(vindx));
-                properties.insert(QStringLiteral("set.test_image"), vindx > -1 ? QStringLiteral("0") : QStringLiteral("1"));
                 Q_EMIT updateClipProperties(m_id, m_originalProperties, properties);
                 m_originalProperties = properties;
             });
@@ -1507,11 +1513,30 @@ void ClipPropertiesController::fillProperties()
 
         // Find maximum stream index values
         m_videoStreams.clear();
-        int aStreams = m_sourceProperties->get_int("meta.media.nb_streams");
+        int aStreams = qMin(99, m_sourceProperties->get_int("meta.media.nb_streams"));
         for (int ix = 0; ix < aStreams; ++ix) {
             const QString propertName = QStringLiteral("meta.media.%1.stream.type").arg(ix);
             QString type = m_sourceProperties->get(propertName.toUtf8().constData());
             if (type == QLatin1String("video")) {
+                // Discard attached pic streams
+                QString key = QStringLiteral("meta.media.%1.codec.name").arg(ix);
+                QString codec_name = m_properties->get(key.toLatin1().constData());
+                if (codec_name == QLatin1String("png")) {
+                    // This is a cover image, skip
+                    continue;
+                }
+                if (codec_name == QLatin1String("mjpeg")) {
+                    key = QStringLiteral("meta.media.%1.stream.frame_rate").arg(ix);
+                    QString fps = m_properties->get(key.toLatin1().constData());
+                    if (fps.isEmpty()) {
+                        key = QStringLiteral("meta.media.%1.codec.frame_rate").arg(ix);
+                        fps = m_properties->get(key.toLatin1().constData());
+                    }
+                    if (fps == QLatin1String("90000")) {
+                        // This is a cover image, skip
+                        continue;
+                    }
+                }
                 m_videoStreams << ix;
             }
         }
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.