gst-plugins-good  1.20.3
About: GStreamer (Good Plugins) is a library for constructing of graphs of media-handling components. A set of good-quality plug-ins (under LGPL license).
  Fossies Dox: gst-plugins-good-1.20.3.tar.xz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

qtdemux_tags.c
Go to the documentation of this file.
1/* GStreamer
2 * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3 * Copyright (C) <2003> David A. Schleef <ds@schleef.org>
4 * Copyright (C) <2006> Wim Taymans <wim@fluendo.com>
5 * Copyright (C) <2007> Julien Moutte <julien@fluendo.com>
6 * Copyright (C) <2009> Tim-Philipp Müller <tim centricular net>
7 * Copyright (C) <2009> STEricsson <benjamin.gaignard@stericsson.com>
8 * Copyright (C) <2013> Sreerenj Balachandran <sreerenj.balachandran@intel.com>
9 * Copyright (C) <2013> Intel Corporation
10 * Copyright (C) <2014> Centricular Ltd
11 * Copyright (C) <2015> YouView TV Ltd.
12 * Copyright (C) <2016> British Broadcasting Corporation
13 *
14 * This library is free software; you can redistribute it and/or
15 * modify it under the terms of the GNU Library General Public
16 * License as published by the Free Software Foundation; either
17 * version 2 of the License, or (at your option) any later version.
18 *
19 * This library is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22 * Library General Public License for more details.
23 *
24 * You should have received a copy of the GNU Library General Public
25 * License along with this library; if not, write to the
26 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
27 * Boston, MA 02110-1301, USA.
28 */
29
30/* Parsing functions for various MP4 standard extension atom groups */
31
32#ifdef HAVE_CONFIG_H
33#include "config.h"
34#endif
35
36#include <stdio.h>
37#include <gst/base/gstbytereader.h>
38#include <gst/tag/tag.h>
39
40#include "qtdemux_tags.h"
41#include "qtdemux_tree.h"
42#include "qtdemux_types.h"
43#include "fourcc.h"
44
45static GstBuffer *
46_gst_buffer_new_wrapped (gpointer mem, gsize size, GFreeFunc free_func)
47{
48 return gst_buffer_new_wrapped_full (free_func ? 0 : GST_MEMORY_FLAG_READONLY,
49 mem, size, 0, size, mem, free_func);
50}
51
52/* check if major or compatible brand is 3GP */
53static inline gboolean
54qtdemux_is_brand_3gp (GstQTDemux * qtdemux, gboolean major)
55{
56 if (major) {
57 return ((qtdemux->major_brand & GST_MAKE_FOURCC (255, 255, 0, 0)) ==
59 } else if (qtdemux->comp_brands != NULL) {
60 GstMapInfo map;
61 guint8 *data;
62 gsize size;
63 gboolean res = FALSE;
64
65 gst_buffer_map (qtdemux->comp_brands, &map, GST_MAP_READ);
66 data = map.data;
67 size = map.size;
68 while (size >= 4) {
69 res = res || ((QT_FOURCC (data) & GST_MAKE_FOURCC (255, 255, 0, 0)) ==
71 data += 4;
72 size -= 4;
73 }
74 gst_buffer_unmap (qtdemux->comp_brands, &map);
75 return res;
76 } else {
77 return FALSE;
78 }
79}
80
81/* check if tag is a spec'ed 3GP tag keyword storing a string */
82static inline gboolean
84{
87 || fourcc == FOURCC_albm;
88}
89
90static void
91qtdemux_tag_add_location (GstQTDemux * qtdemux, GstTagList * taglist,
92 const char *tag, const char *dummy, GNode * node)
93{
94 const gchar *env_vars[] = { "GST_QT_TAG_ENCODING", "GST_TAG_ENCODING", NULL };
95 int offset;
96 char *name;
97 gchar *data;
98 gdouble longitude, latitude, altitude;
99 gint len;
100
101 len = QT_UINT32 (node->data);
102 if (len <= 14)
103 goto short_read;
104
105 data = node->data;
106 offset = 14;
107
108 /* TODO: language code skipped */
109
110 name = gst_tag_freeform_string_to_utf8 (data + offset, -1, env_vars);
111
112 if (!name) {
113 /* do not alarm in trivial case, but bail out otherwise */
114 if (*(data + offset) != 0) {
115 GST_DEBUG_OBJECT (qtdemux, "failed to convert %s tag to UTF-8, "
116 "giving up", tag);
117 }
118 } else {
119 gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE,
120 GST_TAG_GEO_LOCATION_NAME, name, NULL);
121 offset += strlen (name);
122 g_free (name);
123 }
124
125 if (len < offset + 2 + 4 + 4 + 4)
126 goto short_read;
127
128 /* +1 +1 = skip null-terminator and location role byte */
129 offset += 1 + 1;
130 /* table in spec says unsigned, semantics say negative has meaning ... */
131 longitude = QT_SFP32 (data + offset);
132
133 offset += 4;
134 latitude = QT_SFP32 (data + offset);
135
136 offset += 4;
137 altitude = QT_SFP32 (data + offset);
138
139 /* one invalid means all are invalid */
140 if (longitude >= -180.0 && longitude <= 180.0 &&
141 latitude >= -90.0 && latitude <= 90.0) {
142 gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE,
143 GST_TAG_GEO_LOCATION_LATITUDE, latitude,
144 GST_TAG_GEO_LOCATION_LONGITUDE, longitude,
145 GST_TAG_GEO_LOCATION_ELEVATION, altitude, NULL);
146 }
147
148 /* TODO: no GST_TAG_, so astronomical body and additional notes skipped */
149
150 return;
151
152 /* ERRORS */
153short_read:
154 {
155 GST_DEBUG_OBJECT (qtdemux, "short read parsing 3GP location");
156 return;
157 }
158}
159
160
161static void
162qtdemux_tag_add_year (GstQTDemux * qtdemux, GstTagList * taglist,
163 const char *tag, const char *dummy, GNode * node)
164{
165 guint16 y;
166 GDate *date;
167 gint len;
168
169 len = QT_UINT32 (node->data);
170 if (len < 14)
171 return;
172
173 y = QT_UINT16 ((guint8 *) node->data + 12);
174 if (y == 0) {
175 GST_DEBUG_OBJECT (qtdemux, "year: %u is not a valid year", y);
176 return;
177 }
178 GST_DEBUG_OBJECT (qtdemux, "year: %u", y);
179
180 date = g_date_new_dmy (1, 1, y);
181 gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, date, NULL);
182 g_date_free (date);
183}
184
185static void
186qtdemux_tag_add_classification (GstQTDemux * qtdemux, GstTagList * taglist,
187 const char *tag, const char *dummy, GNode * node)
188{
189 int offset;
190 char *tag_str = NULL;
191 guint8 *entity;
192 guint16 table;
193 gint len;
194
195 len = QT_UINT32 (node->data);
196 if (len <= 20)
197 goto short_read;
198
199 offset = 12;
200 entity = (guint8 *) node->data + offset;
201 if (entity[0] == 0 || entity[1] == 0 || entity[2] == 0 || entity[3] == 0) {
202 GST_DEBUG_OBJECT (qtdemux,
203 "classification info: %c%c%c%c invalid classification entity",
204 entity[0], entity[1], entity[2], entity[3]);
205 return;
206 }
207
208 offset += 4;
209 table = QT_UINT16 ((guint8 *) node->data + offset);
210
211 /* Language code skipped */
212
213 offset += 4;
214
215 /* Tag format: "XXXX://Y[YYYY]/classification info string"
216 * XXXX: classification entity, fixed length 4 chars.
217 * Y[YYYY]: classification table, max 5 chars.
218 */
219 tag_str = g_strdup_printf ("----://%u/%s",
220 table, (char *) node->data + offset);
221
222 /* memcpy To be sure we're preserving byte order */
223 memcpy (tag_str, entity, 4);
224 GST_DEBUG_OBJECT (qtdemux, "classification info: %s", tag_str);
225
226 gst_tag_list_add (taglist, GST_TAG_MERGE_APPEND, tag, tag_str, NULL);
227
228 g_free (tag_str);
229
230 return;
231
232 /* ERRORS */
233short_read:
234 {
235 GST_DEBUG_OBJECT (qtdemux, "short read parsing 3GP classification");
236 return;
237 }
238}
239
240static gboolean
241qtdemux_tag_add_str_full (GstQTDemux * qtdemux, GstTagList * taglist,
242 const char *tag, const char *dummy, GNode * node)
243{
244 const gchar *env_vars[] = { "GST_QT_TAG_ENCODING", "GST_TAG_ENCODING", NULL };
245 GNode *data;
246 char *s;
247 int len;
248 guint32 type;
249 int offset;
250 gboolean ret = TRUE;
251 const gchar *charset = NULL;
252
254 if (data) {
255 len = QT_UINT32 (data->data);
256 type = QT_UINT32 ((guint8 *) data->data + 8);
257 if (type == 0x00000001 && len > 16) {
258 s = gst_tag_freeform_string_to_utf8 ((char *) data->data + 16, len - 16,
259 env_vars);
260 if (s) {
261 GST_DEBUG_OBJECT (qtdemux, "adding tag %s", GST_STR_NULL (s));
262 gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, s, NULL);
263 g_free (s);
264 } else {
265 GST_DEBUG_OBJECT (qtdemux, "failed to convert %s tag to UTF-8", tag);
266 }
267 }
268 } else {
269 len = QT_UINT32 (node->data);
270 type = QT_UINT32 ((guint8 *) node->data + 4);
271 if ((type >> 24) == 0xa9 && len > 8 + 4) {
272 gint str_len;
273 gint lang_code;
274
275 /* Type starts with the (C) symbol, so the next data is a list
276 * of (string size(16), language code(16), string) */
277
278 str_len = QT_UINT16 ((guint8 *) node->data + 8);
279 lang_code = QT_UINT16 ((guint8 *) node->data + 10);
280
281 /* the string + fourcc + size + 2 16bit fields,
282 * means that there are more tags in this atom */
283 if (len > str_len + 8 + 4) {
284 /* TODO how to represent the same tag in different languages? */
285 GST_WARNING_OBJECT (qtdemux, "Ignoring metadata entry with multiple "
286 "text alternatives, reading only first one");
287 }
288
289 offset = 12;
290 len = MIN (len, str_len + 8 + 4); /* remove trailing strings that we don't use */
291 GST_DEBUG_OBJECT (qtdemux, "found international text tag");
292
293 if (lang_code < 0x800) { /* MAC encoded string */
294 charset = "mac";
295 }
296 } else if (len > 14 && qtdemux_is_string_tag_3gp (qtdemux,
297 QT_FOURCC ((guint8 *) node->data + 4))) {
298 guint32 type = QT_UINT32 ((guint8 *) node->data + 8);
299
300 /* we go for 3GP style encoding if major brands claims so,
301 * or if no hope for data be ok UTF-8, and compatible 3GP brand present */
302 if (qtdemux_is_brand_3gp (qtdemux, TRUE) ||
303 (qtdemux_is_brand_3gp (qtdemux, FALSE) &&
304 ((type & 0x00FFFFFF) == 0x0) && (type >> 24 <= 0xF))) {
305 offset = 14;
306 /* 16-bit Language code is ignored here as well */
307 GST_DEBUG_OBJECT (qtdemux, "found 3gpp text tag");
308 } else {
309 goto normal;
310 }
311 } else {
312 normal:
313 offset = 8;
314 GST_DEBUG_OBJECT (qtdemux, "found normal text tag");
315 ret = FALSE; /* may have to fallback */
316 }
317 if (charset) {
318 GError *err = NULL;
319
320 s = g_convert ((gchar *) node->data + offset, len - offset, "utf8",
321 charset, NULL, NULL, &err);
322 if (err) {
323 GST_DEBUG_OBJECT (qtdemux, "Failed to convert string from charset %s:"
324 " %s(%d): %s", charset, g_quark_to_string (err->domain), err->code,
325 err->message);
326 g_error_free (err);
327 }
328 } else {
329 s = gst_tag_freeform_string_to_utf8 ((char *) node->data + offset,
330 len - offset, env_vars);
331 }
332 if (s) {
333 GST_DEBUG_OBJECT (qtdemux, "adding tag %s", GST_STR_NULL (s));
334 gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, s, NULL);
335 g_free (s);
336 ret = TRUE;
337 } else {
338 GST_DEBUG_OBJECT (qtdemux, "failed to convert %s tag to UTF-8", tag);
339 }
340 }
341 return ret;
342}
343
344static void
345qtdemux_tag_add_str (GstQTDemux * qtdemux, GstTagList * taglist,
346 const char *tag, const char *dummy, GNode * node)
347{
348 qtdemux_tag_add_str_full (qtdemux, taglist, tag, dummy, node);
349}
350
351static void
352qtdemux_tag_add_keywords (GstQTDemux * qtdemux, GstTagList * taglist,
353 const char *tag, const char *dummy, GNode * node)
354{
355 const gchar *env_vars[] = { "GST_QT_TAG_ENCODING", "GST_TAG_ENCODING", NULL };
356 guint8 *data;
357 char *s, *t, *k = NULL;
358 int len;
359 int offset;
360 int count;
361
362 /* first try normal string tag if major brand not 3GP */
363 if (!qtdemux_is_brand_3gp (qtdemux, TRUE)) {
364 if (!qtdemux_tag_add_str_full (qtdemux, taglist, tag, dummy, node)) {
365 /* hm, that did not work, maybe 3gpp storage in non-3gpp major brand;
366 * let's try it 3gpp way after minor safety check */
367 data = node->data;
368 if (QT_UINT32 (data) < 15 || !qtdemux_is_brand_3gp (qtdemux, FALSE))
369 return;
370 } else
371 return;
372 }
373
374 GST_DEBUG_OBJECT (qtdemux, "found 3gpp keyword tag");
375
376 data = node->data;
377
378 len = QT_UINT32 (data);
379 if (len < 15)
380 goto short_read;
381
382 count = QT_UINT8 (data + 14);
383 offset = 15;
384 for (; count; count--) {
385 gint slen;
386
387 if (offset + 1 > len)
388 goto short_read;
389 slen = QT_UINT8 (data + offset);
390 offset += 1;
391 if (offset + slen > len)
392 goto short_read;
393 s = gst_tag_freeform_string_to_utf8 ((char *) node->data + offset,
394 slen, env_vars);
395 if (s) {
396 GST_DEBUG_OBJECT (qtdemux, "adding keyword %s", GST_STR_NULL (s));
397 if (k) {
398 t = g_strjoin (",", k, s, NULL);
399 g_free (s);
400 g_free (k);
401 k = t;
402 } else {
403 k = s;
404 }
405 } else {
406 GST_DEBUG_OBJECT (qtdemux, "failed to convert keyword to UTF-8");
407 }
408 offset += slen;
409 }
410
411done:
412 if (k) {
413 GST_DEBUG_OBJECT (qtdemux, "adding tag %s", GST_STR_NULL (k));
414 gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, k, NULL);
415 }
416 g_free (k);
417
418 return;
419
420 /* ERRORS */
421short_read:
422 {
423 GST_DEBUG_OBJECT (qtdemux, "short read parsing 3GP keywords");
424 goto done;
425 }
426}
427
428static void
429qtdemux_tag_add_num (GstQTDemux * qtdemux, GstTagList * taglist,
430 const char *tag1, const char *tag2, GNode * node)
431{
432 GNode *data;
433 int len;
434 int type;
435 int n1, n2;
436
438 if (data) {
439 len = QT_UINT32 (data->data);
440 type = QT_UINT32 ((guint8 *) data->data + 8);
441 if (type == 0x00000000 && len >= 22) {
442 n1 = QT_UINT16 ((guint8 *) data->data + 18);
443 n2 = QT_UINT16 ((guint8 *) data->data + 20);
444 if (n1 > 0) {
445 GST_DEBUG_OBJECT (qtdemux, "adding tag %s=%d", tag1, n1);
446 gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag1, n1, NULL);
447 }
448 if (n2 > 0) {
449 GST_DEBUG_OBJECT (qtdemux, "adding tag %s=%d", tag2, n2);
450 gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag2, n2, NULL);
451 }
452 }
453 }
454}
455
456static void
457qtdemux_tag_add_tmpo (GstQTDemux * qtdemux, GstTagList * taglist,
458 const char *tag1, const char *dummy, GNode * node)
459{
460 GNode *data;
461 int len;
462 int type;
463 int n1;
464
466 if (data) {
467 len = QT_UINT32 (data->data);
468 type = QT_UINT32 ((guint8 *) data->data + 8);
469 GST_DEBUG_OBJECT (qtdemux, "have tempo tag, type=%d,len=%d", type, len);
470 /* some files wrongly have a type 0x0f=15, but it should be 0x15 */
471 if ((type == 0x00000015 || type == 0x0000000f) && len >= 18) {
472 n1 = QT_UINT16 ((guint8 *) data->data + 16);
473 if (n1) {
474 /* do not add bpm=0 */
475 GST_DEBUG_OBJECT (qtdemux, "adding tag %d", n1);
476 gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag1, (gdouble) n1,
477 NULL);
478 }
479 }
480 }
481}
482
483static void
484qtdemux_tag_add_uint32 (GstQTDemux * qtdemux, GstTagList * taglist,
485 const char *tag1, const char *dummy, GNode * node)
486{
487 GNode *data;
488 int len;
489 int type;
490 guint32 num;
491
493 if (data) {
494 len = QT_UINT32 (data->data);
495 type = QT_UINT32 ((guint8 *) data->data + 8);
496 GST_DEBUG_OBJECT (qtdemux, "have %s tag, type=%d,len=%d", tag1, type, len);
497 /* some files wrongly have a type 0x0f=15, but it should be 0x15 */
498 if ((type == 0x00000015 || type == 0x0000000f) && len >= 20) {
499 num = QT_UINT32 ((guint8 *) data->data + 16);
500 if (num) {
501 /* do not add num=0 */
502 GST_DEBUG_OBJECT (qtdemux, "adding tag %d", num);
503 gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag1, num, NULL);
504 }
505 }
506 }
507}
508
509static void
510qtdemux_tag_add_covr (GstQTDemux * qtdemux, GstTagList * taglist,
511 const char *tag1, const char *dummy, GNode * node)
512{
513 GNode *data;
514 int len;
515 int type;
516 GstSample *sample;
517
519 if (data) {
520 len = QT_UINT32 (data->data);
521 type = QT_UINT32 ((guint8 *) data->data + 8);
522 GST_DEBUG_OBJECT (qtdemux, "have covr tag, type=%d,len=%d", type, len);
523 if ((type == 0x0000000d || type == 0x0000000e) && len > 16) {
524 GstTagImageType image_type;
525
526 if (gst_tag_list_get_tag_size (taglist, GST_TAG_IMAGE) == 0)
527 image_type = GST_TAG_IMAGE_TYPE_FRONT_COVER;
528 else
529 image_type = GST_TAG_IMAGE_TYPE_NONE;
530
531 if ((sample =
532 gst_tag_image_data_to_image_sample ((guint8 *) data->data + 16,
533 len - 16, image_type))) {
534 GST_DEBUG_OBJECT (qtdemux, "adding tag size %d", len - 16);
535 gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag1, sample, NULL);
536 gst_sample_unref (sample);
537 }
538 }
539 }
540}
541
542static void
543qtdemux_tag_add_date (GstQTDemux * qtdemux, GstTagList * taglist,
544 const char *tag, const char *dummy, GNode * node)
545{
546 GNode *data;
547 GstDateTime *datetime = NULL;
548 char *s;
549 int len;
550 int type;
551
553 if (data) {
554 len = QT_UINT32 (data->data);
555 type = QT_UINT32 ((guint8 *) data->data + 8);
556 if (type == 0x00000001 && len > 16) {
557 guint y, m = 1, d = 1;
558 gint ret;
559
560 s = g_strndup ((char *) data->data + 16, len - 16);
561 GST_DEBUG_OBJECT (qtdemux, "adding date '%s'", s);
562 datetime = gst_date_time_new_from_iso8601_string (s);
563 if (datetime != NULL) {
564 gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, GST_TAG_DATE_TIME,
565 datetime, NULL);
566 gst_date_time_unref (datetime);
567 }
568
569 ret = sscanf (s, "%u-%u-%u", &y, &m, &d);
570 if (ret >= 1 && y > 1500 && y < 3000) {
571 GDate *date;
572
573 date = g_date_new_dmy (d, m, y);
574 gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, date, NULL);
575 g_date_free (date);
576 } else {
577 GST_DEBUG_OBJECT (qtdemux, "could not parse date string '%s'", s);
578 }
579 g_free (s);
580 }
581 }
582}
583
584static void
585qtdemux_tag_add_gnre (GstQTDemux * qtdemux, GstTagList * taglist,
586 const char *tag, const char *dummy, GNode * node)
587{
588 GNode *data;
589
591
592 /* re-route to normal string tag if major brand says so
593 * or no data atom and compatible brand suggests so */
594 if (qtdemux_is_brand_3gp (qtdemux, TRUE) ||
595 (qtdemux_is_brand_3gp (qtdemux, FALSE) && !data)) {
596 qtdemux_tag_add_str (qtdemux, taglist, tag, dummy, node);
597 return;
598 }
599
600 if (data) {
601 guint len, type, n;
602
603 len = QT_UINT32 (data->data);
604 type = QT_UINT32 ((guint8 *) data->data + 8);
605 if (type == 0x00000000 && len >= 18) {
606 n = QT_UINT16 ((guint8 *) data->data + 16);
607 if (n > 0) {
608 const gchar *genre;
609
610 genre = gst_tag_id3_genre_get (n - 1);
611 if (genre != NULL) {
612 GST_DEBUG_OBJECT (qtdemux, "adding %d [%s]", n, genre);
613 gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, genre, NULL);
614 }
615 }
616 }
617 }
618}
619
620static void
621qtdemux_add_double_tag_from_str (GstQTDemux * demux, GstTagList * taglist,
622 const gchar * tag, guint8 * data, guint32 datasize)
623{
624 gdouble value;
625 gchar *datacopy;
626
627 /* make a copy to have \0 at the end */
628 datacopy = g_strndup ((gchar *) data, datasize);
629
630 /* convert the str to double */
631 if (sscanf (datacopy, "%lf", &value) == 1) {
632 GST_DEBUG_OBJECT (demux, "adding tag: %s [%s]", tag, datacopy);
633 gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, value, NULL);
634 } else {
635 GST_WARNING_OBJECT (demux, "Failed to parse double from string: %s",
636 datacopy);
637 }
638 g_free (datacopy);
639}
640
641
642static void
643qtdemux_tag_add_revdns (GstQTDemux * demux, GstTagList * taglist,
644 const char *tag, const char *tag_bis, GNode * node)
645{
646 GNode *mean;
647 GNode *name;
648 GNode *data;
649 guint32 meansize;
650 guint32 namesize;
651 guint32 datatype;
652 guint32 datasize;
653 const gchar *meanstr;
654 const gchar *namestr;
655
656 /* checking the whole ---- atom size for consistency */
657 if (QT_UINT32 (node->data) <= 4 + 12 + 12 + 16) {
658 GST_WARNING_OBJECT (demux, "Tag ---- atom is too small, ignoring");
659 return;
660 }
661
663 if (!mean) {
664 GST_WARNING_OBJECT (demux, "No 'mean' atom found");
665 return;
666 }
667
668 meansize = QT_UINT32 (mean->data);
669 if (meansize <= 12) {
670 GST_WARNING_OBJECT (demux, "Small mean atom, ignoring the whole tag");
671 return;
672 }
673 meanstr = ((gchar *) mean->data) + 12;
674 meansize -= 12;
675
677 if (!name) {
678 GST_WARNING_OBJECT (demux, "'name' atom not found, ignoring tag");
679 return;
680 }
681
682 namesize = QT_UINT32 (name->data);
683 if (namesize <= 12) {
684 GST_WARNING_OBJECT (demux, "'name' atom is too small, ignoring tag");
685 return;
686 }
687 namestr = ((gchar *) name->data) + 12;
688 namesize -= 12;
689
690 /*
691 * Data atom is:
692 * uint32 - size
693 * uint32 - name
694 * uint8 - version
695 * uint24 - data type
696 * uint32 - all 0
697 * rest - the data
698 */
700 if (!data) {
701 GST_WARNING_OBJECT (demux, "No data atom in this tag");
702 return;
703 }
704 datasize = QT_UINT32 (data->data);
705 if (datasize <= 16) {
706 GST_WARNING_OBJECT (demux, "Data atom too small");
707 return;
708 }
709 datatype = QT_UINT32 (((gchar *) data->data) + 8) & 0xFFFFFF;
710
711 if ((strncmp (meanstr, "com.apple.iTunes", meansize) == 0) ||
712 (strncmp (meanstr, "org.hydrogenaudio.replaygain", meansize) == 0)) {
713 static const struct
714 {
715 const gchar name[28];
716 const gchar tag[28];
717 } tags[] = {
718 {
719 "replaygain_track_gain", GST_TAG_TRACK_GAIN}, {
720 "replaygain_track_peak", GST_TAG_TRACK_PEAK}, {
721 "replaygain_album_gain", GST_TAG_ALBUM_GAIN}, {
722 "replaygain_album_peak", GST_TAG_ALBUM_PEAK}, {
723 "MusicBrainz Track Id", GST_TAG_MUSICBRAINZ_TRACKID}, {
724 "MusicBrainz Artist Id", GST_TAG_MUSICBRAINZ_ARTISTID}, {
725 "MusicBrainz Album Id", GST_TAG_MUSICBRAINZ_ALBUMID}, {
726 "MusicBrainz Album Artist Id", GST_TAG_MUSICBRAINZ_ALBUMARTISTID}
727 };
728 int i;
729
730 for (i = 0; i < G_N_ELEMENTS (tags); ++i) {
731 if (!g_ascii_strncasecmp (tags[i].name, namestr, namesize)) {
732 switch (gst_tag_get_type (tags[i].tag)) {
733 case G_TYPE_DOUBLE:
734 qtdemux_add_double_tag_from_str (demux, taglist, tags[i].tag,
735 ((guint8 *) data->data) + 16, datasize - 16);
736 break;
737 case G_TYPE_STRING:
738 qtdemux_tag_add_str (demux, taglist, tags[i].tag, NULL, node);
739 break;
740 default:
741 /* not reached */
742 break;
743 }
744 break;
745 }
746 }
747 if (i == G_N_ELEMENTS (tags))
748 goto unknown_tag;
749 } else {
750 goto unknown_tag;
751 }
752
753 return;
754
755/* errors */
756unknown_tag:
757#ifndef GST_DISABLE_GST_DEBUG
758 {
759 gchar *namestr_dbg;
760 gchar *meanstr_dbg;
761
762 meanstr_dbg = g_strndup (meanstr, meansize);
763 namestr_dbg = g_strndup (namestr, namesize);
764
765 GST_WARNING_OBJECT (demux, "This tag %s:%s type:%u is not mapped, "
766 "file a bug at bugzilla.gnome.org", meanstr_dbg, namestr_dbg, datatype);
767
768 g_free (namestr_dbg);
769 g_free (meanstr_dbg);
770 }
771#endif
772 return;
773}
774
775static void
776qtdemux_tag_add_id32 (GstQTDemux * demux, GstTagList * taglist, const char *tag,
777 const char *tag_bis, GNode * node)
778{
779 guint8 *data;
780 GstBuffer *buf;
781 guint len;
782 GstTagList *id32_taglist = NULL;
783
784 GST_LOG_OBJECT (demux, "parsing ID32");
785
786 data = node->data;
787 len = GST_READ_UINT32_BE (data);
788
789 /* need at least full box and language tag */
790 if (len < 12 + 2)
791 return;
792
793 buf = gst_buffer_new_allocate (NULL, len - 14, NULL);
794 gst_buffer_fill (buf, 0, data + 14, len - 14);
795
796 id32_taglist = gst_tag_list_from_id3v2_tag (buf);
797 if (id32_taglist) {
798 GST_LOG_OBJECT (demux, "parsing ok");
799 gst_tag_list_insert (taglist, id32_taglist, GST_TAG_MERGE_KEEP);
800 gst_tag_list_unref (id32_taglist);
801 } else {
802 GST_LOG_OBJECT (demux, "parsing failed");
803 }
804
805 gst_buffer_unref (buf);
806}
807
808typedef void (*GstQTDemuxAddTagFunc) (GstQTDemux * demux, GstTagList * taglist,
809 const char *tag, const char *tag_bis, GNode * node);
810
811/* unmapped tags
812FOURCC_pcst -> if media is a podcast -> bool
813FOURCC_cpil -> if media is part of a compilation -> bool
814FOURCC_pgap -> if media is part of a gapless context -> bool
815FOURCC_tven -> the tv episode id e.g. S01E23 -> str
816*/
817
818static const struct
819{
820 guint32 fourcc;
821 const gchar *gst_tag;
822 const gchar *gst_tag_bis;
824} add_funcs[] = {
825 {
826 FOURCC__nam, GST_TAG_TITLE, NULL, qtdemux_tag_add_str}, {
827 FOURCC_titl, GST_TAG_TITLE, NULL, qtdemux_tag_add_str}, {
828 FOURCC__grp, GST_TAG_GROUPING, NULL, qtdemux_tag_add_str}, {
829 FOURCC__wrt, GST_TAG_COMPOSER, NULL, qtdemux_tag_add_str}, {
830 FOURCC__ART, GST_TAG_ARTIST, NULL, qtdemux_tag_add_str}, {
831 FOURCC_aART, GST_TAG_ALBUM_ARTIST, NULL, qtdemux_tag_add_str}, {
832 FOURCC_perf, GST_TAG_ARTIST, NULL, qtdemux_tag_add_str}, {
833 FOURCC_auth, GST_TAG_COMPOSER, NULL, qtdemux_tag_add_str}, {
834 FOURCC__alb, GST_TAG_ALBUM, NULL, qtdemux_tag_add_str}, {
835 FOURCC_albm, GST_TAG_ALBUM, NULL, qtdemux_tag_add_str}, {
836 FOURCC_cprt, GST_TAG_COPYRIGHT, NULL, qtdemux_tag_add_str}, {
837 FOURCC__cpy, GST_TAG_COPYRIGHT, NULL, qtdemux_tag_add_str}, {
838 FOURCC__cmt, GST_TAG_COMMENT, NULL, qtdemux_tag_add_str}, {
839 FOURCC__des, GST_TAG_DESCRIPTION, NULL, qtdemux_tag_add_str}, {
840 FOURCC_desc, GST_TAG_DESCRIPTION, NULL, qtdemux_tag_add_str}, {
841 FOURCC_dscp, GST_TAG_DESCRIPTION, NULL, qtdemux_tag_add_str}, {
842 FOURCC__lyr, GST_TAG_LYRICS, NULL, qtdemux_tag_add_str}, {
843 FOURCC__day, GST_TAG_DATE, NULL, qtdemux_tag_add_date}, {
844 FOURCC_yrrc, GST_TAG_DATE, NULL, qtdemux_tag_add_year}, {
845 FOURCC__too, GST_TAG_ENCODER, NULL, qtdemux_tag_add_str}, {
846 FOURCC__inf, GST_TAG_COMMENT, NULL, qtdemux_tag_add_str}, {
847 FOURCC_trkn, GST_TAG_TRACK_NUMBER, GST_TAG_TRACK_COUNT, qtdemux_tag_add_num}, {
848 FOURCC_disk, GST_TAG_ALBUM_VOLUME_NUMBER, GST_TAG_ALBUM_VOLUME_COUNT,
850 FOURCC_disc, GST_TAG_ALBUM_VOLUME_NUMBER, GST_TAG_ALBUM_VOLUME_COUNT,
852 FOURCC__gen, GST_TAG_GENRE, NULL, qtdemux_tag_add_str}, {
853 FOURCC_gnre, GST_TAG_GENRE, NULL, qtdemux_tag_add_gnre}, {
854 FOURCC_tmpo, GST_TAG_BEATS_PER_MINUTE, NULL, qtdemux_tag_add_tmpo}, {
855 FOURCC_covr, GST_TAG_IMAGE, NULL, qtdemux_tag_add_covr}, {
856 FOURCC_sonm, GST_TAG_TITLE_SORTNAME, NULL, qtdemux_tag_add_str}, {
857 FOURCC_soal, GST_TAG_ALBUM_SORTNAME, NULL, qtdemux_tag_add_str}, {
858 FOURCC_soar, GST_TAG_ARTIST_SORTNAME, NULL, qtdemux_tag_add_str}, {
859 FOURCC_soaa, GST_TAG_ALBUM_ARTIST_SORTNAME, NULL, qtdemux_tag_add_str}, {
860 FOURCC_soco, GST_TAG_COMPOSER_SORTNAME, NULL, qtdemux_tag_add_str}, {
861 FOURCC_sosn, GST_TAG_SHOW_SORTNAME, NULL, qtdemux_tag_add_str}, {
862 FOURCC_tvsh, GST_TAG_SHOW_NAME, NULL, qtdemux_tag_add_str}, {
863 FOURCC_tvsn, GST_TAG_SHOW_SEASON_NUMBER, NULL, qtdemux_tag_add_uint32}, {
864 FOURCC_tves, GST_TAG_SHOW_EPISODE_NUMBER, NULL, qtdemux_tag_add_uint32}, {
865 FOURCC_kywd, GST_TAG_KEYWORDS, NULL, qtdemux_tag_add_keywords}, {
866 FOURCC_keyw, GST_TAG_KEYWORDS, NULL, qtdemux_tag_add_str}, {
867 FOURCC__enc, GST_TAG_ENCODER, NULL, qtdemux_tag_add_str}, {
868 FOURCC_loci, GST_TAG_GEO_LOCATION_NAME, NULL, qtdemux_tag_add_location}, {
871 FOURCC__mak, GST_TAG_DEVICE_MANUFACTURER, NULL, qtdemux_tag_add_str}, {
872 FOURCC__mod, GST_TAG_DEVICE_MODEL, NULL, qtdemux_tag_add_str}, {
873 FOURCC__swr, GST_TAG_APPLICATION_NAME, NULL, qtdemux_tag_add_str}, {
874
875 /* This is a special case, some tags are stored in this
876 * 'reverse dns naming', according to:
877 * http://atomicparsley.sourceforge.net/mpeg-4files.html and
878 * bug #614471
879 */
881 /* see http://www.mp4ra.org/specs.html for ID32 in meta box */
884
886{
888 GstTagList *taglist;
889};
891
892static void
893qtdemux_tag_add_blob (GNode * node, GstQtDemuxTagList * qtdemuxtaglist)
894{
895 gint len;
896 guint8 *data;
897 GstBuffer *buf;
898 gchar *media_type;
899 const gchar *style;
900 GstSample *sample;
901 GstStructure *s;
902 guint i;
903 guint8 ndata[4];
904 GstQTDemux *demux = qtdemuxtaglist->demux;
905 GstTagList *taglist = qtdemuxtaglist->taglist;
906
907 data = node->data;
908 len = QT_UINT32 (data);
909 buf = gst_buffer_new_and_alloc (len);
910 gst_buffer_fill (buf, 0, data, len);
911
912 /* heuristic to determine style of tag */
913 if (QT_FOURCC (data + 4) == FOURCC_____ ||
914 (len > 8 + 12 && QT_FOURCC (data + 12) == FOURCC_data))
915 style = "itunes";
916 else if (demux->major_brand == FOURCC_qt__)
917 style = "quicktime";
918 /* fall back to assuming iso/3gp tag style */
919 else
920 style = "iso";
921
922 /* sanitize the name for the caps. */
923 for (i = 0; i < 4; i++) {
924 guint8 d = data[4 + i];
925 if (g_ascii_isalnum (d))
926 ndata[i] = g_ascii_tolower (d);
927 else
928 ndata[i] = '_';
929 }
930
931 media_type = g_strdup_printf ("application/x-gst-qt-%c%c%c%c-tag",
932 ndata[0], ndata[1], ndata[2], ndata[3]);
933 GST_DEBUG_OBJECT (demux, "media type %s", media_type);
934
935 s = gst_structure_new (media_type, "style", G_TYPE_STRING, style, NULL);
936 sample = gst_sample_new (buf, NULL, NULL, s);
937 gst_buffer_unref (buf);
938 g_free (media_type);
939
940 GST_DEBUG_OBJECT (demux, "adding private tag; size %d, info %" GST_PTR_FORMAT,
941 len, s);
942
943 gst_tag_list_add (taglist, GST_TAG_MERGE_APPEND,
944 GST_QT_DEMUX_PRIVATE_TAG, sample, NULL);
945
946 gst_sample_unref (sample);
947}
948
949void
950qtdemux_parse_udta (GstQTDemux * qtdemux, GstTagList * taglist, GNode * udta)
951{
952 GNode *meta;
953 GNode *ilst;
954 GNode *xmp_;
955 GNode *node;
956 gint i;
957 GstQtDemuxTagList demuxtaglist;
958
959 demuxtaglist.demux = qtdemux;
960 demuxtaglist.taglist = taglist;
961
963 if (meta != NULL) {
965 if (ilst == NULL) {
966 GST_LOG_OBJECT (qtdemux, "no ilst");
967 return;
968 }
969 } else {
970 ilst = udta;
971 GST_LOG_OBJECT (qtdemux, "no meta so using udta itself");
972 }
973
974 i = 0;
975 while (i < G_N_ELEMENTS (add_funcs)) {
977 if (node) {
978 gint len;
979
980 len = QT_UINT32 (node->data);
981 if (len < 12) {
982 GST_DEBUG_OBJECT (qtdemux, "too small tag atom %" GST_FOURCC_FORMAT,
983 GST_FOURCC_ARGS (add_funcs[i].fourcc));
984 } else {
985 add_funcs[i].func (qtdemux, taglist, add_funcs[i].gst_tag,
986 add_funcs[i].gst_tag_bis, node);
987 }
988 g_node_destroy (node);
989 } else {
990 i++;
991 }
992 }
993
994 /* parsed nodes have been removed, pass along remainder as blob */
995 g_node_children_foreach (ilst, G_TRAVERSE_ALL,
996 (GNodeForeachFunc) qtdemux_tag_add_blob, &demuxtaglist);
997
998 /* parse up XMP_ node if existing */
1000 if (xmp_ != NULL) {
1001 GstBuffer *buf;
1002 GstTagList *xmptaglist;
1003
1004 buf = _gst_buffer_new_wrapped (((guint8 *) xmp_->data) + 8,
1005 QT_UINT32 ((guint8 *) xmp_->data) - 8, NULL);
1006 xmptaglist = gst_tag_list_from_xmp_buffer (buf);
1007 gst_buffer_unref (buf);
1008
1009 qtdemux_handle_xmp_taglist (qtdemux, taglist, xmptaglist);
1010 } else {
1011 GST_DEBUG_OBJECT (qtdemux, "No XMP_ node found");
1012 }
1013}
1014
1015void
1017 GstTagList * xmptaglist)
1018{
1019 /* Strip out bogus fields */
1020 if (xmptaglist) {
1021 if (gst_tag_list_get_scope (taglist) == GST_TAG_SCOPE_GLOBAL) {
1022 gst_tag_list_remove_tag (xmptaglist, GST_TAG_VIDEO_CODEC);
1023 gst_tag_list_remove_tag (xmptaglist, GST_TAG_AUDIO_CODEC);
1024 } else {
1025 gst_tag_list_remove_tag (xmptaglist, GST_TAG_CONTAINER_FORMAT);
1026 }
1027
1028 GST_DEBUG_OBJECT (qtdemux, "Found XMP tags %" GST_PTR_FORMAT, xmptaglist);
1029
1030 /* prioritize native tags using _KEEP mode */
1031 gst_tag_list_insert (taglist, xmptaglist, GST_TAG_MERGE_KEEP);
1032 gst_tag_list_unref (xmptaglist);
1033 }
1034}
#define FOURCC__des
Definition: fourcc.h:75
#define FOURCC_____
Definition: fourcc.h:69
#define FOURCC__cpy
Definition: fourcc.h:73
#define FOURCC_tvsn
Definition: fourcc.h:264
#define FOURCC_3g__
Definition: fourcc.h:312
#define FOURCC_clsf
Definition: fourcc.h:328
#define FOURCC_disk
Definition: fourcc.h:125
#define FOURCC_aART
Definition: fourcc.h:86
#define FOURCC_auth
Definition: fourcc.h:327
#define FOURCC__enc
Definition: fourcc.h:76
#define FOURCC_ID32
Definition: fourcc.h:336
#define FOURCC_covr
Definition: fourcc.h:108
#define FOURCC_trkn
Definition: fourcc.h:260
#define FOURCC_kywd
Definition: fourcc.h:175
#define FOURCC__lyr
Definition: fourcc.h:80
#define FOURCC_dscp
Definition: fourcc.h:329
#define FOURCC__inf
Definition: fourcc.h:79
#define FOURCC_albm
Definition: fourcc.h:326
#define FOURCC__gen
Definition: fourcc.h:77
#define FOURCC__swr
Definition: fourcc.h:384
#define FOURCC_cprt
Definition: fourcc.h:110
#define FOURCC_soar
Definition: fourcc.h:232
#define FOURCC_sosn
Definition: fourcc.h:235
#define FOURCC__mod
Definition: fourcc.h:383
#define FOURCC_data
Definition: fourcc.h:119
#define FOURCC_qt__
Definition: fourcc.h:211
#define FOURCC_name
Definition: fourcc.h:192
#define FOURCC_tvsh
Definition: fourcc.h:263
#define FOURCC_soco
Definition: fourcc.h:233
#define FOURCC_tves
Definition: fourcc.h:262
#define FOURCC_titl
Definition: fourcc.h:319
#define FOURCC__ART
Definition: fourcc.h:68
#define FOURCC__cmt
Definition: fourcc.h:379
#define FOURCC__alb
Definition: fourcc.h:72
#define FOURCC_loci
Definition: fourcc.h:330
#define FOURCC__mak
Definition: fourcc.h:382
#define FOURCC_yrrc
Definition: fourcc.h:333
#define FOURCC_tmpo
Definition: fourcc.h:257
#define FOURCC__nam
Definition: fourcc.h:82
#define FOURCC_soal
Definition: fourcc.h:231
#define FOURCC_gnre
Definition: fourcc.h:152
#define FOURCC__grp
Definition: fourcc.h:78
#define FOURCC_soaa
Definition: fourcc.h:230
#define FOURCC_keyw
Definition: fourcc.h:173
#define FOURCC__too
Definition: fourcc.h:84
#define FOURCC_mean
Definition: fourcc.h:182
#define FOURCC_perf
Definition: fourcc.h:331
#define FOURCC_ilst
Definition: fourcc.h:163
#define FOURCC_meta
Definition: fourcc.h:183
#define FOURCC__wrt
Definition: fourcc.h:85
#define FOURCC_desc
Definition: fourcc.h:121
#define FOURCC__day
Definition: fourcc.h:74
#define FOURCC_sonm
Definition: fourcc.h:234
#define FOURCC_disc
Definition: fourcc.h:124
#define FOURCC_XMP_
Definition: fourcc.h:66
#define GST_QT_DEMUX_PRIVATE_TAG
Definition: gstqtmux.c:1693
#define QT_UINT16(a)
#define QT_UINT8(a)
#define QT_FOURCC(a)
#define QT_UINT32(a)
static GstStaticPadTemplate t
Definition: gstximagesrc.c:58
#define GST_QT_DEMUX_CLASSIFICATION_TAG
Definition: qtdemux.h:48
static void qtdemux_tag_add_gnre(GstQTDemux *qtdemux, GstTagList *taglist, const char *tag, const char *dummy, GNode *node)
Definition: qtdemux_tags.c:585
static GstBuffer * _gst_buffer_new_wrapped(gpointer mem, gsize size, GFreeFunc free_func)
Definition: qtdemux_tags.c:46
static const struct @120 add_funcs[]
static void qtdemux_tag_add_year(GstQTDemux *qtdemux, GstTagList *taglist, const char *tag, const char *dummy, GNode *node)
Definition: qtdemux_tags.c:162
static gboolean qtdemux_tag_add_str_full(GstQTDemux *qtdemux, GstTagList *taglist, const char *tag, const char *dummy, GNode *node)
Definition: qtdemux_tags.c:241
static void qtdemux_tag_add_uint32(GstQTDemux *qtdemux, GstTagList *taglist, const char *tag1, const char *dummy, GNode *node)
Definition: qtdemux_tags.c:484
static void qtdemux_tag_add_covr(GstQTDemux *qtdemux, GstTagList *taglist, const char *tag1, const char *dummy, GNode *node)
Definition: qtdemux_tags.c:510
void qtdemux_parse_udta(GstQTDemux *qtdemux, GstTagList *taglist, GNode *udta)
Definition: qtdemux_tags.c:950
void qtdemux_handle_xmp_taglist(GstQTDemux *qtdemux, GstTagList *taglist, GstTagList *xmptaglist)
static void qtdemux_tag_add_blob(GNode *node, GstQtDemuxTagList *qtdemuxtaglist)
Definition: qtdemux_tags.c:893
static void qtdemux_tag_add_num(GstQTDemux *qtdemux, GstTagList *taglist, const char *tag1, const char *tag2, GNode *node)
Definition: qtdemux_tags.c:429
static void qtdemux_tag_add_classification(GstQTDemux *qtdemux, GstTagList *taglist, const char *tag, const char *dummy, GNode *node)
Definition: qtdemux_tags.c:186
static void qtdemux_tag_add_keywords(GstQTDemux *qtdemux, GstTagList *taglist, const char *tag, const char *dummy, GNode *node)
Definition: qtdemux_tags.c:352
static void qtdemux_tag_add_tmpo(GstQTDemux *qtdemux, GstTagList *taglist, const char *tag1, const char *dummy, GNode *node)
Definition: qtdemux_tags.c:457
static gboolean qtdemux_is_string_tag_3gp(GstQTDemux *qtdemux, guint32 fourcc)
Definition: qtdemux_tags.c:83
static void qtdemux_tag_add_str(GstQTDemux *qtdemux, GstTagList *taglist, const char *tag, const char *dummy, GNode *node)
Definition: qtdemux_tags.c:345
static void qtdemux_tag_add_date(GstQTDemux *qtdemux, GstTagList *taglist, const char *tag, const char *dummy, GNode *node)
Definition: qtdemux_tags.c:543
static void qtdemux_tag_add_id32(GstQTDemux *demux, GstTagList *taglist, const char *tag, const char *tag_bis, GNode *node)
Definition: qtdemux_tags.c:776
const gchar * gst_tag_bis
Definition: qtdemux_tags.c:822
const gchar * gst_tag
Definition: qtdemux_tags.c:821
static void qtdemux_tag_add_location(GstQTDemux *qtdemux, GstTagList *taglist, const char *tag, const char *dummy, GNode *node)
Definition: qtdemux_tags.c:91
static void qtdemux_tag_add_revdns(GstQTDemux *demux, GstTagList *taglist, const char *tag, const char *tag_bis, GNode *node)
Definition: qtdemux_tags.c:643
void(* GstQTDemuxAddTagFunc)(GstQTDemux *demux, GstTagList *taglist, const char *tag, const char *tag_bis, GNode *node)
Definition: qtdemux_tags.c:808
const GstQTDemuxAddTagFunc func
Definition: qtdemux_tags.c:823
guint32 fourcc
Definition: qtdemux_tags.c:820
static void qtdemux_add_double_tag_from_str(GstQTDemux *demux, GstTagList *taglist, const gchar *tag, guint8 *data, guint32 datasize)
Definition: qtdemux_tags.c:621
static gboolean qtdemux_is_brand_3gp(GstQTDemux *qtdemux, gboolean major)
Definition: qtdemux_tags.c:54
GNode * qtdemux_tree_get_child_by_type(GNode *node, guint32 fourcc)
Definition: qtdemux_tree.c:39
#define QT_SFP32(a)
Definition: qtdemux_types.h:41
GstBuffer * comp_brands
Definition: qtdemux.h:101
guint major_brand
Definition: qtdemux.h:100
GstQTDemux * demux
Definition: qtdemux_tags.c:887
GstTagList * taglist
Definition: qtdemux_tags.c:888