Updated package version
[lms] / lightmediascanner / 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  *
18  * @author Andre Moreira Magalhaes <andre.magalhaes@openbossa.org>
19  */
20
21 /**
22  * @brief
23  *
24  * real media file parser.
25  */
26
27 #ifdef HAVE_CONFIG_H
28 #include "config.h"
29 #endif
30
31 #define _XOPEN_SOURCE 600
32 #include <lightmediascanner_plugin.h>
33 #include <lightmediascanner_db.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <fcntl.h>
37 #include <stdint.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42
43 #define BE_4BYTE(a) ((((unsigned char*)a)[0] << 24) |                   \
44                      (((unsigned char*)a)[1] << 16) |                   \
45                      (((unsigned char*)a)[2] << 8) |                    \
46                      ((unsigned char*)a)[3])
47 #define BE_2BYTE(a) ((((unsigned char*)a)[0] << 8) | ((unsigned char*)a)[1])
48
49 enum StreamTypes {
50     STREAM_TYPE_UNKNOWN = 0,
51     STREAM_TYPE_AUDIO,
52     STREAM_TYPE_VIDEO
53 };
54
55 struct rm_info {
56     struct lms_string_size title;
57     struct lms_string_size artist;
58 };
59
60 struct rm_file_header {
61     char type[4];
62     uint32_t size;
63     uint16_t version;
64 } __attribute__((packed));
65
66 struct plugin {
67     struct lms_plugin plugin;
68     lms_db_audio_t *audio_db;
69     lms_db_video_t *video_db;
70 };
71
72 static const char _name[] = "rm";
73 static const struct lms_string_size _exts[] = {
74     LMS_STATIC_STRING_SIZE(".ra"),
75     LMS_STATIC_STRING_SIZE(".rv"),
76     LMS_STATIC_STRING_SIZE(".rm"),
77     LMS_STATIC_STRING_SIZE(".rmj"),
78     LMS_STATIC_STRING_SIZE(".rmvb")
79 };
80
81 /*
82  * A real media file header has the following format:
83  * dword chunk type ('.RMF')
84  * dword chunk size (typically 0x12)
85  * word  chunk version
86  * dword file version
87  * dword number of headers
88  */
89 static int
90 _parse_file_header(int fd, struct rm_file_header *file_header)
91 {
92     if (read(fd, file_header, sizeof(struct rm_file_header)) == -1) {
93         fprintf(stderr, "ERROR: could not read file header\n");
94         return -1;
95     }
96
97     if (memcmp(file_header->type, ".RMF", 4) != 0) {
98         fprintf(stderr, "ERROR: invalid header type\n");
99         return -1;
100     }
101
102     /* convert to host byte order */
103     file_header->size = BE_4BYTE(&file_header->size);
104
105 #if 0
106     fprintf(stderr, "file_header type=%.*s\n", 4, file_header->type);
107     fprintf(stderr, "file_header size=%d\n", file_header->size);
108     fprintf(stderr, "file_header version=%d\n", file_header->version);
109 #endif
110
111     /* TODO we should ignore these fields just when version is 0 or 1,
112      * but using the test files, if we don't ignore them for version 256
113      * it fails */
114     /* ignore file header extra fields
115      * file version and number of headers */
116     lseek(fd, 8, SEEK_CUR);
117
118     return 0;
119 }
120
121 static int
122 _read_header_type_and_size(int fd, char *type, uint32_t *size)
123 {
124     if (read(fd, type, 4) != 4)
125         return -1;
126
127     if (read(fd, size, 4) != 4)
128         return -1;
129
130     *size = BE_4BYTE(size);
131
132 #if 0
133     fprintf(stderr, "header type=%.*s\n", 4, type);
134     fprintf(stderr, "header size=%d\n", *size);
135 #endif
136
137     return 0;
138 }
139
140 static int
141 _read_string(int fd, char **out, unsigned int *out_len)
142 {
143     char *s;
144     uint16_t len;
145
146     if (read(fd, &len, 2) == -1)
147         return -1;
148
149     len = BE_2BYTE(&len);
150
151     if (out) {
152         if (len > 0) {
153             s = malloc(sizeof(char) * (len + 1));
154             if (read(fd, s, len) == -1) {
155                 free(s);
156                 return -1;
157             }
158             s[len] = '\0';
159             *out = s;
160         } else
161             *out = NULL;
162
163         *out_len = len;
164     } else
165         lseek(fd, len, SEEK_CUR);
166
167     return 0;
168 }
169
170 /*
171  * A CONT header has the following format
172  * dword   Chunk type ('CONT')
173  * dword   Chunk size
174  * word    Chunk version (always 0, for every known file)
175  * word    Title string length
176  * byte[]  Title string
177  * word    Author string length
178  * byte[]  Author string
179  * word    Copyright string length
180  * byte[]  Copyright string
181  * word    Comment string length
182  * byte[]  Comment string
183  */
184 static void
185 _parse_cont_header(int fd, struct rm_info *info)
186 {
187     /* Ps.: type and size were already read */
188
189     /* ignore version */
190     lseek(fd, 2, SEEK_CUR);
191
192     _read_string(fd, &info->title.str, &info->title.len);
193     _read_string(fd, &info->artist.str, &info->artist.len);
194     _read_string(fd, NULL, NULL); /* copyright */
195     _read_string(fd, NULL, NULL); /* comment */
196 }
197
198 static void *
199 _match(struct plugin *p, const char *path, int len, int base)
200 {
201     int i;
202
203     i = lms_which_extension(path, len, _exts, LMS_ARRAY_SIZE(_exts));
204     if (i < 0)
205       return NULL;
206     else
207       return (void*)(i + 1);
208 }
209
210 static int
211 _parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
212 {
213     struct rm_info info = {{0}, {0}};
214     struct lms_audio_info audio_info = {0, {0}, {0}, {0}, {0}, 0, 0, 0};
215     struct lms_video_info video_info = {0, {0}, {0}};
216     int r, fd, stream_type = STREAM_TYPE_UNKNOWN;
217     struct rm_file_header file_header;
218     char type[4];
219     uint32_t size;
220
221     fd = open(finfo->path, O_RDONLY);
222     if (fd < 0) {
223         perror("open");
224         return -1;
225     }
226
227     if (_parse_file_header(fd, &file_header) != 0) {
228         r = -2;
229         goto done;
230     }
231
232     if (_read_header_type_and_size(fd, type, &size) != 0) {
233         r = -3;
234         goto done;
235     }
236     while (memcmp(type, "DATA", 4) != 0) {
237         if (memcmp(type, "CONT", 4) == 0) {
238             _parse_cont_header(fd, &info);
239             break;
240         }
241         /* TODO check for mimetype
242         else if (memcmp(type, "MDPR", 4) == 0) {
243         }
244         */
245         /* ignore other headers */
246         else
247             lseek(fd, size - 8, SEEK_CUR);
248
249         if (_read_header_type_and_size(fd, type, &size) != 0) {
250             r = -4;
251             goto done;
252         }
253     }
254
255     /* try to define stream type by extension */
256     if (stream_type == STREAM_TYPE_UNKNOWN) {
257         int ext_idx = ((int)match) - 1;
258         if (strcmp(_exts[ext_idx].str, ".ra") == 0)
259             stream_type = STREAM_TYPE_AUDIO;
260         /* consider rv, rm, rmj and rmvb as video */
261         else
262             stream_type = STREAM_TYPE_VIDEO;
263     }
264
265     lms_string_size_strip_and_free(&info.title);
266     lms_string_size_strip_and_free(&info.artist);
267
268     if (!info.title.str) {
269         int ext_idx;
270         ext_idx = ((int)match) - 1;
271         info.title.len = finfo->path_len - finfo->base - _exts[ext_idx].len;
272         info.title.str = malloc((info.title.len + 1) * sizeof(char));
273         memcpy(info.title.str, finfo->path + finfo->base, info.title.len);
274         info.title.str[info.title.len] = '\0';
275     }
276     lms_charset_conv(ctxt->cs_conv, &info.title.str, &info.title.len);
277
278     if (info.artist.str)
279         lms_charset_conv(ctxt->cs_conv, &info.artist.str, &info.artist.len);
280
281 #if 0
282     fprintf(stderr, "file %s info\n", finfo->path);
283     fprintf(stderr, "\ttitle=%s\n", info.title);
284     fprintf(stderr, "\tartist=%s\n", info.artist);
285 #endif
286
287     if (stream_type == STREAM_TYPE_AUDIO) {
288         audio_info.id = finfo->id;
289         audio_info.title = info.title;
290         audio_info.artist = info.artist;
291         r = lms_db_audio_add(plugin->audio_db, &audio_info);
292     }
293     else {
294         video_info.id = finfo->id;
295         video_info.title = info.title;
296         video_info.artist = info.artist;
297         r = lms_db_video_add(plugin->video_db, &video_info);
298     }
299
300   done:
301     if (info.title.str)
302         free(info.title.str);
303     if (info.artist.str)
304         free(info.artist.str);
305
306     posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
307     close(fd);
308
309     return r;
310 }
311
312 static int
313 _setup(struct plugin *plugin, struct lms_context *ctxt)
314 {
315     plugin->audio_db = lms_db_audio_new(ctxt->db);
316     if (!plugin->audio_db)
317         return -1;
318     plugin->video_db = lms_db_video_new(ctxt->db);
319     if (!plugin->video_db)
320         return -1;
321
322     return 0;
323 }
324
325 static int
326 _start(struct plugin *plugin, struct lms_context *ctxt)
327 {
328     int r;
329     r = lms_db_audio_start(plugin->audio_db);
330     r |= lms_db_video_start(plugin->video_db);
331     return r;
332 }
333
334 static int
335 _finish(struct plugin *plugin, struct lms_context *ctxt)
336 {
337     if (plugin->audio_db)
338         lms_db_audio_free(plugin->audio_db);
339     if (plugin->video_db)
340         lms_db_video_free(plugin->video_db);
341
342     return 0;
343 }
344
345 static int
346 _close(struct plugin *plugin)
347 {
348     free(plugin);
349     return 0;
350 }
351
352 API struct lms_plugin *
353 lms_plugin_open(void)
354 {
355     struct plugin *plugin;
356
357     plugin = (struct plugin *)malloc(sizeof(*plugin));
358     plugin->plugin.name = _name;
359     plugin->plugin.match = (lms_plugin_match_fn_t)_match;
360     plugin->plugin.parse = (lms_plugin_parse_fn_t)_parse;
361     plugin->plugin.close = (lms_plugin_close_fn_t)_close;
362     plugin->plugin.setup = (lms_plugin_setup_fn_t)_setup;
363     plugin->plugin.start = (lms_plugin_start_fn_t)_start;
364     plugin->plugin.finish = (lms_plugin_finish_fn_t)_finish;
365
366     return (struct lms_plugin *)plugin;
367 }