OSDN Git Service

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