diff --git a/README.md b/README.md
index b069afa2..d4344ff7 100644
--- a/README.md
+++ b/README.md
@@ -54,7 +54,7 @@ __Note__: If no name is provided in the `artist.nfo` file, the name of the conta
### Filtering
It is possible to apply global filters on your collection using `genre`, `mood`, `grouping`, `language`, and by music library. More tags, including custom ones, can be added in the database administration settings.
-__Note__: You can use the `lms-metadata` tool to get an idea of the tags parsed by _LMS_.
+__Note__: You can use the `lms-audioinfo` tool to get an idea of the tags parsed by _LMS_.
### Multiple artists
_LMS_ works best when using the default [Picard](https://picard.musicbrainz.org/) settings, where the `artist` tag contains a single display-friendly value, and the `artists` tag holds the actual artist names. This ensures a cleaner, more organized representation of artist names, when multiple artists are involved.
diff --git a/src/libs/CMakeLists.txt b/src/libs/CMakeLists.txt
index 72a280ca..9a52d2f5 100644
--- a/src/libs/CMakeLists.txt
+++ b/src/libs/CMakeLists.txt
@@ -1,8 +1,7 @@
-add_subdirectory(av)
+add_subdirectory(audio)
add_subdirectory(core)
add_subdirectory(database)
add_subdirectory(image)
-add_subdirectory(metadata)
add_subdirectory(services)
add_subdirectory(som)
add_subdirectory(subsonic)
diff --git a/src/libs/audio/CMakeLists.txt b/src/libs/audio/CMakeLists.txt
new file mode 100644
index 00000000..48f071bb
--- /dev/null
+++ b/src/libs/audio/CMakeLists.txt
@@ -0,0 +1,40 @@
+pkg_check_modules(LIBAV IMPORTED_TARGET libavcodec libavutil libavformat)
+pkg_check_modules(Taglib REQUIRED IMPORTED_TARGET taglib)
+
+add_library(lmsaudio STATIC
+ impl/ffmpeg/AudioFile.cpp
+ impl/ffmpeg/AudioFileInfo.cpp
+ impl/ffmpeg/ImageReader.cpp
+ impl/ffmpeg/TagReader.cpp
+ impl/ffmpeg/Transcoder.cpp
+ impl/ffmpeg/Utils.cpp
+ impl/taglib/AudioFileInfo.cpp
+ impl/taglib/ImageReader.cpp
+ impl/taglib/TagReader.cpp
+ impl/taglib/Utils.cpp
+ impl/AudioTypes.cpp
+ impl/ImageReader.cpp
+ impl/ParseAudioFileInfo.cpp
+ impl/TagReader.cpp
+ )
+
+target_include_directories(lmsaudio INTERFACE
+ include
+ )
+
+target_include_directories(lmsaudio PRIVATE
+ include
+ ${AVCODEC_INCLUDE_DIR}
+ ${AVFORMAT_INCLUDE_DIR}
+ ${AVUTIL_INCLUDE_DIR}
+ )
+
+target_link_libraries(lmsaudio PUBLIC
+ lmscore
+ std::filesystem
+ )
+
+target_link_libraries(lmsaudio PRIVATE
+ PkgConfig::LIBAV
+ PkgConfig::Taglib
+ )
diff --git a/src/libs/audio/impl/AudioTypes.cpp b/src/libs/audio/impl/AudioTypes.cpp
new file mode 100644
index 00000000..433ed0b6
--- /dev/null
+++ b/src/libs/audio/impl/AudioTypes.cpp
@@ -0,0 +1,109 @@
+/*
+ * 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 "audio/AudioTypes.hpp"
+
+namespace lms::audio
+{
+ 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)
+ {
+ case CodecType::AAC:
+ return "AAC";
+ case CodecType::AC3:
+ return "AC3";
+ case CodecType::ALAC:
+ return "ALAC";
+ case CodecType::APE:
+ return "APE";
+ case CodecType::EAC3:
+ return "EAC3";
+ case CodecType::DSD:
+ return "DSD";
+ 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 "";
+ }
+} // namespace lms::audio
\ No newline at end of file
diff --git a/src/libs/audio/impl/ImageReader.cpp b/src/libs/audio/impl/ImageReader.cpp
new file mode 100644
index 00000000..7b314875
--- /dev/null
+++ b/src/libs/audio/impl/ImageReader.cpp
@@ -0,0 +1,77 @@
+/*
+ * 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 "audio/IImageReader.hpp"
+
+namespace lms::audio
+{
+ core::LiteralString imageTypeToString(Image::Type type)
+ {
+ switch (type)
+ {
+ case Image::Type::Other:
+ return "Other";
+ case Image::Type::FileIcon:
+ return "FileIcon";
+ case Image::Type::OtherFileIcon:
+ return "OtherFileIcon";
+ case Image::Type::FrontCover:
+ return "FrontCover";
+ case Image::Type::BackCover:
+ return "BackCover";
+ case Image::Type::LeafletPage:
+ return "LeafletPage";
+ case Image::Type::Media:
+ return "Media";
+ case Image::Type::LeadArtist:
+ return "LeadArtist";
+ case Image::Type::Artist:
+ return "Artist";
+ case Image::Type::Conductor:
+ return "Conductor";
+ case Image::Type::Band:
+ return "Band";
+ case Image::Type::Composer:
+ return "Composer";
+ case Image::Type::Lyricist:
+ return "Lyricist";
+ case Image::Type::RecordingLocation:
+ return "RecordingLocation";
+ case Image::Type::DuringRecording:
+ return "DuringRecording";
+ case Image::Type::DuringPerformance:
+ return "DuringPerformance";
+ case Image::Type::MovieScreenCapture:
+ return "MovieScreenCapture";
+ case Image::Type::ColouredFish:
+ return "ColouredFish";
+ case Image::Type::Illustration:
+ return "Illustration";
+ case Image::Type::BandLogo:
+ return "BandLogo";
+ case Image::Type::PublisherLogo:
+ return "PublisherLogo";
+ case Image::Type::Unknown:
+ break;
+ }
+
+ return "Unknown";
+ }
+
+} // namespace lms::audio
\ No newline at end of file
diff --git a/src/libs/audio/impl/ParseAudioFileInfo.cpp b/src/libs/audio/impl/ParseAudioFileInfo.cpp
new file mode 100644
index 00000000..321d46a7
--- /dev/null
+++ b/src/libs/audio/impl/ParseAudioFileInfo.cpp
@@ -0,0 +1,57 @@
+/*
+ * 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
+
+#include "audio/IAudioFileInfo.hpp"
+
+#include "ffmpeg/AudioFileInfo.hpp"
+#include "ffmpeg/Utils.hpp"
+#include "taglib/AudioFileInfo.hpp"
+#include "taglib/Utils.hpp"
+
+namespace lms::audio
+{
+ std::unique_ptr parseAudioFile(const std::filesystem::path& p, const ParserOptions& parserOptions)
+ {
+ switch (parserOptions.parser)
+ {
+ case ParserOptions::Parser::TagLib:
+ return std::make_unique(p, parserOptions.readStyle, parserOptions.enableExtraDebugLogs);
+ case ParserOptions::Parser::FFmpeg:
+ return std::make_unique(p, parserOptions.enableExtraDebugLogs);
+ }
+
+ return {};
+ }
+
+ std::span getSupportedExtensions(ParserOptions::Parser parser)
+ {
+ switch (parser)
+ {
+ case ParserOptions::Parser::TagLib:
+ return taglib::utils::getSupportedExtensions();
+ case ParserOptions::Parser::FFmpeg:
+ return ffmpeg::utils::getSupportedExtensions();
+ }
+
+ return {};
+ }
+
+} // namespace lms::audio
\ No newline at end of file
diff --git a/src/libs/audio/impl/TagReader.cpp b/src/libs/audio/impl/TagReader.cpp
new file mode 100644
index 00000000..39ef83bd
--- /dev/null
+++ b/src/libs/audio/impl/TagReader.cpp
@@ -0,0 +1,277 @@
+/*
+ * 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 "audio/ITagReader.hpp"
+
+namespace lms::audio
+{
+ core::LiteralString tagTypeToString(TagType type)
+ {
+ switch (type)
+ {
+ case TagType::AcoustID:
+ return "AcoustID";
+ case TagType::AcoustIDFingerprint:
+ return "AcoustIDFingerprint";
+ case TagType::Advisory:
+ return "Advisory";
+ case TagType::Album:
+ return "Album";
+ case TagType::AlbumArtist:
+ return "AlbumArtist";
+ case TagType::AlbumArtists:
+ return "AlbumArtists";
+ case TagType::AlbumArtistSortOrder:
+ return "AlbumArtistSortOrder";
+ case TagType::AlbumArtistsSortOrder:
+ return "AlbumArtistsSortOrder";
+ case TagType::AlbumComment:
+ return "AlbumComment";
+ case TagType::AlbumSortOrder:
+ return "AlbumSortOrder";
+ case TagType::Arranger:
+ return "Arranger";
+ case TagType::Artist:
+ return "Artist";
+ case TagType::ArtistSortOrder:
+ return "ArtistSortOrder";
+ case TagType::Artists:
+ return "Artists";
+ case TagType::ArtistsSortOrder:
+ return "ArtistsSortOrder";
+ case TagType::ASIN:
+ return "ASIN";
+ case TagType::Barcode:
+ return "Barcode";
+ case TagType::BPM:
+ return "BPM";
+ case TagType::CatalogNumber:
+ return "CatalogNumber";
+ case TagType::Comment:
+ return "Comment";
+ case TagType::Compilation:
+ return "Compilation";
+ case TagType::Composer:
+ return "Composer";
+ case TagType::ComposerSortOrder:
+ return "ComposerSortOrder";
+ case TagType::Composers:
+ return "Composers";
+ case TagType::ComposersSortOrder:
+ return "ComposersSortOrder";
+ case TagType::Conductor:
+ return "Conductor";
+ case TagType::ConductorSortOrder:
+ return "ConductorSortOrder";
+ case TagType::Conductors:
+ return "Conductors";
+ case TagType::ConductorsSortOrder:
+ return "ConductorsSortOrder";
+ case TagType::Copyright:
+ return "Copyright";
+ case TagType::CopyrightURL:
+ return "CopyrightURL";
+ case TagType::Date:
+ return "Date";
+ case TagType::Director:
+ return "Director";
+ case TagType::DiscNumber:
+ return "DiscNumber";
+ case TagType::DiscSubtitle:
+ return "DiscSubtitle";
+ case TagType::EncodedBy:
+ return "EncodedBy";
+ case TagType::EncoderSettings:
+ return "EncoderSettings";
+ case TagType::EncodingTime:
+ return "EncodingTime";
+ case TagType::Engineer:
+ return "Engineer";
+ case TagType::GaplessPlayback:
+ return "GaplessPlayback";
+ case TagType::Genre:
+ return "Genre";
+ case TagType::Grouping:
+ return "Grouping";
+ case TagType::InitialKey:
+ return "InitialKey";
+ case TagType::ISRC:
+ return "ISRC";
+ case TagType::Language:
+ return "Language";
+ case TagType::Lyricist:
+ return "Lyricist";
+ case TagType::LyricistSortOrder:
+ return "LyricistSortOrder";
+ case TagType::Lyricists:
+ return "Lyricists";
+ case TagType::LyricistsSortOrder:
+ return "LyricistsSortOrder";
+ case TagType::Media:
+ return "Media";
+ case TagType::MixDJ:
+ return "MixDJ";
+ case TagType::Mixer:
+ return "Mixer";
+ case TagType::MixerSortOrder:
+ return "MixerSortOrder";
+ case TagType::Mixers:
+ return "Mixers";
+ case TagType::MixersSortOrder:
+ return "MixersSortOrder";
+ case TagType::Movement:
+ return "Movement";
+ case TagType::MovementCount:
+ return "MovementCount";
+ case TagType::MovementNumber:
+ return "MovementNumber";
+ case TagType::Mood:
+ return "Mood";
+ case TagType::MusicBrainzArtistID:
+ return "MusicBrainzArtistID";
+ case TagType::MusicBrainzArrangerID:
+ return "MusicBrainzArrangerID";
+ case TagType::MusicBrainzComposerID:
+ return "MusicBrainzComposerID";
+ case TagType::MusicBrainzConductorID:
+ return "MusicBrainzConductorID";
+ case TagType::MusicBrainzDirectorID:
+ return "MusicBrainzDirectorID";
+ case TagType::MusicBrainzDiscID:
+ return "MusicBrainzDiscID";
+ case TagType::MusicBrainzLyricistID:
+ return "MusicBrainzLyricistID";
+ case TagType::MusicBrainzProducerID:
+ return "MusicBrainzProducerID";
+ case TagType::MusicBrainzOriginalArtistID:
+ return "MusicBrainzOriginalArtistID";
+ case TagType::MusicBrainzRecordingID:
+ return "MusicBrainzRecordingID";
+ case TagType::MusicBrainzRemixerID:
+ return "MusicBrainzRemixerID";
+ case TagType::MusicBrainzWorkID:
+ return "MusicBrainzWorkID";
+ case TagType::Remixer:
+ return "Remixer";
+ case TagType::Script:
+ return "Script";
+ case TagType::ShowName:
+ return "ShowName";
+ case TagType::ShowNameSortOrder:
+ return "ShowNameSortOrder";
+ case TagType::ShowWorkAndMovement:
+ return "ShowWorkAndMovement";
+ case TagType::Subtitle:
+ return "Subtitle";
+ case TagType::TrackNumber:
+ return "TrackNumber";
+ case TagType::TrackTitle:
+ return "TrackTitle";
+ case TagType::TrackTitleSortOrder:
+ return "TrackTitleSortOrder";
+ case TagType::WorkTitle:
+ return "WorkTitle";
+ case TagType::Writer:
+ return "Writer";
+ case TagType::License:
+ return "License";
+ case TagType::MusicBrainzOriginalReleaseID:
+ return "MusicBrainzOriginalReleaseID";
+ case TagType::MusicBrainzMixerID:
+ return "MusicBrainzMixerID";
+ case TagType::MusicBrainzReleaseArtistID:
+ return "MusicBrainzReleaseArtistID";
+ case TagType::MusicBrainzReleaseGroupID:
+ return "MusicBrainzReleaseGroupID";
+ case TagType::MusicBrainzReleaseID:
+ return "MusicBrainzReleaseID";
+ case TagType::MusicBrainzTrackID:
+ return "MusicBrainzTrackID";
+ case TagType::MusicIPFingerprint:
+ return "MusicIPFingerprint";
+ case TagType::MusicIPPUID:
+ return "MusicIPPUID";
+ case TagType::OriginalAlbum:
+ return "OriginalAlbum";
+ case TagType::OriginalArtist:
+ return "OriginalArtist";
+ case TagType::OriginalFilename:
+ return "OriginalFilename";
+ case TagType::OriginalReleaseDate:
+ return "OriginalReleaseDate";
+ case TagType::OriginalReleaseYear:
+ return "OriginalReleaseYear";
+ case TagType::Podcast:
+ return "Podcast";
+ case TagType::PodcastURL:
+ return "PodcastURL";
+ case TagType::Producer:
+ return "Producer";
+ case TagType::ProducerSortOrder:
+ return "ProducerSortOrder";
+ case TagType::Producers:
+ return "Producers";
+ case TagType::ProducersSortOrder:
+ return "ProducersSortOrder";
+ case TagType::Rating:
+ return "Rating";
+ case TagType::RecordLabel:
+ return "RecordLabel";
+ case TagType::ReleaseCountry:
+ return "ReleaseCountry";
+ case TagType::ReleaseDate:
+ return "ReleaseDate";
+ case TagType::ReleaseStatus:
+ return "ReleaseStatus";
+ case TagType::ReleaseType:
+ return "ReleaseType";
+ case TagType::RemixerSortOrder:
+ return "RemixerSortOrder";
+ case TagType::Remixers:
+ return "Remixers";
+ case TagType::RemixersSortOrder:
+ return "RemixersSortOrder";
+ case TagType::ReplayGainAlbumGain:
+ return "ReplayGainAlbumGain";
+ case TagType::ReplayGainAlbumPeak:
+ return "ReplayGainAlbumPeak";
+ case TagType::ReplayGainAlbumRange:
+ return "ReplayGainAlbumRange";
+ case TagType::ReplayGainReferenceLoudness:
+ return "ReplayGainReferenceLoudness";
+ case TagType::ReplayGainTrackGain:
+ return "ReplayGainTrackGain";
+ case TagType::ReplayGainTrackPeak:
+ return "ReplayGainTrackPeak";
+ case TagType::ReplayGainTrackRange:
+ return "ReplayGainTrackRange";
+ case TagType::TotalDiscs:
+ return "TotalDiscs";
+ case TagType::TotalTracks:
+ return "TotalTracks";
+ case TagType::Website:
+ return "Website";
+
+ case TagType::Count:
+ break;
+ }
+
+ return "";
+ }
+} // namespace lms::audio
\ No newline at end of file
diff --git a/src/libs/av/impl/AudioFile.cpp b/src/libs/audio/impl/ffmpeg/AudioFile.cpp
similarity index 66%
rename from src/libs/av/impl/AudioFile.cpp
rename to src/libs/audio/impl/ffmpeg/AudioFile.cpp
index 8c3ee0d2..7a72e861 100644
--- a/src/libs/av/impl/AudioFile.cpp
+++ b/src/libs/audio/impl/ffmpeg/AudioFile.cpp
@@ -19,6 +19,9 @@
#include "AudioFile.hpp"
+#include
+#include
+
extern "C"
{
#define __STDC_CONSTANT_MACROS
@@ -27,15 +30,13 @@ extern "C"
#include
}
-#include
-#include
-
#include "core/ILogger.hpp"
#include "core/String.hpp"
-#include "av/Exception.hpp"
+#include "audio/AudioTypes.hpp"
+#include "audio/IAudioFileInfo.hpp"
-namespace lms::av
+namespace lms::audio::ffmpeg
{
namespace
{
@@ -49,11 +50,11 @@ namespace lms::av
return "Unknown error";
}
- class AudioFileException : public Exception
+ class AudioFileException : public AudioFileParsingException
{
public:
AudioFileException(int avError)
- : Exception{ "AudioFileException: " + averror_to_string(avError) }
+ : AudioFileParsingException{ averror_to_string(avError) }
{
}
};
@@ -70,76 +71,104 @@ namespace lms::av
}
}
- DecodingCodec avcodecToDecodingCodec(AVCodecID codec)
+ std::optional avdemuxerToContainerType(std::string_view name)
+ {
+ if (name == "aiff")
+ return ContainerType::AIFF;
+ if (name == "ape")
+ return ContainerType::APE;
+ if (name.starts_with("asf"))
+ return ContainerType::ASF;
+ if (name == "dsf")
+ return ContainerType::DSF;
+ if (name == "flac")
+ return ContainerType::FLAC;
+ if (name.find("mp4") != std::string_view::npos)
+ return ContainerType::MP4;
+ if (name.starts_with("mpc"))
+ return ContainerType::MPC;
+ if (name == "mp3")
+ return ContainerType::MPEG;
+ if (name == "ogg")
+ return ContainerType::Ogg;
+ if (name == "shn")
+ return ContainerType::Shorten;
+ if (name == "tta")
+ return ContainerType::TrueAudio;
+ if (name == "wav")
+ return ContainerType::WAV;
+ if (name == "wv")
+ return ContainerType::WavPack;
+
+ return std::nullopt;
+ }
+
+ std::optional avcodecToCodecType(AVCodecID codec)
{
switch (codec)
{
case AV_CODEC_ID_MP3:
- return DecodingCodec::MP3;
+ return CodecType::MP3;
case AV_CODEC_ID_AAC:
- return DecodingCodec::AAC;
+ return CodecType::AAC;
case AV_CODEC_ID_AC3:
- return DecodingCodec::AC3;
+ return CodecType::AC3;
case AV_CODEC_ID_VORBIS:
- return DecodingCodec::VORBIS;
+ return CodecType::Vorbis;
case AV_CODEC_ID_WMAV1:
- return DecodingCodec::WMAV1;
+ return CodecType::WMA1;
case AV_CODEC_ID_WMAV2:
- return DecodingCodec::WMAV2;
+ return CodecType::WMA2;
+ case AV_CODEC_ID_WMAPRO:
+ return CodecType::WMA9Pro;
+ case AV_CODEC_ID_WMALOSSLESS:
+ return CodecType::WMA9Lossless;
case AV_CODEC_ID_FLAC:
- return DecodingCodec::FLAC;
+ return CodecType::FLAC;
case AV_CODEC_ID_ALAC:
- return DecodingCodec::ALAC;
+ return CodecType::ALAC;
case AV_CODEC_ID_WAVPACK:
- return DecodingCodec::WAVPACK;
+ return CodecType::WavPack;
case AV_CODEC_ID_MUSEPACK7:
- return DecodingCodec::MUSEPACK7;
+ return CodecType::MPC7;
case AV_CODEC_ID_MUSEPACK8:
- return DecodingCodec::MUSEPACK8;
+ return CodecType::MPC8;
case AV_CODEC_ID_APE:
- return DecodingCodec::APE;
+ return CodecType::APE;
case AV_CODEC_ID_EAC3:
- return DecodingCodec::EAC3;
+ return CodecType::EAC3;
case AV_CODEC_ID_MP4ALS:
- return DecodingCodec::MP4ALS;
+ return CodecType::MP4ALS;
case AV_CODEC_ID_OPUS:
- return DecodingCodec::OPUS;
+ return CodecType::Opus;
case AV_CODEC_ID_SHORTEN:
- return DecodingCodec::SHORTEN;
+ return CodecType::Shorten;
case AV_CODEC_ID_DSD_LSBF:
- return DecodingCodec::DSD_LSBF;
case AV_CODEC_ID_DSD_LSBF_PLANAR:
- return DecodingCodec::DSD_LSBF_PLANAR;
case AV_CODEC_ID_DSD_MSBF:
- return DecodingCodec::DSD_MSBF;
case AV_CODEC_ID_DSD_MSBF_PLANAR:
- return DecodingCodec::DSD_MSBF_PLANAR;
+ return CodecType::DSD;
default:
- return DecodingCodec::UNKNOWN;
+ return std::nullopt;
}
}
} // namespace
- std::unique_ptr parseAudioFile(const std::filesystem::path& p)
- {
- return std::make_unique(p);
- }
-
AudioFile::AudioFile(const std::filesystem::path& p)
: _p{ p }
{
int error{ avformat_open_input(&_context, _p.c_str(), nullptr, nullptr) };
if (error < 0)
{
- LMS_LOG(AV, ERROR, "Cannot open " << _p << ": " << averror_to_string(error));
+ LMS_LOG(AUDIO, ERROR, "Cannot open " << _p << ": " << averror_to_string(error));
throw AudioFileException{ error };
}
error = avformat_find_stream_info(_context, nullptr);
if (error < 0)
{
- LMS_LOG(AV, ERROR, "Cannot find stream information on " << _p << ": " << averror_to_string(error));
+ LMS_LOG(AUDIO, ERROR, "Cannot find stream information on " << _p << ": " << averror_to_string(error));
avformat_close_input(&_context);
throw AudioFileException{ error };
}
@@ -158,9 +187,12 @@ namespace lms::av
ContainerInfo AudioFile::getContainerInfo() const
{
ContainerInfo info;
+
+ info.container = avdemuxerToContainerType(_context->iformat->name);
+ info.containerName = _context->iformat->name;
+
info.bitrate = _context->bit_rate;
info.duration = std::chrono::milliseconds{ _context->duration == AV_NOPTS_VALUE ? 0 : _context->duration / AV_TIME_BASE * 1'000 };
- info.name = _context->iformat->name;
return info;
}
@@ -258,7 +290,7 @@ namespace lms::av
if (avstream->codecpar == nullptr)
{
- LMS_LOG(AV, ERROR, "Skipping stream " << i << " since no codecpar is set");
+ LMS_LOG(AUDIO, ERROR, "Skipping stream " << i << " since no codecpar is set");
continue;
}
@@ -275,7 +307,7 @@ namespace lms::av
else
{
picture.mimeType = "application/octet-stream";
- LMS_LOG(AV, ERROR, "CODEC ID " << avstream->codecpar->codec_id << " not handled in mime type conversion");
+ LMS_LOG(AUDIO, ERROR, "CODEC ID " << avstream->codecpar->codec_id << " not handled in mime type conversion");
}
const AVPacket& pkt{ avstream->attached_pic };
@@ -297,7 +329,7 @@ namespace lms::av
if (!avstream->codecpar)
{
- LMS_LOG(AV, ERROR, "Skipping stream " << streamIndex << " since no codecpar is set");
+ LMS_LOG(AUDIO, ERROR, "Skipping stream " << streamIndex << " since no codecpar is set");
return res;
}
@@ -305,19 +337,29 @@ namespace lms::av
return res;
res.emplace();
+
res->index = streamIndex;
- res->bitrate = static_cast(avstream->codecpar->bit_rate);
- res->bitsPerSample = static_cast(avstream->codecpar->bits_per_coded_sample);
-#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(59, 24, 100)
- res->channelCount = static_cast(avstream->codecpar->channels);
-#else
- res->channelCount = static_cast(avstream->codecpar->ch_layout.nb_channels);
-#endif
- res->codec = avcodecToDecodingCodec(avstream->codecpar->codec_id);
+ res->codec = avcodecToCodecType(avstream->codecpar->codec_id);
res->codecName = ::avcodec_get_name(avstream->codecpar->codec_id);
+
+ if (avstream->codecpar->bit_rate)
+ res->bitrate = static_cast(avstream->codecpar->bit_rate);
+ if (avstream->codecpar->bits_per_coded_sample)
+ res->bitsPerSample = static_cast(avstream->codecpar->bits_per_coded_sample);
+ else if (avstream->codecpar->bits_per_raw_sample)
+ res->bitsPerSample = static_cast(avstream->codecpar->bits_per_raw_sample);
+
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(59, 24, 100)
+ if (avstream->codecpar->channels)
+ res->channelCount = static_cast(avstream->codecpar->channels);
+#else
+ if (avstream->codecpar->ch_layout.nb_channels)
+ res->channelCount = static_cast(avstream->codecpar->ch_layout.nb_channels);
+#endif
assert(!res->codecName.empty()); // doc says it is never NULL
- res->sampleRate = static_cast(avstream->codecpar->sample_rate);
+ if (avstream->codecpar->sample_rate)
+ res->sampleRate = static_cast(avstream->codecpar->sample_rate);
return res;
}
-} // namespace lms::av
+} // namespace lms::audio::ffmpeg
\ No newline at end of file
diff --git a/src/libs/audio/impl/ffmpeg/AudioFile.hpp b/src/libs/audio/impl/ffmpeg/AudioFile.hpp
new file mode 100644
index 00000000..2af7fb1b
--- /dev/null
+++ b/src/libs/audio/impl/ffmpeg/AudioFile.hpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2020 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
+#include
+#include
+#include
+#include
+
+#include "audio/AudioTypes.hpp"
+
+extern "C"
+{
+ struct AVFormatContext;
+}
+
+namespace lms::audio::ffmpeg
+{
+ struct Picture
+ {
+ std::string mimeType;
+ std::span data; // valid as long as IAudioFile exists
+ };
+
+ struct ContainerInfo
+ {
+ std::optional container;
+ std::string containerName;
+
+ std::size_t bitrate{};
+ std::chrono::milliseconds duration{};
+ };
+
+ struct StreamInfo
+ {
+ size_t index{};
+ std::optional codec;
+ std::string codecName;
+
+ std::optional bitrate;
+ std::optional bitsPerSample;
+ std::optional channelCount;
+ std::optional sampleRate;
+ };
+
+ class AudioFile
+ {
+ public:
+ AudioFile(const std::filesystem::path& p);
+ ~AudioFile();
+ AudioFile(const AudioFile&) = delete;
+ AudioFile& operator=(const AudioFile&) = delete;
+
+ using MetadataMap = std::unordered_map;
+
+ const std::filesystem::path& getPath() const;
+ ContainerInfo getContainerInfo() const;
+ MetadataMap getMetaData() const;
+ std::vector getStreamInfo() const;
+ std::optional getBestStreamInfo() const;
+ std::optional getBestStreamIndex() const;
+ bool hasAttachedPictures() const;
+ void visitAttachedPictures(std::function func) const;
+
+ private:
+ std::optional getStreamInfo(std::size_t streamIndex) const;
+
+ const std::filesystem::path _p;
+ AVFormatContext* _context{};
+ };
+} // namespace lms::audio::ffmpeg
\ No newline at end of file
diff --git a/src/libs/audio/impl/ffmpeg/AudioFileInfo.cpp b/src/libs/audio/impl/ffmpeg/AudioFileInfo.cpp
new file mode 100644
index 00000000..f8c9f6c6
--- /dev/null
+++ b/src/libs/audio/impl/ffmpeg/AudioFileInfo.cpp
@@ -0,0 +1,82 @@
+/*
+ * 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 "AudioFileInfo.hpp"
+
+#include "audio/AudioTypes.hpp"
+#include "audio/IAudioFileInfo.hpp"
+
+#include "AudioFile.hpp"
+#include "ImageReader.hpp"
+#include "TagReader.hpp"
+
+namespace lms::audio::ffmpeg
+{
+ namespace
+ {
+ AudioProperties computeAudioProperties(const AudioFile& audioFile)
+ {
+ AudioProperties audioProperties;
+
+ const auto containerInfo{ audioFile.getContainerInfo() };
+ const auto bestStreamInfo{ audioFile.getBestStreamInfo() };
+ if (!bestStreamInfo)
+ throw AudioFileParsingException{ audioFile.getPath(), "Cannot find best audio stream" };
+
+ if (!containerInfo.container)
+ throw AudioFileParsingException{ audioFile.getPath(), "Unhandled container type '" + containerInfo.containerName + "'" };
+
+ audioProperties.container = *containerInfo.container;
+ audioProperties.duration = containerInfo.duration;
+ audioProperties.codec = bestStreamInfo->codec;
+ audioProperties.bitrate = bestStreamInfo->bitrate;
+ audioProperties.bitsPerSample = bestStreamInfo->bitsPerSample;
+ audioProperties.channelCount = bestStreamInfo->channelCount;
+ audioProperties.sampleRate = bestStreamInfo->sampleRate;
+
+ return audioProperties;
+ }
+ } // namespace
+
+ AudioFileInfo::AudioFileInfo(const std::filesystem::path& filePath, bool enableExtraDebugLogs)
+ : _audioFile{ std::make_unique(filePath) }
+ , _audioProperties{ std::make_unique(computeAudioProperties(*_audioFile)) }
+ , _tagReader{ std::make_unique(*_audioFile, enableExtraDebugLogs) }
+ , _imageReader{ std::make_unique(*_audioFile) }
+ {
+ }
+
+ AudioFileInfo::~AudioFileInfo() = default;
+
+ const AudioProperties& AudioFileInfo::getAudioProperties() const
+ {
+ return *_audioProperties;
+ }
+
+ const IImageReader& AudioFileInfo::getImageReader() const
+ {
+ return *_imageReader;
+ }
+
+ const ITagReader& AudioFileInfo::getTagReader() const
+ {
+ return *_tagReader;
+ }
+
+} // namespace lms::audio::ffmpeg
diff --git a/src/libs/audio/impl/ffmpeg/AudioFileInfo.hpp b/src/libs/audio/impl/ffmpeg/AudioFileInfo.hpp
new file mode 100644
index 00000000..e8c806a7
--- /dev/null
+++ b/src/libs/audio/impl/ffmpeg/AudioFileInfo.hpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2020 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 "audio/AudioTypes.hpp"
+#include "audio/IAudioFileInfo.hpp"
+
+namespace lms::audio::ffmpeg
+{
+ class AudioFile;
+ class TagReader;
+ class ImageReader;
+
+ class AudioFileInfo final : public IAudioFileInfo
+ {
+ public:
+ AudioFileInfo(const std::filesystem::path& filePath, bool enableExtraDebugLogs);
+ ~AudioFileInfo();
+ AudioFileInfo(const AudioFileInfo&) = delete;
+ AudioFileInfo& operator=(const AudioFileInfo&) = delete;
+
+ private:
+ const AudioProperties& getAudioProperties() const override;
+ const IImageReader& getImageReader() const override;
+ const ITagReader& getTagReader() const override;
+
+ std::unique_ptr _audioFile;
+ std::unique_ptr _audioProperties;
+ std::unique_ptr _tagReader;
+ std::unique_ptr _imageReader;
+ };
+} // namespace lms::audio::ffmpeg
diff --git a/src/libs/metadata/impl/avformat/AvFormatImageReader.cpp b/src/libs/audio/impl/ffmpeg/ImageReader.cpp
similarity index 61%
rename from src/libs/metadata/impl/avformat/AvFormatImageReader.cpp
rename to src/libs/audio/impl/ffmpeg/ImageReader.cpp
index 8a420534..ad935d1e 100644
--- a/src/libs/metadata/impl/avformat/AvFormatImageReader.cpp
+++ b/src/libs/audio/impl/ffmpeg/ImageReader.cpp
@@ -17,35 +17,30 @@
* along with LMS. If not, see .
*/
-#include "AvFormatImageReader.hpp"
+#include "ImageReader.hpp"
-#include "av/Exception.hpp"
-#include "av/IAudioFile.hpp"
-#include "metadata/Exception.hpp"
+#include
-namespace lms::metadata::avformat
+#include "core/String.hpp"
+
+#include "AudioFile.hpp"
+
+namespace lms::audio::ffmpeg
{
- AvFormatImageReader::AvFormatImageReader(const std::filesystem::path& p)
+ ImageReader::ImageReader(const AudioFile& audioFile)
+ : _audioFile{ audioFile }
{
- try
- {
- _audioFile = av::parseAudioFile(p);
- }
- catch (av::Exception& e)
- {
- throw AudioFileParsingException{ e.what() };
- }
}
- AvFormatImageReader::~AvFormatImageReader() = default;
+ ImageReader::~ImageReader() = default;
- void AvFormatImageReader::visitImages(ImageVisitor visitor) const
+ void ImageReader::visitImages(const ImageVisitor& visitor) const
{
- auto metaDataHasKeyword{ [](const av::IAudioFile::MetadataMap& metadata, std::string_view keyword) {
+ auto metaDataHasKeyword{ [](const AudioFile::MetadataMap& metadata, std::string_view keyword) {
return std::any_of(std::cbegin(metadata), std::cend(metadata), [&](const auto& keyValue) { return core::stringUtils::stringCaseInsensitiveContains(keyValue.second, keyword); });
} };
- _audioFile->visitAttachedPictures([&](const av::Picture& picture, const av::IAudioFile::MetadataMap& metaData) {
+ _audioFile.visitAttachedPictures([&](const Picture& picture, const AudioFile::MetadataMap& metaData) {
Image image;
image.data = picture.data;
image.mimeType = picture.mimeType;
@@ -58,4 +53,4 @@ namespace lms::metadata::avformat
});
}
-} // namespace lms::metadata::avformat
+} // namespace lms::audio::ffmpeg
diff --git a/src/libs/audio/impl/ffmpeg/ImageReader.hpp b/src/libs/audio/impl/ffmpeg/ImageReader.hpp
new file mode 100644
index 00000000..27a24b30
--- /dev/null
+++ b/src/libs/audio/impl/ffmpeg/ImageReader.hpp
@@ -0,0 +1,42 @@
+/*
+ * 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 "audio/IImageReader.hpp"
+
+namespace lms::audio::ffmpeg
+{
+ class AudioFile;
+
+ class ImageReader : public IImageReader
+ {
+ public:
+ ImageReader(const AudioFile& audioFile);
+ ~ImageReader() override;
+
+ ImageReader(const ImageReader&) = delete;
+ ImageReader& operator=(const ImageReader&) = delete;
+
+ private:
+ void visitImages(const ImageVisitor& visitor) const override;
+
+ const AudioFile& _audioFile;
+ };
+} // namespace lms::audio::ffmpeg
diff --git a/src/libs/metadata/impl/avformat/AvFormatTagReader.cpp b/src/libs/audio/impl/ffmpeg/TagReader.cpp
similarity index 86%
rename from src/libs/metadata/impl/avformat/AvFormatTagReader.cpp
rename to src/libs/audio/impl/ffmpeg/TagReader.cpp
index fb7981c1..fa5d9e4f 100644
--- a/src/libs/metadata/impl/avformat/AvFormatTagReader.cpp
+++ b/src/libs/audio/impl/ffmpeg/TagReader.cpp
@@ -17,15 +17,12 @@
* along with LMS. If not, see .
*/
-#include "AvFormatTagReader.hpp"
+#include "TagReader.hpp"
-#include "av/Exception.hpp"
-#include "av/IAudioFile.hpp"
#include "core/ILogger.hpp"
#include "core/String.hpp"
-#include "metadata/Exception.hpp"
-namespace lms::metadata::avformat
+namespace lms::audio::ffmpeg
{
namespace
{
@@ -146,41 +143,20 @@ namespace lms::metadata::avformat
};
} // namespace
- AvFormatTagReader::AvFormatTagReader(const std::filesystem::path& p, bool debug)
+ TagReader::TagReader(const AudioFile& audioFile, bool enableExtraDebugLogs)
+ : _audioFile{ audioFile }
+ , _metaDataMap{ audioFile.getMetaData() }
{
- try
+ if (enableExtraDebugLogs && core::Service::get()->isSeverityActive(core::logging::Severity::DEBUG))
{
- const auto audioFile{ av::parseAudioFile(p) };
-
- _audioProperties.duration = audioFile->getContainerInfo().duration;
-
- const auto bestAudioStream{ audioFile->getBestStreamInfo() };
- if (bestAudioStream)
- {
- _audioProperties.bitrate = bestAudioStream->bitrate;
- _audioProperties.bitsPerSample = bestAudioStream->bitsPerSample;
- _audioProperties.channelCount = bestAudioStream->channelCount;
- _audioProperties.sampleRate = bestAudioStream->sampleRate;
- }
-
- _containerInfo = audioFile->getContainerInfo();
- _metaDataMap = audioFile->getMetaData();
-
- if (debug && core::Service::get()->isSeverityActive(core::logging::Severity::DEBUG))
- {
- for (const auto& [key, value] : _metaDataMap)
- LMS_LOG(METADATA, DEBUG, "Key = '" << key << "', value = '" << value << "'");
- }
- }
- catch (av::Exception& e)
- {
- throw AudioFileParsingException{ e.what() };
+ for (const auto& [key, value] : _metaDataMap)
+ LMS_LOG(METADATA, DEBUG, "Key = '" << key << "', value = '" << value << "'");
}
}
- AvFormatTagReader::~AvFormatTagReader() = default;
+ TagReader::~TagReader() = default;
- void AvFormatTagReader::visitTagValues(TagType tag, TagValueVisitor visitor) const
+ void TagReader::visitTagValues(TagType tag, TagValueVisitor visitor) const
{
auto itTagNames{ avFormatTagMapping.find(tag) };
if (itTagNames == std::cend(avFormatTagMapping))
@@ -200,7 +176,7 @@ namespace lms::metadata::avformat
}
}
- void AvFormatTagReader::visitTagValues(std::string_view key, TagValueVisitor visitor) const
+ void TagReader::visitTagValues(std::string_view key, TagValueVisitor visitor) const
{
auto itValues{ _metaDataMap.find(std::string{ key }) };
if (itValues == std::cend(_metaDataMap))
@@ -209,14 +185,14 @@ namespace lms::metadata::avformat
visitor(itValues->second);
}
- void AvFormatTagReader::visitPerformerTags(PerformerVisitor visitor) const
+ void TagReader::visitPerformerTags(PerformerVisitor visitor) const
{
visitTagValues("PERFORMER", [&](std::string_view value) {
visitor("", value);
});
}
- void AvFormatTagReader::visitLyricsTags(LyricsVisitor visitor) const
+ void TagReader::visitLyricsTags(LyricsVisitor visitor) const
{
// MPEG files: need to visit LYRICS-language entries
for (const auto& [tag, value] : _metaDataMap)
@@ -234,4 +210,4 @@ namespace lms::metadata::avformat
visitor("", value);
});
}
-} // namespace lms::metadata::avformat
+} // namespace lms::audio::ffmpeg
diff --git a/src/libs/metadata/impl/avformat/AvFormatTagReader.hpp b/src/libs/audio/impl/ffmpeg/TagReader.hpp
similarity index 61%
rename from src/libs/metadata/impl/avformat/AvFormatTagReader.hpp
rename to src/libs/audio/impl/ffmpeg/TagReader.hpp
index 44b38c38..25e7bd7f 100644
--- a/src/libs/metadata/impl/avformat/AvFormatTagReader.hpp
+++ b/src/libs/audio/impl/ffmpeg/TagReader.hpp
@@ -19,31 +19,27 @@
#pragma once
-#include
+#include "audio/ITagReader.hpp"
-#include "av/IAudioFile.hpp"
+#include "AudioFile.hpp"
-#include "ITagReader.hpp"
-
-namespace lms::metadata::avformat
+namespace lms::audio::ffmpeg
{
- class AvFormatTagReader : public ITagReader
+ class TagReader : public ITagReader
{
public:
- AvFormatTagReader(const std::filesystem::path& path, bool debug);
- ~AvFormatTagReader() override;
- AvFormatTagReader(const AvFormatTagReader&) = delete;
- AvFormatTagReader& operator=(const AvFormatTagReader&) = delete;
+ TagReader(const AudioFile& audioFile, bool enableExtraDebugLogs);
+ ~TagReader() override;
+ TagReader(const TagReader&) = delete;
+ TagReader& operator=(const TagReader&) = delete;
private:
void visitTagValues(TagType tag, TagValueVisitor visitor) const override;
void visitTagValues(std::string_view tag, TagValueVisitor visitor) const override;
void visitPerformerTags(PerformerVisitor visitor) const override;
void visitLyricsTags(LyricsVisitor visitor) const override;
- const AudioProperties& getAudioProperties() const override { return _audioProperties; }
- AudioProperties _audioProperties;
- av::IAudioFile::MetadataMap _metaDataMap;
- av::ContainerInfo _containerInfo;
+ const AudioFile& _audioFile;
+ AudioFile::MetadataMap _metaDataMap;
};
-} // namespace lms::metadata::avformat
+} // namespace lms::audio::ffmpeg
diff --git a/src/libs/av/impl/Transcoder.cpp b/src/libs/audio/impl/ffmpeg/Transcoder.cpp
similarity index 58%
rename from src/libs/av/impl/Transcoder.cpp
rename to src/libs/audio/impl/ffmpeg/Transcoder.cpp
index 8dc4d59d..cd6ea9d5 100644
--- a/src/libs/av/impl/Transcoder.cpp
+++ b/src/libs/audio/impl/ffmpeg/Transcoder.cpp
@@ -27,17 +27,21 @@
#include "core/ILogger.hpp"
#include "core/Service.hpp"
-#include "av/Exception.hpp"
+#include "audio/Exception.hpp"
+#include "audio/TranscodeTypes.hpp"
-namespace lms::av
+namespace lms::audio
+{
+ std::unique_ptr createTranscoder(const TranscodeParameters& parameters)
+ {
+ return std::make_unique(parameters);
+ }
+} // namespace lms::audio
+
+namespace lms::audio::ffmpeg
{
#define LOG(severity, message) LMS_LOG(TRANSCODING, severity, "[" << _debugId << "] - " << message)
- std::unique_ptr createTranscoder(const InputParameters& inputParameters, const OutputParameters& outputParameters)
- {
- return std::make_unique(inputParameters, outputParameters);
- }
-
static std::atomic globalId{};
static std::filesystem::path ffmpegPath;
@@ -48,10 +52,10 @@ namespace lms::av
throw Exception{ "File '" + ffmpegPath.string() + "' does not exist!" };
}
- Transcoder::Transcoder(const InputParameters& inputParams, const OutputParameters& outputParams)
+ Transcoder::Transcoder(const TranscodeParameters& parameters)
: _debugId{ globalId++ }
- , _inputParams{ inputParams }
- , _outputParams{ outputParams }
+ , _inputParams{ parameters.inputParameters }
+ , _outputParams{ parameters.outputParameters }
{
start();
}
@@ -65,18 +69,18 @@ namespace lms::av
try
{
- if (!std::filesystem::exists(_inputParams.file))
- throw Exception{ "File " + _inputParams.file.string() + " does not exist!" };
- if (!std::filesystem::is_regular_file(_inputParams.file))
- throw Exception{ "File " + _inputParams.file.string() + " is not regular!" };
+ if (!std::filesystem::exists(_inputParams.filePath))
+ throw Exception{ "File " + _inputParams.filePath.string() + " does not exist!" };
+ if (!std::filesystem::is_regular_file(_inputParams.filePath))
+ throw Exception{ "File " + _inputParams.filePath.string() + " is not regular!" };
}
catch (const std::filesystem::filesystem_error& e)
{
// TODO store/raise e.code()
- throw Exception{ "File error '" + _inputParams.file.string() + "': " + e.what() };
+ throw Exception{ "File error '" + _inputParams.filePath.string() + "': " + e.what() };
}
- LOG(INFO, "Transcoding file " << _inputParams.file);
+ LOG(INFO, "Transcoding file " << _inputParams.filePath);
std::vector args;
@@ -101,14 +105,7 @@ namespace lms::av
// Input file
args.emplace_back("-i");
- args.emplace_back(_inputParams.file.string());
-
- // Stream mapping, if set
- if (_inputParams.streamIndex)
- {
- args.emplace_back("-map");
- args.emplace_back("0:" + std::to_string(*_inputParams.streamIndex));
- }
+ args.emplace_back(_inputParams.filePath.string());
if (_outputParams.stripMetadata)
{
@@ -121,47 +118,53 @@ namespace lms::av
args.emplace_back("-vn");
// Output bitrates
- args.emplace_back("-b:a");
- args.emplace_back(std::to_string(_outputParams.bitrate));
+ if (_outputParams.bitrate)
+ {
+ args.emplace_back("-b:a");
+ args.emplace_back(std::to_string(*_outputParams.bitrate));
+ }
// Codecs and formats
- switch (_outputParams.format)
+ if (_outputParams.format)
{
- case OutputFormat::MP3:
- args.emplace_back("-f");
- args.emplace_back("mp3");
- break;
+ switch (*_outputParams.format)
+ {
+ case OutputFormat::MP3:
+ args.emplace_back("-f");
+ args.emplace_back("mp3");
+ break;
- case OutputFormat::OGG_OPUS:
- args.emplace_back("-acodec");
- args.emplace_back("libopus");
- args.emplace_back("-f");
- args.emplace_back("ogg");
- break;
+ case OutputFormat::OGG_OPUS:
+ args.emplace_back("-acodec");
+ 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::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");
- args.emplace_back("libvorbis");
- args.emplace_back("-f");
- args.emplace_back("ogg");
- break;
+ case OutputFormat::OGG_VORBIS:
+ args.emplace_back("-acodec");
+ 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");
- break;
+ case OutputFormat::WEBM_VORBIS:
+ args.emplace_back("-acodec");
+ args.emplace_back("libvorbis");
+ args.emplace_back("-f");
+ args.emplace_back("webm");
+ break;
- default:
- throw Exception{ "Unhandled format (" + std::to_string(static_cast(_outputParams.format)) + ")" };
+ default:
+ throw Exception{ "Unhandled format (" + std::to_string(static_cast(*_outputParams.format)) + ")" };
+ }
}
args.emplace_back("pipe:1");
@@ -199,18 +202,22 @@ namespace lms::av
std::string_view Transcoder::getOutputMimeType() const
{
- switch (_outputParams.format)
+ // TODO: use input mime type
+ if (_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";
+ 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 "application/octet-stream"; // default, should not happen
@@ -222,5 +229,4 @@ namespace lms::av
return _childProcess->finished();
}
-
-} // namespace lms::av
+} // namespace lms::audio::ffmpeg
diff --git a/src/libs/av/impl/Transcoder.hpp b/src/libs/audio/impl/ffmpeg/Transcoder.hpp
similarity index 79%
rename from src/libs/av/impl/Transcoder.hpp
rename to src/libs/audio/impl/ffmpeg/Transcoder.hpp
index 685d18b0..715c6d4f 100644
--- a/src/libs/av/impl/Transcoder.hpp
+++ b/src/libs/audio/impl/ffmpeg/Transcoder.hpp
@@ -19,19 +19,19 @@
#pragma once
-#include "av/ITranscoder.hpp"
+#include "audio/ITranscoder.hpp"
namespace lms::core
{
class IChildProcess;
}
-namespace lms::av
+namespace lms::audio::ffmpeg
{
class Transcoder : public ITranscoder
{
public:
- Transcoder(const InputParameters& inputParameters, const OutputParameters& outputParameters);
+ Transcoder(const TranscodeParameters& parameters);
~Transcoder() override;
Transcoder(const Transcoder&) = delete;
Transcoder& operator=(const Transcoder&) = delete;
@@ -41,15 +41,15 @@ namespace lms::av
std::size_t readSome(std::byte* buffer, std::size_t bufferSize) override;
std::string_view getOutputMimeType() const override;
- const OutputParameters& getOutputParameters() const override { return _outputParams; }
+ const TranscodeOutputParameters& getOutputParameters() const override { return _outputParams; }
bool finished() const override;
static void init();
void start();
const std::size_t _debugId{};
- const InputParameters _inputParams;
- const OutputParameters _outputParams;
+ const TranscodeInputParameters _inputParams;
+ const TranscodeOutputParameters _outputParams;
std::unique_ptr _childProcess;
};
-} // namespace lms::av
\ No newline at end of file
+} // namespace lms::audio::ffmpeg
\ No newline at end of file
diff --git a/src/libs/metadata/impl/avformat/Utils.cpp b/src/libs/audio/impl/ffmpeg/Utils.cpp
similarity index 89%
rename from src/libs/metadata/impl/avformat/Utils.cpp
rename to src/libs/audio/impl/ffmpeg/Utils.cpp
index 83055d2f..a3ab243c 100644
--- a/src/libs/metadata/impl/avformat/Utils.cpp
+++ b/src/libs/audio/impl/ffmpeg/Utils.cpp
@@ -19,11 +19,11 @@
#include "Utils.hpp"
-namespace lms::metadata::avformat::utils
+namespace lms::audio::ffmpeg::utils
{
std::span getSupportedExtensions()
{
- // TODO: use av capability to retrieve supported formats
+ // TODO: list demuxers to retrieve supported formats
static const std::array fileExtensions{
".aac",
".alac",
@@ -46,4 +46,4 @@ namespace lms::metadata::avformat::utils
};
return fileExtensions;
}
-} // namespace lms::metadata::avformat::utils
\ No newline at end of file
+} // namespace lms::audio::ffmpeg::utils
\ No newline at end of file
diff --git a/src/libs/metadata/impl/avformat/Utils.hpp b/src/libs/audio/impl/ffmpeg/Utils.hpp
similarity index 90%
rename from src/libs/metadata/impl/avformat/Utils.hpp
rename to src/libs/audio/impl/ffmpeg/Utils.hpp
index b4f655fa..35d7be48 100644
--- a/src/libs/metadata/impl/avformat/Utils.hpp
+++ b/src/libs/audio/impl/ffmpeg/Utils.hpp
@@ -22,7 +22,7 @@
#include
#include
-namespace lms::metadata::avformat::utils
+namespace lms::audio::ffmpeg::utils
{
std::span getSupportedExtensions();
-} // namespace lms::metadata::avformat::utils
\ No newline at end of file
+} // namespace lms::audio::ffmpeg::utils
\ No newline at end of file
diff --git a/src/libs/audio/impl/taglib/AudioFileInfo.cpp b/src/libs/audio/impl/taglib/AudioFileInfo.cpp
new file mode 100644
index 00000000..2b509804
--- /dev/null
+++ b/src/libs/audio/impl/taglib/AudioFileInfo.cpp
@@ -0,0 +1,227 @@
+/*
+ * 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 "AudioFileInfo.hpp"
+
+#include
+
+#include "TagLibDefs.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#if LMS_TAGLIB_HAS_DSF
+ #include
+#endif
+#if LMS_TAGLIB_HAS_SHORTEN
+ #include
+#endif
+
+#include "audio/IAudioFileInfo.hpp"
+
+#include "Utils.hpp"
+
+namespace lms::audio::taglib
+{
+ namespace
+ {
+ AudioProperties computeAudioProperties(const ::TagLib::File& file)
+ {
+ assert(file.audioProperties());
+ const ::TagLib::AudioProperties& properties{ *file.audioProperties() };
+
+ AudioProperties audioProperties;
+
+ // Common properties
+ audioProperties.bitrate = static_cast(properties.bitrate() * 1000);
+ audioProperties.channelCount = static_cast(properties.channels());
+ audioProperties.duration = std::chrono::milliseconds{ properties.lengthInMilliseconds() };
+ audioProperties.sampleRate = static_cast(properties.sampleRate());
+
+ // Guess container from the file type
+ if (const auto* apeFile{ dynamic_cast(&file) })
+ {
+ audioProperties.container = ContainerType::APE;
+ audioProperties.codec = CodecType::APE; // TODO version?
+ audioProperties.bitsPerSample = apeFile->audioProperties()->bitsPerSample();
+ }
+ else if (const auto* asfFile{ dynamic_cast(&file) })
+ {
+ audioProperties.container = ContainerType::ASF;
+
+ switch (asfFile->audioProperties()->codec())
+ {
+ case ::TagLib::ASF::Properties::Codec::WMA1:
+ audioProperties.codec = CodecType::WMA1;
+ break;
+ case ::TagLib::ASF::Properties::Codec::WMA2:
+ audioProperties.codec = CodecType::WMA2;
+ break;
+ case ::TagLib::ASF::Properties::Codec::WMA9Lossless:
+ audioProperties.codec = CodecType::WMA9Lossless;
+ break;
+ case ::TagLib::ASF::Properties::Codec::WMA9Pro:
+ audioProperties.codec = CodecType::WMA9Pro;
+ break;
+ case ::TagLib::ASF::Properties::Codec::Unknown:
+ audioProperties.codec = std::nullopt;
+ break;
+ }
+
+ audioProperties.bitsPerSample = asfFile->audioProperties()->bitsPerSample();
+ }
+#if LMS_TAGLIB_HAS_DSF
+ else if (const auto* dsfFile{ dynamic_cast(&file) })
+ {
+ audioProperties.container = ContainerType::DSF;
+ audioProperties.codec = 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.bitsPerSample = flacFile->audioProperties()->bitsPerSample();
+ }
+ else if (const auto* mp4File{ dynamic_cast(&file) })
+ {
+ audioProperties.container = ContainerType::MP4;
+ switch (mp4File->audioProperties()->codec())
+ {
+ case ::TagLib::MP4::Properties::Codec::AAC:
+ audioProperties.codec = CodecType::AAC;
+ break;
+ case ::TagLib::MP4::Properties::Codec::ALAC:
+ audioProperties.codec = CodecType::ALAC;
+ break;
+ case ::TagLib::MP4::Properties::Codec::Unknown:
+ audioProperties.codec = std::nullopt;
+ break;
+ }
+
+ audioProperties.bitsPerSample = mp4File->audioProperties()->bitsPerSample();
+ }
+ else if (const auto* mpcFile{ dynamic_cast(&file) })
+ {
+ audioProperties.container = ContainerType::MPC;
+
+ switch (mpcFile->audioProperties()->mpcVersion())
+ {
+ case 7:
+ audioProperties.codec = CodecType::MPC7;
+ break;
+ case 8:
+ audioProperties.codec = CodecType::MPC8;
+ break;
+ }
+ }
+ else if (const auto* mpegFile{ dynamic_cast(&file) })
+ {
+ const auto& properties{ *mpegFile->audioProperties() };
+
+ audioProperties.container = 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
+ else if (mpegFile->audioProperties()->isADTS()) // likely AAC
+ audioProperties.codec = CodecType::AAC;
+ }
+ else if (dynamic_cast(&file))
+ {
+ audioProperties.container = ContainerType::Ogg;
+ audioProperties.codec = CodecType::Opus;
+ }
+ else if (dynamic_cast(&file))
+ {
+ audioProperties.container = ContainerType::Ogg;
+ audioProperties.codec = CodecType::Vorbis;
+ }
+ else if (const auto* aiffFile{ dynamic_cast(&file) })
+ {
+ audioProperties.container = ContainerType::AIFF;
+ audioProperties.codec = CodecType::PCM;
+ audioProperties.bitsPerSample = aiffFile->audioProperties()->bitsPerSample();
+ }
+ else if (const auto* wavFile{ dynamic_cast(&file) })
+ {
+ audioProperties.container = ContainerType::WAV;
+ audioProperties.codec = 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.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.bitsPerSample = trueAudioFile->audioProperties()->bitsPerSample();
+ }
+ else if (const auto* wavPackFile{ dynamic_cast(&file) })
+ {
+ audioProperties.container = ContainerType::WavPack;
+ audioProperties.codec = CodecType::WavPack;
+ audioProperties.bitsPerSample = wavPackFile->audioProperties()->bitsPerSample();
+ }
+
+ return audioProperties;
+ }
+ } // namespace
+
+ AudioFileInfo::AudioFileInfo(const std::filesystem::path& filePath, ParserOptions::AudioPropertiesReadStyle readStyle, bool enableExtraDebugLogs)
+ : _filePath{ filePath }
+ , _file{ utils::parseFile(filePath, readStyle) }
+ , _audioProperties{ computeAudioProperties(*_file) }
+ , _tagReader{ *_file, enableExtraDebugLogs }
+ , _imageReader{ *_file }
+ {
+ }
+
+ const AudioProperties& AudioFileInfo::getAudioProperties() const
+ {
+ return _audioProperties;
+ }
+
+ const IImageReader& AudioFileInfo::getImageReader() const
+ {
+ return _imageReader;
+ }
+
+ const ITagReader& AudioFileInfo::getTagReader() const
+ {
+ return _tagReader;
+ }
+
+} // namespace lms::audio::taglib
diff --git a/src/libs/audio/impl/taglib/AudioFileInfo.hpp b/src/libs/audio/impl/taglib/AudioFileInfo.hpp
new file mode 100644
index 00000000..bd0eaae2
--- /dev/null
+++ b/src/libs/audio/impl/taglib/AudioFileInfo.hpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2020 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 "audio/AudioTypes.hpp"
+#include "audio/IAudioFileInfo.hpp"
+#include "audio/IImageReader.hpp"
+#include "audio/ITagReader.hpp"
+
+#include "ImageReader.hpp"
+#include "TagReader.hpp"
+
+namespace lms::audio::taglib
+{
+ class AudioFileInfo final : public IAudioFileInfo
+ {
+ public:
+ AudioFileInfo(const std::filesystem::path& filePath, ParserOptions::AudioPropertiesReadStyle readStyle, bool enableExtraDebugLogs);
+
+ private:
+ const AudioProperties& getAudioProperties() const override;
+ const IImageReader& getImageReader() const override;
+ const ITagReader& getTagReader() const override;
+
+ const std::filesystem::path _filePath;
+ std::unique_ptr<::TagLib::File> _file;
+ const AudioProperties _audioProperties;
+ const TagReader _tagReader;
+ const ImageReader _imageReader;
+ };
+} // namespace lms::audio::taglib
diff --git a/src/libs/metadata/impl/taglib/TagLibImageReader.cpp b/src/libs/audio/impl/taglib/ImageReader.cpp
similarity index 55%
rename from src/libs/metadata/impl/taglib/TagLibImageReader.cpp
rename to src/libs/audio/impl/taglib/ImageReader.cpp
index 927da6c4..adad0a24 100644
--- a/src/libs/metadata/impl/taglib/TagLibImageReader.cpp
+++ b/src/libs/audio/impl/taglib/ImageReader.cpp
@@ -17,7 +17,7 @@
* along with LMS. If not, see .
*/
-#include "TagLibImageReader.hpp"
+#include "ImageReader.hpp"
#include "TagLibDefs.hpp"
@@ -33,16 +33,14 @@
#include
#include
#include
+#include
#include
#include
#include
-#include "core/ILogger.hpp"
-#include "metadata/Exception.hpp"
+#include "core/String.hpp"
-#include "taglib/Utils.hpp"
-
-namespace lms::metadata::taglib
+namespace lms::audio::taglib
{
namespace
{
@@ -50,47 +48,47 @@ namespace lms::metadata::taglib
{
switch (type)
{
- case TagLib::ID3v2::AttachedPictureFrame::Type::Other:
+ case ::TagLib::ID3v2::AttachedPictureFrame::Type::Other:
return Image::Type::Other;
- case TagLib::ID3v2::AttachedPictureFrame::Type::FileIcon:
+ case ::TagLib::ID3v2::AttachedPictureFrame::Type::FileIcon:
return Image::Type::FileIcon;
- case TagLib::ID3v2::AttachedPictureFrame::Type::OtherFileIcon:
+ case ::TagLib::ID3v2::AttachedPictureFrame::Type::OtherFileIcon:
return Image::Type::OtherFileIcon;
- case TagLib::ID3v2::AttachedPictureFrame::Type::FrontCover:
+ case ::TagLib::ID3v2::AttachedPictureFrame::Type::FrontCover:
return Image::Type::FrontCover;
- case TagLib::ID3v2::AttachedPictureFrame::Type::BackCover:
+ case ::TagLib::ID3v2::AttachedPictureFrame::Type::BackCover:
return Image::Type::BackCover;
- case TagLib::ID3v2::AttachedPictureFrame::Type::LeafletPage:
+ case ::TagLib::ID3v2::AttachedPictureFrame::Type::LeafletPage:
return Image::Type::LeafletPage;
- case TagLib::ID3v2::AttachedPictureFrame::Type::Media:
+ case ::TagLib::ID3v2::AttachedPictureFrame::Type::Media:
return Image::Type::Media;
- case TagLib::ID3v2::AttachedPictureFrame::Type::LeadArtist:
+ case ::TagLib::ID3v2::AttachedPictureFrame::Type::LeadArtist:
return Image::Type::LeadArtist;
- case TagLib::ID3v2::AttachedPictureFrame::Type::Artist:
+ case ::TagLib::ID3v2::AttachedPictureFrame::Type::Artist:
return Image::Type::Artist;
- case TagLib::ID3v2::AttachedPictureFrame::Type::Conductor:
+ case ::TagLib::ID3v2::AttachedPictureFrame::Type::Conductor:
return Image::Type::Conductor;
- case TagLib::ID3v2::AttachedPictureFrame::Type::Band:
+ case ::TagLib::ID3v2::AttachedPictureFrame::Type::Band:
return Image::Type::Band;
- case TagLib::ID3v2::AttachedPictureFrame::Type::Composer:
+ case ::TagLib::ID3v2::AttachedPictureFrame::Type::Composer:
return Image::Type::Composer;
- case TagLib::ID3v2::AttachedPictureFrame::Type::Lyricist:
+ case ::TagLib::ID3v2::AttachedPictureFrame::Type::Lyricist:
return Image::Type::Lyricist;
- case TagLib::ID3v2::AttachedPictureFrame::Type::RecordingLocation:
+ case ::TagLib::ID3v2::AttachedPictureFrame::Type::RecordingLocation:
return Image::Type::RecordingLocation;
- case TagLib::ID3v2::AttachedPictureFrame::Type::DuringRecording:
+ case ::TagLib::ID3v2::AttachedPictureFrame::Type::DuringRecording:
return Image::Type::DuringRecording;
- case TagLib::ID3v2::AttachedPictureFrame::Type::DuringPerformance:
+ case ::TagLib::ID3v2::AttachedPictureFrame::Type::DuringPerformance:
return Image::Type::DuringPerformance;
- case TagLib::ID3v2::AttachedPictureFrame::Type::MovieScreenCapture:
+ case ::TagLib::ID3v2::AttachedPictureFrame::Type::MovieScreenCapture:
return Image::Type::MovieScreenCapture;
- case TagLib::ID3v2::AttachedPictureFrame::Type::ColouredFish:
+ case ::TagLib::ID3v2::AttachedPictureFrame::Type::ColouredFish:
return Image::Type::ColouredFish;
- case TagLib::ID3v2::AttachedPictureFrame::Type::Illustration:
+ case ::TagLib::ID3v2::AttachedPictureFrame::Type::Illustration:
return Image::Type::Illustration;
- case TagLib::ID3v2::AttachedPictureFrame::Type::BandLogo:
+ case ::TagLib::ID3v2::AttachedPictureFrame::Type::BandLogo:
return Image::Type::BandLogo;
- case TagLib::ID3v2::AttachedPictureFrame::Type::PublisherLogo:
+ case ::TagLib::ID3v2::AttachedPictureFrame::Type::PublisherLogo:
return Image::Type::PublisherLogo;
}
@@ -101,47 +99,47 @@ namespace lms::metadata::taglib
{
switch (type)
{
- case TagLib::ASF::Picture::Type::Other:
+ case ::TagLib::ASF::Picture::Type::Other:
return Image::Type::Other;
- case TagLib::ASF::Picture::Type::FileIcon:
+ case ::TagLib::ASF::Picture::Type::FileIcon:
return Image::Type::FileIcon;
- case TagLib::ASF::Picture::Type::OtherFileIcon:
+ case ::TagLib::ASF::Picture::Type::OtherFileIcon:
return Image::Type::OtherFileIcon;
- case TagLib::ASF::Picture::Type::FrontCover:
+ case ::TagLib::ASF::Picture::Type::FrontCover:
return Image::Type::FrontCover;
- case TagLib::ASF::Picture::Type::BackCover:
+ case ::TagLib::ASF::Picture::Type::BackCover:
return Image::Type::BackCover;
- case TagLib::ASF::Picture::Type::LeafletPage:
+ case ::TagLib::ASF::Picture::Type::LeafletPage:
return Image::Type::LeafletPage;
- case TagLib::ASF::Picture::Type::Media:
+ case ::TagLib::ASF::Picture::Type::Media:
return Image::Type::Media;
- case TagLib::ASF::Picture::Type::LeadArtist:
+ case ::TagLib::ASF::Picture::Type::LeadArtist:
return Image::Type::LeadArtist;
- case TagLib::ASF::Picture::Type::Artist:
+ case ::TagLib::ASF::Picture::Type::Artist:
return Image::Type::Artist;
- case TagLib::ASF::Picture::Type::Conductor:
+ case ::TagLib::ASF::Picture::Type::Conductor:
return Image::Type::Conductor;
- case TagLib::ASF::Picture::Type::Band:
+ case ::TagLib::ASF::Picture::Type::Band:
return Image::Type::Band;
- case TagLib::ASF::Picture::Type::Composer:
+ case ::TagLib::ASF::Picture::Type::Composer:
return Image::Type::Composer;
- case TagLib::ASF::Picture::Type::Lyricist:
+ case ::TagLib::ASF::Picture::Type::Lyricist:
return Image::Type::Lyricist;
- case TagLib::ASF::Picture::Type::RecordingLocation:
+ case ::TagLib::ASF::Picture::Type::RecordingLocation:
return Image::Type::RecordingLocation;
- case TagLib::ASF::Picture::Type::DuringRecording:
+ case ::TagLib::ASF::Picture::Type::DuringRecording:
return Image::Type::DuringRecording;
- case TagLib::ASF::Picture::Type::DuringPerformance:
+ case ::TagLib::ASF::Picture::Type::DuringPerformance:
return Image::Type::DuringPerformance;
- case TagLib::ASF::Picture::Type::MovieScreenCapture:
+ case ::TagLib::ASF::Picture::Type::MovieScreenCapture:
return Image::Type::MovieScreenCapture;
- case TagLib::ASF::Picture::Type::ColouredFish:
+ case ::TagLib::ASF::Picture::Type::ColouredFish:
return Image::Type::ColouredFish;
- case TagLib::ASF::Picture::Type::Illustration:
+ case ::TagLib::ASF::Picture::Type::Illustration:
return Image::Type::Illustration;
- case TagLib::ASF::Picture::Type::BandLogo:
+ case ::TagLib::ASF::Picture::Type::BandLogo:
return Image::Type::BandLogo;
- case TagLib::ASF::Picture::Type::PublisherLogo:
+ case ::TagLib::ASF::Picture::Type::PublisherLogo:
return Image::Type::PublisherLogo;
}
@@ -152,47 +150,47 @@ namespace lms::metadata::taglib
{
switch (type)
{
- case TagLib::FLAC::Picture::Type::Other:
+ case ::TagLib::FLAC::Picture::Type::Other:
return Image::Type::Other;
- case TagLib::FLAC::Picture::Type::FileIcon:
+ case ::TagLib::FLAC::Picture::Type::FileIcon:
return Image::Type::FileIcon;
- case TagLib::FLAC::Picture::Type::OtherFileIcon:
+ case ::TagLib::FLAC::Picture::Type::OtherFileIcon:
return Image::Type::OtherFileIcon;
- case TagLib::FLAC::Picture::Type::FrontCover:
+ case ::TagLib::FLAC::Picture::Type::FrontCover:
return Image::Type::FrontCover;
- case TagLib::FLAC::Picture::Type::BackCover:
+ case ::TagLib::FLAC::Picture::Type::BackCover:
return Image::Type::BackCover;
- case TagLib::FLAC::Picture::Type::LeafletPage:
+ case ::TagLib::FLAC::Picture::Type::LeafletPage:
return Image::Type::LeafletPage;
- case TagLib::FLAC::Picture::Type::Media:
+ case ::TagLib::FLAC::Picture::Type::Media:
return Image::Type::Media;
- case TagLib::FLAC::Picture::Type::LeadArtist:
+ case ::TagLib::FLAC::Picture::Type::LeadArtist:
return Image::Type::LeadArtist;
- case TagLib::FLAC::Picture::Type::Artist:
+ case ::TagLib::FLAC::Picture::Type::Artist:
return Image::Type::Artist;
- case TagLib::FLAC::Picture::Type::Conductor:
+ case ::TagLib::FLAC::Picture::Type::Conductor:
return Image::Type::Conductor;
- case TagLib::FLAC::Picture::Type::Band:
+ case ::TagLib::FLAC::Picture::Type::Band:
return Image::Type::Band;
- case TagLib::FLAC::Picture::Type::Composer:
+ case ::TagLib::FLAC::Picture::Type::Composer:
return Image::Type::Composer;
- case TagLib::FLAC::Picture::Type::Lyricist:
+ case ::TagLib::FLAC::Picture::Type::Lyricist:
return Image::Type::Lyricist;
- case TagLib::FLAC::Picture::Type::RecordingLocation:
+ case ::TagLib::FLAC::Picture::Type::RecordingLocation:
return Image::Type::RecordingLocation;
- case TagLib::FLAC::Picture::Type::DuringRecording:
+ case ::TagLib::FLAC::Picture::Type::DuringRecording:
return Image::Type::DuringRecording;
- case TagLib::FLAC::Picture::Type::DuringPerformance:
+ case ::TagLib::FLAC::Picture::Type::DuringPerformance:
return Image::Type::DuringPerformance;
- case TagLib::FLAC::Picture::Type::MovieScreenCapture:
+ case ::TagLib::FLAC::Picture::Type::MovieScreenCapture:
return Image::Type::MovieScreenCapture;
- case TagLib::FLAC::Picture::Type::ColouredFish:
+ case ::TagLib::FLAC::Picture::Type::ColouredFish:
return Image::Type::ColouredFish;
- case TagLib::FLAC::Picture::Type::Illustration:
+ case ::TagLib::FLAC::Picture::Type::Illustration:
return Image::Type::Illustration;
- case TagLib::FLAC::Picture::Type::BandLogo:
+ case ::TagLib::FLAC::Picture::Type::BandLogo:
return Image::Type::BandLogo;
- case TagLib::FLAC::Picture::Type::PublisherLogo:
+ case ::TagLib::FLAC::Picture::Type::PublisherLogo:
return Image::Type::PublisherLogo;
}
@@ -203,44 +201,44 @@ namespace lms::metadata::taglib
{
switch (format)
{
- case TagLib::MP4::CoverArt::Format::BMP:
+ case ::TagLib::MP4::CoverArt::Format::BMP:
return "image/bmp";
- case TagLib::MP4::CoverArt::Format::GIF:
+ case ::TagLib::MP4::CoverArt::Format::GIF:
return "image/gif";
- case TagLib::MP4::CoverArt::Format::JPEG:
+ case ::TagLib::MP4::CoverArt::Format::JPEG:
return "image/jpeg";
- case TagLib::MP4::CoverArt::Format::PNG:
+ case ::TagLib::MP4::CoverArt::Format::PNG:
return "image/png";
- case TagLib::MP4::CoverArt::Format::Unknown:
+ case ::TagLib::MP4::CoverArt::Format::Unknown:
return "application/octet-stream";
}
return "application/octet-stream";
}
-#if TAGLIB_HAS_APE_COMPLEX_PROPERTIES
+#if LMS_TAGLIB_HAS_APE_COMPLEX_PROPERTIES
Image::Type imageTypeFromAPEPictureType(std::string_view pictureType)
{
if (core::stringUtils::stringCaseInsensitiveContains(pictureType, "front"))
return Image::Type::FrontCover;
- else if (core::stringUtils::stringCaseInsensitiveContains(pictureType, "back"))
+ if (core::stringUtils::stringCaseInsensitiveContains(pictureType, "back"))
return Image::Type::BackCover;
return Image::Type::Unknown;
}
-#endif // TAGLIB_HAS_APE_COMPLEX_PROPERTIES
+#endif // LMS_TAGLIB_HAS_APE_COMPLEX_PROPERTIES
- void visitID3V2Images(const TagLib::ID3v2::Tag& id3v2Tags, TagLibImageReader::ImageVisitor visitor)
+ void visitID3V2Images(const ::TagLib::ID3v2::Tag& id3v2Tags, const ImageReader::ImageVisitor& visitor)
{
const auto& frameListMap{ id3v2Tags.frameListMap() };
- for (const TagLib::ID3v2::Frame* frame : frameListMap["APIC"])
+ for (const ::TagLib::ID3v2::Frame* frame : frameListMap["APIC"])
{
- const auto* attachedPictureFrame{ dynamic_cast(frame) };
+ const auto* attachedPictureFrame{ dynamic_cast(frame) };
if (!attachedPictureFrame)
continue;
- TagLib::ByteVector picture{ attachedPictureFrame->picture() };
+ ::TagLib::ByteVector picture{ attachedPictureFrame->picture() };
std::span pictureData{ reinterpret_cast(picture.data()), picture.size() };
Image image;
@@ -252,15 +250,15 @@ namespace lms::metadata::taglib
}
}
- void visitASFImages(const TagLib::ASF::Tag& asfTags, TagLibImageReader::ImageVisitor visitor)
+ void visitASFImages(const ::TagLib::ASF::Tag& asfTags, const ImageReader::ImageVisitor& visitor)
{
- for (const TagLib::ASF::Attribute& attribute : asfTags.attribute("WM/Picture"))
+ for (const ::TagLib::ASF::Attribute& attribute : asfTags.attribute("WM/Picture"))
{
- TagLib::ASF::Picture asfPicture{ attribute.toPicture() };
+ ::TagLib::ASF::Picture asfPicture{ attribute.toPicture() };
if (!asfPicture.isValid())
continue;
- TagLib::ByteVector picture{ asfPicture.picture() };
+ ::TagLib::ByteVector picture{ asfPicture.picture() };
std::span pictureData{ reinterpret_cast(picture.data()), picture.size() };
Image image;
@@ -273,22 +271,22 @@ namespace lms::metadata::taglib
}
}
- void visitMP4Images(const TagLib::MP4::File& mp4File, TagLibImageReader::ImageVisitor visitor)
+ void visitMP4Images(const ::TagLib::MP4::File& mp4File, const ImageReader::ImageVisitor& visitor)
{
- const TagLib::MP4::Item coverItem{ mp4File.tag()->item("covr") };
+ const ::TagLib::MP4::Item coverItem{ mp4File.tag()->item("covr") };
if (!coverItem.isValid())
return;
-#if TAGLIB_HAS_MP4_ITEM_TYPE
- if (coverItem.type() != TagLib::MP4::Item::Type::CoverArtList)
+#if LMS_TAGLIB_HAS_MP4_ITEM_TYPE
+ if (coverItem.type() != ::TagLib::MP4::Item::Type::CoverArtList)
return;
#endif
- TagLib::MP4::CoverArtList coverArtList{ coverItem.toCoverArtList() };
+ ::TagLib::MP4::CoverArtList coverArtList{ coverItem.toCoverArtList() };
bool firstCover{ true };
for (TagLib::MP4::CoverArt& coverArt : coverArtList)
{
- TagLib::ByteVector picture{ coverArt.data() };
+ ::TagLib::ByteVector picture{ coverArt.data() };
std::span pictureData{ reinterpret_cast(picture.data()), picture.size() };
Image image;
@@ -303,11 +301,11 @@ namespace lms::metadata::taglib
}
}
- void visitFLACImages(const TagLib::List pictureList, TagLibImageReader::ImageVisitor visitor)
+ void visitFLACImages(const ::TagLib::List& pictureList, const ImageReader::ImageVisitor& visitor)
{
for (TagLib::FLAC::Picture* flacPicture : pictureList)
{
- TagLib::ByteVector picture{ flacPicture->data() };
+ ::TagLib::ByteVector picture{ flacPicture->data() };
std::span pictureData{ reinterpret_cast(picture.data()), picture.size() };
Image image;
@@ -320,14 +318,14 @@ namespace lms::metadata::taglib
}
}
- void visitAPEImages([[maybe_unused]] const TagLib::APE::Tag& apeTags, [[maybe_unused]] TagLibImageReader::ImageVisitor visitor)
+ void visitAPEImages([[maybe_unused]] const ::TagLib::APE::Tag& apeTags, [[maybe_unused]] const ImageReader::ImageVisitor& visitor)
{
-#if TAGLIB_HAS_APE_COMPLEX_PROPERTIES
- const TagLib::List pictureProperties{ apeTags.complexProperties("PICTURE") };
- for (const TagLib::VariantMap& pictureProperty : pictureProperties)
+#if LMS_TAGLIB_HAS_APE_COMPLEX_PROPERTIES
+ const ::TagLib::List pictureProperties{ apeTags.complexProperties("PICTURE") };
+ for (const ::TagLib::VariantMap& pictureProperty : pictureProperties)
{
Image image;
- TagLib::ByteVector picture;
+ ::TagLib::ByteVector picture;
if (auto it{ pictureProperty.find("pictureType") }; it != pictureProperty.cend())
image.type = imageTypeFromAPEPictureType(it->second.toString().to8Bit(true));
@@ -344,81 +342,77 @@ namespace lms::metadata::taglib
if (!image.data.empty())
visitor(image);
}
-
-#endif // TAGLIB_HAS_APE_COMPLEX_PROPERTIES
+#endif // LMS_TAGLIB_HAS_APE_COMPLEX_PROPERTIES
}
} // namespace
- TagLibImageReader::TagLibImageReader(const std::filesystem::path& p)
- : _file{ utils::parseFile(p, TagLib::AudioProperties::ReadStyle::Fast, utils::ReadAudioProperties{ false }) }
+ ImageReader::ImageReader(::TagLib::File& file)
+ : _file{ file }
{
- if (!_file)
- {
- LMS_LOG(METADATA, ERROR, "File " << p << ": parsing failed");
- throw AudioFileParsingException{};
- }
}
- void TagLibImageReader::visitImages(ImageVisitor visitor) const
+ ImageReader::~ImageReader() = default;
+
+ void ImageReader::visitImages(const ImageVisitor& visitor) const
{
// MP3
- if (TagLib::MPEG::File * mp3File{ dynamic_cast(_file.get()) })
+ if (TagLib::MPEG::File * mp3File{ dynamic_cast(&_file) })
{
if (mp3File->hasID3v2Tag())
- visitID3V2Images(*mp3File->ID3v2Tag(), std::move(visitor));
+ visitID3V2Images(*mp3File->ID3v2Tag(), visitor);
}
// MP4
- else if (TagLib::MP4::File * mp4File{ dynamic_cast(_file.get()) })
+ else if (const TagLib::MP4::File * mp4File{ dynamic_cast(&_file) })
{
- visitMP4Images(*mp4File, std::move(visitor));
+ visitMP4Images(*mp4File, visitor);
}
// WMA
- else if (TagLib::ASF::File * asfFile{ dynamic_cast(_file.get()) })
+ else if (const TagLib::ASF::File * asfFile{ dynamic_cast(&_file) })
{
- if (const TagLib::ASF::Tag * tag{ asfFile->tag() })
- visitASFImages(*tag, std::move(visitor));
+ if (const ::TagLib::ASF::Tag * tag{ asfFile->tag() })
+ visitASFImages(*tag, visitor);
}
// FLAC
- else if (TagLib::FLAC::File * flacFile{ dynamic_cast(_file.get()) })
+ else if (TagLib::FLAC::File * flacFile{ dynamic_cast(&_file) })
{
if (flacFile->hasID3v2Tag()) // usage discouraged
- visitID3V2Images(*flacFile->ID3v2Tag(), std::move(visitor));
+ visitID3V2Images(*flacFile->ID3v2Tag(), visitor);
else
- visitFLACImages(flacFile->pictureList(), std::move(visitor));
+ visitFLACImages(flacFile->pictureList(), visitor);
}
// Ogg vorbis
- else if (TagLib::Ogg::Vorbis::File * vorbisFile{ dynamic_cast(_file.get()) })
+ else if (const TagLib::Ogg::Vorbis::File * vorbisFile{ dynamic_cast(&_file) })
{
- visitFLACImages(vorbisFile->tag()->pictureList(), std::move(visitor));
+ visitFLACImages(vorbisFile->tag()->pictureList(), visitor);
}
// Ogg Opus
- else if (TagLib::Ogg::Opus::File * opusFile{ dynamic_cast(_file.get()) })
+ else if (const TagLib::Ogg::Opus::File * opusFile{ dynamic_cast(&_file) })
{
- visitFLACImages(opusFile->tag()->pictureList(), std::move(visitor));
+ visitFLACImages(opusFile->tag()->pictureList(), visitor);
}
// Aiff
- else if (TagLib::RIFF::AIFF::File * aiffFile{ dynamic_cast(_file.get()) })
+ else if (const TagLib::RIFF::AIFF::File * aiffFile{ dynamic_cast(&_file) })
{
if (aiffFile->hasID3v2Tag())
- visitID3V2Images(*aiffFile->tag(), std::move(visitor));
+ visitID3V2Images(*aiffFile->tag(), visitor);
}
// Wav
- else if (TagLib::RIFF::WAV::File * wavFile{ dynamic_cast(_file.get()) })
+ else if (const TagLib::RIFF::WAV::File * wavFile{ dynamic_cast(&_file) })
{
if (wavFile->hasID3v2Tag())
- visitID3V2Images(*wavFile->ID3v2Tag(), std::move(visitor));
+ visitID3V2Images(*wavFile->ID3v2Tag(), visitor);
}
// MPC
- else if (TagLib::MPC::File * mpcFile{ dynamic_cast(_file.get()) })
+ else if (TagLib::MPC::File * mpcFile{ dynamic_cast(&_file) })
{
if (mpcFile->hasAPETag())
- visitAPEImages(*mpcFile->APETag(), std::move(visitor));
+ visitAPEImages(*mpcFile->APETag(), visitor);
}
// WavPack
- else if (TagLib::WavPack::File * wavPackFile{ dynamic_cast(_file.get()) })
+ else if (TagLib::WavPack::File * wavPackFile{ dynamic_cast(&_file) })
{
if (wavPackFile->hasAPETag())
- visitAPEImages(*wavPackFile->APETag(), std::move(visitor));
+ visitAPEImages(*wavPackFile->APETag(), visitor);
}
}
-} // namespace lms::metadata::taglib
+} // namespace lms::audio::taglib
diff --git a/src/libs/metadata/impl/avformat/AvFormatImageReader.hpp b/src/libs/audio/impl/taglib/ImageReader.hpp
similarity index 57%
rename from src/libs/metadata/impl/avformat/AvFormatImageReader.hpp
rename to src/libs/audio/impl/taglib/ImageReader.hpp
index 0658b8a7..b4adb32c 100644
--- a/src/libs/metadata/impl/avformat/AvFormatImageReader.hpp
+++ b/src/libs/audio/impl/taglib/ImageReader.hpp
@@ -19,27 +19,26 @@
#pragma once
-#include "IImageReader.hpp"
+#include "audio/IImageReader.hpp"
-namespace lms::av
+namespace TagLib
{
- class IAudioFile;
+ class File;
}
-namespace lms::metadata::avformat
+namespace lms::audio::taglib
{
- class AvFormatImageReader : public IImageReader
+ class ImageReader : public IImageReader
{
public:
- AvFormatImageReader(const std::filesystem::path& p);
- ~AvFormatImageReader() override;
-
- AvFormatImageReader(const AvFormatImageReader&) = delete;
- AvFormatImageReader& operator=(const AvFormatImageReader&) = delete;
+ ImageReader(TagLib::File& _file);
+ ~ImageReader() override;
+ ImageReader(const ImageReader&) = delete;
+ ImageReader& operator=(const ImageReader&) = delete;
private:
- void visitImages(ImageVisitor visitor) const override;
+ void visitImages(const ImageVisitor& visitor) const override;
- std::unique_ptr _audioFile;
+ TagLib::File& _file;
};
-} // namespace lms::metadata::avformat
+} // namespace lms::audio::taglib
diff --git a/src/libs/metadata/impl/taglib/TagLibDefs.hpp b/src/libs/audio/impl/taglib/TagLibDefs.hpp
similarity index 72%
rename from src/libs/metadata/impl/taglib/TagLibDefs.hpp
rename to src/libs/audio/impl/taglib/TagLibDefs.hpp
index cbcbbf6d..b4f73887 100644
--- a/src/libs/metadata/impl/taglib/TagLibDefs.hpp
+++ b/src/libs/audio/impl/taglib/TagLibDefs.hpp
@@ -22,15 +22,20 @@
#include
#if (TAGLIB_MAJOR_VERSION >= 2)
- #define TAGLIB_HAS_DSF 1
+ #define LMS_TAGLIB_HAS_DSF 1
#endif
-// TAGLIB_HAS_MP4_ITEM_TYPE if version >= 2.0.1
+// LMS_TAGLIB_HAS_MP4_ITEM_TYPE if version >= 2.0.1
#if ((TAGLIB_MAJOR_VERSION > 2) || (TAGLIB_MAJOR_VERSION == 2 && TAGLIB_MINOR_VERSION > 0) || (TAGLIB_MAJOR_VERSION == 2 && TAGLIB_PATCH_VERSION >= 1))
- #define TAGLIB_HAS_MP4_ITEM_TYPE 1
+ #define LMS_TAGLIB_HAS_MP4_ITEM_TYPE 1
#endif
-// TAGLIB_HAS_APE_COMPLEX_PROPERTIES if version >= 2.0.2
+// LMS_TAGLIB_HAS_APE_COMPLEX_PROPERTIES if version >= 2.0.2
#if ((TAGLIB_MAJOR_VERSION > 2) || (TAGLIB_MAJOR_VERSION == 2 && TAGLIB_MINOR_VERSION > 0) || (TAGLIB_MAJOR_VERSION == 2 && TAGLIB_PATCH_VERSION >= 2))
- #define TAGLIB_HAS_APE_COMPLEX_PROPERTIES 1
+ #define LMS_TAGLIB_HAS_APE_COMPLEX_PROPERTIES 1
+#endif
+
+// LMS_TAGLIB_HAS_SHORTEN if version >= 2.1
+#if ((TAGLIB_MAJOR_VERSION > 2) || (TAGLIB_MAJOR_VERSION == 2 && TAGLIB_MINOR_VERSION >= 1))
+ #define LMS_TAGLIB_HAS_SHORTEN 1
#endif
diff --git a/src/libs/metadata/impl/taglib/TagLibTagReader.cpp b/src/libs/audio/impl/taglib/TagReader.cpp
similarity index 72%
rename from src/libs/metadata/impl/taglib/TagLibTagReader.cpp
rename to src/libs/audio/impl/taglib/TagReader.cpp
index b2fd0db8..a905f7b5 100644
--- a/src/libs/metadata/impl/taglib/TagLibTagReader.cpp
+++ b/src/libs/audio/impl/taglib/TagReader.cpp
@@ -17,7 +17,7 @@
* along with LMS. If not, see .
*/
-#include "TagLibTagReader.hpp"
+#include "TagReader.hpp"
#include
@@ -25,7 +25,6 @@
#include
#include
-#include
#include
#include
#include
@@ -38,33 +37,25 @@
#include
#include
#include
+#include
#include
#include
#include
#include
#include
#include
-#if TAGLIB_HAS_DSF
+#if LMS_TAGLIB_HAS_DSF
#include
#include
#endif
#include "core/ILogger.hpp"
#include "core/String.hpp"
-#include "metadata/Exception.hpp"
-#include "Utils.hpp"
-
-namespace lms::metadata::taglib
+namespace lms::audio::taglib
{
namespace
{
- class TagParsingFailedException : public Exception
- {
- public:
- using Exception::Exception;
- };
-
// Mapping to internal taglib names and/or common alternative custom names
const std::unordered_map> tagLibTagMapping{
{ TagType::AcoustID, { "ACOUSTID_ID", "ACOUSTID ID" } },
@@ -181,7 +172,7 @@ namespace lms::metadata::taglib
{ TagType::Writer, { "WRITER" } },
};
- void mergeTagMaps(TagLib::PropertyMap& dst, TagLib::PropertyMap&& src)
+ void mergeTagMaps(TagLib::PropertyMap& dst, ::TagLib::PropertyMap&& src)
{
for (auto&& [tag, values] : src)
{
@@ -190,49 +181,36 @@ namespace lms::metadata::taglib
}
}
- void dedupTagValues(TagLib::PropertyMap& propertyMap, const std::filesystem::path& file)
+ void dedupTagValues(TagLib::PropertyMap& propertyMap)
{
for (auto& [key, values] : propertyMap)
{
if (values.size() <= 1)
continue;
- TagLib::StringList newList;
- for (const TagLib::String& value : values)
+ ::TagLib::StringList newList;
+ for (const ::TagLib::String& value : values)
{
- if (!std::any_of(std::cbegin(newList), std::cend(newList), [&](const TagLib::String& v) { return v == value; }))
+ if (!std::any_of(std::cbegin(newList), std::cend(newList), [&](const ::TagLib::String& v) { return v == value; }))
newList.append(value);
}
if (values != newList)
{
- LMS_LOG(METADATA, DEBUG, "File " << file << ": removed " << (values.size() - newList.size()) << " duplicated value(s) in tag '" << key << "', " << newList.size() << " remaining value(s)");
+ LMS_LOG(METADATA, DEBUG, "Removed " << (values.size() - newList.size()) << " duplicated value(s) in tag '" << key << "', " << newList.size() << " remaining value(s)");
values = newList;
}
}
}
} // namespace
- TagLibTagReader::TagLibTagReader(const std::filesystem::path& p, ParserReadStyle parserReadStyle, bool debug)
- : _file{ utils::parseFile(p, utils::readStyleToTagLibReadStyle(parserReadStyle), utils::ReadAudioProperties{ true }) }
+ TagReader::TagReader(::TagLib::File& file, bool enableExtraDebugLogs)
+ : _file{ file }
{
- if (!_file)
- {
- LMS_LOG(METADATA, ERROR, "File " << p << ": parsing failed");
- throw AudioFileParsingException{ "Parsing failed" };
- }
+ _propertyMap = _file.properties();
- if (!_file->audioProperties())
- {
- LMS_LOG(METADATA, ERROR, "File " << p << ": no audio properties");
- throw AudioFileNoAudioPropertiesException{};
- }
-
- computeAudioProperties();
-
- _propertyMap = _file->properties();
-
- if (debug && core::Service::get()->isSeverityActive(core::logging::Severity::DEBUG))
+ enableExtraDebugLogs &= core::Service::get()->isSeverityActive(core::logging::Severity::DEBUG);
+ if (enableExtraDebugLogs)
{
for (const auto& [key, values] : _propertyMap)
{
@@ -245,7 +223,7 @@ namespace lms::metadata::taglib
}
// Some tags may not be known by TagLib
- auto getAPETags = [&](const TagLib::APE::Tag* apeTag) {
+ auto getAPETags = [&](const ::TagLib::APE::Tag* apeTag) {
if (!apeTag)
return;
@@ -254,7 +232,7 @@ namespace lms::metadata::taglib
auto processID3v2Tags = [&](TagLib::ID3v2::Tag& id3v2Tags) {
// Dedup values for some tags that may be written in both a standard tag and in a custom tag
- dedupTagValues(_propertyMap, p);
+ dedupTagValues(_propertyMap);
const auto& frameListMap{ id3v2Tags.frameListMap() };
@@ -264,26 +242,30 @@ namespace lms::metadata::taglib
// consider each frame hold a different set of lyrics
// Synchronized lyrics frames
- for (const TagLib::ID3v2::Frame* frame : frameListMap["SYLT"])
+ for (const ::TagLib::ID3v2::Frame* frame : frameListMap["SYLT"])
{
- const auto* lyricsFrame{ dynamic_cast(frame) };
+ const auto* lyricsFrame{ dynamic_cast(frame) };
if (!lyricsFrame)
continue; // TODO log or assert?
const std::string language{ lyricsFrame->language().data(), lyricsFrame->language().size() };
std::string lyrics;
- for (const TagLib::ID3v2::SynchronizedLyricsFrame::SynchedText& synchedText : lyricsFrame->synchedText())
+ for (const ::TagLib::ID3v2::SynchronizedLyricsFrame::SynchedText& synchedText : lyricsFrame->synchedText())
{
std::chrono::milliseconds timestamp{};
switch (lyricsFrame->timestampFormat())
{
- case TagLib::ID3v2::SynchronizedLyricsFrame::AbsoluteMilliseconds:
+ case ::TagLib::ID3v2::SynchronizedLyricsFrame::AbsoluteMilliseconds:
timestamp = std::chrono::milliseconds{ synchedText.time };
break;
- case TagLib::ID3v2::SynchronizedLyricsFrame::AbsoluteMpegFrames:
- timestamp = std::chrono::milliseconds{ _audioProperties.sampleRate ? (synchedText.time * 1000) / _audioProperties.sampleRate : 0 };
+ case ::TagLib::ID3v2::SynchronizedLyricsFrame::AbsoluteMpegFrames:
+ {
+ const ::TagLib::AudioProperties* properties{ file.audioProperties() };
+ if (properties && properties->sampleRate())
+ timestamp = std::chrono::milliseconds{ synchedText.time * 1000 / properties->sampleRate() };
+ }
break;
- case TagLib::ID3v2::SynchronizedLyricsFrame::Unknown:
+ case ::TagLib::ID3v2::SynchronizedLyricsFrame::Unknown:
break;
}
@@ -298,9 +280,9 @@ namespace lms::metadata::taglib
}
// Unsynchronized lyrics frames
- for (const TagLib::ID3v2::Frame* frame : frameListMap["USLT"])
+ for (const ::TagLib::ID3v2::Frame* frame : frameListMap["USLT"])
{
- const auto* lyricsFrame{ dynamic_cast(frame) };
+ const auto* lyricsFrame{ dynamic_cast(frame) };
if (!lyricsFrame)
continue; // TODO log or assert?
@@ -310,9 +292,9 @@ namespace lms::metadata::taglib
};
// WMA
- if (TagLib::ASF::File * asfFile{ dynamic_cast(_file.get()) })
+ if (const ::TagLib::ASF::File * asfFile{ dynamic_cast(&_file) })
{
- if (const TagLib::ASF::Tag * tag{ asfFile->tag() })
+ if (const ::TagLib::ASF::Tag * tag{ asfFile->tag() })
{
for (const auto& [name, attributeList] : tag->attributeListMap())
{
@@ -320,19 +302,19 @@ namespace lms::metadata::taglib
continue;
const std::string strName{ core::stringUtils::stringToUpper(name.to8Bit(true)) };
- if (debug)
+ if (enableExtraDebugLogs)
{
for (const auto& attribute : attributeList)
- LMS_LOG(METADATA, DEBUG, "ASF Attribute, Key = '" << strName << "', value = '" << (attribute.type() == TagLib::ASF::Attribute::AttributeTypes::UnicodeType ? attribute.toString() : TagLib::String{ "" }) << "'");
+ LMS_LOG(METADATA, DEBUG, "ASF Attribute, Key = '" << strName << "', value = '" << (attribute.type() == ::TagLib::ASF::Attribute::AttributeTypes::UnicodeType ? attribute.toString() : ::TagLib::String{ "" }) << "'");
}
if (strName.find("WM/") == 0 || _propertyMap.contains(strName))
continue;
- TagLib::StringList strAttributes;
- for (const TagLib::ASF::Attribute& attribute : attributeList)
+ ::TagLib::StringList strAttributes;
+ for (const ::TagLib::ASF::Attribute& attribute : attributeList)
{
- if (attribute.type() == TagLib::ASF::Attribute::AttributeTypes::UnicodeType)
+ if (attribute.type() == ::TagLib::ASF::Attribute::AttributeTypes::UnicodeType)
strAttributes.append(attribute.toString());
}
@@ -356,23 +338,23 @@ namespace lms::metadata::taglib
}
}
// MP3
- else if (TagLib::MPEG::File * mp3File{ dynamic_cast(_file.get()) })
+ else if (TagLib::MPEG::File * mp3File{ dynamic_cast(&_file) })
{
if (mp3File->hasID3v2Tag())
- processID3v2Tags(*mp3File->ID3v2Tag());
+ processID3v2Tags(*mp3File->ID3v2Tag(false));
getAPETags(mp3File->APETag());
}
// MP4
- else if (TagLib::MP4::File * mp4File{ dynamic_cast(_file.get()) })
+ else if (const ::TagLib::MP4::File * mp4File{ dynamic_cast(&_file) })
{
// Taglib does not expose rtng in properties
- if (const TagLib::MP4::Item rtngItem{ mp4File->tag()->item("rtng") }; rtngItem.isValid())
+ if (const ::TagLib::MP4::Item rtngItem{ mp4File->tag()->item("rtng") }; rtngItem.isValid())
{
-#if TAGLIB_HAS_MP4_ITEM_TYPE
- if (rtngItem.type() == TagLib::MP4::Item::Type::Byte)
+#if LMS_TAGLIB_HAS_MP4_ITEM_TYPE
+ if (rtngItem.type() == ::TagLib::MP4::Item::Type::Byte)
#endif
- _propertyMap["ITUNESADVISORY"] = TagLib::String{ std::to_string(rtngItem.toByte()) };
+ _propertyMap["ITUNESADVISORY"] = ::TagLib::String{ std::to_string(rtngItem.toByte()) };
}
if (!_propertyMap.contains("ORIGINALDATE"))
@@ -386,7 +368,7 @@ namespace lms::metadata::taglib
auto itOrigDateTag{ tags.find(origDateString) };
if (itOrigDateTag != std::cend(tags))
{
- const TagLib::StringList dates{ itOrigDateTag->second.toStringList() };
+ const ::TagLib::StringList dates{ itOrigDateTag->second.toStringList() };
if (!dates.isEmpty())
{
_propertyMap["ORIGINALDATE"] = dates.front();
@@ -397,68 +379,36 @@ namespace lms::metadata::taglib
}
}
// MPC
- else if (TagLib::MPC::File * mpcFile{ dynamic_cast(_file.get()) })
+ else if (::TagLib::MPC::File * mpcFile{ dynamic_cast(&_file) })
{
getAPETags(mpcFile->APETag());
}
// WavPack
- else if (TagLib::WavPack::File * wavPackFile{ dynamic_cast(_file.get()) })
+ else if (TagLib::WavPack::File * wavPackFile{ dynamic_cast(&_file) })
{
getAPETags(wavPackFile->APETag());
}
// FLAC
- else if (TagLib::FLAC::File * flacFile{ dynamic_cast(_file.get()) })
+ else if (TagLib::FLAC::File * flacFile{ dynamic_cast(&_file) })
{
if (flacFile->hasID3v2Tag()) // discouraged usage
processID3v2Tags(*flacFile->ID3v2Tag());
}
- else if (TagLib::RIFF::AIFF::File * aiffFile{ dynamic_cast(_file.get()) })
+ else if (const TagLib::RIFF::AIFF::File * aiffFile{ dynamic_cast(&_file) })
{
if (aiffFile->hasID3v2Tag())
processID3v2Tags(*aiffFile->tag());
}
- else if (TagLib::RIFF::WAV::File * wavFile{ dynamic_cast(_file.get()) })
+ else if (const TagLib::RIFF::WAV::File * wavFile{ dynamic_cast(&_file) })
{
if (wavFile->hasID3v2Tag())
processID3v2Tags(*wavFile->ID3v2Tag());
}
}
- TagLibTagReader::~TagLibTagReader() = default;
+ TagReader::~TagReader() = default;
- void TagLibTagReader::computeAudioProperties()
- {
- const TagLib::AudioProperties* properties{ _file->audioProperties() };
-
- // Common properties
- _audioProperties.bitrate = static_cast(properties->bitrate() * 1000);
- _audioProperties.channelCount = static_cast(_file->audioProperties()->channels());
- _audioProperties.duration = std::chrono::milliseconds{ properties->lengthInMilliseconds() };
- _audioProperties.sampleRate = static_cast(properties->sampleRate());
-
- if (const auto* apeProperties{ dynamic_cast(properties) })
- _audioProperties.bitsPerSample = apeProperties->bitsPerSample();
- if (const auto* asfProperties{ dynamic_cast(properties) })
- _audioProperties.bitsPerSample = asfProperties->bitsPerSample();
- else if (const auto* flacProperties{ dynamic_cast(properties) })
- _audioProperties.bitsPerSample = flacProperties->bitsPerSample();
- else if (const auto* mp4Properties{ dynamic_cast(properties) })
- _audioProperties.bitsPerSample = mp4Properties->bitsPerSample();
- else if (const auto* wavePackProperties{ dynamic_cast(properties) })
- _audioProperties.bitsPerSample = wavePackProperties->bitsPerSample();
- else if (const auto* aiffProperties{ dynamic_cast(properties) })
- _audioProperties.bitsPerSample = aiffProperties->bitsPerSample();
- else if (const auto* wavProperties{ dynamic_cast(properties) })
- _audioProperties.bitsPerSample = wavProperties->bitsPerSample();
-#if TAGLIB_HAS_DSF
- else if (const auto* dsfProperties{ dynamic_cast(properties) })
- _audioProperties.bitsPerSample = dsfProperties->bitsPerSample();
- else if (const auto* dsfProperties{ dynamic_cast(properties) })
- _audioProperties.bitsPerSample = dsfProperties->bitsPerSample();
-#endif
- }
-
- void TagLibTagReader::visitTagValues(TagType tag, TagValueVisitor visitor) const
+ void TagReader::visitTagValues(TagType tag, TagValueVisitor visitor) const
{
auto itTagNames{ tagLibTagMapping.find(tag) };
if (itTagNames == std::cend(tagLibTagMapping))
@@ -478,19 +428,19 @@ namespace lms::metadata::taglib
}
}
- void TagLibTagReader::visitTagValues(std::string_view tag, TagValueVisitor visitor) const
+ void TagReader::visitTagValues(std::string_view tag, TagValueVisitor visitor) const
{
- TagLib::String key{ tag.data() /* assume null terminated */, TagLib::String::Type::UTF8 };
+ ::TagLib::String key{ tag.data() /* assume null terminated */, ::TagLib::String::Type::UTF8 };
auto itValues{ _propertyMap.find(key) };
if (itValues == std::cend(_propertyMap))
return;
- for (const TagLib::String& value : itValues->second)
+ for (const ::TagLib::String& value : itValues->second)
visitor(value.to8Bit(true));
}
- void TagLibTagReader::visitPerformerTags(PerformerVisitor visitor) const
+ void TagReader::visitPerformerTags(PerformerVisitor visitor) const
{
visitTagValues("PERFORMER", [&](std::string_view value) {
visitor("", value);
@@ -505,7 +455,7 @@ namespace lms::metadata::taglib
assert(rolePos != std::string::npos);
std::string_view role{ std::string_view{ performerStr }.substr(rolePos + 1) };
- for (const TagLib::String& value : values)
+ for (const ::TagLib::String& value : values)
{
const std::string name{ value.to8Bit(true) };
visitor(role, name);
@@ -514,7 +464,7 @@ namespace lms::metadata::taglib
}
}
- void TagLibTagReader::visitLyricsTags(LyricsVisitor visitor) const
+ void TagReader::visitLyricsTags(LyricsVisitor visitor) const
{
if (!_id3v2Lyrics.empty())
{
@@ -529,4 +479,4 @@ namespace lms::metadata::taglib
});
}
}
-} // namespace lms::metadata::taglib
+} // namespace lms::audio::taglib
diff --git a/src/libs/metadata/impl/taglib/TagLibTagReader.hpp b/src/libs/audio/impl/taglib/TagReader.hpp
similarity index 61%
rename from src/libs/metadata/impl/taglib/TagLibTagReader.hpp
rename to src/libs/audio/impl/taglib/TagReader.hpp
index 07034a38..7bb98e84 100644
--- a/src/libs/metadata/impl/taglib/TagLibTagReader.hpp
+++ b/src/libs/audio/impl/taglib/TagReader.hpp
@@ -19,38 +19,37 @@
#pragma once
-#include
#include