I'm 40% Dolomite!

main
Jay Moore 3 days ago
parent 7383a2b503
commit 2eac47470d

@ -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/

@ -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 <assert.h>
@ -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<DetachedSong>
container_scan(Path path_fs) {
std::forward_list<DetachedSong> 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;
}

@ -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";

Loading…
Cancel
Save