From ffbe1ec9fd3c60a9d7f024ada4b5c4df2a8f7bf0 Mon Sep 17 00:00:00 2001 From: Jonas Kvinge Date: Sat, 21 Jun 2025 22:59:48 +0200 Subject: [PATCH] CDDASongLoader: Load tags from CD --- src/device/cddasongloader.cpp | 221 ++++++++++++++++++++++++---------- 1 file changed, 155 insertions(+), 66 deletions(-) diff --git a/src/device/cddasongloader.cpp b/src/device/cddasongloader.cpp index 027010209..8438cd305 100644 --- a/src/device/cddasongloader.cpp +++ b/src/device/cddasongloader.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include "cddasongloader.h" #include "includes/shared_ptr.h" @@ -124,10 +125,10 @@ void CDDASongLoader::LoadSongsFromCDDA() { } // Get number of tracks - GstFormat fmt = gst_format_get_by_nick("track"); - GstFormat out_fmt = fmt; - gint64 num_tracks = 0; - if (!gst_element_query_duration(cdda_, out_fmt, &num_tracks)) { + GstFormat format_track = gst_format_get_by_nick("track"); + GstFormat format_duration = format_track; + gint64 total_tracks = 0; + if (!gst_element_query_duration(cdda_, format_duration, &total_tracks)) { gst_element_set_state(cdda_, GST_STATE_NULL); gst_object_unref(GST_OBJECT(cdda_)); cdda_ = nullptr; @@ -136,8 +137,8 @@ void CDDASongLoader::LoadSongsFromCDDA() { return; } - if (out_fmt != fmt) { - qLog(Error) << "Error while querying cdda GstElement (2)."; + if (format_duration != format_track) { + qLog(Error) << "Error while querying CDDA GstElement (2)."; gst_element_set_state(cdda_, GST_STATE_NULL); gst_object_unref(GST_OBJECT(cdda_)); cdda_ = nullptr; @@ -146,10 +147,8 @@ void CDDASongLoader::LoadSongsFromCDDA() { return; } - SongList songs; - songs.reserve(num_tracks); - for (int track_number = 1; track_number <= num_tracks; ++track_number) { - // Init song + QMap songs; + for (int track_number = 1; track_number <= total_tracks; ++track_number) { Song song(Song::Source::CDDA); song.set_id(track_number); song.set_valid(true); @@ -157,9 +156,9 @@ void CDDASongLoader::LoadSongsFromCDDA() { song.set_url(GetUrlFromTrack(track_number)); song.set_title(QStringLiteral("Track %1").arg(track_number)); song.set_track(track_number); - songs << song; + songs.insert(track_number, song); } - Q_EMIT SongsLoaded(songs); + Q_EMIT SongsLoaded(songs.values()); gst_tag_register_musicbrainz_tags(); @@ -170,73 +169,162 @@ void CDDASongLoader::LoadSongsFromCDDA() { gst_element_set_state(pipeline, GST_STATE_READY); gst_element_set_state(pipeline, GST_STATE_PAUSED); - // Get TOC and TAG messages GstMessage *msg = nullptr; - GstMessage *msg_toc = nullptr; - GstMessage *msg_tag = nullptr; - while ((!msg_toc || !msg_tag) && (msg = gst_bus_timed_pop_filtered(GST_ELEMENT_BUS(pipeline), GST_SECOND * 5, static_cast(GST_MESSAGE_TOC | GST_MESSAGE_TAG)))) { - if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_TOC) { - if (msg_toc) gst_message_unref(msg_toc); - msg_toc = msg; - } - else if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_TAG) { - if (msg_tag) gst_message_unref(msg_tag); - msg_tag = msg; - } - } + int track_artist_tags = 0; + int track_album_tags = 0; + int track_title_tags = 0; + QString musicbrainz_discid; + GstMessageType msg_filter = static_cast(GST_MESSAGE_TOC|GST_MESSAGE_TAG); + while (msg_filter != 0 && (msg = gst_bus_timed_pop_filtered(GST_ELEMENT_BUS(pipeline), GST_SECOND * 5, msg_filter))) { - // Handle TOC message: get tracks duration - if (msg_toc) { - GstToc *toc = nullptr; - gst_message_parse_toc(msg_toc, &toc, nullptr); - if (toc) { + const QScopeGuard scopeguard_msg = qScopeGuard([msg]() { + gst_message_unref(msg); + }); + + if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_TOC) { + GstToc *toc = nullptr; + gst_message_parse_toc(msg, &toc, nullptr); + const QScopeGuard scopeguard_toc = qScopeGuard([toc]() { + gst_toc_unref(toc); + }); GList *entries = gst_toc_get_entries(toc); - if (entries) { - if (static_cast(songs.size()) <= g_list_length(entries)) { - int i = 0; - for (GList *node = entries; node != nullptr; node = node->next) { - GstTocEntry *entry = static_cast(node->data); - qint64 duration = 0; - gint64 start = 0, stop = 0; - if (gst_toc_entry_get_start_stop_times(entry, &start, &stop)) duration = stop - start; - songs[i++].set_length_nanosec(duration); + int track_number = 0; + for (GList *entry_node = entries; entry_node != nullptr; entry_node = entry_node->next) { + ++track_number; + if (songs.contains(track_number)) { + Song &song = songs[track_number]; + GstTocEntry *entry = static_cast(entry_node->data); + gint64 start = 0, stop = 0; + if (gst_toc_entry_get_start_stop_times(entry, &start, &stop)) { + song.set_length_nanosec(static_cast(stop - start)); } } + msg_filter = static_cast(static_cast(msg_filter) ^ GST_MESSAGE_TOC); } - gst_toc_unref(toc); } - gst_message_unref(msg_toc); - } - Q_EMIT SongsDurationLoaded(songs); - QString musicbrainz_discid; + else if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_TAG) { + + GstTagList *tags = nullptr; + gst_message_parse_tag(msg, &tags); + const QScopeGuard scopeguard_tags = qScopeGuard([tags]() { + gst_tag_list_free(tags); + }); + + gint64 track_index = 0; + gst_element_query_position(cdda_, format_track, &track_index); + + char *tag = nullptr; + #ifdef HAVE_MUSICBRAINZ - // Handle TAG message: generate MusicBrainz DiscId - if (msg_tag) { - GstTagList *tags = nullptr; - gst_message_parse_tag(msg_tag, &tags); - if (tags) { - char *string_mb = nullptr; - if (gst_tag_list_get_string(tags, GST_TAG_CDDA_MUSICBRAINZ_DISCID, &string_mb)) { - musicbrainz_discid = QString::fromUtf8(string_mb); - g_free(string_mb); + if (musicbrainz_discid.isEmpty()) { + if (gst_tag_list_get_string(tags, GST_TAG_CDDA_MUSICBRAINZ_DISCID, &tag)) { + musicbrainz_discid = QString::fromUtf8(tag); + g_free(tag); + tag = nullptr; + } } - gst_tag_list_free(tags); - } - gst_message_unref(msg_tag); - } #endif + guint track_number = 0; + if (!gst_tag_list_get_uint(tags, GST_TAG_TRACK_NUMBER, &track_number)) { + qLog(Error) << "Could not get track number"; + msg_filter = static_cast(static_cast(msg_filter) ^GST_MESSAGE_TAG); + continue; + } + + if (!songs.contains(track_number)) { + qLog(Error) << "Got invalid track number" << track_number; + msg_filter = static_cast(static_cast(msg_filter) ^GST_MESSAGE_TAG); + continue; + } + + Song &song = songs[track_number]; + guint64 duration = 0; + if (gst_tag_list_get_uint64(tags, GST_TAG_DURATION, &duration)) { + song.set_length_nanosec(static_cast(duration)); + } + if (gst_tag_list_get_string(tags, GST_TAG_ALBUM_ARTIST, &tag)) { + song.set_albumartist(QString::fromUtf8(tag)); + g_free(tag); + tag = nullptr; + } + if (gst_tag_list_get_string(tags, GST_TAG_ARTIST, &tag)) { + song.set_artist(QString::fromUtf8(tag)); + g_free(tag); + tag = nullptr; + ++track_artist_tags; + } + if (gst_tag_list_get_string(tags, GST_TAG_ALBUM, &tag)) { + song.set_album(QString::fromUtf8(tag)); + g_free(tag); + tag = nullptr; + ++track_album_tags; + } + if (gst_tag_list_get_string(tags, GST_TAG_TITLE, &tag)) { + song.set_title(QString::fromUtf8(tag)); + g_free(tag); + tag = nullptr; + ++track_title_tags; + } + if (gst_tag_list_get_string(tags, GST_TAG_GENRE, &tag)) { + song.set_genre(QString::fromUtf8(tag)); + tag = nullptr; + g_free(tag); + } + if (gst_tag_list_get_string(tags, GST_TAG_COMPOSER, &tag)) { + song.set_composer(QString::fromUtf8(tag)); + g_free(tag); + tag = nullptr; + } + if (gst_tag_list_get_string(tags, GST_TAG_PERFORMER, &tag)) { + song.set_performer(QString::fromUtf8(tag)); + g_free(tag); + tag = nullptr; + } + if (gst_tag_list_get_string(tags, GST_TAG_COMMENT, &tag)) { + song.set_comment(QString::fromUtf8(tag)); + g_free(tag); + tag = nullptr; + } + guint bitrate = 0; + if (gst_tag_list_get_uint(tags, GST_TAG_BITRATE, &bitrate)) { + song.set_bitrate(static_cast(bitrate)); + g_free(tag); + } + + if (track_number >= total_tracks) { + msg_filter = static_cast(static_cast(msg_filter) ^GST_MESSAGE_TAG); + continue; + } + + const gint64 next_track_index = track_index + 1; + if (!gst_element_seek_simple(pipeline, format_track, static_cast(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_TRICKMODE), next_track_index)) { + qLog(Error) << "Failed to seek to next track index" << next_track_index; + msg_filter = static_cast(static_cast(msg_filter) ^GST_MESSAGE_TAG); + } + + } + } + gst_element_set_state(pipeline, GST_STATE_NULL); // This will also cause cdda_ to be unref'd. gst_object_unref(pipeline); - if (musicbrainz_discid.isEmpty()) { + Q_EMIT SongsMetadataLoaded(songs.values()); + + if ((track_artist_tags >= total_tracks && track_album_tags >= total_tracks && track_title_tags >= total_tracks)) { + qLog(Info) << "Songs loaded from CD-Text"; Q_EMIT SongLoadingFinished(); } else { - qLog(Info) << "MusicBrainz Disc ID:" << musicbrainz_discid; - Q_EMIT MusicBrainzDiscIdLoaded(musicbrainz_discid); + if (musicbrainz_discid.isEmpty()) { + qLog(Info) << "CD is missing tags"; + } + else { + qLog(Info) << "MusicBrainz Disc ID:" << musicbrainz_discid; + Q_EMIT MusicBrainzDiscIdLoaded(musicbrainz_discid); + } } } @@ -263,20 +351,21 @@ void CDDASongLoader::MusicBrainzCDTagsLoaded(const QString &artist, const QStrin SongList songs; songs.reserve(results.count()); - int track_number = 1; - for (const MusicBrainzClient::Result &ret : results) { + int track_number = 0; + for (const MusicBrainzClient::Result &result : results) { + ++track_number; Song song(Song::Source::CDDA); song.set_artist(artist); song.set_album(album); - song.set_title(ret.title_); - song.set_length_nanosec(ret.duration_msec_ * kNsecPerMsec); + song.set_title(result.title_); + song.set_length_nanosec(result.duration_msec_ * kNsecPerMsec); song.set_track(track_number); - song.set_year(ret.year_); + song.set_year(result.year_); song.set_id(track_number); song.set_filetype(Song::FileType::CDDA); song.set_valid(true); // We need to set URL, that's how playlist will find the correct item to update - song.set_url(GetUrlFromTrack(track_number++)); + song.set_url(GetUrlFromTrack(track_number)); songs << song; }