From 5c68e06b001b4dae18adb25de79e8af785a209bf Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Sun, 26 May 2019 20:20:03 +1000 Subject: [PATCH] qtdemux: Split tag reading functions out Move some code out of the enormous qtdemux.c into a separate qtdemux_tags helper, and make some structs available via qtdemux.h to accommodate that. Part-of: --- gst/isomp4/meson.build | 1 + gst/isomp4/qtdemux.c | 1234 +------------------------------------ gst/isomp4/qtdemux.h | 241 ++++++++ gst/isomp4/qtdemux_tags.c | 1034 +++++++++++++++++++++++++++++++ gst/isomp4/qtdemux_tags.h | 30 + 5 files changed, 1311 insertions(+), 1229 deletions(-) create mode 100644 gst/isomp4/qtdemux_tags.c create mode 100644 gst/isomp4/qtdemux_tags.h diff --git a/gst/isomp4/meson.build b/gst/isomp4/meson.build index d5b8afc411..17a83620b2 100644 --- a/gst/isomp4/meson.build +++ b/gst/isomp4/meson.build @@ -5,6 +5,7 @@ mp4_sources = [ 'qtdemux_types.c', 'qtdemux_dump.c', 'qtdemux_lang.c', + 'qtdemux_tags.c', 'qtdemux_tree.c', 'gstisoff.c', 'gstqtmux.c', diff --git a/gst/isomp4/qtdemux.c b/gst/isomp4/qtdemux.c index 77c63209b9..00956a6c25 100644 --- a/gst/isomp4/qtdemux.c +++ b/gst/isomp4/qtdemux.c @@ -56,7 +56,6 @@ #include #include #include -#include #include #include @@ -68,9 +67,9 @@ #include "qtdemux_lang.h" #include "qtdemux.h" #include "qtpalette.h" +#include "qtdemux_tags.h" #include "qtdemux_tree.h" -#include #include #include @@ -106,24 +105,13 @@ #define QTDEMUX_NTH_OLD_STREAM(demux,idx) \ QTDEMUX_STREAM(g_ptr_array_index((demux)->old_streams,idx)) +#define CUR_STREAM(s) (&((s)->stsd_entries[(s)->cur_stsd_entry_index])) + GST_DEBUG_CATEGORY (qtdemux_debug); #define GST_CAT_DEFAULT qtdemux_debug -typedef struct _QtDemuxSegment QtDemuxSegment; -typedef struct _QtDemuxSample QtDemuxSample; - typedef struct _QtDemuxCencSampleSetInfo QtDemuxCencSampleSetInfo; -struct _QtDemuxSample -{ - guint32 size; - gint32 pts_offset; /* Add this value to timestamp to get the pts */ - guint64 offset; - guint64 timestamp; /* DTS In mov time */ - guint32 duration; /* In mov time */ - gboolean keyframe; /* TRUE when this packet is a keyframe */ -}; - /* Macros for converting to/from timescale */ #define QTSTREAMTIME_TO_GSTTIME(stream, value) (gst_util_uint64_scale((value), GST_SECOND, (stream)->timescale)) #define GSTTIME_TO_QTSTREAMTIME(stream, value) (gst_util_uint64_scale((value), (stream)->timescale, GST_SECOND)) @@ -229,239 +217,13 @@ struct _QtDemuxSegment #define QTSEGMENT_IS_EMPTY(s) ((s)->media_start == GST_CLOCK_TIME_NONE) /* Used with fragmented MP4 files (mfra atom) */ -typedef struct +struct _QtDemuxRandomAccessEntry { GstClockTime ts; guint64 moof_offset; -} QtDemuxRandomAccessEntry; - -typedef struct _QtDemuxStreamStsdEntry -{ - GstCaps *caps; - guint32 fourcc; - gboolean sparse; - - /* video info */ - gint width; - gint height; - gint par_w; - gint par_h; - /* Numerator/denominator framerate */ - gint fps_n; - gint fps_d; - GstVideoColorimetry colorimetry; - guint16 bits_per_sample; - guint16 color_table_id; - GstMemory *rgb8_palette; - guint interlace_mode; - guint field_order; - - /* audio info */ - gdouble rate; - gint n_channels; - guint samples_per_packet; - guint samples_per_frame; - guint bytes_per_packet; - guint bytes_per_sample; - guint bytes_per_frame; - guint compression; - - /* if we use chunks or samples */ - gboolean sampled; - guint padding; - -} QtDemuxStreamStsdEntry; - -#define CUR_STREAM(s) (&((s)->stsd_entries[(s)->cur_stsd_entry_index])) - -struct _QtDemuxStream -{ - GstPad *pad; - - GstQTDemux *demux; - gchar *stream_id; - - QtDemuxStreamStsdEntry *stsd_entries; - guint stsd_entries_length; - guint cur_stsd_entry_index; - - /* stream type */ - guint32 subtype; - - gboolean new_caps; /* If TRUE, caps need to be generated (by - * calling _configure_stream()) This happens - * for MSS and fragmented streams */ - - gboolean new_stream; /* signals that a stream_start is required */ - gboolean on_keyframe; /* if this stream last pushed buffer was a - * keyframe. This is important to identify - * where to stop pushing buffers after a - * segment stop time */ - - /* if the stream has a redirect URI in its headers, we store it here */ - gchar *redirect_uri; - - /* track id */ - guint track_id; - - /* duration/scale */ - guint64 duration; /* in timescale units */ - guint32 timescale; - - /* language */ - gchar lang_id[4]; /* ISO 639-2T language code */ - - /* our samples */ - guint32 n_samples; - QtDemuxSample *samples; - gboolean all_keyframe; /* TRUE when all samples are keyframes (no stss) */ - guint32 n_samples_moof; /* sample count in a moof */ - guint64 duration_moof; /* duration in timescale of a moof, used for figure out - * the framerate of fragmented format stream */ - guint64 duration_last_moof; - - guint32 offset_in_sample; /* Offset in the current sample, used for - * streams which have got exceedingly big - * sample size (such as 24s of raw audio). - * Only used when max_buffer_size is non-NULL */ - guint32 min_buffer_size; /* Minimum allowed size for output buffers. - * Currently only set for raw audio streams*/ - guint32 max_buffer_size; /* Maximum allowed size for output buffers. - * Currently only set for raw audio streams*/ - - /* video info */ - /* aspect ratio */ - gint display_width; - gint display_height; - - /* allocation */ - gboolean use_allocator; - GstAllocator *allocator; - GstAllocationParams params; - - gsize alignment; - - /* when a discontinuity is pending */ - gboolean discont; - - /* list of buffers to push first */ - GSList *buffers; - - /* if we need to clip this buffer. This is only needed for uncompressed - * data */ - gboolean need_clip; - - /* buffer needs some custom processing, e.g. subtitles */ - gboolean need_process; - /* buffer needs potentially be split, e.g. CEA608 subtitles */ - gboolean need_split; - - /* current position */ - guint32 segment_index; - guint32 sample_index; - GstClockTime time_position; /* in gst time */ - guint64 accumulated_base; - - /* the Gst segment we are processing out, used for clipping */ - GstSegment segment; - - /* quicktime segments */ - guint32 n_segments; - QtDemuxSegment *segments; - gboolean dummy_segment; - guint32 from_sample; - guint32 to_sample; - - gboolean sent_eos; - GstTagList *stream_tags; - gboolean send_global_tags; - - GstEvent *pending_event; - - GstByteReader stco; - GstByteReader stsz; - GstByteReader stsc; - GstByteReader stts; - GstByteReader stss; - GstByteReader stps; - GstByteReader ctts; - - gboolean chunks_are_samples; /* TRUE means treat chunks as samples */ - gint64 stbl_index; - /* stco */ - guint co_size; - GstByteReader co_chunk; - guint32 first_chunk; - guint32 current_chunk; - guint32 last_chunk; - guint32 samples_per_chunk; - guint32 stsd_sample_description_id; - guint32 stco_sample_index; - /* stsz */ - guint32 sample_size; /* 0 means variable sizes are stored in stsz */ - /* stsc */ - guint32 stsc_index; - guint32 n_samples_per_chunk; - guint32 stsc_chunk_index; - guint32 stsc_sample_index; - guint64 chunk_offset; - /* stts */ - guint32 stts_index; - guint32 stts_samples; - guint32 n_sample_times; - guint32 stts_sample_index; - guint64 stts_time; - guint32 stts_duration; - /* stss */ - gboolean stss_present; - guint32 n_sample_syncs; - guint32 stss_index; - /* stps */ - gboolean stps_present; - guint32 n_sample_partial_syncs; - guint32 stps_index; - QtDemuxRandomAccessEntry *ra_entries; - guint n_ra_entries; - - const QtDemuxRandomAccessEntry *pending_seek; - - /* ctts */ - gboolean ctts_present; - guint32 n_composition_times; - guint32 ctts_index; - guint32 ctts_sample_index; - guint32 ctts_count; - gint32 ctts_soffset; - - /* cslg */ - guint32 cslg_shift; - - /* fragmented */ - gboolean parsed_trex; - guint32 def_sample_description_index; /* index is 1-based */ - guint32 def_sample_duration; - guint32 def_sample_size; - guint32 def_sample_flags; - - gboolean disabled; - - /* stereoscopic video streams */ - GstVideoMultiviewMode multiview_mode; - GstVideoMultiviewFlags multiview_flags; - - /* protected streams */ - gboolean protected; - guint32 protection_scheme_type; - guint32 protection_scheme_version; - gpointer protection_scheme_info; /* specific to the protection scheme */ - GQueue protection_scheme_event_queue; - - /* KEY_UNITS trickmode with an interval */ - GstClockTime last_keyframe_dts; - - gint ref_count; /* atomic */ }; + /* Contains properties and cryptographic info for a set of samples from a * track protected using Common Encryption (cenc) */ struct _QtDemuxCencSampleSetInfo @@ -566,8 +328,6 @@ static gboolean qtdemux_parse_moov (GstQTDemux * qtdemux, static gboolean qtdemux_parse_node (GstQTDemux * qtdemux, GNode * node, const guint8 * buffer, guint length); static gboolean qtdemux_parse_tree (GstQTDemux * qtdemux); -static void qtdemux_parse_udta (GstQTDemux * qtdemux, GstTagList * taglist, - GNode * udta); static void gst_qtdemux_handle_esds (GstQTDemux * qtdemux, QtDemuxStream * stream, QtDemuxStreamStsdEntry * entry, GNode * esds, @@ -2881,27 +2641,6 @@ qtdemux_parse_ftyp (GstQTDemux * qtdemux, const guint8 * buffer, gint length) } } -static void -qtdemux_handle_xmp_taglist (GstQTDemux * qtdemux, GstTagList * taglist, - GstTagList * xmptaglist) -{ - /* Strip out bogus fields */ - if (xmptaglist) { - if (gst_tag_list_get_scope (taglist) == GST_TAG_SCOPE_GLOBAL) { - gst_tag_list_remove_tag (xmptaglist, GST_TAG_VIDEO_CODEC); - gst_tag_list_remove_tag (xmptaglist, GST_TAG_AUDIO_CODEC); - } else { - gst_tag_list_remove_tag (xmptaglist, GST_TAG_CONTAINER_FORMAT); - } - - GST_DEBUG_OBJECT (qtdemux, "Found XMP tags %" GST_PTR_FORMAT, xmptaglist); - - /* prioritize native tags using _KEEP mode */ - gst_tag_list_insert (taglist, xmptaglist, GST_TAG_MERGE_KEEP); - gst_tag_list_unref (xmptaglist); - } -} - static void qtdemux_update_default_sample_encryption_settings (GstQTDemux * qtdemux, QtDemuxCencSampleSetInfo * info, guint32 is_encrypted, guint8 iv_size, @@ -13372,969 +13111,6 @@ qtdemux_expose_streams (GstQTDemux * qtdemux) return GST_FLOW_OK; } -/* check if major or compatible brand is 3GP */ -static inline gboolean -qtdemux_is_brand_3gp (GstQTDemux * qtdemux, gboolean major) -{ - if (major) { - return ((qtdemux->major_brand & GST_MAKE_FOURCC (255, 255, 0, 0)) == - FOURCC_3g__); - } else if (qtdemux->comp_brands != NULL) { - GstMapInfo map; - guint8 *data; - gsize size; - gboolean res = FALSE; - - gst_buffer_map (qtdemux->comp_brands, &map, GST_MAP_READ); - data = map.data; - size = map.size; - while (size >= 4) { - res = res || ((QT_FOURCC (data) & GST_MAKE_FOURCC (255, 255, 0, 0)) == - FOURCC_3g__); - data += 4; - size -= 4; - } - gst_buffer_unmap (qtdemux->comp_brands, &map); - return res; - } else { - return FALSE; - } -} - -/* check if tag is a spec'ed 3GP tag keyword storing a string */ -static inline gboolean -qtdemux_is_string_tag_3gp (GstQTDemux * qtdemux, guint32 fourcc) -{ - return fourcc == FOURCC_cprt || fourcc == FOURCC_gnre || fourcc == FOURCC_titl - || fourcc == FOURCC_dscp || fourcc == FOURCC_perf || fourcc == FOURCC_auth - || fourcc == FOURCC_albm; -} - -static void -qtdemux_tag_add_location (GstQTDemux * qtdemux, GstTagList * taglist, - const char *tag, const char *dummy, GNode * node) -{ - const gchar *env_vars[] = { "GST_QT_TAG_ENCODING", "GST_TAG_ENCODING", NULL }; - int offset; - char *name; - gchar *data; - gdouble longitude, latitude, altitude; - gint len; - - len = QT_UINT32 (node->data); - if (len <= 14) - goto short_read; - - data = node->data; - offset = 14; - - /* TODO: language code skipped */ - - name = gst_tag_freeform_string_to_utf8 (data + offset, -1, env_vars); - - if (!name) { - /* do not alarm in trivial case, but bail out otherwise */ - if (*(data + offset) != 0) { - GST_DEBUG_OBJECT (qtdemux, "failed to convert %s tag to UTF-8, " - "giving up", tag); - } - } else { - gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, - GST_TAG_GEO_LOCATION_NAME, name, NULL); - offset += strlen (name); - g_free (name); - } - - if (len < offset + 2 + 4 + 4 + 4) - goto short_read; - - /* +1 +1 = skip null-terminator and location role byte */ - offset += 1 + 1; - /* table in spec says unsigned, semantics say negative has meaning ... */ - longitude = QT_SFP32 (data + offset); - - offset += 4; - latitude = QT_SFP32 (data + offset); - - offset += 4; - altitude = QT_SFP32 (data + offset); - - /* one invalid means all are invalid */ - if (longitude >= -180.0 && longitude <= 180.0 && - latitude >= -90.0 && latitude <= 90.0) { - gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, - GST_TAG_GEO_LOCATION_LATITUDE, latitude, - GST_TAG_GEO_LOCATION_LONGITUDE, longitude, - GST_TAG_GEO_LOCATION_ELEVATION, altitude, NULL); - } - - /* TODO: no GST_TAG_, so astronomical body and additional notes skipped */ - - return; - - /* ERRORS */ -short_read: - { - GST_DEBUG_OBJECT (qtdemux, "short read parsing 3GP location"); - return; - } -} - - -static void -qtdemux_tag_add_year (GstQTDemux * qtdemux, GstTagList * taglist, - const char *tag, const char *dummy, GNode * node) -{ - guint16 y; - GDate *date; - gint len; - - len = QT_UINT32 (node->data); - if (len < 14) - return; - - y = QT_UINT16 ((guint8 *) node->data + 12); - if (y == 0) { - GST_DEBUG_OBJECT (qtdemux, "year: %u is not a valid year", y); - return; - } - GST_DEBUG_OBJECT (qtdemux, "year: %u", y); - - date = g_date_new_dmy (1, 1, y); - gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, date, NULL); - g_date_free (date); -} - -static void -qtdemux_tag_add_classification (GstQTDemux * qtdemux, GstTagList * taglist, - const char *tag, const char *dummy, GNode * node) -{ - int offset; - char *tag_str = NULL; - guint8 *entity; - guint16 table; - gint len; - - len = QT_UINT32 (node->data); - if (len <= 20) - goto short_read; - - offset = 12; - entity = (guint8 *) node->data + offset; - if (entity[0] == 0 || entity[1] == 0 || entity[2] == 0 || entity[3] == 0) { - GST_DEBUG_OBJECT (qtdemux, - "classification info: %c%c%c%c invalid classification entity", - entity[0], entity[1], entity[2], entity[3]); - return; - } - - offset += 4; - table = QT_UINT16 ((guint8 *) node->data + offset); - - /* Language code skipped */ - - offset += 4; - - /* Tag format: "XXXX://Y[YYYY]/classification info string" - * XXXX: classification entity, fixed length 4 chars. - * Y[YYYY]: classification table, max 5 chars. - */ - tag_str = g_strdup_printf ("----://%u/%s", - table, (char *) node->data + offset); - - /* memcpy To be sure we're preserving byte order */ - memcpy (tag_str, entity, 4); - GST_DEBUG_OBJECT (qtdemux, "classification info: %s", tag_str); - - gst_tag_list_add (taglist, GST_TAG_MERGE_APPEND, tag, tag_str, NULL); - - g_free (tag_str); - - return; - - /* ERRORS */ -short_read: - { - GST_DEBUG_OBJECT (qtdemux, "short read parsing 3GP classification"); - return; - } -} - -static gboolean -qtdemux_tag_add_str_full (GstQTDemux * qtdemux, GstTagList * taglist, - const char *tag, const char *dummy, GNode * node) -{ - const gchar *env_vars[] = { "GST_QT_TAG_ENCODING", "GST_TAG_ENCODING", NULL }; - GNode *data; - char *s; - int len; - guint32 type; - int offset; - gboolean ret = TRUE; - const gchar *charset = NULL; - - data = qtdemux_tree_get_child_by_type (node, FOURCC_data); - if (data) { - len = QT_UINT32 (data->data); - type = QT_UINT32 ((guint8 *) data->data + 8); - if (type == 0x00000001 && len > 16) { - s = gst_tag_freeform_string_to_utf8 ((char *) data->data + 16, len - 16, - env_vars); - if (s) { - GST_DEBUG_OBJECT (qtdemux, "adding tag %s", GST_STR_NULL (s)); - gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, s, NULL); - g_free (s); - } else { - GST_DEBUG_OBJECT (qtdemux, "failed to convert %s tag to UTF-8", tag); - } - } - } else { - len = QT_UINT32 (node->data); - type = QT_UINT32 ((guint8 *) node->data + 4); - if ((type >> 24) == 0xa9 && len > 8 + 4) { - gint str_len; - gint lang_code; - - /* Type starts with the (C) symbol, so the next data is a list - * of (string size(16), language code(16), string) */ - - str_len = QT_UINT16 ((guint8 *) node->data + 8); - lang_code = QT_UINT16 ((guint8 *) node->data + 10); - - /* the string + fourcc + size + 2 16bit fields, - * means that there are more tags in this atom */ - if (len > str_len + 8 + 4) { - /* TODO how to represent the same tag in different languages? */ - GST_WARNING_OBJECT (qtdemux, "Ignoring metadata entry with multiple " - "text alternatives, reading only first one"); - } - - offset = 12; - len = MIN (len, str_len + 8 + 4); /* remove trailing strings that we don't use */ - GST_DEBUG_OBJECT (qtdemux, "found international text tag"); - - if (lang_code < 0x800) { /* MAC encoded string */ - charset = "mac"; - } - } else if (len > 14 && qtdemux_is_string_tag_3gp (qtdemux, - QT_FOURCC ((guint8 *) node->data + 4))) { - guint32 type = QT_UINT32 ((guint8 *) node->data + 8); - - /* we go for 3GP style encoding if major brands claims so, - * or if no hope for data be ok UTF-8, and compatible 3GP brand present */ - if (qtdemux_is_brand_3gp (qtdemux, TRUE) || - (qtdemux_is_brand_3gp (qtdemux, FALSE) && - ((type & 0x00FFFFFF) == 0x0) && (type >> 24 <= 0xF))) { - offset = 14; - /* 16-bit Language code is ignored here as well */ - GST_DEBUG_OBJECT (qtdemux, "found 3gpp text tag"); - } else { - goto normal; - } - } else { - normal: - offset = 8; - GST_DEBUG_OBJECT (qtdemux, "found normal text tag"); - ret = FALSE; /* may have to fallback */ - } - if (charset) { - GError *err = NULL; - - s = g_convert ((gchar *) node->data + offset, len - offset, "utf8", - charset, NULL, NULL, &err); - if (err) { - GST_DEBUG_OBJECT (qtdemux, "Failed to convert string from charset %s:" - " %s(%d): %s", charset, g_quark_to_string (err->domain), err->code, - err->message); - g_error_free (err); - } - } else { - s = gst_tag_freeform_string_to_utf8 ((char *) node->data + offset, - len - offset, env_vars); - } - if (s) { - GST_DEBUG_OBJECT (qtdemux, "adding tag %s", GST_STR_NULL (s)); - gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, s, NULL); - g_free (s); - ret = TRUE; - } else { - GST_DEBUG_OBJECT (qtdemux, "failed to convert %s tag to UTF-8", tag); - } - } - return ret; -} - -static void -qtdemux_tag_add_str (GstQTDemux * qtdemux, GstTagList * taglist, - const char *tag, const char *dummy, GNode * node) -{ - qtdemux_tag_add_str_full (qtdemux, taglist, tag, dummy, node); -} - -static void -qtdemux_tag_add_keywords (GstQTDemux * qtdemux, GstTagList * taglist, - const char *tag, const char *dummy, GNode * node) -{ - const gchar *env_vars[] = { "GST_QT_TAG_ENCODING", "GST_TAG_ENCODING", NULL }; - guint8 *data; - char *s, *t, *k = NULL; - int len; - int offset; - int count; - - /* first try normal string tag if major brand not 3GP */ - if (!qtdemux_is_brand_3gp (qtdemux, TRUE)) { - if (!qtdemux_tag_add_str_full (qtdemux, taglist, tag, dummy, node)) { - /* hm, that did not work, maybe 3gpp storage in non-3gpp major brand; - * let's try it 3gpp way after minor safety check */ - data = node->data; - if (QT_UINT32 (data) < 15 || !qtdemux_is_brand_3gp (qtdemux, FALSE)) - return; - } else - return; - } - - GST_DEBUG_OBJECT (qtdemux, "found 3gpp keyword tag"); - - data = node->data; - - len = QT_UINT32 (data); - if (len < 15) - goto short_read; - - count = QT_UINT8 (data + 14); - offset = 15; - for (; count; count--) { - gint slen; - - if (offset + 1 > len) - goto short_read; - slen = QT_UINT8 (data + offset); - offset += 1; - if (offset + slen > len) - goto short_read; - s = gst_tag_freeform_string_to_utf8 ((char *) node->data + offset, - slen, env_vars); - if (s) { - GST_DEBUG_OBJECT (qtdemux, "adding keyword %s", GST_STR_NULL (s)); - if (k) { - t = g_strjoin (",", k, s, NULL); - g_free (s); - g_free (k); - k = t; - } else { - k = s; - } - } else { - GST_DEBUG_OBJECT (qtdemux, "failed to convert keyword to UTF-8"); - } - offset += slen; - } - -done: - if (k) { - GST_DEBUG_OBJECT (qtdemux, "adding tag %s", GST_STR_NULL (k)); - gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, k, NULL); - } - g_free (k); - - return; - - /* ERRORS */ -short_read: - { - GST_DEBUG_OBJECT (qtdemux, "short read parsing 3GP keywords"); - goto done; - } -} - -static void -qtdemux_tag_add_num (GstQTDemux * qtdemux, GstTagList * taglist, - const char *tag1, const char *tag2, GNode * node) -{ - GNode *data; - int len; - int type; - int n1, n2; - - data = qtdemux_tree_get_child_by_type (node, FOURCC_data); - if (data) { - len = QT_UINT32 (data->data); - type = QT_UINT32 ((guint8 *) data->data + 8); - if (type == 0x00000000 && len >= 22) { - n1 = QT_UINT16 ((guint8 *) data->data + 18); - n2 = QT_UINT16 ((guint8 *) data->data + 20); - if (n1 > 0) { - GST_DEBUG_OBJECT (qtdemux, "adding tag %s=%d", tag1, n1); - gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag1, n1, NULL); - } - if (n2 > 0) { - GST_DEBUG_OBJECT (qtdemux, "adding tag %s=%d", tag2, n2); - gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag2, n2, NULL); - } - } - } -} - -static void -qtdemux_tag_add_tmpo (GstQTDemux * qtdemux, GstTagList * taglist, - const char *tag1, const char *dummy, GNode * node) -{ - GNode *data; - int len; - int type; - int n1; - - data = qtdemux_tree_get_child_by_type (node, FOURCC_data); - if (data) { - len = QT_UINT32 (data->data); - type = QT_UINT32 ((guint8 *) data->data + 8); - GST_DEBUG_OBJECT (qtdemux, "have tempo tag, type=%d,len=%d", type, len); - /* some files wrongly have a type 0x0f=15, but it should be 0x15 */ - if ((type == 0x00000015 || type == 0x0000000f) && len >= 18) { - n1 = QT_UINT16 ((guint8 *) data->data + 16); - if (n1) { - /* do not add bpm=0 */ - GST_DEBUG_OBJECT (qtdemux, "adding tag %d", n1); - gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag1, (gdouble) n1, - NULL); - } - } - } -} - -static void -qtdemux_tag_add_uint32 (GstQTDemux * qtdemux, GstTagList * taglist, - const char *tag1, const char *dummy, GNode * node) -{ - GNode *data; - int len; - int type; - guint32 num; - - data = qtdemux_tree_get_child_by_type (node, FOURCC_data); - if (data) { - len = QT_UINT32 (data->data); - type = QT_UINT32 ((guint8 *) data->data + 8); - GST_DEBUG_OBJECT (qtdemux, "have %s tag, type=%d,len=%d", tag1, type, len); - /* some files wrongly have a type 0x0f=15, but it should be 0x15 */ - if ((type == 0x00000015 || type == 0x0000000f) && len >= 20) { - num = QT_UINT32 ((guint8 *) data->data + 16); - if (num) { - /* do not add num=0 */ - GST_DEBUG_OBJECT (qtdemux, "adding tag %d", num); - gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag1, num, NULL); - } - } - } -} - -static void -qtdemux_tag_add_covr (GstQTDemux * qtdemux, GstTagList * taglist, - const char *tag1, const char *dummy, GNode * node) -{ - GNode *data; - int len; - int type; - GstSample *sample; - - data = qtdemux_tree_get_child_by_type (node, FOURCC_data); - if (data) { - len = QT_UINT32 (data->data); - type = QT_UINT32 ((guint8 *) data->data + 8); - GST_DEBUG_OBJECT (qtdemux, "have covr tag, type=%d,len=%d", type, len); - if ((type == 0x0000000d || type == 0x0000000e) && len > 16) { - GstTagImageType image_type; - - if (gst_tag_list_get_tag_size (taglist, GST_TAG_IMAGE) == 0) - image_type = GST_TAG_IMAGE_TYPE_FRONT_COVER; - else - image_type = GST_TAG_IMAGE_TYPE_NONE; - - if ((sample = - gst_tag_image_data_to_image_sample ((guint8 *) data->data + 16, - len - 16, image_type))) { - GST_DEBUG_OBJECT (qtdemux, "adding tag size %d", len - 16); - gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag1, sample, NULL); - gst_sample_unref (sample); - } - } - } -} - -static void -qtdemux_tag_add_date (GstQTDemux * qtdemux, GstTagList * taglist, - const char *tag, const char *dummy, GNode * node) -{ - GNode *data; - GstDateTime *datetime = NULL; - char *s; - int len; - int type; - - data = qtdemux_tree_get_child_by_type (node, FOURCC_data); - if (data) { - len = QT_UINT32 (data->data); - type = QT_UINT32 ((guint8 *) data->data + 8); - if (type == 0x00000001 && len > 16) { - guint y, m = 1, d = 1; - gint ret; - - s = g_strndup ((char *) data->data + 16, len - 16); - GST_DEBUG_OBJECT (qtdemux, "adding date '%s'", s); - datetime = gst_date_time_new_from_iso8601_string (s); - if (datetime != NULL) { - gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, GST_TAG_DATE_TIME, - datetime, NULL); - gst_date_time_unref (datetime); - } - - ret = sscanf (s, "%u-%u-%u", &y, &m, &d); - if (ret >= 1 && y > 1500 && y < 3000) { - GDate *date; - - date = g_date_new_dmy (d, m, y); - gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, date, NULL); - g_date_free (date); - } else { - GST_DEBUG_OBJECT (qtdemux, "could not parse date string '%s'", s); - } - g_free (s); - } - } -} - -static void -qtdemux_tag_add_gnre (GstQTDemux * qtdemux, GstTagList * taglist, - const char *tag, const char *dummy, GNode * node) -{ - GNode *data; - - data = qtdemux_tree_get_child_by_type (node, FOURCC_data); - - /* re-route to normal string tag if major brand says so - * or no data atom and compatible brand suggests so */ - if (qtdemux_is_brand_3gp (qtdemux, TRUE) || - (qtdemux_is_brand_3gp (qtdemux, FALSE) && !data)) { - qtdemux_tag_add_str (qtdemux, taglist, tag, dummy, node); - return; - } - - if (data) { - guint len, type, n; - - len = QT_UINT32 (data->data); - type = QT_UINT32 ((guint8 *) data->data + 8); - if (type == 0x00000000 && len >= 18) { - n = QT_UINT16 ((guint8 *) data->data + 16); - if (n > 0) { - const gchar *genre; - - genre = gst_tag_id3_genre_get (n - 1); - if (genre != NULL) { - GST_DEBUG_OBJECT (qtdemux, "adding %d [%s]", n, genre); - gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, genre, NULL); - } - } - } - } -} - -static void -qtdemux_add_double_tag_from_str (GstQTDemux * demux, GstTagList * taglist, - const gchar * tag, guint8 * data, guint32 datasize) -{ - gdouble value; - gchar *datacopy; - - /* make a copy to have \0 at the end */ - datacopy = g_strndup ((gchar *) data, datasize); - - /* convert the str to double */ - if (sscanf (datacopy, "%lf", &value) == 1) { - GST_DEBUG_OBJECT (demux, "adding tag: %s [%s]", tag, datacopy); - gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, value, NULL); - } else { - GST_WARNING_OBJECT (demux, "Failed to parse double from string: %s", - datacopy); - } - g_free (datacopy); -} - - -static void -qtdemux_tag_add_revdns (GstQTDemux * demux, GstTagList * taglist, - const char *tag, const char *tag_bis, GNode * node) -{ - GNode *mean; - GNode *name; - GNode *data; - guint32 meansize; - guint32 namesize; - guint32 datatype; - guint32 datasize; - const gchar *meanstr; - const gchar *namestr; - - /* checking the whole ---- atom size for consistency */ - if (QT_UINT32 (node->data) <= 4 + 12 + 12 + 16) { - GST_WARNING_OBJECT (demux, "Tag ---- atom is too small, ignoring"); - return; - } - - mean = qtdemux_tree_get_child_by_type (node, FOURCC_mean); - if (!mean) { - GST_WARNING_OBJECT (demux, "No 'mean' atom found"); - return; - } - - meansize = QT_UINT32 (mean->data); - if (meansize <= 12) { - GST_WARNING_OBJECT (demux, "Small mean atom, ignoring the whole tag"); - return; - } - meanstr = ((gchar *) mean->data) + 12; - meansize -= 12; - - name = qtdemux_tree_get_child_by_type (node, FOURCC_name); - if (!name) { - GST_WARNING_OBJECT (demux, "'name' atom not found, ignoring tag"); - return; - } - - namesize = QT_UINT32 (name->data); - if (namesize <= 12) { - GST_WARNING_OBJECT (demux, "'name' atom is too small, ignoring tag"); - return; - } - namestr = ((gchar *) name->data) + 12; - namesize -= 12; - - /* - * Data atom is: - * uint32 - size - * uint32 - name - * uint8 - version - * uint24 - data type - * uint32 - all 0 - * rest - the data - */ - data = qtdemux_tree_get_child_by_type (node, FOURCC_data); - if (!data) { - GST_WARNING_OBJECT (demux, "No data atom in this tag"); - return; - } - datasize = QT_UINT32 (data->data); - if (datasize <= 16) { - GST_WARNING_OBJECT (demux, "Data atom too small"); - return; - } - datatype = QT_UINT32 (((gchar *) data->data) + 8) & 0xFFFFFF; - - if ((strncmp (meanstr, "com.apple.iTunes", meansize) == 0) || - (strncmp (meanstr, "org.hydrogenaudio.replaygain", meansize) == 0)) { - static const struct - { - const gchar name[28]; - const gchar tag[28]; - } tags[] = { - { - "replaygain_track_gain", GST_TAG_TRACK_GAIN}, { - "replaygain_track_peak", GST_TAG_TRACK_PEAK}, { - "replaygain_album_gain", GST_TAG_ALBUM_GAIN}, { - "replaygain_album_peak", GST_TAG_ALBUM_PEAK}, { - "MusicBrainz Track Id", GST_TAG_MUSICBRAINZ_TRACKID}, { - "MusicBrainz Artist Id", GST_TAG_MUSICBRAINZ_ARTISTID}, { - "MusicBrainz Album Id", GST_TAG_MUSICBRAINZ_ALBUMID}, { - "MusicBrainz Album Artist Id", GST_TAG_MUSICBRAINZ_ALBUMARTISTID} - }; - int i; - - for (i = 0; i < G_N_ELEMENTS (tags); ++i) { - if (!g_ascii_strncasecmp (tags[i].name, namestr, namesize)) { - switch (gst_tag_get_type (tags[i].tag)) { - case G_TYPE_DOUBLE: - qtdemux_add_double_tag_from_str (demux, taglist, tags[i].tag, - ((guint8 *) data->data) + 16, datasize - 16); - break; - case G_TYPE_STRING: - qtdemux_tag_add_str (demux, taglist, tags[i].tag, NULL, node); - break; - default: - /* not reached */ - break; - } - break; - } - } - if (i == G_N_ELEMENTS (tags)) - goto unknown_tag; - } else { - goto unknown_tag; - } - - return; - -/* errors */ -unknown_tag: -#ifndef GST_DISABLE_GST_DEBUG - { - gchar *namestr_dbg; - gchar *meanstr_dbg; - - meanstr_dbg = g_strndup (meanstr, meansize); - namestr_dbg = g_strndup (namestr, namesize); - - GST_WARNING_OBJECT (demux, "This tag %s:%s type:%u is not mapped, " - "file a bug at bugzilla.gnome.org", meanstr_dbg, namestr_dbg, datatype); - - g_free (namestr_dbg); - g_free (meanstr_dbg); - } -#endif - return; -} - -static void -qtdemux_tag_add_id32 (GstQTDemux * demux, GstTagList * taglist, const char *tag, - const char *tag_bis, GNode * node) -{ - guint8 *data; - GstBuffer *buf; - guint len; - GstTagList *id32_taglist = NULL; - - GST_LOG_OBJECT (demux, "parsing ID32"); - - data = node->data; - len = GST_READ_UINT32_BE (data); - - /* need at least full box and language tag */ - if (len < 12 + 2) - return; - - buf = gst_buffer_new_allocate (NULL, len - 14, NULL); - gst_buffer_fill (buf, 0, data + 14, len - 14); - - id32_taglist = gst_tag_list_from_id3v2_tag (buf); - if (id32_taglist) { - GST_LOG_OBJECT (demux, "parsing ok"); - gst_tag_list_insert (taglist, id32_taglist, GST_TAG_MERGE_KEEP); - gst_tag_list_unref (id32_taglist); - } else { - GST_LOG_OBJECT (demux, "parsing failed"); - } - - gst_buffer_unref (buf); -} - -typedef void (*GstQTDemuxAddTagFunc) (GstQTDemux * demux, GstTagList * taglist, - const char *tag, const char *tag_bis, GNode * node); - -/* unmapped tags -FOURCC_pcst -> if media is a podcast -> bool -FOURCC_cpil -> if media is part of a compilation -> bool -FOURCC_pgap -> if media is part of a gapless context -> bool -FOURCC_tven -> the tv episode id e.g. S01E23 -> str -*/ - -static const struct -{ - guint32 fourcc; - const gchar *gst_tag; - const gchar *gst_tag_bis; - const GstQTDemuxAddTagFunc func; -} add_funcs[] = { - { - FOURCC__nam, GST_TAG_TITLE, NULL, qtdemux_tag_add_str}, { - FOURCC_titl, GST_TAG_TITLE, NULL, qtdemux_tag_add_str}, { - FOURCC__grp, GST_TAG_GROUPING, NULL, qtdemux_tag_add_str}, { - FOURCC__wrt, GST_TAG_COMPOSER, NULL, qtdemux_tag_add_str}, { - FOURCC__ART, GST_TAG_ARTIST, NULL, qtdemux_tag_add_str}, { - FOURCC_aART, GST_TAG_ALBUM_ARTIST, NULL, qtdemux_tag_add_str}, { - FOURCC_perf, GST_TAG_ARTIST, NULL, qtdemux_tag_add_str}, { - FOURCC_auth, GST_TAG_COMPOSER, NULL, qtdemux_tag_add_str}, { - FOURCC__alb, GST_TAG_ALBUM, NULL, qtdemux_tag_add_str}, { - FOURCC_albm, GST_TAG_ALBUM, NULL, qtdemux_tag_add_str}, { - FOURCC_cprt, GST_TAG_COPYRIGHT, NULL, qtdemux_tag_add_str}, { - FOURCC__cpy, GST_TAG_COPYRIGHT, NULL, qtdemux_tag_add_str}, { - FOURCC__cmt, GST_TAG_COMMENT, NULL, qtdemux_tag_add_str}, { - FOURCC__des, GST_TAG_DESCRIPTION, NULL, qtdemux_tag_add_str}, { - FOURCC_desc, GST_TAG_DESCRIPTION, NULL, qtdemux_tag_add_str}, { - FOURCC_dscp, GST_TAG_DESCRIPTION, NULL, qtdemux_tag_add_str}, { - FOURCC__lyr, GST_TAG_LYRICS, NULL, qtdemux_tag_add_str}, { - FOURCC__day, GST_TAG_DATE, NULL, qtdemux_tag_add_date}, { - FOURCC_yrrc, GST_TAG_DATE, NULL, qtdemux_tag_add_year}, { - FOURCC__too, GST_TAG_ENCODER, NULL, qtdemux_tag_add_str}, { - FOURCC__inf, GST_TAG_COMMENT, NULL, qtdemux_tag_add_str}, { - FOURCC_trkn, GST_TAG_TRACK_NUMBER, GST_TAG_TRACK_COUNT, qtdemux_tag_add_num}, { - FOURCC_disk, GST_TAG_ALBUM_VOLUME_NUMBER, GST_TAG_ALBUM_VOLUME_COUNT, - qtdemux_tag_add_num}, { - FOURCC_disc, GST_TAG_ALBUM_VOLUME_NUMBER, GST_TAG_ALBUM_VOLUME_COUNT, - qtdemux_tag_add_num}, { - FOURCC__gen, GST_TAG_GENRE, NULL, qtdemux_tag_add_str}, { - FOURCC_gnre, GST_TAG_GENRE, NULL, qtdemux_tag_add_gnre}, { - FOURCC_tmpo, GST_TAG_BEATS_PER_MINUTE, NULL, qtdemux_tag_add_tmpo}, { - FOURCC_covr, GST_TAG_IMAGE, NULL, qtdemux_tag_add_covr}, { - FOURCC_sonm, GST_TAG_TITLE_SORTNAME, NULL, qtdemux_tag_add_str}, { - FOURCC_soal, GST_TAG_ALBUM_SORTNAME, NULL, qtdemux_tag_add_str}, { - FOURCC_soar, GST_TAG_ARTIST_SORTNAME, NULL, qtdemux_tag_add_str}, { - FOURCC_soaa, GST_TAG_ALBUM_ARTIST_SORTNAME, NULL, qtdemux_tag_add_str}, { - FOURCC_soco, GST_TAG_COMPOSER_SORTNAME, NULL, qtdemux_tag_add_str}, { - FOURCC_sosn, GST_TAG_SHOW_SORTNAME, NULL, qtdemux_tag_add_str}, { - FOURCC_tvsh, GST_TAG_SHOW_NAME, NULL, qtdemux_tag_add_str}, { - FOURCC_tvsn, GST_TAG_SHOW_SEASON_NUMBER, NULL, qtdemux_tag_add_uint32}, { - FOURCC_tves, GST_TAG_SHOW_EPISODE_NUMBER, NULL, qtdemux_tag_add_uint32}, { - FOURCC_kywd, GST_TAG_KEYWORDS, NULL, qtdemux_tag_add_keywords}, { - FOURCC_keyw, GST_TAG_KEYWORDS, NULL, qtdemux_tag_add_str}, { - FOURCC__enc, GST_TAG_ENCODER, NULL, qtdemux_tag_add_str}, { - FOURCC_loci, GST_TAG_GEO_LOCATION_NAME, NULL, qtdemux_tag_add_location}, { - FOURCC_clsf, GST_QT_DEMUX_CLASSIFICATION_TAG, NULL, - qtdemux_tag_add_classification}, { - FOURCC__mak, GST_TAG_DEVICE_MANUFACTURER, NULL, qtdemux_tag_add_str}, { - FOURCC__mod, GST_TAG_DEVICE_MODEL, NULL, qtdemux_tag_add_str}, { - FOURCC__swr, GST_TAG_APPLICATION_NAME, NULL, qtdemux_tag_add_str}, { - - /* This is a special case, some tags are stored in this - * 'reverse dns naming', according to: - * http://atomicparsley.sourceforge.net/mpeg-4files.html and - * bug #614471 - */ - FOURCC_____, "", NULL, qtdemux_tag_add_revdns}, { - /* see http://www.mp4ra.org/specs.html for ID32 in meta box */ - FOURCC_ID32, "", NULL, qtdemux_tag_add_id32} -}; - -struct _GstQtDemuxTagList -{ - GstQTDemux *demux; - GstTagList *taglist; -}; -typedef struct _GstQtDemuxTagList GstQtDemuxTagList; - -static void -qtdemux_tag_add_blob (GNode * node, GstQtDemuxTagList * qtdemuxtaglist) -{ - gint len; - guint8 *data; - GstBuffer *buf; - gchar *media_type; - const gchar *style; - GstSample *sample; - GstStructure *s; - guint i; - guint8 ndata[4]; - GstQTDemux *demux = qtdemuxtaglist->demux; - GstTagList *taglist = qtdemuxtaglist->taglist; - - data = node->data; - len = QT_UINT32 (data); - buf = gst_buffer_new_and_alloc (len); - gst_buffer_fill (buf, 0, data, len); - - /* heuristic to determine style of tag */ - if (QT_FOURCC (data + 4) == FOURCC_____ || - (len > 8 + 12 && QT_FOURCC (data + 12) == FOURCC_data)) - style = "itunes"; - else if (demux->major_brand == FOURCC_qt__) - style = "quicktime"; - /* fall back to assuming iso/3gp tag style */ - else - style = "iso"; - - /* sanitize the name for the caps. */ - for (i = 0; i < 4; i++) { - guint8 d = data[4 + i]; - if (g_ascii_isalnum (d)) - ndata[i] = g_ascii_tolower (d); - else - ndata[i] = '_'; - } - - media_type = g_strdup_printf ("application/x-gst-qt-%c%c%c%c-tag", - ndata[0], ndata[1], ndata[2], ndata[3]); - GST_DEBUG_OBJECT (demux, "media type %s", media_type); - - s = gst_structure_new (media_type, "style", G_TYPE_STRING, style, NULL); - sample = gst_sample_new (buf, NULL, NULL, s); - gst_buffer_unref (buf); - g_free (media_type); - - GST_DEBUG_OBJECT (demux, "adding private tag; size %d, info %" GST_PTR_FORMAT, - len, s); - - gst_tag_list_add (taglist, GST_TAG_MERGE_APPEND, - GST_QT_DEMUX_PRIVATE_TAG, sample, NULL); - - gst_sample_unref (sample); -} - -static void -qtdemux_parse_udta (GstQTDemux * qtdemux, GstTagList * taglist, GNode * udta) -{ - GNode *meta; - GNode *ilst; - GNode *xmp_; - GNode *node; - gint i; - GstQtDemuxTagList demuxtaglist; - - demuxtaglist.demux = qtdemux; - demuxtaglist.taglist = taglist; - - meta = qtdemux_tree_get_child_by_type (udta, FOURCC_meta); - if (meta != NULL) { - ilst = qtdemux_tree_get_child_by_type (meta, FOURCC_ilst); - if (ilst == NULL) { - GST_LOG_OBJECT (qtdemux, "no ilst"); - return; - } - } else { - ilst = udta; - GST_LOG_OBJECT (qtdemux, "no meta so using udta itself"); - } - - i = 0; - while (i < G_N_ELEMENTS (add_funcs)) { - node = qtdemux_tree_get_child_by_type (ilst, add_funcs[i].fourcc); - if (node) { - gint len; - - len = QT_UINT32 (node->data); - if (len < 12) { - GST_DEBUG_OBJECT (qtdemux, "too small tag atom %" GST_FOURCC_FORMAT, - GST_FOURCC_ARGS (add_funcs[i].fourcc)); - } else { - add_funcs[i].func (qtdemux, taglist, add_funcs[i].gst_tag, - add_funcs[i].gst_tag_bis, node); - } - g_node_destroy (node); - } else { - i++; - } - } - - /* parsed nodes have been removed, pass along remainder as blob */ - g_node_children_foreach (ilst, G_TRAVERSE_ALL, - (GNodeForeachFunc) qtdemux_tag_add_blob, &demuxtaglist); - - /* parse up XMP_ node if existing */ - xmp_ = qtdemux_tree_get_child_by_type (udta, FOURCC_XMP_); - if (xmp_ != NULL) { - GstBuffer *buf; - GstTagList *xmptaglist; - - buf = _gst_buffer_new_wrapped (((guint8 *) xmp_->data) + 8, - QT_UINT32 ((guint8 *) xmp_->data) - 8, NULL); - xmptaglist = gst_tag_list_from_xmp_buffer (buf); - gst_buffer_unref (buf); - - qtdemux_handle_xmp_taglist (qtdemux, taglist, xmptaglist); - } else { - GST_DEBUG_OBJECT (qtdemux, "No XMP_ node found"); - } -} - typedef struct { GstStructure *structure; /* helper for sort function */ diff --git a/gst/isomp4/qtdemux.h b/gst/isomp4/qtdemux.h index c5e85c7211..81fb9d242c 100644 --- a/gst/isomp4/qtdemux.h +++ b/gst/isomp4/qtdemux.h @@ -24,6 +24,8 @@ #include #include #include +#include +#include #include "gstisoff.h" G_BEGIN_DECLS @@ -48,6 +50,10 @@ G_BEGIN_DECLS typedef struct _GstQTDemux GstQTDemux; typedef struct _GstQTDemuxClass GstQTDemuxClass; typedef struct _QtDemuxStream QtDemuxStream; +typedef struct _QtDemuxSample QtDemuxSample; +typedef struct _QtDemuxSegment QtDemuxSegment; +typedef struct _QtDemuxRandomAccessEntry QtDemuxRandomAccessEntry; +typedef struct _QtDemuxStreamStsdEntry QtDemuxStreamStsdEntry; enum QtDemuxState { @@ -261,6 +267,241 @@ struct _GstQTDemuxClass { GType gst_qtdemux_get_type (void); +struct _QtDemuxStreamStsdEntry +{ + GstCaps *caps; + guint32 fourcc; + gboolean sparse; + + /* video info */ + gint width; + gint height; + gint par_w; + gint par_h; + /* Numerator/denominator framerate */ + gint fps_n; + gint fps_d; + GstVideoColorimetry colorimetry; + guint16 bits_per_sample; + guint16 color_table_id; + GstMemory *rgb8_palette; + guint interlace_mode; + guint field_order; + + /* audio info */ + gdouble rate; + gint n_channels; + guint samples_per_packet; + guint samples_per_frame; + guint bytes_per_packet; + guint bytes_per_sample; + guint bytes_per_frame; + guint compression; + + /* if we use chunks or samples */ + gboolean sampled; + guint padding; + +}; + +struct _QtDemuxSample +{ + guint32 size; + gint32 pts_offset; /* Add this value to timestamp to get the pts */ + guint64 offset; + guint64 timestamp; /* DTS In mov time */ + guint32 duration; /* In mov time */ + gboolean keyframe; /* TRUE when this packet is a keyframe */ +}; + +struct _QtDemuxStream +{ + GstPad *pad; + + GstQTDemux *demux; + gchar *stream_id; + + QtDemuxStreamStsdEntry *stsd_entries; + guint stsd_entries_length; + guint cur_stsd_entry_index; + + /* stream type */ + guint32 subtype; + + gboolean new_caps; /* If TRUE, caps need to be generated (by + * calling _configure_stream()) This happens + * for MSS and fragmented streams */ + + gboolean new_stream; /* signals that a stream_start is required */ + gboolean on_keyframe; /* if this stream last pushed buffer was a + * keyframe. This is important to identify + * where to stop pushing buffers after a + * segment stop time */ + + /* if the stream has a redirect URI in its headers, we store it here */ + gchar *redirect_uri; + + /* track id */ + guint track_id; + + /* duration/scale */ + guint64 duration; /* in timescale units */ + guint32 timescale; + + /* language */ + gchar lang_id[4]; /* ISO 639-2T language code */ + + /* our samples */ + guint32 n_samples; + QtDemuxSample *samples; + gboolean all_keyframe; /* TRUE when all samples are keyframes (no stss) */ + guint32 n_samples_moof; /* sample count in a moof */ + guint64 duration_moof; /* duration in timescale of a moof, used for figure out + * the framerate of fragmented format stream */ + guint64 duration_last_moof; + + guint32 offset_in_sample; /* Offset in the current sample, used for + * streams which have got exceedingly big + * sample size (such as 24s of raw audio). + * Only used when max_buffer_size is non-NULL */ + guint32 min_buffer_size; /* Minimum allowed size for output buffers. + * Currently only set for raw audio streams*/ + guint32 max_buffer_size; /* Maximum allowed size for output buffers. + * Currently only set for raw audio streams*/ + + /* video info */ + /* aspect ratio */ + gint display_width; + gint display_height; + + /* allocation */ + gboolean use_allocator; + GstAllocator *allocator; + GstAllocationParams params; + + gsize alignment; + + /* when a discontinuity is pending */ + gboolean discont; + + /* list of buffers to push first */ + GSList *buffers; + + /* if we need to clip this buffer. This is only needed for uncompressed + * data */ + gboolean need_clip; + + /* buffer needs some custom processing, e.g. subtitles */ + gboolean need_process; + /* buffer needs potentially be split, e.g. CEA608 subtitles */ + gboolean need_split; + + /* current position */ + guint32 segment_index; + guint32 sample_index; + GstClockTime time_position; /* in gst time */ + guint64 accumulated_base; + + /* the Gst segment we are processing out, used for clipping */ + GstSegment segment; + + /* quicktime segments */ + guint32 n_segments; + QtDemuxSegment *segments; + gboolean dummy_segment; + guint32 from_sample; + guint32 to_sample; + + gboolean sent_eos; + GstTagList *stream_tags; + gboolean send_global_tags; + + GstEvent *pending_event; + + GstByteReader stco; + GstByteReader stsz; + GstByteReader stsc; + GstByteReader stts; + GstByteReader stss; + GstByteReader stps; + GstByteReader ctts; + + gboolean chunks_are_samples; /* TRUE means treat chunks as samples */ + gint64 stbl_index; + /* stco */ + guint co_size; + GstByteReader co_chunk; + guint32 first_chunk; + guint32 current_chunk; + guint32 last_chunk; + guint32 samples_per_chunk; + guint32 stsd_sample_description_id; + guint32 stco_sample_index; + /* stsz */ + guint32 sample_size; /* 0 means variable sizes are stored in stsz */ + /* stsc */ + guint32 stsc_index; + guint32 n_samples_per_chunk; + guint32 stsc_chunk_index; + guint32 stsc_sample_index; + guint64 chunk_offset; + /* stts */ + guint32 stts_index; + guint32 stts_samples; + guint32 n_sample_times; + guint32 stts_sample_index; + guint64 stts_time; + guint32 stts_duration; + /* stss */ + gboolean stss_present; + guint32 n_sample_syncs; + guint32 stss_index; + /* stps */ + gboolean stps_present; + guint32 n_sample_partial_syncs; + guint32 stps_index; + QtDemuxRandomAccessEntry *ra_entries; + guint n_ra_entries; + + const QtDemuxRandomAccessEntry *pending_seek; + + /* ctts */ + gboolean ctts_present; + guint32 n_composition_times; + guint32 ctts_index; + guint32 ctts_sample_index; + guint32 ctts_count; + gint32 ctts_soffset; + + /* cslg */ + guint32 cslg_shift; + + /* fragmented */ + gboolean parsed_trex; + guint32 def_sample_description_index; /* index is 1-based */ + guint32 def_sample_duration; + guint32 def_sample_size; + guint32 def_sample_flags; + + gboolean disabled; + + /* stereoscopic video streams */ + GstVideoMultiviewMode multiview_mode; + GstVideoMultiviewFlags multiview_flags; + + /* protected streams */ + gboolean protected; + guint32 protection_scheme_type; + guint32 protection_scheme_version; + gpointer protection_scheme_info; /* specific to the protection scheme */ + GQueue protection_scheme_event_queue; + + /* KEY_UNITS trickmode with an interval */ + GstClockTime last_keyframe_dts; + + gint ref_count; /* atomic */ +}; + G_END_DECLS #endif /* __GST_QTDEMUX_H__ */ diff --git a/gst/isomp4/qtdemux_tags.c b/gst/isomp4/qtdemux_tags.c new file mode 100644 index 0000000000..f2b384d692 --- /dev/null +++ b/gst/isomp4/qtdemux_tags.c @@ -0,0 +1,1034 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen + * Copyright (C) <2003> David A. Schleef + * Copyright (C) <2006> Wim Taymans + * Copyright (C) <2007> Julien Moutte + * Copyright (C) <2009> Tim-Philipp Müller + * Copyright (C) <2009> STEricsson + * Copyright (C) <2013> Sreerenj Balachandran + * Copyright (C) <2013> Intel Corporation + * Copyright (C) <2014> Centricular Ltd + * Copyright (C) <2015> YouView TV Ltd. + * Copyright (C) <2016> British Broadcasting Corporation + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/* Parsing functions for various MP4 standard extension atom groups */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "qtdemux_tags.h" +#include "qtdemux_tree.h" +#include "qtdemux_types.h" +#include "fourcc.h" + +static GstBuffer * +_gst_buffer_new_wrapped (gpointer mem, gsize size, GFreeFunc free_func) +{ + return gst_buffer_new_wrapped_full (free_func ? 0 : GST_MEMORY_FLAG_READONLY, + mem, size, 0, size, mem, free_func); +} + +/* check if major or compatible brand is 3GP */ +static inline gboolean +qtdemux_is_brand_3gp (GstQTDemux * qtdemux, gboolean major) +{ + if (major) { + return ((qtdemux->major_brand & GST_MAKE_FOURCC (255, 255, 0, 0)) == + FOURCC_3g__); + } else if (qtdemux->comp_brands != NULL) { + GstMapInfo map; + guint8 *data; + gsize size; + gboolean res = FALSE; + + gst_buffer_map (qtdemux->comp_brands, &map, GST_MAP_READ); + data = map.data; + size = map.size; + while (size >= 4) { + res = res || ((QT_FOURCC (data) & GST_MAKE_FOURCC (255, 255, 0, 0)) == + FOURCC_3g__); + data += 4; + size -= 4; + } + gst_buffer_unmap (qtdemux->comp_brands, &map); + return res; + } else { + return FALSE; + } +} + +/* check if tag is a spec'ed 3GP tag keyword storing a string */ +static inline gboolean +qtdemux_is_string_tag_3gp (GstQTDemux * qtdemux, guint32 fourcc) +{ + return fourcc == FOURCC_cprt || fourcc == FOURCC_gnre || fourcc == FOURCC_titl + || fourcc == FOURCC_dscp || fourcc == FOURCC_perf || fourcc == FOURCC_auth + || fourcc == FOURCC_albm; +} + +static void +qtdemux_tag_add_location (GstQTDemux * qtdemux, GstTagList * taglist, + const char *tag, const char *dummy, GNode * node) +{ + const gchar *env_vars[] = { "GST_QT_TAG_ENCODING", "GST_TAG_ENCODING", NULL }; + int offset; + char *name; + gchar *data; + gdouble longitude, latitude, altitude; + gint len; + + len = QT_UINT32 (node->data); + if (len <= 14) + goto short_read; + + data = node->data; + offset = 14; + + /* TODO: language code skipped */ + + name = gst_tag_freeform_string_to_utf8 (data + offset, -1, env_vars); + + if (!name) { + /* do not alarm in trivial case, but bail out otherwise */ + if (*(data + offset) != 0) { + GST_DEBUG_OBJECT (qtdemux, "failed to convert %s tag to UTF-8, " + "giving up", tag); + } + } else { + gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, + GST_TAG_GEO_LOCATION_NAME, name, NULL); + offset += strlen (name); + g_free (name); + } + + if (len < offset + 2 + 4 + 4 + 4) + goto short_read; + + /* +1 +1 = skip null-terminator and location role byte */ + offset += 1 + 1; + /* table in spec says unsigned, semantics say negative has meaning ... */ + longitude = QT_SFP32 (data + offset); + + offset += 4; + latitude = QT_SFP32 (data + offset); + + offset += 4; + altitude = QT_SFP32 (data + offset); + + /* one invalid means all are invalid */ + if (longitude >= -180.0 && longitude <= 180.0 && + latitude >= -90.0 && latitude <= 90.0) { + gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, + GST_TAG_GEO_LOCATION_LATITUDE, latitude, + GST_TAG_GEO_LOCATION_LONGITUDE, longitude, + GST_TAG_GEO_LOCATION_ELEVATION, altitude, NULL); + } + + /* TODO: no GST_TAG_, so astronomical body and additional notes skipped */ + + return; + + /* ERRORS */ +short_read: + { + GST_DEBUG_OBJECT (qtdemux, "short read parsing 3GP location"); + return; + } +} + + +static void +qtdemux_tag_add_year (GstQTDemux * qtdemux, GstTagList * taglist, + const char *tag, const char *dummy, GNode * node) +{ + guint16 y; + GDate *date; + gint len; + + len = QT_UINT32 (node->data); + if (len < 14) + return; + + y = QT_UINT16 ((guint8 *) node->data + 12); + if (y == 0) { + GST_DEBUG_OBJECT (qtdemux, "year: %u is not a valid year", y); + return; + } + GST_DEBUG_OBJECT (qtdemux, "year: %u", y); + + date = g_date_new_dmy (1, 1, y); + gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, date, NULL); + g_date_free (date); +} + +static void +qtdemux_tag_add_classification (GstQTDemux * qtdemux, GstTagList * taglist, + const char *tag, const char *dummy, GNode * node) +{ + int offset; + char *tag_str = NULL; + guint8 *entity; + guint16 table; + gint len; + + len = QT_UINT32 (node->data); + if (len <= 20) + goto short_read; + + offset = 12; + entity = (guint8 *) node->data + offset; + if (entity[0] == 0 || entity[1] == 0 || entity[2] == 0 || entity[3] == 0) { + GST_DEBUG_OBJECT (qtdemux, + "classification info: %c%c%c%c invalid classification entity", + entity[0], entity[1], entity[2], entity[3]); + return; + } + + offset += 4; + table = QT_UINT16 ((guint8 *) node->data + offset); + + /* Language code skipped */ + + offset += 4; + + /* Tag format: "XXXX://Y[YYYY]/classification info string" + * XXXX: classification entity, fixed length 4 chars. + * Y[YYYY]: classification table, max 5 chars. + */ + tag_str = g_strdup_printf ("----://%u/%s", + table, (char *) node->data + offset); + + /* memcpy To be sure we're preserving byte order */ + memcpy (tag_str, entity, 4); + GST_DEBUG_OBJECT (qtdemux, "classification info: %s", tag_str); + + gst_tag_list_add (taglist, GST_TAG_MERGE_APPEND, tag, tag_str, NULL); + + g_free (tag_str); + + return; + + /* ERRORS */ +short_read: + { + GST_DEBUG_OBJECT (qtdemux, "short read parsing 3GP classification"); + return; + } +} + +static gboolean +qtdemux_tag_add_str_full (GstQTDemux * qtdemux, GstTagList * taglist, + const char *tag, const char *dummy, GNode * node) +{ + const gchar *env_vars[] = { "GST_QT_TAG_ENCODING", "GST_TAG_ENCODING", NULL }; + GNode *data; + char *s; + int len; + guint32 type; + int offset; + gboolean ret = TRUE; + const gchar *charset = NULL; + + data = qtdemux_tree_get_child_by_type (node, FOURCC_data); + if (data) { + len = QT_UINT32 (data->data); + type = QT_UINT32 ((guint8 *) data->data + 8); + if (type == 0x00000001 && len > 16) { + s = gst_tag_freeform_string_to_utf8 ((char *) data->data + 16, len - 16, + env_vars); + if (s) { + GST_DEBUG_OBJECT (qtdemux, "adding tag %s", GST_STR_NULL (s)); + gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, s, NULL); + g_free (s); + } else { + GST_DEBUG_OBJECT (qtdemux, "failed to convert %s tag to UTF-8", tag); + } + } + } else { + len = QT_UINT32 (node->data); + type = QT_UINT32 ((guint8 *) node->data + 4); + if ((type >> 24) == 0xa9 && len > 8 + 4) { + gint str_len; + gint lang_code; + + /* Type starts with the (C) symbol, so the next data is a list + * of (string size(16), language code(16), string) */ + + str_len = QT_UINT16 ((guint8 *) node->data + 8); + lang_code = QT_UINT16 ((guint8 *) node->data + 10); + + /* the string + fourcc + size + 2 16bit fields, + * means that there are more tags in this atom */ + if (len > str_len + 8 + 4) { + /* TODO how to represent the same tag in different languages? */ + GST_WARNING_OBJECT (qtdemux, "Ignoring metadata entry with multiple " + "text alternatives, reading only first one"); + } + + offset = 12; + len = MIN (len, str_len + 8 + 4); /* remove trailing strings that we don't use */ + GST_DEBUG_OBJECT (qtdemux, "found international text tag"); + + if (lang_code < 0x800) { /* MAC encoded string */ + charset = "mac"; + } + } else if (len > 14 && qtdemux_is_string_tag_3gp (qtdemux, + QT_FOURCC ((guint8 *) node->data + 4))) { + guint32 type = QT_UINT32 ((guint8 *) node->data + 8); + + /* we go for 3GP style encoding if major brands claims so, + * or if no hope for data be ok UTF-8, and compatible 3GP brand present */ + if (qtdemux_is_brand_3gp (qtdemux, TRUE) || + (qtdemux_is_brand_3gp (qtdemux, FALSE) && + ((type & 0x00FFFFFF) == 0x0) && (type >> 24 <= 0xF))) { + offset = 14; + /* 16-bit Language code is ignored here as well */ + GST_DEBUG_OBJECT (qtdemux, "found 3gpp text tag"); + } else { + goto normal; + } + } else { + normal: + offset = 8; + GST_DEBUG_OBJECT (qtdemux, "found normal text tag"); + ret = FALSE; /* may have to fallback */ + } + if (charset) { + GError *err = NULL; + + s = g_convert ((gchar *) node->data + offset, len - offset, "utf8", + charset, NULL, NULL, &err); + if (err) { + GST_DEBUG_OBJECT (qtdemux, "Failed to convert string from charset %s:" + " %s(%d): %s", charset, g_quark_to_string (err->domain), err->code, + err->message); + g_error_free (err); + } + } else { + s = gst_tag_freeform_string_to_utf8 ((char *) node->data + offset, + len - offset, env_vars); + } + if (s) { + GST_DEBUG_OBJECT (qtdemux, "adding tag %s", GST_STR_NULL (s)); + gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, s, NULL); + g_free (s); + ret = TRUE; + } else { + GST_DEBUG_OBJECT (qtdemux, "failed to convert %s tag to UTF-8", tag); + } + } + return ret; +} + +static void +qtdemux_tag_add_str (GstQTDemux * qtdemux, GstTagList * taglist, + const char *tag, const char *dummy, GNode * node) +{ + qtdemux_tag_add_str_full (qtdemux, taglist, tag, dummy, node); +} + +static void +qtdemux_tag_add_keywords (GstQTDemux * qtdemux, GstTagList * taglist, + const char *tag, const char *dummy, GNode * node) +{ + const gchar *env_vars[] = { "GST_QT_TAG_ENCODING", "GST_TAG_ENCODING", NULL }; + guint8 *data; + char *s, *t, *k = NULL; + int len; + int offset; + int count; + + /* first try normal string tag if major brand not 3GP */ + if (!qtdemux_is_brand_3gp (qtdemux, TRUE)) { + if (!qtdemux_tag_add_str_full (qtdemux, taglist, tag, dummy, node)) { + /* hm, that did not work, maybe 3gpp storage in non-3gpp major brand; + * let's try it 3gpp way after minor safety check */ + data = node->data; + if (QT_UINT32 (data) < 15 || !qtdemux_is_brand_3gp (qtdemux, FALSE)) + return; + } else + return; + } + + GST_DEBUG_OBJECT (qtdemux, "found 3gpp keyword tag"); + + data = node->data; + + len = QT_UINT32 (data); + if (len < 15) + goto short_read; + + count = QT_UINT8 (data + 14); + offset = 15; + for (; count; count--) { + gint slen; + + if (offset + 1 > len) + goto short_read; + slen = QT_UINT8 (data + offset); + offset += 1; + if (offset + slen > len) + goto short_read; + s = gst_tag_freeform_string_to_utf8 ((char *) node->data + offset, + slen, env_vars); + if (s) { + GST_DEBUG_OBJECT (qtdemux, "adding keyword %s", GST_STR_NULL (s)); + if (k) { + t = g_strjoin (",", k, s, NULL); + g_free (s); + g_free (k); + k = t; + } else { + k = s; + } + } else { + GST_DEBUG_OBJECT (qtdemux, "failed to convert keyword to UTF-8"); + } + offset += slen; + } + +done: + if (k) { + GST_DEBUG_OBJECT (qtdemux, "adding tag %s", GST_STR_NULL (k)); + gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, k, NULL); + } + g_free (k); + + return; + + /* ERRORS */ +short_read: + { + GST_DEBUG_OBJECT (qtdemux, "short read parsing 3GP keywords"); + goto done; + } +} + +static void +qtdemux_tag_add_num (GstQTDemux * qtdemux, GstTagList * taglist, + const char *tag1, const char *tag2, GNode * node) +{ + GNode *data; + int len; + int type; + int n1, n2; + + data = qtdemux_tree_get_child_by_type (node, FOURCC_data); + if (data) { + len = QT_UINT32 (data->data); + type = QT_UINT32 ((guint8 *) data->data + 8); + if (type == 0x00000000 && len >= 22) { + n1 = QT_UINT16 ((guint8 *) data->data + 18); + n2 = QT_UINT16 ((guint8 *) data->data + 20); + if (n1 > 0) { + GST_DEBUG_OBJECT (qtdemux, "adding tag %s=%d", tag1, n1); + gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag1, n1, NULL); + } + if (n2 > 0) { + GST_DEBUG_OBJECT (qtdemux, "adding tag %s=%d", tag2, n2); + gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag2, n2, NULL); + } + } + } +} + +static void +qtdemux_tag_add_tmpo (GstQTDemux * qtdemux, GstTagList * taglist, + const char *tag1, const char *dummy, GNode * node) +{ + GNode *data; + int len; + int type; + int n1; + + data = qtdemux_tree_get_child_by_type (node, FOURCC_data); + if (data) { + len = QT_UINT32 (data->data); + type = QT_UINT32 ((guint8 *) data->data + 8); + GST_DEBUG_OBJECT (qtdemux, "have tempo tag, type=%d,len=%d", type, len); + /* some files wrongly have a type 0x0f=15, but it should be 0x15 */ + if ((type == 0x00000015 || type == 0x0000000f) && len >= 18) { + n1 = QT_UINT16 ((guint8 *) data->data + 16); + if (n1) { + /* do not add bpm=0 */ + GST_DEBUG_OBJECT (qtdemux, "adding tag %d", n1); + gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag1, (gdouble) n1, + NULL); + } + } + } +} + +static void +qtdemux_tag_add_uint32 (GstQTDemux * qtdemux, GstTagList * taglist, + const char *tag1, const char *dummy, GNode * node) +{ + GNode *data; + int len; + int type; + guint32 num; + + data = qtdemux_tree_get_child_by_type (node, FOURCC_data); + if (data) { + len = QT_UINT32 (data->data); + type = QT_UINT32 ((guint8 *) data->data + 8); + GST_DEBUG_OBJECT (qtdemux, "have %s tag, type=%d,len=%d", tag1, type, len); + /* some files wrongly have a type 0x0f=15, but it should be 0x15 */ + if ((type == 0x00000015 || type == 0x0000000f) && len >= 20) { + num = QT_UINT32 ((guint8 *) data->data + 16); + if (num) { + /* do not add num=0 */ + GST_DEBUG_OBJECT (qtdemux, "adding tag %d", num); + gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag1, num, NULL); + } + } + } +} + +static void +qtdemux_tag_add_covr (GstQTDemux * qtdemux, GstTagList * taglist, + const char *tag1, const char *dummy, GNode * node) +{ + GNode *data; + int len; + int type; + GstSample *sample; + + data = qtdemux_tree_get_child_by_type (node, FOURCC_data); + if (data) { + len = QT_UINT32 (data->data); + type = QT_UINT32 ((guint8 *) data->data + 8); + GST_DEBUG_OBJECT (qtdemux, "have covr tag, type=%d,len=%d", type, len); + if ((type == 0x0000000d || type == 0x0000000e) && len > 16) { + GstTagImageType image_type; + + if (gst_tag_list_get_tag_size (taglist, GST_TAG_IMAGE) == 0) + image_type = GST_TAG_IMAGE_TYPE_FRONT_COVER; + else + image_type = GST_TAG_IMAGE_TYPE_NONE; + + if ((sample = + gst_tag_image_data_to_image_sample ((guint8 *) data->data + 16, + len - 16, image_type))) { + GST_DEBUG_OBJECT (qtdemux, "adding tag size %d", len - 16); + gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag1, sample, NULL); + gst_sample_unref (sample); + } + } + } +} + +static void +qtdemux_tag_add_date (GstQTDemux * qtdemux, GstTagList * taglist, + const char *tag, const char *dummy, GNode * node) +{ + GNode *data; + GstDateTime *datetime = NULL; + char *s; + int len; + int type; + + data = qtdemux_tree_get_child_by_type (node, FOURCC_data); + if (data) { + len = QT_UINT32 (data->data); + type = QT_UINT32 ((guint8 *) data->data + 8); + if (type == 0x00000001 && len > 16) { + guint y, m = 1, d = 1; + gint ret; + + s = g_strndup ((char *) data->data + 16, len - 16); + GST_DEBUG_OBJECT (qtdemux, "adding date '%s'", s); + datetime = gst_date_time_new_from_iso8601_string (s); + if (datetime != NULL) { + gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, GST_TAG_DATE_TIME, + datetime, NULL); + gst_date_time_unref (datetime); + } + + ret = sscanf (s, "%u-%u-%u", &y, &m, &d); + if (ret >= 1 && y > 1500 && y < 3000) { + GDate *date; + + date = g_date_new_dmy (d, m, y); + gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, date, NULL); + g_date_free (date); + } else { + GST_DEBUG_OBJECT (qtdemux, "could not parse date string '%s'", s); + } + g_free (s); + } + } +} + +static void +qtdemux_tag_add_gnre (GstQTDemux * qtdemux, GstTagList * taglist, + const char *tag, const char *dummy, GNode * node) +{ + GNode *data; + + data = qtdemux_tree_get_child_by_type (node, FOURCC_data); + + /* re-route to normal string tag if major brand says so + * or no data atom and compatible brand suggests so */ + if (qtdemux_is_brand_3gp (qtdemux, TRUE) || + (qtdemux_is_brand_3gp (qtdemux, FALSE) && !data)) { + qtdemux_tag_add_str (qtdemux, taglist, tag, dummy, node); + return; + } + + if (data) { + guint len, type, n; + + len = QT_UINT32 (data->data); + type = QT_UINT32 ((guint8 *) data->data + 8); + if (type == 0x00000000 && len >= 18) { + n = QT_UINT16 ((guint8 *) data->data + 16); + if (n > 0) { + const gchar *genre; + + genre = gst_tag_id3_genre_get (n - 1); + if (genre != NULL) { + GST_DEBUG_OBJECT (qtdemux, "adding %d [%s]", n, genre); + gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, genre, NULL); + } + } + } + } +} + +static void +qtdemux_add_double_tag_from_str (GstQTDemux * demux, GstTagList * taglist, + const gchar * tag, guint8 * data, guint32 datasize) +{ + gdouble value; + gchar *datacopy; + + /* make a copy to have \0 at the end */ + datacopy = g_strndup ((gchar *) data, datasize); + + /* convert the str to double */ + if (sscanf (datacopy, "%lf", &value) == 1) { + GST_DEBUG_OBJECT (demux, "adding tag: %s [%s]", tag, datacopy); + gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, value, NULL); + } else { + GST_WARNING_OBJECT (demux, "Failed to parse double from string: %s", + datacopy); + } + g_free (datacopy); +} + + +static void +qtdemux_tag_add_revdns (GstQTDemux * demux, GstTagList * taglist, + const char *tag, const char *tag_bis, GNode * node) +{ + GNode *mean; + GNode *name; + GNode *data; + guint32 meansize; + guint32 namesize; + guint32 datatype; + guint32 datasize; + const gchar *meanstr; + const gchar *namestr; + + /* checking the whole ---- atom size for consistency */ + if (QT_UINT32 (node->data) <= 4 + 12 + 12 + 16) { + GST_WARNING_OBJECT (demux, "Tag ---- atom is too small, ignoring"); + return; + } + + mean = qtdemux_tree_get_child_by_type (node, FOURCC_mean); + if (!mean) { + GST_WARNING_OBJECT (demux, "No 'mean' atom found"); + return; + } + + meansize = QT_UINT32 (mean->data); + if (meansize <= 12) { + GST_WARNING_OBJECT (demux, "Small mean atom, ignoring the whole tag"); + return; + } + meanstr = ((gchar *) mean->data) + 12; + meansize -= 12; + + name = qtdemux_tree_get_child_by_type (node, FOURCC_name); + if (!name) { + GST_WARNING_OBJECT (demux, "'name' atom not found, ignoring tag"); + return; + } + + namesize = QT_UINT32 (name->data); + if (namesize <= 12) { + GST_WARNING_OBJECT (demux, "'name' atom is too small, ignoring tag"); + return; + } + namestr = ((gchar *) name->data) + 12; + namesize -= 12; + + /* + * Data atom is: + * uint32 - size + * uint32 - name + * uint8 - version + * uint24 - data type + * uint32 - all 0 + * rest - the data + */ + data = qtdemux_tree_get_child_by_type (node, FOURCC_data); + if (!data) { + GST_WARNING_OBJECT (demux, "No data atom in this tag"); + return; + } + datasize = QT_UINT32 (data->data); + if (datasize <= 16) { + GST_WARNING_OBJECT (demux, "Data atom too small"); + return; + } + datatype = QT_UINT32 (((gchar *) data->data) + 8) & 0xFFFFFF; + + if ((strncmp (meanstr, "com.apple.iTunes", meansize) == 0) || + (strncmp (meanstr, "org.hydrogenaudio.replaygain", meansize) == 0)) { + static const struct + { + const gchar name[28]; + const gchar tag[28]; + } tags[] = { + { + "replaygain_track_gain", GST_TAG_TRACK_GAIN}, { + "replaygain_track_peak", GST_TAG_TRACK_PEAK}, { + "replaygain_album_gain", GST_TAG_ALBUM_GAIN}, { + "replaygain_album_peak", GST_TAG_ALBUM_PEAK}, { + "MusicBrainz Track Id", GST_TAG_MUSICBRAINZ_TRACKID}, { + "MusicBrainz Artist Id", GST_TAG_MUSICBRAINZ_ARTISTID}, { + "MusicBrainz Album Id", GST_TAG_MUSICBRAINZ_ALBUMID}, { + "MusicBrainz Album Artist Id", GST_TAG_MUSICBRAINZ_ALBUMARTISTID} + }; + int i; + + for (i = 0; i < G_N_ELEMENTS (tags); ++i) { + if (!g_ascii_strncasecmp (tags[i].name, namestr, namesize)) { + switch (gst_tag_get_type (tags[i].tag)) { + case G_TYPE_DOUBLE: + qtdemux_add_double_tag_from_str (demux, taglist, tags[i].tag, + ((guint8 *) data->data) + 16, datasize - 16); + break; + case G_TYPE_STRING: + qtdemux_tag_add_str (demux, taglist, tags[i].tag, NULL, node); + break; + default: + /* not reached */ + break; + } + break; + } + } + if (i == G_N_ELEMENTS (tags)) + goto unknown_tag; + } else { + goto unknown_tag; + } + + return; + +/* errors */ +unknown_tag: +#ifndef GST_DISABLE_GST_DEBUG + { + gchar *namestr_dbg; + gchar *meanstr_dbg; + + meanstr_dbg = g_strndup (meanstr, meansize); + namestr_dbg = g_strndup (namestr, namesize); + + GST_WARNING_OBJECT (demux, "This tag %s:%s type:%u is not mapped, " + "file a bug at bugzilla.gnome.org", meanstr_dbg, namestr_dbg, datatype); + + g_free (namestr_dbg); + g_free (meanstr_dbg); + } +#endif + return; +} + +static void +qtdemux_tag_add_id32 (GstQTDemux * demux, GstTagList * taglist, const char *tag, + const char *tag_bis, GNode * node) +{ + guint8 *data; + GstBuffer *buf; + guint len; + GstTagList *id32_taglist = NULL; + + GST_LOG_OBJECT (demux, "parsing ID32"); + + data = node->data; + len = GST_READ_UINT32_BE (data); + + /* need at least full box and language tag */ + if (len < 12 + 2) + return; + + buf = gst_buffer_new_allocate (NULL, len - 14, NULL); + gst_buffer_fill (buf, 0, data + 14, len - 14); + + id32_taglist = gst_tag_list_from_id3v2_tag (buf); + if (id32_taglist) { + GST_LOG_OBJECT (demux, "parsing ok"); + gst_tag_list_insert (taglist, id32_taglist, GST_TAG_MERGE_KEEP); + gst_tag_list_unref (id32_taglist); + } else { + GST_LOG_OBJECT (demux, "parsing failed"); + } + + gst_buffer_unref (buf); +} + +typedef void (*GstQTDemuxAddTagFunc) (GstQTDemux * demux, GstTagList * taglist, + const char *tag, const char *tag_bis, GNode * node); + +/* unmapped tags +FOURCC_pcst -> if media is a podcast -> bool +FOURCC_cpil -> if media is part of a compilation -> bool +FOURCC_pgap -> if media is part of a gapless context -> bool +FOURCC_tven -> the tv episode id e.g. S01E23 -> str +*/ + +static const struct +{ + guint32 fourcc; + const gchar *gst_tag; + const gchar *gst_tag_bis; + const GstQTDemuxAddTagFunc func; +} add_funcs[] = { + { + FOURCC__nam, GST_TAG_TITLE, NULL, qtdemux_tag_add_str}, { + FOURCC_titl, GST_TAG_TITLE, NULL, qtdemux_tag_add_str}, { + FOURCC__grp, GST_TAG_GROUPING, NULL, qtdemux_tag_add_str}, { + FOURCC__wrt, GST_TAG_COMPOSER, NULL, qtdemux_tag_add_str}, { + FOURCC__ART, GST_TAG_ARTIST, NULL, qtdemux_tag_add_str}, { + FOURCC_aART, GST_TAG_ALBUM_ARTIST, NULL, qtdemux_tag_add_str}, { + FOURCC_perf, GST_TAG_ARTIST, NULL, qtdemux_tag_add_str}, { + FOURCC_auth, GST_TAG_COMPOSER, NULL, qtdemux_tag_add_str}, { + FOURCC__alb, GST_TAG_ALBUM, NULL, qtdemux_tag_add_str}, { + FOURCC_albm, GST_TAG_ALBUM, NULL, qtdemux_tag_add_str}, { + FOURCC_cprt, GST_TAG_COPYRIGHT, NULL, qtdemux_tag_add_str}, { + FOURCC__cpy, GST_TAG_COPYRIGHT, NULL, qtdemux_tag_add_str}, { + FOURCC__cmt, GST_TAG_COMMENT, NULL, qtdemux_tag_add_str}, { + FOURCC__des, GST_TAG_DESCRIPTION, NULL, qtdemux_tag_add_str}, { + FOURCC_desc, GST_TAG_DESCRIPTION, NULL, qtdemux_tag_add_str}, { + FOURCC_dscp, GST_TAG_DESCRIPTION, NULL, qtdemux_tag_add_str}, { + FOURCC__lyr, GST_TAG_LYRICS, NULL, qtdemux_tag_add_str}, { + FOURCC__day, GST_TAG_DATE, NULL, qtdemux_tag_add_date}, { + FOURCC_yrrc, GST_TAG_DATE, NULL, qtdemux_tag_add_year}, { + FOURCC__too, GST_TAG_ENCODER, NULL, qtdemux_tag_add_str}, { + FOURCC__inf, GST_TAG_COMMENT, NULL, qtdemux_tag_add_str}, { + FOURCC_trkn, GST_TAG_TRACK_NUMBER, GST_TAG_TRACK_COUNT, qtdemux_tag_add_num}, { + FOURCC_disk, GST_TAG_ALBUM_VOLUME_NUMBER, GST_TAG_ALBUM_VOLUME_COUNT, + qtdemux_tag_add_num}, { + FOURCC_disc, GST_TAG_ALBUM_VOLUME_NUMBER, GST_TAG_ALBUM_VOLUME_COUNT, + qtdemux_tag_add_num}, { + FOURCC__gen, GST_TAG_GENRE, NULL, qtdemux_tag_add_str}, { + FOURCC_gnre, GST_TAG_GENRE, NULL, qtdemux_tag_add_gnre}, { + FOURCC_tmpo, GST_TAG_BEATS_PER_MINUTE, NULL, qtdemux_tag_add_tmpo}, { + FOURCC_covr, GST_TAG_IMAGE, NULL, qtdemux_tag_add_covr}, { + FOURCC_sonm, GST_TAG_TITLE_SORTNAME, NULL, qtdemux_tag_add_str}, { + FOURCC_soal, GST_TAG_ALBUM_SORTNAME, NULL, qtdemux_tag_add_str}, { + FOURCC_soar, GST_TAG_ARTIST_SORTNAME, NULL, qtdemux_tag_add_str}, { + FOURCC_soaa, GST_TAG_ALBUM_ARTIST_SORTNAME, NULL, qtdemux_tag_add_str}, { + FOURCC_soco, GST_TAG_COMPOSER_SORTNAME, NULL, qtdemux_tag_add_str}, { + FOURCC_sosn, GST_TAG_SHOW_SORTNAME, NULL, qtdemux_tag_add_str}, { + FOURCC_tvsh, GST_TAG_SHOW_NAME, NULL, qtdemux_tag_add_str}, { + FOURCC_tvsn, GST_TAG_SHOW_SEASON_NUMBER, NULL, qtdemux_tag_add_uint32}, { + FOURCC_tves, GST_TAG_SHOW_EPISODE_NUMBER, NULL, qtdemux_tag_add_uint32}, { + FOURCC_kywd, GST_TAG_KEYWORDS, NULL, qtdemux_tag_add_keywords}, { + FOURCC_keyw, GST_TAG_KEYWORDS, NULL, qtdemux_tag_add_str}, { + FOURCC__enc, GST_TAG_ENCODER, NULL, qtdemux_tag_add_str}, { + FOURCC_loci, GST_TAG_GEO_LOCATION_NAME, NULL, qtdemux_tag_add_location}, { + FOURCC_clsf, GST_QT_DEMUX_CLASSIFICATION_TAG, NULL, + qtdemux_tag_add_classification}, { + FOURCC__mak, GST_TAG_DEVICE_MANUFACTURER, NULL, qtdemux_tag_add_str}, { + FOURCC__mod, GST_TAG_DEVICE_MODEL, NULL, qtdemux_tag_add_str}, { + FOURCC__swr, GST_TAG_APPLICATION_NAME, NULL, qtdemux_tag_add_str}, { + + /* This is a special case, some tags are stored in this + * 'reverse dns naming', according to: + * http://atomicparsley.sourceforge.net/mpeg-4files.html and + * bug #614471 + */ + FOURCC_____, "", NULL, qtdemux_tag_add_revdns}, { + /* see http://www.mp4ra.org/specs.html for ID32 in meta box */ + FOURCC_ID32, "", NULL, qtdemux_tag_add_id32} +}; + +struct _GstQtDemuxTagList +{ + GstQTDemux *demux; + GstTagList *taglist; +}; +typedef struct _GstQtDemuxTagList GstQtDemuxTagList; + +static void +qtdemux_tag_add_blob (GNode * node, GstQtDemuxTagList * qtdemuxtaglist) +{ + gint len; + guint8 *data; + GstBuffer *buf; + gchar *media_type; + const gchar *style; + GstSample *sample; + GstStructure *s; + guint i; + guint8 ndata[4]; + GstQTDemux *demux = qtdemuxtaglist->demux; + GstTagList *taglist = qtdemuxtaglist->taglist; + + data = node->data; + len = QT_UINT32 (data); + buf = gst_buffer_new_and_alloc (len); + gst_buffer_fill (buf, 0, data, len); + + /* heuristic to determine style of tag */ + if (QT_FOURCC (data + 4) == FOURCC_____ || + (len > 8 + 12 && QT_FOURCC (data + 12) == FOURCC_data)) + style = "itunes"; + else if (demux->major_brand == FOURCC_qt__) + style = "quicktime"; + /* fall back to assuming iso/3gp tag style */ + else + style = "iso"; + + /* sanitize the name for the caps. */ + for (i = 0; i < 4; i++) { + guint8 d = data[4 + i]; + if (g_ascii_isalnum (d)) + ndata[i] = g_ascii_tolower (d); + else + ndata[i] = '_'; + } + + media_type = g_strdup_printf ("application/x-gst-qt-%c%c%c%c-tag", + ndata[0], ndata[1], ndata[2], ndata[3]); + GST_DEBUG_OBJECT (demux, "media type %s", media_type); + + s = gst_structure_new (media_type, "style", G_TYPE_STRING, style, NULL); + sample = gst_sample_new (buf, NULL, NULL, s); + gst_buffer_unref (buf); + g_free (media_type); + + GST_DEBUG_OBJECT (demux, "adding private tag; size %d, info %" GST_PTR_FORMAT, + len, s); + + gst_tag_list_add (taglist, GST_TAG_MERGE_APPEND, + GST_QT_DEMUX_PRIVATE_TAG, sample, NULL); + + gst_sample_unref (sample); +} + +void +qtdemux_parse_udta (GstQTDemux * qtdemux, GstTagList * taglist, GNode * udta) +{ + GNode *meta; + GNode *ilst; + GNode *xmp_; + GNode *node; + gint i; + GstQtDemuxTagList demuxtaglist; + + demuxtaglist.demux = qtdemux; + demuxtaglist.taglist = taglist; + + meta = qtdemux_tree_get_child_by_type (udta, FOURCC_meta); + if (meta != NULL) { + ilst = qtdemux_tree_get_child_by_type (meta, FOURCC_ilst); + if (ilst == NULL) { + GST_LOG_OBJECT (qtdemux, "no ilst"); + return; + } + } else { + ilst = udta; + GST_LOG_OBJECT (qtdemux, "no meta so using udta itself"); + } + + i = 0; + while (i < G_N_ELEMENTS (add_funcs)) { + node = qtdemux_tree_get_child_by_type (ilst, add_funcs[i].fourcc); + if (node) { + gint len; + + len = QT_UINT32 (node->data); + if (len < 12) { + GST_DEBUG_OBJECT (qtdemux, "too small tag atom %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (add_funcs[i].fourcc)); + } else { + add_funcs[i].func (qtdemux, taglist, add_funcs[i].gst_tag, + add_funcs[i].gst_tag_bis, node); + } + g_node_destroy (node); + } else { + i++; + } + } + + /* parsed nodes have been removed, pass along remainder as blob */ + g_node_children_foreach (ilst, G_TRAVERSE_ALL, + (GNodeForeachFunc) qtdemux_tag_add_blob, &demuxtaglist); + + /* parse up XMP_ node if existing */ + xmp_ = qtdemux_tree_get_child_by_type (udta, FOURCC_XMP_); + if (xmp_ != NULL) { + GstBuffer *buf; + GstTagList *xmptaglist; + + buf = _gst_buffer_new_wrapped (((guint8 *) xmp_->data) + 8, + QT_UINT32 ((guint8 *) xmp_->data) - 8, NULL); + xmptaglist = gst_tag_list_from_xmp_buffer (buf); + gst_buffer_unref (buf); + + qtdemux_handle_xmp_taglist (qtdemux, taglist, xmptaglist); + } else { + GST_DEBUG_OBJECT (qtdemux, "No XMP_ node found"); + } +} + +void +qtdemux_handle_xmp_taglist (GstQTDemux * qtdemux, GstTagList * taglist, + GstTagList * xmptaglist) +{ + /* Strip out bogus fields */ + if (xmptaglist) { + if (gst_tag_list_get_scope (taglist) == GST_TAG_SCOPE_GLOBAL) { + gst_tag_list_remove_tag (xmptaglist, GST_TAG_VIDEO_CODEC); + gst_tag_list_remove_tag (xmptaglist, GST_TAG_AUDIO_CODEC); + } else { + gst_tag_list_remove_tag (xmptaglist, GST_TAG_CONTAINER_FORMAT); + } + + GST_DEBUG_OBJECT (qtdemux, "Found XMP tags %" GST_PTR_FORMAT, xmptaglist); + + /* prioritize native tags using _KEEP mode */ + gst_tag_list_insert (taglist, xmptaglist, GST_TAG_MERGE_KEEP); + gst_tag_list_unref (xmptaglist); + } +} diff --git a/gst/isomp4/qtdemux_tags.h b/gst/isomp4/qtdemux_tags.h new file mode 100644 index 0000000000..a55e993394 --- /dev/null +++ b/gst/isomp4/qtdemux_tags.h @@ -0,0 +1,30 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __QTDEMUX_TAGS_H__ +#define __QTDEMUX_TAGS_H__ + +#include +#include "qtdemux.h" + +void qtdemux_parse_udta (GstQTDemux * qtdemux, GstTagList * taglist, GNode * udta); +void qtdemux_handle_xmp_taglist (GstQTDemux * qtdemux, GstTagList * taglist, + GstTagList * xmptaglist); + +#endif