From 5f1bfc7e13bbf3329429853fffd90b3b7c96cc1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cr=C3=AAte?= Date: Mon, 7 Mar 2011 17:55:48 -0500 Subject: [PATCH 001/140] codec-utils: Add method to convert H.264 text level in a level_idc --- docs/libs/gst-plugins-base-libs-sections.txt | 1 + gst-libs/gst/pbutils/codec-utils.c | 53 ++++++++++++++++++++ gst-libs/gst/pbutils/codec-utils.h | 2 + 3 files changed, 56 insertions(+) diff --git a/docs/libs/gst-plugins-base-libs-sections.txt b/docs/libs/gst-plugins-base-libs-sections.txt index 091b7b5314..2ae87c1c1c 100644 --- a/docs/libs/gst-plugins-base-libs-sections.txt +++ b/docs/libs/gst-plugins-base-libs-sections.txt @@ -1944,6 +1944,7 @@ gst_codec_utils_aac_caps_set_level_and_profile gst_codec_utils_h264_get_profile gst_codec_utils_h264_get_level gst_codec_utils_h264_caps_set_level_and_profile +gst_codec_utils_h264_get_level_idc gst_codec_utils_mpeg4video_get_profile gst_codec_utils_mpeg4video_get_level diff --git a/gst-libs/gst/pbutils/codec-utils.c b/gst-libs/gst/pbutils/codec-utils.c index 3ab61ad903..9708474abc 100644 --- a/gst-libs/gst/pbutils/codec-utils.c +++ b/gst-libs/gst/pbutils/codec-utils.c @@ -37,6 +37,8 @@ #include "pbutils.h" +#include + #define GST_SIMPLE_CAPS_HAS_NAME(caps,name) \ gst_structure_has_name(gst_caps_get_structure((caps),0),(name)) @@ -526,6 +528,57 @@ gst_codec_utils_h264_get_level (const guint8 * sps, guint len) } } +/** + * gst_codec_utils_h264_get_level_id: + * @level: A level string from caps + * + * Transform a level string from the caps into the level_idc + * + * Returns: the level_idc or 0 if the level is unknown + */ + +guint8 +gst_codec_utils_h264_get_level_idc (const gchar * level) +{ + g_return_val_if_fail (level != NULL, 0); + + if (!strcmp (level, "1")) + return 10; + else if (!strcmp (level, "1b")) + return 9; + else if (!strcmp (level, "1.1")) + return 11; + else if (!strcmp (level, "1.2")) + return 12; + else if (!strcmp (level, "1.3")) + return 13; + else if (!strcmp (level, "2")) + return 20; + else if (!strcmp (level, "2.1")) + return 21; + else if (!strcmp (level, "2.2")) + return 22; + else if (!strcmp (level, "3")) + return 30; + else if (!strcmp (level, "3.1")) + return 31; + else if (!strcmp (level, "3.2")) + return 32; + else if (!strcmp (level, "4")) + return 40; + else if (!strcmp (level, "4.1")) + return 41; + else if (!strcmp (level, "4.2")) + return 42; + else if (!strcmp (level, "5")) + return 50; + else if (!strcmp (level, "5.1")) + return 51; + + GST_WARNING ("Invalid level %s", level); + return 0; +} + /** * gst_codec_utils_h264_caps_set_level_and_profile: * @caps: the #GstCaps to which the level and profile are to be added diff --git a/gst-libs/gst/pbutils/codec-utils.h b/gst-libs/gst/pbutils/codec-utils.h index ae5a68b1f6..7c2febf7f3 100644 --- a/gst-libs/gst/pbutils/codec-utils.h +++ b/gst-libs/gst/pbutils/codec-utils.h @@ -44,6 +44,8 @@ const gchar * gst_codec_utils_h264_get_profile (const guint8 * sps, guint len); const gchar * gst_codec_utils_h264_get_level (const guint8 * sps, guint len); +guint8 gst_codec_utils_h264_get_level_idc (const gchar * level); + gboolean gst_codec_utils_h264_caps_set_level_and_profile (GstCaps * caps, const guint8 * sps, guint len); From 1a8b4eae7359fab43a77ba9d3c1188fc76f151d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Thu, 14 Jul 2011 13:56:02 +0100 Subject: [PATCH 002/140] docs: add Since marker to gtk-doc chunk for new codec utils API And add new API to .def file. API: gst_codec_utils_h264_get_level_idc() --- gst-libs/gst/pbutils/codec-utils.c | 5 +++-- win32/common/libgstpbutils.def | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/gst-libs/gst/pbutils/codec-utils.c b/gst-libs/gst/pbutils/codec-utils.c index 9708474abc..0dac15394d 100644 --- a/gst-libs/gst/pbutils/codec-utils.c +++ b/gst-libs/gst/pbutils/codec-utils.c @@ -529,14 +529,15 @@ gst_codec_utils_h264_get_level (const guint8 * sps, guint len) } /** - * gst_codec_utils_h264_get_level_id: + * gst_codec_utils_h264_get_level_idc: * @level: A level string from caps * * Transform a level string from the caps into the level_idc * * Returns: the level_idc or 0 if the level is unknown + * + * Since: 0.10.36 */ - guint8 gst_codec_utils_h264_get_level_idc (const gchar * level) { diff --git a/win32/common/libgstpbutils.def b/win32/common/libgstpbutils.def index 083e294914..c07e39002e 100644 --- a/win32/common/libgstpbutils.def +++ b/win32/common/libgstpbutils.def @@ -5,6 +5,7 @@ EXPORTS gst_codec_utils_aac_get_sample_rate_from_index gst_codec_utils_h264_caps_set_level_and_profile gst_codec_utils_h264_get_level + gst_codec_utils_h264_get_level_idc gst_codec_utils_h264_get_profile gst_codec_utils_mpeg4video_caps_set_level_and_profile gst_codec_utils_mpeg4video_get_level From 9edbc92a27b537f239fb5415fb774bebdc47ddf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Thu, 7 Jul 2011 15:02:19 +0100 Subject: [PATCH 003/140] decodebin: don't plug the same parser multiple times in a row This allows us to make parsers accept both parsed and unparsed input without decodebin plugging them in a loop until things blow up, ie. without affecting applications that still use the old playbin or the old decodebin. (Making parsers accept parsed input is useful for later when we want to use parsers to convert the stream-format into something the decoder can handle. It's also much more convenient for application authors who can plug parsers unconditionally in transcoding pipelines, for example). --- gst/playback/gstdecodebin.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/gst/playback/gstdecodebin.c b/gst/playback/gstdecodebin.c index 854a28dca1..a3fe80186b 100644 --- a/gst/playback/gstdecodebin.c +++ b/gst/playback/gstdecodebin.c @@ -1,5 +1,6 @@ /* GStreamer * Copyright (C) <2004> Wim Taymans + * Copyright (C) 2011 Hewlett-Packard Development Company, L.P. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -1115,12 +1116,26 @@ try_to_link_1 (GstDecodeBin * decode_bin, GstElement * srcelement, GstPad * pad, /* loop over the factories */ for (walk = factories; walk; walk = g_list_next (walk)) { GstElementFactory *factory = GST_ELEMENT_FACTORY (walk->data); + GstElementFactory *src_factory; GstElement *element; GstPadLinkReturn ret; GstPad *sinkpad; - GST_DEBUG_OBJECT (decode_bin, "trying to link %s", - gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory))); + GST_DEBUG_OBJECT (decode_bin, "trying to link %s to %s", + gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory)), + GST_OBJECT_NAME (srcelement)); + + /* don't plug the same parser twice, but allow multiple + * instances of other elements (e.g. id3demux) in a row */ + src_factory = gst_element_get_factory (srcelement); + if (src_factory == factory + && gst_element_factory_list_is_type (factory, + GST_ELEMENT_FACTORY_TYPE_PARSER)) { + GST_DEBUG_OBJECT (decode_bin, + "not inserting parser element %s twice in a row, skipping", + GST_PLUGIN_FEATURE_NAME (factory)); + continue; + } /* make an element from the factory first */ if ((element = gst_element_factory_create (factory, NULL)) == NULL) { From b3a39daed618e54d7374a39bd70b732e22cd467f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Wed, 6 Jul 2011 19:40:48 +0100 Subject: [PATCH 004/140] tests: add decodebin1 test for parser autoplugging Make sure decodebin1 doesn't try to plug the same parser twice in a row (so we can change all parsers to accept parsed input as well without breaking applications still using the old decodebin1 element). --- tests/check/elements/decodebin.c | 174 +++++++++++++++++++++++++++++++ tests/files/Makefile.am | 3 +- tests/files/test.mp3 | Bin 0 -> 3135 bytes 3 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 tests/files/test.mp3 diff --git a/tests/check/elements/decodebin.c b/tests/check/elements/decodebin.c index eb6f38a4e6..d5d327e95d 100644 --- a/tests/check/elements/decodebin.c +++ b/tests/check/elements/decodebin.c @@ -1,6 +1,7 @@ /* GStreamer unit tests for decodebin * * Copyright (C) 2006 Tim-Philipp Müller + * Copyright (C) 2011 Hewlett-Packard Development Company, L.P. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -23,6 +24,7 @@ #endif #include +#include #include static const gchar dummytext[] = @@ -184,6 +186,177 @@ GST_START_TEST (test_reuse_without_decoders) GST_END_TEST; +/* Fake mp3 parser for test */ +typedef GstBaseParse TestMpegAudioParse; +typedef GstBaseParseClass TestMpegAudioParseClass; + +static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/mpeg, mpegversion=1, layer=[1,3], parsed=(b)true") + ); + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/mpeg, mpegversion=1, parsed=(bool) { false, true }") + ); + +static GType test_mpeg_audio_parse_get_type (void); +static gboolean test_mpeg_audio_parse_start (GstBaseParse * parse); +static gboolean test_mpeg_audio_parse_stop (GstBaseParse * parse); +static gboolean test_mpeg_audio_parse_check_valid_frame (GstBaseParse * parse, + GstBaseParseFrame * frame, guint * size, gint * skipsize); +static GstFlowReturn test_mpeg_audio_parse_parse_frame (GstBaseParse * parse, + GstBaseParseFrame * frame); + +GST_BOILERPLATE (TestMpegAudioParse, test_mpeg_audio_parse, GstBaseParse, + GST_TYPE_BASE_PARSE); + +static void +test_mpeg_audio_parse_base_init (gpointer klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sink_template)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&src_template)); + + gst_element_class_set_details_simple (element_class, "MPEG1 Audio Parser", + "Codec/Parser/Audio", "Pretends to parse mpeg1 audio stream", + "Foo Bar "); +} + +static void +test_mpeg_audio_parse_class_init (TestMpegAudioParseClass * klass) +{ + GstBaseParseClass *parse_class = GST_BASE_PARSE_CLASS (klass); + + parse_class->start = test_mpeg_audio_parse_start; + parse_class->stop = test_mpeg_audio_parse_stop; + parse_class->check_valid_frame = test_mpeg_audio_parse_check_valid_frame; + parse_class->parse_frame = test_mpeg_audio_parse_parse_frame; +} + +static gint num_parse_instances = 0; + +static void +test_mpeg_audio_parse_init (TestMpegAudioParse * mp3parse, + TestMpegAudioParseClass * klass) +{ + /* catch decodebin plugging parsers in a loop early */ + fail_unless (++num_parse_instances < 10); +} + +static gboolean +test_mpeg_audio_parse_start (GstBaseParse * parse) +{ + gst_base_parse_set_min_frame_size (parse, 6); + return TRUE; +} + +static gboolean +test_mpeg_audio_parse_stop (GstBaseParse * parse) +{ + return TRUE; +} + +static gboolean +test_mpeg_audio_parse_check_valid_frame (GstBaseParse * parse, + GstBaseParseFrame * frame, guint * framesize, gint * skipsize) +{ + const guint8 *data = GST_BUFFER_DATA (frame->buffer); + + if ((GST_READ_UINT16_BE (data) & 0xffe0) == 0xffe0) { + /* this framesize is hard-coded for ../test.mp3 */ + *framesize = 1045; + return TRUE; + } else { + *skipsize = 1; + return FALSE; + } +} + +static GstFlowReturn +test_mpeg_audio_parse_parse_frame (GstBaseParse * parse, + GstBaseParseFrame * frame) +{ + if (GST_BUFFER_OFFSET (frame->buffer) == 0) { + GstCaps *caps; + + caps = gst_caps_new_simple ("audio/mpeg", "mpegversion", G_TYPE_INT, 1, + "mpegaudioversion", G_TYPE_INT, 1, "layer", G_TYPE_INT, 3, + "rate", G_TYPE_INT, 44100, "channels", G_TYPE_INT, 2, NULL); + gst_buffer_set_caps (frame->buffer, caps); + gst_pad_set_caps (GST_BASE_PARSE_SRC_PAD (parse), caps); + gst_caps_unref (caps); + } + return GST_FLOW_OK; +} + +static gboolean +plugin_init (GstPlugin * plugin) +{ + return gst_element_register (plugin, "testmpegaudioparse", GST_RANK_NONE, + test_mpeg_audio_parse_get_type ()); +} + +GST_START_TEST (test_mp3_parser_loop) +{ + GstStateChangeReturn sret; + GstPluginFeature *feature; + GstMessage *msg; + GstElement *pipe, *src, *dec; + gchar *path; + + num_parse_instances = 0; + + gst_plugin_register_static (GST_VERSION_MAJOR, GST_VERSION_MINOR, + "fakemp3parse", "fakemp3parse", plugin_init, VERSION, "LGPL", + "gst-plugins-base", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN); + + feature = gst_default_registry_find_feature ("testmpegaudioparse", + GST_TYPE_ELEMENT_FACTORY); + + gst_plugin_feature_set_rank (feature, GST_RANK_PRIMARY + 100); + + pipe = gst_pipeline_new (NULL); + + src = gst_element_factory_make ("filesrc", NULL); + fail_unless (src != NULL); + + path = g_build_filename (GST_TEST_FILES_PATH, "test.mp3", NULL); + g_object_set (src, "location", path, NULL); + g_free (path); + + dec = gst_element_factory_make ("decodebin", NULL); + fail_unless (dec != NULL); + + gst_bin_add_many (GST_BIN (pipe), src, dec, NULL); + gst_element_link_many (src, dec, NULL); + + sret = gst_element_set_state (pipe, GST_STATE_PLAYING); + fail_unless_equals_int (sret, GST_STATE_CHANGE_ASYNC); + + /* wait for unlinked error */ + msg = gst_bus_timed_pop_filtered (GST_ELEMENT_BUS (pipe), + GST_CLOCK_TIME_NONE, GST_MESSAGE_ERROR); + gst_message_unref (msg); + + gst_element_set_state (pipe, GST_STATE_NULL); + gst_object_unref (pipe); + + /* make sure out parser got plugged at all though */ + fail_unless_equals_int (num_parse_instances, 1); + + /* don't want to interfere with any other of the other tests */ + gst_plugin_feature_set_rank (feature, GST_RANK_NONE); + gst_object_unref (feature); +} + +GST_END_TEST; + static Suite * decodebin_suite (void) { @@ -193,6 +366,7 @@ decodebin_suite (void) suite_add_tcase (s, tc_chain); tcase_add_test (tc_chain, test_text_plain_streams); tcase_add_test (tc_chain, test_reuse_without_decoders); + tcase_add_test (tc_chain, test_mp3_parser_loop); return s; } diff --git a/tests/files/Makefile.am b/tests/files/Makefile.am index ca997a2a07..dfe968acbd 100644 --- a/tests/files/Makefile.am +++ b/tests/files/Makefile.am @@ -1,4 +1,5 @@ EXTRA_DIST = \ 623663.mts \ hls.m3u8 \ - partialframe.mjpeg + partialframe.mjpeg \ + test.mp3 diff --git a/tests/files/test.mp3 b/tests/files/test.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..304892c3cfd8703af65c45ca5fd917aa51cece90 GIT binary patch literal 3135 zcmeI!XON^N!)ug0qqk1TY{t5a>JMw_X&oo(CwwBP5`?)&iGm-l-g-tYbXzxUgRRlw)i zFZf)*KYc2e)s$6Gv*U=vn1$-Lb00e@$Q@vf5(8tXOSMr)+_r`jECmWuxle zlA)~~Edc##U`k4dg>Lco3hf>o4?g-$Yp!8DiLIBK$z10Ci>$}qK4Q<*o!X0mGR62 z)O^Rrrh%5#GaKY!->Vf>*$HLpzSG7Y=k_L^C0%>`o_S@v+5Y?6VAzbVmM`&5X;q~o za&7)cg_mHN0v3sreksx2*yOB1Am>az=owpJFc|3iFaJQY!R)_vLOQJNvMik_KS7!uhZT%ns@cU{pY_3tn<#!02{WgZlX zXk!WTn+N9u#FKaNqJ)|seVId}aQg>d@y7f}wvW0q0sX?U)w|dx9X2*OwXzgSd%Dwm zEZIMKqNVVgC8Z+FKPfFR(c}s~%%oRh+$n`r#^yGkO-xv%Yp6lz9b=;8{{Uj~X{|Wi{YIvPS#lr()h^CmD{)+p z+MXVhQv*f#foYofP<_(c1)}?KB%17Zyn^k17PrGZVrY8C4vwqdbScuL=zHtJE(=(^ z`KAC`%9tcHp}>$CWkt?hs-q5dX7a{y#tY=u74H3ex(|P5MLMlX>3e^Y)7}*s;`0(S zu&L}l#;<8~$(Ko2AeC+zwv1$LBU1HP{ViQ-s}~NfSkhe~#9>m~ipqUkMzg(&pm6iU zReLhGuGXabj=1_E8^5tQY`Xr`R-vA%98OX)p2yit5u@27&Ji#C_(f{FAaly)^^>Iz zh#eV3tGu7W$6*3}h zmee_`xP#cJ2PY1A+ZoaN*9+HaDVK=pfQn0ItngMEI1}#vCbF~%0=}no(;o7i<5Hg$ zxDdS50p;MKVpZYs2qxU<2F_bdY%>kf4v&Q@F{S+IWHa%I3B!vqmIc+p4(_F0?HGB$ z?>A%ct;T&qg7;-8=eZS-AIy^p5tnjiOha^iDiuj#KV-<#tD|Ep0zIPvT_G}jRZlg0 zus7F*ptrB3hE+sIqW_|(wu$$2$WOS1+)}+d^jQ7%z+zp^dV_qv{ux-OgwW zsx4NI!VxrHbY<=A^R>iG1bGB!^?*|c4F4)91n56m2ZlitNh5%HQE@TvR45BO=}a%y z&?$4nTdgsrz+cIy@*CcDjMS#76tCQ-Ln23a8&P}8uqTqnvDK20QBE;NB)48J)yG)V z-l}gkYvMK~vazJ33eA+_T%fgW+qgm>cDaPeiW>J1;r~ZQi z|6eF1X5W7UQGG(e3`}|R=~Ec){5J~T0MSH1cko2?f+g?7RojK^3Ln)Ksvngq=m^pn zrz1M-3I{_XplzW;g|Q5$y`I#dHA7xM<~71KK=voyC+gv|oNqp6?lyUt^K_s0p{zf5 zm^WI!=5BAt9|>S9U(~!Nto87}ya#JS~som#f*^wB5LmAtG=i&PKZ z#v|ne8PFx5sBC$X>5vKTh%mFmlHbm9lkl`nLMP;%XLcTZ(*KD|3&x+c>qKk6fy9u? zYr~KA&fEc4%#~H89>I}Y-C#0{=GD>-`)l`Be`UMrGG{CJOJdT72#4;kdQ4!# zXpJ|BvbOCD#rbHhBC>W6W(SyTDk1Bi$a53qVnbK%PMlx0ou z-HRfWwotH4Lj#EoB^W%sa2g^{4n#rjeycN@m^~_hH!BeyX3pjQvnl7nQSS5)duYKk zVOUDEi#!SXG@?X!2p zrg>KDRO!_9OhwL!ti`FTJZ+t`==W0Jgxn${zH~)J7Z`Sb0X`>f`<19IB~5;Nv;kX8 zjvS%ai(H!$lnQ76wn10|ekWbWNRkvPzIo>@N!1==e2%helUPO;7Z9Kr8Opa|fM<=w k Date: Fri, 15 Jul 2011 16:46:54 +0100 Subject: [PATCH 005/140] tests: add decodebin2 test for parser autoplugging Make sure decodebin2 doesn't try to plug the same parser twice in a row. --- tests/check/elements/decodebin2.c | 174 ++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) diff --git a/tests/check/elements/decodebin2.c b/tests/check/elements/decodebin2.c index 84cf4054c2..8eb47981e6 100644 --- a/tests/check/elements/decodebin2.c +++ b/tests/check/elements/decodebin2.c @@ -1,6 +1,7 @@ /* GStreamer unit tests for decodebin2 * * Copyright (C) 2006 Tim-Philipp Müller + * Copyright (C) 2011 Hewlett-Packard Development Company, L.P. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -23,6 +24,7 @@ #endif #include +#include #include static const gchar dummytext[] = @@ -187,6 +189,177 @@ GST_START_TEST (test_reuse_without_decoders) GST_END_TEST; +/* Fake mp3 parser for test */ +typedef GstBaseParse TestMpegAudioParse; +typedef GstBaseParseClass TestMpegAudioParseClass; + +static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/mpeg, mpegversion=1, layer=[1,3], parsed=(b)true") + ); + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/mpeg, mpegversion=1, parsed=(bool) { false, true }") + ); + +static GType test_mpeg_audio_parse_get_type (void); +static gboolean test_mpeg_audio_parse_start (GstBaseParse * parse); +static gboolean test_mpeg_audio_parse_stop (GstBaseParse * parse); +static gboolean test_mpeg_audio_parse_check_valid_frame (GstBaseParse * parse, + GstBaseParseFrame * frame, guint * size, gint * skipsize); +static GstFlowReturn test_mpeg_audio_parse_parse_frame (GstBaseParse * parse, + GstBaseParseFrame * frame); + +GST_BOILERPLATE (TestMpegAudioParse, test_mpeg_audio_parse, GstBaseParse, + GST_TYPE_BASE_PARSE); + +static void +test_mpeg_audio_parse_base_init (gpointer klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sink_template)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&src_template)); + + gst_element_class_set_details_simple (element_class, "MPEG1 Audio Parser", + "Codec/Parser/Audio", "Pretends to parse mpeg1 audio stream", + "Foo Bar "); +} + +static void +test_mpeg_audio_parse_class_init (TestMpegAudioParseClass * klass) +{ + GstBaseParseClass *parse_class = GST_BASE_PARSE_CLASS (klass); + + parse_class->start = test_mpeg_audio_parse_start; + parse_class->stop = test_mpeg_audio_parse_stop; + parse_class->check_valid_frame = test_mpeg_audio_parse_check_valid_frame; + parse_class->parse_frame = test_mpeg_audio_parse_parse_frame; +} + +static gint num_parse_instances = 0; + +static void +test_mpeg_audio_parse_init (TestMpegAudioParse * mp3parse, + TestMpegAudioParseClass * klass) +{ + /* catch decodebin plugging parsers in a loop early */ + fail_unless (++num_parse_instances < 10); +} + +static gboolean +test_mpeg_audio_parse_start (GstBaseParse * parse) +{ + gst_base_parse_set_min_frame_size (parse, 6); + return TRUE; +} + +static gboolean +test_mpeg_audio_parse_stop (GstBaseParse * parse) +{ + return TRUE; +} + +static gboolean +test_mpeg_audio_parse_check_valid_frame (GstBaseParse * parse, + GstBaseParseFrame * frame, guint * framesize, gint * skipsize) +{ + const guint8 *data = GST_BUFFER_DATA (frame->buffer); + + if ((GST_READ_UINT16_BE (data) & 0xffe0) == 0xffe0) { + /* this framesize is hard-coded for ../test.mp3 */ + *framesize = 1045; + return TRUE; + } else { + *skipsize = 1; + return FALSE; + } +} + +static GstFlowReturn +test_mpeg_audio_parse_parse_frame (GstBaseParse * parse, + GstBaseParseFrame * frame) +{ + if (GST_BUFFER_OFFSET (frame->buffer) == 0) { + GstCaps *caps; + + caps = gst_caps_new_simple ("audio/mpeg", "mpegversion", G_TYPE_INT, 1, + "mpegaudioversion", G_TYPE_INT, 1, "layer", G_TYPE_INT, 3, + "rate", G_TYPE_INT, 44100, "channels", G_TYPE_INT, 2, NULL); + gst_buffer_set_caps (frame->buffer, caps); + gst_pad_set_caps (GST_BASE_PARSE_SRC_PAD (parse), caps); + gst_caps_unref (caps); + } + return GST_FLOW_OK; +} + +static gboolean +plugin_init (GstPlugin * plugin) +{ + return gst_element_register (plugin, "testmpegaudioparse", GST_RANK_NONE, + test_mpeg_audio_parse_get_type ()); +} + +GST_START_TEST (test_mp3_parser_loop) +{ + GstStateChangeReturn sret; + GstPluginFeature *feature; + GstMessage *msg; + GstElement *pipe, *src, *dec; + gchar *path; + + num_parse_instances = 0; + + gst_plugin_register_static (GST_VERSION_MAJOR, GST_VERSION_MINOR, + "fakemp3parse", "fakemp3parse", plugin_init, VERSION, "LGPL", + "gst-plugins-base", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN); + + feature = gst_default_registry_find_feature ("testmpegaudioparse", + GST_TYPE_ELEMENT_FACTORY); + + gst_plugin_feature_set_rank (feature, GST_RANK_PRIMARY + 100); + + pipe = gst_pipeline_new (NULL); + + src = gst_element_factory_make ("filesrc", NULL); + fail_unless (src != NULL); + + path = g_build_filename (GST_TEST_FILES_PATH, "test.mp3", NULL); + g_object_set (src, "location", path, NULL); + g_free (path); + + dec = gst_element_factory_make ("decodebin2", NULL); + fail_unless (dec != NULL); + + gst_bin_add_many (GST_BIN (pipe), src, dec, NULL); + gst_element_link_many (src, dec, NULL); + + sret = gst_element_set_state (pipe, GST_STATE_PLAYING); + fail_unless_equals_int (sret, GST_STATE_CHANGE_ASYNC); + + /* wait for unlinked error */ + msg = gst_bus_timed_pop_filtered (GST_ELEMENT_BUS (pipe), + GST_CLOCK_TIME_NONE, GST_MESSAGE_ERROR); + gst_message_unref (msg); + + gst_element_set_state (pipe, GST_STATE_NULL); + gst_object_unref (pipe); + + /* make sure out parser got plugged at all though */ + fail_unless_equals_int (num_parse_instances, 1); + + /* don't want to interfere with any other of the other tests */ + gst_plugin_feature_set_rank (feature, GST_RANK_NONE); + gst_object_unref (feature); +} + +GST_END_TEST; + static Suite * decodebin2_suite (void) { @@ -196,6 +369,7 @@ decodebin2_suite (void) suite_add_tcase (s, tc_chain); tcase_add_test (tc_chain, test_text_plain_streams); tcase_add_test (tc_chain, test_reuse_without_decoders); + tcase_add_test (tc_chain, test_mp3_parser_loop); return s; } From 0667b1adf5df4c025dd301f345d7d8a986098886 Mon Sep 17 00:00:00 2001 From: Stefan Sauer Date: Wed, 20 Jul 2011 13:46:31 +0200 Subject: [PATCH 006/140] textoverlay: keep untimestamped textbuffer until next one Instead of discarding untimestamped text-buffers immeditely after rendering, keep them until we receive the next text buffer. Fixes #654959 --- ext/pango/gsttextoverlay.c | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/ext/pango/gsttextoverlay.c b/ext/pango/gsttextoverlay.c index 590baa9eec..6d15d3f380 100644 --- a/ext/pango/gsttextoverlay.c +++ b/ext/pango/gsttextoverlay.c @@ -2390,16 +2390,22 @@ gst_text_overlay_text_chain (GstPad * pad, GstBuffer * buffer) else if (GST_BUFFER_DURATION_IS_VALID (buffer)) GST_BUFFER_DURATION (buffer) = clip_stop - clip_start; - /* Wait for the previous buffer to go away */ - while (overlay->text_buffer != NULL) { - GST_DEBUG ("Pad %s:%s has a buffer queued, waiting", - GST_DEBUG_PAD_NAME (pad)); - GST_TEXT_OVERLAY_WAIT (overlay); - GST_DEBUG ("Pad %s:%s resuming", GST_DEBUG_PAD_NAME (pad)); - if (overlay->text_flushing) { - GST_OBJECT_UNLOCK (overlay); - ret = GST_FLOW_WRONG_STATE; - goto beach; + if (overlay->text_buffer + && (!GST_BUFFER_TIMESTAMP_IS_VALID (overlay->text_buffer) + || !GST_BUFFER_DURATION_IS_VALID (overlay->text_buffer))) { + gst_text_overlay_pop_text (overlay); + } else { + /* Wait for the previous buffer to go away */ + while (overlay->text_buffer != NULL) { + GST_DEBUG ("Pad %s:%s has a buffer queued, waiting", + GST_DEBUG_PAD_NAME (pad)); + GST_TEXT_OVERLAY_WAIT (overlay); + GST_DEBUG ("Pad %s:%s resuming", GST_DEBUG_PAD_NAME (pad)); + if (overlay->text_flushing) { + GST_OBJECT_UNLOCK (overlay); + ret = GST_FLOW_WRONG_STATE; + goto beach; + } } } @@ -2548,7 +2554,6 @@ wait_for_text_buf: !GST_BUFFER_DURATION_IS_VALID (overlay->text_buffer)) { GST_WARNING_OBJECT (overlay, "Got text buffer with invalid timestamp or duration"); - pop_text = TRUE; valid_text_time = FALSE; } else { text_start = GST_BUFFER_TIMESTAMP (overlay->text_buffer); From ed1f02e2a19d82fe759ca8409336e6a960d5e83b Mon Sep 17 00:00:00 2001 From: Stefan Sauer Date: Sat, 23 Jul 2011 13:46:31 +0200 Subject: [PATCH 007/140] test: print actual timestamp on failure --- tests/check/pipelines/basetime.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/check/pipelines/basetime.c b/tests/check/pipelines/basetime.c index 2b84f9dd92..f08cd12640 100644 --- a/tests/check/pipelines/basetime.c +++ b/tests/check/pipelines/basetime.c @@ -46,7 +46,8 @@ buffer_probe_cb (GstPad * pad, GstBuffer * buffer) { if (old_ts != GST_CLOCK_TIME_NONE) { fail_unless (GST_BUFFER_TIMESTAMP (buffer) != old_ts, - "Two buffers had same timestamp"); + "Two buffers had same timestamp: %" GST_TIME_FORMAT, + GST_TIME_ARGS (old_ts)); } old_ts = GST_BUFFER_TIMESTAMP (buffer); From 5d5ab90e51869e915efb73281a0bcb925581478c Mon Sep 17 00:00:00 2001 From: Stefan Sauer Date: Sat, 23 Jul 2011 14:21:27 +0200 Subject: [PATCH 008/140] textoverlay: add example for feeding from stdin --- ext/pango/gsttextoverlay.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ext/pango/gsttextoverlay.c b/ext/pango/gsttextoverlay.c index 6d15d3f380..cf61a8ad30 100644 --- a/ext/pango/gsttextoverlay.c +++ b/ext/pango/gsttextoverlay.c @@ -65,6 +65,12 @@ * Uh? What are you talking about? * I don't understand (18-62s) * ]| + * one can also feed live text into the element + * |[ + * gst-launch fdsrc fd=0 ! text/plain ! txt. videotestsrc ! \ + * textoverlay name=txt shaded-background=yes font-desc="Serif 40" wait-text=false ! \ + * xvimagesink + * ]| This shows new text as entered on the terminal (stdin). * * */ From c91928f7a2461aba57f64e014f507bcb62d7adc6 Mon Sep 17 00:00:00 2001 From: Edward Hervey Date: Mon, 25 Jul 2011 10:41:04 +0200 Subject: [PATCH 009/140] decodebin2: Properly handle multi-stream chains When we have a multi-stream (i.e. audio and video) input and the demuxer adds/removes pads for a new stream (common in a mpeg-ts stream when the program stream mapping is updated), the algorithm for EOS handling was previously wrong (it would only drop the EOS of the *last* pad but would let the EOS on the other pads go through). The logic has only been changed a tiny bit for EOS handling resulting in: * If there is no next group, let the EOS go through * If there is a next group, but not all pads are drained in the active group, drop the EOS event * If there is a next group and all pads are drained, then the ghostpads will be removed and the EOS event will be dropped automatically. --- gst/playback/gstdecodebin2.c | 41 ++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/gst/playback/gstdecodebin2.c b/gst/playback/gstdecodebin2.c index 0e8c0405fa..260fac8455 100644 --- a/gst/playback/gstdecodebin2.c +++ b/gst/playback/gstdecodebin2.c @@ -424,7 +424,7 @@ static void gst_decode_group_free (GstDecodeGroup * group); static GstDecodeGroup *gst_decode_group_new (GstDecodeBin * dbin, GstDecodeChain * chain); static gboolean gst_decode_chain_is_complete (GstDecodeChain * chain); -static void gst_decode_chain_handle_eos (GstDecodeChain * chain); +static gboolean gst_decode_chain_handle_eos (GstDecodeChain * chain); static gboolean gst_decode_chain_expose (GstDecodeChain * chain, GList ** endpads, gboolean * missing_plugin); static gboolean gst_decode_chain_is_drained (GstDecodeChain * chain); @@ -2908,14 +2908,14 @@ out: /* check if the group is drained, meaning all pads have seen an EOS * event. */ -static void +static gboolean gst_decode_pad_handle_eos (GstDecodePad * pad) { GstDecodeChain *chain = pad->chain; GST_LOG_OBJECT (pad->dbin, "chain : %p, pad %p", chain, pad); pad->drained = TRUE; - gst_decode_chain_handle_eos (chain); + return gst_decode_chain_handle_eos (chain); } /* gst_decode_chain_handle_eos: @@ -2926,17 +2926,23 @@ gst_decode_pad_handle_eos (GstDecodePad * pad) * If there are groups to switch to, hide the current active * one and expose the new one. * + * If a group isn't completely drained (i.e. we received EOS + * only on one of the streams) this function will return FALSE + * to indicate the EOS on the given chain should be dropped + * to avoid it from going downstream. + * * MT-safe, don't call with chain lock! */ -static void +static gboolean gst_decode_chain_handle_eos (GstDecodeChain * eos_chain) { GstDecodeBin *dbin = eos_chain->dbin; GstDecodeGroup *group; GstDecodeChain *chain = eos_chain; gboolean drained; + gboolean forward_eos = TRUE; - g_return_if_fail (eos_chain->endpad); + g_return_val_if_fail (eos_chain->endpad, TRUE); CHAIN_MUTEX_LOCK (chain); while ((group = chain->parent)) { @@ -2956,6 +2962,8 @@ gst_decode_chain_handle_eos (GstDecodeChain * eos_chain) /* Now either group == NULL and chain == dbin->decode_chain * or chain is the lowest chain that has a non-drained group */ if (chain->active_group && drained && chain->next_groups) { + /* There's an active group which is drained and we have another + * one to switch to. */ GST_DEBUG_OBJECT (dbin, "Hiding current group %p", chain->active_group); gst_decode_group_hide (chain->active_group); chain->old_groups = g_list_prepend (chain->old_groups, chain->active_group); @@ -2970,6 +2978,7 @@ gst_decode_chain_handle_eos (GstDecodeChain * eos_chain) gst_decode_bin_expose (dbin); EXPOSE_UNLOCK (dbin); } else if (!chain->active_group || drained) { + /* The group is drained and there isn't a future one */ g_assert (chain == dbin->decode_chain); CHAIN_MUTEX_UNLOCK (chain); @@ -2980,7 +2989,11 @@ gst_decode_chain_handle_eos (GstDecodeChain * eos_chain) CHAIN_MUTEX_UNLOCK (chain); GST_DEBUG_OBJECT (dbin, "Current active group in chain %p is not drained yet", chain); + /* Instruct caller to drop EOS event */ + forward_eos = FALSE; } + + return forward_eos; } /* gst_decode_group_is_drained: @@ -3481,18 +3494,24 @@ source_pad_blocked_cb (GstPad * pad, gboolean blocked, GstDecodePad * dpad) static gboolean source_pad_event_probe (GstPad * pad, GstEvent * event, GstDecodePad * dpad) { + gboolean res = TRUE; + GST_LOG_OBJECT (pad, "%s dpad:%p", GST_EVENT_TYPE_NAME (event), dpad); if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) { GST_DEBUG_OBJECT (pad, "we received EOS"); - /* Check if all pads are drained. If there is a next group to expose, we - * will remove the ghostpad of the current group first, which unlinks the - * peer and so drops the EOS. */ - gst_decode_pad_handle_eos (dpad); + /* Check if all pads are drained. + * * If there is no next group, we will let the EOS go through. + * * If there is a next group but the current group isn't completely + * drained, we will drop the EOS event. + * * If there is a next group to expose and this was the last non-drained + * pad for that group, we will remove the ghostpad of the current group + * first, which unlinks the peer and so drops the EOS. */ + res = gst_decode_pad_handle_eos (dpad); } - /* never drop events */ - return TRUE; + + return res; } static void From 2db389f775160e75fca744fd3b9a3a4b0cade1b1 Mon Sep 17 00:00:00 2001 From: Stefan Sauer Date: Mon, 25 Jul 2011 12:04:02 +0200 Subject: [PATCH 010/140] textoverlay: improve the example Mentioned that this is not ment to be used with subtitles and suggest alternatives. --- ext/pango/gsttextoverlay.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ext/pango/gsttextoverlay.c b/ext/pango/gsttextoverlay.c index cf61a8ad30..675479d1ec 100644 --- a/ext/pango/gsttextoverlay.c +++ b/ext/pango/gsttextoverlay.c @@ -65,12 +65,15 @@ * Uh? What are you talking about? * I don't understand (18-62s) * ]| - * one can also feed live text into the element + * One can also feed arbitrary live text into the element: * |[ * gst-launch fdsrc fd=0 ! text/plain ! txt. videotestsrc ! \ * textoverlay name=txt shaded-background=yes font-desc="Serif 40" wait-text=false ! \ * xvimagesink - * ]| This shows new text as entered on the terminal (stdin). + * ]| This shows new text as entered on the terminal (stdin). This is not suited + * for subtitles as the test overlay is not timed. Subtitles should use + * timestamped formats. For the above use case one can also read the text from + * the application as set the #GstTextOverlay:text property. * * */ From 059db89633151248f1612edb64de0c9d6208e4b1 Mon Sep 17 00:00:00 2001 From: Edward Hervey Date: Mon, 25 Jul 2011 18:37:15 +0200 Subject: [PATCH 011/140] playbin2: Avoid resetting playsink when not needed When we don't have specific {audio|video|text}-sink properties, don't set them on playsink when reconfiguring. If we do that, we end up setting the previous configured sink to GST_STATE_NULL resulting in any potentially pending push being returned with GST_FLOW_WRONG_STATE which will cause the upstream elements to silently stop. https://bugzilla.gnome.org/show_bug.cgi?id=655279 --- gst/playback/gstplaybin2.c | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/gst/playback/gstplaybin2.c b/gst/playback/gstplaybin2.c index 93f409bff3..d788ebec19 100644 --- a/gst/playback/gstplaybin2.c +++ b/gst/playback/gstplaybin2.c @@ -2787,20 +2787,26 @@ no_more_pads_cb (GstElement * decodebin, GstSourceGroup * group) /* if we have custom sinks, configure them now */ GST_SOURCE_GROUP_LOCK (group); - GST_INFO_OBJECT (playbin, "setting custom audio sink %" GST_PTR_FORMAT, - group->audio_sink); - gst_play_sink_set_sink (playbin->playsink, GST_PLAY_SINK_TYPE_AUDIO, - group->audio_sink); + if (group->audio_sink) { + GST_INFO_OBJECT (playbin, "setting custom audio sink %" GST_PTR_FORMAT, + group->audio_sink); + gst_play_sink_set_sink (playbin->playsink, GST_PLAY_SINK_TYPE_AUDIO, + group->audio_sink); + } - GST_INFO_OBJECT (playbin, "setting custom video sink %" GST_PTR_FORMAT, - group->video_sink); - gst_play_sink_set_sink (playbin->playsink, GST_PLAY_SINK_TYPE_VIDEO, - group->video_sink); + if (group->video_sink) { + GST_INFO_OBJECT (playbin, "setting custom video sink %" GST_PTR_FORMAT, + group->video_sink); + gst_play_sink_set_sink (playbin->playsink, GST_PLAY_SINK_TYPE_VIDEO, + group->video_sink); + } - GST_INFO_OBJECT (playbin, "setting custom text sink %" GST_PTR_FORMAT, - playbin->text_sink); - gst_play_sink_set_sink (playbin->playsink, GST_PLAY_SINK_TYPE_TEXT, - playbin->text_sink); + if (playbin->text_sink) { + GST_INFO_OBJECT (playbin, "setting custom text sink %" GST_PTR_FORMAT, + playbin->text_sink); + gst_play_sink_set_sink (playbin->playsink, GST_PLAY_SINK_TYPE_TEXT, + playbin->text_sink); + } GST_SOURCE_GROUP_UNLOCK (group); From 38a6919a7bb36b4db340ec783cf95afc1410cadf Mon Sep 17 00:00:00 2001 From: Edward Hervey Date: Tue, 26 Jul 2011 12:33:56 +0200 Subject: [PATCH 012/140] decodebin2: Allow all EOS to go through if we don't have a next group Only drop them if the current group isn't drained .. AND there is a next group to switch to. Should Fix #655268 --- gst/playback/gstdecodebin2.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gst/playback/gstdecodebin2.c b/gst/playback/gstdecodebin2.c index 260fac8455..77942609d0 100644 --- a/gst/playback/gstdecodebin2.c +++ b/gst/playback/gstdecodebin2.c @@ -2989,8 +2989,9 @@ gst_decode_chain_handle_eos (GstDecodeChain * eos_chain) CHAIN_MUTEX_UNLOCK (chain); GST_DEBUG_OBJECT (dbin, "Current active group in chain %p is not drained yet", chain); - /* Instruct caller to drop EOS event */ - forward_eos = FALSE; + /* Instruct caller to drop EOS event if we have future groups */ + if (chain->next_groups) + forward_eos = FALSE; } return forward_eos; From a8228b062a47b02ee7413dd2ea453d01e84e8129 Mon Sep 17 00:00:00 2001 From: Stefan Kost Date: Mon, 25 Jul 2011 19:11:59 +0200 Subject: [PATCH 013/140] adder: more debug logging for events --- gst/adder/gstadder.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/gst/adder/gstadder.c b/gst/adder/gstadder.c index 1a93fe7a09..6eb9f515be 100644 --- a/gst/adder/gstadder.c +++ b/gst/adder/gstadder.c @@ -627,6 +627,9 @@ gst_adder_src_event (GstPad * pad, GstEvent * event) adder = GST_ADDER (gst_pad_get_parent (pad)); + GST_DEBUG_OBJECT (pad, "Got %s event on src pad", + GST_EVENT_TYPE_NAME (event)); + switch (GST_EVENT_TYPE (event)) { case GST_EVENT_SEEK: { @@ -739,8 +742,8 @@ gst_adder_sink_event (GstPad * pad, GstEvent * event) adder = GST_ADDER (gst_pad_get_parent (pad)); - GST_DEBUG ("Got %s event on pad %s:%s", GST_EVENT_TYPE_NAME (event), - GST_DEBUG_PAD_NAME (pad)); + GST_DEBUG_OBJECT (pad, "Got %s event on sink pad", + GST_EVENT_TYPE_NAME (event)); switch (GST_EVENT_TYPE (event)) { case GST_EVENT_FLUSH_STOP: From 9a26e6c7bc232dbdc70a0877e02ddd38eadbb2c6 Mon Sep 17 00:00:00 2001 From: Stefan Kost Date: Mon, 25 Jul 2011 19:39:55 +0200 Subject: [PATCH 014/140] adder: rework pending event handling Use atomic ops on pending flags. Rename the segment_pending to new_segment_pending. Set new_segment_pending not when we received seek, but when we received the first upstream new_segment. --- gst/adder/gstadder.c | 27 ++++++++++++++++++--------- gst/adder/gstadder.h | 5 +++-- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/gst/adder/gstadder.c b/gst/adder/gstadder.c index 6eb9f515be..7d06239ab8 100644 --- a/gst/adder/gstadder.c +++ b/gst/adder/gstadder.c @@ -672,7 +672,7 @@ gst_adder_src_event (GstPad * pad, GstEvent * event) * forwarding the seek upstream or from gst_adder_collected, * whichever happens first. */ - adder->flush_stop_pending = TRUE; + g_atomic_int_set (&adder->flush_stop_pending, TRUE); } GST_DEBUG_OBJECT (adder, "handling seek event: %" GST_PTR_FORMAT, event); @@ -688,9 +688,6 @@ gst_adder_src_event (GstPad * pad, GstEvent * event) adder->segment_end = end; else adder->segment_end = GST_CLOCK_TIME_NONE; - /* make sure we push a new segment, to inform about new basetime - * see FIXME in gst_adder_collected() */ - adder->segment_pending = TRUE; if (flush) { /* Yes, we need to call _set_flushing again *WHEN* the streaming threads * have stopped so that the cookie gets properly updated. */ @@ -700,6 +697,9 @@ gst_adder_src_event (GstPad * pad, GstEvent * event) GST_DEBUG_OBJECT (adder, "forwarding seek event: %" GST_PTR_FORMAT, event); + /* we're forwarding seek to all upstream peers and wait for one to reply + * with a newsegment-event before we send a newsegment-event downstream */ + g_atomic_int_set (&adder->wait_for_new_segment, TRUE); result = forward_event (adder, event, flush); if (!result) { /* seek failed. maybe source is a live source. */ @@ -755,8 +755,8 @@ gst_adder_sink_event (GstPad * pad, GstEvent * event) * flush-stop from the collect function later. */ GST_OBJECT_LOCK (adder->collect); - adder->segment_pending = TRUE; - adder->flush_stop_pending = FALSE; + g_atomic_int_set (&adder->new_segment_pending, TRUE); + g_atomic_int_set (&adder->flush_stop_pending, FALSE); /* Clear pending tags */ if (adder->pending_events) { g_list_foreach (adder->pending_events, (GFunc) gst_event_unref, NULL); @@ -771,6 +771,14 @@ gst_adder_sink_event (GstPad * pad, GstEvent * event) adder->pending_events = g_list_append (adder->pending_events, event); GST_OBJECT_UNLOCK (adder->collect); goto beach; + case GST_EVENT_NEWSEGMENT: + if (g_atomic_int_compare_and_exchange (&adder->wait_for_new_segment, + TRUE, FALSE)) { + /* make sure we push a new segment, to inform about new basetime + * see FIXME in gst_adder_collected() */ + g_atomic_int_set (&adder->new_segment_pending, TRUE); + } + break; default: break; } @@ -1150,7 +1158,8 @@ gst_adder_collected (GstCollectPads * pads, gpointer user_data) /* we had an output buffer, unref the gapbuffer we kept */ gst_buffer_unref (gapbuf); - if (adder->segment_pending) { + if (g_atomic_int_compare_and_exchange (&adder->new_segment_pending, TRUE, + FALSE)) { GstEvent *event; /* FIXME, use rate/applied_rate as set on all sinkpads. @@ -1180,7 +1189,6 @@ gst_adder_collected (GstCollectPads * pads, gpointer user_data) GST_WARNING_OBJECT (adder->srcpad, "Sending event %p (%s) failed.", event, GST_EVENT_TYPE_NAME (event)); } - adder->segment_pending = FALSE; } else { GST_WARNING_OBJECT (adder->srcpad, "Creating new segment event for " "start:%" G_GINT64_FORMAT " end:%" G_GINT64_FORMAT " failed", @@ -1268,7 +1276,8 @@ gst_adder_change_state (GstElement * element, GstStateChange transition) adder->timestamp = 0; adder->offset = 0; adder->flush_stop_pending = FALSE; - adder->segment_pending = TRUE; + adder->new_segment_pending = TRUE; + adder->wait_for_new_segment = FALSE; adder->segment_start = 0; adder->segment_end = GST_CLOCK_TIME_NONE; adder->segment_rate = 1.0; diff --git a/gst/adder/gstadder.h b/gst/adder/gstadder.h index f0151b480b..7fbdde6d00 100644 --- a/gst/adder/gstadder.h +++ b/gst/adder/gstadder.h @@ -85,11 +85,12 @@ struct _GstAdder { /* sink event handling */ GstPadEventFunction collect_event; GstSegment segment; - gboolean segment_pending; guint64 segment_start, segment_end; gdouble segment_rate; + volatile gboolean new_segment_pending; + volatile gboolean wait_for_new_segment; /* src event handling */ - gboolean flush_stop_pending; + volatile gboolean flush_stop_pending; /* target caps */ GstCaps *filter_caps; From 4019e919f69e4bdd15cd443c286ee321c13d4404 Mon Sep 17 00:00:00 2001 From: Stefan Kost Date: Mon, 25 Jul 2011 19:51:24 +0200 Subject: [PATCH 015/140] tests: rename the test suite to match the binary This unbreaks determining the name for make elements/playbin2-compressed.check from the test output. --- tests/check/elements/playbin2-compressed.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/check/elements/playbin2-compressed.c b/tests/check/elements/playbin2-compressed.c index c992b1cb31..5f3c544283 100644 --- a/tests/check/elements/playbin2-compressed.c +++ b/tests/check/elements/playbin2-compressed.c @@ -2476,7 +2476,7 @@ GST_END_TEST; static Suite * playbin2_compressed_suite (void) { - Suite *s = suite_create ("playbin2_compressed"); + Suite *s = suite_create ("playbin2-compressed"); TCase *tc_chain = tcase_create ("general"); suite_add_tcase (s, tc_chain); From 7ca9c4ec407d44bad934ba3bd9f7aec550b17cf8 Mon Sep 17 00:00:00 2001 From: Stefan Sauer Date: Tue, 26 Jul 2011 13:51:31 +0200 Subject: [PATCH 016/140] basetime: fix failing test Always use audiotestsrc as it seems to have been the intention according to the comment header. The test does not work with live-audiosources. --- tests/check/pipelines/basetime.c | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/tests/check/pipelines/basetime.c b/tests/check/pipelines/basetime.c index f08cd12640..6f7ff717dd 100644 --- a/tests/check/pipelines/basetime.c +++ b/tests/check/pipelines/basetime.c @@ -44,12 +44,15 @@ break_mainloop (gpointer data) static gboolean buffer_probe_cb (GstPad * pad, GstBuffer * buffer) { + GstClockTime new_ts = GST_BUFFER_TIMESTAMP (buffer); + + GST_LOG ("ts = %" GST_TIME_FORMAT, GST_TIME_ARGS (new_ts)); if (old_ts != GST_CLOCK_TIME_NONE) { - fail_unless (GST_BUFFER_TIMESTAMP (buffer) != old_ts, + fail_unless (new_ts != old_ts, "Two buffers had same timestamp: %" GST_TIME_FORMAT, GST_TIME_ARGS (old_ts)); } - old_ts = GST_BUFFER_TIMESTAMP (buffer); + old_ts = new_ts; return TRUE; } @@ -61,11 +64,6 @@ GST_START_TEST (test_basetime_calculation) GstPad *pad; GMainLoop *loop; - /* Don't run with osxaudiosrc . This is because libcheck runs the actual - * test in a forked process and causes havoc with osx's API. */ - if (G_UNLIKELY (!g_ascii_strcasecmp (DEFAULT_AUDIOSRC, "osxaudiosrc"))) - return; - loop = g_main_loop_new (NULL, FALSE); /* The "main" pipeline */ @@ -73,10 +71,9 @@ GST_START_TEST (test_basetime_calculation) fail_if (p1 == NULL); /* Create a sub-bin that is activated only in "certain situations" */ - asrc = gst_element_factory_make (DEFAULT_AUDIOSRC, NULL); + asrc = gst_element_factory_make ("audiotestsrc", NULL); if (asrc == NULL) { - GST_WARNING ("Cannot run test. test audio source %s not available", - DEFAULT_AUDIOSRC); + GST_WARNING ("Cannot run test. 'audiotestsrc' not available"); gst_element_set_state (p1, GST_STATE_NULL); gst_object_unref (p1); return; From 5d0f279feaa3f8c8a4a360853f4ef97237fc1487 Mon Sep 17 00:00:00 2001 From: Mark Nauwelaerts Date: Wed, 1 Jun 2011 10:21:39 +0200 Subject: [PATCH 017/140] baseaudiosink: drop samples that are too late ... rather than having all of them rendered at 0 or subsequently aligned, likely inevitably leading to repeated resyncing. --- gst-libs/gst/audio/gstbaseaudiosink.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/gst-libs/gst/audio/gstbaseaudiosink.c b/gst-libs/gst/audio/gstbaseaudiosink.c index fea7a8e273..ffe449872e 100644 --- a/gst-libs/gst/audio/gstbaseaudiosink.c +++ b/gst-libs/gst/audio/gstbaseaudiosink.c @@ -1614,6 +1614,12 @@ gst_base_audio_sink_render (GstBaseSink * bsink, GstBuffer * buf) render_stop = 0; } + /* in some clock slaving cases, all late samples end up at 0 first, + * and subsequent ones align with that until threshold exceeded, + * and then sync back to 0 and so on, so avoid that altogether */ + if (G_UNLIKELY (render_start == 0 && render_stop == 0)) + goto too_late; + /* and bring the time to the rate corrected offset in the buffer */ render_start = gst_util_uint64_scale_int (render_start, ringbuf->spec.rate, GST_SECOND); @@ -1738,6 +1744,11 @@ out_of_segment: ret = GST_FLOW_OK; goto done; } +too_late: + { + GST_DEBUG_OBJECT (sink, "dropping late sample"); + return GST_FLOW_OK; + } /* ERRORS */ payload_failed: { From 68231a645a69f434fb27a1e15e530696bdad7145 Mon Sep 17 00:00:00 2001 From: Mark Nauwelaerts Date: Thu, 28 Jul 2011 11:44:20 +0200 Subject: [PATCH 018/140] baseaudiosink: fix max latency calculation ... to allow infinite max, as also claimed by comment. --- gst-libs/gst/audio/gstbaseaudiosink.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gst-libs/gst/audio/gstbaseaudiosink.c b/gst-libs/gst/audio/gstbaseaudiosink.c index ffe449872e..7e462f4272 100644 --- a/gst-libs/gst/audio/gstbaseaudiosink.c +++ b/gst-libs/gst/audio/gstbaseaudiosink.c @@ -443,12 +443,16 @@ gst_base_audio_sink_query (GstElement * element, GstQuery * query) min_latency = min_latency + min_l; /* the max latency is the max of the peer, we can delay an infinite * amount of time. */ - max_latency = min_latency + (max_l == -1 ? 0 : max_l); + max_latency = (max_l == -1) ? -1 : (min_latency + max_l); GST_DEBUG_OBJECT (basesink, "peer min %" GST_TIME_FORMAT ", our min latency: %" GST_TIME_FORMAT, GST_TIME_ARGS (min_l), GST_TIME_ARGS (min_latency)); + GST_DEBUG_OBJECT (basesink, + "peer max %" GST_TIME_FORMAT ", our max latency: %" + GST_TIME_FORMAT, GST_TIME_ARGS (max_l), + GST_TIME_ARGS (max_latency)); } else { GST_DEBUG_OBJECT (basesink, "peer or we are not live, don't care about latency"); From 264d91a502705b2ef6fe9ccba660e7551dfdfe99 Mon Sep 17 00:00:00 2001 From: Stefan Sauer Date: Wed, 20 Jul 2011 18:10:57 +0200 Subject: [PATCH 019/140] baseaudiosink: fix latency calculation for live elements Max_latency was computed on already adjusted min_latency. Introduce a new variable for clarity. Spotted by Blaise Gassend. Fixes #644284 --- gst-libs/gst/audio/gstbaseaudiosink.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gst-libs/gst/audio/gstbaseaudiosink.c b/gst-libs/gst/audio/gstbaseaudiosink.c index 7e462f4272..6dafb4b322 100644 --- a/gst-libs/gst/audio/gstbaseaudiosink.c +++ b/gst-libs/gst/audio/gstbaseaudiosink.c @@ -415,7 +415,7 @@ gst_base_audio_sink_query (GstElement * element, GstQuery * query) if ((res = gst_base_sink_query_latency (GST_BASE_SINK_CAST (basesink), &live, &us_live, &min_l, &max_l))) { - GstClockTime min_latency, max_latency; + GstClockTime base_latency, min_latency, max_latency; /* we and upstream are both live, adjust the min_latency */ if (live && us_live) { @@ -434,16 +434,16 @@ gst_base_audio_sink_query (GstElement * element, GstQuery * query) basesink->priv->us_latency = min_l; - min_latency = + base_latency = gst_util_uint64_scale_int (spec->seglatency * spec->segsize, GST_SECOND, spec->rate * spec->bytes_per_sample); GST_OBJECT_UNLOCK (basesink); /* we cannot go lower than the buffer size and the min peer latency */ - min_latency = min_latency + min_l; + min_latency = base_latency + min_l; /* the max latency is the max of the peer, we can delay an infinite * amount of time. */ - max_latency = (max_l == -1) ? -1 : (min_latency + max_l); + max_latency = (max_l == -1) ? -1 : (base_latency + max_l); GST_DEBUG_OBJECT (basesink, "peer min %" GST_TIME_FORMAT ", our min latency: %" From c874edd089db57e901687bdfd0ae82f6eecce3b0 Mon Sep 17 00:00:00 2001 From: Thiago Santos Date: Thu, 28 Jul 2011 11:21:26 -0300 Subject: [PATCH 020/140] encodebin: rename flags names Rename flags names from native-audio/-video to no-audio/video-conversion to be more explicit on what it does --- gst/encoding/gstencodebin.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/gst/encoding/gstencodebin.c b/gst/encoding/gstencodebin.c index dd50ac165e..b41f29bd51 100644 --- a/gst/encoding/gstencodebin.c +++ b/gst/encoding/gstencodebin.c @@ -120,8 +120,8 @@ typedef enum { - GST_ENC_FLAG_NATIVE_AUDIO = (1 << 0), - GST_ENC_FLAG_NATIVE_VIDEO = (1 << 1) + GST_ENC_FLAG_NO_AUDIO_CONVERSION = (1 << 0), + GST_ENC_FLAG_NO_VIDEO_CONVERSION = (1 << 1) } GstEncFlags; #define GST_TYPE_ENC_FLAGS (gst_enc_flags_get_type()) @@ -267,10 +267,10 @@ GType gst_enc_flags_get_type (void) { static const GFlagsValue values[] = { - {C_FLAGS (GST_ENC_FLAG_NATIVE_AUDIO), "Only use native audio formats", - "native-audio"}, - {C_FLAGS (GST_ENC_FLAG_NATIVE_VIDEO), "Only use native video formats", - "native-video"}, + {C_FLAGS (GST_ENC_FLAG_NO_AUDIO_CONVERSION), "Do not use audio conversion " + "elements", "no-audio-conversion"}, + {C_FLAGS (GST_ENC_FLAG_NO_VIDEO_CONVERSION), "Do not use video conversion " + "elements", "no-video-conversion"}, {0, NULL, NULL} }; static volatile GType id = 0; @@ -1261,7 +1261,8 @@ _create_stream_group (GstEncodeBin * ebin, GstEncodingProfile * sprof, /* 3.2. restriction elements */ /* FIXME : Once we have properties for specific converters, use those */ if (GST_IS_ENCODING_VIDEO_PROFILE (sprof)) { - const gboolean native_video = ! !(ebin->flags & GST_ENC_FLAG_NATIVE_VIDEO); + const gboolean native_video = + ! !(ebin->flags & GST_ENC_FLAG_NO_VIDEO_CONVERSION); GstElement *cspace = NULL, *scale, *vrate, *cspace2 = NULL; GST_LOG ("Adding conversion elements for video stream"); @@ -1323,7 +1324,7 @@ _create_stream_group (GstEncodeBin * ebin, GstEncodingProfile * sprof, } } else if (GST_IS_ENCODING_AUDIO_PROFILE (sprof) - && !(ebin->flags & GST_ENC_FLAG_NATIVE_AUDIO)) { + && !(ebin->flags & GST_ENC_FLAG_NO_AUDIO_CONVERSION)) { GstElement *aconv, *ares, *arate, *aconv2; GST_LOG ("Adding conversion elements for audio stream"); From 1db1be1df83cc58e32aad319149ab82bc447c950 Mon Sep 17 00:00:00 2001 From: Christophe Fergeau Date: Thu, 9 Mar 2006 17:44:17 +0000 Subject: [PATCH 021/140] new id3v2 muxer based on TagLib MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Original commit message from CVS: 2006-03-09 Christophe Fergeau reviewed by: Tim-Philipp Müller * configure.ac: * ext/Makefile.am: * ext/taglib/Makefile.am: * ext/taglib/gsttaglib.cc: * ext/taglib/gsttaglib.h: new id3v2 muxer based on TagLib --- gst-libs/gst/tag/gsttagmux.c | 446 +++++++++++++++++++++++++++++++++++ gst-libs/gst/tag/gsttagmux.h | 62 +++++ 2 files changed, 508 insertions(+) create mode 100644 gst-libs/gst/tag/gsttagmux.c create mode 100644 gst-libs/gst/tag/gsttagmux.h diff --git a/gst-libs/gst/tag/gsttagmux.c b/gst-libs/gst/tag/gsttagmux.c new file mode 100644 index 0000000000..2a86c6a759 --- /dev/null +++ b/gst-libs/gst/tag/gsttagmux.c @@ -0,0 +1,446 @@ +/* + * (c) 2006 Christophe Fergeau + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include "gsttaglib.h" + +using namespace TagLib; + +GST_DEBUG_CATEGORY_STATIC (gst_tag_lib_mux_debug); +#define GST_CAT_DEFAULT gst_tag_lib_mux_debug + +static void +gst_tag_lib_mux_iface_init (GType taglib_type) +{ + static const GInterfaceInfo tag_setter_info = { + NULL, + NULL, + NULL + }; + + g_type_add_interface_static (taglib_type, GST_TYPE_TAG_SETTER, + &tag_setter_info); +} + +GST_BOILERPLATE_FULL (GstTagLibMux, gst_tag_lib_mux, + GstElement, GST_TYPE_ELEMENT, gst_tag_lib_mux_iface_init); + + +static GstStateChangeReturn +gst_tag_lib_mux_change_state (GstElement * element, GstStateChange transition); +static GstFlowReturn gst_tag_lib_mux_chain (GstPad * pad, GstBuffer * buffer); +static gboolean gst_tag_lib_mux_sink_event (GstPad * pad, GstEvent * event); + + +static void +gst_tag_lib_mux_finalize (GObject * obj) +{ + GstTagLibMux *taglib = GST_TAGLIB_MUX (obj); + + if (taglib->tags) { + gst_tag_list_free (taglib->tags); + taglib->tags = NULL; + } + G_OBJECT_CLASS (parent_class)->finalize (obj); +} + + +static GstStaticPadTemplate gst_tag_lib_mux_sink_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/mpeg")); + + +static GstStaticPadTemplate gst_tag_lib_mux_src_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-id3")); + + +static void +gst_tag_lib_mux_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + static GstElementDetails gst_tag_lib_mux_details = { + "TagLib ID3 Muxer", + "Formatter/Metadata", + "Adds an ID3v2 header to the beginning of MP3 files", + "Christophe Fergeau " + }; + + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&gst_tag_lib_mux_src_template)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&gst_tag_lib_mux_sink_template)); + gst_element_class_set_details (element_class, &gst_tag_lib_mux_details); +} + +static void +gst_tag_lib_mux_class_init (GstTagLibMuxClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + + gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_tag_lib_mux_finalize); + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_tag_lib_mux_change_state); +} + +static void +gst_tag_lib_mux_init (GstTagLibMux * taglib, + GstTagLibMuxClass * taglibmux_class) +{ + GstElementClass *klass = GST_ELEMENT_CLASS (taglibmux_class); + + /* pad through which data comes in to the element */ + taglib->sinkpad = + gst_pad_new_from_template (gst_element_class_get_pad_template (klass, + "sink"), "sink"); + gst_pad_set_setcaps_function (taglib->sinkpad, + GST_DEBUG_FUNCPTR (gst_pad_proxy_setcaps)); + gst_pad_set_chain_function (taglib->sinkpad, + GST_DEBUG_FUNCPTR (gst_tag_lib_mux_chain)); + gst_pad_set_event_function (taglib->sinkpad, + GST_DEBUG_FUNCPTR (gst_tag_lib_mux_sink_event)); + gst_element_add_pad (GST_ELEMENT (taglib), taglib->sinkpad); + + /* pad through which data goes out of the element */ + taglib->srcpad = + gst_pad_new_from_template (gst_element_class_get_pad_template (klass, + "src"), "src"); + gst_element_add_pad (GST_ELEMENT (taglib), taglib->srcpad); + + taglib->render_tag = TRUE; +} + +static void +add_one_tag (const GstTagList * list, const gchar * tag, gpointer user_data) +{ + ID3v2::Tag * id3v2tag = (ID3v2::Tag *) user_data; + gboolean result; + + /* FIXME: if there are several values set for the same tag, this won't + * work, only the first value will be taken into account + */ + if (strcmp (tag, GST_TAG_TITLE) == 0) { + char *title; + + result = gst_tag_list_get_string_index (list, tag, 0, &title); + if (result != FALSE) { + GST_DEBUG ("Setting title to %s", title); + id3v2tag->setTitle (String::String (title, String::UTF8)); + } + g_free (title); + } else if (strcmp (tag, GST_TAG_ALBUM) == 0) { + char *album; + + result = gst_tag_list_get_string_index (list, tag, 0, &album); + if (result != FALSE) { + GST_DEBUG ("Setting album to %s", album); + id3v2tag->setAlbum (String::String (album, String::UTF8)); + } + g_free (album); + } else if (strcmp (tag, GST_TAG_ARTIST) == 0) { + char *artist; + + result = gst_tag_list_get_string_index (list, tag, 0, &artist); + if (result != FALSE) { + GST_DEBUG ("Setting artist to %s", artist); + id3v2tag->setArtist (String::String (artist, String::UTF8)); + } + g_free (artist); + } else if (strcmp (tag, GST_TAG_GENRE) == 0) { + char *genre; + + result = gst_tag_list_get_string_index (list, tag, 0, &genre); + if (result != FALSE) { + GST_DEBUG ("Setting genre to %s", genre); + id3v2tag->setGenre (String::String (genre, String::UTF8)); + } + g_free (genre); + } else if (strcmp (tag, GST_TAG_COMMENT) == 0) { + char *comment; + + result = gst_tag_list_get_string_index (list, tag, 0, &comment); + if (result != FALSE) { + GST_DEBUG ("Setting comment to %s", comment); + id3v2tag->setComment (String::String (comment, String::UTF8)); + } + g_free (comment); + } else if (strcmp (tag, GST_TAG_DATE) == 0) { + GDate *date; + + result = gst_tag_list_get_date_index (list, tag, 0, &date); + if (result != FALSE) { + GDateYear year; + + year = g_date_get_year (date); + GST_DEBUG ("Setting track year to %d", year); + id3v2tag->setYear (year); + g_date_free (date); + } + } else if (strcmp (tag, GST_TAG_TRACK_NUMBER) == 0) { + guint track_number; + + result = gst_tag_list_get_uint_index (list, tag, 0, &track_number); + if (result != FALSE) { + guint total_tracks; + + result = gst_tag_list_get_uint_index (list, GST_TAG_TRACK_COUNT, + 0, &total_tracks); + if (result) { + gchar *tag_str; + + ID3v2::TextIdentificationFrame * frame; + + frame = new ID3v2::TextIdentificationFrame ("TRCK", String::UTF8); + tag_str = g_strdup_printf ("%d/%d", track_number, total_tracks); + GST_DEBUG ("Setting track number to %s", tag_str); + id3v2tag->addFrame (frame); + frame->setText (tag_str); + g_free (tag_str); + } else { + GST_DEBUG ("Setting track number to %d", track_number); + id3v2tag->setTrack (track_number); + } + } + } else if (strcmp (tag, GST_TAG_ALBUM_VOLUME_NUMBER) == 0) { + guint volume_number; + + result = gst_tag_list_get_uint_index (list, tag, 0, &volume_number); + + if (result != FALSE) { + guint volume_count; + gchar *tag_str; + + ID3v2::TextIdentificationFrame * frame; + + frame = new ID3v2::TextIdentificationFrame ("TRCK", String::UTF8); + result = gst_tag_list_get_uint_index (list, GST_TAG_ALBUM_VOLUME_COUNT, + 0, &volume_count); + if (result) { + tag_str = g_strdup_printf ("%d/%d", volume_number, volume_count); + } else { + tag_str = g_strdup_printf ("%d", volume_number); + } + + id3v2tag->addFrame (frame); + frame->setText (tag_str); + g_free (tag_str); + GST_DEBUG ("Setting track number to %s", tag_str); + } + } else { + GST_WARNING ("Unsupported tag: %s", tag); + } +} + + +static GstBuffer * +gst_tag_lib_mux_render_tag (GstTagLibMux * taglib) +{ + ID3v2::Tag id3v2tag; + ByteVector rendered_tag; + GstBuffer *buffer; + GstTagSetter *tagsetter = GST_TAG_SETTER (taglib); + GstTagList *taglist; + GstEvent *event; + + if (taglib->tags != NULL) { + taglist = gst_tag_list_copy (taglib->tags); + } else { + taglist = gst_tag_list_new (); + } + + if (gst_tag_setter_get_tag_list (tagsetter)) { + gst_tag_list_insert (taglist, + gst_tag_setter_get_tag_list (tagsetter), + gst_tag_setter_get_tag_merge_mode (tagsetter)); + } + + + /* Render the tag */ + gst_tag_list_foreach (taglist, add_one_tag, &id3v2tag); + rendered_tag = id3v2tag.render (); + taglib->tag_size = rendered_tag.size (); + buffer = gst_buffer_new_and_alloc (rendered_tag.size ()); + memcpy (GST_BUFFER_DATA (buffer), rendered_tag.data (), rendered_tag.size ()); + gst_buffer_set_caps (buffer, GST_PAD_CAPS (taglib->srcpad)); + /* gst_util_dump_mem (GST_BUFFER_DATA (buffer), rendered_tag.size()); */ + + /* Send an event about the new tags to downstream elements */ + /* gst_event_new_tag takes ownership of the list, so no need to unref it */ + event = gst_event_new_tag (taglist); + gst_pad_push_event (taglib->srcpad, event); + + return buffer; +} + + +static GstFlowReturn +gst_tag_lib_mux_chain (GstPad * pad, GstBuffer * buffer) +{ + GstTagLibMux *taglib = GST_TAGLIB_MUX (GST_OBJECT_PARENT (pad)); + + if (taglib->render_tag) { + GstFlowReturn ret; + + GST_INFO ("Adding tags to stream"); + ret = gst_pad_push (taglib->srcpad, gst_tag_lib_mux_render_tag (taglib)); + if (ret != GST_FLOW_OK) { + gst_buffer_unref (buffer); + return ret; + } + taglib->render_tag = FALSE; + } + + gst_buffer_set_caps (buffer, GST_PAD_CAPS (taglib->srcpad)); + return gst_pad_push (taglib->srcpad, buffer); +} + +static gboolean +gst_tag_lib_mux_sink_event (GstPad * pad, GstEvent * event) +{ + GstTagLibMux *taglib; + gboolean result; + + taglib = GST_TAGLIB_MUX (gst_pad_get_parent (pad)); + result = FALSE; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_TAG: + { + GstTagList *tags; + + GST_INFO ("Got tag event"); + + gst_event_parse_tag (event, &tags); + if (taglib->tags != NULL) { + /* FIXME: which policy is the best here? PREPEND or something else? */ + gst_tag_list_insert (taglib->tags, tags, GST_TAG_MERGE_PREPEND); + } else { + taglib->tags = gst_tag_list_copy (tags); + } + /* We'll push a new tag event in render_tag */ + gst_event_unref (event); + result = TRUE; + break; + } + case GST_EVENT_NEWSEGMENT: + if (taglib->tag_size == 0) { + result = gst_pad_push_event (taglib->srcpad, event); + } else { + gboolean update; + gdouble rate; + GstFormat format; + gint64 value, end_value, base; + + gst_event_parse_new_segment (event, &update, &rate, &format, + &value, &end_value, &base); + gst_event_unref (event); + if (format == GST_FORMAT_BYTES && gst_pad_is_linked (taglib->srcpad)) { + GstEvent *new_event; + + GST_INFO ("Adjusting NEW_SEGMENT event by %d", taglib->tag_size); + value += taglib->tag_size; + if (end_value != -1) { + end_value += taglib->tag_size; + } + + new_event = gst_event_new_new_segment (update, rate, format, + value, end_value, base); + result = gst_pad_push_event (taglib->srcpad, new_event); + } else { + result = FALSE; + } + } + break; + + default: + result = gst_pad_event_default (pad, event); + break; + } + gst_object_unref (GST_OBJECT (taglib)); + + return result; +} + + +static GstStateChangeReturn +gst_tag_lib_mux_change_state (GstElement * element, GstStateChange transition) +{ + GstTagLibMux *taglib; + GstStateChangeReturn result; + + taglib = GST_TAGLIB_MUX (element); + + result = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (result != GST_STATE_CHANGE_SUCCESS) { + return result; + } + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + if (taglib->tags) { + gst_tag_list_free (taglib->tags); + taglib->tags = NULL; + } + taglib->tag_size = 0; + taglib->render_tag = TRUE; + break; + default: + break; + } + + return result; +} + + +static gboolean +plugin_init (GstPlugin * plugin) +{ + if (!gst_element_register (plugin, "tagid3v2mux", GST_RANK_NONE, + GST_TYPE_TAGLIB_MUX)) + return FALSE; + + GST_DEBUG_CATEGORY_INIT (gst_tag_lib_mux_debug, "taglibmux", 0, + "ID3 Tag Muxer"); + + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "taglibmux", + "Tag-writing plug-in based on taglib", + plugin_init, VERSION, "LGPL", GST_PACKAGE, GST_ORIGIN) diff --git a/gst-libs/gst/tag/gsttagmux.h b/gst-libs/gst/tag/gsttagmux.h new file mode 100644 index 0000000000..1751ee7e96 --- /dev/null +++ b/gst-libs/gst/tag/gsttagmux.h @@ -0,0 +1,62 @@ +/* + * (c) 2006 Christophe Fergeau + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef GST_TAG_LIB_H +#define GST_TAG_LIB_H + +#include + +G_BEGIN_DECLS + +typedef struct _GstTagLibMuxPriv GstTagLibMuxPriv; + +/* Definition of structure storing data for this element. */ +typedef struct _GstTagLibMux { + GstElement element; + + GstPad *sinkpad, *srcpad; + GstTagList *tags; + gsize tag_size; + gboolean render_tag; + +} GstTagLibMux; + +/* Standard definition defining a class for this element. */ +typedef struct _GstTagLibMuxClass { + GstElementClass parent_class; +} GstTagLibMuxClass; + +/* Standard macros for defining types for this element. */ +#define GST_TYPE_TAGLIB_MUX \ + (gst_tag_lib_mux_get_type()) +#define GST_TAGLIB_MUX(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_TAGLIB_MUX,GstTagLibMux)) +#define GST_TAGLIB_MUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_TAGLIB_MUX,GstTagLibMuxClass)) +#define GST_IS_TAGLIB_MUX(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_TAGLIB_MUX)) +#define GST_IS_TAGLIB_MUX_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_TAGLIB_MUX)) + +/* Standard function returning type information. */ +GType gst_my_filter_get_type (void); + +G_END_DECLS + +#endif From 3352dc437d7f1533778d7a1a2f67981022440519 Mon Sep 17 00:00:00 2001 From: Alex Lancaster Date: Sat, 11 Mar 2006 10:58:08 +0000 Subject: [PATCH 022/140] ext/taglib/gsttaglib.cc: and add support for TCOP (copyright) Original commit message from CVS: 2006-03-11 Christophe Fergeau Patch by: Alex Lancaster * ext/taglib/gsttaglib.cc: fix writing of TPOS tags (album number), and add support for TCOP (copyright) --- gst-libs/gst/tag/gsttagmux.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/gst-libs/gst/tag/gsttagmux.c b/gst-libs/gst/tag/gsttagmux.c index 2a86c6a759..781c64da03 100644 --- a/gst-libs/gst/tag/gsttagmux.c +++ b/gst-libs/gst/tag/gsttagmux.c @@ -246,7 +246,7 @@ add_one_tag (const GstTagList * list, const gchar * tag, gpointer user_data) ID3v2::TextIdentificationFrame * frame; - frame = new ID3v2::TextIdentificationFrame ("TRCK", String::UTF8); + frame = new ID3v2::TextIdentificationFrame ("TPOS", String::UTF8); result = gst_tag_list_get_uint_index (list, GST_TAG_ALBUM_VOLUME_COUNT, 0, &volume_count); if (result) { @@ -258,7 +258,22 @@ add_one_tag (const GstTagList * list, const gchar * tag, gpointer user_data) id3v2tag->addFrame (frame); frame->setText (tag_str); g_free (tag_str); - GST_DEBUG ("Setting track number to %s", tag_str); + GST_DEBUG ("Setting album number to %s", tag_str); + } + } else if (strcmp (tag, GST_TAG_COPYRIGHT) == 0) { + gchar *copyright; + + result = gst_tag_list_get_string_index (list, tag, 0, ©right); + + if (result != FALSE) { + ID3v2::TextIdentificationFrame * frame; + + frame = new ID3v2::TextIdentificationFrame ("TCOP", String::UTF8); + + id3v2tag->addFrame (frame); + frame->setText (copyright); + g_free (copyright); + GST_DEBUG ("Setting copyright to %s", copyright); } } else { GST_WARNING ("Unsupported tag: %s", tag); From eca34e3cf90b4d2bae5b05194f1fa55eb66b49c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Sun, 12 Mar 2006 15:02:02 +0000 Subject: [PATCH 023/140] ext/taglib/: Add support for writing MusicBrainz IDs. Original commit message from CVS: * ext/taglib/Makefile.am: * ext/taglib/gsttaglib.cc: * ext/taglib/gsttaglib.h: Add support for writing MusicBrainz IDs. --- gst-libs/gst/tag/gsttagmux.c | 77 +++++++++++++++++++++++++++++++++++- gst-libs/gst/tag/gsttagmux.h | 4 +- 2 files changed, 78 insertions(+), 3 deletions(-) diff --git a/gst-libs/gst/tag/gsttagmux.c b/gst-libs/gst/tag/gsttagmux.c index 781c64da03..79cea0062a 100644 --- a/gst-libs/gst/tag/gsttagmux.c +++ b/gst-libs/gst/tag/gsttagmux.c @@ -1,4 +1,4 @@ -/* +/* GStreamer taglib-based ID3 muxer * (c) 2006 Christophe Fergeau * * This library is free software; you can redistribute it and/or @@ -24,8 +24,10 @@ #include #include +#include #include #include +#include #include "gsttaglib.h" using namespace TagLib; @@ -144,6 +146,32 @@ gst_tag_lib_mux_init (GstTagLibMux * taglib, taglib->render_tag = TRUE; } +static void +add_one_txxx_musicbrainz_tag (ID3v2::Tag * id3v2tag, const gchar * spec_id, + const gchar * realworld_id, const gchar * id_str) +{ + ID3v2::UserTextIdentificationFrame * frame; + + if (id_str == NULL) + return; + + GST_DEBUG ("Setting %s to %s", GST_STR_NULL (spec_id), id_str); + + if (spec_id) { + frame = new ID3v2::UserTextIdentificationFrame (String::Latin1); + id3v2tag->addFrame (frame); + frame->setDescription (spec_id); + frame->setText (id_str); + } + + if (realworld_id) { + frame = new ID3v2::UserTextIdentificationFrame (String::Latin1); + id3v2tag->addFrame (frame); + frame->setDescription (realworld_id); + frame->setText (id_str); + } +} + static void add_one_tag (const GstTagList * list, const gchar * tag, gpointer user_data) { @@ -275,6 +303,51 @@ add_one_tag (const GstTagList * list, const gchar * tag, gpointer user_data) g_free (copyright); GST_DEBUG ("Setting copyright to %s", copyright); } + } else if (strcmp (tag, GST_TAG_MUSICBRAINZ_ARTISTID) == 0) { + gchar *id_str; + + if (gst_tag_list_get_string_index (list, tag, 0, &id_str) && id_str) { + add_one_txxx_musicbrainz_tag (id3v2tag, "MusicBrainz Artist Id", + "musicbrainz_artistid", id_str); + g_free (id_str); + } + } else if (strcmp (tag, GST_TAG_MUSICBRAINZ_ALBUMID) == 0) { + gchar *id_str; + + if (gst_tag_list_get_string_index (list, tag, 0, &id_str) && id_str) { + add_one_txxx_musicbrainz_tag (id3v2tag, "MusicBrainz Album Id", + "musicbrainz_albumid", id_str); + g_free (id_str); + } + } else if (strcmp (tag, GST_TAG_MUSICBRAINZ_ALBUMARTISTID) == 0) { + gchar *id_str; + + if (gst_tag_list_get_string_index (list, tag, 0, &id_str) && id_str) { + add_one_txxx_musicbrainz_tag (id3v2tag, "MusicBrainz Album Artist Id", + "musicbrainz_albumartistid", id_str); + g_free (id_str); + } + } else if (strcmp (tag, GST_TAG_MUSICBRAINZ_TRMID) == 0) { + gchar *id_str; + + if (gst_tag_list_get_string_index (list, tag, 0, &id_str) && id_str) { + add_one_txxx_musicbrainz_tag (id3v2tag, "MusicBrainz TRM Id", + "musicbrainz_trmid", id_str); + g_free (id_str); + } + } else if (strcmp (tag, GST_TAG_MUSICBRAINZ_TRACKID) == 0) { + gchar *id_str; + + if (gst_tag_list_get_string_index (list, tag, 0, &id_str) && id_str) { + ID3v2::UniqueFileIdentifierFrame * frame; + + GST_DEBUG ("Setting Musicbrainz Track Id to %s", id_str); + + frame = new ID3v2::UniqueFileIdentifierFrame ("http://musicbrainz.org", + id_str); + id3v2tag->addFrame (frame); + g_free (id_str); + } } else { GST_WARNING ("Unsupported tag: %s", tag); } @@ -451,6 +524,8 @@ plugin_init (GstPlugin * plugin) GST_DEBUG_CATEGORY_INIT (gst_tag_lib_mux_debug, "taglibmux", 0, "ID3 Tag Muxer"); + gst_tag_register_musicbrainz_tags (); + return TRUE; } diff --git a/gst-libs/gst/tag/gsttagmux.h b/gst-libs/gst/tag/gsttagmux.h index 1751ee7e96..11344d619c 100644 --- a/gst-libs/gst/tag/gsttagmux.h +++ b/gst-libs/gst/tag/gsttagmux.h @@ -1,4 +1,4 @@ -/* +/* GStreamer taglib-based ID3 muxer * (c) 2006 Christophe Fergeau * * This library is free software; you can redistribute it and/or @@ -51,7 +51,7 @@ typedef struct _GstTagLibMuxClass { (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_TAGLIB_MUX,GstTagLibMuxClass)) #define GST_IS_TAGLIB_MUX(obj) \ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_TAGLIB_MUX)) -#define GST_IS_TAGLIB_MUX_CLASS(obj) \ +#define GST_IS_TAGLIB_MUX_CLASS(klass) \ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_TAGLIB_MUX)) /* Standard function returning type information. */ From afbd2e9c5c52e2ec6fe8bc19f5e40b8b78ea0fad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Mon, 13 Mar 2006 17:22:19 +0000 Subject: [PATCH 024/140] ext/taglib/gsttaglib.cc: Add gtk-doc blurb (unused for the time being); match registered plugin name to the filename ... Original commit message from CVS: * ext/taglib/gsttaglib.cc: Add gtk-doc blurb (unused for the time being); match registered plugin name to the filename of the plugin (taglibmux => taglib) --- gst-libs/gst/tag/gsttagmux.c | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/gst-libs/gst/tag/gsttagmux.c b/gst-libs/gst/tag/gsttagmux.c index 79cea0062a..99af3eeb66 100644 --- a/gst-libs/gst/tag/gsttagmux.c +++ b/gst-libs/gst/tag/gsttagmux.c @@ -17,6 +17,32 @@ * Boston, MA 02111-1307, USA. */ +/** + * SECTION:element-tagid3v2mux + * @see_also: #GstID3Demux, #GstTagSetter + * + * + * + * This element adds ID3v2 tags to the beginning of a stream using the taglib + * library. More precisely, the tags written are ID3 version 2.4.0 tags (which + * means in practice that some hardware players or outdated programs might not + * be able to read them properly). + * + * + * Applications can set the tags to write using the #GstTagSetter interface. + * Tags sent by upstream elements will be picked up automatically (and merged + * according to the merge mode set via the tag setter interface). + * + * + * Here is a simple pipeline that transcodes a file from Ogg/Vorbis to mp3 + * format with an ID3v2 that contains the same as the the Ogg/Vorbis file: + * + * gst-launch -v filesrc location=foo.ogg ! decodebin ! audioconvert ! lame ! tagid3v2mux ! filesink location=foo.mp3 + * + * + * + */ + #ifdef HAVE_CONFIG_H #include @@ -531,6 +557,6 @@ plugin_init (GstPlugin * plugin) GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, - "taglibmux", + "taglib", "Tag-writing plug-in based on taglib", plugin_init, VERSION, "LGPL", GST_PACKAGE, GST_ORIGIN) From e17a8554eb0f6fe5e775058d04656bd29b387319 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Mon, 20 Mar 2006 08:59:29 +0000 Subject: [PATCH 025/140] ext/taglib/gsttaglib.h: Fix left-over gst_my_filter_get_type. Original commit message from CVS: * ext/taglib/gsttaglib.h: Fix left-over gst_my_filter_get_type. --- gst-libs/gst/tag/gsttagmux.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gst-libs/gst/tag/gsttagmux.h b/gst-libs/gst/tag/gsttagmux.h index 11344d619c..6240e1400d 100644 --- a/gst-libs/gst/tag/gsttagmux.h +++ b/gst-libs/gst/tag/gsttagmux.h @@ -55,7 +55,7 @@ typedef struct _GstTagLibMuxClass { (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_TAGLIB_MUX)) /* Standard function returning type information. */ -GType gst_my_filter_get_type (void); +GType gst_tag_lib_mux_get_type (void); G_END_DECLS From 800c145a3a37e0e8d4db6b3b7a8534931d06f163 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Sat, 25 Mar 2006 21:57:24 +0000 Subject: [PATCH 026/140] ext/taglib/gsttaglib.cc: We do not want to proxy the caps on the sink pad; our source pad should have application/x-i... Original commit message from CVS: * ext/taglib/gsttaglib.cc: We do not want to proxy the caps on the sink pad; our source pad should have application/x-id3 caps; also, don't use already-freed strings in debug messages; finally, adjust buffer offsets on buffers sent out. --- gst-libs/gst/tag/gsttagmux.c | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/gst-libs/gst/tag/gsttagmux.c b/gst-libs/gst/tag/gsttagmux.c index 99af3eeb66..23ce3282fe 100644 --- a/gst-libs/gst/tag/gsttagmux.c +++ b/gst-libs/gst/tag/gsttagmux.c @@ -149,14 +149,11 @@ static void gst_tag_lib_mux_init (GstTagLibMux * taglib, GstTagLibMuxClass * taglibmux_class) { - GstElementClass *klass = GST_ELEMENT_CLASS (taglibmux_class); + GstCaps *srccaps; /* pad through which data comes in to the element */ taglib->sinkpad = - gst_pad_new_from_template (gst_element_class_get_pad_template (klass, - "sink"), "sink"); - gst_pad_set_setcaps_function (taglib->sinkpad, - GST_DEBUG_FUNCPTR (gst_pad_proxy_setcaps)); + gst_pad_new_from_static_template (&gst_tag_lib_mux_sink_template, "sink"); gst_pad_set_chain_function (taglib->sinkpad, GST_DEBUG_FUNCPTR (gst_tag_lib_mux_chain)); gst_pad_set_event_function (taglib->sinkpad, @@ -165,8 +162,10 @@ gst_tag_lib_mux_init (GstTagLibMux * taglib, /* pad through which data goes out of the element */ taglib->srcpad = - gst_pad_new_from_template (gst_element_class_get_pad_template (klass, - "src"), "src"); + gst_pad_new_from_static_template (&gst_tag_lib_mux_src_template, "src"); + srccaps = gst_static_pad_template_get_caps (&gst_tag_lib_mux_src_template); + gst_pad_use_fixed_caps (taglib->srcpad); + gst_pad_set_caps (taglib->srcpad, srccaps); gst_element_add_pad (GST_ELEMENT (taglib), taglib->srcpad); taglib->render_tag = TRUE; @@ -309,10 +308,11 @@ add_one_tag (const GstTagList * list, const gchar * tag, gpointer user_data) tag_str = g_strdup_printf ("%d", volume_number); } + GST_DEBUG ("Setting album number to %s", tag_str); + id3v2tag->addFrame (frame); frame->setText (tag_str); g_free (tag_str); - GST_DEBUG ("Setting album number to %s", tag_str); } } else if (strcmp (tag, GST_TAG_COPYRIGHT) == 0) { gchar *copyright; @@ -322,12 +322,13 @@ add_one_tag (const GstTagList * list, const gchar * tag, gpointer user_data) if (result != FALSE) { ID3v2::TextIdentificationFrame * frame; + GST_DEBUG ("Setting copyright to %s", copyright); + frame = new ID3v2::TextIdentificationFrame ("TCOP", String::UTF8); id3v2tag->addFrame (frame); frame->setText (copyright); g_free (copyright); - GST_DEBUG ("Setting copyright to %s", copyright); } } else if (strcmp (tag, GST_TAG_MUSICBRAINZ_ARTISTID) == 0) { gchar *id_str; @@ -417,6 +418,8 @@ gst_tag_lib_mux_render_tag (GstTagLibMux * taglib) event = gst_event_new_tag (taglist); gst_pad_push_event (taglib->srcpad, event); + GST_BUFFER_OFFSET (buffer) = 0; + return buffer; } @@ -429,7 +432,7 @@ gst_tag_lib_mux_chain (GstPad * pad, GstBuffer * buffer) if (taglib->render_tag) { GstFlowReturn ret; - GST_INFO ("Adding tags to stream"); + GST_INFO_OBJECT (taglib, "Adding tags to stream"); ret = gst_pad_push (taglib->srcpad, gst_tag_lib_mux_render_tag (taglib)); if (ret != GST_FLOW_OK) { gst_buffer_unref (buffer); @@ -438,6 +441,13 @@ gst_tag_lib_mux_chain (GstPad * pad, GstBuffer * buffer) taglib->render_tag = FALSE; } + if (GST_BUFFER_OFFSET (buffer) != GST_BUFFER_OFFSET_NONE) { + GST_LOG_OBJECT (taglib, "Adjusting buffer offset from %" G_GINT64_FORMAT + " to %" G_GINT64_FORMAT, GST_BUFFER_OFFSET (buffer), + GST_BUFFER_OFFSET (buffer) + taglib->tag_size); + GST_BUFFER_OFFSET (buffer) += taglib->tag_size; + } + gst_buffer_set_caps (buffer, GST_PAD_CAPS (taglib->srcpad)); return gst_pad_push (taglib->srcpad, buffer); } From 4a3ff452ddd75c34bd04272a1743b7451f562bb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Sun, 26 Mar 2006 19:56:37 +0000 Subject: [PATCH 027/140] ext/taglib/gsttaglib.*: Fix newsegment event handling a bit. We need to cache the first newsegment event, because we ... Original commit message from CVS: * ext/taglib/gsttaglib.cc: * ext/taglib/gsttaglib.h: Fix newsegment event handling a bit. We need to cache the first newsegment event, because we can't adjust offsets yet when we get it, as we don't know the size of the tag yet for sure at that point. Also do some minor cleaning up here and there and add some debug statements. --- gst-libs/gst/tag/gsttagmux.c | 225 +++++++++++++++++++++++------------ gst-libs/gst/tag/gsttagmux.h | 12 +- 2 files changed, 155 insertions(+), 82 deletions(-) diff --git a/gst-libs/gst/tag/gsttagmux.c b/gst-libs/gst/tag/gsttagmux.c index 23ce3282fe..1f466518d0 100644 --- a/gst-libs/gst/tag/gsttagmux.c +++ b/gst-libs/gst/tag/gsttagmux.c @@ -61,6 +61,26 @@ using namespace TagLib; GST_DEBUG_CATEGORY_STATIC (gst_tag_lib_mux_debug); #define GST_CAT_DEFAULT gst_tag_lib_mux_debug +static const GstElementDetails gst_tag_lib_mux_details = +GST_ELEMENT_DETAILS ("TagLib ID3 Muxer", + "Formatter/Metadata", + "Adds an ID3v2 header to the beginning of MP3 files", + "Christophe Fergeau "); + +static GstStaticPadTemplate gst_tag_lib_mux_sink_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/mpeg")); + + +static GstStaticPadTemplate gst_tag_lib_mux_src_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-id3")); + + static void gst_tag_lib_mux_iface_init (GType taglib_type) { @@ -83,47 +103,29 @@ gst_tag_lib_mux_change_state (GstElement * element, GstStateChange transition); static GstFlowReturn gst_tag_lib_mux_chain (GstPad * pad, GstBuffer * buffer); static gboolean gst_tag_lib_mux_sink_event (GstPad * pad, GstEvent * event); - static void gst_tag_lib_mux_finalize (GObject * obj) { GstTagLibMux *taglib = GST_TAGLIB_MUX (obj); - if (taglib->tags) { - gst_tag_list_free (taglib->tags); - taglib->tags = NULL; + if (taglib->newsegment_ev) { + gst_event_unref (taglib->newsegment_ev); + taglib->newsegment_ev = NULL; } + + if (taglib->event_tags) { + gst_tag_list_free (taglib->event_tags); + taglib->event_tags = NULL; + } + G_OBJECT_CLASS (parent_class)->finalize (obj); } - -static GstStaticPadTemplate gst_tag_lib_mux_sink_template = -GST_STATIC_PAD_TEMPLATE ("sink", - GST_PAD_SINK, - GST_PAD_ALWAYS, - GST_STATIC_CAPS ("audio/mpeg")); - - -static GstStaticPadTemplate gst_tag_lib_mux_src_template = -GST_STATIC_PAD_TEMPLATE ("src", - GST_PAD_SRC, - GST_PAD_ALWAYS, - GST_STATIC_CAPS ("application/x-id3")); - - static void gst_tag_lib_mux_base_init (gpointer g_class) { GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); - static GstElementDetails gst_tag_lib_mux_details = { - "TagLib ID3 Muxer", - "Formatter/Metadata", - "Adds an ID3v2 header to the beginning of MP3 files", - "Christophe Fergeau " - }; - - gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&gst_tag_lib_mux_src_template)); gst_element_class_add_pad_template (element_class, @@ -380,7 +382,6 @@ add_one_tag (const GstTagList * list, const gchar * tag, gpointer user_data) } } - static GstBuffer * gst_tag_lib_mux_render_tag (GstTagLibMux * taglib) { @@ -388,30 +389,46 @@ gst_tag_lib_mux_render_tag (GstTagLibMux * taglib) ByteVector rendered_tag; GstBuffer *buffer; GstTagSetter *tagsetter = GST_TAG_SETTER (taglib); + const GstTagList *tagsetter_tags; GstTagList *taglist; GstEvent *event; - if (taglib->tags != NULL) { - taglist = gst_tag_list_copy (taglib->tags); + if (taglib->event_tags != NULL) { + taglist = gst_tag_list_copy (taglib->event_tags); } else { taglist = gst_tag_list_new (); } - if (gst_tag_setter_get_tag_list (tagsetter)) { - gst_tag_list_insert (taglist, - gst_tag_setter_get_tag_list (tagsetter), - gst_tag_setter_get_tag_merge_mode (tagsetter)); + tagsetter_tags = gst_tag_setter_get_tag_list (tagsetter); + if (tagsetter_tags) { + GstTagMergeMode merge_mode; + + merge_mode = gst_tag_setter_get_tag_merge_mode (tagsetter); + GST_LOG_OBJECT (taglib, "merging tags, merge mode = %d", merge_mode); + GST_LOG_OBJECT (taglib, "event tags: %" GST_PTR_FORMAT, taglist); + GST_LOG_OBJECT (taglib, "set tags: %" GST_PTR_FORMAT, tagsetter_tags); + gst_tag_list_insert (taglist, tagsetter_tags, merge_mode); } + GST_LOG_OBJECT (taglib, "final tags: %" GST_PTR_FORMAT, taglist); /* Render the tag */ gst_tag_list_foreach (taglist, add_one_tag, &id3v2tag); + rendered_tag = id3v2tag.render (); taglib->tag_size = rendered_tag.size (); - buffer = gst_buffer_new_and_alloc (rendered_tag.size ()); - memcpy (GST_BUFFER_DATA (buffer), rendered_tag.data (), rendered_tag.size ()); + + GST_LOG_OBJECT (taglib, "tag size = %d bytes", taglib->tag_size); + + /* Create buffer with tag */ + buffer = gst_buffer_new_and_alloc (taglib->tag_size); + memcpy (GST_BUFFER_DATA (buffer), rendered_tag.data (), taglib->tag_size); gst_buffer_set_caps (buffer, GST_PAD_CAPS (taglib->srcpad)); - /* gst_util_dump_mem (GST_BUFFER_DATA (buffer), rendered_tag.size()); */ + + /* Send newsegment event from byte position 0, so the tag really gets + * written to the start of the file, independent of the upstream segment */ + gst_pad_push_event (taglib->srcpad, + gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES, 0, -1, 0)); /* Send an event about the new tags to downstream elements */ /* gst_event_new_tag takes ownership of the list, so no need to unref it */ @@ -423,6 +440,31 @@ gst_tag_lib_mux_render_tag (GstTagLibMux * taglib) return buffer; } +static GstEvent * +gst_tag_lib_mux_adjust_event_offsets (GstTagLibMux * taglib, + const GstEvent * newsegment_event) +{ + GstFormat format; + gint64 start, stop, cur; + + gst_event_parse_new_segment ((GstEvent *) newsegment_event, NULL, NULL, + &format, &start, &stop, &cur); + + g_assert (format == GST_FORMAT_BYTES); + + if (start != -1) + start += taglib->tag_size; + if (stop != -1) + stop += taglib->tag_size; + if (cur != -1) + cur += taglib->tag_size; + + GST_DEBUG_OBJECT (taglib, "adjusting newsegment event offsets to start=%" + G_GINT64_FORMAT ", stop=%" G_GINT64_FORMAT ", cur=%" G_GINT64_FORMAT + " (delta = +%u)", start, stop, cur, taglib->tag_size); + + return gst_event_new_new_segment (TRUE, 1.0, format, start, stop, cur); +} static GstFlowReturn gst_tag_lib_mux_chain (GstPad * pad, GstBuffer * buffer) @@ -435,12 +477,27 @@ gst_tag_lib_mux_chain (GstPad * pad, GstBuffer * buffer) GST_INFO_OBJECT (taglib, "Adding tags to stream"); ret = gst_pad_push (taglib->srcpad, gst_tag_lib_mux_render_tag (taglib)); if (ret != GST_FLOW_OK) { + GST_DEBUG_OBJECT (taglib, "flow: %s", gst_flow_get_name (ret)); gst_buffer_unref (buffer); return ret; } + + /* Now send the cached newsegment event that we got from upstream */ + if (taglib->newsegment_ev) { + GST_DEBUG_OBJECT (taglib, "sending cached newsegment event"); + gst_pad_push_event (taglib->srcpad, + gst_tag_lib_mux_adjust_event_offsets (taglib, taglib->newsegment_ev)); + gst_event_unref (taglib->newsegment_ev); + taglib->newsegment_ev = NULL; + } else { + /* upstream sent no newsegment event or only one in a non-BYTE format */ + } + taglib->render_tag = FALSE; } + buffer = gst_buffer_make_metadata_writable (buffer); + if (GST_BUFFER_OFFSET (buffer) != GST_BUFFER_OFFSET_NONE) { GST_LOG_OBJECT (taglib, "Adjusting buffer offset from %" G_GINT64_FORMAT " to %" G_GINT64_FORMAT, GST_BUFFER_OFFSET (buffer), @@ -462,59 +519,68 @@ gst_tag_lib_mux_sink_event (GstPad * pad, GstEvent * event) result = FALSE; switch (GST_EVENT_TYPE (event)) { - case GST_EVENT_TAG: - { + case GST_EVENT_TAG:{ GstTagList *tags; - GST_INFO ("Got tag event"); - gst_event_parse_tag (event, &tags); - if (taglib->tags != NULL) { - /* FIXME: which policy is the best here? PREPEND or something else? */ - gst_tag_list_insert (taglib->tags, tags, GST_TAG_MERGE_PREPEND); + + GST_INFO_OBJECT (taglib, "Got tag event: %" GST_PTR_FORMAT, tags); + + if (taglib->event_tags != NULL) { + gst_tag_list_insert (taglib->event_tags, tags, GST_TAG_MERGE_REPLACE); } else { - taglib->tags = gst_tag_list_copy (tags); + taglib->event_tags = gst_tag_list_copy (tags); } - /* We'll push a new tag event in render_tag */ + + GST_INFO_OBJECT (taglib, "Event tags are now: %" GST_PTR_FORMAT, + taglib->event_tags); + + /* just drop the event, we'll push a new tag event in render_tag */ gst_event_unref (event); result = TRUE; break; } - case GST_EVENT_NEWSEGMENT: - if (taglib->tag_size == 0) { - result = gst_pad_push_event (taglib->srcpad, event); - } else { - gboolean update; - gdouble rate; - GstFormat format; - gint64 value, end_value, base; + case GST_EVENT_NEWSEGMENT:{ + GstFormat fmt; - gst_event_parse_new_segment (event, &update, &rate, &format, - &value, &end_value, &base); + gst_event_parse_new_segment (event, NULL, NULL, &fmt, NULL, NULL, NULL); + + if (fmt != GST_FORMAT_BYTES) { + GST_WARNING_OBJECT (taglib, "dropping newsegment event in %s format", + gst_format_get_name (fmt)); gst_event_unref (event); - if (format == GST_FORMAT_BYTES && gst_pad_is_linked (taglib->srcpad)) { - GstEvent *new_event; - - GST_INFO ("Adjusting NEW_SEGMENT event by %d", taglib->tag_size); - value += taglib->tag_size; - if (end_value != -1) { - end_value += taglib->tag_size; - } - - new_event = gst_event_new_new_segment (update, rate, format, - value, end_value, base); - result = gst_pad_push_event (taglib->srcpad, new_event); - } else { - result = FALSE; - } + break; } - break; + if (taglib->render_tag) { + /* we have not rendered the tag yet, which means that we don't know + * how large it is going to be yet, so we can't adjust the offsets + * here at this point and need to cache the newsegment event for now + * (also, there could be tag events coming after this newsegment event + * and before the first buffer). */ + if (taglib->newsegment_ev) { + GST_WARNING_OBJECT (taglib, "discarding old cached newsegment event"); + gst_event_unref (taglib->newsegment_ev); + } + + GST_LOG_OBJECT (taglib, "caching newsegment event for later"); + taglib->newsegment_ev = event; + } else { + GST_DEBUG_OBJECT (taglib, "got newsegment event, adjusting offsets"); + gst_pad_push_event (taglib->srcpad, + gst_tag_lib_mux_adjust_event_offsets (taglib, event)); + gst_event_unref (event); + } + event = NULL; + result = TRUE; + break; + } default: result = gst_pad_event_default (pad, event); break; } - gst_object_unref (GST_OBJECT (taglib)); + + gst_object_unref (taglib); return result; } @@ -534,14 +600,19 @@ gst_tag_lib_mux_change_state (GstElement * element, GstStateChange transition) } switch (transition) { - case GST_STATE_CHANGE_PAUSED_TO_READY: - if (taglib->tags) { - gst_tag_list_free (taglib->tags); - taglib->tags = NULL; + case GST_STATE_CHANGE_PAUSED_TO_READY:{ + if (taglib->newsegment_ev) { + gst_event_unref (taglib->newsegment_ev); + taglib->newsegment_ev = NULL; + } + if (taglib->event_tags) { + gst_tag_list_free (taglib->event_tags); + taglib->event_tags = NULL; } taglib->tag_size = 0; taglib->render_tag = TRUE; break; + } default: break; } diff --git a/gst-libs/gst/tag/gsttagmux.h b/gst-libs/gst/tag/gsttagmux.h index 6240e1400d..b98a339cd5 100644 --- a/gst-libs/gst/tag/gsttagmux.h +++ b/gst-libs/gst/tag/gsttagmux.h @@ -28,13 +28,15 @@ typedef struct _GstTagLibMuxPriv GstTagLibMuxPriv; /* Definition of structure storing data for this element. */ typedef struct _GstTagLibMux { - GstElement element; + GstElement element; - GstPad *sinkpad, *srcpad; - GstTagList *tags; - gsize tag_size; - gboolean render_tag; + GstPad *srcpad; + GstPad *sinkpad; + GstTagList *event_tags; /* tags received from upstream elements */ + gsize tag_size; + gboolean render_tag; + GstEvent *newsegment_ev; /* cached newsegment event from upstream */ } GstTagLibMux; /* Standard definition defining a class for this element. */ From 3446b35424890082374b46b36955e16ad9d10c6b Mon Sep 17 00:00:00 2001 From: Thomas Vander Stichele Date: Sat, 1 Apr 2006 16:50:49 +0000 Subject: [PATCH 028/140] add taglib checks and docs Original commit message from CVS: add taglib checks and docs --- gst-libs/gst/tag/gsttagmux.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gst-libs/gst/tag/gsttagmux.c b/gst-libs/gst/tag/gsttagmux.c index 1f466518d0..affcb60039 100644 --- a/gst-libs/gst/tag/gsttagmux.c +++ b/gst-libs/gst/tag/gsttagmux.c @@ -62,7 +62,7 @@ GST_DEBUG_CATEGORY_STATIC (gst_tag_lib_mux_debug); #define GST_CAT_DEFAULT gst_tag_lib_mux_debug static const GstElementDetails gst_tag_lib_mux_details = -GST_ELEMENT_DETAILS ("TagLib ID3 Muxer", +GST_ELEMENT_DETAILS ("TagLib ID3v2 Muxer", "Formatter/Metadata", "Adds an ID3v2 header to the beginning of MP3 files", "Christophe Fergeau "); @@ -640,4 +640,4 @@ GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, "taglib", "Tag-writing plug-in based on taglib", - plugin_init, VERSION, "LGPL", GST_PACKAGE, GST_ORIGIN) + plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN); From 4ee80726bd15f29ddbebba509af3f284250daece Mon Sep 17 00:00:00 2001 From: Thomas Vander Stichele Date: Fri, 28 Apr 2006 15:33:09 +0000 Subject: [PATCH 029/140] pedantic cleanups Original commit message from CVS: pedantic cleanups --- gst-libs/gst/tag/gsttagmux.c | 184 ++++++++++++++++++----------------- gst-libs/gst/tag/gsttagmux.h | 20 ++-- 2 files changed, 104 insertions(+), 100 deletions(-) diff --git a/gst-libs/gst/tag/gsttagmux.c b/gst-libs/gst/tag/gsttagmux.c index affcb60039..abb3e1e09d 100644 --- a/gst-libs/gst/tag/gsttagmux.c +++ b/gst-libs/gst/tag/gsttagmux.c @@ -37,7 +37,12 @@ * Here is a simple pipeline that transcodes a file from Ogg/Vorbis to mp3 * format with an ID3v2 that contains the same as the the Ogg/Vorbis file: * - * gst-launch -v filesrc location=foo.ogg ! decodebin ! audioconvert ! lame ! tagid3v2mux ! filesink location=foo.mp3 + * gst-launch -v filesrc location=foo.ogg ! decodebin ! audioconvert ! lame ! id3v2mux ! filesink location=foo.mp3 + * + * Make sure the Ogg/Vorbis file actually has comments to preserve. + * You can verify the tags were written using: + * + * gst-launch -m filesrc location=foo.mp3 ! id3demux ! fakesink silent=TRUE 2¨ /dev/null | grep taglist * * * @@ -64,7 +69,7 @@ GST_DEBUG_CATEGORY_STATIC (gst_tag_lib_mux_debug); static const GstElementDetails gst_tag_lib_mux_details = GST_ELEMENT_DETAILS ("TagLib ID3v2 Muxer", "Formatter/Metadata", - "Adds an ID3v2 header to the beginning of MP3 files", + "Adds an ID3v2 header to the beginning of MP3 files using taglib", "Christophe Fergeau "); static GstStaticPadTemplate gst_tag_lib_mux_sink_template = @@ -106,16 +111,16 @@ static gboolean gst_tag_lib_mux_sink_event (GstPad * pad, GstEvent * event); static void gst_tag_lib_mux_finalize (GObject * obj) { - GstTagLibMux *taglib = GST_TAGLIB_MUX (obj); + GstTagLibMux *mux = GST_TAG_LIB_MUX (obj); - if (taglib->newsegment_ev) { - gst_event_unref (taglib->newsegment_ev); - taglib->newsegment_ev = NULL; + if (mux->newsegment_ev) { + gst_event_unref (mux->newsegment_ev); + mux->newsegment_ev = NULL; } - if (taglib->event_tags) { - gst_tag_list_free (taglib->event_tags); - taglib->event_tags = NULL; + if (mux->event_tags) { + gst_tag_list_free (mux->event_tags); + mux->event_tags = NULL; } G_OBJECT_CLASS (parent_class)->finalize (obj); @@ -148,29 +153,28 @@ gst_tag_lib_mux_class_init (GstTagLibMuxClass * klass) } static void -gst_tag_lib_mux_init (GstTagLibMux * taglib, - GstTagLibMuxClass * taglibmux_class) +gst_tag_lib_mux_init (GstTagLibMux * mux, GstTagLibMuxClass * muxmux_class) { GstCaps *srccaps; /* pad through which data comes in to the element */ - taglib->sinkpad = + mux->sinkpad = gst_pad_new_from_static_template (&gst_tag_lib_mux_sink_template, "sink"); - gst_pad_set_chain_function (taglib->sinkpad, + gst_pad_set_chain_function (mux->sinkpad, GST_DEBUG_FUNCPTR (gst_tag_lib_mux_chain)); - gst_pad_set_event_function (taglib->sinkpad, + gst_pad_set_event_function (mux->sinkpad, GST_DEBUG_FUNCPTR (gst_tag_lib_mux_sink_event)); - gst_element_add_pad (GST_ELEMENT (taglib), taglib->sinkpad); + gst_element_add_pad (GST_ELEMENT (mux), mux->sinkpad); /* pad through which data goes out of the element */ - taglib->srcpad = + mux->srcpad = gst_pad_new_from_static_template (&gst_tag_lib_mux_src_template, "src"); srccaps = gst_static_pad_template_get_caps (&gst_tag_lib_mux_src_template); - gst_pad_use_fixed_caps (taglib->srcpad); - gst_pad_set_caps (taglib->srcpad, srccaps); - gst_element_add_pad (GST_ELEMENT (taglib), taglib->srcpad); + gst_pad_use_fixed_caps (mux->srcpad); + gst_pad_set_caps (mux->srcpad, srccaps); + gst_element_add_pad (GST_ELEMENT (mux), mux->srcpad); - taglib->render_tag = TRUE; + mux->render_tag = TRUE; } static void @@ -383,18 +387,18 @@ add_one_tag (const GstTagList * list, const gchar * tag, gpointer user_data) } static GstBuffer * -gst_tag_lib_mux_render_tag (GstTagLibMux * taglib) +gst_tag_lib_mux_render_tag (GstTagLibMux * mux) { ID3v2::Tag id3v2tag; ByteVector rendered_tag; GstBuffer *buffer; - GstTagSetter *tagsetter = GST_TAG_SETTER (taglib); + GstTagSetter *tagsetter = GST_TAG_SETTER (mux); const GstTagList *tagsetter_tags; GstTagList *taglist; GstEvent *event; - if (taglib->event_tags != NULL) { - taglist = gst_tag_list_copy (taglib->event_tags); + if (mux->event_tags != NULL) { + taglist = gst_tag_list_copy (mux->event_tags); } else { taglist = gst_tag_list_new (); } @@ -404,36 +408,36 @@ gst_tag_lib_mux_render_tag (GstTagLibMux * taglib) GstTagMergeMode merge_mode; merge_mode = gst_tag_setter_get_tag_merge_mode (tagsetter); - GST_LOG_OBJECT (taglib, "merging tags, merge mode = %d", merge_mode); - GST_LOG_OBJECT (taglib, "event tags: %" GST_PTR_FORMAT, taglist); - GST_LOG_OBJECT (taglib, "set tags: %" GST_PTR_FORMAT, tagsetter_tags); + GST_LOG_OBJECT (mux, "merging tags, merge mode = %d", merge_mode); + GST_LOG_OBJECT (mux, "event tags: %" GST_PTR_FORMAT, taglist); + GST_LOG_OBJECT (mux, "set tags: %" GST_PTR_FORMAT, tagsetter_tags); gst_tag_list_insert (taglist, tagsetter_tags, merge_mode); } - GST_LOG_OBJECT (taglib, "final tags: %" GST_PTR_FORMAT, taglist); + GST_LOG_OBJECT (mux, "final tags: %" GST_PTR_FORMAT, taglist); /* Render the tag */ gst_tag_list_foreach (taglist, add_one_tag, &id3v2tag); rendered_tag = id3v2tag.render (); - taglib->tag_size = rendered_tag.size (); + mux->tag_size = rendered_tag.size (); - GST_LOG_OBJECT (taglib, "tag size = %d bytes", taglib->tag_size); + GST_LOG_OBJECT (mux, "tag size = %d bytes", mux->tag_size); /* Create buffer with tag */ - buffer = gst_buffer_new_and_alloc (taglib->tag_size); - memcpy (GST_BUFFER_DATA (buffer), rendered_tag.data (), taglib->tag_size); - gst_buffer_set_caps (buffer, GST_PAD_CAPS (taglib->srcpad)); + buffer = gst_buffer_new_and_alloc (mux->tag_size); + memcpy (GST_BUFFER_DATA (buffer), rendered_tag.data (), mux->tag_size); + gst_buffer_set_caps (buffer, GST_PAD_CAPS (mux->srcpad)); /* Send newsegment event from byte position 0, so the tag really gets * written to the start of the file, independent of the upstream segment */ - gst_pad_push_event (taglib->srcpad, + gst_pad_push_event (mux->srcpad, gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES, 0, -1, 0)); /* Send an event about the new tags to downstream elements */ /* gst_event_new_tag takes ownership of the list, so no need to unref it */ event = gst_event_new_tag (taglist); - gst_pad_push_event (taglib->srcpad, event); + gst_pad_push_event (mux->srcpad, event); GST_BUFFER_OFFSET (buffer) = 0; @@ -441,7 +445,7 @@ gst_tag_lib_mux_render_tag (GstTagLibMux * taglib) } static GstEvent * -gst_tag_lib_mux_adjust_event_offsets (GstTagLibMux * taglib, +gst_tag_lib_mux_adjust_event_offsets (GstTagLibMux * mux, const GstEvent * newsegment_event) { GstFormat format; @@ -453,15 +457,15 @@ gst_tag_lib_mux_adjust_event_offsets (GstTagLibMux * taglib, g_assert (format == GST_FORMAT_BYTES); if (start != -1) - start += taglib->tag_size; + start += mux->tag_size; if (stop != -1) - stop += taglib->tag_size; + stop += mux->tag_size; if (cur != -1) - cur += taglib->tag_size; + cur += mux->tag_size; - GST_DEBUG_OBJECT (taglib, "adjusting newsegment event offsets to start=%" + GST_DEBUG_OBJECT (mux, "adjusting newsegment event offsets to start=%" G_GINT64_FORMAT ", stop=%" G_GINT64_FORMAT ", cur=%" G_GINT64_FORMAT - " (delta = +%u)", start, stop, cur, taglib->tag_size); + " (delta = +%u)", start, stop, cur, mux->tag_size); return gst_event_new_new_segment (TRUE, 1.0, format, start, stop, cur); } @@ -469,53 +473,53 @@ gst_tag_lib_mux_adjust_event_offsets (GstTagLibMux * taglib, static GstFlowReturn gst_tag_lib_mux_chain (GstPad * pad, GstBuffer * buffer) { - GstTagLibMux *taglib = GST_TAGLIB_MUX (GST_OBJECT_PARENT (pad)); + GstTagLibMux *mux = GST_TAG_LIB_MUX (GST_OBJECT_PARENT (pad)); - if (taglib->render_tag) { + if (mux->render_tag) { GstFlowReturn ret; - GST_INFO_OBJECT (taglib, "Adding tags to stream"); - ret = gst_pad_push (taglib->srcpad, gst_tag_lib_mux_render_tag (taglib)); + GST_INFO_OBJECT (mux, "Adding tags to stream"); + ret = gst_pad_push (mux->srcpad, gst_tag_lib_mux_render_tag (mux)); if (ret != GST_FLOW_OK) { - GST_DEBUG_OBJECT (taglib, "flow: %s", gst_flow_get_name (ret)); + GST_DEBUG_OBJECT (mux, "flow: %s", gst_flow_get_name (ret)); gst_buffer_unref (buffer); return ret; } /* Now send the cached newsegment event that we got from upstream */ - if (taglib->newsegment_ev) { - GST_DEBUG_OBJECT (taglib, "sending cached newsegment event"); - gst_pad_push_event (taglib->srcpad, - gst_tag_lib_mux_adjust_event_offsets (taglib, taglib->newsegment_ev)); - gst_event_unref (taglib->newsegment_ev); - taglib->newsegment_ev = NULL; + if (mux->newsegment_ev) { + GST_DEBUG_OBJECT (mux, "sending cached newsegment event"); + gst_pad_push_event (mux->srcpad, + gst_tag_lib_mux_adjust_event_offsets (mux, mux->newsegment_ev)); + gst_event_unref (mux->newsegment_ev); + mux->newsegment_ev = NULL; } else { /* upstream sent no newsegment event or only one in a non-BYTE format */ } - taglib->render_tag = FALSE; + mux->render_tag = FALSE; } buffer = gst_buffer_make_metadata_writable (buffer); if (GST_BUFFER_OFFSET (buffer) != GST_BUFFER_OFFSET_NONE) { - GST_LOG_OBJECT (taglib, "Adjusting buffer offset from %" G_GINT64_FORMAT + GST_LOG_OBJECT (mux, "Adjusting buffer offset from %" G_GINT64_FORMAT " to %" G_GINT64_FORMAT, GST_BUFFER_OFFSET (buffer), - GST_BUFFER_OFFSET (buffer) + taglib->tag_size); - GST_BUFFER_OFFSET (buffer) += taglib->tag_size; + GST_BUFFER_OFFSET (buffer) + mux->tag_size); + GST_BUFFER_OFFSET (buffer) += mux->tag_size; } - gst_buffer_set_caps (buffer, GST_PAD_CAPS (taglib->srcpad)); - return gst_pad_push (taglib->srcpad, buffer); + gst_buffer_set_caps (buffer, GST_PAD_CAPS (mux->srcpad)); + return gst_pad_push (mux->srcpad, buffer); } static gboolean gst_tag_lib_mux_sink_event (GstPad * pad, GstEvent * event) { - GstTagLibMux *taglib; + GstTagLibMux *mux; gboolean result; - taglib = GST_TAGLIB_MUX (gst_pad_get_parent (pad)); + mux = GST_TAG_LIB_MUX (gst_pad_get_parent (pad)); result = FALSE; switch (GST_EVENT_TYPE (event)) { @@ -524,16 +528,16 @@ gst_tag_lib_mux_sink_event (GstPad * pad, GstEvent * event) gst_event_parse_tag (event, &tags); - GST_INFO_OBJECT (taglib, "Got tag event: %" GST_PTR_FORMAT, tags); + GST_INFO_OBJECT (mux, "Got tag event: %" GST_PTR_FORMAT, tags); - if (taglib->event_tags != NULL) { - gst_tag_list_insert (taglib->event_tags, tags, GST_TAG_MERGE_REPLACE); + if (mux->event_tags != NULL) { + gst_tag_list_insert (mux->event_tags, tags, GST_TAG_MERGE_REPLACE); } else { - taglib->event_tags = gst_tag_list_copy (tags); + mux->event_tags = gst_tag_list_copy (tags); } - GST_INFO_OBJECT (taglib, "Event tags are now: %" GST_PTR_FORMAT, - taglib->event_tags); + GST_INFO_OBJECT (mux, "Event tags are now: %" GST_PTR_FORMAT, + mux->event_tags); /* just drop the event, we'll push a new tag event in render_tag */ gst_event_unref (event); @@ -546,29 +550,29 @@ gst_tag_lib_mux_sink_event (GstPad * pad, GstEvent * event) gst_event_parse_new_segment (event, NULL, NULL, &fmt, NULL, NULL, NULL); if (fmt != GST_FORMAT_BYTES) { - GST_WARNING_OBJECT (taglib, "dropping newsegment event in %s format", + GST_WARNING_OBJECT (mux, "dropping newsegment event in %s format", gst_format_get_name (fmt)); gst_event_unref (event); break; } - if (taglib->render_tag) { + if (mux->render_tag) { /* we have not rendered the tag yet, which means that we don't know * how large it is going to be yet, so we can't adjust the offsets * here at this point and need to cache the newsegment event for now * (also, there could be tag events coming after this newsegment event * and before the first buffer). */ - if (taglib->newsegment_ev) { - GST_WARNING_OBJECT (taglib, "discarding old cached newsegment event"); - gst_event_unref (taglib->newsegment_ev); + if (mux->newsegment_ev) { + GST_WARNING_OBJECT (mux, "discarding old cached newsegment event"); + gst_event_unref (mux->newsegment_ev); } - GST_LOG_OBJECT (taglib, "caching newsegment event for later"); - taglib->newsegment_ev = event; + GST_LOG_OBJECT (mux, "caching newsegment event for later"); + mux->newsegment_ev = event; } else { - GST_DEBUG_OBJECT (taglib, "got newsegment event, adjusting offsets"); - gst_pad_push_event (taglib->srcpad, - gst_tag_lib_mux_adjust_event_offsets (taglib, event)); + GST_DEBUG_OBJECT (mux, "got newsegment event, adjusting offsets"); + gst_pad_push_event (mux->srcpad, + gst_tag_lib_mux_adjust_event_offsets (mux, event)); gst_event_unref (event); } event = NULL; @@ -580,7 +584,7 @@ gst_tag_lib_mux_sink_event (GstPad * pad, GstEvent * event) break; } - gst_object_unref (taglib); + gst_object_unref (mux); return result; } @@ -589,10 +593,10 @@ gst_tag_lib_mux_sink_event (GstPad * pad, GstEvent * event) static GstStateChangeReturn gst_tag_lib_mux_change_state (GstElement * element, GstStateChange transition) { - GstTagLibMux *taglib; + GstTagLibMux *mux; GstStateChangeReturn result; - taglib = GST_TAGLIB_MUX (element); + mux = GST_TAG_LIB_MUX (element); result = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); if (result != GST_STATE_CHANGE_SUCCESS) { @@ -601,16 +605,16 @@ gst_tag_lib_mux_change_state (GstElement * element, GstStateChange transition) switch (transition) { case GST_STATE_CHANGE_PAUSED_TO_READY:{ - if (taglib->newsegment_ev) { - gst_event_unref (taglib->newsegment_ev); - taglib->newsegment_ev = NULL; + if (mux->newsegment_ev) { + gst_event_unref (mux->newsegment_ev); + mux->newsegment_ev = NULL; } - if (taglib->event_tags) { - gst_tag_list_free (taglib->event_tags); - taglib->event_tags = NULL; + if (mux->event_tags) { + gst_tag_list_free (mux->event_tags); + mux->event_tags = NULL; } - taglib->tag_size = 0; - taglib->render_tag = TRUE; + mux->tag_size = 0; + mux->render_tag = TRUE; break; } default: @@ -624,12 +628,12 @@ gst_tag_lib_mux_change_state (GstElement * element, GstStateChange transition) static gboolean plugin_init (GstPlugin * plugin) { - if (!gst_element_register (plugin, "tagid3v2mux", GST_RANK_NONE, - GST_TYPE_TAGLIB_MUX)) + if (!gst_element_register (plugin, "id3v2mux", GST_RANK_NONE, + GST_TYPE_TAG_LIB_MUX)) return FALSE; GST_DEBUG_CATEGORY_INIT (gst_tag_lib_mux_debug, "taglibmux", 0, - "ID3 Tag Muxer"); + "taglib-based muxer"); gst_tag_register_musicbrainz_tags (); diff --git a/gst-libs/gst/tag/gsttagmux.h b/gst-libs/gst/tag/gsttagmux.h index b98a339cd5..eacfc840ee 100644 --- a/gst-libs/gst/tag/gsttagmux.h +++ b/gst-libs/gst/tag/gsttagmux.h @@ -1,4 +1,4 @@ -/* GStreamer taglib-based ID3 muxer +/* GStreamer taglib-based muxer * (c) 2006 Christophe Fergeau * * This library is free software; you can redistribute it and/or @@ -45,16 +45,16 @@ typedef struct _GstTagLibMuxClass { } GstTagLibMuxClass; /* Standard macros for defining types for this element. */ -#define GST_TYPE_TAGLIB_MUX \ +#define GST_TYPE_TAG_LIB_MUX \ (gst_tag_lib_mux_get_type()) -#define GST_TAGLIB_MUX(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_TAGLIB_MUX,GstTagLibMux)) -#define GST_TAGLIB_MUX_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_TAGLIB_MUX,GstTagLibMuxClass)) -#define GST_IS_TAGLIB_MUX(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_TAGLIB_MUX)) -#define GST_IS_TAGLIB_MUX_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_TAGLIB_MUX)) +#define GST_TAG_LIB_MUX(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_TAG_LIB_MUX,GstTagLibMux)) +#define GST_TAG_LIB_MUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_TAG_LIB_MUX,GstTagLibMuxClass)) +#define GST_IS_TAG_LIB_MUX(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_TAG_LIB_MUX)) +#define GST_IS_TAG_LIB_MUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_TAG_LIB_MUX)) /* Standard function returning type information. */ GType gst_tag_lib_mux_get_type (void); From 873f24298f4bbcc23f50c682769e2b41a5c88d1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Sat, 29 Apr 2006 18:18:24 +0000 Subject: [PATCH 030/140] ext/taglib/: Split the actual ID3v2 tag rendering code into its own subclass. Original commit message from CVS: * ext/taglib/Makefile.am: * ext/taglib/gstid3v2mux.cc: * ext/taglib/gstid3v2mux.h: * ext/taglib/gsttaglib.cc: * ext/taglib/gsttaglib.h: Split the actual ID3v2 tag rendering code into its own subclass. --- gst-libs/gst/tag/gsttagmux.c | 346 ++++------------------------------- gst-libs/gst/tag/gsttagmux.h | 17 +- 2 files changed, 50 insertions(+), 313 deletions(-) diff --git a/gst-libs/gst/tag/gsttagmux.c b/gst-libs/gst/tag/gsttagmux.c index abb3e1e09d..d47803bea8 100644 --- a/gst-libs/gst/tag/gsttagmux.c +++ b/gst-libs/gst/tag/gsttagmux.c @@ -1,6 +1,7 @@ -/* GStreamer taglib-based ID3 muxer - * (c) 2006 Christophe Fergeau - * +/* GStreamer taglib-based muxer base class + * Copyright (C) 2006 Christophe Fergeau + * Copyright (C) 2006 Tim-Philipp Müller + * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either @@ -17,73 +18,24 @@ * Boston, MA 02111-1307, USA. */ -/** - * SECTION:element-tagid3v2mux - * @see_also: #GstID3Demux, #GstTagSetter - * - * - * - * This element adds ID3v2 tags to the beginning of a stream using the taglib - * library. More precisely, the tags written are ID3 version 2.4.0 tags (which - * means in practice that some hardware players or outdated programs might not - * be able to read them properly). - * - * - * Applications can set the tags to write using the #GstTagSetter interface. - * Tags sent by upstream elements will be picked up automatically (and merged - * according to the merge mode set via the tag setter interface). - * - * - * Here is a simple pipeline that transcodes a file from Ogg/Vorbis to mp3 - * format with an ID3v2 that contains the same as the the Ogg/Vorbis file: - * - * gst-launch -v filesrc location=foo.ogg ! decodebin ! audioconvert ! lame ! id3v2mux ! filesink location=foo.mp3 - * - * Make sure the Ogg/Vorbis file actually has comments to preserve. - * You can verify the tags were written using: - * - * gst-launch -m filesrc location=foo.mp3 ! id3demux ! fakesink silent=TRUE 2¨ /dev/null | grep taglist - * - * - * - */ - - #ifdef HAVE_CONFIG_H #include #endif #include -#include -#include -#include #include #include #include "gsttaglib.h" - -using namespace TagLib; +#include "gstid3v2mux.h" GST_DEBUG_CATEGORY_STATIC (gst_tag_lib_mux_debug); #define GST_CAT_DEFAULT gst_tag_lib_mux_debug -static const GstElementDetails gst_tag_lib_mux_details = -GST_ELEMENT_DETAILS ("TagLib ID3v2 Muxer", - "Formatter/Metadata", - "Adds an ID3v2 header to the beginning of MP3 files using taglib", - "Christophe Fergeau "); - static GstStaticPadTemplate gst_tag_lib_mux_sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, - GST_STATIC_CAPS ("audio/mpeg")); - - -static GstStaticPadTemplate gst_tag_lib_mux_src_template = -GST_STATIC_PAD_TEMPLATE ("src", - GST_PAD_SRC, - GST_PAD_ALWAYS, - GST_STATIC_CAPS ("application/x-id3")); + GST_STATIC_CAPS ("ANY")); static void @@ -131,11 +83,11 @@ gst_tag_lib_mux_base_init (gpointer g_class) { GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); - gst_element_class_add_pad_template (element_class, - gst_static_pad_template_get (&gst_tag_lib_mux_src_template)); gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&gst_tag_lib_mux_sink_template)); - gst_element_class_set_details (element_class, &gst_tag_lib_mux_details); + + GST_DEBUG_CATEGORY_INIT (gst_tag_lib_mux_debug, "taglibmux", 0, + "taglib-based muxer"); } static void @@ -153,9 +105,10 @@ gst_tag_lib_mux_class_init (GstTagLibMuxClass * klass) } static void -gst_tag_lib_mux_init (GstTagLibMux * mux, GstTagLibMuxClass * muxmux_class) +gst_tag_lib_mux_init (GstTagLibMux * mux, GstTagLibMuxClass * mux_class) { - GstCaps *srccaps; + GstElementClass *element_klass = GST_ELEMENT_CLASS (mux_class); + GstPadTemplate *tmpl; /* pad through which data comes in to the element */ mux->sinkpad = @@ -167,230 +120,21 @@ gst_tag_lib_mux_init (GstTagLibMux * mux, GstTagLibMuxClass * muxmux_class) gst_element_add_pad (GST_ELEMENT (mux), mux->sinkpad); /* pad through which data goes out of the element */ - mux->srcpad = - gst_pad_new_from_static_template (&gst_tag_lib_mux_src_template, "src"); - srccaps = gst_static_pad_template_get_caps (&gst_tag_lib_mux_src_template); - gst_pad_use_fixed_caps (mux->srcpad); - gst_pad_set_caps (mux->srcpad, srccaps); - gst_element_add_pad (GST_ELEMENT (mux), mux->srcpad); + tmpl = gst_element_class_get_pad_template (element_klass, "src"); + if (tmpl) { + mux->srcpad = gst_pad_new_from_template (tmpl, "src"); + gst_pad_use_fixed_caps (mux->srcpad); + gst_pad_set_caps (mux->srcpad, gst_pad_template_get_caps (tmpl)); + gst_element_add_pad (GST_ELEMENT (mux), mux->srcpad); + } mux->render_tag = TRUE; } -static void -add_one_txxx_musicbrainz_tag (ID3v2::Tag * id3v2tag, const gchar * spec_id, - const gchar * realworld_id, const gchar * id_str) -{ - ID3v2::UserTextIdentificationFrame * frame; - - if (id_str == NULL) - return; - - GST_DEBUG ("Setting %s to %s", GST_STR_NULL (spec_id), id_str); - - if (spec_id) { - frame = new ID3v2::UserTextIdentificationFrame (String::Latin1); - id3v2tag->addFrame (frame); - frame->setDescription (spec_id); - frame->setText (id_str); - } - - if (realworld_id) { - frame = new ID3v2::UserTextIdentificationFrame (String::Latin1); - id3v2tag->addFrame (frame); - frame->setDescription (realworld_id); - frame->setText (id_str); - } -} - -static void -add_one_tag (const GstTagList * list, const gchar * tag, gpointer user_data) -{ - ID3v2::Tag * id3v2tag = (ID3v2::Tag *) user_data; - gboolean result; - - /* FIXME: if there are several values set for the same tag, this won't - * work, only the first value will be taken into account - */ - if (strcmp (tag, GST_TAG_TITLE) == 0) { - char *title; - - result = gst_tag_list_get_string_index (list, tag, 0, &title); - if (result != FALSE) { - GST_DEBUG ("Setting title to %s", title); - id3v2tag->setTitle (String::String (title, String::UTF8)); - } - g_free (title); - } else if (strcmp (tag, GST_TAG_ALBUM) == 0) { - char *album; - - result = gst_tag_list_get_string_index (list, tag, 0, &album); - if (result != FALSE) { - GST_DEBUG ("Setting album to %s", album); - id3v2tag->setAlbum (String::String (album, String::UTF8)); - } - g_free (album); - } else if (strcmp (tag, GST_TAG_ARTIST) == 0) { - char *artist; - - result = gst_tag_list_get_string_index (list, tag, 0, &artist); - if (result != FALSE) { - GST_DEBUG ("Setting artist to %s", artist); - id3v2tag->setArtist (String::String (artist, String::UTF8)); - } - g_free (artist); - } else if (strcmp (tag, GST_TAG_GENRE) == 0) { - char *genre; - - result = gst_tag_list_get_string_index (list, tag, 0, &genre); - if (result != FALSE) { - GST_DEBUG ("Setting genre to %s", genre); - id3v2tag->setGenre (String::String (genre, String::UTF8)); - } - g_free (genre); - } else if (strcmp (tag, GST_TAG_COMMENT) == 0) { - char *comment; - - result = gst_tag_list_get_string_index (list, tag, 0, &comment); - if (result != FALSE) { - GST_DEBUG ("Setting comment to %s", comment); - id3v2tag->setComment (String::String (comment, String::UTF8)); - } - g_free (comment); - } else if (strcmp (tag, GST_TAG_DATE) == 0) { - GDate *date; - - result = gst_tag_list_get_date_index (list, tag, 0, &date); - if (result != FALSE) { - GDateYear year; - - year = g_date_get_year (date); - GST_DEBUG ("Setting track year to %d", year); - id3v2tag->setYear (year); - g_date_free (date); - } - } else if (strcmp (tag, GST_TAG_TRACK_NUMBER) == 0) { - guint track_number; - - result = gst_tag_list_get_uint_index (list, tag, 0, &track_number); - if (result != FALSE) { - guint total_tracks; - - result = gst_tag_list_get_uint_index (list, GST_TAG_TRACK_COUNT, - 0, &total_tracks); - if (result) { - gchar *tag_str; - - ID3v2::TextIdentificationFrame * frame; - - frame = new ID3v2::TextIdentificationFrame ("TRCK", String::UTF8); - tag_str = g_strdup_printf ("%d/%d", track_number, total_tracks); - GST_DEBUG ("Setting track number to %s", tag_str); - id3v2tag->addFrame (frame); - frame->setText (tag_str); - g_free (tag_str); - } else { - GST_DEBUG ("Setting track number to %d", track_number); - id3v2tag->setTrack (track_number); - } - } - } else if (strcmp (tag, GST_TAG_ALBUM_VOLUME_NUMBER) == 0) { - guint volume_number; - - result = gst_tag_list_get_uint_index (list, tag, 0, &volume_number); - - if (result != FALSE) { - guint volume_count; - gchar *tag_str; - - ID3v2::TextIdentificationFrame * frame; - - frame = new ID3v2::TextIdentificationFrame ("TPOS", String::UTF8); - result = gst_tag_list_get_uint_index (list, GST_TAG_ALBUM_VOLUME_COUNT, - 0, &volume_count); - if (result) { - tag_str = g_strdup_printf ("%d/%d", volume_number, volume_count); - } else { - tag_str = g_strdup_printf ("%d", volume_number); - } - - GST_DEBUG ("Setting album number to %s", tag_str); - - id3v2tag->addFrame (frame); - frame->setText (tag_str); - g_free (tag_str); - } - } else if (strcmp (tag, GST_TAG_COPYRIGHT) == 0) { - gchar *copyright; - - result = gst_tag_list_get_string_index (list, tag, 0, ©right); - - if (result != FALSE) { - ID3v2::TextIdentificationFrame * frame; - - GST_DEBUG ("Setting copyright to %s", copyright); - - frame = new ID3v2::TextIdentificationFrame ("TCOP", String::UTF8); - - id3v2tag->addFrame (frame); - frame->setText (copyright); - g_free (copyright); - } - } else if (strcmp (tag, GST_TAG_MUSICBRAINZ_ARTISTID) == 0) { - gchar *id_str; - - if (gst_tag_list_get_string_index (list, tag, 0, &id_str) && id_str) { - add_one_txxx_musicbrainz_tag (id3v2tag, "MusicBrainz Artist Id", - "musicbrainz_artistid", id_str); - g_free (id_str); - } - } else if (strcmp (tag, GST_TAG_MUSICBRAINZ_ALBUMID) == 0) { - gchar *id_str; - - if (gst_tag_list_get_string_index (list, tag, 0, &id_str) && id_str) { - add_one_txxx_musicbrainz_tag (id3v2tag, "MusicBrainz Album Id", - "musicbrainz_albumid", id_str); - g_free (id_str); - } - } else if (strcmp (tag, GST_TAG_MUSICBRAINZ_ALBUMARTISTID) == 0) { - gchar *id_str; - - if (gst_tag_list_get_string_index (list, tag, 0, &id_str) && id_str) { - add_one_txxx_musicbrainz_tag (id3v2tag, "MusicBrainz Album Artist Id", - "musicbrainz_albumartistid", id_str); - g_free (id_str); - } - } else if (strcmp (tag, GST_TAG_MUSICBRAINZ_TRMID) == 0) { - gchar *id_str; - - if (gst_tag_list_get_string_index (list, tag, 0, &id_str) && id_str) { - add_one_txxx_musicbrainz_tag (id3v2tag, "MusicBrainz TRM Id", - "musicbrainz_trmid", id_str); - g_free (id_str); - } - } else if (strcmp (tag, GST_TAG_MUSICBRAINZ_TRACKID) == 0) { - gchar *id_str; - - if (gst_tag_list_get_string_index (list, tag, 0, &id_str) && id_str) { - ID3v2::UniqueFileIdentifierFrame * frame; - - GST_DEBUG ("Setting Musicbrainz Track Id to %s", id_str); - - frame = new ID3v2::UniqueFileIdentifierFrame ("http://musicbrainz.org", - id_str); - id3v2tag->addFrame (frame); - g_free (id_str); - } - } else { - GST_WARNING ("Unsupported tag: %s", tag); - } -} - static GstBuffer * gst_tag_lib_mux_render_tag (GstTagLibMux * mux) { - ID3v2::Tag id3v2tag; - ByteVector rendered_tag; + GstTagLibMuxClass *klass; GstBuffer *buffer; GstTagSetter *tagsetter = GST_TAG_SETTER (mux); const GstTagList *tagsetter_tags; @@ -416,19 +160,19 @@ gst_tag_lib_mux_render_tag (GstTagLibMux * mux) GST_LOG_OBJECT (mux, "final tags: %" GST_PTR_FORMAT, taglist); - /* Render the tag */ - gst_tag_list_foreach (taglist, add_one_tag, &id3v2tag); + klass = GST_TAG_LIB_MUX_CLASS (G_OBJECT_GET_CLASS (mux)); - rendered_tag = id3v2tag.render (); - mux->tag_size = rendered_tag.size (); + if (klass->render_tag == NULL) + goto no_vfunc; + buffer = klass->render_tag (mux, taglist); + + if (buffer == NULL) + goto render_error; + + mux->tag_size = GST_BUFFER_SIZE (buffer); GST_LOG_OBJECT (mux, "tag size = %d bytes", mux->tag_size); - /* Create buffer with tag */ - buffer = gst_buffer_new_and_alloc (mux->tag_size); - memcpy (GST_BUFFER_DATA (buffer), rendered_tag.data (), mux->tag_size); - gst_buffer_set_caps (buffer, GST_PAD_CAPS (mux->srcpad)); - /* Send newsegment event from byte position 0, so the tag really gets * written to the start of the file, independent of the upstream segment */ gst_pad_push_event (mux->srcpad, @@ -442,6 +186,16 @@ gst_tag_lib_mux_render_tag (GstTagLibMux * mux) GST_BUFFER_OFFSET (buffer) = 0; return buffer; + +no_vfunc: + { + return NULL; /* FIXME */ + } + +render_error: + { + return NULL; /* FIXME */ + } } static GstEvent * @@ -623,25 +377,3 @@ gst_tag_lib_mux_change_state (GstElement * element, GstStateChange transition) return result; } - - -static gboolean -plugin_init (GstPlugin * plugin) -{ - if (!gst_element_register (plugin, "id3v2mux", GST_RANK_NONE, - GST_TYPE_TAG_LIB_MUX)) - return FALSE; - - GST_DEBUG_CATEGORY_INIT (gst_tag_lib_mux_debug, "taglibmux", 0, - "taglib-based muxer"); - - gst_tag_register_musicbrainz_tags (); - - return TRUE; -} - -GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, - GST_VERSION_MINOR, - "taglib", - "Tag-writing plug-in based on taglib", - plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN); diff --git a/gst-libs/gst/tag/gsttagmux.h b/gst-libs/gst/tag/gsttagmux.h index eacfc840ee..5b91a93e70 100644 --- a/gst-libs/gst/tag/gsttagmux.h +++ b/gst-libs/gst/tag/gsttagmux.h @@ -1,6 +1,7 @@ -/* GStreamer taglib-based muxer - * (c) 2006 Christophe Fergeau - * +/* GStreamer taglib-based muxer base class + * Copyright (C) 2006 Christophe Fergeau + * Copyright (C) 2006 Tim-Philipp Müller + * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either @@ -24,7 +25,8 @@ G_BEGIN_DECLS -typedef struct _GstTagLibMuxPriv GstTagLibMuxPriv; +typedef struct _GstTagLibMux GstTagLibMux; +typedef struct _GstTagLibMuxClass GstTagLibMuxClass; /* Definition of structure storing data for this element. */ typedef struct _GstTagLibMux { @@ -37,12 +39,15 @@ typedef struct _GstTagLibMux { gboolean render_tag; GstEvent *newsegment_ev; /* cached newsegment event from upstream */ -} GstTagLibMux; +}; /* Standard definition defining a class for this element. */ typedef struct _GstTagLibMuxClass { GstElementClass parent_class; -} GstTagLibMuxClass; + + /* vfuncs */ + GstBuffer * (*render_tag) (GstTagLibMux * mux, GstTagList * tag_list); +}; /* Standard macros for defining types for this element. */ #define GST_TYPE_TAG_LIB_MUX \ From 7ef4528676e4bc7037c329b835b541e8554eca49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Sat, 29 Apr 2006 18:46:36 +0000 Subject: [PATCH 031/140] ext/taglib/gsttaglib.cc: Post an error message on the bus in the (extremely unlikely) case of an error. Original commit message from CVS: * ext/taglib/gsttaglib.cc: Post an error message on the bus in the (extremely unlikely) case of an error. --- gst-libs/gst/tag/gsttagmux.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/gst-libs/gst/tag/gsttagmux.c b/gst-libs/gst/tag/gsttagmux.c index d47803bea8..f2bc521875 100644 --- a/gst-libs/gst/tag/gsttagmux.c +++ b/gst-libs/gst/tag/gsttagmux.c @@ -189,12 +189,14 @@ gst_tag_lib_mux_render_tag (GstTagLibMux * mux) no_vfunc: { - return NULL; /* FIXME */ + GST_ERROR_OBJECT (mux, "Subclass does not implement render_tag vfunc!"); + return NULL; } render_error: { - return NULL; /* FIXME */ + GST_ERROR_OBJECT (mux, "Failed to render tag"); + return NULL; } } @@ -231,9 +233,13 @@ gst_tag_lib_mux_chain (GstPad * pad, GstBuffer * buffer) if (mux->render_tag) { GstFlowReturn ret; + GstBuffer *tag_buffer; GST_INFO_OBJECT (mux, "Adding tags to stream"); - ret = gst_pad_push (mux->srcpad, gst_tag_lib_mux_render_tag (mux)); + tag_buffer = gst_tag_lib_mux_render_tag (mux); + if (tag_buffer == NULL) + goto no_tag_buffer; + ret = gst_pad_push (mux->srcpad, tag_buffer); if (ret != GST_FLOW_OK) { GST_DEBUG_OBJECT (mux, "flow: %s", gst_flow_get_name (ret)); gst_buffer_unref (buffer); @@ -265,6 +271,13 @@ gst_tag_lib_mux_chain (GstPad * pad, GstBuffer * buffer) gst_buffer_set_caps (buffer, GST_PAD_CAPS (mux->srcpad)); return gst_pad_push (mux->srcpad, buffer); + +/* ERRORS */ +no_tag_buffer: + { + GST_ELEMENT_ERROR (mux, LIBRARY, ENCODE, (NULL), (NULL)); + return GST_FLOW_ERROR; + } } static gboolean From 400dc037fd97f856828cdd7825acbec216e54254 Mon Sep 17 00:00:00 2001 From: Thomas Vander Stichele Date: Sun, 30 Apr 2006 16:16:59 +0000 Subject: [PATCH 032/140] small cleanups Original commit message from CVS: small cleanups --- gst-libs/gst/tag/gsttagmux.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/gst-libs/gst/tag/gsttagmux.c b/gst-libs/gst/tag/gsttagmux.c index f2bc521875..35ec89fe4b 100644 --- a/gst-libs/gst/tag/gsttagmux.c +++ b/gst-libs/gst/tag/gsttagmux.c @@ -26,7 +26,6 @@ #include #include #include "gsttaglib.h" -#include "gstid3v2mux.h" GST_DEBUG_CATEGORY_STATIC (gst_tag_lib_mux_debug); #define GST_CAT_DEFAULT gst_tag_lib_mux_debug @@ -37,7 +36,6 @@ GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_ALWAYS, GST_STATIC_CAPS ("ANY")); - static void gst_tag_lib_mux_iface_init (GType taglib_type) { From 87e56b77134f46f59025b9bc2d5d33bea04e23e7 Mon Sep 17 00:00:00 2001 From: Thomas Vander Stichele Date: Mon, 1 May 2006 11:46:33 +0000 Subject: [PATCH 033/140] docs/plugins/Makefile.am: also check .cc files for gtk-doc markup Original commit message from CVS: * docs/plugins/Makefile.am: also check .cc files for gtk-doc markup * configure.ac: * docs/plugins/gst-plugins-good-plugins-docs.sgml: * docs/plugins/gst-plugins-good-plugins-sections.txt: * tests/check/Makefile.am: * tests/check/elements/id3v2mux.c: (id3v2mux_suite), (main): * ext/Makefile.am: * ext/taglib/Makefile.am: * ext/taglib/gstid3v2mux.h: * gst-libs/gst/tag/gsttagmux.c: * gst-libs/gst/tag/gsttagmux.h: move taglib-based id3v2muxer to -good. Fixes #336110. --- gst-libs/gst/tag/gsttagmux.c | 3 ++- gst-libs/gst/tag/gsttagmux.h | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/gst-libs/gst/tag/gsttagmux.c b/gst-libs/gst/tag/gsttagmux.c index 35ec89fe4b..5224e6c159 100644 --- a/gst-libs/gst/tag/gsttagmux.c +++ b/gst-libs/gst/tag/gsttagmux.c @@ -25,7 +25,8 @@ #include #include #include -#include "gsttaglib.h" + +#include "gsttaglibmux.h" GST_DEBUG_CATEGORY_STATIC (gst_tag_lib_mux_debug); #define GST_CAT_DEFAULT gst_tag_lib_mux_debug diff --git a/gst-libs/gst/tag/gsttagmux.h b/gst-libs/gst/tag/gsttagmux.h index 5b91a93e70..26c9de7b39 100644 --- a/gst-libs/gst/tag/gsttagmux.h +++ b/gst-libs/gst/tag/gsttagmux.h @@ -18,8 +18,8 @@ * Boston, MA 02111-1307, USA. */ -#ifndef GST_TAG_LIB_H -#define GST_TAG_LIB_H +#ifndef GST_TAG_LIB_MUX_H +#define GST_TAG_LIB_MUX_H #include @@ -29,7 +29,7 @@ typedef struct _GstTagLibMux GstTagLibMux; typedef struct _GstTagLibMuxClass GstTagLibMuxClass; /* Definition of structure storing data for this element. */ -typedef struct _GstTagLibMux { +struct _GstTagLibMux { GstElement element; GstPad *srcpad; @@ -42,7 +42,7 @@ typedef struct _GstTagLibMux { }; /* Standard definition defining a class for this element. */ -typedef struct _GstTagLibMuxClass { +struct _GstTagLibMuxClass { GstElementClass parent_class; /* vfuncs */ From e286dcec812b6f0ea58b8cb2f9c35b0adb19f4e3 Mon Sep 17 00:00:00 2001 From: James Doc Livingston Date: Thu, 18 May 2006 12:46:08 +0000 Subject: [PATCH 034/140] gst-libs/gst/tag/gsttagmux.c: Merge event tags and tag setter tags correctly (#339918). Also, don't leak taglist in case... Original commit message from CVS: Patch by: James "Doc" Livingston * gst-libs/gst/tag/gsttagmux.c: (gst_tag_lib_mux_render_tag): Merge event tags and tag setter tags correctly (#339918). Also, don't leak taglist in case of an error. --- gst-libs/gst/tag/gsttagmux.c | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/gst-libs/gst/tag/gsttagmux.c b/gst-libs/gst/tag/gsttagmux.c index 5224e6c159..f3321d324b 100644 --- a/gst-libs/gst/tag/gsttagmux.c +++ b/gst-libs/gst/tag/gsttagmux.c @@ -134,28 +134,23 @@ static GstBuffer * gst_tag_lib_mux_render_tag (GstTagLibMux * mux) { GstTagLibMuxClass *klass; + GstTagMergeMode merge_mode; + GstTagSetter *tagsetter; GstBuffer *buffer; - GstTagSetter *tagsetter = GST_TAG_SETTER (mux); const GstTagList *tagsetter_tags; GstTagList *taglist; GstEvent *event; - if (mux->event_tags != NULL) { - taglist = gst_tag_list_copy (mux->event_tags); - } else { - taglist = gst_tag_list_new (); - } + tagsetter = GST_TAG_SETTER (mux); tagsetter_tags = gst_tag_setter_get_tag_list (tagsetter); - if (tagsetter_tags) { - GstTagMergeMode merge_mode; + merge_mode = gst_tag_setter_get_tag_merge_mode (tagsetter); - merge_mode = gst_tag_setter_get_tag_merge_mode (tagsetter); - GST_LOG_OBJECT (mux, "merging tags, merge mode = %d", merge_mode); - GST_LOG_OBJECT (mux, "event tags: %" GST_PTR_FORMAT, taglist); - GST_LOG_OBJECT (mux, "set tags: %" GST_PTR_FORMAT, tagsetter_tags); - gst_tag_list_insert (taglist, tagsetter_tags, merge_mode); - } + GST_LOG_OBJECT (mux, "merging tags, merge mode = %d", merge_mode); + GST_LOG_OBJECT (mux, "event tags: %" GST_PTR_FORMAT, mux->event_tags); + GST_LOG_OBJECT (mux, "set tags: %" GST_PTR_FORMAT, tagsetter_tags); + + taglist = gst_tag_list_merge (tagsetter_tags, mux->event_tags, merge_mode); GST_LOG_OBJECT (mux, "final tags: %" GST_PTR_FORMAT, taglist); @@ -189,12 +184,14 @@ gst_tag_lib_mux_render_tag (GstTagLibMux * mux) no_vfunc: { GST_ERROR_OBJECT (mux, "Subclass does not implement render_tag vfunc!"); + gst_tag_list_free (taglist); return NULL; } render_error: { GST_ERROR_OBJECT (mux, "Failed to render tag"); + gst_tag_list_free (taglist); return NULL; } } From b303e8e19ec98036bddebba0ae2f912c9b843836 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 30 May 2006 14:35:18 +0000 Subject: [PATCH 035/140] Add apev2mux element (#343122). MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Original commit message from CVS: Patch by: Sebastian Dröge * docs/plugins/gst-plugins-good-plugins-docs.sgml: * docs/plugins/gst-plugins-good-plugins-sections.txt: * ext/taglib/Makefile.am: * ext/taglib/gstapev2mux.cc: * ext/taglib/gstapev2mux.h: * ext/taglib/gstid3v2mux.cc: * gst-libs/gst/tag/gsttagmux.c: (plugin_init): * gst-libs/gst/tag/gsttagmux.h: Add apev2mux element (#343122). * tests/check/Makefile.am: * tests/check/elements/apev2mux.c: (test_taglib_apev2mux_create_tags), (test_taglib_apev2mux_check_tags), (fill_mp3_buffer), (got_buffer), (demux_pad_added), (test_taglib_apev2mux_check_output_buffer), (test_taglib_apev2mux_with_tags), (GST_START_TEST), (apev2mux_suite), (main): Add unit test for apev2mux element. --- gst-libs/gst/tag/gsttagmux.c | 14 ++++++++++++++ gst-libs/gst/tag/gsttagmux.h | 2 ++ 2 files changed, 16 insertions(+) diff --git a/gst-libs/gst/tag/gsttagmux.c b/gst-libs/gst/tag/gsttagmux.c index f3321d324b..b0f22eaac5 100644 --- a/gst-libs/gst/tag/gsttagmux.c +++ b/gst-libs/gst/tag/gsttagmux.c @@ -1,6 +1,7 @@ /* GStreamer taglib-based muxer base class * Copyright (C) 2006 Christophe Fergeau * Copyright (C) 2006 Tim-Philipp Müller + * Copyright (C) 2006 Sebastian Dröge * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -386,3 +387,16 @@ gst_tag_lib_mux_change_state (GstElement * element, GstStateChange transition) return result; } + +static gboolean +plugin_init (GstPlugin * plugin) +{ + return (gst_id3v2_mux_plugin_init (plugin) + && gst_apev2_mux_plugin_init (plugin)); +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "taglib", + "Tag writing plug-in based on taglib", + plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN); diff --git a/gst-libs/gst/tag/gsttagmux.h b/gst-libs/gst/tag/gsttagmux.h index 26c9de7b39..d26ff30867 100644 --- a/gst-libs/gst/tag/gsttagmux.h +++ b/gst-libs/gst/tag/gsttagmux.h @@ -63,6 +63,8 @@ struct _GstTagLibMuxClass { /* Standard function returning type information. */ GType gst_tag_lib_mux_get_type (void); +gboolean gst_apev2_mux_plugin_init (GstPlugin * plugin); +gboolean gst_id3v2_mux_plugin_init (GstPlugin * plugin); G_END_DECLS From dcfd4027a49dc30c37a6a6d0f97d9494e5864587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 13 Sep 2007 15:04:15 +0000 Subject: [PATCH 036/140] Update my mail address. Original commit message from CVS: * ext/taglib/gstapev2mux.cc: * ext/taglib/gstapev2mux.h: * gst-libs/gst/tag/gsttagmux.c: * tests/check/elements/apev2mux.c: Update my mail address. --- gst-libs/gst/tag/gsttagmux.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gst-libs/gst/tag/gsttagmux.c b/gst-libs/gst/tag/gsttagmux.c index b0f22eaac5..a20e18cc31 100644 --- a/gst-libs/gst/tag/gsttagmux.c +++ b/gst-libs/gst/tag/gsttagmux.c @@ -1,7 +1,7 @@ /* GStreamer taglib-based muxer base class * Copyright (C) 2006 Christophe Fergeau * Copyright (C) 2006 Tim-Philipp Müller - * Copyright (C) 2006 Sebastian Dröge + * Copyright (C) 2006 Sebastian Dröge * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public From 992cf09a5a343ffc05f7fb121ea0bfc8fa2073e8 Mon Sep 17 00:00:00 2001 From: Julien Moutte Date: Tue, 20 Nov 2007 11:41:13 +0000 Subject: [PATCH 037/140] Fix build on Mac OS X 10.5 Original commit message from CVS: 2007-11-20 Julien MOUTTE * gst-libs/gst/tag/gsttagmux.c: (gst_tag_lib_mux_render_tag), (gst_tag_lib_mux_adjust_event_offsets): * gst/qtdemux/qtdemux.c: (qtdemux_parse_theora_extension): * sys/osxaudio/Makefile.am: * sys/osxvideo/cocoawindow.h: * sys/osxvideo/cocoawindow.m: Fix build on Mac OS X 10.5 --- gst-libs/gst/tag/gsttagmux.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gst-libs/gst/tag/gsttagmux.c b/gst-libs/gst/tag/gsttagmux.c index a20e18cc31..21cdb77ac9 100644 --- a/gst-libs/gst/tag/gsttagmux.c +++ b/gst-libs/gst/tag/gsttagmux.c @@ -166,7 +166,7 @@ gst_tag_lib_mux_render_tag (GstTagLibMux * mux) goto render_error; mux->tag_size = GST_BUFFER_SIZE (buffer); - GST_LOG_OBJECT (mux, "tag size = %d bytes", mux->tag_size); + GST_LOG_OBJECT (mux, "tag size = %" G_GSIZE_FORMAT " bytes", mux->tag_size); /* Send newsegment event from byte position 0, so the tag really gets * written to the start of the file, independent of the upstream segment */ @@ -218,7 +218,7 @@ gst_tag_lib_mux_adjust_event_offsets (GstTagLibMux * mux, GST_DEBUG_OBJECT (mux, "adjusting newsegment event offsets to start=%" G_GINT64_FORMAT ", stop=%" G_GINT64_FORMAT ", cur=%" G_GINT64_FORMAT - " (delta = +%u)", start, stop, cur, mux->tag_size); + " (delta = +%" G_GSIZE_FORMAT ")", start, stop, cur, mux->tag_size); return gst_event_new_new_segment (TRUE, 1.0, format, start, stop, cur); } From b567db4155952e64b40e4e012088e4d71e1078cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sun, 6 Jun 2010 18:00:22 +0200 Subject: [PATCH 038/140] ext: Don't use GST_DEBUG_FUNCPTR for GObject vfuncs --- gst-libs/gst/tag/gsttagmux.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gst-libs/gst/tag/gsttagmux.c b/gst-libs/gst/tag/gsttagmux.c index 21cdb77ac9..7e8cf6e250 100644 --- a/gst-libs/gst/tag/gsttagmux.c +++ b/gst-libs/gst/tag/gsttagmux.c @@ -99,7 +99,7 @@ gst_tag_lib_mux_class_init (GstTagLibMuxClass * klass) gobject_class = (GObjectClass *) klass; gstelement_class = (GstElementClass *) klass; - gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_tag_lib_mux_finalize); + gobject_class->finalize = gst_tag_lib_mux_finalize; gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_tag_lib_mux_change_state); } From e16f2f891a4e83f9a7a13b5131ee576d18f3e9df Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 28 Jul 2011 23:31:03 +0100 Subject: [PATCH 039/140] tagmux: add support for end tags Originally "id3tag: Add new id3 tagging plugin, supports v1, v2.3, and v2.4." from gst-plugins-bad. This is an artificial bridge commit. --- gst-libs/gst/tag/gsttagmux.c | 304 ++++++++++++++++++++++------------- gst-libs/gst/tag/gsttagmux.h | 56 ++++--- 2 files changed, 228 insertions(+), 132 deletions(-) diff --git a/gst-libs/gst/tag/gsttagmux.c b/gst-libs/gst/tag/gsttagmux.c index 7e8cf6e250..bfa4e1bcc7 100644 --- a/gst-libs/gst/tag/gsttagmux.c +++ b/gst-libs/gst/tag/gsttagmux.c @@ -1,8 +1,10 @@ -/* GStreamer taglib-based muxer base class +/* GStreamer tag muxer base class + * * Copyright (C) 2006 Christophe Fergeau * Copyright (C) 2006 Tim-Philipp Müller * Copyright (C) 2006 Sebastian Dröge - + * Copyright (C) 2009 Pioneers of the Inevitable + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either @@ -27,19 +29,22 @@ #include #include -#include "gsttaglibmux.h" +#include "gsttagmux.h" -GST_DEBUG_CATEGORY_STATIC (gst_tag_lib_mux_debug); -#define GST_CAT_DEFAULT gst_tag_lib_mux_debug +GST_DEBUG_CATEGORY_STATIC (gst_tag_mux_debug); +#define GST_CAT_DEFAULT gst_tag_mux_debug -static GstStaticPadTemplate gst_tag_lib_mux_sink_template = +/* Subclass provides a src template and pad. We accept anything as input here, + however. */ + +static GstStaticPadTemplate gst_tag_mux_sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS ("ANY")); static void -gst_tag_lib_mux_iface_init (GType taglib_type) +gst_tag_mux_iface_init (GType tag_type) { static const GInterfaceInfo tag_setter_info = { NULL, @@ -47,23 +52,22 @@ gst_tag_lib_mux_iface_init (GType taglib_type) NULL }; - g_type_add_interface_static (taglib_type, GST_TYPE_TAG_SETTER, - &tag_setter_info); + g_type_add_interface_static (tag_type, GST_TYPE_TAG_SETTER, &tag_setter_info); } -GST_BOILERPLATE_FULL (GstTagLibMux, gst_tag_lib_mux, - GstElement, GST_TYPE_ELEMENT, gst_tag_lib_mux_iface_init); +GST_BOILERPLATE_FULL (GstTagMux, gst_tag_mux, + GstElement, GST_TYPE_ELEMENT, gst_tag_mux_iface_init); static GstStateChangeReturn -gst_tag_lib_mux_change_state (GstElement * element, GstStateChange transition); -static GstFlowReturn gst_tag_lib_mux_chain (GstPad * pad, GstBuffer * buffer); -static gboolean gst_tag_lib_mux_sink_event (GstPad * pad, GstEvent * event); +gst_tag_mux_change_state (GstElement * element, GstStateChange transition); +static GstFlowReturn gst_tag_mux_chain (GstPad * pad, GstBuffer * buffer); +static gboolean gst_tag_mux_sink_event (GstPad * pad, GstEvent * event); static void -gst_tag_lib_mux_finalize (GObject * obj) +gst_tag_mux_finalize (GObject * obj) { - GstTagLibMux *mux = GST_TAG_LIB_MUX (obj); + GstTagMux *mux = GST_TAG_MUX (obj); if (mux->newsegment_ev) { gst_event_unref (mux->newsegment_ev); @@ -75,23 +79,28 @@ gst_tag_lib_mux_finalize (GObject * obj) mux->event_tags = NULL; } + if (mux->final_tags) { + gst_tag_list_free (mux->final_tags); + mux->final_tags = NULL; + } + G_OBJECT_CLASS (parent_class)->finalize (obj); } static void -gst_tag_lib_mux_base_init (gpointer g_class) +gst_tag_mux_base_init (gpointer g_class) { GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); gst_element_class_add_pad_template (element_class, - gst_static_pad_template_get (&gst_tag_lib_mux_sink_template)); + gst_static_pad_template_get (&gst_tag_mux_sink_template)); - GST_DEBUG_CATEGORY_INIT (gst_tag_lib_mux_debug, "taglibmux", 0, - "taglib-based muxer"); + GST_DEBUG_CATEGORY_INIT (gst_tag_mux_debug, "tagmux", 0, + "tag muxer base class"); } static void -gst_tag_lib_mux_class_init (GstTagLibMuxClass * klass) +gst_tag_mux_class_init (GstTagMuxClass * klass) { GObjectClass *gobject_class; GstElementClass *gstelement_class; @@ -99,24 +108,23 @@ gst_tag_lib_mux_class_init (GstTagLibMuxClass * klass) gobject_class = (GObjectClass *) klass; gstelement_class = (GstElementClass *) klass; - gobject_class->finalize = gst_tag_lib_mux_finalize; - gstelement_class->change_state = - GST_DEBUG_FUNCPTR (gst_tag_lib_mux_change_state); + gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_tag_mux_finalize); + gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_tag_mux_change_state); } static void -gst_tag_lib_mux_init (GstTagLibMux * mux, GstTagLibMuxClass * mux_class) +gst_tag_mux_init (GstTagMux * mux, GstTagMuxClass * mux_class) { GstElementClass *element_klass = GST_ELEMENT_CLASS (mux_class); GstPadTemplate *tmpl; /* pad through which data comes in to the element */ mux->sinkpad = - gst_pad_new_from_static_template (&gst_tag_lib_mux_sink_template, "sink"); + gst_pad_new_from_static_template (&gst_tag_mux_sink_template, "sink"); gst_pad_set_chain_function (mux->sinkpad, - GST_DEBUG_FUNCPTR (gst_tag_lib_mux_chain)); + GST_DEBUG_FUNCPTR (gst_tag_mux_chain)); gst_pad_set_event_function (mux->sinkpad, - GST_DEBUG_FUNCPTR (gst_tag_lib_mux_sink_event)); + GST_DEBUG_FUNCPTR (gst_tag_mux_sink_event)); gst_element_add_pad (GST_ELEMENT (mux), mux->sinkpad); /* pad through which data goes out of the element */ @@ -128,21 +136,19 @@ gst_tag_lib_mux_init (GstTagLibMux * mux, GstTagLibMuxClass * mux_class) gst_element_add_pad (GST_ELEMENT (mux), mux->srcpad); } - mux->render_tag = TRUE; + mux->render_start_tag = TRUE; + mux->render_end_tag = TRUE; } -static GstBuffer * -gst_tag_lib_mux_render_tag (GstTagLibMux * mux) +static GstTagList * +gst_tag_mux_get_tags (GstTagMux * mux) { - GstTagLibMuxClass *klass; - GstTagMergeMode merge_mode; - GstTagSetter *tagsetter; - GstBuffer *buffer; + GstTagSetter *tagsetter = GST_TAG_SETTER (mux); const GstTagList *tagsetter_tags; - GstTagList *taglist; - GstEvent *event; + GstTagMergeMode merge_mode; - tagsetter = GST_TAG_SETTER (mux); + if (mux->final_tags) + return mux->final_tags; tagsetter_tags = gst_tag_setter_get_tag_list (tagsetter); merge_mode = gst_tag_setter_get_tag_merge_mode (tagsetter); @@ -151,22 +157,42 @@ gst_tag_lib_mux_render_tag (GstTagLibMux * mux) GST_LOG_OBJECT (mux, "event tags: %" GST_PTR_FORMAT, mux->event_tags); GST_LOG_OBJECT (mux, "set tags: %" GST_PTR_FORMAT, tagsetter_tags); - taglist = gst_tag_list_merge (tagsetter_tags, mux->event_tags, merge_mode); + mux->final_tags = + gst_tag_list_merge (tagsetter_tags, mux->event_tags, merge_mode); - GST_LOG_OBJECT (mux, "final tags: %" GST_PTR_FORMAT, taglist); + GST_LOG_OBJECT (mux, "final tags: %" GST_PTR_FORMAT, mux->final_tags); - klass = GST_TAG_LIB_MUX_CLASS (G_OBJECT_GET_CLASS (mux)); + return mux->final_tags; +} - if (klass->render_tag == NULL) +static GstFlowReturn +gst_tag_mux_render_start_tag (GstTagMux * mux) +{ + GstTagMuxClass *klass; + GstBuffer *buffer; + GstTagList *taglist; + GstEvent *event; + GstFlowReturn ret; + + taglist = gst_tag_mux_get_tags (mux); + + klass = GST_TAG_MUX_CLASS (G_OBJECT_GET_CLASS (mux)); + + if (klass->render_start_tag == NULL) goto no_vfunc; - buffer = klass->render_tag (mux, taglist); + buffer = klass->render_start_tag (mux, taglist); - if (buffer == NULL) - goto render_error; + /* Null buffer is ok, just means we're not outputting anything */ + if (buffer == NULL) { + GST_INFO_OBJECT (mux, "No start tag generated"); + mux->start_tag_size = 0; + return GST_FLOW_OK; + } - mux->tag_size = GST_BUFFER_SIZE (buffer); - GST_LOG_OBJECT (mux, "tag size = %" G_GSIZE_FORMAT " bytes", mux->tag_size); + mux->start_tag_size = GST_BUFFER_SIZE (buffer); + GST_LOG_OBJECT (mux, "tag size = %" G_GSIZE_FORMAT " bytes", + mux->start_tag_size); /* Send newsegment event from byte position 0, so the tag really gets * written to the start of the file, independent of the upstream segment */ @@ -174,31 +200,74 @@ gst_tag_lib_mux_render_tag (GstTagLibMux * mux) gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES, 0, -1, 0)); /* Send an event about the new tags to downstream elements */ - /* gst_event_new_tag takes ownership of the list, so no need to unref it */ - event = gst_event_new_tag (taglist); + /* gst_event_new_tag takes ownership of the list, so use a copy */ + event = gst_event_new_tag (gst_tag_list_copy (taglist)); gst_pad_push_event (mux->srcpad, event); GST_BUFFER_OFFSET (buffer) = 0; + ret = gst_pad_push (mux->srcpad, buffer); - return buffer; + mux->current_offset = mux->start_tag_size; + mux->max_offset = MAX (mux->max_offset, mux->current_offset); + + return ret; no_vfunc: { - GST_ERROR_OBJECT (mux, "Subclass does not implement render_tag vfunc!"); - gst_tag_list_free (taglist); - return NULL; + GST_ERROR_OBJECT (mux, "Subclass does not implement " + "render_start_tag vfunc!"); + return GST_FLOW_ERROR; + } +} + +static GstFlowReturn +gst_tag_mux_render_end_tag (GstTagMux * mux) +{ + GstTagMuxClass *klass; + GstBuffer *buffer; + GstTagList *taglist; + GstFlowReturn ret; + + taglist = gst_tag_mux_get_tags (mux); + + klass = GST_TAG_MUX_CLASS (G_OBJECT_GET_CLASS (mux)); + + if (klass->render_end_tag == NULL) + goto no_vfunc; + + buffer = klass->render_end_tag (mux, taglist); + + if (buffer == NULL) { + GST_INFO_OBJECT (mux, "No end tag generated"); + mux->end_tag_size = 0; + return GST_FLOW_OK; } -render_error: + mux->end_tag_size = GST_BUFFER_SIZE (buffer); + GST_LOG_OBJECT (mux, "tag size = %" G_GSIZE_FORMAT " bytes", + mux->end_tag_size); + + /* Send newsegment event from the end of the file, so it gets written there, + independent of whatever new segment events upstream has sent us */ + gst_pad_push_event (mux->srcpad, + gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES, mux->max_offset, + -1, 0)); + + GST_BUFFER_OFFSET (buffer) = mux->max_offset; + ret = gst_pad_push (mux->srcpad, buffer); + + return ret; + +no_vfunc: { - GST_ERROR_OBJECT (mux, "Failed to render tag"); - gst_tag_list_free (taglist); - return NULL; + GST_ERROR_OBJECT (mux, "Subclass does not implement " + "render_end_tag vfunc!"); + return GST_FLOW_ERROR; } } static GstEvent * -gst_tag_lib_mux_adjust_event_offsets (GstTagLibMux * mux, +gst_tag_mux_adjust_event_offsets (GstTagMux * mux, const GstEvent * newsegment_event) { GstFormat format; @@ -210,33 +279,30 @@ gst_tag_lib_mux_adjust_event_offsets (GstTagLibMux * mux, g_assert (format == GST_FORMAT_BYTES); if (start != -1) - start += mux->tag_size; + start += mux->start_tag_size; if (stop != -1) - stop += mux->tag_size; + stop += mux->start_tag_size; if (cur != -1) - cur += mux->tag_size; + cur += mux->start_tag_size; GST_DEBUG_OBJECT (mux, "adjusting newsegment event offsets to start=%" G_GINT64_FORMAT ", stop=%" G_GINT64_FORMAT ", cur=%" G_GINT64_FORMAT - " (delta = +%" G_GSIZE_FORMAT ")", start, stop, cur, mux->tag_size); + " (delta = +%" G_GSIZE_FORMAT ")", start, stop, cur, mux->start_tag_size); return gst_event_new_new_segment (TRUE, 1.0, format, start, stop, cur); } static GstFlowReturn -gst_tag_lib_mux_chain (GstPad * pad, GstBuffer * buffer) +gst_tag_mux_chain (GstPad * pad, GstBuffer * buffer) { - GstTagLibMux *mux = GST_TAG_LIB_MUX (GST_OBJECT_PARENT (pad)); + GstTagMux *mux = GST_TAG_MUX (GST_OBJECT_PARENT (pad)); + GstFlowReturn ret; + int length; - if (mux->render_tag) { - GstFlowReturn ret; - GstBuffer *tag_buffer; + if (mux->render_start_tag) { GST_INFO_OBJECT (mux, "Adding tags to stream"); - tag_buffer = gst_tag_lib_mux_render_tag (mux); - if (tag_buffer == NULL) - goto no_tag_buffer; - ret = gst_pad_push (mux->srcpad, tag_buffer); + ret = gst_tag_mux_render_start_tag (mux); if (ret != GST_FLOW_OK) { GST_DEBUG_OBJECT (mux, "flow: %s", gst_flow_get_name (ret)); gst_buffer_unref (buffer); @@ -245,16 +311,25 @@ gst_tag_lib_mux_chain (GstPad * pad, GstBuffer * buffer) /* Now send the cached newsegment event that we got from upstream */ if (mux->newsegment_ev) { + gint64 start; + GstEvent *newseg; + GST_DEBUG_OBJECT (mux, "sending cached newsegment event"); - gst_pad_push_event (mux->srcpad, - gst_tag_lib_mux_adjust_event_offsets (mux, mux->newsegment_ev)); + newseg = gst_tag_mux_adjust_event_offsets (mux, mux->newsegment_ev); gst_event_unref (mux->newsegment_ev); mux->newsegment_ev = NULL; + + gst_event_parse_new_segment (newseg, NULL, NULL, NULL, &start, NULL, + NULL); + + gst_pad_push_event (mux->srcpad, newseg); + mux->current_offset = start; + mux->max_offset = MAX (mux->max_offset, mux->current_offset); } else { /* upstream sent no newsegment event or only one in a non-BYTE format */ } - mux->render_tag = FALSE; + mux->render_start_tag = FALSE; } buffer = gst_buffer_make_metadata_writable (buffer); @@ -262,28 +337,28 @@ gst_tag_lib_mux_chain (GstPad * pad, GstBuffer * buffer) if (GST_BUFFER_OFFSET (buffer) != GST_BUFFER_OFFSET_NONE) { GST_LOG_OBJECT (mux, "Adjusting buffer offset from %" G_GINT64_FORMAT " to %" G_GINT64_FORMAT, GST_BUFFER_OFFSET (buffer), - GST_BUFFER_OFFSET (buffer) + mux->tag_size); - GST_BUFFER_OFFSET (buffer) += mux->tag_size; + GST_BUFFER_OFFSET (buffer) + mux->start_tag_size); + GST_BUFFER_OFFSET (buffer) += mux->start_tag_size; } + length = GST_BUFFER_SIZE (buffer); + gst_buffer_set_caps (buffer, GST_PAD_CAPS (mux->srcpad)); - return gst_pad_push (mux->srcpad, buffer); + ret = gst_pad_push (mux->srcpad, buffer); -/* ERRORS */ -no_tag_buffer: - { - GST_ELEMENT_ERROR (mux, LIBRARY, ENCODE, (NULL), (NULL)); - return GST_FLOW_ERROR; - } + mux->current_offset += length; + mux->max_offset = MAX (mux->max_offset, mux->current_offset); + + return ret; } static gboolean -gst_tag_lib_mux_sink_event (GstPad * pad, GstEvent * event) +gst_tag_mux_sink_event (GstPad * pad, GstEvent * event) { - GstTagLibMux *mux; + GstTagMux *mux; gboolean result; - mux = GST_TAG_LIB_MUX (gst_pad_get_parent (pad)); + mux = GST_TAG_MUX (gst_pad_get_parent (pad)); result = FALSE; switch (GST_EVENT_TYPE (event)) { @@ -303,15 +378,16 @@ gst_tag_lib_mux_sink_event (GstPad * pad, GstEvent * event) GST_INFO_OBJECT (mux, "Event tags are now: %" GST_PTR_FORMAT, mux->event_tags); - /* just drop the event, we'll push a new tag event in render_tag */ + /* just drop the event, we'll push a new tag event in render_start_tag */ gst_event_unref (event); result = TRUE; break; } case GST_EVENT_NEWSEGMENT:{ GstFormat fmt; + gint64 start; - gst_event_parse_new_segment (event, NULL, NULL, &fmt, NULL, NULL, NULL); + gst_event_parse_new_segment (event, NULL, NULL, &fmt, &start, NULL, NULL); if (fmt != GST_FORMAT_BYTES) { GST_WARNING_OBJECT (mux, "dropping newsegment event in %s format", @@ -320,7 +396,7 @@ gst_tag_lib_mux_sink_event (GstPad * pad, GstEvent * event) break; } - if (mux->render_tag) { + if (mux->render_start_tag) { /* we have not rendered the tag yet, which means that we don't know * how large it is going to be yet, so we can't adjust the offsets * here at this point and need to cache the newsegment event for now @@ -336,13 +412,34 @@ gst_tag_lib_mux_sink_event (GstPad * pad, GstEvent * event) } else { GST_DEBUG_OBJECT (mux, "got newsegment event, adjusting offsets"); gst_pad_push_event (mux->srcpad, - gst_tag_lib_mux_adjust_event_offsets (mux, event)); + gst_tag_mux_adjust_event_offsets (mux, event)); gst_event_unref (event); + + mux->current_offset = start; + mux->max_offset = MAX (mux->max_offset, mux->current_offset); } event = NULL; result = TRUE; break; } + case GST_EVENT_EOS:{ + if (mux->render_end_tag) { + GstFlowReturn ret; + + GST_INFO_OBJECT (mux, "Adding tags to stream"); + ret = gst_tag_mux_render_end_tag (mux); + if (ret != GST_FLOW_OK) { + GST_DEBUG_OBJECT (mux, "flow: %s", gst_flow_get_name (ret)); + return ret; + } + + mux->render_end_tag = FALSE; + } + + /* Now forward EOS */ + result = gst_pad_event_default (pad, event); + break; + } default: result = gst_pad_event_default (pad, event); break; @@ -355,12 +452,12 @@ gst_tag_lib_mux_sink_event (GstPad * pad, GstEvent * event) static GstStateChangeReturn -gst_tag_lib_mux_change_state (GstElement * element, GstStateChange transition) +gst_tag_mux_change_state (GstElement * element, GstStateChange transition) { - GstTagLibMux *mux; + GstTagMux *mux; GstStateChangeReturn result; - mux = GST_TAG_LIB_MUX (element); + mux = GST_TAG_MUX (element); result = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); if (result != GST_STATE_CHANGE_SUCCESS) { @@ -377,8 +474,12 @@ gst_tag_lib_mux_change_state (GstElement * element, GstStateChange transition) gst_tag_list_free (mux->event_tags); mux->event_tags = NULL; } - mux->tag_size = 0; - mux->render_tag = TRUE; + mux->start_tag_size = 0; + mux->end_tag_size = 0; + mux->render_start_tag = TRUE; + mux->render_end_tag = TRUE; + mux->current_offset = 0; + mux->max_offset = 0; break; } default: @@ -387,16 +488,3 @@ gst_tag_lib_mux_change_state (GstElement * element, GstStateChange transition) return result; } - -static gboolean -plugin_init (GstPlugin * plugin) -{ - return (gst_id3v2_mux_plugin_init (plugin) - && gst_apev2_mux_plugin_init (plugin)); -} - -GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, - GST_VERSION_MINOR, - "taglib", - "Tag writing plug-in based on taglib", - plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN); diff --git a/gst-libs/gst/tag/gsttagmux.h b/gst-libs/gst/tag/gsttagmux.h index d26ff30867..c13a7326d2 100644 --- a/gst-libs/gst/tag/gsttagmux.h +++ b/gst-libs/gst/tag/gsttagmux.h @@ -1,7 +1,9 @@ -/* GStreamer taglib-based muxer base class +/* GStreamer tag muxer base class + * * Copyright (C) 2006 Christophe Fergeau * Copyright (C) 2006 Tim-Philipp Müller - + * Copyright (C) 2009 Pioneers of the Inevitable + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either @@ -18,54 +20,60 @@ * Boston, MA 02111-1307, USA. */ -#ifndef GST_TAG_LIB_MUX_H -#define GST_TAG_LIB_MUX_H +#ifndef GST_TAG_MUX_H +#define GST_TAG_MUX_H #include G_BEGIN_DECLS -typedef struct _GstTagLibMux GstTagLibMux; -typedef struct _GstTagLibMuxClass GstTagLibMuxClass; +typedef struct _GstTagMux GstTagMux; +typedef struct _GstTagMuxClass GstTagMuxClass; /* Definition of structure storing data for this element. */ -struct _GstTagLibMux { +struct _GstTagMux { GstElement element; GstPad *srcpad; GstPad *sinkpad; GstTagList *event_tags; /* tags received from upstream elements */ - gsize tag_size; - gboolean render_tag; + GstTagList *final_tags; /* Final set of tags used for muxing */ + gsize start_tag_size; + gsize end_tag_size; + gboolean render_start_tag; + gboolean render_end_tag; + + gint64 current_offset; + gint64 max_offset; GstEvent *newsegment_ev; /* cached newsegment event from upstream */ }; /* Standard definition defining a class for this element. */ -struct _GstTagLibMuxClass { +struct _GstTagMuxClass { GstElementClass parent_class; /* vfuncs */ - GstBuffer * (*render_tag) (GstTagLibMux * mux, GstTagList * tag_list); + GstBuffer * (*render_start_tag) (GstTagMux * mux, GstTagList * tag_list); + GstBuffer * (*render_end_tag) (GstTagMux * mux, GstTagList * tag_list); }; /* Standard macros for defining types for this element. */ -#define GST_TYPE_TAG_LIB_MUX \ - (gst_tag_lib_mux_get_type()) -#define GST_TAG_LIB_MUX(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_TAG_LIB_MUX,GstTagLibMux)) -#define GST_TAG_LIB_MUX_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_TAG_LIB_MUX,GstTagLibMuxClass)) -#define GST_IS_TAG_LIB_MUX(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_TAG_LIB_MUX)) -#define GST_IS_TAG_LIB_MUX_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_TAG_LIB_MUX)) +#define GST_TYPE_TAG_MUX \ + (gst_tag_mux_get_type()) +#define GST_TAG_MUX(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_TAG_MUX,GstTagMux)) +#define GST_TAG_MUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_TAG_MUX,GstTagMuxClass)) +#define GST_IS_TAG_MUX(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_TAG_MUX)) +#define GST_IS_TAG_MUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_TAG_MUX)) /* Standard function returning type information. */ -GType gst_tag_lib_mux_get_type (void); -gboolean gst_apev2_mux_plugin_init (GstPlugin * plugin); -gboolean gst_id3v2_mux_plugin_init (GstPlugin * plugin); +GType gst_tag_mux_get_type (void); G_END_DECLS #endif + From db88b19f1ee92ed72a7b9136c9adc410ddfbbe47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Fri, 15 Jul 2011 20:39:20 +0100 Subject: [PATCH 040/140] tagmux: fix up private base class header so it can be made public Move private bits into a private struct, add some padding. https://bugzilla.gnome.org/show_bug.cgi?id=555437 --- gst-libs/gst/tag/gsttagmux.c | 212 +++++++++++++++++++++-------------- gst-libs/gst/tag/gsttagmux.h | 20 ++-- 2 files changed, 132 insertions(+), 100 deletions(-) diff --git a/gst-libs/gst/tag/gsttagmux.c b/gst-libs/gst/tag/gsttagmux.c index bfa4e1bcc7..ad0e21edf0 100644 --- a/gst-libs/gst/tag/gsttagmux.c +++ b/gst-libs/gst/tag/gsttagmux.c @@ -1,5 +1,4 @@ /* GStreamer tag muxer base class - * * Copyright (C) 2006 Christophe Fergeau * Copyright (C) 2006 Tim-Philipp Müller * Copyright (C) 2006 Sebastian Dröge @@ -31,6 +30,23 @@ #include "gsttagmux.h" +struct _GstTagMuxPrivate +{ + GstPad *srcpad; + GstPad *sinkpad; + GstTagList *event_tags; /* tags received from upstream elements */ + GstTagList *final_tags; /* Final set of tags used for muxing */ + gsize start_tag_size; + gsize end_tag_size; + gboolean render_start_tag; + gboolean render_end_tag; + + gint64 current_offset; + gint64 max_offset; + + GstEvent *newsegment_ev; /* cached newsegment event from upstream */ +}; + GST_DEBUG_CATEGORY_STATIC (gst_tag_mux_debug); #define GST_CAT_DEFAULT gst_tag_mux_debug @@ -69,19 +85,19 @@ gst_tag_mux_finalize (GObject * obj) { GstTagMux *mux = GST_TAG_MUX (obj); - if (mux->newsegment_ev) { - gst_event_unref (mux->newsegment_ev); - mux->newsegment_ev = NULL; + if (mux->priv->newsegment_ev) { + gst_event_unref (mux->priv->newsegment_ev); + mux->priv->newsegment_ev = NULL; } - if (mux->event_tags) { - gst_tag_list_free (mux->event_tags); - mux->event_tags = NULL; + if (mux->priv->event_tags) { + gst_tag_list_free (mux->priv->event_tags); + mux->priv->event_tags = NULL; } - if (mux->final_tags) { - gst_tag_list_free (mux->final_tags); - mux->final_tags = NULL; + if (mux->priv->final_tags) { + gst_tag_list_free (mux->priv->final_tags); + mux->priv->final_tags = NULL; } G_OBJECT_CLASS (parent_class)->finalize (obj); @@ -110,6 +126,8 @@ gst_tag_mux_class_init (GstTagMuxClass * klass) gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_tag_mux_finalize); gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_tag_mux_change_state); + + g_type_class_add_private (klass, sizeof (GstTagMuxPrivate)); } static void @@ -118,26 +136,30 @@ gst_tag_mux_init (GstTagMux * mux, GstTagMuxClass * mux_class) GstElementClass *element_klass = GST_ELEMENT_CLASS (mux_class); GstPadTemplate *tmpl; + mux->priv = + G_TYPE_INSTANCE_GET_PRIVATE (mux, GST_TYPE_TAG_MUX, GstTagMuxPrivate); + /* pad through which data comes in to the element */ - mux->sinkpad = + mux->priv->sinkpad = gst_pad_new_from_static_template (&gst_tag_mux_sink_template, "sink"); - gst_pad_set_chain_function (mux->sinkpad, + gst_pad_set_chain_function (mux->priv->sinkpad, GST_DEBUG_FUNCPTR (gst_tag_mux_chain)); - gst_pad_set_event_function (mux->sinkpad, + gst_pad_set_event_function (mux->priv->sinkpad, GST_DEBUG_FUNCPTR (gst_tag_mux_sink_event)); - gst_element_add_pad (GST_ELEMENT (mux), mux->sinkpad); + gst_element_add_pad (GST_ELEMENT (mux), mux->priv->sinkpad); /* pad through which data goes out of the element */ tmpl = gst_element_class_get_pad_template (element_klass, "src"); if (tmpl) { - mux->srcpad = gst_pad_new_from_template (tmpl, "src"); - gst_pad_use_fixed_caps (mux->srcpad); - gst_pad_set_caps (mux->srcpad, gst_pad_template_get_caps (tmpl)); - gst_element_add_pad (GST_ELEMENT (mux), mux->srcpad); + mux->priv->srcpad = gst_pad_new_from_template (tmpl, "src"); + gst_pad_use_fixed_caps (mux->priv->srcpad); + /* FIXME: we assume the template caps are fixed.. */ + gst_pad_set_caps (mux->priv->srcpad, gst_pad_template_get_caps (tmpl)); + gst_element_add_pad (GST_ELEMENT (mux), mux->priv->srcpad); } - mux->render_start_tag = TRUE; - mux->render_end_tag = TRUE; + mux->priv->render_start_tag = TRUE; + mux->priv->render_end_tag = TRUE; } static GstTagList * @@ -147,22 +169,22 @@ gst_tag_mux_get_tags (GstTagMux * mux) const GstTagList *tagsetter_tags; GstTagMergeMode merge_mode; - if (mux->final_tags) - return mux->final_tags; + if (mux->priv->final_tags) + return mux->priv->final_tags; tagsetter_tags = gst_tag_setter_get_tag_list (tagsetter); merge_mode = gst_tag_setter_get_tag_merge_mode (tagsetter); GST_LOG_OBJECT (mux, "merging tags, merge mode = %d", merge_mode); - GST_LOG_OBJECT (mux, "event tags: %" GST_PTR_FORMAT, mux->event_tags); + GST_LOG_OBJECT (mux, "event tags: %" GST_PTR_FORMAT, mux->priv->event_tags); GST_LOG_OBJECT (mux, "set tags: %" GST_PTR_FORMAT, tagsetter_tags); - mux->final_tags = - gst_tag_list_merge (tagsetter_tags, mux->event_tags, merge_mode); + mux->priv->final_tags = + gst_tag_list_merge (tagsetter_tags, mux->priv->event_tags, merge_mode); - GST_LOG_OBJECT (mux, "final tags: %" GST_PTR_FORMAT, mux->final_tags); + GST_LOG_OBJECT (mux, "final tags: %" GST_PTR_FORMAT, mux->priv->final_tags); - return mux->final_tags; + return mux->priv->final_tags; } static GstFlowReturn @@ -186,29 +208,35 @@ gst_tag_mux_render_start_tag (GstTagMux * mux) /* Null buffer is ok, just means we're not outputting anything */ if (buffer == NULL) { GST_INFO_OBJECT (mux, "No start tag generated"); - mux->start_tag_size = 0; + mux->priv->start_tag_size = 0; return GST_FLOW_OK; } - mux->start_tag_size = GST_BUFFER_SIZE (buffer); + if (GST_BUFFER_CAPS (buffer) == NULL) { + buffer = gst_buffer_make_metadata_writable (buffer); + gst_buffer_set_caps (buffer, GST_PAD_CAPS (mux->priv->srcpad)); + } + + mux->priv->start_tag_size = GST_BUFFER_SIZE (buffer); GST_LOG_OBJECT (mux, "tag size = %" G_GSIZE_FORMAT " bytes", - mux->start_tag_size); + mux->priv->start_tag_size); /* Send newsegment event from byte position 0, so the tag really gets * written to the start of the file, independent of the upstream segment */ - gst_pad_push_event (mux->srcpad, + gst_pad_push_event (mux->priv->srcpad, gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES, 0, -1, 0)); /* Send an event about the new tags to downstream elements */ /* gst_event_new_tag takes ownership of the list, so use a copy */ event = gst_event_new_tag (gst_tag_list_copy (taglist)); - gst_pad_push_event (mux->srcpad, event); + gst_pad_push_event (mux->priv->srcpad, event); GST_BUFFER_OFFSET (buffer) = 0; - ret = gst_pad_push (mux->srcpad, buffer); + ret = gst_pad_push (mux->priv->srcpad, buffer); - mux->current_offset = mux->start_tag_size; - mux->max_offset = MAX (mux->max_offset, mux->current_offset); + mux->priv->current_offset = mux->priv->start_tag_size; + mux->priv->max_offset = + MAX (mux->priv->max_offset, mux->priv->current_offset); return ret; @@ -239,22 +267,27 @@ gst_tag_mux_render_end_tag (GstTagMux * mux) if (buffer == NULL) { GST_INFO_OBJECT (mux, "No end tag generated"); - mux->end_tag_size = 0; + mux->priv->end_tag_size = 0; return GST_FLOW_OK; } - mux->end_tag_size = GST_BUFFER_SIZE (buffer); + if (GST_BUFFER_CAPS (buffer) == NULL) { + buffer = gst_buffer_make_metadata_writable (buffer); + gst_buffer_set_caps (buffer, GST_PAD_CAPS (mux->priv->srcpad)); + } + + mux->priv->end_tag_size = GST_BUFFER_SIZE (buffer); GST_LOG_OBJECT (mux, "tag size = %" G_GSIZE_FORMAT " bytes", - mux->end_tag_size); + mux->priv->end_tag_size); /* Send newsegment event from the end of the file, so it gets written there, independent of whatever new segment events upstream has sent us */ - gst_pad_push_event (mux->srcpad, - gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES, mux->max_offset, - -1, 0)); + gst_pad_push_event (mux->priv->srcpad, + gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES, + mux->priv->max_offset, -1, 0)); - GST_BUFFER_OFFSET (buffer) = mux->max_offset; - ret = gst_pad_push (mux->srcpad, buffer); + GST_BUFFER_OFFSET (buffer) = mux->priv->max_offset; + ret = gst_pad_push (mux->priv->srcpad, buffer); return ret; @@ -279,15 +312,16 @@ gst_tag_mux_adjust_event_offsets (GstTagMux * mux, g_assert (format == GST_FORMAT_BYTES); if (start != -1) - start += mux->start_tag_size; + start += mux->priv->start_tag_size; if (stop != -1) - stop += mux->start_tag_size; + stop += mux->priv->start_tag_size; if (cur != -1) - cur += mux->start_tag_size; + cur += mux->priv->start_tag_size; GST_DEBUG_OBJECT (mux, "adjusting newsegment event offsets to start=%" G_GINT64_FORMAT ", stop=%" G_GINT64_FORMAT ", cur=%" G_GINT64_FORMAT - " (delta = +%" G_GSIZE_FORMAT ")", start, stop, cur, mux->start_tag_size); + " (delta = +%" G_GSIZE_FORMAT ")", start, stop, cur, + mux->priv->start_tag_size); return gst_event_new_new_segment (TRUE, 1.0, format, start, stop, cur); } @@ -299,7 +333,7 @@ gst_tag_mux_chain (GstPad * pad, GstBuffer * buffer) GstFlowReturn ret; int length; - if (mux->render_start_tag) { + if (mux->priv->render_start_tag) { GST_INFO_OBJECT (mux, "Adding tags to stream"); ret = gst_tag_mux_render_start_tag (mux); @@ -310,26 +344,27 @@ gst_tag_mux_chain (GstPad * pad, GstBuffer * buffer) } /* Now send the cached newsegment event that we got from upstream */ - if (mux->newsegment_ev) { + if (mux->priv->newsegment_ev) { gint64 start; GstEvent *newseg; GST_DEBUG_OBJECT (mux, "sending cached newsegment event"); - newseg = gst_tag_mux_adjust_event_offsets (mux, mux->newsegment_ev); - gst_event_unref (mux->newsegment_ev); - mux->newsegment_ev = NULL; + newseg = gst_tag_mux_adjust_event_offsets (mux, mux->priv->newsegment_ev); + gst_event_unref (mux->priv->newsegment_ev); + mux->priv->newsegment_ev = NULL; gst_event_parse_new_segment (newseg, NULL, NULL, NULL, &start, NULL, NULL); - gst_pad_push_event (mux->srcpad, newseg); - mux->current_offset = start; - mux->max_offset = MAX (mux->max_offset, mux->current_offset); + gst_pad_push_event (mux->priv->srcpad, newseg); + mux->priv->current_offset = start; + mux->priv->max_offset = + MAX (mux->priv->max_offset, mux->priv->current_offset); } else { /* upstream sent no newsegment event or only one in a non-BYTE format */ } - mux->render_start_tag = FALSE; + mux->priv->render_start_tag = FALSE; } buffer = gst_buffer_make_metadata_writable (buffer); @@ -337,17 +372,18 @@ gst_tag_mux_chain (GstPad * pad, GstBuffer * buffer) if (GST_BUFFER_OFFSET (buffer) != GST_BUFFER_OFFSET_NONE) { GST_LOG_OBJECT (mux, "Adjusting buffer offset from %" G_GINT64_FORMAT " to %" G_GINT64_FORMAT, GST_BUFFER_OFFSET (buffer), - GST_BUFFER_OFFSET (buffer) + mux->start_tag_size); - GST_BUFFER_OFFSET (buffer) += mux->start_tag_size; + GST_BUFFER_OFFSET (buffer) + mux->priv->start_tag_size); + GST_BUFFER_OFFSET (buffer) += mux->priv->start_tag_size; } length = GST_BUFFER_SIZE (buffer); - gst_buffer_set_caps (buffer, GST_PAD_CAPS (mux->srcpad)); - ret = gst_pad_push (mux->srcpad, buffer); + gst_buffer_set_caps (buffer, GST_PAD_CAPS (mux->priv->srcpad)); + ret = gst_pad_push (mux->priv->srcpad, buffer); - mux->current_offset += length; - mux->max_offset = MAX (mux->max_offset, mux->current_offset); + mux->priv->current_offset += length; + mux->priv->max_offset = + MAX (mux->priv->max_offset, mux->priv->current_offset); return ret; } @@ -369,14 +405,15 @@ gst_tag_mux_sink_event (GstPad * pad, GstEvent * event) GST_INFO_OBJECT (mux, "Got tag event: %" GST_PTR_FORMAT, tags); - if (mux->event_tags != NULL) { - gst_tag_list_insert (mux->event_tags, tags, GST_TAG_MERGE_REPLACE); + if (mux->priv->event_tags != NULL) { + gst_tag_list_insert (mux->priv->event_tags, tags, + GST_TAG_MERGE_REPLACE); } else { - mux->event_tags = gst_tag_list_copy (tags); + mux->priv->event_tags = gst_tag_list_copy (tags); } GST_INFO_OBJECT (mux, "Event tags are now: %" GST_PTR_FORMAT, - mux->event_tags); + mux->priv->event_tags); /* just drop the event, we'll push a new tag event in render_start_tag */ gst_event_unref (event); @@ -396,34 +433,35 @@ gst_tag_mux_sink_event (GstPad * pad, GstEvent * event) break; } - if (mux->render_start_tag) { + if (mux->priv->render_start_tag) { /* we have not rendered the tag yet, which means that we don't know * how large it is going to be yet, so we can't adjust the offsets * here at this point and need to cache the newsegment event for now * (also, there could be tag events coming after this newsegment event * and before the first buffer). */ - if (mux->newsegment_ev) { + if (mux->priv->newsegment_ev) { GST_WARNING_OBJECT (mux, "discarding old cached newsegment event"); - gst_event_unref (mux->newsegment_ev); + gst_event_unref (mux->priv->newsegment_ev); } GST_LOG_OBJECT (mux, "caching newsegment event for later"); - mux->newsegment_ev = event; + mux->priv->newsegment_ev = event; } else { GST_DEBUG_OBJECT (mux, "got newsegment event, adjusting offsets"); - gst_pad_push_event (mux->srcpad, + gst_pad_push_event (mux->priv->srcpad, gst_tag_mux_adjust_event_offsets (mux, event)); gst_event_unref (event); - mux->current_offset = start; - mux->max_offset = MAX (mux->max_offset, mux->current_offset); + mux->priv->current_offset = start; + mux->priv->max_offset = + MAX (mux->priv->max_offset, mux->priv->current_offset); } event = NULL; result = TRUE; break; } case GST_EVENT_EOS:{ - if (mux->render_end_tag) { + if (mux->priv->render_end_tag) { GstFlowReturn ret; GST_INFO_OBJECT (mux, "Adding tags to stream"); @@ -433,7 +471,7 @@ gst_tag_mux_sink_event (GstPad * pad, GstEvent * event) return ret; } - mux->render_end_tag = FALSE; + mux->priv->render_end_tag = FALSE; } /* Now forward EOS */ @@ -466,20 +504,20 @@ gst_tag_mux_change_state (GstElement * element, GstStateChange transition) switch (transition) { case GST_STATE_CHANGE_PAUSED_TO_READY:{ - if (mux->newsegment_ev) { - gst_event_unref (mux->newsegment_ev); - mux->newsegment_ev = NULL; + if (mux->priv->newsegment_ev) { + gst_event_unref (mux->priv->newsegment_ev); + mux->priv->newsegment_ev = NULL; } - if (mux->event_tags) { - gst_tag_list_free (mux->event_tags); - mux->event_tags = NULL; + if (mux->priv->event_tags) { + gst_tag_list_free (mux->priv->event_tags); + mux->priv->event_tags = NULL; } - mux->start_tag_size = 0; - mux->end_tag_size = 0; - mux->render_start_tag = TRUE; - mux->render_end_tag = TRUE; - mux->current_offset = 0; - mux->max_offset = 0; + mux->priv->start_tag_size = 0; + mux->priv->end_tag_size = 0; + mux->priv->render_start_tag = TRUE; + mux->priv->render_end_tag = TRUE; + mux->priv->current_offset = 0; + mux->priv->max_offset = 0; break; } default: diff --git a/gst-libs/gst/tag/gsttagmux.h b/gst-libs/gst/tag/gsttagmux.h index c13a7326d2..49a0ab912d 100644 --- a/gst-libs/gst/tag/gsttagmux.h +++ b/gst-libs/gst/tag/gsttagmux.h @@ -29,24 +29,16 @@ G_BEGIN_DECLS typedef struct _GstTagMux GstTagMux; typedef struct _GstTagMuxClass GstTagMuxClass; +typedef struct _GstTagMuxPrivate GstTagMuxPrivate; /* Definition of structure storing data for this element. */ struct _GstTagMux { GstElement element; - GstPad *srcpad; - GstPad *sinkpad; - GstTagList *event_tags; /* tags received from upstream elements */ - GstTagList *final_tags; /* Final set of tags used for muxing */ - gsize start_tag_size; - gsize end_tag_size; - gboolean render_start_tag; - gboolean render_end_tag; + /*< private >*/ + GstTagMuxPrivate *priv; - gint64 current_offset; - gint64 max_offset; - - GstEvent *newsegment_ev; /* cached newsegment event from upstream */ + gpointer _gst_reserved[GST_PADDING]; }; /* Standard definition defining a class for this element. */ @@ -56,6 +48,9 @@ struct _GstTagMuxClass { /* vfuncs */ GstBuffer * (*render_start_tag) (GstTagMux * mux, GstTagList * tag_list); GstBuffer * (*render_end_tag) (GstTagMux * mux, GstTagList * tag_list); + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; }; /* Standard macros for defining types for this element. */ @@ -76,4 +71,3 @@ GType gst_tag_mux_get_type (void); G_END_DECLS #endif - From 9f0745bab42e5fa6978bc9841573a85eb41c2771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Fri, 15 Jul 2011 20:57:47 +0100 Subject: [PATCH 041/140] tagmux: const-ify GstTagList argument of render vfuncs --- gst-libs/gst/tag/gsttagmux.h | 52 +++++++++++++++++------------------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/gst-libs/gst/tag/gsttagmux.h b/gst-libs/gst/tag/gsttagmux.h index 49a0ab912d..4677c32f75 100644 --- a/gst-libs/gst/tag/gsttagmux.h +++ b/gst-libs/gst/tag/gsttagmux.h @@ -27,33 +27,6 @@ G_BEGIN_DECLS -typedef struct _GstTagMux GstTagMux; -typedef struct _GstTagMuxClass GstTagMuxClass; -typedef struct _GstTagMuxPrivate GstTagMuxPrivate; - -/* Definition of structure storing data for this element. */ -struct _GstTagMux { - GstElement element; - - /*< private >*/ - GstTagMuxPrivate *priv; - - gpointer _gst_reserved[GST_PADDING]; -}; - -/* Standard definition defining a class for this element. */ -struct _GstTagMuxClass { - GstElementClass parent_class; - - /* vfuncs */ - GstBuffer * (*render_start_tag) (GstTagMux * mux, GstTagList * tag_list); - GstBuffer * (*render_end_tag) (GstTagMux * mux, GstTagList * tag_list); - - /*< private >*/ - gpointer _gst_reserved[GST_PADDING]; -}; - -/* Standard macros for defining types for this element. */ #define GST_TYPE_TAG_MUX \ (gst_tag_mux_get_type()) #define GST_TAG_MUX(obj) \ @@ -65,7 +38,30 @@ struct _GstTagMuxClass { #define GST_IS_TAG_MUX_CLASS(klass) \ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_TAG_MUX)) -/* Standard function returning type information. */ +typedef struct _GstTagMux GstTagMux; +typedef struct _GstTagMuxClass GstTagMuxClass; +typedef struct _GstTagMuxPrivate GstTagMuxPrivate; + +struct _GstTagMux { + GstElement element; + + /*< private >*/ + GstTagMuxPrivate *priv; + + gpointer _gst_reserved[GST_PADDING]; +}; + +struct _GstTagMuxClass { + GstElementClass parent_class; + + /* vfuncs */ + GstBuffer * (*render_start_tag) (GstTagMux * mux, const GstTagList * tag_list); + GstBuffer * (*render_end_tag) (GstTagMux * mux, const GstTagList * tag_list); + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + GType gst_tag_mux_get_type (void); G_END_DECLS From 2633692072cacff72726ec23cd1cab679cf1b047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Thu, 28 Jul 2011 20:38:37 +0100 Subject: [PATCH 042/140] tagmux: require subclass to install sink pad template Require the subclass to install both source and sink pad templates. Also, print some warnings if the subclass doesn't do that. https://bugzilla.gnome.org/show_bug.cgi?id=555437 --- gst-libs/gst/tag/gsttagmux.c | 42 ++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/gst-libs/gst/tag/gsttagmux.c b/gst-libs/gst/tag/gsttagmux.c index ad0e21edf0..0432d07030 100644 --- a/gst-libs/gst/tag/gsttagmux.c +++ b/gst-libs/gst/tag/gsttagmux.c @@ -20,6 +20,11 @@ * Boston, MA 02111-1307, USA. */ +/** + * FIXME: documentation + * + * Since: 0.10.36 + */ #ifdef HAVE_CONFIG_H #include #endif @@ -50,15 +55,6 @@ struct _GstTagMuxPrivate GST_DEBUG_CATEGORY_STATIC (gst_tag_mux_debug); #define GST_CAT_DEFAULT gst_tag_mux_debug -/* Subclass provides a src template and pad. We accept anything as input here, - however. */ - -static GstStaticPadTemplate gst_tag_mux_sink_template = -GST_STATIC_PAD_TEMPLATE ("sink", - GST_PAD_SINK, - GST_PAD_ALWAYS, - GST_STATIC_CAPS ("ANY")); - static void gst_tag_mux_iface_init (GType tag_type) { @@ -106,11 +102,6 @@ gst_tag_mux_finalize (GObject * obj) static void gst_tag_mux_base_init (gpointer g_class) { - GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); - - gst_element_class_add_pad_template (element_class, - gst_static_pad_template_get (&gst_tag_mux_sink_template)); - GST_DEBUG_CATEGORY_INIT (gst_tag_mux_debug, "tagmux", 0, "tag muxer base class"); } @@ -140,8 +131,14 @@ gst_tag_mux_init (GstTagMux * mux, GstTagMuxClass * mux_class) G_TYPE_INSTANCE_GET_PRIVATE (mux, GST_TYPE_TAG_MUX, GstTagMuxPrivate); /* pad through which data comes in to the element */ - mux->priv->sinkpad = - gst_pad_new_from_static_template (&gst_tag_mux_sink_template, "sink"); + tmpl = gst_element_class_get_pad_template (element_klass, "sink"); + if (tmpl) { + mux->priv->sinkpad = gst_pad_new_from_template (tmpl, "sink"); + } else { + g_warning ("GstTagMux subclass '%s' did not install a %s pad template!\n", + G_OBJECT_CLASS_NAME (mux_class), "sink"); + mux->priv->sinkpad = gst_pad_new ("sink", GST_PAD_SINK); + } gst_pad_set_chain_function (mux->priv->sinkpad, GST_DEBUG_FUNCPTR (gst_tag_mux_chain)); gst_pad_set_event_function (mux->priv->sinkpad, @@ -151,12 +148,19 @@ gst_tag_mux_init (GstTagMux * mux, GstTagMuxClass * mux_class) /* pad through which data goes out of the element */ tmpl = gst_element_class_get_pad_template (element_klass, "src"); if (tmpl) { + GstCaps *tmpl_caps = gst_pad_template_get_caps (tmpl); + mux->priv->srcpad = gst_pad_new_from_template (tmpl, "src"); gst_pad_use_fixed_caps (mux->priv->srcpad); - /* FIXME: we assume the template caps are fixed.. */ - gst_pad_set_caps (mux->priv->srcpad, gst_pad_template_get_caps (tmpl)); - gst_element_add_pad (GST_ELEMENT (mux), mux->priv->srcpad); + if (tmpl_caps != NULL && gst_caps_is_fixed (tmpl_caps)) { + gst_pad_set_caps (mux->priv->srcpad, tmpl_caps); + } + } else { + g_warning ("GstTagMux subclass '%s' did not install a %s pad template!\n", + G_OBJECT_CLASS_NAME (mux_class), "source"); + mux->priv->srcpad = gst_pad_new ("src", GST_PAD_SRC); } + gst_element_add_pad (GST_ELEMENT (mux), mux->priv->srcpad); mux->priv->render_start_tag = TRUE; mux->priv->render_end_tag = TRUE; From b83c7e0a0e229c447775a3a17417b65be771241e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Fri, 29 Jul 2011 10:22:26 +0100 Subject: [PATCH 043/140] docs: add documentation for GstTagMux --- docs/libs/gst-plugins-base-libs-docs.sgml | 1 + docs/libs/gst-plugins-base-libs-sections.txt | 15 ++++++++++ gst-libs/gst/tag/gsttagmux.c | 31 ++++++++++++++++++-- gst-libs/gst/tag/gsttagmux.h | 24 +++++++++++++-- 4 files changed, 67 insertions(+), 4 deletions(-) diff --git a/docs/libs/gst-plugins-base-libs-docs.sgml b/docs/libs/gst-plugins-base-libs-docs.sgml index fdceb38539..7ed534aac0 100644 --- a/docs/libs/gst-plugins-base-libs-docs.sgml +++ b/docs/libs/gst-plugins-base-libs-docs.sgml @@ -191,6 +191,7 @@ + diff --git a/docs/libs/gst-plugins-base-libs-sections.txt b/docs/libs/gst-plugins-base-libs-sections.txt index 2ae87c1c1c..dd3e691337 100644 --- a/docs/libs/gst-plugins-base-libs-sections.txt +++ b/docs/libs/gst-plugins-base-libs-sections.txt @@ -1821,6 +1821,21 @@ gst_tag_demux_get_type gst_tag_demux_result_get_type +
+gsttagmux +gst/tag/gsttagmux.h +GstTagMux +GstTagMuxClass + +GstTagMuxPrivate +GST_IS_TAG_MUX +GST_IS_TAG_MUX_CLASS +GST_TAG_MUX +GST_TAG_MUX_CLASS +GST_TYPE_TAG_MUX +gst_tag_demux_get_type +
+
gsttaglanguagecodes gst/tag/tag.h diff --git a/gst-libs/gst/tag/gsttagmux.c b/gst-libs/gst/tag/gsttagmux.c index 0432d07030..9c0d2345f2 100644 --- a/gst-libs/gst/tag/gsttagmux.c +++ b/gst-libs/gst/tag/gsttagmux.c @@ -21,9 +21,36 @@ */ /** - * FIXME: documentation + * SECTION:gsttagmux + * @see_also: GstApeMux, GstId3Mux + * @short_description: Base class for adding tags that are in one single chunk + * directly at the beginning or at the end of a file * - * Since: 0.10.36 + * + * + * Provides a base class for adding tags at the beginning or end of a + * stream. + * + * Deriving from GstTagMux + * + * Subclasses have to do the following things: + * + * + * In their base init function, they must add pad templates for the sink + * pad and the source pad to the element class, describing the media type + * they accept and output in the caps of the pad template. + * + * + * In their class init function, they must override the + * GST_TAG_MUX_CLASS(mux_klass)->render_start_tag and/or + * GST_TAG_MUX_CLASS(mux_klass)->render_end_tag vfuncs and set up a render + * function. + * + * + * + * + * + * Since 0.10.36 */ #ifdef HAVE_CONFIG_H #include diff --git a/gst-libs/gst/tag/gsttagmux.h b/gst-libs/gst/tag/gsttagmux.h index 4677c32f75..ddb5988c05 100644 --- a/gst-libs/gst/tag/gsttagmux.h +++ b/gst-libs/gst/tag/gsttagmux.h @@ -1,7 +1,6 @@ /* GStreamer tag muxer base class - * * Copyright (C) 2006 Christophe Fergeau - * Copyright (C) 2006 Tim-Philipp Müller + * Copyright (C) 2006,2011 Tim-Philipp Müller * Copyright (C) 2009 Pioneers of the Inevitable * * This library is free software; you can redistribute it and/or @@ -42,6 +41,14 @@ typedef struct _GstTagMux GstTagMux; typedef struct _GstTagMuxClass GstTagMuxClass; typedef struct _GstTagMuxPrivate GstTagMuxPrivate; +/** + * GstTagMux: + * @element: parent element + * + * Opaque #GstTagMux structure. + * + * Since: 0.10.36 + */ struct _GstTagMux { GstElement element; @@ -51,6 +58,19 @@ struct _GstTagMux { gpointer _gst_reserved[GST_PADDING]; }; +/** + * GstTagMuxClass: + * @parent_class: the parent class. + * @render_start_tag: create a tag buffer to add to the beginning of the + * input stream given a tag list, or NULL + * @render_end_tag: create a tag buffer to add to the end of the + * input stream given a tag list, or NULL + * + * The #GstTagMuxClass structure. Subclasses need to override at least one + * of the two render vfuncs. + * + * Since: 0.10.36 + */ struct _GstTagMuxClass { GstElementClass parent_class; From e1613349881f7c1dd058b84976912e5086a766cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Fri, 29 Jul 2011 10:23:02 +0100 Subject: [PATCH 044/140] Add new GstTagMux base class Hook up new tag muxing base class to build system. https://bugzilla.gnome.org/show_bug.cgi?id=555437 API: GstTagMux --- gst-libs/gst/tag/Makefile.am | 4 ++-- win32/common/libgsttag.def | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/gst-libs/gst/tag/Makefile.am b/gst-libs/gst/tag/Makefile.am index 9977d66432..1e8acbb9ed 100644 --- a/gst-libs/gst/tag/Makefile.am +++ b/gst-libs/gst/tag/Makefile.am @@ -2,13 +2,13 @@ libgsttagincludedir = \ $(includedir)/gstreamer-@GST_MAJORMINOR@/gst/tag libgsttaginclude_HEADERS = \ - tag.h gsttagdemux.h xmpwriter.h + tag.h gsttagdemux.h gsttagmux.h xmpwriter.h lib_LTLIBRARIES = libgsttag-@GST_MAJORMINOR@.la libgsttag_@GST_MAJORMINOR@_la_SOURCES = \ gstvorbistag.c gstid3tag.c gstxmptag.c gstexiftag.c \ - lang.c tags.c gsttagdemux.c gsttageditingprivate.c xmpwriter.c + lang.c tags.c gsttagdemux.c gsttagmux.c gsttageditingprivate.c xmpwriter.c libgsttag_@GST_MAJORMINOR@_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS) libgsttag_@GST_MAJORMINOR@_la_LIBADD = $(GST_BASE_LIBS) $(GST_LIBS) $(LIBM) libgsttag_@GST_MAJORMINOR@_la_LDFLAGS = $(GST_LIB_LDFLAGS) $(GST_ALL_LDFLAGS) $(GST_LT_LDFLAGS) diff --git a/win32/common/libgsttag.def b/win32/common/libgsttag.def index bab6bbda21..dd91decc1a 100644 --- a/win32/common/libgsttag.def +++ b/win32/common/libgsttag.def @@ -25,6 +25,7 @@ EXPORTS gst_tag_list_to_vorbiscomment_buffer gst_tag_list_to_xmp_buffer gst_tag_list_to_xmp_buffer_full + gst_tag_mux_get_type gst_tag_parse_extended_comment gst_tag_register_musicbrainz_tags gst_tag_to_id3_tag From 7d22d91fca6b9f24ca205eef98031f1ed6a44d33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20J=C3=A4genstedt?= Date: Tue, 26 Jul 2011 16:10:17 +0200 Subject: [PATCH 045/140] theoradec: segfault on 0-byte ogg_packet in _chain_reverse --- ext/theora/gsttheoradec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/theora/gsttheoradec.c b/ext/theora/gsttheoradec.c index 96e12eecb5..25d68dbac5 100644 --- a/ext/theora/gsttheoradec.c +++ b/ext/theora/gsttheoradec.c @@ -1440,7 +1440,7 @@ theora_dec_chain_reverse (GstTheoraDec * dec, gboolean discont, GstBuffer * buf) /* if we copied a keyframe, flush and decode the decode queue */ data = GST_BUFFER_DATA (gbuf); - if ((data[0] & 0x40) == 0) { + if (data && (data[0] & 0x40) == 0) { GST_DEBUG_OBJECT (dec, "copied keyframe"); res = theora_dec_flush_decode (dec); } From fe1758556837d3f6913509e6898040dc35bd4fcd Mon Sep 17 00:00:00 2001 From: Jens Georg Date: Thu, 28 Jul 2011 14:43:53 +0200 Subject: [PATCH 046/140] pbutils: Add SP levels 4a, 5 and 6 https://bugzilla.gnome.org/show_bug.cgi?id=655503 --- gst-libs/gst/pbutils/codec-utils.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/gst-libs/gst/pbutils/codec-utils.c b/gst-libs/gst/pbutils/codec-utils.c index 0dac15394d..282e03ed0b 100644 --- a/gst-libs/gst/pbutils/codec-utils.c +++ b/gst-libs/gst/pbutils/codec-utils.c @@ -712,13 +712,16 @@ gst_codec_utils_mpeg4video_get_profile (const guint8 * vis_obj_seq, guint len) const gchar * gst_codec_utils_mpeg4video_get_level (const guint8 * vis_obj_seq, guint len) { - /* The profile/level codes are from 14496-2, table G-1, and the Wireshark - * sources: epan/dissectors/packet-mp4ves.c + /* The profile/level codes are from 14496-2, table G-1, the Wireshark + * sources: epan/dissectors/packet-mp4ves.c and the Xvid Sources: + * src/xvid.h. + * Levels 4a and 5 for SP were added in Amendment 2, level 6 in Amendment 4 + * (see Xvid sources vfw/config.c) * * Each profile has a different maximum level it defines. Some of them still * need special case handling, because not all levels start from 1, and the * Simple profile defines an intermediate level as well. */ - static const int level_max[] = { 3, 2, 2, 4, 2, 1, 2, 2, 2, 4, 3, 4, 2, 3, 4, + static const int level_max[] = { 6, 2, 2, 4, 2, 1, 2, 2, 2, 4, 3, 4, 2, 3, 4, 5 }; int profile_id, level_id; @@ -772,6 +775,9 @@ gst_codec_utils_mpeg4video_get_level (const guint8 * vis_obj_seq, guint len) else if (profile_id == 0 && level_id == 9) /* Simple Profile / Level 0b */ return "0b"; + else if (profile_id == 0 && level_id == 4) + /* Simple Profile / Level 4a */ + return "4a"; else if (profile_id == 0xf && level_id > 7) /* Fine Granularity Scalable Profile */ return digit_to_string (level_id - 8); From 8bd3bdaf3768b3fb6adc85b3bc681fdce07b786e Mon Sep 17 00:00:00 2001 From: Jonathan Liu Date: Wed, 3 Aug 2011 10:18:29 +0200 Subject: [PATCH 047/140] oggstream: Fix crashes with 0-byte vorbis packets Fixes bug #655574. --- ext/ogg/gstoggstream.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/ogg/gstoggstream.c b/ext/ogg/gstoggstream.c index e843f48721..77f39d44ae 100644 --- a/ext/ogg/gstoggstream.c +++ b/ext/ogg/gstoggstream.c @@ -792,7 +792,7 @@ packet_duration_vorbis (GstOggStream * pad, ogg_packet * packet) int size; int duration; - if (packet->packet[0] & 1) + if (packet->bytes == 0 || packet->packet[0] & 1) return 0; mode = (packet->packet[0] >> 1) & ((1 << pad->vorbis_log2_num_modes) - 1); From b263bacc579f582f4983ad744a82dde6537a88e0 Mon Sep 17 00:00:00 2001 From: Thiago Santos Date: Wed, 3 Aug 2011 13:40:19 -0300 Subject: [PATCH 048/140] encodebin: Fix typo on installing properties queue buffers and bytes properties have ids swapped, fix it. --- gst/encoding/gstencodebin.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gst/encoding/gstencodebin.c b/gst/encoding/gstencodebin.c index b41f29bd51..22a09de0df 100644 --- a/gst/encoding/gstencodebin.c +++ b/gst/encoding/gstencodebin.c @@ -348,13 +348,13 @@ gst_encode_bin_class_init (GstEncodeBinClass * klass) "The GstEncodingProfile to use", GST_TYPE_ENCODING_PROFILE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - g_object_class_install_property (gobject_klass, PROP_QUEUE_BUFFERS_MAX, + g_object_class_install_property (gobject_klass, PROP_QUEUE_BYTES_MAX, g_param_spec_uint ("queue-bytes-max", "Max. size (kB)", "Max. amount of data in the queue (bytes, 0=disable)", 0, G_MAXUINT, DEFAULT_QUEUE_BYTES_MAX, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - g_object_class_install_property (gobject_klass, PROP_QUEUE_BYTES_MAX, + g_object_class_install_property (gobject_klass, PROP_QUEUE_BUFFERS_MAX, g_param_spec_uint ("queue-buffers-max", "Max. size (buffers)", "Max. number of buffers in the queue (0=disable)", 0, G_MAXUINT, DEFAULT_QUEUE_BUFFERS_MAX, From 3687b056c42d86c06f0ba0793abc190db62a98d6 Mon Sep 17 00:00:00 2001 From: Thiago Santos Date: Wed, 3 Aug 2011 14:14:55 -0300 Subject: [PATCH 049/140] encodebin: Set queues to silent=true As encodebin doesn't connect to the queue signals, it can set queues to silent mode to make queue not emit them. Check https://bugzilla.gnome.org/show_bug.cgi?id=621299 for more info on queue's silent property. --- gst/encoding/gstencodebin.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gst/encoding/gstencodebin.c b/gst/encoding/gstencodebin.c index 22a09de0df..e4a5833a50 100644 --- a/gst/encoding/gstencodebin.c +++ b/gst/encoding/gstencodebin.c @@ -1046,7 +1046,8 @@ _create_stream_group (GstEncodeBin * ebin, GstEncodingProfile * sprof, * in the input queue */ last = sgroup->outqueue = gst_element_factory_make ("queue", NULL); g_object_set (sgroup->outqueue, "max-size-buffers", (guint32) 1, - "max-size-bytes", (guint32) 0, "max-size-time", (guint64) 0, NULL); + "max-size-bytes", (guint32) 0, "max-size-time", (guint64) 0, + "silent", TRUE, NULL); gst_bin_add (GST_BIN (ebin), sgroup->outqueue); tosync = g_list_append (tosync, sgroup->outqueue); @@ -1166,7 +1167,7 @@ _create_stream_group (GstEncodeBin * ebin, GstEncodingProfile * sprof, g_object_set (sgroup->inqueue, "max-size-buffers", (guint32) ebin->queue_buffers_max, "max-size-bytes", (guint32) ebin->queue_bytes_max, "max-size-time", - (guint64) ebin->queue_time_max, NULL); + (guint64) ebin->queue_time_max, "silent", TRUE, NULL); gst_bin_add (GST_BIN (ebin), sgroup->inqueue); tosync = g_list_append (tosync, sgroup->inqueue); From 5580dd6a4d0519a3e634fdd6702c3e945cd78a02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Fri, 5 Aug 2011 13:05:43 +0200 Subject: [PATCH 050/140] volume: Update disted ORC files --- gst/volume/gstvolumeorc-dist.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/gst/volume/gstvolumeorc-dist.c b/gst/volume/gstvolumeorc-dist.c index 1028649a2d..3159e905bf 100644 --- a/gst/volume/gstvolumeorc-dist.c +++ b/gst/volume/gstvolumeorc-dist.c @@ -544,7 +544,7 @@ orc_process_int16 (gint16 * ORC_RESTRICT d1, int p1, int n) /* 2: mulswl */ var36.i = var33.i * var34.i; /* 3: shrsl */ - var37.i = var36.i >> 13; + var37.i = var36.i >> 11; /* 4: convlw */ var35.i = var37.i; /* 5: storew */ @@ -577,7 +577,7 @@ _backup_orc_process_int16 (OrcExecutor * ORC_RESTRICT ex) /* 2: mulswl */ var36.i = var33.i * var34.i; /* 3: shrsl */ - var37.i = var36.i >> 13; + var37.i = var36.i >> 11; /* 4: convlw */ var35.i = var37.i; /* 5: storew */ @@ -630,7 +630,7 @@ orc_process_int16_clamp (gint16 * ORC_RESTRICT d1, int p1, int n) /* 2: mulswl */ var36.i = var33.i * var34.i; /* 3: shrsl */ - var37.i = var36.i >> 13; + var37.i = var36.i >> 11; /* 4: convssslw */ var35.i = ORC_CLAMP_SW (var37.i); /* 5: storew */ @@ -663,7 +663,7 @@ _backup_orc_process_int16_clamp (OrcExecutor * ORC_RESTRICT ex) /* 2: mulswl */ var36.i = var33.i * var34.i; /* 3: shrsl */ - var37.i = var36.i >> 13; + var37.i = var36.i >> 11; /* 4: convssslw */ var35.i = ORC_CLAMP_SW (var37.i); /* 5: storew */ @@ -716,7 +716,7 @@ orc_process_int8 (gint8 * ORC_RESTRICT d1, int p1, int n) /* 2: mulsbw */ var36.i = var33 * var34; /* 3: shrsw */ - var37.i = var36.i >> 5; + var37.i = var36.i >> 3; /* 4: convwb */ var35 = var37.i; /* 5: storeb */ @@ -749,7 +749,7 @@ _backup_orc_process_int8 (OrcExecutor * ORC_RESTRICT ex) /* 2: mulsbw */ var36.i = var33 * var34; /* 3: shrsw */ - var37.i = var36.i >> 5; + var37.i = var36.i >> 3; /* 4: convwb */ var35 = var37.i; /* 5: storeb */ @@ -802,7 +802,7 @@ orc_process_int8_clamp (gint8 * ORC_RESTRICT d1, int p1, int n) /* 2: mulsbw */ var36.i = var33 * var34; /* 3: shrsw */ - var37.i = var36.i >> 5; + var37.i = var36.i >> 3; /* 4: convssswb */ var35 = ORC_CLAMP_SB (var37.i); /* 5: storeb */ @@ -835,7 +835,7 @@ _backup_orc_process_int8_clamp (OrcExecutor * ORC_RESTRICT ex) /* 2: mulsbw */ var36.i = var33 * var34; /* 3: shrsw */ - var37.i = var36.i >> 5; + var37.i = var36.i >> 3; /* 4: convssswb */ var35 = ORC_CLAMP_SB (var37.i); /* 5: storeb */ @@ -2375,7 +2375,7 @@ gst_volume_orc_init (void) orc_program_set_name (p, "orc_process_int16"); orc_program_set_backup_function (p, _backup_orc_process_int16); orc_program_add_destination (p, 2, "d1"); - orc_program_add_constant (p, 4, 0x0000000d, "c1"); + orc_program_add_constant (p, 4, 0x0000000b, "c1"); orc_program_add_parameter (p, 2, "p1"); orc_program_add_temporary (p, 4, "t1"); @@ -2398,7 +2398,7 @@ gst_volume_orc_init (void) orc_program_set_name (p, "orc_process_int16_clamp"); orc_program_set_backup_function (p, _backup_orc_process_int16_clamp); orc_program_add_destination (p, 2, "d1"); - orc_program_add_constant (p, 4, 0x0000000d, "c1"); + orc_program_add_constant (p, 4, 0x0000000b, "c1"); orc_program_add_parameter (p, 2, "p1"); orc_program_add_temporary (p, 4, "t1"); @@ -2421,7 +2421,7 @@ gst_volume_orc_init (void) orc_program_set_name (p, "orc_process_int8"); orc_program_set_backup_function (p, _backup_orc_process_int8); orc_program_add_destination (p, 1, "d1"); - orc_program_add_constant (p, 4, 0x00000005, "c1"); + orc_program_add_constant (p, 4, 0x00000003, "c1"); orc_program_add_parameter (p, 1, "p1"); orc_program_add_temporary (p, 2, "t1"); @@ -2444,7 +2444,7 @@ gst_volume_orc_init (void) orc_program_set_name (p, "orc_process_int8_clamp"); orc_program_set_backup_function (p, _backup_orc_process_int8_clamp); orc_program_add_destination (p, 1, "d1"); - orc_program_add_constant (p, 4, 0x00000005, "c1"); + orc_program_add_constant (p, 4, 0x00000003, "c1"); orc_program_add_parameter (p, 1, "p1"); orc_program_add_temporary (p, 2, "t1"); From c2ce145e72fb283c1705dcea12bde47a49a9d666 Mon Sep 17 00:00:00 2001 From: Vincent Penquerc'h Date: Fri, 5 Aug 2011 11:32:09 +0100 Subject: [PATCH 051/140] volume: fix sample depth typo https://bugzilla.gnome.org/show_bug.cgi?id=656022 --- gst/volume/gstvolume.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gst/volume/gstvolume.c b/gst/volume/gstvolume.c index 152943df65..1f40f62d4f 100644 --- a/gst/volume/gstvolume.c +++ b/gst/volume/gstvolume.c @@ -263,7 +263,7 @@ volume_choose_func (GstVolume * self) case 8: /* only clamp if the gain is greater than 1.0 */ - if (self->current_vol_i16 > VOLUME_UNITY_INT8) { + if (self->current_vol_i8 > VOLUME_UNITY_INT8) { self->process = volume_process_int8_clamp; } else { self->process = volume_process_int8; From c87f164eff749e14bc2fc3dce5abed2af807ec60 Mon Sep 17 00:00:00 2001 From: Vincent Penquerc'h Date: Fri, 5 Aug 2011 16:53:47 +0100 Subject: [PATCH 052/140] gstvorbistag: map ENCODER Vorbis comment to application-name What GStreamer calls encoder ("encoder used to encode this stream") is stored in the vendor string in Vorbis/Theora/Kate and possibly others. The Vorbis comment packet used in those streams uses ENCODER as the name of the encoding program, which GStreamer calls application-name. https://bugzilla.gnome.org/show_bug.cgi?id=656034 --- gst-libs/gst/tag/gstvorbistag.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gst-libs/gst/tag/gstvorbistag.c b/gst-libs/gst/tag/gstvorbistag.c index da1a1f326c..8fb2f85531 100644 --- a/gst-libs/gst/tag/gstvorbistag.c +++ b/gst-libs/gst/tag/gstvorbistag.c @@ -98,6 +98,11 @@ static const GstTagEntryMatch tag_matches[] = { * http://mail.kde.org/pipermail/amarok/2006-May/000090.html */ {GST_TAG_BEATS_PER_MINUTE, "BPM"}, + /* What GStreamer calls encoder ("encoder used to encode this stream") is + stored in the vendor string in Vorbis/Theora/Kate and possibly others. + The Vorbis comment packet used in those streams uses ENCODER as the name + of the encoding program, which GStreamer calls application-name. */ + {GST_TAG_APPLICATION_NAME, "ENCODER"}, {NULL, NULL} }; From fa3e246866220680e1d3b7950de8cd0079809997 Mon Sep 17 00:00:00 2001 From: Vincent Penquerc'h Date: Mon, 8 Aug 2011 10:00:40 +0100 Subject: [PATCH 053/140] typefind: bump probability if all frames we found are similar Similar meaning same layer, same bitrate, and same number of channels This fixes misdetection of (some MP3 files that have zero padding between the ID3 tag and the MP3 stream) as H.264 video. https://bugzilla.gnome.org/show_bug.cgi?id=656018 --- gst/typefind/gsttypefindfunctions.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gst/typefind/gsttypefindfunctions.c b/gst/typefind/gsttypefindfunctions.c index 4b6f80507d..5d3d95cbef 100644 --- a/gst/typefind/gsttypefindfunctions.c +++ b/gst/typefind/gsttypefindfunctions.c @@ -1014,6 +1014,7 @@ mp3_type_find_at_offset (GstTypeFind * tf, guint64 start_off, guint layer = 0, bitrate, samplerate, channels; guint found = 0; /* number of valid headers found */ guint64 offset = skipped; + gboolean changed = FALSE; while (found < GST_MP3_TYPEFIND_TRY_HEADERS) { guint32 head; @@ -1064,6 +1065,8 @@ mp3_type_find_at_offset (GstTypeFind * tf, guint64 start_off, * that this is not a mp3 but just a random bytestream. It could * be a freaking funky encoded mp3 though. We'll just not count * this header*/ + if (prev_layer) + changed = TRUE; prev_layer = layer; prev_channels = channels; prev_samplerate = samplerate; @@ -1094,6 +1097,8 @@ mp3_type_find_at_offset (GstTypeFind * tf, guint64 start_off, probability = GST_TYPE_FIND_MINIMUM; if (start_off > 0) probability /= 2; + if (!changed) + probability = (probability + GST_TYPE_FIND_MAXIMUM) / 2; GST_INFO ("audio/mpeg calculated %u = %u * %u / %u * (%u - %" From c4bab487e700d932626d02c3df0bb4fc3b677358 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Fri, 15 Jul 2011 13:07:55 +0100 Subject: [PATCH 054/140] tag: add convenience API to handle creative commons licenses Based on liblicense's RDF files. API: GstTagLicenseFlags API: gst_tag_get_licenses() API: gst_tag_get_license_flags() API: gst_tag_get_license_nick() API: gst_tag_get_license_title() API: gst_tag_get_license_version() API: gst_tag_get_license_description() API: gst_tag_get_license_jurisdiction() https://bugzilla.gnome.org/show_bug.cgi?id=646868 --- gst-libs/gst/tag/Makefile.am | 22 +- gst-libs/gst/tag/license-translations.dict | Bin 0 -> 44604 bytes gst-libs/gst/tag/licenses-tables.dat | 467 ++++++++++++++++++ gst-libs/gst/tag/licenses.c | 526 +++++++++++++++++++++ gst-libs/gst/tag/tag.h | 77 +++ 5 files changed, 1088 insertions(+), 4 deletions(-) create mode 100644 gst-libs/gst/tag/license-translations.dict create mode 100644 gst-libs/gst/tag/licenses-tables.dat create mode 100644 gst-libs/gst/tag/licenses.c diff --git a/gst-libs/gst/tag/Makefile.am b/gst-libs/gst/tag/Makefile.am index 1e8acbb9ed..cf44ddcb9c 100644 --- a/gst-libs/gst/tag/Makefile.am +++ b/gst-libs/gst/tag/Makefile.am @@ -7,14 +7,19 @@ libgsttaginclude_HEADERS = \ lib_LTLIBRARIES = libgsttag-@GST_MAJORMINOR@.la libgsttag_@GST_MAJORMINOR@_la_SOURCES = \ - gstvorbistag.c gstid3tag.c gstxmptag.c gstexiftag.c \ - lang.c tags.c gsttagdemux.c gsttagmux.c gsttageditingprivate.c xmpwriter.c -libgsttag_@GST_MAJORMINOR@_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS) + gstvorbistag.c gstid3tag.c gstxmptag.c gstexiftag.c \ + lang.c licenses.c tags.c gsttagdemux.c gsttagmux.c \ + gsttageditingprivate.c xmpwriter.c + +libgsttag_@GST_MAJORMINOR@_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) \ + $(GST_BASE_CFLAGS) $(GST_CFLAGS) \ + -DLICENSE_TRANSLATIONS_PATH=\"$(pkgdatadir)/license-translations.dict\" libgsttag_@GST_MAJORMINOR@_la_LIBADD = $(GST_BASE_LIBS) $(GST_LIBS) $(LIBM) libgsttag_@GST_MAJORMINOR@_la_LDFLAGS = $(GST_LIB_LDFLAGS) $(GST_ALL_LDFLAGS) $(GST_LT_LDFLAGS) # lang-tables.dat contains generated static data and is included by lang.c -EXTRA_DIST = lang-tables.dat +# licenses-tables.dat contains generated data and is included by licenses.c +EXTRA_DIST = lang-tables.dat licenses-tables.dat noinst_HEADERS = gsttageditingprivate.h @@ -81,6 +86,15 @@ endif noinst_PROGRAMS = $(ISO_CODE_PROGS) +#if USE_NLS +# Yes, this is not great, but it's only an implementation detail. The +# translations come from an external source here, so we don't want the +# strings retranslated, but also we don't want to create 30 1kB .mo files, +# so just do something for now, we can change it later if someone really cares. +licensetransdir = $(pkgdatadir) +licensetrans_DATA = $(builddir)/license-translations.dict +#endif + Android.mk: Makefile.am androgenizer -:PROJECT libgsttag -:SHARED libgsttag-@GST_MAJORMINOR@ \ -:TAGS eng debug \ diff --git a/gst-libs/gst/tag/license-translations.dict b/gst-libs/gst/tag/license-translations.dict new file mode 100644 index 0000000000000000000000000000000000000000..1fa8ce4c0aab003fc9a94d26186d67864de34450 GIT binary patch literal 44604 zcmdU&378b;dFQLUhtYjmlI_G-+1V^o#Nc?dx!0>8fn`|;;YG-ilB`lQUCh*Uch$J7 zdqy*UHem)N0RjOMNFZ^I4B`;SAOzwPc(=jp^%Fb8ZnA8SO?ExZ00PCio;=BJ_R0R< z@2l#rKBlJ!Avt~|psVVuufFp=|L^-fFn6|LEih*APa=~thF=`sJp3v@edB?-v(39^ z+rI6ZGkUCyIU{qok?8AqGS_2VT=>fQH9JP1-aY#C{;}tmjO|`F_S(j=-6zbuc`D)W_ej8HdtC33OW<{;${JR?UZVc#V^;%n@Jfk>(oJyQoe4w z*XCp#n(mdi-EqrXZu?JK*`VGwr*J?fBhppJ4Lj)@&6I6RH(oaLR7F{{RPh|oj8>dC zW;i)7o6EP%>SGY>Y_5-$V71J)+*w(RCs`-!q#m$Z8M+0IF~`Z+g{>{K95a*8=e(5f zSS_=<3R_cFzROCd890}6S!1r1v$L*~E9^8|%(<3tCa2ENIcdkYWZ6k`f;qLr=eGH< zmvk05ZK{NkXiuxrEbO=2)IyNal(kw~3gyGuVH;=H^WV#7*Pb10IlGgeoA|lYJiFoS zDwE%XJlb(~eaqQZJR0QD4wE0R^JfPayUep&&c1N=C4FHeI(cYD_mAy)%}As?%a8+?v_gj7QXxDJMSJ| za(VkgO~f{`Dc@u_zQs0N$Nps_o>^GAcb^$Pd}dw6?j2smJ40;Y0Uj>om+bmA)O2ik zND;-kHhVIabF$U~%Wb(YpR!!FyjU>lmPZ{%?om72>f|iei<@RkS{c9F&SvbikQJdh zxp0w`h)%mLWXJxREwqXEe9w5)w_B}tD|dR(Ynf&GcDrZxTINITJ$9?t^62S7+w?7S zft~ZxJv=j-y3)q{KG$*`CW!@{^>aN|%ly;Fd!ZL5clGY>?R0H5%lTZ7mn|IVG5f5v zefqfD+ty(ux?~E4ggS-og>QN-^L@{pBR@*ZQ!Y)*^q^I&eMKXZ7R0hE_hbWa&&_&H ztFD}v&8%&uojyx_Os-|&`_WzOr;WK;N4nKXx4Kogx~NXO7w~+Y4 zR+qPu5YD!g^quVmm4^sIIW0{CbHW6mRIoJT{5x-su3FI|>u>Ed=MKLzws8BocTU3M zTJ!=OJHF=pvFAhXH@f{rN{{VW&#DdH8QZppcf!(Zj+}dEgVEfVH@<3hbkQG2i@}?LhR~VHIYcnaY~&?AEpp#G?Cb+0?L7*|jy9Y0~laj$*rJvR0>1y!!mK zvQPEdX|Z&X_vxXSfV0ka+H4omC>S^?U5~eh#Wnjk5o8K`y0TuI2L~UI(avRJ?39(u z-)CnTr7|vC&RAO>rD!hSjnp%F4x7zc?ZQrcc(0puWMTW}gK%i*F*gfa%=FX;lk70( z>v_h00`6NJ&R{_`&U}S0?}Fcc(}dTqIlE>8PCL`-l)c~OB~4a{lQHw%oi_h62KD)F zVMTkdV#7HP{@dExAm>$s(~W3rBF%WFpR;|4d&Wy5-;KwC&mUSpw)Esgym#gL%Nvhh z9vpgi|67_5zxVQzOB>(0ay}d(5b)m3idd{W(=Qx(_?wW4W88UDL~2x&y4-j!?9@Y) zcPw|FokfmokRzv^&J>iQ1}8==spQ4ndf4koTgU-Iz9+?q=U8SA#w(;j{^_x@c{gP> z$cfcbMfB_tp+D!DUBYHEwx6-gwCwJ}cKX{OTNWBhMW*ZdCIl|SKgp`fnj7NA3|yxz z>{^z#?OG{x27b4DolZ^OjPO+C&Ae;PhD}byl3zVMx@!5@OGieJuSEYmcl3=07<2TU zCC|t^SIV9rcbINiwA(9dgkIfaEH{oA?-{=kdos2nb}IJQv1#$g;(hTyiC>@iQsQ?K z`w~A)+|u-=rtNU#>!*Bi%D|NGPWf2#6^b$%iK5DvO zS4zhxB+rcy<7!nXLVr_A7n%&gZA8O?NHw5zOhn4+Q?Loi)s@mFBk?KY5hDXV+Gm_L z&KZf==VJH8elyk+dp-8;*k8tOj^7iX6SvV9j>Z2pZX|vx@kqi?tVd@!oA~?0&o=#J z1tn5Kr5_k50t-bsNh&v*RhZPqRS(8DlvV2J5&K#V>Q!Yoi<+9M=~<=Gtiq~RgThss z%;;dbQLTdHm08Y8HnmzLa9+9Hti0vbB$47p5x=Z#4N1Rl*=k}Lt+!-QE2Es%R#m*Nu(o2vyx>(+0$wh?5Np{ zu9mE7ruNIWs4@Y+GIXrIV%~|>e76*cLSrE7}!KtFPg|Iw{Mw= ztp-adGniFa(`qsZO>fHVWtBFyYNkq4;uUbfM%diMtgQiFR*!Wxz_6r9rQ9B6JhcS(s^@i>(FFWwJdQJt>|V}c z8sH#T!Zhk|hKX50W7L0qT0bFuzjCTR5#er>Qdd!C_)|!mzchg>V;oXE0nIInHZ@Hl zSIHewMo%ZEpyN@@3CZK+D5B^BbB&zw61IsyH2&WBcwRV0s_9~}L~%WiYo!lO?c-bK!xHt4@pa7a^~~+>+niqP>i8D)dS>#; z*u@(%g*R&Wu4nAN5)*f1!B5hP-I&?>0o(`Uo2eTxNjGkM4ySa|re;}#S8i9Hj3v32 z<@kKmq*B|7UzA_LW#Vwmx<+_FrPx$EU>~j(5jj ziGK$p%XNu|6JJlPP7EdfJaK2!BTb8%HZ&b=`rl2>Q$7|YuVq5yI8tM^WLPN&R!eA*l)aJe8)Izyleb} zac%75vAbjcI`-vQTPz=28ha%+6#I{{KLQ)@x3O#EpNijTJnrSeHTgNy3LtW}nd`7k zARsBzan9SVm<2$r-W)g6T0<8#^ z!UX4%21I9eJ6ZLlhezOj)Nkr{sK3YJB|ER5d-P`umz6?Tn7ALVFv(0^)eVECS|HZ`!GLFyW?7E+JR^^GjIASD{ZE-02FZO*=Bd) zb+AP_KD3RhTr*SHmb7UORq;RWEO3S_j|F@rmvh=X?4;jeF`pFm+JmW=GTe|)*anh} zIokoc($@}_MQTY~`BuFm9<#IEfNd5Owx+2Ic*Hc5Tdi4e6kGtJNScL13l`wEq|dCw zud&_&fQ2s@Mc-6ND1W+Ofr78Nb_doHR&il7zR3lJ<18T>ry!5~^353v_4IkL2^~2q z=>V>g^-TdoswUg+lkKg>qkJt~$7*VvoNs#U1&ZQBWtMtNbCi<-a{R8e#ZACO^1=cy znQF6!17slT2QS`OVm!2!BPdonGsl)?4&j|C(|LIp7EGN)4RbuSpi4{7cOIr z*V@c6fM!bm3bv$g^3XcH2M`nptkZi;xmK&ochW6go^K~D{J_kP?W=_tS%*@Lbx>;+ z_Kvd(87B?%((5qO>Ub6!UwM^ObLVDVO(BUvdrfoMqy*Pg?mbm^{j`mU4YIv*aSe zvB}M3p0Z$M-DDo0{&e$^Y|>(<0&|>Zc4|0nU&=G>b|=kimWJM{dM&3*KGAA}FJtGj zK7rfLR`lM7_poNIP74SGgwp|h0Q8p6NdrAcVA|76x3IN0mn&?|$UA(DE<2@b7Js_1 z)wDa3UYZGldU2)MiB#`fQoEDRc)VlLvUDoXZYXS3l>tz4I)MrTkfdwUw5&0Es<&W4 zmS*X>qMRu}A|@`&Kj4Y7HglQGJ6TO0)XrrmC?cN`g3b@EO-Fb&zUZVtGzne?D1a!bfLF9Fras5K$< zUop`V16YuKGR<6rijqo&*U|+=4FYWFk6)jf!EHTK+%&>k$0KQlJxoQErl~W}T;mooZ?IHK5k(&%PpvYmLMnGyz+KtX{*l23oH%@8an$xw##v z^@}`MceiBFJwHRi+NKvi3W~mq!hT^oI|}HKTfRxac4Sx z-Cov3U{GXE_Bvp;8K&Q7XZudCHoGmsu9=;nf&6?rX9|8C#sR#%!^#x~vtFN<%ORQ^ zVBwl6(7N)7W)=>mUG{~UIlW(`l)Jjp)42+3E$n1>njjK0;GxsG!f{vx#Mm$FBy{3# z+5o<7WYNx%fvqD0$3_+%7#TP+(!ck@j{O(jIB{X&()QdZ$ zysCes|K*YX6Y^-`z)1f_>Ky5RZe(EP$iOmQtZvol%9o?98hw7VZi8y)-jRWQBa3#A z3>+R=v}k0}68VZk&a1EN8tGp>GC<3RM*4S2{qo&cM+RQx(?-G&0R6_dF2AxxrR57RKevTC)c3SJgOXPyy- zg1%VqFN&-wfeElx z(b9xrKw0_M%vdSdj8e?dW{^eOi3CAU5S}GJU9-pO>_ZY$u?mlvKEQRGSOwc8P9awe zU7*Jv0Sp-+?DP;sJAJZ|63kY*LU9RQV53vYZSS!Fr?z{o6oCp$X_pcmj?_x2v?V?C z`J@VU$n_KsWa*LwFZ3!3)YjK~>RGb~aoO_=2U<-J<>&OPY0vG2R`ub?eN<_E zH7z3TB|SzLy&%aOp=f7X5vCLBaM~X{Q_5=2jq)fYFZc=-)RdZSpuK^FnJ>PmC1#Ck zGV_R|R;#ET%!QD3dg`L+$-0DkJ6YFmXK6{O1#*)s|3li5!ct5QVy~C>xDJX!lcgZT z?_@nh_$0Dc+HMQk2BS(a2D&K9CQ5*4nIP!9x;ngWP^zMrnOR zxPh9QGyTF=I07qD-o^4D&gQX!9iu0ozBqVnZ28jjPZOuH0qQum?tSI_D~kh^=)NOk8+KE2^u_)QCttlVxLgN(DAK4S8g&#SuZ}+55AB8@@cZJk zD>Ts`Tl>ulq7TI%efB7|Y64A7Vn`fX!8d<+eEEgf-Y8W_W4A1+Xl(K6*wTd+jpg-o zCpVs7yB_MVJ4cG9*YYENhLX`@Dzvz+OEjPzQbtejgQ7Q#bWWd;$KQ^PE2hYz;OB1DTk)q z+x*APo3HV&y?pIAuDj>@eb>KrL)VR8xarlKZoK(-Z~lXuTW@*mmY=!x8@GP{*2TA7 zyzLS68W=GGJ;ZE|5{6YA<|A&d$04c_3@!6J$HMv(1YK@{vI-GDtmx}fEHGGKbeIS< z^>7ePImc>KSbtUv>c8!Fdubrv(AJzXfy_^KJ1OG!+I_&f*ek?lF-tz#mP%uU@KGAL zP5g)L89;Ed&Z(sqA{Ax5PNhQY`jN@?2dr!s1p|PD)@q|qXWD-FG*C?ET7^Bnezojj z-gp4I4h4reCq>apqD6?hMKq!X9kx!9pRhB)vdz4iwGh!d2_-@>`;y(6KD|1LU`DuD zUtzG_}O&R$1<@2tgd@wzaP7V#dT`@f%tF6{rI!-P4Tzmw;RBxj3r)o43H+1Vnf~U%(jOzqURiGeS8h5RR5vNv5(G*%98da}}!QLw& zrzA2_@!9OmFcz`Qvl^{Tv;4}KC&GrQBM80?i_~_lmIo5oajPY@71kvldL5M0|ETekM?mU$9)WT|29V?RM`iugpZAQ2yI zB)E41?`t#~zYK2JG91|-u#NhWL=};|b8E*ARYmVsIAJTJgIcN`u{A?->pEj=gyDu$ z-H1!J#_NqcW{c}Vh1%j~tsC4U!L~(mMzAgAnd~5-r&e(7xGq{9s531``-AI@m2A6;{PiiOMEiHZo9IK zc9XDcg|Y@@v7(GPF2ZGDne)VI4?FgP{vpJLO;mSiV~N0q(o<&W{5eyJfqvUX8;NS?hszoo4EZQYM28Ko! zVZGuKYt#|t`!3SR%Kk~tmGU)KMi{GB*_l^Fyp~nANLCrvigId*ILB7Q1(Y`L&!eC} z%5RH&0g|UcdW14B$q61!qx~tpUR3;fWho1^)?P*X$VN~$BueC~CRips-jc9mk4Csb z!T`17f(Xa)UMa6!0MX&fMc!u3mOmGDwSt6ImOzS((y`H%FO6+^Tc-*bUAATP_|fxA zmlWknl}$j?u=7igj4s(G#dmyD378EKulXtDPNYxEe9&i<^C)n0yi}gJLn3#K-!}dO zIDr3Tj2RN;b3ge2mc+Kl{uFKP)A4)bDWuPD$A1|AWa54ze6}RMmH0v8+NQg}^?fqf z8o}lkXMTic6@&WA*H2{_PJP0ol&u<(GRg&8PFs{4G@KJ80?0>M-AH_R;>u`flBTAK>>5XaN91=d@}R!Hfa$^S!AO>hu`Y(@KAz#$Vg(C(ia4_lM2GImF$xAAmYbJr?zn!h*^$S3r>Y1m*AN~VFpn148GPaj8I;QQVo{Q$S{uvZG=XVe z=gkUj>KNlC9f-0ps6-(O@}mswZ-MPugsDCN{D?0A`~EV@7ESVE;0NGCk(EGwR0$EL z-qxwP#2cW6E3{^1!pB9RKQDNopa~k~qwO3DOLRi%*yI4yMZ*kVr5GsjK%AIv4!>aX zr6H^(lHP=l$Y09Cp!y)Lg>l}MqSStz=ZEBn?waP!ClCo`G^mftbq6a`kYOkj&jYff z0Gu$IiD%>;Mn}0t`0iogPk@;M>kT&!o>?MA(2n?A;KOJH>tuXzU?;dTsRKYfk;pCW zpXuvU{+9hd?Z<2sWD}bYl+*3Z}3vJq`Czl073$!PY0Bi3Npi zLP~Tq$@OLePhc+x_VN!KlJ!Jhr@t|iV6eb&sm-sEU;##pgis2=1*9a2fggeaB#T}E z+0x`irK7XWCwef_Dbm#=HeeSE0F(v<7#(+UImn!pL(Bkn;WkznG1G7B$T|xG1lcUd z?O}Wr*o+xAlJ=8U4#RV01sbFF*!#w(JqW%movQ^a>{^gLMWXN_0Aoly zR;NZk0sAZ&NK?2bl*|G!0$*G}8u>#6p&@>B|odWhjD+0_q@p0h)rS(n7$r zP(ZT9;rhhC8nA&Rgge3q_)VY0&n`GTK`PQUuT`?E;rvRgjyU8!!uzQSC@HW26G1Vw zWqDTVcGLw-YjRmZ;|Xl848$w0;11;r?ltYsz!%Jjl>odNI4M{@MX~uH?qY|_ux~EX zrSQ0*%R%4Ddjv~fn*9lTF(EK?8k`mb2z*Kgn(p`@ z|Eeabyd#sJo?(mr~H4E5WxY+He}eo*jCe3}aMY4?GLR3MZrY1vqW$X4c4>@I#= zg54BgKpMIe0eXu35j@s+k^gOc_ofXxRPZKO6U#XrZqNLnABDJ0L;(PtCfOVn0u*E? z08yb1VOmz-p)3td0q@L{w3REcp0rPEBPD<-Kr^W-2Y>?U3`xOPI8i*t3X%HH753>) z1xkYT3+jP5NccZsh^cCi2}P{Lm;z1}a)^YiXks873sDr@DiB!ie?33dNke^zF#y&h zAdFlXL=Yyi6`~)4U`3b$Pl~wz&l4B>qZuH=F{+@oY+)Ix9}DE|k7m@$mQ}OyK|y+> z;sfd*x}*{x6su~lCq6hzdH{*u?L}pW!hTqDNpvV-_qF7p(HFG=y8`9ifk_cR0?C?n zp60U~x0ES)z-Hq}8R!wp5@}Os|q!w)5g@u7M{9(C5Lk zukyaiFHHp+;w+zCPXz*1Hhwf%W#nH>d!UsC;0(Hu_#41&z}qD3L6Jns%|oH?EWto6 zrstD=Q}|!f12HK(TcN2{4#Q*swGPznHqeI}(OiTuBU#$X!p?Lr+!cj?(DzaRlM93V zL`g?#JH21uPV=lVm=oJww$t|eG(wr{RyzZN5csn^CNKa?1*jY=sc#5Y9aM9N<@dS3 zr3(jA;3W}bQt<6GnHAgkHb_Tsdf4~EQaBAd3xg?nQ8xk9v>^Dp1vn&8Jn%rowp8Ib z&}w*j*as{hRu|zNycBjYS2&QS(?7lt>S~O21o;gVdns-ipiU$@1$aF&@B#>I0a62@ z?q7Uy{i*<{T?Gf%Y0A&Px)*HTNdI0sQ1a<`gy8;b9xZ%>Fh3_o} z+kIg1rIm{>Z(L_yUUuZt0O?(Ug%7<-ZgunW+GPUvesArDYUH@i#cp2Owe#|p{>vMV zUm8?}1aZE0i+Ono)xG@8S2bjG;eGRG500 z$Bqgs#aN4d0(~4ZDXKH?2}o1DN4#ROC?*DCLp$@SYDD~WT*o>BN`Ho=A)s@hUx^-Ph>Xt zi!D*rg`TW(hYz+`<>BcghCu!lzs_gN{5sSZX^D;!=&)NEI2Fj674xSoAb|=z49XwO zF>6GoPAoHo6#`)vWHP7%0gM8++$|>B!F9~C#-|Q3`;vJRn0z;wcr?ZVo~MK<0XQW zsYu*kQgcU-ZbMEtT$PvTg=oKYh{9}{ogl)%G6_Hcluaa}>-OSy<0JIZBWl8dA%lGF z@_LA8YU^-GRxP)@B#_u_pIi`O)zeEzaIY#&sD;-l&%}cq_0kbt1)h zS;3%?1IpUT*PHW=KF{)5H}k^%9j4 z+UAtCDZrGxpKy@S0ysf1$cNf3LBGr_kIr?PySetTGR{R!=Nknz2O5aNp zNE}3_bO#GlP&yKzLIud`Edug}vYcAMMiyyZ=8g_%OrnZDX^V>Lzm*Kq5JLgnc2XK7#UU34tZ@2(m1R?i1)fT~ zqwi4eQ0Vf)R4U=g>an=_q%*8pXs3EJQezMTgUg-9Vx`)KS6`tnioq=sp|ba|j!+rh zx?*(6AqhfUxtg#R9hP`u`x1T-8z^Ck3;V~`oETfTMIIeFIrhRH9V)|#S{F_}d+y}m zSpUBB@2oz5Y{TfP)g0(`?)b~+j&G4-VZaRGCgygpwg5S%44@#==(jBXezHIwfz87fBFNED5V z?V-Tvs@F%CJ{yhF(d|7yaFhlD+0qTv;Ff5Rj|R0A<816qH;jP88-yLOp5S4AW)y zy1NPqnWI^l-4MkV&B6@wqI6st?ViIPORCe%6f$>?_Xr|^tp_p3$!OKs+`5^Sqi2v+ zW>S8iDU&BmlC1YpBT11(^hMS!Cfj5jW+4_MPAbV-A1|h$gX84etbL@Ij?@xaN((-b zL%bx}am68GQd&+6n_T+MS`~>%Q0`d?q%N+XtynOR*kzGpO)&#f=Yr}n@B2fZzWa4fi6J-W$Et~n74xTg^u

ttAQK>~ol<71| zI5Y-0l6GCgfa{V71q3dm0tyFQW7}&i@T0*tFFzh|oE=@gcay@dg%MrZMm4H-rHTWu z+J{Q0jiw9_Wx?nsD}sn??JI!}|D5q4A(=U2iLt>LQo!LK8U_jRrp4yR>{u@c@@!%g z{nyyJ*gwW@kAFV?mH5K=3-KNCBk}LW-;G~4Sy+)M4jPIh@L|tQ_X1@C3I^*)Dii|V zR?;Q_0M^;{#E&8fVm+og0Eg(HJ}zg+u_sfgQ;%yrHZ* zlfwx^_rs5^*|eV1K8|AZPbmnB{=+d9K7(Em0}pXYCu}z_{DkDR(KPTxQ|+? zR;9v6iG@z(6fC9*6!jC89PkT8gZ<7gjhLE>LZ5HXqt_!JqYVo_>Vw71Dhn~kT(TR? zJiuDOuL@S1Vf1O8LeeS>?SibG!J7wOR@`KgH-d{I6jyJS@AUN)`{Mi&Fbmn4y(PzNoWsLY6%Hhhr3>^CtQ2N@M$6ORdJsRcvY6r)yWCz zdn6~q#}TBGjF1^1O1e=3s4I~dbvqCzgy)G^Azlu#hN))B5fbCz`p_jN1G^;a57rNb z1RSqj(|`eLvRA)dfzmxryQ*Q6k_NM8E?2azt2<)Svhk6SLcbv{B<2jzs^Ve_?V4nh z3Lud<3nFkxW`nh)rVZ-D%A^J>TVxPrBc-Pp!+D0;3oHvNVUL))uz#h=8=1!&0|^GG zAot!!mPv&~#I{QACIv&x3HUHDW8?&}0kY9av5F=MLhQslsFF!elv@&Cre|qqnw$!s zv&f5=BV7({B2+=E9AtkbqX9WzIZBlu(wYw*OsI~vRXAvU;;o?igdHl2x_|@)F{pK) z!cIAg6FMOIrrO*#NLwvpDe z&aMtp%=hY$u3!VGRgddb9Yh|;MGU3zK}ESZ<5i}3BFxt+!p3(Bf~yhKmkC~~4ED7{ zCw>%wn4qozU&)EW0hVhM_ke$y)W1Pzjtmk|0te&P*;SHSaG05kQvY1zNA9 z4P7F{fGObD7J}Vh3Q=H!C|gbGLH<)ub?(bG(&?*~dNNEpi7ATIaIWq20oW42mFtK{ zvWxKgAU!2ue%w-p3!)XZb1#B}LhX0`kgB<0Q{y2wF1Rd;u}_K`jK{%toz3 zVU?k-Ad3;mRS6Fzm@ZM2+&01A;x|9Nnyiu_83b8HDztVFq?Zp4o_|PT+*jC{g6_)4 zQi$Sxg*_VNm6Z)Pc?8uPcx-eYN}afj}{=q{9rG@y6)pqa*#>Xvw*w%g1&P;#kp1&?Hl4 z|Gox3w|%64#g*l!e0ONW7osV2auO{*_2-v91_=dCyp4%$N zB!~rw{F9_0Q4&}gv?nkQH3l*gX}uj+T%|ck^nYyx!cf&!#0^RNuOgaf0VyXKVz38Y z?z;M}vRk-eq-(SSR-cG;MXOd(zCS2M8~tPx!?&+=<6^_h>2rQpF8&S2L$fWn2ym%9@Mc7yI|Nba0NfK403Vdo8Vx`TzTQ-kmPwCAVarK zmpZ?M+9(1Y8bxl@z-^#yp%11yye`ljKo>1?tGYQ!unN>7-_;@Ot}G3!n2$O*K?4(K zi1#4)062#bs#HEr5MW}P@qc5l#ht`wnqF`E#BwddW%mfCb8kX|N^MguPT~gGlEkwzXNMBSN z)R(gxl-}_bved%|gii4;vi?JbLwzZc%azZKYUN}FPr64N`KFS#SC1@uNoCJkq!Oo- zJ!|R6qCv?6zvzJWx-CJ_&^a;`uv{?OlJ3-(wh55g>ajA4TdRiK0F8t369#x+J`K86 zSP%eZ{^7(K3)eYm2e8ENp&5}efFhRnfsGH?YV_&-7hij3bo1uW^%dOPvW|P^U1jrl zw|0eLBzIE4epp?zGC+F>P*=wSy5S8G#Fkl4^p0}hNfbp1YA5j(hRn)O>DcY!-I<~LvMdv*0s;a& z%9^Fy7}CPqwKIO!%JKOq5wW>^H|tQP<}KyyEZQ@KgA#B=GBkBwNO~%{e#xPZ5k#%~ zVnc(G3bRgC9gA2$`DX2Wo7D+6qe(UErqYbeIgt_dvt`bOw}N^GK#$A9&Jsqew#fsP zhZ0t$znp{6Cc+F8D*u3S&bujq$V!L_N2ZaimW`*dWEpCJDAE&rnk)yzO?7bwMJKxk z8Gz?jF-c|FX@F&i29`Z3C84a!F!>Qtcsu zzZd&6PG)P4{~WI9C*tY&()gS4Kac-Y{KmvjCBB&WYT|~896g~u7a3n$s3KnFz#+|j z<$`9E101)Y?Mc**6x1dgHRrU#zj~ZphbkxcCDj~aHBKs`gvEaiMFkO}@>a<~aT9x~ z0}pkjQ<-*BsY+IbpAYXcvKW#Q;5(qZpz3iq>S%4mT5&+@-NN3%^DJ=;;@KAML^-GV z9TqMono{YpI9~{FwWRm?VEoh*`K2f0_Z2#-ozlvud_Xi?ZmG;nWMCB6Gi-_ERT3B^ zsxp5`;FES;6J<0_JEPweueD;<5*tODC(28pkEDN+>{&$lNchtcxh>x$5UGxLz1S*M zJaCKa<1`{Ut{C9@KI?FKl1vOmfoar>R07ij7d6=<1vH_K$}8;PGz;LDU8d2_$W_gHL7;iU#Yuv~J??yh$aTFo9IFL(3 z|3LiiOv-Pahe_<=@(%Co<&L{VE`dELn(e0u9gx$@#E-&V;#uJOSR#YsDua}-E0ab| z=w2=!g4Yf7quli?-acZKsQWQNc*sdx?O`+8ND*)9Tu2xc*gHH^fi(@ehQldgCt^V@ zdUqqlqKKLgKG??i6XzjhpfTFem~%MN5JnS_QEk$76M${~s3c^BBs&-()Z!5Fo>fO!F@I!fN{W|uj6QOoMO-Sby=D->l*ssY zl`9rfEQRHSzOX5J_fVy7i+D}7f3uZ{4840)e39NeJInTt93sauCEh6??v{qq+gzP* zbA$^AeX_laU$5^?eg9uA<(y#~mwmNCPifc$jCxhBA3b+|9y4I!D8P)shoq)~Ay%$X zO$>yNi3661YzeXUI2na>1I&1s+9mW|;+p{6UwOcu(^q!H9*$#@v82TFDa1*9j-+WJ zqZn7oJb&qYEYP$p|4T%-%HE>vFLnH(;>YAZ6vu=q;k7Ea-d$s>52GYxkPlL zqS95(cZ2ixQUVVu9i}H|xjagBP>ZmfWMpBrH{uNq#~&J}(mCO&3;-xa7bV%O)WLdU zsbXlzea(hM;#P;_XUHrTp_qeVu0<-Sgp)5664s6CX8IU7sm(T60D1&83tNhVkXA}8T# zm#9jsQFd3AyC zg~&6SB8!z)9#e?tU7njiwOZoebUuoSQ%Y#(CGT=F4i<*+6h789vIzMENj1TdE&5|@ zL5X3*KT*m;QSObrtLUoaY`we1IoXXJ11Tetgn~h7#64N=j9gxoSz+yn96zU=k#Z0o zN8RlhUB#Vs6(z?u4q|5%hvH$1;R~eXN&J*Gyom!Z1jp@_AFfw^THhPhXYJ{;!7DNx zEF)z~ndeS<xY?db&>;85gZr&VEB+uN^nMHY*&gwJ$G4Aq%Z0ND)oUR1rVL0EXhK#h@>ST zZ-6Pu3!)`LZ=6amP|Aj&>}7MK$I!7%>m~<~#i~7G#iQ@EuPpGkIYhbPP}2ea%F!l_ zh;aNa@&89|Ll@igc-NdD)W0%J|NXhyD)d<4mqkUEO;0ZsC>y8^uqjd;BRq(jsx~I6 zwUpipyxQsHjP*d14Y)w~R9-=84r`!P#LcEr-K95FY&|+|@43W>>5PS;b1@M$j`w%lAAtc%3c7h~taun~D4MAQez_i^5++z3ziBdEWDL;Gge zjEIMDR>i;T;P%3S9?%|XPWV&qGVNERc*zR}3e3T~CT5QCIMhz?lmNWsc0MVIW}VXm z{6f+>wZ4n!dO2Q-4iygYTpc3FkdEs=6Q;-^^@64g83SiCRf98AWwUU`a`dQen z80@Z4G7tDG8kl+?OLE9BIBHk`B046jcF=ia?GOi@94;oh!uU-=r=F-SlZX@#hebt- z7d>pELZJf*_v6%bWoiq0iw~5O1mTNfh-0-$ZYGj?2*>0oLh>*0NV7KKz%Whm`CegU zci1)&j|0yeSRUfFIkqqe>!umXZ&zUjVEi(~YFAqU)B_0u7wEYP?^cZs1I)1mpyV-` zQNP}(_KJlO+ZUgGRwAJfKY!ubU88UB$9<(8YGX?m>TqLm#;sU+VeejTaTNcSI@VC% z6qndW4w8^)?+N{JihtKLq zW3O!-+kFC7tL2D~b9hR{;W;R7^D57>5&3(Z^JwM?_#>25)xkF+#!YZ=jAUJ^ zXqc8JqUXOnEQl9%;EU*39#-NBk9<+CubM}+h_9>OIVsg=qtrS0#=H + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:gsttaglicenses + * @short_description: utility functions for Creative Commons licenses + * @see_also: #GstTagList + * + * + * + * Provides information about Creative Commons media licenses, which are + * often expressed in media files as a license URI in tags. Also useful + * for applications creating media files, in case the user wants to license + * the content under a Creative Commons license. + * + * + */ + +/* FIXME: add API to check obsolete-ness / replace-by */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include + +#include "tag.h" +#include "licenses-tables.dat" + +#ifndef GST_DISABLE_GST_DEBUG + +#define GST_CAT_DEFAULT ensure_debug_category() + +static GstDebugCategory * +ensure_debug_category (void) +{ + static gsize cat_gonce = 0; + + if (g_once_init_enter (&cat_gonce)) { + gsize cat_done; + + cat_done = (gsize) _gst_debug_category_new ("tag-licenses", 0, + "GstTag licenses"); + + g_once_init_leave (&cat_gonce, cat_done); + } + + return (GstDebugCategory *) cat_gonce; +} + +#else + +#define ensure_debug_category() /* NOOP */ + +#endif /* GST_DISABLE_GST_DEBUG */ + +/* ------------------------------------------------------------------------- + * Translations + * ------------------------------------------------------------------------- */ + +#ifdef ENABLE_NLS +static GVariant * +gst_tag_get_license_translations_dictionary (void) +{ + static gsize var_gonce = 0; + + if (g_once_init_enter (&var_gonce)) { + const gchar *dict_path; + GVariant *var = NULL; + GError *err = NULL; + gchar *data; + gsize len; + + /* for gst-uninstalled */ + dict_path = g_getenv ("GST_TAG_LICENSE_TRANSLATIONS_DICT"); + + if (dict_path == NULL) + dict_path = LICENSE_TRANSLATIONS_PATH; + + GST_INFO ("Loading license translations from '%s'", dict_path); + if (g_file_get_contents (dict_path, &data, &len, &err)) { + var = g_variant_new_from_data (G_VARIANT_TYPE ("a{sa{ss}}"), data, len, + TRUE, (GDestroyNotify) g_free, data); + } else { + GST_WARNING ("Could not load translation dictionary %s", err->message); + g_error_free (err); + var = g_variant_new_array (G_VARIANT_TYPE ("{sa{ss}}"), NULL, 0); + } + + g_once_init_leave (&var_gonce, (gsize) var); + } + + return (GVariant *) var_gonce; +} +#endif + +#ifdef ENABLE_NLS +static gboolean +gst_variant_lookup_string_value (GVariant * dict, const gchar * lang, + const gchar ** translation) +{ + GVariant *trans; + + trans = g_variant_lookup_value (dict, lang, G_VARIANT_TYPE ("s")); + if (trans == NULL) + return FALSE; + + *translation = g_variant_get_string (trans, NULL); + /* string will stay valid */ + g_variant_unref (trans); + GST_TRACE ("Result: '%s' for language '%s'", *translation, lang); + return TRUE; +} +#endif + +static const gchar * +gst_license_str_translate (const gchar * s) +{ +#ifdef ENABLE_NLS + GVariant *v, *dict, *trans; + + v = gst_tag_get_license_translations_dictionary (); + g_assert (v != NULL); + + dict = g_variant_lookup_value (v, s, G_VARIANT_TYPE ("a{ss}")); + if (dict != NULL) { + const gchar *const *lang; + const gchar *env_lang; + + /* for unit tests */ + if ((env_lang = g_getenv ("GST_TAG_LICENSE_TRANSLATIONS_LANG"))) { + if (gst_variant_lookup_string_value (dict, env_lang, &s)) + GST_TRACE ("Result: '%s' for forced language '%s'", s, env_lang); + goto beach; + } + + lang = g_get_language_names (); + while (lang != NULL && *lang != NULL) { + GST_TRACE ("Looking up '%s' for language '%s'", s, *lang); + trans = g_variant_lookup_value (dict, *lang, G_VARIANT_TYPE ("s")); + + if (trans != NULL) { + s = g_variant_get_string (trans, NULL); + /* s will stay valid */ + g_variant_unref (trans); + GST_TRACE ("Result: '%s'", s); + break; + } + + GST_TRACE ("No result for '%s' for language '%s'", s, *lang); + ++lang; + } + + beach: + + g_variant_unref (dict); + } else { + GST_WARNING ("No dict for string '%s'", s); + } +#endif + + return s; +} + +/* ------------------------------------------------------------------------- + * License handling + * ------------------------------------------------------------------------- */ + +#define CC_LICENSE_REF_PREFIX "http://creativecommons.org/licenses/" + +/* is this license 'generic' (and a base for any of the supported + * jurisdictions), or jurisdiction-specific only? */ +#define JURISDICTION_GENERIC (G_GUINT64_CONSTANT (1) << 63) + +static const gchar jurisdictions[] = + "ar\000at\000au\000be\000bg\000br\000ca\000ch\000cl\000cn\000co\000de\000" + "dk\000es\000fi\000fr\000hr\000hu\000il\000in\000it\000jp\000kr\000mk\000" + "mt\000mx\000my\000nl\000pe\000pl\000pt\000scotland\000se\000si\000tw\000" + "uk\000us\000za"; + +/** + * gst_tag_get_licenses: + * + * Returns a list of known license references (in form of URIs). This is + * useful for UIs to build a list of available licenses for tagging purposes + * (e.g. to tag an audio track appropriately in a video or audio editor, or + * an image in a camera application). + * + * Returns: NULL-terminated array of license strings. Free with g_strfreev() + * when no longer needed. + * + * Since: 0.10.36 + */ +gchar ** +gst_tag_get_licenses (void) +{ + GPtrArray *arr; + int i; + + arr = g_ptr_array_new (); + for (i = 0; i < G_N_ELEMENTS (licenses); ++i) { + const gchar *jurs; + gboolean is_generic; + guint64 jbits; + gchar *ref; + + jbits = licenses[i].jurisdictions; + is_generic = (jbits & JURISDICTION_GENERIC) != 0; + if (is_generic) { + ref = g_strconcat (CC_LICENSE_REF_PREFIX, licenses[i].ref, NULL); + GST_LOG ("Adding %2d %s (generic)", i, ref); + g_ptr_array_add (arr, ref); + jbits &= ~JURISDICTION_GENERIC; + } + + jurs = jurisdictions; + while (jbits != 0) { + if ((jbits & 1)) { + ref = g_strconcat (CC_LICENSE_REF_PREFIX, licenses[i].ref, jurs, "/", + NULL); + GST_LOG ("Adding %2d %s (%s: %s)", i, ref, + (is_generic) ? "derived" : "specific", jurs); + g_ptr_array_add (arr, ref); + } + g_assert (jurs < (jurisdictions + sizeof (jurisdictions))); + jurs += strlen (jurs) + 1; + jbits >>= 1; + } + } + g_ptr_array_add (arr, NULL); + return (gchar **) g_ptr_array_free (arr, FALSE); +} + +static gint +gst_tag_get_license_idx (const gchar * license_ref, const gchar ** jurisdiction) +{ + const gchar *ref, *jur_suffix; + int i; + + GST_TRACE ("Looking up '%s'", license_ref); + + if (!g_str_has_prefix (license_ref, CC_LICENSE_REF_PREFIX)) { + GST_WARNING ("unknown license prefix in ref '%s'", license_ref); + return -1; + } + + if (jurisdiction != NULL) + *jurisdiction = NULL; + + ref = license_ref + sizeof (CC_LICENSE_REF_PREFIX) - 1; + for (i = 0; i < G_N_ELEMENTS (licenses); ++i) { + guint64 jbits = licenses[i].jurisdictions; + const gchar *jurs, *lref = licenses[i].ref; + gsize lref_len = strlen (lref); + + /* table should have "foo/bar/" with trailing slash */ + g_assert (lref[lref_len - 1] == '/'); + + if ((jbits & JURISDICTION_GENERIC)) { + GST_TRACE ("[%2d] %s checking generic match", i, licenses[i].ref); + + /* exact match? */ + if (strcmp (ref, lref) == 0) + return i; + + /* exact match but without the trailing slash in ref? */ + if (strncmp (ref, lref, lref_len - 1) == 0 && ref[lref_len - 1] == '\0') + return i; + } + + if (!g_str_has_prefix (ref, lref)) + continue; + + GST_TRACE ("[%2d] %s checking jurisdictions", i, licenses[i].ref); + + jbits &= ~JURISDICTION_GENERIC; + + jur_suffix = ref + lref_len; + if (*jur_suffix == '\0') + continue; + + jurs = jurisdictions; + while (jbits != 0) { + guint jur_len = strlen (jurs); + + if ((jbits & 1)) { + if (strncmp (jur_suffix, jurs, jur_len) == 0 && + (jur_suffix[jur_len] == '\0' || jur_suffix[jur_len] == '/')) { + GST_LOG ("matched %s to %s with jurisdiction %s (idx %d)", + license_ref, licenses[i].ref, jurs, i); + if (jurisdiction != NULL) + *jurisdiction = jurs; + return i; + } + } + g_assert (jurs < (jurisdictions + sizeof (jurisdictions))); + jurs += jur_len + 1; + jbits >>= 1; + } + } + + GST_WARNING ("unhandled license ref '%s'", license_ref); + return -1; +} + +/** + * gst_tag_get_license_flags: + * @license_ref: a license reference string in form of a URI, + * e.g. "http://creativecommons.org/licenses/by-nc-nd/2.0/" + * + * Get the flags of a license, which describe most of the features of + * a license in their most general form. + * + * Returns: the flags of the license, or 0 if the license is unknown + * + * Since: 0.10.36 + */ +GstTagLicenseFlags +gst_tag_get_license_flags (const gchar * license_ref) +{ + int idx; + + g_return_val_if_fail (license_ref != NULL, 0); + + idx = gst_tag_get_license_idx (license_ref, NULL); + return (idx < 0) ? 0 : licenses[idx].flags; +} + +/** + * gst_tag_get_license_nick: + * @license_ref: a license reference string in form of a URI, + * e.g. "http://creativecommons.org/licenses/by-nc-nd/2.0/" + * + * Get the nick name of a license, which is a short (untranslated) string + * such as e.g. "CC BY-NC-ND 2.0 UK". + * + * Returns: the nick name of the license, or NULL if the license is unknown + * + * Since: 0.10.36 + */ +const gchar * +gst_tag_get_license_nick (const gchar * license_ref) +{ + GstTagLicenseFlags flags; + const gchar *creator_prefix, *res; + gchar *nick, *c; + + g_return_val_if_fail (license_ref != NULL, NULL); + + flags = gst_tag_get_license_flags (license_ref); + + if ((flags & GST_TAG_LICENSE_CREATIVE_COMMONS_LICENSE)) { + creator_prefix = "CC "; + } else if ((flags & GST_TAG_LICENSE_FREE_SOFTWARE_FOUNDATION_LICENSE)) { + creator_prefix = "FSF "; + } else if (g_str_has_suffix (license_ref, "publicdomain/")) { + creator_prefix = ""; + } else { + return NULL; + } + + nick = g_strdup_printf ("%s%s", creator_prefix, + license_ref + sizeof (CC_LICENSE_REF_PREFIX) - 1); + g_strdelimit (nick, "/", ' '); + g_strchomp (nick); + for (c = nick; *c != '\0'; ++c) + *c = g_ascii_toupper (*c); + + GST_LOG ("%s => nick %s", license_ref, nick); + res = g_intern_string (nick); /* for convenience */ + g_free (nick); + + return res; +} + +/** + * gst_tag_get_license_title: + * @license_ref: a license reference string in form of a URI, + * e.g. "http://creativecommons.org/licenses/by-nc-nd/2.0/" + * + * Get the title of a license, which is a short translated description + * of the license's features (generally not very pretty though). + * + * Returns: the title of the license, or NULL if the license is unknown or + * no title is available. + * + * Since: 0.10.36 + */ +const gchar * +gst_tag_get_license_title (const gchar * license_ref) +{ + int idx; + + g_return_val_if_fail (license_ref != NULL, NULL); + + idx = gst_tag_get_license_idx (license_ref, NULL); + + if (idx < 0 || licenses[idx].title_idx < 0) + return NULL; + + return gst_license_str_translate (&license_strings[licenses[idx].title_idx]); +} + +/** + * gst_tag_get_license_description: + * @license_ref: a license reference string in form of a URI, + * e.g. "http://creativecommons.org/licenses/by-nc-nd/2.0/" + * + * Get the description of a license, which is a translated description + * of the license's main features. + * + * Returns: the description of the license, or NULL if the license is unknown + * or a description is not available. + * + * Since: 0.10.36 + */ +const gchar * +gst_tag_get_license_description (const gchar * license_ref) +{ + int idx; + + g_return_val_if_fail (license_ref != NULL, NULL); + + idx = gst_tag_get_license_idx (license_ref, NULL); + + if (idx < 0 || licenses[idx].desc_idx < 0) + return NULL; + + return gst_license_str_translate (&license_strings[licenses[idx].desc_idx]); +} + +/** + * gst_tag_get_license_jurisdiction: + * @license_ref: a license reference string in form of a URI, + * e.g. "http://creativecommons.org/licenses/by-nc-nd/2.0/" + * + * Get the jurisdiction code of a license. This is usually a two-letter + * ISO 3166-1 alpha-2 code, but there is also the special case of Scotland, + * for which no code exists and which is thus represented as "scotland". + * + * Known jurisdictions: ar, at, au, be, bg, br, ca, ch, cl, cn, co, de, + * dk, es, fi, fr, hr, hu, il, in, it, jp, kr, mk, mt, mx, my, nl, pe, pl, + * pt, scotland, se, si, tw, uk, us, za. + * + * Returns: the jurisdiction code of the license, or NULL if the license is + * unknown or is not specific to a particular jurisdiction. + * + * Since: 0.10.36 + */ +const gchar * +gst_tag_get_license_jurisdiction (const gchar * license_ref) +{ + const gchar *jurisdiction; + int idx; + + g_return_val_if_fail (license_ref != NULL, NULL); + + idx = gst_tag_get_license_idx (license_ref, &jurisdiction); + return (idx < 0) ? NULL : jurisdiction; +} + +/** + * gst_tag_get_license_version: + * @license_ref: a license reference string in form of a URI, + * e.g. "http://creativecommons.org/licenses/by-nc-nd/2.0/" + * + * Get the version of a license. + * + * Returns: the version of the license, or NULL if the license is not known or + * has no version + * + * Since: 0.10.36 + */ +const gchar * +gst_tag_get_license_version (const gchar * license_ref) +{ + int idx; + + g_return_val_if_fail (license_ref != NULL, NULL); + + idx = gst_tag_get_license_idx (license_ref, NULL); + if (idx < 0) + return NULL; + +#define LICENSE_FLAG_CC_OR_FSF \ + (GST_TAG_LICENSE_CREATIVE_COMMONS_LICENSE|\ + GST_TAG_LICENSE_FREE_SOFTWARE_FOUNDATION_LICENSE) + + /* e.g. publicdomain isn't versioned */ + if (!(licenses[idx].flags & LICENSE_FLAG_CC_OR_FSF)) + return NULL; + + /* KISS for now... */ + if (strstr (licenses[idx].ref, "/1.0/")) + return "1.0"; + else if (strstr (licenses[idx].ref, "/2.0/")) + return "2.0"; + else if (strstr (licenses[idx].ref, "/2.1/")) + return "2.1"; + else if (strstr (licenses[idx].ref, "/2.5/")) + return "2.5"; + else if (strstr (licenses[idx].ref, "/3.0/")) + return "3.0"; + + GST_ERROR ("Could not determine version for ref '%s'", license_ref); + return NULL; +} diff --git a/gst-libs/gst/tag/tag.h b/gst-libs/gst/tag/tag.h index 8eea4111a3..6d70bef565 100644 --- a/gst-libs/gst/tag/tag.h +++ b/gst-libs/gst/tag/tag.h @@ -1,5 +1,6 @@ /* GStreamer * Copyright (C) 2003 Benjamin Otte + * Copyright (C) 2006-2011 Tim-Philipp Müller * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -544,6 +545,82 @@ const gchar * gst_tag_get_language_code_iso_639_2T (const gchar * lang_code); #define gst_tag_get_language_code(lang_code) \ gst_tag_get_language_code_iso_639_1(lang_code) + +/* functions to deal with (mostly) Creative Commons licenses */ + +/** + * GstTagLicenseFlags: + * @GST_TAG_LICENSE_PERMITS_REPRODUCTION: making multiple copies + * is allowed + * @GST_TAG_LICENSE_PERMITS_DISTRIBUTION: distribution, public display + * and public performance are allowed + * @GST_TAG_LICENSE_PERMITS_DERIVATIVE_WORKS: distribution of derivative + * works is allowed + * @GST_TAG_LICENSE_PERMITS_SHARING: commercial derivatives are allowed, + * but only non-commercial distribution is allowed + * @GST_TAG_LICENSE_REQUIRES_NOTICE: copyright and license notices + * must be kept intact + * @GST_TAG_LICENSE_REQUIRES_ATTRIBUTION: credit must be given to + * copyright holder and/or author + * @GST_TAG_LICENSE_REQUIRES_SHARE_ALIKE: derivative works must be + * licensed under the same terms or compatible terms as the original work + * @GST_TAG_LICENSE_REQUIRES_SOURCE_CODE: source code (the preferred + * form for making modifications) must be provided when exercising some + * rights granted by the license + * @GST_TAG_LICENSE_REQUIRES_COPYLEFT: derivative and combined works + * must be licensed under specified terms, similar to those of the original + * work + * @GST_TAG_LICENSE_REQUIRES_LESSER_COPYLEFT: derivative works must be + * licensed under specified terms, with at least the same conditions as + * the original work; combinations with the work may be licensed under + * different terms + * @GST_TAG_LICENSE_PROHIBITS_COMMERCIAL_USE: exercising rights for + * commercial purposes is prohibited + * @GST_TAG_LICENSE_PROHIBITS_HIGH_INCOME_NATION_USE: use in a + * non-developing country is prohibited + * @GST_TAG_LICENSE_CREATIVE_COMMONS_LICENSE: this license was created + * by the Creative Commons project + * @GST_TAG_LICENSE_FREE_SOFTWARE_FOUNDATION_LICENSE: this license was + * created by the Free Software Foundation (FSF) + * + * See http://creativecommons.org/ns for more information. + * + * Since: 0.10.36 + */ +typedef enum { + GST_TAG_LICENSE_PERMITS_REPRODUCTION = (1 << 0), + GST_TAG_LICENSE_PERMITS_DISTRIBUTION = (1 << 1), + GST_TAG_LICENSE_PERMITS_DERIVATIVE_WORKS = (1 << 2), + GST_TAG_LICENSE_PERMITS_SHARING = (1 << 3), + + GST_TAG_LICENSE_REQUIRES_NOTICE = (1 << 8), + GST_TAG_LICENSE_REQUIRES_ATTRIBUTION = (1 << 9), + GST_TAG_LICENSE_REQUIRES_SHARE_ALIKE = (1 << 10), + GST_TAG_LICENSE_REQUIRES_SOURCE_CODE = (1 << 11), + GST_TAG_LICENSE_REQUIRES_COPYLEFT = (1 << 12), + GST_TAG_LICENSE_REQUIRES_LESSER_COPYLEFT = (1 << 13), + + GST_TAG_LICENSE_PROHIBITS_COMMERCIAL_USE = (1 << 16), + GST_TAG_LICENSE_PROHIBITS_HIGH_INCOME_NATION_USE = (1 << 17), + + GST_TAG_LICENSE_CREATIVE_COMMONS_LICENSE = (1 << 24), + GST_TAG_LICENSE_FREE_SOFTWARE_FOUNDATION_LICENSE = (1 << 25) +} GstTagLicenseFlags; + +gchar ** gst_tag_get_licenses (void); + +GstTagLicenseFlags gst_tag_get_license_flags (const gchar * license_ref); + +const gchar * gst_tag_get_license_nick (const gchar * license_ref); + +const gchar * gst_tag_get_license_title (const gchar * license_ref); + +const gchar * gst_tag_get_license_version (const gchar * license_ref); + +const gchar * gst_tag_get_license_description (const gchar * license_ref); + +const gchar * gst_tag_get_license_jurisdiction (const gchar * license_ref); + G_END_DECLS #endif /* __GST_TAG_TAG_H__ */ From 25f8f6453ea22cfc8e77575f3899acd952d85c6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Fri, 15 Jul 2011 13:14:16 +0100 Subject: [PATCH 055/140] tag: add mklicensestables utility Add (uninstalled) tool to create licenses-table.dat from liblicense's RDF files. It's not very pretty and makes loats of assumptions about the input, but should work. If things change, we can fix it then. https://bugzilla.gnome.org/show_bug.cgi?id=646868 --- .gitignore | 5 +- gst-libs/gst/tag/Makefile.am | 13 +- gst-libs/gst/tag/mklicensestables.c | 890 ++++++++++++++++++++++++++++ 3 files changed, 905 insertions(+), 3 deletions(-) create mode 100644 gst-libs/gst/tag/mklicensestables.c diff --git a/.gitignore b/.gitignore index 005392e43b..78f083eac8 100644 --- a/.gitignore +++ b/.gitignore @@ -43,8 +43,9 @@ Makefile *.gir *.typelib -gst-libs/gst/pbutils/gstpluginsbaseversion.h -gst-libs/gst/tag/mklangtables +/gst-libs/gst/pbutils/gstpluginsbaseversion.h +/gst-libs/gst/tag/mklangtables +/gst-libs/gst/tag/mklicensestables tmp-orc.c gst*orc.h diff --git a/gst-libs/gst/tag/Makefile.am b/gst-libs/gst/tag/Makefile.am index cf44ddcb9c..28e7b09c57 100644 --- a/gst-libs/gst/tag/Makefile.am +++ b/gst-libs/gst/tag/Makefile.am @@ -84,7 +84,18 @@ else ISO_CODE_PROGS = endif -noinst_PROGRAMS = $(ISO_CODE_PROGS) +LICENSE_PROGS = mklicensestables +mklicensestables_SOURCES = mklicensestables.c +mklicensestables_CFLAGS = $(GST_CFLAGS) +mklicensestables_LDADD = $(GST_LIBS) + +noinst_PROGRAMS = $(ISO_CODE_PROGS) $(LICENSE_PROGS) + +update-licenses: mklicensestables + $(builddir)/mklicensestables \ + --translation-dictionary=$(builddir)/license-translations.dict \ + > $(builddir)/licenses-tables.dat && \ + echo "Updated licenses-tables.dat and license-translations.dict" #if USE_NLS # Yes, this is not great, but it's only an implementation detail. The diff --git a/gst-libs/gst/tag/mklicensestables.c b/gst-libs/gst/tag/mklicensestables.c new file mode 100644 index 0000000000..1bd96212f3 --- /dev/null +++ b/gst-libs/gst/tag/mklicensestables.c @@ -0,0 +1,890 @@ +/* GStreamer License Utility Functions + * Copyright (C) 2011 Tim-Philipp Müller + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* mklicensestables.c: + * little program that reads liblicense's license RDF files and outputs tables + * with the most important information, so we don't have to parse megabytes + * of mostly redundant RDF files to get some basic information (and vendors + * don't have to ship it all). + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "tag.h" + +#include +#include +#include + +/* TODO: we can merge some of the jurisdiction-only license table entries + * into one entry with multiple jurisdictions and without the 'generic' flag, + * .e.g. by-nc-nd/2.5/es + by-nc-nd/2.5/au => by-nc-nd/2.5/{es,au} */ + +#define LIBLICENSE_DATA_PREFIX "/usr/share/liblicense/licenses" + +static GHashTable *unknown_sources; /* NULL */ + +static GList *licenses; /* NULL */ + +/* list of languages used for translations */ +static GList *langs; /* NULL */ + +/* keep in sync with licenses.c */ +static const gchar jurisdictions[] = + "ar\000at\000au\000be\000bg\000br\000ca\000ch\000cl\000cn\000co\000de\000" + "dk\000es\000fi\000fr\000hr\000hu\000il\000in\000it\000jp\000kr\000mk\000" + "mt\000mx\000my\000nl\000pe\000pl\000pt\000scotland\000se\000si\000tw\000" + "uk\000us\000za"; + +/* keep in sync with gst_tag_get_license_version() */ +static const gchar known_versions[] = "1.0/2.0/2.1/2.5/3.0/"; + +/* is this license 'generic' (and a base for any of the supported + * jurisdictions), or jurisdiction-specific only? */ +#define JURISDICTION_GENERIC (G_GUINT64_CONSTANT (1) << 63) + +typedef struct +{ + gchar *ref; + guint64 jurisdiction; + gchar *jurisdiction_suffix; /* if not generic (e.g. "jp/") */ + gchar *legalcode; + gchar *version; + gchar *replaced_by; + gchar *source; + + GstTagLicenseFlags flags; + + gboolean deprecated; + + GHashTable *titles; + GHashTable *descriptions; + + /* for processing */ + const gchar *cur_lang; + gboolean packed_into_source; + + /* list of licenses packed into this one (ie. this is the source of those) */ + GList *derived; +} License; + +static GstTagLicenseFlags +ref_to_flag (const gchar * ref) +{ + if (strcmp (ref, "http://creativecommons.org/ns#Reproduction") == 0) + return GST_TAG_LICENSE_PERMITS_REPRODUCTION; + if (strcmp (ref, "http://creativecommons.org/ns#Distribution") == 0) + return GST_TAG_LICENSE_PERMITS_DISTRIBUTION; + if (strcmp (ref, "http://creativecommons.org/ns#DerivativeWorks") == 0) + return GST_TAG_LICENSE_PERMITS_DERIVATIVE_WORKS; + if (strcmp (ref, "http://creativecommons.org/ns#Sharing") == 0) + return GST_TAG_LICENSE_PERMITS_SHARING; + if (strcmp (ref, "http://creativecommons.org/ns#Notice") == 0) + return GST_TAG_LICENSE_REQUIRES_NOTICE; + if (strcmp (ref, "http://creativecommons.org/ns#Attribution") == 0) + return GST_TAG_LICENSE_REQUIRES_ATTRIBUTION; + if (strcmp (ref, "http://creativecommons.org/ns#ShareAlike") == 0) + return GST_TAG_LICENSE_REQUIRES_SHARE_ALIKE; + if (strcmp (ref, "http://creativecommons.org/ns#SourceCode") == 0) + return GST_TAG_LICENSE_REQUIRES_SOURCE_CODE; + if (strcmp (ref, "http://creativecommons.org/ns#Copyleft") == 0) + return GST_TAG_LICENSE_REQUIRES_COPYLEFT; + if (strcmp (ref, "http://creativecommons.org/ns#LesserCopyleft") == 0) + return GST_TAG_LICENSE_REQUIRES_LESSER_COPYLEFT; + if (strcmp (ref, "http://creativecommons.org/ns#CommercialUse") == 0) + return GST_TAG_LICENSE_PROHIBITS_COMMERCIAL_USE; + if (strcmp (ref, "http://creativecommons.org/ns#HighIncomeNationUse") == 0) + return GST_TAG_LICENSE_PROHIBITS_HIGH_INCOME_NATION_USE; + + g_error ("Unknown permits/requires/prohibits: %s\n", ref); + return 0; +}; + +static guint64 +ref_to_jurisdiction (const gchar * ref) +{ + const gchar *j = jurisdictions; + gchar *jur; + guint64 bit = 1; + + jur = g_strdup (ref + strlen ("http://creativecommons.org/international/")); + g_strdelimit (jur, "/", '\0'); + while (j < jurisdictions + sizeof (jurisdictions)) { + if (strcmp (j, jur) == 0) { + g_free (jur); + g_assert (bit != 0 && bit != JURISDICTION_GENERIC); + return bit; + } + j += strlen (j) + 1; + bit <<= 1; + } + g_error ("Unknown jurisdiction '%s'\n", ref); +} + +typedef enum +{ + TAG_CC_LICENSE, + TAG_CC_JURISDICTION, + TAG_CC_LEGALCODE, + TAG_CC_PROHIBITS, + TAG_CC_REQUIRES, + TAG_CC_PERMITS, + TAG_CC_DEPRECATED_ON, + TAG_DC_CREATOR, + TAG_DC_SOURCE, + TAG_DC_TITLE, + TAG_DC_DESCRIPTION, + TAG_DCQ_HAS_VERSION, + TAG_DCQ_IS_REPLACED_BY, + TAG_RDF_RDF, + TAG_RDF_DESCRIPTION, +} Tag; + +static const struct +{ + const gchar *element_name; + const gchar *attribute; + const Tag element_tag; +} tag_map[] = { + { + "cc:License", "rdf:about", TAG_CC_LICENSE}, { + "cc:deprecatedOn", "rdf:datatype", TAG_CC_DEPRECATED_ON}, { + "cc:jurisdiction", "rdf:resource", TAG_CC_JURISDICTION}, { + "cc:legalcode", "rdf:resource", TAG_CC_LEGALCODE}, { + "cc:prohibits", "rdf:resource", TAG_CC_PROHIBITS}, { + "cc:requires", "rdf:resource", TAG_CC_REQUIRES}, { + "cc:permits", "rdf:resource", TAG_CC_PERMITS}, { + "dc:creator", "rdf:resource", TAG_DC_CREATOR}, { + "dc:source", "rdf:resource", TAG_DC_SOURCE}, { + "dc:title", "xml:lang", TAG_DC_TITLE}, { + "dc:description", "xml:lang", TAG_DC_DESCRIPTION}, { + "dcq:hasVersion", NULL, TAG_DCQ_HAS_VERSION}, { + "dcq:isReplacedBy", "rdf:resource", TAG_DCQ_IS_REPLACED_BY}, { + "rdf:RDF", NULL, TAG_RDF_RDF}, { + "rdf:Description", "rdf:about", TAG_RDF_DESCRIPTION}, + /* these three are just for by-nc-nd_2.0_jp_.rdf */ + { + "dc:isBasedOn", "rdf:resource", TAG_DC_SOURCE}, { + "dc:hasVersion", NULL, TAG_DCQ_HAS_VERSION}, { + "dc:isReplacedBy", "rdf:resource", TAG_DCQ_IS_REPLACED_BY} +}; + +static void +parse_start (GMarkupParseContext * ctx, const gchar * element_name, + const gchar ** attr_names, const gchar ** attr_vals, + gpointer user_data, GError ** err) +{ + License *license = user_data; + const gchar *ref = NULL; + int i; + + for (i = 0; i < G_N_ELEMENTS (tag_map); ++i) { + if (strcmp (element_name, tag_map[i].element_name) == 0) + break; + } + + if (i == G_N_ELEMENTS (tag_map)) + g_error ("Unexpected tag '%s'\n", element_name); + + if (tag_map[i].attribute == NULL) + return; + + if (!g_markup_collect_attributes (element_name, attr_names, attr_vals, + err, G_MARKUP_COLLECT_STRING, tag_map[i].attribute, &ref, + G_MARKUP_COLLECT_INVALID)) { + return; + } + + switch (tag_map[i].element_tag) { + case TAG_CC_LICENSE: + if (!g_str_has_prefix (ref, "http://creativecommons.org/licenses/")) + g_error ("Unexpected license reference: %s\n", ref); + /* we assume one license per file, and CC license ref */ + g_assert (license->ref == NULL); + license->ref = g_strdup (ref); + break; + case TAG_CC_JURISDICTION: + if (!g_str_has_prefix (ref, "http://creativecommons.org/international/")) + g_error ("Unknown license jurisdiction: %s\n", ref); + /* we assume one jurisdiction per license */ + g_assert (license->jurisdiction == JURISDICTION_GENERIC); + license->jurisdiction = ref_to_jurisdiction (ref); + license->jurisdiction_suffix = + g_strdup (ref + strlen ("http://creativecommons.org/international/")); + break; + case TAG_CC_LEGALCODE: + if (!g_str_has_prefix (ref, "http://creativecommons.org/licenses/")) + g_error ("Unexpected legalcode reference: %s\n", ref); + /* we assume one legalcode per license */ + g_assert (license->legalcode == NULL); + license->legalcode = g_strdup (ref); + break; + case TAG_DC_CREATOR: + if (strcmp (ref, "http://creativecommons.org") == 0) { + license->flags |= GST_TAG_LICENSE_CREATIVE_COMMONS_LICENSE; + } else if (strcmp (ref, "http://fsf.org") == 0) { + license->flags |= GST_TAG_LICENSE_FREE_SOFTWARE_FOUNDATION_LICENSE; + } else { + g_error ("Unknown license creator: %s\n", ref); + } + break; + case TAG_CC_DEPRECATED_ON: + break; + case TAG_CC_PROHIBITS: + case TAG_CC_REQUIRES: + case TAG_CC_PERMITS: + license->flags |= ref_to_flag (ref); + break; + case TAG_DC_TITLE:{ + gchar *cur_lang; + + cur_lang = g_strdelimit (g_strdup (ref), "-", '_'); + license->cur_lang = g_intern_string (cur_lang); + if (!g_list_find_custom (langs, cur_lang, (GCompareFunc) strcmp)) + langs = g_list_prepend (langs, (gpointer) license->cur_lang); + + g_free (cur_lang); + break; + } + case TAG_DC_DESCRIPTION:{ + gchar *cur_lang; + + cur_lang = g_strdelimit (g_strdup (ref), "-", '_'); + license->cur_lang = g_intern_string (cur_lang); + if (!g_list_find_custom (langs, cur_lang, (GCompareFunc) strcmp)) + langs = g_list_prepend (langs, (gpointer) license->cur_lang); + + g_free (cur_lang); + break; + } + case TAG_DCQ_IS_REPLACED_BY: + /* we assume one replacer per license for now */ + g_assert (license->replaced_by == NULL); + license->replaced_by = g_strdup (ref); + break; + case TAG_RDF_DESCRIPTION: + if (!g_str_has_prefix (ref, "http://creativecommons.org/licenses/")) + g_error ("Unexpected license reference: %s\n", ref); + if (license->ref != NULL && strcmp (license->ref, ref) != 0) { + gchar *f, *r = g_strdup (ref); + + /* work around bug in some of the RDFs ... */ + if ((f = strstr (r, "by-nc-nd"))) { + memcpy (f, "by-nd-nc", 8); + } + if (strcmp (license->ref, r) != 0) { + g_error ("rdf:Description chunk for other than current license"); + } + g_free (r); + } + break; + case TAG_DC_SOURCE: + if (!g_str_has_prefix (ref, "http://creativecommons.org/licenses/")) + g_error ("Unexpected source reference: %s\n", ref); + /* we assume one source (for jurisdiction-specific versions) */ + g_assert (license->source == NULL); + license->source = g_strdup (ref); + break; + default: + g_printerr ("unhandled start tag: %s\n", element_name); + break; + } +} + +static void +parse_text (GMarkupParseContext * ctx, const gchar * text, gsize text_len, + gpointer user_data, GError ** err) +{ + License *license = user_data; + const gchar *element_name, *found; + int i; + + element_name = g_markup_parse_context_get_element (ctx); + for (i = 0; i < G_N_ELEMENTS (tag_map); ++i) { + if (strcmp (element_name, tag_map[i].element_name) == 0) + break; + } + + if (i == G_N_ELEMENTS (tag_map)) + g_error ("Unexpected tag '%s'\n", element_name); + + switch (tag_map[i].element_tag) { + case TAG_CC_LICENSE: + case TAG_CC_JURISDICTION: + case TAG_CC_LEGALCODE: + case TAG_DC_CREATOR: + case TAG_CC_PROHIBITS: + case TAG_CC_REQUIRES: + case TAG_CC_PERMITS: + case TAG_RDF_RDF: + case TAG_RDF_DESCRIPTION: + break; + case TAG_DC_TITLE: + if (license->titles == NULL) { + license->titles = g_hash_table_new (g_str_hash, g_str_equal); + } + g_hash_table_insert (license->titles, (gpointer) license->cur_lang, + (gpointer) g_intern_string (text)); + break; + case TAG_DC_DESCRIPTION:{ + gchar *txt = g_strdup (text); + + if (license->descriptions == NULL) { + license->descriptions = g_hash_table_new (g_str_hash, g_str_equal); + } + g_strdelimit (txt, "\n", ' '); + g_hash_table_insert (license->descriptions, (gpointer) license->cur_lang, + (gpointer) g_intern_string (txt)); + g_free (txt); + break; + } + case TAG_DCQ_HAS_VERSION: + /* we assume one version per license */ + g_assert (license->version == NULL); + license->version = g_strdup (text); + found = strstr (known_versions, license->version); + if (found == NULL || found[strlen (license->version)] != '/') + g_error ("Unexpected version '%s', please add to table.", text); + break; + case TAG_CC_DEPRECATED_ON: + license->deprecated = TRUE; + break; + case TAG_DC_SOURCE: // FIXME + default: + g_print ("text (%s) (%s): '%s'\n", element_name, license->cur_lang, text); + } +} + +static void +parse_passthrough (GMarkupParseContext * ctx, const gchar * text, gsize len, + gpointer user_data, GError ** err) +{ + if (!g_str_has_prefix (text, "message); +} + +static const GMarkupParser license_rdf_parser = { + parse_start, NULL, parse_text, parse_passthrough, parse_error +}; + +static void +parse_license_rdf (const gchar * fn, const gchar * rdf) +{ + GMarkupParseContext *ctx; + License *license; + GError *err = NULL; + + if (!g_utf8_validate (rdf, -1, NULL)) { + g_error ("%s is not valid UTF-8\n", fn); + } + + license = g_new0 (License, 1); + + /* mark as generic until proven otherwise */ + license->jurisdiction = JURISDICTION_GENERIC; + + ctx = g_markup_parse_context_new (&license_rdf_parser, + G_MARKUP_TREAT_CDATA_AS_TEXT, license, NULL); + + /* g_print ("Parsing %s\n", fn); */ + + if (!g_markup_parse_context_parse (ctx, rdf, -1, &err)) { + g_error ("Error parsing file %s: %s\n", fn, err->message); + } + + licenses = g_list_append (licenses, license); + + g_markup_parse_context_free (ctx); +} + +static void +read_licenses (const gchar * licenses_dir) +{ + const gchar *name; + GError *err = NULL; + GDir *dir; + + dir = g_dir_open (licenses_dir, 0, &err); + + if (dir == NULL) + g_error ("Failed to g_dir_open('%s'): %s", licenses_dir, err->message); + + while ((name = g_dir_read_name (dir))) { + gchar *fn, *rdf; + + fn = g_build_filename (licenses_dir, name, NULL); + if (g_file_get_contents (fn, &rdf, NULL, &err)) { + parse_license_rdf (fn, rdf); + g_free (rdf); + } else { + g_printerr ("Could not read file '%s': %s\n", fn, err->message); + g_error_free (err); + err = NULL; + } + g_free (fn); + } + + g_dir_close (dir); +} + +static License * +find_license (const gchar * ref) +{ + GList *l; + + if (!g_str_has_prefix (ref, "http://creativecommons.org/")) + return NULL; + + for (l = licenses; l != NULL; l = l->next) { + License *license = l->data; + + if (strcmp (license->ref, ref) == 0) + return license; + } + + return NULL; +} + +static int +license_ref_cmp (License * a, License * b) +{ + return strcmp (a->ref, b->ref); +} + +#define STRING_TABLE_MAX_STRINGS 100 +typedef struct +{ + GString *s; + guint num_escaped; + guint num_strings; + guint indices[STRING_TABLE_MAX_STRINGS]; + gchar *strings[STRING_TABLE_MAX_STRINGS]; /* unescaped strings */ +} StringTable; + +static StringTable * +string_table_new (void) +{ + StringTable *t = g_new0 (StringTable, 1); + + t->s = g_string_new (NULL); + return t; +} + +static void +string_table_free (StringTable * t) +{ + int i; + + for (i = 0; i < t->num_strings; ++i) + g_free (t->strings[i]); + + g_string_free (t->s, TRUE); + g_free (t); +} + +static guint +string_table_add_string (StringTable * t, const gchar * str) +{ + const gchar *s; + guint idx, i; + + /* check if we already have this string */ + for (i = 0; i < t->num_strings; ++i) { + if (strcmp (t->strings[i], str) == 0) + return t->indices[i]; + } + + /* save current offset */ + idx = t->s->len; + + /* adjust for fact that \000 is 4 chars now but will take up only 1 later */ + idx -= t->num_escaped * 3; + + /* append one char at a time, making sure to escape UTF-8 characters */ + for (s = str; s != NULL && *s != '\0'; ++s) { + if (g_ascii_isprint (*s) && *s != '"' && *s != '\\') { + g_string_append_c (t->s, *s); + } else { + g_string_append_printf (t->s, "\\%03o", (unsigned char) *s); + t->num_escaped++; + } + } + g_string_append (t->s, "\\000"); + t->num_escaped++; + + t->indices[t->num_strings] = idx; + t->strings[t->num_strings] = g_strdup (str); + ++t->num_strings; + + return idx; +} + +static void +string_table_print (StringTable * t) +{ + const gchar *s; + + s = t->s->str; + while (s != NULL && *s != '\0') { + gchar line[74], *lastesc; + guint left; + + left = strlen (s); + g_strlcpy (line, s, MIN (left, sizeof (line))); + s += sizeof (line) - 1; + /* avoid partial escaped codes at the end of a line */ + if ((lastesc = strrchr (line, '\\')) && strlen (lastesc) < 4) { + s -= strlen (lastesc); + *lastesc = '\0'; + } + g_print (" \"%s\"", line); + if (left < 74) + break; + g_print ("\n"); + } + g_print (";\n"); +} + +/* skip translation if translated string for e.g. "fr_ca" is same as for "fr" */ +static gboolean +skip_translation (GHashTable * ht_strings, const gchar * lang, + const gchar * trans) +{ + const gchar *simple_trans; + gchar *simple_lang; + + if (strchr (lang, '_') == NULL) + return FALSE; + + simple_lang = g_strdup (lang); + g_strdelimit (simple_lang, "_", '\0'); + + simple_trans = g_hash_table_lookup (ht_strings, (gpointer) simple_lang); + g_free (simple_lang); + + return (simple_trans != NULL && strcmp (trans, simple_trans) == 0); +} + +static GVariant * +create_translation_dict (GHashTable * ht_strings, const gchar * en) +{ + GVariantBuilder array; + guint count = 0; + GList *l; + + g_variant_builder_init (&array, G_VARIANT_TYPE_ARRAY); + + for (l = langs; l != NULL; l = l->next) { + const gchar *trans, *lang; + + lang = (const gchar *) l->data; + trans = g_hash_table_lookup (ht_strings, (gpointer) lang); + if (trans != NULL && *trans != '\0' && strcmp (en, trans) != 0 && + !skip_translation (ht_strings, lang, trans)) { + g_print ("%s (%s) => %s\n", en, lang, trans); + g_variant_builder_add_value (&array, + g_variant_new_dict_entry (g_variant_new_string (lang), + g_variant_new_string (trans))); + ++count; + } + } + + if (count == 0) { + g_variant_builder_clear (&array); + return NULL; + } + + return g_variant_builder_end (&array); +} + +static void +write_translations_dictionary (GList * licenses, const gchar * dict_filename) +{ + /* maps C string => (dictionary of: locale => translation) */ + GVariantBuilder array; + /* maps C string => boolean (if it's in the dictionary already */ + GHashTable *translations; + GVariant *var; + GList *l; + FILE *f; + + /* sort langs for prettiness / to make variant dumps easier to read */ + langs = g_list_sort (langs, (GCompareFunc) strcmp); + + g_variant_builder_init (&array, G_VARIANT_TYPE_ARRAY); + + translations = g_hash_table_new (g_str_hash, g_str_equal); + + for (l = licenses; l != NULL; l = l->next) { + const gchar *en; + License *license; + + license = l->data; + + if (license->packed_into_source) + continue; + + /* add title + translations */ + en = g_hash_table_lookup (license->titles, "en"); + g_assert (en != NULL); + + /* check if we already have added translations for this string */ + if (!g_hash_table_lookup (translations, (gpointer) en)) { + GVariant *trans; + + trans = create_translation_dict (license->titles, en); + if (trans != NULL) { + g_variant_builder_add_value (&array, + g_variant_new_dict_entry (g_variant_new_string (en), trans)); + g_hash_table_insert (translations, (gpointer) en, + GINT_TO_POINTER (TRUE)); + } + } + + /* add description + translations */ + if (license->descriptions == NULL) + continue; + + en = g_hash_table_lookup (license->descriptions, "en"); + g_assert (en != NULL); + + /* check if we already have added translations for this string */ + if (!g_hash_table_lookup (translations, (gpointer) en)) { + GVariant *trans; + + trans = create_translation_dict (license->descriptions, en); + if (trans != NULL) { + g_variant_builder_add_value (&array, + g_variant_new_dict_entry (g_variant_new_string (en), trans)); + g_hash_table_insert (translations, (gpointer) en, + GINT_TO_POINTER (TRUE)); + } + } + } + + var = g_variant_builder_end (&array); + + f = fopen (dict_filename, "wb"); + if (fwrite (g_variant_get_data (var), g_variant_get_size (var), 1, f) != 1) { + g_error ("failed to write dict to file: %s", g_strerror (errno)); + } + fclose (f); + + g_printerr ("Wrote dictionary to %s, size: %u, type: %s\n", dict_filename, + (guint) g_variant_get_size (var), (gchar *) g_variant_get_type (var)); + + g_variant_unref (var); + g_hash_table_destroy (translations); +} + +int +main (int argc, char **argv) +{ + gchar *translation_dict_fn = NULL; + GOptionContext *ctx; + GOptionEntry options[] = { + {"translation-dictionary", 0, 0, G_OPTION_ARG_FILENAME, + &translation_dict_fn, "Filename of translations dictionary to write", + NULL}, + {NULL} + }; + StringTable *string_table; + GError *err = NULL; + GList *l; + int idx = 0; + + g_type_init (); + + ctx = g_option_context_new (""); + g_option_context_add_main_entries (ctx, options, NULL); + if (!g_option_context_parse (ctx, &argc, &argv, &err)) { + g_printerr ("Error initializing: %s\n", err->message); + exit (1); + } + g_option_context_free (ctx); + + read_licenses (LIBLICENSE_DATA_PREFIX); + + g_printerr ("%d licenses\n", g_list_length (licenses)); + + unknown_sources = g_hash_table_new (g_str_hash, g_str_equal); + + for (l = licenses; l != NULL; l = l->next) { + License *license = l->data; + + /* if the license has as source, check if we can 'pack' it into the + * original license as a jurisdiction-specific variant */ + if (license->source != NULL) { + License *source = find_license (license->source); + + if (source != NULL) { + if (source->flags != license->flags) { + g_printerr ("Source and derived license have different flags:\n" + "\t0x%08x : %s\n\t0x%08x : %s\n", source->flags, source->ref, + license->flags, license->ref); + source = NULL; + } else { + if (source->descriptions == NULL) { + /* neither should the derived one then */ + g_assert (license->descriptions == NULL); + } else { + /* make sure we're not settling for fewer descriptions than + * there are */ + g_assert (g_hash_table_size (license->titles) <= + g_hash_table_size (source->titles)); + g_assert (g_hash_table_size (license->descriptions) <= + g_hash_table_size (source->descriptions)); + } + } + } else { + /* a source is referenced that we haven't encountered + * (possibly a referencing bug? seems to happen e.g. when there's a + * 2.1 version of a jurisdiction license and it refers to a 2.1 + * source version, but there's only a 2.0 or 2.5 source version. So + * maybe it's supposed to refer to the 2.0 source then, who knows) */ + if (!g_hash_table_lookup (unknown_sources, license->source)) { + g_printerr ("Unknown source license %s\n", license->source); + g_hash_table_insert (unknown_sources, g_strdup (license->source), + GUINT_TO_POINTER (TRUE)); + } + /* g_print ("Unknown source license %s referenced from %s\n", + * license->source, license->ref); */ + } + + /* should we pack this into the source or not */ + if (source != NULL) { + source->jurisdiction |= license->jurisdiction; + source->derived = g_list_insert_sorted (source->derived, license, + (GCompareFunc) license_ref_cmp); + license->packed_into_source = TRUE; + } + } else { + /* no source license */ + if (license->titles == NULL) + g_error ("License has no titles: %s\n", license->ref); + if (license->descriptions == NULL); + g_printerr ("License %s has no descriptions!\n", license->ref); + } + } + + licenses = g_list_sort (licenses, (GCompareFunc) license_ref_cmp); + + string_table = string_table_new (); + + g_print ("/* created by mklicensestables.c */\n"); + g_print ("static const struct {\n" + " /* jurisdictions in addition to the generic version, bitfield */\n" + " const guint64 jurisdictions;\n" + " const GstTagLicenseFlags flags;\n" + " /* the bit after http://creativecommons.org/licenses/ */\n" + " const gchar ref[18];\n" + " gint16 title_idx; /* index in string table */\n" + " gint16 desc_idx; /* index in string table */\n" + "} licenses[] = {\n"); + + for (l = licenses; l != NULL; l = l->next) { + const gchar *title_en, *desc_en; + int idx_title, idx_desc; + License *license; + + license = l->data; + + if (license->packed_into_source) + continue; + + title_en = g_hash_table_lookup (license->titles, "en"); + g_assert (title_en != NULL); + idx_title = string_table_add_string (string_table, title_en); + g_assert (idx_title <= G_MAXINT16); + + if (license->descriptions != NULL) { + desc_en = g_hash_table_lookup (license->descriptions, "en"); + g_assert (desc_en != NULL); + idx_desc = string_table_add_string (string_table, desc_en); + g_assert (idx_desc <= G_MAXINT16); + } else { + idx_desc = -1; + } + + /* output comments with license refs covered by the next stanza */ + if (license->derived != NULL) { + GList *d; + + g_print (" /* %2d %s\n", idx, license->ref); + + for (d = license->derived; d != NULL; d = d->next) { + License *derived_license = d->data; + + g_print (" * %2d %s%s\n", idx, derived_license->ref, + (d->next == NULL) ? " */" : ""); + } + } else { + g_print (" /* %2d %s */\n", idx, license->ref); + } + /* output essential data */ + { + gchar *ref; + + ref = + g_strdup (license->ref + + strlen ("http://creativecommons.org/licenses/")); + + /* remove jurisdiction suffix from ref if this is non-generic, since + * the suffix is already contained in the jurisdiction flags */ + if (license->jurisdiction_suffix != NULL) { + gsize suffix_len = strlen (license->jurisdiction_suffix); + gchar *cutoff; + + cutoff = ref + strlen (ref) - suffix_len; + g_assert (!strncmp (cutoff, license->jurisdiction_suffix, suffix_len)); + g_assert (cutoff[suffix_len - 1] == '/'); + g_assert (cutoff[suffix_len] == '\0'); + *cutoff = '\0'; + } + + g_print (" { 0x%016" G_GINT64_MODIFIER "x, 0x%08x, \"%s\", %d, %d }%s\n", + license->jurisdiction, license->flags, ref, idx_title, idx_desc, + (l->next != NULL) ? "," : ""); + + g_free (ref); + } + ++idx; + } + g_print ("};\n"); + + g_print ("\nstatic const gchar license_strings[] =\n"); + string_table_print (string_table); + string_table_free (string_table); + string_table = NULL; + + if (translation_dict_fn != NULL) { + write_translations_dictionary (licenses, translation_dict_fn); + } + + return 0; +} From 54e143c5e79ca9e2076d9d7bb22a58fd80da0a27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Fri, 15 Jul 2011 13:19:38 +0100 Subject: [PATCH 056/140] tag: add unit test for new license API https://bugzilla.gnome.org/show_bug.cgi?id=646868 --- tests/check/Makefile.am | 3 +- tests/check/libs/tag.c | 188 +++++++++++++++++++- tests/files/Makefile.am | 1 + tests/files/license-uris | 376 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 566 insertions(+), 2 deletions(-) create mode 100644 tests/files/license-uris diff --git a/tests/check/Makefile.am b/tests/check/Makefile.am index cae871467c..0bca65c23b 100644 --- a/tests/check/Makefile.am +++ b/tests/check/Makefile.am @@ -12,7 +12,8 @@ TESTS_ENVIRONMENT = \ $(REGISTRY_ENVIRONMENT) \ GST_PLUGIN_SYSTEM_PATH= \ GST_PLUGIN_PATH=$(top_builddir)/gst:$(top_builddir)/sys:$(top_builddir)/ext:$(GST_PLUGINS_DIR) \ - GST_PLUGIN_LOADING_WHITELIST="gstreamer:gst-plugins-base@$(top_builddir)" + GST_PLUGIN_LOADING_WHITELIST="gstreamer:gst-plugins-base@$(top_builddir)" \ + GST_TAG_LICENSE_TRANSLATIONS_DICT="$(top_srcdir)/gst-libs/gst/tag/license-translations.dict" # ths core dumps of some machines have PIDs appended diff --git a/tests/check/libs/tag.c b/tests/check/libs/tag.c index a65cdf1616..1a22840569 100644 --- a/tests/check/libs/tag.c +++ b/tests/check/libs/tag.c @@ -2,7 +2,7 @@ * * unit tests for the tag support library * - * Copyright (C) 2006-2009 Tim-Philipp Müller + * Copyright (C) 2006-2011 Tim-Philipp Müller * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -29,6 +29,7 @@ #include #include #include +#include GST_START_TEST (test_parse_extended_comment) { @@ -750,6 +751,190 @@ GST_START_TEST (test_language_utils) GST_END_TEST; +#define SPECIFIC_L "http://creativecommons.org/licenses/by-nc-sa/2.5/scotland/" +#define GENERIC_L "http://creativecommons.org/licenses/by/1.0/" +#define DERIVED_L "http://creativecommons.org/licenses/sampling+/1.0/tw/" + +GST_START_TEST (test_license_utils) +{ + GHashTable *ht; + GError *err = NULL; + gchar **liblicense_refs, **r; + gchar **lrefs, **l; + gchar *path, *data = NULL; + gsize data_len; + + /* test jurisdiction-specific license */ + fail_unless_equals_int (gst_tag_get_license_flags (SPECIFIC_L), 0x01010703); + fail_unless_equals_string (gst_tag_get_license_nick (SPECIFIC_L), + "CC BY-NC-SA 2.5 SCOTLAND"); + fail_unless_equals_string (gst_tag_get_license_version (SPECIFIC_L), "2.5"); + fail_unless_equals_string (gst_tag_get_license_jurisdiction (SPECIFIC_L), + "scotland"); + + g_setenv ("GST_TAG_LICENSE_TRANSLATIONS_LANG", "C", TRUE); + fail_unless_equals_string (gst_tag_get_license_title (SPECIFIC_L), + "Attribution-NonCommercial-ShareAlike"); + fail_unless (gst_tag_get_license_description (SPECIFIC_L) == NULL); + + /* test generic license */ + fail_unless_equals_int (gst_tag_get_license_flags (GENERIC_L), 0x01000307); + fail_unless_equals_string (gst_tag_get_license_nick (GENERIC_L), "CC BY 1.0"); + fail_unless_equals_string (gst_tag_get_license_version (GENERIC_L), "1.0"); + fail_unless (gst_tag_get_license_jurisdiction (GENERIC_L) == NULL); + + g_setenv ("GST_TAG_LICENSE_TRANSLATIONS_LANG", "C", TRUE); + fail_unless_equals_string (gst_tag_get_license_title (GENERIC_L), + "Attribution"); + fail_unless_equals_string (gst_tag_get_license_description (GENERIC_L), + "You must attribute the work in the manner specified by the author or licensor."); + +#ifdef ENABLE_NLS + g_setenv ("GST_TAG_LICENSE_TRANSLATIONS_LANG", "fr", TRUE); + fail_unless_equals_string (gst_tag_get_license_title (GENERIC_L), + "Paternité"); + fail_unless_equals_string (gst_tag_get_license_description (GENERIC_L), + "L'offrant autorise les autres à reproduire, distribuer et communiquer cette création au public. En échange, les personnes qui acceptent ce contrat doivent citer le nom de l'auteur original."); +#endif + + /* test derived (for a certain jurisdiction) license */ + fail_unless_equals_int (gst_tag_get_license_flags (DERIVED_L), 0x0100030d); + fail_unless_equals_string (gst_tag_get_license_nick (DERIVED_L), + "CC SAMPLING+ 1.0 TW"); + fail_unless_equals_string (gst_tag_get_license_version (DERIVED_L), "1.0"); + fail_unless_equals_string (gst_tag_get_license_jurisdiction (DERIVED_L), + "tw"); + + g_setenv ("GST_TAG_LICENSE_TRANSLATIONS_LANG", "C", TRUE); + fail_unless_equals_string (gst_tag_get_license_title (DERIVED_L), + "Sampling Plus"); + fail_unless_equals_string (gst_tag_get_license_description (GENERIC_L), + "You must attribute the work in the manner specified by the author or licensor."); + + /* test all we know about */ + lrefs = gst_tag_get_licenses (); + fail_unless (lrefs != NULL); + fail_unless (*lrefs != NULL); + + GST_INFO ("%d licenses", g_strv_length (lrefs)); + fail_unless (g_strv_length (lrefs) >= 376); + + ht = g_hash_table_new (g_str_hash, g_str_equal); + + for (l = lrefs; l != NULL && *l != NULL; ++l) { + const gchar *ref, *nick, *title, *desc G_GNUC_UNUSED; + + ref = (const gchar *) *l; + nick = gst_tag_get_license_nick (ref); + title = gst_tag_get_license_title (ref); + desc = gst_tag_get_license_description (ref); + fail_unless (nick != NULL, "no nick for license '%s'", ref); + fail_unless (title != NULL, "no title for license '%s'", ref); + GST_LOG ("ref: %s [nick %s]", ref, (nick) ? nick : "none"); + GST_TRACE (" %s : %s", title, (desc) ? desc : "(no description)"); + + /* make sure the list contains no duplicates */ + fail_if (g_hash_table_lookup (ht, (gpointer) ref) != NULL); + g_hash_table_insert (ht, (gpointer) ref, (gpointer) "meep"); + } + g_hash_table_destroy (ht); + + /* trailing slash shouldn't make a difference */ + fail_unless_equals_int (gst_tag_get_license_flags + ("http://creativecommons.org/licenses/by-nd/1.0/"), + gst_tag_get_license_flags + ("http://creativecommons.org/licenses/by-nd/1.0")); + fail_unless_equals_string (gst_tag_get_license_nick + ("http://creativecommons.org/licenses/by-nd/1.0/"), + gst_tag_get_license_nick + ("http://creativecommons.org/licenses/by-nd/1.0")); + fail_unless_equals_int (gst_tag_get_license_flags + ("http://creativecommons.org/licenses/by-nd/2.5/ca/"), + gst_tag_get_license_flags + ("http://creativecommons.org/licenses/by-nd/2.5/ca")); + fail_unless_equals_string (gst_tag_get_license_nick + ("http://creativecommons.org/licenses/by-nd/2.5/ca/"), + gst_tag_get_license_nick + ("http://creativecommons.org/licenses/by-nd/2.5/ca")); + + /* unknown licenses */ + fail_unless (gst_tag_get_license_nick + ("http://creativecommons.org/licenses/by-nd/25/ca/") == NULL); + fail_unless (gst_tag_get_license_flags + ("http://creativecommons.org/licenses/by-nd/25/ca") == 0); + fail_unless (gst_tag_get_license_jurisdiction + ("http://creativecommons.org/licenses/by-nd/25/ca/") == NULL); + fail_unless (gst_tag_get_license_jurisdiction + ("http://creativecommons.org/licenses/by-nd/25/ca") == NULL); + fail_unless (gst_tag_get_license_title + ("http://creativecommons.org/licenses/by-nd/25/ca") == NULL); + fail_unless (gst_tag_get_license_jurisdiction + ("http://creativecommons.org/licenses/by-nd/25/ca") == NULL); + + /* unknown prefixes even */ + fail_unless (gst_tag_get_license_nick + ("http://copycats.org/licenses/by-nd/2.5/ca/") == NULL); + fail_unless (gst_tag_get_license_flags + ("http://copycats.org/licenses/by-nd/2.5/ca") == 0); + fail_unless (gst_tag_get_license_jurisdiction + ("http://copycats.org/licenses/by-nd/2.5/ca/") == NULL); + fail_unless (gst_tag_get_license_title + ("http://copycats.org/licenses/by-nd/2.5/ca/") == NULL); + fail_unless (gst_tag_get_license_description + ("http://copycats.org/licenses/by-nd/2.5/ca/") == NULL); + + /* read list of liblicense refs from file */ + path = g_build_filename (GST_TEST_FILES_PATH, "license-uris", NULL); + GST_LOG ("reading file '%s'", path); + if (!g_file_get_contents (path, &data, &data_len, &err)) { + g_error ("error loading test file: %s", err->message); + } + + while (data_len > 0 && data[data_len - 1] == '\n') { + data[--data_len] = '\0'; + } + + liblicense_refs = g_strsplit (data, "\n", -1); + g_free (data); + g_free (path); + + fail_unless (g_strv_length (lrefs) >= g_strv_length (liblicense_refs)); + + for (r = liblicense_refs; r != NULL && *r != NULL; ++r) { + GstTagLicenseFlags flags; + const gchar *version, *nick, *jur; + const gchar *ref = *r; + + GST_LOG ("liblicense ref: %s", ref); + + version = gst_tag_get_license_version (ref); + if (strstr (ref, "publicdomain") != NULL) + fail_unless (version == NULL); + else + fail_unless (version != NULL, "expected version for license %s", ref); + + flags = gst_tag_get_license_flags (ref); + fail_unless (flags != 0, "expected non-zero flags for license %s", ref); + + nick = gst_tag_get_license_nick (ref); + fail_unless (nick != NULL, "expected nick for license %s", ref); + + jur = gst_tag_get_license_jurisdiction (ref); + if (g_str_has_suffix (ref, "de/")) { + fail_unless_equals_string (jur, "de"); + } else if (g_str_has_suffix (ref, "scotland")) { + fail_unless_equals_string (jur, "scotland"); + } else if (g_str_has_suffix (ref, ".0") || g_str_has_suffix (ref, ".1")) { + fail_unless (jur == NULL); + } + } + + g_strfreev (liblicense_refs); + g_strfreev (lrefs); +} + +GST_END_TEST; + GST_START_TEST (test_xmp_formatting) { GstTagList *list; @@ -1634,6 +1819,7 @@ tag_suite (void) tcase_add_test (tc_chain, test_id3_tags); tcase_add_test (tc_chain, test_id3v1_utf8_tag); tcase_add_test (tc_chain, test_language_utils); + tcase_add_test (tc_chain, test_license_utils); tcase_add_test (tc_chain, test_xmp_formatting); tcase_add_test (tc_chain, test_xmp_parsing); tcase_add_test (tc_chain, test_xmp_tags_serialization_deserialization); diff --git a/tests/files/Makefile.am b/tests/files/Makefile.am index dfe968acbd..9556f5efb9 100644 --- a/tests/files/Makefile.am +++ b/tests/files/Makefile.am @@ -1,5 +1,6 @@ EXTRA_DIST = \ 623663.mts \ hls.m3u8 \ + license-uris \ partialframe.mjpeg \ test.mp3 diff --git a/tests/files/license-uris b/tests/files/license-uris new file mode 100644 index 0000000000..651a447a6f --- /dev/null +++ b/tests/files/license-uris @@ -0,0 +1,376 @@ +http://creativecommons.org/licenses/by/1.0/fi/ +http://creativecommons.org/licenses/by/1.0/il/ +http://creativecommons.org/licenses/by/1.0/nl/ +http://creativecommons.org/licenses/by/1.0/ +http://creativecommons.org/licenses/by/2.0/at/ +http://creativecommons.org/licenses/by/2.0/au/ +http://creativecommons.org/licenses/by/2.0/be/ +http://creativecommons.org/licenses/by/2.0/br/ +http://creativecommons.org/licenses/by/2.0/ca/ +http://creativecommons.org/licenses/by/2.0/cl/ +http://creativecommons.org/licenses/by/2.0/de/ +http://creativecommons.org/licenses/by/2.0/es/ +http://creativecommons.org/licenses/by/2.0/fr/ +http://creativecommons.org/licenses/by/2.0/hr/ +http://creativecommons.org/licenses/by/2.0/it/ +http://creativecommons.org/licenses/by/2.0/jp/ +http://creativecommons.org/licenses/by/2.0/kr/ +http://creativecommons.org/licenses/by/2.0/nl/ +http://creativecommons.org/licenses/by/2.0/pl/ +http://creativecommons.org/licenses/by/2.0/ +http://creativecommons.org/licenses/by/2.0/tw/ +http://creativecommons.org/licenses/by/2.0/uk/ +http://creativecommons.org/licenses/by/2.0/za/ +http://creativecommons.org/licenses/by/2.1/au/ +http://creativecommons.org/licenses/by/2.1/es/ +http://creativecommons.org/licenses/by/2.1/jp/ +http://creativecommons.org/licenses/by/2.5/ar/ +http://creativecommons.org/licenses/by/2.5/au/ +http://creativecommons.org/licenses/by/2.5/bg/ +http://creativecommons.org/licenses/by/2.5/br/ +http://creativecommons.org/licenses/by/2.5/ca/ +http://creativecommons.org/licenses/by/2.5/ch/ +http://creativecommons.org/licenses/by/2.5/cn/ +http://creativecommons.org/licenses/by/2.5/co/ +http://creativecommons.org/licenses/by/2.5/dk/ +http://creativecommons.org/licenses/by/2.5/es/ +http://creativecommons.org/licenses/by/2.5/hr/ +http://creativecommons.org/licenses/by/2.5/hu/ +http://creativecommons.org/licenses/by/2.5/il/ +http://creativecommons.org/licenses/by/2.5/in/ +http://creativecommons.org/licenses/by/2.5/it/ +http://creativecommons.org/licenses/by/2.5/mk/ +http://creativecommons.org/licenses/by/2.5/mt/ +http://creativecommons.org/licenses/by/2.5/mx/ +http://creativecommons.org/licenses/by/2.5/my/ +http://creativecommons.org/licenses/by/2.5/nl/ +http://creativecommons.org/licenses/by/2.5/pe/ +http://creativecommons.org/licenses/by/2.5/pl/ +http://creativecommons.org/licenses/by/2.5/pt/ +http://creativecommons.org/licenses/by/2.5/ +http://creativecommons.org/licenses/by/2.5/scotland/ +http://creativecommons.org/licenses/by/2.5/se/ +http://creativecommons.org/licenses/by/2.5/si/ +http://creativecommons.org/licenses/by/2.5/tw/ +http://creativecommons.org/licenses/by/2.5/za/ +http://creativecommons.org/licenses/by/3.0/ +http://creativecommons.org/licenses/by/3.0/us/ +http://creativecommons.org/licenses/by-nc/1.0/fi/ +http://creativecommons.org/licenses/by-nc/1.0/il/ +http://creativecommons.org/licenses/by-nc/1.0/nl/ +http://creativecommons.org/licenses/by-nc/1.0/ +http://creativecommons.org/licenses/by-nc/2.0/at/ +http://creativecommons.org/licenses/by-nc/2.0/au/ +http://creativecommons.org/licenses/by-nc/2.0/be/ +http://creativecommons.org/licenses/by-nc/2.0/br/ +http://creativecommons.org/licenses/by-nc/2.0/ca/ +http://creativecommons.org/licenses/by-nc/2.0/cl/ +http://creativecommons.org/licenses/by-nc/2.0/de/ +http://creativecommons.org/licenses/by-nc/2.0/es/ +http://creativecommons.org/licenses/by-nc/2.0/fr/ +http://creativecommons.org/licenses/by-nc/2.0/hr/ +http://creativecommons.org/licenses/by-nc/2.0/it/ +http://creativecommons.org/licenses/by-nc/2.0/jp/ +http://creativecommons.org/licenses/by-nc/2.0/kr/ +http://creativecommons.org/licenses/by-nc/2.0/nl/ +http://creativecommons.org/licenses/by-nc/2.0/pl/ +http://creativecommons.org/licenses/by-nc/2.0/ +http://creativecommons.org/licenses/by-nc/2.0/tw/ +http://creativecommons.org/licenses/by-nc/2.0/uk/ +http://creativecommons.org/licenses/by-nc/2.0/za/ +http://creativecommons.org/licenses/by-nc/2.1/au/ +http://creativecommons.org/licenses/by-nc/2.1/es/ +http://creativecommons.org/licenses/by-nc/2.1/jp/ +http://creativecommons.org/licenses/by-nc/2.5/ar/ +http://creativecommons.org/licenses/by-nc/2.5/au/ +http://creativecommons.org/licenses/by-nc/2.5/bg/ +http://creativecommons.org/licenses/by-nc/2.5/br/ +http://creativecommons.org/licenses/by-nc/2.5/ca/ +http://creativecommons.org/licenses/by-nc/2.5/ch/ +http://creativecommons.org/licenses/by-nc/2.5/cn/ +http://creativecommons.org/licenses/by-nc/2.5/co/ +http://creativecommons.org/licenses/by-nc/2.5/dk/ +http://creativecommons.org/licenses/by-nc/2.5/es/ +http://creativecommons.org/licenses/by-nc/2.5/hr/ +http://creativecommons.org/licenses/by-nc/2.5/hu/ +http://creativecommons.org/licenses/by-nc/2.5/il/ +http://creativecommons.org/licenses/by-nc/2.5/in/ +http://creativecommons.org/licenses/by-nc/2.5/it/ +http://creativecommons.org/licenses/by-nc/2.5/mk/ +http://creativecommons.org/licenses/by-nc/2.5/mt/ +http://creativecommons.org/licenses/by-nc/2.5/mx/ +http://creativecommons.org/licenses/by-nc/2.5/my/ +http://creativecommons.org/licenses/by-nc/2.5/nl/ +http://creativecommons.org/licenses/by-nc/2.5/pe/ +http://creativecommons.org/licenses/by-nc/2.5/pl/ +http://creativecommons.org/licenses/by-nc/2.5/pt/ +http://creativecommons.org/licenses/by-nc/2.5/ +http://creativecommons.org/licenses/by-nc/2.5/scotland/ +http://creativecommons.org/licenses/by-nc/2.5/se/ +http://creativecommons.org/licenses/by-nc/2.5/si/ +http://creativecommons.org/licenses/by-nc/2.5/tw/ +http://creativecommons.org/licenses/by-nc/2.5/za/ +http://creativecommons.org/licenses/by-nc/3.0/ +http://creativecommons.org/licenses/by-nc/3.0/us/ +http://creativecommons.org/licenses/by-nc-nd/2.0/at/ +http://creativecommons.org/licenses/by-nc-nd/2.0/au/ +http://creativecommons.org/licenses/by-nc-nd/2.0/be/ +http://creativecommons.org/licenses/by-nc-nd/2.0/br/ +http://creativecommons.org/licenses/by-nc-nd/2.0/ca/ +http://creativecommons.org/licenses/by-nc-nd/2.0/cl/ +http://creativecommons.org/licenses/by-nc-nd/2.0/de/ +http://creativecommons.org/licenses/by-nc-nd/2.0/es/ +http://creativecommons.org/licenses/by-nc-nd/2.0/fr/ +http://creativecommons.org/licenses/by-nc-nd/2.0/hr/ +http://creativecommons.org/licenses/by-nc-nd/2.0/it/ +http://creativecommons.org/licenses/by-nc-nd/2.0/jp/ +http://creativecommons.org/licenses/by-nc-nd/2.0/kr/ +http://creativecommons.org/licenses/by-nc-nd/2.0/nl/ +http://creativecommons.org/licenses/by-nc-nd/2.0/pl/ +http://creativecommons.org/licenses/by-nc-nd/2.0/ +http://creativecommons.org/licenses/by-nc-nd/2.0/tw/ +http://creativecommons.org/licenses/by-nc-nd/2.0/uk/ +http://creativecommons.org/licenses/by-nc-nd/2.0/za/ +http://creativecommons.org/licenses/by-nc-nd/2.1/au/ +http://creativecommons.org/licenses/by-nc-nd/2.1/es/ +http://creativecommons.org/licenses/by-nc-nd/2.1/jp/ +http://creativecommons.org/licenses/by-nc-nd/2.5/ar/ +http://creativecommons.org/licenses/by-nc-nd/2.5/au/ +http://creativecommons.org/licenses/by-nc-nd/2.5/bg/ +http://creativecommons.org/licenses/by-nc-nd/2.5/br/ +http://creativecommons.org/licenses/by-nc-nd/2.5/ca/ +http://creativecommons.org/licenses/by-nc-nd/2.5/ch/ +http://creativecommons.org/licenses/by-nc-nd/2.5/cn/ +http://creativecommons.org/licenses/by-nc-nd/2.5/co/ +http://creativecommons.org/licenses/by-nc-nd/2.5/dk/ +http://creativecommons.org/licenses/by-nc-nd/2.5/es/ +http://creativecommons.org/licenses/by-nc-nd/2.5/hr/ +http://creativecommons.org/licenses/by-nc-nd/2.5/hu/ +http://creativecommons.org/licenses/by-nc-nd/2.5/il/ +http://creativecommons.org/licenses/by-nc-nd/2.5/in/ +http://creativecommons.org/licenses/by-nc-nd/2.5/it/ +http://creativecommons.org/licenses/by-nc-nd/2.5/mk/ +http://creativecommons.org/licenses/by-nc-nd/2.5/mt/ +http://creativecommons.org/licenses/by-nc-nd/2.5/mx/ +http://creativecommons.org/licenses/by-nc-nd/2.5/my/ +http://creativecommons.org/licenses/by-nc-nd/2.5/nl/ +http://creativecommons.org/licenses/by-nc-nd/2.5/pe/ +http://creativecommons.org/licenses/by-nc-nd/2.5/pl/ +http://creativecommons.org/licenses/by-nc-nd/2.5/pt/ +http://creativecommons.org/licenses/by-nc-nd/2.5/ +http://creativecommons.org/licenses/by-nc-nd/2.5/scotland/ +http://creativecommons.org/licenses/by-nc-nd/2.5/se/ +http://creativecommons.org/licenses/by-nc-nd/2.5/si/ +http://creativecommons.org/licenses/by-nc-nd/2.5/tw/ +http://creativecommons.org/licenses/by-nc-nd/2.5/za/ +http://creativecommons.org/licenses/by-nc-nd/3.0/ +http://creativecommons.org/licenses/by-nc-nd/3.0/us/ +http://creativecommons.org/licenses/by-nc-sa/1.0/fi/ +http://creativecommons.org/licenses/by-nc-sa/1.0/il/ +http://creativecommons.org/licenses/by-nc-sa/1.0/nl/ +http://creativecommons.org/licenses/by-nc-sa/1.0/ +http://creativecommons.org/licenses/by-nc-sa/2.0/at/ +http://creativecommons.org/licenses/by-nc-sa/2.0/au/ +http://creativecommons.org/licenses/by-nc-sa/2.0/be/ +http://creativecommons.org/licenses/by-nc-sa/2.0/br/ +http://creativecommons.org/licenses/by-nc-sa/2.0/ca/ +http://creativecommons.org/licenses/by-nc-sa/2.0/cl/ +http://creativecommons.org/licenses/by-nc-sa/2.0/de/ +http://creativecommons.org/licenses/by-nc-sa/2.0/es/ +http://creativecommons.org/licenses/by-nc-sa/2.0/fr/ +http://creativecommons.org/licenses/by-nc-sa/2.0/hr/ +http://creativecommons.org/licenses/by-nc-sa/2.0/it/ +http://creativecommons.org/licenses/by-nc-sa/2.0/jp/ +http://creativecommons.org/licenses/by-nc-sa/2.0/kr/ +http://creativecommons.org/licenses/by-nc-sa/2.0/nl/ +http://creativecommons.org/licenses/by-nc-sa/2.0/pl/ +http://creativecommons.org/licenses/by-nc-sa/2.0/ +http://creativecommons.org/licenses/by-nc-sa/2.0/tw/ +http://creativecommons.org/licenses/by-nc-sa/2.0/uk/ +http://creativecommons.org/licenses/by-nc-sa/2.0/za/ +http://creativecommons.org/licenses/by-nc-sa/2.1/au/ +http://creativecommons.org/licenses/by-nc-sa/2.1/es/ +http://creativecommons.org/licenses/by-nc-sa/2.1/jp/ +http://creativecommons.org/licenses/by-nc-sa/2.5/ar/ +http://creativecommons.org/licenses/by-nc-sa/2.5/au/ +http://creativecommons.org/licenses/by-nc-sa/2.5/bg/ +http://creativecommons.org/licenses/by-nc-sa/2.5/br/ +http://creativecommons.org/licenses/by-nc-sa/2.5/ca/ +http://creativecommons.org/licenses/by-nc-sa/2.5/ch/ +http://creativecommons.org/licenses/by-nc-sa/2.5/cn/ +http://creativecommons.org/licenses/by-nc-sa/2.5/co/ +http://creativecommons.org/licenses/by-nc-sa/2.5/dk/ +http://creativecommons.org/licenses/by-nc-sa/2.5/es/ +http://creativecommons.org/licenses/by-nc-sa/2.5/hr/ +http://creativecommons.org/licenses/by-nc-sa/2.5/hu/ +http://creativecommons.org/licenses/by-nc-sa/2.5/il/ +http://creativecommons.org/licenses/by-nc-sa/2.5/in/ +http://creativecommons.org/licenses/by-nc-sa/2.5/it/ +http://creativecommons.org/licenses/by-nc-sa/2.5/mk/ +http://creativecommons.org/licenses/by-nc-sa/2.5/mt/ +http://creativecommons.org/licenses/by-nc-sa/2.5/mx/ +http://creativecommons.org/licenses/by-nc-sa/2.5/my/ +http://creativecommons.org/licenses/by-nc-sa/2.5/nl/ +http://creativecommons.org/licenses/by-nc-sa/2.5/pe/ +http://creativecommons.org/licenses/by-nc-sa/2.5/pl/ +http://creativecommons.org/licenses/by-nc-sa/2.5/pt/ +http://creativecommons.org/licenses/by-nc-sa/2.5/ +http://creativecommons.org/licenses/by-nc-sa/2.5/scotland/ +http://creativecommons.org/licenses/by-nc-sa/2.5/se/ +http://creativecommons.org/licenses/by-nc-sa/2.5/si/ +http://creativecommons.org/licenses/by-nc-sa/2.5/tw/ +http://creativecommons.org/licenses/by-nc-sa/2.5/za/ +http://creativecommons.org/licenses/by-nc-sa/3.0/ +http://creativecommons.org/licenses/by-nc-sa/3.0/us/ +http://creativecommons.org/licenses/by-nd/1.0/fi/ +http://creativecommons.org/licenses/by-nd/1.0/il/ +http://creativecommons.org/licenses/by-nd/1.0/nl/ +http://creativecommons.org/licenses/by-nd/1.0/ +http://creativecommons.org/licenses/by-nd/2.0/at/ +http://creativecommons.org/licenses/by-nd/2.0/au/ +http://creativecommons.org/licenses/by-nd/2.0/be/ +http://creativecommons.org/licenses/by-nd/2.0/br/ +http://creativecommons.org/licenses/by-nd/2.0/ca/ +http://creativecommons.org/licenses/by-nd/2.0/cl/ +http://creativecommons.org/licenses/by-nd/2.0/de/ +http://creativecommons.org/licenses/by-nd/2.0/es/ +http://creativecommons.org/licenses/by-nd/2.0/fr/ +http://creativecommons.org/licenses/by-nd/2.0/hr/ +http://creativecommons.org/licenses/by-nd/2.0/it/ +http://creativecommons.org/licenses/by-nd/2.0/jp/ +http://creativecommons.org/licenses/by-nd/2.0/kr/ +http://creativecommons.org/licenses/by-nd/2.0/nl/ +http://creativecommons.org/licenses/by-nd/2.0/pl/ +http://creativecommons.org/licenses/by-nd/2.0/ +http://creativecommons.org/licenses/by-nd/2.0/tw/ +http://creativecommons.org/licenses/by-nd/2.0/uk/ +http://creativecommons.org/licenses/by-nd/2.0/za/ +http://creativecommons.org/licenses/by-nd/2.1/au/ +http://creativecommons.org/licenses/by-nd/2.1/es/ +http://creativecommons.org/licenses/by-nd/2.1/jp/ +http://creativecommons.org/licenses/by-nd/2.5/ar/ +http://creativecommons.org/licenses/by-nd/2.5/au/ +http://creativecommons.org/licenses/by-nd/2.5/bg/ +http://creativecommons.org/licenses/by-nd/2.5/br/ +http://creativecommons.org/licenses/by-nd/2.5/ca/ +http://creativecommons.org/licenses/by-nd/2.5/ch/ +http://creativecommons.org/licenses/by-nd/2.5/cn/ +http://creativecommons.org/licenses/by-nd/2.5/co/ +http://creativecommons.org/licenses/by-nd/2.5/dk/ +http://creativecommons.org/licenses/by-nd/2.5/es/ +http://creativecommons.org/licenses/by-nd/2.5/hr/ +http://creativecommons.org/licenses/by-nd/2.5/hu/ +http://creativecommons.org/licenses/by-nd/2.5/il/ +http://creativecommons.org/licenses/by-nd/2.5/in/ +http://creativecommons.org/licenses/by-nd/2.5/it/ +http://creativecommons.org/licenses/by-nd/2.5/mk/ +http://creativecommons.org/licenses/by-nd/2.5/mt/ +http://creativecommons.org/licenses/by-nd/2.5/mx/ +http://creativecommons.org/licenses/by-nd/2.5/my/ +http://creativecommons.org/licenses/by-nd/2.5/nl/ +http://creativecommons.org/licenses/by-nd/2.5/pe/ +http://creativecommons.org/licenses/by-nd/2.5/pl/ +http://creativecommons.org/licenses/by-nd/2.5/pt/ +http://creativecommons.org/licenses/by-nd/2.5/ +http://creativecommons.org/licenses/by-nd/2.5/scotland/ +http://creativecommons.org/licenses/by-nd/2.5/se/ +http://creativecommons.org/licenses/by-nd/2.5/si/ +http://creativecommons.org/licenses/by-nd/2.5/tw/ +http://creativecommons.org/licenses/by-nd/2.5/za/ +http://creativecommons.org/licenses/by-nd/3.0/ +http://creativecommons.org/licenses/by-nd/3.0/us/ +http://creativecommons.org/licenses/by-nd-nc/1.0/fi/ +http://creativecommons.org/licenses/by-nd-nc/1.0/il/ +http://creativecommons.org/licenses/by-nd-nc/1.0/nl/ +http://creativecommons.org/licenses/by-nd-nc/1.0/ +http://creativecommons.org/licenses/by-nd-nc/2.0/jp/ +http://creativecommons.org/licenses/by-sa/1.0/fi/ +http://creativecommons.org/licenses/by-sa/1.0/il/ +http://creativecommons.org/licenses/by-sa/1.0/nl/ +http://creativecommons.org/licenses/by-sa/1.0/ +http://creativecommons.org/licenses/by-sa/2.0/at/ +http://creativecommons.org/licenses/by-sa/2.0/au/ +http://creativecommons.org/licenses/by-sa/2.0/be/ +http://creativecommons.org/licenses/by-sa/2.0/br/ +http://creativecommons.org/licenses/by-sa/2.0/ca/ +http://creativecommons.org/licenses/by-sa/2.0/cl/ +http://creativecommons.org/licenses/by-sa/2.0/de/ +http://creativecommons.org/licenses/by-sa/2.0/es/ +http://creativecommons.org/licenses/by-sa/2.0/fr/ +http://creativecommons.org/licenses/by-sa/2.0/hr/ +http://creativecommons.org/licenses/by-sa/2.0/it/ +http://creativecommons.org/licenses/by-sa/2.0/jp/ +http://creativecommons.org/licenses/by-sa/2.0/kr/ +http://creativecommons.org/licenses/by-sa/2.0/nl/ +http://creativecommons.org/licenses/by-sa/2.0/pl/ +http://creativecommons.org/licenses/by-sa/2.0/ +http://creativecommons.org/licenses/by-sa/2.0/tw/ +http://creativecommons.org/licenses/by-sa/2.0/uk/ +http://creativecommons.org/licenses/by-sa/2.0/za/ +http://creativecommons.org/licenses/by-sa/2.1/au/ +http://creativecommons.org/licenses/by-sa/2.1/es/ +http://creativecommons.org/licenses/by-sa/2.1/jp/ +http://creativecommons.org/licenses/by-sa/2.5/ar/ +http://creativecommons.org/licenses/by-sa/2.5/au/ +http://creativecommons.org/licenses/by-sa/2.5/bg/ +http://creativecommons.org/licenses/by-sa/2.5/br/ +http://creativecommons.org/licenses/by-sa/2.5/ca/ +http://creativecommons.org/licenses/by-sa/2.5/ch/ +http://creativecommons.org/licenses/by-sa/2.5/cn/ +http://creativecommons.org/licenses/by-sa/2.5/co/ +http://creativecommons.org/licenses/by-sa/2.5/dk/ +http://creativecommons.org/licenses/by-sa/2.5/es/ +http://creativecommons.org/licenses/by-sa/2.5/hr/ +http://creativecommons.org/licenses/by-sa/2.5/hu/ +http://creativecommons.org/licenses/by-sa/2.5/il/ +http://creativecommons.org/licenses/by-sa/2.5/in/ +http://creativecommons.org/licenses/by-sa/2.5/it/ +http://creativecommons.org/licenses/by-sa/2.5/mk/ +http://creativecommons.org/licenses/by-sa/2.5/mt/ +http://creativecommons.org/licenses/by-sa/2.5/mx/ +http://creativecommons.org/licenses/by-sa/2.5/my/ +http://creativecommons.org/licenses/by-sa/2.5/nl/ +http://creativecommons.org/licenses/by-sa/2.5/pe/ +http://creativecommons.org/licenses/by-sa/2.5/pl/ +http://creativecommons.org/licenses/by-sa/2.5/pt/ +http://creativecommons.org/licenses/by-sa/2.5/ +http://creativecommons.org/licenses/by-sa/2.5/scotland/ +http://creativecommons.org/licenses/by-sa/2.5/se/ +http://creativecommons.org/licenses/by-sa/2.5/si/ +http://creativecommons.org/licenses/by-sa/2.5/tw/ +http://creativecommons.org/licenses/by-sa/2.5/za/ +http://creativecommons.org/licenses/by-sa/3.0/ +http://creativecommons.org/licenses/by-sa/3.0/us/ +http://creativecommons.org/licenses/devnations/2.0/ +http://creativecommons.org/licenses/GPL/2.0/ +http://creativecommons.org/licenses/LGPL/2.1/ +http://creativecommons.org/licenses/nc/1.0/fi/ +http://creativecommons.org/licenses/nc/1.0/nl/ +http://creativecommons.org/licenses/nc/1.0/ +http://creativecommons.org/licenses/nc/2.0/jp/ +http://creativecommons.org/licenses/nc-sa/1.0/fi/ +http://creativecommons.org/licenses/nc-sa/1.0/nl/ +http://creativecommons.org/licenses/nc-sa/1.0/ +http://creativecommons.org/licenses/nc-sa/2.0/jp/ +http://creativecommons.org/licenses/nc-sampling+/1.0/ +http://creativecommons.org/licenses/nc-sampling+/1.0/tw/ +http://creativecommons.org/licenses/nd/1.0/fi/ +http://creativecommons.org/licenses/nd/1.0/nl/ +http://creativecommons.org/licenses/nd/1.0/ +http://creativecommons.org/licenses/nd/2.0/jp/ +http://creativecommons.org/licenses/nd-nc/1.0/fi/ +http://creativecommons.org/licenses/nd-nc/1.0/nl/ +http://creativecommons.org/licenses/nd-nc/1.0/ +http://creativecommons.org/licenses/nd-nc/2.0/jp/ +http://creativecommons.org/licenses/publicdomain/ +http://creativecommons.org/licenses/sa/1.0/fi/ +http://creativecommons.org/licenses/sa/1.0/nl/ +http://creativecommons.org/licenses/sa/1.0/ +http://creativecommons.org/licenses/sa/2.0/jp/ +http://creativecommons.org/licenses/sampling/1.0/br/ +http://creativecommons.org/licenses/sampling+/1.0/br/ +http://creativecommons.org/licenses/sampling+/1.0/de/ +http://creativecommons.org/licenses/sampling/1.0/ +http://creativecommons.org/licenses/sampling+/1.0/ +http://creativecommons.org/licenses/sampling/1.0/tw/ +http://creativecommons.org/licenses/sampling+/1.0/tw/ From 1c0fbbce10e28c291681670f838da192a4917a0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Wed, 10 Aug 2011 10:49:38 +0100 Subject: [PATCH 057/140] subparse: fix runtime warnings when doing position query Add missing 'break'. --- gst/subparse/gstsubparse.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gst/subparse/gstsubparse.c b/gst/subparse/gstsubparse.c index eaf03c337a..8d495f01b0 100644 --- a/gst/subparse/gstsubparse.c +++ b/gst/subparse/gstsubparse.c @@ -263,6 +263,7 @@ gst_sub_parse_src_query (GstPad * pad, GstQuery * query) gst_query_set_position (query, GST_FORMAT_TIME, self->segment.last_stop); } + break; } case GST_QUERY_SEEKING: { @@ -282,7 +283,6 @@ gst_sub_parse_src_query (GstPad * pad, GstQuery * query) } gst_query_set_seeking (query, fmt, seekable, seekable ? 0 : -1, -1); - break; } default: From 3e34c2da1c76ba9a1ae545da00d9fded9ba45cd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Wed, 10 Aug 2011 14:57:14 +0100 Subject: [PATCH 058/140] tag: add GType for GstTagLicenseFlags API: gst_tag_license_flags_get_type() --- docs/libs/gst-plugins-base-libs-sections.txt | 1 + gst-libs/gst/tag/licenses.c | 52 ++++++++++++++++++++ gst-libs/gst/tag/tag.h | 2 + 3 files changed, 55 insertions(+) diff --git a/docs/libs/gst-plugins-base-libs-sections.txt b/docs/libs/gst-plugins-base-libs-sections.txt index dd3e691337..54d97223cc 100644 --- a/docs/libs/gst-plugins-base-libs-sections.txt +++ b/docs/libs/gst-plugins-base-libs-sections.txt @@ -1751,6 +1751,7 @@ GstTagImageType GST_TYPE_TAG_IMAGE_TYPE gst_tag_image_type_get_type +gst_tag_license_flags_get_type GstTagEntryMatch

diff --git a/gst-libs/gst/tag/licenses.c b/gst-libs/gst/tag/licenses.c index f42e971add..31632b1d1e 100644 --- a/gst-libs/gst/tag/licenses.c +++ b/gst-libs/gst/tag/licenses.c @@ -524,3 +524,55 @@ gst_tag_get_license_version (const gchar * license_ref) GST_ERROR ("Could not determine version for ref '%s'", license_ref); return NULL; } + +GType +gst_tag_license_flags_get_type (void) +{ + /* FIXME: we should really be using glib-mkenums for this.. */ +#define C_FLAGS(v) ((guint) v) + static gsize id = 0; + static const GFlagsValue values[] = { + {C_FLAGS (GST_TAG_LICENSE_PERMITS_REPRODUCTION), + "GST_TAG_LICENSE_PERMITS_REPRODUCTION", "permits-reproduction"}, + {C_FLAGS (GST_TAG_LICENSE_PERMITS_DISTRIBUTION), + "GST_TAG_LICENSE_PERMITS_DISTRIBUTION", "permits-distribution"}, + {C_FLAGS (GST_TAG_LICENSE_PERMITS_DERIVATIVE_WORKS), + "GST_TAG_LICENSE_PERMITS_DERIVATIVE_WORKS", + "permits-derivative-works"}, + {C_FLAGS (GST_TAG_LICENSE_PERMITS_SHARING), + "GST_TAG_LICENSE_PERMITS_SHARING", "permits-sharing"}, + {C_FLAGS (GST_TAG_LICENSE_REQUIRES_NOTICE), + "GST_TAG_LICENSE_REQUIRES_NOTICE", "requires-notice"}, + {C_FLAGS (GST_TAG_LICENSE_REQUIRES_ATTRIBUTION), + "GST_TAG_LICENSE_REQUIRES_ATTRIBUTION", "requires-attributions"}, + {C_FLAGS (GST_TAG_LICENSE_REQUIRES_SHARE_ALIKE), + "GST_TAG_LICENSE_REQUIRES_SHARE_ALIKE", "requires-share-alike"}, + {C_FLAGS (GST_TAG_LICENSE_REQUIRES_SOURCE_CODE), + "GST_TAG_LICENSE_REQUIRES_SOURCE_CODE", "requires-source-code"}, + {C_FLAGS (GST_TAG_LICENSE_REQUIRES_COPYLEFT), + "GST_TAG_LICENSE_REQUIRES_COPYLEFT", "requires-copyleft"}, + {C_FLAGS (GST_TAG_LICENSE_REQUIRES_LESSER_COPYLEFT), + "GST_TAG_LICENSE_REQUIRES_LESSER_COPYLEFT", + "requires-lesser-copyleft"}, + {C_FLAGS (GST_TAG_LICENSE_PROHIBITS_COMMERCIAL_USE), + "GST_TAG_LICENSE_PROHIBITS_COMMERCIAL_USE", + "prohibits-commercial-use"}, + {C_FLAGS (GST_TAG_LICENSE_PROHIBITS_HIGH_INCOME_NATION_USE), + "GST_TAG_LICENSE_PROHIBITS_HIGH_INCOME_NATION_USE", + "prohibits-high-income-nation-use"}, + {C_FLAGS (GST_TAG_LICENSE_CREATIVE_COMMONS_LICENSE), + "GST_TAG_LICENSE_CREATIVE_COMMONS_LICENSE", + "creative-commons-license"}, + {C_FLAGS (GST_TAG_LICENSE_FREE_SOFTWARE_FOUNDATION_LICENSE), + "GST_TAG_LICENSE_FREE_SOFTWARE_FOUNDATION_LICENSE", + "free-software-foundation-license"}, + {0, NULL, NULL} + }; + + if (g_once_init_enter (&id)) { + GType tmp = g_flags_register_static ("GstTagLicenseFlags", values); + g_once_init_leave (&id, tmp); + } + + return (GType) id; +} diff --git a/gst-libs/gst/tag/tag.h b/gst-libs/gst/tag/tag.h index 6d70bef565..08ff9421c2 100644 --- a/gst-libs/gst/tag/tag.h +++ b/gst-libs/gst/tag/tag.h @@ -621,6 +621,8 @@ const gchar * gst_tag_get_license_description (const gchar * license_ref) const gchar * gst_tag_get_license_jurisdiction (const gchar * license_ref); +GType gst_tag_license_flags_get_type (void); + G_END_DECLS #endif /* __GST_TAG_TAG_H__ */ From 12ab3c92045e510df789db60c67c5d4df16a9027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Wed, 10 Aug 2011 15:06:59 +0100 Subject: [PATCH 059/140] tag: fix compilation of new licenses code with GLib versions < 2.28 Add local g_variant_lookup_value() fallback for now when compiling against older GLib versions. --- gst-libs/gst/tag/licenses.c | 58 +++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/gst-libs/gst/tag/licenses.c b/gst-libs/gst/tag/licenses.c index 31632b1d1e..4d2c4769d0 100644 --- a/gst-libs/gst/tag/licenses.c +++ b/gst-libs/gst/tag/licenses.c @@ -114,6 +114,64 @@ gst_tag_get_license_translations_dictionary (void) #endif #ifdef ENABLE_NLS + +#if !GLIB_CHECK_VERSION(2,28,0) +static GVariant * +gst_g_variant_lookup_value (GVariant * dictionary, const gchar * key, + const GVariantType * expected_type) +{ + GVariantIter iter; + GVariant *entry; + GVariant *value; + + GST_ERROR ("here, using fallback"); + + g_assert (g_variant_is_of_type (dictionary, G_VARIANT_TYPE ("a{s*}"))); + g_assert (expected_type != NULL); + + g_variant_iter_init (&iter, dictionary); + while ((entry = g_variant_iter_next_value (&iter))) { + GVariant *entry_key; + gboolean matches; + + entry_key = g_variant_get_child_value (entry, 0); + matches = strcmp (g_variant_get_string (entry_key, NULL), key) == 0; + g_variant_unref (entry_key); + + if (matches) + break; + + g_variant_unref (entry); + } + + if (entry == NULL) + return NULL; + + value = g_variant_get_child_value (entry, 1); + g_variant_unref (entry); + + if (g_variant_is_of_type (value, G_VARIANT_TYPE_VARIANT)) { + GVariant *tmp; + + tmp = g_variant_get_variant (value); + g_variant_unref (value); + + if (expected_type && !g_variant_is_of_type (tmp, expected_type)) { + g_variant_unref (tmp); + tmp = NULL; + } + + value = tmp; + } + + g_assert (value == NULL || g_variant_is_of_type (value, expected_type)); + + return value; +} + +#define g_variant_lookup_value gst_g_variant_lookup_value +#endif /* !GLIB_CHECK_VERSION(2,28,0) */ + static gboolean gst_variant_lookup_string_value (GVariant * dict, const gchar * lang, const gchar ** translation) From 3b2b96bc569451a767c2dbe8aadb7a284e045e38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Wed, 10 Aug 2011 15:20:37 +0100 Subject: [PATCH 060/140] tag: fix stray printf in mklicensestables Don't dump debug output to stdout. --- gst-libs/gst/tag/mklicensestables.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gst-libs/gst/tag/mklicensestables.c b/gst-libs/gst/tag/mklicensestables.c index 1bd96212f3..66249d924c 100644 --- a/gst-libs/gst/tag/mklicensestables.c +++ b/gst-libs/gst/tag/mklicensestables.c @@ -606,7 +606,7 @@ create_translation_dict (GHashTable * ht_strings, const gchar * en) trans = g_hash_table_lookup (ht_strings, (gpointer) lang); if (trans != NULL && *trans != '\0' && strcmp (en, trans) != 0 && !skip_translation (ht_strings, lang, trans)) { - g_print ("%s (%s) => %s\n", en, lang, trans); + /* g_print ("%s (%s) => %s\n", en, lang, trans); */ g_variant_builder_add_value (&array, g_variant_new_dict_entry (g_variant_new_string (lang), g_variant_new_string (trans))); From 7c1fef18ac5eb11328254e8ec515e159c7d1e1eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Wed, 10 Aug 2011 15:21:41 +0100 Subject: [PATCH 061/140] tag: don't build helper programs that generate/update data by default No point building these by default. Also, these generated files should go into the srcdir, not the builddir in this case, since they're version controlled. --- gst-libs/gst/tag/Makefile.am | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/gst-libs/gst/tag/Makefile.am b/gst-libs/gst/tag/Makefile.am index 28e7b09c57..48137d6c87 100644 --- a/gst-libs/gst/tag/Makefile.am +++ b/gst-libs/gst/tag/Makefile.am @@ -75,26 +75,24 @@ endif # little program that reads iso_639.xml and outputs tables for us as fallback # for when iso-codes are not available (and so we don't have to read the xml # just to map codes) -if USE_ISO_CODES -ISO_CODE_PROGS = mklangtables mklangtables_SOURCES = mklangtables.c mklangtables_CFLAGS = $(GST_CFLAGS) mklangtables_LDADD = $(GST_LIBS) -else -ISO_CODE_PROGS = -endif -LICENSE_PROGS = mklicensestables mklicensestables_SOURCES = mklicensestables.c mklicensestables_CFLAGS = $(GST_CFLAGS) mklicensestables_LDADD = $(GST_LIBS) -noinst_PROGRAMS = $(ISO_CODE_PROGS) $(LICENSE_PROGS) +EXTRA_PROGRAMS = mklangtables mklicensestables + +update-isocodes: mklangtables + $(builddir)/mklangtables > $(srcdir)/lang-tables.dat && \ + echo "Updated lang-tables.dat" update-licenses: mklicensestables $(builddir)/mklicensestables \ - --translation-dictionary=$(builddir)/license-translations.dict \ - > $(builddir)/licenses-tables.dat && \ + --translation-dictionary=$(srcdir)/license-translations.dict \ + > $(srcdir)/licenses-tables.dat && \ echo "Updated licenses-tables.dat and license-translations.dict" #if USE_NLS From d0503165c4d43b9c2afcbfbcd99a7ad58b81ddf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Wed, 10 Aug 2011 15:57:02 +0100 Subject: [PATCH 062/140] win32: update libgsttag.def for new API --- win32/common/libgsttag.def | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/win32/common/libgsttag.def b/win32/common/libgsttag.def index dd91decc1a..f8572d838f 100644 --- a/win32/common/libgsttag.def +++ b/win32/common/libgsttag.def @@ -10,10 +10,18 @@ EXPORTS gst_tag_get_language_code_iso_639_2T gst_tag_get_language_codes gst_tag_get_language_name + gst_tag_get_license_description + gst_tag_get_license_flags + gst_tag_get_license_jurisdiction + gst_tag_get_license_nick + gst_tag_get_license_title + gst_tag_get_license_version + gst_tag_get_licenses gst_tag_id3_genre_count gst_tag_id3_genre_get gst_tag_image_data_to_image_buffer gst_tag_image_type_get_type + gst_tag_license_flags_get_type gst_tag_list_add_id3_image gst_tag_list_from_exif_buffer gst_tag_list_from_exif_buffer_with_tiff_header From cd46e0ca1338b3cf62aa1bd46c3c8cd3c452abf0 Mon Sep 17 00:00:00 2001 From: Thiago Santos Date: Tue, 9 Aug 2011 16:02:28 -0300 Subject: [PATCH 063/140] tag: exif: Exif strings should be ascii Use g_convert to turn all strings into extended ascii before writing to the exif buffer and converting back from ascii to utf8 when reading them. --- gst-libs/gst/tag/gstexiftag.c | 50 ++++++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/gst-libs/gst/tag/gstexiftag.c b/gst-libs/gst/tag/gstexiftag.c index 2527e99dbf..dadeeea5fd 100644 --- a/gst-libs/gst/tag/gstexiftag.c +++ b/gst-libs/gst/tag/gstexiftag.c @@ -781,22 +781,41 @@ write_exif_ascii_tag (GstExifWriter * writer, guint16 tag, const gchar * str) { gint size; guint32 offset = 0; + gchar *ascii_str; + gsize ascii_size; + GError *error = NULL; size = strlen (str) + 1; - if (size > 4) { + ascii_str = + g_convert (str, size, "latin1", "utf8", NULL, &ascii_size, &error); + + if (error) { + GST_WARNING ("Failed to convert exif tag to ascii: 0x%x - %s. Error: %s", + tag, str, error->message); + g_error_free (error); + g_free (ascii_str); + return; + } + + /* add the \0 at the end */ + ascii_size++; + + if (ascii_size > 4) { /* we only use the data offset here, later we add up the * resulting tag headers offset and the base offset */ offset = gst_byte_writer_get_size (&writer->datawriter); gst_exif_writer_write_tag_header (writer, tag, EXIF_TYPE_ASCII, - size, offset, FALSE); - gst_byte_writer_put_string (&writer->datawriter, str); + ascii_size, offset, FALSE); + gst_byte_writer_put_string (&writer->datawriter, ascii_str); } else { /* small enough to go in the offset */ - memcpy ((guint8 *) & offset, str, size); + memcpy ((guint8 *) & offset, ascii_str, ascii_size); gst_exif_writer_write_tag_header (writer, tag, EXIF_TYPE_ASCII, - size, offset, TRUE); + ascii_size, offset, TRUE); } + + g_free (ascii_str); } static void @@ -1164,7 +1183,9 @@ parse_exif_ascii_tag (GstExifReader * reader, const GstExifTagMatch * tag, { GType tagtype; gchar *str; + gchar *utfstr; guint32 real_offset; + GError *error = NULL; if (count > 4) { if (offset < reader->base_offset) { @@ -1187,11 +1208,22 @@ parse_exif_ascii_tag (GstExifReader * reader, const GstExifTagMatch * tag, str = g_strndup ((gchar *) offset_as_data, count); } + /* convert from ascii to utf8 */ + utfstr = g_convert (str, count, "utf8", "latin1", NULL, NULL, &error); + g_free (str); + if (error) { + GST_WARNING ("Skipping tag %d:%s. Failed to convert ascii string " + "to utf8 : %s - %s", tag->exif_tag, tag->gst_tag, str, error->message); + g_error_free (error); + g_free (utfstr); + return; + } + tagtype = gst_tag_get_type (tag->gst_tag); if (tagtype == GST_TYPE_DATE_TIME) { gint year = 0, month = 1, day = 1, hour = 0, minute = 0, second = 0; - if (sscanf (str, "%04d:%02d:%02d %02d:%02d:%02d", &year, &month, &day, + if (sscanf (utfstr, "%04d:%02d:%02d %02d:%02d:%02d", &year, &month, &day, &hour, &minute, &second) > 0) { GstDateTime *d; @@ -1203,13 +1235,13 @@ parse_exif_ascii_tag (GstExifReader * reader, const GstExifTagMatch * tag, GST_WARNING ("Failed to parse %s into a datetime tag", str); } } else if (tagtype == G_TYPE_STRING) { - gst_tag_list_add (reader->taglist, GST_TAG_MERGE_REPLACE, tag->gst_tag, str, - NULL); + gst_tag_list_add (reader->taglist, GST_TAG_MERGE_REPLACE, tag->gst_tag, + utfstr, NULL); } else { GST_WARNING ("No parsing function associated to %x(%s)", tag->exif_tag, tag->gst_tag); } - g_free (str); + g_free (utfstr); } static void From 436c533624176fc074c35da6a3a5312e08a3b920 Mon Sep 17 00:00:00 2001 From: Thiago Santos Date: Wed, 10 Aug 2011 13:16:13 -0300 Subject: [PATCH 064/140] tests: tag: exif: Add tests for 'non-trivial' chars Adds two new cases to check that characters are properly converted to ascii when writen to exif and parsed correctly back to utf8 when read. --- tests/check/libs/tag.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/check/libs/tag.c b/tests/check/libs/tag.c index 1a22840569..e6faa8f40b 100644 --- a/tests/check/libs/tag.c +++ b/tests/check/libs/tag.c @@ -1407,7 +1407,6 @@ do_simple_exif_tag_serialization_deserialization (const gchar * gsttag, GstTagList *taglist = gst_tag_list_new (); gst_tag_list_add_value (taglist, GST_TAG_MERGE_REPLACE, gsttag, value); - do_exif_tag_serialization_deserialization (taglist); gst_tag_list_free (taglist); @@ -1466,6 +1465,12 @@ GST_START_TEST (test_exif_tags_serialization_deserialization) do_simple_exif_tag_serialization_deserialization (GST_TAG_APPLICATION_NAME, &value); + /* non ascii chars */ + g_value_set_static_string (&value, "AaÄäEeËëIiÏïOoÖöUuÜü"); + do_simple_exif_tag_serialization_deserialization (GST_TAG_ARTIST, &value); + g_value_set_static_string (&value, "Äë"); + do_simple_exif_tag_serialization_deserialization (GST_TAG_ARTIST, &value); + /* image orientation tests */ g_value_set_static_string (&value, "rotate-0"); do_simple_exif_tag_serialization_deserialization (GST_TAG_IMAGE_ORIENTATION, From 72b92af807ed4fb44e6d0002490af1019ce3322a Mon Sep 17 00:00:00 2001 From: Thiago Santos Date: Thu, 11 Aug 2011 12:12:07 -0300 Subject: [PATCH 065/140] tag: exif: Check for utf8 before trying to convert If the string is already on utf8, there is no need to try to convert it, because it is useless and it might garble the string. --- gst-libs/gst/tag/gstexiftag.c | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/gst-libs/gst/tag/gstexiftag.c b/gst-libs/gst/tag/gstexiftag.c index dadeeea5fd..3e5e53b0b5 100644 --- a/gst-libs/gst/tag/gstexiftag.c +++ b/gst-libs/gst/tag/gstexiftag.c @@ -1209,14 +1209,22 @@ parse_exif_ascii_tag (GstExifReader * reader, const GstExifTagMatch * tag, } /* convert from ascii to utf8 */ - utfstr = g_convert (str, count, "utf8", "latin1", NULL, NULL, &error); - g_free (str); - if (error) { - GST_WARNING ("Skipping tag %d:%s. Failed to convert ascii string " - "to utf8 : %s - %s", tag->exif_tag, tag->gst_tag, str, error->message); - g_error_free (error); - g_free (utfstr); - return; + if (g_utf8_validate (str, -1, NULL)) { + GST_DEBUG ("Exif string is already on utf8: %s", str); + utfstr = str; + } else { + GST_DEBUG ("Exif string isn't utf8, trying to convert from latin1: %s", + str); + utfstr = g_convert (str, count, "utf8", "latin1", NULL, NULL, &error); + g_free (str); + if (error) { + GST_WARNING ("Skipping tag %d:%s. Failed to convert ascii string " + "to utf8 : %s - %s", tag->exif_tag, tag->gst_tag, str, + error->message); + g_error_free (error); + g_free (utfstr); + return; + } } tagtype = gst_tag_get_type (tag->gst_tag); From 58fd202b7d5cb0e734713be153cca63330ba87e0 Mon Sep 17 00:00:00 2001 From: Vincent Penquerc'h Date: Thu, 11 Aug 2011 19:23:42 +0100 Subject: [PATCH 066/140] audioresample: fix SSE2 building with double precision The full double implementation was missing. https://bugzilla.gnome.org/show_bug.cgi?id=636562 --- gst/audioresample/resample_sse.h | 48 ++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/gst/audioresample/resample_sse.h b/gst/audioresample/resample_sse.h index 64be8a1616..c418ca1d5c 100644 --- a/gst/audioresample/resample_sse.h +++ b/gst/audioresample/resample_sse.h @@ -75,6 +75,22 @@ static inline float interpolate_product_single(const float *a, const float *b, u #include #define OVERRIDE_INNER_PRODUCT_DOUBLE +#ifdef DOUBLE_PRECISION +static inline double inner_product_double(const double *a, const double *b, unsigned int len) +{ + int i; + double ret; + __m128d sum = _mm_setzero_pd(); + for (i=0;i Date: Thu, 11 Aug 2011 15:54:15 +0100 Subject: [PATCH 067/140] audioresample: use SSE/SSE2 when possible Compile in the code on i386 and x86_64, and use ORC to determine when the runtime platform can run the code. https://bugzilla.gnome.org/show_bug.cgi?id=636562 --- configure.ac | 2 + gst/audioresample/resample.c | 99 ++++++++++++++++++++-- gst/audioresample/resample_sse.h | 4 + gst/audioresample/speex_resampler_double.c | 1 + gst/audioresample/speex_resampler_float.c | 2 + 5 files changed, 99 insertions(+), 9 deletions(-) diff --git a/configure.ac b/configure.ac index f63700e8da..f14abe19eb 100644 --- a/configure.ac +++ b/configure.ac @@ -213,6 +213,8 @@ LIBS="$save_libs" dnl used in gst-libs/gst/pbutils and associated unit test AC_CHECK_HEADERS([process.h sys/types.h sys/wait.h sys/stat.h]) +AC_CHECK_HEADERS([xmmintrin.h emmintrin.h]) + dnl ffmpegcolorspace includes _stdint.h dnl also, Windows does not have long long AX_CREATE_STDINT_H diff --git a/gst/audioresample/resample.c b/gst/audioresample/resample.c index a10c9f698d..ca4960804e 100644 --- a/gst/audioresample/resample.c +++ b/gst/audioresample/resample.c @@ -64,10 +64,30 @@ #ifdef OUTSIDE_SPEEX #include +#ifdef HAVE_STRING_H +#include +#endif + #include +#ifdef HAVE_ORC +#include +#endif + #define EXPORT G_GNUC_INTERNAL +#ifdef _USE_SSE +#ifndef HAVE_XMMINTRIN_H +#undef _USE_SSE +#endif +#endif + +#ifdef _USE_SSE2 +#ifndef HAVE_EMMINTRIN_H +#undef _USE_SSE2 +#endif +#endif + static inline void * speex_alloc (int size) { @@ -110,7 +130,7 @@ speex_free (void *ptr) #define NULL 0 #endif -#ifdef _USE_SSE +#if defined _USE_SSE || defined _USE_SSE2 #include "resample_sse.h" #endif @@ -121,6 +141,28 @@ speex_free (void *ptr) #define FIXED_STACK_ALLOC 1024 #endif +/* Allow selecting SSE or not when compiled with SSE support */ +#ifdef _USE_SSE +#define SSE_FALLBACK(macro) \ + if (st->use_sse) goto sse_##macro##_sse; { +#define SSE_IMPLEMENTATION(macro) \ + goto sse_##macro##_end; } sse_##macro##_sse: { +#define SSE_END(macro) sse_##macro##_end:; } +#else +#define SSE_FALLBACK(macro) +#endif + +#ifdef _USE_SSE2 +#define SSE2_FALLBACK(macro) \ + if (st->use_sse2) goto sse2_##macro##_sse2; { +#define SSE2_IMPLEMENTATION(macro) \ + goto sse2_##macro##_end; } sse2_##macro##_sse2: { +#define SSE2_END(macro) sse2_##macro##_end:; } +#else +#define SSE2_FALLBACK(macro) +#endif + + typedef int (*resampler_basic_func) (SpeexResamplerState *, spx_uint32_t, const spx_word16_t *, spx_uint32_t *, spx_word16_t *, spx_uint32_t *); @@ -155,6 +197,9 @@ struct SpeexResamplerState_ int in_stride; int out_stride; + + int use_sse:1; + int use_sse2:1; }; static double kaiser12_table[68] = { @@ -410,7 +455,7 @@ resampler_basic_direct_single (SpeexResamplerState * st, const spx_word16_t *sinc = &sinc_table[samp_frac_num * N]; const spx_word16_t *iptr = &in[last_sample]; -#ifndef OVERRIDE_INNER_PRODUCT_SINGLE + SSE_FALLBACK (INNER_PRODUCT_SINGLE) sum = 0; for (j = 0; j < N; j++) sum += MULT16_16 (sinc[j], iptr[j]); @@ -427,8 +472,10 @@ resampler_basic_direct_single (SpeexResamplerState * st, } sum = accum[0] + accum[1] + accum[2] + accum[3]; */ -#else +#ifdef OVERRIDE_INNER_PRODUCT_SINGLE + SSE_IMPLEMENTATION (INNER_PRODUCT_SINGLE) sum = inner_product_single (sinc, iptr, N); + SSE_END(INNER_PRODUCT_SINGLE) #endif out[out_stride * out_sample++] = SATURATE32 (PSHR32 (sum, 15), 32767); @@ -471,7 +518,7 @@ resampler_basic_direct_double (SpeexResamplerState * st, const spx_word16_t *sinc = &sinc_table[samp_frac_num * N]; const spx_word16_t *iptr = &in[last_sample]; -#ifndef OVERRIDE_INNER_PRODUCT_DOUBLE + SSE2_FALLBACK (INNER_PRODUCT_DOUBLE) double accum[4] = { 0, 0, 0, 0 }; for (j = 0; j < N; j += 4) { @@ -481,8 +528,10 @@ resampler_basic_direct_double (SpeexResamplerState * st, accum[3] += sinc[j + 3] * iptr[j + 3]; } sum = accum[0] + accum[1] + accum[2] + accum[3]; -#else +#ifdef OVERRIDE_INNER_PRODUCT_DOUBLE + SSE2_IMPLEMENTATION (INNER_PRODUCT_DOUBLE) sum = inner_product_double (sinc, iptr, N); + SSE2_END (INNER_PRODUCT_DOUBLE) #endif out[out_stride * out_sample++] = PSHR32 (sum, 15); @@ -534,7 +583,7 @@ resampler_basic_interpolate_single (SpeexResamplerState * st, spx_word16_t interp[4]; -#ifndef OVERRIDE_INTERPOLATE_PRODUCT_SINGLE + SSE_FALLBACK (INTERPOLATE_PRODUCT_SINGLE) spx_word32_t accum[4] = { 0, 0, 0, 0 }; for (j = 0; j < N; j++) { @@ -559,12 +608,14 @@ resampler_basic_interpolate_single (SpeexResamplerState * st, 1)) + MULT16_32_Q15 (interp[1], SHR32 (accum[1], 1)) + MULT16_32_Q15 (interp[2], SHR32 (accum[2], 1)) + MULT16_32_Q15 (interp[3], SHR32 (accum[3], 1)); -#else +#ifdef OVERRIDE_INTERPOLATE_PRODUCT_SINGLE + SSE_IMPLEMENTATION (INTERPOLATE_PRODUCT_SINGLE) cubic_coef (frac, interp); sum = interpolate_product_single (iptr, st->sinc_table + st->oversample + 4 - offset - 2, N, st->oversample, interp); + SSE_END (INTERPOLATE_PRODUCT_SINGLE) #endif out[out_stride * out_sample++] = SATURATE32 (PSHR32 (sum, 14), 32767); @@ -624,7 +675,7 @@ resampler_basic_interpolate_double (SpeexResamplerState * st, spx_word16_t interp[4]; -#ifndef OVERRIDE_INTERPOLATE_PRODUCT_DOUBLE + SSE2_FALLBACK (INTERPOLATE_PRODUCT_DOUBLE) double accum[4] = { 0, 0, 0, 0 }; for (j = 0; j < N; j++) { @@ -648,12 +699,14 @@ resampler_basic_interpolate_double (SpeexResamplerState * st, MULT16_32_Q15 (interp[0], accum[0]) + MULT16_32_Q15 (interp[1], accum[1]) + MULT16_32_Q15 (interp[2], accum[2]) + MULT16_32_Q15 (interp[3], accum[3]); -#else +#ifdef OVERRIDE_INTERPOLATE_PRODUCT_DOUBLE + SSE2_IMPLEMENTATION (INTERPOLATE_PRODUCT_DOUBLE) cubic_coef (frac, interp); sum = interpolate_product_double (iptr, st->sinc_table + st->oversample + 4 - offset - 2, N, st->oversample, interp); + SSE2_END (INTERPOLATE_PRODUCT_DOUBLE) #endif out[out_stride * out_sample++] = PSHR32 (sum, 15); @@ -875,6 +928,17 @@ speex_resampler_init (spx_uint32_t nb_channels, spx_uint32_t in_rate, out_rate, quality, err); } +static void +check_insn_set (SpeexResamplerState * st, const char *name) +{ + if (!name) + return; + if (!strcmp (name, "sse")) + st->use_sse = 1; + if (!strcmp (name, "sse2")) + st->use_sse = st->use_sse2 = 1; +} + EXPORT SpeexResamplerState * speex_resampler_init_frac (spx_uint32_t nb_channels, spx_uint32_t ratio_num, spx_uint32_t ratio_den, spx_uint32_t in_rate, spx_uint32_t out_rate, @@ -912,6 +976,23 @@ speex_resampler_init_frac (spx_uint32_t nb_channels, spx_uint32_t ratio_num, st->buffer_size = 160; #endif + st->use_sse = st->use_sse2 = 0; +#if defined HAVE_ORC && !defined DISABLE_ORC + orc_init (); + { + OrcTarget *target = orc_target_get_default (); + if (target) { + unsigned int flags = orc_target_get_default_flags (target); + check_insn_set (st, orc_target_get_name (target)); + for (i = 0; i < 32; ++i) { + if (flags & (1 << i)) { + check_insn_set (st, orc_target_get_flag_name (target, i)); + } + } + } + } +#endif + /* Per channel data */ st->last_sample = (spx_int32_t *) speex_alloc (nb_channels * sizeof (int)); st->magic_samples = (spx_uint32_t *) speex_alloc (nb_channels * sizeof (int)); diff --git a/gst/audioresample/resample_sse.h b/gst/audioresample/resample_sse.h index c418ca1d5c..36522a3659 100644 --- a/gst/audioresample/resample_sse.h +++ b/gst/audioresample/resample_sse.h @@ -34,7 +34,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#ifdef HAVE_XMMINTRIN_H #include +#endif #define OVERRIDE_INNER_PRODUCT_SINGLE static inline float inner_product_single(const float *a, const float *b, unsigned int len) @@ -72,7 +74,9 @@ static inline float interpolate_product_single(const float *a, const float *b, u } #ifdef _USE_SSE2 +#ifdef HAVE_EMMINTRIN_H #include +#endif #define OVERRIDE_INNER_PRODUCT_DOUBLE #ifdef DOUBLE_PRECISION diff --git a/gst/audioresample/speex_resampler_double.c b/gst/audioresample/speex_resampler_double.c index e5a25714ca..ef2503d609 100644 --- a/gst/audioresample/speex_resampler_double.c +++ b/gst/audioresample/speex_resampler_double.c @@ -17,6 +17,7 @@ * Boston, MA 02111-1307, USA. */ +#define _USE_SSE2 #define FLOATING_POINT #define DOUBLE_PRECISION #define OUTSIDE_SPEEX diff --git a/gst/audioresample/speex_resampler_float.c b/gst/audioresample/speex_resampler_float.c index f13f60c5b0..ef3df1551b 100644 --- a/gst/audioresample/speex_resampler_float.c +++ b/gst/audioresample/speex_resampler_float.c @@ -17,6 +17,8 @@ * Boston, MA 02111-1307, USA. */ +#define _USE_SSE +#define _USE_SSE2 #define FLOATING_POINT #define OUTSIDE_SPEEX #define RANDOM_PREFIX resample_float From 49ec6899f4909e94cfca2d7844800c1ac77f80a6 Mon Sep 17 00:00:00 2001 From: Vincent Penquerc'h Date: Thu, 11 Aug 2011 18:50:08 +0100 Subject: [PATCH 068/140] audioresample: fix quality setting being ignored by the resampler state https://bugzilla.gnome.org/show_bug.cgi?id=636562 --- gst/audioresample/gstaudioresample.c | 33 ++++++++++++++-------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/gst/audioresample/gstaudioresample.c b/gst/audioresample/gstaudioresample.c index 9364a8666c..e63d106db0 100644 --- a/gst/audioresample/gstaudioresample.c +++ b/gst/audioresample/gstaudioresample.c @@ -1346,18 +1346,19 @@ gst_audio_resample_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstAudioResample *resample; + gint quality; resample = GST_AUDIO_RESAMPLE (object); switch (prop_id) { case PROP_QUALITY: GST_BASE_TRANSFORM_LOCK (resample); - resample->quality = g_value_get_int (value); - GST_DEBUG_OBJECT (resample, "new quality %d", resample->quality); + quality = g_value_get_int (value); + GST_DEBUG_OBJECT (resample, "new quality %d", quality); gst_audio_resample_update_state (resample, resample->width, resample->channels, resample->inrate, resample->outrate, - resample->quality, resample->fp); + quality, resample->fp); GST_BASE_TRANSFORM_UNLOCK (resample); break; case PROP_FILTER_LENGTH:{ @@ -1365,33 +1366,33 @@ gst_audio_resample_set_property (GObject * object, guint prop_id, GST_BASE_TRANSFORM_LOCK (resample); if (filter_length <= 8) - resample->quality = 0; + quality = 0; else if (filter_length <= 16) - resample->quality = 1; + quality = 1; else if (filter_length <= 32) - resample->quality = 2; + quality = 2; else if (filter_length <= 48) - resample->quality = 3; + quality = 3; else if (filter_length <= 64) - resample->quality = 4; + quality = 4; else if (filter_length <= 80) - resample->quality = 5; + quality = 5; else if (filter_length <= 96) - resample->quality = 6; + quality = 6; else if (filter_length <= 128) - resample->quality = 7; + quality = 7; else if (filter_length <= 160) - resample->quality = 8; + quality = 8; else if (filter_length <= 192) - resample->quality = 9; + quality = 9; else - resample->quality = 10; + quality = 10; - GST_DEBUG_OBJECT (resample, "new quality %d", resample->quality); + GST_DEBUG_OBJECT (resample, "new quality %d", quality); gst_audio_resample_update_state (resample, resample->width, resample->channels, resample->inrate, resample->outrate, - resample->quality, resample->fp); + quality, resample->fp); GST_BASE_TRANSFORM_UNLOCK (resample); break; } From 83bffb642c58fe8dee36e9c6bca97ca4e9f3d619 Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Sun, 18 Dec 2005 15:14:44 +0000 Subject: [PATCH 069/140] tag: id3v2: all new LGPL id3 demuxer, can use zlib for compressed frames Original commit message from CVS: * configure.ac: Check for optional dependency on zlib for id3demux * gst-libs/gst/tag/Makefile.am: * gst-libs/gst/tag/gstid3demux.c: (gst_gst_id3demux_get_type), (gst_id3demux_base_init), (gst_id3demux_class_init), (gst_id3demux_reset), (gst_id3demux_init), (gst_id3demux_dispose), (gst_id3demux_add_srcpad), (gst_id3demux_remove_srcpad), (gst_id3demux_trim_buffer), (gst_id3demux_chain), (gst_id3demux_set_property), (gst_id3demux_get_property), (id3demux_get_upstream_size), (gst_id3demux_srcpad_event), (gst_id3demux_read_id3v1), (gst_id3demux_read_id3v2), (gst_id3demux_sink_activate), (gst_id3demux_src_activate_pull), (gst_id3demux_src_checkgetrange), (gst_id3demux_read_range), (gst_id3demux_src_getrange), (gst_id3demux_change_state), (gst_id3demux_pad_query), (gst_id3demux_get_query_types), (simple_find_peek), (simple_find_suggest), (gst_id3demux_do_typefind), (gst_id3demux_send_tag_event), (plugin_init): * gst-libs/gst/tag/gstid3demux.h: * gst-libs/gst/tag/id3v2.c: (read_synch_uint), (id3demux_read_id3v1_tag), (id3demux_read_id3v2_tag), (id3demux_id3v2_frame_hdr_size), (convert_fid_to_v240), (id3demux_id3v2_frames_to_tag_list): * gst-libs/gst/tag/id3v2.h: * gst-libs/gst/tag/id3v2.4.0-frames.txt: * gst-libs/gst/tag/id3v2.4.0-structure.txt: * gst-libs/gst/tag/id3v2frames.c: (id3demux_id3v2_parse_frame), (parse_comment_frame), (parse_text_identification_frame), (id3v2_tag_to_taglist), (parse_split_strings): All new LGPL id3 demuxer. Can use zlib for compressed frames, otherwise it discards them. Works on my test files. * gst/wavparse/gstwavparse.c: (gst_wavparse_loop): Don't send EOS to a non-existing srcpad The debug category can be static --- gst-libs/gst/tag/id3v2.c | 431 +++++++++++++++++++++++++++++++++ gst-libs/gst/tag/id3v2.h | 112 +++++++++ gst-libs/gst/tag/id3v2frames.c | 382 +++++++++++++++++++++++++++++ 3 files changed, 925 insertions(+) create mode 100644 gst-libs/gst/tag/id3v2.c create mode 100644 gst-libs/gst/tag/id3v2.h create mode 100644 gst-libs/gst/tag/id3v2frames.c diff --git a/gst-libs/gst/tag/id3v2.c b/gst-libs/gst/tag/id3v2.c new file mode 100644 index 0000000000..1cc8c14ec9 --- /dev/null +++ b/gst-libs/gst/tag/id3v2.c @@ -0,0 +1,431 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: t; c-basic-offset: 2 -*- */ +/* Copyright 2005 Jan Schmidt + * Copyright 2002,2003 Scott Wheeler (portions from taglib) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "id3tags.h" + +GST_DEBUG_CATEGORY_EXTERN (id3demux_debug); +#define GST_CAT_DEFAULT (id3demux_debug) + +#define HANDLE_INVALID_SYNCSAFE +static ID3TagsResult +id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size); + +guint +read_synch_uint (guint8 * data, guint size) +{ + gint i; + guint result = 0; + gint invalid = 0; + + g_assert (size <= 4); + + size--; + for (i = 0; i <= size; i++) { + invalid |= data[i] & 0x80; + result |= (data[i] & 0x7f) << ((size - i) * 7); + } + +#ifdef HANDLE_INVALID_SYNCSAFE + if (invalid) { + GST_WARNING ("Invalid synch-safe integer in ID3v2 frame " + "- using the actual value instead"); + result = 0; + for (i = 0; i <= size; i++) { + result |= data[i] << ((size - i) * 8); + } + } +#endif + return result; +} + +ID3TagsResult +id3demux_read_id3v1_tag (GstBuffer * buffer, guint * id3v1_size, + GstTagList ** tags) +{ + GstTagList *new_tags; + + guint8 *data; + + g_return_val_if_fail (buffer != NULL, ID3TAGS_V1_BAD_SIZE); + + data = GST_BUFFER_DATA (buffer); + + if (GST_BUFFER_SIZE (buffer) != ID3V1_TAG_SIZE) + return ID3TAGS_V1_BAD_SIZE; + + /* Check that buffer starts with 'TAG' */ + if (data[0] != 'T' || data[1] != 'A' || data[2] != 'G') { + if (id3v1_size) + *id3v1_size = 0; + GST_DEBUG ("No ID3v1 tag in data"); + return ID3TAGS_READ_TAG; + } + + g_return_val_if_fail (tags != NULL, ID3TAGS_READ_TAG); + + new_tags = gst_tag_list_new_from_id3v1 (GST_BUFFER_DATA (buffer)); + if (new_tags == NULL) + return ID3TAGS_BROKEN_TAG; + + if (*tags) { + GstTagList *merged; + + merged = gst_tag_list_merge (*tags, new_tags, GST_TAG_MERGE_REPLACE); + gst_tag_list_free (*tags); + gst_tag_list_free (new_tags); + *tags = merged; + } else + *tags = new_tags; + + return ID3TAGS_READ_TAG; +} + +ID3TagsResult +id3demux_read_id3v2_tag (GstBuffer * buffer, guint * id3v2_size, + GstTagList ** tags) +{ + guint8 *data; + guint read_size; + ID3TagsWorking work; + guint8 flags; + ID3TagsResult result; + guint16 version; + + g_return_val_if_fail (buffer != NULL, ID3TAGS_MORE_DATA); + + if (GST_BUFFER_SIZE (buffer) < ID3V2_MARK_SIZE) + return ID3TAGS_MORE_DATA; /* Need more data to decide with */ + + data = GST_BUFFER_DATA (buffer); + + /* Check for 'ID3' string at start of buffer */ + if (data[0] != 'I' || data[1] != 'D' || data[2] != '3') { + if (id3v2_size) + *id3v2_size = 0; + GST_DEBUG ("No ID3v2 tag in data"); + return ID3TAGS_READ_TAG; + } + + /* OK, get enough data to read the entire header */ + if (GST_BUFFER_SIZE (buffer) < ID3V2_HDR_SIZE) + return ID3TAGS_MORE_DATA; /* Need more data to decide with */ + + /* Read the version */ + version = GST_READ_UINT16_BE (data + 3); + + /* Read the flags */ + flags = data[5]; + + /* Read the size from the header */ + read_size = read_synch_uint (data + 6, 4); + if (read_size == 0) { + return ID3TAGS_BROKEN_TAG; + } + read_size += 10; + + /* Expand the read size to include a footer if there is one */ + if (flags & ID3V2_HDR_FLAG_FOOTER) { + read_size += 10; + } + + if (id3v2_size) + *id3v2_size = read_size; + + /* Validate the version. At the moment, we only support up to 2.4.0 */ + if (ID3V2_VER_MAJOR (version) > 4 || ID3V2_VER_MINOR (version) > 0) { + GST_WARNING ("ID3v2 tag is from revision 2.%d.%d, " + "but decoder only supports 2.%d.%d. Ignoring as per spec.", + version >> 8, version & 0xff, ID3V2_VERSION >> 8, ID3V2_VERSION & 0xff); + return ID3TAGS_READ_TAG; + } + GST_DEBUG ("ID3v2 tag with revision 2.%d.%d\n", version >> 8, version & 0xff); + + if (GST_BUFFER_SIZE (buffer) < read_size) + return ID3TAGS_MORE_DATA; /* Need more data to decode with */ + + g_return_val_if_fail (tags != NULL, ID3TAGS_READ_TAG); + + memset (&work, 0, sizeof (ID3TagsWorking)); + work.buffer = buffer; + work.hdr.version = version; + work.hdr.size = read_size; + work.hdr.flags = flags; + work.hdr.frame_data = GST_BUFFER_DATA (buffer) + ID3V2_HDR_SIZE; + if (flags & ID3V2_HDR_FLAG_FOOTER) + work.hdr.frame_data_size = read_size - ID3V2_HDR_SIZE - 10; + else + work.hdr.frame_data_size = read_size - ID3V2_HDR_SIZE; + + result = id3demux_id3v2_frames_to_tag_list (&work, read_size); + + /* Actually read the tags */ + if (work.tags != NULL) { + if (*tags) { + GstTagList *merged; + + merged = gst_tag_list_merge (*tags, work.tags, GST_TAG_MERGE_REPLACE); + gst_tag_list_free (*tags); + gst_tag_list_free (work.tags); + *tags = merged; + } else + *tags = work.tags; + } + + return result; +} + +static guint +id3demux_id3v2_frame_hdr_size (guint id3v2ver) +{ + /* ID3v2 < 2.3.0 only had 6 byte header */ + switch (ID3V2_VER_MAJOR (id3v2ver)) { + case 0: + case 1: + case 2: + return 6; + case 3: + case 4: + default: + return 10; + } +} + +static const gchar *obsolete_frame_ids[] = { + "CRM", "EQU", "LNK", "RVA", "TIM", "TSI", /* From 2.2 */ + "EQUA", "RVAD", "TIME", "TRDA", "TSIZ", /* From 2.3 */ + NULL +}; + +const struct ID3v2FrameIDConvert +{ + gchar *orig; + gchar *new; +} frame_id_conversions[] = { + /* 2.3.x frames */ + { + "TDAT", "TDRC"}, { + "TORY", "TDOR"}, { + "TYER", "TDRC"}, + /* 2.2.x frames */ + { + "BUF", "RBUF"}, { + "CNT", "PCNT"}, { + "COM", "COMM"}, { + "CRA", "AENC"}, { + "ETC", "ETCO"}, { + "GEO", "GEOB"}, { + "IPL", "TIPL"}, { + "MCI", "MCDI"}, { + "MLL", "MLLT"}, { + "PIC", "APIC"}, { + "POP", "POPM"}, { + "REV", "RVRB"}, { + "SLT", "SYLT"}, { + "STC", "SYTC"}, { + "TAL", "TALB"}, { + "TBP", "TBPM"}, { + "TCM", "TCOM"}, { + "TCR", "TCOP"}, { + "TDA", "TDRC"}, { + "TDY", "TDLY"}, { + "TEN", "TENC"}, { + "TFT", "TFLT"}, { + "TKE", "TKEY"}, { + "TLA", "TLAN"}, { + "TLE", "TLEN"}, { + "TMT", "TMED"}, { + "TOA", "TOAL"}, { + "TOF", "TOFN"}, { + "TOL", "TOLY"}, { + "TOR", "TDOR"}, { + "TOT", "TOAL"}, { + "TP1", "TPE1"}, { + "TP2", "TPE2"}, { + "TP3", "TPE3"}, { + "TP4", "TPE4"}, { + "TPA", "TPOS"}, { + "TPB", "TPUB"}, { + "TRC", "TSRC"}, { + "TRD", "TDRC"}, { + "TRK", "TRCK"}, { + "TSS", "TSSE"}, { + "TT1", "TIT1"}, { + "TT2", "TIT2"}, { + "TT3", "TIT3"}, { + "TXT", "TOLY"}, { + "TXX", "TXXX"}, { + "TYE", "TDRC"}, { + "UFI", "UFID"}, { + "ULT", "USLT"}, { + "WAF", "WOAF"}, { + "WAR", "WOAR"}, { + "WAS", "WOAS"}, { + "WCM", "WCOM"}, { + "WCP", "WCOP"}, { + "WPB", "WPUB"}, { + "WXX", "WXXX"}, { + NULL, NULL} +}; + +static gboolean +convert_fid_to_v240 (gchar * frame_id) +{ + gint i = 0; + + while (obsolete_frame_ids[i] != NULL) { + if (strncmp (frame_id, obsolete_frame_ids[i], 5) == 0) + return TRUE; + i++; + } + + i = 0; + while (frame_id_conversions[i].orig != NULL) { + if (strncmp (frame_id, frame_id_conversions[i].orig, 5) == 0) { + strcpy (frame_id, frame_id_conversions[i].new); + return FALSE; + } + i++; + } + return FALSE; +} + +static ID3TagsResult +id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size) +{ + guint frame_hdr_size; + gboolean read_a_frame = FALSE; + guint8 *start; + + /* Extended header if present */ + if (work->hdr.flags & ID3V2_HDR_FLAG_EXTHDR) { + work->hdr.ext_hdr_size = read_synch_uint (work->hdr.frame_data, 4); + if (work->hdr.ext_hdr_size < 6 || + (work->hdr.ext_hdr_size) > work->hdr.frame_data_size) { + return ID3TAGS_BROKEN_TAG; + } + work->hdr.ext_flag_bytes = work->hdr.frame_data[4]; + if (5 + work->hdr.ext_flag_bytes > work->hdr.frame_data_size) { + GST_DEBUG + ("Tag claims extended header, but doesn't have enough bytes. Broken tag"); + return ID3TAGS_BROKEN_TAG; + } + + work->hdr.ext_flag_data = work->hdr.frame_data + 5; + work->hdr.frame_data += work->hdr.ext_hdr_size; + work->hdr.frame_data_size -= work->hdr.ext_hdr_size; + } + + start = GST_BUFFER_DATA (work->buffer); + frame_hdr_size = id3demux_id3v2_frame_hdr_size (work->hdr.version); + if (work->hdr.frame_data_size <= frame_hdr_size) { + GST_DEBUG ("Tag has no data frames. Broken tag"); + return ID3TAGS_BROKEN_TAG; /* Must have at least one frame */ + } + + work->tags = gst_tag_list_new (); + g_return_val_if_fail (work->tags != NULL, ID3TAGS_READ_TAG); + + while (work->hdr.frame_data_size > frame_hdr_size) { + guint frame_size = 0; + gchar frame_id[5] = ""; + guint16 frame_flags = 0x0; + gboolean obsolete_id = FALSE; + + /* Read the header */ + switch (ID3V2_VER_MAJOR (work->hdr.version)) { + case 0: + case 1: + case 2: + frame_id[0] = work->hdr.frame_data[0]; + frame_id[1] = work->hdr.frame_data[1]; + frame_id[2] = work->hdr.frame_data[2]; + frame_id[3] = 0; + frame_id[4] = 0; + obsolete_id = convert_fid_to_v240 (frame_id); + + frame_size = read_synch_uint (work->hdr.frame_data + 3, 3); + frame_flags = 0; + break; + case 3: + case 4: + default: + frame_id[0] = work->hdr.frame_data[0]; + frame_id[1] = work->hdr.frame_data[1]; + frame_id[2] = work->hdr.frame_data[2]; + frame_id[3] = work->hdr.frame_data[3]; + frame_id[4] = 0; + frame_size = read_synch_uint (work->hdr.frame_data + 4, 4); + frame_flags = GST_READ_UINT16_BE (work->hdr.frame_data + 8); + + if (ID3V2_VER_MAJOR (work->hdr.version) == 3) { + frame_flags &= ID3V2_3_FRAME_FLAGS_MASK; + obsolete_id = convert_fid_to_v240 (frame_id); + } + break; + } + + work->hdr.frame_data += frame_hdr_size; + work->hdr.frame_data_size -= frame_hdr_size; + + if (frame_size > work->hdr.frame_data_size || + frame_size == 0 || strcmp (frame_id, "") == 0) + break; /* No more frames to read */ + +#if 0 + g_print + ("Frame @ %d (0x%02x) id %s size %d, next=%d (0x%02x) obsolete=%d\n", + work->hdr.frame_data - start, work->hdr.frame_data - start, frame_id, + frame_size, work->hdr.frame_data + frame_hdr_size + frame_size - start, + work->hdr.frame_data + frame_hdr_size + frame_size - start, + obsolete_id); +#endif + + if (!obsolete_id) { + /* Now, read, decompress etc the contents of the frame + * into a TagList entry */ + work->cur_frame_size = frame_size; + work->frame_id = frame_id; + work->frame_flags = frame_flags; + + if (id3demux_id3v2_parse_frame (work)) { + read_a_frame = TRUE; + GST_LOG ("Extracted frame with id %s", frame_id); + } + } + work->hdr.frame_data += frame_size; + work->hdr.frame_data_size -= frame_size; + } + + if (!read_a_frame) { + GST_DEBUG ("Could not extract any frames from tag. Broken tag"); + gst_tag_list_free (work->tags); + work->tags = NULL; + return ID3TAGS_BROKEN_TAG; + } + + return ID3TAGS_READ_TAG; +} diff --git a/gst-libs/gst/tag/id3v2.h b/gst-libs/gst/tag/id3v2.h new file mode 100644 index 0000000000..c81cd55066 --- /dev/null +++ b/gst-libs/gst/tag/id3v2.h @@ -0,0 +1,112 @@ +/* Copyright 2005 Jan Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ID3TAGS_H__ +#define __ID3TAGS_H__ + +#include + +G_BEGIN_DECLS + +#define ID3V1_TAG_SIZE 128 +#define ID3V2_MARK_SIZE 3 +#define ID3V2_HDR_SIZE 10 + +typedef enum { + ID3TAGS_V1_BAD_SIZE, + ID3TAGS_MORE_DATA, + ID3TAGS_READ_TAG, + ID3TAGS_BROKEN_TAG +} ID3TagsResult; + +/* From id3tags.c */ +ID3TagsResult id3demux_read_id3v1_tag (GstBuffer *buffer, guint *id3v1_size, + GstTagList **tags); +ID3TagsResult id3demux_read_id3v2_tag (GstBuffer *buffer, guint *id3v2_size, + GstTagList **tags); +G_END_DECLS + +/* Things shared by id3tags.c and id3v2frames.c */ +#define ID3V2_VERSION 0x0400 +#define ID3V2_VER_MAJOR(v) ((v) >> 8) +#define ID3V2_VER_MINOR(v) ((v) & 0xff) + +typedef struct { + guint16 version; + guint8 flags; + guint32 size; + + guint8 *frame_data; + guint32 frame_data_size; + + guint32 ext_hdr_size; + guint8 ext_flag_bytes; + guint8 *ext_flag_data; +} ID3v2Header; + +typedef struct { + ID3v2Header hdr; + + GstBuffer *buffer; + GstTagList *tags; + + /* Current frame decoding */ + guint cur_frame_size; + gchar *frame_id; + guint16 frame_flags; + + guint8 *parse_data; + guint parse_size; +} ID3TagsWorking; + +enum { + ID3V2_HDR_FLAG_UNSYNC = 0x80, + ID3V2_HDR_FLAG_EXTHDR = 0x40, + ID3V2_HDR_FLAG_EXPERIMENTAL = 0x20, + ID3V2_HDR_FLAG_FOOTER = 0x10 +}; + +enum { + ID3V2_EXT_FLAG_UPDATE = 0x80, + ID3V2_EXT_FLAG_CRC = 0x40, + ID3V2_EXT_FLAG_RESTRICTED = 0x20 +}; + +enum { + ID3V2_FRAME_STATUS_FRAME_ALTER_PRESERVE = 0x4000, + ID3V2_FRAME_STATUS_FILE_ALTER_PRESERVE = 0x2000, + ID3V2_FRAME_STATUS_READONLY = 0x1000, + ID3V2_FRAME_FORMAT_GROUPING_ID = 0x0040, + ID3V2_FRAME_FORMAT_COMPRESSION = 0x0008, + ID3V2_FRAME_FORMAT_ENCRYPTION = 0x0004, + ID3V2_FRAME_FORMAT_UNSYNCHRONISATION = 0x0002, + ID3V2_FRAME_FORMAT_DATA_LENGTH_INDICATOR = 0x0001 +}; + +#define ID3V2_3_FRAME_FLAGS_MASK \ + (ID3V2_FRAME_STATUS_FRAME_ALTER_PRESERVE | \ + ID3V2_FRAME_STATUS_FILE_ALTER_PRESERVE | \ + ID3V2_FRAME_STATUS_READONLY | \ + ID3V2_FRAME_FORMAT_GROUPING_ID | \ + ID3V2_FRAME_FORMAT_COMPRESSION | \ + ID3V2_FRAME_FORMAT_ENCRYPTION) + +/* From id3v2frames.c */ +gboolean id3demux_id3v2_parse_frame (ID3TagsWorking *work); + +#endif diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c new file mode 100644 index 0000000000..fec70123ad --- /dev/null +++ b/gst-libs/gst/tag/id3v2frames.c @@ -0,0 +1,382 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: t; c-basic-offset: 2 -*- */ +/* Copyright 2005 Jan Schmidt + * Copyright 2002,2003 Scott Wheeler (portions from taglib) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#ifdef HAVE_ZLIB +#include +#endif + +#include "id3tags.h" + +GST_DEBUG_CATEGORY_EXTERN (id3demux_debug); +#define GST_CAT_DEFAULT (id3demux_debug) + +static gchar *parse_comment_frame (ID3TagsWorking * work); +static gchar *parse_text_identification_frame (ID3TagsWorking * work); +static gboolean id3v2_tag_to_taglist (ID3TagsWorking * work, + const gchar * tag_name, gchar * tag_str); +static void parse_split_strings (ID3TagsWorking * work, guint8 encoding, + gchar ** field1, gchar ** field2); + +#define ID3V2_ENCODING_ISO8859 0x00 +#define ID3V2_ENCODING_UTF16 0x01 +#define ID3V2_ENCODING_UTF16BE 0x02 +#define ID3V2_ENCODING_UTF8 0x03 + +extern guint read_synch_uint (guint8 * data, guint size); + +gboolean +id3demux_id3v2_parse_frame (ID3TagsWorking * work) +{ + const gchar *tag_name; + gboolean result = FALSE; + gint i; + guint8 *frame_data = work->hdr.frame_data; + guint frame_data_size = work->cur_frame_size; + gchar *tag_str = NULL; + + /* Check that the frame id is valid */ + for (i = 0; i < 5 && work->frame_id[i] != '\0'; i++) { + if (!g_ascii_isalnum (work->frame_id[i])) { + GST_DEBUG ("Encountered invalid frame_id"); + return FALSE; + } + } + + /* Can't handle encrypted frames right now */ + if (work->frame_flags & ID3V2_FRAME_FORMAT_ENCRYPTION) { + GST_WARNING ("Encrypted frames are not supported"); + return FALSE; + } + + if (work->frame_flags & ID3V2_FRAME_FORMAT_UNSYNCHRONISATION) { + GST_WARNING ("ID3v2 frame with unsupported unsynchronisation applied. " + "May fail badly"); + } + + tag_name = gst_tag_from_id3_tag (work->frame_id); + if (tag_name == NULL) + return FALSE; + + if (work->frame_flags & (ID3V2_FRAME_FORMAT_COMPRESSION | + ID3V2_FRAME_FORMAT_DATA_LENGTH_INDICATOR)) { + if (work->hdr.frame_data_size <= 4) + return FALSE; + work->parse_size = read_synch_uint (frame_data, 4); + frame_data += 4; + frame_data_size -= 4; + } else + work->parse_size = frame_data_size; + + if (work->frame_flags & ID3V2_FRAME_FORMAT_COMPRESSION) { + uLongf destSize = work->parse_size; + Bytef *dest, *src; + + work->parse_data = g_malloc (work->parse_size); + g_return_val_if_fail (work->parse_data != NULL, FALSE); + + dest = (Bytef *) work->parse_data; + src = (Bytef *) frame_data; + + if (uncompress (dest, &destSize, src, frame_data_size) != Z_OK) { + g_free (work->parse_data); + return FALSE; + } + } else { + work->parse_data = work->hdr.frame_data; + } + + if (work->frame_id[0] == 'T') { + if (strcmp (work->frame_id, "TXXX") != 0) { + /* Text identification frame */ + tag_str = parse_text_identification_frame (work); + } else { + /* Handle user text frame */ + } + } else if (!strcmp (work->frame_id, "COMM")) { + /* Comment */ + tag_str = parse_comment_frame (work); + } else if (!strcmp (work->frame_id, "APIC")) { + /* Attached picture */ + } else if (!strcmp (work->frame_id, "RVA2")) { + /* Relative volume */ + } else if (!strcmp (work->frame_id, "UFID")) { + /* Unique file identifier */ + } + + if (work->frame_flags & ID3V2_FRAME_FORMAT_COMPRESSION) { + g_free (work->parse_data); + } + + if (tag_str != NULL) { + /* g_print ("Tag %s value %s\n", tag_name, tag_str); */ + result = id3v2_tag_to_taglist (work, tag_name, tag_str); + g_free (tag_str); + } + + return result; +} + +static gchar * +parse_comment_frame (ID3TagsWorking * work) +{ + guint8 encoding; + gchar language[4]; + gchar *description = NULL; + gchar *text = NULL; + gchar *out_str = NULL; + + if (work->parse_size < 6) + return NULL; + + encoding = work->parse_data[0]; + language[0] = work->parse_data[1]; + language[1] = work->parse_data[2]; + language[2] = work->parse_data[3]; + language[3] = 0; + + parse_split_strings (work, encoding, &description, &text); + + if (text == NULL || description == NULL) { + GST_ERROR ("Failed to decode comment frame"); + goto fail; + } + + if (!g_utf8_validate (text, -1, NULL)) { + GST_ERROR ("Converted string is not valid utf-8"); + goto fail; + } else { + if (strlen (description) > 0 && g_utf8_validate (description, -1, NULL)) { + out_str = g_strdup_printf ("Description: %s\nComment: %s", + description, text); + } else { + out_str = g_strdup (text); + } + } + +fail: + g_free (description); + g_free (text); + + return out_str; +} + +static gchar * +parse_text_identification_frame (ID3TagsWorking * work) +{ + guchar encoding; + gchar *text = NULL; + + if (work->parse_size < 2) + return NULL; + + encoding = work->parse_data[0]; + + switch (encoding) { + case ID3V2_ENCODING_ISO8859: + text = g_convert ((gchar *) (work->parse_data + 1), + work->parse_size - 1, "UTF-8", "ISO-8859-1", NULL, NULL, NULL); + break; + case ID3V2_ENCODING_UTF8: + text = g_strndup ((gchar *) (work->parse_data + 1), work->parse_size - 1); + break; + case ID3V2_ENCODING_UTF16: + text = g_convert ((gchar *) (work->parse_data + 1), + work->parse_size - 1, "UTF-8", "UTF-16", NULL, NULL, NULL); + break; + case ID3V2_ENCODING_UTF16BE: + text = g_convert ((gchar *) (work->parse_data + 1), + work->parse_size - 1, "UTF-8", "UTF-16BE", NULL, NULL, NULL); + break; + } + + if (text != NULL && !g_utf8_validate (text, -1, NULL)) { + GST_ERROR ("Converted string is not valid utf-8"); + g_free (text); + text = NULL; + } + + return text; +} + +static gboolean +id3v2_tag_to_taglist (ID3TagsWorking * work, const gchar * tag_name, + gchar * tag_str) +{ + GType tag_type = gst_tag_get_type (tag_name); + GstTagList *tag_list = work->tags; + + switch (tag_type) { + case G_TYPE_UINT: + { + guint tmp; + gchar *check; + + tmp = strtoul ((char *) tag_str, &check, 10); + + if (strcmp (tag_name, GST_TAG_DATE) == 0) { + GDate *d; + + if (*check != '\0') + break; + if (tmp == 0) + break; + d = g_date_new_dmy (1, 1, tmp); + tmp = g_date_get_julian (d); + g_date_free (d); + } else if (strcmp (tag_name, GST_TAG_TRACK_NUMBER) == 0) { + if (*check == '/') { + guint total; + + check++; + total = strtoul (check, &check, 10); + if (*check != '\0') + break; + + gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND, + GST_TAG_TRACK_COUNT, total, NULL); + } + } else if (strcmp (tag_name, GST_TAG_ALBUM_VOLUME_NUMBER) == 0) { + if (*check == '/') { + guint total; + + check++; + total = strtoul (check, &check, 10); + if (*check != '\0') + break; + + gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND, + GST_TAG_ALBUM_VOLUME_COUNT, total, NULL); + } + } + + if (*check != '\0') + break; + + gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND, tag_name, tmp, NULL); + break; + } + case G_TYPE_UINT64: + { + guint64 tmp; + + g_assert (strcmp (tag_name, GST_TAG_DURATION) == 0); + tmp = strtoul ((char *) tag_str, NULL, 10); + if (tmp == 0) { + break; + } + gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND, + GST_TAG_DURATION, tmp * 1000 * 1000, NULL); + break; + } + case G_TYPE_STRING:{ + gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND, + tag_name, (const gchar *) tag_str, NULL); + break; + } + /* handles GST_TYPE_DATE and anything else */ + default:{ + GValue src = { 0, }; + GValue dest = { 0, }; + + g_value_init (&src, G_TYPE_STRING); + g_value_set_string (&src, (const gchar *) tag_str); + + g_value_init (&dest, tag_type); + if (g_value_transform (&src, &dest)) { + gst_tag_list_add_values (tag_list, GST_TAG_MERGE_APPEND, + tag_name, &dest, NULL); + } else { + GST_WARNING ("Failed to transform tag from string to type '%s'", + g_type_name (tag_type)); + } + g_value_unset (&src); + g_value_unset (&dest); + break; + } + } + + return TRUE; +} + +static void +parse_split_strings (ID3TagsWorking * work, guint8 encoding, + gchar ** field1, gchar ** field2) +{ + guint text_pos; + + *field1 = *field2 = NULL; + + switch (encoding) { + case ID3V2_ENCODING_ISO8859: + for (text_pos = 4; text_pos < work->parse_size - 1; text_pos++) { + if (work->parse_data[text_pos] == 0) { + *field1 = g_convert ((gchar *) (work->parse_data + 4), + text_pos - 4, "UTF-8", "ISO-8859-1", NULL, NULL, NULL); + *field2 = g_convert ((gchar *) (work->parse_data + text_pos + 5), + work->parse_size - text_pos - 5, + "UTF-8", "ISO-8859-1", NULL, NULL, NULL); + break; + } + } + break; + case ID3V2_ENCODING_UTF8: + *field1 = g_strndup ((gchar *) (work->parse_data + 4), + work->parse_size - 4); + text_pos = 4 + strlen (*field1) + 1; /* Offset by one more for the null */ + if (text_pos < work->parse_size) { + *field2 = g_strndup ((gchar *) (work->parse_data + text_pos), + work->parse_size - text_pos); + } + break; + case ID3V2_ENCODING_UTF16: + case ID3V2_ENCODING_UTF16BE: + { + /* Find '\0\0' terminator */ + for (text_pos = 4; text_pos < work->parse_size - 2; text_pos++) { + if (work->parse_data[text_pos] == 0 && + work->parse_data[text_pos + 1] == 0) { + /* found our delimiter */ + if (encoding == ID3V2_ENCODING_UTF16) { + *field1 = g_convert ((gchar *) (work->parse_data + 4), + text_pos - 4, "UTF-8", "UTF-16", NULL, NULL, NULL); + *field2 = g_convert ((gchar *) (work->parse_data + text_pos + 6), + work->parse_size - text_pos - 6, + "UTF-8", "UTF-16", NULL, NULL, NULL); + } else { + *field1 = g_convert ((gchar *) (work->parse_data + 4), + text_pos - 4, "UTF-8", "UTF-16BE", NULL, NULL, NULL); + *field2 = g_convert ((gchar *) (work->parse_data + text_pos + 6), + work->parse_size - text_pos - 6, + "UTF-8", "UTF-16BE", NULL, NULL, NULL); + } + break; + } + } + break; + } + } +} From a2415c8edb01291daaec36b660558bba9e0e334a Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Wed, 28 Dec 2005 18:55:32 +0000 Subject: [PATCH 070/140] tag: id3v2: If a broken tag has 0 bytes payload, at least still skip the 10 byte header Original commit message from CVS: * gst-libs/gst/tag/id3v2.c: (id3demux_read_id3v2_tag): If a broken tag has 0 bytes payload, at least still skip the 10 byte header --- gst-libs/gst/tag/id3v2.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gst-libs/gst/tag/id3v2.c b/gst-libs/gst/tag/id3v2.c index 1cc8c14ec9..e25b429163 100644 --- a/gst-libs/gst/tag/id3v2.c +++ b/gst-libs/gst/tag/id3v2.c @@ -143,9 +143,12 @@ id3demux_read_id3v2_tag (GstBuffer * buffer, guint * id3v2_size, /* Read the size from the header */ read_size = read_synch_uint (data + 6, 4); if (read_size == 0) { + /* Tag has no frames attached. Ignore it, but skip the header */ + if (id3v2_size) + *id3v2_size = ID3V2_HDR_SIZE; return ID3TAGS_BROKEN_TAG; } - read_size += 10; + read_size += ID3V2_HDR_SIZE; /* Expand the read size to include a footer if there is one */ if (flags & ID3V2_HDR_FLAG_FOOTER) { From f34424e933d0550c97a7b522aac7583d0098de8f Mon Sep 17 00:00:00 2001 From: Edward Hervey Date: Fri, 6 Jan 2006 11:46:53 +0000 Subject: [PATCH 071/140] tag: id3v2: Add gst_element_no_more_pads() for proper decodebin behaviour. Original commit message from CVS: * gst-libs/gst/tag/gstid3demux.c: (gst_id3demux_add_srcpad): Add gst_element_no_more_pads() for proper decodebin behaviour. * gst-libs/gst/tag/id3v2frames.c: (parse_comment_frame), (parse_text_identification_frame), (parse_split_strings): Failure to decode some tags is not a GST_ERROR() but a GST_WARNING() When iterating over a chunk of text, check that we haven't gone too far. --- gst-libs/gst/tag/id3v2frames.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index fec70123ad..7b3b9bcf06 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -161,12 +161,12 @@ parse_comment_frame (ID3TagsWorking * work) parse_split_strings (work, encoding, &description, &text); if (text == NULL || description == NULL) { - GST_ERROR ("Failed to decode comment frame"); + GST_WARNING ("Failed to decode comment frame"); goto fail; } if (!g_utf8_validate (text, -1, NULL)) { - GST_ERROR ("Converted string is not valid utf-8"); + GST_WARNING ("Converted string is not valid utf-8"); goto fail; } else { if (strlen (description) > 0 && g_utf8_validate (description, -1, NULL)) { @@ -214,7 +214,7 @@ parse_text_identification_frame (ID3TagsWorking * work) } if (text != NULL && !g_utf8_validate (text, -1, NULL)) { - GST_ERROR ("Converted string is not valid utf-8"); + GST_WARNING ("Converted string is not valid utf-8"); g_free (text); text = NULL; } @@ -332,7 +332,7 @@ parse_split_strings (ID3TagsWorking * work, guint8 encoding, switch (encoding) { case ID3V2_ENCODING_ISO8859: - for (text_pos = 4; text_pos < work->parse_size - 1; text_pos++) { + for (text_pos = 4; text_pos < work->parse_size - 5; text_pos++) { if (work->parse_data[text_pos] == 0) { *field1 = g_convert ((gchar *) (work->parse_data + 4), text_pos - 4, "UTF-8", "ISO-8859-1", NULL, NULL, NULL); @@ -356,7 +356,7 @@ parse_split_strings (ID3TagsWorking * work, guint8 encoding, case ID3V2_ENCODING_UTF16BE: { /* Find '\0\0' terminator */ - for (text_pos = 4; text_pos < work->parse_size - 2; text_pos++) { + for (text_pos = 4; text_pos < work->parse_size - 6; text_pos++) { if (work->parse_data[text_pos] == 0 && work->parse_data[text_pos + 1] == 0) { /* found our delimiter */ From a9c6822e3fc55021eeea642cf98a36cd4cfe838e Mon Sep 17 00:00:00 2001 From: Sergey Scobich Date: Sun, 15 Jan 2006 20:21:48 +0000 Subject: [PATCH 072/140] tag: id3v2: Fix compilation of id3demux when zlib is not present. Original commit message from CVS: * gst-libs/gst/tag/id3v2frames.c: (id3demux_id3v2_parse_frame): Fix compilation of id3demux when zlib is not present. (Fixes #326602; patch by: Sergey Scobich) --- gst-libs/gst/tag/id3v2frames.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index 7b3b9bcf06..ed4c10421f 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -92,6 +92,7 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) work->parse_size = frame_data_size; if (work->frame_flags & ID3V2_FRAME_FORMAT_COMPRESSION) { +#ifdef HAVE_ZLIB uLongf destSize = work->parse_size; Bytef *dest, *src; @@ -105,6 +106,11 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) g_free (work->parse_data); return FALSE; } +#else + GST_WARNING ("Compressed ID3v2 tag frame could not be decompressed" + " because gstid3demux was compiled without zlib support"); + return FALSE; +#endif } else { work->parse_data = work->hdr.frame_data; } From a6f7ebffa2f6160780989615fbb114db17dfc290 Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Mon, 23 Jan 2006 09:22:17 +0000 Subject: [PATCH 073/140] tag: id3v2: Rewrite parsing of text tags to handle multiple NULL terminated strings. Parse numeric genre strings a... Original commit message from CVS: * gst-libs/gst/tag/id3v2.c: (id3demux_read_id3v2_tag): * gst-libs/gst/tag/id3v2.h: * gst-libs/gst/tag/id3v2frames.c: (id3demux_id3v2_parse_frame), (parse_comment_frame), (parse_text_identification_frame), (id3v2_tag_to_taglist), (id3v2_are_digits), (id3v2_genre_string_to_taglist), (id3v2_genre_fields_to_taglist), (parse_split_strings), (free_tag_strings): Rewrite parsing of text tags to handle multiple NULL terminated strings. Parse numeric genre strings and ID3v2 type "(3)(6)Alternative" style genre strings. Parse dates that are only YYYY or YYYY-mm format. --- gst-libs/gst/tag/id3v2.c | 3 + gst-libs/gst/tag/id3v2.h | 3 + gst-libs/gst/tag/id3v2frames.c | 314 ++++++++++++++++++++++++--------- 3 files changed, 236 insertions(+), 84 deletions(-) diff --git a/gst-libs/gst/tag/id3v2.c b/gst-libs/gst/tag/id3v2.c index e25b429163..12765e61fe 100644 --- a/gst-libs/gst/tag/id3v2.c +++ b/gst-libs/gst/tag/id3v2.c @@ -198,6 +198,9 @@ id3demux_read_id3v2_tag (GstBuffer * buffer, guint * id3v2_size, *tags = work.tags; } + if (work.prev_genre) + g_free (work.prev_genre); + return result; } diff --git a/gst-libs/gst/tag/id3v2.h b/gst-libs/gst/tag/id3v2.h index c81cd55066..cdf165d8f1 100644 --- a/gst-libs/gst/tag/id3v2.h +++ b/gst-libs/gst/tag/id3v2.h @@ -72,6 +72,9 @@ typedef struct { guint8 *parse_data; guint parse_size; + + /* Previous genre string, for simple duplicate removal */ + gchar *prev_genre; } ID3TagsWorking; enum { diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index ed4c10421f..fb99f10d0e 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -23,6 +23,7 @@ #endif #include +#include #include #ifdef HAVE_ZLIB @@ -35,11 +36,16 @@ GST_DEBUG_CATEGORY_EXTERN (id3demux_debug); #define GST_CAT_DEFAULT (id3demux_debug) static gchar *parse_comment_frame (ID3TagsWorking * work); -static gchar *parse_text_identification_frame (ID3TagsWorking * work); +static GArray *parse_text_identification_frame (ID3TagsWorking * work); static gboolean id3v2_tag_to_taglist (ID3TagsWorking * work, - const gchar * tag_name, gchar * tag_str); -static void parse_split_strings (ID3TagsWorking * work, guint8 encoding, - gchar ** field1, gchar ** field2); + const gchar * tag_name, const gchar * tag_str); +/* Parse a single string into an array of gchar* */ +static void parse_split_strings (guint8 encoding, gchar * data, gint data_size, + GArray ** out_fields); +static void free_tag_strings (GArray * fields); +static gboolean +id3v2_genre_fields_to_taglist (ID3TagsWorking * work, const gchar * tag_name, + GArray * tag_fields); #define ID3V2_ENCODING_ISO8859 0x00 #define ID3V2_ENCODING_UTF16 0x01 @@ -57,6 +63,7 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) guint8 *frame_data = work->hdr.frame_data; guint frame_data_size = work->cur_frame_size; gchar *tag_str = NULL; + GArray *tag_fields = NULL; /* Check that the frame id is valid */ for (i = 0; i < 5 && work->frame_id[i] != '\0'; i++) { @@ -118,7 +125,7 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) if (work->frame_id[0] == 'T') { if (strcmp (work->frame_id, "TXXX") != 0) { /* Text identification frame */ - tag_str = parse_text_identification_frame (work); + tag_fields = parse_text_identification_frame (work); } else { /* Handle user text frame */ } @@ -142,6 +149,16 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) result = id3v2_tag_to_taglist (work, tag_name, tag_str); g_free (tag_str); } + if (tag_fields != NULL) { + if (strcmp (work->frame_id, "TCON") == 0) { + /* Genre strings need special treatment */ + result |= id3v2_genre_fields_to_taglist (work, tag_name, tag_fields); + } else { + tag_str = g_array_index (tag_fields, gchar *, 0); + result |= id3v2_tag_to_taglist (work, tag_name, tag_str); + } + free_tag_strings (tag_fields); + } return result; } @@ -151,9 +168,9 @@ parse_comment_frame (ID3TagsWorking * work) { guint8 encoding; gchar language[4]; - gchar *description = NULL; - gchar *text = NULL; + GArray *fields = NULL; gchar *out_str = NULL; + gchar *description, *text; if (work->parse_size < 6) return NULL; @@ -164,12 +181,15 @@ parse_comment_frame (ID3TagsWorking * work) language[2] = work->parse_data[3]; language[3] = 0; - parse_split_strings (work, encoding, &description, &text); + parse_split_strings (encoding, (gchar *) work->parse_data + 4, + work->parse_size - 4, &fields); - if (text == NULL || description == NULL) { + if (fields == NULL || fields->len < 2) { GST_WARNING ("Failed to decode comment frame"); goto fail; } + description = g_array_index (fields, gchar *, 0); + text = g_array_index (fields, gchar *, 1); if (!g_utf8_validate (text, -1, NULL)) { GST_WARNING ("Converted string is not valid utf-8"); @@ -184,53 +204,30 @@ parse_comment_frame (ID3TagsWorking * work) } fail: - g_free (description); - g_free (text); + free_tag_strings (fields); return out_str; } -static gchar * +static GArray * parse_text_identification_frame (ID3TagsWorking * work) { guchar encoding; - gchar *text = NULL; + GArray *fields = NULL; if (work->parse_size < 2) return NULL; encoding = work->parse_data[0]; + parse_split_strings (encoding, (gchar *) work->parse_data + 1, + work->parse_size - 1, &fields); - switch (encoding) { - case ID3V2_ENCODING_ISO8859: - text = g_convert ((gchar *) (work->parse_data + 1), - work->parse_size - 1, "UTF-8", "ISO-8859-1", NULL, NULL, NULL); - break; - case ID3V2_ENCODING_UTF8: - text = g_strndup ((gchar *) (work->parse_data + 1), work->parse_size - 1); - break; - case ID3V2_ENCODING_UTF16: - text = g_convert ((gchar *) (work->parse_data + 1), - work->parse_size - 1, "UTF-8", "UTF-16", NULL, NULL, NULL); - break; - case ID3V2_ENCODING_UTF16BE: - text = g_convert ((gchar *) (work->parse_data + 1), - work->parse_size - 1, "UTF-8", "UTF-16BE", NULL, NULL, NULL); - break; - } - - if (text != NULL && !g_utf8_validate (text, -1, NULL)) { - GST_WARNING ("Converted string is not valid utf-8"); - g_free (text); - text = NULL; - } - - return text; + return fields; } static gboolean id3v2_tag_to_taglist (ID3TagsWorking * work, const gchar * tag_name, - gchar * tag_str) + const gchar * tag_str) { GType tag_type = gst_tag_get_type (tag_name); GstTagList *tag_list = work->tags; @@ -243,17 +240,7 @@ id3v2_tag_to_taglist (ID3TagsWorking * work, const gchar * tag_name, tmp = strtoul ((char *) tag_str, &check, 10); - if (strcmp (tag_name, GST_TAG_DATE) == 0) { - GDate *d; - - if (*check != '\0') - break; - if (tmp == 0) - break; - d = g_date_new_dmy (1, 1, tmp); - tmp = g_date_get_julian (d); - g_date_free (d); - } else if (strcmp (tag_name, GST_TAG_TRACK_NUMBER) == 0) { + if (strcmp (tag_name, GST_TAG_TRACK_NUMBER) == 0) { if (*check == '/') { guint total; @@ -290,7 +277,7 @@ id3v2_tag_to_taglist (ID3TagsWorking * work, const gchar * tag_name, guint64 tmp; g_assert (strcmp (tag_name, GST_TAG_DURATION) == 0); - tmp = strtoul ((char *) tag_str, NULL, 10); + tmp = strtoul (tag_str, NULL, 10); if (tmp == 0) { break; } @@ -299,19 +286,41 @@ id3v2_tag_to_taglist (ID3TagsWorking * work, const gchar * tag_name, break; } case G_TYPE_STRING:{ + if (!strcmp (tag_name, GST_TAG_GENRE)) { + if (work->prev_genre && !strcmp (tag_str, work->prev_genre)) + break; /* Same as the last genre */ + g_free (work->prev_genre); + work->prev_genre = g_strdup (tag_str); + } gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND, - tag_name, (const gchar *) tag_str, NULL); + tag_name, tag_str, NULL); break; } - /* handles GST_TYPE_DATE and anything else */ + default:{ + gchar *tmp = NULL; + + if (tag_type == GST_TYPE_DATE) { + guint year = 1901, month = 1, day = 1; + + /* Dates can be yyyy-MM-dd, yyyy-MM or yyyy, but we need + * the first type */ + if (sscanf (tag_str, "%04u-%02u-%02u", &year, &month, &day) == 0) + break; + + tmp = g_strdup_printf ("%04u-%02u-%02u", year, month, day); + tag_str = tmp; + break; + } + + /* handles anything else */ GValue src = { 0, }; GValue dest = { 0, }; g_value_init (&src, G_TYPE_STRING); g_value_set_string (&src, (const gchar *) tag_str); - g_value_init (&dest, tag_type); + if (g_value_transform (&src, &dest)) { gst_tag_list_add_values (tag_list, GST_TAG_MERGE_APPEND, tag_name, &dest, NULL); @@ -319,8 +328,10 @@ id3v2_tag_to_taglist (ID3TagsWorking * work, const gchar * tag_name, GST_WARNING ("Failed to transform tag from string to type '%s'", g_type_name (tag_type)); } + g_value_unset (&src); g_value_unset (&dest); + g_free (tmp); break; } } @@ -328,61 +339,196 @@ id3v2_tag_to_taglist (ID3TagsWorking * work, const gchar * tag_name, return TRUE; } -static void -parse_split_strings (ID3TagsWorking * work, guint8 encoding, - gchar ** field1, gchar ** field2) +/* Check that an array of characters contains only digits */ +static gboolean +id3v2_are_digits (const gchar * chars, gint size) { - guint text_pos; + gint i; - *field1 = *field2 = NULL; + for (i = 0; i < size; i++) { + if (!g_ascii_isdigit (chars[i])) + return FALSE; + } + return TRUE; +} + +static gboolean +id3v2_genre_string_to_taglist (ID3TagsWorking * work, const gchar * tag_name, + const gchar * tag_str, gint len) +{ + g_return_val_if_fail (tag_str != NULL, FALSE); + + /* If it's a number, it might be a defined genre */ + if (id3v2_are_digits (tag_str, len)) { + tag_str = gst_tag_id3_genre_get (strtol (tag_str, NULL, 10)); + if (tag_str != NULL) + return id3v2_tag_to_taglist (work, tag_name, tag_str); + } + /* Otherwise it might be "RX" or "CR" */ + if (len == 2) { + if (g_ascii_strncasecmp ("rx", tag_str, len) == 0) + return id3v2_tag_to_taglist (work, tag_name, "Remix"); + + if (g_ascii_strncasecmp ("cr", tag_str, len) == 0) + return id3v2_tag_to_taglist (work, tag_name, "Cover"); + } + + /* Otherwise it's a string */ + return id3v2_tag_to_taglist (work, tag_name, tag_str); +} + +static gboolean +id3v2_genre_fields_to_taglist (ID3TagsWorking * work, const gchar * tag_name, + GArray * tag_fields) +{ + gchar *tag_str = NULL; + gboolean result = FALSE; + gint i; + + for (i = 0; i < tag_fields->len; i++) { + gint len; + + tag_str = g_array_index (tag_fields, gchar *, 0); + if (tag_str == NULL) + continue; + + len = strlen (tag_str); + if (work->hdr.version <= 0x300) { /* <= 2.3.0 */ + /* Check for genre numbers wrapped in parentheses, possibly + * followed by a string */ + while (len >= 2) { + gint pos; + gboolean found = FALSE; + + /* Double parenthesis ends the numeric genres */ + if (tag_str[0] == '(' && tag_str[1] == '(') + break; + + for (pos = 1; pos < len; pos++) { + if (tag_str[pos] == ')') { + gchar *tmp_str; + + tmp_str = g_strndup (tag_str + 1, pos - 1); + result |= + id3v2_genre_string_to_taglist (work, tag_name, tmp_str, + pos - 1); + g_free (tmp_str); + tag_str += pos + 1; + len -= pos + 1; + found = TRUE; + break; + } + } + if (!found) + break; /* There was no closing parenthesis */ + } + } + + if (len > 0) + result |= id3v2_genre_string_to_taglist (work, tag_name, tag_str, len); + } + return result; +} + +static void +parse_split_strings (guint8 encoding, gchar * data, gint data_size, + GArray ** out_fields) +{ + GArray *fields = g_array_new (FALSE, TRUE, sizeof (gchar *)); + gchar *field; + gint text_pos; + gint prev = 0; + + g_return_if_fail (out_fields != NULL); switch (encoding) { case ID3V2_ENCODING_ISO8859: - for (text_pos = 4; text_pos < work->parse_size - 5; text_pos++) { - if (work->parse_data[text_pos] == 0) { - *field1 = g_convert ((gchar *) (work->parse_data + 4), - text_pos - 4, "UTF-8", "ISO-8859-1", NULL, NULL, NULL); - *field2 = g_convert ((gchar *) (work->parse_data + text_pos + 5), - work->parse_size - text_pos - 5, + for (text_pos = 0; text_pos < data_size; text_pos++) { + if (data[text_pos] == 0) { + field = g_convert (data + prev, text_pos - prev + 1, "UTF-8", "ISO-8859-1", NULL, NULL, NULL); - break; + if (field) + g_array_append_val (fields, field); + prev = text_pos + 1; } } + if (data_size - prev > 0 && data[prev] != 0x00) { + field = g_convert (data + prev, data_size - prev, + "UTF-8", "ISO-8859-1", NULL, NULL, NULL); + if (field) + g_array_append_val (fields, field); + } + break; case ID3V2_ENCODING_UTF8: - *field1 = g_strndup ((gchar *) (work->parse_data + 4), - work->parse_size - 4); - text_pos = 4 + strlen (*field1) + 1; /* Offset by one more for the null */ - if (text_pos < work->parse_size) { - *field2 = g_strndup ((gchar *) (work->parse_data + text_pos), - work->parse_size - text_pos); + for (prev = 0, text_pos = 0; text_pos < data_size; text_pos++) { + if (data[text_pos]) { + field = g_strndup (data + prev, text_pos - prev + 1); + if (field) + g_array_append_val (fields, field); + prev = text_pos + 1; + } + } + if (data_size - prev > 0 && data[prev] != 0x00) { + field = g_strndup (data + prev, data_size - prev); + if (field) + g_array_append_val (fields, field); } break; case ID3V2_ENCODING_UTF16: case ID3V2_ENCODING_UTF16BE: { /* Find '\0\0' terminator */ - for (text_pos = 4; text_pos < work->parse_size - 6; text_pos++) { - if (work->parse_data[text_pos] == 0 && - work->parse_data[text_pos + 1] == 0) { - /* found our delimiter */ + for (text_pos = 0; text_pos < data_size - 1; text_pos += 2) { + if (data[text_pos] == 0 && data[text_pos + 1] == 0) { + /* found a delimiter */ if (encoding == ID3V2_ENCODING_UTF16) { - *field1 = g_convert ((gchar *) (work->parse_data + 4), - text_pos - 4, "UTF-8", "UTF-16", NULL, NULL, NULL); - *field2 = g_convert ((gchar *) (work->parse_data + text_pos + 6), - work->parse_size - text_pos - 6, + field = g_convert (data + prev, text_pos - prev + 2, "UTF-8", "UTF-16", NULL, NULL, NULL); } else { - *field1 = g_convert ((gchar *) (work->parse_data + 4), - text_pos - 4, "UTF-8", "UTF-16BE", NULL, NULL, NULL); - *field2 = g_convert ((gchar *) (work->parse_data + text_pos + 6), - work->parse_size - text_pos - 6, + field = g_convert (data + prev, text_pos - prev + 2, "UTF-8", "UTF-16BE", NULL, NULL, NULL); } + if (field) + g_array_append_val (fields, field); + text_pos++; /* Advance to the 2nd NULL terminator */ + prev = text_pos + 1; break; } } + if (data_size - prev > 1 && + (data[prev] != 0x00 || data[prev + 1] != 0x00)) { + /* There were 2 or more non-null chars left, convert those too */ + if (encoding == ID3V2_ENCODING_UTF16) { + field = g_convert (data + prev, data_size - prev, + "UTF-8", "UTF-16", NULL, NULL, NULL); + } else { + field = g_convert (data + prev, data_size - prev, + "UTF-8", "UTF-16BE", NULL, NULL, NULL); + } + if (field) + g_array_append_val (fields, field); + } break; } } + if (fields->len > 0) + *out_fields = fields; + else + g_array_free (fields, TRUE); +} + +static void +free_tag_strings (GArray * fields) +{ + if (fields) { + gint i; + gchar *c; + + for (i = 0; i < fields->len; i++) { + c = g_array_index (fields, gchar *, i); + g_free (c); + } + g_array_free (fields, TRUE); + } } From 3a91d17b76cae0738fa6e014d6e43aa8123b3fb9 Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Mon, 23 Jan 2006 14:32:47 +0000 Subject: [PATCH 074/140] tag: id3v2: Remove errant break statement, and fix compilation with older GCC. Original commit message from CVS: * gst-libs/gst/tag/id3v2frames.c: (id3v2_tag_to_taglist): Remove errant break statement, and fix compilation with older GCC. --- gst-libs/gst/tag/id3v2frames.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index fb99f10d0e..0bc48e1db5 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -299,7 +299,10 @@ id3v2_tag_to_taglist (ID3TagsWorking * work, const gchar * tag_name, default:{ gchar *tmp = NULL; + GValue src = { 0, }; + GValue dest = { 0, }; + /* Ensure that any date string is complete */ if (tag_type == GST_TYPE_DATE) { guint year = 1901, month = 1, day = 1; @@ -310,13 +313,9 @@ id3v2_tag_to_taglist (ID3TagsWorking * work, const gchar * tag_name, tmp = g_strdup_printf ("%04u-%02u-%02u", year, month, day); tag_str = tmp; - break; } /* handles anything else */ - GValue src = { 0, }; - GValue dest = { 0, }; - g_value_init (&src, G_TYPE_STRING); g_value_set_string (&src, (const gchar *) tag_str); g_value_init (&dest, tag_type); From fbd1cbe006644a05c8f4ad0170e880b4651505b7 Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Wed, 25 Jan 2006 18:23:05 +0000 Subject: [PATCH 075/140] tag: id3v2: Never trust ANY information encoded in a media file, especially when it's giving you size... Original commit message from CVS: * gst-libs/gst/tag/id3v2frames.c: (id3demux_id3v2_parse_frame): Never trust ANY information encoded in a media file, especially when it's giving you sizes. (Fixes #328452) --- gst-libs/gst/tag/id3v2frames.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index 0bc48e1db5..c4c41ff00d 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -95,6 +95,11 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) work->parse_size = read_synch_uint (frame_data, 4); frame_data += 4; frame_data_size -= 4; + if (work->parse_size < frame_data_size) { + GST_WARNING ("ID3v2 frame %s has invalid size %d.", tag_name, + frame_data_size); + return FALSE; + } } else work->parse_size = frame_data_size; @@ -113,6 +118,12 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) g_free (work->parse_data); return FALSE; } + if (destSize != work->parse_size) { + GST_WARNING + ("Decompressing ID3v2 frame %s did not produce expected size %d bytes (got %d)", + tag_name, work->parse_data, destSize); + return FALSE; + } #else GST_WARNING ("Compressed ID3v2 tag frame could not be decompressed" " because gstid3demux was compiled without zlib support"); From 78aa922244c2008982b9da4f17817395b042d911 Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Mon, 30 Jan 2006 23:13:05 +0000 Subject: [PATCH 076/140] tag: id3v2: Someone should kick my butt. Remove ID3v1 tags from the end of the file. Original commit message from CVS: * gst-libs/gst/tag/gstid3demux.c: (gst_id3demux_chain), (gst_id3demux_read_id3v1), (gst_id3demux_sink_activate), (gst_id3demux_send_tag_event): * gst-libs/gst/tag/id3v2.c: (id3demux_read_id3v1_tag): Someone should kick my butt. Remove ID3v1 tags from the end of the file. Improve error messages. Send the TAG message as soon as we complete typefinding, instead of waiting until we send the first buffer. Downstream tag event is still sent before the first buffer. --- gst-libs/gst/tag/id3v2.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gst-libs/gst/tag/id3v2.c b/gst-libs/gst/tag/id3v2.c index 12765e61fe..a14eccef9c 100644 --- a/gst-libs/gst/tag/id3v2.c +++ b/gst-libs/gst/tag/id3v2.c @@ -101,6 +101,8 @@ id3demux_read_id3v1_tag (GstBuffer * buffer, guint * id3v1_size, } else *tags = new_tags; + if (id3v1_size) + *id3v1_size = ID3V1_TAG_SIZE; return ID3TAGS_READ_TAG; } From 207f8e9f232e368be1a650d6070bdf1fc90c074f Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Fri, 3 Feb 2006 13:06:24 +0000 Subject: [PATCH 077/140] tag: id3v2: Never output a tag with a null contents string. Original commit message from CVS: * gst-libs/gst/tag/id3v2frames.c: (parse_text_identification_frame), (id3v2_tag_to_taglist), (id3v2_genre_string_to_taglist), (id3v2_genre_fields_to_taglist): Never output a tag with a null contents string. --- gst-libs/gst/tag/id3v2frames.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index c4c41ff00d..3ebfc07d71 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -232,6 +232,16 @@ parse_text_identification_frame (ID3TagsWorking * work) encoding = work->parse_data[0]; parse_split_strings (encoding, (gchar *) work->parse_data + 1, work->parse_size - 1, &fields); + if (fields) { + if (fields->len > 0) { + GST_LOG ("Read %d fields from Text ID frame of size %d. First is '%s'", + fields->len, work->parse_size - 1, + g_array_index (fields, gchar *, 0)); + } else { + GST_LOG ("Read %d fields from Text ID frame of size %d", fields->len, + work->parse_size - 1); + } + } return fields; } @@ -243,6 +253,9 @@ id3v2_tag_to_taglist (ID3TagsWorking * work, const gchar * tag_name, GType tag_type = gst_tag_get_type (tag_name); GstTagList *tag_list = work->tags; + if (tag_str == NULL) + return FALSE; + switch (tag_type) { case G_TYPE_UINT: { @@ -371,8 +384,7 @@ id3v2_genre_string_to_taglist (ID3TagsWorking * work, const gchar * tag_name, /* If it's a number, it might be a defined genre */ if (id3v2_are_digits (tag_str, len)) { tag_str = gst_tag_id3_genre_get (strtol (tag_str, NULL, 10)); - if (tag_str != NULL) - return id3v2_tag_to_taglist (work, tag_name, tag_str); + return id3v2_tag_to_taglist (work, tag_name, tag_str); } /* Otherwise it might be "RX" or "CR" */ if (len == 2) { @@ -434,7 +446,7 @@ id3v2_genre_fields_to_taglist (ID3TagsWorking * work, const gchar * tag_name, } } - if (len > 0) + if (len > 0 && tag_str != NULL) result |= id3v2_genre_string_to_taglist (work, tag_name, tag_str, len); } return result; From 05a5fd1e514fd22e6718c452b1e9684e1274e10a Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Sat, 4 Feb 2006 13:30:12 +0000 Subject: [PATCH 078/140] tag: id3v2: Adjust for data length indicators when parsing (Fixes #329810) Original commit message from CVS: * gst-libs/gst/tag/id3v2frames.c: (id3demux_id3v2_parse_frame), (parse_split_strings): Adjust for data length indicators when parsing (Fixes #329810) Fix stupid bug parsing UTF-8 tag text. Output tag strings with multiple fields as multiple tags, so the app gets all the data. --- gst-libs/gst/tag/id3v2frames.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index 3ebfc07d71..48fe9f1c66 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -100,8 +100,9 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) frame_data_size); return FALSE; } - } else - work->parse_size = frame_data_size; + } + + work->parse_size = frame_data_size; if (work->frame_flags & ID3V2_FRAME_FORMAT_COMPRESSION) { #ifdef HAVE_ZLIB @@ -130,7 +131,7 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) return FALSE; #endif } else { - work->parse_data = work->hdr.frame_data; + work->parse_data = frame_data; } if (work->frame_id[0] == 'T') { @@ -151,9 +152,8 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) /* Unique file identifier */ } - if (work->frame_flags & ID3V2_FRAME_FORMAT_COMPRESSION) { + if (work->frame_flags & ID3V2_FRAME_FORMAT_COMPRESSION) g_free (work->parse_data); - } if (tag_str != NULL) { /* g_print ("Tag %s value %s\n", tag_name, tag_str); */ @@ -165,8 +165,13 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) /* Genre strings need special treatment */ result |= id3v2_genre_fields_to_taglist (work, tag_name, tag_fields); } else { - tag_str = g_array_index (tag_fields, gchar *, 0); - result |= id3v2_tag_to_taglist (work, tag_name, tag_str); + gint t; + + for (t = 0; t < tag_fields->len; t++) { + tag_str = g_array_index (tag_fields, gchar *, t); + if (tag_str != NULL && tag_str[0] != '\0') + result |= id3v2_tag_to_taglist (work, tag_name, tag_str); + } } free_tag_strings (tag_fields); } @@ -484,7 +489,7 @@ parse_split_strings (guint8 encoding, gchar * data, gint data_size, break; case ID3V2_ENCODING_UTF8: for (prev = 0, text_pos = 0; text_pos < data_size; text_pos++) { - if (data[text_pos]) { + if (data[text_pos] == '\0') { field = g_strndup (data + prev, text_pos - prev + 1); if (field) g_array_append_val (fields, field); @@ -502,7 +507,7 @@ parse_split_strings (guint8 encoding, gchar * data, gint data_size, { /* Find '\0\0' terminator */ for (text_pos = 0; text_pos < data_size - 1; text_pos += 2) { - if (data[text_pos] == 0 && data[text_pos + 1] == 0) { + if (data[text_pos] == '\0' && data[text_pos + 1] == '\0') { /* found a delimiter */ if (encoding == ID3V2_ENCODING_UTF16) { field = g_convert (data + prev, text_pos - prev + 2, From dc5506d3729be5b3ca64d796c15e642ebaa3c1c1 Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Mon, 13 Feb 2006 12:00:51 +0000 Subject: [PATCH 079/140] tag: id3v2: Add more validation to ensure that a char encoding conversion produced a valid UTF-8 string. Original commit message from CVS: * gst-libs/gst/tag/id3v2frames.c: (parse_insert_string_field), (parse_split_strings): Add more validation to ensure that a char encoding conversion produced a valid UTF-8 string. --- gst-libs/gst/tag/id3v2frames.c | 68 +++++++++++++++++----------------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index 48fe9f1c66..cf356433c8 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -457,12 +457,27 @@ id3v2_genre_fields_to_taglist (ID3TagsWorking * work, const gchar * tag_name, return result; } +static void +parse_insert_string_field (const gchar * encoding, gchar * data, gint data_size, + GArray * fields) +{ + gchar *field; + + field = g_convert (data, data_size, "UTF-8", encoding, NULL, NULL, NULL); + if (field && !g_utf8_validate (field, -1, NULL)) { + GST_DEBUG ("%s was bad UTF-8. Ignoring", field); + g_free (field); + field = NULL; + } + if (field) + g_array_append_val (fields, field); +} + static void parse_split_strings (guint8 encoding, gchar * data, gint data_size, GArray ** out_fields) { GArray *fields = g_array_new (FALSE, TRUE, sizeof (gchar *)); - gchar *field; gint text_pos; gint prev = 0; @@ -472,52 +487,46 @@ parse_split_strings (guint8 encoding, gchar * data, gint data_size, case ID3V2_ENCODING_ISO8859: for (text_pos = 0; text_pos < data_size; text_pos++) { if (data[text_pos] == 0) { - field = g_convert (data + prev, text_pos - prev + 1, - "UTF-8", "ISO-8859-1", NULL, NULL, NULL); - if (field) - g_array_append_val (fields, field); + parse_insert_string_field ("ISO-8859-1", data + prev, + text_pos - prev + 1, fields); prev = text_pos + 1; } } if (data_size - prev > 0 && data[prev] != 0x00) { - field = g_convert (data + prev, data_size - prev, - "UTF-8", "ISO-8859-1", NULL, NULL, NULL); - if (field) - g_array_append_val (fields, field); + parse_insert_string_field ("ISO-8859-1", data + prev, + data_size - prev, fields); } break; case ID3V2_ENCODING_UTF8: for (prev = 0, text_pos = 0; text_pos < data_size; text_pos++) { if (data[text_pos] == '\0') { - field = g_strndup (data + prev, text_pos - prev + 1); - if (field) - g_array_append_val (fields, field); + parse_insert_string_field ("UTF-8", data + prev, + text_pos - prev + 1, fields); prev = text_pos + 1; } } if (data_size - prev > 0 && data[prev] != 0x00) { - field = g_strndup (data + prev, data_size - prev); - if (field) - g_array_append_val (fields, field); + parse_insert_string_field ("UTF-8", data + prev, + data_size - prev, fields); } break; case ID3V2_ENCODING_UTF16: case ID3V2_ENCODING_UTF16BE: { + const gchar *in_encode; + + if (encoding == ID3V2_ENCODING_UTF16) + in_encode = "UTF-16"; + else + in_encode = "UTF-16BE"; + /* Find '\0\0' terminator */ for (text_pos = 0; text_pos < data_size - 1; text_pos += 2) { if (data[text_pos] == '\0' && data[text_pos + 1] == '\0') { /* found a delimiter */ - if (encoding == ID3V2_ENCODING_UTF16) { - field = g_convert (data + prev, text_pos - prev + 2, - "UTF-8", "UTF-16", NULL, NULL, NULL); - } else { - field = g_convert (data + prev, text_pos - prev + 2, - "UTF-8", "UTF-16BE", NULL, NULL, NULL); - } - if (field) - g_array_append_val (fields, field); + parse_insert_string_field (in_encode, data + prev, + text_pos - prev + 2, fields); text_pos++; /* Advance to the 2nd NULL terminator */ prev = text_pos + 1; break; @@ -526,15 +535,8 @@ parse_split_strings (guint8 encoding, gchar * data, gint data_size, if (data_size - prev > 1 && (data[prev] != 0x00 || data[prev + 1] != 0x00)) { /* There were 2 or more non-null chars left, convert those too */ - if (encoding == ID3V2_ENCODING_UTF16) { - field = g_convert (data + prev, data_size - prev, - "UTF-8", "UTF-16", NULL, NULL, NULL); - } else { - field = g_convert (data + prev, data_size - prev, - "UTF-8", "UTF-16BE", NULL, NULL, NULL); - } - if (field) - g_array_append_val (fields, field); + parse_insert_string_field (in_encode, data + prev, + data_size - prev, fields); } break; } From e4af0fe41fbc196653af56bf2f54fea84f641d30 Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Thu, 16 Feb 2006 10:58:18 +0000 Subject: [PATCH 080/140] tag: id3v2: 3 2.3.0 used synch-safe integers for the tag size, but not for the frame size. (Fixes #331368) Original commit message from CVS: * gst-libs/gst/tag/id3v2.c: (id3demux_id3v2_frames_to_tag_list): ID3 2.3.0 used synch-safe integers for the tag size, but not for the frame size. (Fixes #331368) --- gst-libs/gst/tag/id3v2.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/gst-libs/gst/tag/id3v2.c b/gst-libs/gst/tag/id3v2.c index a14eccef9c..2ce23e0db8 100644 --- a/gst-libs/gst/tag/id3v2.c +++ b/gst-libs/gst/tag/id3v2.c @@ -362,6 +362,7 @@ id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size) gchar frame_id[5] = ""; guint16 frame_flags = 0x0; gboolean obsolete_id = FALSE; + gboolean read_synch_size = TRUE; /* Read the header */ switch (ID3V2_VER_MAJOR (work->hdr.version)) { @@ -375,10 +376,13 @@ id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size) frame_id[4] = 0; obsolete_id = convert_fid_to_v240 (frame_id); - frame_size = read_synch_uint (work->hdr.frame_data + 3, 3); + /* 3 byte non-synchsafe size */ + frame_size = work->hdr.frame_data[3] << 16 | + work->hdr.frame_data[4] << 8 | work->hdr.frame_data[5]; frame_flags = 0; break; case 3: + read_synch_size = FALSE; /* 2.3 frame size is not synch-safe */ case 4: default: frame_id[0] = work->hdr.frame_data[0]; @@ -386,7 +390,11 @@ id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size) frame_id[2] = work->hdr.frame_data[2]; frame_id[3] = work->hdr.frame_data[3]; frame_id[4] = 0; - frame_size = read_synch_uint (work->hdr.frame_data + 4, 4); + if (read_synch_size) + frame_size = read_synch_uint (work->hdr.frame_data + 4, 4); + else + frame_size = GST_READ_UINT32_BE (work->hdr.frame_data + 4); + frame_flags = GST_READ_UINT16_BE (work->hdr.frame_data + 8); if (ID3V2_VER_MAJOR (work->hdr.version) == 3) { @@ -403,8 +411,8 @@ id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size) frame_size == 0 || strcmp (frame_id, "") == 0) break; /* No more frames to read */ -#if 0 - g_print +#if 1 + GST_LOG ("Frame @ %d (0x%02x) id %s size %d, next=%d (0x%02x) obsolete=%d\n", work->hdr.frame_data - start, work->hdr.frame_data - start, frame_id, frame_size, work->hdr.frame_data + frame_hdr_size + frame_size - start, From dbcf8fc320094caadc8f84d0e91af8380885d5dc Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Sat, 18 Feb 2006 20:48:09 +0000 Subject: [PATCH 081/140] tag: id3v2: Handle 0 data size in otherwise valid frames. Original commit message from CVS: * gst-libs/gst/tag/id3v2.c: (id3demux_id3v2_frames_to_tag_list): * gst-libs/gst/tag/id3v2frames.c: (id3v2_genre_fields_to_taglist): Handle 0 data size in otherwise valid frames. Handle numeric strings in 2.4.0 even when not in parentheses --- gst-libs/gst/tag/id3v2.c | 6 ++++-- gst-libs/gst/tag/id3v2frames.c | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/gst-libs/gst/tag/id3v2.c b/gst-libs/gst/tag/id3v2.c index 2ce23e0db8..157d601d9e 100644 --- a/gst-libs/gst/tag/id3v2.c +++ b/gst-libs/gst/tag/id3v2.c @@ -333,6 +333,7 @@ id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size) work->hdr.ext_hdr_size = read_synch_uint (work->hdr.frame_data, 4); if (work->hdr.ext_hdr_size < 6 || (work->hdr.ext_hdr_size) > work->hdr.frame_data_size) { + GST_DEBUG ("Invalid extended header. Broken tag"); return ID3TAGS_BROKEN_TAG; } work->hdr.ext_flag_bytes = work->hdr.frame_data[4]; @@ -400,6 +401,8 @@ id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size) if (ID3V2_VER_MAJOR (work->hdr.version) == 3) { frame_flags &= ID3V2_3_FRAME_FLAGS_MASK; obsolete_id = convert_fid_to_v240 (frame_id); + if (obsolete_id) + GST_DEBUG ("Ignoring v2.3 frame %s", frame_id); } break; } @@ -407,8 +410,7 @@ id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size) work->hdr.frame_data += frame_hdr_size; work->hdr.frame_data_size -= frame_hdr_size; - if (frame_size > work->hdr.frame_data_size || - frame_size == 0 || strcmp (frame_id, "") == 0) + if (frame_size > work->hdr.frame_data_size || strcmp (frame_id, "") == 0) break; /* No more frames to read */ #if 1 diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index cf356433c8..bab609ded7 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -420,7 +420,9 @@ id3v2_genre_fields_to_taglist (ID3TagsWorking * work, const gchar * tag_name, continue; len = strlen (tag_str); - if (work->hdr.version <= 0x300) { /* <= 2.3.0 */ + /* Only supposed to see '(n)' type numeric genre strings in ID3 <= 2.3.0 + * but apparently we see them in 2.4.0 sometimes too */ + if (TRUE || work->hdr.version <= 0x300) { /* <= 2.3.0 */ /* Check for genre numbers wrapped in parentheses, possibly * followed by a string */ while (len >= 2) { From d8900f72b52cc429464bb99c6076a077aa6c35b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Tue, 14 Mar 2006 17:56:02 +0000 Subject: [PATCH 082/140] configure.ac: Bump -base requirement to 0.10.5 for gst_tag_from_id3_user_tag(), used by id3demux. Original commit message from CVS: * configure.ac: Bump -base requirement to 0.10.5 for gst_tag_from_id3_user_tag(), used by id3demux. * gst-libs/gst/tag/gstid3demux.c: (plugin_init): * gst-libs/gst/tag/id3v2frames.c: (id3demux_id3v2_parse_frame), (parse_user_text_identification_frame), (parse_unique_file_identifier): Add support for UFID and TXXX frames and extract musicbrainz tags. --- gst-libs/gst/tag/id3v2frames.c | 92 +++++++++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index bab609ded7..81ff347250 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -37,6 +37,10 @@ GST_DEBUG_CATEGORY_EXTERN (id3demux_debug); static gchar *parse_comment_frame (ID3TagsWorking * work); static GArray *parse_text_identification_frame (ID3TagsWorking * work); +static gchar *parse_user_text_identification_frame (ID3TagsWorking * work, + const gchar ** tag_name); +static gchar *parse_unique_file_identifier (ID3TagsWorking * work, + const gchar ** tag_name); static gboolean id3v2_tag_to_taglist (ID3TagsWorking * work, const gchar * tag_name, const gchar * tag_str); /* Parse a single string into an array of gchar* */ @@ -85,8 +89,11 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) } tag_name = gst_tag_from_id3_tag (work->frame_id); - if (tag_name == NULL) + if (tag_name == NULL && + strncmp (work->frame_id, "TXXX", 4) != 0 && + strncmp (work->frame_id, "UFID", 4) != 0) { return FALSE; + } if (work->frame_flags & (ID3V2_FRAME_FORMAT_COMPRESSION | ID3V2_FRAME_FORMAT_DATA_LENGTH_INDICATOR)) { @@ -140,6 +147,7 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) tag_fields = parse_text_identification_frame (work); } else { /* Handle user text frame */ + tag_str = parse_user_text_identification_frame (work, &tag_name); } } else if (!strcmp (work->frame_id, "COMM")) { /* Comment */ @@ -150,6 +158,7 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) /* Relative volume */ } else if (!strcmp (work->frame_id, "UFID")) { /* Unique file identifier */ + tag_str = parse_unique_file_identifier (work, &tag_name); } if (work->frame_flags & ID3V2_FRAME_FORMAT_COMPRESSION) @@ -251,6 +260,87 @@ parse_text_identification_frame (ID3TagsWorking * work) return fields; } +static gchar * +parse_user_text_identification_frame (ID3TagsWorking * work, + const gchar ** tag_name) +{ + gchar *ret; + guchar encoding; + GArray *fields = NULL; + + *tag_name = NULL; + + if (work->parse_size < 2) + return NULL; + + encoding = work->parse_data[0]; + + parse_split_strings (encoding, (gchar *) work->parse_data + 1, + work->parse_size - 1, &fields); + + if (fields == NULL) + return NULL; + + if (fields->len != 2) { + GST_WARNING ("Expected 2 fields in TXXX frame, but got %d", fields->len); + free_tag_strings (fields); + return NULL; + } + + *tag_name = + gst_tag_from_id3_user_tag ("TXXX", g_array_index (fields, gchar *, 0)); + + GST_LOG ("TXXX frame of size %d. Mapped descriptor '%s' to GStreamer tag %s", + work->parse_size - 1, g_array_index (fields, gchar *, 0), + GST_STR_NULL (*tag_name)); + + if (*tag_name) { + ret = g_strdup (g_array_index (fields, gchar *, 1)); + /* GST_LOG ("%s = %s", *tag_name, GST_STR_NULL (ret)); */ + } else { + ret = NULL; + } + + free_tag_strings (fields); + return ret; +} + +static gchar * +parse_unique_file_identifier (ID3TagsWorking * work, const gchar ** tag_name) +{ + gint len, datalen; + gchar *owner_id, *data, *ret = NULL; + + if (work->parse_size < 2) + return NULL; + + GST_LOG ("parsing UFID frame of size %d", work->parse_size); + + for (len = 0; len < work->parse_size - 1; ++len) { + if (work->parse_data[len] == '\0') + break; + } + + datalen = work->parse_size - (len + 1); + if (datalen <= 0) + return NULL; + + owner_id = g_strndup ((gchar *) work->parse_data, len); + data = (gchar *) work->parse_data + len + 1; + GST_LOG ("UFID owner ID: %s (+ %d bytes of data)", owner_id, datalen); + + if (strcmp (owner_id, "http://musicbrainz.org") == 0 && + g_utf8_validate (data, datalen, NULL)) { + *tag_name = GST_TAG_MUSICBRAINZ_TRACKID; + ret = g_strndup (data, datalen); + } else { + GST_INFO ("Unknown UFID owner ID: %s", owner_id); + } + g_free (owner_id); + + return ret; +} + static gboolean id3v2_tag_to_taglist (ID3TagsWorking * work, const gchar * tag_name, const gchar * tag_str) From 84cc22cacc3d2cee7db3edb92b92a04dfba2de27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Thu, 16 Mar 2006 13:22:28 +0000 Subject: [PATCH 083/140] tag: id3v2: Read replay gain tags Original commit message from CVS: * gst-libs/gst/tag/id3v2frames.c: (id3demux_id3v2_parse_frame), (parse_id_string), (parse_unique_file_identifier), (parse_relative_volume_adjustment_two), (id3v2_tag_to_taglist): Read replay gain tags (#323721). --- gst-libs/gst/tag/id3v2frames.c | 116 ++++++++++++++++++++++++++++++--- 1 file changed, 108 insertions(+), 8 deletions(-) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index 81ff347250..0c866f761a 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -41,6 +41,7 @@ static gchar *parse_user_text_identification_frame (ID3TagsWorking * work, const gchar ** tag_name); static gchar *parse_unique_file_identifier (ID3TagsWorking * work, const gchar ** tag_name); +static gboolean parse_relative_volume_adjustment_two (ID3TagsWorking * work); static gboolean id3v2_tag_to_taglist (ID3TagsWorking * work, const gchar * tag_name, const gchar * tag_str); /* Parse a single string into an array of gchar* */ @@ -90,6 +91,7 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) tag_name = gst_tag_from_id3_tag (work->frame_id); if (tag_name == NULL && + strncmp (work->frame_id, "RVA2", 4) != 0 && strncmp (work->frame_id, "TXXX", 4) != 0 && strncmp (work->frame_id, "UFID", 4) != 0) { return FALSE; @@ -156,6 +158,7 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) /* Attached picture */ } else if (!strcmp (work->frame_id, "RVA2")) { /* Relative volume */ + result = parse_relative_volume_adjustment_two (work); } else if (!strcmp (work->frame_id, "UFID")) { /* Unique file identifier */ tag_str = parse_unique_file_identifier (work, &tag_name); @@ -305,16 +308,14 @@ parse_user_text_identification_frame (ID3TagsWorking * work, return ret; } -static gchar * -parse_unique_file_identifier (ID3TagsWorking * work, const gchar ** tag_name) +static gboolean +parse_id_string (ID3TagsWorking * work, gchar ** p_str, gint * p_len, + gint * p_datalen) { gint len, datalen; - gchar *owner_id, *data, *ret = NULL; if (work->parse_size < 2) - return NULL; - - GST_LOG ("parsing UFID frame of size %d", work->parse_size); + return FALSE; for (len = 0; len < work->parse_size - 1; ++len) { if (work->parse_data[len] == '\0') @@ -322,10 +323,27 @@ parse_unique_file_identifier (ID3TagsWorking * work, const gchar ** tag_name) } datalen = work->parse_size - (len + 1); - if (datalen <= 0) + if (len == 0 || datalen <= 0) + return FALSE; + + *p_str = g_strndup ((gchar *) work->parse_data, len); + *p_len = len; + *p_datalen = datalen; + + return TRUE; +} + +static gchar * +parse_unique_file_identifier (ID3TagsWorking * work, const gchar ** tag_name) +{ + gint len, datalen; + gchar *owner_id, *data, *ret = NULL; + + GST_LOG ("parsing UFID frame of size %d", work->parse_size); + + if (!parse_id_string (work, &owner_id, &len, &datalen)) return NULL; - owner_id = g_strndup ((gchar *) work->parse_data, len); data = (gchar *) work->parse_data + len + 1; GST_LOG ("UFID owner ID: %s (+ %d bytes of data)", owner_id, datalen); @@ -341,6 +359,81 @@ parse_unique_file_identifier (ID3TagsWorking * work, const gchar ** tag_name) return ret; } +static gboolean +parse_relative_volume_adjustment_two (ID3TagsWorking * work) +{ + const gchar *gain_tag_name = NULL; + const gchar *peak_tag_name = NULL; + gdouble gain_dB, peak_val; + guint64 peak; + guint8 *data, chan, peak_bits; + gchar *id; + gint len, datalen, i; + + if (!parse_id_string (work, &id, &len, &datalen)) + return FALSE; + + if (datalen < (1 + 2 + 1)) { + GST_WARNING ("broken RVA2 frame, data size only %d bytes", datalen); + g_free (id); + return FALSE; + } + + data = work->parse_data + len + 1; + chan = GST_READ_UINT8 (data); + gain_dB = (gdouble) ((gint16) GST_READ_UINT16_BE (data + 1)) / 512.0; + /* The meaning of the peak value is not defined in the ID3v2 spec. However, + * the first/only implementation of this seems to have been in XMMS, and + * other libs (like mutagen) seem to follow that implementation as well: + * see http://bugs.xmms.org/attachment.cgi?id=113&action=view */ + peak_bits = GST_READ_UINT8 (data + 1 + 2); + if (peak_bits > 64) { + GST_WARNING ("silly peak precision of %d bits, ignoring", (gint) peak_bits); + peak_bits = 0; + } + data += 1 + 2 + 1; + datalen -= 1 + 2 + 1; + if (peak_bits == 16) { + peak = GST_READ_UINT16_BE (data); + } else { + peak = 0; + for (i = 0; i < (GST_ROUND_UP_8 (peak_bits) / 8) && datalen > 0; ++i) { + peak = peak << 8; + peak |= GST_READ_UINT8 (data); + ++data; + --datalen; + } + } + + peak = peak << (64 - GST_ROUND_UP_8 (peak_bits)); + peak_val = (gdouble) peak / gst_util_guint64_to_gdouble (G_MAXINT64); + GST_LOG ("RVA2 frame: id=%s, chan=%u, adj=%.2fdB, peak_bits=%u, peak=%.2f", + id, chan, gain_dB, (guint) peak_bits, peak_val); + + if (strcmp (id, "track") == 0) { + gain_tag_name = GST_TAG_TRACK_GAIN; + peak_tag_name = GST_TAG_TRACK_PEAK; + } else if (strcmp (id, "album") == 0) { + gain_tag_name = GST_TAG_ALBUM_GAIN; + peak_tag_name = GST_TAG_ALBUM_PEAK; + } else { + GST_INFO ("Unhandled RVA2 frame id '%s'", id); + } + + if (gain_tag_name) { + gst_tag_list_add (work->tags, GST_TAG_MERGE_APPEND, + gain_tag_name, gain_dB, NULL); + } + if (peak_tag_name && peak_bits > 0) { + gst_tag_list_add (work->tags, GST_TAG_MERGE_APPEND, + peak_tag_name, peak_val, NULL); + } + + g_free (id); + + return (gain_tag_name != NULL || peak_tag_name != NULL); +} + static gboolean id3v2_tag_to_taglist (ID3TagsWorking * work, const gchar * tag_name, const gchar * tag_str) @@ -442,6 +535,13 @@ id3v2_tag_to_taglist (ID3TagsWorking * work, const gchar * tag_name, if (g_value_transform (&src, &dest)) { gst_tag_list_add_values (tag_list, GST_TAG_MERGE_APPEND, tag_name, &dest, NULL); + } else if (tag_type == G_TYPE_DOUBLE) { + /* replaygain tags in TXXX frames ... */ + g_value_set_double (&dest, g_strtod (tag_str, NULL)); + gst_tag_list_add_values (tag_list, GST_TAG_MERGE_KEEP, + tag_name, &dest, NULL); + GST_LOG ("Converted string '%s' to double %f", tag_str, + g_value_get_double (&dest)); } else { GST_WARNING ("Failed to transform tag from string to type '%s'", g_type_name (tag_type)); From 17be9f715b584169cecbb905997c592f491b2899 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Thu, 16 Mar 2006 16:06:22 +0000 Subject: [PATCH 084/140] tag: id3v2: We only care about gain and peak data for the master volume. Original commit message from CVS: * gst-libs/gst/tag/id3v2frames.c: (parse_relative_volume_adjustment_two): We only care about gain and peak data for the master volume. --- gst-libs/gst/tag/id3v2frames.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index 0c866f761a..8e1ee04877 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -359,6 +359,8 @@ parse_unique_file_identifier (ID3TagsWorking * work, const gchar ** tag_name) return ret; } +#define ID3V2_RVA2_CHANNEL_MASTER 1 + static gboolean parse_relative_volume_adjustment_two (ID3TagsWorking * work) { @@ -410,14 +412,14 @@ parse_relative_volume_adjustment_two (ID3TagsWorking * work) GST_LOG ("RVA2 frame: id=%s, chan=%u, adj=%.2fdB, peak_bits=%u, peak=%.2f", id, chan, gain_dB, (guint) peak_bits, peak_val); - if (strcmp (id, "track") == 0) { + if (chan == ID3V2_RVA2_CHANNEL_MASTER && strcmp (id, "track") == 0) { gain_tag_name = GST_TAG_TRACK_GAIN; peak_tag_name = GST_TAG_TRACK_PEAK; - } else if (strcmp (id, "album") == 0) { + } else if (chan == ID3V2_RVA2_CHANNEL_MASTER && strcmp (id, "album") == 0) { gain_tag_name = GST_TAG_ALBUM_GAIN; peak_tag_name = GST_TAG_ALBUM_PEAK; } else { - GST_INFO ("Unhandled RVA2 frame id '%s'", id); + GST_INFO ("Unhandled RVA2 frame id '%s' for channel %d", id, chan); } if (gain_tag_name) { From a2e391cb689ce56ea24f0c0aadda3f0ecdaee4f3 Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Wed, 22 Mar 2006 13:00:34 +0000 Subject: [PATCH 085/140] tag: id3v2: Don't attempt typefinding on too-short buffers that have been completely trimmed away. Original commit message from CVS: * gst/apetag/gsttagdemux.c: (gst_tag_demux_chain): * gst-libs/gst/tag/gstid3demux.c: (gst_id3demux_chain): Don't attempt typefinding on too-short buffers that have been completely trimmed away. * gst-libs/gst/tag/id3v2.c: (id3demux_read_id3v2_tag): Improve the debug output --- gst-libs/gst/tag/id3v2.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/gst-libs/gst/tag/id3v2.c b/gst-libs/gst/tag/id3v2.c index 157d601d9e..bd370ed5be 100644 --- a/gst-libs/gst/tag/id3v2.c +++ b/gst-libs/gst/tag/id3v2.c @@ -167,10 +167,17 @@ id3demux_read_id3v2_tag (GstBuffer * buffer, guint * id3v2_size, version >> 8, version & 0xff, ID3V2_VERSION >> 8, ID3V2_VERSION & 0xff); return ID3TAGS_READ_TAG; } - GST_DEBUG ("ID3v2 tag with revision 2.%d.%d\n", version >> 8, version & 0xff); - if (GST_BUFFER_SIZE (buffer) < read_size) + if (GST_BUFFER_SIZE (buffer) < read_size) { + GST_DEBUG + ("Found ID3v2 tag with revision 2.%d.%d - need %u more bytes to read", + version >> 8, version & 0xff, + (guint) (read_size - GST_BUFFER_SIZE (buffer))); return ID3TAGS_MORE_DATA; /* Need more data to decode with */ + } + + GST_DEBUG ("Reading ID3v2 tag with revision 2.%d.%d of size %u", version >> 8, + version & 0xff, read_size); g_return_val_if_fail (tags != NULL, ID3TAGS_READ_TAG); From 571d8f9d8677a09d78d53b63f76d6cbf1da33a94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Moutte?= Date: Thu, 30 Mar 2006 23:37:16 +0000 Subject: [PATCH 086/140] tag: id3v2: use of GST_DEBUG instead of DEBUG(a...) for WIN32 Original commit message from CVS: * ext\jpeg\smokecodec.c: use of GST_DEBUG instead of DEBUG(a...) for WIN32 * ext\speex\gstspeexenc.c: (gst_speexenc_set_header_on_caps): move first instruction after all variables declarations * gst\alpha\gstalpha.c: * gst\effectv\gstshagadelic.c: * gst\smpte\paint.c: * gst\videofilter\gstvideobalance.c: define M_PI if it's not defined (it's not defined on WIN32) * gst\cutter\gstcutter.c: (gst_cutter_chain): * gst\id3demux\id3v2frames.c: (parse_relative_volume_adjustment_two): * gst\level\gstlevel.c: (gst_level_set_property), (gst_level_transform_ip): * gst\matroska\matroska-demux.c: (gst_matroska_demux_parse_info), (gst_matroska_demux_video_caps): * gst\matroska\matroska-mux.c: (gst_matroska_mux_start), (gst_matroska_mux_finish): * gst\wavparse\gstwavparse.c: (gst_wavparse_stream_data): use gst_guint64_to_gdouble for conversions * gst\goom\filters.c: (setPixelRGB_): fix a debug which was using undefined variable * gst\level\gstlevel.c: (gst_level_set_caps), (gst_level_transform_ip): * gst\matroska\ebml-read.c: (gst_ebml_read_sint): replace LL suffix with L suffix (LL isn't supported by MSVC6.0) * win32/vs6: add vs6 projects files for most of plugins-good --- gst-libs/gst/tag/id3v2frames.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index 8e1ee04877..cf56f8bd17 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -408,7 +408,8 @@ parse_relative_volume_adjustment_two (ID3TagsWorking * work) } peak = peak << (64 - GST_ROUND_UP_8 (peak_bits)); - peak_val = (gdouble) peak / gst_util_guint64_to_gdouble (G_MAXINT64); + peak_val = + gst_guint64_to_gdouble (peak) / gst_util_guint64_to_gdouble (G_MAXINT64); GST_LOG ("RVA2 frame: id=%s, chan=%u, adj=%.2fdB, peak_bits=%u, peak=%.2f", id, chan, gain_dB, (guint) peak_bits, peak_val); From e3aa8b3163717266611f2b47dff31fc84eecb224 Mon Sep 17 00:00:00 2001 From: Alex Lancaster Date: Mon, 17 Apr 2006 10:01:51 +0000 Subject: [PATCH 087/140] tag: id3v2: Recognise TCO (Genre) tags in ID3v2.2 Original commit message from CVS: * gst-libs/gst/tag/id3v2.c: Recognise TCO (Genre) tags in ID3v2.2. Patch by Alex Lancaster (Fixes #338713) --- gst-libs/gst/tag/id3v2.c | 1 + 1 file changed, 1 insertion(+) diff --git a/gst-libs/gst/tag/id3v2.c b/gst-libs/gst/tag/id3v2.c index bd370ed5be..35571eca6c 100644 --- a/gst-libs/gst/tag/id3v2.c +++ b/gst-libs/gst/tag/id3v2.c @@ -264,6 +264,7 @@ const struct ID3v2FrameIDConvert "TAL", "TALB"}, { "TBP", "TBPM"}, { "TCM", "TCOM"}, { + "TCO", "TCON"}, { "TCR", "TCOP"}, { "TDA", "TDRC"}, { "TDY", "TDLY"}, { From 1b885bafbad13ad13ca234a66c057188eaab5322 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Fri, 28 Apr 2006 11:37:22 +0000 Subject: [PATCH 088/140] tag: id3v2: Recognise and skip any byte order marker (BOM) in Original commit message from CVS: * gst-libs/gst/tag/id3v2frames.c: (has_utf16_bom), (parse_split_strings): Recognise and skip any byte order marker (BOM) in UTF-16 strings. --- gst-libs/gst/tag/id3v2frames.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index cf56f8bd17..b97e4d0184 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -668,6 +668,24 @@ parse_insert_string_field (const gchar * encoding, gchar * data, gint data_size, g_array_append_val (fields, field); } +static gboolean +has_utf16_bom (gchar * data, const gchar ** p_in_encoding) +{ + guint16 marker = (GST_READ_UINT8 (data) << 8) | GST_READ_UINT8 (data + 1); + + switch (marker) { + case 0xFFFE: + *p_in_encoding = "UTF16LE"; + return TRUE; + case 0xFEFF: + *p_in_encoding = "UTF16BE"; + return TRUE; + default: + break; + } + return FALSE; +} + static void parse_split_strings (guint8 encoding, gchar * data, gint data_size, GArray ** out_fields) @@ -719,6 +737,9 @@ parse_split_strings (guint8 encoding, gchar * data, gint data_size, /* Find '\0\0' terminator */ for (text_pos = 0; text_pos < data_size - 1; text_pos += 2) { if (data[text_pos] == '\0' && data[text_pos + 1] == '\0') { + if (has_utf16_bom (data + prev, &in_encode)) { + prev += 2; /* skip BOM */ + } /* found a delimiter */ parse_insert_string_field (in_encode, data + prev, text_pos - prev + 2, fields); @@ -729,6 +750,9 @@ parse_split_strings (guint8 encoding, gchar * data, gint data_size, } if (data_size - prev > 1 && (data[prev] != 0x00 || data[prev + 1] != 0x00)) { + if (has_utf16_bom (data + prev, &in_encode)) { + prev += 2; /* skip BOM */ + } /* There were 2 or more non-null chars left, convert those too */ parse_insert_string_field (in_encode, data + prev, data_size - prev, fields); From 9f4827af39d691042af321c181071dab935c0dbc Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Wed, 10 May 2006 13:51:01 +0000 Subject: [PATCH 089/140] tag: id3v2: Fix parsing of numeric genre strings some more, by ensuring that we only try and parse st... Original commit message from CVS: * gst-libs/gst/tag/id3v2frames.c: (id3v2_genre_fields_to_taglist): Fix parsing of numeric genre strings some more, by ensuring that we only try and parse strings that a) Start with '(' and b) Consist only of digits. Also, when finding an escaping '((' sequence, bust it back to '(' by swallowing the first parenthesis --- gst-libs/gst/tag/id3v2frames.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index b97e4d0184..832b7b2284 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -622,8 +622,17 @@ id3v2_genre_fields_to_taglist (ID3TagsWorking * work, const gchar * tag_name, gint pos; gboolean found = FALSE; - /* Double parenthesis ends the numeric genres */ - if (tag_str[0] == '(' && tag_str[1] == '(') + /* Double parenthesis ends the numeric genres, but we need + * to swallow the first one so we actually output '(' */ + if (tag_str[0] == '(' && tag_str[1] == '(') { + tag_str++; + len--; + break; + } + + /* If the first char is not a parenthesis, then stop + * looking for parenthesised genre strings */ + if (tag_str[0] != '(') break; for (pos = 1; pos < len; pos++) { @@ -640,6 +649,12 @@ id3v2_genre_fields_to_taglist (ID3TagsWorking * work, const gchar * tag_name, found = TRUE; break; } + + /* If we encounter a non-digit while searching for a closing + * parenthesis, we should not try and interpret this as a + * numeric genre string */ + if (!g_ascii_isdigit (tag_str[pos])) + break; } if (!found) break; /* There was no closing parenthesis */ From 2fd7d6c3ebdbdd42a45d6039f53371cfc95f7bce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Fri, 12 May 2006 08:21:37 +0000 Subject: [PATCH 090/140] tag: id3v2: Some more debug info. No need to check whether the string returned by g_convert() is real... Original commit message from CVS: * gst-libs/gst/tag/id3v2frames.c: (parse_insert_string_field): Some more debug info. No need to check whether the string returned by g_convert() is really UTF-8 - either it is or we get NULL returned. --- gst-libs/gst/tag/id3v2frames.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index 832b7b2284..6690f5a509 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -671,14 +671,20 @@ static void parse_insert_string_field (const gchar * encoding, gchar * data, gint data_size, GArray * fields) { - gchar *field; + gchar *field = NULL; - field = g_convert (data, data_size, "UTF-8", encoding, NULL, NULL, NULL); - if (field && !g_utf8_validate (field, -1, NULL)) { - GST_DEBUG ("%s was bad UTF-8. Ignoring", field); - g_free (field); - field = NULL; + if (strcmp (encoding, "UTF-8") != 0) { + field = g_convert (data, data_size, "UTF-8", encoding, NULL, NULL, NULL); + if (field == NULL) { + GST_WARNING ("could not convert string from %s to UTF-8. Ignoring", + encoding); + } + } else if (g_utf8_validate (data, data_size, NULL)) { + field = g_strndup (data, data_size); + } else { + GST_WARNING ("alleged UTF-8 string is not valid UTF-8. Ignoring"); } + if (field) g_array_append_val (fields, field); } From 5b67108b7da23a3373f4b077e32b79cf8de4c21a Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Tue, 16 May 2006 14:07:29 +0000 Subject: [PATCH 091/140] tag: id3v2: Rework string parsing to always walk over BOM markers in UTF16 strings, using the endianness indicated by the innermost one ... Original commit message from CVS: * gst/autodetect/gstautoaudiosink.c: (gst_auto_audio_sink_find_best): * gst/autodetect/gstautovideosink.c: (gst_auto_video_sink_find_best): Make the name of the child element be based on the name of the parent, so that debug output is more useful. * gst-libs/gst/tag/id3v2frames.c: (find_utf16_bom), (parse_insert_string_field), (parse_split_strings): Rework string parsing to always walk over BOM markers in UTF16 strings, using the endianness indicated by the innermost one, then trying the opposite endianness if that fails to convert to valid UTF-8. Fixes #341774 --- gst-libs/gst/tag/id3v2frames.c | 112 ++++++++++++++++++++------------- 1 file changed, 69 insertions(+), 43 deletions(-) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index 6690f5a509..21ca4f80f6 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -667,39 +667,21 @@ id3v2_genre_fields_to_taglist (ID3TagsWorking * work, const gchar * tag_name, return result; } -static void -parse_insert_string_field (const gchar * encoding, gchar * data, gint data_size, - GArray * fields) -{ - gchar *field = NULL; - - if (strcmp (encoding, "UTF-8") != 0) { - field = g_convert (data, data_size, "UTF-8", encoding, NULL, NULL, NULL); - if (field == NULL) { - GST_WARNING ("could not convert string from %s to UTF-8. Ignoring", - encoding); - } - } else if (g_utf8_validate (data, data_size, NULL)) { - field = g_strndup (data, data_size); - } else { - GST_WARNING ("alleged UTF-8 string is not valid UTF-8. Ignoring"); - } - - if (field) - g_array_append_val (fields, field); -} +static const gchar utf16enc[] = "UTF-16"; +static const gchar utf16leenc[] = "UTF-16LE"; +static const gchar utf16beenc[] = "UTF-16BE"; static gboolean -has_utf16_bom (gchar * data, const gchar ** p_in_encoding) +find_utf16_bom (gchar * data, const gchar ** p_in_encoding) { guint16 marker = (GST_READ_UINT8 (data) << 8) | GST_READ_UINT8 (data + 1); switch (marker) { case 0xFFFE: - *p_in_encoding = "UTF16LE"; + *p_in_encoding = utf16leenc; return TRUE; case 0xFEFF: - *p_in_encoding = "UTF16BE"; + *p_in_encoding = utf16beenc; return TRUE; default: break; @@ -707,6 +689,63 @@ has_utf16_bom (gchar * data, const gchar ** p_in_encoding) return FALSE; } +static void +parse_insert_string_field (guint8 encoding, gchar * data, gint data_size, + GArray * fields) +{ + gchar *field = NULL; + + switch (encoding) { + case ID3V2_ENCODING_UTF16: + case ID3V2_ENCODING_UTF16BE: + { + const gchar *in_encode; + + if (encoding == ID3V2_ENCODING_UTF16) + in_encode = utf16enc; + else + in_encode = utf16beenc; + + /* Sometimes we see strings with multiple BOM markers at the start. + * In that case, we assume the innermost one is correct. If that fails + * to produce valid UTF-8, we try the other endianness anyway */ + while (data_size > 2 && find_utf16_bom (data, &in_encode)) { + data += 2; /* skip BOM */ + data_size -= 2; + } + + field = g_convert (data, data_size, "UTF-8", in_encode, NULL, NULL, NULL); + + if (field == NULL || g_utf8_validate (field, -1, NULL) == FALSE) { + /* As a fallback, try interpreting UTF-16 in the other endianness */ + if (in_encode == utf16beenc) + field = g_convert (data, data_size, "UTF-8", utf16leenc, + NULL, NULL, NULL); + } + } + + break; + case ID3V2_ENCODING_ISO8859: + field = g_convert (data, data_size, "UTF-8", "ISO-8859-1", + NULL, NULL, NULL); + break; + default: + field = g_strndup (data, data_size); + break; + } + + if (field) { + if (g_utf8_validate (field, -1, NULL)) { + g_array_append_val (fields, field); + return; + } + + GST_DEBUG ("%s was bad UTF-8 after conversion from encoding %d. Ignoring", + field, encoding); + g_free (field); + } +} + static void parse_split_strings (guint8 encoding, gchar * data, gint data_size, GArray ** out_fields) @@ -721,13 +760,13 @@ parse_split_strings (guint8 encoding, gchar * data, gint data_size, case ID3V2_ENCODING_ISO8859: for (text_pos = 0; text_pos < data_size; text_pos++) { if (data[text_pos] == 0) { - parse_insert_string_field ("ISO-8859-1", data + prev, + parse_insert_string_field (encoding, data + prev, text_pos - prev + 1, fields); prev = text_pos + 1; } } if (data_size - prev > 0 && data[prev] != 0x00) { - parse_insert_string_field ("ISO-8859-1", data + prev, + parse_insert_string_field (encoding, data + prev, data_size - prev, fields); } @@ -735,34 +774,24 @@ parse_split_strings (guint8 encoding, gchar * data, gint data_size, case ID3V2_ENCODING_UTF8: for (prev = 0, text_pos = 0; text_pos < data_size; text_pos++) { if (data[text_pos] == '\0') { - parse_insert_string_field ("UTF-8", data + prev, + parse_insert_string_field (encoding, data + prev, text_pos - prev + 1, fields); prev = text_pos + 1; } } if (data_size - prev > 0 && data[prev] != 0x00) { - parse_insert_string_field ("UTF-8", data + prev, + parse_insert_string_field (encoding, data + prev, data_size - prev, fields); } break; case ID3V2_ENCODING_UTF16: case ID3V2_ENCODING_UTF16BE: { - const gchar *in_encode; - - if (encoding == ID3V2_ENCODING_UTF16) - in_encode = "UTF-16"; - else - in_encode = "UTF-16BE"; - /* Find '\0\0' terminator */ for (text_pos = 0; text_pos < data_size - 1; text_pos += 2) { if (data[text_pos] == '\0' && data[text_pos + 1] == '\0') { - if (has_utf16_bom (data + prev, &in_encode)) { - prev += 2; /* skip BOM */ - } /* found a delimiter */ - parse_insert_string_field (in_encode, data + prev, + parse_insert_string_field (encoding, data + prev, text_pos - prev + 2, fields); text_pos++; /* Advance to the 2nd NULL terminator */ prev = text_pos + 1; @@ -771,11 +800,8 @@ parse_split_strings (guint8 encoding, gchar * data, gint data_size, } if (data_size - prev > 1 && (data[prev] != 0x00 || data[prev + 1] != 0x00)) { - if (has_utf16_bom (data + prev, &in_encode)) { - prev += 2; /* skip BOM */ - } /* There were 2 or more non-null chars left, convert those too */ - parse_insert_string_field (in_encode, data + prev, + parse_insert_string_field (encoding, data + prev, data_size - prev, fields); } break; From 7e7ee7278b3b10a3a3a5d87134b08f962acf6da8 Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Fri, 19 May 2006 14:05:53 +0000 Subject: [PATCH 092/140] tag: id3v2: Don't output any tag when we encounter a negative track number - the tag type is uint, so... Original commit message from CVS: * gst-libs/gst/tag/id3v2frames.c: (id3v2_tag_to_taglist): Don't output any tag when we encounter a negative track number - the tag type is uint, so we end up outputting huge positive numbers instead. (Fixes: #342029) --- gst-libs/gst/tag/id3v2frames.c | 55 +++++++++++++++------------------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index 21ca4f80f6..f8c1453666 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -450,41 +450,34 @@ id3v2_tag_to_taglist (ID3TagsWorking * work, const gchar * tag_name, switch (tag_type) { case G_TYPE_UINT: { - guint tmp; - gchar *check; + gint current, total; - tmp = strtoul ((char *) tag_str, &check, 10); - - if (strcmp (tag_name, GST_TAG_TRACK_NUMBER) == 0) { - if (*check == '/') { - guint total; - - check++; - total = strtoul (check, &check, 10); - if (*check != '\0') - break; - - gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND, - GST_TAG_TRACK_COUNT, total, NULL); - } - } else if (strcmp (tag_name, GST_TAG_ALBUM_VOLUME_NUMBER) == 0) { - if (*check == '/') { - guint total; - - check++; - total = strtoul (check, &check, 10); - if (*check != '\0') - break; - - gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND, - GST_TAG_ALBUM_VOLUME_COUNT, total, NULL); + if (sscanf (tag_str, "%d/%d", ¤t, &total) == 2) { + if (total < 0) { + GST_WARNING ("Ignoring negative value for total %d in tag %s", + total, tag_name); + } else { + if (strcmp (tag_name, GST_TAG_TRACK_NUMBER) == 0) { + gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND, + GST_TAG_TRACK_COUNT, total, NULL); + } else if (strcmp (tag_name, GST_TAG_ALBUM_VOLUME_NUMBER) == 0) { + gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND, + GST_TAG_ALBUM_VOLUME_COUNT, total, NULL); + } } + } else if (sscanf (tag_str, "%d", ¤t) != 1) { + /* Not an integer in the string */ + GST_WARNING ("Tag string for tag %s does not contain an integer - " + "ignoring", tag_name); + break; } - if (*check != '\0') - break; - - gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND, tag_name, tmp, NULL); + if (current < 0) + GST_WARNING ("Ignoring negative value %d in tag %s", current, tag_name); + else { + gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND, tag_name, current, + NULL); + } break; } case G_TYPE_UINT64: From 6f5e16f4ba36eedc3355f4b139f03144a8d0df55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Sun, 28 May 2006 10:05:47 +0000 Subject: [PATCH 093/140] tag: id3v2: A track/volume number or count of 0 does not make sense, just ignore it along with negati... Original commit message from CVS: * gst-libs/gst/tag/id3v2frames.c: (id3v2_tag_to_taglist): A track/volume number or count of 0 does not make sense, just ignore it along with negative numbers (a tag might only contain a track count without a track number). --- gst-libs/gst/tag/id3v2frames.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index f8c1453666..8c20619f1a 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -453,8 +453,8 @@ id3v2_tag_to_taglist (ID3TagsWorking * work, const gchar * tag_name, gint current, total; if (sscanf (tag_str, "%d/%d", ¤t, &total) == 2) { - if (total < 0) { - GST_WARNING ("Ignoring negative value for total %d in tag %s", + if (total <= 0) { + GST_WARNING ("Ignoring invalid value for total %d in tag %s", total, tag_name); } else { if (strcmp (tag_name, GST_TAG_TRACK_NUMBER) == 0) { @@ -472,9 +472,9 @@ id3v2_tag_to_taglist (ID3TagsWorking * work, const gchar * tag_name, break; } - if (current < 0) - GST_WARNING ("Ignoring negative value %d in tag %s", current, tag_name); - else { + if (current <= 0) { + GST_WARNING ("Ignoring invalid value %d in tag %s", current, tag_name); + } else { gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND, tag_name, current, NULL); } From 1cc10b36113911cf33e9353e205301d63ebe2b46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Sun, 11 Jun 2006 19:31:10 +0000 Subject: [PATCH 094/140] tag: id3v2: Extract images from ID3v2 tags (APIC frames). Fixes #339704. Original commit message from CVS: * gst-libs/gst/tag/id3v2frames.c: (id3demux_id3v2_parse_frame), (scan_encoded_string), (parse_picture_frame): Extract images from ID3v2 tags (APIC frames). Fixes #339704. * configure.ac: Require core >= 0.10.8 (for GST_TAG_IMAGE and GST_TAG_PPEVIEW_IMAGE used in the patch above). --- gst-libs/gst/tag/id3v2frames.c | 170 ++++++++++++++++++++++++++++++++- 1 file changed, 169 insertions(+), 1 deletion(-) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index 8c20619f1a..949bbbf0d8 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -1,5 +1,6 @@ /* -*- Mode: C; tab-width: 2; indent-tabs-mode: t; c-basic-offset: 2 -*- */ -/* Copyright 2005 Jan Schmidt +/* Copyright 2006 Tim-Philipp Müller + * Copyright 2005 Jan Schmidt * Copyright 2002,2003 Scott Wheeler (portions from taglib) * * This library is free software; you can redistribute it and/or @@ -25,6 +26,7 @@ #include #include #include +#include #ifdef HAVE_ZLIB #include @@ -51,6 +53,7 @@ static void free_tag_strings (GArray * fields); static gboolean id3v2_genre_fields_to_taglist (ID3TagsWorking * work, const gchar * tag_name, GArray * tag_fields); +static gboolean parse_picture_frame (ID3TagsWorking * work); #define ID3V2_ENCODING_ISO8859 0x00 #define ID3V2_ENCODING_UTF16 0x01 @@ -156,6 +159,7 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) tag_str = parse_comment_frame (work); } else if (!strcmp (work->frame_id, "APIC")) { /* Attached picture */ + result = parse_picture_frame (work); } else if (!strcmp (work->frame_id, "RVA2")) { /* Relative volume */ result = parse_relative_volume_adjustment_two (work); @@ -359,6 +363,170 @@ parse_unique_file_identifier (ID3TagsWorking * work, const gchar ** tag_name) return ret; } +/* parse data and return length of the next string in the given encoding, + * including the NUL terminator */ +static gint +scan_encoded_string (guint8 encoding, gchar * data, gint data_size) +{ + gint i; + + switch (encoding) { + case ID3V2_ENCODING_ISO8859: + case ID3V2_ENCODING_UTF8: + for (i = 0; i < data_size; ++i) { + if (data[i] == '\0') + return i + 1; + } + break; + case ID3V2_ENCODING_UTF16: + case ID3V2_ENCODING_UTF16BE: + /* we don't care about BOMs here and treat them as part of the string */ + /* Find '\0\0' terminator */ + for (i = 0; i < data_size - 1; i += 2) { + if (data[i] == '\0' && data[i + 1] == '\0') + return i + 2; + } + break; + default: + break; + } + + return 0; +} + +static gboolean +parse_picture_frame (ID3TagsWorking * work) +{ + GstBuffer *image = NULL; + GstCaps *image_caps = NULL; + gboolean is_pic_uri = FALSE; + guint8 txt_encoding, pic_type; + gchar *mime_str = NULL; + gint len, datalen; + + GST_LOG ("APIC frame"); + + if (work->parse_size < 1 + 1 + 1 + 1 + 1) + goto not_enough_data; + + txt_encoding = work->parse_data[0]; + ++work->parse_data; + --work->parse_size; + + if (!parse_id_string (work, &mime_str, &len, &datalen)) { + return FALSE; + } + + is_pic_uri = (mime_str != NULL && strcmp (mime_str, "-->") == 0); + + if (mime_str && *mime_str && strchr (mime_str, '/') == NULL && !is_pic_uri) { + gchar *tmp; + + tmp = g_strdup_printf ("image/%s", mime_str); + g_free (mime_str); + mime_str = tmp; + } + + if (work->parse_size < (len + 1) + 1 + 1 + 1) + goto not_enough_data; + + work->parse_data += (len + 1); + work->parse_size -= (len + 1); + + pic_type = work->parse_data[0]; + ++work->parse_data; + --work->parse_size; + + GST_LOG ("APIC frame mime type : %s", GST_STR_NULL (mime_str)); + GST_LOG ("APIC frame picture type : 0x%02x", (guint) pic_type); + + if (work->parse_size < 1 + 1) + goto not_enough_data; + + len = scan_encoded_string (txt_encoding, (gchar *) work->parse_data, + work->parse_size); + + if (len < 1) + goto error; + + /* just skip the description string ... */ + GST_LOG ("Skipping description string (%d bytes in original coding)", len); + + if (work->parse_size < len + 1) + goto not_enough_data; + + work->parse_data += len; + work->parse_size -= len; + + GST_DEBUG ("image data is %u bytes", work->parse_size); + + if (work->parse_size <= 0) + goto not_enough_data; + + if (is_pic_uri) { + gchar *uri; + + uri = g_strndup ((gchar *) work->parse_data, work->parse_size); + GST_DEBUG ("image URI: %s", uri); + + image = gst_buffer_new (); + GST_BUFFER_MALLOCDATA (image) = (guint8 *) uri; /* take ownership */ + GST_BUFFER_DATA (image) = (guint8 *) uri; + GST_BUFFER_SIZE (image) = work->parse_size; + + image_caps = gst_caps_new_simple ("text/uri-list", NULL); + } else { + image = gst_buffer_new_and_alloc (work->parse_size); + memcpy (GST_BUFFER_DATA (image), work->parse_data, work->parse_size); + + /* if possible use GStreamer media type rather than declared type */ + image_caps = gst_type_find_helper_for_buffer (NULL, image, NULL); + if (image_caps) { + GST_DEBUG ("Found GStreamer media type: %" GST_PTR_FORMAT, image_caps); + } else if (mime_str && *mime_str) { + GST_DEBUG ("No GStreamer media type found, using declared type"); + image_caps = gst_caps_new_simple (mime_str, NULL); + } else { + GST_DEBUG ("Empty declared mime type, ignoring image frame"); + image = NULL; + image_caps = NULL; + goto error; + } + } + + if (image && image_caps) { + /* FIXME: use an enum here, declare in -base/gst-libs/gst/tag/? */ + /* gst_structure_set (gst_caps_get_structure (image_caps, 0), + "id3-picture-type", G_TYPE_INT, (gint) pic_type, NULL); */ + gst_buffer_set_caps (image, image_caps); + gst_caps_unref (image_caps); + if (pic_type == 0x01 || pic_type == 0x02) { + /* file icon of some sort */ + gst_tag_list_add (work->tags, GST_TAG_MERGE_APPEND, + GST_TAG_PREVIEW_IMAGE, image, NULL); + } else { + gst_tag_list_add (work->tags, GST_TAG_MERGE_APPEND, + GST_TAG_IMAGE, image, NULL); + } + gst_buffer_unref (image); + } + + g_free (mime_str); + return TRUE; + +not_enough_data: + { + GST_DEBUG ("not enough data, skipping APIC frame"); + /* fall through to error */ + } +error: + { + GST_DEBUG ("problem parsing APIC frame, skipping"); + g_free (mime_str); + return FALSE; + } +} + #define ID3V2_RVA2_CHANNEL_MASTER 1 static gboolean From f0a1b71dfe4484c149f91897208f6b88e1c40d1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Thu, 22 Jun 2006 12:17:13 +0000 Subject: [PATCH 095/140] tag: id3v2: Set image type from APIC frame as "image-type" field of GST_TAG_IMAGE buffer caps (#344605). Original commit message from CVS: * gst-libs/gst/tag/id3v2frames.c: (parse_picture_frame): Set image type from APIC frame as "image-type" field of GST_TAG_IMAGE buffer caps (#344605). --- gst-libs/gst/tag/id3v2frames.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index 949bbbf0d8..b2e3353b6c 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -495,9 +495,15 @@ parse_picture_frame (ID3TagsWorking * work) } if (image && image_caps) { - /* FIXME: use an enum here, declare in -base/gst-libs/gst/tag/? */ - /* gst_structure_set (gst_caps_get_structure (image_caps, 0), - "id3-picture-type", G_TYPE_INT, (gint) pic_type, NULL); */ + /* FIXME: remove #ifdef once we depend on -base >= 0.10.9 */ +#ifdef GST_TYPE_TAG_IMAGE_TYPE + if (pic_type > 0x14) + pic_type = GST_TAG_IMAGE_TYPE_UNDEFINED; + gst_structure_set (gst_caps_get_structure (image_caps, 0), + "image-type", GST_TYPE_TAG_IMAGE_TYPE, + (GstTagImageType) pic_type, NULL); +#endif + gst_buffer_set_caps (image, image_caps); gst_caps_unref (image_caps); if (pic_type == 0x01 || pic_type == 0x02) { From 488a625bb47460f5217dd11437fc4bf501c5a54f Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 21 Jul 2006 10:57:00 +0000 Subject: [PATCH 096/140] tag: id3v2: Don't use \n in debug lines Original commit message from CVS: * gst/avi/gstavidemux.c: (gst_avi_demux_parse_stream), (gst_avi_demux_process_next_entry): Fix some leaks. * gst-libs/gst/tag/id3v2.c: (id3demux_id3v2_frames_to_tag_list): Don't use \n in debug lines. --- gst-libs/gst/tag/id3v2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gst-libs/gst/tag/id3v2.c b/gst-libs/gst/tag/id3v2.c index 35571eca6c..22ea147d8c 100644 --- a/gst-libs/gst/tag/id3v2.c +++ b/gst-libs/gst/tag/id3v2.c @@ -423,7 +423,7 @@ id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size) #if 1 GST_LOG - ("Frame @ %d (0x%02x) id %s size %d, next=%d (0x%02x) obsolete=%d\n", + ("Frame @ %d (0x%02x) id %s size %d, next=%d (0x%02x) obsolete=%d", work->hdr.frame_data - start, work->hdr.frame_data - start, frame_id, frame_size, work->hdr.frame_data + frame_hdr_size + frame_size - start, work->hdr.frame_data + frame_hdr_size + frame_size - start, From ba3a9d3b9d103712c5db779e232eb51b6e72703b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Sun, 23 Jul 2006 10:56:27 +0000 Subject: [PATCH 097/140] tag: id3v2: Put ID3v2 frames we can't parse as binary blobs into private tags, so that they are not lost ... Original commit message from CVS: * gst-libs/gst/tag/id3v2.c: (id3demux_add_id3v2_frame_blob_to_taglist), (id3demux_id3v2_frames_to_tag_list): Put ID3v2 frames we can't parse as binary blobs into private tags, so that they are not lost when retagging, at least once id3v2mux has been taught to re-inject those frames again. See bug #334375. --- gst-libs/gst/tag/id3v2.c | 46 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/gst-libs/gst/tag/id3v2.c b/gst-libs/gst/tag/id3v2.c index 22ea147d8c..a34964c067 100644 --- a/gst-libs/gst/tag/id3v2.c +++ b/gst-libs/gst/tag/id3v2.c @@ -329,11 +329,47 @@ convert_fid_to_v240 (gchar * frame_id) return FALSE; } + +#define GST_ID3_DEMUX_TAG_ID3V2_FRAME "private-id3v2-frame" + +/* add unknown or unhandled ID3v2 frames to the taglist as binary blobs */ +static void +id3demux_add_id3v2_frame_blob_to_taglist (ID3TagsWorking * work, guint size) +{ + GstBuffer *blob; + GstCaps *caps; + guint8 *frame_data; + gchar *media_type; + guint frame_size; + + /* ensure private tag is registered */ + gst_tag_register (GST_ID3_DEMUX_TAG_ID3V2_FRAME, GST_TAG_FLAG_META, + GST_TYPE_BUFFER, "ID3v2 frame", "unparsed id3v2 tag frame", + gst_tag_merge_use_first); + + frame_data = work->hdr.frame_data - ID3V2_HDR_SIZE; + frame_size = size + ID3V2_HDR_SIZE; + + blob = gst_buffer_new_and_alloc (frame_size); + memcpy (GST_BUFFER_DATA (blob), frame_data, frame_size); + + media_type = g_strdup_printf ("application/x-gst-id3v2-%c%c%c%c-frame", + g_ascii_tolower (frame_data[0]), g_ascii_tolower (frame_data[1]), + g_ascii_tolower (frame_data[2]), g_ascii_tolower (frame_data[3])); + caps = gst_caps_new_simple (media_type, NULL); + gst_buffer_set_caps (blob, caps); + gst_caps_unref (caps); + g_free (media_type); + + gst_tag_list_add (work->tags, GST_TAG_MERGE_APPEND, + GST_ID3_DEMUX_TAG_ID3V2_FRAME, blob, NULL); + gst_buffer_unref (blob); +} + static ID3TagsResult id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size) { guint frame_hdr_size; - gboolean read_a_frame = FALSE; guint8 *start; /* Extended header if present */ @@ -438,16 +474,18 @@ id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size) work->frame_flags = frame_flags; if (id3demux_id3v2_parse_frame (work)) { - read_a_frame = TRUE; GST_LOG ("Extracted frame with id %s", frame_id); + } else { + GST_LOG ("Failed to extract frame with id %s", frame_id); + id3demux_add_id3v2_frame_blob_to_taglist (work, frame_size); } } work->hdr.frame_data += frame_size; work->hdr.frame_data_size -= frame_size; } - if (!read_a_frame) { - GST_DEBUG ("Could not extract any frames from tag. Broken tag"); + if (gst_structure_n_fields (GST_STRUCTURE (work->tags)) == 0) { + GST_DEBUG ("Could not extract any frames from tag. Broken or empty tag"); gst_tag_list_free (work->tags); work->tags = NULL; return ID3TAGS_BROKEN_TAG; From 3f1bc48d0b173631efc9a52c99a1e123c5b4af0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Sun, 23 Jul 2006 11:33:54 +0000 Subject: [PATCH 098/140] tag: id3v2: On second thought, it might be wiser and more efficient not to do tag registration from a streaming th... Original commit message from CVS: * gst-libs/gst/tag/gstid3demux.c: (plugin_init): * gst-libs/gst/tag/id3v2.c: (id3demux_add_id3v2_frame_blob_to_taglist): * gst-libs/gst/tag/id3v2.h: On second thought, it might be wiser and more efficient not to do tag registration from a streaming thread. --- gst-libs/gst/tag/id3v2.c | 7 ------- gst-libs/gst/tag/id3v2.h | 3 +++ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/gst-libs/gst/tag/id3v2.c b/gst-libs/gst/tag/id3v2.c index a34964c067..6af215d948 100644 --- a/gst-libs/gst/tag/id3v2.c +++ b/gst-libs/gst/tag/id3v2.c @@ -330,8 +330,6 @@ convert_fid_to_v240 (gchar * frame_id) } -#define GST_ID3_DEMUX_TAG_ID3V2_FRAME "private-id3v2-frame" - /* add unknown or unhandled ID3v2 frames to the taglist as binary blobs */ static void id3demux_add_id3v2_frame_blob_to_taglist (ID3TagsWorking * work, guint size) @@ -342,11 +340,6 @@ id3demux_add_id3v2_frame_blob_to_taglist (ID3TagsWorking * work, guint size) gchar *media_type; guint frame_size; - /* ensure private tag is registered */ - gst_tag_register (GST_ID3_DEMUX_TAG_ID3V2_FRAME, GST_TAG_FLAG_META, - GST_TYPE_BUFFER, "ID3v2 frame", "unparsed id3v2 tag frame", - gst_tag_merge_use_first); - frame_data = work->hdr.frame_data - ID3V2_HDR_SIZE; frame_size = size + ID3V2_HDR_SIZE; diff --git a/gst-libs/gst/tag/id3v2.h b/gst-libs/gst/tag/id3v2.h index cdf165d8f1..c87c871095 100644 --- a/gst-libs/gst/tag/id3v2.h +++ b/gst-libs/gst/tag/id3v2.h @@ -23,6 +23,9 @@ G_BEGIN_DECLS +/* private tag for storing unprocessed ID3v2 frames */ +#define GST_ID3_DEMUX_TAG_ID3V2_FRAME "private-id3v2-frame" + #define ID3V1_TAG_SIZE 128 #define ID3V2_MARK_SIZE 3 #define ID3V2_HDR_SIZE 10 From 2444a6459b18d2852ed192637c93224ceeebe2f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Tue, 25 Jul 2006 16:47:04 +0000 Subject: [PATCH 099/140] tag: id3v2: Extract frames for ID3v2 versions prior to ID3v2.3.0 properly as well, and add the version to... Original commit message from CVS: * gst-libs/gst/tag/id3v2.c: (id3demux_add_id3v2_frame_blob_to_taglist): Extract frames for ID3v2 versions prior to ID3v2.3.0 properly as well, and add the version to the blob's buffer caps, since that information will be needed for deserialisation later on (#348644). --- gst-libs/gst/tag/id3v2.c | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/gst-libs/gst/tag/id3v2.c b/gst-libs/gst/tag/id3v2.c index 6af215d948..7e354f2a5f 100644 --- a/gst-libs/gst/tag/id3v2.c +++ b/gst-libs/gst/tag/id3v2.c @@ -338,10 +338,23 @@ id3demux_add_id3v2_frame_blob_to_taglist (ID3TagsWorking * work, guint size) GstCaps *caps; guint8 *frame_data; gchar *media_type; - guint frame_size; + guint frame_size, header_size; - frame_data = work->hdr.frame_data - ID3V2_HDR_SIZE; - frame_size = size + ID3V2_HDR_SIZE; + switch (ID3V2_VER_MAJOR (work->hdr.version)) { + case 1: + case 2: + header_size = 3 + 3; + break; + case 3: + case 4: + header_size = 4 + 4 + 2; + break; + default: + g_return_if_reached (); + } + + frame_data = work->hdr.frame_data - header_size; + frame_size = size + header_size; blob = gst_buffer_new_and_alloc (frame_size); memcpy (GST_BUFFER_DATA (blob), frame_data, frame_size); @@ -349,11 +362,14 @@ id3demux_add_id3v2_frame_blob_to_taglist (ID3TagsWorking * work, guint size) media_type = g_strdup_printf ("application/x-gst-id3v2-%c%c%c%c-frame", g_ascii_tolower (frame_data[0]), g_ascii_tolower (frame_data[1]), g_ascii_tolower (frame_data[2]), g_ascii_tolower (frame_data[3])); - caps = gst_caps_new_simple (media_type, NULL); + caps = gst_caps_new_simple (media_type, "version", G_TYPE_INT, + (gint) ID3V2_VER_MAJOR (work->hdr.version), NULL); gst_buffer_set_caps (blob, caps); gst_caps_unref (caps); g_free (media_type); + /* gst_util_dump_mem (GST_BUFFER_DATA (blob), GST_BUFFER_SIZE (blob)); */ + gst_tag_list_add (work->tags, GST_TAG_MERGE_APPEND, GST_ID3_DEMUX_TAG_ID3V2_FRAME, blob, NULL); gst_buffer_unref (blob); From 075ceac5e707f1f3d5c6ad36040fca82b93af23e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Wed, 16 Aug 2006 13:01:32 +0000 Subject: [PATCH 100/140] configure.ac: Require CVS of GStreamer core and -base (for Original commit message from CVS: * configure.ac: Require CVS of GStreamer core and -base (for GST_TAG_EXTENDED_COMMENT and gst_tag_parse_extended_comment()). * ext/taglib/gstid3v2mux.cc: Write extended comment tags properly (#348762). * gst-libs/gst/tag/id3v2frames.c: (id3demux_id3v2_parse_frame), (parse_comment_frame): Extract COMM frames into extended comments, which makes it easier to properly retain the description bit of the tag and maintain this information when re-tagging (#348762). --- gst-libs/gst/tag/id3v2frames.c | 48 +++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index b2e3353b6c..4e98a5eb70 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -37,7 +37,7 @@ GST_DEBUG_CATEGORY_EXTERN (id3demux_debug); #define GST_CAT_DEFAULT (id3demux_debug) -static gchar *parse_comment_frame (ID3TagsWorking * work); +static gboolean parse_comment_frame (ID3TagsWorking * work); static GArray *parse_text_identification_frame (ID3TagsWorking * work); static gchar *parse_user_text_identification_frame (ID3TagsWorking * work, const gchar ** tag_name); @@ -156,7 +156,7 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) } } else if (!strcmp (work->frame_id, "COMM")) { /* Comment */ - tag_str = parse_comment_frame (work); + result = parse_comment_frame (work); } else if (!strcmp (work->frame_id, "APIC")) { /* Attached picture */ result = parse_picture_frame (work); @@ -195,17 +195,17 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) return result; } -static gchar * +static gboolean parse_comment_frame (ID3TagsWorking * work) { + guint dummy; guint8 encoding; gchar language[4]; GArray *fields = NULL; - gchar *out_str = NULL; gchar *description, *text; if (work->parse_size < 6) - return NULL; + return FALSE; encoding = work->parse_data[0]; language[0] = work->parse_data[1]; @@ -226,19 +226,37 @@ parse_comment_frame (ID3TagsWorking * work) if (!g_utf8_validate (text, -1, NULL)) { GST_WARNING ("Converted string is not valid utf-8"); goto fail; - } else { - if (strlen (description) > 0 && g_utf8_validate (description, -1, NULL)) { - out_str = g_strdup_printf ("Description: %s\nComment: %s", - description, text); - } else { - out_str = g_strdup (text); - } } -fail: - free_tag_strings (fields); + /* skip our own dummy descriptions (from id3v2mux) */ + if (strlen (description) > 0 && g_utf8_validate (description, -1, NULL) && + sscanf (description, "c%u", &dummy) != 1) { + gchar *s; - return out_str; + if (language[0] != '\0') { + s = g_strdup_printf ("%s[%s]=%s", description, language, text); + } else { + s = g_strdup_printf ("%s=%s", description, text); + } + gst_tag_list_add (work->tags, GST_TAG_MERGE_APPEND, + GST_TAG_EXTENDED_COMMENT, s, NULL); + g_free (s); + } else if (text != NULL && *text != '\0') { + gst_tag_list_add (work->tags, GST_TAG_MERGE_APPEND, + GST_TAG_COMMENT, text, NULL); + } else { + goto fail; + } + + free_tag_strings (fields); + return TRUE; + +fail: + { + GST_WARNING ("failed to parse COMM frame"); + free_tag_strings (fields); + return FALSE; + } } static GArray * From 8bdde43777f9d68f750ed7f1426f11eb706c2005 Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Tue, 22 Aug 2006 13:53:34 +0000 Subject: [PATCH 101/140] tag: id3v2: If strings in text fields are marked ISO8859-1, but contain valid UTF-8 already, then han... Original commit message from CVS: * gst-libs/gst/tag/id3v2frames.c: (parse_text_identification_frame), (parse_insert_string_field): If strings in text fields are marked ISO8859-1, but contain valid UTF-8 already, then handle them as UTF-8 and ignore the encoding. (#351794) --- gst-libs/gst/tag/id3v2frames.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index 4e98a5eb70..96fa88ec0d 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -273,12 +273,12 @@ parse_text_identification_frame (ID3TagsWorking * work) work->parse_size - 1, &fields); if (fields) { if (fields->len > 0) { - GST_LOG ("Read %d fields from Text ID frame of size %d. First is '%s'", - fields->len, work->parse_size - 1, + GST_LOG ("Read %d fields from Text ID frame of size %d with encoding %d" + ". First is '%s'", fields->len, work->parse_size - 1, encoding, g_array_index (fields, gchar *, 0)); } else { - GST_LOG ("Read %d fields from Text ID frame of size %d", fields->len, - work->parse_size - 1); + GST_LOG ("Read 0 fields from Text ID frame of size %d with encoding %d", + work->parse_size - 1, encoding); } } @@ -911,8 +911,11 @@ parse_insert_string_field (guint8 encoding, gchar * data, gint data_size, break; case ID3V2_ENCODING_ISO8859: - field = g_convert (data, data_size, "UTF-8", "ISO-8859-1", - NULL, NULL, NULL); + if (g_utf8_validate (data, data_size, NULL)) + field = g_strndup (data, data_size); + else + field = g_convert (data, data_size, "UTF-8", "ISO-8859-1", + NULL, NULL, NULL); break; default: field = g_strndup (data, data_size); From bac37d99170b0a8628fca57bf341c7796916e96b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Thu, 5 Oct 2006 16:37:33 +0000 Subject: [PATCH 102/140] tag: id3v2: Printf format fixes. Original commit message from CVS: * ext/cairo/gsttimeoverlay.c: (gst_cairo_time_overlay_update_font_height): * ext/gdk_pixbuf/pixbufscale.c: (gst_pixbufscale_transform_caps): * ext/jpeg/gstjpegdec.c: (gst_jpeg_dec_parse_image_data): * ext/jpeg/gstjpegenc.c: (gst_jpegenc_chain): * ext/jpeg/gstsmokedec.c: (gst_smokedec_chain): * ext/jpeg/gstsmokeenc.c: (gst_smokeenc_chain): * ext/libpng/gstpngdec.c: (user_endrow_callback): * gst/auparse/gstauparse.c: (gst_au_parse_parse_header): * gst/avi/gstavidemux.c: (gst_avi_demux_parse_superindex), (gst_avi_demux_parse_subindex), (gst_avi_demux_parse_stream), (gst_avi_demux_stream_data): * gst/cutter/gstcutter.c: (gst_cutter_chain): * gst/debug/efence.c: (gst_efence_buffer_alloc), (gst_fenced_buffer_copy): * gst-libs/gst/tag/id3v2frames.c: (id3demux_id3v2_parse_frame): * gst/matroska/matroska-demux.c: (gst_matroska_demux_add_stream): * gst/matroska/matroska-mux.c: (gst_matroska_mux_start): * gst/rtsp/gstrtspsrc.c: (gst_rtspsrc_send), (gst_rtspsrc_handle_message): * gst/wavparse/gstwavparse.c: (gst_wavparse_stream_headers): * sys/ximage/ximageutil.c: (ximageutil_xcontext_get): Printf format fixes. --- gst-libs/gst/tag/id3v2frames.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index 96fa88ec0d..8f4711a7f8 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -133,8 +133,8 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) } if (destSize != work->parse_size) { GST_WARNING - ("Decompressing ID3v2 frame %s did not produce expected size %d bytes (got %d)", - tag_name, work->parse_data, destSize); + ("Decompressing ID3v2 frame %s did not produce expected size %d bytes (got %lu)", + tag_name, work->parse_size, destSize); return FALSE; } #else From 07feb53dbdcc4b8da5523a7f7a37941272f038c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Wed, 1 Nov 2006 13:59:49 +0000 Subject: [PATCH 103/140] tag: id3v2: We require a -base more recent than 0.10.9, so it's safe to use Original commit message from CVS: * gst-libs/gst/tag/id3v2frames.c: (parse_picture_frame): We require a -base more recent than 0.10.9, so it's safe to use GST_TYPE_TAG_IMAGE_TYPE unconditionally now. * ext/dv/gstdvdec.c: (gst_dvdec_sink_event): * ext/jpeg/gstjpegdec.c: (gst_jpeg_dec_sink_event): Use _newsegment_full() now that we depend on a recent enough core. * gst/wavparse/gstwavparse.c: Remove cruft that we don't need any longer now that we depend on a recent enough -base. --- gst-libs/gst/tag/id3v2frames.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index 8f4711a7f8..d58d4628de 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -513,14 +513,11 @@ parse_picture_frame (ID3TagsWorking * work) } if (image && image_caps) { - /* FIXME: remove #ifdef once we depend on -base >= 0.10.9 */ -#ifdef GST_TYPE_TAG_IMAGE_TYPE if (pic_type > 0x14) pic_type = GST_TAG_IMAGE_TYPE_UNDEFINED; gst_structure_set (gst_caps_get_structure (image_caps, 0), "image-type", GST_TYPE_TAG_IMAGE_TYPE, (GstTagImageType) pic_type, NULL); -#endif gst_buffer_set_caps (image, image_caps); gst_caps_unref (image_caps); From 763eea481e95a5a5b9e6fcfc3f1d0b957f9924ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Stadler?= Date: Sun, 19 Nov 2006 13:41:53 +0000 Subject: [PATCH 104/140] tag: id3v2: Make sure that g_free always gets called on the same pointer that was returned by g_mallo... MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Original commit message from CVS: * gst-libs/gst/tag/id3v2frames.c: (id3demux_id3v2_parse_frame): Make sure that g_free always gets called on the same pointer that was returned by g_malloc. Fixes #376594. Do not leak memory if decompressed size is wrong. Remove unneeded check of return value of g_malloc. Patch by: René Stadler --- gst-libs/gst/tag/id3v2frames.c | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index d58d4628de..459a1ff3fc 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -73,6 +73,10 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) gchar *tag_str = NULL; GArray *tag_fields = NULL; +#ifdef HAVE_ZLIB + guint8 *uncompressed_data = NULL; +#endif + /* Check that the frame id is valid */ for (i = 0; i < 5 && work->frame_id[i] != '\0'; i++) { if (!g_ascii_isalnum (work->frame_id[i])) { @@ -121,22 +125,23 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) uLongf destSize = work->parse_size; Bytef *dest, *src; - work->parse_data = g_malloc (work->parse_size); - g_return_val_if_fail (work->parse_data != NULL, FALSE); + uncompressed_data = g_malloc (work->parse_size); - dest = (Bytef *) work->parse_data; + dest = (Bytef *) uncompressed_data; src = (Bytef *) frame_data; if (uncompress (dest, &destSize, src, frame_data_size) != Z_OK) { - g_free (work->parse_data); + g_free (uncompressed_data); return FALSE; } if (destSize != work->parse_size) { GST_WARNING ("Decompressing ID3v2 frame %s did not produce expected size %d bytes (got %lu)", tag_name, work->parse_size, destSize); + g_free (uncompressed_data); return FALSE; } + work->parse_data = uncompressed_data; #else GST_WARNING ("Compressed ID3v2 tag frame could not be decompressed" " because gstid3demux was compiled without zlib support"); @@ -167,9 +172,13 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) /* Unique file identifier */ tag_str = parse_unique_file_identifier (work, &tag_name); } - - if (work->frame_flags & ID3V2_FRAME_FORMAT_COMPRESSION) - g_free (work->parse_data); +#ifdef HAVE_ZLIB + if (work->frame_flags & ID3V2_FRAME_FORMAT_COMPRESSION) { + g_free (uncompressed_data); + uncompressed_data = NULL; + work->parse_data = frame_data; + } +#endif if (tag_str != NULL) { /* g_print ("Tag %s value %s\n", tag_name, tag_str); */ From c76c78670d9f23783fe68a90ff87f740fa906d87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Tue, 6 Mar 2007 18:16:49 +0000 Subject: [PATCH 105/140] tag: id3v2: Do not convert obsolete TDA/TDAT frames to TDRC frames, otherwise the four-digit number will be interp... Original commit message from CVS: * gst-libs/gst/tag/id3v2.c: (id3demux_id3v2_frames_to_tag_list): * gst-libs/gst/tag/id3v2.h: * gst-libs/gst/tag/id3v2frames.c: (id3demux_id3v2_parse_frame), (parse_obsolete_tdat_frame): Do not convert obsolete TDA/TDAT frames to TDRC frames, otherwise the four-digit number will be interpreted as a year, whereas it is month and day in DDMM format. Instead, parse TDAT frames and fix up the date in the GST_TAG_DATE tag later if we also extracted a year. Fixes #407349. --- gst-libs/gst/tag/id3v2.c | 16 ++++++++++++++-- gst-libs/gst/tag/id3v2.h | 4 ++++ gst-libs/gst/tag/id3v2frames.c | 30 ++++++++++++++++++++++++++---- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/gst-libs/gst/tag/id3v2.c b/gst-libs/gst/tag/id3v2.c index 7e354f2a5f..249acd0a4b 100644 --- a/gst-libs/gst/tag/id3v2.c +++ b/gst-libs/gst/tag/id3v2.c @@ -242,7 +242,6 @@ const struct ID3v2FrameIDConvert } frame_id_conversions[] = { /* 2.3.x frames */ { - "TDAT", "TDRC"}, { "TORY", "TDOR"}, { "TYER", "TDRC"}, /* 2.2.x frames */ @@ -266,7 +265,7 @@ const struct ID3v2FrameIDConvert "TCM", "TCOM"}, { "TCO", "TCON"}, { "TCR", "TCOP"}, { - "TDA", "TDRC"}, { + "TDA", "TDAT"}, { /* obsolete, but we need to parse it anyway */ "TDY", "TDLY"}, { "TEN", "TENC"}, { "TFT", "TFLT"}, { @@ -500,5 +499,18 @@ id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size) return ID3TAGS_BROKEN_TAG; } + /* Set day/month now if they were in a separate (obsolete) TDAT frame */ + if (work->pending_day != 0 && work->pending_month != 0) { + GDate *date = NULL; + + if (gst_tag_list_get_date (work->tags, GST_TAG_DATE, &date)) { + g_date_set_day (date, work->pending_day); + g_date_set_month (date, work->pending_month); + gst_tag_list_add (work->tags, GST_TAG_MERGE_REPLACE, GST_TAG_DATE, + date, NULL); + g_date_free (date); + } + } + return ID3TAGS_READ_TAG; } diff --git a/gst-libs/gst/tag/id3v2.h b/gst-libs/gst/tag/id3v2.h index c87c871095..85a17cd4c7 100644 --- a/gst-libs/gst/tag/id3v2.h +++ b/gst-libs/gst/tag/id3v2.h @@ -78,6 +78,10 @@ typedef struct { /* Previous genre string, for simple duplicate removal */ gchar *prev_genre; + + /* To collect day/month from obsolete TDAT frame if it exists */ + guint pending_month; + guint pending_day; } ID3TagsWorking; enum { diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index 459a1ff3fc..f02f1241f5 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -44,6 +44,7 @@ static gchar *parse_user_text_identification_frame (ID3TagsWorking * work, static gchar *parse_unique_file_identifier (ID3TagsWorking * work, const gchar ** tag_name); static gboolean parse_relative_volume_adjustment_two (ID3TagsWorking * work); +static void parse_obsolete_tdat_frame (ID3TagsWorking * work); static gboolean id3v2_tag_to_taglist (ID3TagsWorking * work, const gchar * tag_name, const gchar * tag_str); /* Parse a single string into an array of gchar* */ @@ -100,6 +101,7 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) if (tag_name == NULL && strncmp (work->frame_id, "RVA2", 4) != 0 && strncmp (work->frame_id, "TXXX", 4) != 0 && + strncmp (work->frame_id, "TDAT", 4) != 0 && strncmp (work->frame_id, "UFID", 4) != 0) { return FALSE; } @@ -152,12 +154,15 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) } if (work->frame_id[0] == 'T') { - if (strcmp (work->frame_id, "TXXX") != 0) { - /* Text identification frame */ - tag_fields = parse_text_identification_frame (work); - } else { + if (strcmp (work->frame_id, "TDAT") == 0) { + parse_obsolete_tdat_frame (work); + result = TRUE; + } else if (strcmp (work->frame_id, "TXXX") == 0) { /* Handle user text frame */ tag_str = parse_user_text_identification_frame (work, &tag_name); + } else { + /* Text identification frame */ + tag_fields = parse_text_identification_frame (work); } } else if (!strcmp (work->frame_id, "COMM")) { /* Comment */ @@ -635,6 +640,23 @@ parse_relative_volume_adjustment_two (ID3TagsWorking * work) return (gain_tag_name != NULL || peak_tag_name != NULL); } +static void +parse_obsolete_tdat_frame (ID3TagsWorking * work) +{ + if (work->parse_size >= 5 && + work->parse_data[0] == ID3V2_ENCODING_ISO8859 && + g_ascii_isdigit (work->parse_data[1]) && + g_ascii_isdigit (work->parse_data[2]) && + g_ascii_isdigit (work->parse_data[3]) && + g_ascii_isdigit (work->parse_data[4])) { + work->pending_day = (10 * g_ascii_digit_value (work->parse_data[1])) + + g_ascii_digit_value (work->parse_data[2]); + work->pending_month = (10 * g_ascii_digit_value (work->parse_data[3])) + + g_ascii_digit_value (work->parse_data[4]); + GST_LOG ("date (dd/mm) %02u/%02u", work->pending_day, work->pending_month); + } +} + static gboolean id3v2_tag_to_taglist (ID3TagsWorking * work, const gchar * tag_name, const gchar * tag_str) From 0277ee0b79036bde0ef83f69e54f2dbdc05b5a99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Mon, 12 Mar 2007 13:28:29 +0000 Subject: [PATCH 106/140] tag: id3v2: Fix parsing of ID3 v2.2.0 PIC frames. Only in version >= 2.3.0 is the image format a vari... Original commit message from CVS: * gst-libs/gst/tag/id3v2frames.c: (parse_picture_frame): Fix parsing of ID3 v2.2.0 PIC frames. Only in version >= 2.3.0 is the image format a variable-length NUL-terminated string; in versions before that the image format is a fixed-length string of 3 characters (see #348644 for a sample tag). Also make supplied mime type lower-case and fix up 'jpg' to 'jpeg'. --- gst-libs/gst/tag/id3v2frames.c | 39 ++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index f02f1241f5..153d49430e 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -433,10 +433,10 @@ parse_picture_frame (ID3TagsWorking * work) GstCaps *image_caps = NULL; gboolean is_pic_uri = FALSE; guint8 txt_encoding, pic_type; - gchar *mime_str = NULL; + gchar *c, *mime_str = NULL; gint len, datalen; - GST_LOG ("APIC frame"); + GST_LOG ("APIC frame (ID3v2.%u)", ID3V2_VER_MAJOR (work->hdr.version)); if (work->parse_size < 1 + 1 + 1 + 1 + 1) goto not_enough_data; @@ -445,8 +445,32 @@ parse_picture_frame (ID3TagsWorking * work) ++work->parse_data; --work->parse_size; - if (!parse_id_string (work, &mime_str, &len, &datalen)) { - return FALSE; + /* Read image format; in early ID3v2 versions this is a fixed-length + * 3-character string without terminator; in later versions (>= 2.3.0) + * this is a NUL-terminated string of variable length */ + if (ID3V2_VER_MAJOR (work->hdr.version) < 3) { + if (work->parse_size < 3) + goto not_enough_data; + + mime_str = g_strndup ((gchar *) work->parse_data, 3); + len = 3; + } else { + if (!parse_id_string (work, &mime_str, &len, &datalen)) + return FALSE; + ++len; /* for string terminator */ + } + + /* Fix up 'jpg' => 'jpeg' in mime/media type */ + if (mime_str != NULL && + (g_ascii_strcasecmp (mime_str, "jpg") == 0 || + g_ascii_strcasecmp (mime_str, "image/jpg") == 0)) { + g_free (mime_str); + mime_str = g_strdup ("jpeg"); + } + + /* Make lower-case */ + for (c = mime_str; c != NULL && *c != '\0'; ++c) { + *c = g_ascii_tolower (*c); } is_pic_uri = (mime_str != NULL && strcmp (mime_str, "-->") == 0); @@ -459,12 +483,13 @@ parse_picture_frame (ID3TagsWorking * work) mime_str = tmp; } - if (work->parse_size < (len + 1) + 1 + 1 + 1) + if (work->parse_size < len + 1 + 1 + 1) goto not_enough_data; - work->parse_data += (len + 1); - work->parse_size -= (len + 1); + work->parse_data += len; + work->parse_size -= len; + /* Read image type */ pic_type = work->parse_data[0]; ++work->parse_data; --work->parse_size; From 2ff93c3826e1aaa85ce6ebec6681fc6a65e090ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Sat, 6 Oct 2007 16:13:14 +0000 Subject: [PATCH 107/140] tag: id3v2: Port ID3 tag demuxer over to the new GstTagDemux in -base (now would be a good time to test re-importi... Original commit message from CVS: * gst-libs/gst/tag/gstid3demux.c: * gst-libs/gst/tag/gstid3demux.h: * gst-libs/gst/tag/id3v2.c: * gst-libs/gst/tag/id3v2.h: * gst-libs/gst/tag/id3v2frames.c: Port ID3 tag demuxer over to the new GstTagDemux in -base (now would be a good time to test re-importing your music collection). --- gst-libs/gst/tag/id3v2.c | 112 ++++++++++----------------------- gst-libs/gst/tag/id3v2.h | 9 +-- gst-libs/gst/tag/id3v2frames.c | 2 - 3 files changed, 39 insertions(+), 84 deletions(-) diff --git a/gst-libs/gst/tag/id3v2.c b/gst-libs/gst/tag/id3v2.c index 249acd0a4b..b017f2a64c 100644 --- a/gst-libs/gst/tag/id3v2.c +++ b/gst-libs/gst/tag/id3v2.c @@ -35,7 +35,7 @@ static ID3TagsResult id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size); guint -read_synch_uint (guint8 * data, guint size) +read_synch_uint (const guint8 * data, guint size) { gint i; guint result = 0; @@ -62,50 +62,42 @@ read_synch_uint (guint8 * data, guint size) return result; } -ID3TagsResult -id3demux_read_id3v1_tag (GstBuffer * buffer, guint * id3v1_size, - GstTagList ** tags) +guint +id3demux_calc_id3v2_tag_size (GstBuffer * buf) { - GstTagList *new_tags; + guint8 *data, flags; + guint size; - guint8 *data; + g_assert (buf != NULL); + g_assert (GST_BUFFER_SIZE (buf) >= ID3V2_HDR_SIZE); - g_return_val_if_fail (buffer != NULL, ID3TAGS_V1_BAD_SIZE); + data = GST_BUFFER_DATA (buf); - data = GST_BUFFER_DATA (buffer); - - if (GST_BUFFER_SIZE (buffer) != ID3V1_TAG_SIZE) - return ID3TAGS_V1_BAD_SIZE; - - /* Check that buffer starts with 'TAG' */ - if (data[0] != 'T' || data[1] != 'A' || data[2] != 'G') { - if (id3v1_size) - *id3v1_size = 0; - GST_DEBUG ("No ID3v1 tag in data"); - return ID3TAGS_READ_TAG; + /* Check for 'ID3' string at start of buffer */ + if (data[0] != 'I' || data[1] != 'D' || data[2] != '3') { + GST_DEBUG ("No ID3v2 tag in data"); + return 0; } - g_return_val_if_fail (tags != NULL, ID3TAGS_READ_TAG); + /* Read the flags */ + flags = data[5]; - new_tags = gst_tag_list_new_from_id3v1 (GST_BUFFER_DATA (buffer)); - if (new_tags == NULL) - return ID3TAGS_BROKEN_TAG; + /* Read the size from the header */ + size = read_synch_uint (data + 6, 4); + if (size == 0) + return ID3V2_HDR_SIZE; - if (*tags) { - GstTagList *merged; + size += ID3V2_HDR_SIZE; - merged = gst_tag_list_merge (*tags, new_tags, GST_TAG_MERGE_REPLACE); - gst_tag_list_free (*tags); - gst_tag_list_free (new_tags); - *tags = merged; - } else - *tags = new_tags; + /* Expand the read size to include a footer if there is one */ + if ((flags & ID3V2_HDR_FLAG_FOOTER)) + size += 10; - if (id3v1_size) - *id3v1_size = ID3V1_TAG_SIZE; - return ID3TAGS_READ_TAG; + GST_DEBUG ("ID3v2 tag, size: %u bytes", size); + return size; } +/* caller must pass buffer with full ID3 tag */ ID3TagsResult id3demux_read_id3v2_tag (GstBuffer * buffer, guint * id3v2_size, GstTagList ** tags) @@ -117,49 +109,23 @@ id3demux_read_id3v2_tag (GstBuffer * buffer, guint * id3v2_size, ID3TagsResult result; guint16 version; - g_return_val_if_fail (buffer != NULL, ID3TAGS_MORE_DATA); + read_size = id3demux_calc_id3v2_tag_size (buffer); - if (GST_BUFFER_SIZE (buffer) < ID3V2_MARK_SIZE) - return ID3TAGS_MORE_DATA; /* Need more data to decide with */ + if (id3v2_size) + *id3v2_size = read_size; + + /* Ignore tag if it has no frames attached, but skip the header then */ + if (read_size <= ID3V2_HDR_SIZE) + return ID3TAGS_BROKEN_TAG; data = GST_BUFFER_DATA (buffer); - /* Check for 'ID3' string at start of buffer */ - if (data[0] != 'I' || data[1] != 'D' || data[2] != '3') { - if (id3v2_size) - *id3v2_size = 0; - GST_DEBUG ("No ID3v2 tag in data"); - return ID3TAGS_READ_TAG; - } - - /* OK, get enough data to read the entire header */ - if (GST_BUFFER_SIZE (buffer) < ID3V2_HDR_SIZE) - return ID3TAGS_MORE_DATA; /* Need more data to decide with */ - /* Read the version */ version = GST_READ_UINT16_BE (data + 3); /* Read the flags */ flags = data[5]; - /* Read the size from the header */ - read_size = read_synch_uint (data + 6, 4); - if (read_size == 0) { - /* Tag has no frames attached. Ignore it, but skip the header */ - if (id3v2_size) - *id3v2_size = ID3V2_HDR_SIZE; - return ID3TAGS_BROKEN_TAG; - } - read_size += ID3V2_HDR_SIZE; - - /* Expand the read size to include a footer if there is one */ - if (flags & ID3V2_HDR_FLAG_FOOTER) { - read_size += 10; - } - - if (id3v2_size) - *id3v2_size = read_size; - /* Validate the version. At the moment, we only support up to 2.4.0 */ if (ID3V2_VER_MAJOR (version) > 4 || ID3V2_VER_MINOR (version) > 0) { GST_WARNING ("ID3v2 tag is from revision 2.%d.%d, " @@ -168,6 +134,7 @@ id3demux_read_id3v2_tag (GstBuffer * buffer, guint * id3v2_size, return ID3TAGS_READ_TAG; } + /* This shouldn't really happen! Caller should have checked first */ if (GST_BUFFER_SIZE (buffer) < read_size) { GST_DEBUG ("Found ID3v2 tag with revision 2.%d.%d - need %u more bytes to read", @@ -194,18 +161,7 @@ id3demux_read_id3v2_tag (GstBuffer * buffer, guint * id3v2_size, result = id3demux_id3v2_frames_to_tag_list (&work, read_size); - /* Actually read the tags */ - if (work.tags != NULL) { - if (*tags) { - GstTagList *merged; - - merged = gst_tag_list_merge (*tags, work.tags, GST_TAG_MERGE_REPLACE); - gst_tag_list_free (*tags); - gst_tag_list_free (work.tags); - *tags = merged; - } else - *tags = work.tags; - } + *tags = work.tags; if (work.prev_genre) g_free (work.prev_genre); diff --git a/gst-libs/gst/tag/id3v2.h b/gst-libs/gst/tag/id3v2.h index 85a17cd4c7..705a6a0dda 100644 --- a/gst-libs/gst/tag/id3v2.h +++ b/gst-libs/gst/tag/id3v2.h @@ -31,18 +31,17 @@ G_BEGIN_DECLS #define ID3V2_HDR_SIZE 10 typedef enum { - ID3TAGS_V1_BAD_SIZE, ID3TAGS_MORE_DATA, ID3TAGS_READ_TAG, ID3TAGS_BROKEN_TAG } ID3TagsResult; /* From id3tags.c */ -ID3TagsResult id3demux_read_id3v1_tag (GstBuffer *buffer, guint *id3v1_size, - GstTagList **tags); +guint id3demux_calc_id3v2_tag_size (GstBuffer * buf); ID3TagsResult id3demux_read_id3v2_tag (GstBuffer *buffer, guint *id3v2_size, GstTagList **tags); -G_END_DECLS + +guint read_synch_uint (const guint8 * data, guint size); /* Things shared by id3tags.c and id3v2frames.c */ #define ID3V2_VERSION 0x0400 @@ -119,4 +118,6 @@ enum { /* From id3v2frames.c */ gboolean id3demux_id3v2_parse_frame (ID3TagsWorking *work); +G_END_DECLS + #endif diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index 153d49430e..76d4cbc3ea 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -61,8 +61,6 @@ static gboolean parse_picture_frame (ID3TagsWorking * work); #define ID3V2_ENCODING_UTF16BE 0x02 #define ID3V2_ENCODING_UTF8 0x03 -extern guint read_synch_uint (guint8 * data, guint size); - gboolean id3demux_id3v2_parse_frame (ID3TagsWorking * work) { From 66c4980024c53ab3281e026150427b4cc3fb856c Mon Sep 17 00:00:00 2001 From: Jason Kivlighn Date: Thu, 11 Oct 2007 17:55:29 +0000 Subject: [PATCH 108/140] tag: id3v2: Extract license/copyright URIs from ID3v2 WCOP frames (Fixes #447000). Original commit message from CVS: Based on patch by: Jason Kivlighn * gst-libs/gst/tag/id3v2frames.c: Extract license/copyright URIs from ID3v2 WCOP frames (Fixes #447000). * tests/check/elements/id3demux.c: * tests/files/Makefile.am: * tests/files/id3-447000-wcop.tag: Add simple unit test. --- gst-libs/gst/tag/id3v2frames.c | 57 ++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index 76d4cbc3ea..efbef079ee 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -38,6 +38,8 @@ GST_DEBUG_CATEGORY_EXTERN (id3demux_debug); #define GST_CAT_DEFAULT (id3demux_debug) static gboolean parse_comment_frame (ID3TagsWorking * work); +static gchar *parse_url_link_frame (ID3TagsWorking * work, + const gchar ** tag_name); static GArray *parse_text_identification_frame (ID3TagsWorking * work); static gchar *parse_user_text_identification_frame (ID3TagsWorking * work, const gchar ** tag_name); @@ -162,6 +164,9 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) /* Text identification frame */ tag_fields = parse_text_identification_frame (work); } + } else if (work->frame_id[0] == 'W' && strcmp (work->frame_id, "WXXX") != 0) { + /* URL link frame: ISO-8859-1 encoded, one frame per tag */ + tag_str = parse_url_link_frame (work, &tag_name); } else if (!strcmp (work->frame_id, "COMM")) { /* Comment */ result = parse_comment_frame (work); @@ -297,6 +302,58 @@ parse_text_identification_frame (ID3TagsWorking * work) return fields; } +static gboolean +link_is_known_license (const gchar * url) +{ + return g_str_has_prefix (url, "http://creativecommons.org/licenses/"); +} + +static gchar * +parse_url_link_frame (ID3TagsWorking * work, const gchar ** tag_name) +{ + gsize len; + gchar *nul, *data, *link; + + *tag_name = NULL; + + if (work->parse_size == 0) + return NULL; + + data = (gchar *) work->parse_data; + /* if there's more data then the string is long, we only want to parse the + * data up to the terminating zero to g_convert and ignore the rest, as + * per spec */ + nul = memchr (data, '\0', work->parse_size); + if (nul != NULL) { + len = (gsize) (nul - data); + } else { + len = work->parse_size; + } + + link = g_convert (data, len, "UTF-8", "ISO-8859-1", NULL, NULL, NULL); + + if (link == NULL || !gst_uri_is_valid (link)) { + GST_DEBUG ("Invalid URI in %s frame: %s", work->frame_id, + GST_STR_NULL (link)); + g_free (link); + return NULL; + } + + /* we don't know if it's a link to a page that explains the copyright + * situation, or a link that points to/represents a license, the ID3 spec + * does not separate those two things; for now only put known license URIs + * into GST_TAG_LICENSE_URI and everything else into GST_TAG_COPYRIGHT_URI */ + if (strcmp (work->frame_id, "WCOP") == 0) { + if (link_is_known_license (link)) + *tag_name = GST_TAG_LICENSE_URI; + else + *tag_name = GST_TAG_COPYRIGHT_URI; + } + + return link; +} + + static gchar * parse_user_text_identification_frame (ID3TagsWorking * work, const gchar ** tag_name) From 1dd535010979c4a42593afe9d506e59f3874ba97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Wed, 14 Nov 2007 21:39:47 +0000 Subject: [PATCH 109/140] tag: id3v2: We don't want the same string multiple times in a tag list for the same tag ever, for any tag, not jus... Original commit message from CVS: * gst-libs/gst/tag/id3v2.c: * gst-libs/gst/tag/id3v2.h: * gst-libs/gst/tag/id3v2frames.c: (id3v2_tag_to_taglist): We don't want the same string multiple times in a tag list for the same tag ever, for any tag, not just for GST_TAG_GENRE, so make sure this doesn't happen and remove special-case code for GST_TAG_GENRE. --- gst-libs/gst/tag/id3v2.c | 3 --- gst-libs/gst/tag/id3v2.h | 3 --- gst-libs/gst/tag/id3v2frames.c | 23 ++++++++++++++++------- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/gst-libs/gst/tag/id3v2.c b/gst-libs/gst/tag/id3v2.c index b017f2a64c..0a7a3df9a0 100644 --- a/gst-libs/gst/tag/id3v2.c +++ b/gst-libs/gst/tag/id3v2.c @@ -163,9 +163,6 @@ id3demux_read_id3v2_tag (GstBuffer * buffer, guint * id3v2_size, *tags = work.tags; - if (work.prev_genre) - g_free (work.prev_genre); - return result; } diff --git a/gst-libs/gst/tag/id3v2.h b/gst-libs/gst/tag/id3v2.h index 705a6a0dda..b5bc950dd4 100644 --- a/gst-libs/gst/tag/id3v2.h +++ b/gst-libs/gst/tag/id3v2.h @@ -75,9 +75,6 @@ typedef struct { guint8 *parse_data; guint parse_size; - /* Previous genre string, for simple duplicate removal */ - gchar *prev_genre; - /* To collect day/month from obsolete TDAT frame if it exists */ guint pending_month; guint pending_day; diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index efbef079ee..c8acb886cd 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -794,14 +794,23 @@ id3v2_tag_to_taglist (ID3TagsWorking * work, const gchar * tag_name, break; } case G_TYPE_STRING:{ - if (!strcmp (tag_name, GST_TAG_GENRE)) { - if (work->prev_genre && !strcmp (tag_str, work->prev_genre)) - break; /* Same as the last genre */ - g_free (work->prev_genre); - work->prev_genre = g_strdup (tag_str); + const GValue *val; + guint i, num; + + /* make sure we add each unique string only once per tag, we don't want + * to have the same genre in the genre list multiple times, for example, + * or the same DiscID in there twice just because it's contained in the + * tag multiple times under different TXXX user tags */ + num = gst_tag_list_get_tag_size (tag_list, tag_name); + for (i = 0; i < num; ++i) { + val = gst_tag_list_get_value_index (tag_list, tag_name, i); + if (val != NULL && strcmp (g_value_get_string (val), tag_str) == 0) + break; + } + if (i == num) { + gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND, + tag_name, tag_str, NULL); } - gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND, - tag_name, tag_str, NULL); break; } From d4b43861a2288fbebf30d425d53f0844c1bf1344 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Fri, 14 Dec 2007 10:17:10 +0000 Subject: [PATCH 110/140] tag: id3v2: Parse WOAF frames and put the result into GST_TAG_CONTACT, which is where it would end up... Original commit message from CVS: * tag: id3v2: (parse_url_link_frame): Parse WOAF frames and put the result into GST_TAG_CONTACT, which is where it would end up if the same information was put in a vorbis comment (don't think it's worth adding a new URI tag for this). Fixes #488112. --- gst-libs/gst/tag/id3v2frames.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index c8acb886cd..b67f356d4d 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -348,6 +348,11 @@ parse_url_link_frame (ID3TagsWorking * work, const gchar ** tag_name) *tag_name = GST_TAG_LICENSE_URI; else *tag_name = GST_TAG_COPYRIGHT_URI; + } else if (strcmp (work->frame_id, "WOAF") == 0) { + /* can't be bothered to create a CONTACT_URI tag for this, so let's just + * put into into GST_TAG_CONTACT, which is where it ends up when reading + * the info from vorbis comments as well */ + *tag_name = GST_TAG_CONTACT; } return link; From 6d7fcfbcdea084a016e3945b6f8f6bef8fe478a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tommi=20My=C3=B6h=C3=A4nen?= Date: Wed, 9 Jan 2008 15:20:19 +0000 Subject: [PATCH 111/140] tag: id3v2: Make sure the ISO 639-X language code in ID3v2 COMM frames so we don't end up with non-UT... MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Original commit message from CVS: Based on patch by: Tommi Myöhänen * gst-libs/gst/tag/id3v2frames.c: (parse_comment_frame): Make sure the ISO 639-X language code in ID3v2 COMM frames is actually valid UTF-8 (or rather: ASCII), so we don't end up with non-UTF8 strings in tags if there's garbage in the language field. Also make sure the language code is always lower case. Fixes: #508291. --- gst-libs/gst/tag/id3v2frames.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index b67f356d4d..ec06dd2484 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -225,10 +225,10 @@ parse_comment_frame (ID3TagsWorking * work) return FALSE; encoding = work->parse_data[0]; - language[0] = work->parse_data[1]; - language[1] = work->parse_data[2]; - language[2] = work->parse_data[3]; - language[3] = 0; + language[0] = g_ascii_tolower (work->parse_data[1]); + language[1] = g_ascii_tolower (work->parse_data[2]); + language[2] = g_ascii_tolower (work->parse_data[3]); + language[3] = '\0'; parse_split_strings (encoding, (gchar *) work->parse_data + 4, work->parse_size - 4, &fields); @@ -250,7 +250,11 @@ parse_comment_frame (ID3TagsWorking * work) sscanf (description, "c%u", &dummy) != 1) { gchar *s; - if (language[0] != '\0') { + /* must be either an ISO-639-1 or ISO-639-2 language code */ + if (language[0] != '\0' && + g_ascii_isalpha (language[0]) && + g_ascii_isalpha (language[1]) && + (g_ascii_isalpha (language[2]) || language[2] == '\0')) { s = g_strdup_printf ("%s[%s]=%s", description, language, text); } else { s = g_strdup_printf ("%s=%s", description, text); From 904051e9d454577c28da93a2032233dbe540a09c Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Fri, 11 Jan 2008 21:08:59 +0000 Subject: [PATCH 112/140] tag: id3v2: Generate the image-type values correctly. Leave them out of the caps when outputting a "preview image" tag, since it ... Original commit message from CVS: * ext/flac/gstflacdec.c: (gst_flac_extract_picture_buffer): * gst-libs/gst/tag/id3v2frames.c: (parse_picture_frame): Generate the image-type values correctly. Leave them out of the caps when outputting a "preview image" tag, since it only makes sense to have one of those - the type is irrelevant. * sys/sunaudio/gstsunaudiomixerctrl.c: (gst_sunaudiomixer_ctrl_open): If we can, mark the mixer multiple open when we use it, in case (for some reason) the process wants to open it again elsewhere. --- gst-libs/gst/tag/id3v2frames.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index ec06dd2484..0d0944ed6d 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -616,22 +616,29 @@ parse_picture_frame (ID3TagsWorking * work) } if (image && image_caps) { - if (pic_type > 0x14) - pic_type = GST_TAG_IMAGE_TYPE_UNDEFINED; - gst_structure_set (gst_caps_get_structure (image_caps, 0), - "image-type", GST_TYPE_TAG_IMAGE_TYPE, - (GstTagImageType) pic_type, NULL); - gst_buffer_set_caps (image, image_caps); - gst_caps_unref (image_caps); if (pic_type == 0x01 || pic_type == 0x02) { - /* file icon of some sort */ + /* file icon for preview. Don't add image-type to caps, since there + * is only supposed to be one of these. */ + gst_buffer_set_caps (image, image_caps); gst_tag_list_add (work->tags, GST_TAG_MERGE_APPEND, GST_TAG_PREVIEW_IMAGE, image, NULL); } else { + GstTagImageType gst_tag_pic_type = GST_TAG_IMAGE_TYPE_UNDEFINED; + + /* Remap the ID3v2 APIC type our ImageType enum */ + if (pic_type >= 0x3 && pic_type <= 0x14) + gst_tag_pic_type = (GstTagImageType) (pic_type - 2); + + gst_structure_set (gst_caps_get_structure (image_caps, 0), + "image-type", GST_TYPE_TAG_IMAGE_TYPE, gst_tag_pic_type, NULL); + + gst_buffer_set_caps (image, image_caps); gst_tag_list_add (work->tags, GST_TAG_MERGE_APPEND, GST_TAG_IMAGE, image, NULL); } + + gst_caps_unref (image_caps); gst_buffer_unref (image); } From a91620dbdf3cf0fb26b5e1090b0b93e95f5cdef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Wed, 4 Jun 2008 10:42:46 +0000 Subject: [PATCH 113/140] tag: id3v2: Use new utility functions in libgsttag to process coverart (#512333). Original commit message from CVS: * ext/flac/gstflacdec.c: (gst_flac_extract_picture_buffer): * gst-libs/gst/tag/id3v2frames.c: (parse_picture_frame): Use new utility functions in libgsttag to process coverart (#512333). --- gst-libs/gst/tag/id3v2frames.c | 89 ++-------------------------------- 1 file changed, 5 insertions(+), 84 deletions(-) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index 0d0944ed6d..377d84cf5d 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -1,5 +1,5 @@ /* -*- Mode: C; tab-width: 2; indent-tabs-mode: t; c-basic-offset: 2 -*- */ -/* Copyright 2006 Tim-Philipp Müller +/* Copyright 2006-2008 Tim-Philipp Müller * Copyright 2005 Jan Schmidt * Copyright 2002,2003 Scott Wheeler (portions from taglib) * @@ -493,11 +493,8 @@ scan_encoded_string (guint8 encoding, gchar * data, gint data_size) static gboolean parse_picture_frame (ID3TagsWorking * work) { - GstBuffer *image = NULL; - GstCaps *image_caps = NULL; - gboolean is_pic_uri = FALSE; guint8 txt_encoding, pic_type; - gchar *c, *mime_str = NULL; + gchar *mime_str = NULL; gint len, datalen; GST_LOG ("APIC frame (ID3v2.%u)", ID3V2_VER_MAJOR (work->hdr.version)); @@ -524,29 +521,6 @@ parse_picture_frame (ID3TagsWorking * work) ++len; /* for string terminator */ } - /* Fix up 'jpg' => 'jpeg' in mime/media type */ - if (mime_str != NULL && - (g_ascii_strcasecmp (mime_str, "jpg") == 0 || - g_ascii_strcasecmp (mime_str, "image/jpg") == 0)) { - g_free (mime_str); - mime_str = g_strdup ("jpeg"); - } - - /* Make lower-case */ - for (c = mime_str; c != NULL && *c != '\0'; ++c) { - *c = g_ascii_tolower (*c); - } - - is_pic_uri = (mime_str != NULL && strcmp (mime_str, "-->") == 0); - - if (mime_str && *mime_str && strchr (mime_str, '/') == NULL && !is_pic_uri) { - gchar *tmp; - - tmp = g_strdup_printf ("image/%s", mime_str); - g_free (mime_str); - mime_str = tmp; - } - if (work->parse_size < len + 1 + 1 + 1) goto not_enough_data; @@ -584,62 +558,9 @@ parse_picture_frame (ID3TagsWorking * work) if (work->parse_size <= 0) goto not_enough_data; - if (is_pic_uri) { - gchar *uri; - - uri = g_strndup ((gchar *) work->parse_data, work->parse_size); - GST_DEBUG ("image URI: %s", uri); - - image = gst_buffer_new (); - GST_BUFFER_MALLOCDATA (image) = (guint8 *) uri; /* take ownership */ - GST_BUFFER_DATA (image) = (guint8 *) uri; - GST_BUFFER_SIZE (image) = work->parse_size; - - image_caps = gst_caps_new_simple ("text/uri-list", NULL); - } else { - image = gst_buffer_new_and_alloc (work->parse_size); - memcpy (GST_BUFFER_DATA (image), work->parse_data, work->parse_size); - - /* if possible use GStreamer media type rather than declared type */ - image_caps = gst_type_find_helper_for_buffer (NULL, image, NULL); - if (image_caps) { - GST_DEBUG ("Found GStreamer media type: %" GST_PTR_FORMAT, image_caps); - } else if (mime_str && *mime_str) { - GST_DEBUG ("No GStreamer media type found, using declared type"); - image_caps = gst_caps_new_simple (mime_str, NULL); - } else { - GST_DEBUG ("Empty declared mime type, ignoring image frame"); - image = NULL; - image_caps = NULL; - goto error; - } - } - - if (image && image_caps) { - - if (pic_type == 0x01 || pic_type == 0x02) { - /* file icon for preview. Don't add image-type to caps, since there - * is only supposed to be one of these. */ - gst_buffer_set_caps (image, image_caps); - gst_tag_list_add (work->tags, GST_TAG_MERGE_APPEND, - GST_TAG_PREVIEW_IMAGE, image, NULL); - } else { - GstTagImageType gst_tag_pic_type = GST_TAG_IMAGE_TYPE_UNDEFINED; - - /* Remap the ID3v2 APIC type our ImageType enum */ - if (pic_type >= 0x3 && pic_type <= 0x14) - gst_tag_pic_type = (GstTagImageType) (pic_type - 2); - - gst_structure_set (gst_caps_get_structure (image_caps, 0), - "image-type", GST_TYPE_TAG_IMAGE_TYPE, gst_tag_pic_type, NULL); - - gst_buffer_set_caps (image, image_caps); - gst_tag_list_add (work->tags, GST_TAG_MERGE_APPEND, - GST_TAG_IMAGE, image, NULL); - } - - gst_caps_unref (image_caps); - gst_buffer_unref (image); + if (!gst_tag_list_add_id3_image (work->tags, (guint8 *) work->parse_data, + work->parse_size, pic_type)) { + goto error; } g_free (mime_str); From bd738166a702384ce61d22652e5da9abc51b4972 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Fri, 24 Apr 2009 01:01:53 +0100 Subject: [PATCH 114/140] tag: id3v2: pass the right size value for size of all frames to the parser Frame data size is tag size adjusted for size of the tag header and footer, not tag size including header and footer. --- gst-libs/gst/tag/id3v2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gst-libs/gst/tag/id3v2.c b/gst-libs/gst/tag/id3v2.c index 0a7a3df9a0..f2e06344a4 100644 --- a/gst-libs/gst/tag/id3v2.c +++ b/gst-libs/gst/tag/id3v2.c @@ -159,7 +159,7 @@ id3demux_read_id3v2_tag (GstBuffer * buffer, guint * id3v2_size, else work.hdr.frame_data_size = read_size - ID3V2_HDR_SIZE; - result = id3demux_id3v2_frames_to_tag_list (&work, read_size); + result = id3demux_id3v2_frames_to_tag_list (&work, work.hdr.frame_data_size); *tags = work.tags; From 941d5080fa170e6b775914bcaecc3e3be2286f09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Fri, 24 Apr 2009 01:51:35 +0100 Subject: [PATCH 115/140] tag: id3v2: parse unsynchronised tags properly We didn't handle unsynchronization at all up to now, which might have caused frames to not be extracted - esp. frames after an APIC picture frame. Fixes #577468. --- gst-libs/gst/tag/id3v2.c | 59 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/gst-libs/gst/tag/id3v2.c b/gst-libs/gst/tag/id3v2.c index f2e06344a4..1d511f3365 100644 --- a/gst-libs/gst/tag/id3v2.c +++ b/gst-libs/gst/tag/id3v2.c @@ -97,12 +97,40 @@ id3demux_calc_id3v2_tag_size (GstBuffer * buf) return size; } +static guint8 * +id3demux_ununsync_data (const guint8 * unsync_data, guint32 * size) +{ + const guint8 *end; + guint8 *out, *uu; + guint out_size; + + uu = out = g_malloc (*size); + + for (end = unsync_data + *size; unsync_data < end - 1; ++unsync_data, ++uu) { + *uu = *unsync_data; + if (G_UNLIKELY (*unsync_data == 0xff && *(unsync_data + 1) == 0x00)) + ++unsync_data; + } + + /* take care of last byte (if last two bytes weren't 0xff 0x00) */ + if (unsync_data < end) { + *uu = *unsync_data; + ++uu; + } + + out_size = uu - out; + GST_DEBUG ("size after un-unsyncing: %u (before: %u)", out_size, *size); + + *size = out_size; + return out; +} + /* caller must pass buffer with full ID3 tag */ ID3TagsResult id3demux_read_id3v2_tag (GstBuffer * buffer, guint * id3v2_size, GstTagList ** tags) { - guint8 *data; + guint8 *data, *uu_data = NULL; guint read_size; ID3TagsWorking work; guint8 flags; @@ -134,6 +162,12 @@ id3demux_read_id3v2_tag (GstBuffer * buffer, guint * id3v2_size, return ID3TAGS_READ_TAG; } + GST_DEBUG ("ID3v2 header flags: %s %s %s %s", + (flags & ID3V2_HDR_FLAG_UNSYNC) ? "UNSYNC" : "", + (flags & ID3V2_HDR_FLAG_EXTHDR) ? "EXTENDED_HEADER" : "", + (flags & ID3V2_HDR_FLAG_EXPERIMENTAL) ? "EXPERIMENTAL" : "", + (flags & ID3V2_HDR_FLAG_FOOTER) ? "FOOTER" : ""); + /* This shouldn't really happen! Caller should have checked first */ if (GST_BUFFER_SIZE (buffer) < read_size) { GST_DEBUG @@ -148,6 +182,8 @@ id3demux_read_id3v2_tag (GstBuffer * buffer, guint * id3v2_size, g_return_val_if_fail (tags != NULL, ID3TAGS_READ_TAG); + GST_MEMDUMP ("ID3v2 tag", GST_BUFFER_DATA (buffer), read_size); + memset (&work, 0, sizeof (ID3TagsWorking)); work.buffer = buffer; work.hdr.version = version; @@ -159,10 +195,20 @@ id3demux_read_id3v2_tag (GstBuffer * buffer, guint * id3v2_size, else work.hdr.frame_data_size = read_size - ID3V2_HDR_SIZE; + if ((flags & ID3V2_HDR_FLAG_UNSYNC)) { + GST_DEBUG ("Un-unsyncing entire tag"); + uu_data = id3demux_ununsync_data (work.hdr.frame_data, + &work.hdr.frame_data_size); + work.hdr.frame_data = uu_data; + GST_MEMDUMP ("ID3v2 tag (un-unsyced)", uu_data, work.hdr.frame_data_size); + } + result = id3demux_id3v2_frames_to_tag_list (&work, work.hdr.frame_data_size); *tags = work.tags; + g_free (uu_data); + return result; } @@ -425,6 +471,17 @@ id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size) frame_size, work->hdr.frame_data + frame_hdr_size + frame_size - start, work->hdr.frame_data + frame_hdr_size + frame_size - start, obsolete_id); +#define flag_string(flag,str) \ + ((frame_flags & (flag)) ? (str) : "") + GST_LOG ("Frame header flags: 0x%04x %s %s %s %s %s %s %s", frame_flags, + flag_string (ID3V2_FRAME_STATUS_FRAME_ALTER_PRESERVE, "ALTER_PRESERVE"), + flag_string (ID3V2_FRAME_STATUS_READONLY, "READONLY"), + flag_string (ID3V2_FRAME_FORMAT_GROUPING_ID, "GROUPING_ID"), + flag_string (ID3V2_FRAME_FORMAT_COMPRESSION, "COMPRESSION"), + flag_string (ID3V2_FRAME_FORMAT_ENCRYPTION, "ENCRYPTION"), + flag_string (ID3V2_FRAME_FORMAT_UNSYNCHRONISATION, "UNSYNC"), + flag_string (ID3V2_FRAME_FORMAT_DATA_LENGTH_INDICATOR, "LENGHT_IND")); +#undef flag_str #endif if (!obsolete_id) { From c2c21806ba4255b081610d4033b7044936e7b5be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Fri, 7 Aug 2009 16:02:23 +0100 Subject: [PATCH 116/140] tag: id3v2: fix parsing of unsync'ed ID3 v2.4 tags and frames Reversing the unsynchronisation seems to work slightly differently for ID3 v2.3 tags and v2.4 tags: v2.3 tags don't have syncsafe frame sizes in the frame header, so the unsynchronisation is applied to the whole frame data including all the frame headers. v2.4 frames have sync-safe sizes, however, so the unsynchronisation only needs to be applied to the actual frame data, and it seems that's what's being done as well. So we need to undo the unsynchronisation on a per-frame basis for v2.4 tags for things to work properly. Fixes extraction of coverart/images from APIC frames in ID3 v2.4 tags (#588148). Add unit test for this as well. --- gst-libs/gst/tag/id3v2.c | 7 +++++-- gst-libs/gst/tag/id3v2.h | 2 ++ gst-libs/gst/tag/id3v2frames.c | 28 ++++++++++++++++++++++------ 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/gst-libs/gst/tag/id3v2.c b/gst-libs/gst/tag/id3v2.c index 1d511f3365..96d25521d6 100644 --- a/gst-libs/gst/tag/id3v2.c +++ b/gst-libs/gst/tag/id3v2.c @@ -97,7 +97,7 @@ id3demux_calc_id3v2_tag_size (GstBuffer * buf) return size; } -static guint8 * +guint8 * id3demux_ununsync_data (const guint8 * unsync_data, guint32 * size) { const guint8 *end; @@ -195,7 +195,10 @@ id3demux_read_id3v2_tag (GstBuffer * buffer, guint * id3v2_size, else work.hdr.frame_data_size = read_size - ID3V2_HDR_SIZE; - if ((flags & ID3V2_HDR_FLAG_UNSYNC)) { + /* in v2.3 the frame sizes are not syncsafe, so the entire tag had to be + * unsynced. In v2.4 the frame sizes are syncsafe so it's just the frame + * data that needs un-unsyncing, but not the frame headers. */ + if ((flags & ID3V2_HDR_FLAG_UNSYNC) != 0 && ID3V2_VER_MAJOR (version) <= 3) { GST_DEBUG ("Un-unsyncing entire tag"); uu_data = id3demux_ununsync_data (work.hdr.frame_data, &work.hdr.frame_data_size); diff --git a/gst-libs/gst/tag/id3v2.h b/gst-libs/gst/tag/id3v2.h index b5bc950dd4..14a42de38b 100644 --- a/gst-libs/gst/tag/id3v2.h +++ b/gst-libs/gst/tag/id3v2.h @@ -115,6 +115,8 @@ enum { /* From id3v2frames.c */ gboolean id3demux_id3v2_parse_frame (ID3TagsWorking *work); +guint8 * id3demux_ununsync_data (const guint8 * unsync_data, guint32 * size); + G_END_DECLS #endif diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index 377d84cf5d..a0331ef385 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -73,6 +73,7 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) guint frame_data_size = work->cur_frame_size; gchar *tag_str = NULL; GArray *tag_fields = NULL; + guint8 *uu_data = NULL; #ifdef HAVE_ZLIB guint8 *uncompressed_data = NULL; @@ -86,17 +87,14 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) } } - /* Can't handle encrypted frames right now */ + /* Can't handle encrypted frames right now (in case we ever do, we'll have + * to do the decryption after the un-unsynchronisation and decompression, + * not here) */ if (work->frame_flags & ID3V2_FRAME_FORMAT_ENCRYPTION) { GST_WARNING ("Encrypted frames are not supported"); return FALSE; } - if (work->frame_flags & ID3V2_FRAME_FORMAT_UNSYNCHRONISATION) { - GST_WARNING ("ID3v2 frame with unsupported unsynchronisation applied. " - "May fail badly"); - } - tag_name = gst_tag_from_id3_tag (work->frame_id); if (tag_name == NULL && strncmp (work->frame_id, "RVA2", 4) != 0 && @@ -120,6 +118,19 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) } } + /* in v2.3 the frame sizes are not syncsafe, so the entire tag had to be + * unsynced. In v2.4 the frame sizes are syncsafe so it's just the frame + * data that needs un-unsyncing, but not the frame headers. */ + if (ID3V2_VER_MAJOR (work->hdr.version) == 4) { + if ((work->hdr.flags & ID3V2_HDR_FLAG_UNSYNC) != 0 || + ((work->frame_flags & ID3V2_FRAME_FORMAT_UNSYNCHRONISATION) != 0)) { + GST_DEBUG ("Un-unsyncing frame %s", work->frame_id); + uu_data = id3demux_ununsync_data (frame_data, &frame_data_size); + frame_data = uu_data; + GST_MEMDUMP ("ID3v2 frame (un-unsyced)", frame_data, frame_data_size); + } + } + work->parse_size = frame_data_size; if (work->frame_flags & ID3V2_FRAME_FORMAT_COMPRESSION) { @@ -134,6 +145,7 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) if (uncompress (dest, &destSize, src, frame_data_size) != Z_OK) { g_free (uncompressed_data); + g_free (uu_data); return FALSE; } if (destSize != work->parse_size) { @@ -141,12 +153,14 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) ("Decompressing ID3v2 frame %s did not produce expected size %d bytes (got %lu)", tag_name, work->parse_size, destSize); g_free (uncompressed_data); + g_free (uu_data); return FALSE; } work->parse_data = uncompressed_data; #else GST_WARNING ("Compressed ID3v2 tag frame could not be decompressed" " because gstid3demux was compiled without zlib support"); + g_free (uu_data); return FALSE; #endif } else { @@ -209,6 +223,8 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) free_tag_strings (tag_fields); } + g_free (uu_data); + return result; } From 891ed455e79ccf63d4dc8b974bfedcf72d6397c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Fri, 7 Aug 2009 16:36:55 +0100 Subject: [PATCH 117/140] tag: id3v2: fix typo in debug message --- gst-libs/gst/tag/id3v2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gst-libs/gst/tag/id3v2.c b/gst-libs/gst/tag/id3v2.c index 96d25521d6..7cf26d1f0f 100644 --- a/gst-libs/gst/tag/id3v2.c +++ b/gst-libs/gst/tag/id3v2.c @@ -483,7 +483,7 @@ id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size) flag_string (ID3V2_FRAME_FORMAT_COMPRESSION, "COMPRESSION"), flag_string (ID3V2_FRAME_FORMAT_ENCRYPTION, "ENCRYPTION"), flag_string (ID3V2_FRAME_FORMAT_UNSYNCHRONISATION, "UNSYNC"), - flag_string (ID3V2_FRAME_FORMAT_DATA_LENGTH_INDICATOR, "LENGHT_IND")); + flag_string (ID3V2_FRAME_FORMAT_DATA_LENGTH_INDICATOR, "LENGTH_IND")); #undef flag_str #endif From 09b26dbf5ca4a14e5cc53529546d71c1509f3c94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Fri, 7 Aug 2009 16:42:39 +0100 Subject: [PATCH 118/140] tag: id3v2: sizes in ID3 v2.3 are unlikely to be sync-safe integers In ID3 v2.3 compressed frames will have a 4-byte data length indicator after the frame header to indicate the size of the decompressed data. This integer is unlikely to be a sync-safe integer for v2.3 tags, only in v2.4 it's sync-safe. --- gst-libs/gst/tag/id3v2frames.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index a0331ef385..80a123a27a 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -108,7 +108,11 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) ID3V2_FRAME_FORMAT_DATA_LENGTH_INDICATOR)) { if (work->hdr.frame_data_size <= 4) return FALSE; - work->parse_size = read_synch_uint (frame_data, 4); + if (ID3V2_VER_MAJOR (work->hdr.version) == 3) { + work->parse_size = GST_READ_UINT32_BE (frame_data); + } else { + work->parse_size = read_synch_uint (frame_data, 4); + } frame_data += 4; frame_data_size -= 4; if (work->parse_size < frame_data_size) { From 687055c8528aeb29d861992dac5fbba2cc7dcf52 Mon Sep 17 00:00:00 2001 From: LoneStar Date: Sun, 9 Aug 2009 12:52:17 +0200 Subject: [PATCH 119/140] tag: id3v2: Try GST_*_TAG_ENCODING and locale encoding if tags are not UTF8 Fixes bug #499242. --- gst-libs/gst/tag/id3v2frames.c | 72 +++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index 80a123a27a..fd9391c67d 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -948,6 +948,73 @@ find_utf16_bom (gchar * data, const gchar ** p_in_encoding) return FALSE; } +static void * +string_utf8_dup (const gchar * start, const guint size) +{ + const gchar *env; + gsize bytes_read; + gchar *utf8; + + /* Should we try the charsets specified + * via environment variables FIRST ? */ + if (g_utf8_validate (start, size, NULL)) { + utf8 = g_strndup (start, size); + goto beach; + } + + env = g_getenv ("GST_ID3V1_TAG_ENCODING"); + if (!env || *env == '\0') + env = g_getenv ("GST_ID3_TAG_ENCODING"); + if (!env || *env == '\0') + env = g_getenv ("GST_TAG_ENCODING"); + + /* Try charsets specified via the environment */ + if (env && *env != '\0') { + gchar **c, **csets; + + csets = g_strsplit (env, G_SEARCHPATH_SEPARATOR_S, -1); + + for (c = csets; c && *c; ++c) { + if ((utf8 = + g_convert (start, size, "UTF-8", *c, &bytes_read, NULL, NULL))) { + if (bytes_read == size) { + GST_DEBUG ("Using charset %s to interperate id3 tags\n", c); + g_strfreev (csets); + goto beach; + } + g_free (utf8); + utf8 = NULL; + } + } + } + /* Try current locale (if not UTF-8) */ + if (!g_get_charset (&env)) { + if ((utf8 = g_locale_to_utf8 (start, size, &bytes_read, NULL, NULL))) { + if (bytes_read == size) { + goto beach; + } + g_free (utf8); + utf8 = NULL; + } + } + + /* Try ISO-8859-1 */ + utf8 = + g_convert (start, size, "UTF-8", "ISO-8859-1", &bytes_read, NULL, NULL); + if (utf8 != NULL && bytes_read == size) { + goto beach; + } + + g_free (utf8); + return NULL; + +beach: + + g_strchomp (utf8); + + return (utf8); +} + static void parse_insert_string_field (guint8 encoding, gchar * data, gint data_size, GArray * fields) @@ -988,8 +1055,9 @@ parse_insert_string_field (guint8 encoding, gchar * data, gint data_size, if (g_utf8_validate (data, data_size, NULL)) field = g_strndup (data, data_size); else - field = g_convert (data, data_size, "UTF-8", "ISO-8859-1", - NULL, NULL, NULL); + /* field = g_convert (data, data_size, "UTF-8", "ISO-8859-1", + NULL, NULL, NULL); */ + field = string_utf8_dup (data, data_size); break; default: field = g_strndup (data, data_size); From 4a885b094fde5b2fad5f8c0f4a90ac6db2fda169 Mon Sep 17 00:00:00 2001 From: Alessandro Decina Date: Tue, 22 Sep 2009 15:03:20 +0200 Subject: [PATCH 120/140] tag: id3v2: Fix compile warnings with gcc 4.0.1. --- gst-libs/gst/tag/id3v2frames.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index fd9391c67d..076abc70d5 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -978,7 +978,7 @@ string_utf8_dup (const gchar * start, const guint size) if ((utf8 = g_convert (start, size, "UTF-8", *c, &bytes_read, NULL, NULL))) { if (bytes_read == size) { - GST_DEBUG ("Using charset %s to interperate id3 tags\n", c); + GST_DEBUG ("Using charset %s to interperate id3 tags\n", *c); g_strfreev (csets); goto beach; } From ed05fcfb2c3c1f9dba1d5bb9a536127ccc246282 Mon Sep 17 00:00:00 2001 From: Stefan Kost Date: Wed, 7 Oct 2009 14:03:20 +0300 Subject: [PATCH 121/140] tag: id3v2: fprintf, sprintf, sscanf need stdio.h --- gst-libs/gst/tag/id3v2frames.c | 1 + 1 file changed, 1 insertion(+) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index 076abc70d5..a91279c412 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -24,6 +24,7 @@ #endif #include +#include #include #include #include From 67fabdb461a13867136b2ab16776a291ef92c7a9 Mon Sep 17 00:00:00 2001 From: Josep Torra Date: Fri, 9 Oct 2009 11:42:36 +0200 Subject: [PATCH 122/140] tag: id3v2: fix printf warnings on macosx --- gst-libs/gst/tag/id3v2.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/gst-libs/gst/tag/id3v2.c b/gst-libs/gst/tag/id3v2.c index 7cf26d1f0f..cbfa659862 100644 --- a/gst-libs/gst/tag/id3v2.c +++ b/gst-libs/gst/tag/id3v2.c @@ -470,9 +470,10 @@ id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size) #if 1 GST_LOG ("Frame @ %d (0x%02x) id %s size %d, next=%d (0x%02x) obsolete=%d", - work->hdr.frame_data - start, work->hdr.frame_data - start, frame_id, - frame_size, work->hdr.frame_data + frame_hdr_size + frame_size - start, - work->hdr.frame_data + frame_hdr_size + frame_size - start, + (gint) (work->hdr.frame_data - start), + (gint) (work->hdr.frame_data - start), frame_id, frame_size, + (gint) (work->hdr.frame_data + frame_hdr_size + frame_size - start), + (gint) (work->hdr.frame_data + frame_hdr_size + frame_size - start), obsolete_id); #define flag_string(flag,str) \ ((frame_flags & (flag)) ? (str) : "") From dd3826a3a623e4c708f5af9993299255f9a92860 Mon Sep 17 00:00:00 2001 From: Stefan Kost Date: Fri, 9 Oct 2009 13:38:17 +0300 Subject: [PATCH 123/140] tag: id3v2: don't cast, but use the right format specified instead This correct some of the previous macos fixes. --- gst-libs/gst/tag/id3v2.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gst-libs/gst/tag/id3v2.c b/gst-libs/gst/tag/id3v2.c index cbfa659862..ecac8182d2 100644 --- a/gst-libs/gst/tag/id3v2.c +++ b/gst-libs/gst/tag/id3v2.c @@ -469,11 +469,11 @@ id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size) #if 1 GST_LOG - ("Frame @ %d (0x%02x) id %s size %d, next=%d (0x%02x) obsolete=%d", - (gint) (work->hdr.frame_data - start), - (gint) (work->hdr.frame_data - start), frame_id, frame_size, - (gint) (work->hdr.frame_data + frame_hdr_size + frame_size - start), - (gint) (work->hdr.frame_data + frame_hdr_size + frame_size - start), + ("Frame @ %u (0x%02x) id %s size %u, next=%u (0x%02x) obsolete=%d", + work->hdr.frame_data - start, + work->hdr.frame_data - start, frame_id, frame_size, + work->hdr.frame_data + frame_hdr_size + frame_size - start, + work->hdr.frame_data + frame_hdr_size + frame_size - start, obsolete_id); #define flag_string(flag,str) \ ((frame_flags & (flag)) ? (str) : "") From aa6ff34c57017d03ee7678fdd127acea9b86457a Mon Sep 17 00:00:00 2001 From: Stefan Kost Date: Fri, 9 Oct 2009 14:44:02 +0300 Subject: [PATCH 124/140] tag: id3v2: cast pointer math results to glong --- gst-libs/gst/tag/id3v2.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gst-libs/gst/tag/id3v2.c b/gst-libs/gst/tag/id3v2.c index ecac8182d2..c7c1a11235 100644 --- a/gst-libs/gst/tag/id3v2.c +++ b/gst-libs/gst/tag/id3v2.c @@ -469,11 +469,11 @@ id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size) #if 1 GST_LOG - ("Frame @ %u (0x%02x) id %s size %u, next=%u (0x%02x) obsolete=%d", - work->hdr.frame_data - start, - work->hdr.frame_data - start, frame_id, frame_size, - work->hdr.frame_data + frame_hdr_size + frame_size - start, - work->hdr.frame_data + frame_hdr_size + frame_size - start, + ("Frame @ %ld (0x%02x) id %s size %ld, next=%ld (0x%02x) obsolete=%d", + (glong) (work->hdr.frame_data - start), + (glong) (work->hdr.frame_data - start), frame_id, frame_size, + (glong) (work->hdr.frame_data + frame_hdr_size + frame_size - start), + (glong) (work->hdr.frame_data + frame_hdr_size + frame_size - start), obsolete_id); #define flag_string(flag,str) \ ((frame_flags & (flag)) ? (str) : "") From ad10b0185a47472b6be9a684a68cd854c46b0582 Mon Sep 17 00:00:00 2001 From: Josep Torra Date: Fri, 9 Oct 2009 15:59:25 +0200 Subject: [PATCH 125/140] tag: id3v2: fixes warnings building on macosx Another round on the formating of that debug line. --- gst-libs/gst/tag/id3v2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gst-libs/gst/tag/id3v2.c b/gst-libs/gst/tag/id3v2.c index c7c1a11235..fb0e42d506 100644 --- a/gst-libs/gst/tag/id3v2.c +++ b/gst-libs/gst/tag/id3v2.c @@ -469,7 +469,7 @@ id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size) #if 1 GST_LOG - ("Frame @ %ld (0x%02x) id %s size %ld, next=%ld (0x%02x) obsolete=%d", + ("Frame @ %ld (0x%02lx) id %s size %u, next=%ld (0x%02lx) obsolete=%d", (glong) (work->hdr.frame_data - start), (glong) (work->hdr.frame_data - start), frame_id, frame_size, (glong) (work->hdr.frame_data + frame_hdr_size + frame_size - start), From e4752b52b81db51caf10c41fb5b26c293c23b8fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Sun, 13 Dec 2009 13:19:43 +0000 Subject: [PATCH 126/140] tag: id3v2: prefer two letter ISO 639-1 code for extended comment --- gst-libs/gst/tag/id3v2frames.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index a91279c412..79fdf61b32 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -276,7 +276,12 @@ parse_comment_frame (ID3TagsWorking * work) g_ascii_isalpha (language[0]) && g_ascii_isalpha (language[1]) && (g_ascii_isalpha (language[2]) || language[2] == '\0')) { - s = g_strdup_printf ("%s[%s]=%s", description, language, text); + const gchar *lang_code; + + /* prefer two-letter ISO 639-1 code if we have a mapping */ + lang_code = gst_tag_get_language_code (language); + s = g_strdup_printf ("%s[%s]=%s", description, + (lang_code) ? lang_code : language, text); } else { s = g_strdup_printf ("%s=%s", description, text); } From 50a6cb545036ba36a83c3490bbc9f9023938ad35 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sat, 20 Mar 2010 00:54:14 +0100 Subject: [PATCH 127/140] Add -Wwrite-strings to the configure flags ... and fix all warnings --- gst-libs/gst/tag/id3v2.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gst-libs/gst/tag/id3v2.c b/gst-libs/gst/tag/id3v2.c index fb0e42d506..68a88f82e6 100644 --- a/gst-libs/gst/tag/id3v2.c +++ b/gst-libs/gst/tag/id3v2.c @@ -239,8 +239,8 @@ static const gchar *obsolete_frame_ids[] = { const struct ID3v2FrameIDConvert { - gchar *orig; - gchar *new; + const gchar *orig; + const gchar *new; } frame_id_conversions[] = { /* 2.3.x frames */ { From 366ed8edb4b3bb66bf5729e575fc957a012a22bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Tue, 30 Mar 2010 01:50:32 +0100 Subject: [PATCH 128/140] tag: id3v2: fix parsing of unsynced frames with data length indicator Fixes bug #614158. --- gst-libs/gst/tag/id3v2frames.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index 79fdf61b32..021a3d173c 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -116,9 +116,11 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) } frame_data += 4; frame_data_size -= 4; - if (work->parse_size < frame_data_size) { - GST_WARNING ("ID3v2 frame %s has invalid size %d.", tag_name, - frame_data_size); + GST_LOG ("Un-unsynced data size %d (of %d)", work->parse_size, + frame_data_size); + if (work->parse_size > frame_data_size) { + GST_WARNING ("ID3v2 frame %s data has invalid size %d (>%d)", + work->frame_id, work->parse_size, frame_data_size); return FALSE; } } From d2cec9e102dabb0fd9a1f903859a455bcf886306 Mon Sep 17 00:00:00 2001 From: Edward Hervey Date: Fri, 24 Sep 2010 15:19:15 +0200 Subject: [PATCH 129/140] tag: id3v2: Sanitize id3 frame names This is similar to what is done in qtdemux. Avoids providing invalid structure/tags names --- gst-libs/gst/tag/id3v2.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/gst-libs/gst/tag/id3v2.c b/gst-libs/gst/tag/id3v2.c index 68a88f82e6..709cda87d3 100644 --- a/gst-libs/gst/tag/id3v2.c +++ b/gst-libs/gst/tag/id3v2.c @@ -340,6 +340,7 @@ id3demux_add_id3v2_frame_blob_to_taglist (ID3TagsWorking * work, guint size) guint8 *frame_data; gchar *media_type; guint frame_size, header_size; + guint i; switch (ID3V2_VER_MAJOR (work->hdr.version)) { case 1: @@ -360,6 +361,12 @@ id3demux_add_id3v2_frame_blob_to_taglist (ID3TagsWorking * work, guint size) blob = gst_buffer_new_and_alloc (frame_size); memcpy (GST_BUFFER_DATA (blob), frame_data, frame_size); + /* Sanitize frame id */ + for (i = 0; i < 4; i++) { + if (!g_ascii_isalnum (frame_data[i])) + frame_data[i] = '_'; + } + media_type = g_strdup_printf ("application/x-gst-id3v2-%c%c%c%c-frame", g_ascii_tolower (frame_data[0]), g_ascii_tolower (frame_data[1]), g_ascii_tolower (frame_data[2]), g_ascii_tolower (frame_data[3])); @@ -418,6 +425,7 @@ id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size) guint16 frame_flags = 0x0; gboolean obsolete_id = FALSE; gboolean read_synch_size = TRUE; + guint i; /* Read the header */ switch (ID3V2_VER_MAJOR (work->hdr.version)) { @@ -467,6 +475,22 @@ id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size) if (frame_size > work->hdr.frame_data_size || strcmp (frame_id, "") == 0) break; /* No more frames to read */ + /* Sanitize frame id */ + switch (ID3V2_VER_MAJOR (work->hdr.version)) { + case 0: + case 1: + case 2: + for (i = 0; i < 3; i++) { + if (!g_ascii_isalnum (frame_id[i])) + frame_id[i] = '_'; + } + break; + default: + for (i = 0; i < 4; i++) { + if (!g_ascii_isalnum (frame_id[i])) + frame_id[i] = '_'; + } + } #if 1 GST_LOG ("Frame @ %ld (0x%02lx) id %s size %u, next=%ld (0x%02lx) obsolete=%d", From afe6ee7f3c18e1c230af7cfe1e13e7ee13b9831d Mon Sep 17 00:00:00 2001 From: Erich Schubert Date: Sun, 2 Jan 2011 19:23:51 +0000 Subject: [PATCH 130/140] tag: id3v2: fix parsing of ID3v2.4 genre frames with multiple genres We'd only extract the first genre (multiple times) instead of all genres. https://bugzilla.gnome.org/show_bug.cgi?id=638535 --- gst-libs/gst/tag/id3v2frames.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index 021a3d173c..e51bbb78ec 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -875,7 +875,7 @@ id3v2_genre_fields_to_taglist (ID3TagsWorking * work, const gchar * tag_name, for (i = 0; i < tag_fields->len; i++) { gint len; - tag_str = g_array_index (tag_fields, gchar *, 0); + tag_str = g_array_index (tag_fields, gchar *, i); if (tag_str == NULL) continue; From 4f042aeb1a1475f66b1d2c51a6f254c8225767ff Mon Sep 17 00:00:00 2001 From: Stefan Kost Date: Tue, 22 Feb 2011 15:19:00 +0200 Subject: [PATCH 131/140] tag: id3v2: return ID3TAGS_BROKEN_TAG for unsupported versions This prevents us for trying to work with a NULL taglist. --- gst-libs/gst/tag/id3v2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gst-libs/gst/tag/id3v2.c b/gst-libs/gst/tag/id3v2.c index 709cda87d3..d201d7c289 100644 --- a/gst-libs/gst/tag/id3v2.c +++ b/gst-libs/gst/tag/id3v2.c @@ -159,7 +159,7 @@ id3demux_read_id3v2_tag (GstBuffer * buffer, guint * id3v2_size, GST_WARNING ("ID3v2 tag is from revision 2.%d.%d, " "but decoder only supports 2.%d.%d. Ignoring as per spec.", version >> 8, version & 0xff, ID3V2_VERSION >> 8, ID3V2_VERSION & 0xff); - return ID3TAGS_READ_TAG; + return ID3TAGS_BROKEN_TAG; } GST_DEBUG ("ID3v2 header flags: %s %s %s %s", From 08e6b5c54e030da30bca944dd1b0e0aca00fb6eb Mon Sep 17 00:00:00 2001 From: Mark Nauwelaerts Date: Mon, 18 Jul 2011 18:09:53 +0200 Subject: [PATCH 132/140] tag: id3v2: add id3v2 tag parsing helpers https://bugzilla.gnome.org/show_bug.cgi?id=654388 --- configure.ac | 11 ++ docs/libs/gst-plugins-base-libs-sections.txt | 2 + gst-libs/gst/tag/Makefile.am | 8 +- gst-libs/gst/tag/id3v2.c | 109 ++++++++++--------- gst-libs/gst/tag/id3v2.h | 36 +++--- gst-libs/gst/tag/id3v2frames.c | 15 +-- gst-libs/gst/tag/tag.h | 16 +++ gst-libs/gst/tag/tags.c | 5 + win32/common/libgsttag.def | 2 + 9 files changed, 122 insertions(+), 82 deletions(-) diff --git a/configure.ac b/configure.ac index f14abe19eb..a76691f994 100644 --- a/configure.ac +++ b/configure.ac @@ -505,6 +505,17 @@ else AM_CONDITIONAL(USE_ISO_CODES, false) fi +dnl *** zlib is optionally used by id3 tag parsing in libgsttag *** +translit(dnm, m, l) AM_CONDITIONAL(USE_ZLIB, true) +AG_GST_CHECK_FEATURE(ZLIB, [zlib support for ID3 parsing in libgsttag],, [ + AG_GST_CHECK_LIBHEADER(ZLIB, + z, uncompress,, zlib.h, [ + HAVE_ZLIB="yes" + ZLIB_LIBS="-lz" + AC_SUBST(ZLIB_LIBS) + ]) +]) + dnl *** sys plug-ins *** echo diff --git a/docs/libs/gst-plugins-base-libs-sections.txt b/docs/libs/gst-plugins-base-libs-sections.txt index 54d97223cc..19cd66f2fe 100644 --- a/docs/libs/gst-plugins-base-libs-sections.txt +++ b/docs/libs/gst-plugins-base-libs-sections.txt @@ -1782,6 +1782,8 @@ gst_tag_from_id3_tag gst_tag_from_id3_user_tag gst_tag_to_id3_tag gst_tag_list_add_id3_image +gst_tag_get_id3v2_tag_size +gst_tag_list_from_id3v2_tag
diff --git a/gst-libs/gst/tag/Makefile.am b/gst-libs/gst/tag/Makefile.am index 48137d6c87..e7dd5627d5 100644 --- a/gst-libs/gst/tag/Makefile.am +++ b/gst-libs/gst/tag/Makefile.am @@ -9,19 +9,19 @@ lib_LTLIBRARIES = libgsttag-@GST_MAJORMINOR@.la libgsttag_@GST_MAJORMINOR@_la_SOURCES = \ gstvorbistag.c gstid3tag.c gstxmptag.c gstexiftag.c \ lang.c licenses.c tags.c gsttagdemux.c gsttagmux.c \ - gsttageditingprivate.c xmpwriter.c + gsttageditingprivate.c id3v2.c id3v2frames.c xmpwriter.c libgsttag_@GST_MAJORMINOR@_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) \ - $(GST_BASE_CFLAGS) $(GST_CFLAGS) \ + $(GST_BASE_CFLAGS) $(GST_CFLAGS) $(ZLIB_CFLAGS) \ -DLICENSE_TRANSLATIONS_PATH=\"$(pkgdatadir)/license-translations.dict\" -libgsttag_@GST_MAJORMINOR@_la_LIBADD = $(GST_BASE_LIBS) $(GST_LIBS) $(LIBM) +libgsttag_@GST_MAJORMINOR@_la_LIBADD = $(GST_BASE_LIBS) $(GST_LIBS) $(LIBM) $(ZLIB_LIBS) libgsttag_@GST_MAJORMINOR@_la_LDFLAGS = $(GST_LIB_LDFLAGS) $(GST_ALL_LDFLAGS) $(GST_LT_LDFLAGS) # lang-tables.dat contains generated static data and is included by lang.c # licenses-tables.dat contains generated data and is included by licenses.c EXTRA_DIST = lang-tables.dat licenses-tables.dat -noinst_HEADERS = gsttageditingprivate.h +noinst_HEADERS = gsttageditingprivate.h id3v2.h if HAVE_INTROSPECTION BUILT_GIRSOURCES = GstTag-@GST_MAJORMINOR@.gir diff --git a/gst-libs/gst/tag/id3v2.c b/gst-libs/gst/tag/id3v2.c index d201d7c289..5c99262046 100644 --- a/gst-libs/gst/tag/id3v2.c +++ b/gst-libs/gst/tag/id3v2.c @@ -25,17 +25,14 @@ #include #include -#include "id3tags.h" - -GST_DEBUG_CATEGORY_EXTERN (id3demux_debug); -#define GST_CAT_DEFAULT (id3demux_debug) +#include "id3v2.h" #define HANDLE_INVALID_SYNCSAFE -static ID3TagsResult -id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size); + +static gboolean id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size); guint -read_synch_uint (const guint8 * data, guint size) +id3v2_read_synch_uint (const guint8 * data, guint size) { gint i; guint result = 0; @@ -62,16 +59,29 @@ read_synch_uint (const guint8 * data, guint size) return result; } +/** + * gst_tag_get_id3v2_tag_size: + * @buffer: buffer holding ID3v2 tag (or at least the start of one) + * + * Determines size of an ID3v2 tag on buffer containing at least ID3v2 header, + * i.e. at least #GST_TAG_ID3V2_HEADER_SIZE (10) bytes; + * + * Returns: Size of tag, or 0 if header is invalid or too small. + * + * Since: 0.10.36 + */ guint -id3demux_calc_id3v2_tag_size (GstBuffer * buf) +gst_tag_get_id3v2_tag_size (GstBuffer * buffer) { guint8 *data, flags; guint size; - g_assert (buf != NULL); - g_assert (GST_BUFFER_SIZE (buf) >= ID3V2_HDR_SIZE); + g_return_val_if_fail (buffer != NULL, 0); - data = GST_BUFFER_DATA (buf); + if (GST_BUFFER_SIZE (buffer) < ID3V2_HDR_SIZE) + return 0; + + data = GST_BUFFER_DATA (buffer); /* Check for 'ID3' string at start of buffer */ if (data[0] != 'I' || data[1] != 'D' || data[2] != '3') { @@ -83,7 +93,7 @@ id3demux_calc_id3v2_tag_size (GstBuffer * buf) flags = data[5]; /* Read the size from the header */ - size = read_synch_uint (data + 6, 4); + size = id3v2_read_synch_uint (data + 6, 4); if (size == 0) return ID3V2_HDR_SIZE; @@ -98,7 +108,7 @@ id3demux_calc_id3v2_tag_size (GstBuffer * buf) } guint8 * -id3demux_ununsync_data (const guint8 * unsync_data, guint32 * size) +id3v2_ununsync_data (const guint8 * unsync_data, guint32 * size) { const guint8 *end; guint8 *out, *uu; @@ -125,26 +135,32 @@ id3demux_ununsync_data (const guint8 * unsync_data, guint32 * size) return out; } -/* caller must pass buffer with full ID3 tag */ -ID3TagsResult -id3demux_read_id3v2_tag (GstBuffer * buffer, guint * id3v2_size, - GstTagList ** tags) +/** + * gst_tag_list_from_id3v2_tag: + * @buffer: buffer to convert + * + * Creates a new tag list that contains the information parsed out of a + * ID3 tag. + * + * Returns: A new #GstTagList with all tags that could be extracted from the + * given vorbiscomment buffer or NULL on error. + * + * Since: 0.10.36 + */ +GstTagList * +gst_tag_list_from_id3v2_tag (GstBuffer * buffer) { guint8 *data, *uu_data = NULL; guint read_size; ID3TagsWorking work; guint8 flags; - ID3TagsResult result; guint16 version; - read_size = id3demux_calc_id3v2_tag_size (buffer); - - if (id3v2_size) - *id3v2_size = read_size; + read_size = gst_tag_get_id3v2_tag_size (buffer); /* Ignore tag if it has no frames attached, but skip the header then */ - if (read_size <= ID3V2_HDR_SIZE) - return ID3TAGS_BROKEN_TAG; + if (read_size < ID3V2_HDR_SIZE) + return NULL; data = GST_BUFFER_DATA (buffer); @@ -159,7 +175,7 @@ id3demux_read_id3v2_tag (GstBuffer * buffer, guint * id3v2_size, GST_WARNING ("ID3v2 tag is from revision 2.%d.%d, " "but decoder only supports 2.%d.%d. Ignoring as per spec.", version >> 8, version & 0xff, ID3V2_VERSION >> 8, ID3V2_VERSION & 0xff); - return ID3TAGS_BROKEN_TAG; + return NULL; } GST_DEBUG ("ID3v2 header flags: %s %s %s %s", @@ -174,14 +190,12 @@ id3demux_read_id3v2_tag (GstBuffer * buffer, guint * id3v2_size, ("Found ID3v2 tag with revision 2.%d.%d - need %u more bytes to read", version >> 8, version & 0xff, (guint) (read_size - GST_BUFFER_SIZE (buffer))); - return ID3TAGS_MORE_DATA; /* Need more data to decode with */ + return NULL; } GST_DEBUG ("Reading ID3v2 tag with revision 2.%d.%d of size %u", version >> 8, version & 0xff, read_size); - g_return_val_if_fail (tags != NULL, ID3TAGS_READ_TAG); - GST_MEMDUMP ("ID3v2 tag", GST_BUFFER_DATA (buffer), read_size); memset (&work, 0, sizeof (ID3TagsWorking)); @@ -200,23 +214,21 @@ id3demux_read_id3v2_tag (GstBuffer * buffer, guint * id3v2_size, * data that needs un-unsyncing, but not the frame headers. */ if ((flags & ID3V2_HDR_FLAG_UNSYNC) != 0 && ID3V2_VER_MAJOR (version) <= 3) { GST_DEBUG ("Un-unsyncing entire tag"); - uu_data = id3demux_ununsync_data (work.hdr.frame_data, + uu_data = id3v2_ununsync_data (work.hdr.frame_data, &work.hdr.frame_data_size); work.hdr.frame_data = uu_data; GST_MEMDUMP ("ID3v2 tag (un-unsyced)", uu_data, work.hdr.frame_data_size); } - result = id3demux_id3v2_frames_to_tag_list (&work, work.hdr.frame_data_size); - - *tags = work.tags; + id3v2_frames_to_tag_list (&work, work.hdr.frame_data_size); g_free (uu_data); - return result; + return work.tags; } static guint -id3demux_id3v2_frame_hdr_size (guint id3v2ver) +id3v2_frame_hdr_size (guint id3v2ver) { /* ID3v2 < 2.3.0 only had 6 byte header */ switch (ID3V2_VER_MAJOR (id3v2ver)) { @@ -333,7 +345,7 @@ convert_fid_to_v240 (gchar * frame_id) /* add unknown or unhandled ID3v2 frames to the taglist as binary blobs */ static void -id3demux_add_id3v2_frame_blob_to_taglist (ID3TagsWorking * work, guint size) +id3v2_add_id3v2_frame_blob_to_taglist (ID3TagsWorking * work, guint size) { GstBuffer *blob; GstCaps *caps; @@ -379,29 +391,29 @@ id3demux_add_id3v2_frame_blob_to_taglist (ID3TagsWorking * work, guint size) /* gst_util_dump_mem (GST_BUFFER_DATA (blob), GST_BUFFER_SIZE (blob)); */ gst_tag_list_add (work->tags, GST_TAG_MERGE_APPEND, - GST_ID3_DEMUX_TAG_ID3V2_FRAME, blob, NULL); + GST_TAG_ID3V2_FRAME, blob, NULL); gst_buffer_unref (blob); } -static ID3TagsResult -id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size) +static gboolean +id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size) { guint frame_hdr_size; guint8 *start; /* Extended header if present */ if (work->hdr.flags & ID3V2_HDR_FLAG_EXTHDR) { - work->hdr.ext_hdr_size = read_synch_uint (work->hdr.frame_data, 4); + work->hdr.ext_hdr_size = id3v2_read_synch_uint (work->hdr.frame_data, 4); if (work->hdr.ext_hdr_size < 6 || (work->hdr.ext_hdr_size) > work->hdr.frame_data_size) { GST_DEBUG ("Invalid extended header. Broken tag"); - return ID3TAGS_BROKEN_TAG; + return FALSE; } work->hdr.ext_flag_bytes = work->hdr.frame_data[4]; if (5 + work->hdr.ext_flag_bytes > work->hdr.frame_data_size) { GST_DEBUG ("Tag claims extended header, but doesn't have enough bytes. Broken tag"); - return ID3TAGS_BROKEN_TAG; + return FALSE; } work->hdr.ext_flag_data = work->hdr.frame_data + 5; @@ -410,14 +422,13 @@ id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size) } start = GST_BUFFER_DATA (work->buffer); - frame_hdr_size = id3demux_id3v2_frame_hdr_size (work->hdr.version); + frame_hdr_size = id3v2_frame_hdr_size (work->hdr.version); if (work->hdr.frame_data_size <= frame_hdr_size) { GST_DEBUG ("Tag has no data frames. Broken tag"); - return ID3TAGS_BROKEN_TAG; /* Must have at least one frame */ + return FALSE; /* Must have at least one frame */ } work->tags = gst_tag_list_new (); - g_return_val_if_fail (work->tags != NULL, ID3TAGS_READ_TAG); while (work->hdr.frame_data_size > frame_hdr_size) { guint frame_size = 0; @@ -454,7 +465,7 @@ id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size) frame_id[3] = work->hdr.frame_data[3]; frame_id[4] = 0; if (read_synch_size) - frame_size = read_synch_uint (work->hdr.frame_data + 4, 4); + frame_size = id3v2_read_synch_uint (work->hdr.frame_data + 4, 4); else frame_size = GST_READ_UINT32_BE (work->hdr.frame_data + 4); @@ -519,11 +530,11 @@ id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size) work->frame_id = frame_id; work->frame_flags = frame_flags; - if (id3demux_id3v2_parse_frame (work)) { + if (id3v2_parse_frame (work)) { GST_LOG ("Extracted frame with id %s", frame_id); } else { GST_LOG ("Failed to extract frame with id %s", frame_id); - id3demux_add_id3v2_frame_blob_to_taglist (work, frame_size); + id3v2_add_id3v2_frame_blob_to_taglist (work, frame_size); } } work->hdr.frame_data += frame_size; @@ -534,7 +545,7 @@ id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size) GST_DEBUG ("Could not extract any frames from tag. Broken or empty tag"); gst_tag_list_free (work->tags); work->tags = NULL; - return ID3TAGS_BROKEN_TAG; + return FALSE; } /* Set day/month now if they were in a separate (obsolete) TDAT frame */ @@ -550,5 +561,5 @@ id3demux_id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size) } } - return ID3TAGS_READ_TAG; + return TRUE; } diff --git a/gst-libs/gst/tag/id3v2.h b/gst-libs/gst/tag/id3v2.h index 14a42de38b..d9ce504160 100644 --- a/gst-libs/gst/tag/id3v2.h +++ b/gst-libs/gst/tag/id3v2.h @@ -23,25 +23,11 @@ G_BEGIN_DECLS -/* private tag for storing unprocessed ID3v2 frames */ -#define GST_ID3_DEMUX_TAG_ID3V2_FRAME "private-id3v2-frame" - -#define ID3V1_TAG_SIZE 128 #define ID3V2_MARK_SIZE 3 -#define ID3V2_HDR_SIZE 10 +#define ID3V2_HDR_SIZE GST_TAG_ID3V2_HEADER_SIZE -typedef enum { - ID3TAGS_MORE_DATA, - ID3TAGS_READ_TAG, - ID3TAGS_BROKEN_TAG -} ID3TagsResult; - -/* From id3tags.c */ -guint id3demux_calc_id3v2_tag_size (GstBuffer * buf); -ID3TagsResult id3demux_read_id3v2_tag (GstBuffer *buffer, guint *id3v2_size, - GstTagList **tags); - -guint read_synch_uint (const guint8 * data, guint size); +/* From id3v2.c */ +guint id3v2_read_synch_uint (const guint8 * data, guint size); /* Things shared by id3tags.c and id3v2frames.c */ #define ID3V2_VERSION 0x0400 @@ -112,10 +98,20 @@ enum { ID3V2_FRAME_FORMAT_COMPRESSION | \ ID3V2_FRAME_FORMAT_ENCRYPTION) -/* From id3v2frames.c */ -gboolean id3demux_id3v2_parse_frame (ID3TagsWorking *work); +/* FIXME 0.11: remove 'private' bit from GST_TAG_ID3V2_FRAME */ +/** + * GST_TAG_ID3V2_FRAME: + * + * Contains a single unprocessed ID3v2 frame. (buffer) + * + * (Not public API for now) + */ +#define GST_TAG_ID3V2_FRAME "private-id3v2-frame" -guint8 * id3demux_ununsync_data (const guint8 * unsync_data, guint32 * size); +/* From id3v2frames.c */ +gboolean id3v2_parse_frame (ID3TagsWorking *work); + +guint8 * id3v2_ununsync_data (const guint8 * unsync_data, guint32 * size); G_END_DECLS diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index e51bbb78ec..f6b997cbd0 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -33,10 +33,7 @@ #include #endif -#include "id3tags.h" - -GST_DEBUG_CATEGORY_EXTERN (id3demux_debug); -#define GST_CAT_DEFAULT (id3demux_debug) +#include "id3v2.h" static gboolean parse_comment_frame (ID3TagsWorking * work); static gchar *parse_url_link_frame (ID3TagsWorking * work, @@ -65,7 +62,7 @@ static gboolean parse_picture_frame (ID3TagsWorking * work); #define ID3V2_ENCODING_UTF8 0x03 gboolean -id3demux_id3v2_parse_frame (ID3TagsWorking * work) +id3v2_parse_frame (ID3TagsWorking * work) { const gchar *tag_name; gboolean result = FALSE; @@ -112,7 +109,7 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) if (ID3V2_VER_MAJOR (work->hdr.version) == 3) { work->parse_size = GST_READ_UINT32_BE (frame_data); } else { - work->parse_size = read_synch_uint (frame_data, 4); + work->parse_size = id3v2_read_synch_uint (frame_data, 4); } frame_data += 4; frame_data_size -= 4; @@ -132,7 +129,7 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) if ((work->hdr.flags & ID3V2_HDR_FLAG_UNSYNC) != 0 || ((work->frame_flags & ID3V2_FRAME_FORMAT_UNSYNCHRONISATION) != 0)) { GST_DEBUG ("Un-unsyncing frame %s", work->frame_id); - uu_data = id3demux_ununsync_data (frame_data, &frame_data_size); + uu_data = id3v2_ununsync_data (frame_data, &frame_data_size); frame_data = uu_data; GST_MEMDUMP ("ID3v2 frame (un-unsyced)", frame_data, frame_data_size); } @@ -165,8 +162,8 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work) } work->parse_data = uncompressed_data; #else - GST_WARNING ("Compressed ID3v2 tag frame could not be decompressed" - " because gstid3demux was compiled without zlib support"); + GST_WARNING ("Compressed ID3v2 tag frame could not be decompressed, because" + " libgsttag-" GST_MAJORMINOR " was compiled without zlib support"); g_free (uu_data); return FALSE; #endif diff --git a/gst-libs/gst/tag/tag.h b/gst-libs/gst/tag/tag.h index 08ff9421c2..ba8f87849d 100644 --- a/gst-libs/gst/tag/tag.h +++ b/gst-libs/gst/tag/tag.h @@ -442,6 +442,14 @@ typedef enum { #define GST_TYPE_TAG_IMAGE_TYPE (gst_tag_image_type_get_type ()) GType gst_tag_image_type_get_type (void); +/** + * GST_TAG_ID3V2_HEADER_SIZE: + * + * ID3V2 header size considered minimum input for some functions. + * + * Since: 0.10.36 + */ +#define GST_TAG_ID3V2_HEADER_SIZE 10 /* functions for vorbis comment manipulation */ @@ -466,6 +474,10 @@ GstBuffer * gst_tag_list_to_vorbiscomment_buffer (const GstTagLis /* functions for ID3 tag manipulation */ +/* FIXME 0.11: inconsistent API naming: gst_tag_list_new_from_id3v1(), gst_tag_list_from_*_buffer(), + * gst_tag_list_from_id3v2_tag(). Also, note gst.tag.list_xyz() namespace vs. gst.tag_list_xyz(), + * which is a bit confusing and possibly doesn't map too well */ + guint gst_tag_id3_genre_count (void); const gchar * gst_tag_id3_genre_get (const guint id); GstTagList * gst_tag_list_new_from_id3v1 (const guint8 * data); @@ -480,6 +492,10 @@ gboolean gst_tag_list_add_id3_image (GstTagList * tag_list, guint image_data_len, guint id3_picture_type); +GstTagList * gst_tag_list_from_id3v2_tag (GstBuffer * buffer); + +guint gst_tag_get_id3v2_tag_size (GstBuffer * buffer); + /* functions to convert GstBuffers with xmp packets contents to GstTagLists and back */ GstTagList * gst_tag_list_from_xmp_buffer (const GstBuffer * buffer); GstBuffer * gst_tag_list_to_xmp_buffer (const GstTagList * list, diff --git a/gst-libs/gst/tag/tags.c b/gst-libs/gst/tag/tags.c index 51f3bc7011..f89b348c7e 100644 --- a/gst-libs/gst/tag/tags.c +++ b/gst-libs/gst/tag/tags.c @@ -26,6 +26,7 @@ #include #include #include "tag.h" +#include "id3v2.h" #include @@ -189,6 +190,10 @@ gst_tag_register_tags_internal (gpointer unused) G_TYPE_DOUBLE, _("image vertical ppi"), _("Media (image/video) intended vertical pixel density in ppi"), NULL); + gst_tag_register (GST_TAG_ID3V2_FRAME, GST_TAG_FLAG_META, + GST_TYPE_BUFFER, _("ID3v2 frame"), _("unparsed id3v2 tag frame"), + gst_tag_merge_use_first); + return NULL; } diff --git a/win32/common/libgsttag.def b/win32/common/libgsttag.def index f8572d838f..8fa522c525 100644 --- a/win32/common/libgsttag.def +++ b/win32/common/libgsttag.def @@ -5,6 +5,7 @@ EXPORTS gst_tag_from_id3_tag gst_tag_from_id3_user_tag gst_tag_from_vorbis_tag + gst_tag_get_id3v2_tag_size gst_tag_get_language_code_iso_639_1 gst_tag_get_language_code_iso_639_2B gst_tag_get_language_code_iso_639_2T @@ -25,6 +26,7 @@ EXPORTS gst_tag_list_add_id3_image gst_tag_list_from_exif_buffer gst_tag_list_from_exif_buffer_with_tiff_header + gst_tag_list_from_id3v2_tag gst_tag_list_from_vorbiscomment_buffer gst_tag_list_from_xmp_buffer gst_tag_list_new_from_id3v1 From f25c71510454bca5405d85197cb7f741c79e5ed9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Sun, 14 Aug 2011 01:47:41 +0100 Subject: [PATCH 133/140] tag: id3v2: add debug category for ID3 tag parsing --- gst-libs/gst/tag/id3v2.c | 22 ++++++++++++++++++++++ gst-libs/gst/tag/id3v2.h | 2 ++ gst-libs/gst/tag/id3v2frames.c | 4 ++++ 3 files changed, 28 insertions(+) diff --git a/gst-libs/gst/tag/id3v2.c b/gst-libs/gst/tag/id3v2.c index 5c99262046..a8bcdba4aa 100644 --- a/gst-libs/gst/tag/id3v2.c +++ b/gst-libs/gst/tag/id3v2.c @@ -31,6 +31,28 @@ static gboolean id3v2_frames_to_tag_list (ID3TagsWorking * work, guint size); +#ifndef GST_DISABLE_GST_DEBUG + +#define GST_CAT_DEFAULT id3v2_ensure_debug_category() + +GstDebugCategory * +id3v2_ensure_debug_category (void) +{ + static gsize cat_gonce = 0; + + if (g_once_init_enter (&cat_gonce)) { + gsize cat; + + cat = (gsize) _gst_debug_category_new ("id3v2", 0, "ID3v2 tag parsing"); + + g_once_init_leave (&cat_gonce, cat); + } + + return (GstDebugCategory *) cat_gonce; +} + +#endif /* GST_DISABLE_GST_DEBUG */ + guint id3v2_read_synch_uint (const guint8 * data, guint size) { diff --git a/gst-libs/gst/tag/id3v2.h b/gst-libs/gst/tag/id3v2.h index d9ce504160..fd21bb64ea 100644 --- a/gst-libs/gst/tag/id3v2.h +++ b/gst-libs/gst/tag/id3v2.h @@ -113,6 +113,8 @@ gboolean id3v2_parse_frame (ID3TagsWorking *work); guint8 * id3v2_ununsync_data (const guint8 * unsync_data, guint32 * size); +GstDebugCategory * id3v2_ensure_debug_category (void); + G_END_DECLS #endif diff --git a/gst-libs/gst/tag/id3v2frames.c b/gst-libs/gst/tag/id3v2frames.c index f6b997cbd0..78aa80e0f9 100644 --- a/gst-libs/gst/tag/id3v2frames.c +++ b/gst-libs/gst/tag/id3v2frames.c @@ -35,6 +35,10 @@ #include "id3v2.h" +#ifndef GST_DISABLE_GST_DEBUG +#define GST_CAT_DEFAULT id3v2_ensure_debug_category() +#endif + static gboolean parse_comment_frame (ID3TagsWorking * work); static gchar *parse_url_link_frame (ID3TagsWorking * work, const gchar ** tag_name); From 169c5033f065dd239a58b4362f588ca48f0af0a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Sun, 14 Aug 2011 13:32:12 +0100 Subject: [PATCH 134/140] tag: id3v2: avoid some relocations, make table static --- gst-libs/gst/tag/id3v2.c | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/gst-libs/gst/tag/id3v2.c b/gst-libs/gst/tag/id3v2.c index a8bcdba4aa..6dabddd16b 100644 --- a/gst-libs/gst/tag/id3v2.c +++ b/gst-libs/gst/tag/id3v2.c @@ -265,16 +265,15 @@ id3v2_frame_hdr_size (guint id3v2ver) } } -static const gchar *obsolete_frame_ids[] = { - "CRM", "EQU", "LNK", "RVA", "TIM", "TSI", /* From 2.2 */ - "EQUA", "RVAD", "TIME", "TRDA", "TSIZ", /* From 2.3 */ - NULL +static const gchar obsolete_frame_ids[][5] = { + {"CRM"}, {"EQU"}, {"LNK"}, {"RVA"}, {"TIM"}, {"TSI"}, /* From 2.2 */ + {"EQUA"}, {"RVAD"}, {"TIME"}, {"TRDA"}, {"TSIZ"} /* From 2.3 */ }; -const struct ID3v2FrameIDConvert +static const struct ID3v2FrameIDConvert { - const gchar *orig; - const gchar *new; + const gchar orig[5]; + const gchar new[5]; } frame_id_conversions[] = { /* 2.3.x frames */ { @@ -338,28 +337,24 @@ const struct ID3v2FrameIDConvert "WCM", "WCOM"}, { "WCP", "WCOP"}, { "WPB", "WPUB"}, { - "WXX", "WXXX"}, { - NULL, NULL} + "WXX", "WXXX"} }; static gboolean convert_fid_to_v240 (gchar * frame_id) { - gint i = 0; + gint i; - while (obsolete_frame_ids[i] != NULL) { + for (i = 0; i < G_N_ELEMENTS (obsolete_frame_ids); ++i) { if (strncmp (frame_id, obsolete_frame_ids[i], 5) == 0) return TRUE; - i++; } - i = 0; - while (frame_id_conversions[i].orig != NULL) { + for (i = 0; i < G_N_ELEMENTS (frame_id_conversions); ++i) { if (strncmp (frame_id, frame_id_conversions[i].orig, 5) == 0) { strcpy (frame_id, frame_id_conversions[i].new); return FALSE; } - i++; } return FALSE; } From 685eb3b954f2806bce985b8c0dd0a0de1b830d17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Sun, 14 Aug 2011 20:44:19 +0100 Subject: [PATCH 135/140] tag: id3v2: add specs to git for reference --- gst-libs/gst/tag/id3v2.3.0.txt | 1942 ++++++++++++++++++++++ gst-libs/gst/tag/id3v2.4.0-frames.txt | 1734 +++++++++++++++++++ gst-libs/gst/tag/id3v2.4.0-structure.txt | 733 ++++++++ 3 files changed, 4409 insertions(+) create mode 100644 gst-libs/gst/tag/id3v2.3.0.txt create mode 100644 gst-libs/gst/tag/id3v2.4.0-frames.txt create mode 100644 gst-libs/gst/tag/id3v2.4.0-structure.txt diff --git a/gst-libs/gst/tag/id3v2.3.0.txt b/gst-libs/gst/tag/id3v2.3.0.txt new file mode 100644 index 0000000000..5b26d638e8 --- /dev/null +++ b/gst-libs/gst/tag/id3v2.3.0.txt @@ -0,0 +1,1942 @@ +* id3v2.3.0 + +Informal Standard +Document: id3v2.3 +M. Nilsson +3rd February 1999 + + +1. ID3 tag version 2.3.0 + + +1.1. Status of this document + +This document is an informal standard and replaces the id3v2.2.0 standard. The +informal standard is released so that implementors could have a set standard +before a formal standard is set. The formal standard will use another version +or revision number if not identical to what is described in this document. The +contents in this document may change for clarifications but never for added or +altered functionallity. +Distribution of this document is unlimited. + +1.2. Abstract + +This document describes the ID3v2.3.0 standard, which is a more developed +version of the ID3v2 informal standard (version [:id3v2-00: 2.2.0]), evolved +from the ID3 tagging system. The ID3v2 offers a flexible way of storing +information about an audio file within itself to determine its origin and +contents. The information may be technical information, such as equalisation +curves, as well as related meta information, such as title, performer, +copyright etc. +Contents + + 1. ID3_tag_version_2.3.0 + + 1. Status_of_this_document + 2. Abstract + + 2. Conventions_in_this_document + 3. ID3v2_overview + + 1. ID3v2_header + 2. ID3v2_extended_header + 3. ID3v2_frame_overview + + 1. Frame_header_flags + + 4. Default_flags + + 4. Declared_ID3v2_frames + + 1. Unique_file_identifier + 2. Text_information_frames + + 1. Text_information_frames_-_details + 2. User_defined_text_information_frame + + 3. URL_link_frames + + 1. URL_link_frames_-_details + 2. User_defined_URL_link_frame + + 4. Involved_people_list + 5. Music_CD_identifier + 6. Event_timing_codes + 7. MPEG_location_lookup_table + 8. Synchronised_tempo_codes + 9. Unsychronised_lyrics/text_transcription + 10. Synchronised_lyrics/text + 11. Comments + 12. Relative_volume_adjustment + 13. Equalisation + 14. Reverb + 15. Attached_picture + 16. General_encapsulated_object + 17. Play_counter + 18. Popularimeter + 19. Recommended_buffer_size + 20. Audio_encryption + 21. Linked_information + 22. Position_synchronisation_frame + 23. Terms_of_use_frame + 24. Ownership_frame + 25. Commercial_frame + 26. Encryption_method_registration + 27. Group_identification_registration + 28. Private_frame + + 5. The_unsynchronisation_scheme + 6. Copyright + 7. References + 8. Appendix + + 1. Appendix_A_-_Genre_List_from_ID3v1 + + 9. Author's_Address + + + +2. Conventions in this document + +In the examples, text within "" is a text string exactly as it appears in a +file. Numbers preceded with $ are hexadecimal and numbers preceded with % are +binary. $xx is used to indicate a byte with unknown content. %x is used to +indicate a bit with unknown content. The most significant bit (MSB) of a byte +is called 'bit 7' and the least significant bit (LSB) is called 'bit 0'. +A tag is the whole tag described in this document. A frame is a block of +information in the tag. The tag consists of a header, frames and optional +padding. A field is a piece of information; one value, a string etc. A numeric +string is a string that consists of the characters 0-9 only. + + +3. ID3v2 overview + +The two biggest design goals were to be able to implement ID3v2 without +disturbing old software too much and that ID3v2 should be as flexible and +expandable as possible. +The first criterion is met by the simple fact that the MPEG decoding software +uses a syncsignal, embedded in the audiostream, to 'lock on to' the audio. +Since the ID3v2 tag doesn't contain a valid syncsignal, no software will +attempt to play the tag. If, for any reason, coincidence make a syncsignal +appear within the tag it will be taken care of by the 'unsynchronisation +scheme' described in section_5. +The second criterion has made a more noticeable impact on the design of the +ID3v2 tag. It is constructed as a container for several information blocks, +called frames, whose format need not be known to the software that encounters +them. At the start of every frame there is an identifier that explains the +frames' format and content, and a size descriptor that allows software to skip +unknown frames. +If a total revision of the ID3v2 tag should be needed, there is a version +number and a size descriptor in the ID3v2 header. +The ID3 tag described in this document is mainly targeted at files encoded with +MPEG-1/2 layer I, MPEG-1/2 layer II, MPEG-1/2 layer III and MPEG-2.5, but may +work with other types of encoded audio. +The bitorder in ID3v2 is most significant bit first (MSB). The byteorder in +multibyte numbers is most significant byte first (e.g. $12345678 would be +encoded $12 34 56 78). +It is permitted to include padding after all the final frame (at the end of the +ID3 tag), making the size of all the frames together smaller than the size +given in the head of the tag. A possible purpose of this padding is to allow +for adding a few additional frames or enlarge existing frames within the tag +without having to rewrite the entire file. The value of the padding bytes must +be $00. + +3.1. ID3v2 header + +The ID3v2 tag header, which should be the first information in the file, is 10 +bytes as follows: + + ID3v2/file identifier "ID3" + ID3v2 version $03 00 + ID3v2 flags %abc00000 + ID3v2 size 4 * %0xxxxxxx + +The first three bytes of the tag are always "ID3" to indicate that this is an +ID3v2 tag, directly followed by the two version bytes. The first byte of ID3v2 +version is it's major version, while the second byte is its revision number. In +this case this is ID3v2.3.0. All revisions are backwards compatible while major +versions are not. If software with ID3v2.2.0 and below support should encounter +version three or higher it should simply ignore the whole tag. Version and +revision will never be $FF. +The version is followed by one the ID3v2 flags field, of which currently only +three flags are used. + + + a - Unsynchronisation + Bit 7 in the 'ID3v2 flags' indicates whether or not unsynchronisation is + used (see section_5 for details); a set bit indicates usage. + + b - Extended header + The second bit (bit 6) indicates whether or not the header is followed by + an extended header. The extended header is described in section_3.2. + + c - Experimental indicator + The third bit (bit 5) should be used as an 'experimental indicator'. This + flag should always be set when the tag is in an experimental stage. + +All the other flags should be cleared. If one of these undefined flags are set +that might mean that the tag is not readable for a parser that does not know +the flags function. +The ID3v2 tag size is encoded with four bytes where the most significant bit +(bit 7) is set to zero in every byte, making a total of 28 bits. The zeroed +bits are ignored, so a 257 bytes long tag is represented as $00 00 02 01. +The ID3v2 tag size is the size of the complete tag after unsychronisation, +including padding, excluding the header but not excluding the extended header +(total tag size - 10). Only 28 bits (representing up to 256MB) are used in the +size description to avoid the introducuction of 'false syncsignals'. +An ID3v2 tag can be detected with the following pattern: + + $49 44 33 yy yy xx zz zz zz zz + +Where yy is less than $FF, xx is the 'flags' byte and zz is less than $80. + +3.2. ID3v2 extended header + +The extended header contains information that is not vital to the correct +parsing of the tag information, hence the extended header is optional. + + Extended header size $xx xx xx xx + Extended Flags $xx xx + Size of padding $xx xx xx xx + +Where the 'Extended header size', currently 6 or 10 bytes, excludes itself. The +'Size of padding' is simply the total tag size excluding the frames and the +headers, in other words the padding. The extended header is considered separate +from the header proper, and as such is subject to unsynchronisation. +The extended flags are a secondary flag set which describes further attributes +of the tag. These attributes are currently defined as follows + + %x0000000 00000000 + + + + x - CRC data present + If this flag is set four bytes of CRC-32 data is appended to the extended + header. The CRC should be calculated before unsynchronisation on the data + between the extended header and the padding, i.e. the frames and only the + frames. + + Total frame CRC $xx xx xx xx + + + +3.3. ID3v2 frame overview + +As the tag consists of a tag header and a tag body with one or more frames, all +the frames consists of a frame header followed by one or more fields containing +the actual information. The layout of the frame header: + + Frame ID $xx xx xx xx (four characters) + Size $xx xx xx xx + Flags $xx xx + +The frame ID made out of the characters capital A-Z and 0-9. Identifiers +beginning with "X", "Y" and "Z" are for experimental use and free for everyone +to use, without the need to set the experimental bit in the tag header. Have in +mind that someone else might have used the same identifier as you. All other +identifiers are either used or reserved for future use. +The frame ID is followed by a size descriptor, making a total header size of +ten bytes in every frame. The size is calculated as frame size excluding frame +header (frame size - 10). +In the frame header the size descriptor is followed by two flags bytes. These +flags are described in section_3.3.1. +There is no fixed order of the frames' appearance in the tag, although it is +desired that the frames are arranged in order of significance concerning the +recognition of the file. An example of such order: UFID, TIT2, MCDI, TRCK ... +A tag must contain at least one frame. A frame must be at least 1 byte big, +excluding the header. +If nothing else is said a string is represented as ISO-8859-1 characters in the +range $20 - $FF. Such strings are represented as , or if newlines are allowed, in the frame descriptions. All Unicode strings +use 16-bit unicode 2.0 (ISO/IEC 10646-1:1993, UCS-2). Unicode strings must +begin with the Unicode BOM ($FF FE or $FE FF) to identify the byte order. +All numeric strings and URLs are always encoded as ISO-8859-1. Terminated +strings are terminated with $00 if encoded with ISO-8859-1 and $00 00 if +encoded as unicode. If nothing else is said newline character is forbidden. In +ISO-8859-1 a new line is represented, when allowed, with $0A only. Frames that +allow different types of text encoding have a text encoding description byte +directly after the frame size. If ISO-8859-1 is used this byte should be $00, +if Unicode is used it should be $01. Strings dependent on encoding is +represented as , or if newlines are allowed. Any empty Unicode strings which +are NULL-terminated may have the Unicode BOM followed by a Unicode NULL ($FF FE +00 00 or $FE FF 00 00). +The three byte language field is used to describe the language of the frame's +content, according to ISO-639-2. +All URLs may be relative, e.g. "picture.png", "../doc.txt". +If a frame is longer than it should be, e.g. having more fields than specified +in this document, that indicates that additions to the frame have been made in +a later version of the ID3v2 standard. This is reflected by the revision number +in the header of the tag. + +3.3.1. Frame header flags + +In the frame header the size descriptor is followed by two flags bytes. All +unused flags must be cleared. The first byte is for 'status messages' and the +second byte is for encoding purposes. If an unknown flag is set in the first +byte the frame may not be changed without the bit cleared. If an unknown flag +is set in the second byte it is likely to not be readable. The flags field is +defined as follows. + + %abc00000 %ijk00000 + + + + a - Tag alter preservation + This flag tells the software what to do with this frame if it is unknown + and the tag is altered in any way. This applies to all kinds of + alterations, including adding more padding and reordering the frames. + + 0 Frame should be preserved. + 1 Frame should be discarded. + + + b - File alter preservation + This flag tells the software what to do with this frame if it is unknown + and the file, excluding the tag, is altered. This does not apply when the + audio is completely replaced with other audio data. + + 0 Frame should be preserved. + 1 Frame should be discarded. + + + c - Read only + This flag, if set, tells the software that the contents of this frame is + intended to be read only. Changing the contents might break something, + e.g. a signature. If the contents are changed, without knowledge in why + the frame was flagged read only and without taking the proper means to + compensate, e.g. recalculating the signature, the bit should be cleared. + + i - Compression + This flag indicates whether or not the frame is compressed. + + 0 Frame is not compressed. + 1 Frame is compressed using [#ZLIB zlib] with 4 bytes for + 'decompressed size' appended to the frame header. + + + j - Encryption + This flag indicates wether or not the frame is enrypted. If set one byte + indicating with which method it was encrypted will be appended to the + frame header. See section_4.26. for more information about encryption + method registration. + + 0 Frame is not encrypted. + 1 Frame is encrypted. + + + k - Grouping identity + This flag indicates whether or not this frame belongs in a group with + other frames. If set a group identifier byte is added to the frame + header. Every frame with the same group identifier belongs to the same + group. + + 0 Frame does not contain group information + 1 Frame contains group information + + +Some flags indicates that the frame header is extended with additional +information. This information will be added to the frame header in the same +order as the flags indicating the additions. I.e. the four bytes of +decompressed size will preceed the encryption method byte. These additions to +the frame header, while not included in the frame header size but are included +in the 'frame size' field, are not subject to encryption or compression. + +3.4. Default flags + +The default settings for the frames described in this document can be divided +into the following classes. The flags may be set differently if found more +suitable by the software. + + 1. Discarded if tag is altered, discarded if file is altered. + + o None. + + 2. Discarded if tag is altered, preserved if file is altered. + + o None. + + 3. Preserved if tag is altered, discarded if file is altered. + + o AENC, ETCO, EQUA, MLLT, POSS, SYLT, SYTC, RVAD, TENC, TLEN, TSIZ + + 4. Preserved if tag is altered, preserved if file is altered. + + o The rest of the frames. + + + + +4. Declared ID3v2 frames + +The following frames are declared in this draft. + + 4.20 AENC [#sec4.20 Audio encryption] + 4.15 APIC [#sec4.15 Attached picture] + 4.11 COMM [#sec4.11 Comments] + 4.25 COMR [#sec4.25 Commercial frame] + 4.26 ENCR [#sec4.26 Encryption method registration] + 4.13 EQUA [#sec4.13 Equalization] + 4.6 ETCO [#sec4.6 Event timing codes] + 4.16 GEOB [#sec4.16 General encapsulated object] + 4.27 GRID [#sec4.27 Group identification registration] + 4.4 IPLS [#sec4.4 Involved people list] + 4.21 LINK [#sec4.21 Linked information] + 4.5 MCDI [#sec4.5 Music CD identifier] + 4.7 MLLT [#sec4.7 MPEG location lookup table] + 4.24 OWNE [#sec4.24 Ownership frame] + 4.28 PRIV [#sec4.28 Private frame] + 4.17 PCNT [#sec4.17 Play counter] + 4.18 POPM [#sec4.18 Popularimeter] + 4.22 POSS [#sec4.22 Position synchronisation frame] + 4.19 RBUF [#sec4.19 Recommended buffer size] + 4.12 RVAD [#sec4.12 Relative volume adjustment] + 4.14 RVRB [#sec4.14 Reverb] + 4.10 SYLT [#sec4.10 Synchronized lyric/text] + 4.8 SYTC [#sec4.8 Synchronized tempo codes] + 4.2.1 TALB [#TALB Album/Movie/Show title] + 4.2.1 TBPM [#TBPM BPM (beats per minute)] + 4.2.1 TCOM [#TCOM Composer] + 4.2.1 TCON [#TCON Content type] + 4.2.1 TCOP [#TCOP Copyright message] + 4.2.1 TDAT [#TDAT Date] + 4.2.1 TDLY [#TDLY Playlist delay] + 4.2.1 TENC [#TENC Encoded by] + 4.2.1 TEXT [#TEXT Lyricist/Text writer] + 4.2.1 TFLT [#TFLT File type] + 4.2.1 TIME [#TIME Time] + 4.2.1 TIT1 [#TIT1 Content group description] + 4.2.1 TIT2 [#TIT2 Title/songname/content description] + 4.2.1 TIT3 [#TIT3 Subtitle/Description refinement] + 4.2.1 TKEY [#TKEY Initial key] + 4.2.1 TLAN [#TLAN Language(s)] + 4.2.1 TLEN [#TLEN Length] + 4.2.1 TMED [#TMED Media type] + 4.2.1 TOAL [#TOAL Original album/movie/show title] + 4.2.1 TOFN [#TOFN Original filename] + 4.2.1 TOLY [#TOLY Original lyricist(s)/text writer(s)] + 4.2.1 TOPE [#TOPE Original artist(s)/performer(s)] + 4.2.1 TORY [#TORY Original release year] + 4.2.1 TOWN [#TOWN File owner/licensee] + 4.2.1 TPE1 [#TPE1 Lead performer(s)/Soloist(s)] + 4.2.1 TPE2 [#TPE2 Band/orchestra/accompaniment] + 4.2.1 TPE3 [#TPE3 Conductor/performer refinement] + 4.2.1 TPE4 [#TPE4 Interpreted, remixed, or otherwise modified by] + 4.2.1 TPOS [#TPOS Part of a set] + 4.2.1 TPUB [#TPUB Publisher] + 4.2.1 TRCK [#TRCK Track number/Position in set] + 4.2.1 TRDA [#TRDA Recording dates] + 4.2.1 TRSN [#TRSN Internet radio station name] + 4.2.1 TRSO [#TRSO Internet radio station owner] + 4.2.1 TSIZ [#TSIZ Size] + 4.2.1 TSRC [#TSRC ISRC (international standard recording code)] + 4.2.1 TSSE [#TSEE Software/Hardware and settings used for encoding] + 4.2.1 TYER [#TYER Year] + 4.2.2 TXXX [#TXXX User defined text information frame] + 4.1 UFID [#sec4.1 Unique file identifier] + 4.23 USER [#sec4.23 Terms of use] + 4.9 USLT [#sec4.9 Unsychronized lyric/text transcription] + 4.3.1 WCOM [#WCOM Commercial information] + 4.3.1 WCOP [#WCOP Copyright/Legal information] + 4.3.1 WOAF [#WOAF Official audio file webpage] + 4.3.1 WOAR [#WOAR Official artist/performer webpage] + 4.3.1 WOAS [#WOAS Official audio source webpage] + 4.3.1 WORS [#WORS Official internet radio station homepage] + 4.3.1 WPAY [#WPAY Payment] + 4.3.1 WPUB [#WPUB Publishers official webpage] + 4.3.2 WXXX [#WXXX User defined URL link frame] + + +4.1. Unique file identifier + +This frame's purpose is to be able to identify the audio file in a database +that may contain more information relevant to the content. Since +standardisation of such a database is beyond this document, all frames begin +with a null-terminated string with a URL containing an email address, or a link +to a location where an email address can be found, that belongs to the +organisation responsible for this specific database implementation. Questions +regarding the database should be sent to the indicated email address. The URL +should not be used for the actual database queries. The string "http:// +www.id3.org/dummy/ufid.html" should be used for tests. Software that isn't told +otherwise may safely remove such frames. The 'Owner identifier' must be non- +empty (more than just a termination). The 'Owner identifier' is then followed +by the actual identifier, which may be up to 64 bytes. There may be more than +one "UFID" frame in a tag, but only one with the same 'Owner identifier'. + +
+ Owner identifier $00 + Identifier + + +4.2. Text information frames + +The text information frames are the most important frames, containing +information like artist, album and more. There may only be one text information +frame of its kind in an tag. If the textstring is followed by a termination +($00 (00)) all the following information should be ignored and not be +displayed. All text frame identifiers begin with "T". Only text frame +identifiers begin with "T", with the exception of the "TXXX" frame. All the +text information frames have the following format: + +
+ Text encoding $xx + Information + + +4.2.1. Text information frames - details + + + + TALB + The 'Album/Movie/Show title' frame is intended for the title of the + recording(/source of sound) which the audio in the file is taken from. + + + + TBPM + The 'BPM' frame contains the number of beats per minute in the mainpart + of the audio. The BPM is an integer and represented as a numerical + string. + + + + TCOM + The 'Composer(s)' frame is intended for the name of the composer(s). They + are seperated with the "/" character. + + + + TCON + The 'Content type', which previously was stored as a one byte numeric + value only, is now a numeric string. You may use one or several of the + types as ID3v1.1 did or, since the category list would be impossible to + maintain with accurate and up to date categories, define your own. + + References to the ID3v1 genres can be made by, as first byte, enter "(" + followed by a number from the genres list (appendix A) and ended with a + ")" character. This is optionally followed by a refinement, e.g. "(21)" + or "(4)Eurodisco". Several references can be made in the same frame, e.g. + "(51)(39)". If the refinement should begin with a "(" character it should + be replaced with "((", e.g. "((I can figure out any genre)" or "(55)((I + think...)". The following new content types is defined in ID3v2 and is + implemented in the same way as the numerig content types, e.g. "(RX)". + + RX Remix + CR Cover + + + + + TCOP + The 'Copyright message' frame, which must begin with a year and a space + character (making five characters), is intended for the copyright holder + of the original sound, not the audio file itself. The absence of this + frame means only that the copyright information is unavailable or has + been removed, and must not be interpreted to mean that the sound is + public domain. Every time this field is displayed the field must be + preceded with "Copyright © ". + + + + TDAT + The 'Date' frame is a numeric string in the DDMM format containing the + date for the recording. This field is always four characters long. + + + + TDLY + The 'Playlist delay' defines the numbers of milliseconds of silence + between every song in a playlist. The player should use the "ETC" frame, + if present, to skip initial silence and silence at the end of the audio + to match the 'Playlist delay' time. The time is represented as a numeric + string. + + + + TENC + The 'Encoded by' frame contains the name of the person or organisation + that encoded the audio file. This field may contain a copyright message, + if the audio file also is copyrighted by the encoder. + + + + TEXT + The 'Lyricist(s)/Text writer(s)' frame is intended for the writer(s) of + the text or lyrics in the recording. They are seperated with the "/ + " character. + + + + TFLT + The 'File type' frame indicates which type of audio this tag defines. The + following type and refinements are defined: + + MPG MPEG Audio + /1 MPEG 1/2 layer I + /2 MPEG 1/2 layer II + /3 MPEG 1/2 layer III + /2.5 MPEG 2.5 + /AAC Advanced audio compression + VQF Transform-domain Weighted Interleave Vector Quantization + PCM Pulse Code Modulated audio + + +but other types may be used, not for these types though. This is used in a +similar way to the predefined types in the "TMED" frame, but without +parentheses. If this frame is not present audio type is assumed to be "MPG". + + + TIME + The 'Time' frame is a numeric string in the HHMM format containing the + time for the recording. This field is always four characters long. + + + + TIT1 + The 'Content group description' frame is used if the sound belongs to a + larger category of sounds/music. For example, classical music is often + sorted in different musical sections (e.g. "Piano Concerto", "Weather - + Hurricane"). + + + + TIT2 + The 'Title/Songname/Content description' frame is the actual name of the + piece (e.g. "Adagio", "Hurricane Donna"). + + + + TIT3 + The 'Subtitle/Description refinement' frame is used for information + directly related to the contents title (e.g. "Op. 16" or "Performed live + at Wembley"). + + + + TKEY + The 'Initial key' frame contains the musical key in which the sound + starts. It is represented as a string with a maximum length of three + characters. The ground keys are represented with "A","B","C","D","E", "F" + and "G" and halfkeys represented with "b" and "#". Minor is represented + as "m". Example "Cbm". Off key is represented with an "o" only. + + + + TLAN + The 'Language(s)' frame should contain the languages of the text or + lyrics spoken or sung in the audio. The language is represented with + three characters according to ISO-639-2. If more than one language is + used in the text their language codes should follow according to their + usage. + + + + TLEN + The 'Length' frame contains the length of the audiofile in milliseconds, + represented as a numeric string. + + + + TMED + The 'Media type' frame describes from which media the sound originated. + This may be a text string or a reference to the predefined media types + found in the list below. References are made within "(" and ")" and are + optionally followed by a text refinement, e.g. "(MC) with four channels". + If a text refinement should begin with a "(" character it should be + replaced with "((" in the same way as in the "TCO" frame. Predefined + refinements is appended after the media type, e.g. "(CD/A)" or "(VID/PAL/ + VHS)". + + DIG Other digital media + /A Analog transfer from media + + ANA Other analog media + /WAC Wax cylinder + /8CA 8-track tape cassette + + CD CD + /A Analog transfer from media + /DD DDD + /AD ADD + /AA AAD + + LD Laserdisc + /A Analog transfer from media + + TT Turntable records + /33 33.33 rpm + /45 45 rpm + /71 71.29 rpm + /76 76.59 rpm + /78 78.26 rpm + /80 80 rpm + + MD MiniDisc + /A Analog transfer from media + + DAT DAT + /A Analog transfer from media + /1 standard, 48 kHz/16 bits, linear + /2 mode 2, 32 kHz/16 bits, linear + /3 mode 3, 32 kHz/12 bits, nonlinear, low speed + /4 mode 4, 32 kHz/12 bits, 4 channels + /5 mode 5, 44.1 kHz/16 bits, linear + /6 mode 6, 44.1 kHz/16 bits, 'wide track' play + + DCC DCC + /A Analog transfer from media + + DVD DVD + /A Analog transfer from media + + TV Television + /PAL PAL + /NTSC NTSC + /SECAM SECAM + + VID Video + /PAL PAL + /NTSC NTSC + /SECAM SECAM + /VHS VHS + /SVHS S-VHS + /BETA BETAMAX + + RAD Radio + /FM FM + /AM AM + /LW LW + /MW MW + + TEL Telephone + /I ISDN + + MC MC (normal cassette) + /4 4.75 cm/s (normal speed for a two sided cassette) + /9 9.5 cm/s + /I Type I cassette (ferric/normal) + /II Type II cassette (chrome) + /III Type III cassette (ferric chrome) + /IV Type IV cassette (metal) + + REE Reel + /9 9.5 cm/s + /19 19 cm/s + /38 38 cm/s + /76 76 cm/s + /I Type I cassette (ferric/normal) + /II Type II cassette (chrome) + /III Type III cassette (ferric chrome) + /IV Type IV cassette (metal) + + + + + TOAL + The 'Original album/movie/show title' frame is intended for the title of + the original recording (or source of sound), if for example the music in + the file should be a cover of a previously released song. + + + + TOFN + The 'Original filename' frame contains the preferred filename for the + file, since some media doesn't allow the desired length of the filename. + The filename is case sensitive and includes its suffix. + + + + TOLY + The 'Original lyricist(s)/text writer(s)' frame is intended for the text + writer(s) of the original recording, if for example the music in the file + should be a cover of a previously released song. The text writers are + seperated with the "/" character. + + + + TOPE + The 'Original artist(s)/performer(s)' frame is intended for the performer + (s) of the original recording, if for example the music in the file + should be a cover of a previously released song. The performers are + seperated with the "/" character. + + + + TORY + The 'Original release year' frame is intended for the year when the + original recording, if for example the music in the file should be a + cover of a previously released song, was released. The field is formatted + as in the "TYER" frame. + + + + TOWN + The 'File owner/licensee' frame contains the name of the owner or + licensee of the file and it's contents. + + + + TPE1 + The 'Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group' is + used for the main artist(s). They are seperated with the "/" character. + + + + TPE2 + The 'Band/Orchestra/Accompaniment' frame is used for additional + information about the performers in the recording. + + + + TPE3 + The 'Conductor' frame is used for the name of the conductor. + + + + TPE4 + The 'Interpreted, remixed, or otherwise modified by' frame contains more + information about the people behind a remix and similar interpretations + of another existing piece. + + + + TPOS + The 'Part of a set' frame is a numeric string that describes which part + of a set the audio came from. This frame is used if the source described + in the "TALB" frame is divided into several mediums, e.g. a double CD. + The value may be extended with a "/" character and a numeric string + containing the total number of parts in the set. E.g. "1/2". + + + + TPUB + The 'Publisher' frame simply contains the name of the label or publisher. + + + + TRCK + The 'Track number/Position in set' frame is a numeric string containing + the order number of the audio-file on its original recording. This may be + extended with a "/" character and a numeric string containing the total + numer of tracks/elements on the original recording. E.g. "4/9". + + + + TRDA + The 'Recording dates' frame is a intended to be used as complement to the + "TYER", "TDAT" and "TIME" frames. E.g. "4th-7th June, 12th June" in + combination with the "TYER" frame. + + + + TRSN + The 'Internet radio station name' frame contains the name of the internet + radio station from which the audio is streamed. + + + + TRSO + The 'Internet radio station owner' frame contains the name of the owner + of the internet radio station from which the audio is streamed. + + + + TSIZ + The 'Size' frame contains the size of the audiofile in bytes, excluding + the ID3v2 tag, represented as a numeric string. + + + + TSRC + The 'ISRC' frame should contain the International Standard Recording Code + (ISRC) (12 characters). + + + + TSSE + The 'Software/Hardware and settings used for encoding' frame includes the + used audio encoder and its settings when the file was encoded. Hardware + refers to hardware encoders, not the computer on which a program was run. + + + + TYER + The 'Year' frame is a numeric string with a year of the recording. This + frames is always four characters long (until the year 10000). + + +4.2.2. User defined text information frame + +This frame is intended for one-string text information concerning the audiofile +in a similar way to the other "T"-frames. The frame body consists of a +description of the string, represented as a terminated string, followed by the +actual string. There may be more than one "TXXX" frame in each tag, but only +one with the same description. + +
+ Text encoding $xx + Description $00 (00) + Value + + +4.3. URL link frames + +With these frames dynamic data such as webpages with touring information, price +information or plain ordinary news can be added to the tag. There may only be +one URL link frame of its kind in an tag, except when stated otherwise in the +frame description. If the textstring is followed by a termination ($00 (00)) +all the following information should be ignored and not be displayed. All URL +link frame identifiers begins with "W". Only URL link frame identifiers begins +with "W". All URL link frames have the following format: + +
+ URL + + +4.3.1. URL link frames - details + + + + WCOM + The 'Commercial information' frame is a URL pointing at a webpage with + information such as where the album can be bought. There may be more than + one "WCOM" frame in a tag, but not with the same content. + + + + WCOP + The 'Copyright/Legal information' frame is a URL pointing at a webpage + where the terms of use and ownership of the file is described. + + + + WOAF + The 'Official audio file webpage' frame is a URL pointing at a file + specific webpage. + + + + WOAR + The 'Official artist/performer webpage' frame is a URL pointing at the + artists official webpage. There may be more than one "WOAR" frame in a + tag if the audio contains more than one performer, but not with the same + content. + + + + WOAS + The 'Official audio source webpage' frame is a URL pointing at the + official webpage for the source of the audio file, e.g. a movie. + + + + WORS + The 'Official internet radio station homepage' contains a URL pointing at + the homepage of the internet radio station. + + + + WPAY + The 'Payment' frame is a URL pointing at a webpage that will handle the + process of paying for this file. + + + + WPUB + The 'Publishers official webpage' frame is a URL pointing at the official + wepage for the publisher. + + +4.3.2. User defined URL link frame + +This frame is intended for URL links concerning the audiofile in a similar way +to the other "W"-frames. The frame body consists of a description of the +string, represented as a terminated string, followed by the actual URL. The URL +is always encoded with ISO-8859-1. There may be more than one "WXXX" frame in +each tag, but only one with the same description. + +
+ Text encoding $xx + Description $00 (00) + URL + + +4.4. Involved people list + +Since there might be a lot of people contributing to an audio file in various +ways, such as musicians and technicians, the 'Text information frames' are +often insufficient to list everyone involved in a project. The 'Involved people +list' is a frame containing the names of those involved, and how they were +involved. The body simply contains a terminated string with the involvement +directly followed by a terminated string with the involvee followed by a new +involvement and so on. There may only be one "IPLS" frame in each tag. + +
+ Text encoding $xx + People list strings + + +4.5. Music CD identifier + +This frame is intended for music that comes from a CD, so that the CD can be +identified in databases such as the CDDB. The frame consists of a binary dump +of the Table Of Contents, TOC, from the CD, which is a header of 4 bytes and +then 8 bytes/track on the CD plus 8 bytes for the 'lead out' making a maximum +of 804 bytes. The offset to the beginning of every track on the CD should be +described with a four bytes absolute CD-frame address per track, and not with +absolute time. This frame requires a present and valid "TRCK" frame, even if +the CD's only got one track. There may only be one "MCDI" frame in each tag. + +
+ CD TOC + + +4.6. Event timing codes + +This frame allows synchronisation with key events in a song or sound. The +header is: + +
+ Time stamp format $xx + +Where time stamp format is: + + $01 Absolute time, 32 bit sized, using MPEG frames as unit + $02 Absolute time, 32 bit sized, using milliseconds as unit + +Abolute time means that every stamp contains the time from the beginning of the +file. +Followed by a list of key events in the following format: + + Type of event $xx + Time stamp $xx (xx ...) + +The 'Time stamp' is set to zero if directly at the beginning of the sound or +after the previous event. All events should be sorted in chronological order. +The type of event is as follows: + + $00 padding (has no meaning) + $01 end of initial silence + $02 intro start + $03 mainpart start + $04 outro start + $05 outro end + $06 verse start + $07 refrain start + $08 interlude start + $09 theme start + $0A variation start + $0B key change + $0C time change + $0D momentary unwanted noise (Snap, Crackle & Pop) + $0E sustained noise + $0F sustained noise end + $10 intro end + $11 mainpart end + $12 verse end + $13 refrain end + $14 theme end + $15-$DF reserved for future use + $E0-$EF not predefined sync 0-F + $F0-$FC reserved for future use + $FD audio end (start of silence) + $FE audio file ends + $FF one more byte of events follows (all the following bytes with the + value $FF have the same function) + +Terminating the start events such as "intro start" is not required. The 'Not +predefined sync's ($E0-EF) are for user events. You might want to synchronise +your music to something, like setting of an explosion on-stage, turning on your +screensaver etc. +There may only be one "ETCO" frame in each tag. + +4.7. MPEG location lookup table + +To increase performance and accuracy of jumps within a MPEG audio file, frames +with timecodes in different locations in the file might be useful. The ID3v2 +frame includes references that the software can use to calculate positions in +the file. After the frame header is a descriptor of how much the 'frame +counter' should increase for every reference. If this value is two then the +first reference points out the second frame, the 2nd reference the 4th frame, +the 3rd reference the 6th frame etc. In a similar way the 'bytes between +reference' and 'milliseconds between reference' points out bytes and +milliseconds respectively. +Each reference consists of two parts; a certain number of bits, as defined in +'bits for bytes deviation', that describes the difference between what is said +in 'bytes between reference' and the reality and a certain number of bits, as +defined in 'bits for milliseconds deviation', that describes the difference +between what is said in 'milliseconds between reference' and the reality. The +number of bits in every reference, i.e. 'bits for bytes deviation'+'bits for +milliseconds deviation', must be a multiple of four. There may only be one +"MLLT" frame in each tag. + +
+ MPEG frames between reference $xx xx + Bytes between reference $xx xx xx + Milliseconds between reference $xx xx xx + Bits for bytes deviation $xx + Bits for milliseconds dev. $xx + +Then for every reference the following data is included; + + Deviation in bytes %xxx.... + Deviation in milliseconds %xxx.... + + +4.8. Synchronised tempo codes + +For a more accurate description of the tempo of a musical piece this frame +might be used. After the header follows one byte describing which time stamp +format should be used. Then follows one or more tempo codes. Each tempo code +consists of one tempo part and one time part. The tempo is in BPM described +with one or two bytes. If the first byte has the value $FF, one more byte +follows, which is added to the first giving a range from 2 - 510 BPM, since $00 +and $01 is reserved. $00 is used to describe a beat-free time period, which is +not the same as a music-free time period. $01 is used to indicate one single +beat-stroke followed by a beat-free period. +The tempo descriptor is followed by a time stamp. Every time the tempo in the +music changes, a tempo descriptor may indicate this for the player. All tempo +descriptors should be sorted in chronological order. The first beat-stroke in a +time-period is at the same time as the beat description occurs. There may only +be one "SYTC" frame in each tag. + +
+ Time stamp format $xx + Tempo data + +Where time stamp format is: + + $01 Absolute time, 32 bit sized, using MPEG frames as unit + $02 Absolute time, 32 bit sized, using milliseconds as unit + +Abolute time means that every stamp contains the time from the beginning of the +file. + +4.9. Unsychronised lyrics/text transcription + +This frame contains the lyrics of the song or a text transcription of other +vocal activities. The head includes an encoding descriptor and a content +descriptor. The body consists of the actual text. The 'Content descriptor' is a +terminated string. If no descriptor is entered, 'Content descriptor' is $00 +(00) only. Newline characters are allowed in the text. There may be more than +one 'Unsynchronised lyrics/text transcription' frame in each tag, but only one +with the same language and content descriptor. + +
+ Text encoding $xx + Language $xx xx xx + Content descriptor $00 (00) + Lyrics/text + + +4.10. Synchronised lyrics/text + +This is another way of incorporating the words, said or sung lyrics, in the +audio file as text, this time, however, in sync with the audio. It might also +be used to describing events e.g. occurring on a stage or on the screen in sync +with the audio. The header includes a content descriptor, represented with as +terminated textstring. If no descriptor is entered, 'Content descriptor' is $00 +(00) only. + +
+ Text encoding $xx + Language $xx xx xx + Time stamp format $xx + Content type $xx + Content descriptor $00 (00) + +Encoding: + + $00 ISO-8859-1 character set is used => $00 is sync identifier. + $01 Unicode character set is used => $00 00 is sync identifier. + +Content type: + + $00 is other + $01 is lyrics + $02 is text transcription + $03 is movement/part name (e.g. "Adagio") + $04 is events (e.g. "Don Quijote enters the stage") + $05 is chord (e.g. "Bb F Fsus") + $06 is trivia/'pop up' information + +Time stamp format is: + + $01 Absolute time, 32 bit sized, using MPEG frames as unit + $02 Absolute time, 32 bit sized, using milliseconds as unit + +Abolute time means that every stamp contains the time from the beginning of the +file. +The text that follows the frame header differs from that of the unsynchronised +lyrics/text transcription in one major way. Each syllable (or whatever size of +text is considered to be convenient by the encoder) is a null terminated string +followed by a time stamp denoting where in the sound file it belongs. Each sync +thus has the following structure: + + Terminated text to be synced (typically a syllable) + Sync identifier (terminator to above string) $00 (00) + Time stamp $xx (xx ...) + +The 'time stamp' is set to zero or the whole sync is omitted if located +directly at the beginning of the sound. All time stamps should be sorted in +chronological order. The sync can be considered as a validator of the +subsequent string. +Newline ($0A) characters are allowed in all "SYLT" frames and should be used +after every entry (name, event etc.) in a frame with the content type $03 - +$04. +A few considerations regarding whitespace characters: Whitespace separating +words should mark the beginning of a new word, thus occurring in front of the +first syllable of a new word. This is also valid for new line characters. A +syllable followed by a comma should not be broken apart with a sync (both the +syllable and the comma should be before the sync). +An example: The "USLT" passage + + "Strangers in the night" $0A "Exchanging glances" + +would be "SYLT" encoded as: + + "Strang" $00 xx xx "ers" $00 xx xx " in" $00 xx xx " the" $00 + xx xx " night" $00 xx xx 0A "Ex" $00 xx xx "chang" $00 xx xx + "ing" $00 xx xx "glan" $00 xx xx "ces" $00 xx xx + +There may be more than one "SYLT" frame in each tag, but only one with the same +language and content descriptor. + +4.11. Comments + +This frame is indended for any kind of full text information that does not fit +in any other frame. It consists of a frame header followed by encoding, +language and content descriptors and is ended with the actual comment as a text +string. Newline characters are allowed in the comment text string. There may be +more than one comment frame in each tag, but only one with the same language +and content descriptor. + +
+ Text encoding $xx + Language $xx xx xx + Short content descrip. $00 (00) + The actual text + + +4.12. Relative volume adjustment + +This is a more subjective function than the previous ones. It allows the user +to say how much he wants to increase/decrease the volume on each channel while +the file is played. The purpose is to be able to align all files to a reference +volume, so that you don't have to change the volume constantly. This frame may +also be used to balance adjust the audio. If the volume peak levels are known +then this could be described with the 'Peak volume right' and 'Peak volume +left' field. If Peakvolume is not known these fields could be left zeroed or, +if no other data follows, be completely omitted. There may only be one "RVAD" +frame in each tag. + +
+ Increment/decrement %00xxxxxx + Bits used for volume descr. $xx + Relative volume change, right $xx xx (xx ...) + Relative volume change, left $xx xx (xx ...) + Peak volume right $xx xx (xx ...) + Peak volume left $xx xx (xx ...) + +In the increment/decrement field bit 0 is used to indicate the right channel +and bit 1 is used to indicate the left channel. 1 is increment and 0 is +decrement. +The 'bits used for volume description' field is normally $10 (16 bits) for MPEG +2 layer I, II and III and MPEG 2.5. This value may not be $00. The volume is +always represented with whole bytes, padded in the beginning (highest bits) +when 'bits used for volume description' is not a multiple of eight. +This datablock is then optionally followed by a volume definition for the left +and right back channels. If this information is appended to the frame the first +two channels will be treated as front channels. In the increment/decrement +field bit 2 is used to indicate the right back channel and bit 3 for the left +back channel. + + Relative volume change, right back $xx xx (xx ...) + Relative volume change, left back $xx xx (xx ...) + Peak volume right back $xx xx (xx ...) + Peak volume left back $xx xx (xx ...) + +If the center channel adjustment is present the following is appended to the +existing frame, after the left and right back channels. The center channel is +represented by bit 4 in the increase/decrease field. + + Relative volume change, center $xx xx (xx ...) + Peak volume center $xx xx (xx ...) + +If the bass channel adjustment is present the following is appended to the +existing frame, after the center channel. The bass channel is represented by +bit 5 in the increase/decrease field. + + Relative volume change, bass $xx xx (xx ...) + Peak volume bass $xx xx (xx ...) + + +4.13. Equalisation + +This is another subjective, alignment frame. It allows the user to predefine an +equalisation curve within the audio file. There may only be one "EQUA" frame in +each tag. + +
+ Adjustment bits $xx + +The 'adjustment bits' field defines the number of bits used for representation +of the adjustment. This is normally $10 (16 bits) for MPEG 2 layer I, II and +III and MPEG 2.5. This value may not be $00. +This is followed by 2 bytes + ('adjustment bits' rounded up to the nearest +byte) for every equalisation band in the following format, giving a frequency +range of 0 - 32767Hz: + + Increment/decrement %x (MSB of the Frequency) + Frequency (lower 15 bits) + Adjustment $xx (xx ...) + +The increment/decrement bit is 1 for increment and 0 for decrement. The +equalisation bands should be ordered increasingly with reference to frequency. +All frequencies don't have to be declared. The equalisation curve in the +reading software should be interpolated between the values in this frame. Three +equal adjustments for three subsequent frequencies. A frequency should only be +described once in the frame. + +4.14. Reverb + +Yet another subjective one. You may here adjust echoes of different kinds. +Reverb left/right is the delay between every bounce in ms. Reverb bounces left/ +right is the number of bounces that should be made. $FF equals an infinite +number of bounces. Feedback is the amount of volume that should be returned to +the next echo bounce. $00 is 0%, $FF is 100%. If this value were $7F, there +would be 50% volume reduction on the first bounce, 50% of that on the second +and so on. Left to left means the sound from the left bounce to be played in +the left speaker, while left to right means sound from the left bounce to be +played in the right speaker. +'Premix left to right' is the amount of left sound to be mixed in the right +before any reverb is applied, where $00 id 0% and $FF is 100%. 'Premix right to +left' does the same thing, but right to left. Setting both premix to $FF would +result in a mono output (if the reverb is applied symmetric). There may only be +one "RVRB" frame in each tag. + +
+ Reverb left (ms) $xx xx + Reverb right (ms) $xx xx + Reverb bounces, left $xx + Reverb bounces, right $xx + Reverb feedback, left to left $xx + Reverb feedback, left to right $xx + Reverb feedback, right to right $xx + Reverb feedback, right to left $xx + Premix left to right $xx + Premix right to left $xx + + +4.15. Attached picture + +This frame contains a picture directly related to the audio file. Image format +is the MIME type and subtype for the image. In the event that the MIME media +type name is omitted, "image/" will be implied. The "image/png" or "image/jpeg" +picture format should be used when interoperability is wanted. Description is a +short description of the picture, represented as a terminated textstring. The +description has a maximum length of 64 characters, but may be empty. There may +be several pictures attached to one file, each in their individual "APIC" +frame, but only one with the same content descriptor. There may only be one +picture with the picture type declared as picture type $01 and $02 +respectively. There is the possibility to put only a link to the image file by +using the 'MIME type' "-->" and having a complete URL instead of picture data. +The use of linked files should however be used sparingly since there is the +risk of separation of files. + +
+ Text encoding $xx + MIME type $00 + Picture type $xx + Description $00 (00) + Picture data + +Picture type: + + $00 Other + $01 32x32 pixels 'file icon' (PNG only) + $02 Other file icon + $03 Cover (front) + $04 Cover (back) + $05 Leaflet page + $06 Media (e.g. lable side of CD) + $07 Lead artist/lead performer/soloist + $08 Artist/performer + $09 Conductor + $0A Band/Orchestra + $0B Composer + $0C Lyricist/text writer + $0D Recording Location + $0E During recording + $0F During performance + $10 Movie/video screen capture + $11 A bright coloured fish + $12 Illustration + $13 Band/artist logotype + $14 Publisher/Studio logotype + + +4.16. General encapsulated object + +In this frame any type of file can be encapsulated. After the header, 'Frame +size' and 'Encoding' follows 'MIME type' represented as as a terminated string +encoded with ISO-8859-1. The filename is case sensitive and is encoded as +'Encoding'. Then follows a content description as terminated string, encoded as +'Encoding'. The last thing in the frame is the actual object. The first two +strings may be omitted, leaving only their terminations. There may be more than +one "GEOB" frame in each tag, but only one with the same content descriptor. + +
+ Text encoding $xx + MIME type $00 + Filename $00 (00) + Content description $00 (00) + Encapsulated object + + +4.17. Play counter + +This is simply a counter of the number of times a file has been played. The +value is increased by one every time the file begins to play. There may only be +one "PCNT" frame in each tag. When the counter reaches all one's, one byte is +inserted in front of the counter thus making the counter eight bits bigger. The +counter must be at least 32-bits long to begin with. + +
+ Counter $xx xx xx xx (xx ...) + + +4.18. Popularimeter + +The purpose of this frame is to specify how good an audio file is. Many +interesting applications could be found to this frame such as a playlist that +features better audiofiles more often than others or it could be used to +profile a person's taste and find other 'good' files by comparing people's +profiles. The frame is very simple. It contains the email address to the user, +one rating byte and a four byte play counter, intended to be increased with one +for every time the file is played. The email is a terminated string. The rating +is 1-255 where 1 is worst and 255 is best. 0 is unknown. If no personal counter +is wanted it may be omitted. When the counter reaches all one's, one byte is +inserted in front of the counter thus making the counter eight bits bigger in +the same away as the play counter ("PCNT"). There may be more than one "POPM" +frame in each tag, but only one with the same email address. + +
+ Email to user $00 + Rating $xx + Counter $xx xx xx xx (xx ...) + + +4.19. Recommended buffer size + +Sometimes the server from which a audio file is streamed is aware of +transmission or coding problems resulting in interruptions in the audio stream. +In these cases, the size of the buffer can be recommended by the server using +this frame. If the 'embedded info flag' is true (1) then this indicates that an +ID3 tag with the maximum size described in 'Buffer size' may occur in the +audiostream. In such case the tag should reside between two MPEG frames, if the +audio is MPEG encoded. If the position of the next tag is known, 'offset to +next tag' may be used. The offset is calculated from the end of tag in which +this frame resides to the first byte of the header in the next. This field may +be omitted. Embedded tags are generally not recommended since this could render +unpredictable behaviour from present software/hardware. +For applications like streaming audio it might be an idea to embed tags into +the audio stream though. If the clients connects to individual connections like +HTTP and there is a possibility to begin every transmission with a tag, then +this tag should include a 'recommended buffer size' frame. If the client is +connected to a arbitrary point in the stream, such as radio or multicast, then +the 'recommended buffer size' frame should be included in every tag. Every tag +that is picked up after the initial/first tag is to be considered as an update +of the previous one. E.g. if there is a "TIT2" frame in the first received tag +and one in the second tag, then the first should be 'replaced' with the second. +The 'Buffer size' should be kept to a minimum. There may only be one "RBUF" +frame in each tag. + +
+ Buffer size $xx xx xx + Embedded info flag %0000000x + Offset to next tag $xx xx xx xx + + +4.20. Audio encryption + +This frame indicates if the actual audio stream is encrypted, and by whom. +Since standardisation of such encrypion scheme is beyond this document, all +"AENC" frames begin with a terminated string with a URL containing an email +address, or a link to a location where an email address can be found, that +belongs to the organisation responsible for this specific encrypted audio file. +Questions regarding the encrypted audio should be sent to the email address +specified. If a $00 is found directly after the 'Frame size' and the audiofile +indeed is encrypted, the whole file may be considered useless. +After the 'Owner identifier', a pointer to an unencrypted part of the audio can +be specified. The 'Preview start' and 'Preview length' is described in frames. +If no part is unencrypted, these fields should be left zeroed. After the +'preview length' field follows optionally a datablock required for decryption +of the audio. There may be more than one "AENC" frames in a tag, but only one +with the same 'Owner identifier'. + +
+ Owner identifier $00 + Preview start $xx xx + Preview length $xx xx + Encryption info + + +4.21. Linked information + +To keep space waste as low as possible this frame may be used to link +information from another ID3v2 tag that might reside in another audio file or +alone in a binary file. It is recommended that this method is only used when +the files are stored on a CD-ROM or other circumstances when the risk of file +seperation is low. The frame contains a frame identifier, which is the frame +that should be linked into this tag, a URL field, where a reference to the file +where the frame is given, and additional ID data, if needed. Data should be +retrieved from the first tag found in the file to which this link points. There +may be more than one "LINK" frame in a tag, but only one with the same +contents. A linked frame is to be considered as part of the tag and has the +same restrictions as if it was a physical part of the tag (i.e. only one "RVRB" +frame allowed, whether it's linked or not). + +
+ Frame identifier $xx xx xx + URL $00 + ID and additional data + +Frames that may be linked and need no additional data are "IPLS", "MCID", +"ETCO", "MLLT", "SYTC", "RVAD", "EQUA", "RVRB", "RBUF", the text information +frames and the URL link frames. +The "TXXX", "APIC", "GEOB" and "AENC" frames may be linked with the content +descriptor as additional ID data. +The "COMM", "SYLT" and "USLT" frames may be linked with three bytes of language +descriptor directly followed by a content descriptor as additional ID data. + +4.22. Position synchronisation frame + +This frame delivers information to the listener of how far into the audio +stream he picked up; in effect, it states the time offset of the first frame in +the stream. The frame layout is: + + + Time stamp format $xx + Position $xx (xx ...) + +Where time stamp format is: + + $01 Absolute time, 32 bit sized, using MPEG frames as unit + $02 Absolute time, 32 bit sized, using milliseconds as unit + +and position is where in the audio the listener starts to receive, i.e. the +beginning of the next frame. If this frame is used in the beginning of a file +the value is always 0. There may only be one "POSS" frame in each tag. + +4.23. Terms of use frame + +This frame contains a brief description of the terms of use and ownership of +the file. More detailed information concerning the legal terms might be +available through the "WCOP" frame. Newlines are allowed in the text. There may +only be one "USER" frame in a tag. + +
+ Text encoding $xx + Language $xx xx xx + The actual text + + +4.24. Ownership frame + +The ownership frame might be used as a reminder of a made transaction or, if +signed, as proof. Note that the "USER" and "TOWN" frames are good to use in +conjunction with this one. The frame begins, after the frame ID, size and +encoding fields, with a 'price payed' field. The first three characters of this +field contains the currency used for the transaction, encoded according to ISO- +4217 alphabetic currency code. Concatenated to this is the actual price payed, +as a numerical string using "." as the decimal separator. Next is an 8 +character date string (YYYYMMDD) followed by a string with the name of the +seller as the last field in the frame. There may only be one "OWNE" frame in a +tag. + +
+ Text encoding $xx + Price payed $00 + Date of purch. + Seller + + +4.25. Commercial frame + +This frame enables several competing offers in the same tag by bundling all +needed information. That makes this frame rather complex but it's an easier +solution than if one tries to achieve the same result with several frames. The +frame begins, after the frame ID, size and encoding fields, with a price string +field. A price is constructed by one three character currency code, encoded +according to ISO-4217 alphabetic currency code, followed by a numerical value +where "." is used as decimal seperator. In the price string several prices may +be concatenated, seperated by a "/" character, but there may only be one +currency of each type. +The price string is followed by an 8 character date string in the format +YYYYMMDD, describing for how long the price is valid. After that is a contact +URL, with which the user can contact the seller, followed by a one byte +'received as' field. It describes how the audio is delivered when bought +according to the following list: + + $00 Other + $01 Standard CD album with other songs + $02 Compressed audio on CD + $03 File over the Internet + $04 Stream over the Internet + $05 As note sheets + $06 As note sheets in a book with other sheets + $07 Music on other media + $08 Non-musical merchandise + +Next follows a terminated string with the name of the seller followed by a +terminated string with a short description of the product. The last thing is +the ability to include a company logotype. The first of them is the 'Picture +MIME type' field containing information about which picture format is used. In +the event that the MIME media type name is omitted, "image/" will be implied. +Currently only "image/png" and "image/jpeg" are allowed. This format string is +followed by the binary picture data. This two last fields may be omitted if no +picture is to attach. + +
+ Text encoding $xx + Price string $00 + Valid until + Contact URL $00 + Received as $xx + Name of seller $00 (00) + Description $00 (00) + Picture MIME type $00 + Seller logo + + +4.26. Encryption method registration + +To identify with which method a frame has been encrypted the encryption method +must be registered in the tag with this frame. The 'Owner identifier' is a +null-terminated string with a URL containing an email address, or a link to a +location where an email address can be found, that belongs to the organisation +responsible for this specific encryption method. Questions regarding the +encryption method should be sent to the indicated email address. The 'Method +symbol' contains a value that is associated with this method throughout the +whole tag. Values below $80 are reserved. The 'Method symbol' may optionally be +followed by encryption specific data. There may be several "ENCR" frames in a +tag but only one containing the same symbol and only one containing the same +owner identifier. The method must be used somewhere in the tag. See section +3.3.1, flag j for more information. + +
+ Owner identifier $00 + Method symbol $xx + Encryption data + + +4.27. Group identification registration + +This frame enables grouping of otherwise unrelated frames. This can be used +when some frames are to be signed. To identify which frames belongs to a set of +frames a group identifier must be registered in the tag with this frame. The +'Owner identifier' is a null-terminated string with a URL containing an email +address, or a link to a location where an email address can be found, that +belongs to the organisation responsible for this grouping. Questions regarding +the grouping should be sent to the indicated email address. The 'Group symbol' +contains a value that associates the frame with this group throughout the whole +tag. Values below $80 are reserved. The 'Group symbol' may optionally be +followed by some group specific data, e.g. a digital signature. There may be +several "GRID" frames in a tag but only one containing the same symbol and only +one containing the same owner identifier. The group symbol must be used +somewhere in the tag. See section_3.3.1, flag j for more information. + +
+ Owner identifier $00 + Group symbol $xx + Group dependent data + + +4.28. Private frame + +This frame is used to contain information from a software producer that its +program uses and does not fit into the other frames. The frame consists of an +'Owner identifier' string and the binary data. The 'Owner identifier' is a +null-terminated string with a URL containing an email address, or a link to a +location where an email address can be found, that belongs to the organisation +responsible for the frame. Questions regarding the frame should be sent to the +indicated email address. The tag may contain more than one "PRIV" frame but +only with different contents. It is recommended to keep the number of "PRIV" +frames as low as possible. + +
+ Owner identifier $00 + The private data + + + +5. The unsynchronisation scheme + +The only purpose of the 'unsynchronisation scheme' is to make the ID3v2 tag as +compatible as possible with existing software. There is no use in +'unsynchronising' tags if the file is only to be processed by new software. +Unsynchronisation may only be made with MPEG 2 layer I, II and III and MPEG 2.5 +files. +Whenever a false synchronisation is found within the tag, one zeroed byte is +inserted after the first false synchronisation byte. The format of a correct +sync that should be altered by ID3 encoders is as follows: + + %11111111 111xxxxx + +And should be replaced with: + + %11111111 00000000 111xxxxx + +This has the side effect that all $FF 00 combinations have to be altered, so +they won't be affected by the decoding process. Therefore all the $FF 00 +combinations have to be replaced with the $FF 00 00 combination during the +unsynchronisation. +To indicate usage of the unsynchronisation, the first bit in 'ID3 flags' should +be set. This bit should only be set if the tag contains a, now corrected, false +synchronisation. The bit should only be clear if the tag does not contain any +false synchronisations. +Do bear in mind, that if a compression scheme is used by the encoder, the +unsynchronisation scheme should be applied *afterwards*. When decoding a +compressed, 'unsynchronised' file, the 'unsynchronisation scheme' should be +parsed first, decompression afterwards. +If the last byte in the tag is $FF, and there is a need to eliminate false +synchronisations in the tag, at least one byte of padding should be added. + + +6. Copyright + +Copyright © Martin Nilsson 1998. All Rights Reserved. +This document and translations of it may be copied and furnished to others, and +derivative works that comment on or otherwise explain it or assist in its +implementation may be prepared, copied, published and distributed, in whole or +in part, without restriction of any kind, provided that a reference to this +document is included on all such copies and derivative works. However, this +document itself may not be modified in any way and reissued as the original +document. +The limited permissions granted above are perpetual and will not be revoked. +This document and the information contained herein is provided on an "AS IS" +basis and THE AUTHORS DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING +BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION HEREIN WILL NOT +INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR +A PARTICULAR PURPOSE. + + +7. References + +[CDDB] Compact Disc Data Base http://www.cddb.com + +[ID3v2] Martin Nilsson, "ID3v2_informal_standard". + + [ISO-639-2] ISO/FDIS 639-2. Codes for the representation of names of +languages, Part 2: Alpha-3 code. Technical committee / subcommittee: TC 37 / SC +2 + + [ISO-4217] ISO 4217:1995. Codes for the representation of currencies and +funds. Technical committee / subcommittee: TC 68 + + [ISO-8859-1] ISO/IEC DIS 8859-1. 8-bit single-byte coded graphic character +sets, Part 1: Latin alphabet No. 1. Technical committee / subcommittee: JTC 1 / +SC 2 + + [ISRC] ISO 3901:1986 International Standard Recording Code (ISRC). Technical +committee / subcommittee: TC 46 / SC 9 + + [JFIF] JPEG File Interchange Format, version 1.02, http://www.w3.org/Graphics/ +JPEG/jfif.txt + + [MIME] Freed, N. and N. Borenstein, "Multipurpose Internet Mail Extensions +(MIME) Part One: Format of Internet Message Bodies", RFC 2045, November 1996., +ftp://ftp.isi.edu/in-notes/rfc2045.txt + + [MPEG] ISO/IEC 11172-3:1993. Coding of moving pictures and associated audio +for digital storage media at up to about 1,5 Mbit/s, Part 3: Audio. Technical +committee / subcommittee: JTC 1 / SC 29 and ISO/IEC 13818-3:1995 Generic coding +of moving pictures and associated audio information, Part 3: Audio. Technical +committee / subcommittee: JTC 1 / SC 29 and ISO/IEC DIS 13818-3 Generic coding +of moving pictures and associated audio information, Part 3: Audio (Revision of +ISO/IEC 13818-3:1995) + + [PNG] Portable Network Graphics, version 1.0, http://www.w3.org/TR/REC-png- +multi.html + + [UNICODE] ISO/IEC 10646-1:1993. Universal Multiple-Octet Coded Character Set +(UCS), Part 1: Architecture and Basic Multilingual Plane. Technical committee / +subcommittee: JTC 1 / SC 2, http://www.unicode.org + + [URL] T. Berners-Lee, L. Masinter & M. McCahill, "Uniform Resource +Locators (URL).", RFC 1738, December 1994., ftp://ftp.isi.edu/in-notes/ +rfc1738.txt + + [ZLIB] P. Deutsch, Aladdin Enterprises & J-L. Gailly, "ZLIB Compressed +Data Format Specification version 3.3", RFC 1950, May 1996., url:ftp:// +ftp.isi.edu/in-notes/rfc1950.txt + + + +8. Appendix + + +8.1. Appendix A - Genre List from ID3v1 + +The following genres is defined in ID3v1 + + 0. Blues + 1. Classic Rock + 2. Country + 3. Dance + 4. Disco + 5. Funk + 6. Grunge + 7. Hip-Hop + 8. Jazz + 9. Metal + 10. New Age + 11. Oldies + 12. Other + 13. Pop + 14. R&B + 15. Rap + 16. Reggae + 17. Rock + 18. Techno + 19. Industrial + 20. Alternative + 21. Ska + 22. Death Metal + 23. Pranks + 24. Soundtrack + 25. Euro-Techno + 26. Ambient + 27. Trip-Hop + 28. Vocal + 29. Jazz+Funk + 30. Fusion + 31. Trance + 32. Classical + 33. Instrumental + 34. Acid + 35. House + 36. Game + 37. Sound Clip + 38. Gospel + 39. Noise + 40. AlternRock + 41. Bass + 42. Soul + 43. Punk + 44. Space + 45. Meditative + 46. Instrumental Pop + 47. Instrumental Rock + 48. Ethnic + 49. Gothic + 50. Darkwave + 51. Techno-Industrial + 52. Electronic + 53. Pop-Folk + 54. Eurodance + 55. Dream + 56. Southern Rock + 57. Comedy + 58. Cult + 59. Gangsta + 60. Top 40 + 61. Christian Rap + 62. Pop/Funk + 63. Jungle + 64. Native American + 65. Cabaret + 66. New Wave + 67. Psychadelic + 68. Rave + 69. Showtunes + 70. Trailer + 71. Lo-Fi + 72. Tribal + 73. Acid Punk + 74. Acid Jazz + 75. Polka + 76. Retro + 77. Musical + 78. Rock & Roll + 79. Hard Rock + +The following genres are Winamp extensions + + 80. Folk + 81. Folk-Rock + 82. National Folk + 83. Swing + 84. Fast Fusion + 85. Bebob + 86. Latin + 87. Revival + 88. Celtic + 89. Bluegrass + 90. Avantgarde + 91. Gothic Rock + 92. Progressive Rock + 93. Psychedelic Rock + 94. Symphonic Rock + 95. Slow Rock + 96. Big Band + 97. Chorus + 98. Easy Listening + 99. Acoustic + 100. Humour + 101. Speech + 102. Chanson + 103. Opera + 104. Chamber Music + 105. Sonata + 106. Symphony + 107. Booty Bass + 108. Primus + 109. Porn Groove + 110. Satire + 111. Slow Jam + 112. Club + 113. Tango + 114. Samba + 115. Folklore + 116. Ballad + 117. Power Ballad + 118. Rhythmic Soul + 119. Freestyle + 120. Duet + 121. Punk Rock + 122. Drum Solo + 123. A capella + 124. Euro-House + 125. Dance Hall + + + +9. Author's Address + +Written by +Martin Nilsson +Rydsven 246 C. 30 +S-584 34 Linkoping +Sweden + +Email: nilsson at id3.org + +Edited by +Dirk Mahoney +57 Pechey Street +Chermside Q +Australia 4032 + +Email: dirk at id3.org + +Johan Sundstrom +Alsttersgatan 5 A. 34 +S-584 35 Linkoping +Sweden + +Email: johan at id3.org +id3v2.3.0 (last edited 2006-12-18 06:25:16 by DanONeill) diff --git a/gst-libs/gst/tag/id3v2.4.0-frames.txt b/gst-libs/gst/tag/id3v2.4.0-frames.txt new file mode 100644 index 0000000000..74a21bed3d --- /dev/null +++ b/gst-libs/gst/tag/id3v2.4.0-frames.txt @@ -0,0 +1,1734 @@ +$Id$ + +Informal standard M. Nilsson +Document: id3v2.4.0-frames.txt 1st November 2000 + + + ID3 tag version 2.4.0 - Native Frames + +Status of this document + + This document is an informal standard and replaces the ID3v2.3.0 + standard [ID3v2]. A formal standard will use another revision number + even if the content is identical to document. The contents in this + document may change for clarifications but never for added or altered + functionallity. + + Distribution of this document is unlimited. + + +Abstract + + This document describes the frames natively supported by ID3v2.4.0, + which is a revised version of the ID3v2 informal standard [ID3v2.3.0] + version 2.3.0. The ID3v2 offers a flexible way of storing audio meta + information within audio file itself. The information may be + technical information, such as equalisation curves, as well as title, + performer, copyright etc. + + ID3v2.4.0 is meant to be as close as possible to ID3v2.3.0 in order + to allow for implementations to be revised as easily as possible. + + +1. Table of contents + + 2. Conventions in this document + 3. Default flags + 4. Declared ID3v2 frames + 4.1. Unique file identifier + 4.2. Text information frames + 4.2.1. Identification frames + 4.2.2. Involved persons frames + 4.2.3. Derived and subjective properties frames + 4.2.4. Rights and license frames + 4.2.5. Other text frames + 4.2.6. User defined text information frame + 4.3. URL link frames + 4.3.1. URL link frames - details + 4.3.2. User defined URL link frame + 4.4. Music CD Identifier + 4.5. Event timing codes + 4.6. MPEG location lookup table + 4.7. Synced tempo codes + 4.8. Unsynchronised lyrics/text transcription + 4.9. Synchronised lyrics/text + 4.10. Comments + 4.11. Relative volume adjustment (2) + 4.12. Equalisation (2) + 4.13. Reverb + 4.14. Attached picture + 4.15. General encapsulated object + 4.16. Play counter + 4.17. Popularimeter + 4.18. Recommended buffer size + 4.19. Audio encryption + 4.20. Linked information + 4.21. Position synchronisation frame + 4.22. Terms of use + 4.23. Ownership frame + 4.24. Commercial frame + 4.25. Encryption method registration + 4.26. Group identification registration + 4.27. Private frame + 4.28. Signature frame + 4.29. Seek frame + 4.30. Audio seek point index + 5. Copyright + 6. References + 7. Appendix + A. Appendix A - Genre List from ID3v1 + 8. Author's Address + + +2. Conventions in this document + + Text within "" is a text string exactly as it appears in a tag. + Numbers preceded with $ are hexadecimal and numbers preceded with % + are binary. $xx is used to indicate a byte with unknown content. %x + is used to indicate a bit with unknown content. The most significant + bit (MSB) of a byte is called 'bit 7' and the least significant bit + (LSB) is called 'bit 0'. + + A tag is the whole tag described the ID3v2 main structure document + [ID3v2-strct]. A frame is a block of information in the tag. The tag + consists of a header, frames and optional padding. A field is a piece + of information; one value, a string etc. A numeric string is a string + that consists of the characters "0123456789" only. + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this + document are to be interpreted as described in RFC 2119 [KEYWORDS]. + + +3. Default flags + + The default settings for the frames described in this document can be + divided into the following classes. The flags may be set differently + if found more suitable by the software. + + 1. Discarded if tag is altered, discarded if file is altered. + + None. + + 2. Discarded if tag is altered, preserved if file is altered. + + None. + + 3. Preserved if tag is altered, discarded if file is altered. + + ASPI, AENC, ETCO, EQU2, MLLT, POSS, SEEK, SYLT, SYTC, RVA2, TENC, + TLEN + + 4. Preserved if tag is altered, preserved if file is altered. + + The rest of the frames. + + +4. Declared ID3v2 frames + + The following frames are declared in this draft. + + 4.19 AENC Audio encryption + 4.14 APIC Attached picture + 4.30 ASPI Audio seek point index + + 4.10 COMM Comments + 4.24 COMR Commercial frame + + 4.25 ENCR Encryption method registration + 4.12 EQU2 Equalisation (2) + 4.5 ETCO Event timing codes + + 4.15 GEOB General encapsulated object + 4.26 GRID Group identification registration + + 4.20 LINK Linked information + + 4.4 MCDI Music CD identifier + 4.6 MLLT MPEG location lookup table + + 4.23 OWNE Ownership frame + + 4.27 PRIV Private frame + 4.16 PCNT Play counter + 4.17 POPM Popularimeter + 4.21 POSS Position synchronisation frame + + 4.18 RBUF Recommended buffer size + 4.11 RVA2 Relative volume adjustment (2) + 4.13 RVRB Reverb + + 4.29 SEEK Seek frame + 4.28 SIGN Signature frame + 4.9 SYLT Synchronised lyric/text + 4.7 SYTC Synchronised tempo codes + + 4.2.1 TALB Album/Movie/Show title + 4.2.3 TBPM BPM (beats per minute) + 4.2.2 TCOM Composer + 4.2.3 TCON Content type + 4.2.4 TCOP Copyright message + 4.2.5 TDEN Encoding time + 4.2.5 TDLY Playlist delay + 4.2.5 TDOR Original release time + 4.2.5 TDRC Recording time + 4.2.5 TDRL Release time + 4.2.5 TDTG Tagging time + 4.2.2 TENC Encoded by + 4.2.2 TEXT Lyricist/Text writer + 4.2.3 TFLT File type + 4.2.2 TIPL Involved people list + 4.2.1 TIT1 Content group description + 4.2.1 TIT2 Title/songname/content description + 4.2.1 TIT3 Subtitle/Description refinement + 4.2.3 TKEY Initial key + 4.2.3 TLAN Language(s) + 4.2.3 TLEN Length + 4.2.2 TMCL Musician credits list + 4.2.3 TMED Media type + 4.2.3 TMOO Mood + 4.2.1 TOAL Original album/movie/show title + 4.2.5 TOFN Original filename + 4.2.2 TOLY Original lyricist(s)/text writer(s) + 4.2.2 TOPE Original artist(s)/performer(s) + 4.2.4 TOWN File owner/licensee + 4.2.2 TPE1 Lead performer(s)/Soloist(s) + 4.2.2 TPE2 Band/orchestra/accompaniment + 4.2.2 TPE3 Conductor/performer refinement + 4.2.2 TPE4 Interpreted, remixed, or otherwise modified by + 4.2.1 TPOS Part of a set + 4.2.4 TPRO Produced notice + 4.2.4 TPUB Publisher + 4.2.1 TRCK Track number/Position in set + 4.2.4 TRSN Internet radio station name + 4.2.4 TRSO Internet radio station owner + 4.2.5 TSOA Album sort order + 4.2.5 TSOP Performer sort order + 4.2.5 TSOT Title sort order + 4.2.1 TSRC ISRC (international standard recording code) + 4.2.5 TSSE Software/Hardware and settings used for encoding + 4.2.1 TSST Set subtitle + 4.2.2 TXXX User defined text information frame + + 4.1 UFID Unique file identifier + 4.22 USER Terms of use + 4.8 USLT Unsynchronised lyric/text transcription + + 4.3.1 WCOM Commercial information + 4.3.1 WCOP Copyright/Legal information + 4.3.1 WOAF Official audio file webpage + 4.3.1 WOAR Official artist/performer webpage + 4.3.1 WOAS Official audio source webpage + 4.3.1 WORS Official Internet radio station homepage + 4.3.1 WPAY Payment + 4.3.1 WPUB Publishers official webpage + 4.3.2 WXXX User defined URL link frame + + +4.1. Unique file identifier + + This frame's purpose is to be able to identify the audio file in a + database, that may provide more information relevant to the content. + Since standardisation of such a database is beyond this document, all + UFID frames begin with an 'owner identifier' field. It is a null- + terminated string with a URL [URL] containing an email address, or a + link to a location where an email address can be found, that belongs + to the organisation responsible for this specific database + implementation. Questions regarding the database should be sent to + the indicated email address. The URL should not be used for the + actual database queries. The string + "http://www.id3.org/dummy/ufid.html" should be used for tests. The + 'Owner identifier' must be non-empty (more than just a termination). + The 'Owner identifier' is then followed by the actual identifier, + which may be up to 64 bytes. There may be more than one "UFID" frame + in a tag, but only one with the same 'Owner identifier'. + +
+ Owner identifier $00 + Identifier + + +4.2. Text information frames + + The text information frames are often the most important frames, + containing information like artist, album and more. There may only be + one text information frame of its kind in an tag. All text + information frames supports multiple strings, stored as a null + separated list, where null is reperesented by the termination code + for the charater encoding. All text frame identifiers begin with "T". + Only text frame identifiers begin with "T", with the exception of the + "TXXX" frame. All the text information frames have the following + format: + +
+ Text encoding $xx + Information + + +4.2.1. Identification frames + + TIT1 + The 'Content group description' frame is used if the sound belongs to + a larger category of sounds/music. For example, classical music is + often sorted in different musical sections (e.g. "Piano Concerto", + "Weather - Hurricane"). + + TIT2 + The 'Title/Songname/Content description' frame is the actual name of + the piece (e.g. "Adagio", "Hurricane Donna"). + + TIT3 + The 'Subtitle/Description refinement' frame is used for information + directly related to the contents title (e.g. "Op. 16" or "Performed + live at Wembley"). + + TALB + The 'Album/Movie/Show title' frame is intended for the title of the + recording (or source of sound) from which the audio in the file is + taken. + + TOAL + The 'Original album/movie/show title' frame is intended for the title + of the original recording (or source of sound), if for example the + music in the file should be a cover of a previously released song. + + TRCK + The 'Track number/Position in set' frame is a numeric string + containing the order number of the audio-file on its original + recording. This MAY be extended with a "/" character and a numeric + string containing the total number of tracks/elements on the original + recording. E.g. "4/9". + + TPOS + The 'Part of a set' frame is a numeric string that describes which + part of a set the audio came from. This frame is used if the source + described in the "TALB" frame is divided into several mediums, e.g. a + double CD. The value MAY be extended with a "/" character and a + numeric string containing the total number of parts in the set. E.g. + "1/2". + + TSST + The 'Set subtitle' frame is intended for the subtitle of the part of + a set this track belongs to. + + TSRC + The 'ISRC' frame should contain the International Standard Recording + Code [ISRC] (12 characters). + + +4.2.2. Involved persons frames + + TPE1 + The 'Lead artist/Lead performer/Soloist/Performing group' is + used for the main artist. + + TPE2 + The 'Band/Orchestra/Accompaniment' frame is used for additional + information about the performers in the recording. + + TPE3 + The 'Conductor' frame is used for the name of the conductor. + + TPE4 + The 'Interpreted, remixed, or otherwise modified by' frame contains + more information about the people behind a remix and similar + interpretations of another existing piece. + + TOPE + The 'Original artist/performer' frame is intended for the performer + of the original recording, if for example the music in the file + should be a cover of a previously released song. + + TEXT + The 'Lyricist/Text writer' frame is intended for the writer of the + text or lyrics in the recording. + + TOLY + The 'Original lyricist/text writer' frame is intended for the + text writer of the original recording, if for example the music in + the file should be a cover of a previously released song. + + TCOM + The 'Composer' frame is intended for the name of the composer. + + TMCL + The 'Musician credits list' is intended as a mapping between + instruments and the musician that played it. Every odd field is an + instrument and every even is an artist or a comma delimited list of + artists. + + TIPL + The 'Involved people list' is very similar to the musician credits + list, but maps between functions, like producer, and names. + + TENC + The 'Encoded by' frame contains the name of the person or + organisation that encoded the audio file. This field may contain a + copyright message, if the audio file also is copyrighted by the + encoder. + + +4.2.3. Derived and subjective properties frames + + TBPM + The 'BPM' frame contains the number of beats per minute in the + main part of the audio. The BPM is an integer and represented as a + numerical string. + + TLEN + The 'Length' frame contains the length of the audio file in + milliseconds, represented as a numeric string. + + TKEY + The 'Initial key' frame contains the musical key in which the sound + starts. It is represented as a string with a maximum length of three + characters. The ground keys are represented with "A","B","C","D","E", + "F" and "G" and halfkeys represented with "b" and "#". Minor is + represented as "m", e.g. "Dbm" $00. Off key is represented with an + "o" only. + + TLAN + The 'Language' frame should contain the languages of the text or + lyrics spoken or sung in the audio. The language is represented with + three characters according to ISO-639-2 [ISO-639-2]. If more than one + language is used in the text their language codes should follow + according to the amount of their usage, e.g. "eng" $00 "sve" $00. + + TCON + The 'Content type', which ID3v1 was stored as a one byte numeric + value only, is now a string. You may use one or several of the ID3v1 + types as numerical strings, or, since the category list would be + impossible to maintain with accurate and up to date categories, + define your own. Example: "21" $00 "Eurodisco" $00 + + You may also use any of the following keywords: + + RX Remix + CR Cover + + TFLT + The 'File type' frame indicates which type of audio this tag defines. + The following types and refinements are defined: + + MIME MIME type follows + MPG MPEG Audio + /1 MPEG 1/2 layer I + /2 MPEG 1/2 layer II + /3 MPEG 1/2 layer III + /2.5 MPEG 2.5 + /AAC Advanced audio compression + VQF Transform-domain Weighted Interleave Vector Quantisation + PCM Pulse Code Modulated audio + + but other types may be used, but not for these types though. This is + used in a similar way to the predefined types in the "TMED" frame, + but without parentheses. If this frame is not present audio type is + assumed to be "MPG". + + TMED + The 'Media type' frame describes from which media the sound + originated. This may be a text string or a reference to the + predefined media types found in the list below. Example: + "VID/PAL/VHS" $00. + + DIG Other digital media + /A Analogue transfer from media + + ANA Other analogue media + /WAC Wax cylinder + /8CA 8-track tape cassette + + CD CD + /A Analogue transfer from media + /DD DDD + /AD ADD + /AA AAD + + LD Laserdisc + + TT Turntable records + /33 33.33 rpm + /45 45 rpm + /71 71.29 rpm + /76 76.59 rpm + /78 78.26 rpm + /80 80 rpm + + MD MiniDisc + /A Analogue transfer from media + + DAT DAT + /A Analogue transfer from media + /1 standard, 48 kHz/16 bits, linear + /2 mode 2, 32 kHz/16 bits, linear + /3 mode 3, 32 kHz/12 bits, non-linear, low speed + /4 mode 4, 32 kHz/12 bits, 4 channels + /5 mode 5, 44.1 kHz/16 bits, linear + /6 mode 6, 44.1 kHz/16 bits, 'wide track' play + + DCC DCC + /A Analogue transfer from media + + DVD DVD + /A Analogue transfer from media + + TV Television + /PAL PAL + /NTSC NTSC + /SECAM SECAM + + VID Video + /PAL PAL + /NTSC NTSC + /SECAM SECAM + /VHS VHS + /SVHS S-VHS + /BETA BETAMAX + + RAD Radio + /FM FM + /AM AM + /LW LW + /MW MW + + TEL Telephone + /I ISDN + + MC MC (normal cassette) + /4 4.75 cm/s (normal speed for a two sided cassette) + /9 9.5 cm/s + /I Type I cassette (ferric/normal) + /II Type II cassette (chrome) + /III Type III cassette (ferric chrome) + /IV Type IV cassette (metal) + + REE Reel + /9 9.5 cm/s + /19 19 cm/s + /38 38 cm/s + /76 76 cm/s + /I Type I cassette (ferric/normal) + /II Type II cassette (chrome) + /III Type III cassette (ferric chrome) + /IV Type IV cassette (metal) + + TMOO + The 'Mood' frame is intended to reflect the mood of the audio with a + few keywords, e.g. "Romantic" or "Sad". + + +4.2.4. Rights and license frames + + TCOP + The 'Copyright message' frame, in which the string must begin with a + year and a space character (making five characters), is intended for + the copyright holder of the original sound, not the audio file + itself. The absence of this frame means only that the copyright + information is unavailable or has been removed, and must not be + interpreted to mean that the audio is public domain. Every time this + field is displayed the field must be preceded with "Copyright " (C) " + ", where (C) is one character showing a C in a circle. + + TPRO + The 'Produced notice' frame, in which the string must begin with a + year and a space character (making five characters), is intended for + the production copyright holder of the original sound, not the audio + file itself. The absence of this frame means only that the production + copyright information is unavailable or has been removed, and must + not be interpreted to mean that the audio is public domain. Every + time this field is displayed the field must be preceded with + "Produced " (P) " ", where (P) is one character showing a P in a + circle. + + TPUB + The 'Publisher' frame simply contains the name of the label or + publisher. + + TOWN + The 'File owner/licensee' frame contains the name of the owner or + licensee of the file and it's contents. + + TRSN + The 'Internet radio station name' frame contains the name of the + internet radio station from which the audio is streamed. + + TRSO + The 'Internet radio station owner' frame contains the name of the + owner of the internet radio station from which the audio is + streamed. + +4.2.5. Other text frames + + TOFN + The 'Original filename' frame contains the preferred filename for the + file, since some media doesn't allow the desired length of the + filename. The filename is case sensitive and includes its suffix. + + TDLY + The 'Playlist delay' defines the numbers of milliseconds of silence + that should be inserted before this audio. The value zero indicates + that this is a part of a multifile audio track that should be played + continuously. + + TDEN + The 'Encoding time' frame contains a timestamp describing when the + audio was encoded. Timestamp format is described in the ID3v2 + structure document [ID3v2-strct]. + + TDOR + The 'Original release time' frame contains a timestamp describing + when the original recording of the audio was released. Timestamp + format is described in the ID3v2 structure document [ID3v2-strct]. + + TDRC + The 'Recording time' frame contains a timestamp describing when the + audio was recorded. Timestamp format is described in the ID3v2 + structure document [ID3v2-strct]. + + TDRL + The 'Release time' frame contains a timestamp describing when the + audio was first released. Timestamp format is described in the ID3v2 + structure document [ID3v2-strct]. + + TDTG + The 'Tagging time' frame contains a timestamp describing then the + audio was tagged. Timestamp format is described in the ID3v2 + structure document [ID3v2-strct]. + + TSSE + The 'Software/Hardware and settings used for encoding' frame + includes the used audio encoder and its settings when the file was + encoded. Hardware refers to hardware encoders, not the computer on + which a program was run. + + TSOA + The 'Album sort order' frame defines a string which should be used + instead of the album name (TALB) for sorting purposes. E.g. an album + named "A Soundtrack" might preferably be sorted as "Soundtrack". + + TSOP + The 'Performer sort order' frame defines a string which should be + used instead of the performer (TPE2) for sorting purposes. + + TSOT + The 'Title sort order' frame defines a string which should be used + instead of the title (TIT2) for sorting purposes. + + +4.2.6. User defined text information frame + + This frame is intended for one-string text information concerning the + audio file in a similar way to the other "T"-frames. The frame body + consists of a description of the string, represented as a terminated + string, followed by the actual string. There may be more than one + "TXXX" frame in each tag, but only one with the same description. + +
+ Text encoding $xx + Description $00 (00) + Value + + +4.3. URL link frames + + With these frames dynamic data such as webpages with touring + information, price information or plain ordinary news can be added to + the tag. There may only be one URL [URL] link frame of its kind in an + tag, except when stated otherwise in the frame description. If the + text string is followed by a string termination, all the following + information should be ignored and not be displayed. All URL link + frame identifiers begins with "W". Only URL link frame identifiers + begins with "W", except for "WXXX". All URL link frames have the + following format: + +
+ URL + + +4.3.1. URL link frames - details + + WCOM + The 'Commercial information' frame is a URL pointing at a webpage + with information such as where the album can be bought. There may be + more than one "WCOM" frame in a tag, but not with the same content. + + WCOP + The 'Copyright/Legal information' frame is a URL pointing at a + webpage where the terms of use and ownership of the file is + described. + + WOAF + The 'Official audio file webpage' frame is a URL pointing at a file + specific webpage. + + WOAR + The 'Official artist/performer webpage' frame is a URL pointing at + the artists official webpage. There may be more than one "WOAR" frame + in a tag if the audio contains more than one performer, but not with + the same content. + + WOAS + The 'Official audio source webpage' frame is a URL pointing at the + official webpage for the source of the audio file, e.g. a movie. + + WORS + The 'Official Internet radio station homepage' contains a URL + pointing at the homepage of the internet radio station. + + WPAY + The 'Payment' frame is a URL pointing at a webpage that will handle + the process of paying for this file. + + WPUB + The 'Publishers official webpage' frame is a URL pointing at the + official webpage for the publisher. + + +4.3.2. User defined URL link frame + + This frame is intended for URL [URL] links concerning the audio file + in a similar way to the other "W"-frames. The frame body consists + of a description of the string, represented as a terminated string, + followed by the actual URL. The URL is always encoded with ISO-8859-1 + [ISO-8859-1]. There may be more than one "WXXX" frame in each tag, + but only one with the same description. + +
+ Text encoding $xx + Description $00 (00) + URL + + +4.4. Music CD identifier + + This frame is intended for music that comes from a CD, so that the CD + can be identified in databases such as the CDDB [CDDB]. The frame + consists of a binary dump of the Table Of Contents, TOC, from the CD, + which is a header of 4 bytes and then 8 bytes/track on the CD plus 8 + bytes for the 'lead out', making a maximum of 804 bytes. The offset + to the beginning of every track on the CD should be described with a + four bytes absolute CD-frame address per track, and not with absolute + time. When this frame is used the presence of a valid "TRCK" frame is + REQUIRED, even if the CD's only got one track. It is recommended that + this frame is always added to tags originating from CDs. There may + only be one "MCDI" frame in each tag. + +
+ CD TOC + + +4.5. Event timing codes + + This frame allows synchronisation with key events in the audio. The + header is: + +
+ Time stamp format $xx + + Where time stamp format is: + + $01 Absolute time, 32 bit sized, using MPEG [MPEG] frames as unit + $02 Absolute time, 32 bit sized, using milliseconds as unit + + Absolute time means that every stamp contains the time from the + beginning of the file. + + Followed by a list of key events in the following format: + + Type of event $xx + Time stamp $xx (xx ...) + + The 'Time stamp' is set to zero if directly at the beginning of the + sound or after the previous event. All events MUST be sorted in + chronological order. The type of event is as follows: + + $00 padding (has no meaning) + $01 end of initial silence + $02 intro start + $03 main part start + $04 outro start + $05 outro end + $06 verse start + $07 refrain start + $08 interlude start + $09 theme start + $0A variation start + $0B key change + $0C time change + $0D momentary unwanted noise (Snap, Crackle & Pop) + $0E sustained noise + $0F sustained noise end + $10 intro end + $11 main part end + $12 verse end + $13 refrain end + $14 theme end + $15 profanity + $16 profanity end + + $17-$DF reserved for future use + + $E0-$EF not predefined synch 0-F + + $F0-$FC reserved for future use + + $FD audio end (start of silence) + $FE audio file ends + $FF one more byte of events follows (all the following bytes with + the value $FF have the same function) + + Terminating the start events such as "intro start" is OPTIONAL. The + 'Not predefined synch's ($E0-EF) are for user events. You might want + to synchronise your music to something, like setting off an explosion + on-stage, activating a screensaver etc. + + There may only be one "ETCO" frame in each tag. + + +4.6. MPEG location lookup table + + To increase performance and accuracy of jumps within a MPEG [MPEG] + audio file, frames with time codes in different locations in the file + might be useful. This ID3v2 frame includes references that the + software can use to calculate positions in the file. After the frame + header follows a descriptor of how much the 'frame counter' should be + increased for every reference. If this value is two then the first + reference points out the second frame, the 2nd reference the 4th + frame, the 3rd reference the 6th frame etc. In a similar way the + 'bytes between reference' and 'milliseconds between reference' points + out bytes and milliseconds respectively. + + Each reference consists of two parts; a certain number of bits, as + defined in 'bits for bytes deviation', that describes the difference + between what is said in 'bytes between reference' and the reality and + a certain number of bits, as defined in 'bits for milliseconds + deviation', that describes the difference between what is said in + 'milliseconds between reference' and the reality. The number of bits + in every reference, i.e. 'bits for bytes deviation'+'bits for + milliseconds deviation', must be a multiple of four. There may only + be one "MLLT" frame in each tag. + +
+ MPEG frames between reference $xx xx + Bytes between reference $xx xx xx + Milliseconds between reference $xx xx xx + Bits for bytes deviation $xx + Bits for milliseconds dev. $xx + + Then for every reference the following data is included; + + Deviation in bytes %xxx.... + Deviation in milliseconds %xxx.... + + +4.7. Synchronised tempo codes + + For a more accurate description of the tempo of a musical piece, this + frame might be used. After the header follows one byte describing + which time stamp format should be used. Then follows one or more + tempo codes. Each tempo code consists of one tempo part and one time + part. The tempo is in BPM described with one or two bytes. If the + first byte has the value $FF, one more byte follows, which is added + to the first giving a range from 2 - 510 BPM, since $00 and $01 is + reserved. $00 is used to describe a beat-free time period, which is + not the same as a music-free time period. $01 is used to indicate one + single beat-stroke followed by a beat-free period. + + The tempo descriptor is followed by a time stamp. Every time the + tempo in the music changes, a tempo descriptor may indicate this for + the player. All tempo descriptors MUST be sorted in chronological + order. The first beat-stroke in a time-period is at the same time as + the beat description occurs. There may only be one "SYTC" frame in + each tag. + +
+ Time stamp format $xx + Tempo data + + Where time stamp format is: + + $01 Absolute time, 32 bit sized, using MPEG [MPEG] frames as unit + $02 Absolute time, 32 bit sized, using milliseconds as unit + + Absolute time means that every stamp contains the time from the + beginning of the file. + + +4.8. Unsynchronised lyrics/text transcription + + This frame contains the lyrics of the song or a text transcription of + other vocal activities. The head includes an encoding descriptor and + a content descriptor. The body consists of the actual text. The + 'Content descriptor' is a terminated string. If no descriptor is + entered, 'Content descriptor' is $00 (00) only. Newline characters + are allowed in the text. There may be more than one 'Unsynchronised + lyrics/text transcription' frame in each tag, but only one with the + same language and content descriptor. + +
+ Text encoding $xx + Language $xx xx xx + Content descriptor $00 (00) + Lyrics/text + + +4.9. Synchronised lyrics/text + + This is another way of incorporating the words, said or sung lyrics, + in the audio file as text, this time, however, in sync with the + audio. It might also be used to describing events e.g. occurring on a + stage or on the screen in sync with the audio. The header includes a + content descriptor, represented with as terminated text string. If no + descriptor is entered, 'Content descriptor' is $00 (00) only. + +
+ Text encoding $xx + Language $xx xx xx + Time stamp format $xx + Content type $xx + Content descriptor $00 (00) + + Content type: $00 is other + $01 is lyrics + $02 is text transcription + $03 is movement/part name (e.g. "Adagio") + $04 is events (e.g. "Don Quijote enters the stage") + $05 is chord (e.g. "Bb F Fsus") + $06 is trivia/'pop up' information + $07 is URLs to webpages + $08 is URLs to images + + Time stamp format: + + $01 Absolute time, 32 bit sized, using MPEG [MPEG] frames as unit + $02 Absolute time, 32 bit sized, using milliseconds as unit + + Absolute time means that every stamp contains the time from the + beginning of the file. + + The text that follows the frame header differs from that of the + unsynchronised lyrics/text transcription in one major way. Each + syllable (or whatever size of text is considered to be convenient by + the encoder) is a null terminated string followed by a time stamp + denoting where in the sound file it belongs. Each sync thus has the + following structure: + + Terminated text to be synced (typically a syllable) + Sync identifier (terminator to above string) $00 (00) + Time stamp $xx (xx ...) + + The 'time stamp' is set to zero or the whole sync is omitted if + located directly at the beginning of the sound. All time stamps + should be sorted in chronological order. The sync can be considered + as a validator of the subsequent string. + + Newline characters are allowed in all "SYLT" frames and MUST be used + after every entry (name, event etc.) in a frame with the content type + $03 - $04. + + A few considerations regarding whitespace characters: Whitespace + separating words should mark the beginning of a new word, thus + occurring in front of the first syllable of a new word. This is also + valid for new line characters. A syllable followed by a comma should + not be broken apart with a sync (both the syllable and the comma + should be before the sync). + + An example: The "USLT" passage + + "Strangers in the night" $0A "Exchanging glances" + + would be "SYLT" encoded as: + + "Strang" $00 xx xx "ers" $00 xx xx " in" $00 xx xx " the" $00 xx xx + " night" $00 xx xx 0A "Ex" $00 xx xx "chang" $00 xx xx "ing" $00 xx + xx "glan" $00 xx xx "ces" $00 xx xx + + There may be more than one "SYLT" frame in each tag, but only one + with the same language and content descriptor. + + +4.10. Comments + + This frame is intended for any kind of full text information that + does not fit in any other frame. It consists of a frame header + followed by encoding, language and content descriptors and is ended + with the actual comment as a text string. Newline characters are + allowed in the comment text string. There may be more than one + comment frame in each tag, but only one with the same language and + content descriptor. + +
+ Text encoding $xx + Language $xx xx xx + Short content descrip. $00 (00) + The actual text + + +4.11. Relative volume adjustment (2) + + This is a more subjective frame than the previous ones. It allows the + user to say how much he wants to increase/decrease the volume on each + channel when the file is played. The purpose is to be able to align + all files to a reference volume, so that you don't have to change the + volume constantly. This frame may also be used to balance adjust the + audio. The volume adjustment is encoded as a fixed point decibel + value, 16 bit signed integer representing (adjustment*512), giving + +/- 64 dB with a precision of 0.001953125 dB. E.g. +2 dB is stored as + $04 00 and -2 dB is $FC 00. There may be more than one "RVA2" frame + in each tag, but only one with the same identification string. + +
+ Identification $00 + + The 'identification' string is used to identify the situation and/or + device where this adjustment should apply. The following is then + repeated for every channel + + Type of channel $xx + Volume adjustment $xx xx + Bits representing peak $xx + Peak volume $xx (xx ...) + + + Type of channel: $00 Other + $01 Master volume + $02 Front right + $03 Front left + $04 Back right + $05 Back left + $06 Front centre + $07 Back centre + $08 Subwoofer + + Bits representing peak can be any number between 0 and 255. 0 means + that there is no peak volume field. The peak volume field is always + padded to whole bytes, setting the most significant bits to zero. + + +4.12. Equalisation (2) + + This is another subjective, alignment frame. It allows the user to + predefine an equalisation curve within the audio file. There may be + more than one "EQU2" frame in each tag, but only one with the same + identification string. + +
+ Interpolation method $xx + Identification $00 + + The 'interpolation method' describes which method is preferred when + an interpolation between the adjustment point that follows. The + following methods are currently defined: + + $00 Band + No interpolation is made. A jump from one adjustment level to + another occurs in the middle between two adjustment points. + $01 Linear + Interpolation between adjustment points is linear. + + The 'identification' string is used to identify the situation and/or + device where this adjustment should apply. The following is then + repeated for every adjustment point + + Frequency $xx xx + Volume adjustment $xx xx + + The frequency is stored in units of 1/2 Hz, giving it a range from 0 + to 32767 Hz. + + The volume adjustment is encoded as a fixed point decibel value, 16 + bit signed integer representing (adjustment*512), giving +/- 64 dB + with a precision of 0.001953125 dB. E.g. +2 dB is stored as $04 00 + and -2 dB is $FC 00. + + Adjustment points should be ordered by frequency and one frequency + should only be described once in the frame. + + +4.13. Reverb + + Yet another subjective frame, with which you can adjust echoes of + different kinds. Reverb left/right is the delay between every bounce + in ms. Reverb bounces left/right is the number of bounces that should + be made. $FF equals an infinite number of bounces. Feedback is the + amount of volume that should be returned to the next echo bounce. $00 + is 0%, $FF is 100%. If this value were $7F, there would be 50% volume + reduction on the first bounce, 50% of that on the second and so on. + Left to left means the sound from the left bounce to be played in the + left speaker, while left to right means sound from the left bounce to + be played in the right speaker. + + 'Premix left to right' is the amount of left sound to be mixed in the + right before any reverb is applied, where $00 id 0% and $FF is 100%. + 'Premix right to left' does the same thing, but right to left. + Setting both premix to $FF would result in a mono output (if the + reverb is applied symmetric). There may only be one "RVRB" frame in + each tag. + +
+ Reverb left (ms) $xx xx + Reverb right (ms) $xx xx + Reverb bounces, left $xx + Reverb bounces, right $xx + Reverb feedback, left to left $xx + Reverb feedback, left to right $xx + Reverb feedback, right to right $xx + Reverb feedback, right to left $xx + Premix left to right $xx + Premix right to left $xx + + +4.14. Attached picture + + This frame contains a picture directly related to the audio file. + Image format is the MIME type and subtype [MIME] for the image. In + the event that the MIME media type name is omitted, "image/" will be + implied. The "image/png" [PNG] or "image/jpeg" [JFIF] picture format + should be used when interoperability is wanted. Description is a + short description of the picture, represented as a terminated + text string. There may be several pictures attached to one file, each + in their individual "APIC" frame, but only one with the same content + descriptor. There may only be one picture with the picture type + declared as picture type $01 and $02 respectively. There is the + possibility to put only a link to the image file by using the 'MIME + type' "-->" and having a complete URL [URL] instead of picture data. + The use of linked files should however be used sparingly since there + is the risk of separation of files. + +
+ Text encoding $xx + MIME type $00 + Picture type $xx + Description $00 (00) + Picture data + + + Picture type: $00 Other + $01 32x32 pixels 'file icon' (PNG only) + $02 Other file icon + $03 Cover (front) + $04 Cover (back) + $05 Leaflet page + $06 Media (e.g. label side of CD) + $07 Lead artist/lead performer/soloist + $08 Artist/performer + $09 Conductor + $0A Band/Orchestra + $0B Composer + $0C Lyricist/text writer + $0D Recording Location + $0E During recording + $0F During performance + $10 Movie/video screen capture + $11 A bright coloured fish + $12 Illustration + $13 Band/artist logotype + $14 Publisher/Studio logotype + + +4.15. General encapsulated object + + In this frame any type of file can be encapsulated. After the header, + 'Frame size' and 'Encoding' follows 'MIME type' [MIME] represented as + as a terminated string encoded with ISO 8859-1 [ISO-8859-1]. The + filename is case sensitive and is encoded as 'Encoding'. Then follows + a content description as terminated string, encoded as 'Encoding'. + The last thing in the frame is the actual object. The first two + strings may be omitted, leaving only their terminations. MIME type is + always an ISO-8859-1 text string. There may be more than one "GEOB" + frame in each tag, but only one with the same content descriptor. + +
+ Text encoding $xx + MIME type $00 + Filename $00 (00) + Content description $00 (00) + Encapsulated object + + +4.16. Play counter + + This is simply a counter of the number of times a file has been + played. The value is increased by one every time the file begins to + play. There may only be one "PCNT" frame in each tag. When the + counter reaches all one's, one byte is inserted in front of the + counter thus making the counter eight bits bigger. The counter must + be at least 32-bits long to begin with. + +
+ Counter $xx xx xx xx (xx ...) + + +4.17. Popularimeter + + The purpose of this frame is to specify how good an audio file is. + Many interesting applications could be found to this frame such as a + playlist that features better audio files more often than others or + it could be used to profile a person's taste and find other 'good' + files by comparing people's profiles. The frame contains the email + address to the user, one rating byte and a four byte play counter, + intended to be increased with one for every time the file is played. + The email is a terminated string. The rating is 1-255 where 1 is + worst and 255 is best. 0 is unknown. If no personal counter is wanted + it may be omitted. When the counter reaches all one's, one byte is + inserted in front of the counter thus making the counter eight bits + bigger in the same away as the play counter ("PCNT"). There may be + more than one "POPM" frame in each tag, but only one with the same + email address. + +
+ Email to user $00 + Rating $xx + Counter $xx xx xx xx (xx ...) + + +4.18. Recommended buffer size + + Sometimes the server from which an audio file is streamed is aware of + transmission or coding problems resulting in interruptions in the + audio stream. In these cases, the size of the buffer can be + recommended by the server using this frame. If the 'embedded info + flag' is true (1) then this indicates that an ID3 tag with the + maximum size described in 'Buffer size' may occur in the audio + stream. In such case the tag should reside between two MPEG [MPEG] + frames, if the audio is MPEG encoded. If the position of the next tag + is known, 'offset to next tag' may be used. The offset is calculated + from the end of tag in which this frame resides to the first byte of + the header in the next. This field may be omitted. Embedded tags are + generally not recommended since this could render unpredictable + behaviour from present software/hardware. + + For applications like streaming audio it might be an idea to embed + tags into the audio stream though. If the clients connects to + individual connections like HTTP and there is a possibility to begin + every transmission with a tag, then this tag should include a + 'recommended buffer size' frame. If the client is connected to a + arbitrary point in the stream, such as radio or multicast, then the + 'recommended buffer size' frame SHOULD be included in every tag. + + The 'Buffer size' should be kept to a minimum. There may only be one + "RBUF" frame in each tag. + +
+ Buffer size $xx xx xx + Embedded info flag %0000000x + Offset to next tag $xx xx xx xx + + +4.19. Audio encryption + + This frame indicates if the actual audio stream is encrypted, and by + whom. Since standardisation of such encryption scheme is beyond this + document, all "AENC" frames begin with a terminated string with a + URL containing an email address, or a link to a location where an + email address can be found, that belongs to the organisation + responsible for this specific encrypted audio file. Questions + regarding the encrypted audio should be sent to the email address + specified. If a $00 is found directly after the 'Frame size' and the + audio file indeed is encrypted, the whole file may be considered + useless. + + After the 'Owner identifier', a pointer to an unencrypted part of the + audio can be specified. The 'Preview start' and 'Preview length' is + described in frames. If no part is unencrypted, these fields should + be left zeroed. After the 'preview length' field follows optionally a + data block required for decryption of the audio. There may be more + than one "AENC" frames in a tag, but only one with the same 'Owner + identifier'. + +
+ Owner identifier $00 + Preview start $xx xx + Preview length $xx xx + Encryption info + + +4.20. Linked information + + To keep information duplication as low as possible this frame may be + used to link information from another ID3v2 tag that might reside in + another audio file or alone in a binary file. It is RECOMMENDED that + this method is only used when the files are stored on a CD-ROM or + other circumstances when the risk of file separation is low. The + frame contains a frame identifier, which is the frame that should be + linked into this tag, a URL [URL] field, where a reference to the + file where the frame is given, and additional ID data, if needed. + Data should be retrieved from the first tag found in the file to + which this link points. There may be more than one "LINK" frame in a + tag, but only one with the same contents. A linked frame is to be + considered as part of the tag and has the same restrictions as if it + was a physical part of the tag (i.e. only one "RVRB" frame allowed, + whether it's linked or not). + +
+ Frame identifier $xx xx xx xx + URL $00 + ID and additional data + + Frames that may be linked and need no additional data are "ASPI", + "ETCO", "EQU2", "MCID", "MLLT", "OWNE", "RVA2", "RVRB", "SYTC", the + text information frames and the URL link frames. + + The "AENC", "APIC", "GEOB" and "TXXX" frames may be linked with + the content descriptor as additional ID data. + + The "USER" frame may be linked with the language field as additional + ID data. + + The "PRIV" frame may be linked with the owner identifier as + additional ID data. + + The "COMM", "SYLT" and "USLT" frames may be linked with three bytes + of language descriptor directly followed by a content descriptor as + additional ID data. + + +4.21. Position synchronisation frame + + This frame delivers information to the listener of how far into the + audio stream he picked up; in effect, it states the time offset from + the first frame in the stream. The frame layout is: + + + Time stamp format $xx + Position $xx (xx ...) + + Where time stamp format is: + + $01 Absolute time, 32 bit sized, using MPEG frames as unit + $02 Absolute time, 32 bit sized, using milliseconds as unit + + and position is where in the audio the listener starts to receive, + i.e. the beginning of the next frame. If this frame is used in the + beginning of a file the value is always 0. There may only be one + "POSS" frame in each tag. + + +4.22. Terms of use frame + + This frame contains a brief description of the terms of use and + ownership of the file. More detailed information concerning the legal + terms might be available through the "WCOP" frame. Newlines are + allowed in the text. There may be more than one 'Terms of use' frame + in a tag, but only one with the same 'Language'. + +
+ Text encoding $xx + Language $xx xx xx + The actual text + + +4.23. Ownership frame + + The ownership frame might be used as a reminder of a made transaction + or, if signed, as proof. Note that the "USER" and "TOWN" frames are + good to use in conjunction with this one. The frame begins, after the + frame ID, size and encoding fields, with a 'price paid' field. The + first three characters of this field contains the currency used for + the transaction, encoded according to ISO 4217 [ISO-4217] alphabetic + currency code. Concatenated to this is the actual price paid, as a + numerical string using "." as the decimal separator. Next is an 8 + character date string (YYYYMMDD) followed by a string with the name + of the seller as the last field in the frame. There may only be one + "OWNE" frame in a tag. + +
+ Text encoding $xx + Price paid $00 + Date of purch. + Seller + + +4.24. Commercial frame + + This frame enables several competing offers in the same tag by + bundling all needed information. That makes this frame rather complex + but it's an easier solution than if one tries to achieve the same + result with several frames. The frame begins, after the frame ID, + size and encoding fields, with a price string field. A price is + constructed by one three character currency code, encoded according + to ISO 4217 [ISO-4217] alphabetic currency code, followed by a + numerical value where "." is used as decimal separator. In the price + string several prices may be concatenated, separated by a "/" + character, but there may only be one currency of each type. + + The price string is followed by an 8 character date string in the + format YYYYMMDD, describing for how long the price is valid. After + that is a contact URL, with which the user can contact the seller, + followed by a one byte 'received as' field. It describes how the + audio is delivered when bought according to the following list: + + $00 Other + $01 Standard CD album with other songs + $02 Compressed audio on CD + $03 File over the Internet + $04 Stream over the Internet + $05 As note sheets + $06 As note sheets in a book with other sheets + $07 Music on other media + $08 Non-musical merchandise + + Next follows a terminated string with the name of the seller followed + by a terminated string with a short description of the product. The + last thing is the ability to include a company logotype. The first of + them is the 'Picture MIME type' field containing information about + which picture format is used. In the event that the MIME media type + name is omitted, "image/" will be implied. Currently only "image/png" + and "image/jpeg" are allowed. This format string is followed by the + binary picture data. This two last fields may be omitted if no + picture is attached. There may be more than one 'commercial frame' in + a tag, but no two may be identical. + +
+ Text encoding $xx + Price string $00 + Valid until + Contact URL $00 + Received as $xx + Name of seller $00 (00) + Description $00 (00) + Picture MIME type $00 + Seller logo + + +4.25. Encryption method registration + + To identify with which method a frame has been encrypted the + encryption method must be registered in the tag with this frame. The + 'Owner identifier' is a null-terminated string with a URL [URL] + containing an email address, or a link to a location where an email + address can be found, that belongs to the organisation responsible + for this specific encryption method. Questions regarding the + encryption method should be sent to the indicated email address. The + 'Method symbol' contains a value that is associated with this method + throughout the whole tag, in the range $80-F0. All other values are + reserved. The 'Method symbol' may optionally be followed by + encryption specific data. There may be several "ENCR" frames in a tag + but only one containing the same symbol and only one containing the + same owner identifier. The method must be used somewhere in the tag. + See the description of the frame encryption flag in the ID3v2 + structure document [ID3v2-strct] for more information. + +
+ Owner identifier $00 + Method symbol $xx + Encryption data + + +4.26. Group identification registration + + This frame enables grouping of otherwise unrelated frames. This can + be used when some frames are to be signed. To identify which frames + belongs to a set of frames a group identifier must be registered in + the tag with this frame. The 'Owner identifier' is a null-terminated + string with a URL [URL] containing an email address, or a link to a + location where an email address can be found, that belongs to the + organisation responsible for this grouping. Questions regarding the + grouping should be sent to the indicated email address. The 'Group + symbol' contains a value that associates the frame with this group + throughout the whole tag, in the range $80-F0. All other values are + reserved. The 'Group symbol' may optionally be followed by some group + specific data, e.g. a digital signature. There may be several "GRID" + frames in a tag but only one containing the same symbol and only one + containing the same owner identifier. The group symbol must be used + somewhere in the tag. See the description of the frame grouping flag + in the ID3v2 structure document [ID3v2-strct] for more information. + +
+ Owner identifier $00 + Group symbol $xx + Group dependent data + + +4.27. Private frame + + This frame is used to contain information from a software producer + that its program uses and does not fit into the other frames. The + frame consists of an 'Owner identifier' string and the binary data. + The 'Owner identifier' is a null-terminated string with a URL [URL] + containing an email address, or a link to a location where an email + address can be found, that belongs to the organisation responsible + for the frame. Questions regarding the frame should be sent to the + indicated email address. The tag may contain more than one "PRIV" + frame but only with different contents. + +
+ Owner identifier $00 + The private data + + +4.28. Signature frame + + This frame enables a group of frames, grouped with the 'Group + identification registration', to be signed. Although signatures can + reside inside the registration frame, it might be desired to store + the signature elsewhere, e.g. in watermarks. There may be more than + one 'signature frame' in a tag, but no two may be identical. + +
+ Group symbol $xx + Signature + + +4.29. Seek frame + + This frame indicates where other tags in a file/stream can be found. + The 'minimum offset to next tag' is calculated from the end of this + tag to the beginning of the next. There may only be one 'seek frame' + in a tag. + +
+ Minimum offset to next tag $xx xx xx xx + + +4.30. Audio seek point index + + Audio files with variable bit rates are intrinsically difficult to + deal with in the case of seeking within the file. The ASPI frame + makes seeking easier by providing a list a seek points within the + audio file. The seek points are a fractional offset within the audio + data, providing a starting point from which to find an appropriate + point to start decoding. The presence of an ASPI frame requires the + existence of a TLEN frame, indicating the duration of the file in + milliseconds. There may only be one 'audio seek point index' frame in + a tag. + +
+ Indexed data start (S) $xx xx xx xx + Indexed data length (L) $xx xx xx xx + Number of index points (N) $xx xx + Bits per index point (b) $xx + + Then for every index point the following data is included; + + Fraction at index (Fi) $xx (xx) + + 'Indexed data start' is a byte offset from the beginning of the file. + 'Indexed data length' is the byte length of the audio data being + indexed. 'Number of index points' is the number of index points, as + the name implies. The recommended number is 100. 'Bits per index + point' is 8 or 16, depending on the chosen precision. 8 bits works + well for short files (less than 5 minutes of audio), while 16 bits is + advantageous for long files. 'Fraction at index' is the numerator of + the fraction representing a relative position in the data. The + denominator is 2 to the power of b. + + Here are the algorithms to be used in the calculation. The known data + must be the offset of the start of the indexed data (S), the offset + of the end of the indexed data (E), the number of index points (N), + the offset at index i (Oi). We calculate the fraction at index i + (Fi). + + Oi is the offset of the frame whose start is soonest after the point + for which the time offset is (i/N * duration). + + The frame data should be calculated as follows: + + Fi = Oi/L * 2^b (rounded down to the nearest integer) + + Offset calculation should be calculated as follows from data in the + frame: + + Oi = (Fi/2^b)*L (rounded up to the nearest integer) + + +5. Copyright + + Copyright (C) Martin Nilsson 2000. All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that a reference to this document is included on all + such copies and derivative works. However, this document itself may + not be modified in any way and reissued as the original document. + + The limited permissions granted above are perpetual and will not be + revoked. + + This document and the information contained herein is provided on an + "AS IS" basis and THE AUTHORS DISCLAIMS ALL WARRANTIES, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF + THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED + WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + + +6. References + + [CDDB] Compact Disc Data Base + + + + [ID3v2.3.0] Martin Nilsson, "ID3v2 informal standard". + + + + [ID3v2-strct] Martin Nilsson, + "ID3 tag version 2.4.0 - Main Structure" + + + + [ISO-639-2] ISO/FDIS 639-2. + Codes for the representation of names of languages, Part 2: Alpha-3 + code. Technical committee / subcommittee: TC 37 / SC 2 + + [ISO-4217] ISO 4217:1995. + Codes for the representation of currencies and funds. + Technical committee / subcommittee: TC 68 + + [ISO-8859-1] ISO/IEC DIS 8859-1. + 8-bit single-byte coded graphic character sets, Part 1: Latin + alphabet No. 1. Technical committee / subcommittee: JTC 1 / SC 2 + + [ISRC] ISO 3901:1986 + International Standard Recording Code (ISRC). + Technical committee / subcommittee: TC 46 / SC 9 + + [JFIF] JPEG File Interchange Format, version 1.02 + + + + [KEYWORDS] S. Bradner, 'Key words for use in RFCs to Indicate + Requirement Levels', RFC 2119, March 1997. + + + + [MIME] Freed, N. and N. Borenstein, "Multipurpose Internet Mail + Extensions (MIME) Part One: Format of Internet Message Bodies", + RFC 2045, November 1996. + + + + [MPEG] ISO/IEC 11172-3:1993. + Coding of moving pictures and associated audio for digital storage + media at up to about 1,5 Mbit/s, Part 3: Audio. + Technical committee / subcommittee: JTC 1 / SC 29 + and + ISO/IEC 13818-3:1995 + Generic coding of moving pictures and associated audio information, + Part 3: Audio. + Technical committee / subcommittee: JTC 1 / SC 29 + and + ISO/IEC DIS 13818-3 + Generic coding of moving pictures and associated audio information, + Part 3: Audio (Revision of ISO/IEC 13818-3:1995) + + + [PNG] Portable Network Graphics, version 1.0 + + + + [URL] T. Berners-Lee, L. Masinter & M. McCahill, "Uniform Resource + Locators (URL).", RFC 1738, December 1994. + + + + [ZLIB] P. Deutsch, Aladdin Enterprises & J-L. Gailly, "ZLIB + Compressed + Data Format Specification version 3.3", RFC 1950, May 1996. + + + + +7. Appendix + + +A. Appendix A - Genre List from ID3v1 + + The following genres is defined in ID3v1 + + 0.Blues + 1.Classic Rock + 2.Country + 3.Dance + 4.Disco + 5.Funk + 6.Grunge + 7.Hip-Hop + 8.Jazz + 9.Metal + 10.New Age + 11.Oldies + 12.Other + 13.Pop + 14.R&B + 15.Rap + 16.Reggae + 17.Rock + 18.Techno + 19.Industrial + 20.Alternative + 21.Ska + 22.Death Metal + 23.Pranks + 24.Soundtrack + 25.Euro-Techno + 26.Ambient + 27.Trip-Hop + 28.Vocal + 29.Jazz+Funk + 30.Fusion + 31.Trance + 32.Classical + 33.Instrumental + 34.Acid + 35.House + 36.Game + 37.Sound Clip + 38.Gospel + 39.Noise + 40.AlternRock + 41.Bass + 42.Soul + 43.Punk + 44.Space + 45.Meditative + 46.Instrumental Pop + 47.Instrumental Rock + 48.Ethnic + 49.Gothic + 50.Darkwave + 51.Techno-Industrial + 52.Electronic + 53.Pop-Folk + 54.Eurodance + 55.Dream + 56.Southern Rock + 57.Comedy + 58.Cult + 59.Gangsta + 60.Top 40 + 61.Christian Rap + 62.Pop/Funk + 63.Jungle + 64.Native American + 65.Cabaret + 66.New Wave + 67.Psychadelic + 68.Rave + 69.Showtunes + 70.Trailer + 71.Lo-Fi + 72.Tribal + 73.Acid Punk + 74.Acid Jazz + 75.Polka + 76.Retro + 77.Musical + 78.Rock & Roll + 79.Hard Rock + + +8. Author's Address + + Written by + + Martin Nilsson + Rydsvgen 246 C. 30 + SE-584 34 Linkping + Sweden + + Email: nilsson@id3.org diff --git a/gst-libs/gst/tag/id3v2.4.0-structure.txt b/gst-libs/gst/tag/id3v2.4.0-structure.txt new file mode 100644 index 0000000000..5fa156a0ad --- /dev/null +++ b/gst-libs/gst/tag/id3v2.4.0-structure.txt @@ -0,0 +1,733 @@ + +Informal standard M. Nilsson +Document: id3v2.4.0-structure.txt 16 September 2001 + + + ID3 tag version 2.4.0 - Main Structure + +Status of this document + + This document is an informal standard and replaces the ID3v2.3.0 + standard [ID3v2]. A formal standard will use another revision number + even if the content is identical to document. The contents in this + document may change for clarifications but never for added or altered + functionallity. + + Distribution of this document is unlimited. + + +Abstract + + This document describes the main structure of ID3v2.4.0, which is a + revised version of the ID3v2 informal standard [ID3v2] version + 2.3.0. The ID3v2 offers a flexible way of storing audio meta + information within the audio file itself. The information may be + technical information, such as equalisation curves, as well as + title, performer, copyright etc. + + ID3v2.4.0 is meant to be as close as possible to ID3v2.3.0 in order + to allow for implementations to be revised as easily as possible. + + +1. Table of contents + + Status of this document + Abstract + 1. Table of contents + 2. Conventions in this document + 2. Standard overview + 3. ID3v2 overview + 3.1. ID3v2 header + 3.2. ID3v2 extended header + 3.3. Padding + 3.4. ID3v2 footer + 4. ID3v2 frames overview + 4.1. Frame header flags + 4.1.1. Frame status flags + 4.1.2. Frame format flags + 5. Tag location + 6. Unsynchronisation + 6.1. The unsynchronisation scheme + 6.2. Synchsafe integers + 7. Copyright + 8. References + 9. Author's Address + + +2. Conventions in this document + + Text within "" is a text string exactly as it appears in a tag. + Numbers preceded with $ are hexadecimal and numbers preceded with % + are binary. $xx is used to indicate a byte with unknown content. %x + is used to indicate a bit with unknown content. The most significant + bit (MSB) of a byte is called 'bit 7' and the least significant bit + (LSB) is called 'bit 0'. + + A tag is the whole tag described in this document. A frame is a block + of information in the tag. The tag consists of a header, frames and + optional padding. A field is a piece of information; one value, a + string etc. A numeric string is a string that consists of the + characters "0123456789" only. + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this + document are to be interpreted as described in RFC 2119 [KEYWORDS]. + + +3. ID3v2 overview + + ID3v2 is a general tagging format for audio, which makes it possible + to store meta data about the audio inside the audio file itself. The + ID3 tag described in this document is mainly targeted at files + encoded with MPEG-1/2 layer I, MPEG-1/2 layer II, MPEG-1/2 layer III + and MPEG-2.5, but may work with other types of encoded audio or as a + stand alone format for audio meta data. + + ID3v2 is designed to be as flexible and expandable as possible to + meet new meta information needs that might arise. To achieve that + ID3v2 is constructed as a container for several information blocks, + called frames, whose format need not be known to the software that + encounters them. At the start of every frame is an unique and + predefined identifier, a size descriptor that allows software to skip + unknown frames and a flags field. The flags describes encoding + details and if the frame should remain in the tag, should it be + unknown to the software, if the file is altered. + + The bitorder in ID3v2 is most significant bit first (MSB). The + byteorder in multibyte numbers is most significant byte first (e.g. + $12345678 would be encoded $12 34 56 78), also known as big endian + and network byte order. + + Overall tag structure: + + +-----------------------------+ + | Header (10 bytes) | + +-----------------------------+ + | Extended Header | + | (variable length, OPTIONAL) | + +-----------------------------+ + | Frames (variable length) | + +-----------------------------+ + | Padding | + | (variable length, OPTIONAL) | + +-----------------------------+ + | Footer (10 bytes, OPTIONAL) | + +-----------------------------+ + + In general, padding and footer are mutually exclusive. See details in + sections 3.3, 3.4 and 5. + + +3.1. ID3v2 header + + The first part of the ID3v2 tag is the 10 byte tag header, laid out + as follows: + + ID3v2/file identifier "ID3" + ID3v2 version $04 00 + ID3v2 flags %abcd0000 + ID3v2 size 4 * %0xxxxxxx + + The first three bytes of the tag are always "ID3", to indicate that + this is an ID3v2 tag, directly followed by the two version bytes. The + first byte of ID3v2 version is its major version, while the second + byte is its revision number. In this case this is ID3v2.4.0. All + revisions are backwards compatible while major versions are not. If + software with ID3v2.4.0 and below support should encounter version + five or higher it should simply ignore the whole tag. Version or + revision will never be $FF. + + The version is followed by the ID3v2 flags field, of which currently + four flags are used. + + + a - Unsynchronisation + + Bit 7 in the 'ID3v2 flags' indicates whether or not + unsynchronisation is applied on all frames (see section 6.1 for + details); a set bit indicates usage. + + + b - Extended header + + The second bit (bit 6) indicates whether or not the header is + followed by an extended header. The extended header is described in + section 3.2. A set bit indicates the presence of an extended + header. + + + c - Experimental indicator + + The third bit (bit 5) is used as an 'experimental indicator'. This + flag SHALL always be set when the tag is in an experimental stage. + + + d - Footer present + + Bit 4 indicates that a footer (section 3.4) is present at the very + end of the tag. A set bit indicates the presence of a footer. + + + All the other flags MUST be cleared. If one of these undefined flags + are set, the tag might not be readable for a parser that does not + know the flags function. + + The ID3v2 tag size is stored as a 32 bit synchsafe integer (section + 6.2), making a total of 28 effective bits (representing up to 256MB). + + The ID3v2 tag size is the sum of the byte length of the extended + header, the padding and the frames after unsynchronisation. If a + footer is present this equals to ('total size' - 20) bytes, otherwise + ('total size' - 10) bytes. + + An ID3v2 tag can be detected with the following pattern: + $49 44 33 yy yy xx zz zz zz zz + Where yy is less than $FF, xx is the 'flags' byte and zz is less than + $80. + + +3.2. Extended header + + The extended header contains information that can provide further + insight in the structure of the tag, but is not vital to the correct + parsing of the tag information; hence the extended header is + optional. + + Extended header size 4 * %0xxxxxxx + Number of flag bytes $01 + Extended Flags $xx + + Where the 'Extended header size' is the size of the whole extended + header, stored as a 32 bit synchsafe integer. An extended header can + thus never have a size of fewer than six bytes. + + The extended flags field, with its size described by 'number of flag + bytes', is defined as: + + %0bcd0000 + + Each flag that is set in the extended header has data attached, which + comes in the order in which the flags are encountered (i.e. the data + for flag 'b' comes before the data for flag 'c'). Unset flags cannot + have any attached data. All unknown flags MUST be unset and their + corresponding data removed when a tag is modified. + + Every set flag's data starts with a length byte, which contains a + value between 0 and 127 ($00 - $7f), followed by data that has the + field length indicated by the length byte. If a flag has no attached + data, the value $00 is used as length byte. + + + b - Tag is an update + + If this flag is set, the present tag is an update of a tag found + earlier in the present file or stream. If frames defined as unique + are found in the present tag, they are to override any + corresponding ones found in the earlier tag. This flag has no + corresponding data. + + Flag data length $00 + + c - CRC data present + + If this flag is set, a CRC-32 [ISO-3309] data is included in the + extended header. The CRC is calculated on all the data between the + header and footer as indicated by the header's tag length field, + minus the extended header. Note that this includes the padding (if + there is any), but excludes the footer. The CRC-32 is stored as an + 35 bit synchsafe integer, leaving the upper four bits always + zeroed. + + Flag data length $05 + Total frame CRC 5 * %0xxxxxxx + + d - Tag restrictions + + For some applications it might be desired to restrict a tag in more + ways than imposed by the ID3v2 specification. Note that the + presence of these restrictions does not affect how the tag is + decoded, merely how it was restricted before encoding. If this flag + is set the tag is restricted as follows: + + Flag data length $01 + Restrictions %ppqrrstt + + p - Tag size restrictions + + 00 No more than 128 frames and 1 MB total tag size. + 01 No more than 64 frames and 128 KB total tag size. + 10 No more than 32 frames and 40 KB total tag size. + 11 No more than 32 frames and 4 KB total tag size. + + q - Text encoding restrictions + + 0 No restrictions + 1 Strings are only encoded with ISO-8859-1 [ISO-8859-1] or + UTF-8 [UTF-8]. + + r - Text fields size restrictions + + 00 No restrictions + 01 No string is longer than 1024 characters. + 10 No string is longer than 128 characters. + 11 No string is longer than 30 characters. + + Note that nothing is said about how many bytes is used to + represent those characters, since it is encoding dependent. If a + text frame consists of more than one string, the sum of the + strungs is restricted as stated. + + s - Image encoding restrictions + + 0 No restrictions + 1 Images are encoded only with PNG [PNG] or JPEG [JFIF]. + + t - Image size restrictions + + 00 No restrictions + 01 All images are 256x256 pixels or smaller. + 10 All images are 64x64 pixels or smaller. + 11 All images are exactly 64x64 pixels, unless required + otherwise. + + +3.3. Padding + + It is OPTIONAL to include padding after the final frame (at the end + of the ID3 tag), making the size of all the frames together smaller + than the size given in the tag header. A possible purpose of this + padding is to allow for adding a few additional frames or enlarge + existing frames within the tag without having to rewrite the entire + file. The value of the padding bytes must be $00. A tag MUST NOT have + any padding between the frames or between the tag header and the + frames. Furthermore it MUST NOT have any padding when a tag footer is + added to the tag. + + +3.4. ID3v2 footer + + To speed up the process of locating an ID3v2 tag when searching from + the end of a file, a footer can be added to the tag. It is REQUIRED + to add a footer to an appended tag, i.e. a tag located after all + audio data. The footer is a copy of the header, but with a different + identifier. + + ID3v2 identifier "3DI" + ID3v2 version $04 00 + ID3v2 flags %abcd0000 + ID3v2 size 4 * %0xxxxxxx + + +4. ID3v2 frame overview + + All ID3v2 frames consists of one frame header followed by one or more + fields containing the actual information. The header is always 10 + bytes and laid out as follows: + + Frame ID $xx xx xx xx (four characters) + Size 4 * %0xxxxxxx + Flags $xx xx + + The frame ID is made out of the characters capital A-Z and 0-9. + Identifiers beginning with "X", "Y" and "Z" are for experimental + frames and free for everyone to use, without the need to set the + experimental bit in the tag header. Bear in mind that someone else + might have used the same identifier as you. All other identifiers are + either used or reserved for future use. + + The frame ID is followed by a size descriptor containing the size of + the data in the final frame, after encryption, compression and + unsynchronisation. The size is excluding the frame header ('total + frame size' - 10 bytes) and stored as a 32 bit synchsafe integer. + + In the frame header the size descriptor is followed by two flag + bytes. These flags are described in section 4.1. + + There is no fixed order of the frames' appearance in the tag, + although it is desired that the frames are arranged in order of + significance concerning the recognition of the file. An example of + such order: UFID, TIT2, MCDI, TRCK ... + + A tag MUST contain at least one frame. A frame must be at least 1 + byte big, excluding the header. + + If nothing else is said, strings, including numeric strings and URLs + [URL], are represented as ISO-8859-1 [ISO-8859-1] characters in the + range $20 - $FF. Such strings are represented in frame descriptions + as , or if newlines are allowed. If + nothing else is said newline character is forbidden. In ISO-8859-1 a + newline is represented, when allowed, with $0A only. + + Frames that allow different types of text encoding contains a text + encoding description byte. Possible encodings: + + $00 ISO-8859-1 [ISO-8859-1]. Terminated with $00. + $01 UTF-16 [UTF-16] encoded Unicode [UNICODE] with BOM. All + strings in the same frame SHALL have the same byteorder. + Terminated with $00 00. + $02 UTF-16BE [UTF-16] encoded Unicode [UNICODE] without BOM. + Terminated with $00 00. + $03 UTF-8 [UTF-8] encoded Unicode [UNICODE]. Terminated with $00. + + Strings dependent on encoding are represented in frame descriptions + as , or if newlines are allowed. Any empty strings of + type $01 which are NULL-terminated may have the Unicode BOM followed + by a Unicode NULL ($FF FE 00 00 or $FE FF 00 00). + + The timestamp fields are based on a subset of ISO 8601. When being as + precise as possible the format of a time string is + yyyy-MM-ddTHH:mm:ss (year, "-", month, "-", day, "T", hour (out of + 24), ":", minutes, ":", seconds), but the precision may be reduced by + removing as many time indicators as wanted. Hence valid timestamps + are + yyyy, yyyy-MM, yyyy-MM-dd, yyyy-MM-ddTHH, yyyy-MM-ddTHH:mm and + yyyy-MM-ddTHH:mm:ss. All time stamps are UTC. For durations, use + the slash character as described in 8601, and for multiple non- + contiguous dates, use multiple strings, if allowed by the frame + definition. + + The three byte language field, present in several frames, is used to + describe the language of the frame's content, according to ISO-639-2 + [ISO-639-2]. The language should be represented in lower case. If the + language is not known the string "XXX" should be used. + + All URLs [URL] MAY be relative, e.g. "picture.png", "../doc.txt". + + If a frame is longer than it should be, e.g. having more fields than + specified in this document, that indicates that additions to the + frame have been made in a later version of the ID3v2 standard. This + is reflected by the revision number in the header of the tag. + + +4.1. Frame header flags + + In the frame header the size descriptor is followed by two flag + bytes. All unused flags MUST be cleared. The first byte is for + 'status messages' and the second byte is a format description. If an + unknown flag is set in the first byte the frame MUST NOT be changed + without that bit cleared. If an unknown flag is set in the second + byte the frame is likely to not be readable. Some flags in the second + byte indicates that extra information is added to the header. These + fields of extra information is ordered as the flags that indicates + them. The flags field is defined as follows (l and o left out because + ther resemblence to one and zero): + + %0abc0000 %0h00kmnp + + Some frame format flags indicate that additional information fields + are added to the frame. This information is added after the frame + header and before the frame data in the same order as the flags that + indicates them. I.e. the four bytes of decompressed size will precede + the encryption method byte. These additions affects the 'frame size' + field, but are not subject to encryption or compression. + + The default status flags setting for a frame is, unless stated + otherwise, 'preserved if tag is altered' and 'preserved if file is + altered', i.e. %00000000. + + +4.1.1. Frame status flags + + a - Tag alter preservation + + This flag tells the tag parser what to do with this frame if it is + unknown and the tag is altered in any way. This applies to all + kinds of alterations, including adding more padding and reordering + the frames. + + 0 Frame should be preserved. + 1 Frame should be discarded. + + + b - File alter preservation + + This flag tells the tag parser what to do with this frame if it is + unknown and the file, excluding the tag, is altered. This does not + apply when the audio is completely replaced with other audio data. + + 0 Frame should be preserved. + 1 Frame should be discarded. + + + c - Read only + + This flag, if set, tells the software that the contents of this + frame are intended to be read only. Changing the contents might + break something, e.g. a signature. If the contents are changed, + without knowledge of why the frame was flagged read only and + without taking the proper means to compensate, e.g. recalculating + the signature, the bit MUST be cleared. + + +4.1.2. Frame format flags + + h - Grouping identity + + This flag indicates whether or not this frame belongs in a group + with other frames. If set, a group identifier byte is added to the + frame. Every frame with the same group identifier belongs to the + same group. + + 0 Frame does not contain group information + 1 Frame contains group information + + + k - Compression + + This flag indicates whether or not the frame is compressed. + A 'Data Length Indicator' byte MUST be included in the frame. + + 0 Frame is not compressed. + 1 Frame is compressed using zlib [zlib] deflate method. + If set, this requires the 'Data Length Indicator' bit + to be set as well. + + + m - Encryption + + This flag indicates whether or not the frame is encrypted. If set, + one byte indicating with which method it was encrypted will be + added to the frame. See description of the ENCR frame for more + information about encryption method registration. Encryption + should be done after compression. Whether or not setting this flag + requires the presence of a 'Data Length Indicator' depends on the + specific algorithm used. + + 0 Frame is not encrypted. + 1 Frame is encrypted. + + n - Unsynchronisation + + This flag indicates whether or not unsynchronisation was applied + to this frame. See section 6 for details on unsynchronisation. + If this flag is set all data from the end of this header to the + end of this frame has been unsynchronised. Although desirable, the + presence of a 'Data Length Indicator' is not made mandatory by + unsynchronisation. + + 0 Frame has not been unsynchronised. + 1 Frame has been unsyrchronised. + + p - Data length indicator + + This flag indicates that a data length indicator has been added to + the frame. The data length indicator is the value one would write + as the 'Frame length' if all of the frame format flags were + zeroed, represented as a 32 bit synchsafe integer. + + 0 There is no Data Length Indicator. + 1 A data length Indicator has been added to the frame. + + +5. Tag location + + The default location of an ID3v2 tag is prepended to the audio so + that players can benefit from the information when the data is + streamed. It is however possible to append the tag, or make a + prepend/append combination. When deciding upon where an unembedded + tag should be located, the following order of preference SHOULD be + considered. + + 1. Prepend the tag. + + 2. Prepend a tag with all vital information and add a second tag at + the end of the file, before tags from other tagging systems. The + first tag is required to have a SEEK frame. + + 3. Add a tag at the end of the file, before tags from other tagging + systems. + + In case 2 and 3 the tag can simply be appended if no other known tags + are present. The suggested method to find ID3v2 tags are: + + 1. Look for a prepended tag using the pattern found in section 3.1. + + 2. If a SEEK frame was found, use its values to guide further + searching. + + 3. Look for a tag footer, scanning from the back of the file. + + For every new tag that is found, the old tag should be discarded + unless the update flag in the extended header (section 3.2) is set. + + +6. Unsynchronisation + + The only purpose of unsynchronisation is to make the ID3v2 tag as + compatible as possible with existing software and hardware. There is + no use in 'unsynchronising' tags if the file is only to be processed + only by ID3v2 aware software and hardware. Unsynchronisation is only + useful with tags in MPEG 1/2 layer I, II and III, MPEG 2.5 and AAC + files. + + +6.1. The unsynchronisation scheme + + Whenever a false synchronisation is found within the tag, one zeroed + byte is inserted after the first false synchronisation byte. The + format of synchronisations that should be altered by ID3 encoders is + as follows: + + %11111111 111xxxxx + + and should be replaced with: + + %11111111 00000000 111xxxxx + + This has the side effect that all $FF 00 combinations have to be + altered, so they will not be affected by the decoding process. + Therefore all the $FF 00 combinations have to be replaced with the + $FF 00 00 combination during the unsynchronisation. + + To indicate usage of the unsynchronisation, the unsynchronisation + flag in the frame header should be set. This bit MUST be set if the + frame was altered by the unsynchronisation and SHOULD NOT be set if + unaltered. If all frames in the tag are unsynchronised the + unsynchronisation flag in the tag header SHOULD be set. It MUST NOT + be set if the tag has a frame which is not unsynchronised. + + Assume the first byte of the audio to be $FF. The special case when + the last byte of the last frame is $FF and no padding nor footer is + used will then introduce a false synchronisation. This can be solved + by adding a footer, adding padding or unsynchronising the frame and + add $00 to the end of the frame data, thus adding more byte to the + frame size than a normal unsynchronisation would. Although not + preferred, it is allowed to apply the last method on all frames + ending with $FF. + + It is preferred that the tag is either completely unsynchronised or + not unsynchronised at all. A completely unsynchronised tag has no + false synchonisations in it, as defined above, and does not end with + $FF. A completely non-unsynchronised tag contains no unsynchronised + frames, and thus the unsynchronisation flag in the header is cleared. + + Do bear in mind, that if compression or encryption is used, the + unsynchronisation scheme MUST be applied afterwards. When decoding an + unsynchronised frame, the unsynchronisation scheme MUST be reversed + first, encryption and decompression afterwards. + + +6.2. Synchsafe integers + + In some parts of the tag it is inconvenient to use the + unsychronisation scheme because the size of unsynchronised data is + not known in advance, which is particularly problematic with size + descriptors. The solution in ID3v2 is to use synchsafe integers, in + which there can never be any false synchs. Synchsafe integers are + integers that keep its highest bit (bit 7) zeroed, making seven bits + out of eight available. Thus a 32 bit synchsafe integer can store 28 + bits of information. + + Example: + + 255 (%11111111) encoded as a 16 bit synchsafe integer is 383 + (%00000001 01111111). + + +7. Copyright + + Copyright (C) Martin Nilsson 2000. All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that a reference to this document is included on all + such copies and derivative works. However, this document itself may + not be modified in any way and reissued as the original document. + + The limited permissions granted above are perpetual and will not be + revoked. + + This document and the information contained herein is provided on an + 'AS IS' basis and THE AUTHORS DISCLAIMS ALL WARRANTIES, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF + THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED + WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + + +8. References + + [ID3v2] Martin Nilsson, 'ID3v2 informal standard'. + + + + [ISO-639-2] ISO/FDIS 639-2. + 'Codes for the representation of names of languages, Part 2: Alpha-3 + code.' Technical committee / subcommittee: TC 37 / SC 2 + + [ISO-3309] ISO 3309 + 'Information Processing Systems--Data Communication High-Level Data + Link Control Procedure--Frame Structure', IS 3309, October 1984, 3rd + Edition. + + [ISO-8859-1] ISO/IEC DIS 8859-1. + '8-bit single-byte coded graphic character sets, Part 1: Latin + alphabet No. 1.' Technical committee / subcommittee: JTC 1 / SC 2 + + [JFIF] 'JPEG File Interchange Format, version 1.02' + + + + [KEYWORDS] S. Bradner, 'Key words for use in RFCs to Indicate + Requirement Levels', RFC 2119, March 1997. + + + + [MPEG] ISO/IEC 11172-3:1993. + 'Coding of moving pictures and associated audio for digital storage + media at up to about 1,5 Mbit/s, Part 3: Audio.' + Technical committee / subcommittee: JTC 1 / SC 29 + and + ISO/IEC 13818-3:1995 + 'Generic coding of moving pictures and associated audio information, + Part 3: Audio.' + Technical committee / subcommittee: JTC 1 / SC 29 + and + ISO/IEC DIS 13818-3 + 'Generic coding of moving pictures and associated audio information, + Part 3: Audio (Revision of ISO/IEC 13818-3:1995)' + + [PNG] 'Portable Network Graphics, version 1.0' + + + + [UNICODE] The Unicode Consortium, + 'The Unicode Standard Version 3.0', ISBN 0-201-61633-5. + + + + [URL] T. Berners-Lee, L. Masinter & M. McCahill, 'Uniform Resource + Locators (URL)', RFC 1738, December 1994. + + + + [UTF-8] F. Yergeau, 'UTF-8, a transformation format of ISO 10646', + RFC 2279, January 1998. + + + + [UTF-16] F. Yergeau, 'UTF-16, an encoding of ISO 10646', RFC 2781, + February 2000. + + + + [ZLIB] P. Deutsch, Aladdin Enterprises & J-L. Gailly, 'ZLIB + Compressed Data Format Specification version 3.3', RFC 1950, + May 1996. + + + + +9. Author's Address + + Written by + + Martin Nilsson + Rydsvgen 246 C. 30 + SE-584 34 Linkping + Sweden + + Email: nilsson@id3.org + From a5933eb14bd3cc661ef5446ca4f42704bfad37d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Mon, 15 Aug 2011 00:03:39 +0100 Subject: [PATCH 136/140] configure: try pkg-config first when looking for zlib --- configure.ac | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/configure.ac b/configure.ac index a76691f994..56eae4e380 100644 --- a/configure.ac +++ b/configure.ac @@ -508,12 +508,15 @@ fi dnl *** zlib is optionally used by id3 tag parsing in libgsttag *** translit(dnm, m, l) AM_CONDITIONAL(USE_ZLIB, true) AG_GST_CHECK_FEATURE(ZLIB, [zlib support for ID3 parsing in libgsttag],, [ - AG_GST_CHECK_LIBHEADER(ZLIB, - z, uncompress,, zlib.h, [ - HAVE_ZLIB="yes" - ZLIB_LIBS="-lz" - AC_SUBST(ZLIB_LIBS) - ]) + PKG_CHECK_MODULES(ZLIB, [ zlib ], [ + HAVE_ZLIB="yes" + ], [ + AG_GST_CHECK_LIBHEADER(ZLIB, z, uncompress,, zlib.h, [ + HAVE_ZLIB="yes" + ZLIB_LIBS="-lz" + AC_SUBST(ZLIB_LIBS) + ]) + ]) ]) dnl *** sys plug-ins *** From 59f2249284764f9acbf0bf6928b2b98fd5dd22e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Mon, 15 Aug 2011 00:17:14 +0100 Subject: [PATCH 137/140] docs: add new license API to docs --- docs/libs/gst-plugins-base-libs-docs.sgml | 1 + docs/libs/gst-plugins-base-libs-sections.txt | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/docs/libs/gst-plugins-base-libs-docs.sgml b/docs/libs/gst-plugins-base-libs-docs.sgml index 7ed534aac0..605930cbb1 100644 --- a/docs/libs/gst-plugins-base-libs-docs.sgml +++ b/docs/libs/gst-plugins-base-libs-docs.sgml @@ -193,6 +193,7 @@ + diff --git a/docs/libs/gst-plugins-base-libs-sections.txt b/docs/libs/gst-plugins-base-libs-sections.txt index 19cd66f2fe..42f5d78a93 100644 --- a/docs/libs/gst-plugins-base-libs-sections.txt +++ b/docs/libs/gst-plugins-base-libs-sections.txt @@ -1751,7 +1751,6 @@ GstTagImageType GST_TYPE_TAG_IMAGE_TYPE gst_tag_image_type_get_type -gst_tag_license_flags_get_type GstTagEntryMatch
@@ -1850,6 +1849,20 @@ gst_tag_get_language_code_iso_639_2B gst_tag_get_language_code_iso_639_2T +
+gsttaglicenses +gst/tag/tag.h +gst_tag_get_license_flags +gst_tag_get_license_nick +gst_tag_get_license_title +gst_tag_get_license_description +gst_tag_get_license_jurisdiction +gst_tag_get_license_version +gst_tag_get_licenses + +gst_tag_license_flags_get_type +
+
gsttagxmpwriter gst_tag_xmp_writer_add_all_schemas From 22cc529409a7dc62b36be256bc23899156e3c1c2 Mon Sep 17 00:00:00 2001 From: Alessandro Decina Date: Mon, 15 Aug 2011 23:41:24 +0200 Subject: [PATCH 138/140] rtspconnection: add OSX specific hack to detect when a connection is refused Unlike linux, OSX wakes up select with POLLOUT (instead of POLLERR) when connect() is done async and the connection is refused. Therefore always check for the socket error state using getsockopt (..., SO_ERROR, ...) after a connection attempt. --- gst-libs/gst/rtsp/gstrtspconnection.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/gst-libs/gst/rtsp/gstrtspconnection.c b/gst-libs/gst/rtsp/gstrtspconnection.c index 3e29efb280..2de21b9777 100644 --- a/gst-libs/gst/rtsp/gstrtspconnection.c +++ b/gst-libs/gst/rtsp/gstrtspconnection.c @@ -592,6 +592,14 @@ do_connect (const gchar * ip, guint16 port, GstPollFD * fdout, getsockopt (fd, SOL_SOCKET, SO_ERROR, (char *) &errno, &len); #endif goto sys_error; + } else { +#ifdef __APPLE__ + /* osx wakes up select with POLLOUT if the connection is refused... */ + socklen_t len = sizeof (errno); + getsockopt (fd, SOL_SOCKET, SO_ERROR, (char *) &errno, &len); + if (errno != 0) + goto sys_error; +#endif } gst_poll_fd_ignored (fdset, fdout); From 505bae099e60b391dc97bc55039178610f5736ea Mon Sep 17 00:00:00 2001 From: Vincent Penquerc'h Date: Fri, 12 Aug 2011 12:07:32 +0100 Subject: [PATCH 139/140] audioresample: add FFT based checks Send a few simple tones through audioresample and check that the main frequency spot is the same for the input and the resampled output. https://bugzilla.gnome.org/show_bug.cgi?id=656392 --- tests/check/Makefile.am | 1 + tests/check/elements/audioresample.c | 195 ++++++++++++++++++++++++++- 2 files changed, 194 insertions(+), 2 deletions(-) diff --git a/tests/check/Makefile.am b/tests/check/Makefile.am index 0bca65c23b..3280ba3018 100644 --- a/tests/check/Makefile.am +++ b/tests/check/Makefile.am @@ -400,6 +400,7 @@ elements_audioresample_CFLAGS = \ $(AM_CFLAGS) elements_audioresample_LDADD = \ + $(top_builddir)/gst-libs/gst/fft/libgstfft-@GST_MAJORMINOR@.la \ $(top_builddir)/gst-libs/gst/audio/libgstaudio-@GST_MAJORMINOR@.la \ $(top_builddir)/gst-libs/gst/interfaces/libgstinterfaces-@GST_MAJORMINOR@.la \ $(GST_BASE_LIBS) \ diff --git a/tests/check/elements/audioresample.c b/tests/check/elements/audioresample.c index a8a79a306b..792661a947 100644 --- a/tests/check/elements/audioresample.c +++ b/tests/check/elements/audioresample.c @@ -27,6 +27,12 @@ #include +#include +#include +#include +#include +#include + /* For ease of programming we use globals to keep refs for our floating * src and sink pads we create; otherwise we always have to do get_pad, * get_peer, and then remove references in every test function */ @@ -44,8 +50,8 @@ static GstPad *mysrcpad, *mysinkpad; "channels = (int) [ 1, MAX ], " \ "rate = (int) [ 1, MAX ], " \ "endianness = (int) BYTE_ORDER, " \ - "width = (int) 16, " \ - "depth = (int) 16, " \ + "width = (int) { 16, 32 }, " \ + "depth = (int) { 16, 32 }, " \ "signed = (bool) TRUE" #define RESAMPLE_CAPS_TEMPLATE_STRING \ @@ -900,6 +906,190 @@ GST_START_TEST (test_timestamp_drift) } GST_END_TEST; +#define FFT_HELPERS(type,ffttag,ffttag2,scale); \ +static gdouble magnitude##ffttag (const GstFFT##ffttag##Complex *c) \ +{ \ + gdouble mag = (gdouble) c->r * (gdouble) c->r; \ + mag += (gdouble) c->i * (gdouble) c->i; \ + mag /= scale * scale; \ + mag = 10.0 * log10 (mag); \ + return mag; \ +} \ +static gdouble find_main_frequency_spot_##ffttag (const GstFFT##ffttag##Complex *v, \ + int elements) \ +{ \ + int i; \ + gdouble maxmag = -9999; \ + int maxidx = 0; \ + for (i=0; i maxmag) { \ + maxmag = mag; \ + maxidx = i; \ + } \ + } \ + return maxidx / (gdouble) elements; \ +} \ +static gboolean is_zero_except_##ffttag (const GstFFT##ffttag##Complex *v, int elements, \ + gdouble spot) \ +{ \ + int i; \ + for (i=0; i 0.01) { \ + if (mag > -55.0) { \ + return FALSE; \ + } \ + } \ + } \ + return TRUE; \ +} \ +static void compare_ffts_##ffttag (const GstBuffer *inbuffer, const GstBuffer *outbuffer) \ +{ \ + int insamples = GST_BUFFER_SIZE (inbuffer) / sizeof(type) & ~1; \ + int outsamples = GST_BUFFER_SIZE (outbuffer) / sizeof(type) & ~1; \ + gdouble inspot, outspot; \ + \ + GstFFT##ffttag *inctx = gst_fft_##ffttag2##_new (insamples, FALSE); \ + GstFFT##ffttag##Complex *in = g_new (GstFFT##ffttag##Complex, insamples / 2 + 1); \ + GstFFT##ffttag *outctx = gst_fft_##ffttag2##_new (outsamples, FALSE); \ + GstFFT##ffttag##Complex *out = g_new (GstFFT##ffttag##Complex, outsamples / 2 + 1); \ + \ + gst_fft_##ffttag2##_window (inctx, (type*)GST_BUFFER_DATA (inbuffer), \ + GST_FFT_WINDOW_HAMMING); \ + gst_fft_##ffttag2##_fft (inctx, (type*)GST_BUFFER_DATA (inbuffer), in); \ + gst_fft_##ffttag2##_window (outctx, (type*)GST_BUFFER_DATA (outbuffer), \ + GST_FFT_WINDOW_HAMMING); \ + gst_fft_##ffttag2##_fft (outctx, (type*)GST_BUFFER_DATA (outbuffer), out); \ + \ + inspot = find_main_frequency_spot_##ffttag (in, insamples / 2 + 1); \ + outspot = find_main_frequency_spot_##ffttag (out, outsamples / 2 + 1); \ + GST_LOG ("Spots are %.3f and %.3f", inspot, outspot); \ + fail_unless (fabs (outspot - inspot) < 0.05); \ + fail_unless (is_zero_except_##ffttag (in, insamples / 2 + 1, inspot)); \ + fail_unless (is_zero_except_##ffttag (out, outsamples / 2 + 1, outspot)); \ + \ + gst_fft_##ffttag2##_free (inctx); \ + gst_fft_##ffttag2##_free (outctx); \ + g_free (in); \ + g_free (out); \ +} +FFT_HELPERS (float, F32, f32, 2048.0f); +FFT_HELPERS (double, F64, f64, 2048.0); +FFT_HELPERS (gint16, S16, s16, 32767.0); +FFT_HELPERS (gint32, S32, s32, 2147483647.0); + +#define FILL_BUFFER(type, desc, value); \ + static void init_##type##_##desc (GstBuffer *buffer) \ + { \ + type *ptr = (type *) GST_BUFFER_DATA (buffer); \ + int i, nsamples = GST_BUFFER_SIZE (buffer) / sizeof (type); \ + for (i = 0; i < nsamples; ++i) { \ + *ptr++ = value; \ + } \ + } + +FILL_BUFFER (float, silence, 0.0f); +FILL_BUFFER (double, silence, 0.0); +FILL_BUFFER (gint16, silence, 0); +FILL_BUFFER (gint32, silence, 0); +FILL_BUFFER (float, sine, sinf (i * 0.01f)); +FILL_BUFFER (float, sine2, sinf (i * 1.8f)); +FILL_BUFFER (double, sine, sin (i * 0.01)); +FILL_BUFFER (double, sine2, sin (i * 1.8)); +FILL_BUFFER (gint16, sine, (gint16) (32767 * sinf (i * 0.01f))); +FILL_BUFFER (gint16, sine2, (gint16) (32767 * sinf (i * 1.8f))); +FILL_BUFFER (gint32, sine, (gint32) (2147483647 * sinf (i * 0.01f))); +FILL_BUFFER (gint32, sine2, (gint32) (2147483647 * sinf (i * 1.8f))); + +static void +run_fft_pipeline (int inrate, int outrate, int quality, int width, gboolean fp, + void (*init) (GstBuffer *), + void (*compare_ffts) (const GstBuffer *, const GstBuffer *)) +{ + GstElement *audioresample; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + const int nsamples = 2048; + + audioresample = setup_audioresample (1, inrate, outrate, width, fp); + fail_unless (audioresample != NULL); + g_object_set (audioresample, "quality", quality, NULL); + caps = gst_pad_get_negotiated_caps (mysrcpad); + fail_unless (gst_caps_is_fixed (caps)); + + fail_unless (gst_element_set_state (audioresample, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + inbuffer = gst_buffer_new_and_alloc (nsamples * width / 8); + GST_BUFFER_DURATION (inbuffer) = GST_FRAMES_TO_CLOCK_TIME (nsamples, inrate); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + gst_buffer_set_caps (inbuffer, caps); + gst_buffer_ref (inbuffer); + + (*init) (inbuffer); + + /* pushing gives away my reference ... */ + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + /* ... but it ends up being collected on the global buffer list */ + fail_unless_equals_int (g_list_length (buffers), 1); + /* retrieve out buffer */ + fail_if ((outbuffer = (GstBuffer *) buffers->data) == NULL); + + fail_unless (gst_element_set_state (audioresample, + GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS, "could not set to null"); + + (*compare_ffts) (inbuffer, outbuffer); + + /* cleanup */ + gst_buffer_unref (inbuffer); + cleanup_audioresample (audioresample); +} + +GST_START_TEST (test_fft) +{ + int quality; + size_t f0, f1; + static const int frequencies[] = + { 8000, 16000, 44100, 48000, 128000, 12345, 54321 }; + + /* audioresample uses a mixed float/double code path for floats with quality>8, make sure we test it */ + for (quality = 0; quality <= 10; quality += 5) { + for (f0 = 0; f0 < G_N_ELEMENTS (frequencies); ++f0) { + for (f1 = 0; f1 < G_N_ELEMENTS (frequencies); ++f1) { + run_fft_pipeline (frequencies[f0], frequencies[f0], quality, 32, TRUE, + &init_float_silence, &compare_ffts_F32); + run_fft_pipeline (frequencies[f0], frequencies[f0], quality, 32, TRUE, + &init_float_sine, &compare_ffts_F32); + run_fft_pipeline (frequencies[f0], frequencies[f0], quality, 32, TRUE, + &init_float_sine2, &compare_ffts_F32); + run_fft_pipeline (frequencies[f0], frequencies[f0], quality, 64, TRUE, + &init_double_silence, &compare_ffts_F64); + run_fft_pipeline (frequencies[f0], frequencies[f0], quality, 64, TRUE, + &init_double_sine, &compare_ffts_F64); + run_fft_pipeline (frequencies[f0], frequencies[f0], quality, 64, TRUE, + &init_double_sine2, &compare_ffts_F64); + run_fft_pipeline (frequencies[f0], frequencies[f0], quality, 16, FALSE, + &init_gint16_silence, &compare_ffts_S16); + run_fft_pipeline (frequencies[f0], frequencies[f0], quality, 16, FALSE, + &init_gint16_sine, &compare_ffts_S16); + run_fft_pipeline (frequencies[f0], frequencies[f0], quality, 16, FALSE, + &init_gint16_sine2, &compare_ffts_S16); + run_fft_pipeline (frequencies[f0], frequencies[f0], quality, 32, FALSE, + &init_gint32_silence, &compare_ffts_S32); + run_fft_pipeline (frequencies[f0], frequencies[f0], quality, 32, FALSE, + &init_gint32_sine, &compare_ffts_S32); + run_fft_pipeline (frequencies[f0], frequencies[f0], quality, 32, FALSE, + &init_gint32_sine2, &compare_ffts_S32); + } + } + } +} + +GST_END_TEST; + static Suite * audioresample_suite (void) { @@ -913,6 +1103,7 @@ audioresample_suite (void) tcase_add_test (tc_chain, test_shutdown); tcase_add_test (tc_chain, test_live_switch); tcase_add_test (tc_chain, test_timestamp_drift); + tcase_add_test (tc_chain, test_fft); #ifndef GST_DISABLE_PARSE tcase_set_timeout (tc_chain, 360); From 6d875c5ccd710a0ae0e646a3a8c9a8636aba5c95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Mon, 15 Aug 2011 01:22:02 +0100 Subject: [PATCH 140/140] tag: id3: avoid some more relocations in genre table --- gst-libs/gst/tag/gstid3tag.c | 198 ++++++++--------------------------- tests/check/libs/tag.c | 1 + 2 files changed, 47 insertions(+), 152 deletions(-) diff --git a/gst-libs/gst/tag/gstid3tag.c b/gst-libs/gst/tag/gstid3tag.c index 1613be7f4a..8b1b8f1db7 100644 --- a/gst-libs/gst/tag/gstid3tag.c +++ b/gst-libs/gst/tag/gstid3tag.c @@ -41,155 +41,45 @@ #include #include -static const gchar *genres[] = { - "Blues", - "Classic Rock", - "Country", - "Dance", - "Disco", - "Funk", - "Grunge", - "Hip-Hop", - "Jazz", - "Metal", - "New Age", - "Oldies", - "Other", - "Pop", - "R&B", - "Rap", - "Reggae", - "Rock", - "Techno", - "Industrial", - "Alternative", - "Ska", - "Death Metal", - "Pranks", - "Soundtrack", - "Euro-Techno", - "Ambient", - "Trip-Hop", - "Vocal", - "Jazz+Funk", - "Fusion", - "Trance", - "Classical", - "Instrumental", - "Acid", - "House", - "Game", - "Sound Clip", - "Gospel", - "Noise", - "Alternative Rock", - "Bass", - "Soul", - "Punk", - "Space", - "Meditative", - "Instrumental Pop", - "Instrumental Rock", - "Ethnic", - "Gothic", - "Darkwave", - "Techno-Industrial", - "Electronic", - "Pop-Folk", - "Eurodance", - "Dream", - "Southern Rock", - "Comedy", - "Cult", - "Gangsta", - "Top 40", - "Christian Rap", - "Pop/Funk", - "Jungle", - "Native American", - "Cabaret", - "New Wave", - "Psychedelic", - "Rave", - "Showtunes", - "Trailer", - "Lo-Fi", - "Tribal", - "Acid Punk", - "Acid Jazz", - "Polka", - "Retro", - "Musical", - "Rock & Roll", - "Hard Rock", - "Folk", - "Folk/Rock", - "National Folk", - "Swing", - "Fusion", - "Bebob", - "Latin", - "Revival", - "Celtic", - "Bluegrass", - "Avantgarde", - "Gothic Rock", - "Progressive Rock", - "Psychedelic Rock", - "Symphonic Rock", - "Slow Rock", - "Big Band", - "Chorus", - "Easy Listening", - "Acoustic", - "Humour", - "Speech", - "Chanson", - "Opera", - "Chamber Music", - "Sonata", - "Symphony", - "Booty Bass", - "Primus", - "Porn Groove", - "Satire", - "Slow Jam", - "Club", - "Tango", - "Samba", - "Folklore", - "Ballad", - "Power Ballad", - "Rhythmic Soul", - "Freestyle", - "Duet", - "Punk Rock", - "Drum Solo", - "A Capella", - "Euro-House", - "Dance Hall", - "Goa", - "Drum & Bass", - "Club-House", - "Hardcore", - "Terror", - "Indie", - "BritPop", - "Negerpunk", - "Polsk Punk", - "Beat", - "Christian Gangsta Rap", - "Heavy Metal", - "Black Metal", - "Crossover", - "Contemporary Christian", - "Christian Rock", - "Merengue", - "Salsa", - "Thrash Metal", - "Anime", - "Jpop", - "Synthpop" +static const gchar genres[] = + "Blues\000Classic Rock\000Country\000Dance\000Disco\000Funk\000Grunge\000" + "Hip-Hop\000Jazz\000Metal\000New Age\000Oldies\000Other\000Pop\000R&B\000" + "Rap\000Reggae\000Rock\000Techno\000Industrial\000Alternative\000Ska\000" + "Death Metal\000Pranks\000Soundtrack\000Euro-Techno\000Ambient\000Trip-Hop" + "\000Vocal\000Jazz+Funk\000Fusion\000Trance\000Classical\000Instrumental\000" + "Acid\000House\000Game\000Sound Clip\000Gospel\000Noise\000Alternative Rock" + "\000Bass\000Soul\000Punk\000Space\000Meditative\000Instrumental Pop\000" + "Instrumental Rock\000Ethnic\000Gothic\000Darkwave\000Techno-Industrial\000" + "Electronic\000Pop-Folk\000Eurodance\000Dream\000Southern Rock\000Comedy" + "\000Cult\000Gangsta\000Top 40\000Christian Rap\000Pop/Funk\000Jungle\000" + "Native American\000Cabaret\000New Wave\000Psychedelic\000Rave\000Showtunes" + "\000Trailer\000Lo-Fi\000Tribal\000Acid Punk\000Acid Jazz\000Polka\000" + "Retro\000Musical\000Rock & Roll\000Hard Rock\000Folk\000Folk/Rock\000" + "National Folk\000Swing\000Bebob\000Latin\000Revival\000Celtic\000Bluegrass" + "\000Avantgarde\000Gothic Rock\000Progressive Rock\000Psychedelic Rock\000" + "Symphonic Rock\000Slow Rock\000Big Band\000Chorus\000Easy Listening\000" + "Acoustic\000Humour\000Speech\000Chanson\000Opera\000Chamber Music\000" + "Sonata\000Symphony\000Booty Bass\000Primus\000Porn Groove\000Satire\000" + "Slow Jam\000Club\000Tango\000Samba\000Folklore\000Ballad\000Power Ballad\000" + "Rhythmic Soul\000Freestyle\000Duet\000Punk Rock\000Drum Solo\000A Capella" + "\000Euro-House\000Dance Hall\000Goa\000Drum & Bass\000Club-House\000" + "Hardcore\000Terror\000Indie\000BritPop\000Negerpunk\000Polsk Punk\000" + "Beat\000Christian Gangsta Rap\000Heavy Metal\000Black Metal\000" + "Crossover\000Contemporary Christian\000Christian Rock\000Merengue\000" + "Salsa\000Thrash Metal\000Anime\000Jpop\000Synthpop"; + +static const guint16 genres_idx[] = { + 0, 6, 19, 27, 33, 39, 44, 51, 59, 64, 70, 78, 85, 91, 95, 99, 103, 110, 115, + 122, 133, 145, 149, 161, 168, 179, 191, 199, 208, 214, 224, 231, 238, 248, + 261, 266, 272, 277, 288, 295, 301, 318, 323, 328, 333, 339, 350, 367, 385, + 392, 399, 408, 426, 437, 446, 456, 462, 476, 483, 488, 496, 503, 517, 526, + 533, 549, 557, 566, 578, 583, 593, 601, 607, 614, 624, 634, 640, 646, 654, + 666, 676, 681, 691, 705, 224, 711, 717, 723, 731, 738, 748, 759, 771, 788, + 805, 820, 830, 839, 846, 861, 870, 877, 884, 892, 898, 912, 919, 928, 939, + 946, 958, 965, 974, 979, 985, 991, 1000, 1007, 1020, 1034, 1044, 1049, + 1059, 1069, 1079, 1090, 1101, 1105, 1117, 1128, 1137, 1144, 1150, 1158, + 1168, 1179, 1184, 1206, 1218, 1230, 1240, 1263, 1278, 1287, 1293, 1306, + 1312, 1317 }; static const GstTagEntryMatch tag_matches[] = { @@ -412,7 +302,7 @@ gst_tag_list_new_from_id3v1 (const guint8 * data) guint gst_tag_id3_genre_count (void) { - return G_N_ELEMENTS (genres); + return G_N_ELEMENTS (genres_idx); } /** @@ -426,9 +316,13 @@ gst_tag_id3_genre_count (void) const gchar * gst_tag_id3_genre_get (const guint id) { - if (id >= G_N_ELEMENTS (genres)) + guint idx; + + if (id >= G_N_ELEMENTS (genres_idx)) return NULL; - return genres[id]; + idx = genres_idx[id]; + g_assert (idx < sizeof (genres)); + return &genres[idx]; } /** diff --git a/tests/check/libs/tag.c b/tests/check/libs/tag.c index e6faa8f40b..5b53e8900d 100644 --- a/tests/check/libs/tag.c +++ b/tests/check/libs/tag.c @@ -578,6 +578,7 @@ GST_START_TEST (test_id3_tags) const gchar *genre; genre = gst_tag_id3_genre_get (i); + GST_LOG ("genre: %s", genre); fail_unless (genre != NULL); }