source: trunk/SapphireMetaData.m @ 50

Revision 50, 18.5 KB checked in by gbooker, 7 years ago (diff)

Audited the metadata code and removed many bugs.

Line 
1//
2//  SapphireMetaData.m
3//  Sapphire
4//
5//  Created by Graham Booker on 6/22/07.
6//  Copyright 2007 __MyCompanyName__. All rights reserved.
7//
8
9#import "SapphireMetaData.h"
10#import <QTKit/QTKit.h>
11
12//Structure Specific Keys
13#define FILES_KEY                                       @"Files"
14#define DIRS_KEY                                        @"Dirs"
15#define META_VERSION_KEY                        @"Version"
16#define META_VERSION                            1
17
18//File Specific Keys
19#define MODIFIED_KEY                    @"Modified"
20#define WATCHED_KEY                             @"Watched"
21#define FAVORITE_KEY                    @"Favorite"
22#define RESUME_KEY                              @"Resume Time"
23#define SIZE_KEY                                @"Size"
24#define DURATION_KEY                    @"Duration"
25#define AUDIO_DESC_KEY                  @"Audio Description"
26#define SAMPLE_RATE_KEY                 @"Sample Rate"
27#define VIDEO_DESC_KEY                  @"Video Description"
28
29//TV Show Specific Keys
30#define EPISODE_NUMBER_KEY              @"Episode Number"
31#define EPISODE_TITLE_KEY               @"Episode Title"
32#define SEASON_NUMBER_KEY               @"Season"
33#define SHOW_NAME_KEY                   @"Show Name"
34#define SHOW_DESCRIPTION_KEY    @"Show Description"
35#define SHOW_AIR_DATE                   @"Air Date"
36
37
38@implementation NSString (episodeSorting)
39
40// Custom TV Episode handler
41- (NSComparisonResult) episodeCompare:(NSString *)other
42{
43        return [self compare:other options:NSCaseInsensitiveSearch | NSNumericSearch];
44}
45
46@end
47
48@implementation SapphireMetaData
49
50// Static set of file extensions to filter
51static NSArray *extensions = nil;
52
53+(void)load
54{
55        extensions = [[NSArray alloc] initWithObjects:@"avi", @"mov", @"mpg", @"wmv",@"mkv", @"flv", @"divx", @"mp4", nil];
56}
57
58- (id)initWithDictionary:(NSDictionary *)dict parent:(SapphireMetaData *)myParent path:(NSString *)myPath
59{
60        self = [super init];
61        if(!self)
62                return nil;
63       
64        else if(dict == nil)
65                metaData = [NSMutableDictionary new];
66        else
67                metaData = [dict mutableCopy];
68        path = [myPath retain];
69        parent = myParent;
70       
71        return self;
72}
73
74- (void)dealloc
75{
76        [metaData release];
77        [path release];
78        [super dealloc];
79}
80
81- (NSMutableDictionary *)dict
82{
83        return metaData;
84}
85
86- (NSString *)path
87{
88        return path;
89}
90
91- (void)setDelegate:(id <SapphireMetaDataDelegate>)newDelegate
92{
93        delegate = newDelegate;
94}
95
96- (void)writeMetaData
97{
98        [parent writeMetaData];
99}
100
101- (BOOL)isDirectory:(NSString *)fullPath
102{
103        BOOL isDir = NO;
104        return [[NSFileManager defaultManager] fileExistsAtPath:fullPath isDirectory:&isDir] && isDir;
105}
106
107@end
108
109@implementation SapphireMetaDataCollection
110
111- (id)initWithFile:(NSString *)dictionary path:(NSString *)myPath
112{
113        self = [super init];
114        if(!self)
115                return nil;
116       
117        dictionaryPath = [dictionary retain];
118        mainDirectory = [[SapphireDirectoryMetaData alloc] initWithDictionary:metaData parent:self path:myPath];
119        metaData = [[mainDirectory dict] retain];
120        [metaData setObject:[NSNumber numberWithInt:META_VERSION] forKey:META_VERSION_KEY];
121       
122        return self;
123}
124
125- (void)dealloc
126{
127        [mainDirectory release];
128        [dictionaryPath release];
129        [super dealloc];
130}
131
132- (SapphireDirectoryMetaData *)rootDirectory
133{
134        return mainDirectory;
135}
136
137static void makeParentDir(NSFileManager *manager, NSString *dir)
138{
139        NSString *parent = [dir stringByDeletingLastPathComponent];
140       
141        BOOL isDir;
142        if(![manager fileExistsAtPath:parent isDirectory:&isDir])
143                makeParentDir(manager, parent);
144        else if(!isDir)
145                //Can't work with this
146                return;
147       
148        [manager createDirectoryAtPath:dir attributes:nil];
149}
150
151- (void)writeMetaData
152{
153        makeParentDir([NSFileManager defaultManager], [dictionaryPath stringByDeletingLastPathComponent]);
154        [[mainDirectory dict] writeToFile:dictionaryPath atomically:YES];
155}
156
157@end
158
159@interface SapphireDirectoryMetaData (private)
160- (void)reloadDirectoryContents;
161@end
162
163@implementation SapphireDirectoryMetaData
164
165- (id)initWithDictionary:(NSDictionary *)dict parent:(SapphireMetaData *)myParent path:(NSString *)myPath
166{
167        self = [super initWithDictionary:dict parent:myParent path:myPath];
168        if(!self)
169                return nil;
170       
171        metaFiles = [metaData objectForKey:FILES_KEY];
172        if(metaFiles == nil)
173                metaFiles = [NSMutableDictionary new];
174        else
175                metaFiles = [metaFiles mutableCopy];
176        [metaData setObject:metaFiles forKey:FILES_KEY];
177        [metaFiles release];
178
179        metaDirs = [metaData objectForKey:DIRS_KEY];
180        if(metaDirs == nil)
181                metaDirs = [NSMutableDictionary new];
182        else
183                metaDirs = [metaDirs mutableCopy];
184        [metaData setObject:metaDirs forKey:DIRS_KEY];
185        [metaDirs release];
186       
187        cachedMetaDirs = [NSMutableDictionary new];
188        cachedMetaFiles = [NSMutableDictionary new];
189       
190        return self;
191}
192
193- (void)dealloc
194{
195        [importTimer invalidate];
196        [importArray release];
197        [cachedMetaDirs release];
198        [cachedMetaFiles release];
199        [files release];
200        [directories release];
201        [super dealloc];
202}
203
204- (void)reloadDirectoryContents
205{
206        [files release];
207        [directories release];
208        files = [NSMutableArray new];
209        directories = [NSMutableArray new];
210       
211        NSArray *names = [[NSFileManager defaultManager] directoryContentsAtPath:path];
212       
213        NSEnumerator *nameEnum = [names objectEnumerator];
214        NSString *name = nil;
215        // Display Menu Items
216        while((name = [nameEnum nextObject]) != nil)
217        {
218                if([name hasPrefix:@"."])
219                        continue;
220                //Only accept if it is a directory or right extension
221                NSString *extension = [name pathExtension];
222                if([self isDirectory:[path stringByAppendingPathComponent:name]])
223                        [directories addObject:name];
224                else if([extensions containsObject:extension])
225                        [files addObject:name];
226        }
227        [directories sortUsingSelector:@selector(episodeCompare:)];
228        [files sortUsingSelector:@selector(episodeCompare:)];
229        [self updateMetaData];
230}
231
232- (NSArray *)files
233{
234        return files;
235}
236
237- (NSArray *)directories
238{
239        return directories;
240}
241
242- (BOOL)hasPredicatedFiles:(SapphirePredicate *)predicate
243{
244        NSEnumerator *fileEnum = [files objectEnumerator];
245        NSString *file = nil;
246        while((file = [fileEnum nextObject]) != nil)
247        {
248                BOOL include = NO;
249                if([metaFiles objectForKey:file] != nil)
250                {
251                        SapphireFileMetaData *meta = [self metaDataForFile:file];
252                        include = [predicate accept:[meta path] meta:meta];
253                }
254                else
255                        include = [predicate accept:[path stringByAppendingPathComponent:file] meta:nil];
256                if(include)
257                        return YES;
258        }
259        return NO;
260}
261
262- (BOOL)hasPredicatedDirectories:(SapphirePredicate *)predicate
263{
264        NSEnumerator *directoryEnum = [directories objectEnumerator];
265        NSString *directory = nil;
266        while((directory = [directoryEnum nextObject]) != nil)
267        {
268                SapphireDirectoryMetaData *meta = [self metaDataForDirectory:directory];
269                [meta cancelImport];
270               
271                if([meta hasPredicatedFiles:predicate] || [meta hasPredicatedDirectories:predicate])
272                        return YES;
273        }
274        return NO;
275}
276
277- (NSArray *)predicatedFiles:(SapphirePredicate *)predicate
278{
279        NSMutableArray *ret = [NSMutableArray array];
280        NSEnumerator *fileEnum = [files objectEnumerator];
281        NSString *file = nil;
282        while((file = [fileEnum nextObject]) != nil)
283        {
284                BOOL include = NO;
285                if([metaFiles objectForKey:file] != nil)
286                {
287                        SapphireFileMetaData *meta = [self metaDataForFile:file];
288                        include = [predicate accept:[meta path] meta:meta];
289                }
290                else
291                        include = [predicate accept:[path stringByAppendingPathComponent:file] meta:nil];
292                if(include)
293                        [ret addObject:file];
294        }
295        return ret;
296}
297- (NSArray *)predicatedDirectories:(SapphirePredicate *)predicate
298{
299        NSMutableArray *ret = [NSMutableArray array];
300        NSEnumerator *directoryEnum = [directories objectEnumerator];
301        NSString *directory = nil;
302        while((directory = [directoryEnum nextObject]) != nil)
303        {
304                SapphireDirectoryMetaData *meta = [self metaDataForDirectory:directory];
305                [meta cancelImport];
306
307                if([meta hasPredicatedFiles:predicate] || [meta hasPredicatedDirectories:predicate])
308                        [ret addObject:directory];
309        }
310        return ret;
311}
312
313- (SapphireFileMetaData *)metaDataForFile:(NSString *)file
314{
315        SapphireFileMetaData *ret = [cachedMetaFiles objectForKey:file];
316        if(ret == nil)
317        {
318                ret = [[SapphireFileMetaData alloc] initWithDictionary:[metaFiles objectForKey:file] parent:self path:[path stringByAppendingPathComponent:file]];
319                [metaFiles setObject:[ret dict] forKey:file];
320                [cachedMetaFiles setObject:ret forKey:file];
321                [ret autorelease];
322        }
323        return ret;
324}
325
326- (SapphireDirectoryMetaData *)metaDataForDirectory:(NSString *)file
327{
328        SapphireDirectoryMetaData *ret = [cachedMetaDirs objectForKey:file];
329        if(ret == nil)
330        {
331                ret = [[SapphireDirectoryMetaData alloc] initWithDictionary:[metaDirs objectForKey:file] parent:self path:[path stringByAppendingPathComponent:file]];
332                [metaDirs setObject:[ret dict] forKey:file];
333                [cachedMetaDirs setObject:ret forKey:file];
334                [ret autorelease];             
335        }
336        return ret;
337}
338
339- (BOOL)pruneMetaData
340{
341        BOOL ret = NO;
342        NSSet *existingSet = [NSSet setWithArray:files];
343        NSArray *metaArray = [metaFiles allKeys];
344        NSMutableSet *pruneSet = [NSMutableSet setWithArray:metaArray];
345       
346        [pruneSet minusSet:existingSet];
347        if([pruneSet anyObject] != nil)
348        {
349                NSEnumerator *pruneEnum = [pruneSet objectEnumerator];
350                NSString *pruneKey = nil;
351                while((pruneKey = [pruneEnum nextObject]) != nil)
352                        [metaFiles removeObjectForKey:pruneKey];
353                ret = YES;             
354        }
355       
356        existingSet = [NSSet setWithArray:directories];
357        metaArray = [metaDirs allKeys];
358        pruneSet = [NSMutableSet setWithArray:metaArray];
359       
360        [pruneSet minusSet:existingSet];
361        if([pruneSet anyObject] != nil)
362        {
363                NSEnumerator *pruneEnum = [pruneSet objectEnumerator];
364                NSString *pruneKey = nil;
365                while((pruneKey = [pruneEnum nextObject]) != nil)
366                        [metaDirs removeObjectForKey:pruneKey];
367                ret = YES;
368        }
369       
370        return ret;
371}
372
373- (BOOL)updateMetaData
374{
375        NSEnumerator *fileEnum = [files objectEnumerator];
376        NSString *fileName = nil;
377        importArray = [[NSMutableArray alloc] init];
378        while((fileName = [fileEnum nextObject]) != nil)
379        {
380                NSDictionary *fileMeta = [metaFiles objectForKey:fileName];
381                if(fileMeta == nil)
382                        [importArray addObject:fileName];
383                else
384                {
385                        NSString *filePath = [path stringByAppendingPathComponent:fileName];
386                        NSDictionary *props = [[NSFileManager defaultManager] fileAttributesAtPath:filePath traverseLink:YES];
387                        NSDate *modDate = [props objectForKey:NSFileModificationDate];
388                        if([[fileMeta objectForKey:MODIFIED_KEY] intValue] != [modDate timeIntervalSince1970] || [[fileMeta objectForKey:META_VERSION_KEY] intValue] != META_VERSION)
389                                [importArray addObject:fileName];
390                }
391        }
392        return NO;
393}
394
395- (void)processFiles:(NSTimer *)timer
396{
397        NSString *file = [importArray objectAtIndex:0];
398       
399        [[self metaDataForFile:file] updateMetaData];
400       
401        [self writeMetaData];
402        [delegate updateComplete];
403       
404        [importArray removeObjectAtIndex:0];
405        [self resumeImport];
406}
407
408- (void)cancelImport
409{
410        [importTimer invalidate];
411        importTimer = nil;
412}
413
414- (void)resumeImport
415{
416        [importTimer invalidate];
417        if([importArray count])
418                importTimer = [NSTimer scheduledTimerWithTimeInterval:1.1 target:self selector:@selector(processFiles:) userInfo:nil repeats:NO];
419        else
420        {
421                [importTimer invalidate];
422                importTimer = nil;
423                [importArray release];
424                importArray = nil;
425        }
426}
427
428- (void)resumeDelayedImport
429{
430        [importTimer invalidate];
431        if([importArray count])
432                importTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(resumeImport) userInfo:nil repeats:NO];
433}
434
435- (SapphireMetaData *)metaDataForSubPath:(NSString *)subPath
436{
437        NSArray *components = [subPath pathComponents];
438        if(![components count])
439                return self;
440        NSString *file = [components objectAtIndex:0];
441       
442        if([self isDirectory:[path stringByAppendingPathComponent:file]])
443        {
444                NSMutableArray *newComp = [components mutableCopy];
445                [newComp removeObjectAtIndex:0];
446                [newComp autorelease];
447                SapphireDirectoryMetaData *nextLevel = [self metaDataForDirectory:file];
448                return [nextLevel metaDataForSubPath:[NSString pathWithComponents:newComp]];
449        }
450        else if([components count] > 1)
451                return nil;
452        return [self metaDataForFile:file];
453}
454
455- (NSArray *)subFileMetas
456{
457        NSMutableArray *ret = [[NSMutableArray alloc] init];
458        [self reloadDirectoryContents];
459        NSEnumerator *dirEnum = [directories objectEnumerator];
460        NSString *dir = nil;
461        while((dir = [dirEnum nextObject]) != nil)
462        {
463                SapphireDirectoryMetaData *dirMeta = [self metaDataForDirectory:dir];
464                if(dirMeta != nil)
465                        [ret addObjectsFromArray:[dirMeta subFileMetas]];
466        }
467        NSEnumerator *fileEnum = [files objectEnumerator];
468        NSString *file = nil;
469        while((file = [fileEnum nextObject]) != nil)
470        {
471                SapphireFileMetaData *fileMeta = [self metaDataForFile:file];
472                if(fileMeta != nil)
473                        [ret addObject:fileMeta];
474        }
475        return [ret autorelease];
476}
477
478- (BOOL)watched
479{
480        NSEnumerator *fileEnum = [files objectEnumerator];
481        NSString *file = nil;
482        while((file = [fileEnum nextObject]) != nil)
483                if(![[self metaDataForFile:file] watched])
484                        return NO;
485
486        NSEnumerator *dirEnum = [directories objectEnumerator];
487        NSString *dir = nil;
488        while((dir = [dirEnum nextObject]) != nil)
489                if(![[self metaDataForDirectory:dir] watched])
490                        return NO;
491
492        return YES;
493}
494
495- (void)setWatched:(BOOL)watched
496{
497        NSEnumerator *dirEnum = [directories objectEnumerator];
498        NSString *dir = nil;
499        while((dir = [dirEnum nextObject]) != nil)
500                [[self metaDataForDirectory:dir] setWatched:watched];
501       
502        NSEnumerator *fileEnum = [files objectEnumerator];
503        NSString *file = nil;
504        while((file = [fileEnum nextObject]) != nil)
505                [[self metaDataForFile:file] setWatched:watched];
506}
507
508- (BOOL)favorite
509{
510        [self reloadDirectoryContents];
511        NSEnumerator *fileEnum = [files objectEnumerator];
512        NSString *file = nil;
513        while((file = [fileEnum nextObject]) != nil)
514                if([[self metaDataForFile:file] favorite])
515                        return YES;
516
517        NSEnumerator *dirEnum = [directories objectEnumerator];
518        NSString *dir = nil;
519        while((dir = [dirEnum nextObject]) != nil)
520                if([[self metaDataForDirectory:dir] favorite])
521                        return YES;
522       
523        return NO;
524}
525
526- (void)setFavorite:(BOOL)favorite
527{
528        NSEnumerator *dirEnum = [directories objectEnumerator];
529        NSString *dir = nil;
530        while((dir = [dirEnum nextObject]) != nil)
531                [[self metaDataForDirectory:dir] setFavorite:favorite];
532       
533        NSEnumerator *fileEnum = [files objectEnumerator];
534        NSString *file = nil;
535        while((file = [fileEnum nextObject]) != nil)
536                [[self metaDataForFile:file] setFavorite:favorite];
537}
538
539@end
540
541@implementation SapphireFileMetaData : SapphireMetaData
542
543- (void) updateMetaData
544{
545        NSDictionary *props = [[NSFileManager defaultManager] fileAttributesAtPath:path traverseLink:YES];
546        int modTime = [[props objectForKey:NSFileModificationDate] timeIntervalSince1970];
547       
548        if(props == nil)
549                //No file
550                return;
551       
552        if(modTime != [self modified] || [[metaData objectForKey:META_VERSION_KEY] intValue] != META_VERSION)
553        {
554                NSMutableDictionary *fileMeta = [NSMutableDictionary dictionary];
555               
556                [fileMeta setObject:[NSNumber numberWithInt:modTime] forKey:MODIFIED_KEY];
557                [fileMeta setObject:[props objectForKey:NSFileSize] forKey:SIZE_KEY];
558                [fileMeta setObject:[NSNumber numberWithInt:META_VERSION] forKey:META_VERSION_KEY];
559               
560                NSError *error = nil;
561                QTMovie *movie = [QTMovie movieWithFile:path error:&error];
562                QTTime duration = [movie duration];
563                [fileMeta setObject:[NSNumber numberWithFloat:(float)duration.timeValue/(float)duration.timeScale] forKey:DURATION_KEY];
564                NSArray *audioTracks = [movie tracksOfMediaType:@"soun"];
565                NSNumber *audioSampleRate = nil;
566                if([audioTracks count])
567                {
568                        QTTrack *track = [audioTracks objectAtIndex:0];
569                        QTMedia *media = [track media];
570                        audioSampleRate = [media attributeForKey:QTMediaTimeScaleAttribute];
571                        if(media != nil)
572                        {
573                                Media qtMedia = [media quickTimeMedia];
574                                Handle sampleDesc = NewHandle(1);
575                                GetMediaSampleDescription(qtMedia, 1, (SampleDescriptionHandle)sampleDesc);
576                                CFStringRef userText = nil;
577                                ByteCount       propSize = 0;
578                                QTSoundDescriptionGetProperty((SoundDescriptionHandle)sampleDesc, kQTPropertyClass_SoundDescription, kQTSoundDescriptionPropertyID_UserReadableText, sizeof(userText), &userText, &propSize);
579                                DisposeHandle(sampleDesc);
580                               
581                                if(userText != nil)
582                                {
583                                        [fileMeta setObject:(NSString *)userText forKey:AUDIO_DESC_KEY];
584                                        CFRelease(userText);
585                                }
586                        }
587                }
588                if(audioSampleRate != nil)
589                        [fileMeta setObject:audioSampleRate forKey:SAMPLE_RATE_KEY];
590                NSArray *videoTracks = [movie tracksOfMediaType:@"vide"];
591                if([videoTracks count])
592                {
593                        QTTrack *track = [videoTracks objectAtIndex:0];
594                        QTMedia *media = [track media];
595                        if(media != nil)
596                        {
597                                Media qtMedia = [media quickTimeMedia];
598                                Handle sampleDesc = NewHandle(1);
599                                GetMediaSampleDescription(qtMedia, 1, (SampleDescriptionHandle)sampleDesc);
600                                CFStringRef userText = nil;
601                                ByteCount       propSize = 0;
602                                ICMImageDescriptionGetProperty((ImageDescriptionHandle)sampleDesc, kQTPropertyClass_ImageDescription, kICMImageDescriptionPropertyID_SummaryString, sizeof(userText), &userText, &propSize);
603                                DisposeHandle(sampleDesc);
604                               
605                                if(userText != nil)
606                                {
607                                        [fileMeta setObject:(NSString *)userText forKey:VIDEO_DESC_KEY];
608                                        CFRelease(userText);
609                                }
610                        }
611                }
612                [metaData addEntriesFromDictionary:fileMeta];
613        }
614}
615
616- (int)modified
617{
618        return [[metaData objectForKey:MODIFIED_KEY] intValue];
619}
620
621- (BOOL)watched
622{
623        return [[metaData objectForKey:WATCHED_KEY] boolValue];
624}
625
626- (void)setWatched:(BOOL)watched
627{
628        [metaData setObject:[NSNumber numberWithBool:watched] forKey:WATCHED_KEY];
629}
630
631- (BOOL)favorite
632{
633        return [[metaData objectForKey:FAVORITE_KEY] boolValue];
634}
635
636- (void)setFavorite:(BOOL)favorite
637{
638        [metaData setObject:[NSNumber numberWithBool:favorite] forKey:FAVORITE_KEY];
639}
640
641- (unsigned int)resumeTime
642{
643        return [[metaData objectForKey:RESUME_KEY] unsignedIntValue];
644}
645
646- (void)setResumeTime:(unsigned int)resumeTime
647{
648        [metaData setObject:[NSNumber numberWithUnsignedInt:resumeTime] forKey:RESUME_KEY];
649}
650
651- (long long)size
652{
653        return [[metaData objectForKey:SIZE_KEY] longLongValue];
654}
655
656- (float)duration
657{
658        return [[metaData objectForKey:DURATION_KEY] floatValue];
659}
660
661- (int)sampleRate
662{
663        return [[metaData objectForKey:SAMPLE_RATE_KEY] intValue];
664}
665
666- (NSString *)sizeString
667{
668        float size = [self size];
669        if(size == 0)
670                return @"-";
671        char letter = ' ';
672        if(size >= 1024000)
673        {
674                if(size >= 1024*1024000)
675                {
676                        size /= 1024 * 1024 * 1024;
677                        letter = 'G';
678                }
679                else
680                {
681                        size /= 1024 * 1024;
682                        letter = 'M';
683                }
684        }
685        else if (size >= 1000)
686        {
687                size /= 1024;
688                letter = 'K';
689        }
690        return [NSString stringWithFormat:@"%.1f%cB", size, letter];   
691}
692
693- (NSString *)metaDataDescription
694{
695        NSString *name = [path lastPathComponent];
696        int duration = [self duration];
697        int secs = duration % 60;
698        int mins = (duration /60) % 60;
699        int hours = duration / 3600;
700        NSString *durationStr = nil;
701        if(hours != 0)
702                durationStr = [NSString stringWithFormat:@"%d:%02d:%02d", hours, mins, secs];
703        else if (mins != 0)
704                durationStr = [NSString stringWithFormat:@"%d:%02d", mins, secs];
705        else
706                durationStr = [NSString stringWithFormat:@"%ds", secs];
707        NSMutableString *ret = [NSMutableString stringWithFormat:
708                                                                                                   @"Name: \t%@\n"
709                                                                                                        "Duration: \t%@\n"
710                                                                                                        "Size: \t%@", name, durationStr, [self sizeString]];
711        NSString *videoDesc = [metaData objectForKey:VIDEO_DESC_KEY];
712        if(videoDesc != nil)
713                [ret appendFormat:@"\nVideo: \t%@", videoDesc];
714        NSString *audioDesc = [metaData objectForKey:AUDIO_DESC_KEY];
715        if(audioDesc != nil)
716                [ret appendFormat:@"\nAudio: \t%@", audioDesc];
717        return ret;
718}
719
720@end
Note: See TracBrowser for help on using the repository browser.