root/liboggz/trunk/src/tools/oggz-comment.c

Revision 3819, 15.5 kB (checked in by conrad, 1 month ago)

oggz-comment: handle out-of-memory

Line 
1 /*
2    Copyright (C) 2008 Annodex Association
3
4    Redistribution and use in source and binary forms, with or without
5    modification, are permitted provided that the following conditions
6    are met:
7
8    - Redistributions of source code must retain the above copyright
9    notice, this list of conditions and the following disclaimer.
10
11    - Redistributions in binary form must reproduce the above copyright
12    notice, this list of conditions and the following disclaimer in the
13    documentation and/or other materials provided with the distribution.
14
15    - Neither the name of the Annodex Association nor the names of its
16    contributors may be used to endorse or promote products derived from
17    this software without specific prior written permission.
18
19    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20    ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22    PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE ASSOCIATION OR
23    CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27    LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 /* Kangyuan Niu: original version (Aug 2007) */
33 /* Conrad Parker: modified based on modify-headers example (January 2008) */
34
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <errno.h>
39 #include <getopt.h>
40
41 #include <oggz/oggz.h>
42
43 #include "oggz_tools.h"
44
45 #define ID_WRITE_DIRECT
46 /* define USE_FLUSH_NEXT */
47
48 #ifdef USE_FLUSH_NEXT
49 static int flush_next = 0;
50 #endif
51
52 #define S_SERIALNO 0x7
53
54 typedef struct {
55   int do_delete;
56   int do_all;
57   int got_non_bos;
58   OGGZ * reader;
59   OGGZ * writer;
60   OGGZ * storer; /* Just used for storing comments from commandline */
61   FILE * outfile;
62   OggzTable * seen_tracks;
63   OggzTable * serialno_table;
64   OggzTable * content_types_table;
65 } OCData;
66
67 static char * progname;
68
69 static void
70 usage (char * progname)
71 {
72   printf ("Usage: %s filename [options] tagname=tagvalue ...\n", progname);
73   printf ("List or edit comments in an Ogg file.\n");
74   printf ("\nSee http://wiki.xiph.org/index.php/VorbisComment for usage recommendations.\n");
75   printf ("Note that VorbisComment metadata cannot be used with Dirac video tracks.\n");
76   printf ("\nOutput options\n");
77   printf ("  -l, --list             List the comments in the given file.\n");
78   printf ("\nEditing options\n");
79   printf ("  -o filename, --output filename\n");
80   printf ("                         Specify output filename\n");
81   printf ("  -d, --delete           Delete comments before editing\n");
82   printf ("  -a, --all              Edit comments for all logical bitstreams\n");
83   printf ("  -c content-type, --content-type content-type\n");
84   printf ("                         Edit comments of the logical bitstreams with\n");
85   printf ("                         specified content-type\n");
86   printf ("  -s serialno, --serialno serialno\n");
87   printf ("                         Edit comments of the logical bitstream with\n");
88   printf ("                         specified serialno\n");
89   printf ("\nMiscellaneous options\n");
90   printf ("  -h, --help             Display this help and exit\n");
91   printf ("  -v, --version          Output version information and exit\n");
92   printf ("\n");
93   printf ("Please report bugs to <ogg-dev@xiph.org>\n");
94 }
95
96 static OCData *
97 ocdata_new ()
98 {
99   OCData *ocdata = malloc (sizeof (OCData));
100
101   if (ocdata == NULL) return NULL;
102
103   memset (ocdata, 0, sizeof (OCData));
104
105   ocdata->do_all = 1;
106
107   ocdata->storer = oggz_new (OGGZ_WRITE);
108   if (ocdata->storer == NULL)
109     goto err_storer;
110  
111   ocdata->seen_tracks = oggz_table_new ();
112   if (ocdata->seen_tracks == NULL)
113     goto err_seen_tracks;
114
115   ocdata->serialno_table = oggz_table_new();
116   if (ocdata->serialno_table == NULL)
117     goto err_serialno_table;
118
119   ocdata->content_types_table = oggz_table_new();
120   if (ocdata->content_types_table == NULL)
121     goto err_content_types_table;
122  
123   return ocdata;
124
125 err_content_types_table:
126   free (ocdata->serialno_table);
127 err_serialno_table:
128   free (ocdata->seen_tracks);
129 err_seen_tracks:
130   free (ocdata->storer);
131 err_storer:
132   free (ocdata);
133
134   return NULL;
135 }
136
137 static void
138 ocdata_delete (OCData *ocdata)
139 {
140   oggz_table_delete (ocdata->seen_tracks);
141   oggz_table_delete (ocdata->serialno_table);
142   oggz_table_delete (ocdata->content_types_table);
143  
144   if (ocdata->reader)
145     oggz_close (ocdata->reader);
146   if (ocdata->storer)
147     oggz_close (ocdata->storer);
148   if (ocdata->outfile)
149     fclose (ocdata->outfile);
150  
151   free (ocdata);
152 }
153
154 static void
155 fail_dirac (OCData *ocdata)
156 {
157   fprintf (stderr, "oggz-comment: Error: Comments cannot be stored in Dirac\n");
158
159   ocdata_delete (ocdata);
160
161   exit (1);
162 }
163
164 static int
165 filter_stream_p (const OCData *ocdata, long serialno)
166 {
167   if (oggz_table_lookup (ocdata->seen_tracks, serialno) == NULL)
168     return 0;
169
170   if (ocdata->do_all)
171     return 1;
172
173   if (oggz_table_lookup (ocdata->serialno_table, serialno) != NULL)
174     return 1;
175
176   return 0;
177 }
178
179 static int
180 read_bos (OGGZ * oggz, const ogg_page * og, long serialno, void * user_data)
181 {
182   OCData * ocdata = (OCData *)user_data;
183   const char * ident;
184   int i, n;
185   OggzStreamContent content;
186
187   if (ogg_page_bos ((ogg_page *)og)) {
188     ocdata->got_non_bos = 0;
189   } else {
190     ocdata->got_non_bos = 1;
191     return OGGZ_CONTINUE;
192   }
193
194   /* Record this track in the seen_tracks table, unless it is Skeleton or Dirac.
195    * We don't need to store any information about the track, just the fact
196    * that it exists.
197    * Store a dummy value (as NULL is not allowed in an OggzTable).
198    */
199   content = oggz_stream_get_content (oggz, serialno);
200   switch (content) {
201   case OGGZ_CONTENT_SKELETON:
202     /* No VorbisComment for Skeleton, and no need to warn */
203     break;
204   case OGGZ_CONTENT_DIRAC:
205     /* No VorbisComment for Dirac, see:
206      * http://lists.xiph.org/pipermail/ogg-dev/2008-November/001277.html
207      */
208     if (ocdata->do_all) {
209       fprintf (stderr, "oggz-comment: Warning: Ignoring Dirac track, serialno %010lu\n",
210                serialno);
211     } else {
212       fail_dirac (ocdata);
213     }
214     break;
215   default:
216     oggz_table_insert (ocdata->seen_tracks, serialno, (void *)0x01);
217   }
218
219   ident = ot_page_identify (oggz, og, NULL);
220   if (ident != NULL) {
221     n = oggz_table_size (ocdata->content_types_table);
222     for (i = 0; i < n; i++) {
223       char * c = oggz_table_nth (ocdata->content_types_table, i, NULL);
224       if (strcasecmp (c, ident) == 0) {
225         oggz_table_insert (ocdata->serialno_table, serialno, (void *)0x7);
226       }
227     }
228   }
229
230   return OGGZ_CONTINUE;
231 }
232
233 static int
234 more_headers (OCData * ocdata, ogg_packet * op, long serialno)
235 {
236   if (!ocdata->got_non_bos) return OGGZ_CONTINUE;
237
238   /* Determine if we're finished processing headers */
239   if (op->packetno+1 >= oggz_stream_get_numheaders (ocdata->reader, serialno)) {
240     /* If this was the last header for this track, remove it from the
241        track list */
242     oggz_table_remove (ocdata->seen_tracks, serialno);
243
244     /* If no more tracks are left in the track list, then we have processed
245      * all the headers; stop processing of packets. */
246     if (oggz_table_size (ocdata->seen_tracks) == 0) {
247       return OGGZ_STOP_ERR;
248     }
249   }
250
251   return OGGZ_CONTINUE;
252 }
253
254 static int
255 read_page (OGGZ * oggz, const ogg_page * og, long serialno, void * user_data)
256 {
257   OCData * ocdata = (OCData *)user_data;
258
259   fwrite (og->header, 1, og->header_len, ocdata->outfile);
260   fwrite (og->body, 1, og->body_len, ocdata->outfile);
261
262   return OGGZ_CONTINUE;
263 }
264
265 static int
266 read_packet (OGGZ * oggz, ogg_packet * op, long serialno, void * user_data)
267 {
268   OCData * ocdata = (OCData *)user_data;
269   const char * vendor;
270   int flush;
271   int ret;
272
273 #ifdef USE_FLUSH_NEXT
274   flush = flush_next;
275   if (op->granulepos == -1) {
276     flush_next = 0;
277   } else {
278     flush_next = OGGZ_FLUSH_BEFORE;
279   }
280 #else
281   if (op->granulepos == -1) {
282     flush = 0;
283   } else {
284     flush = OGGZ_FLUSH_AFTER;
285   }
286 #endif
287
288   /* Edit the packet data if required */
289   if (filter_stream_p (ocdata, serialno) && op->packetno == 1) {
290     vendor = oggz_comment_get_vendor (ocdata->reader, serialno);
291
292     /* Copy across the comments, unless "delete comments before editing" */
293     if (!ocdata->do_delete)
294       oggz_comments_copy (ocdata->reader, serialno, ocdata->writer, serialno);
295
296     /* Add stored comments from commandline */
297     oggz_comments_copy (ocdata->storer, S_SERIALNO, ocdata->writer, serialno);
298
299     /* Ensure the original vendor is preserved */
300     oggz_comment_set_vendor (ocdata->writer, serialno, vendor);
301
302     /* Generate the replacement comments packet */
303     op = oggz_comments_generate (ocdata->writer, serialno, 0);
304   }
305
306   /* Feed the packet into the writer */
307   if ((ret = oggz_write_feed (ocdata->writer, op, serialno, flush, NULL)) != 0)
308     fprintf (stderr, "oggz_write_feed: %d\n", ret);
309
310   return more_headers (ocdata, op, serialno);
311 }
312
313 static int
314 edit_comments (OCData * ocdata, char * outfilename)
315 {
316   unsigned char buf[1024];
317   long n;
318
319   if (outfilename == NULL) {
320     ocdata->outfile = stdout;
321   } else {
322     ocdata->outfile = fopen (outfilename, "wb");
323     if (ocdata->outfile == NULL) {
324       fprintf (stderr, "%s: unable to open output file %s\n",
325                progname, outfilename);
326       return -1;
327     }
328   }
329
330   /* Set up writer, filling in ocdata for callbacks */
331   if ((ocdata->writer = oggz_new (OGGZ_WRITE)) == NULL) {
332     fprintf (stderr, "Unable to create new writer: out of memory\n");
333   }
334
335   /* Set a page reader to process bos pages */
336   oggz_set_read_page (ocdata->reader, -1, read_bos, ocdata);
337
338   /* First, process headers packet-by-packet. */
339   oggz_set_read_callback (ocdata->reader, -1, read_packet, ocdata);
340   while ((n = oggz_read (ocdata->reader, 1024)) > 0) {
341     long nn;
342     while ((nn=oggz_write_output (ocdata->writer, buf, n)) > 0) {
343       fwrite (buf, 1, nn, ocdata->outfile);
344     }
345   }
346
347   /* We actually don't use the writer any more from here, so close it */
348   oggz_close (ocdata->writer);
349
350   /* Now, the headers are processed. We deregister the packet reading
351    * callback. */
352   oggz_set_read_callback (ocdata->reader, -1, NULL, NULL);
353
354   /* We deal with the rest of the file as pages. */
355   /* Register a callback that copies page data directly across to outfile */
356   oggz_set_read_page (ocdata->reader, -1, read_page, ocdata);
357
358   if (oggz_run (ocdata->reader) == OGGZ_ERR_OK)
359     return 0;
360   else
361     return 1;
362 }
363
364 static int
365 read_comments(OGGZ *oggz, ogg_packet *op, long serialno, void *user_data)
366 {
367   OCData * ocdata = (OCData *)user_data;
368   const OggzComment * comment;
369   const char * codec_name;
370
371   if (filter_stream_p (ocdata, serialno) && op->packetno == 1) {
372     codec_name = oggz_stream_get_content_type(oggz, serialno);
373     if (codec_name) {
374       printf ("%s: serialno %010lu\n", codec_name, serialno);
375     } else {
376       printf ("???: serialno %010lu\n", serialno);
377     }
378
379     printf("\tVendor: %s\n", oggz_comment_get_vendor(oggz, serialno));
380
381     for (comment = oggz_comment_first(oggz, serialno); comment;
382          comment = oggz_comment_next(oggz, serialno, comment))
383       printf ("\t%s: %s\n", comment->name, comment->value);
384   }
385
386   return more_headers (ocdata, op, serialno);
387 }
388
389 static int
390 list_comments (OCData * ocdata)
391 {
392   /* Set a page reader to process bos pages */
393   oggz_set_read_page (ocdata->reader, -1, read_bos, ocdata);
394
395   /* First, process headers packet-by-packet. */
396   oggz_set_read_callback (ocdata->reader, -1, read_comments, ocdata);
397
398   if (oggz_run (ocdata->reader) == OGGZ_ERR_STOP_OK)
399     return 0;
400   else
401     return 1;
402 }
403
404 static void
405 store_comment (OCData * ocdata, char * s)
406 {
407   char * c, * name, * value = NULL;
408
409   if (s == NULL) return;
410
411   name = s;
412
413   c = strchr (s, '=');
414   if (c != NULL) {
415     *c = '\0';
416     value = c+1;
417   }
418
419   oggz_comment_add_byname (ocdata->storer, S_SERIALNO, name, value);
420 }
421
422 int
423 main (int argc, char ** argv)
424 {
425   char * infilename = NULL, * outfilename = NULL;
426   OCData * ocdata;
427
428   int filter_serialnos = 0;
429   int filter_content_types = 0;
430
431   int show_version = 0;
432   int show_help = 0;
433   int do_list = 0;
434
435   long serialno;
436   long n;
437   int i = 1;
438
439   char * optstring = "lo:dac:s:hv";
440
441 #ifdef HAVE_GETOPT_LONG
442   static struct option long_options[] = {
443     {"list",     no_argument, 0, 'l'},
444     {"output",   required_argument, 0, 'o'},
445     {"delete",   no_argument, 0, 'd'},
446     {"all",      no_argument, 0, 'a'},
447     {"content-type", required_argument, 0, 'c'},
448     {"serialno", required_argument, 0, 's'},
449     {"help",     no_argument, 0, 'h'},
450     {"version",  no_argument, 0, 'v'},
451     {0,0,0,0}
452   };
453 #endif
454
455   ot_init ();
456
457   progname = argv[0];
458
459   if (argc == 2 && !strncmp (argv[1], "-?", 2)) {
460 #ifdef HAVE_GETOPT_LONG
461     ot_print_options (long_options, optstring);
462 #else
463     ot_print_short_options (optstring);
464 #endif
465     exit (0);
466   }
467
468   if (argc < 3) {
469     usage (progname);
470     return (1);
471   }
472
473   ocdata = ocdata_new ();
474   if (ocdata == NULL) {
475     fprintf (stderr, "oggz-comment: out of memory\n");
476     exit (1);
477   }
478
479   while (1) {
480 #ifdef HAVE_GETOPT_LONG
481     i = getopt_long(argc, argv, optstring, long_options, NULL);
482 #else
483     i = getopt (argc, argv, optstring);
484 #endif
485     if (i == -1) break;
486     if (i == ':') {
487       usage (progname);
488       goto exit_err;
489     }
490
491     switch (i) {
492     case 'l': /* list */
493       do_list = 1;
494       break;
495     case 'd': /* delete */
496       ocdata->do_delete = 1;
497       break;
498     case 'a': /* all */
499       ocdata->do_all = 1;
500       break;
501     case 's': /* serialno */
502       filter_serialnos = 1;
503       ocdata->do_all = 0;
504       serialno = atol (optarg);
505       oggz_table_insert (ocdata->serialno_table, serialno, (void *)0x7);
506       break;
507     case 'c': /* content-type */
508       if (strcasecmp (optarg, "dirac") == 0) {
509         fail_dirac (ocdata);
510       }
511       filter_content_types = 1;
512       ocdata->do_all = 0;
513       n = oggz_table_size (ocdata->content_types_table);
514       oggz_table_insert (ocdata->content_types_table, n, optarg);
515       break;
516     case 'h': /* help */
517       show_help = 1;
518       break;
519     case 'v': /* version */
520       show_version = 1;
521       break;
522     case 'o': /* output */
523       outfilename = optarg;
524       break;
525     default:
526       break;
527     }
528   }
529
530   if (show_version) {
531     printf ("%s version " VERSION "\n", progname);
532   }
533
534   if (show_help) {
535     usage (progname);
536   }
537
538   if (show_version || show_help) {
539     goto exit_ok;
540   }
541
542   if (optind >= argc) {
543     usage (progname);
544     goto exit_err;
545   }
546
547   /* Parse out new comments and infilename */
548   for (; optind < argc; optind++) {
549     char * s = argv[optind];
550
551     if(strchr(s, '=') != NULL) {
552       if (!do_list) store_comment (ocdata, s);
553     } else {
554       infilename = s;
555     }
556   }
557
558   /* Set up reader */
559   if (infilename == NULL || strcmp (infilename, "-") == 0) {
560     ocdata->reader = oggz_open_stdio (stdin, OGGZ_READ|OGGZ_AUTO);
561   } else {
562     ocdata->reader = oggz_open (infilename, OGGZ_READ|OGGZ_AUTO);
563   }
564
565   if (ocdata->reader == NULL) {
566     if (errno == 0) {
567       fprintf (stderr, "%s: %s: error opening input file\n",
568               progname, infilename);
569     } else {
570       fprintf (stderr, "%s: %s: %s\n",
571 &