plugins/mp4: Add audio codec
[platform/upstream/lightmediascanner.git] / src / plugins / mp4 / mp4.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  * mp4 file parser.
25  */
26
27 static const char PV[] = PACKAGE_VERSION; /* mp4.h screws PACKAGE_VERSION */
28
29 #include <lightmediascanner_plugin.h>
30 #include <lightmediascanner_db.h>
31 #include <shared/util.h>
32
33 #ifdef HAVE_MP4V2
34 #include <mp4v2/mp4v2.h>
35 #else
36 #include <mp4.h>
37 #endif
38 #include <string.h>
39 #include <stdlib.h>
40
41 struct mp4_info {
42     struct lms_string_size title;
43     struct lms_string_size artist;
44     struct lms_string_size album;
45     struct lms_string_size genre;
46     uint16_t trackno;
47     uint64_t length;
48 };
49
50 struct plugin {
51     struct lms_plugin plugin;
52     lms_db_audio_t *audio_db;
53     lms_db_video_t *video_db;
54 };
55
56 #define DECL_STR(cname, str)                                            \
57     static const struct lms_string_size cname = LMS_STATIC_STRING_SIZE(str)
58
59 DECL_STR(_codec_audio_mpeg4aac_main, "mpeg4aac-main");
60 DECL_STR(_codec_audio_mpeg4aac_lc, "mpeg4aac-lc");
61 DECL_STR(_codec_audio_mpeg4aac_ssr, "mpeg4aac-ssr");
62 DECL_STR(_codec_audio_mpeg4aac_ltp, "mpeg4aac-ltp");
63 DECL_STR(_codec_audio_mpeg4aac_he, "mpeg4aac-he");
64 DECL_STR(_codec_audio_mpeg4aac_scalable, "mpeg4aac-scalable");
65 DECL_STR(_codec_audio_mpeg4_twinvq, "mpeg4twinvq");
66 DECL_STR(_codec_audio_mpeg4_celp, "mpeg4celp");
67 DECL_STR(_codec_audio_mpeg4_hvxc, "mpeg4hvxc");
68 /* NULL */
69 DECL_STR(_codec_audio_mpeg4_tssi, "mpeg4ttsi");
70 DECL_STR(_codec_audio_mpeg4_main_synthetic,"mpeg4main-synthetic");
71 DECL_STR(_codec_audio_mpeg4_wavetable_syn, "mpeg4wavetable-syn");
72 DECL_STR(_codec_audio_mpeg4_general_midi, "mpeg4general-midi");
73 DECL_STR(_codec_audio_mpeg4_algo_syn_and_audio_fx, "mpeg4algo-syn-and-audio-fx");
74 DECL_STR(_codec_audio_mpeg4_er_aac_lc, "mpeg4er-aac-lc");
75 /* NULL */
76 DECL_STR(_codec_audio_mpeg4_er_aac_ltp, "mpeg4er-aac-ltp");
77 DECL_STR(_codec_audio_mpeg4_er_aac_scalable, "mpeg4er-aac-scalable");
78 DECL_STR(_codec_audio_mpeg4_er_twinvq, "mpeg4er-twinvq");
79 DECL_STR(_codec_audio_mpeg4_er_bsac, "mpeg4er-bsac");
80 DECL_STR(_codec_audio_mpeg4_er_acc_ld, "mpeg4er-acc-ld");
81 DECL_STR(_codec_audio_mpeg4_er_celp, "mpeg4er-celp");
82 DECL_STR(_codec_audio_mpeg4_er_hvxc, "mpeg4er-hvxc");
83 DECL_STR(_codec_audio_mpeg4_er_hiln, "mpeg4er-hiln");
84 DECL_STR(_codec_audio_mpeg4_er_parametric, "mpeg4er-parametric");
85 DECL_STR(_codec_audio_mpeg4_ssc, "mpeg4ssc");
86 DECL_STR(_codec_audio_mpeg4_ps, "mpeg4ps");
87 DECL_STR(_codec_audio_mpeg4_mpeg_surround, "mpeg4mpeg-surround");
88 /* NULL */
89 DECL_STR(_codec_audio_mpeg4_layer1, "mpeg4layer1");
90 DECL_STR(_codec_audio_mpeg4_layer2, "mpeg4layer2");
91 DECL_STR(_codec_audio_mpeg4_layer3, "mpeg4layer3");
92 DECL_STR(_codec_audio_mpeg4_dst, "mpeg4dst");
93 DECL_STR(_codec_audio_mpeg4_audio_lossless, "mpeg4audio-lossless");
94 DECL_STR(_codec_audio_mpeg4_sls, "mpeg4sls");
95 DECL_STR(_codec_audio_mpeg4_sls_non_core, "mpeg4sls-non-core");
96
97 /* --- */
98 DECL_STR(_codec_audio_amr, "amr");
99 DECL_STR(_codec_audio_amr_wb, "amr-wb");
100 #undef DECL_STR
101
102 static const struct lms_string_size *_audio_codecs[] = {
103     &_codec_audio_mpeg4aac_main,
104     &_codec_audio_mpeg4aac_lc,
105     &_codec_audio_mpeg4aac_ssr,
106     &_codec_audio_mpeg4aac_ltp,
107     &_codec_audio_mpeg4aac_he,
108     &_codec_audio_mpeg4aac_scalable,
109     &_codec_audio_mpeg4_twinvq,
110     &_codec_audio_mpeg4_celp,
111     &_codec_audio_mpeg4_hvxc,
112     NULL,
113     &_codec_audio_mpeg4_tssi,
114     &_codec_audio_mpeg4_main_synthetic,
115     &_codec_audio_mpeg4_wavetable_syn,
116     &_codec_audio_mpeg4_general_midi,
117     &_codec_audio_mpeg4_algo_syn_and_audio_fx,
118     &_codec_audio_mpeg4_er_aac_lc,
119     NULL,
120     &_codec_audio_mpeg4_er_aac_ltp,
121     &_codec_audio_mpeg4_er_aac_scalable,
122     &_codec_audio_mpeg4_er_twinvq,
123     &_codec_audio_mpeg4_er_bsac,
124     &_codec_audio_mpeg4_er_acc_ld,
125     &_codec_audio_mpeg4_er_celp,
126     &_codec_audio_mpeg4_er_hvxc,
127     &_codec_audio_mpeg4_er_hiln,
128     &_codec_audio_mpeg4_er_parametric,
129     &_codec_audio_mpeg4_ssc,
130     &_codec_audio_mpeg4_ps,
131     &_codec_audio_mpeg4_mpeg_surround,
132     NULL,
133     &_codec_audio_mpeg4_layer1,
134     &_codec_audio_mpeg4_layer2,
135     &_codec_audio_mpeg4_layer3,
136     &_codec_audio_mpeg4_dst,
137     &_codec_audio_mpeg4_audio_lossless,
138     &_codec_audio_mpeg4_sls,
139     &_codec_audio_mpeg4_sls_non_core,
140 };
141 #define N_AUDIO_CODECS (sizeof(_audio_codecs) / sizeof(_audio_codecs[0]))
142
143 static const char _name[] = "mp4";
144 static const struct lms_string_size _exts[] = {
145     LMS_STATIC_STRING_SIZE(".mp4"),
146     LMS_STATIC_STRING_SIZE(".m4a"),
147     LMS_STATIC_STRING_SIZE(".m4v"),
148     LMS_STATIC_STRING_SIZE(".mov"),
149     LMS_STATIC_STRING_SIZE(".qt"),
150     LMS_STATIC_STRING_SIZE(".3gp")
151 };
152 static const char *_cats[] = {
153     "multimedia",
154     "audio",
155     "video",
156     NULL
157 };
158 static const char *_authors[] = {
159     "Andre Moreira Magalhaes",
160     NULL
161 };
162
163 static void *
164 _match(struct plugin *p, const char *path, int len, int base)
165 {
166     long i;
167
168     i = lms_which_extension(path, len, _exts, LMS_ARRAY_SIZE(_exts));
169     if (i < 0)
170       return NULL;
171     else
172       return (void*)(i + 1);
173 }
174
175 #ifdef HAVE_MP4V2_2_0_API
176 static struct lms_string_size
177 _get_audio_codec(MP4FileHandle mp4_fh, MP4TrackId id)
178 {
179     const char *data_name = MP4GetTrackMediaDataName(mp4_fh, id);
180     struct lms_string_size nullret = { };
181     uint8_t type;
182
183     if (!data_name)
184         return nullret;
185     if (strcasecmp(data_name, "samr") == 0)
186         return _codec_audio_amr;
187     if (strcasecmp(data_name, "sawb") == 0)
188         return _codec_audio_amr_wb;
189     if ((strcasecmp(data_name, "mp4a") != 0) ||
190         (type = MP4GetTrackEsdsObjectTypeId(mp4_fh, id)) != MP4_MPEG4_AUDIO_TYPE ||
191         (type = MP4GetTrackAudioMpeg4Type(mp4_fh, id)) == 0 ||
192         (type > N_AUDIO_CODECS) ||
193         (_audio_codecs[type - 1] == NULL))
194         return nullret;
195
196     return *_audio_codecs[type - 1];
197 }
198
199 static int
200 _parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
201 {
202     struct mp4_info info = { };
203     struct lms_audio_info audio_info = { };
204     struct lms_video_info video_info = { };
205     int r, stream_type = LMS_STREAM_TYPE_AUDIO;
206     MP4FileHandle mp4_fh;
207     u_int32_t num_tracks, i;
208     const MP4Tags *tags;
209
210     mp4_fh = MP4Read(finfo->path);
211     if (mp4_fh == MP4_INVALID_FILE_HANDLE) {
212         fprintf(stderr, "ERROR: cannot read mp4 file %s\n", finfo->path);
213         return -1;
214     }
215
216     tags = MP4TagsAlloc();
217     if (!tags)
218         return -1;
219
220     if (!MP4TagsFetch(tags, mp4_fh)) {
221         r = -1;
222         goto fail;
223     }
224
225 #define STR_FIELD_FROM_TAG(_tags_field, _field)                         \
226     do {                                                                \
227         if (_tags_field) {                                              \
228             _field.len = strlen(_tags_field);                           \
229             _field.str = malloc(_field.len);                            \
230             memcpy(_field.str, _tags_field, _field.len + 1);            \
231         }                                                               \
232     } while (0)
233
234     STR_FIELD_FROM_TAG(tags->name, info.title);
235     STR_FIELD_FROM_TAG(tags->artist, info.artist);
236
237     /* check if the file contains a video track */
238     num_tracks = MP4GetNumberOfTracks(mp4_fh, MP4_VIDEO_TRACK_TYPE, 0);
239     if (num_tracks > 0)
240         stream_type = LMS_STREAM_TYPE_VIDEO;
241
242     info.length = MP4GetDuration(mp4_fh) /
243         MP4GetTimeScale(mp4_fh) ?: 1;
244
245     if (stream_type == LMS_STREAM_TYPE_AUDIO) {
246         MP4TrackId id;
247
248         STR_FIELD_FROM_TAG(tags->album, info.album);
249         STR_FIELD_FROM_TAG(tags->genre, info.genre);
250         if (tags->track)
251             info.trackno = tags->track->index;
252
253         id = MP4FindTrackId(mp4_fh, 0, MP4_AUDIO_TRACK_TYPE, 0);
254         audio_info.bitrate = MP4GetTrackBitRate(mp4_fh, id);
255         audio_info.channels = MP4GetTrackAudioChannels(mp4_fh, id);
256         audio_info.sampling_rate = MP4GetTrackTimeScale(mp4_fh, id);
257         audio_info.length = info.length;
258         audio_info.codec = _get_audio_codec(mp4_fh, id);
259     } else {
260         num_tracks = MP4GetNumberOfTracks(mp4_fh, NULL, 0);
261         for (i = 0; i < num_tracks; i++) {
262             MP4TrackId id = MP4FindTrackId(mp4_fh, i, NULL, 0);
263             const char *type = MP4GetTrackType(mp4_fh, id);
264             enum lms_stream_type lmstype;
265             struct lms_stream *s;
266
267             if (strcmp(type, MP4_AUDIO_TRACK_TYPE) == 0)
268                 lmstype = LMS_STREAM_TYPE_AUDIO;
269             else if (strcmp(type, MP4_VIDEO_TRACK_TYPE) == 0)
270                 lmstype = LMS_STREAM_TYPE_VIDEO;
271             else
272                 continue;
273
274             s = calloc(1, sizeof(*s));
275             s->type = lmstype;
276             s->stream_id = id;
277
278             if (lmstype == LMS_STREAM_TYPE_AUDIO) {
279                 s->audio.channels = MP4GetTrackAudioChannels(mp4_fh, id);
280                 s->audio.bitrate = MP4GetTrackBitRate(mp4_fh, id);
281                 s->codec = _get_audio_codec(mp4_fh, id);
282             } else if (lmstype == LMS_STREAM_TYPE_VIDEO) {
283                 s->video.width = MP4GetTrackVideoWidth(mp4_fh, id);
284                 s->video.height = MP4GetTrackVideoHeight(mp4_fh, id);
285                 s->video.framerate = MP4GetTrackVideoFrameRate(mp4_fh, id);
286             }
287
288             s->next = video_info.streams;
289             video_info.streams = s;
290         }
291         video_info.length = info.length;
292     }
293 #undef STR_FIELD_FROM_TAG
294
295     lms_string_size_strip_and_free(&info.title);
296     lms_string_size_strip_and_free(&info.artist);
297     lms_string_size_strip_and_free(&info.album);
298     lms_string_size_strip_and_free(&info.genre);
299
300     if (!info.title.str)
301         info.title = str_extract_name_from_path(finfo->path, finfo->path_len,
302                                                 finfo->base,
303                                                 &_exts[((long) match) - 1],
304                                                 NULL);
305     if (info.title.str)
306         lms_charset_conv(ctxt->cs_conv, &info.title.str, &info.title.len);
307     if (info.artist.str)
308         lms_charset_conv(ctxt->cs_conv, &info.artist.str, &info.artist.len);
309     if (info.album.str)
310         lms_charset_conv(ctxt->cs_conv, &info.album.str, &info.album.len);
311     if (info.genre.str)
312         lms_charset_conv(ctxt->cs_conv, &info.genre.str, &info.genre.len);
313
314     if (stream_type == LMS_STREAM_TYPE_AUDIO) {
315         audio_info.id = finfo->id;
316         audio_info.title = info.title;
317         audio_info.artist = info.artist;
318         audio_info.album = info.album;
319         audio_info.genre = info.genre;
320         audio_info.trackno = info.trackno;
321         r = lms_db_audio_add(plugin->audio_db, &audio_info);
322     } else {
323         video_info.id = finfo->id;
324         video_info.title = info.title;
325         video_info.artist = info.artist;
326         r = lms_db_video_add(plugin->video_db, &video_info);
327     }
328
329 fail:
330     MP4TagsFree(tags);
331
332     free(info.title.str);
333     free(info.artist.str);
334     free(info.album.str);
335     free(info.genre.str);
336
337     while (video_info.streams) {
338         struct lms_stream *s = video_info.streams;
339         video_info.streams = s->next;
340         free(s);
341     }
342
343     MP4Close(mp4_fh, 0);
344
345     return r;
346 }
347 #else
348 static int
349 _parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
350 {
351     struct mp4_info info = { };
352     struct lms_audio_info audio_info = { };
353     struct lms_video_info video_info = { };
354     int r, stream_type = LMS_STREAM_TYPE_AUDIO;
355     MP4FileHandle mp4_fh;
356     u_int32_t num_tracks;
357
358     mp4_fh = MP4Read(finfo->path, 0);
359     if (mp4_fh == MP4_INVALID_FILE_HANDLE) {
360         fprintf(stderr, "ERROR: cannot read mp4 file %s\n", finfo->path);
361         return -1;
362     }
363
364     /* check if the file contains a video track */
365     num_tracks = MP4GetNumberOfTracks(mp4_fh, MP4_VIDEO_TRACK_TYPE, 0);
366     if (num_tracks > 0)
367         stream_type = LMS_STREAM_TYPE_VIDEO;
368
369     MP4GetMetadataName(mp4_fh, &info.title.str);
370     if (info.title.str)
371         info.title.len = strlen(info.title.str);
372     MP4GetMetadataArtist(mp4_fh, &info.artist.str);
373     if (info.artist.str)
374         info.artist.len = strlen(info.artist.str);
375
376     if (stream_type == LMS_STREAM_TYPE_AUDIO) {
377         u_int16_t total_tracks;
378
379         MP4GetMetadataAlbum(mp4_fh, &info.album.str);
380         if (info.album.str)
381             info.album.len = strlen(info.album.str);
382         MP4GetMetadataGenre(mp4_fh, &info.genre.str);
383         if (info.genre.str)
384             info.genre.len = strlen(info.genre.str);
385
386         MP4GetMetadataTrack(mp4_fh, &info.trackno, &total_tracks);
387     }
388
389     lms_string_size_strip_and_free(&info.title);
390     lms_string_size_strip_and_free(&info.artist);
391     lms_string_size_strip_and_free(&info.album);
392     lms_string_size_strip_and_free(&info.genre);
393
394     if (!info.title.str)
395         info.title = str_extract_name_from_path(finfo->path, finfo->path_len,
396                                                 finfo->base,
397                                                 &_exts[((long) match) - 1],
398                                                 NULL);
399     if (info.title.str)
400         lms_charset_conv(ctxt->cs_conv, &info.title.str, &info.title.len);
401
402     if (info.artist.str)
403         lms_charset_conv(ctxt->cs_conv, &info.artist.str, &info.artist.len);
404     if (info.album.str)
405         lms_charset_conv(ctxt->cs_conv, &info.album.str, &info.album.len);
406     if (info.genre.str)
407         lms_charset_conv(ctxt->cs_conv, &info.genre.str, &info.genre.len);
408
409 #if 0
410     fprintf(stderr, "file %s info\n", finfo->path);
411     fprintf(stderr, "\ttitle='%s'\n", info.title.str);
412     fprintf(stderr, "\tartist='%s'\n", info.artist.str);
413     fprintf(stderr, "\talbum='%s'\n", info.album.str);
414     fprintf(stderr, "\tgenre='%s'\n", info.genre.str);
415 #endif
416
417     if (stream_type == LMS_STREAM_TYPE_AUDIO) {
418         audio_info.id = finfo->id;
419         audio_info.title = info.title;
420         audio_info.artist = info.artist;
421         audio_info.album = info.album;
422         audio_info.genre = info.genre;
423         audio_info.trackno = info.trackno;
424         r = lms_db_audio_add(plugin->audio_db, &audio_info);
425     }
426     else {
427         video_info.id = finfo->id;
428         video_info.title = info.title;
429         video_info.artist = info.artist;
430         r = lms_db_video_add(plugin->video_db, &video_info);
431     }
432
433     MP4Close(mp4_fh);
434
435     free(info.title.str);
436     free(info.artist.str);
437     free(info.album.str);
438     free(info.genre.str);
439
440     return r;
441 }
442 #endif
443
444 static int
445 _setup(struct plugin *plugin, struct lms_context *ctxt)
446 {
447     plugin->audio_db = lms_db_audio_new(ctxt->db);
448     if (!plugin->audio_db)
449         return -1;
450     plugin->video_db = lms_db_video_new(ctxt->db);
451     if (!plugin->video_db)
452         return -1;
453
454     return 0;
455 }
456
457 static int
458 _start(struct plugin *plugin, struct lms_context *ctxt)
459 {
460     int r;
461     r = lms_db_audio_start(plugin->audio_db);
462     r |= lms_db_video_start(plugin->video_db);
463     return r;
464 }
465
466 static int
467 _finish(struct plugin *plugin, struct lms_context *ctxt)
468 {
469     if (plugin->audio_db)
470         lms_db_audio_free(plugin->audio_db);
471     if (plugin->video_db)
472         lms_db_video_free(plugin->video_db);
473
474     return 0;
475 }
476
477 static int
478 _close(struct plugin *plugin)
479 {
480     free(plugin);
481     return 0;
482 }
483
484 API struct lms_plugin *
485 lms_plugin_open(void)
486 {
487     struct plugin *plugin;
488
489     plugin = (struct plugin *)malloc(sizeof(*plugin));
490     plugin->plugin.name = _name;
491     plugin->plugin.match = (lms_plugin_match_fn_t)_match;
492     plugin->plugin.parse = (lms_plugin_parse_fn_t)_parse;
493     plugin->plugin.close = (lms_plugin_close_fn_t)_close;
494     plugin->plugin.setup = (lms_plugin_setup_fn_t)_setup;
495     plugin->plugin.start = (lms_plugin_start_fn_t)_start;
496     plugin->plugin.finish = (lms_plugin_finish_fn_t)_finish;
497
498     return (struct lms_plugin *)plugin;
499 }
500
501 API const struct lms_plugin_info *
502 lms_plugin_info(void)
503 {
504     static struct lms_plugin_info info = {
505         _name,
506         _cats,
507         "MP4 files (MP4, M4A, MOV, QT, 3GP)",
508         PV,
509         _authors,
510         "http://lms.garage.maemo.org"
511     };
512
513     return &info;
514 }