Factorized some container/codec handling code + removed the no longer needed transcode output formats mka and webm

This commit is contained in:
emeric
2025-11-30 18:48:04 +01:00
parent c3cd30f248
commit d834f34683
65 changed files with 1424 additions and 1003 deletions
+34 -35
View File
@@ -35,7 +35,6 @@ extern "C"
#include "core/ITraceLogger.hpp"
#include "core/String.hpp"
#include "audio/AudioTypes.hpp"
#include "audio/Exception.hpp"
namespace lms::audio::ffmpeg
@@ -71,83 +70,83 @@ namespace lms::audio::ffmpeg
res[core::stringUtils::stringToUpper(tag->key)] = tag->value;
}
std::optional<ContainerType> avdemuxerToContainerType(std::string_view name)
std::optional<core::media::ContainerType> avdemuxerToContainerType(std::string_view name)
{
if (name == "aiff")
return ContainerType::AIFF;
return core::media::ContainerType::AIFF;
if (name == "ape")
return ContainerType::APE;
return core::media::ContainerType::APE;
if (name.starts_with("asf"))
return ContainerType::ASF;
return core::media::ContainerType::ASF;
if (name == "dsf")
return ContainerType::DSF;
return core::media::ContainerType::DSF;
if (name == "flac")
return ContainerType::FLAC;
return core::media::ContainerType::FLAC;
if (name.find("mp4") != std::string_view::npos)
return ContainerType::MP4;
return core::media::ContainerType::MP4;
if (name.starts_with("mpc"))
return ContainerType::MPC;
return core::media::ContainerType::MPC;
if (name == "mp3")
return ContainerType::MPEG;
return core::media::ContainerType::MPEG;
if (name == "ogg")
return ContainerType::Ogg;
return core::media::ContainerType::Ogg;
if (name == "shn")
return ContainerType::Shorten;
return core::media::ContainerType::Shorten;
if (name == "tta")
return ContainerType::TrueAudio;
return core::media::ContainerType::TrueAudio;
if (name == "wav")
return ContainerType::WAV;
return core::media::ContainerType::WAV;
if (name == "wv")
return ContainerType::WavPack;
return core::media::ContainerType::WavPack;
return std::nullopt;
}
std::optional<CodecType> avcodecToCodecType(AVCodecID codec)
std::optional<core::media::CodecType> avcodecToCodecType(AVCodecID codec)
{
switch (codec)
{
case AV_CODEC_ID_AAC:
return CodecType::AAC;
return core::media::CodecType::AAC;
case AV_CODEC_ID_AC3:
return CodecType::AC3;
return core::media::CodecType::AC3;
case AV_CODEC_ID_ALAC:
return CodecType::ALAC;
return core::media::CodecType::ALAC;
case AV_CODEC_ID_APE:
return CodecType::APE;
return core::media::CodecType::APE;
case AV_CODEC_ID_DSD_LSBF:
case AV_CODEC_ID_DSD_LSBF_PLANAR:
case AV_CODEC_ID_DSD_MSBF:
case AV_CODEC_ID_DSD_MSBF_PLANAR:
return CodecType::DSD;
return core::media::CodecType::DSD;
case AV_CODEC_ID_EAC3:
return CodecType::EAC3;
return core::media::CodecType::EAC3;
case AV_CODEC_ID_FLAC:
return CodecType::FLAC;
return core::media::CodecType::FLAC;
case AV_CODEC_ID_MP3:
return CodecType::MP3;
return core::media::CodecType::MP3;
case AV_CODEC_ID_MP4ALS:
return CodecType::MP4ALS;
return core::media::CodecType::MP4ALS;
case AV_CODEC_ID_MUSEPACK7:
return CodecType::MPC7;
return core::media::CodecType::MPC7;
case AV_CODEC_ID_MUSEPACK8:
return CodecType::MPC8;
return core::media::CodecType::MPC8;
case AV_CODEC_ID_OPUS:
return CodecType::Opus;
return core::media::CodecType::Opus;
case AV_CODEC_ID_SHORTEN:
return CodecType::Shorten;
return core::media::CodecType::Shorten;
case AV_CODEC_ID_VORBIS:
return CodecType::Vorbis;
return core::media::CodecType::Vorbis;
case AV_CODEC_ID_WAVPACK:
return CodecType::WavPack;
return core::media::CodecType::WavPack;
case AV_CODEC_ID_WMALOSSLESS:
return CodecType::WMA9Lossless;
return core::media::CodecType::WMA9Lossless;
case AV_CODEC_ID_WMAPRO:
return CodecType::WMA9Pro;
return core::media::CodecType::WMA9Pro;
case AV_CODEC_ID_WMAV1:
return CodecType::WMA1;
return core::media::CodecType::WMA1;
case AV_CODEC_ID_WMAV2:
return CodecType::WMA2;
return core::media::CodecType::WMA2;
default:
return std::nullopt;
+4 -3
View File
@@ -27,7 +27,8 @@
#include <string>
#include <vector>
#include "audio/AudioTypes.hpp"
#include "core/media/CodecType.hpp"
#include "core/media/ContainerType.hpp"
extern "C"
{
@@ -44,7 +45,7 @@ namespace lms::audio::ffmpeg
struct ContainerInfo
{
std::optional<ContainerType> container;
std::optional<core::media::ContainerType> container;
std::string containerName;
std::optional<std::size_t> bitrate;
@@ -54,7 +55,7 @@ namespace lms::audio::ffmpeg
struct StreamInfo
{
size_t index{};
std::optional<CodecType> codec;
std::optional<core::media::CodecType> codec;
std::string codecName;
std::optional<size_t> bitrate;
+1 -1
View File
@@ -24,7 +24,7 @@
#include "core/ILogger.hpp"
#include "audio/AudioTypes.hpp"
#include "audio/AudioProperties.hpp"
#include "audio/IAudioFileInfo.hpp"
#include "AudioFile.hpp"
+1 -1
View File
@@ -22,7 +22,7 @@
#include <filesystem>
#include <optional>
#include "audio/AudioTypes.hpp"
#include "audio/AudioProperties.hpp"
#include "audio/IAudioFileInfo.hpp"
#include "audio/IAudioFileInfoParser.hpp"
+2 -2
View File
@@ -45,9 +45,9 @@ namespace lms::audio::ffmpeg
image.data = picture.data;
image.mimeType = picture.mimeType;
if (metaDataHasKeyword(metaData, "front"))
image.type = Image::Type::FrontCover;
image.type = core::media::ImageType::FrontCover;
else if (metaDataHasKeyword(metaData, "back"))
image.type = Image::Type::BackCover;
image.type = core::media::ImageType::BackCover;
visitor(image);
});
+65 -63
View File
@@ -19,13 +19,14 @@
#include "Transcoder.hpp"
#include <atomic>
#include <filesystem>
#include <iomanip>
#include "core/IChildProcessManager.hpp"
#include "core/IConfig.hpp"
#include "core/ILogger.hpp"
#include "core/Service.hpp"
#include "core/media/MimeType.hpp"
#include "audio/Exception.hpp"
#include "audio/TranscodeTypes.hpp"
@@ -42,18 +43,29 @@ namespace lms::audio::ffmpeg
{
#define LOG(severity, message) LMS_LOG(TRANSCODING, severity, "[" << _debugId << "] - " << message)
static std::atomic<size_t> globalId{};
static std::filesystem::path ffmpegPath;
void Transcoder::init()
namespace
{
ffmpegPath = core::Service<core::IConfig>::get()->getPath("ffmpeg-file", "/usr/bin/ffmpeg");
if (!std::filesystem::exists(ffmpegPath))
throw Exception{ "File '" + ffmpegPath.string() + "' does not exist!" };
}
class FFmpegPath
{
public:
FFmpegPath()
{
path = core::Service<core::IConfig>::get()->getPath("ffmpeg-file", "/usr/bin/ffmpeg");
if (!std::filesystem::exists(path))
throw Exception{ "File '" + path.string() + "' does not exist!" };
}
const std::filesystem::path& get() const { return path; }
private:
std::filesystem::path path;
};
} // namespace
std::atomic<std::size_t> Transcoder::_nextDebugId;
Transcoder::Transcoder(const TranscodeParameters& parameters)
: _debugId{ globalId++ }
: _debugId{ _nextDebugId++ }
, _inputParams{ parameters.inputParameters }
, _outputParams{ parameters.outputParameters }
{
@@ -64,8 +76,7 @@ namespace lms::audio::ffmpeg
void Transcoder::start()
{
if (ffmpegPath.empty())
init();
static FFmpegPath ffmpegPath;
try
{
@@ -76,15 +87,16 @@ namespace lms::audio::ffmpeg
}
catch (const std::filesystem::filesystem_error& e)
{
// TODO store/raise e.code()
throw Exception{ "File error '" + _inputParams.filePath.string() + "': " + e.what() };
throw IOFileException{ _inputParams.filePath, "Failed to test file existence", e.code() };
}
LOG(INFO, "Transcoding file " << _inputParams.filePath);
std::vector<std::string> args;
args.emplace_back(ffmpegPath.string());
args.emplace_back(ffmpegPath.get().string());
// TODO some codecs have restrictions, take them into account (channel count, sample rate, etc.)
// Make sure:
// - we do not produce anything in the stderr output
@@ -114,7 +126,7 @@ namespace lms::audio::ffmpeg
args.emplace_back("-1");
}
// Skip video flows (including covers)
// Skip video flows (including covers!)
args.emplace_back("-vn");
// Output bitrates
@@ -124,63 +136,68 @@ namespace lms::audio::ffmpeg
args.emplace_back(std::to_string(*_outputParams.bitrate));
}
// Codecs and formats
if (_outputParams.format)
{
switch (*_outputParams.format)
args.emplace_back("-f");
switch (_outputParams.format->container)
{
case OutputFormat::MP3:
args.emplace_back("-f");
case core::media::ContainerType::FLAC:
args.emplace_back("flac");
break;
case core::media::ContainerType::Ogg:
args.emplace_back("ogg");
break;
case core::media::ContainerType::MPEG:
args.emplace_back("mp3");
break;
case OutputFormat::OGG_OPUS:
args.emplace_back("-acodec");
default:
throw Exception{ "Unsupported container type " + std::string{ core::media::containerTypeToString(_outputParams.format->container).str() } };
}
args.emplace_back("-acodec");
switch (_outputParams.format->codec)
{
case core::media::CodecType::MP3:
args.emplace_back("libmp3lame");
break;
case core::media::CodecType::Opus:
args.emplace_back("libopus");
args.emplace_back("-f");
args.emplace_back("ogg");
break;
case OutputFormat::MATROSKA_OPUS:
args.emplace_back("-acodec");
args.emplace_back("libopus");
args.emplace_back("-f");
args.emplace_back("matroska");
break;
case OutputFormat::OGG_VORBIS:
args.emplace_back("-acodec");
case core::media::CodecType::Vorbis:
args.emplace_back("libvorbis");
args.emplace_back("-f");
args.emplace_back("ogg");
break;
case OutputFormat::WEBM_VORBIS:
args.emplace_back("-acodec");
args.emplace_back("libvorbis");
args.emplace_back("-f");
args.emplace_back("webm");
case core::media::CodecType::FLAC:
args.emplace_back("flac");
break;
default:
throw Exception{ "Unhandled format (" + std::to_string(static_cast<int>(*_outputParams.format)) + ")" };
throw Exception{ "Unhandled codec type " + std::string{ codecTypeToString(_outputParams.format->codec).str() } };
}
}
args.emplace_back("pipe:1");
LOG(DEBUG, "Dumping args (" << args.size() << ")");
for (const std::string& arg : args)
LOG(DEBUG, "Arg = '" << arg << "'");
if (core::Service<core::logging::ILogger>::get()->isSeverityActive(core::logging::Severity::DEBUG))
{
LOG(DEBUG, "Dumping args (" << args.size() << ")");
for (const std::string& arg : args)
LOG(DEBUG, "Arg = '" << arg << "'");
}
// Caution: stdin must have been closed before
try
{
_childProcess = core::Service<core::IChildProcessManager>::get()->spawnChildProcess(ffmpegPath, args);
_childProcess = core::Service<core::IChildProcessManager>::get()->spawnChildProcess(ffmpegPath.get(), args);
}
catch (core::ChildProcessException& exception)
{
throw Exception{ "Cannot execute '" + ffmpegPath.string() + "': " + exception.what() };
throw Exception{ "Cannot execute '" + ffmpegPath.get().string() + "': " + exception.what() };
}
}
@@ -202,25 +219,10 @@ namespace lms::audio::ffmpeg
std::string_view Transcoder::getOutputMimeType() const
{
// TODO: use input mime type
if (_outputParams.format)
{
switch (*_outputParams.format)
{
case OutputFormat::MP3:
return "audio/mpeg";
case OutputFormat::OGG_OPUS:
return "audio/opus";
case OutputFormat::MATROSKA_OPUS:
return "audio/x-matroska";
case OutputFormat::OGG_VORBIS:
return "audio/ogg";
case OutputFormat::WEBM_VORBIS:
return "audio/webm";
}
}
return core::media::getMimeType(_outputParams.format->container, _outputParams.format->codec).str();
return "application/octet-stream"; // default, should not happen
return core::media::getMimeType(_inputParams.audioProperties.container, _inputParams.audioProperties.codec).str();
}
bool Transcoder::finished() const
+4 -1
View File
@@ -19,6 +19,8 @@
#pragma once
#include <atomic>
#include "audio/ITranscoder.hpp"
namespace lms::core
@@ -47,7 +49,8 @@ namespace lms::audio::ffmpeg
static void init();
void start();
const std::size_t _debugId{};
static std::atomic<std::size_t> _nextDebugId;
const std::size_t _debugId;
const TranscodeInputParameters _inputParams;
const TranscodeOutputParameters _outputParams;
std::unique_ptr<core::IChildProcess> _childProcess;