1 /// 2 /// Read media file metadata using gstreamer. 3 /// @file gstreammetadata.cpp - pianod2 4 /// @author Perette Barella 5 /// @date 2016-08-22 6 /// @copyright Copyright © 2016 Devious Fish. All rights reserved. 7 /// 8 9 #include <config.h> 10 11 12 13 #include <string> 14 #include <memory> 15 16 #include "metadata.h" 17 #include "ffmpegmetadata.h" 18 19 #pragma GCC diagnostic push 20 #pragma GCC diagnostic ignored "-Wdocumentation" 21 #pragma GCC diagnostic ignored "-Wshadow" 22 #include <gst/gst.h> 23 #pragma GCC diagnostic pop 24 25 #include "fundamentals.h" 26 #include "logging.h" 27 28 // This is hacky, but we need the base class of the audio player 29 #include "../audio/gstreamplayer.h" 30 #include "gstreammetadata.h" 31 32 33 namespace Media { 34 static const char *media_formats[] = { 35 ".mp3", 36 ".m4a", 37 ".flac", 38 ".wav", 39 ".mp4a", 40 ".wma", 41 ".snd", 42 ".aiff", 43 ".aac", 44 ".ogg", 45 ".mpa", 46 ".alac", 47 nullptr 48 }; 49 50 GstreamerMetadataReader(const std::string & path)51 GstreamerMetadataReader::GstreamerMetadataReader (const std::string &path) : 52 Audio::GstreamerMediaReader (path, 5) { 53 // Gstreamer (or is it libmad?) hasn't proven trustworthy with non-audio files. 54 // Ignore anything that doesn't look sane. 55 const char **extension; 56 for (extension = media_formats; *extension; extension++) { 57 int len = strlen (*extension); 58 if (path.length() >= len) { 59 if (strcasecmp (*extension, path.c_str() + path.length() - len) == 0) { 60 break; 61 } 62 } 63 }; 64 if (!*extension) { 65 throw MediaException ("Not obviously an audio file"); 66 } 67 68 GstElement *fake_sink = createElement ("fakesink"); 69 push (fake_sink); 70 71 setPipelineState (GST_STATE_PAUSED); 72 73 bool keep_going = true; 74 while (keep_going) { 75 auto message = gst_bus_timed_pop (GST_ELEMENT_BUS (pipeline), 5E9); 76 keep_going = notification (message); 77 gst_message_unref (message); 78 } 79 setPipelineState (GST_STATE_NULL); 80 } 81 82 83 /** Retrieve a value out of the tag list. 84 @param tag_list The tag collection to extract a value from. 85 @param tag The tag whose value to retrieve. 86 @param type The expected datatype of the tag's value. 87 @return If found an the expected type, the value, otherwise null. */ retrieveTag(const GstTagList * tag_list,const gchar * tag,GType type)88 static const GValue *retrieveTag (const GstTagList *tag_list, const gchar *tag, GType type) { 89 auto count = gst_tag_list_get_tag_size (tag_list, tag); 90 if (count > 0) { 91 const GValue *val = gst_tag_list_get_value_index (tag_list, tag, 0); 92 if (G_VALUE_TYPE (val) == type) { 93 if (count > 1) { 94 flog (LOG_WHERE (LOG_WARNING), "Tag ", tag, " has ", count, " values"); 95 } 96 return val; 97 } 98 flog (LOG_WHERE (LOG_WARNING), "Tag ", tag, " contains ", G_VALUE_TYPE_NAME (val), 99 " instead of ", g_type_name (type)); 100 } 101 return nullptr; 102 } 103 104 /** Extract a floating point value from the tag list. 105 @param list A dictionary of tags and values. 106 @param tag The tag whose value to retrieve. 107 @param value The variable to retrieve into. */ retrieveTag(const GstTagList * list,const gchar * tag,float * value)108 static void retrieveTag (const GstTagList *list, const gchar *tag, float *value) { 109 const GValue *val = retrieveTag (list, tag, G_TYPE_DOUBLE); 110 if (val) { 111 *value = g_value_get_double (val); 112 } 113 } 114 115 /** Extract an unsigned integer value from the tag list. 116 @param list A dictionary of tags and values. 117 @param tag The tag whose value to retrieve. 118 @param value The variable to retrieve into. */ retrieveUintTag(const GstTagList * list,const gchar * tag,int * value)119 static void retrieveUintTag (const GstTagList *list, const gchar *tag, int *value) { 120 const GValue *val = retrieveTag (list, tag, G_TYPE_UINT); 121 if (val) { 122 *value = g_value_get_uint (val); 123 } 124 } 125 126 /** Extract the year from the tag list. 127 @param list A dictionary of tags and values. 128 @param tag The tag whose value to retrieve. 129 @param value The variable to retrieve into. */ retrieveYearTag(const GstTagList * list,const gchar * tag,int * value)130 static void retrieveYearTag (const GstTagList *list, const gchar *tag, int *value) { 131 auto count = gst_tag_list_get_tag_size (list, tag); 132 if (count > 0) { 133 if (count > 1) { 134 flog (LOG_WHERE (LOG_WARNING), "Tag ", tag, " has ", count, " values"); 135 } 136 const GValue *val = retrieveTag (list, tag, G_TYPE_DATE); 137 if (val) { 138 GDate *date = nullptr; 139 kept_assert (gst_tag_list_get_date_index (list, tag, 0, &date)); 140 *value = g_date_get_year (date); 141 g_date_free (date); 142 } 143 } 144 } 145 146 /** Extract a string value from the tag list. 147 @param list A dictionary of tags and values. 148 @param tag The tag whose value to retrieve. 149 @param value The variable to retrieve into. */ retrieveDurationTag(const GstTagList * list,const gchar * tag,float * value)150 static void retrieveDurationTag (const GstTagList *list, const gchar *tag, float *value) { 151 const GValue *val = retrieveTag (list, tag, G_TYPE_UINT64); 152 if (val) { 153 *value = g_value_get_uint64 (val) / 1E9; 154 } 155 } 156 157 /** Extract a string value from the tag list. 158 @param list A dictionary of tags and values. 159 @param tag The tag whose value to retrieve. 160 @param value The variable to retrieve into. */ retrieveTag(const GstTagList * list,const gchar * tag,std::string * value)161 static void retrieveTag (const GstTagList *list, const gchar *tag, std::string *value) { 162 const GValue *val = retrieveTag (list, tag, G_TYPE_STRING); 163 if (val) { 164 *value = g_value_get_string (val); 165 } 166 } 167 168 notification(GstMessage * message)169 bool GstreamerMetadataReader::notification (GstMessage *message) { 170 // If all tags have been read, or error encountered, or end of stream, 171 // stop processing and shutdown the pipeline. 172 GstMessageType msg_type = GST_MESSAGE_TYPE (message); 173 if (msg_type == GST_MESSAGE_ASYNC_DONE || 174 msg_type == GST_MESSAGE_ERROR || 175 msg_type == GST_MESSAGE_EOS) { 176 // Need to initiate pipeline shutdown before ringing the notifier 177 // to prevent race conditions. 178 return false; 179 } 180 181 // Ignore anything not metadata-related. 182 if (msg_type != GST_MESSAGE_TAG) 183 return true; 184 185 // Extract meta tags & values. 186 GstTagList *tag_list = nullptr; 187 gst_message_parse_tag (message, &tag_list); 188 if (tag_list) { 189 retrieveTag (tag_list, GST_TAG_ARTIST, &artist); 190 retrieveTag (tag_list, GST_TAG_ALBUM, &album); 191 retrieveTag (tag_list, GST_TAG_TITLE, &title); 192 // retrieveTag (tag_list, Gst::TAG, &cddb_id); 193 retrieveTag (tag_list, GST_TAG_GENRE, &genre); 194 195 retrieveYearTag (tag_list, GST_TAG_DATE, &year); 196 retrieveUintTag (tag_list, GST_TAG_TRACK_NUMBER, &track_number); 197 retrieveUintTag (tag_list, GST_TAG_TRACK_COUNT, &track_count); 198 retrieveUintTag (tag_list, GST_TAG_ALBUM_VOLUME_NUMBER, &disc_number); 199 retrieveUintTag (tag_list, GST_TAG_ALBUM_VOLUME_COUNT, &disc_count); 200 retrieveDurationTag (tag_list, GST_TAG_DURATION, &duration); 201 retrieveTag (tag_list, GST_TAG_REFERENCE_LEVEL, &gain); 202 } else { 203 flog (LOG_WHERE (LOG_ERROR), "gst_message_parse_tag: provided null tag list."); 204 } 205 206 gst_tag_list_unref(tag_list); 207 return true; 208 } 209 210 } 211