introduce and use helpers lms_string_size_strndup() and lms_string_size_dup()
[platform/upstream/lightmediascanner.git] / src / plugins / rm / rm.c
1 /**
2  * Copyright (C) 2008 by INdT
3  *
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.
8  *
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.
13  *
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.
17  *
18  * @author Andre Moreira Magalhaes <andre.magalhaes@openbossa.org>
19  */
20
21 /**
22  * @brief
23  *
24  * References:
25  *   http://wiki.multimedia.cx/index.php?title=RealMedia
26  *
27  * real media file parser.
28  */
29
30 #include <lightmediascanner_plugin.h>
31 #include <lightmediascanner_db.h>
32 #include <shared/util.h>
33
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <endian.h>
37 #include <fcntl.h>
38 #include <stdbool.h>
39 #include <inttypes.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <unistd.h>
44
45 struct rm_info {
46     struct lms_string_size title;
47     struct lms_string_size artist;
48     struct lms_string_size codec;
49
50     uint32_t bitrate;
51     uint32_t length; /* msec */
52
53     uint16_t sampling_rate;
54     uint16_t channels;
55     enum lms_stream_type stream_type; /* we only care for the first stream */
56 };
57
58 struct rm_file_header {
59     char type[4];
60     uint32_t size;
61     uint16_t version;
62 } __attribute__((packed));
63
64 struct plugin {
65     struct lms_plugin plugin;
66     lms_db_audio_t *audio_db;
67     lms_db_video_t *video_db;
68 };
69
70 static const char _name[] = "rm";
71 static const struct lms_string_size _exts[] = {
72     LMS_STATIC_STRING_SIZE(".ra"),
73     LMS_STATIC_STRING_SIZE(".rv"),
74     LMS_STATIC_STRING_SIZE(".rm"),
75     LMS_STATIC_STRING_SIZE(".rmj"),
76     LMS_STATIC_STRING_SIZE(".rmvb")
77 };
78 static const char *_cats[] = {
79     "multimedia",
80     "audio",
81     "video",
82     NULL
83 };
84 static const char *_authors[] = {
85     "Andre Moreira Magalhaes",
86     NULL
87 };
88
89 /*
90  * A real media file header has the following format:
91  * dword chunk type ('.RMF')
92  * dword chunk size (typically 0x12)
93  * word  chunk version
94  * dword file version
95  * dword number of headers
96  *
97  * Old RealAudio files (up to version 5) are not supported - they have the
98  * .ra\xfd
99  */
100 static int
101 _parse_file_header(int fd, struct rm_file_header *file_header)
102 {
103     if (read(fd, file_header, sizeof(struct rm_file_header)) == -1) {
104         fprintf(stderr, "ERROR: could not read file header\n");
105         return -1;
106     }
107
108     if (memcmp(file_header->type, ".RMF", 4) != 0) {
109         fprintf(stderr, "ERROR: invalid header type\n");
110         return -1;
111     }
112
113     file_header->size = be32toh(file_header->size);
114
115 #if 0
116     fprintf(stderr, "file_header type=%.*s\n", 4, file_header->type);
117     fprintf(stderr, "file_header size=%d\n", file_header->size);
118     fprintf(stderr, "file_header version=%d\n", file_header->version);
119 #endif
120
121     /* TODO we should ignore these fields just when version is 0 or 1,
122      * but using the test files, if we don't ignore them for version 256
123      * it fails */
124     /* ignore file header extra fields
125      * file version and number of headers */
126     lseek(fd, 8, SEEK_CUR);
127
128     return 0;
129 }
130
131 static int
132 _read_header_type_and_size(int fd, char *type, uint32_t *size)
133 {
134     if (read(fd, type, 4) != 4)
135         return -1;
136
137     if (read(fd, size, 4) != 4)
138         return -1;
139
140     *size = be32toh(*size);
141
142 #if 0
143     fprintf(stderr, "header type=%.*s\n", 4, type);
144     fprintf(stderr, "header size=%d\n", *size);
145 #endif
146
147     return 0;
148 }
149
150 static int
151 _read_string(int fd, char **out, unsigned int *out_len)
152 {
153     char *s;
154     uint16_t len;
155
156     if (read(fd, &len, 2) == -1)
157         return -1;
158
159     len = be16toh(len);
160
161     if (out) {
162         if (len > 0) {
163             s = malloc(sizeof(char) * (len + 1));
164             if (read(fd, s, len) == -1) {
165                 free(s);
166                 return -1;
167             }
168             s[len] = '\0';
169             *out = s;
170         } else
171             *out = NULL;
172
173         *out_len = len;
174     } else
175         lseek(fd, len, SEEK_CUR);
176
177     return 0;
178 }
179
180 /*
181  * A CONT header has the following format
182  * dword   Chunk type ('CONT')
183  * dword   Chunk size
184  * word    Chunk version (always 0, for every known file)
185  * word    Title string length
186  * byte[]  Title string
187  * word    Author string length
188  * byte[]  Author string
189  * word    Copyright string length
190  * byte[]  Copyright string
191  * word    Comment string length
192  * byte[]  Comment string
193  */
194 static long
195 _parse_cont_header(int fd, struct rm_info *info)
196 {
197     long pos1;
198     /* Ps.: type and size were already read */
199
200     /* ignore version */
201     pos1 = lseek(fd, 2, SEEK_CUR);
202     if (pos1 < 0)
203         return pos1;
204
205     _read_string(fd, &info->title.str, &info->title.len);
206     _read_string(fd, &info->artist.str, &info->artist.len);
207     _read_string(fd, NULL, NULL); /* copyright */
208     _read_string(fd, NULL, NULL); /* comment */
209
210     return lseek(fd, 0, SEEK_CUR) - pos1;
211 }
212
213 static struct lms_string_size
214 _ra_codec_to_str(uint8_t fourcc[4])
215 {
216     struct {
217         char fourcc[4];
218         struct lms_string_size str;
219     } *iter, _codecs[] = {
220         { "RV10", LMS_STATIC_STRING_SIZE("rv10") },
221         { "RV20", LMS_STATIC_STRING_SIZE("rv20") },
222         { "RV20", LMS_STATIC_STRING_SIZE("rv20") },
223         { "RVTR", LMS_STATIC_STRING_SIZE("rv20") },
224         { "RV20", LMS_STATIC_STRING_SIZE("rv20") },
225         { "RV30", LMS_STATIC_STRING_SIZE("rv30") },
226         { "RV40", LMS_STATIC_STRING_SIZE("rv40") },
227         { "dnet", LMS_STATIC_STRING_SIZE("ac3") },
228         { "lpcj", LMS_STATIC_STRING_SIZE("rm_144") },
229         { "28_8", LMS_STATIC_STRING_SIZE("rm_288") },
230         { "cook", LMS_STATIC_STRING_SIZE("cook") },
231         { "atrc", LMS_STATIC_STRING_SIZE("atrac3") },
232         { "atrc", LMS_STATIC_STRING_SIZE("atrac3") },
233         { "sipr", LMS_STATIC_STRING_SIZE("sipr") },
234         { "raac", LMS_STATIC_STRING_SIZE("aac") },
235         { "racp", LMS_STATIC_STRING_SIZE("aac") },
236         { "LSD:", LMS_STATIC_STRING_SIZE("ralf") },
237         { }
238     };
239
240     for (iter = _codecs; iter->str.str; iter++)
241         if (memcmp(fourcc, iter->fourcc, 4) == 0)
242             break;
243
244     return iter->str;
245 }
246
247 static bool
248 _parse_mdpr_codec_header(int fd, struct rm_info *info)
249 {
250     uint32_t size;
251     uint8_t fourcc[4];
252     uint16_t version;
253     long skipbytes;
254
255     if (read(fd, &size, sizeof(size)) != sizeof(size)
256         || read(fd, fourcc, sizeof(fourcc)) != sizeof(fourcc))
257         return false;
258
259     if (memcmp(fourcc, ".ra\xfd", 4) != 0)
260         return false;
261
262     if (read(fd, &version, sizeof(version)) != sizeof(version))
263         return false;
264     version = be16toh(version);
265
266     if (version == 3) {
267         info->codec = LMS_STATIC_STRING_SIZE("rm_144");
268         info->sampling_rate = 8000;
269         info->channels = 1;
270         return true;
271     }
272     if (version == 4)
273         skipbytes = 42;
274     else if (version == 5)
275         skipbytes = 48;
276     else
277         return false;
278
279     if (lseek(fd, skipbytes, SEEK_CUR) < 0
280         && read(fd, &info->sampling_rate, 2) != 2)
281         return false;
282
283     info->sampling_rate = be16toh(info->sampling_rate);
284
285     if (lseek(fd, 4, SEEK_CUR) < 0
286         || read(fd, &info->channels, 2) != 2)
287         return true;
288
289     info->channels = be16toh(info->channels);
290
291     if (version == 4)
292         skipbytes = 9;
293     else
294         skipbytes = 4;
295
296     if (read(fd, fourcc, 4) != 4)
297         return true;
298
299     info->codec = _ra_codec_to_str(fourcc);
300     return true;
301 }
302
303 static int
304 _parse_mdpr_header(int fd, struct rm_info *info, bool *has_mdpr)
305 {
306     uint16_t object_version;
307     uint8_t slen;
308     char buf[32];
309     long pos1;
310
311     pos1 = lseek(fd, 0, SEEK_CUR);
312
313     if (read(fd, &object_version, sizeof(object_version)) !=
314         sizeof(object_version))
315         return -1;
316
317     if (object_version != 0)
318         return sizeof(object_version);
319
320     lseek(fd, 7 * sizeof(uint32_t), SEEK_CUR);
321
322     /* stream description string: ignore */
323     if (read(fd, &slen, sizeof(slen)) != sizeof(slen))
324         return -1;
325     lseek(fd, slen, SEEK_CUR);
326
327     /* mime type string */
328     if (read(fd, &slen, sizeof(slen)) != sizeof(slen)
329         || slen > 32
330         || read(fd, buf, slen) != slen)
331         goto done;
332
333     buf[slen] = '\0';
334
335     if (strcmp(buf, "audio/x-pn-realaudio") != 0 &&
336         strcmp(buf, "audio/x-pn-multirate-realaudio") != 0)
337         goto done;
338
339     *has_mdpr = _parse_mdpr_codec_header(fd, info);
340     if (*has_mdpr)
341         info->stream_type = LMS_STREAM_TYPE_AUDIO;
342
343 done:
344     return lseek(fd, 0, SEEK_CUR) - pos1;
345 }
346
347 static int
348 _parse_prop_header(int fd, struct rm_info *info)
349 {
350     uint16_t object_version;
351     struct {
352         uint32_t max_bit_rate;
353         uint32_t avg_bit_rate;
354         uint32_t max_packet_size;
355         uint32_t avg_packet_size;
356         uint32_t num_packets;
357         uint32_t duration;
358         uint32_t preroll;
359         uint32_t index_offset;
360         uint32_t data_offset;
361         uint16_t num_streams;
362         uint16_t flags;
363     } __attribute__((packed, aligned)) hdr;
364
365     if (read(fd, &object_version, sizeof(object_version))
366         != sizeof(object_version)
367         || object_version != 0)
368         return -1;
369
370     if (read(fd, &hdr, sizeof(hdr)) != sizeof(hdr))
371         return -1;
372
373     info->bitrate = be32toh(hdr.avg_bit_rate);
374     info->length = be32toh(hdr.duration);
375
376     return sizeof(object_version) + sizeof(hdr);
377 }
378
379 static void *
380 _match(struct plugin *p, const char *path, int len, int base)
381 {
382     long i;
383
384     i = lms_which_extension(path, len, _exts, LMS_ARRAY_SIZE(_exts));
385     if (i < 0)
386       return NULL;
387     else
388       return (void*)(i + 1);
389 }
390
391 static int
392 _parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
393 {
394     struct rm_info info = { .stream_type = LMS_STREAM_TYPE_UNKNOWN };
395     struct lms_audio_info audio_info = { };
396     struct lms_video_info video_info = { };
397     int r, fd;
398     struct rm_file_header file_header;
399     char type[4];
400     uint32_t size;
401     bool has_cont = false, has_prop = false, has_mdpr = false;
402
403     fd = open(finfo->path, O_RDONLY);
404     if (fd < 0) {
405         perror("open");
406         return -1;
407     }
408
409     if (_parse_file_header(fd, &file_header) != 0) {
410         r = -2;
411         goto done;
412     }
413
414     do {
415         if (_read_header_type_and_size(fd, type, &size) != 0) {
416             r = -3;
417             goto done;
418         }
419
420         /* Give up, already reached DATA section */
421         if (memcmp(type, "DATA", 4) == 0)
422             break;
423
424         if (memcmp(type, "CONT", 4) == 0) {
425             r = _parse_cont_header(fd, &info);
426             if (r < 0)
427                 goto done;
428             lseek(fd, size - 8 - r, SEEK_CUR);
429             has_cont = true;
430         } else if (memcmp(type, "PROP", 4) == 0) {
431             r = _parse_prop_header(fd, &info);
432             if (r < 0)
433                 goto done;
434             lseek(fd, size - 8 - r, SEEK_CUR);
435             has_prop = true;
436         } else if (memcmp(type, "MDPR", 4)) {
437             r = _parse_mdpr_header(fd, &info, &has_mdpr);
438             if (r < 0)
439                 goto done;
440             lseek(fd, size - 8 - r, SEEK_CUR);
441         } else
442             /* Ignore other headers */
443             lseek(fd, size - 8, SEEK_CUR);
444     } while (!has_cont && !has_prop && !has_mdpr);
445
446     /* try to define stream type by extension */
447     if (info.stream_type == LMS_STREAM_TYPE_UNKNOWN) {
448         long ext_idx = ((long)match) - 1;
449         if (strcmp(_exts[ext_idx].str, ".ra") == 0)
450             info.stream_type = LMS_STREAM_TYPE_AUDIO;
451         /* consider rv, rm, rmj and rmvb as video */
452         else
453             info.stream_type = LMS_STREAM_TYPE_VIDEO;
454     }
455
456     lms_string_size_strip_and_free(&info.title);
457     lms_string_size_strip_and_free(&info.artist);
458
459     if (!info.title.str)
460         info.title = str_extract_name_from_path(finfo->path, finfo->path_len,
461                                                 finfo->base,
462                                                 &_exts[((long) match) - 1],
463                                                 NULL);
464     if (info.title.str)
465         lms_charset_conv(ctxt->cs_conv, &info.title.str, &info.title.len);
466
467     if (info.artist.str)
468         lms_charset_conv(ctxt->cs_conv, &info.artist.str, &info.artist.len);
469
470 #if 0
471     fprintf(stderr, "file %s info\n", finfo->path);
472     fprintf(stderr, "\ttitle=%s\n", info.title);
473     fprintf(stderr, "\tartist=%s\n", info.artist);
474 #endif
475
476     if (info.stream_type == LMS_STREAM_TYPE_AUDIO) {
477         audio_info.id = finfo->id;
478         audio_info.title = info.title;
479         audio_info.artist = info.artist;
480         r = lms_db_audio_add(plugin->audio_db, &audio_info);
481     }
482     else {
483         video_info.id = finfo->id;
484         video_info.title = info.title;
485         video_info.artist = info.artist;
486         r = lms_db_video_add(plugin->video_db, &video_info);
487     }
488
489   done:
490     free(info.title.str);
491     free(info.artist.str);
492
493     posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
494     close(fd);
495
496     return r;
497 }
498
499 static int
500 _setup(struct plugin *plugin, struct lms_context *ctxt)
501 {
502     plugin->audio_db = lms_db_audio_new(ctxt->db);
503     if (!plugin->audio_db)
504         return -1;
505     plugin->video_db = lms_db_video_new(ctxt->db);
506     if (!plugin->video_db)
507         return -1;
508
509     return 0;
510 }
511
512 static int
513 _start(struct plugin *plugin, struct lms_context *ctxt)
514 {
515     int r;
516     r = lms_db_audio_start(plugin->audio_db);
517     r |= lms_db_video_start(plugin->video_db);
518     return r;
519 }
520
521 static int
522 _finish(struct plugin *plugin, struct lms_context *ctxt)
523 {
524     if (plugin->audio_db)
525         lms_db_audio_free(plugin->audio_db);
526     if (plugin->video_db)
527         lms_db_video_free(plugin->video_db);
528
529     return 0;
530 }
531
532 static int
533 _close(struct plugin *plugin)
534 {
535     free(plugin);
536     return 0;
537 }
538
539 API struct lms_plugin *
540 lms_plugin_open(void)
541 {
542     struct plugin *plugin;
543
544     plugin = (struct plugin *)malloc(sizeof(*plugin));
545     plugin->plugin.name = _name;
546     plugin->plugin.match = (lms_plugin_match_fn_t)_match;
547     plugin->plugin.parse = (lms_plugin_parse_fn_t)_parse;
548     plugin->plugin.close = (lms_plugin_close_fn_t)_close;
549     plugin->plugin.setup = (lms_plugin_setup_fn_t)_setup;
550     plugin->plugin.start = (lms_plugin_start_fn_t)_start;
551     plugin->plugin.finish = (lms_plugin_finish_fn_t)_finish;
552
553     return (struct lms_plugin *)plugin;
554 }
555
556 API const struct lms_plugin_info *
557 lms_plugin_info(void)
558 {
559     static struct lms_plugin_info info = {
560         _name,
561         _cats,
562         "Real Networks audio and video files (RA, RV, RM, RMJ, RMVB)",
563         PACKAGE_VERSION,
564         _authors,
565         "http://lms.garage.maemo.org"
566     };
567
568     return &info;
569 }