2 * Copyright (C) 2008 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 * @author Andre Moreira Magalhaes <andre.magalhaes@openbossa.org>
24 * real media file parser.
31 #define _XOPEN_SOURCE 600
32 #include <lightmediascanner_plugin.h>
33 #include <lightmediascanner_db.h>
34 #include <sys/types.h>
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])
50 STREAM_TYPE_UNKNOWN = 0,
56 struct lms_string_size title;
57 struct lms_string_size artist;
60 struct rm_file_header {
64 } __attribute__((packed));
67 struct lms_plugin plugin;
68 lms_db_audio_t *audio_db;
69 lms_db_video_t *video_db;
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")
82 * A real media file header has the following format:
83 * dword chunk type ('.RMF')
84 * dword chunk size (typically 0x12)
87 * dword number of headers
90 _parse_file_header(int fd, struct rm_file_header *file_header)
92 if (read(fd, file_header, sizeof(struct rm_file_header)) == -1) {
93 fprintf(stderr, "ERROR: could not read file header\n");
97 if (memcmp(file_header->type, ".RMF", 4) != 0) {
98 fprintf(stderr, "ERROR: invalid header type\n");
102 /* convert to host byte order */
103 file_header->size = BE_4BYTE(&file_header->size);
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);
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
114 /* ignore file header extra fields
115 * file version and number of headers */
116 lseek(fd, 8, SEEK_CUR);
122 _read_header_type_and_size(int fd, char *type, uint32_t *size)
124 if (read(fd, type, 4) != 4)
127 if (read(fd, size, 4) != 4)
130 *size = BE_4BYTE(size);
133 fprintf(stderr, "header type=%.*s\n", 4, type);
134 fprintf(stderr, "header size=%d\n", *size);
141 _read_string(int fd, char **out, unsigned int *out_len)
146 if (read(fd, &len, 2) == -1)
149 len = BE_2BYTE(&len);
153 s = malloc(sizeof(char) * (len + 1));
154 if (read(fd, s, len) == -1) {
165 lseek(fd, len, SEEK_CUR);
171 * A CONT header has the following format
172 * dword Chunk type ('CONT')
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
185 _parse_cont_header(int fd, struct rm_info *info)
187 /* Ps.: type and size were already read */
190 lseek(fd, 2, SEEK_CUR);
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 */
199 _strstrip(char **str, unsigned int *p_len)
202 lms_strstrip(*str, p_len);
204 if (*p_len == 0 && *str) {
211 _match(struct plugin *p, const char *path, int len, int base)
215 i = lms_which_extension(path, len, _exts, LMS_ARRAY_SIZE(_exts));
219 return (void*)(i + 1);
223 _parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
225 struct rm_info info = {{0}, {0}};
226 struct lms_audio_info audio_info = {0, {0}, {0}, {0}, {0}, 0, 0, 0};
227 struct lms_video_info video_info = {0, {0}, {0}};
228 int r, fd, stream_type = STREAM_TYPE_UNKNOWN;
229 struct rm_file_header file_header;
233 fd = open(finfo->path, O_RDONLY);
239 if (_parse_file_header(fd, &file_header) != 0) {
244 if (_read_header_type_and_size(fd, type, &size) != 0) {
248 while (memcmp(type, "DATA", 4) != 0) {
249 if (memcmp(type, "CONT", 4) == 0) {
250 _parse_cont_header(fd, &info);
253 /* TODO check for mimetype
254 else if (memcmp(type, "MDPR", 4) == 0) {
257 /* ignore other headers */
259 lseek(fd, size - 8, SEEK_CUR);
261 if (_read_header_type_and_size(fd, type, &size) != 0) {
267 /* try to define stream type by extension */
268 if (stream_type == STREAM_TYPE_UNKNOWN) {
269 int ext_idx = ((int)match) - 1;
270 if (strcmp(_exts[ext_idx].str, ".ra") == 0)
271 stream_type = STREAM_TYPE_AUDIO;
272 /* consider rv, rm, rmj and rmvb as video */
274 stream_type = STREAM_TYPE_VIDEO;
277 _strstrip(&info.title.str, &info.title.len);
278 _strstrip(&info.artist.str, &info.artist.len);
280 if (!info.title.str) {
282 ext_idx = ((int)match) - 1;
283 info.title.len = finfo->path_len - finfo->base - _exts[ext_idx].len;
284 info.title.str = malloc((info.title.len + 1) * sizeof(char));
285 memcpy(info.title.str, finfo->path + finfo->base, info.title.len);
286 info.title.str[info.title.len] = '\0';
287 lms_charset_conv(ctxt->cs_conv, &info.title.str, &info.title.len);
291 fprintf(stderr, "file %s info\n", finfo->path);
292 fprintf(stderr, "\ttitle=%s\n", info.title);
293 fprintf(stderr, "\tartist=%s\n", info.artist);
296 if (stream_type == STREAM_TYPE_AUDIO) {
297 audio_info.id = finfo->id;
298 audio_info.title = info.title;
299 audio_info.artist = info.artist;
300 r = lms_db_audio_add(plugin->audio_db, &audio_info);
303 video_info.id = finfo->id;
304 video_info.title = info.title;
305 video_info.artist = info.artist;
306 r = lms_db_video_add(plugin->video_db, &video_info);
311 free(info.title.str);
313 free(info.artist.str);
315 posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
322 _setup(struct plugin *plugin, struct lms_context *ctxt)
324 plugin->audio_db = lms_db_audio_new(ctxt->db);
325 if (!plugin->audio_db)
327 plugin->video_db = lms_db_video_new(ctxt->db);
328 if (!plugin->video_db)
335 _start(struct plugin *plugin, struct lms_context *ctxt)
338 r = lms_db_audio_start(plugin->audio_db);
339 r |= lms_db_video_start(plugin->video_db);
344 _finish(struct plugin *plugin, struct lms_context *ctxt)
346 if (plugin->audio_db)
347 lms_db_audio_free(plugin->audio_db);
348 if (plugin->video_db)
349 lms_db_video_free(plugin->video_db);
355 _close(struct plugin *plugin)
361 API struct lms_plugin *
362 lms_plugin_open(void)
364 struct plugin *plugin;
366 plugin = (struct plugin *)malloc(sizeof(*plugin));
367 plugin->plugin.name = _name;
368 plugin->plugin.match = (lms_plugin_match_fn_t)_match;
369 plugin->plugin.parse = (lms_plugin_parse_fn_t)_parse;
370 plugin->plugin.close = (lms_plugin_close_fn_t)_close;
371 plugin->plugin.setup = (lms_plugin_setup_fn_t)_setup;
372 plugin->plugin.start = (lms_plugin_start_fn_t)_start;
373 plugin->plugin.finish = (lms_plugin_finish_fn_t)_finish;
375 return (struct lms_plugin *)plugin;