diff --git a/Dockerfile-release b/Dockerfile-release index 1bf40d82..0a162378 100644 --- a/Dockerfile-release +++ b/Dockerfile-release @@ -67,11 +67,12 @@ RUN \ --enable-libopenjpeg \ --enable-libopus \ --enable-libvorbis \ + --enable-zlib \ --disable-everything \ - --enable-decoder=aac*,ac3*,alac,als,dsd*,flac,mp3*,libopus,pcm*,libvorbis,wavpack,wma*,libopenjpg,png \ + --enable-decoder=aac*,ac3*,alac,als,ape,asf,dsd*,flac,libopus,pcm*,libvorbis,mp3*,mpc7,mpc8,shorten,tta,wavpack,wma*,libopenjpg,png \ --enable-encoder=libmp3lame,libopus,libvorbis \ - --enable-demuxer=aac,aiff,asf,dsf,flac,ipod,ogg,matroska,mov,mp3,mp4,wav,wv,webm \ - --enable-muxer=ogg,matroska,mp3,webm \ + --enable-demuxer=aac,aiff,ape,asf,dsf,flac,m4a,mp3,mov,mpc,mpc8,ogg,shn,tta,wav,wv \ + --enable-muxer=ogg,mp3 \ --enable-protocol=file,pipe \ --enable-filter=aresample \ --enable-lto \ diff --git a/approot/messages.xml b/approot/messages.xml index 9a4fd79f..00d92c46 100644 --- a/approot/messages.xml +++ b/approot/messages.xml @@ -371,11 +371,9 @@ Never Transcoding output bitrate Transcoding output format -Matroska/Opus MP3 Ogg/Opus Ogg/Vorbis -WebM/Vorbis User Interface diff --git a/approot/messages_es.xml b/approot/messages_es.xml index b60112a2..5800e983 100644 --- a/approot/messages_es.xml +++ b/approot/messages_es.xml @@ -370,11 +370,9 @@ Nunca Bitrate de transcodificación Formato de transcodificación -Matroska/Opus MP3 Ogg/Opus Ogg/Vorbis -WebM/Vorbis Interfaz de usuario diff --git a/approot/messages_fr.xml b/approot/messages_fr.xml index ed0a2b30..4ca0b8a5 100644 --- a/approot/messages_fr.xml +++ b/approot/messages_fr.xml @@ -370,11 +370,9 @@ Jamais Bitrate du transcodage Format du transcodage -Matroska/Opus MP3 Ogg/Opus Ogg/Vorbis -WebM/Vorbis Interface utilisateur diff --git a/approot/messages_it.xml b/approot/messages_it.xml index 13e74ee0..a1856a6b 100644 --- a/approot/messages_it.xml +++ b/approot/messages_it.xml @@ -370,11 +370,9 @@ Mai Bitrate transcodifica Formato di transcodifica -Matroska/Opus MP3 Ogg/Opus Ogg/Vorbis -WebM/Vorbis Interfaccia utente diff --git a/approot/messages_pl.xml b/approot/messages_pl.xml index 2687adc6..6e8ff867 100644 --- a/approot/messages_pl.xml +++ b/approot/messages_pl.xml @@ -396,11 +396,9 @@ Nigdy Przepływność transkodowania Format wyjściowy transkodowania -Matroska/Opus MP3 Ogg/Opus Ogg/Vorbis -WebM/Vorbis Interfejs użytkownika diff --git a/approot/messages_zh.xml b/approot/messages_zh.xml index e5f4cda8..37f11a77 100644 --- a/approot/messages_zh.xml +++ b/approot/messages_zh.xml @@ -367,11 +367,9 @@ 从不 转码比特率 转码格式 -Matroska/Opus MP3 Ogg/Opus Ogg/Vorbis -WebM/Vorbis diff --git a/src/libs/audio/CMakeLists.txt b/src/libs/audio/CMakeLists.txt index bd8d1d4f..0979b975 100644 --- a/src/libs/audio/CMakeLists.txt +++ b/src/libs/audio/CMakeLists.txt @@ -14,8 +14,6 @@ add_library(lmsaudio STATIC impl/taglib/ImageReader.cpp impl/taglib/TagReader.cpp impl/taglib/Utils.cpp - impl/AudioTypes.cpp - impl/ImageReader.cpp impl/AudioFileInfoParser.cpp impl/TagReader.cpp ) diff --git a/src/libs/audio/impl/ffmpeg/AudioFile.cpp b/src/libs/audio/impl/ffmpeg/AudioFile.cpp index 9e5eed5c..2afd4617 100644 --- a/src/libs/audio/impl/ffmpeg/AudioFile.cpp +++ b/src/libs/audio/impl/ffmpeg/AudioFile.cpp @@ -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 avdemuxerToContainerType(std::string_view name) + std::optional 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 avcodecToCodecType(AVCodecID codec) + std::optional 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; diff --git a/src/libs/audio/impl/ffmpeg/AudioFile.hpp b/src/libs/audio/impl/ffmpeg/AudioFile.hpp index fde8a031..61aa973c 100644 --- a/src/libs/audio/impl/ffmpeg/AudioFile.hpp +++ b/src/libs/audio/impl/ffmpeg/AudioFile.hpp @@ -27,7 +27,8 @@ #include #include -#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 container; + std::optional container; std::string containerName; std::optional bitrate; @@ -54,7 +55,7 @@ namespace lms::audio::ffmpeg struct StreamInfo { size_t index{}; - std::optional codec; + std::optional codec; std::string codecName; std::optional bitrate; diff --git a/src/libs/audio/impl/ffmpeg/AudioFileInfo.cpp b/src/libs/audio/impl/ffmpeg/AudioFileInfo.cpp index 62fe54e4..e987412e 100644 --- a/src/libs/audio/impl/ffmpeg/AudioFileInfo.cpp +++ b/src/libs/audio/impl/ffmpeg/AudioFileInfo.cpp @@ -24,7 +24,7 @@ #include "core/ILogger.hpp" -#include "audio/AudioTypes.hpp" +#include "audio/AudioProperties.hpp" #include "audio/IAudioFileInfo.hpp" #include "AudioFile.hpp" diff --git a/src/libs/audio/impl/ffmpeg/AudioFileInfo.hpp b/src/libs/audio/impl/ffmpeg/AudioFileInfo.hpp index 8d89a766..e0874668 100644 --- a/src/libs/audio/impl/ffmpeg/AudioFileInfo.hpp +++ b/src/libs/audio/impl/ffmpeg/AudioFileInfo.hpp @@ -22,7 +22,7 @@ #include #include -#include "audio/AudioTypes.hpp" +#include "audio/AudioProperties.hpp" #include "audio/IAudioFileInfo.hpp" #include "audio/IAudioFileInfoParser.hpp" diff --git a/src/libs/audio/impl/ffmpeg/ImageReader.cpp b/src/libs/audio/impl/ffmpeg/ImageReader.cpp index 8014ea93..cedd6b8c 100644 --- a/src/libs/audio/impl/ffmpeg/ImageReader.cpp +++ b/src/libs/audio/impl/ffmpeg/ImageReader.cpp @@ -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); }); diff --git a/src/libs/audio/impl/ffmpeg/Transcoder.cpp b/src/libs/audio/impl/ffmpeg/Transcoder.cpp index cd6ea9d5..72261de6 100644 --- a/src/libs/audio/impl/ffmpeg/Transcoder.cpp +++ b/src/libs/audio/impl/ffmpeg/Transcoder.cpp @@ -19,13 +19,14 @@ #include "Transcoder.hpp" -#include +#include #include #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 globalId{}; - static std::filesystem::path ffmpegPath; - - void Transcoder::init() + namespace { - ffmpegPath = core::Service::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::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 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 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(*_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::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::get()->spawnChildProcess(ffmpegPath, args); + _childProcess = core::Service::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 diff --git a/src/libs/audio/impl/ffmpeg/Transcoder.hpp b/src/libs/audio/impl/ffmpeg/Transcoder.hpp index 715c6d4f..7204922c 100644 --- a/src/libs/audio/impl/ffmpeg/Transcoder.hpp +++ b/src/libs/audio/impl/ffmpeg/Transcoder.hpp @@ -19,6 +19,8 @@ #pragma once +#include + #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 _nextDebugId; + const std::size_t _debugId; const TranscodeInputParameters _inputParams; const TranscodeOutputParameters _outputParams; std::unique_ptr _childProcess; diff --git a/src/libs/audio/impl/taglib/AudioFileInfo.cpp b/src/libs/audio/impl/taglib/AudioFileInfo.cpp index 02b41ed5..1a749dd7 100644 --- a/src/libs/audio/impl/taglib/AudioFileInfo.cpp +++ b/src/libs/audio/impl/taglib/AudioFileInfo.cpp @@ -45,7 +45,7 @@ #include "core/ILogger.hpp" -#include "audio/AudioTypes.hpp" +#include "audio/AudioProperties.hpp" #include "audio/IAudioFileInfo.hpp" #include "audio/IAudioFileInfoParser.hpp" @@ -104,27 +104,27 @@ namespace lms::audio::taglib // Guess container from the file type if (const auto* apeFile{ dynamic_cast(&file) }) { - audioProperties.container = ContainerType::APE; - audioProperties.codec = CodecType::APE; // TODO version? + audioProperties.container = core::media::ContainerType::APE; + audioProperties.codec = core::media::CodecType::APE; // TODO version? audioProperties.bitsPerSample = apeFile->audioProperties()->bitsPerSample(); } else if (const auto* asfFile{ dynamic_cast(&file) }) { - audioProperties.container = ContainerType::ASF; + audioProperties.container = core::media::ContainerType::ASF; switch (asfFile->audioProperties()->codec()) { case ::TagLib::ASF::Properties::Codec::WMA1: - audioProperties.codec = CodecType::WMA1; + audioProperties.codec = core::media::CodecType::WMA1; break; case ::TagLib::ASF::Properties::Codec::WMA2: - audioProperties.codec = CodecType::WMA2; + audioProperties.codec = core::media::CodecType::WMA2; break; case ::TagLib::ASF::Properties::Codec::WMA9Lossless: - audioProperties.codec = CodecType::WMA9Lossless; + audioProperties.codec = core::media::CodecType::WMA9Lossless; break; case ::TagLib::ASF::Properties::Codec::WMA9Pro: - audioProperties.codec = CodecType::WMA9Pro; + audioProperties.codec = core::media::CodecType::WMA9Pro; break; case ::TagLib::ASF::Properties::Codec::Unknown: LMS_LOG(AUDIO, DEBUG, "Unhandled ASF codec in " << filePath); @@ -136,27 +136,27 @@ namespace lms::audio::taglib #if LMS_TAGLIB_HAS_DSF else if (const auto* dsfFile{ dynamic_cast(&file) }) { - audioProperties.container = ContainerType::DSF; - audioProperties.codec = CodecType::DSD; + audioProperties.container = core::media::ContainerType::DSF; + audioProperties.codec = core::media::CodecType::DSD; audioProperties.bitsPerSample = dsfFile->audioProperties()->bitsPerSample(); } #endif // LMS_TAGLIB_HAS_DSF else if (const auto* flacFile{ dynamic_cast(&file) }) { - audioProperties.container = ContainerType::FLAC; - audioProperties.codec = CodecType::FLAC; + audioProperties.container = core::media::ContainerType::FLAC; + audioProperties.codec = core::media::CodecType::FLAC; audioProperties.bitsPerSample = flacFile->audioProperties()->bitsPerSample(); } else if (const auto* mp4File{ dynamic_cast(&file) }) { - audioProperties.container = ContainerType::MP4; + audioProperties.container = core::media::ContainerType::MP4; switch (mp4File->audioProperties()->codec()) { case ::TagLib::MP4::Properties::Codec::AAC: - audioProperties.codec = CodecType::AAC; + audioProperties.codec = core::media::CodecType::AAC; break; case ::TagLib::MP4::Properties::Codec::ALAC: - audioProperties.codec = CodecType::ALAC; + audioProperties.codec = core::media::CodecType::ALAC; break; case ::TagLib::MP4::Properties::Codec::Unknown: LMS_LOG(AUDIO, DEBUG, "Unhandled MP4 codec in " << filePath); @@ -167,15 +167,15 @@ namespace lms::audio::taglib } else if (const auto* mpcFile{ dynamic_cast(&file) }) { - audioProperties.container = ContainerType::MPC; + audioProperties.container = core::media::ContainerType::MPC; switch (mpcFile->audioProperties()->mpcVersion()) { case 7: - audioProperties.codec = CodecType::MPC7; + audioProperties.codec = core::media::CodecType::MPC7; break; case 8: - audioProperties.codec = CodecType::MPC8; + audioProperties.codec = core::media::CodecType::MPC8; break; default: LMS_LOG(AUDIO, DEBUG, "Unhandled MPC codec version " << mpcFile->audioProperties()->mpcVersion() << " in " << filePath); @@ -186,13 +186,13 @@ namespace lms::audio::taglib { const auto& properties{ *mpegFile->audioProperties() }; - audioProperties.container = ContainerType::MPEG; + audioProperties.container = core::media::ContainerType::MPEG; if ((properties.version() == TagLib::MPEG::Header::Version::Version1 || properties.version() == TagLib::MPEG::Header::Version::Version2 || properties.version() == TagLib::MPEG::Header::Version::Version2_5) && mpegFile->audioProperties()->layer() == 3) - audioProperties.codec = CodecType::MP3; // could be MPEG-1 layer 3 or MPEG-2(.5) layer 3 + audioProperties.codec = core::media::CodecType::MP3; // could be MPEG-1 layer 3 or MPEG-2(.5) layer 3 #if LMS_TAGLIB_HAS_ADTS else if (mpegFile->audioProperties()->isADTS()) // likely AAC - audioProperties.codec = CodecType::AAC; + audioProperties.codec = core::media::CodecType::AAC; #endif else { @@ -202,44 +202,44 @@ namespace lms::audio::taglib } else if (dynamic_cast(&file)) { - audioProperties.container = ContainerType::Ogg; - audioProperties.codec = CodecType::Opus; + audioProperties.container = core::media::ContainerType::Ogg; + audioProperties.codec = core::media::CodecType::Opus; } else if (dynamic_cast(&file)) { - audioProperties.container = ContainerType::Ogg; - audioProperties.codec = CodecType::Vorbis; + audioProperties.container = core::media::ContainerType::Ogg; + audioProperties.codec = core::media::CodecType::Vorbis; } else if (const auto* aiffFile{ dynamic_cast(&file) }) { - audioProperties.container = ContainerType::AIFF; - audioProperties.codec = CodecType::PCM; + audioProperties.container = core::media::ContainerType::AIFF; + audioProperties.codec = core::media::CodecType::PCM; audioProperties.bitsPerSample = aiffFile->audioProperties()->bitsPerSample(); } else if (const auto* wavFile{ dynamic_cast(&file) }) { - audioProperties.container = ContainerType::WAV; - audioProperties.codec = CodecType::PCM; + audioProperties.container = core::media::ContainerType::WAV; + audioProperties.codec = core::media::CodecType::PCM; audioProperties.bitsPerSample = wavFile->audioProperties()->bitsPerSample(); } #if LMS_TAGLIB_HAS_SHORTEN else if (const auto* shortenFile{ dynamic_cast(&file) }) { - audioProperties.container = ContainerType::Shorten; - audioProperties.codec = CodecType::Shorten; + audioProperties.container = core::media::ContainerType::Shorten; + audioProperties.codec = core::media::CodecType::Shorten; audioProperties.bitsPerSample = shortenFile->audioProperties()->bitsPerSample(); } #endif // LMS_TAGLIB_HAS_SHORTEN else if (const auto* trueAudioFile{ dynamic_cast(&file) }) { - audioProperties.container = ContainerType::TrueAudio; - audioProperties.codec = CodecType::TrueAudio; + audioProperties.container = core::media::ContainerType::TrueAudio; + audioProperties.codec = core::media::CodecType::TrueAudio; audioProperties.bitsPerSample = trueAudioFile->audioProperties()->bitsPerSample(); } else if (const auto* wavPackFile{ dynamic_cast(&file) }) { - audioProperties.container = ContainerType::WavPack; - audioProperties.codec = CodecType::WavPack; + audioProperties.container = core::media::ContainerType::WavPack; + audioProperties.codec = core::media::CodecType::WavPack; audioProperties.bitsPerSample = wavPackFile->audioProperties()->bitsPerSample(); } else diff --git a/src/libs/audio/impl/taglib/AudioFileInfo.hpp b/src/libs/audio/impl/taglib/AudioFileInfo.hpp index 4dca6f08..00d734ac 100644 --- a/src/libs/audio/impl/taglib/AudioFileInfo.hpp +++ b/src/libs/audio/impl/taglib/AudioFileInfo.hpp @@ -22,7 +22,7 @@ #include #include -#include "audio/AudioTypes.hpp" +#include "audio/AudioProperties.hpp" #include "audio/IAudioFileInfo.hpp" #include "audio/IAudioFileInfoParser.hpp" #include "audio/IImageReader.hpp" diff --git a/src/libs/audio/impl/taglib/ImageReader.cpp b/src/libs/audio/impl/taglib/ImageReader.cpp index adad0a24..35378549 100644 --- a/src/libs/audio/impl/taglib/ImageReader.cpp +++ b/src/libs/audio/impl/taglib/ImageReader.cpp @@ -44,157 +44,157 @@ namespace lms::audio::taglib { namespace { - Image::Type imageTypeFromfromID3v2(TagLib::ID3v2::AttachedPictureFrame::Type type) + core::media::ImageType imageTypeFromfromID3v2(TagLib::ID3v2::AttachedPictureFrame::Type type) { switch (type) { case ::TagLib::ID3v2::AttachedPictureFrame::Type::Other: - return Image::Type::Other; + return core::media::ImageType::Other; case ::TagLib::ID3v2::AttachedPictureFrame::Type::FileIcon: - return Image::Type::FileIcon; + return core::media::ImageType::FileIcon; case ::TagLib::ID3v2::AttachedPictureFrame::Type::OtherFileIcon: - return Image::Type::OtherFileIcon; + return core::media::ImageType::OtherFileIcon; case ::TagLib::ID3v2::AttachedPictureFrame::Type::FrontCover: - return Image::Type::FrontCover; + return core::media::ImageType::FrontCover; case ::TagLib::ID3v2::AttachedPictureFrame::Type::BackCover: - return Image::Type::BackCover; + return core::media::ImageType::BackCover; case ::TagLib::ID3v2::AttachedPictureFrame::Type::LeafletPage: - return Image::Type::LeafletPage; + return core::media::ImageType::LeafletPage; case ::TagLib::ID3v2::AttachedPictureFrame::Type::Media: - return Image::Type::Media; + return core::media::ImageType::Media; case ::TagLib::ID3v2::AttachedPictureFrame::Type::LeadArtist: - return Image::Type::LeadArtist; + return core::media::ImageType::LeadArtist; case ::TagLib::ID3v2::AttachedPictureFrame::Type::Artist: - return Image::Type::Artist; + return core::media::ImageType::Artist; case ::TagLib::ID3v2::AttachedPictureFrame::Type::Conductor: - return Image::Type::Conductor; + return core::media::ImageType::Conductor; case ::TagLib::ID3v2::AttachedPictureFrame::Type::Band: - return Image::Type::Band; + return core::media::ImageType::Band; case ::TagLib::ID3v2::AttachedPictureFrame::Type::Composer: - return Image::Type::Composer; + return core::media::ImageType::Composer; case ::TagLib::ID3v2::AttachedPictureFrame::Type::Lyricist: - return Image::Type::Lyricist; + return core::media::ImageType::Lyricist; case ::TagLib::ID3v2::AttachedPictureFrame::Type::RecordingLocation: - return Image::Type::RecordingLocation; + return core::media::ImageType::RecordingLocation; case ::TagLib::ID3v2::AttachedPictureFrame::Type::DuringRecording: - return Image::Type::DuringRecording; + return core::media::ImageType::DuringRecording; case ::TagLib::ID3v2::AttachedPictureFrame::Type::DuringPerformance: - return Image::Type::DuringPerformance; + return core::media::ImageType::DuringPerformance; case ::TagLib::ID3v2::AttachedPictureFrame::Type::MovieScreenCapture: - return Image::Type::MovieScreenCapture; + return core::media::ImageType::MovieScreenCapture; case ::TagLib::ID3v2::AttachedPictureFrame::Type::ColouredFish: - return Image::Type::ColouredFish; + return core::media::ImageType::ColouredFish; case ::TagLib::ID3v2::AttachedPictureFrame::Type::Illustration: - return Image::Type::Illustration; + return core::media::ImageType::Illustration; case ::TagLib::ID3v2::AttachedPictureFrame::Type::BandLogo: - return Image::Type::BandLogo; + return core::media::ImageType::BandLogo; case ::TagLib::ID3v2::AttachedPictureFrame::Type::PublisherLogo: - return Image::Type::PublisherLogo; + return core::media::ImageType::PublisherLogo; } - return Image::Type::Unknown; + return core::media::ImageType::Unknown; } - Image::Type imageTypeFromfromASF(TagLib::ASF::Picture::Type type) + core::media::ImageType imageTypeFromfromASF(TagLib::ASF::Picture::Type type) { switch (type) { case ::TagLib::ASF::Picture::Type::Other: - return Image::Type::Other; + return core::media::ImageType::Other; case ::TagLib::ASF::Picture::Type::FileIcon: - return Image::Type::FileIcon; + return core::media::ImageType::FileIcon; case ::TagLib::ASF::Picture::Type::OtherFileIcon: - return Image::Type::OtherFileIcon; + return core::media::ImageType::OtherFileIcon; case ::TagLib::ASF::Picture::Type::FrontCover: - return Image::Type::FrontCover; + return core::media::ImageType::FrontCover; case ::TagLib::ASF::Picture::Type::BackCover: - return Image::Type::BackCover; + return core::media::ImageType::BackCover; case ::TagLib::ASF::Picture::Type::LeafletPage: - return Image::Type::LeafletPage; + return core::media::ImageType::LeafletPage; case ::TagLib::ASF::Picture::Type::Media: - return Image::Type::Media; + return core::media::ImageType::Media; case ::TagLib::ASF::Picture::Type::LeadArtist: - return Image::Type::LeadArtist; + return core::media::ImageType::LeadArtist; case ::TagLib::ASF::Picture::Type::Artist: - return Image::Type::Artist; + return core::media::ImageType::Artist; case ::TagLib::ASF::Picture::Type::Conductor: - return Image::Type::Conductor; + return core::media::ImageType::Conductor; case ::TagLib::ASF::Picture::Type::Band: - return Image::Type::Band; + return core::media::ImageType::Band; case ::TagLib::ASF::Picture::Type::Composer: - return Image::Type::Composer; + return core::media::ImageType::Composer; case ::TagLib::ASF::Picture::Type::Lyricist: - return Image::Type::Lyricist; + return core::media::ImageType::Lyricist; case ::TagLib::ASF::Picture::Type::RecordingLocation: - return Image::Type::RecordingLocation; + return core::media::ImageType::RecordingLocation; case ::TagLib::ASF::Picture::Type::DuringRecording: - return Image::Type::DuringRecording; + return core::media::ImageType::DuringRecording; case ::TagLib::ASF::Picture::Type::DuringPerformance: - return Image::Type::DuringPerformance; + return core::media::ImageType::DuringPerformance; case ::TagLib::ASF::Picture::Type::MovieScreenCapture: - return Image::Type::MovieScreenCapture; + return core::media::ImageType::MovieScreenCapture; case ::TagLib::ASF::Picture::Type::ColouredFish: - return Image::Type::ColouredFish; + return core::media::ImageType::ColouredFish; case ::TagLib::ASF::Picture::Type::Illustration: - return Image::Type::Illustration; + return core::media::ImageType::Illustration; case ::TagLib::ASF::Picture::Type::BandLogo: - return Image::Type::BandLogo; + return core::media::ImageType::BandLogo; case ::TagLib::ASF::Picture::Type::PublisherLogo: - return Image::Type::PublisherLogo; + return core::media::ImageType::PublisherLogo; } - return Image::Type::Unknown; + return core::media::ImageType::Unknown; } - Image::Type imageTypeFromfromFLAC(TagLib::FLAC::Picture::Type type) + core::media::ImageType imageTypeFromfromFLAC(TagLib::FLAC::Picture::Type type) { switch (type) { case ::TagLib::FLAC::Picture::Type::Other: - return Image::Type::Other; + return core::media::ImageType::Other; case ::TagLib::FLAC::Picture::Type::FileIcon: - return Image::Type::FileIcon; + return core::media::ImageType::FileIcon; case ::TagLib::FLAC::Picture::Type::OtherFileIcon: - return Image::Type::OtherFileIcon; + return core::media::ImageType::OtherFileIcon; case ::TagLib::FLAC::Picture::Type::FrontCover: - return Image::Type::FrontCover; + return core::media::ImageType::FrontCover; case ::TagLib::FLAC::Picture::Type::BackCover: - return Image::Type::BackCover; + return core::media::ImageType::BackCover; case ::TagLib::FLAC::Picture::Type::LeafletPage: - return Image::Type::LeafletPage; + return core::media::ImageType::LeafletPage; case ::TagLib::FLAC::Picture::Type::Media: - return Image::Type::Media; + return core::media::ImageType::Media; case ::TagLib::FLAC::Picture::Type::LeadArtist: - return Image::Type::LeadArtist; + return core::media::ImageType::LeadArtist; case ::TagLib::FLAC::Picture::Type::Artist: - return Image::Type::Artist; + return core::media::ImageType::Artist; case ::TagLib::FLAC::Picture::Type::Conductor: - return Image::Type::Conductor; + return core::media::ImageType::Conductor; case ::TagLib::FLAC::Picture::Type::Band: - return Image::Type::Band; + return core::media::ImageType::Band; case ::TagLib::FLAC::Picture::Type::Composer: - return Image::Type::Composer; + return core::media::ImageType::Composer; case ::TagLib::FLAC::Picture::Type::Lyricist: - return Image::Type::Lyricist; + return core::media::ImageType::Lyricist; case ::TagLib::FLAC::Picture::Type::RecordingLocation: - return Image::Type::RecordingLocation; + return core::media::ImageType::RecordingLocation; case ::TagLib::FLAC::Picture::Type::DuringRecording: - return Image::Type::DuringRecording; + return core::media::ImageType::DuringRecording; case ::TagLib::FLAC::Picture::Type::DuringPerformance: - return Image::Type::DuringPerformance; + return core::media::ImageType::DuringPerformance; case ::TagLib::FLAC::Picture::Type::MovieScreenCapture: - return Image::Type::MovieScreenCapture; + return core::media::ImageType::MovieScreenCapture; case ::TagLib::FLAC::Picture::Type::ColouredFish: - return Image::Type::ColouredFish; + return core::media::ImageType::ColouredFish; case ::TagLib::FLAC::Picture::Type::Illustration: - return Image::Type::Illustration; + return core::media::ImageType::Illustration; case ::TagLib::FLAC::Picture::Type::BandLogo: - return Image::Type::BandLogo; + return core::media::ImageType::BandLogo; case ::TagLib::FLAC::Picture::Type::PublisherLogo: - return Image::Type::PublisherLogo; + return core::media::ImageType::PublisherLogo; } - return Image::Type::Unknown; + return core::media::ImageType::Unknown; } const char* mp4ImageFormatToMimeType(TagLib::MP4::CoverArt::Format format) @@ -217,14 +217,14 @@ namespace lms::audio::taglib } #if LMS_TAGLIB_HAS_APE_COMPLEX_PROPERTIES - Image::Type imageTypeFromAPEPictureType(std::string_view pictureType) + core::media::ImageType imageTypeFromAPEPictureType(std::string_view pictureType) { if (core::stringUtils::stringCaseInsensitiveContains(pictureType, "front")) - return Image::Type::FrontCover; + return core::media::ImageType::FrontCover; if (core::stringUtils::stringCaseInsensitiveContains(pictureType, "back")) - return Image::Type::BackCover; + return core::media::ImageType::BackCover; - return Image::Type::Unknown; + return core::media::ImageType::Unknown; } #endif // LMS_TAGLIB_HAS_APE_COMPLEX_PROPERTIES @@ -294,7 +294,7 @@ namespace lms::audio::taglib image.data = pictureData; // By convention, consider the first cover art as the front cover - image.type = firstCover ? Image::Type::FrontCover : Image::Type::Unknown; + image.type = firstCover ? core::media::ImageType::FrontCover : core::media::ImageType::Unknown; firstCover = false; visitor(image); diff --git a/src/libs/audio/impl/taglib/Utils.cpp b/src/libs/audio/impl/taglib/Utils.cpp index 121a2a6a..594a8b10 100644 --- a/src/libs/audio/impl/taglib/Utils.cpp +++ b/src/libs/audio/impl/taglib/Utils.cpp @@ -111,7 +111,7 @@ namespace lms::audio::taglib::utils { const std::error_code ec{ errno, std::generic_category() }; LMS_LOG(METADATA, DEBUG, "fopen failed for " << p << ": " << ec.message()); - throw IOFileException{ "fopen failed", ec }; + throw IOFileException{ p, "fopen failed", ec }; } int fd{ ::fileno(file) }; @@ -119,7 +119,7 @@ namespace lms::audio::taglib::utils { const std::error_code ec{ errno, std::generic_category() }; LMS_LOG(METADATA, DEBUG, "fileno failed for " << p << ": " << ec.message()); - throw IOFileException{ "fileno failed", ec }; + throw IOFileException{ p, "fileno failed", ec }; } return TagLib::FileStream{ fd, true }; diff --git a/src/libs/audio/include/audio/AudioProperties.hpp b/src/libs/audio/include/audio/AudioProperties.hpp new file mode 100644 index 00000000..2b70ee7f --- /dev/null +++ b/src/libs/audio/include/audio/AudioProperties.hpp @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2025 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#pragma once + +#include +#include + +#include "core/media/CodecType.hpp" +#include "core/media/ContainerType.hpp" + +namespace lms::audio +{ + struct AudioProperties + { + core::media::ContainerType container; + core::media::CodecType codec; + std::chrono::milliseconds duration; + unsigned bitrate; + unsigned channelCount; + unsigned sampleRate; + std::optional bitsPerSample; + }; +} // namespace lms::audio \ No newline at end of file diff --git a/src/libs/audio/include/audio/Exception.hpp b/src/libs/audio/include/audio/Exception.hpp index 6c3c9e52..feaa9560 100644 --- a/src/libs/audio/include/audio/Exception.hpp +++ b/src/libs/audio/include/audio/Exception.hpp @@ -19,6 +19,7 @@ #pragma once +#include #include #include "core/Exception.hpp" @@ -34,15 +35,18 @@ namespace lms::audio class IOFileException : public Exception { public: - IOFileException(std::string_view message, std::error_code err) - : Exception{ std::string{ message } + ": " + err.message() } + IOFileException(const std::filesystem::path& path, std::string_view message, std::error_code err) + : Exception{ "File '" + path.string() + "': " + std::string{ message } + ": " + err.message() } + , _path{ path } , _err{ err } { } + const std::filesystem::path& getPath() const { return _path; } std::error_code getErrorCode() const { return _err; } private: + std::filesystem::path _path; std::error_code _err; }; diff --git a/src/libs/audio/include/audio/IImageReader.hpp b/src/libs/audio/include/audio/IImageReader.hpp index 0063151c..8d301b45 100644 --- a/src/libs/audio/include/audio/IImageReader.hpp +++ b/src/libs/audio/include/audio/IImageReader.hpp @@ -23,69 +23,18 @@ #include #include -#include "core/LiteralString.hpp" +#include "core/media/ImageType.hpp" namespace lms::audio { struct Image { - // See TagLib types (based on ID3v2 APIC types) - enum class Type - { - // No information - Unknown, - // A type not enumerated below - Other, - // 32x32 PNG image that should be used as the file icon - FileIcon, - // File icon of a different size or format - OtherFileIcon, - // Front cover image of the album - FrontCover, - // Back cover image of the album - BackCover, - // Inside leaflet page of the album - LeafletPage, - // Image from the album itself - Media, - // Picture of the lead artist or soloist - LeadArtist, - // Picture of the artist or performer - Artist, - // Picture of the conductor - Conductor, - // Picture of the band or orchestra - Band, - // Picture of the composer - Composer, - // Picture of the lyricist or text writer - Lyricist, - // Picture of the recording location or studio - RecordingLocation, - // Picture of the artists during recording - DuringRecording, - // Picture of the artists during performance - DuringPerformance, - // Picture from a movie or video related to the track - MovieScreenCapture, - // Picture of a large, coloured fish - ColouredFish, - // Illustration related to the track - Illustration, - // Logo of the band or performer - BandLogo, - // Logo of the publisher (record company) - PublisherLogo - }; - - Type type{ Type::Unknown }; + core::media::ImageType type{ core::media::ImageType::Unknown }; std::string mimeType{ "application/octet-stream" }; std::string description; std::span data; }; - core::LiteralString imageTypeToString(Image::Type type); - class IImageReader { public: diff --git a/src/libs/audio/include/audio/TranscodeTypes.hpp b/src/libs/audio/include/audio/TranscodeTypes.hpp index 1aaf34cf..a2324646 100644 --- a/src/libs/audio/include/audio/TranscodeTypes.hpp +++ b/src/libs/audio/include/audio/TranscodeTypes.hpp @@ -23,28 +23,30 @@ #include #include +#include "core/media/CodecType.hpp" +#include "core/media/ContainerType.hpp" + +#include "audio/AudioProperties.hpp" + namespace lms::audio { - // TODO deprecate? - enum class OutputFormat - { - MP3, - OGG_OPUS, - MATROSKA_OPUS, - OGG_VORBIS, - WEBM_VORBIS, - }; - struct TranscodeInputParameters { std::filesystem::path filePath; - std::chrono::milliseconds duration{}; // Duration of the audio file - std::chrono::milliseconds offset{}; // Offset in the audio file to start transcoding from + AudioProperties audioProperties; // properties of the audio file + std::chrono::milliseconds offset{}; // Offset in the audio file to start transcoding from + }; + + struct TranscodeOutputFormat + { + core::media::ContainerType container; + core::media::CodecType codec; }; struct TranscodeOutputParameters { - std::optional format; + // Not setting a value here means to use the same value as the input + std::optional format; std::optional bitrate; std::optional bitsPerSample; std::optional channelCount; diff --git a/src/libs/core/CMakeLists.txt b/src/libs/core/CMakeLists.txt index d25b4264..0ddfff3a 100644 --- a/src/libs/core/CMakeLists.txt +++ b/src/libs/core/CMakeLists.txt @@ -13,6 +13,10 @@ configure_file( add_library(lmscore STATIC impl/http/Client.cpp impl/http/SendQueue.cpp + impl/media/CodecType.cpp + impl/media/ContainerType.cpp + impl/media/ImageType.cpp + impl/media/MimeType.cpp impl/ArchiveZipper.cpp impl/ChildProcess.cpp impl/ChildProcessManager.cpp diff --git a/src/libs/audio/impl/AudioTypes.cpp b/src/libs/core/impl/media/CodecType.cpp similarity index 66% rename from src/libs/audio/impl/AudioTypes.cpp rename to src/libs/core/impl/media/CodecType.cpp index ad360bcb..b5b778f0 100644 --- a/src/libs/audio/impl/AudioTypes.cpp +++ b/src/libs/core/impl/media/CodecType.cpp @@ -17,45 +17,10 @@ * along with LMS. If not, see . */ -#include "audio/AudioTypes.hpp" +#include "core/media/CodecType.hpp" -namespace lms::audio +namespace lms::core::media { - core::LiteralString containerTypeToString(ContainerType type) - { - switch (type) - { - case ContainerType::AIFF: - return "AIFF"; - case ContainerType::APE: - return "APE"; - case ContainerType::ASF: - return "ASF"; - case ContainerType::DSF: - return "DSF"; - case ContainerType::FLAC: - return "FLAC"; - case ContainerType::MP4: - return "MP4"; - case ContainerType::MPC: - return "MPC"; - case ContainerType::MPEG: - return "MPEG"; - case ContainerType::Ogg: - return "Ogg"; - case ContainerType::Shorten: - return "Shorten"; - case ContainerType::TrueAudio: - return "TrueAudio"; - case ContainerType::WAV: - return "WAV"; - case ContainerType::WavPack: - return "WavPack"; - } - - return ""; - } - core::LiteralString codecTypeToString(CodecType type) { switch (type) @@ -104,6 +69,6 @@ namespace lms::audio return "WMA9Lossless"; } - return ""; + return "Unknown"; } -} // namespace lms::audio \ No newline at end of file +} // namespace lms::core::media \ No newline at end of file diff --git a/src/libs/core/impl/media/ContainerType.cpp b/src/libs/core/impl/media/ContainerType.cpp new file mode 100644 index 00000000..cf1fe447 --- /dev/null +++ b/src/libs/core/impl/media/ContainerType.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2025 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#include "core/media/ContainerType.hpp" + +namespace lms::core::media +{ + core::LiteralString containerTypeToString(ContainerType type) + { + switch (type) + { + case ContainerType::AIFF: + return "AIFF"; + case ContainerType::APE: + return "APE"; + case ContainerType::ASF: + return "ASF"; + case ContainerType::DSF: + return "DSF"; + case ContainerType::FLAC: + return "FLAC"; + case ContainerType::MP4: + return "MP4"; + case ContainerType::MPC: + return "MPC"; + case ContainerType::MPEG: + return "MPEG"; + case ContainerType::Ogg: + return "Ogg"; + case ContainerType::Shorten: + return "Shorten"; + case ContainerType::TrueAudio: + return "TrueAudio"; + case ContainerType::WAV: + return "WAV"; + case ContainerType::WavPack: + return "WavPack"; + } + + return "Unknown"; + } +} // namespace lms::core::media \ No newline at end of file diff --git a/src/libs/audio/impl/ImageReader.cpp b/src/libs/core/impl/media/ImageType.cpp similarity index 59% rename from src/libs/audio/impl/ImageReader.cpp rename to src/libs/core/impl/media/ImageType.cpp index 7b314875..894f1150 100644 --- a/src/libs/audio/impl/ImageReader.cpp +++ b/src/libs/core/impl/media/ImageType.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Emeric Poupon + * Copyright (C) 2025 Emeric Poupon * * This file is part of LMS. * @@ -17,61 +17,60 @@ * along with LMS. If not, see . */ -#include "audio/IImageReader.hpp" +#include "core/media/ImageType.hpp" -namespace lms::audio +namespace lms::core::media { - core::LiteralString imageTypeToString(Image::Type type) + core::LiteralString imageTypeToString(ImageType type) { switch (type) { - case Image::Type::Other: + case ImageType::Other: return "Other"; - case Image::Type::FileIcon: + case ImageType::FileIcon: return "FileIcon"; - case Image::Type::OtherFileIcon: + case ImageType::OtherFileIcon: return "OtherFileIcon"; - case Image::Type::FrontCover: + case ImageType::FrontCover: return "FrontCover"; - case Image::Type::BackCover: + case ImageType::BackCover: return "BackCover"; - case Image::Type::LeafletPage: + case ImageType::LeafletPage: return "LeafletPage"; - case Image::Type::Media: + case ImageType::Media: return "Media"; - case Image::Type::LeadArtist: + case ImageType::LeadArtist: return "LeadArtist"; - case Image::Type::Artist: + case ImageType::Artist: return "Artist"; - case Image::Type::Conductor: + case ImageType::Conductor: return "Conductor"; - case Image::Type::Band: + case ImageType::Band: return "Band"; - case Image::Type::Composer: + case ImageType::Composer: return "Composer"; - case Image::Type::Lyricist: + case ImageType::Lyricist: return "Lyricist"; - case Image::Type::RecordingLocation: + case ImageType::RecordingLocation: return "RecordingLocation"; - case Image::Type::DuringRecording: + case ImageType::DuringRecording: return "DuringRecording"; - case Image::Type::DuringPerformance: + case ImageType::DuringPerformance: return "DuringPerformance"; - case Image::Type::MovieScreenCapture: + case ImageType::MovieScreenCapture: return "MovieScreenCapture"; - case Image::Type::ColouredFish: + case ImageType::ColouredFish: return "ColouredFish"; - case Image::Type::Illustration: + case ImageType::Illustration: return "Illustration"; - case Image::Type::BandLogo: + case ImageType::BandLogo: return "BandLogo"; - case Image::Type::PublisherLogo: + case ImageType::PublisherLogo: return "PublisherLogo"; - case Image::Type::Unknown: + case ImageType::Unknown: break; } return "Unknown"; } - -} // namespace lms::audio \ No newline at end of file +} // namespace lms::core::media \ No newline at end of file diff --git a/src/libs/core/impl/media/MimeType.cpp b/src/libs/core/impl/media/MimeType.cpp new file mode 100644 index 00000000..aa8d1d1f --- /dev/null +++ b/src/libs/core/impl/media/MimeType.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2025 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#include "core/media/MimeType.hpp" + +namespace lms::core::media +{ + core::LiteralString getMimeType(ContainerType container, CodecType codec) + { + switch (container) + { + case ContainerType::AIFF: + return "audio/x-aiff"; + case ContainerType::APE: + return "audio/x-monkeys-audio"; + case ContainerType::ASF: + return "audio/x-ms-wma"; + case ContainerType::DSF: + return "audio/x-dsd-dsf"; + case ContainerType::FLAC: + return "audio/flac"; + case ContainerType::MP4: + switch (codec) + { + case CodecType::AAC: + return "audio/mp4; codecs=\"mp4a.40.2\""; + case CodecType::ALAC: + return "audio/mp4; codecs=\"alac\""; + case CodecType::MP4ALS: + return "audio/mp4; codecs=\"mp4als\""; + default: + return "audio/mp4"; + } + + case ContainerType::MPC: + return "audio/x-musepack"; + case ContainerType::MPEG: + return "audio/mpeg"; + case ContainerType::Ogg: + switch (codec) + { + case CodecType::Opus: + return "audio/opus"; + case CodecType::Vorbis: + return "audio/ogg; codecs=\"vorbis\""; + case CodecType::FLAC: + return "audio/ogg; codecs=\"flac\""; + default: + return "audio/ogg"; + } + + case ContainerType::Shorten: + return "audio/x-shn"; + case ContainerType::TrueAudio: + return "audio/x-tta"; + case ContainerType::WAV: + return "audio/wav"; + case ContainerType::WavPack: + return "audio/x-wavpack"; + } + + return "application/octet-stream"; + } +} // namespace lms::core::media \ No newline at end of file diff --git a/src/libs/audio/include/audio/AudioTypes.hpp b/src/libs/core/include/core/media/CodecType.hpp similarity index 65% rename from src/libs/audio/include/audio/AudioTypes.hpp rename to src/libs/core/include/core/media/CodecType.hpp index 952f9c14..dac67e6d 100644 --- a/src/libs/audio/include/audio/AudioTypes.hpp +++ b/src/libs/core/include/core/media/CodecType.hpp @@ -19,32 +19,10 @@ #pragma once -#include -#include - #include "core/LiteralString.hpp" -namespace lms::audio +namespace lms::core::media { - enum class ContainerType - { - AIFF, - APE, // Monkey's Audio - ASF, // Advanced Systems Format - DSF, - FLAC, - MP4, - MPC, // Musepack - MPEG, - Ogg, - Shorten, - TrueAudio, - WAV, - WavPack, - }; - - core::LiteralString containerTypeToString(ContainerType type); - enum class CodecType { AAC, @@ -71,15 +49,4 @@ namespace lms::audio }; core::LiteralString codecTypeToString(CodecType type); - - struct AudioProperties - { - ContainerType container; - CodecType codec; - std::chrono::milliseconds duration; - unsigned bitrate; - unsigned channelCount; - unsigned sampleRate; - std::optional bitsPerSample; - }; -} // namespace lms::audio \ No newline at end of file +} // namespace lms::core::media \ No newline at end of file diff --git a/src/libs/core/include/core/media/ContainerType.hpp b/src/libs/core/include/core/media/ContainerType.hpp new file mode 100644 index 00000000..709d8203 --- /dev/null +++ b/src/libs/core/include/core/media/ContainerType.hpp @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2025 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#pragma once + +#include "core/LiteralString.hpp" + +namespace lms::core::media +{ + enum class ContainerType + { + AIFF, + APE, // Monkey's Audio + ASF, // Advanced Systems Format + DSF, + FLAC, + MP4, + MPC, // Musepack + MPEG, + Ogg, + Shorten, + TrueAudio, + WAV, + WavPack, + }; + + core::LiteralString containerTypeToString(ContainerType type); +} // namespace lms::core::media \ No newline at end of file diff --git a/src/libs/core/include/core/media/ImageType.hpp b/src/libs/core/include/core/media/ImageType.hpp new file mode 100644 index 00000000..de710938 --- /dev/null +++ b/src/libs/core/include/core/media/ImageType.hpp @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2025 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#pragma once + +#include "core/LiteralString.hpp" + +namespace lms::core::media +{ + // Based on ID3v2 APIC types + enum class ImageType + { + // No information + Unknown, + // A type not enumerated below + Other, + // 32x32 PNG image that should be used as the file icon + FileIcon, + // File icon of a different size or format + OtherFileIcon, + // Front cover image of the album + FrontCover, + // Back cover image of the album + BackCover, + // Inside leaflet page of the album + LeafletPage, + // Image from the album itself + Media, + // Picture of the lead artist or soloist + LeadArtist, + // Picture of the artist or performer + Artist, + // Picture of the conductor + Conductor, + // Picture of the band or orchestra + Band, + // Picture of the composer + Composer, + // Picture of the lyricist or text writer + Lyricist, + // Picture of the recording location or studio + RecordingLocation, + // Picture of the artists during recording + DuringRecording, + // Picture of the artists during performance + DuringPerformance, + // Picture from a movie or video related to the track + MovieScreenCapture, + // Picture of a large, coloured fish + ColouredFish, + // Illustration related to the track + Illustration, + // Logo of the band or performer + BandLogo, + // Logo of the publisher (record company) + PublisherLogo + }; + + core::LiteralString imageTypeToString(ImageType type); +} // namespace lms::core::media \ No newline at end of file diff --git a/src/libs/core/include/core/media/MimeType.hpp b/src/libs/core/include/core/media/MimeType.hpp new file mode 100644 index 00000000..319d70de --- /dev/null +++ b/src/libs/core/include/core/media/MimeType.hpp @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2025 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#pragma once + +#include "core/LiteralString.hpp" +#include "core/media/CodecType.hpp" +#include "core/media/ContainerType.hpp" + +namespace lms::core::media +{ + core::LiteralString getMimeType(ContainerType container, CodecType codec); +} \ No newline at end of file diff --git a/src/libs/database/CMakeLists.txt b/src/libs/database/CMakeLists.txt index c896269e..2ba86032 100644 --- a/src/libs/database/CMakeLists.txt +++ b/src/libs/database/CMakeLists.txt @@ -1,4 +1,5 @@ add_library(lmsdatabase STATIC + impl/objects/detail/Types.cpp impl/objects/Artist.cpp impl/objects/ArtistInfo.cpp impl/objects/Artwork.cpp diff --git a/src/libs/database/impl/objects/PodcastEpisode.cpp b/src/libs/database/impl/objects/PodcastEpisode.cpp index a0d52d91..a8aab5f2 100644 --- a/src/libs/database/impl/objects/PodcastEpisode.cpp +++ b/src/libs/database/impl/objects/PodcastEpisode.cpp @@ -27,6 +27,7 @@ #include "database/objects/Podcast.hpp" #include "Utils.hpp" +#include "detail/Types.hpp" #include "traits/IdTypeTraits.hpp" #include "traits/PathTraits.hpp" @@ -98,6 +99,16 @@ namespace lms::db utils::forEachQueryRangeResult(query, params.range, func); } + std::optional PodcastEpisode::getContainer() const + { + return detail::getMediaContainerType(_container); + } + + std::optional PodcastEpisode::getCodec() const + { + return detail::getMediaCodecType(_codec); + } + ObjectPtr PodcastEpisode::getArtwork() const { return _artwork; @@ -108,6 +119,16 @@ namespace lms::db return _artwork.id(); } + void PodcastEpisode::setContainer(core::media::ContainerType container) + { + _container = detail::getDbContainerType(container); + } + + void PodcastEpisode::setCodec(core::media::CodecType codec) + { + _codec = detail::getDbCodecType(codec); + } + void PodcastEpisode::setArtwork(ObjectPtr artwork) { _artwork = getDboPtr(artwork); diff --git a/src/libs/database/impl/objects/Release.cpp b/src/libs/database/impl/objects/Release.cpp index fab11b60..6f572a2c 100644 --- a/src/libs/database/impl/objects/Release.cpp +++ b/src/libs/database/impl/objects/Release.cpp @@ -40,6 +40,7 @@ #include "SqlQuery.hpp" #include "Utils.hpp" +#include "detail/Types.hpp" #include "traits/EnumSetTraits.hpp" #include "traits/IdTypeTraits.hpp" #include "traits/PartialDateTimeTraits.hpp" @@ -679,12 +680,20 @@ namespace lms::db return utils::fetchQuerySingleResult(session()->query("SELECT COALESCE(AVG(t.bitrate), 0) FROM track t").where("release_id = ?").bind(getId()).where("bitrate > 0")); } - std::vector Release::getCodecs() const + std::vector Release::getCodecs() const { assert(session()); // Get the codec ordered by frequency - return utils::fetchQueryResults(session()->query("SELECT t.codec FROM track t").where("release_id = ?").bind(getId()).groupBy("t.codec").orderBy("COUNT(t.id) DESC")); + auto query{ session()->query("SELECT t.codec FROM track t").where("release_id = ?").bind(getId()).groupBy("t.codec").orderBy("COUNT(t.id) DESC") }; + + std::vector res; + utils::forEachQueryResult(query, [&](detail::CodecType codec) { + if (const auto mediaCodecType{ detail::getMediaCodecType(codec) }) + res.push_back(*mediaCodecType); + }); + + return res; } std::vector Release::getArtists(TrackArtistLinkType linkType) const diff --git a/src/libs/database/impl/objects/Track.cpp b/src/libs/database/impl/objects/Track.cpp index d42dcf48..5ad089b2 100644 --- a/src/libs/database/impl/objects/Track.cpp +++ b/src/libs/database/impl/objects/Track.cpp @@ -23,6 +23,7 @@ #include #include "core/ILogger.hpp" + #include "database/Session.hpp" #include "database/Types.hpp" #include "database/objects/Artist.hpp" @@ -41,6 +42,7 @@ #include "SqlQuery.hpp" #include "Utils.hpp" +#include "objects/detail/Types.hpp" #include "traits/IdTypeTraits.hpp" #include "traits/PartialDateTimeTraits.hpp" #include "traits/PathTraits.hpp" @@ -523,6 +525,16 @@ namespace lms::db _absoluteFilePath = filePath; } + void Track::setContainer(core::media::ContainerType container) + { + _container = detail::getDbContainerType(container); + } + + void Track::setCodec(core::media::CodecType codec) + { + _codec = detail::getDbCodecType(codec); + } + void Track::setName(std::string_view name) { _name = std::string{ name, 0, _maxNameLength }; @@ -603,6 +615,16 @@ namespace lms::db _preferredMediaArtwork = getDboPtr(artwork); } + std::optional Track::getContainer() const + { + return detail::getMediaContainerType(_container); + } + + std::optional Track::getCodec() const + { + return detail::getMediaCodecType(_codec); + } + std::optional Track::getYear() const { return _date.getYear(); diff --git a/src/libs/database/impl/objects/TrackEmbeddedImage.cpp b/src/libs/database/impl/objects/TrackEmbeddedImage.cpp index c6866f13..507ca4bb 100644 --- a/src/libs/database/impl/objects/TrackEmbeddedImage.cpp +++ b/src/libs/database/impl/objects/TrackEmbeddedImage.cpp @@ -28,6 +28,8 @@ #include "database/objects/TrackEmbeddedImageLink.hpp" #include "Utils.hpp" +#include "detail/Types.hpp" +#include "objects/detail/Types.hpp" #include "traits/IdTypeTraits.hpp" #include "traits/ImageHashTypeTraits.hpp" @@ -83,7 +85,7 @@ namespace lms::db } if (params.imageType.has_value()) - query.where("t_e_i_l.type = ?").bind(params.imageType.value()); + query.where("t_e_i_l.type = ?").bind(detail::getDbImageType(params.imageType.value())); } switch (params.sortMethod) diff --git a/src/libs/database/impl/objects/TrackEmbeddedImageLink.cpp b/src/libs/database/impl/objects/TrackEmbeddedImageLink.cpp index e39b2965..4cd05a08 100644 --- a/src/libs/database/impl/objects/TrackEmbeddedImageLink.cpp +++ b/src/libs/database/impl/objects/TrackEmbeddedImageLink.cpp @@ -23,11 +23,11 @@ #include #include "database/Session.hpp" -#include "database/Types.hpp" #include "database/objects/Track.hpp" #include "database/objects/TrackEmbeddedImage.hpp" #include "Utils.hpp" +#include "detail/Types.hpp" #include "traits/IdTypeTraits.hpp" #include "traits/ImageHashTypeTraits.hpp" @@ -77,4 +77,14 @@ namespace lms::db { return _image; } + + core::media::ImageType TrackEmbeddedImageLink::getType() const + { + return detail::getMediaImageType(_type); + } + + void TrackEmbeddedImageLink::setType(core::media::ImageType type) + { + _type = detail::getDbImageType(type); + } } // namespace lms::db diff --git a/src/libs/database/impl/objects/Types.cpp b/src/libs/database/impl/objects/Types.cpp index 32388860..2ae8bede 100644 --- a/src/libs/database/impl/objects/Types.cpp +++ b/src/libs/database/impl/objects/Types.cpp @@ -72,96 +72,4 @@ namespace lms::db { return allowedAudioBitrates.find(bitrate) != std::cend(allowedAudioBitrates); } - - core::LiteralString containerTypeToString(ContainerType type) - { - switch (type) - { - case ContainerType::Unknown: - break; - - case ContainerType::AIFF: - return "AIFF"; - case ContainerType::APE: - return "APE"; - case ContainerType::ASF: - return "ASF"; - case ContainerType::DSF: - return "DSF"; - case ContainerType::FLAC: - return "FLAC"; - case ContainerType::MP4: - return "MP4"; - case ContainerType::MPC: - return "MPC"; - case ContainerType::MPEG: - return "MPEG"; - case ContainerType::Ogg: - return "Ogg"; - case ContainerType::Shorten: - return "Shorten"; - case ContainerType::TrueAudio: - return "TrueAudio"; - case ContainerType::WAV: - return "WAV"; - case ContainerType::WavPack: - return "WavPack"; - } - - return "Unknown"; - } - - core::LiteralString codecTypeToString(CodecType type) - { - switch (type) - { - case CodecType::Unknown: - break; - - case CodecType::AAC: - return "AAC"; - case CodecType::AC3: - return "AC3"; - case CodecType::ALAC: - return "ALAC"; - case CodecType::APE: - return "APE"; - case CodecType::DSD: - return "DSD"; - case CodecType::EAC3: - return "EAC3"; - case CodecType::FLAC: - return "FLAC"; - case CodecType::MP3: - return "MP3"; - case CodecType::MP4ALS: - return "MP4ALS"; - case CodecType::MPC7: - return "MPC7"; - case CodecType::MPC8: - return "MPC8"; - case CodecType::Opus: - return "Opus"; - case CodecType::PCM: - return "PCM"; - case CodecType::Shorten: - return "Shorten"; - case CodecType::TrueAudio: - return "TrueAudio"; - case CodecType::Vorbis: - return "Vorbis"; - case CodecType::WavPack: - return "WavPack"; - case CodecType::WMA1: - return "WMA1"; - case CodecType::WMA2: - return "WMA2"; - case CodecType::WMA9Pro: - return "WMA9Pro"; - case CodecType::WMA9Lossless: - return "WMA9Lossless"; - } - - return "Unknown"; - } } // namespace lms::db diff --git a/src/libs/database/impl/objects/detail/Types.cpp b/src/libs/database/impl/objects/detail/Types.cpp new file mode 100644 index 00000000..aa2573ed --- /dev/null +++ b/src/libs/database/impl/objects/detail/Types.cpp @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2015 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#include "Types.hpp" + +namespace lms::db::detail +{ + std::optional getMediaContainerType(db::detail::ContainerType container) + { + switch (container) + { + case ContainerType::AIFF: + return core::media::ContainerType::AIFF; + case ContainerType::APE: + return core::media::ContainerType::APE; + case ContainerType::ASF: + return core::media::ContainerType::ASF; + case ContainerType::DSF: + return core::media::ContainerType::DSF; + case ContainerType::FLAC: + return core::media::ContainerType::FLAC; + case ContainerType::MP4: + return core::media::ContainerType::MP4; + case ContainerType::MPC: + return core::media::ContainerType::MPC; + case ContainerType::MPEG: + return core::media::ContainerType::MPEG; + case ContainerType::Ogg: + return core::media::ContainerType::Ogg; + case ContainerType::Shorten: + return core::media::ContainerType::Shorten; + case ContainerType::TrueAudio: + return core::media::ContainerType::TrueAudio; + case ContainerType::WAV: + return core::media::ContainerType::WAV; + case ContainerType::WavPack: + return core::media::ContainerType::WavPack; + + case ContainerType::Unknown: + break; + } + + return std::nullopt; + } + + db::detail::ContainerType getDbContainerType(core::media::ContainerType container) + { + switch (container) + { + case core::media::ContainerType::AIFF: + return ContainerType::AIFF; + case core::media::ContainerType::APE: + return ContainerType::APE; + case core::media::ContainerType::ASF: + return ContainerType::ASF; + case core::media::ContainerType::DSF: + return ContainerType::DSF; + case core::media::ContainerType::FLAC: + return ContainerType::FLAC; + case core::media::ContainerType::MP4: + return ContainerType::MP4; + case core::media::ContainerType::MPC: + return ContainerType::MPC; + case core::media::ContainerType::MPEG: + return ContainerType::MPEG; + case core::media::ContainerType::Ogg: + return ContainerType::Ogg; + case core::media::ContainerType::Shorten: + return ContainerType::Shorten; + case core::media::ContainerType::TrueAudio: + return ContainerType::TrueAudio; + case core::media::ContainerType::WAV: + return ContainerType::WAV; + case core::media::ContainerType::WavPack: + return ContainerType::WavPack; + } + + return ContainerType::Unknown; + } + + std::optional getMediaCodecType(db::detail::CodecType codec) + { + switch (codec) + { + case CodecType::AAC: + return core::media::CodecType::AAC; + case CodecType::AC3: + return core::media::CodecType::AC3; + case CodecType::ALAC: + return core::media::CodecType::ALAC; + case CodecType::APE: + return core::media::CodecType::APE; + case CodecType::DSD: + return core::media::CodecType::DSD; + case CodecType::EAC3: + return core::media::CodecType::EAC3; + case CodecType::FLAC: + return core::media::CodecType::FLAC; + case CodecType::MP3: + return core::media::CodecType::MP3; + case CodecType::MP4ALS: + return core::media::CodecType::MP4ALS; + case CodecType::MPC7: + return core::media::CodecType::MPC7; + case CodecType::MPC8: + return core::media::CodecType::MPC8; + case CodecType::Opus: + return core::media::CodecType::Opus; + case CodecType::PCM: + return core::media::CodecType::PCM; + case CodecType::Shorten: + return core::media::CodecType::Shorten; + case CodecType::TrueAudio: + return core::media::CodecType::TrueAudio; + case CodecType::Vorbis: + return core::media::CodecType::Vorbis; + case CodecType::WavPack: + return core::media::CodecType::WavPack; + case CodecType::WMA1: + return core::media::CodecType::WMA1; + case CodecType::WMA2: + return core::media::CodecType::WMA2; + case CodecType::WMA9Pro: + return core::media::CodecType::WMA9Pro; + case CodecType::WMA9Lossless: + return core::media::CodecType::WMA9Lossless; + + case CodecType::Unknown: + break; + } + + return std::nullopt; + } + + db::detail::CodecType getDbCodecType(core::media::CodecType codec) + { + switch (codec) + { + case core::media::CodecType::AAC: + return CodecType::AAC; + case core::media::CodecType::AC3: + return CodecType::AC3; + case core::media::CodecType::ALAC: + return CodecType::ALAC; + case core::media::CodecType::APE: + return CodecType::APE; + case core::media::CodecType::DSD: + return CodecType::DSD; + case core::media::CodecType::EAC3: + return CodecType::EAC3; + case core::media::CodecType::FLAC: + return CodecType::FLAC; + case core::media::CodecType::MP3: + return CodecType::MP3; + case core::media::CodecType::MP4ALS: + return CodecType::MP4ALS; + case core::media::CodecType::MPC7: + return CodecType::MPC7; + case core::media::CodecType::MPC8: + return CodecType::MPC8; + case core::media::CodecType::Opus: + return CodecType::Opus; + case core::media::CodecType::PCM: + return CodecType::PCM; + case core::media::CodecType::Shorten: + return CodecType::Shorten; + case core::media::CodecType::TrueAudio: + return CodecType::TrueAudio; + case core::media::CodecType::Vorbis: + return CodecType::Vorbis; + case core::media::CodecType::WavPack: + return CodecType::WavPack; + case core::media::CodecType::WMA1: + return CodecType::WMA1; + case core::media::CodecType::WMA2: + return CodecType::WMA2; + case core::media::CodecType::WMA9Pro: + return CodecType::WMA9Pro; + case core::media::CodecType::WMA9Lossless: + return CodecType::WMA9Lossless; + } + + return CodecType::Unknown; + } + + core::media::ImageType getMediaImageType(db::detail::ImageType type) + { + switch (type) + { + case db::detail::ImageType::Unknown: + return core::media::ImageType::Unknown; + case db::detail::ImageType::Other: + return core::media::ImageType::Other; + case db::detail::ImageType::FileIcon: + return core::media::ImageType::FileIcon; + case db::detail::ImageType::OtherFileIcon: + return core::media::ImageType::OtherFileIcon; + case db::detail::ImageType::FrontCover: + return core::media::ImageType::FrontCover; + case db::detail::ImageType::BackCover: + return core::media::ImageType::BackCover; + case db::detail::ImageType::LeafletPage: + return core::media::ImageType::LeafletPage; + case db::detail::ImageType::Media: + return core::media::ImageType::Media; + case db::detail::ImageType::LeadArtist: + return core::media::ImageType::LeadArtist; + case db::detail::ImageType::Artist: + return core::media::ImageType::Artist; + case db::detail::ImageType::Conductor: + return core::media::ImageType::Conductor; + case db::detail::ImageType::Band: + return core::media::ImageType::Band; + case db::detail::ImageType::Composer: + return core::media::ImageType::Composer; + case db::detail::ImageType::Lyricist: + return core::media::ImageType::Lyricist; + case db::detail::ImageType::RecordingLocation: + return core::media::ImageType::RecordingLocation; + case db::detail::ImageType::DuringRecording: + return core::media::ImageType::DuringRecording; + case db::detail::ImageType::DuringPerformance: + return core::media::ImageType::DuringPerformance; + case db::detail::ImageType::MovieScreenCapture: + return core::media::ImageType::MovieScreenCapture; + case db::detail::ImageType::ColouredFish: + return core::media::ImageType::ColouredFish; + case db::detail::ImageType::Illustration: + return core::media::ImageType::Illustration; + case db::detail::ImageType::BandLogo: + return core::media::ImageType::BandLogo; + case db::detail::ImageType::PublisherLogo: + return core::media::ImageType::PublisherLogo; + } + + return core::media::ImageType::Unknown; + } + + db::detail::ImageType getDbImageType(core::media::ImageType type) + { + switch (type) + { + case core::media::ImageType::Unknown: + return db::detail::ImageType::Unknown; + case core::media::ImageType::Other: + return db::detail::ImageType::Other; + case core::media::ImageType::FileIcon: + return db::detail::ImageType::FileIcon; + case core::media::ImageType::OtherFileIcon: + return db::detail::ImageType::OtherFileIcon; + case core::media::ImageType::FrontCover: + return db::detail::ImageType::FrontCover; + case core::media::ImageType::BackCover: + return db::detail::ImageType::BackCover; + case core::media::ImageType::LeafletPage: + return db::detail::ImageType::LeafletPage; + case core::media::ImageType::Media: + return db::detail::ImageType::Media; + case core::media::ImageType::LeadArtist: + return db::detail::ImageType::LeadArtist; + case core::media::ImageType::Artist: + return db::detail::ImageType::Artist; + case core::media::ImageType::Conductor: + return db::detail::ImageType::Conductor; + case core::media::ImageType::Band: + return db::detail::ImageType::Band; + case core::media::ImageType::Composer: + return db::detail::ImageType::Composer; + case core::media::ImageType::Lyricist: + return db::detail::ImageType::Lyricist; + case core::media::ImageType::RecordingLocation: + return db::detail::ImageType::RecordingLocation; + case core::media::ImageType::DuringRecording: + return db::detail::ImageType::DuringRecording; + case core::media::ImageType::DuringPerformance: + return db::detail::ImageType::DuringPerformance; + case core::media::ImageType::MovieScreenCapture: + return db::detail::ImageType::MovieScreenCapture; + case core::media::ImageType::ColouredFish: + return db::detail::ImageType::ColouredFish; + case core::media::ImageType::Illustration: + return db::detail::ImageType::Illustration; + case core::media::ImageType::BandLogo: + return db::detail::ImageType::BandLogo; + case core::media::ImageType::PublisherLogo: + return db::detail::ImageType::PublisherLogo; + } + + return db::detail::ImageType::Unknown; + } +} // namespace lms::db::detail \ No newline at end of file diff --git a/src/libs/database/impl/objects/detail/Types.hpp b/src/libs/database/impl/objects/detail/Types.hpp new file mode 100644 index 00000000..eed0b5e4 --- /dev/null +++ b/src/libs/database/impl/objects/detail/Types.hpp @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2015 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#pragma once + +#include + +#include "core/media/CodecType.hpp" +#include "core/media/ContainerType.hpp" +#include "core/media/ImageType.hpp" + +#include "database/objects/detail/Types.hpp" + +namespace lms::db::detail +{ + std::optional getMediaContainerType(db::detail::ContainerType container); + db::detail::ContainerType getDbContainerType(core::media::ContainerType container); + + std::optional getMediaCodecType(db::detail::CodecType codec); + db::detail::CodecType getDbCodecType(core::media::CodecType codec); + + core::media::ImageType getMediaImageType(db::detail::ImageType type); + db::detail::ImageType getDbImageType(core::media::ImageType type); +} // namespace lms::db::detail \ No newline at end of file diff --git a/src/libs/database/include/database/objects/PodcastEpisode.hpp b/src/libs/database/include/database/objects/PodcastEpisode.hpp index 0f1dc25c..7911375c 100644 --- a/src/libs/database/include/database/objects/PodcastEpisode.hpp +++ b/src/libs/database/include/database/objects/PodcastEpisode.hpp @@ -29,12 +29,16 @@ #include #include +#include "core/media/CodecType.hpp" +#include "core/media/ContainerType.hpp" + #include "database/Object.hpp" #include "database/Types.hpp" #include "database/objects/ArtworkId.hpp" #include "database/objects/PodcastEpisodeId.hpp" #include "database/objects/PodcastId.hpp" #include "database/objects/Types.hpp" +#include "database/objects/detail/Types.hpp" namespace lms::db { @@ -93,8 +97,8 @@ namespace lms::db // Audio properties std::chrono::milliseconds getDuration() const { return _duration; } - ContainerType getContainer() const { return _container; } - CodecType getCodec() const { return _codec; } + std::optional getContainer() const; + std::optional getCodec() const; std::size_t getBitrate() const { return _bitrate; } std::size_t getChannelCount() const { return _channelCount; } std::size_t getSampleRate() const { return _sampleRate; } @@ -124,8 +128,8 @@ namespace lms::db // Audio properties void setDuration(std::chrono::milliseconds duration) { _duration = duration; } - void setContainer(ContainerType container) { _container = container; } - void setCodec(CodecType codec) { _codec = codec; } + void setContainer(core::media::ContainerType container); + void setCodec(core::media::CodecType codec); void setBitrate(std::size_t bitrate) { _bitrate = bitrate; } void setChannelCount(std::size_t channelCount) { _channelCount = channelCount; } void setSampleRate(std::size_t sampleRate) { _sampleRate = sampleRate; } @@ -189,8 +193,8 @@ namespace lms::db // Audio properties std::chrono::duration _duration{ 0 }; - ContainerType _container{ ContainerType::Unknown }; - CodecType _codec{ CodecType::Unknown }; + detail::ContainerType _container{ detail::ContainerType::Unknown }; + detail::CodecType _codec{ detail::CodecType::Unknown }; int _bitrate{}; // in bps int _channelCount{}; int _sampleRate{}; diff --git a/src/libs/database/include/database/objects/Release.hpp b/src/libs/database/include/database/objects/Release.hpp index f8b8a3d8..b2a078e4 100644 --- a/src/libs/database/include/database/objects/Release.hpp +++ b/src/libs/database/include/database/objects/Release.hpp @@ -31,6 +31,7 @@ #include "core/EnumSet.hpp" #include "core/PartialDateTime.hpp" #include "core/UUID.hpp" +#include "core/media/CodecType.hpp" #include "database/IdRange.hpp" #include "database/Object.hpp" @@ -279,7 +280,7 @@ namespace lms::db std::optional getCopyright() const; std::optional getCopyrightURL() const; std::size_t getMeanBitrate() const; - std::vector getCodecs() const; + std::vector getCodecs() const; // Accessors std::string_view getName() const { return _name; } diff --git a/src/libs/database/include/database/objects/Track.hpp b/src/libs/database/include/database/objects/Track.hpp index 88f7e95f..b1d2d38a 100644 --- a/src/libs/database/include/database/objects/Track.hpp +++ b/src/libs/database/include/database/objects/Track.hpp @@ -34,6 +34,8 @@ #include "core/EnumSet.hpp" #include "core/PartialDateTime.hpp" #include "core/UUID.hpp" +#include "core/media/CodecType.hpp" +#include "core/media/ContainerType.hpp" #include "database/IdRange.hpp" #include "database/Object.hpp" @@ -51,6 +53,7 @@ #include "database/objects/TrackListId.hpp" #include "database/objects/Types.hpp" #include "database/objects/UserId.hpp" +#include "database/objects/detail/Types.hpp" namespace lms::db { @@ -229,8 +232,8 @@ namespace lms::db // Audio properties void setDuration(std::chrono::milliseconds duration) { _duration = duration; } - void setContainer(ContainerType container) { _container = container; } - void setCodec(CodecType codec) { _codec = codec; } + void setContainer(core::media::ContainerType container); + void setCodec(core::media::CodecType codec); void setBitrate(std::size_t bitrate) { _bitrate = bitrate; } void setChannelCount(std::size_t channelCount) { _channelCount = channelCount; } void setSampleRate(std::size_t sampleRate) { _sampleRate = sampleRate; } @@ -275,8 +278,8 @@ namespace lms::db // Audio properties std::chrono::milliseconds getDuration() const { return _duration; } - ContainerType getContainer() const { return _container; } - CodecType getCodec() const { return _codec; } + std::optional getContainer() const; + std::optional getCodec() const; std::size_t getBitrate() const { return _bitrate; } std::size_t getChannelCount() const { return _channelCount; } std::size_t getSampleRate() const { return _sampleRate; } @@ -380,8 +383,8 @@ namespace lms::db // Audio properties std::chrono::duration _duration{}; - ContainerType _container{ ContainerType::Unknown }; - CodecType _codec{ CodecType::Unknown }; + detail::ContainerType _container{ detail::ContainerType::Unknown }; + detail::CodecType _codec{ detail::CodecType::Unknown }; int _bitrate{}; // in bps int _channelCount{}; int _sampleRate{}; diff --git a/src/libs/database/include/database/objects/TrackEmbeddedImage.hpp b/src/libs/database/include/database/objects/TrackEmbeddedImage.hpp index 4237ec84..2c4bf49f 100644 --- a/src/libs/database/include/database/objects/TrackEmbeddedImage.hpp +++ b/src/libs/database/include/database/objects/TrackEmbeddedImage.hpp @@ -26,6 +26,8 @@ #include #include +#include "core/media/ImageType.hpp" + #include "database/Object.hpp" #include "database/Types.hpp" #include "database/objects/MediumId.hpp" @@ -53,7 +55,7 @@ namespace lms::db ReleaseId release; MediumId medium; TrackListId trackList; - std::optional imageType; + std::optional imageType; TrackEmbeddedImageSortMethod sortMethod{ TrackEmbeddedImageSortMethod::None }; FindParameters& setRange(std::optional _range) @@ -81,7 +83,7 @@ namespace lms::db trackList = _trackList; return *this; } - FindParameters& setImageType(std::optional _imageType) + FindParameters& setImageType(std::optional _imageType) { imageType = _imageType; return *this; diff --git a/src/libs/database/include/database/objects/TrackEmbeddedImageLink.hpp b/src/libs/database/include/database/objects/TrackEmbeddedImageLink.hpp index 8334616c..c99f3e0c 100644 --- a/src/libs/database/include/database/objects/TrackEmbeddedImageLink.hpp +++ b/src/libs/database/include/database/objects/TrackEmbeddedImageLink.hpp @@ -24,10 +24,13 @@ #include +#include "core/media/ImageType.hpp" + #include "database/Object.hpp" #include "database/objects/TrackEmbeddedImageId.hpp" #include "database/objects/TrackEmbeddedImageLinkId.hpp" #include "database/objects/Types.hpp" +#include "database/objects/detail/Types.hpp" namespace lms::db { @@ -49,12 +52,12 @@ namespace lms::db ObjectPtr getTrack() const; ObjectPtr getImage() const; std::size_t getIndex() const { return _index; } - ImageType getType() const { return _type; } + core::media::ImageType getType() const; std::string_view getDescription() const { return _description; } // setters void setIndex(std::size_t index) { _index = static_cast(index); } - void setType(ImageType type) { _type = type; } + void setType(core::media::ImageType type); void setDescription(std::string_view description) { _description = description; } template @@ -75,7 +78,7 @@ namespace lms::db static pointer create(Session& session, ObjectPtr track, ObjectPtr image); int _index{}; // index within the track - ImageType _type{ ImageType::Unknown }; + detail::ImageType _type{ detail::ImageType::Unknown }; std::string _description; Wt::Dbo::ptr _track; diff --git a/src/libs/database/include/database/objects/Types.hpp b/src/libs/database/include/database/objects/Types.hpp index 6cdbf15a..47953b83 100644 --- a/src/libs/database/include/database/objects/Types.hpp +++ b/src/libs/database/include/database/objects/Types.hpp @@ -155,32 +155,6 @@ namespace lms::db EmbeddedFirst, }; - enum class ImageType - { - Unknown = 0, - Other = 1, - FileIcon = 2, - OtherFileIcon = 3, - FrontCover = 4, - BackCover = 5, - LeafletPage = 6, - Media = 7, - LeadArtist = 8, - Artist = 9, - Conductor = 10, - Band = 11, - Composer = 12, - Lyricist = 13, - RecordingLocation = 14, - DuringRecording = 15, - DuringPerformance = 16, - MovieScreenCapture = 17, - ColouredFish = 18, - Illustration = 19, - BandLogo = 20, - PublisherLogo = 21 - }; - enum class TrackArtistLinkType { Artist = 0, // regular track artist @@ -204,8 +178,8 @@ namespace lms::db MP3 = 1, OGG_OPUS = 2, OGG_VORBIS = 3, - WEBM_VORBIS = 4, - MATROSKA_OPUS = 5, + // WEBM_VORBIS = 4, + // MATROSKA_OPUS = 5, }; using Bitrate = std::uint32_t; @@ -266,54 +240,4 @@ namespace lms::db Clean = 2, Explicit = 3, }; - - enum class ContainerType - { - Unknown = 0, - - AIFF = 1, - APE = 2, - ASF = 3, - DSF = 4, - FLAC = 5, - MP4 = 6, - MPC = 7, - MPEG = 8, - Ogg = 9, - Shorten = 10, - TrueAudio = 11, - WAV = 12, - WavPack = 13, - }; - - core::LiteralString containerTypeToString(ContainerType type); - - enum class CodecType - { - Unknown = 0, - - AAC = 1, - AC3 = 2, - ALAC = 3, - APE = 4, - DSD = 5, - EAC3 = 6, - FLAC = 7, - MP3 = 8, - MP4ALS = 9, - MPC7 = 10, - MPC8 = 11, - Opus = 12, - PCM = 13, - Shorten = 14, - TrueAudio = 15, - Vorbis = 16, - WavPack = 17, - WMA1 = 18, - WMA2 = 19, - WMA9Pro = 20, - WMA9Lossless = 21, - }; - - core::LiteralString codecTypeToString(CodecType type); } // namespace lms::db diff --git a/src/libs/database/include/database/objects/detail/Types.hpp b/src/libs/database/include/database/objects/detail/Types.hpp new file mode 100644 index 00000000..854a3c87 --- /dev/null +++ b/src/libs/database/include/database/objects/detail/Types.hpp @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2025 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#pragma once + +namespace lms::db::detail +{ + // For enums that have explicit values, never change enum values as they are stored in database. + // => only add new values or remove unused values, do not recycle values. + enum class ImageType + { + Unknown = 0, + Other = 1, + FileIcon = 2, + OtherFileIcon = 3, + FrontCover = 4, + BackCover = 5, + LeafletPage = 6, + Media = 7, + LeadArtist = 8, + Artist = 9, + Conductor = 10, + Band = 11, + Composer = 12, + Lyricist = 13, + RecordingLocation = 14, + DuringRecording = 15, + DuringPerformance = 16, + MovieScreenCapture = 17, + ColouredFish = 18, + Illustration = 19, + BandLogo = 20, + PublisherLogo = 21 + }; + + enum class ContainerType + { + Unknown = 0, + + AIFF = 1, + APE = 2, + ASF = 3, + DSF = 4, + FLAC = 5, + MP4 = 6, + MPC = 7, + MPEG = 8, + Ogg = 9, + Shorten = 10, + TrueAudio = 11, + WAV = 12, + WavPack = 13, + }; + + enum class CodecType + { + Unknown = 0, + + AAC = 1, + AC3 = 2, + ALAC = 3, + APE = 4, + DSD = 5, + EAC3 = 6, + FLAC = 7, + MP3 = 8, + MP4ALS = 9, + MPC7 = 10, + MPC8 = 11, + Opus = 12, + PCM = 13, + Shorten = 14, + TrueAudio = 15, + Vorbis = 16, + WavPack = 17, + WMA1 = 18, + WMA2 = 19, + WMA9Pro = 20, + WMA9Lossless = 21, + }; +} // namespace lms::db::detail diff --git a/src/libs/database/test/Release.cpp b/src/libs/database/test/Release.cpp index 54f1419c..4b2757a0 100644 --- a/src/libs/database/test/Release.cpp +++ b/src/libs/database/test/Release.cpp @@ -1229,22 +1229,22 @@ namespace lms::db::tests { auto transaction{ session.createWriteTransaction() }; - track1.get().modify()->setCodec(CodecType::FLAC); + track1.get().modify()->setCodec(core::media::CodecType::FLAC); track1.get().modify()->setRelease(release1.get()); - track2.get().modify()->setCodec(CodecType::MP3); + track2.get().modify()->setCodec(core::media::CodecType::MP3); track2.get().modify()->setRelease(release1.get()); - track3.get().modify()->setCodec(CodecType::MP3); + track3.get().modify()->setCodec(core::media::CodecType::MP3); track3.get().modify()->setRelease(release1.get()); - track4.get().modify()->setCodec(CodecType::AAC); + track4.get().modify()->setCodec(core::media::CodecType::AAC); track4.get().modify()->setRelease(release1.get()); } { auto transaction{ session.createReadTransaction() }; - EXPECT_EQ(release1->getCodecs(), (std::vector{ CodecType::MP3, CodecType::FLAC, CodecType::AAC })); + EXPECT_EQ(release1->getCodecs(), (std::vector{ core::media::CodecType::MP3, core::media::CodecType::FLAC, core::media::CodecType::AAC })); } } diff --git a/src/libs/database/test/TrackEmbeddedImage.cpp b/src/libs/database/test/TrackEmbeddedImage.cpp index 161e215c..61bcda1a 100644 --- a/src/libs/database/test/TrackEmbeddedImage.cpp +++ b/src/libs/database/test/TrackEmbeddedImage.cpp @@ -130,7 +130,7 @@ namespace lms::db::tests { auto transaction{ session.createWriteTransaction() }; - link.get().modify()->setType(ImageType::FrontCover); + link.get().modify()->setType(core::media::ImageType::FrontCover); } { @@ -173,6 +173,30 @@ namespace lms::db::tests TrackEmbeddedImage::find(session, params, [&](const auto&) { visited = true; }); EXPECT_TRUE(visited); } + + { + auto transaction{ session.createReadTransaction() }; + + TrackEmbeddedImage::FindParameters params; + params.setTrack(track.getId()); + params.setImageType(core::media::ImageType::FrontCover); + + bool visited{}; + TrackEmbeddedImage::find(session, params, [&](const auto&) { visited = true; }); + EXPECT_TRUE(visited); + } + + { + auto transaction{ session.createReadTransaction() }; + + TrackEmbeddedImage::FindParameters params; + params.setTrack(track.getId()); + params.setImageType(core::media::ImageType::BackCover); + + bool visited{}; + TrackEmbeddedImage::find(session, params, [&](const auto&) { visited = true; }); + EXPECT_FALSE(visited); + } } TEST_F(DatabaseFixture, TrackEmbeddedImage_findByParams_sorts) @@ -200,18 +224,18 @@ namespace lms::db::tests track1.get().modify()->setMedium(medium1.get()); track1.get().modify()->setTrackNumber(2); - link1.get().modify()->setType(ImageType::FrontCover); + link1.get().modify()->setType(core::media::ImageType::FrontCover); image1.get().modify()->setSize(750); - link2.get().modify()->setType(ImageType::Media); + link2.get().modify()->setType(core::media::ImageType::Media); image2.get().modify()->setSize(1000); - link3.get().modify()->setType(ImageType::Media); + link3.get().modify()->setType(core::media::ImageType::Media); image3.get().modify()->setSize(2000); track2.get().modify()->setRelease(release.get()); track2.get().modify()->setMedium(medium2.get()); track2.get().modify()->setTrackNumber(1); - link4.get().modify()->setType(ImageType::Media); + link4.get().modify()->setType(core::media::ImageType::Media); image4.get().modify()->setSize(1500); } @@ -220,7 +244,7 @@ namespace lms::db::tests TrackEmbeddedImage::FindParameters params; params.setRelease(release.getId()); - params.setImageType(ImageType::Media); + params.setImageType(core::media::ImageType::Media); params.setSortMethod(TrackEmbeddedImageSortMethod::SizeDesc); std::vector visitedIds; @@ -236,7 +260,7 @@ namespace lms::db::tests TrackEmbeddedImage::FindParameters params; params.setMedium(medium1.getId()); - params.setImageType(ImageType::Media); + params.setImageType(core::media::ImageType::Media); params.setSortMethod(TrackEmbeddedImageSortMethod::TrackNumberThenSizeDesc); std::vector visitedIds; @@ -251,7 +275,7 @@ namespace lms::db::tests TrackEmbeddedImage::FindParameters params; params.setRelease(release.getId()); - params.setImageType(ImageType::Media); + params.setImageType(core::media::ImageType::Media); params.setSortMethod(TrackEmbeddedImageSortMethod::DiscNumberThenTrackNumberThenSizeDesc); std::vector visitedIds; @@ -267,7 +291,7 @@ namespace lms::db::tests TrackEmbeddedImage::FindParameters params; params.setRelease(release.getId()); - params.setImageType(ImageType::BackCover); + params.setImageType(core::media::ImageType::BackCover); params.setSortMethod(TrackEmbeddedImageSortMethod::DiscNumberThenTrackNumberThenSizeDesc); std::vector visitedIds; @@ -383,7 +407,7 @@ namespace lms::db::tests const TrackEmbeddedImageLink::pointer link{ TrackEmbeddedImageLink::find(session, imageLink.getId()) }; ASSERT_NE(link, TrackEmbeddedImageLink::pointer{}); EXPECT_EQ(link->getIndex(), 0); - EXPECT_EQ(link->getType(), ImageType::Unknown); + EXPECT_EQ(link->getType(), core::media::ImageType::Unknown); EXPECT_EQ(link->getDescription(), ""); EXPECT_EQ(link->getTrack(), track.get()); EXPECT_EQ(link->getImage(), image.get()); @@ -395,7 +419,7 @@ namespace lms::db::tests TrackEmbeddedImageLink::pointer link{ TrackEmbeddedImageLink::find(session, imageLink.getId()) }; ASSERT_NE(link, TrackEmbeddedImage::pointer{}); link.modify()->setIndex(2); - link.modify()->setType(ImageType::FrontCover); + link.modify()->setType(core::media::ImageType::FrontCover); link.modify()->setDescription("MyDesc"); } @@ -405,7 +429,7 @@ namespace lms::db::tests const TrackEmbeddedImageLink::pointer img{ TrackEmbeddedImageLink::find(session, imageLink.getId()) }; ASSERT_NE(img, TrackEmbeddedImage::pointer{}); EXPECT_EQ(img->getIndex(), 2); - EXPECT_EQ(img->getType(), ImageType::FrontCover); + EXPECT_EQ(img->getType(), core::media::ImageType::FrontCover); EXPECT_EQ(img->getDescription(), "MyDesc"); } @@ -415,7 +439,7 @@ namespace lms::db::tests bool visited{}; TrackEmbeddedImageLink::find(session, image->getId(), [&](const TrackEmbeddedImageLink::pointer& link) { EXPECT_EQ(link->getIndex(), 2); - EXPECT_EQ(link->getType(), ImageType::FrontCover); + EXPECT_EQ(link->getType(), core::media::ImageType::FrontCover); EXPECT_EQ(link->getDescription(), "MyDesc"); EXPECT_EQ(link->getTrack(), track.get()); diff --git a/src/libs/services/podcast/impl/steps/DownloadEpisodesStep.cpp b/src/libs/services/podcast/impl/steps/DownloadEpisodesStep.cpp index 25b2ad7f..a3704984 100644 --- a/src/libs/services/podcast/impl/steps/DownloadEpisodesStep.cpp +++ b/src/libs/services/podcast/impl/steps/DownloadEpisodesStep.cpp @@ -26,7 +26,7 @@ #include "core/Service.hpp" #include "core/http/IClient.hpp" -#include "audio/AudioTypes.hpp" +#include "audio/AudioProperties.hpp" #include "audio/Exception.hpp" #include "audio/IAudioFileInfo.hpp" #include "audio/IAudioFileInfoParser.hpp" @@ -42,92 +42,6 @@ namespace lms::podcast { namespace { - db::ContainerType audioContainerToDbContainer(audio::ContainerType type) - { - switch (type) - { - case audio::ContainerType::AIFF: - return db::ContainerType::AIFF; - case audio::ContainerType::APE: - return db::ContainerType::APE; - case audio::ContainerType::ASF: - return db::ContainerType::ASF; - case audio::ContainerType::DSF: - return db::ContainerType::DSF; - case audio::ContainerType::FLAC: - return db::ContainerType::FLAC; - case audio::ContainerType::MP4: - return db::ContainerType::MP4; - case audio::ContainerType::MPC: - return db::ContainerType::MPC; - case audio::ContainerType::MPEG: - return db::ContainerType::MPEG; - case audio::ContainerType::Shorten: - return db::ContainerType::Shorten; - case audio::ContainerType::Ogg: - return db::ContainerType::Ogg; - case audio::ContainerType::TrueAudio: - return db::ContainerType::TrueAudio; - case audio::ContainerType::WAV: - return db::ContainerType::WAV; - case audio::ContainerType::WavPack: - return db::ContainerType::WavPack; - } - - return db::ContainerType::Unknown; - } - - db::CodecType audioCodecToDbCodec(audio::CodecType type) - { - switch (type) - { - case audio::CodecType::AAC: - return db::CodecType::AAC; - case audio::CodecType::AC3: - return db::CodecType::AC3; - case audio::CodecType::ALAC: - return db::CodecType::ALAC; - case audio::CodecType::APE: - return db::CodecType::APE; - case audio::CodecType::DSD: - return db::CodecType::DSD; - case audio::CodecType::EAC3: - return db::CodecType::EAC3; - case audio::CodecType::FLAC: - return db::CodecType::FLAC; - case audio::CodecType::MP3: - return db::CodecType::MP3; - case audio::CodecType::MP4ALS: - return db::CodecType::MP4ALS; - case audio::CodecType::MPC7: - return db::CodecType::MPC7; - case audio::CodecType::MPC8: - return db::CodecType::MPC8; - case audio::CodecType::Opus: - return db::CodecType::Opus; - case audio::CodecType::PCM: - return db::CodecType::PCM; - case audio::CodecType::Shorten: - return db::CodecType::Shorten; - case audio::CodecType::TrueAudio: - return db::CodecType::TrueAudio; - case audio::CodecType::Vorbis: - return db::CodecType::Vorbis; - case audio::CodecType::WavPack: - return db::CodecType::WavPack; - case audio::CodecType::WMA1: - return db::CodecType::WMA1; - case audio::CodecType::WMA2: - return db::CodecType::WMA2; - case audio::CodecType::WMA9Pro: - return db::CodecType::WMA9Pro; - case audio::CodecType::WMA9Lossless: - return db::CodecType::WMA9Lossless; - } - - return db::CodecType::Unknown; - } - void updateEpisode(db::Session& session, db::PodcastEpisodeId episodeId, const std::filesystem::path& relativeFilePath, const audio::AudioProperties& audioProperties) { auto transaction{ session.createWriteTransaction() }; @@ -139,8 +53,8 @@ namespace lms::podcast dbEpisode.modify()->setAudioRelativeFilePath(relativeFilePath); dbEpisode.modify()->setDuration(audioProperties.duration); - dbEpisode.modify()->setContainer(audioContainerToDbContainer(audioProperties.container)); - dbEpisode.modify()->setCodec(audioCodecToDbCodec(audioProperties.codec)); + dbEpisode.modify()->setContainer(audioProperties.container); + dbEpisode.modify()->setCodec(audioProperties.codec); dbEpisode.modify()->setBitrate(audioProperties.bitrate); dbEpisode.modify()->setChannelCount(audioProperties.channelCount); dbEpisode.modify()->setSampleRate(audioProperties.sampleRate); diff --git a/src/libs/services/scanner/impl/scanners/audiofile/AudioFileScanOperation.cpp b/src/libs/services/scanner/impl/scanners/audiofile/AudioFileScanOperation.cpp index b189e793..51e94eb1 100644 --- a/src/libs/services/scanner/impl/scanners/audiofile/AudioFileScanOperation.cpp +++ b/src/libs/services/scanner/impl/scanners/audiofile/AudioFileScanOperation.cpp @@ -28,12 +28,9 @@ #include "audio/Exception.hpp" #include "audio/IAudioFileInfo.hpp" #include "audio/IAudioFileInfoParser.hpp" -#include "image/Exception.hpp" -#include "image/Image.hpp" - +#include "audio/IImageReader.hpp" #include "database/IDb.hpp" #include "database/Session.hpp" -#include "database/Types.hpp" #include "database/objects/Artist.hpp" #include "database/objects/Artwork.hpp" #include "database/objects/Cluster.hpp" @@ -47,6 +44,8 @@ #include "database/objects/TrackEmbeddedImageLink.hpp" #include "database/objects/TrackFeatures.hpp" #include "database/objects/TrackLyrics.hpp" +#include "image/Exception.hpp" +#include "image/Image.hpp" #include "services/scanner/ScanErrors.hpp" @@ -296,145 +295,6 @@ namespace lms::scanner return dbLyrics; } - db::ImageType audioImageTypeToDbImageType(audio::Image::Type type) - { - switch (type) - { - case audio::Image::Type::Unknown: - return db::ImageType::Unknown; - case audio::Image::Type::Other: - return db::ImageType::Other; - case audio::Image::Type::FileIcon: - return db::ImageType::FileIcon; - case audio::Image::Type::OtherFileIcon: - return db::ImageType::OtherFileIcon; - case audio::Image::Type::FrontCover: - return db::ImageType::FrontCover; - case audio::Image::Type::BackCover: - return db::ImageType::BackCover; - case audio::Image::Type::LeafletPage: - return db::ImageType::LeafletPage; - case audio::Image::Type::Media: - return db::ImageType::Media; - case audio::Image::Type::LeadArtist: - return db::ImageType::LeadArtist; - case audio::Image::Type::Artist: - return db::ImageType::Artist; - case audio::Image::Type::Conductor: - return db::ImageType::Conductor; - case audio::Image::Type::Band: - return db::ImageType::Band; - case audio::Image::Type::Composer: - return db::ImageType::Composer; - case audio::Image::Type::Lyricist: - return db::ImageType::Lyricist; - case audio::Image::Type::RecordingLocation: - return db::ImageType::RecordingLocation; - case audio::Image::Type::DuringRecording: - return db::ImageType::DuringRecording; - case audio::Image::Type::DuringPerformance: - return db::ImageType::DuringPerformance; - case audio::Image::Type::MovieScreenCapture: - return db::ImageType::MovieScreenCapture; - case audio::Image::Type::ColouredFish: - return db::ImageType::ColouredFish; - case audio::Image::Type::Illustration: - return db::ImageType::Illustration; - case audio::Image::Type::BandLogo: - return db::ImageType::BandLogo; - case audio::Image::Type::PublisherLogo: - return db::ImageType::PublisherLogo; - } - - return db::ImageType::Unknown; - } - - db::ContainerType audioContainerToDbContainer(audio::ContainerType type) - { - switch (type) - { - case audio::ContainerType::AIFF: - return db::ContainerType::AIFF; - case audio::ContainerType::APE: - return db::ContainerType::APE; - case audio::ContainerType::ASF: - return db::ContainerType::ASF; - case audio::ContainerType::DSF: - return db::ContainerType::DSF; - case audio::ContainerType::FLAC: - return db::ContainerType::FLAC; - case audio::ContainerType::MP4: - return db::ContainerType::MP4; - case audio::ContainerType::MPC: - return db::ContainerType::MPC; - case audio::ContainerType::MPEG: - return db::ContainerType::MPEG; - case audio::ContainerType::Shorten: - return db::ContainerType::Shorten; - case audio::ContainerType::Ogg: - return db::ContainerType::Ogg; - case audio::ContainerType::TrueAudio: - return db::ContainerType::TrueAudio; - case audio::ContainerType::WAV: - return db::ContainerType::WAV; - case audio::ContainerType::WavPack: - return db::ContainerType::WavPack; - } - - return db::ContainerType::Unknown; - } - - db::CodecType audioCodecToDbCodec(audio::CodecType type) - { - switch (type) - { - case audio::CodecType::AAC: - return db::CodecType::AAC; - case audio::CodecType::AC3: - return db::CodecType::AC3; - case audio::CodecType::ALAC: - return db::CodecType::ALAC; - case audio::CodecType::APE: - return db::CodecType::APE; - case audio::CodecType::DSD: - return db::CodecType::DSD; - case audio::CodecType::EAC3: - return db::CodecType::EAC3; - case audio::CodecType::FLAC: - return db::CodecType::FLAC; - case audio::CodecType::MP3: - return db::CodecType::MP3; - case audio::CodecType::MP4ALS: - return db::CodecType::MP4ALS; - case audio::CodecType::MPC7: - return db::CodecType::MPC7; - case audio::CodecType::MPC8: - return db::CodecType::MPC8; - case audio::CodecType::Opus: - return db::CodecType::Opus; - case audio::CodecType::PCM: - return db::CodecType::PCM; - case audio::CodecType::Shorten: - return db::CodecType::Shorten; - case audio::CodecType::TrueAudio: - return db::CodecType::TrueAudio; - case audio::CodecType::Vorbis: - return db::CodecType::Vorbis; - case audio::CodecType::WavPack: - return db::CodecType::WavPack; - case audio::CodecType::WMA1: - return db::CodecType::WMA1; - case audio::CodecType::WMA2: - return db::CodecType::WMA2; - case audio::CodecType::WMA9Pro: - return db::CodecType::WMA9Pro; - case audio::CodecType::WMA9Lossless: - return db::CodecType::WMA9Lossless; - } - - return db::CodecType::Unknown; - } - db::TrackEmbeddedImage::pointer getOrCreateTrackEmbeddedImage(db::Session& session, const ImageInfo& imageInfo) { db::TrackEmbeddedImage::pointer image{ db::TrackEmbeddedImage::find(session, imageInfo.size, db::ImageHashType{ imageInfo.hash }) }; @@ -458,7 +318,7 @@ namespace lms::scanner const db::TrackEmbeddedImage::pointer image{ getOrCreateTrackEmbeddedImage(session, imageInfo) }; db::TrackEmbeddedImageLink::pointer imageLink{ session.create(dbTrack, image) }; imageLink.modify()->setIndex(imageInfo.index); - imageLink.modify()->setType(audioImageTypeToDbImageType(imageInfo.type)); + imageLink.modify()->setType(imageInfo.type); imageLink.modify()->setDescription(imageInfo.description); return imageLink; @@ -654,7 +514,7 @@ namespace lms::scanner } catch (const audio::IOFileException& e) { - addError(getFilePath(), e.getErrorCode()); + addError(e.getPath(), e.getErrorCode()); } catch (const audio::Exception& e) { @@ -782,8 +642,8 @@ namespace lms::scanner // Audio properties track.modify()->setDuration(_file->audioProperties.duration); - track.modify()->setContainer(audioContainerToDbContainer(_file->audioProperties.container)); - track.modify()->setCodec(audioCodecToDbCodec(_file->audioProperties.codec)); + track.modify()->setContainer(_file->audioProperties.container); + track.modify()->setCodec(_file->audioProperties.codec); track.modify()->setBitrate(_file->audioProperties.bitrate); track.modify()->setChannelCount(_file->audioProperties.channelCount); track.modify()->setSampleRate(_file->audioProperties.sampleRate); diff --git a/src/libs/services/scanner/impl/scanners/audiofile/AudioFileScanOperation.hpp b/src/libs/services/scanner/impl/scanners/audiofile/AudioFileScanOperation.hpp index 0cb62b57..d78f1bb0 100644 --- a/src/libs/services/scanner/impl/scanners/audiofile/AudioFileScanOperation.hpp +++ b/src/libs/services/scanner/impl/scanners/audiofile/AudioFileScanOperation.hpp @@ -21,8 +21,9 @@ #include -#include "audio/AudioTypes.hpp" -#include "audio/IImageReader.hpp" +#include "core/media/ImageType.hpp" + +#include "audio/AudioProperties.hpp" #include "image/Types.hpp" #include "scanners/FileScanOperationBase.hpp" @@ -44,7 +45,7 @@ namespace lms::scanner struct ImageInfo { std::size_t index; - audio::Image::Type type{ audio::Image::Type::Unknown }; + core::media::ImageType type{ core::media::ImageType::Unknown }; std::uint64_t hash{}; std::size_t size{}; image::ImageProperties properties; diff --git a/src/libs/services/scanner/impl/steps/ScanStepAssociateMediumImages.cpp b/src/libs/services/scanner/impl/steps/ScanStepAssociateMediumImages.cpp index 331bf201..895e0a9f 100644 --- a/src/libs/services/scanner/impl/steps/ScanStepAssociateMediumImages.cpp +++ b/src/libs/services/scanner/impl/steps/ScanStepAssociateMediumImages.cpp @@ -125,7 +125,7 @@ namespace lms::scanner db::TrackEmbeddedImage::FindParameters params; params.setMedium(medium->getId()); - params.setImageType(db::ImageType::Media); + params.setImageType(core::media::ImageType::Media); params.setSortMethod(db::TrackEmbeddedImageSortMethod::TrackNumberThenSizeDesc); db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& foundImage) { diff --git a/src/libs/services/scanner/impl/steps/ScanStepAssociateReleaseImages.cpp b/src/libs/services/scanner/impl/steps/ScanStepAssociateReleaseImages.cpp index b32b1ab5..5a91a074 100644 --- a/src/libs/services/scanner/impl/steps/ScanStepAssociateReleaseImages.cpp +++ b/src/libs/services/scanner/impl/steps/ScanStepAssociateReleaseImages.cpp @@ -140,7 +140,7 @@ namespace lms::scanner { db::TrackEmbeddedImage::FindParameters params; params.setRelease(release->getId()); - params.setImageType(db::ImageType::FrontCover); + params.setImageType(core::media::ImageType::FrontCover); params.setSortMethod(db::TrackEmbeddedImageSortMethod::DiscNumberThenTrackNumberThenSizeDesc); db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) { if (!artwork) @@ -155,7 +155,7 @@ namespace lms::scanner { db::TrackEmbeddedImage::FindParameters params; params.setRelease(release->getId()); - params.setImageType(db::ImageType::Media); + params.setImageType(core::media::ImageType::Media); params.setSortMethod(db::TrackEmbeddedImageSortMethod::DiscNumberThenTrackNumberThenSizeDesc); db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) { if (!artwork) @@ -167,7 +167,7 @@ namespace lms::scanner { db::TrackEmbeddedImage::FindParameters params; params.setRelease(release->getId()); - params.setImageType(db::ImageType::Other); + params.setImageType(core::media::ImageType::Other); params.setSortMethod(db::TrackEmbeddedImageSortMethod::DiscNumberThenTrackNumberThenSizeDesc); db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) { if (!artwork) diff --git a/src/libs/services/scanner/impl/steps/ScanStepAssociateTrackImages.cpp b/src/libs/services/scanner/impl/steps/ScanStepAssociateTrackImages.cpp index e24f97a9..592b0b56 100644 --- a/src/libs/services/scanner/impl/steps/ScanStepAssociateTrackImages.cpp +++ b/src/libs/services/scanner/impl/steps/ScanStepAssociateTrackImages.cpp @@ -64,7 +64,7 @@ namespace lms::scanner { db::TrackEmbeddedImage::FindParameters params; params.setTrack(track->getId()); - params.setImageType(db::ImageType::FrontCover); + params.setImageType(core::media::ImageType::FrontCover); params.setSortMethod(db::TrackEmbeddedImageSortMethod::SizeDesc); db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) { @@ -94,7 +94,7 @@ namespace lms::scanner { db::TrackEmbeddedImage::FindParameters params; params.setTrack(track->getId()); - params.setImageType(db::ImageType::Media); + params.setImageType(core::media::ImageType::Media); params.setSortMethod(db::TrackEmbeddedImageSortMethod::SizeDesc); db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) { diff --git a/src/libs/services/transcoding/impl/TranscodeService.cpp b/src/libs/services/transcoding/impl/TranscodeService.cpp index f9b503b5..5108e8df 100644 --- a/src/libs/services/transcoding/impl/TranscodeService.cpp +++ b/src/libs/services/transcoding/impl/TranscodeService.cpp @@ -19,11 +19,7 @@ #include "TranscodeService.hpp" -#include "audio/ITranscoder.hpp" #include "core/ILogger.hpp" -#include "core/UUID.hpp" -#include "database/IDb.hpp" -#include "database/Session.hpp" #include "TranscodeResourceHandler.hpp" @@ -61,10 +57,10 @@ namespace lms::transcoding if (estimateContentLength) { - if (parameters.inputParameters.offset < parameters.inputParameters.duration) - estimatedContentLength = doEstimateContentLength(*parameters.outputParameters.bitrate, parameters.inputParameters.duration - parameters.inputParameters.offset); + if (parameters.inputParameters.offset < parameters.inputParameters.audioProperties.duration) + estimatedContentLength = doEstimateContentLength(*parameters.outputParameters.bitrate, parameters.inputParameters.audioProperties.duration - parameters.inputParameters.offset); else - LMS_LOG(TRANSCODING, WARNING, "Offset " << parameters.inputParameters.offset << " is greater than audio file duration " << parameters.inputParameters.duration << ": not estimating content length"); + LMS_LOG(TRANSCODING, WARNING, "Offset " << parameters.inputParameters.offset << " is greater than audio file duration " << parameters.inputParameters.audioProperties.duration << ": not estimating content length"); } return std::make_unique(parameters, estimatedContentLength); diff --git a/src/libs/subsonic/impl/endpoints/MediaRetrieval.cpp b/src/libs/subsonic/impl/endpoints/MediaRetrieval.cpp index 91ff9f36..f31c9dc7 100644 --- a/src/libs/subsonic/impl/endpoints/MediaRetrieval.cpp +++ b/src/libs/subsonic/impl/endpoints/MediaRetrieval.cpp @@ -26,8 +26,9 @@ #include "core/ILogger.hpp" #include "core/IResourceHandler.hpp" #include "core/String.hpp" +#include "core/media/MimeType.hpp" -#include "audio/AudioTypes.hpp" +#include "audio/AudioProperties.hpp" #include "audio/Exception.hpp" #include "audio/IAudioFileInfo.hpp" #include "audio/IAudioFileInfoParser.hpp" @@ -37,6 +38,7 @@ #include "database/objects/PodcastEpisodeId.hpp" #include "database/objects/Track.hpp" #include "database/objects/TrackLyrics.hpp" +#include "database/objects/Types.hpp" #include "database/objects/User.hpp" #include "services/artwork/IArtworkService.hpp" @@ -54,84 +56,73 @@ namespace lms::api::subsonic { namespace { - std::optional subsonicStreamFormatToTranscodingOutputFormat(std::string_view format) + struct OutputFormat { - for (const auto& [str, avFormat] : std::initializer_list>{ - { "mp3", audio::OutputFormat::MP3 }, - { "opus", audio::OutputFormat::OGG_OPUS }, - { "vorbis", audio::OutputFormat::OGG_VORBIS }, - }) - { - if (core::stringUtils::stringCaseInsensitiveEqual(str, format)) - return avFormat; - } - return std::nullopt; + std::string name; + core::media::ContainerType container; + core::media::CodecType codec; + }; + constexpr std::array outputFormats{ + OutputFormat{ "mp3", core::media::ContainerType::MPEG, core::media::CodecType::MP3 }, + OutputFormat{ "opus", core::media::ContainerType::Ogg, core::media::CodecType::Opus }, + OutputFormat{ "vorbis", core::media::ContainerType::Ogg, core::media::CodecType::Vorbis }, + }; + + std::optional getOutputFormatByName(std::string_view format) + { + std::optional res; + + const auto itFormat{ std::find_if(outputFormats.begin(), outputFormats.end(), [&format, &res](const OutputFormat& outputFormat) { + if (core::stringUtils::stringCaseInsensitiveEqual(format, outputFormat.name)) + { + res = outputFormat; + return true; + } + return false; + }) }; + if (itFormat != outputFormats.end()) + res = *itFormat; + + return res; } - audio::OutputFormat userTranscodeFormatToTranscodingFormat(db::TranscodingOutputFormat format) + std::optional userTranscodeFormatToOutputFormat(db::TranscodingOutputFormat format) { + std::optional res; + switch (format) { case db::TranscodingOutputFormat::MP3: - return audio::OutputFormat::MP3; + res = getOutputFormatByName("mp3"); + break; case db::TranscodingOutputFormat::OGG_OPUS: - return audio::OutputFormat::OGG_OPUS; - case db::TranscodingOutputFormat::MATROSKA_OPUS: - return audio::OutputFormat::MATROSKA_OPUS; + res = getOutputFormatByName("opus"); + break; case db::TranscodingOutputFormat::OGG_VORBIS: - return audio::OutputFormat::OGG_VORBIS; - case db::TranscodingOutputFormat::WEBM_VORBIS: - return audio::OutputFormat::WEBM_VORBIS; + res = getOutputFormatByName("vorbis"); + break; } - return audio::OutputFormat::OGG_OPUS; + return res; } - bool isCodecCompatibleWithOutputFormat(audio::CodecType codec, audio::OutputFormat outputFormat) + audio::AudioProperties getAudioProperties(const std::filesystem::path& trackPath) { - switch (outputFormat) - { - case audio::OutputFormat::MP3: - return codec == audio::CodecType::MP3; - - case audio::OutputFormat::OGG_OPUS: - case audio::OutputFormat::MATROSKA_OPUS: - return codec == audio::CodecType::Opus; - - case audio::OutputFormat::OGG_VORBIS: - case audio::OutputFormat::WEBM_VORBIS: - return codec == audio::CodecType::Vorbis; - } - - return true; - } - - struct StreamParameters - { - std::filesystem::path filePath; - std::string fileMimeType; // set if known - std::optional transcodeParameters; - bool estimateContentLength{}; - }; - - bool isOutputFormatCompatible(const std::filesystem::path& trackPath, audio::OutputFormat outputFormat) - { - // TODO: put this information in db during scan - // It is in base only for tracks, not yet for podcasts try { const auto parser{ audio::createAudioFileInfoParser(audio::AudioFileInfoParserBackend::FFmpeg) }; audio::AudioFileInfoParseOptions parseOptions; - parseOptions.audioPropertiesReadStyle = audio::AudioFileInfoParseOptions::AudioPropertiesReadStyle::Fast; // only coded needed + parseOptions.audioPropertiesReadStyle = audio::AudioFileInfoParseOptions::AudioPropertiesReadStyle::Average; parseOptions.readImages = false; parseOptions.readTags = false; const auto audioFile{ parser->parse(trackPath, parseOptions) }; - if (!audioFile->getAudioProperties()) + const audio::AudioProperties* properties{ audioFile->getAudioProperties() }; + if (!properties) throw RequestedDataNotFoundError{}; - return isCodecCompatibleWithOutputFormat(audioFile->getAudioProperties()->codec, outputFormat); + return *properties; } catch (const audio::Exception& e) { @@ -143,9 +134,7 @@ namespace lms::api::subsonic struct AudioFileInfo { std::filesystem::path path; - std::chrono::milliseconds duration{}; - std::size_t bitrate{}; - std::string mimeType; // set if known + audio::AudioProperties audioProperties; }; AudioFileInfo getAudioFileInfo(db::Session& session, AudioFileId audioFileId) @@ -161,8 +150,20 @@ namespace lms::api::subsonic throw RequestedDataNotFoundError{}; res.path = track->getAbsoluteFilePath(); - res.duration = track->getDuration(); - res.bitrate = track->getBitrate(); + if (track->getContainer() && track->getCodec()) + { + res.audioProperties.container = *track->getContainer(); + res.audioProperties.codec = *track->getCodec(); + res.audioProperties.duration = track->getDuration(); + res.audioProperties.bitrate = track->getBitrate(); + res.audioProperties.channelCount = track->getChannelCount(); + res.audioProperties.sampleRate = track->getSampleRate(); + res.audioProperties.bitsPerSample = track->getBitsPerSample(); + } + else + { + res.audioProperties = getAudioProperties(res.path); + } } else if (const db::PodcastEpisodeId * episodeId{ std::get_if(&audioFileId) }) { @@ -173,14 +174,33 @@ namespace lms::api::subsonic std::filesystem::path podcastCachePath{ core::Service::get()->getCachePath() }; res.path = podcastCachePath / episode->getAudioRelativeFilePath(); - res.duration = episode->getDuration(); - res.bitrate = episode->getEnclosureLength() / std::chrono::duration_cast(episode->getDuration()).count() * 8; - res.mimeType = episode->getEnclosureContentType(); + if (episode->getContainer() && episode->getCodec()) + { + res.audioProperties.container = *episode->getContainer(); + res.audioProperties.codec = *episode->getCodec(); + res.audioProperties.duration = episode->getDuration(); + res.audioProperties.bitrate = episode->getBitrate(); + res.audioProperties.channelCount = episode->getChannelCount(); + res.audioProperties.sampleRate = episode->getSampleRate(); + res.audioProperties.bitsPerSample = episode->getBitsPerSample(); + } + else + { + res.audioProperties = getAudioProperties(res.path); + } } return res; } + struct StreamParameters + { + std::filesystem::path filePath; + audio::AudioProperties audioProperties; + std::optional transcodeParameters; + bool estimateContentLength{}; + }; + StreamParameters getStreamParameters(RequestContext& context) { // Mandatory params @@ -201,32 +221,31 @@ namespace lms::api::subsonic StreamParameters parameters; parameters.filePath = audioFileInfo.path; - parameters.fileMimeType = audioFileInfo.mimeType; parameters.estimateContentLength = estimateContentLength; if (format == "raw") // raw => no transcoding return parameters; // TODO: what if offset is not 0? - std::optional requestedFormat{ subsonicStreamFormatToTranscodingOutputFormat(format) }; + std::optional requestedFormat{ getOutputFormatByName(format) }; if (!requestedFormat) { if (context.getUser()->getSubsonicEnableTranscodingByDefault()) - requestedFormat = userTranscodeFormatToTranscodingFormat(context.getUser()->getSubsonicDefaultTranscodingOutputFormat()); + requestedFormat = userTranscodeFormatToOutputFormat(context.getUser()->getSubsonicDefaultTranscodingOutputFormat()); } - if (!requestedFormat && (maxBitRate == 0 || audioFileInfo.bitrate <= maxBitRate)) + if (!requestedFormat && (maxBitRate == 0 || audioFileInfo.audioProperties.bitrate <= maxBitRate)) { LMS_LOG(API_SUBSONIC, DEBUG, "File's bitrate is compatible with parameters => no transcoding"); return parameters; // no transcoding needed } - // scan the file to check if its format is compatible with the actual requested format + // Check if the input file is compatible with the actual requested format // same codec => apply max bitrate // otherwise => apply default bitrate (because we can't really compare bitrates between formats) + max bitrate) std::size_t bitrate{}; - if (requestedFormat && isOutputFormatCompatible(audioFileInfo.path, *requestedFormat)) + if (requestedFormat && requestedFormat->container == audioFileInfo.audioProperties.container && requestedFormat->codec == audioFileInfo.audioProperties.codec) { - if (maxBitRate == 0 || audioFileInfo.bitrate <= maxBitRate) + if (maxBitRate == 0 || audioFileInfo.audioProperties.bitrate <= maxBitRate) { LMS_LOG(API_SUBSONIC, DEBUG, "File's bitrate and format are compatible with parameters => no transcoding"); return parameters; // no transcoding needed @@ -235,22 +254,24 @@ namespace lms::api::subsonic } // Need to transcode here - if (!requestedFormat) - requestedFormat = userTranscodeFormatToTranscodingFormat(context.getUser()->getSubsonicDefaultTranscodingOutputFormat()); - if (!bitrate) - bitrate = context.getUser()->getSubsonicDefaultTranscodingOutputBitrate(); - if (maxBitRate) - bitrate = std::min(bitrate, maxBitRate); - audio::TranscodeParameters& transcodeParameters{ parameters.transcodeParameters.emplace() }; + if (!requestedFormat) // no format provided => use user's default + requestedFormat = userTranscodeFormatToOutputFormat(context.getUser()->getSubsonicDefaultTranscodingOutputFormat()); + if (!bitrate) // no bitrate provided => use user's default + bitrate = context.getUser()->getSubsonicDefaultTranscodingOutputBitrate(); + if (maxBitRate) // but still honor provided maxBitRate, if any + bitrate = std::min(bitrate, maxBitRate); + transcodeParameters.inputParameters.filePath = audioFileInfo.path; - transcodeParameters.inputParameters.duration = audioFileInfo.duration; + transcodeParameters.inputParameters.audioProperties = audioFileInfo.audioProperties; transcodeParameters.inputParameters.offset = std::chrono::seconds{ timeOffset }; - ; transcodeParameters.outputParameters.bitrate = bitrate; - transcodeParameters.outputParameters.format = *requestedFormat; + transcodeParameters.outputParameters.format.emplace(); + transcodeParameters.outputParameters.format->container = requestedFormat->container; + transcodeParameters.outputParameters.format->codec = requestedFormat->codec; + transcodeParameters.outputParameters.stripMetadata = false; // We want clients to use metadata (offline use, replay gain, etc.) return parameters; @@ -371,7 +392,7 @@ namespace lms::api::subsonic if (streamParameters.transcodeParameters) resourceHandler = core::Service::get()->createTranscodeResourceHandler(*streamParameters.transcodeParameters, streamParameters.estimateContentLength); else - resourceHandler = core::createFileResourceHandler(streamParameters.filePath, streamParameters.fileMimeType); + resourceHandler = core::createFileResourceHandler(streamParameters.filePath, core::media::getMimeType(streamParameters.audioProperties.container, streamParameters.audioProperties.codec).str()); } else { diff --git a/src/libs/subsonic/impl/responses/Song.cpp b/src/libs/subsonic/impl/responses/Song.cpp index 2a23f26b..b7a44fda 100644 --- a/src/libs/subsonic/impl/responses/Song.cpp +++ b/src/libs/subsonic/impl/responses/Song.cpp @@ -51,31 +51,25 @@ namespace lms::api::subsonic { - using namespace db; - namespace { - std::string_view formatToSuffix(TranscodingOutputFormat format) + std::string_view formatToSuffix(db::TranscodingOutputFormat format) { switch (format) { - case TranscodingOutputFormat::MP3: + case db::TranscodingOutputFormat::MP3: return "mp3"; - case TranscodingOutputFormat::OGG_OPUS: + case db::TranscodingOutputFormat::OGG_OPUS: return "opus"; - case TranscodingOutputFormat::MATROSKA_OPUS: - return "mka"; - case TranscodingOutputFormat::OGG_VORBIS: + case db::TranscodingOutputFormat::OGG_VORBIS: return "ogg"; - case TranscodingOutputFormat::WEBM_VORBIS: - return "webm"; } return ""; } } // namespace - Response::Node createSongNode(RequestContext& context, const Track::pointer& track, bool id3) + Response::Node createSongNode(RequestContext& context, const db::Track::pointer& track, bool id3) { LMS_SCOPED_TRACE_DETAILED("Subsonic", "CreateSong"); @@ -136,7 +130,7 @@ namespace lms::api::subsonic trackResponse.setAttribute("coverArt", idToString(coverArtId)); } - const std::vector& artists{ track->getArtists({ TrackArtistLinkType::Artist }) }; + const std::vector& artists{ track->getArtists({ db::TrackArtistLinkType::Artist }) }; if (!artists.empty()) { if (!track->getArtistDisplayName().empty()) @@ -148,7 +142,7 @@ namespace lms::api::subsonic trackResponse.setAttribute("artistId", idToString(artists.front()->getId())); } - const Release::pointer release{ track->getRelease() }; + const db::Release::pointer release{ track->getRelease() }; if (release) { trackResponse.setAttribute("album", release->getName()); @@ -167,13 +161,13 @@ namespace lms::api::subsonic trackResponse.setAttribute("starred", core::stringUtils::toISO8601String(dateTime)); // Report the first GENRE for this track - std::vector genres; + std::vector genres; { - Cluster::FindParameters params; + db::Cluster::FindParameters params; params.setTrack(track->getId()); params.setClusterTypeName("GENRE"); - genres = Cluster::find(context.getDbSession(), params).results; + genres = db::Cluster::find(context.getDbSession(), params).results; if (!genres.empty()) trackResponse.setAttribute("genre", genres.front()->getName()); } @@ -204,13 +198,13 @@ namespace lms::api::subsonic trackResponse.createEmptyArrayChild("artists"); trackResponse.createEmptyArrayChild("contributors"); - TrackArtistLink::find(context.getDbSession(), track->getId(), [&](const TrackArtistLink::pointer& link, const Artist::pointer& artist) { + db::TrackArtistLink::find(context.getDbSession(), track->getId(), [&](const db::TrackArtistLink::pointer& link, const db::Artist::pointer& artist) { switch (link->getType()) { - case TrackArtistLinkType::Artist: + case db::TrackArtistLinkType::Artist: trackResponse.addArrayChild("artists", createArtistNode(artist)); break; - case TrackArtistLinkType::ReleaseArtist: + case db::TrackArtistLinkType::ReleaseArtist: trackResponse.addArrayChild("albumartists", createArtistNode(artist)); break; default: @@ -226,11 +220,11 @@ namespace lms::api::subsonic auto addClusters{ [&](Response::Node::Key field, std::string_view clusterTypeName) { trackResponse.createEmptyArrayValue(field); - Cluster::FindParameters params; + db::Cluster::FindParameters params; params.setTrack(track->getId()); params.setClusterTypeName(clusterTypeName); - for (const auto& cluster : Cluster::find(context.getDbSession(), params).results) + for (const auto& cluster : db::Cluster::find(context.getDbSession(), params).results) trackResponse.addArrayValue(field, cluster->getName()); } }; diff --git a/src/lms/ui/MediaPlayer.cpp b/src/lms/ui/MediaPlayer.cpp index 6fa7e23f..6155327d 100644 --- a/src/lms/ui/MediaPlayer.cpp +++ b/src/lms/ui/MediaPlayer.cpp @@ -101,9 +101,7 @@ namespace lms::ui { case MediaPlayer::Format::MP3: case MediaPlayer::Format::OGG_OPUS: - case MediaPlayer::Format::MATROSKA_OPUS: case MediaPlayer::Format::OGG_VORBIS: - case MediaPlayer::Format::WEBM_VORBIS: return format; } diff --git a/src/lms/ui/SettingsView.cpp b/src/lms/ui/SettingsView.cpp index 94a3078a..8707d4a7 100644 --- a/src/lms/ui/SettingsView.cpp +++ b/src/lms/ui/SettingsView.cpp @@ -478,9 +478,7 @@ namespace lms::ui _transcodingOutputFormatModel = std::make_shared>(); _transcodingOutputFormatModel->add(Wt::WString::tr("Lms.Settings.transcoding-output-format.mp3"), db::TranscodingOutputFormat::MP3); _transcodingOutputFormatModel->add(Wt::WString::tr("Lms.Settings.transcoding-output-format.ogg_opus"), db::TranscodingOutputFormat::OGG_OPUS); - _transcodingOutputFormatModel->add(Wt::WString::tr("Lms.Settings.transcoding-output-format.matroska_opus"), db::TranscodingOutputFormat::MATROSKA_OPUS); _transcodingOutputFormatModel->add(Wt::WString::tr("Lms.Settings.transcoding-output-format.ogg_vorbis"), db::TranscodingOutputFormat::OGG_VORBIS); - _transcodingOutputFormatModel->add(Wt::WString::tr("Lms.Settings.transcoding-output-format.webm_vorbis"), db::TranscodingOutputFormat::WEBM_VORBIS); _replayGainModeModel = std::make_shared(); _replayGainModeModel->add(Wt::WString::tr("Lms.Settings.replaygain-mode.none"), MediaPlayer::Settings::ReplayGain::Mode::None); diff --git a/src/lms/ui/explore/ReleaseView.cpp b/src/lms/ui/explore/ReleaseView.cpp index 3f6e0bb2..56ffb17f 100644 --- a/src/lms/ui/explore/ReleaseView.cpp +++ b/src/lms/ui/explore/ReleaseView.cpp @@ -142,11 +142,11 @@ namespace lms::ui // Codecs { std::string codecStr; - for (db::CodecType codec : release->getCodecs()) + for (core::media::CodecType codec : release->getCodecs()) { if (!codecStr.empty()) codecStr += " · "; - codecStr += db::codecTypeToString(codec).str(); + codecStr += core::media::codecTypeToString(codec).str(); } if (!codecStr.empty()) diff --git a/src/lms/ui/explore/TrackListHelpers.cpp b/src/lms/ui/explore/TrackListHelpers.cpp index 6bead9d7..c83d3be9 100644 --- a/src/lms/ui/explore/TrackListHelpers.cpp +++ b/src/lms/ui/explore/TrackListHelpers.cpp @@ -127,8 +127,11 @@ namespace lms::ui::TrackListHelpers } } - trackInfo->setCondition("if-has-codec", true); - trackInfo->bindString("codec", db::codecTypeToString(track->getCodec()).c_str(), Wt::TextFormat::Plain); + if (const auto codec{ track->getCodec() }) + { + trackInfo->setCondition("if-has-codec", true); + trackInfo->bindString("codec", core::media::codecTypeToString(*codec).c_str(), Wt::TextFormat::Plain); + } trackInfo->bindString("duration", utils::durationToString(track->getDuration())); if (track->getBitrate()) diff --git a/src/lms/ui/resource/AudioTranscodingResource.cpp b/src/lms/ui/resource/AudioTranscodingResource.cpp index a5977b8c..dd8f434d 100644 --- a/src/lms/ui/resource/AudioTranscodingResource.cpp +++ b/src/lms/ui/resource/AudioTranscodingResource.cpp @@ -27,6 +27,11 @@ #include "core/IResourceHandler.hpp" #include "core/Service.hpp" #include "core/String.hpp" + +#include "audio/AudioProperties.hpp" +#include "audio/Exception.hpp" +#include "audio/IAudioFileInfo.hpp" +#include "audio/IAudioFileInfoParser.hpp" #include "database/Session.hpp" #include "database/objects/Track.hpp" #include "database/objects/User.hpp" @@ -51,14 +56,8 @@ namespace lms::core::stringUtils switch (static_cast(*encodedFormat)) { case db::TranscodingOutputFormat::MP3: - [[fallthrough]]; case db::TranscodingOutputFormat::OGG_OPUS: - [[fallthrough]]; - case db::TranscodingOutputFormat::MATROSKA_OPUS: - [[fallthrough]]; case db::TranscodingOutputFormat::OGG_VORBIS: - [[fallthrough]]; - case db::TranscodingOutputFormat::WEBM_VORBIS: return format; } @@ -72,20 +71,64 @@ namespace lms::ui { namespace { - std::optional AudioFormatToTranscodingFormat(db::TranscodingOutputFormat format) + std::optional getAudioProperties(const std::filesystem::path& trackPath) + { + std::optional res; + + try + { + const auto parser{ audio::createAudioFileInfoParser(audio::AudioFileInfoParserBackend::FFmpeg) }; + + audio::AudioFileInfoParseOptions parseOptions; + parseOptions.audioPropertiesReadStyle = audio::AudioFileInfoParseOptions::AudioPropertiesReadStyle::Average; + parseOptions.readImages = false; + parseOptions.readTags = false; + const auto audioFile{ parser->parse(trackPath, parseOptions) }; + + if (const audio::AudioProperties * properties{ audioFile->getAudioProperties() }) + res = *properties; + } + catch (const audio::Exception& e) + { + LMS_LOG(UI, DEBUG, "Cannot parse audio properties in " << trackPath << ": " << e.what()); + } + + return res; + } + + std::optional getAudioProperties(const db::Track::pointer& track) + { + std::optional res; + + if (track->getContainer() && track->getCodec()) + { + res.emplace(); + res->container = *track->getContainer(); + res->codec = *track->getCodec(); + res->duration = track->getDuration(); + res->bitrate = track->getBitrate(); + res->channelCount = track->getChannelCount(); + res->sampleRate = track->getSampleRate(); + res->bitsPerSample = track->getBitsPerSample(); + } + else + { + res = getAudioProperties(track->getAbsoluteFilePath()); + } + + return res; + } + + std::optional AudioFormatToTranscodingFormat(db::TranscodingOutputFormat format) { switch (format) { case db::TranscodingOutputFormat::MP3: - return audio::OutputFormat::MP3; + return audio::TranscodeOutputFormat{ core::media::ContainerType::MPEG, core::media::CodecType::MP3 }; case db::TranscodingOutputFormat::OGG_OPUS: - return audio::OutputFormat::OGG_OPUS; - case db::TranscodingOutputFormat::MATROSKA_OPUS: - return audio::OutputFormat::MATROSKA_OPUS; + return audio::TranscodeOutputFormat{ core::media::ContainerType::Ogg, core::media::CodecType::Opus }; case db::TranscodingOutputFormat::OGG_VORBIS: - return audio::OutputFormat::OGG_VORBIS; - case db::TranscodingOutputFormat::WEBM_VORBIS: - return audio::OutputFormat::WEBM_VORBIS; + return audio::TranscodeOutputFormat{ core::media::ContainerType::Ogg, core::media::CodecType::Vorbis }; } TRANSCODE_LOG(ERROR, "Cannot convert from db audio format to transcoding format"); @@ -128,8 +171,8 @@ namespace lms::ui return std::nullopt; } - const std::optional avFormat{ AudioFormatToTranscodingFormat(*format) }; - if (!avFormat) + const std::optional outputFormat{ AudioFormatToTranscodingFormat(*format) }; + if (!outputFormat) return std::nullopt; // optional parameter @@ -145,12 +188,16 @@ namespace lms::ui return std::nullopt; parameters.inputParameters.filePath = track->getAbsoluteFilePath(); - parameters.inputParameters.duration = track->getDuration(); + const auto audioProperties{ getAudioProperties(track) }; + if (!audioProperties) + return std::nullopt; + + parameters.inputParameters.audioProperties = *audioProperties; } parameters.inputParameters.offset = std::chrono::seconds{ offset }; parameters.outputParameters.stripMetadata = true; - parameters.outputParameters.format = *avFormat; + parameters.outputParameters.format = *outputFormat; parameters.outputParameters.bitrate = *bitrate; return parameters; diff --git a/src/tools/audioinfo/LmsAudioInfo.cpp b/src/tools/audioinfo/LmsAudioInfo.cpp index d7df8362..2044890f 100644 --- a/src/tools/audioinfo/LmsAudioInfo.cpp +++ b/src/tools/audioinfo/LmsAudioInfo.cpp @@ -27,8 +27,9 @@ #include "core/ILogger.hpp" #include "core/String.hpp" +#include "core/media/MimeType.hpp" -#include "audio/AudioTypes.hpp" +#include "audio/AudioProperties.hpp" #include "audio/Exception.hpp" #include "audio/IAudioFileInfo.hpp" #include "audio/IAudioFileInfoParser.hpp" @@ -39,14 +40,15 @@ namespace lms::audio { std::ostream& operator<<(std::ostream& os, const AudioProperties& audioProperties) { - os << "\tDuration: " << std::fixed << std::setprecision(2) << std::chrono::duration_cast>(audioProperties.duration) << std::endl; - os << "\tContainer: " << containerTypeToString(audioProperties.container) << std::endl; - os << "\tCodec: " << codecTypeToString(audioProperties.codec) << std::endl; - os << "\tBitrate: " << audioProperties.bitrate << " bps" << std::endl; + os << "\tDuration: " << std::fixed << std::setprecision(2) << std::chrono::duration_cast>(audioProperties.duration) << '\n'; + os << "\tContainer: " << core::media::containerTypeToString(audioProperties.container) << '\n'; + os << "\tCodec: " << core::media::codecTypeToString(audioProperties.codec) << '\n'; + os << "\tMimeType: " << core::media::getMimeType(audioProperties.container, audioProperties.codec) << '\n'; + os << "\tBitrate: " << audioProperties.bitrate << " bps" << '\n'; if (audioProperties.bitsPerSample) - os << "\tBitsPerSample: " << *audioProperties.bitsPerSample << std::endl; - os << "\tChannelCount: " << audioProperties.channelCount << std::endl; - os << "\tSampleRate: " << audioProperties.sampleRate << std::endl; + os << "\tBitsPerSample: " << *audioProperties.bitsPerSample << '\n'; + os << "\tChannelCount: " << audioProperties.channelCount << '\n'; + os << "\tSampleRate: " << audioProperties.sampleRate << '\n'; return os; } @@ -115,7 +117,7 @@ namespace lms::audio std::ostream& operator<<(std::ostream& os, const Image& image) { - os << "\ttype = " << imageTypeToString(image.type) << std::endl; + os << "\ttype = " << core::media::imageTypeToString(image.type) << std::endl; if (!image.description.empty()) os << "\tdesc: " << image.description << std::endl; os << "\tmimeType: " << image.mimeType << std::endl;