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