From 2eac47470d55aef56089faa39b170f7f72135565 Mon Sep 17 00:00:00 2001 From: Jay Moore Date: Mon, 1 Sep 2025 02:24:51 -0400 Subject: [PATCH] I'm 40% Dolomite! --- README.md | 19 +- src/decoder/plugins/DvdaIsoDecoderPlugin.cxx | 178 +++++++++++++++---- src/lib/dvdaiso/dvda_disc.cpp | 53 ++---- 3 files changed, 167 insertions(+), 83 deletions(-) diff --git a/README.md b/README.md index bb3144a..ba0f1cc 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# mpd-dbcreate | Jay's MPD Database Creator - "Hot Diggity Daffodil!" +# mpd-dbcreate | Jay's MPD Database Creator - "I'm 40% Dolomite!" It's mpd without the daemon and playback functionality. Creates an mpd database based on "better" rules regarding CUE sheets, multi-channel files, and SACD content. @@ -17,7 +17,7 @@ mpd-dbcreate is a command-line tool that creates MPD-compatible database files w ## Features - Scans music directories and creates MPD-compatible database files - *At least the way I want them made.* -- Supports all audio formats that MPD supports - *SACD playback support requires Max's fork of mpd. Milage may vary. Hi Max!* +- Supports all audio formats that MPD supports - *SACD playback support requires Maxim's fork of mpd. Milage may vary. Hi Maxim!* - Handles large collections (tested with multi-TB libraries) - *and I did it over the network in about an hour! Like reasonable frames with your perscription!* - Looks for every reason to not use a .CUE sheet. - *There are valid reasons and that's why I wrote this!* - ~~Network filesystem support (NFS, WebDAV)~~ - *URI's are supported, but broken? Please report. SMB is disabled.* @@ -30,7 +30,7 @@ I created this for the basic reason that mpd was not generating databases that w ### CUE Sheet Handling -You don't always need a CUE sheet for playback. If your media files are already split up, as the usually already are, then a CUE sheet doesn't give you any advantage on playback. In fact, this is a disadvantage. If mpd sees a cue sheet; it will only index that cue sheet. This is fine except when it doesn't properly parse the sheet and you wind up with most of an album unplayable. +You don't always need a CUE sheet for playback. If your media files are already split up, as they usually already are, then a CUE sheet doesn't give you any advantage on playback. In fact, this is a disadvantage. If mpd sees a cue sheet; it will only index that cue sheet. This is fine except when it doesn't properly parse the sheet and you wind up with most of an album unplayable. The solution is to just not use CUE sheets unless necessary. It compares the number of tracks in the CUE sheet to the number of media files in the folder and makes that decision based on the following rules: @@ -49,10 +49,11 @@ You can specify if you want to keep just stereo, just multichannel, or everythin SACD's are presented to mpd in a non-standard way; so we had to go down in to that code and implement some changes. The primary one is that it follows stereo/multichannel rules like for the rest of the media. So a stereo only database will not have 5.1 SACD stuff mixed in. -We also cleaned up the tags! The plugin added technical information to the album and track title tags. It's ugly. Primarily putting the channel configuration and track number in the track title is a bit much. So...we clean everything up! Track titles are sanitized/cleaned to be just the title, same with the album titles. Afterall...if you're only running stereo content, then it's all stereo; you're not at a risk of picking the multichannel version. Likewise, if you've got a multi-channel database; you won't be getting any stereo content. +We also cleaned up the tags! The plugin added technical information to the album and track title tags. It's ugly. Primarily putting the channel configuration and track number in the track title is a bit much. So...we clean everything up! Track titles are sanitized/cleaned to be just the title, same with the album titles. After all...if you're only running stereo content, then it's all stereo; you're not at a risk of picking the multichannel version. Likewise, if you've got a multi-channel database; you won't be getting any stereo content. And if you kept everything, you're still covered! We append (Stereo) or (Multichannel) to the album title. Clean, more human-readable. But we only do this on SACD. You'll have to tag your non-SACD multichannel releases yourself. (For now. It's literally not that difficult to have it mark non-SACD multi-channel; I just haven't done that yet.) + ## Building I've built this on CachyOS. If you have OSX or Windows good luck. It probably won't work. You guys aren't using mpd anyway; what am I worried about? @@ -105,17 +106,17 @@ I have found it's best to feed full paths for everything. It should be obvious i Create a database of only stereo content: ```bash -mpd-dbcreate --music-dir /path/to/media --database /path/to/file.db -stereo +mpd-dbcreate --music-dir /path/to/media --database /path/to/file.db --stereo ``` Scan a database of only multichannel content: ```bash -mpd-dbcreate --music-dir /path/to/media --database /path/to/file.db -multichannel +mpd-dbcreate --music-dir /path/to/media --database /path/to/file.db --multichannel ``` Create a database of all content: ```bash -mpd-dbcreate --music-dir /path/to/media --database /path/to/file.db -all +mpd-dbcreate --music-dir /path/to/media --database /path/to/file.db --all ``` Update an existing database: @@ -128,6 +129,7 @@ mpd-dbcreate --music-dir /path/to/media --database /path/to/file.db (--stereo|-- ``` 28-AUG-2025 - Initial hacking of database tool from mpd-sacd itself. Multichannel, CUE, SACD logic updates. 29-AUG-2025 - Removed systemd, output, and daemonization features where possible. Restored verbose output from systemd hijack. Got --update working. +01-SEP-2025 - After being able to actually test DVD-Audio; modified DVD-Audio plugin to match SACD behavior for track and channel filtering. ``` ## License @@ -136,5 +138,6 @@ GPL-2.0-or-later (inherited from MPD). Please see the `COPYING` or `LICENSE` fil ## Credits -Based on Max's fork of mpd with SACD and DVD-A plguins: https://sourceforge.net/projects/mpd.sacddecoder.p/ - Thanks Max! +Based on Maxim's fork of mpd with SACD and DVD-A plguins: https://sourceforge.net/projects/mpd.sacddecoder.p/ - Thanks Maxim! + Original MPD project: https://www.musicpd.org/ diff --git a/src/decoder/plugins/DvdaIsoDecoderPlugin.cxx b/src/decoder/plugins/DvdaIsoDecoderPlugin.cxx index 990d2f2..6df165b 100644 --- a/src/decoder/plugins/DvdaIsoDecoderPlugin.cxx +++ b/src/decoder/plugins/DvdaIsoDecoderPlugin.cxx @@ -39,6 +39,7 @@ #include "util/BitReverse.hxx" #include "util/UriExtract.hxx" #include "util/Domain.hxx" +#include "lib/fmt/ToBuffer.hxx" #include "Log.hxx" #include @@ -196,6 +197,16 @@ finish() noexcept { my_av_log_set_default_callback(); } +// External function to get channel mode for database creation +enum class ChannelMode { + STEREO, + MULTICHANNEL, + ALL +}; +extern "C" __attribute__((weak)) ChannelMode GetChannelMode() noexcept { + return ChannelMode::ALL; // Default for normal MPD +} + static std::forward_list container_scan(Path path_fs) { std::forward_list list; @@ -205,58 +216,130 @@ container_scan(Path path_fs) { TagBuilder tag_builder; auto tail = list.before_begin(); auto suffix = path_fs.GetExtension(); + + // Check our channel mode for database creation + auto channel_mode = GetChannelMode(); + FmtDebug(dvdaiso_domain, "container_scan: GetChannelMode returned {}", + (channel_mode == ChannelMode::STEREO ? "STEREO" : + channel_mode == ChannelMode::MULTICHANNEL ? "MULTICHANNEL" : "ALL")); + for (auto track_index = 0u; track_index < dvda_reader->get_tracks(); track_index++) { if (dvda_reader->select_track(track_index)) { auto duration = dvda_reader->get_duration(); if (param_no_short_tracks && duration < SHORT_TRACK_SEC) { continue; } - auto add_track = false; - auto add_downmix = false; - switch (param_playable_area) { - case CHMODE_MULCH: - if (dvda_reader->get_channels() > 2) { - add_track = true; + + auto channels = dvda_reader->get_channels(); + bool is_multichannel = channels > 2; + + // Filter based on channel mode + bool process_track = false; + bool process_downmix = false; + + // First check database creation channel mode + if (channel_mode == ChannelMode::STEREO) { + // Only process stereo tracks or downmixes of multichannel + if (!is_multichannel) { + process_track = true; + } else if (!param_no_downmixes && dvda_reader->can_downmix()) { + process_downmix = true; } - break; - case CHMODE_TWOCH: - if (dvda_reader->get_channels() <= 2) { - add_track = true; + } else if (channel_mode == ChannelMode::MULTICHANNEL) { + // Only process multichannel tracks + if (is_multichannel) { + process_track = true; } - if (!param_no_downmixes && dvda_reader->can_downmix()) { - add_downmix = true; - } - break; - default: - add_track = true; - if (!param_no_downmixes && dvda_reader->can_downmix()) { - add_downmix = true; + } else { // ChannelMode::ALL + // Process everything based on param_playable_area + switch (param_playable_area) { + case CHMODE_MULCH: + if (is_multichannel) { + process_track = true; + } + break; + case CHMODE_TWOCH: + if (!is_multichannel) { + process_track = true; + } + if (!param_no_downmixes && dvda_reader->can_downmix()) { + process_downmix = true; + } + break; + default: + process_track = true; + if (!param_no_downmixes && dvda_reader->can_downmix()) { + process_downmix = true; + } + break; } - break; } + char area; char track_name[64]; - if (add_track) { + if (process_track) { AddTagHandler h(tag_builder); - area = dvda_reader->get_channels() > 2 ? 'M' : 'S'; + area = is_multichannel ? 'M' : 'S'; scan_info(track_index, false, h); - std::sprintf(track_name, DVDA_TRACKXXX_FMT, track_index + 1, area, suffix), - tail = list.emplace_after( - tail, - track_name, - tag_builder.Commit() - ); + + // Add channel indicator to album title in ALL mode + if (channel_mode == ChannelMode::ALL) { + auto tag = tag_builder.Commit(); + const char *album = tag.GetValue(TAG_ALBUM); + if (album != nullptr) { + TagBuilder modified_builder(std::move(tag)); + std::string new_album(album); + new_album += is_multichannel ? " (Multichannel)" : " (Stereo)"; + modified_builder.RemoveType(TAG_ALBUM); + modified_builder.AddItem(TAG_ALBUM, new_album); + tag = modified_builder.Commit(); + } + std::sprintf(track_name, DVDA_TRACKXXX_FMT, track_index + 1, area, suffix); + tail = list.emplace_after( + tail, + track_name, + std::move(tag) + ); + } else { + std::sprintf(track_name, DVDA_TRACKXXX_FMT, track_index + 1, area, suffix); + tail = list.emplace_after( + tail, + track_name, + tag_builder.Commit() + ); + } } - if (add_downmix) { + if (process_downmix) { AddTagHandler h(tag_builder); area = 'D'; scan_info(track_index, true, h); - std::sprintf(track_name, DVDA_TRACKXXX_FMT, track_index + 1, area, suffix), - tail = list.emplace_after( - tail, - track_name, - tag_builder.Commit() - ); + + // Add channel indicator to album title in ALL mode for downmixes + if (channel_mode == ChannelMode::ALL) { + auto tag = tag_builder.Commit(); + const char *album = tag.GetValue(TAG_ALBUM); + if (album != nullptr) { + TagBuilder modified_builder(std::move(tag)); + std::string new_album(album); + new_album += " (Downmix)"; + modified_builder.RemoveType(TAG_ALBUM); + modified_builder.AddItem(TAG_ALBUM, new_album); + tag = modified_builder.Commit(); + } + std::sprintf(track_name, DVDA_TRACKXXX_FMT, track_index + 1, area, suffix); + tail = list.emplace_after( + tail, + track_name, + std::move(tag) + ); + } else { + std::sprintf(track_name, DVDA_TRACKXXX_FMT, track_index + 1, area, suffix); + tail = list.emplace_after( + tail, + track_name, + tag_builder.Commit() + ); + } } } else { @@ -336,6 +419,33 @@ scan_file(Path path_fs, TagHandler& handler) noexcept { LogError(dvdaiso_domain, "cannot get track number"); return false; } + + // Check our channel mode for database creation + auto channel_mode = GetChannelMode(); + + // Select the track to get channel info + if (!dvda_reader->select_track(track_index)) { + LogError(dvdaiso_domain, "cannot select track for scan"); + return false; + } + + auto channels = dvda_reader->get_channels(); + bool is_multichannel = channels > 2; + + // Filter based on channel mode + if (channel_mode == ChannelMode::STEREO) { + // Skip multichannel tracks unless it's a downmix + if (is_multichannel && !downmix) { + return false; + } + } else if (channel_mode == ChannelMode::MULTICHANNEL) { + // Skip stereo tracks and downmixes + if (!is_multichannel || downmix) { + return false; + } + } + // For ChannelMode::ALL, scan everything + scan_info(track_index, downmix, handler); return true; } diff --git a/src/lib/dvdaiso/dvda_disc.cpp b/src/lib/dvdaiso/dvda_disc.cpp index f601581..dad3a98 100644 --- a/src/lib/dvdaiso/dvda_disc.cpp +++ b/src/lib/dvdaiso/dvda_disc.cpp @@ -105,19 +105,21 @@ void dvda_disc_t::get_info(uint32_t track_index, bool downmix, TagHandler& handl tag_value = label_ok ? disc_label : "DVD-Audio"; handler.OnTag(TAG_DISC, tag_value.c_str()); + // Clean album title - no format info appended tag_value = !disc_name.empty() ? disc_name : "Album"; - tag_value += " ("; + handler.OnTag(TAG_ALBUM, tag_value.c_str()); + + // Store channel/format info in comment tag for reference + std::string comment_value = "DVDA_"; if (!downmix) { - tag_value += std::to_string(info.group1_channels + info.group2_channels); - tag_value += "CH"; + comment_value += std::to_string(info.group1_channels + info.group2_channels); + comment_value += "CH_"; } else { - tag_value += "DMX"; + comment_value += "DMX_"; } - tag_value += "-"; - tag_value += info.stream_id == MLP_STREAM_ID ? (info.stream_type == STREAM_TYPE_MLP ? "MLP" : "TrueHD") : "PCM"; - tag_value += ")"; - handler.OnTag(TAG_ALBUM, tag_value.c_str()); + comment_value += info.stream_id == MLP_STREAM_ID ? (info.stream_type == STREAM_TYPE_MLP ? "MLP" : "TrueHD") : "PCM"; + handler.OnTag(TAG_COMMENT, comment_value.c_str()); tag_value = "Artist"; handler.OnTag(TAG_ARTIST, tag_value.c_str()); @@ -125,42 +127,11 @@ void dvda_disc_t::get_info(uint32_t track_index, bool downmix, TagHandler& handl char track_number_string[16]; //sprintf(track_number_string, "%02d.%02d.%02d", ts, ti, tr); sprintf(track_number_string, "%02d", tr); + + // Clean track title - just track number and name tag_value = track_number_string; tag_value += " - "; tag_value += "Track " + std::to_string(tr); - tag_value += " ("; - if (!(downmix && track_list.get_track_by_index(track_index).audio_stream_info.can_downmix)) { - for (int i = 0; i < info.group1_channels; i++) { - if (i > 0) { - tag_value += "-"; - } - tag_value += info.get_channel_name(i); - } - tag_value += " "; - tag_value += std::to_string(info.group1_bits); - tag_value += "/"; - tag_value += std::to_string(info.group1_samplerate); - if (info.group2_channels > 0) { - tag_value += " + "; - for (int i = 0; i < info.group2_channels; i++) { - if (i > 0) { - tag_value += "-"; - } - tag_value += info.get_channel_name(info.group1_channels + i); - } - tag_value += " "; - tag_value += std::to_string(info.group2_bits); - tag_value += "/"; - tag_value += std::to_string(info.group2_samplerate); - } - } - else { - tag_value += "DMX "; - tag_value += std::to_string(info.group1_bits); - tag_value += "/"; - tag_value += std::to_string(info.group1_samplerate); - } - tag_value += ")"; handler.OnTag(TAG_TITLE, tag_value.c_str()); tag_value = "Composer";