2016-06-04 06:55:52 +00:00
|
|
|
---
|
|
|
|
title: Metadata
|
|
|
|
...
|
|
|
|
|
|
|
|
# Metadata
|
|
|
|
|
|
|
|
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
|
|
|
|
`GstPad` by getting the current (negotiated) `GstCaps` for that pad.
|
|
|
|
|
|
|
|
# Metadata reading
|
|
|
|
|
|
|
|
Stream information can most easily be read by reading it from a
|
|
|
|
`GstPad`. This has already been discussed before in [Using capabilities
|
|
|
|
for metadata](manual-pads.md#using-capabilities-for-metadata).
|
|
|
|
Therefore, we will skip it here. Note that this requires access to all
|
|
|
|
pads of which you want stream information.
|
|
|
|
|
|
|
|
Tag reading is done through a bus in GStreamer, which has been discussed
|
|
|
|
previously in [Bus](manual-bus.md). You can listen for
|
|
|
|
`GST_MESSAGE_TAG` messages and handle them as you wish.
|
|
|
|
|
|
|
|
Note, however, that the `GST_MESSAGE_TAG` 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 `gst_tag_list_merge ()` 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 `GST_TAG_MERGE_PREPEND` as merging mode, so that a new
|
|
|
|
title (which came in later) has a preference over the old one for
|
|
|
|
display.
|
|
|
|
|
|
|
|
The following example will extract tags from a file and print them:
|
|
|
|
|
2016-06-06 01:50:32 +00:00
|
|
|
``` c
|
2016-06-04 06:55:52 +00:00
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
# Tag writing
|
|
|
|
|
|
|
|
Tag writing is done using the
|
|
|
|
[`GstTagSetter`](http://gstreamer.freedesktop.org/data/doc/gstreamer/stable/gstreamer/html/GstTagSetter.html)
|
|
|
|
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
|
|
|
|
`gst_bin_iterate_all_by_interface (pipeline,
|
|
|
|
GST_TYPE_TAG_SETTER)`. On the resulting element, usually an encoder or
|
|
|
|
muxer, you can use `gst_tag_setter_merge
|
|
|
|
()` (with a taglist) or `gst_tag_setter_add
|
|
|
|
()` (with individual tags) to set tags on it.
|
|
|
|
|
|
|
|
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.
|
|
|
|
|