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