1 /* ogg.c
2 * Copyright (C) 2004, 2005 Sylvain Cresto <scresto@gmail.com>
3 *
4 * This file is part of graveman!
5 *
6 * graveman! is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2, or
9 * (at your option) any later version.
10 *
11 * graveman! is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with program; see the file COPYING. If not, write to the
18 * Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
19 * MA 02111-1307, USA.
20 *
21 * URL: http://www.nongnu.org/graveman/
22 *
23 */
24
25 #include "graveman.h"
26
27 #ifdef ENABLE_OGG
28 #include <vorbis/codec.h>
29
30 /* ce code est bas� sur ogginfo2.c, livr� avec les vorbis-tools-1.0
31 * en telechargement sur http://www.vorbis.com/download.psp */
32
33
34
35 /* Ogginfo
36 *
37 * A tool to describe ogg file contents and metadata.
38 *
39 * Copyright 2002 Michael Smith <msmith@layrinth.net.au>
40 * Licensed under the GNU GPL, distributed with this program.
41 */
42 #define OGG_TITLE "TITLE"
43 #define OGG_ALBUM "ALBUM"
44 #define OGG_ARTIST "ARTIST"
45
46 #define CHUNK 4500
47
48 /* TODO:
49 *
50 * - detect decreasing granulepos
51 * - detect violations of muxing constraints
52 * - better EOS detection (when EOS not explicitly set)
53 * - detect granulepos 'gaps' (possibly vorbis-specific).
54 * - check for serial number == (unsigned)-1 (will break some tools?)
55 * - more options (e.g. less or more verbose)
56 */
57
58 typedef struct _stream_processor {
59 void (*process_page)(struct _stream_processor *, ogg_page *);
60 void (*process_end)(struct _stream_processor *);
61 gint isillegal;
62 gint shownillegal;
63 gint isnew;
64 glong seqno;
65 gint lostseq;
66
67 gint start;
68 gint end;
69
70 gint num;
71 gchar *type;
72
73 ogg_uint32_t serial; /* must be 32 bit unsigned */
74 ogg_stream_state os;
75 void *data;
76 } stream_processor;
77
78 typedef struct {
79 stream_processor *streams;
80 gint allocated;
81 gint used;
82
83 gint in_headers;
84 } stream_set;
85
86 typedef struct {
87 glong bytes;
88 vorbis_comment vc;
89 vorbis_info vi;
90 ogg_int64_t lastgranulepos;
91
92 gint doneheaders;
93
94 gchar *title, *artist, *album; /* les informations qui nous interessent */
95 /* glong minutes, seconds; */
96 guint32 length;
97 } misc_vorbis_info;
98
create_stream_set(void)99 static stream_set *create_stream_set(void) {
100 stream_set *set = g_malloc0(sizeof(stream_set));
101
102 set->streams = g_new0(stream_processor, 5);
103 set->allocated = 5;
104 set->used = 0;
105
106 return set;
107 }
108
vorbis_process(stream_processor * stream,ogg_page * page)109 static void vorbis_process(stream_processor *stream, ogg_page *page )
110 {
111 ogg_packet packet;
112 misc_vorbis_info *inf = stream->data;
113 gint i, header=0;
114
115 ogg_stream_pagein(&stream->os, page);
116
117 while(ogg_stream_packetout(&stream->os, &packet) > 0) {
118 if(inf->doneheaders < 3) {
119 if(vorbis_synthesis_headerin(&inf->vi, &inf->vc, &packet) < 0) {
120 g_warning(_("Warning: Could not decode vorbis header packet - invalid vorbis stream (%d)"), stream->num);
121 continue;
122 }
123 header = 1;
124 inf->doneheaders++;
125
126 if(inf->doneheaders == 3) {
127 for(i=0; i < inf->vc.comments; i++) {
128 gchar *sep = strchr(inf->vc.user_comments[i], '=');
129
130 /* extraction des commentaires */
131 if(sep == NULL) continue;
132
133 *(sep++) = 0;
134 if (!g_ascii_strcasecmp(inf->vc.user_comments[i], OGG_TITLE)) {
135 inf->title = sc_realloc_cat(" ", inf->title, sep);
136 } else if (!g_ascii_strcasecmp(inf->vc.user_comments[i], OGG_ARTIST)) {
137 inf->artist = sc_realloc_cat(" ", inf->artist, sep);
138 } else if (!g_ascii_strcasecmp(inf->vc.user_comments[i], OGG_ALBUM)) {
139 inf->album = sc_realloc_cat(" ", inf->album, sep);
140 }
141 }
142 }
143 }
144 }
145
146 if(!header) {
147 ogg_int64_t gp = ogg_page_granulepos(page);
148 if(gp > 0) {
149 inf->lastgranulepos = gp;
150 }
151 inf->bytes += page->header_len + page->body_len;
152 }
153 }
154
vorbis_end(stream_processor * stream)155 static void vorbis_end(stream_processor *stream)
156 {
157 misc_vorbis_info *inf = stream->data;
158
159 inf->length = (guint32)inf->lastgranulepos / inf->vi.rate;
160
161 vorbis_comment_clear(&inf->vc);
162 vorbis_info_clear(&inf->vi);
163 }
164
process_null(stream_processor * stream,ogg_page * page)165 static void process_null(stream_processor *stream, ogg_page *page)
166 {
167 /* This is for invalid streams. */
168 }
169
free_stream_set(stream_set * set)170 static void free_stream_set(stream_set *set)
171 {
172 gint i;
173 for(i=0; i < set->used; i++) {
174 if(!set->streams[i].end) {
175 g_warning(_("Warning: EOS not set on stream %d"), set->streams[i].num);
176 if(set->streams[i].process_end)
177 set->streams[i].process_end(&set->streams[i]);
178 }
179 ogg_stream_clear(&set->streams[i].os);
180 }
181
182 free(set->streams);
183 free(set);
184 }
185
streams_open(stream_set * set)186 static int streams_open(stream_set *set)
187 {
188 gint i;
189 gint res=0;
190 for(i=0; i < set->used; i++) {
191 if(!set->streams[i].end)
192 res++;
193 }
194
195 return res;
196 }
197
null_start(stream_processor * stream)198 static void null_start(stream_processor *stream)
199 {
200 stream->process_end = NULL;
201 stream->type = "invalid";
202 stream->process_page = process_null;
203 }
204
vorbis_start(stream_processor * stream,misc_vorbis_info * inf)205 static void vorbis_start(stream_processor *stream, misc_vorbis_info *inf)
206 {
207 stream->type = "vorbis";
208 stream->process_page = vorbis_process;
209 stream->process_end = vorbis_end;
210
211 stream->data = inf;
212
213 inf = stream->data;
214
215 vorbis_comment_init(&inf->vc);
216 vorbis_info_init(&inf->vi);
217 }
218
find_stream_processor(stream_set * set,ogg_page * page,misc_vorbis_info * inf)219 static stream_processor *find_stream_processor(stream_set *set, ogg_page *page, misc_vorbis_info *inf)
220 {
221 ogg_uint32_t serial = ogg_page_serialno(page);
222 gint i, found = 0;
223 gint invalid = 0;
224 stream_processor *stream;
225
226 for(i=0; i < set->used; i++) {
227 if(serial == set->streams[i].serial) {
228 /* We have a match! */
229 found = 1;
230 stream = &(set->streams[i]);
231
232 set->in_headers = 0;
233 /* if we have detected EOS, then this can't occur here. */
234 if(stream->end) {
235 stream->isillegal = 1;
236 return stream;
237 }
238
239 stream->isnew = 0;
240 stream->start = ogg_page_bos(page);
241 stream->end = ogg_page_eos(page);
242 stream->serial = serial;
243 return stream;
244 }
245 }
246
247 /* If there are streams open, and we've reached the end of the
248 * headers, then we can't be starting a new stream.
249 * XXX: might this sometimes catch ok streams if EOS flag is missing,
250 * but the stream is otherwise ok?
251 */
252 if(streams_open(set) && !set->in_headers)
253 invalid = 1;
254
255 set->in_headers = 1;
256
257 if(set->allocated < set->used) stream = &set->streams[set->used];
258 else {
259 set->allocated += 5;
260 set->streams = realloc(set->streams, sizeof(stream_processor)* set->allocated);
261 stream = &set->streams[set->used];
262 }
263 set->used++;
264 stream->num = set->used; /* We count from 1 */
265
266 stream->isnew = 1;
267 stream->isillegal = invalid;
268
269 {
270 gint res;
271 ogg_packet packet;
272
273 /* We end up processing the header page twice, but that's ok. */
274 ogg_stream_init(&stream->os, serial);
275 ogg_stream_pagein(&stream->os, page);
276 res = ogg_stream_packetout(&stream->os, &packet);
277 if(res > 0 && packet.bytes >= 7 && memcmp(packet.packet, "\001vorbis", 7)==0) {
278 vorbis_start(stream, inf);
279 } else {
280 g_warning(_("Warning: no vorbis"));
281 null_start(stream);
282 }
283
284 res = ogg_stream_packetout(&stream->os, &packet);
285 if(res > 0) {
286 g_warning(_("Warning: Invalid header page in stream %d, contains multiple packets"), stream->num);
287 }
288
289 /* re-init, ready for processing */
290 ogg_stream_clear(&stream->os);
291 ogg_stream_init(&stream->os, serial);
292 }
293
294 stream->start = ogg_page_bos(page);
295 stream->end = ogg_page_eos(page);
296 stream->serial = serial;
297
298 return stream;
299 }
300
get_next_page(FILE * f,ogg_sync_state * sync,ogg_page * page,ogg_int64_t * written)301 static gint get_next_page(FILE *f, ogg_sync_state *sync, ogg_page *page,
302 ogg_int64_t *written)
303 {
304 gint ret;
305 gchar *buffer;
306 gint bytes;
307
308 while((ret = ogg_sync_pageout(sync, page)) <= 0) {
309 buffer = ogg_sync_buffer(sync, CHUNK);
310 bytes = fread(buffer, 1, CHUNK, f);
311 if(bytes <= 0) {
312 ogg_sync_wrote(sync, 0);
313 return 0;
314 }
315 ogg_sync_wrote(sync, bytes);
316 *written += bytes;
317 }
318
319 return 1;
320 }
321
getOggInfo(gchar * Apath,gchar ** Atitle,gchar ** Aalbum,gchar ** Aartist,guint32 * Alength,GError ** Aerror)322 gboolean getOggInfo(gchar *Apath, gchar **Atitle, gchar **Aalbum, gchar **Aartist, guint32 *Alength, GError **Aerror) {
323 FILE *Lfile = fopen(Apath, "rb");
324 ogg_sync_state sync;
325 ogg_page page;
326 stream_set *processors = create_stream_set();
327 gint gotpage = 0;
328 ogg_int64_t written = 0;
329 misc_vorbis_info inf;
330
331 *(Atitle) = *(Aalbum) = *(Aartist) = NULL;
332 *(Alength) = 0;
333
334 bzero(&inf, sizeof(misc_vorbis_info));
335
336 if(!Lfile) {
337 g_set_error(Aerror, G_FILE_ERROR, g_file_error_from_errno(errno), "%s:%s\n%s",
338 _("Cannot read file"), Apath, strerror(errno));
339 return FALSE;
340 }
341
342 ogg_sync_init(&sync);
343
344 while(get_next_page(Lfile, &sync, &page, &written)) {
345 stream_processor *p = find_stream_processor(processors, &page, &inf);
346 gotpage = 1;
347 if(!p) {
348 g_set_error(Aerror, GRAVEMAN_ERROR, _ERR_INAPPROPRIATE_DATA, _("%s is not a valid .ogg file !"), Apath);
349 return FALSE;
350 }
351
352 if(p->isillegal && !p->shownillegal) {
353 g_warning(_("Warning: illegally placed page(s) for logical stream %d\n"
354 "This indicates a corrupt ogg file."), p->num);
355 p->shownillegal = 1;
356 continue;
357 }
358
359 if(p->seqno++ != ogg_page_pageno(&page)) {
360 if(!p->lostseq)
361 g_warning(_("Warning: sequence number gap in stream %d. Got "
362 "page %ld when expecting page %ld. Indicates missing data."),
363 p->num, ogg_page_pageno(&page), p->seqno - 1);
364 p->seqno = ogg_page_pageno(&page);
365 p->lostseq = 1;
366 } else p->lostseq = 0;
367
368 if(!p->isillegal) {
369 p->process_page(p, &page);
370 if(p->end) {
371 if(p->process_end) p->process_end(p);
372 p->isillegal = 1;
373 }
374 }
375 }
376
377 free_stream_set(processors);
378
379 ogg_sync_clear(&sync);
380
381 fclose(Lfile);
382
383 if (gotpage) {
384 *Atitle = g_strdup(inf.title ? inf.title : "");
385 *Aalbum = g_strdup(inf.album ? inf.album : "");
386 *Aartist = g_strdup(inf.artist ? inf.artist : "");
387 /* *Alength = g_strdup_printf("%02ld:%02ld", inf.minutes, inf.seconds); */
388 *(Alength) = inf.length;
389 } else {
390 g_set_error(Aerror, GRAVEMAN_ERROR, _ERR_INAPPROPRIATE_DATA, _("%s is not a valid .ogg file !"), Apath);
391 }
392
393 if (inf.title) g_free(inf.title);
394 if (inf.album) g_free(inf.album);
395 if (inf.artist) g_free(inf.artist);
396
397 return gotpage ? TRUE : FALSE;
398 }
399
400 #endif /* ifdef ENABLE_OGG */
401
402 /*
403 * vim:et:ts=8:sts=2:sw=2
404 */
405