| <chapter id="chapter-metadata"> |
| <title>Metadata</title> |
| |
| <para> |
| &GStreamer; makes a clear distinction between two types of metadata, and |
| has support for both types. The first is stream tags, which describe the |
| content of a stream in a non-technical way. Examples include the author |
| of a song, the title of that very same song or the album it is a part of. |
| The other type of metadata is stream-info, which is a somewhat technical |
| description of the properties of a stream. This can include video size, |
| audio samplerate, codecs used and so on. Tags are handled using the |
| &GStreamer; tagging system. Stream-info can be retrieved from a |
| <classname>GstPad</classname> by getting the current (negotiated) |
| <classname>GstCaps</classname> for that pad. |
| </para> |
| |
| <sect1 id="section-tags-read"> |
| <title>Metadata reading</title> |
| |
| <para> |
| Stream information can most easily be read by reading it from a |
| <classname>GstPad</classname>. This has already been discussed before |
| in <xref linkend="section-caps-metadata"/>. Therefore, we will skip |
| it here. Note that this requires access to all pads of which you |
| want stream information. |
| </para> |
| |
| <para> |
| Tag reading is done through a bus in &GStreamer;, which has been |
| discussed previously in <xref linkend="chapter-bus"/>. You can |
| listen for <classname>GST_MESSAGE_TAG</classname> messages and handle |
| them as you wish. |
| </para> |
| <para> |
| Note, however, that the <classname>GST_MESSAGE_TAG</classname> |
| message may be fired multiple times in the pipeline. It is the |
| application's responsibility to put all those tags together and |
| display them to the user in a nice, coherent way. Usually, using |
| <function>gst_tag_list_merge ()</function> is a good enough way |
| of doing this; make sure to empty the cache when loading a new song, |
| or after every few minutes when listening to internet radio. Also, |
| make sure you use <classname>GST_TAG_MERGE_PREPEND</classname> as |
| merging mode, so that a new title (which came in later) has a |
| preference over the old one for display. |
| </para> |
| <para> |
| The following example will extract tags from a file and print them: |
| </para> |
| <programlisting> |
| /* compile with: |
| * gcc -o tags tags.c `pkg-config --cflags --libs gstreamer-1.0` */ |
| #include <gst/gst.h> |
| |
| static void |
| print_one_tag (const GstTagList * list, const gchar * tag, gpointer user_data) |
| { |
| int i, num; |
| |
| num = gst_tag_list_get_tag_size (list, tag); |
| for (i = 0; i < num; ++i) { |
| const GValue *val; |
| |
| /* Note: when looking for specific tags, use the gst_tag_list_get_xyz() API, |
| * we only use the GValue approach here because it is more generic */ |
| val = gst_tag_list_get_value_index (list, tag, i); |
| if (G_VALUE_HOLDS_STRING (val)) { |
| g_print ("\t%20s : %s\n", tag, g_value_get_string (val)); |
| } else if (G_VALUE_HOLDS_UINT (val)) { |
| g_print ("\t%20s : %u\n", tag, g_value_get_uint (val)); |
| } else if (G_VALUE_HOLDS_DOUBLE (val)) { |
| g_print ("\t%20s : %g\n", tag, g_value_get_double (val)); |
| } else if (G_VALUE_HOLDS_BOOLEAN (val)) { |
| g_print ("\t%20s : %s\n", tag, |
| (g_value_get_boolean (val)) ? "true" : "false"); |
| } else if (GST_VALUE_HOLDS_BUFFER (val)) { |
| GstBuffer *buf = gst_value_get_buffer (val); |
| guint buffer_size = gst_buffer_get_size (buf); |
| |
| g_print ("\t%20s : buffer of size %u\n", tag, buffer_size); |
| } else if (GST_VALUE_HOLDS_DATE_TIME (val)) { |
| GstDateTime *dt = g_value_get_boxed (val); |
| gchar *dt_str = gst_date_time_to_iso8601_string (dt); |
| |
| g_print ("\t%20s : %s\n", tag, dt_str); |
| g_free (dt_str); |
| } else { |
| g_print ("\t%20s : tag of type '%s'\n", tag, G_VALUE_TYPE_NAME (val)); |
| } |
| } |
| } |
| |
| static void |
| on_new_pad (GstElement * dec, GstPad * pad, GstElement * fakesink) |
| { |
| GstPad *sinkpad; |
| |
| sinkpad = gst_element_get_static_pad (fakesink, "sink"); |
| if (!gst_pad_is_linked (sinkpad)) { |
| if (gst_pad_link (pad, sinkpad) != GST_PAD_LINK_OK) |
| g_error ("Failed to link pads!"); |
| } |
| gst_object_unref (sinkpad); |
| } |
| |
| int |
| main (int argc, char ** argv) |
| { |
| GstElement *pipe, *dec, *sink; |
| GstMessage *msg; |
| gchar *uri; |
| |
| gst_init (&argc, &argv); |
| |
| if (argc < 2) |
| g_error ("Usage: %s FILE or URI", argv[0]); |
| |
| if (gst_uri_is_valid (argv[1])) { |
| uri = g_strdup (argv[1]); |
| } else { |
| uri = gst_filename_to_uri (argv[1], NULL); |
| } |
| |
| pipe = gst_pipeline_new ("pipeline"); |
| |
| dec = gst_element_factory_make ("uridecodebin", NULL); |
| g_object_set (dec, "uri", uri, NULL); |
| gst_bin_add (GST_BIN (pipe), dec); |
| |
| sink = gst_element_factory_make ("fakesink", NULL); |
| gst_bin_add (GST_BIN (pipe), sink); |
| |
| g_signal_connect (dec, "pad-added", G_CALLBACK (on_new_pad), sink); |
| |
| gst_element_set_state (pipe, GST_STATE_PAUSED); |
| |
| while (TRUE) { |
| GstTagList *tags = NULL; |
| |
| msg = gst_bus_timed_pop_filtered (GST_ELEMENT_BUS (pipe), |
| GST_CLOCK_TIME_NONE, |
| GST_MESSAGE_ASYNC_DONE | GST_MESSAGE_TAG | GST_MESSAGE_ERROR); |
| |
| if (GST_MESSAGE_TYPE (msg) != GST_MESSAGE_TAG) /* error or async_done */ |
| break; |
| |
| gst_message_parse_tag (msg, &tags); |
| |
| g_print ("Got tags from element %s:\n", GST_OBJECT_NAME (msg->src)); |
| gst_tag_list_foreach (tags, print_one_tag, NULL); |
| g_print ("\n"); |
| gst_tag_list_unref (tags); |
| |
| gst_message_unref (msg); |
| } |
| |
| if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) { |
| GError *err = NULL; |
| |
| gst_message_parse_error (msg, &err, NULL); |
| g_printerr ("Got error: %s\n", err->message); |
| g_error_free (err); |
| } |
| |
| gst_message_unref (msg); |
| gst_element_set_state (pipe, GST_STATE_NULL); |
| gst_object_unref (pipe); |
| g_free (uri); |
| return 0; |
| } |
| </programlisting> |
| </sect1> |
| |
| <sect1 id="section-tags-write"> |
| <title>Tag writing</title> |
| |
| <para> |
| Tag writing is done using the <ulink type="http" |
| url="&URLAPI;GstTagSetter.html"><classname>GstTagSetter</classname></ulink> |
| interface. All that's required is a tag-set-supporting element in |
| your pipeline. In order to see if any of the elements in your |
| pipeline supports tag writing, you can use the function |
| <function>gst_bin_iterate_all_by_interface (pipeline, |
| GST_TYPE_TAG_SETTER)</function>. On the resulting element, usually |
| an encoder or muxer, you can use <function>gst_tag_setter_merge |
| ()</function> (with a taglist) or <function>gst_tag_setter_add |
| ()</function> (with individual tags) to set tags on it. |
| </para> |
| <para> |
| A nice extra feature in &GStreamer; tag support is that tags are |
| preserved in pipelines. This means that if you transcode one file |
| containing tags into another media type, and that new media type |
| supports tags too, then the tags will be handled as part of the |
| data stream and be merged into the newly written media file, too. |
| </para> |
| </sect1> |
| </chapter> |