wave: provide dlna information.
[platform/upstream/lightmediascanner.git] / src / plugins / wave / wave.c
1 /**
2  * Copyright (C) 2013  Intel Corporation. All rights reserved.
3  *
4  * This library 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.1 of
7  * the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
17  * 02110-1301 USA
18  *
19  * @author Lucas De Marchi <lucas.demarchi@intel.com>
20  */
21
22 /**
23  * @brief
24  *
25  * wave file parser.
26  *
27  * References:
28  *   Format specification:
29  *     http://www-mmsp.ece.mcgill.ca/documents/AudioFormats/WAVE/WAVE.html
30  *     http://www.sonicspot.com/guide/wavefiles.html
31  *   INFO tags:
32  *     http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/RIFF.html#Info
33  *     http://www.aelius.com/njh/wavemetatools/bsiwave_tag_map.html
34  */
35
36 #include <lightmediascanner_plugin.h>
37 #include <lightmediascanner_db.h>
38 #include <lightmediascanner_charset_conv.h>
39 #include <shared/util.h>
40
41 #include <sys/types.h>
42 #include <sys/stat.h>
43 #include <fcntl.h>
44 #include <errno.h>
45 #include <stdlib.h>
46 #include <stdio.h>
47 #include <string.h>
48 #include <unistd.h>
49
50 #define DECL_STR(cname, str)                                            \
51     static const struct lms_string_size cname = LMS_STATIC_STRING_SIZE(str)
52
53 DECL_STR(_dlna_lpcm, "LPCM");
54
55 DECL_STR(_dlna_mime_44_mono, "audio/L16;rate=44100;channels=1");
56 DECL_STR(_dlna_mime_44_stereo, "audio/L16;rate=44100;channels=2");
57 DECL_STR(_dlna_mime_48_mono, "audio/L16;rate=48000;channels=1");
58 DECL_STR(_dlna_mime_48_stereo, "audio/L16;rate=48000;channels=2");
59 #undef DECL_STR
60
61 static void
62 _fill_dlna_profile(struct lms_audio_info *info)
63 {
64     if (info->channels == 1 && info->sampling_rate == 44100) {
65         info->dlna_profile = _dlna_lpcm;
66         info->dlna_mime = _dlna_mime_44_mono;
67     } else if (info->channels == 2 && info->sampling_rate == 44100) {
68         info->dlna_profile = _dlna_lpcm;
69         info->dlna_mime = _dlna_mime_44_stereo;
70     } else if (info->channels == 1 && info->sampling_rate == 48000) {
71         info->dlna_profile = _dlna_lpcm;
72         info->dlna_mime = _dlna_mime_48_mono;
73     } else if (info->channels == 2 && info->sampling_rate == 48000) {
74         info->dlna_profile = _dlna_lpcm;
75         info->dlna_mime = _dlna_mime_48_stereo;
76     }
77 }
78
79 static const char _name[] = "wave";
80 static const struct lms_string_size _exts[] = {
81     LMS_STATIC_STRING_SIZE(".wav"),
82     LMS_STATIC_STRING_SIZE(".wave"),
83 };
84 static const char *_cats[] = {
85     "audio",
86     NULL
87 };
88 static const char *_authors[] = {
89     "Lucas De Marchi",
90     NULL
91 };
92 static const struct lms_string_size _container = LMS_STATIC_STRING_SIZE("wave");
93
94 struct plugin {
95     struct lms_plugin plugin;
96     lms_db_audio_t *audio_db;
97 };
98
99 static void *
100 _match(struct plugin *p, const char *path, int len, int base)
101 {
102     long i;
103
104     i = lms_which_extension(path, len, _exts, LMS_ARRAY_SIZE(_exts));
105     if (i < 0)
106         return NULL;
107     else
108         return (void*)(i + 1);
109 }
110
111 static int
112 _parse_info(int fd, struct lms_audio_info *info)
113 {
114     struct hdr {
115         uint8_t id[4];
116         uint32_t size;
117         uint8_t infoid[4];
118     } __attribute__((packed)) hdr;
119     long maxsize = 0;
120
121     /* Search the LIST INFO chunk */
122     do {
123         uint32_t size;
124
125         if (read(fd, &hdr, sizeof(hdr)) != sizeof(hdr))
126             goto done;
127
128         size = get_le32(&hdr.size) - sizeof(hdr.infoid);
129
130         if (memcmp(hdr.id, "LIST", 4) == 0
131             && memcmp(hdr.infoid, "INFO", 4) == 0) {
132             maxsize = size;
133             break;
134         } else if ((size & 0x1) && memcmp(hdr.id, "data", 4) == 0)
135             /* 'data' chunk has a 1-byte padding if size is odd */
136             size++;
137
138         lseek(fd, size, SEEK_CUR);
139     } while (1);
140
141     while (maxsize > 8) {
142         uint8_t chunkid[4];
143         uint32_t size;
144         struct lms_string_size *str;
145
146         if (read(fd, chunkid, sizeof(chunkid)) != sizeof(chunkid)
147             || read(fd, &size, sizeof(size)) != sizeof(size))
148             break;
149
150         size = le32toh(size);
151
152         if (memcmp(chunkid, "INAM", 4) == 0)
153             str = &info->title;
154         else if (memcmp(chunkid, "IART", 4) == 0)
155             str = &info->artist;
156         else if (memcmp(chunkid, "IPRD", 4) == 0)
157             str = &info->album;
158         else if (memcmp(chunkid, "IGNR", 4) == 0)
159             str = &info->genre;
160         else {
161             lseek(fd, size, SEEK_CUR);
162             maxsize -= size;
163             goto next_field;
164         }
165
166         str->str = malloc(size + 1);
167         read(fd, str->str, size);
168         str->str[size] = '\0';
169         str->len = size;
170
171     next_field:
172         /* Ignore trailing '\0', even if they are not part of the previous
173          * size */
174         while (maxsize > 0 && read(fd, chunkid, 1) == 1) {
175             if (chunkid[0] != '\0') {
176                 lseek(fd, -1, SEEK_CUR);
177                 break;
178             }
179             maxsize--;
180         }
181     }
182
183 done:
184     return 0;
185 }
186
187 static int
188 _parse_fmt(int fd, struct lms_audio_info *info)
189 {
190     struct fmt {
191         uint8_t fmt[4];
192         uint32_t size;
193         uint16_t audio_format;
194         uint16_t n_channels;
195         uint32_t sample_rate;
196         uint32_t byte_rate;
197         uint16_t block_align;
198         uint16_t bps;
199     } __attribute__((packed)) fmt;
200     long remain;
201
202     if (read(fd, &fmt, sizeof(fmt)) != sizeof(fmt)
203         || memcmp(fmt.fmt, "fmt ", 4) != 0)
204         return -1;
205
206     info->channels = get_le16(&fmt.n_channels);
207     info->bitrate = get_le32(&fmt.byte_rate) * 8;
208     info->sampling_rate = get_le32(&fmt.sample_rate);
209
210     remain = get_le32(&fmt.size) - sizeof(fmt) +
211         offsetof(struct fmt, audio_format);
212     if (remain > 0)
213         lseek(fd, remain, SEEK_CUR);
214
215     return 0;
216 }
217
218 static int
219 _parse_wave(int fd, struct lms_audio_info *info)
220 {
221     struct hdr {
222         uint8_t riff[4];
223         uint32_t size;
224         uint8_t wave[4];
225     } __attribute__((packed)) hdr;
226
227     if (read(fd, &hdr, sizeof(hdr)) != sizeof(hdr)
228         || memcmp(hdr.riff, "RIFF", 4) != 0
229         || memcmp(hdr.wave, "WAVE", 4) != 0
230         || _parse_fmt(fd, info) < 0)
231         return -1;
232
233     info->container = _container;
234
235     return 0;
236 }
237
238 static int
239 _parse(struct plugin *plugin, struct lms_context *ctxt,
240        const struct lms_file_info *finfo, void *match)
241 {
242     struct lms_audio_info info = { };
243     int r, fd;
244
245     fd = open(finfo->path, O_RDONLY);
246     if (fd < 0)
247         return -errno;
248
249     r = _parse_wave(fd, &info);
250     if (r < 0)
251         goto done;
252
253     /* Ignore errors, waves likely don't have any additional information */
254     _parse_info(fd, &info);
255
256     if (!info.title.str)
257         info.title = str_extract_name_from_path(finfo->path, finfo->path_len,
258                                                 finfo->base,
259                                                 &_exts[((long) match) - 1],
260                                                 ctxt->cs_conv);
261
262     info.id = finfo->id;
263
264     _fill_dlna_profile(&info);
265
266     r = lms_db_audio_add(plugin->audio_db, &info);
267
268 done:
269     posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
270     close(fd);
271
272     free(info.title.str);
273     free(info.artist.str);
274     free(info.album.str);
275     free(info.genre.str);
276
277     return r;
278 }
279
280 static int
281 _setup(struct plugin *plugin,  struct lms_context *ctxt)
282 {
283     plugin->audio_db = lms_db_audio_new(ctxt->db);
284     if (!plugin->audio_db)
285         return -1;
286
287     return 0;
288 }
289
290 static int
291 _start(struct plugin *plugin, struct lms_context *ctxt)
292 {
293
294     return lms_db_audio_start(plugin->audio_db);
295 }
296
297 static int
298 _finish(struct plugin *plugin, struct lms_context *ctxt)
299 {
300     if (plugin->audio_db)
301         lms_db_audio_free(plugin->audio_db);
302
303     return 0;
304 }
305
306 static int
307 _close(struct plugin *plugin)
308 {
309     free(plugin);
310     return 0;
311 }
312
313 API struct lms_plugin *
314 lms_plugin_open(void)
315 {
316     struct lms_plugin *plugin;
317
318     plugin = (struct lms_plugin *) malloc(sizeof(struct plugin));
319     plugin->name = _name;
320     plugin->match = (lms_plugin_match_fn_t) _match;
321     plugin->parse = (lms_plugin_parse_fn_t) _parse;
322     plugin->close = (lms_plugin_close_fn_t) _close;
323     plugin->setup = (lms_plugin_setup_fn_t) _setup;
324     plugin->start = (lms_plugin_start_fn_t) _start;
325     plugin->finish = (lms_plugin_finish_fn_t) _finish;
326
327     return plugin;
328 }
329
330 API const struct lms_plugin_info *
331 lms_plugin_info(void)
332 {
333     static struct lms_plugin_info info = {
334         _name,
335         _cats,
336         "Wave files",
337         PACKAGE_VERSION,
338         _authors,
339         "http://01.org"
340     };
341
342     return &info;
343 }