2 * Copyright (C) 2007 by INdT
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * @author Renato Chencarek <renato.chencarek@openbossa.org>
19 * @author Eduardo Lima (Etrunko) <eduardo.lima@indt.org.br>
29 * http://xiph.org/ogg/doc/libogg/decoding.html
30 * http://xiph.org/vorbis/doc/libvorbis/overview.html
33 #include <lightmediascanner_plugin.h>
34 #include <lightmediascanner_db.h>
35 #include <lightmediascanner_utils.h>
36 #include <shared/util.h>
42 #include <theora/theoradec.h>
44 #include "lms_ogg_private.h"
50 #define MAX_CHUNKS_PER_PAGE 10
53 struct lms_stream base;
71 struct lms_string_size title;
72 struct lms_string_size artist;
73 struct lms_string_size album;
74 struct lms_string_size genre;
75 enum lms_stream_type type;
76 unsigned char trackno;
77 unsigned int channels;
78 unsigned int sampling_rate;
81 struct stream *streams;
84 static const struct lms_string_size _container = LMS_STATIC_STRING_SIZE("ogg");
85 static const struct lms_string_size _audio_codec =
86 LMS_STATIC_STRING_SIZE("vorbis");
87 static const struct lms_string_size _video_codec =
88 LMS_STATIC_STRING_SIZE("theora");
91 _id3_tag_size(FILE *file)
96 if (fread(tmp, 1, 4, file) == 4) {
97 if (tmp[0] == 'I' && tmp[1] == 'D' &&
98 tmp[2] == '3' && tmp[3] < 0xFF) {
99 fseek(file, 2, SEEK_CUR);
100 if (fread(tmp, 1, 4, file) == 4) {
101 size = 10 + ( (long)(tmp[3])
102 | ((long)(tmp[2]) << 7)
103 | ((long)(tmp[1]) << 14)
104 | ((long)(tmp[0]) << 21) );
114 _set_lms_info(struct lms_string_size *info, const char *tag)
127 info->str = malloc(size * sizeof(char));
128 memcpy(info->str, tag, size);
129 lms_string_size_strip_and_free(info);
132 static bool _ogg_read_page(FILE *fp, ogg_sync_state *osync, ogg_page *page)
136 for (i = 0; i < MAX_CHUNKS_PER_PAGE && ogg_sync_pageout(osync, page) != 1;
138 lms_ogg_buffer_t buffer = lms_get_ogg_sync_buffer(osync, CHUNKSIZE);
139 int bytes = fread(buffer, 1, CHUNKSIZE, fp);
145 ogg_sync_wrote(osync, bytes);
148 if (i > MAX_CHUNKS_PER_PAGE)
154 static struct stream *_stream_new(int serial, int id)
158 s = calloc(1, sizeof(*s));
163 s->os = lms_create_ogg_stream(serial);
165 s->base.type = LMS_STREAM_TYPE_UNKNOWN;
166 s->base.stream_id = id;
171 static void _stream_free(struct stream *s)
173 switch (s->base.type) {
174 case LMS_STREAM_TYPE_UNKNOWN:
175 case LMS_STREAM_TYPE_SUBTITLE:
177 case LMS_STREAM_TYPE_AUDIO:
178 vorbis_comment_clear(&s->audio.vc);
179 vorbis_info_clear(&s->audio.vi);
181 case LMS_STREAM_TYPE_VIDEO:
182 th_comment_clear(&s->video.tc);
183 th_info_clear(&s->video.ti);
184 th_setup_free(s->video.tsi);
185 free(s->base.video.aspect_ratio.str);
189 lms_destroy_ogg_stream(s->os);
193 static struct stream *_info_find_stream(struct ogg_info *info, int serial)
197 for (s = info->streams; s; s = (struct stream *) s->base.next) {
198 if (s->serial == serial)
205 static struct stream *_info_prepend_stream(struct ogg_info *info, int serial,
208 struct stream *s = _stream_new(serial, id);
211 s->base.next = (struct lms_stream *) info->streams;
216 static int _stream_handle_page(struct stream *s, ogg_page *page)
222 s->os = lms_create_ogg_stream(s->serial);
224 if (ogg_stream_pagein(s->os, page) < 0)
228 r = ogg_stream_packetout(s->os, &packet);
234 switch (s->base.type) {
235 case LMS_STREAM_TYPE_UNKNOWN:
236 th_info_init(&s->video.ti);
237 th_comment_init(&s->video.tc);
239 if (th_decode_headerin(&s->video.ti, &s->video.tc, &s->video.tsi,
240 &packet) != TH_ENOTFORMAT) {
241 s->base.type = LMS_STREAM_TYPE_VIDEO;
242 s->remain_headers = 2;
244 th_info_clear(&s->video.ti);
245 th_comment_clear(&s->video.tc);
247 th_setup_free(s->video.tsi);
248 vorbis_info_init(&s->audio.vi);
249 vorbis_comment_init(&s->audio.vc);
250 if (vorbis_synthesis_headerin(&s->audio.vi, &s->audio.vc,
252 vorbis_info_clear(&s->audio.vi);
253 vorbis_comment_clear(&s->audio.vc);
254 s->remain_headers = -1;
258 s->base.type = LMS_STREAM_TYPE_AUDIO;
259 s->remain_headers = 2;
262 case LMS_STREAM_TYPE_VIDEO:
263 assert(s->remain_headers > 0);
264 r = th_decode_headerin(&s->video.ti, &s->video.tc, &s->video.tsi,
267 s->remain_headers = -1;
272 if (!s->remain_headers)
275 case LMS_STREAM_TYPE_AUDIO:
276 assert(s->remain_headers > 0);
277 r = vorbis_synthesis_headerin(&s->audio.vi, &s->audio.vc, &packet);
279 s->remain_headers = -1;
284 if (!s->remain_headers)
288 s->remain_headers = -1;
296 static void _parse_theora_and_vorbis_streams(struct ogg_info *info,
297 struct stream *video_stream)
299 struct stream *s, *prev, *next;
302 /* filter unknown or incomplete streams */
304 for (s = info->streams, next = NULL; s; s = next) {
305 next = (struct stream *) s->base.next;
306 if (s->base.type != LMS_STREAM_TYPE_UNKNOWN && s->remain_headers == 0)
315 for (prev = s, s = next; s; s = next) {
316 next = (struct stream *) s->base.next;
317 if (s->base.type != LMS_STREAM_TYPE_UNKNOWN && s->remain_headers == 0) {
320 prev->base.next = (struct lms_stream *) next;
325 /* add information to each stream */
326 for (s = info->streams; s; s = (struct stream *) s->base.next) {
327 if (s->base.type == LMS_STREAM_TYPE_AUDIO) {
328 s->base.codec = _audio_codec;
329 s->base.audio.channels = s->audio.vi.channels;
330 s->base.audio.bitrate = s->audio.vi.bitrate_nominal;
331 } else if (s->base.type == LMS_STREAM_TYPE_VIDEO) {
332 unsigned int num, den;
334 s->base.codec = _video_codec;
335 s->base.video.bitrate = s->video.ti.target_bitrate;
336 s->base.video.width = s->video.ti.frame_width;
337 s->base.video.height = s->video.ti.frame_height;
338 num = s->video.ti.fps_numerator;
339 den = s->video.ti.fps_denominator;
341 s->base.video.framerate = (double)(num) / den;
343 num = s->video.ti.aspect_numerator;
344 den = s->video.ti.aspect_denominator;
346 reduce_gcd(num, den, &num, &den);
347 asprintf(&s->base.video.aspect_ratio.str, "%u:%u", num, den);
348 s->base.video.aspect_ratio.len =
349 s->base.video.aspect_ratio.str ?
350 strlen(s->base.video.aspect_ratio.str) : 0;
355 /* query the first video stream about relevant metdata */
356 tag = th_comment_query(&video_stream->video.tc, (char *) "TITLE", 0);
357 _set_lms_info(&info->title, tag);
359 tag = th_comment_query(&video_stream->video.tc, (char *) "ARTIST", 0);
360 _set_lms_info(&info->artist, tag);
363 static void _parse_vorbis_stream(struct ogg_info *info, struct stream *s)
367 info->channels = s->audio.vi.channels;
368 info->sampling_rate = s->audio.vi.rate;
369 info->bitrate = s->audio.vi.bitrate_nominal;
371 tag = vorbis_comment_query(&s->audio.vc, "TITLE", 0);
372 _set_lms_info(&info->title, tag);
374 tag = vorbis_comment_query(&s->audio.vc, "ARTIST", 0);
375 _set_lms_info(&info->artist, tag);
377 tag = vorbis_comment_query(&s->audio.vc, "ALBUM", 0);
378 _set_lms_info(&info->album, tag);
380 tag = vorbis_comment_query(&s->audio.vc, "GENRE", 0);
381 _set_lms_info(&info->genre, tag);
383 tag = vorbis_comment_query(&s->audio.vc, "TRACKNUMBER", 0);
385 info->trackno = atoi(tag);
388 static int _parse_ogg(const char *filename, struct ogg_info *info)
392 ogg_sync_state *osync;
394 /* no numeration in the protocol, start arbitrarily from 1 */
396 /* the 1st audio stream, the one used if audio */
397 struct stream *s, *audio_stream = NULL, *video_stream = NULL;
402 fp = fopen(filename, "rb");
406 /* Skip ID3 on the beginning */
407 fseek(fp, _id3_tag_size(fp), SEEK_SET);
409 osync = lms_create_ogg_sync();
410 while (_ogg_read_page(fp, osync, &page)) {
411 int serial = ogg_page_serialno(&page);
413 s = _info_find_stream(info, serial);
415 /* A new page for a stream that has all the headers already */
417 if (s->remain_headers == 0)
419 else if (s->remain_headers < 0)
422 /* We didn't find the stream, but we are not at its start page
423 * neither: it's an unknown stream, go to the next one */
424 if (!ogg_page_bos(&page))
427 s = _info_prepend_stream(info, serial, ++id);
434 r = _stream_handle_page(s, &page);
440 if (s->remain_headers == 0) {
441 if (s->base.type == LMS_STREAM_TYPE_AUDIO && !audio_stream)
443 else if (s->base.type == LMS_STREAM_TYPE_VIDEO && !video_stream)
449 _parse_theora_and_vorbis_streams(info, video_stream);
450 info->type = LMS_STREAM_TYPE_VIDEO;
451 } else if (audio_stream) {
452 _parse_vorbis_stream(info, audio_stream);
453 info->type = LMS_STREAM_TYPE_AUDIO;
457 lms_destroy_ogg_sync(osync);
464 static const char _name[] = "ogg";
465 static const struct lms_string_size _exts[] = {
466 LMS_STATIC_STRING_SIZE(".ogg"),
467 LMS_STATIC_STRING_SIZE(".ogv")
469 static const char *_cats[] = {
474 static const char *_authors[] = {
476 "Eduardo Lima (Etrunko)",
481 struct lms_plugin plugin;
482 lms_db_audio_t *audio_db;
483 lms_db_video_t *video_db;
487 _match(struct plugin *p, const char *path, int len, int base)
491 i = lms_which_extension(path, len, _exts, LMS_ARRAY_SIZE(_exts));
495 return (void*)(i + 1);
499 _parse(struct plugin *plugin, struct lms_context *ctxt,
500 const struct lms_file_info *finfo, void *match)
502 struct ogg_info info = { .type = LMS_STREAM_TYPE_UNKNOWN };
505 r = _parse_ogg(finfo->path, &info);
510 info.title = str_extract_name_from_path(finfo->path, finfo->path_len,
512 &_exts[((long) match) - 1],
515 lms_charset_conv(ctxt->cs_conv, &info.title.str, &info.title.len);
517 lms_charset_conv(ctxt->cs_conv, &info.artist.str, &info.artist.len);
519 if (info.type == LMS_STREAM_TYPE_AUDIO) {
520 struct lms_audio_info audio_info = { };
523 lms_charset_conv(ctxt->cs_conv, &info.album.str, &info.album.len);
525 lms_charset_conv(ctxt->cs_conv, &info.genre.str, &info.genre.len);
527 audio_info.id = finfo->id;
528 audio_info.title = info.title;
529 audio_info.artist = info.artist;
530 audio_info.album = info.album;
531 audio_info.genre = info.genre;
533 audio_info.trackno = info.trackno;
534 audio_info.container = _container;
535 audio_info.codec = _audio_codec;
536 audio_info.channels = info.channels;
537 audio_info.sampling_rate = info.sampling_rate;
538 audio_info.bitrate = info.bitrate;
540 r = lms_db_audio_add(plugin->audio_db, &audio_info);
541 } else if (info.type == LMS_STREAM_TYPE_VIDEO) {
542 struct lms_video_info video_info = { };
544 video_info.id = finfo->id;
545 video_info.title = info.title;
546 video_info.artist = info.artist;
547 video_info.streams = (struct lms_stream *) info.streams;
548 r = lms_db_video_add(plugin->video_db, &video_info);
552 while (info.streams) {
553 struct stream *s = info.streams;
554 info.streams = (struct stream *) s->base.next;
558 free(info.title.str);
559 free(info.artist.str);
560 free(info.album.str);
561 free(info.genre.str);
567 _setup(struct plugin *plugin, struct lms_context *ctxt)
569 plugin->audio_db = lms_db_audio_new(ctxt->db);
570 if (!plugin->audio_db)
572 plugin->video_db = lms_db_video_new(ctxt->db);
573 if (!plugin->video_db)
580 _start(struct plugin *plugin, struct lms_context *ctxt)
583 r = lms_db_audio_start(plugin->audio_db);
584 r |= lms_db_video_start(plugin->video_db);
589 _finish(struct plugin *plugin, struct lms_context *ctxt)
591 if (plugin->audio_db)
592 lms_db_audio_free(plugin->audio_db);
593 if (plugin->video_db)
594 lms_db_video_free(plugin->video_db);
599 _close(struct plugin *plugin)
605 API struct lms_plugin *
606 lms_plugin_open(void)
608 struct plugin *plugin;
610 plugin = (struct plugin *)malloc(sizeof(*plugin));
611 plugin->plugin.name = _name;
612 plugin->plugin.match = (lms_plugin_match_fn_t)_match;
613 plugin->plugin.parse = (lms_plugin_parse_fn_t)_parse;
614 plugin->plugin.close = (lms_plugin_close_fn_t)_close;
615 plugin->plugin.setup = (lms_plugin_setup_fn_t)_setup;
616 plugin->plugin.start = (lms_plugin_start_fn_t)_start;
617 plugin->plugin.finish = (lms_plugin_finish_fn_t)_finish;
619 return (struct lms_plugin *)plugin;
622 API const struct lms_plugin_info *
623 lms_plugin_info(void)
625 static struct lms_plugin_info info = {
631 "http://lms.garage.maemo.org"