plugins/mp4: Add tracks information
[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 static const char _name[] = "mp4";
57 static const struct lms_string_size _exts[] = {
58     LMS_STATIC_STRING_SIZE(".mp4"),
59     LMS_STATIC_STRING_SIZE(".m4a"),
60     LMS_STATIC_STRING_SIZE(".m4v"),
61     LMS_STATIC_STRING_SIZE(".mov"),
62     LMS_STATIC_STRING_SIZE(".qt"),
63     LMS_STATIC_STRING_SIZE(".3gp")
64 };
65 static const char *_cats[] = {
66     "multimedia",
67     "audio",
68     "video",
69     NULL
70 };
71 static const char *_authors[] = {
72     "Andre Moreira Magalhaes",
73     NULL
74 };
75
76 static void *
77 _match(struct plugin *p, const char *path, int len, int base)
78 {
79     long i;
80
81     i = lms_which_extension(path, len, _exts, LMS_ARRAY_SIZE(_exts));
82     if (i < 0)
83       return NULL;
84     else
85       return (void*)(i + 1);
86 }
87
88 #ifdef HAVE_MP4V2_2_0_API
89 static int
90 _parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
91 {
92     struct mp4_info info = { };
93     struct lms_audio_info audio_info = { };
94     struct lms_video_info video_info = { };
95     int r, stream_type = LMS_STREAM_TYPE_AUDIO;
96     MP4FileHandle mp4_fh;
97     u_int32_t num_tracks, i;
98     const MP4Tags *tags;
99
100     mp4_fh = MP4Read(finfo->path);
101     if (mp4_fh == MP4_INVALID_FILE_HANDLE) {
102         fprintf(stderr, "ERROR: cannot read mp4 file %s\n", finfo->path);
103         return -1;
104     }
105
106     tags = MP4TagsAlloc();
107     if (!tags)
108         return -1;
109
110     if (!MP4TagsFetch(tags, mp4_fh)) {
111         r = -1;
112         goto fail;
113     }
114
115 #define STR_FIELD_FROM_TAG(_tags_field, _field)                         \
116     do {                                                                \
117         if (_tags_field) {                                              \
118             _field.len = strlen(_tags_field);                           \
119             _field.str = malloc(_field.len);                            \
120             memcpy(_field.str, _tags_field, _field.len + 1);            \
121         }                                                               \
122     } while (0)
123
124     STR_FIELD_FROM_TAG(tags->name, info.title);
125     STR_FIELD_FROM_TAG(tags->artist, info.artist);
126
127     /* check if the file contains a video track */
128     num_tracks = MP4GetNumberOfTracks(mp4_fh, MP4_VIDEO_TRACK_TYPE, 0);
129     if (num_tracks > 0)
130         stream_type = LMS_STREAM_TYPE_VIDEO;
131
132     info.length = MP4GetDuration(mp4_fh) /
133         MP4GetTimeScale(mp4_fh) ?: 1;
134
135     if (stream_type == LMS_STREAM_TYPE_AUDIO) {
136         MP4TrackId id;
137
138         STR_FIELD_FROM_TAG(tags->album, info.album);
139         STR_FIELD_FROM_TAG(tags->genre, info.genre);
140         if (tags->track)
141             info.trackno = tags->track->index;
142
143         id = MP4FindTrackId(mp4_fh, 0, MP4_AUDIO_TRACK_TYPE, 0);
144         audio_info.bitrate = MP4GetTrackBitRate(mp4_fh, id);
145         audio_info.channels = MP4GetTrackAudioChannels(mp4_fh, id);
146         audio_info.sampling_rate = MP4GetTrackTimeScale(mp4_fh, id);
147         audio_info.length = info.length;
148     } else {
149         num_tracks = MP4GetNumberOfTracks(mp4_fh, NULL, 0);
150         for (i = 0; i < num_tracks; i++) {
151             MP4TrackId id = MP4FindTrackId(mp4_fh, i, NULL, 0);
152             const char *type = MP4GetTrackType(mp4_fh, id);
153             enum lms_stream_type lmstype;
154             struct lms_stream *s;
155
156             if (strcmp(type, MP4_AUDIO_TRACK_TYPE) == 0)
157                 lmstype = LMS_STREAM_TYPE_AUDIO;
158             else if (strcmp(type, MP4_VIDEO_TRACK_TYPE) == 0)
159                 lmstype = LMS_STREAM_TYPE_VIDEO;
160             else
161                 continue;
162
163             s = calloc(1, sizeof(*s));
164             s->type = lmstype;
165             s->stream_id = id;
166
167             if (lmstype == LMS_STREAM_TYPE_AUDIO) {
168                 s->audio.channels = MP4GetTrackAudioChannels(mp4_fh, id);
169                 s->audio.bitrate = MP4GetTrackBitRate(mp4_fh, id);
170             } else if (lmstype == LMS_STREAM_TYPE_VIDEO) {
171                 s->video.width = MP4GetTrackVideoWidth(mp4_fh, id);
172                 s->video.height = MP4GetTrackVideoHeight(mp4_fh, id);
173                 s->video.framerate = MP4GetTrackVideoFrameRate(mp4_fh, id);
174             }
175
176             s->next = video_info.streams;
177             video_info.streams = s;
178         }
179         video_info.length = info.length;
180     }
181 #undef STR_FIELD_FROM_TAG
182
183     lms_string_size_strip_and_free(&info.title);
184     lms_string_size_strip_and_free(&info.artist);
185     lms_string_size_strip_and_free(&info.album);
186     lms_string_size_strip_and_free(&info.genre);
187
188     if (!info.title.str)
189         info.title = str_extract_name_from_path(finfo->path, finfo->path_len,
190                                                 finfo->base,
191                                                 &_exts[((long) match) - 1],
192                                                 NULL);
193     if (info.title.str)
194         lms_charset_conv(ctxt->cs_conv, &info.title.str, &info.title.len);
195     if (info.artist.str)
196         lms_charset_conv(ctxt->cs_conv, &info.artist.str, &info.artist.len);
197     if (info.album.str)
198         lms_charset_conv(ctxt->cs_conv, &info.album.str, &info.album.len);
199     if (info.genre.str)
200         lms_charset_conv(ctxt->cs_conv, &info.genre.str, &info.genre.len);
201
202     if (stream_type == LMS_STREAM_TYPE_AUDIO) {
203         audio_info.id = finfo->id;
204         audio_info.title = info.title;
205         audio_info.artist = info.artist;
206         audio_info.album = info.album;
207         audio_info.genre = info.genre;
208         audio_info.trackno = info.trackno;
209         r = lms_db_audio_add(plugin->audio_db, &audio_info);
210     } else {
211         video_info.id = finfo->id;
212         video_info.title = info.title;
213         video_info.artist = info.artist;
214         r = lms_db_video_add(plugin->video_db, &video_info);
215     }
216
217 fail:
218     MP4TagsFree(tags);
219
220     free(info.title.str);
221     free(info.artist.str);
222     free(info.album.str);
223     free(info.genre.str);
224
225     while (video_info.streams) {
226         struct lms_stream *s = video_info.streams;
227         video_info.streams = s->next;
228         free(s);
229     }
230
231     MP4Close(mp4_fh, 0);
232
233     return r;
234 }
235 #else
236 static int
237 _parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
238 {
239     struct mp4_info info = { };
240     struct lms_audio_info audio_info = { };
241     struct lms_video_info video_info = { };
242     int r, stream_type = LMS_STREAM_TYPE_AUDIO;
243     MP4FileHandle mp4_fh;
244     u_int32_t num_tracks;
245
246     mp4_fh = MP4Read(finfo->path, 0);
247     if (mp4_fh == MP4_INVALID_FILE_HANDLE) {
248         fprintf(stderr, "ERROR: cannot read mp4 file %s\n", finfo->path);
249         return -1;
250     }
251
252     /* check if the file contains a video track */
253     num_tracks = MP4GetNumberOfTracks(mp4_fh, MP4_VIDEO_TRACK_TYPE, 0);
254     if (num_tracks > 0)
255         stream_type = LMS_STREAM_TYPE_VIDEO;
256
257     MP4GetMetadataName(mp4_fh, &info.title.str);
258     if (info.title.str)
259         info.title.len = strlen(info.title.str);
260     MP4GetMetadataArtist(mp4_fh, &info.artist.str);
261     if (info.artist.str)
262         info.artist.len = strlen(info.artist.str);
263
264     if (stream_type == LMS_STREAM_TYPE_AUDIO) {
265         u_int16_t total_tracks;
266
267         MP4GetMetadataAlbum(mp4_fh, &info.album.str);
268         if (info.album.str)
269             info.album.len = strlen(info.album.str);
270         MP4GetMetadataGenre(mp4_fh, &info.genre.str);
271         if (info.genre.str)
272             info.genre.len = strlen(info.genre.str);
273
274         MP4GetMetadataTrack(mp4_fh, &info.trackno, &total_tracks);
275     }
276
277     lms_string_size_strip_and_free(&info.title);
278     lms_string_size_strip_and_free(&info.artist);
279     lms_string_size_strip_and_free(&info.album);
280     lms_string_size_strip_and_free(&info.genre);
281
282     if (!info.title.str)
283         info.title = str_extract_name_from_path(finfo->path, finfo->path_len,
284                                                 finfo->base,
285                                                 &_exts[((long) match) - 1],
286                                                 NULL);
287     if (info.title.str)
288         lms_charset_conv(ctxt->cs_conv, &info.title.str, &info.title.len);
289
290     if (info.artist.str)
291         lms_charset_conv(ctxt->cs_conv, &info.artist.str, &info.artist.len);
292     if (info.album.str)
293         lms_charset_conv(ctxt->cs_conv, &info.album.str, &info.album.len);
294     if (info.genre.str)
295         lms_charset_conv(ctxt->cs_conv, &info.genre.str, &info.genre.len);
296
297 #if 0
298     fprintf(stderr, "file %s info\n", finfo->path);
299     fprintf(stderr, "\ttitle='%s'\n", info.title.str);
300     fprintf(stderr, "\tartist='%s'\n", info.artist.str);
301     fprintf(stderr, "\talbum='%s'\n", info.album.str);
302     fprintf(stderr, "\tgenre='%s'\n", info.genre.str);
303 #endif
304
305     if (stream_type == LMS_STREAM_TYPE_AUDIO) {
306         audio_info.id = finfo->id;
307         audio_info.title = info.title;
308         audio_info.artist = info.artist;
309         audio_info.album = info.album;
310         audio_info.genre = info.genre;
311         audio_info.trackno = info.trackno;
312         r = lms_db_audio_add(plugin->audio_db, &audio_info);
313     }
314     else {
315         video_info.id = finfo->id;
316         video_info.title = info.title;
317         video_info.artist = info.artist;
318         r = lms_db_video_add(plugin->video_db, &video_info);
319     }
320
321     MP4Close(mp4_fh);
322
323     free(info.title.str);
324     free(info.artist.str);
325     free(info.album.str);
326     free(info.genre.str);
327
328     return r;
329 }
330 #endif
331
332 static int
333 _setup(struct plugin *plugin, struct lms_context *ctxt)
334 {
335     plugin->audio_db = lms_db_audio_new(ctxt->db);
336     if (!plugin->audio_db)
337         return -1;
338     plugin->video_db = lms_db_video_new(ctxt->db);
339     if (!plugin->video_db)
340         return -1;
341
342     return 0;
343 }
344
345 static int
346 _start(struct plugin *plugin, struct lms_context *ctxt)
347 {
348     int r;
349     r = lms_db_audio_start(plugin->audio_db);
350     r |= lms_db_video_start(plugin->video_db);
351     return r;
352 }
353
354 static int
355 _finish(struct plugin *plugin, struct lms_context *ctxt)
356 {
357     if (plugin->audio_db)
358         lms_db_audio_free(plugin->audio_db);
359     if (plugin->video_db)
360         lms_db_video_free(plugin->video_db);
361
362     return 0;
363 }
364
365 static int
366 _close(struct plugin *plugin)
367 {
368     free(plugin);
369     return 0;
370 }
371
372 API struct lms_plugin *
373 lms_plugin_open(void)
374 {
375     struct plugin *plugin;
376
377     plugin = (struct plugin *)malloc(sizeof(*plugin));
378     plugin->plugin.name = _name;
379     plugin->plugin.match = (lms_plugin_match_fn_t)_match;
380     plugin->plugin.parse = (lms_plugin_parse_fn_t)_parse;
381     plugin->plugin.close = (lms_plugin_close_fn_t)_close;
382     plugin->plugin.setup = (lms_plugin_setup_fn_t)_setup;
383     plugin->plugin.start = (lms_plugin_start_fn_t)_start;
384     plugin->plugin.finish = (lms_plugin_finish_fn_t)_finish;
385
386     return (struct lms_plugin *)plugin;
387 }
388
389 API const struct lms_plugin_info *
390 lms_plugin_info(void)
391 {
392     static struct lms_plugin_info info = {
393         _name,
394         _cats,
395         "MP4 files (MP4, M4A, MOV, QT, 3GP)",
396         PV,
397         _authors,
398         "http://lms.garage.maemo.org"
399     };
400
401     return &info;
402 }