LCOV - code coverage report
Current view: top level - muse/framework/media/internal - ffmpegutils.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 87.2 % 117 102
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-Studio-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2025 MuseScore Limited
       9              :  *
      10              :  * This program is free software; you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : 
      23              : #include "ffmpegutils.h"
      24              : 
      25              : #include <algorithm>
      26              : 
      27              : #include "io/fileinfo.h"
      28              : #include "io/path.h"
      29              : 
      30              : #if defined(Q_OS_WIN)
      31              : #include <QSettings>
      32              : #endif
      33              : 
      34              : using namespace muse::media;
      35              : using namespace muse;
      36              : 
      37              : namespace muse::media {
      38            0 : io::paths_t defaultSearchPaths()
      39              : {
      40            0 :     io::paths_t paths;
      41              : #if defined(Q_OS_WIN)
      42              :     QSettings settings(
      43              :         "HKEY_LOCAL_MACHINE\\Software\\FFmpeg\\Providers",
      44              :         QSettings::NativeFormat
      45              :         );
      46              : 
      47              :     for (const QString& child : settings.childGroups()) {
      48              :         settings.beginGroup(child);
      49              :         QVariant value = settings.value("InstallPath");
      50              :         if (value.isValid()) {
      51              :             QString path = value.toString();
      52              :             if (!path.isEmpty()) {
      53              :                 paths.push_back(path.replace('\\', '/') + "/bin");
      54              :             }
      55              :         }
      56              :         settings.endGroup(); // Back to parent group.
      57              :     }
      58              : #elif defined(Q_OS_MAC)
      59              :     RetVal<io::paths_t> providers = io::Dir::scanFiles("/Library/Application Support/FFmpeg/Providers", { "*" },
      60              :                                                        io::ScanMode::FoldersInCurrentDir);
      61              :     if (providers.ret) {
      62              :         for (const io::path_t& dir : providers.val) {
      63              :             paths.push_back(dir + "/lib");
      64              :         }
      65              :     }
      66              :     paths.push_back("/opt/homebrew/lib");
      67              :     paths.push_back("/usr/local/lib");
      68              : #else
      69            0 :     paths.push_back("/usr/lib/x86_64-linux-gnu");
      70            0 :     paths.push_back("/usr/lib64");
      71            0 :     paths.push_back("/usr/lib");
      72              : #endif
      73            0 :     return paths;
      74            0 : }
      75              : 
      76           72 : static FFmpegLibPaths libraryPathsForVersion(int ffmpegVer, const io::path_t& searchDir, FFmpegCandidateOrigin origin)
      77              : {
      78           72 :     FFmpegVersionInfo ffmpegVersionInfo;
      79          252 :     for (const auto& [ffmpegVersion, componentsVersions] : FFMPEG_COMPONENTS_VERSIONS) {
      80          252 :         if (ffmpegVersion == ffmpegVer) {
      81           72 :             ffmpegVersionInfo = componentsVersions;
      82           72 :             break;
      83              :         }
      84              :     }
      85              : 
      86           72 :     if (!ffmpegVersionInfo.isValid()) {
      87            0 :         return {};
      88              :     }
      89              : 
      90           72 :     io::path_t avutilName, avcodecName, avformatName, swscaleName, swresampleName;
      91              : #if defined(Q_OS_MAC)
      92              :     avutilName = io::path_t("libavutil." + std::to_string(ffmpegVersionInfo.avUtilVersion) + ".dylib");
      93              :     avcodecName = io::path_t("libavcodec." + std::to_string(ffmpegVersionInfo.avCodecVersion) + ".dylib");
      94              :     avformatName = io::path_t("libavformat." + std::to_string(ffmpegVersionInfo.avFormatVersion) + ".dylib");
      95              :     swscaleName = io::path_t("libswscale." + std::to_string(ffmpegVersionInfo.swScaleVersion) + ".dylib");
      96              :     swresampleName = io::path_t("libswresample." + std::to_string(ffmpegVersionInfo.swResampleVersion) + ".dylib");
      97              : #elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
      98           72 :     avutilName = io::path_t("libavutil.so." + std::to_string(ffmpegVersionInfo.avUtilVersion));
      99           72 :     avcodecName = io::path_t("libavcodec.so." + std::to_string(ffmpegVersionInfo.avCodecVersion));
     100           72 :     avformatName = io::path_t("libavformat.so." + std::to_string(ffmpegVersionInfo.avFormatVersion));
     101           72 :     swscaleName = io::path_t("libswscale.so." + std::to_string(ffmpegVersionInfo.swScaleVersion));
     102           72 :     swresampleName = io::path_t("libswresample.so." + std::to_string(ffmpegVersionInfo.swResampleVersion));
     103              : #elif defined(Q_OS_WIN)
     104              :     avutilName = io::path_t("avutil-" + std::to_string(ffmpegVersionInfo.avUtilVersion) + ".dll");
     105              :     avcodecName = io::path_t("avcodec-" + std::to_string(ffmpegVersionInfo.avCodecVersion) + ".dll");
     106              :     avformatName = io::path_t("avformat-" + std::to_string(ffmpegVersionInfo.avFormatVersion) + ".dll");
     107              :     swscaleName = io::path_t("swscale-" + std::to_string(ffmpegVersionInfo.swScaleVersion) + ".dll");
     108              :     swresampleName = io::path_t("swresample-" + std::to_string(ffmpegVersionInfo.swResampleVersion) + ".dll");
     109              : #endif
     110              : 
     111           72 :     FFmpegLibPaths result;
     112           72 :     io::path_t avutilPath = searchDir.appendingComponent(avutilName);
     113           72 :     io::path_t avcodecPath = searchDir.appendingComponent(avcodecName);
     114           72 :     io::path_t avformatPath = searchDir.appendingComponent(avformatName);
     115           72 :     io::path_t swscalePath = searchDir.appendingComponent(swscaleName);
     116           72 :     io::path_t swresamplePath = searchDir.appendingComponent(swresampleName);
     117           84 :     if (io::FileInfo::exists(avutilPath) && io::FileInfo::exists(avcodecPath)
     118           12 :         && io::FileInfo::exists(avformatPath) && io::FileInfo::exists(swscalePath)
     119           84 :         && io::FileInfo::exists(swresamplePath)) {
     120           11 :         result.ffmpegVersion = ffmpegVer;
     121           11 :         result.origin = origin;
     122           11 :         result.searchDir = searchDir;
     123           11 :         result.avUtilPath = avutilPath;
     124           11 :         result.avCodecPath = avcodecPath;
     125           11 :         result.avFormatPath = avformatPath;
     126           11 :         result.swScalePath = swscalePath;
     127           11 :         result.swResamplePath = swresamplePath;
     128           11 :         return result;
     129              :     }
     130              : 
     131           61 :     return result;
     132           72 : }
     133              : 
     134            1 : FFmpegVersion versionFromAVFormatPath(const io::path_t& path)
     135              : {
     136            1 :     std::string name = io::filename(path, true).toStdString();
     137            1 :     int avVer = -1;
     138              : #if defined(Q_OS_MAC)
     139              :     // libavformat.60.dylib
     140              :     size_t dot = name.rfind('.');
     141              :     if (dot != std::string::npos && dot > 0) {
     142              :         size_t verStart = name.rfind('.', dot - 1);
     143              :         if (verStart != std::string::npos && verStart + 1 < dot) {
     144              :             try {
     145              :                 avVer = std::stoi(name.substr(verStart + 1, dot - verStart - 1));
     146              :             } catch (...) {}
     147              :         }
     148              :     }
     149              : #elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
     150              :     // libavformat.so.60
     151            1 :     size_t so = name.rfind(".so.");
     152            1 :     if (so != std::string::npos && so + 4 < name.size()) {
     153              :         try {
     154            1 :             avVer = std::stoi(name.substr(so + 4));
     155            0 :         } catch (...) {}
     156              :     }
     157              : #elif defined(Q_OS_WIN)
     158              :     // avformat-60.dll
     159              :     size_t dash = name.find('-');
     160              :     if (dash != std::string::npos && dash + 1 < name.size()) {
     161              :         size_t dot = name.find('.', dash);
     162              :         if (dot != std::string::npos) {
     163              :             try {
     164              :                 avVer = std::stoi(name.substr(dash + 1, dot - dash - 1));
     165              :             } catch (...) {}
     166              :         }
     167              :     }
     168              : #endif
     169              : 
     170            1 :     for (const auto& [ver, componentsVersions] : FFMPEG_COMPONENTS_VERSIONS) {
     171            1 :         if (componentsVersions.avFormatVersion == avVer) {
     172            1 :             return ver;
     173              :         }
     174              :     }
     175              : 
     176            0 :     return FFMPEG_INVALID_VERSION;
     177            1 : }
     178              : 
     179           15 : static io::path_t normalizedSearchPath(const io::path_t& path)
     180              : {
     181           15 :     const io::FileInfo fileInfo(path);
     182           15 :     const String canonicalPath = fileInfo.canonicalFilePath();
     183           30 :     return canonicalPath.empty() ? io::path_t(fileInfo.absoluteFilePath()) : io::path_t(canonicalPath);
     184           15 : }
     185              : 
     186           40 : static String searchPathKey(const io::path_t& path)
     187              : {
     188              : #if defined(Q_OS_WIN)
     189              :     return path.toString().toLower();
     190              : #else
     191           40 :     return path.toString();
     192              : #endif
     193              : }
     194              : 
     195           13 : static bool containsSearchPath(const io::paths_t& paths, const io::path_t& path)
     196              : {
     197           13 :     const String pathKey = searchPathKey(path);
     198           13 :     return std::find_if(paths.cbegin(), paths.cend(), [&pathKey](const io::path_t& candidate) {
     199            7 :         return searchPathKey(candidate) == pathKey;
     200           39 :     }) != paths.cend();
     201           13 : }
     202              : 
     203            6 : static io::paths_t normalizedSearchPaths(const io::paths_t& paths)
     204              : {
     205            6 :     io::paths_t result;
     206            6 :     result.reserve(paths.size());
     207              : 
     208           16 :     for (const io::path_t& path : paths) {
     209           10 :         const io::path_t normalizedPath = normalizedSearchPath(path);
     210           10 :         if (normalizedPath.empty() || containsSearchPath(result, normalizedPath)) {
     211            0 :             continue;
     212              :         }
     213              : 
     214           10 :         result.push_back(normalizedPath);
     215           10 :     }
     216              : 
     217            6 :     std::sort(result.begin(), result.end(), [](const io::path_t& first, const io::path_t& second) {
     218            6 :         return searchPathKey(first) < searchPathKey(second);
     219              :     });
     220            6 :     return result;
     221            0 : }
     222              : 
     223           12 : static void appendLibraryPathsForAllVersions(FFmpegLibPathsList& result, const io::path_t& searchPath,
     224              :                                              FFmpegCandidateOrigin origin)
     225              : {
     226           84 :     for (const auto& [ffmpegVersion, _] : FFMPEG_COMPONENTS_VERSIONS) {
     227           72 :         FFmpegLibPaths paths = libraryPathsForVersion(ffmpegVersion, searchPath, origin);
     228           72 :         if (!paths.avFormatPath.empty()) {
     229           11 :             result.push_back(std::move(paths));
     230              :         }
     231           72 :     }
     232           12 : }
     233              : 
     234            0 : FFmpegLibPathsList findLibraryPaths(const io::path_t& configPath)
     235              : {
     236            0 :     return findLibraryPaths(configPath, defaultSearchPaths());
     237              : }
     238              : 
     239            6 : FFmpegLibPathsList findLibraryPaths(const io::path_t& configPath, const io::paths_t& defaultPaths)
     240              : {
     241            6 :     FFmpegLibPathsList result;
     242            6 :     const io::paths_t automaticSearchPaths = normalizedSearchPaths(defaultPaths);
     243              : 
     244            6 :     if (!configPath.empty() && io::FileInfo::exists(configPath)) {
     245              :         const io::path_t configuredSearchPath = normalizedSearchPath(
     246            3 :             io::FileInfo(configPath).entryType() == io::EntryType::Dir ? configPath : io::dirpath(configPath));
     247              : 
     248            3 :         if (!containsSearchPath(automaticSearchPaths, configuredSearchPath)) {
     249            2 :             appendLibraryPathsForAllVersions(result, configuredSearchPath, FFmpegCandidateOrigin::Configured);
     250              :         }
     251            3 :     }
     252              : 
     253            6 :     FFmpegLibPathsList automaticCandidates;
     254           16 :     for (const io::path_t& path : automaticSearchPaths) {
     255           10 :         appendLibraryPathsForAllVersions(automaticCandidates, path, FFmpegCandidateOrigin::Automatic);
     256              :     }
     257              : 
     258            6 :     std::sort(automaticCandidates.begin(), automaticCandidates.end(), [](const FFmpegLibPaths& first, const FFmpegLibPaths& second) {
     259            4 :         if (first.ffmpegVersion != second.ffmpegVersion) {
     260            2 :             return first.ffmpegVersion > second.ffmpegVersion;
     261              :         }
     262            2 :         return searchPathKey(first.searchDir) < searchPathKey(second.searchDir);
     263              :     });
     264              : 
     265            6 :     result.insert(result.end(), automaticCandidates.cbegin(), automaticCandidates.cend());
     266              : 
     267            6 :     return result;
     268            6 : }
     269              : 
     270            3 : std::optional<io::path_t> configuredPathToPersist(const io::path_t& requestedPath, const FFmpegLibPaths& loadedCandidate)
     271              : {
     272            3 :     if (requestedPath.empty()) {
     273            1 :         return io::path_t();
     274              :     }
     275              : 
     276            2 :     const io::FileInfo requestedFileInfo(requestedPath);
     277            2 :     if (!requestedFileInfo.exists()) {
     278            0 :         return std::nullopt;
     279              :     }
     280              : 
     281              :     const io::path_t requestedSearchPath = normalizedSearchPath(
     282            2 :         requestedFileInfo.entryType() == io::EntryType::Dir ? requestedPath : io::dirpath(requestedPath));
     283            2 :     if (searchPathKey(requestedSearchPath) != searchPathKey(loadedCandidate.searchDir)) {
     284            1 :         return std::nullopt;
     285              :     }
     286              : 
     287            1 :     return requestedSearchPath;
     288            2 : }
     289              : }
        

Generated by: LCOV version 2.0-1