// Copyright (C) 2024 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "commandlineparser.h" #include namespace { bool contains(const QStringList &list, const QStringView &str) { return list.contains(str, Qt::CaseInsensitive); } bool equals(const QStringView &a, const QStringView &b) { return a.compare(b, Qt::CaseInsensitive) == 0; } template QString join(const Container &container, const Separator &separator, ToString &&toString) { QString result; for (const auto &element : container) { if (!result.isEmpty()) result += separator; result += toString(element); } return result; } template QString joinNumberElements(const Numbers &numbers, const Separator &separator) { return join(numbers, separator, [](auto number) { return QString::number(number); }); } template QString joinMapStringValues(const Map &map, const Separator &separator) { return join(map, separator, [](const auto &pair) { return pair.second; }); } auto toUInt(const QString &value, bool &ok) { return value.toUInt(&ok); } auto toUIntList(const QString &value, bool &ok) { std::vector result; const QStringList split = value.split(','); ok = !split.isEmpty(); for (auto it = split.cbegin(); ok && it != split.cend(); ++it) result.push_back(it->toUInt(&ok)); return result; } auto toSize(const QString &value, bool &ok) { const QStringList xy = value.split('x'); ok = xy.size() == 2; QSize result; if (ok) result.setWidth(xy.front().toUInt(&ok)); if (ok) result.setHeight(xy.back().toUInt(&ok)); return result; } auto toPositiveFloat(const QString &value, bool &ok) { const float result = value.toFloat(&ok); ok = ok && result > 0; return result; } auto toUrl(const QString &value, [[maybe_unused]] bool &ok) { return QUrl::fromLocalFile(value); } auto toStreams(const QStringList &streams, const QString &value, bool &ok) { const QStringList result = value.split(','); ok = !result.empty() && std::all_of(result.cbegin(), result.cend(), [&](const QString &s) { return streams.contains(s, Qt::CaseInsensitive); }); return result; } auto toMode(const QStringList &modes, const QString &value, bool &ok) { ok = modes.contains(value, Qt::CaseInsensitive); return value; } auto toEnum = [](const auto &enumMap, const QString &value, bool &ok) { auto found = std::find_if(enumMap.begin(), enumMap.end(), [&](const auto &pair) { return equals(pair.second, value); }); ok = found != enumMap.end(); return ok ? found->first : decltype(found->first)(); }; template auto toMsCount(Duration duration) { return std::chrono::duration_cast(duration).count(); } const char *noCheck() { return nullptr; }; } // namespace CommandLineParser::CommandLineParser() : m_result{ AudioGeneratorSettings{}, VideoGeneratorSettings{}, PushModeSettings{}, {} } { m_parser.setApplicationDescription(QStringLiteral( "The application tests QtMultimedia media frame inputs with media " "recording and shows a simplistic result preview via the media player.")); m_parser.addHelpOption(); } CommandLineParser::Result CommandLineParser::process() { m_parser.process(*QApplication::instance()); parseCommonSettings(); // parse common settings first parseAudioGeneratorSettings(); parseVideoGeneratorSettings(); parsePushModeSettings(); parseRecorderSettings(); return std::move(m_result); } void CommandLineParser::parseCommonSettings() { if (auto duration = parsedValue(m_options.duration, noCheck, toUInt)) { m_result.audioGeneratorSettings->duration = std::chrono::milliseconds(*duration); m_result.videoGeneratorSettings->duration = std::chrono::milliseconds(*duration); } if (auto streams = parsedValue(m_options.streams, noCheck, toStreams, m_streams)) { if (!contains(*streams, m_audioStream)) m_result.audioGeneratorSettings.reset(); if (!contains(*streams, m_videoStream)) m_result.videoGeneratorSettings.reset(); } if (auto mode = parsedValue(m_options.mode, noCheck, toMode, m_modes)) m_mode = *mode; if (!equals(m_mode, m_pushMode)) m_result.pushModeSettings.reset(); } void CommandLineParser::parseAudioGeneratorSettings() { AudioGeneratorSettingsOpt &settings = m_result.audioGeneratorSettings; auto check = [&settings]() -> const char * { return settings ? nullptr : "audio stream is not enabled"; }; if (auto frequencies = parsedValue(m_options.channelFrequencies, check, toUIntList)) settings->channelFrequencies = std::move(*frequencies); if (auto bufferDuration = parsedValue(m_options.audioBufferDuration, check, toUInt)) settings->bufferDuration = std::chrono::milliseconds(*bufferDuration); if (auto format = parsedValue(m_options.sampleFormat, check, toEnum, m_sampleFormats)) settings->sampleFormat = *format; if (auto config = parsedValue(m_options.channelConfig, check, toEnum, m_channelConfigs)) settings->channelConfig = *config; } void CommandLineParser::parseVideoGeneratorSettings() { VideoGeneratorSettingsOpt &settings = m_result.videoGeneratorSettings; auto check = [&settings]() -> const char * { return settings ? nullptr : "video stream is not enabled"; }; if (auto frameRate = parsedValue(m_options.frameRate, check, toUInt)) settings->frameRate = *frameRate; if (auto resolution = parsedValue(m_options.resolution, check, toSize)) settings->resolution = *resolution; if (auto patternWidth = parsedValue(m_options.framePatternWidth, check, toUInt)) settings->patternWidth = *patternWidth; if (auto patternSpeed = parsedValue(m_options.framePatternSpeed, check, toUInt)) settings->patternSpeed = *patternSpeed; } void CommandLineParser::parsePushModeSettings() { PushModeSettingsOpt &settings = m_result.pushModeSettings; auto check = [&settings]() -> const char * { return settings ? nullptr : "push mode is not enabled"; }; if (auto producingRate = parsedValue(m_options.pushModeProducingRate, check, toPositiveFloat)) settings->producingRate = *producingRate; if (auto maxQueueSize = parsedValue(m_options.pushModeMaxQueueSize, check, toUInt)) settings->maxQueueSize = *maxQueueSize; } void CommandLineParser::parseRecorderSettings() { RecorderSettings &settings = m_result.recorderSettings; if (auto location = parsedValue(m_options.outputLocation, noCheck, toUrl)) settings.outputLocation = *location; if (auto frameRate = parsedValue(m_options.recorderFrameRate, noCheck, toUInt)) settings.frameRate = *frameRate; if (auto resolution = parsedValue(m_options.recorderResolution, noCheck, toSize)) settings.resolution = *resolution; settings.quality = parsedValue(m_options.recorderQuality, noCheck, toEnum, m_recorderQualities); settings.audioCodec = parsedValue(m_options.recorderAudioCodec, noCheck, toEnum, m_audioCodecs); settings.videoCodec = parsedValue(m_options.recorderVideoCodec, noCheck, toEnum, m_videoCodecs); settings.fileFormat = parsedValue(m_options.recorderFileFormat, noCheck, toEnum, m_fileFormats); } template auto CommandLineParser::parsedValue(const QString &option, ContextChecker &&contextChecker, ResultGetter &&...resultGetter) -> std::optional()))> { Q_ASSERT(!option.isEmpty()); if (!m_parser.isSet(option)) return {}; const QString value = m_parser.value(option); if (value.isEmpty()) return {}; bool ok = true; auto result = std::invoke(std::forward(resultGetter)..., value, ok); if (!ok) { qWarning() << "Invalid value for option" << option << ":" << value; } else if (const auto error = contextChecker()) { ok = false; qWarning() << error << ", so the option" << option << "will not be applied"; } if (!ok) return {}; return { std::move(result) }; } QString CommandLineParser::addOption(QCommandLineOption option) { m_parser.addOption(option); return option.names().constFirst(); } CommandLineParser::Options CommandLineParser::createOptions() { Options result; Q_ASSERT(m_result.audioGeneratorSettings && m_result.videoGeneratorSettings && m_result.pushModeSettings); const AudioGeneratorSettings &audioGeneratorSettings = *m_result.audioGeneratorSettings; const VideoGeneratorSettings &videoGeneratorSettings = *m_result.videoGeneratorSettings; const PushModeSettings &pushModeSettings = *m_result.pushModeSettings; result.streams = addOption({ QStringLiteral("streams"), QStringLiteral("Types of generated streams (%1).\nDefaults to %2.") .arg(m_streams.join(u", "), m_streams.join(',')), QStringLiteral("list of enum") }); result.mode = addOption({ QStringLiteral("mode"), QStringLiteral("Media frames generation mode (%1).\nDefaults to %2.") .arg(m_modes.join(u", "), m_pullMode), QStringLiteral("list of enum") }); result.duration = addOption({ { QStringLiteral("generator.duration"), QStringLiteral("duration") }, QStringLiteral("Media duration.\nDefaults to %1.") .arg(toMsCount(audioGeneratorSettings.duration)), QStringLiteral("ms") }); result.channelFrequencies = addOption( { { QStringLiteral("generator.frequencies"), QStringLiteral("frequencies") }, QStringLiteral("Generated audio frequencies for each channel. It's " "possible to set one or several ones: x,y,z.\nDefaults to %1.") .arg(joinNumberElements(audioGeneratorSettings.channelFrequencies, u",")), QStringLiteral("list of hz") }); result.audioBufferDuration = addOption({ { QStringLiteral("generator.audioBufferDuration"), QStringLiteral("audioBufferDuration") }, QStringLiteral("Duration of single audio buffer.\nDefaults to %1.") .arg(toMsCount(audioGeneratorSettings.bufferDuration)), QStringLiteral("ms") }); result.sampleFormat = addOption( { { QStringLiteral("generator.sampleFormat"), QStringLiteral("sampleFormat") }, QStringLiteral("Generated audio sample format (%1).\nDefaults to %2.") .arg(joinMapStringValues(m_sampleFormats, u", "), m_sampleFormats.at(audioGeneratorSettings.sampleFormat)), QStringLiteral("enum") }); result.channelConfig = addOption( { { QStringLiteral("generator.channelConfig"), QStringLiteral("channelConfig") }, QStringLiteral("Generated audio channel config (%1).\nDefaults to %2.") .arg(joinMapStringValues(m_channelConfigs, u", "), m_channelConfigs.at(audioGeneratorSettings.channelConfig)), QStringLiteral("enum") }); result.frameRate = addOption({ { QStringLiteral("generator.frameRate"), QStringLiteral("frameRate") }, QStringLiteral("Generated video frame rate.\nDefaults to %1.") .arg(videoGeneratorSettings.frameRate), QStringLiteral("uint") }); result.resolution = addOption( { { QStringLiteral("generator.resolution"), QStringLiteral("resolution") }, QStringLiteral("Generated video frame resolution.\nDefaults to %1x%2.") .arg(QString::number(videoGeneratorSettings.resolution.width()), QString::number(videoGeneratorSettings.resolution.height())), QStringLiteral("WxH") }); result.framePatternWidth = addOption({ { QStringLiteral("generator.framePatternWidth"), QStringLiteral("framePatternWidth") }, QStringLiteral("Generated video frame patern pixel width.\nDefaults to %1.") .arg(videoGeneratorSettings.patternWidth), QStringLiteral("uint") }); result.framePatternSpeed = addOption( { { QStringLiteral("generator.framePatternSpeed"), QStringLiteral("framePatternSpeed") }, QStringLiteral("Relative speed of moving the frame pattern.\nDefaults to %1.") .arg(videoGeneratorSettings.patternSpeed), QStringLiteral("float") }); result.pushModeProducingRate = addOption( { { QStringLiteral("generator.pushMode.producingRate"), QStringLiteral("producingRate") }, QStringLiteral( "Relative factor of media producing rate in push mode.\nDefaults to %1.") .arg(pushModeSettings.producingRate), QStringLiteral("positive float") }); result.pushModeMaxQueueSize = addOption({ { QStringLiteral("generator.pushMode.maxQueueSize"), QStringLiteral("maxQueueSize"), }, QStringLiteral("Max number of generated media frames in the queue on " "the front of media recorder.\nDefaults to %1.") .arg(pushModeSettings.maxQueueSize), QStringLiteral("uint") }); result.outputLocation = addOption( { { QStringLiteral("recorder.outputLocation"), QStringLiteral("outputLocation") }, QStringLiteral("Output location for the media."), QStringLiteral("file or directory") }); result.recorderFrameRate = addOption({ QStringLiteral("recorder.frameRate"), QStringLiteral("Video frame rate for media recorder.\nNo default value."), QStringLiteral("uint") }); result.recorderQuality = addOption({ { QStringLiteral("recorder.quality"), QStringLiteral("quality") }, QStringLiteral("Quality of media recording (%1).\nNo default value.") .arg(joinMapStringValues(m_recorderQualities, u", ")), QStringLiteral("enum") }); result.recorderResolution = addOption({ QStringLiteral("recorder.resolution"), QStringLiteral("Video resolution for media recorder.\nNo default value."), QStringLiteral("WxH") }); result.recorderAudioCodec = addOption({ { QStringLiteral("recorder.audioCodec"), QStringLiteral("audioCodec") }, QStringLiteral("Audio codec for media recorder. \nNo default value."), QStringLiteral("enum") }); result.recorderVideoCodec = addOption({ { QStringLiteral("recorder.videoCodec"), QStringLiteral("videoCodec") }, QStringLiteral("Video codec for media recorder.\nNo default value."), QStringLiteral("enum") }); result.recorderFileFormat = addOption({ { QStringLiteral("recorder.fileFormat"), QStringLiteral("fileFormat") }, QStringLiteral("File format for media recorder.\nNo default value."), QStringLiteral("enum") }); return result; }