OSDN Git Service

Updating codebase to OpenCORE 2.0. More documentation on the changes to follow.
[android-x86/external-opencore.git] / android / mediascanner.cpp
1 /*
2  * Copyright (C) 2008, Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
13  * express or implied.
14  * See the License for the specific language governing permissions
15  * and limitations under the License.
16  * -------------------------------------------------------------------
17  */
18
19 #include <media/mediascanner.h>
20 #include <stdio.h>
21
22
23 #include "pvlogger.h"
24 #include "pv_id3_parcom.h"
25 #include "oscl_string_containers.h"
26 #include "oscl_file_io.h"
27 #include "oscl_assert.h"
28 #include "oscl_lock_base.h"
29 #include "oscl_snprintf.h"
30 #include "oscl_string_utf8.h"
31 #include "pvmf_return_codes.h"
32 #include "pv_mime_string_utils.h"
33 #include "pv_id3_parcom_constants.h"
34 #include "oscl_utf8conv.h"
35 #include "imp3ff.h"
36 #include "impeg4file.h"
37 #include "autodetect.h"
38
39 // Ogg Vorbis includes
40 #include "ivorbiscodec.h"
41 #include "ivorbisfile.h"
42
43 // Sonivox includes
44 #include <libsonivox/eas.h>
45
46 // used for WMA support
47 #include "media/mediametadataretriever.h"
48
49 #include <media/thread_init.h>
50 #include <utils/string_array.h>
51
52 #define MAX_BUFF_SIZE   1024
53
54 #include <sys/types.h>
55 #include <sys/stat.h>
56 #include <unistd.h>
57 #include <dirent.h>
58 #include <errno.h>
59
60 #include "unicode/ucnv.h"
61 #include "unicode/ustring.h"
62
63 #undef LOG_TAG
64 #define LOG_TAG "MediaScanner"
65 #include "utils/Log.h"
66
67 #define MAX_STR_LEN    1000
68
69
70 namespace android {
71
72
73 MediaScanner::MediaScanner()
74     :   mLocale(NULL)
75 {
76 }
77
78 MediaScanner::~MediaScanner()
79 {
80     free(mLocale);
81 }
82
83 static PVMFStatus parseMP3(const char *filename, MediaScannerClient& client)
84 {
85     PVID3ParCom pvId3Param;
86     PVFile fileHandle;
87     Oscl_FileServer iFs;
88     uint32 duration;
89
90     if (iFs.Connect() != 0)
91     {
92         LOGE("iFs.Connect failed\n");
93         return PVMFFailure;
94     }
95
96     oscl_wchar output[MAX_BUFF_SIZE];
97     oscl_UTF8ToUnicode((const char *)filename, oscl_strlen((const char *)filename), (oscl_wchar *)output, MAX_BUFF_SIZE);
98     if (0 != fileHandle.Open((oscl_wchar *)output, Oscl_File::MODE_READ | Oscl_File::MODE_BINARY, iFs) )
99     {
100         LOGE("Could not open the input file for reading(Test: parse id3).\n");
101         return PVMFFailure;
102     }
103
104     fileHandle.Seek(0, Oscl_File::SEEKSET);
105     pvId3Param.ParseID3Tag(&fileHandle);
106     fileHandle.Close();
107     iFs.Close();
108
109     //Get the frames information from ID3 library
110     PvmiKvpSharedPtrVector framevector;
111     pvId3Param.GetID3Frames(framevector);
112
113     uint32 num_frames = framevector.size();
114
115     for (uint32 i = 0; i < num_frames;i++)
116     {
117         const char* key = framevector[i]->key;
118         bool validUtf8 = true;
119
120         // type should follow first semicolon
121         const char* type = strchr(key, ';') + 1;
122         if (type == 0) continue;
123
124         
125         const char* value = framevector[i]->value.pChar_value;
126
127         // KVP_VALTYPE_UTF8_CHAR check must be first, since KVP_VALTYPE_ISO88591_CHAR 
128         // is a substring of KVP_VALTYPE_UTF8_CHAR.
129         // Similarly, KVP_VALTYPE_UTF16BE_WCHAR must be checked before KVP_VALTYPE_UTF16_WCHAR
130         if (oscl_strncmp(type, KVP_VALTYPE_UTF8_CHAR, KVP_VALTYPE_UTF8_CHAR_LEN) == 0) {
131             // utf8 can be passed through directly
132             // but first validate to make sure it is legal utf8
133             uint32 valid_chars;
134             validUtf8 = oscl_str_is_valid_utf8((const uint8 *)value, valid_chars);
135             if (validUtf8 && !client.handleStringTag(key, value)) goto failure;
136         } 
137
138         // if the value is not valid utf8, then we will treat it as iso-8859-1 
139         // and our native encoding detection will try to figure out what it is
140         if (oscl_strncmp(type, KVP_VALTYPE_ISO88591_CHAR, KVP_VALTYPE_ISO88591_CHAR_LEN) == 0 
141                 || !validUtf8) {
142             // iso-8859-1
143             // convert to utf8
144             // worse case is 2x inflation
145             const unsigned char* src = (const unsigned char *)value;
146             char* temp = (char *)alloca(strlen(value) * 2 + 1);
147             if (temp) {
148                 char* dest = temp;
149                 unsigned int uch;
150                 while ((uch = *src++) != 0) {
151                     if (uch & 0x80) {
152                         *dest++ = (uch >> 6) | 0xc0;
153                         *dest++ = (uch & 0x3f) | 0x80;
154                     } else *dest++ = uch;
155                 }
156                 *dest = 0;
157                 if (!client.addStringTag(key, temp)) goto failure;           
158             }
159         } else if (oscl_strncmp(type, KVP_VALTYPE_UTF16BE_WCHAR, KVP_VALTYPE_UTF16BE_WCHAR_LEN) == 0 ||
160                 oscl_strncmp(type, KVP_VALTYPE_UTF16_WCHAR, KVP_VALTYPE_UTF16_WCHAR_LEN) == 0) {
161             // convert wchar to utf8
162             // the id3parcom library has already taken care of byteswapping
163             const oscl_wchar*  src = framevector[i]->value.pWChar_value;
164             int srcLen = oscl_strlen(src);
165             // worse case is 3 bytes per character, plus zero termination
166             int destLen = srcLen * 3 + 1;
167             char* dest = (char *)alloca(destLen);
168
169             if (oscl_UnicodeToUTF8(src, oscl_strlen(src), dest, destLen) > 0) {
170                 if (!client.addStringTag(key, dest)) goto failure;           
171             }                 
172         } else if (oscl_strncmp(type, KVP_VALTYPE_UINT32, KVP_VALTYPE_UINT32_LEN) == 0) {
173             char temp[20];
174             snprintf(temp, sizeof(temp), "%d", (int)framevector[i]->value.uint32_value);
175             if (!client.addStringTag(key, temp)) goto failure;
176         } else {
177             //LOGE("unknown tag type %s for key %s\n", type, key);
178         }
179     }
180
181     // extract non-ID3 properties below
182     {
183         OSCL_wHeapString<OsclMemAllocator> mp3filename(output);
184         MP3ErrorType    err;
185         IMpeg3File mp3File(mp3filename, err);
186         if (err != MP3_SUCCESS) {
187             LOGE("IMpeg3File constructor returned %d.\n", err);
188             return err;
189         }
190         err = mp3File.ParseMp3File();
191         if (err != MP3_SUCCESS) {
192             LOGE("IMpeg3File::ParseMp3File returned %d.\n", err);
193             return err;
194         }
195
196         char buffer[20];
197         duration = mp3File.GetDuration();
198         sprintf(buffer, "%d", duration);
199         if (!client.addStringTag("duration", buffer)) goto failure;
200     }
201
202     return PVMFSuccess;
203
204 failure:
205     return PVMFFailure;
206 }
207
208 static PVMFStatus reportM4ATags(IMpeg4File *mp4Input, MediaScannerClient& client)
209 {
210
211     OSCL_wHeapString<OsclMemAllocator> valuestring=NULL;
212     MP4FFParserOriginalCharEnc charType = ORIGINAL_CHAR_TYPE_UNKNOWN;
213     uint16 iLangCode=0;
214     uint64 duration;
215     uint32 timeScale;
216     uint16 trackNum;
217     uint16 totalTracks;
218     uint32 val;
219
220     char buffer[MAX_STR_LEN];
221
222     // Title
223     uint32 i = 0;
224     for (i = 0; i < mp4Input->getNumTitle(); ++i)
225     {
226         mp4Input->getTitle(i,valuestring,iLangCode,charType);
227         if (oscl_UnicodeToUTF8(valuestring.get_cstr(),valuestring.get_size(),
228             buffer,sizeof(buffer)) > 0)
229         {
230             if (!client.addStringTag("title", buffer)) goto failure;
231             break;
232         }
233     }
234
235     // Artist
236     for (i = 0; i < mp4Input->getNumArtist(); ++i)
237     {
238         mp4Input->getArtist(i,valuestring,iLangCode,charType);
239         if (oscl_UnicodeToUTF8(valuestring.get_cstr(),valuestring.get_size(),
240             buffer,sizeof(buffer)) > 0)
241         {
242             if (!client.addStringTag("artist", buffer)) goto failure; 
243             break;
244         }
245     }
246
247     // Album
248     for (i = 0; i < mp4Input->getNumAlbum(); ++i)
249     {
250         mp4Input->getAlbum(i,valuestring,iLangCode,charType);
251         if (oscl_UnicodeToUTF8(valuestring.get_cstr(),valuestring.get_size(),
252             buffer,sizeof(buffer)) > 0)
253         {
254             if (!client.addStringTag("album", buffer)) goto failure;
255             break;
256         }
257     }
258
259     // Year
260     val = 0;
261     for (i = 0; i < mp4Input->getNumYear(); ++i)
262     {
263         mp4Input->getYear(i,val);
264         sprintf(buffer, "%d", val);
265         if (buffer[0])
266         {
267             if (!client.addStringTag("year", buffer)) goto failure;
268             break;
269         }
270     }
271
272     // Writer/Composer
273     if (oscl_UnicodeToUTF8(mp4Input->getITunesWriter().get_cstr(),
274         mp4Input->getITunesWriter().get_size(),buffer,sizeof(buffer)) > 0)
275         if (!client.addStringTag("composer", buffer)) goto failure;
276
277     // Track Data
278     trackNum = mp4Input->getITunesThisTrackNo();
279     totalTracks = mp4Input->getITunesTotalTracks();
280     sprintf(buffer, "%d/%d", trackNum, totalTracks);
281     if (!client.addStringTag("tracknumber", buffer)) goto failure;
282
283     // Duration
284     duration = mp4Input->getMovieDuration();
285     timeScale =  mp4Input->getMovieTimescale();
286     // adjust duration to milliseconds if necessary
287     if (timeScale != 1000)
288         duration = (duration * 1000) / timeScale;
289     sprintf(buffer, "%lld", duration);
290     if (!client.addStringTag("duration", buffer)) goto failure;
291
292     // Genre
293     buffer[0] = 0;
294     for(i=0; i<mp4Input->getNumGenre(); i++)
295     {
296         mp4Input->getGenre(i,valuestring,iLangCode,charType);
297         if (oscl_UnicodeToUTF8(valuestring.get_cstr(),valuestring.get_size(), buffer,sizeof(buffer)) > 0)
298             break;
299     }
300     if (buffer[0]) {
301         if (!client.addStringTag("genre", buffer)) goto failure;
302     } else {
303         uint16 id = mp4Input->getITunesGnreID();
304         if (id > 0) {
305             sprintf(buffer, "(%d)", id - 1);
306             if (!client.addStringTag("genre", buffer)) goto failure;
307         }
308     }
309
310     return PVMFSuccess;
311
312 failure:
313     return PVMFFailure;
314 }
315
316 static PVMFStatus parseMP4(const char *filename, MediaScannerClient& client)
317 {
318     PVFile fileHandle;
319     Oscl_FileServer iFs;
320
321     if (iFs.Connect() != 0)
322     {
323         LOGE("Connection with the file server for the parse id3 test failed.\n");
324         return PVMFFailure;
325     }
326
327     oscl_wchar output[MAX_BUFF_SIZE];
328     oscl_UTF8ToUnicode((const char *)filename, oscl_strlen((const char *)filename), (oscl_wchar *)output, MAX_BUFF_SIZE);
329     OSCL_wHeapString<OsclMemAllocator> mpegfilename(output);
330
331     IMpeg4File *mp4Input = IMpeg4File::readMP4File(mpegfilename, NULL, NULL, 1 /* parsing_mode */, &iFs);
332     if (mp4Input)
333     {
334         // check to see if the file contains video
335         int32 count = mp4Input->getNumTracks();
336         uint32* tracks = new uint32[count];
337         bool hasAudio = false;
338         bool hasVideo = false;
339         if (tracks) {
340             mp4Input->getTrackIDList(tracks, count);
341             for (int i = 0; i < count; ++i) {
342                 uint32 trackType = mp4Input->getTrackMediaType(tracks[i]);
343                 OSCL_HeapString<OsclMemAllocator> streamtype;
344                 mp4Input->getTrackMIMEType(tracks[i], streamtype);
345                 char streamtypeutf8[128];
346                 strncpy (streamtypeutf8, streamtype.get_str(), streamtype.get_size());
347                 if (streamtypeutf8[0])
348                 {                                                                           
349                     if (strcmp(streamtypeutf8,"FORMATUNKNOWN") != 0) {
350                                                         if (trackType ==  MEDIA_TYPE_AUDIO) {
351                                                                 hasAudio = true;
352                                                         } else if (trackType ==  MEDIA_TYPE_VISUAL) {
353                                                                 hasVideo = true;
354                                                         }
355                     } else {
356                         //LOGI("@@@@@@@@ %100s: %s\n", filename, streamtypeutf8);
357                     }
358                 }
359             }
360
361             delete[] tracks;
362         }
363
364         if (hasVideo) {
365             if (!client.setMimeType("video/mp4")) return PVMFFailure;
366         } else if (hasAudio) {
367             if (!client.setMimeType("audio/mp4")) return PVMFFailure;
368         } else {
369             iFs.Close();
370             IMpeg4File::DestroyMP4FileObject(mp4Input);
371             return PVMFFailure;
372         }
373
374         PVMFStatus result = reportM4ATags(mp4Input, client);
375         iFs.Close();
376         IMpeg4File::DestroyMP4FileObject(mp4Input);
377         return result;
378     }
379
380     return PVMFSuccess;
381 }
382
383 static PVMFStatus parseOgg(const char *filename, MediaScannerClient& client)
384 {
385     int duration;
386
387     FILE *file = fopen(filename,"r");
388     if (!file)
389         return PVMFFailure;
390
391     OggVorbis_File vf;
392     if (ov_open(file, &vf, NULL, 0) < 0) {
393         return PVMFFailure;
394     }
395
396     char **ptr=ov_comment(&vf,-1)->user_comments;
397     while(*ptr){
398         char *val = strstr(*ptr, "=");
399         if (val) {
400             int keylen = val++ - *ptr;
401             char key[keylen + 1];
402             strncpy(key, *ptr, keylen);
403             key[keylen] = 0;
404             if (!client.addStringTag(key, val)) goto failure;
405         }
406         ++ptr;
407     }
408
409     // Duration
410     duration = ov_time_total(&vf, -1);
411     if (duration > 0) {
412         char buffer[20];
413         sprintf(buffer, "%d", duration);
414         if (!client.addStringTag("duration", buffer)) goto failure;
415     }
416
417     ov_clear(&vf); // this also closes the FILE
418     return PVMFSuccess;
419
420 failure:
421     ov_clear(&vf); // this also closes the FILE
422     return PVMFFailure;
423 }
424
425 static PVMFStatus parseMidi(const char *filename, MediaScannerClient& client) {
426
427     // get the library configuration and do sanity check
428     const S_EAS_LIB_CONFIG* pLibConfig = EAS_Config();
429     if ((pLibConfig == NULL) || (LIB_VERSION != pLibConfig->libVersion)) {
430         LOGE("EAS library/header mismatch\n");
431         return PVMFFailure;
432     }
433     EAS_I32 temp;
434
435     // spin up a new EAS engine
436     EAS_DATA_HANDLE easData = NULL;
437     EAS_HANDLE easHandle = NULL;
438     EAS_RESULT result = EAS_Init(&easData);
439     if (result == EAS_SUCCESS) {
440         EAS_FILE file;
441         file.path = filename;
442         file.fd = 0;
443         file.offset = 0;
444         file.length = 0;
445         result = EAS_OpenFile(easData, &file, &easHandle, NULL);
446     }
447     if (result == EAS_SUCCESS) {
448         result = EAS_Prepare(easData, easHandle);
449     }
450     if (result == EAS_SUCCESS) {
451         result = EAS_ParseMetaData(easData, easHandle, &temp);
452     }
453     if (easHandle) {
454         EAS_CloseFile(easData, easHandle);
455     }
456     if (easData) {
457         EAS_Shutdown(easData);
458     }
459
460     if (result != EAS_SUCCESS) {
461         return PVMFFailure;
462     }
463
464     char buffer[20];
465     sprintf(buffer, "%ld", temp);
466     if (!client.addStringTag("duration", buffer)) return PVMFFailure;
467     return PVMFSuccess;
468 }
469
470 static PVMFStatus parseWMA(const char *filename, MediaScannerClient& client)
471 {
472     sp<MediaMetadataRetriever> retriever = new MediaMetadataRetriever();
473     retriever->setMode( 1 /*MediaMetadataRetriever.MODE_GET_METADATA_ONLY*/);
474     status_t status = retriever->setDataSource(filename);
475     if (status != NO_ERROR) {
476         LOGE("parseWMA setDataSource failed (%d)", status);
477         retriever->disconnect();
478         return PVMFFailure;
479     }
480
481     const char* value;
482
483     value = retriever->extractMetadata(METADATA_KEY_IS_DRM_CRIPPLED);
484     if (value && strcmp(value, "true") == 0) {
485         // we don't support WMDRM currently
486         // setting this invalid mimetype will make the java side ignore this file
487         client.setMimeType("audio/x-wma-drm");
488     }
489     value = retriever->extractMetadata(METADATA_KEY_CODEC);
490     if (value && strcmp(value, "Windows Media Audio 10 Professional") == 0) {
491         // we don't support WM 10 Professional currently
492         // setting this invalid mimetype will make the java side ignore this file
493         client.setMimeType("audio/x-wma-10-professional");
494     }
495
496     value = retriever->extractMetadata(METADATA_KEY_ALBUM);
497     if (value)
498         client.addStringTag("album", value);
499
500     // Look for "author" tag first, if it is not found, try "artist" tag
501     value = retriever->extractMetadata(METADATA_KEY_AUTHOR);
502     if (!value) {
503         value = retriever->extractMetadata(METADATA_KEY_ARTIST);
504     }
505     if (value)
506         client.addStringTag("artist", value);
507     value = retriever->extractMetadata(METADATA_KEY_COMPOSER);
508     if (value)
509         client.addStringTag("composer", value);
510     value = retriever->extractMetadata(METADATA_KEY_GENRE);
511     if (value)
512         client.addStringTag("genre", value);
513     value = retriever->extractMetadata(METADATA_KEY_TITLE);
514     if (value)
515         client.addStringTag("title", value);
516     value = retriever->extractMetadata(METADATA_KEY_YEAR);
517     if (value)
518         client.addStringTag("year", value);
519     value = retriever->extractMetadata(METADATA_KEY_CD_TRACK_NUMBER);
520     if (value)
521         client.addStringTag("tracknumber", value);
522
523     retriever->disconnect();
524     return PVMFSuccess;
525 }
526
527 status_t MediaScanner::processFile(const char *path, const char* mimeType, MediaScannerClient& client)
528 {
529     status_t result;
530     InitializeForThread();
531
532     client.setLocale(mLocale);
533     client.beginFile();
534     
535     //LOGD("processFile %s mimeType: %s\n", path, mimeType);
536     const char* extension = strrchr(path, '.');
537
538     if (extension && strcasecmp(extension, ".mp3") == 0) {
539         result = parseMP3(path, client);
540     } else if (extension &&
541         (strcasecmp(extension, ".mp4") == 0 || strcasecmp(extension, ".m4a") == 0 ||
542          strcasecmp(extension, ".3gp") == 0 || strcasecmp(extension, ".3gpp") == 0 ||
543          strcasecmp(extension, ".3g2") == 0 || strcasecmp(extension, ".3gpp2") == 0)) {
544         result = parseMP4(path, client);
545     } else if (extension && strcasecmp(extension, ".ogg") == 0) {
546         result = parseOgg(path, client);
547     } else if (extension &&
548         ( strcasecmp(extension, ".mid") == 0 || strcasecmp(extension, ".smf") == 0
549         || strcasecmp(extension, ".imy") == 0)) {
550         result = parseMidi(path, client);
551     } else if (extension && strcasecmp(extension, ".wma") == 0) {
552         result = parseWMA(path, client);
553     } else {
554         result = PVMFFailure;
555     }
556
557     client.endFile();
558
559     return result;
560 }
561
562 static bool fileMatchesExtension(const char* path, const char* extensions) {
563     char* extension = strrchr(path, '.');
564     if (!extension) return false;
565     ++extension;    // skip the dot
566     if (extension[0] == 0) return false;
567
568     while (extensions[0]) {
569         char* comma = strchr(extensions, ',');
570         int length = (comma ? comma - extensions : strlen(extensions));
571         if (length == strlen(extension) && strncasecmp(extension, extensions, length) == 0) return true;
572         extensions += length;
573         if (extensions[0] == ',') ++extensions;
574     }
575
576     return false;
577 }
578
579 status_t MediaScanner::doProcessDirectory(char *path, int pathRemaining, const char* extensions,
580         MediaScannerClient& client, ExceptionCheck exceptionCheck, void* exceptionEnv)
581 {
582     // place to copy file or directory name
583     char* fileSpot = path + strlen(path);
584     struct dirent* entry;
585
586     // ignore directories that contain a  ".nomedia" file
587     if (pathRemaining >= 8 /* strlen(".nomedia") */ ) {
588         strcpy(fileSpot, ".nomedia");
589         if (access(path, F_OK) == 0) {
590             LOGD("found .nomedia, skipping directory\n");
591             return OK;
592         }
593
594         // restore path
595         fileSpot[0] = 0;
596     }
597
598     DIR* dir = opendir(path);
599     if (!dir) {
600         LOGD("opendir %s failed, errno: %d", path, errno);
601         return PVMFFailure;
602     }
603
604     while ((entry = readdir(dir))) {
605         const char* name = entry->d_name;
606
607         // ignore "." and ".."
608         if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0))) {
609             continue;
610         }
611
612         int type = entry->d_type;
613         if (type == DT_REG || type == DT_DIR) {
614             int nameLength = strlen(name);
615             bool isDirectory = (type == DT_DIR);
616
617             if (nameLength > pathRemaining || (isDirectory && nameLength + 1 > pathRemaining)) {
618                 // path too long!
619                 continue;
620             }
621
622             strcpy(fileSpot, name);
623             if (isDirectory) {
624                 // ignore directories with a name that starts with '.'
625                 // for example, the Mac ".Trashes" directory
626                 if (name[0] == '.') continue;
627
628                 strcat(fileSpot, "/");
629                 int err = doProcessDirectory(path, pathRemaining - nameLength - 1, extensions, client, exceptionCheck, exceptionEnv);
630                 if (err) goto failure;
631             } else if (fileMatchesExtension(path, extensions)) {
632                 struct stat statbuf;
633                 stat(path, &statbuf);
634                 if (statbuf.st_size > 0) {
635                     client.scanFile(path, statbuf.st_mtime, statbuf.st_size);
636                 }
637                 if (exceptionCheck && exceptionCheck(exceptionEnv)) goto failure;
638             }
639         }
640     }
641
642     closedir(dir);
643     return OK;
644 failure:
645     closedir(dir);
646     return -1;
647 }
648
649 status_t MediaScanner::processDirectory(const char *path, const char* extensions,
650         MediaScannerClient& client, ExceptionCheck exceptionCheck, void* exceptionEnv)
651 {
652     InitializeForThread();
653
654     int pathLength = strlen(path);
655     if (pathLength >= PATH_MAX) {
656         return PVMFFailure;
657     }
658     char* pathBuffer = (char *)malloc(PATH_MAX + 1);
659     if (!pathBuffer) {
660         return PVMFFailure;
661     }
662
663     int pathRemaining = PATH_MAX - pathLength;
664     strcpy(pathBuffer, path);
665     if (pathBuffer[pathLength - 1] != '/') {
666         pathBuffer[pathLength] = '/';
667         pathBuffer[pathLength + 1] = 0;
668         --pathRemaining;
669     }
670
671     client.setLocale(mLocale);
672     status_t result = doProcessDirectory(pathBuffer, pathRemaining, extensions, client, exceptionCheck, exceptionEnv);
673
674     free(pathBuffer);
675     return result;
676 }
677
678 void MediaScanner::setLocale(const char* locale)
679 {
680     if (mLocale) {
681         free(mLocale);
682         mLocale = NULL;
683     }
684     if (locale) {
685         mLocale = strdup(locale);
686     }
687 }
688
689 static char* doExtractAlbumArt(PvmfApicStruct* aApic)
690 {
691     char *data = (char*)malloc(aApic->iGraphicDataLen + 4);
692     if (data) {
693         long *len = (long*)data;
694         *len = aApic->iGraphicDataLen;
695         memcpy(data + 4, aApic->iGraphicData, *len);
696     }
697     return data;
698 }
699
700 static char* extractMP3AlbumArt(int fd)
701 {
702     PVID3ParCom pvId3Param;
703     PVFile file;
704     OsclFileHandle *filehandle;
705     Oscl_FileServer iFs;
706
707     if(iFs.Connect() != 0)
708     {
709         LOGE("Connection with the file server for the parse id3 test failed.\n");
710         return NULL;
711     }
712
713     FILE *f = fdopen(fd, "r");
714     filehandle = new OsclFileHandle(f);
715     file.SetFileHandle(filehandle);
716
717     if( 0 != file.Open(NULL, Oscl_File::MODE_READ | Oscl_File::MODE_BINARY, iFs) )
718     {
719         LOGE("Could not open the input file for reading(Test: parse id3).\n");
720         return NULL;
721     }
722
723     file.Seek(0, Oscl_File::SEEKSET);
724     pvId3Param.ParseID3Tag(&file);
725     file.Close();
726     iFs.Close();
727
728     //Get the frames information from ID3 library
729     PvmiKvpSharedPtrVector framevector;
730     pvId3Param.GetID3Frames(framevector);
731
732     uint32 num_frames = framevector.size();
733     for (uint32 i = 0; i < num_frames; i++)
734     {
735         const char* key = framevector[i]->key;
736
737         // type should follow first semicolon
738         const char* type = strchr(key, ';') + 1;
739         if (type == 0) continue;
740         const char* value = framevector[i]->value.pChar_value;
741         const unsigned char* src = (const unsigned char *)value;
742
743         if (oscl_strncmp(key,KVP_KEY_ALBUMART,oscl_strlen(KVP_KEY_ALBUMART)) == 0)
744         {
745             PvmfApicStruct* aApic = (PvmfApicStruct*)framevector[i]->value.key_specific_value;
746             if (aApic) {
747                 char* result = doExtractAlbumArt(aApic);
748                 if (result)
749                     return result;
750             }
751         }
752     }
753
754     return NULL;
755 }
756
757 static char* extractM4AAlbumArt(int fd)
758 {
759     PVFile file;
760     OsclFileHandle *filehandle;
761     Oscl_FileServer iFs;
762     char* result = NULL;
763
764     if(iFs.Connect() != 0)
765     {
766          LOGE("Connection with the file server for the parse id3 test failed.\n");
767         return NULL;
768     }
769
770     FILE *f = fdopen(fd, "r");
771     filehandle = new OsclFileHandle(f);
772     file.SetFileHandle(filehandle);
773
774     oscl_wchar output[MAX_BUFF_SIZE];
775     oscl_UTF8ToUnicode("", 0, (oscl_wchar *)output, MAX_BUFF_SIZE);
776     OSCL_wHeapString<OsclMemAllocator> mpegfilename(output);
777     IMpeg4File *mp4Input = IMpeg4File::readMP4File(
778             mpegfilename, /* name */
779             NULL, /* plugin access interface factory */
780             filehandle,
781             0, /* parsing_mode */
782             &iFs);
783
784     if (!mp4Input)
785         return NULL;
786
787     PvmfApicStruct* aApic = mp4Input->getITunesImageData();
788     if (aApic) {
789         result = doExtractAlbumArt(aApic);
790     }
791
792     IMpeg4File::DestroyMP4FileObject(mp4Input);
793     return result;
794 }
795
796
797 char* MediaScanner::extractAlbumArt(int fd)
798 {
799     InitializeForThread();
800
801     int32 ident;
802     lseek(fd, 4, SEEK_SET);
803     read(fd, &ident, sizeof(ident));
804
805     if (ident == 0x70797466) {
806         // some kind of mpeg 4 stream
807         lseek(fd, 0, SEEK_SET);
808         return extractM4AAlbumArt(fd);
809     } else {
810         // might be mp3
811         return extractMP3AlbumArt(fd);
812     }
813 }
814
815 MediaScannerClient::MediaScannerClient()
816     :   mNames(NULL),
817         mValues(NULL),
818         mLocaleEncoding(kEncodingNone)
819 {
820 }
821
822 MediaScannerClient::~MediaScannerClient()
823 {
824     delete mNames;
825     delete mValues;
826 }
827
828 void MediaScannerClient::setLocale(const char* locale)
829 {
830     if (!locale) return;
831     
832     if (!strncmp(locale, "ja", 2))
833         mLocaleEncoding = kEncodingShiftJIS;
834     else if (!strncmp(locale, "ko", 2))
835         mLocaleEncoding = kEncodingEUCKR;
836     else if (!strncmp(locale, "zh", 2)) {
837         if (!strcmp(locale, "zh_CN")) {
838             // simplified chinese for mainland China
839             mLocaleEncoding = kEncodingGBK;
840         } else {
841             // assume traditional for non-mainland Chinese locales (Taiwan, Hong Kong, Singapore)
842             mLocaleEncoding = kEncodingBig5;
843         }
844     }
845 }
846
847 void MediaScannerClient::beginFile()
848 {
849     mNames = new StringArray;
850     mValues = new StringArray;
851 }
852
853 bool MediaScannerClient::addStringTag(const char* name, const char* value)
854 {
855     if (mLocaleEncoding != kEncodingNone) {
856         // don't bother caching strings that are all ASCII.
857         // call handleStringTag directly instead.
858         // check to see if value (which should be utf8) has any non-ASCII characters
859         bool nonAscii = false;
860         const char* chp = value;
861         char ch;
862         while ((ch = *chp++)) {
863             if (ch & 0x80) {
864                 nonAscii = true;
865                 break;
866             }
867         }
868
869         if (nonAscii) {
870             // save the strings for later so they can be used for native encoding detection
871             mNames->push_back(name);
872             mValues->push_back(value); 
873             return true;
874         }
875         // else fall through
876     }
877
878     // autodetection is not necessary, so no need to cache the values
879     // pass directly to the client instead
880     return handleStringTag(name, value);
881 }
882
883 static uint32_t possibleEncodings(const char* s)
884 {
885     uint32_t result = kEncodingAll;
886     // if s contains a native encoding, then it was mistakenly encoded in utf8 as if it were latin-1
887     // so we need to reverse the latin-1 -> utf8 conversion to get the native chars back
888     uint8 ch1, ch2;
889     uint8* chp = (uint8 *)s;
890     
891     while ((ch1 = *chp++)) {
892         if (ch1 & 0x80) {
893             ch2 = *chp++;
894             ch1 = ((ch1 << 6) & 0xC0) | (ch2 & 0x3F);
895             // ch1 is now the first byte of the potential native char 
896             
897             ch2 = *chp++;
898             if (ch2 & 0x80)
899                 ch2 = ((ch2 << 6) & 0xC0) | (*chp++ & 0x3F);
900             // ch2 is now the second byte of the potential native char
901             int ch = (int)ch1 << 8 | (int)ch2;
902             result &= findPossibleEncodings(ch);
903         }
904         // else ASCII character, which could be anything
905     }
906
907     return result;
908 }
909
910 void MediaScannerClient::convertValues(uint32_t encoding)
911 {
912     const char* enc = NULL;
913     switch (encoding) {
914         case kEncodingShiftJIS:
915             enc = "shift-jis";
916             break;
917         case kEncodingGBK:
918             enc = "gbk";
919             break;
920         case kEncodingBig5:
921             enc = "Big5";
922             break;
923         case kEncodingEUCKR:
924             enc = "EUC-KR";
925             break;
926     }
927
928     if (enc) {
929         UErrorCode status = U_ZERO_ERROR;
930
931         UConverter *conv = ucnv_open(enc, &status);
932         if (U_FAILURE(status)) {
933             LOGE("could not create UConverter for %s\n", enc);
934             return;
935         }
936         UConverter *utf8Conv = ucnv_open("UTF-8", &status);
937         if (U_FAILURE(status)) {
938             LOGE("could not create UConverter for UTF-8\n");
939             ucnv_close(conv);
940             return;
941         }
942
943         // for each value string, convert from native encoding to UTF-8
944         for (int i = 0; i < mNames->size(); i++) {
945             // first we need to untangle the utf8 and convert it back to the original bytes
946             // since we are reducing the length of the string, we can do this in place
947             uint8* src = (uint8 *)mValues->getEntry(i);
948             int len = strlen((char *)src);
949             uint8* dest = src;
950
951             uint8 uch;
952             while ((uch = *src++)) {
953                 if (uch & 0x80)
954                     *dest++ = ((uch << 6) & 0xC0) | (*src++ & 0x3F);
955                 else
956                     *dest++ = uch;
957             }
958             *dest = 0;
959
960             // now convert from native encoding to UTF-8
961             const char* source = mValues->getEntry(i);
962             int targetLength = len * 3 + 1;
963             char* buffer = new char[targetLength];
964             if (!buffer)
965                 break;
966             char* target = buffer;
967
968             ucnv_convertEx(utf8Conv, conv, &target, target + targetLength,
969                     &source, (const char *)dest, NULL, NULL, NULL, NULL, TRUE, TRUE, &status);
970             if (U_FAILURE(status)) {
971                 LOGE("ucnv_convertEx failed: %d\n", status);
972                 mValues->setEntry(i, "???");
973             } else {
974                 // zero terminate
975                 *target = 0;
976                 mValues->setEntry(i, buffer);
977             }         
978
979             delete[] buffer;
980         }
981
982         ucnv_close(conv);
983         ucnv_close(utf8Conv);
984     }
985 }
986
987 void MediaScannerClient::endFile()
988 {
989     if (mLocaleEncoding != kEncodingNone) {
990         int size = mNames->size();
991         uint32_t encoding = kEncodingAll;
992         
993         // compute a bit mask containing all possible encodings
994         for (int i = 0; i < mNames->size(); i++)
995             encoding &= possibleEncodings(mValues->getEntry(i));
996         
997         // if the locale encoding matches, then assume we have a native encoding.
998         if (encoding & mLocaleEncoding)
999             convertValues(mLocaleEncoding);
1000         
1001         // finally, push all name/value pairs to the client
1002         for (int i = 0; i < mNames->size(); i++) {
1003             if (!handleStringTag(mNames->getEntry(i), mValues->getEntry(i)))
1004                 break;
1005         }
1006     }
1007     // else addStringTag() has done all the work so we have nothing to do
1008     
1009     delete mNames;
1010     delete mValues;
1011     mNames = NULL;
1012     mValues = NULL;
1013 }
1014
1015 }; // namespace android