source: trunk/SapphireFrappliance/MetaData/SapphireMObjects/SapphireFileMetaData.m @ 1322

Revision 1322, 40.5 KB checked in by gbooker, 4 years ago (diff)

If audio is found, but video is not, mark as not having video rather than unsure.

Line 
1#import "SapphireFileMetaData.h"
2#import "SapphireXMLData.h"
3#import "SapphireMovie.h"
4#import "SapphireEpisode.h"
5#import "SapphireDirectoryMetaData.h"
6#import "SapphireVideoTsParser.h"
7#import "SapphireMetaDataSupport.h"
8#import "SapphireMediaPreview.h"
9#import "SapphireFileSymLink.h"
10#import "SapphireSettings.h"
11#import "CoreDataSupportFunctions.h"
12
13#import "SapphireTVShow.h"
14#import "SapphireSeason.h"
15#import "SapphireTVTranslation.h"
16#import "SapphireMovieTranslation.h"
17#import "SapphireJoinedFile.h"
18
19#import "NSArray-Extensions.h"
20#import "NSString-Extensions.h"
21#import "NSFileManager-Extensions.h"
22
23#import <QTKit/QTKit.h>
24
25@implementation SapphireFileMetaData
26
27//ATV Extra Info
28NSString *META_SHOW_BROADCASTER_KEY =           @"Broadcast Company";
29NSString *META_SHOW_AQUIRED_DATE =                      @"Date Aquired";
30NSString *META_SHOW_RATING_KEY =                        @"Rating";
31NSString *META_SHOW_FAVORITE_RATING_KEY =       @"User Rating";
32NSString *META_COPYRIGHT_KEY =                          @"Copyright";
33
34//General Keys
35NSString *META_TITLE_KEY =                                      @"Title";
36NSString *META_DESCRIPTION_KEY =                        @"Show Description";
37NSString *META_SUMMARY_KEY =                            @"Summary";
38NSString *META_RATING_KEY =                                     @"Rating";
39NSString *FILE_CLASS_KEY =                                      @"File Class";
40
41//IMDB Type Info
42NSString *META_MOVIE_TITLE_KEY =                                @"Title";
43NSString *META_MOVIE_CAST_KEY =                                 @"Cast";
44NSString *META_MOVIE_RELEASE_DATE_KEY =                 @"Release Date";
45NSString *META_MOVIE_DIRECTOR_KEY =                             @"Director";
46NSString *META_MOVIE_WIRTERS_KEY =                              @"Writers";
47NSString *META_MOVIE_GENRES_KEY =                               @"Genres";
48NSString *META_MOVIE_PLOT_KEY =                                 @"Plot";
49NSString *META_MOVIE_IMDB_RATING_KEY =                  @"IMDB Rating";
50NSString *META_MOVIE_IMDB_250_KEY =                             @"IMDB Top 250";
51NSString *META_MOVIE_MPAA_RATING_KEY =                  @"MPAA Rating";
52NSString *META_MOVIE_OSCAR_KEY =                                @"Oscars";
53NSString *META_MOVIE_IDENTIFIER_KEY =                   @"Movie ID";
54NSString *META_SEARCH_IMDB_NUMBER_KEY =                 @"Search IMDB Number";
55NSString *META_MOVIE_SORT_TITLE_KEY =                   @"Movie Sort Title";
56
57//TV Show Specific Keys
58NSString *META_SEASON_NUMBER_KEY =                      @"Season";
59NSString *META_EPISODE_NUMBER_KEY =                     @"Episode";
60NSString *META_SHOW_NAME_KEY =                          @"Show Name";
61NSString *META_SHOW_AIR_DATE =                          @"Air Date";
62NSString *META_ABSOLUTE_EP_NUMBER_KEY =         @"Episode Number";
63NSString *META_SHOW_IDENTIFIER_KEY =            @"Show ID";
64NSString *META_EPISODE_2_NUMBER_KEY =           @"Episode 2";
65NSString *META_ABSOLUTE_EP_2_NUMBER_KEY =       @"Episode Number 2";
66NSString *META_SEARCH_SEASON_NUMBER_KEY =       @"Search Season";
67NSString *META_SEARCH_EPISODE_NUMBER_KEY =      @"Search Episode";
68NSString *META_SEARCH_EPISODE_2_NUMBER_KEY =    @"Search Episode 2";
69
70//File Specific Keys
71NSString *META_FILE_MODIFIED_KEY =                              @"Modified";
72NSString *META_FILE_WATCHED_KEY =                               @"Watched";
73NSString *META_FILE_FAVORITE_KEY =                              @"Favorite";
74NSString *META_FILE_RESUME_KEY =                                @"Resume Time";
75NSString *META_FILE_SIZE_KEY =                                  @"Size";
76NSString *META_FILE_DURATION_KEY =                              @"Duration";
77NSString *META_FILE_AUDIO_DESC_KEY =                    @"Audio Description";
78NSString *META_FILE_SAMPLE_RATE_KEY =                   @"Sample Rate";
79NSString *META_FILE_VIDEO_DESC_KEY =                    @"Video Description";
80NSString *META_FILE_AUDIO_FORMAT_KEY =                  @"Audio Format";
81NSString *META_FILE_SUBTITLES_KEY =                     @"Subtitles";
82NSString *META_FILE_JOINED_FILE_KEY =                   @"Joined File";
83
84static NSSet *displayedMetaData;
85static NSArray *displayedMetaDataOrder;
86static NSSet *secondaryFiles;
87
88+ (void)load
89{
90        displayedMetaDataOrder = [[NSArray alloc] initWithObjects:
91                                                          META_MOVIE_IMDB_250_KEY,
92                                                          META_MOVIE_IMDB_RATING_KEY,                                     
93                                                          META_MOVIE_DIRECTOR_KEY,
94                                                          META_MOVIE_CAST_KEY,
95                                                          META_MOVIE_GENRES_KEY,
96                                                          META_EPISODE_AND_SEASON_KEY,
97                                                          META_SEASON_NUMBER_KEY,
98                                                          META_EPISODE_NUMBER_KEY,
99                                                          META_MOVIE_IMDB_STATS_KEY,
100                                                          META_FILE_SIZE_KEY,
101                                                          META_FILE_DURATION_KEY,
102                                                          VIDEO_DESC_LABEL_KEY,
103                                                          VIDEO2_DESC_LABEL_KEY,
104                                                          AUDIO_DESC_LABEL_KEY,
105                                                          AUDIO2_DESC_LABEL_KEY,
106                                                          META_FILE_SUBTITLES_KEY,
107                                                          nil];
108        displayedMetaData = [[NSSet alloc] initWithArray:displayedMetaDataOrder];
109        secondaryFiles = [[NSSet alloc] initWithObjects:
110                                          @"xml",
111                                          @"srt",
112                                          @"sub",
113                                          @"idx",
114                                          @"ass",
115                                          @"ssa",
116                                          nil];
117}
118
119+ (SapphireFileMetaData *)fileWithPath:(NSString *)path inContext:(NSManagedObjectContext *)moc
120{
121        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"path == %@", path];
122        return (SapphireFileMetaData *)doSingleFetchRequest(SapphireFileMetaDataName, moc, predicate);
123}
124
125+ (SapphireFileMetaData *)internalCreateFileWithPath:(NSString *)path parent:(SapphireDirectoryMetaData *)parent inContext:(NSManagedObjectContext *)moc
126{
127        SapphireFileMetaData *ret = [NSEntityDescription insertNewObjectForEntityForName:SapphireFileMetaDataName inManagedObjectContext:moc];
128        ret.parent = parent;
129        ret.path = path;
130        ret.added = [NSDate date];
131       
132        return ret;
133}
134
135+ (SapphireFileMetaData *)createFileWithPath:(NSString *)path inContext:(NSManagedObjectContext *)moc
136{
137        SapphireFileMetaData *ret = [SapphireFileMetaData fileWithPath:path inContext:moc];
138        if(ret != nil)
139                return ret;
140       
141        SapphireDirectoryMetaData *parent = [SapphireDirectoryMetaData createDirectoryWithPath:[path stringByDeletingLastPathComponent] inContext:moc];
142        ret = [SapphireFileMetaData internalCreateFileWithPath:path parent:parent inContext:moc];
143       
144        return ret;
145}
146
147+ (SapphireFileMetaData *)createFileWithPath:(NSString *)path parent:(SapphireDirectoryMetaData *)parent inContext:(NSManagedObjectContext *)moc
148{
149        SapphireFileMetaData *ret = [SapphireFileMetaData fileWithPath:path inContext:moc];
150        if(ret != nil)
151                return ret;
152       
153        return [SapphireFileMetaData internalCreateFileWithPath:path parent:parent inContext:moc];
154}
155
156+ (NSDictionary *)upgradeFilesVersion:(int)version fromContext:(NSManagedObjectContext *)oldMoc toContext:(NSManagedObjectContext *)newMoc withMovies:(NSDictionary *)movieLookup directories:(NSDictionary *)dirLookup
157{
158        NSMutableDictionary *lookup = [NSMutableDictionary dictionary];
159        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
160        NSArray *files = doFetchRequest(SapphireFileMetaDataName, oldMoc, nil);
161        NSEnumerator *fileEnum = [files objectEnumerator];
162        NSManagedObject *oldFile;
163        while((oldFile = [fileEnum nextObject]) != nil)
164        {
165                SapphireFileMetaData *newFile = [NSEntityDescription insertNewObjectForEntityForName:SapphireFileMetaDataName inManagedObjectContext:newMoc];
166                NSString *path = [oldFile valueForKey:@"path"];
167                newFile.path = path;
168                newFile.parent = [dirLookup objectForKey:[path stringByDeletingLastPathComponent]];
169                newFile.audioDescription = [oldFile valueForKey:@"audioDescription"];
170                newFile.audioFormatID = [oldFile valueForKey:@"audioFormatID"];
171                newFile.duration = [oldFile valueForKey:@"duration"];
172                newFile.favorite = [oldFile valueForKey:@"favorite"];
173                newFile.fileClass = [oldFile valueForKey:@"fileClass"];
174                newFile.fileContainerType = [oldFile valueForKey:@"fileContainerType"];
175                newFile.hasVideo = [oldFile valueForKey:@"hasVideo"];
176                newFile.importTypeValue = [[oldFile valueForKey:@"importType"] intValue] & ~ImportTypeMaskXML;
177                newFile.modified = [oldFile valueForKey:@"modified"];
178                newFile.added = [oldFile valueForKey:@"modified"];
179                newFile.resumeTime = [oldFile valueForKey:@"resumeTime"];
180                newFile.sampleRate = [oldFile valueForKey:@"sampleRate"];
181                newFile.size = [oldFile valueForKey:@"size"];
182                newFile.subtitlesDescription = [oldFile valueForKey:@"subtitlesDescription"];
183                newFile.videoDescription = [oldFile valueForKey:@"videoDescription"];
184                newFile.watched = [oldFile valueForKey:@"watched"];
185                NSManagedObject *oldMovie = [oldFile valueForKey:@"movie"];
186                if(oldMovie != nil)
187                {
188                        NSNumber *oldMovieNumber = [oldMovie valueForKey:@"imdbNumber"];
189                        SapphireMovie *movie = nil;
190                        if(oldMovieNumber != nil)
191                                movie = [movieLookup objectForKey:oldMovieNumber];
192                        if(movie == nil)
193                        {
194                                NSString *oldMovieName = [oldMovie valueForKey:@"title"];
195                                if(oldMovieName != nil)
196                                        movie = [movieLookup objectForKey:oldMovieName];
197                        }
198                        newFile.movie = movie;
199                }
200               
201                [lookup setObject:newFile forKey:path];
202        }
203        [pool drain];
204        return lookup;
205}
206
207- (void)insertDictionary:(NSDictionary *)dict withDefer:(NSMutableDictionary *)defer
208{
209        self.audioDescription = [dict objectForKey:META_FILE_AUDIO_DESC_KEY];
210        self.audioFormatID = [dict objectForKey:META_FILE_AUDIO_FORMAT_KEY];
211        self.duration = [dict objectForKey:META_FILE_DURATION_KEY];
212        self.favoriteValue = [[dict objectForKey:META_FILE_FAVORITE_KEY] boolValue];
213        self.fileClass = [dict objectForKey:@"File Class"];
214        self.fileContainerType = [dict objectForKey:@"File Container Type"];
215        id value = [dict objectForKey:META_FILE_MODIFIED_KEY];
216        if(value != nil)
217                self.modified = [NSDate dateWithTimeIntervalSince1970:[value intValue]];
218        self.resumeTimeValue = [[dict objectForKey:META_FILE_RESUME_KEY] unsignedIntValue];
219        self.sampleRate = [dict objectForKey:META_FILE_SAMPLE_RATE_KEY];
220        self.size = [dict objectForKey:META_FILE_SIZE_KEY];
221        self.subtitlesDescription = [dict objectForKey:META_FILE_SUBTITLES_KEY];
222        self.videoDescription = [dict objectForKey:META_FILE_VIDEO_DESC_KEY];
223        self.watchedValue = [[dict objectForKey:META_FILE_WATCHED_KEY] boolValue];
224        self.hasVideoValue = self.videoDescription != nil;
225        value = [dict objectForKey:@"XML Source"];
226        if(value != nil)
227        {
228                NSDictionary *xmlDict = (NSDictionary *)value;
229                SapphireXMLData *xml = self.xmlData;
230                if(xml == nil)
231                {
232                        xml = [NSEntityDescription insertNewObjectForEntityForName:SapphireXMLDataName inManagedObjectContext:[self managedObjectContext]];
233                        self.xmlData = xml;
234                }
235                [xml insertDictionary:xmlDict];
236                xml.modified = [NSDate dateWithTimeIntervalSince1970:[[xmlDict objectForKey:META_FILE_MODIFIED_KEY] intValue]];
237                self.importTypeValue |= ImportTypeMaskXML;
238        }
239        value = [dict objectForKey:@"TVRage Source"];
240        if(value != nil)
241        {
242                SapphireEpisode *ep = [SapphireEpisode episodeWithDictionaries:[NSArray arrayWithObject:value] inContext:[self managedObjectContext]];
243                self.tvEpisode = ep;
244                if(ep != nil)
245                {
246                        self.fileClassValue = FileClassTVShow;
247                        NSString *epCoverPath = [[SapphireMetaDataSupport collectionArtPath] stringByAppendingPathComponent:[ep path]];
248                        NSString *oldBasePath = [[epCoverPath stringByDeletingLastPathComponent] stringByAppendingPathComponent:[self fileName]];
249                        NSString *oldCoverPath = searchCoverArtExtForPath(oldBasePath);
250                        if(oldCoverPath != nil)
251                        {
252                                NSString *newPath = [epCoverPath stringByAppendingPathExtension:[oldCoverPath pathExtension]];
253                                NSFileManager *fm = [NSFileManager defaultManager];
254                                [fm movePath:oldCoverPath toPath:newPath handler:nil];
255                        }
256                        [(NSMutableDictionary *)[defer objectForKey:@"TV Shows"] setObject:ep.tvShow forKey:[value objectForKey:META_SHOW_IDENTIFIER_KEY]];
257                }
258                self.importTypeValue |= ImportTypeMaskTVShow;
259        }
260        value = [dict objectForKey:@"IMDB Source"];
261        if(value != nil)
262        {
263                SapphireMovie *movie = [SapphireMovie movieWithDictionary:(NSDictionary *)value inContext:[self managedObjectContext] lookup:defer];
264                self.movie = movie;
265                if(movie != nil)
266                {
267                        self.fileClassValue = FileClassMovie;
268                        NSString *movieCoverPath = [movie coverArtPath];
269                        NSString *oldBasePath = [[[SapphireMetaDataSupport collectionArtPath] stringByAppendingPathComponent:@"@MOVIES"] stringByAppendingPathComponent:[self fileName]];
270                        NSString *oldCoverPath = searchCoverArtExtForPath(oldBasePath);
271                        if(oldCoverPath != nil)
272                        {
273                                NSString *newPath = [movieCoverPath stringByAppendingPathExtension:[oldCoverPath pathExtension]];
274                                NSFileManager *fm = [NSFileManager defaultManager];
275                                [fm movePath:oldCoverPath toPath:newPath handler:nil];
276                        }
277                }
278                self.importTypeValue |= ImportTypeMaskMovie;
279        }
280        NSString *joinPath = [dict objectForKey:META_FILE_JOINED_FILE_KEY];
281        if(joinPath != nil)
282        {
283                NSMutableDictionary *joinDict = [defer objectForKey:@"Join"];
284                NSMutableArray *joinList = [joinDict objectForKey:joinPath];
285                if(joinList == nil)
286                {
287                        joinList = [NSMutableArray array];
288                        [joinDict setObject:joinList forKey:joinPath];
289                }
290                [joinList addObject:self];
291        }
292}
293
294/*Custom TV Episode handler*/
295- (NSComparisonResult) episodeCompare:(SapphireFileMetaData *)other
296{
297        /*Sort by episode*/
298        SapphireEpisode *myEp = self.tvEpisode;
299        SapphireEpisode *theirEp = nil;
300        if([other isKindOfClass:[SapphireFileSymLink class]])
301                theirEp = ((SapphireFileSymLink *)other).file.tvEpisode;
302        else
303                theirEp = other.tvEpisode;
304       
305        if(myEp != nil)
306        {
307                if(theirEp != nil)
308                        return [myEp compare:theirEp];
309                else
310                        return NSOrderedAscending;
311        }
312        else if (theirEp != nil)
313                return NSOrderedDescending;
314
315        return NSOrderedSame;
316}
317
318- (NSComparisonResult) movieCompare:(SapphireFileMetaData *)other
319{
320        SapphireMovie *myMovie = self.movie;
321        SapphireMovie *theirMovie = other.movie;
322       
323        if(myMovie != nil)
324                if(theirMovie != nil)
325                {
326                        NSComparisonResult ret = [myMovie titleCompare:theirMovie];
327                        if(ret == NSOrderedSame)
328                                ret = [myMovie releaseDateCompare:theirMovie];
329                        return ret;
330                }
331                else
332                        return NSOrderedAscending;
333        else if(theirMovie != nil)
334                return NSOrderedDescending;
335       
336        return NSOrderedSame;
337}
338
339- (BOOL) needsUpdating
340{
341        /*Check modified date*/
342        NSString *path = nil;
343        NSFileManager *fm = [NSFileManager defaultManager];
344        if([self fileContainerTypeValue] == FileContainerTypeVideoTS)
345        {
346                NSString *vtsPath = [self.path stringByAppendingPathComponent:@"VIDEO_TS"];
347                NSEnumerator *fileEnum = [[fm directoryContentsAtPath:vtsPath] objectEnumerator];
348                NSString *file;
349                while((file = [fileEnum nextObject]) != nil)
350                {
351                        NSString *lowerFile = [file lowercaseString];
352                        if([lowerFile hasSuffix:@".ifo"] && ![[lowerFile lastPathComponent] isEqualToString:@"video_ts.ifo"])
353                        {
354                                path = [vtsPath stringByAppendingPathComponent:file];
355                                break;
356                        }
357                }
358        }
359        else
360                path = self.path;
361        NSDictionary *props = [fm fileAttributesAtPath:path traverseLink:YES];
362        int modTime = [[props objectForKey:NSFileModificationDate] timeIntervalSince1970];
363       
364        if(props == nil)
365        /*No file*/
366                return FALSE;
367       
368        /*Has it been modified since last import?*/
369        if(modTime != [self.modified timeIntervalSince1970])
370                return YES;
371       
372        if(self.hasVideo == nil)
373                return YES;
374       
375        return NO;
376}
377
378- (BOOL)needsImporting
379{
380        NSFileManager *fm = [NSFileManager defaultManager];
381       
382        if(self.joinedToFile != nil)
383                return NO;
384
385        if([self needsUpdating])
386                return YES;
387       
388        //Check XML
389        NSString *xmlFilePath=[[self extensionlessPath] stringByAppendingPathExtension:@"xml"];
390        SapphireXMLData *xml = self.xmlData;
391        NSDictionary *xmlProps = [fm fileAttributesAtPath:xmlFilePath traverseLink:YES];
392       
393        if(xmlProps == nil && xml != nil)
394                //XML file is gone, but we still reference it
395                return YES;
396
397        int modTime = 0;
398        if(xmlProps)
399                modTime = [[xmlProps objectForKey:NSFileModificationDate] timeIntervalSince1970];
400        if(modTime != [self importedTimeFromSource:ImportTypeMaskXML])
401                //XML modification time does not match our last import
402                return YES;
403       
404        //Match imports, but exclude xml and file b/c they are tracked through other means
405        int match = ImportTypeMaskAll & ~ImportTypeMaskFile & ~ImportTypeMaskXML;
406        switch (self.fileClassValue) {
407                case FileClassTVShow:
408                        match &= ~ImportTypeMaskMovie;
409                        break;
410                case FileClassMovie:
411                        match &= ~ImportTypeMaskTVShow;
412                        break;
413                default:
414                        break;
415        }
416       
417        int completed = self.importTypeValue & match;
418        BOOL ret = (match != completed);
419        return ret;
420}
421
422- (oneway void)addFileData:(bycopy NSDictionary *)fileMeta
423{
424        NSString *audioDesc = [fileMeta objectForKey:META_FILE_AUDIO_DESC_KEY];
425        self.audioDescription = audioDesc;
426        self.audioFormatID = [fileMeta objectForKey:META_FILE_AUDIO_FORMAT_KEY];
427        self.duration = [fileMeta objectForKey:META_FILE_DURATION_KEY];
428        id value = [fileMeta objectForKey:META_FILE_MODIFIED_KEY];
429        if(value != nil)
430                self.modified = [NSDate dateWithTimeIntervalSince1970:[value intValue]];
431        self.sampleRate = [fileMeta objectForKey:META_FILE_SAMPLE_RATE_KEY];
432        self.size = [fileMeta objectForKey:META_FILE_SIZE_KEY];
433        self.subtitlesDescription = [fileMeta objectForKey:META_FILE_SUBTITLES_KEY];
434        NSString *videoDesc = [fileMeta objectForKey:META_FILE_VIDEO_DESC_KEY];
435        self.videoDescription = videoDesc;
436        if(videoDesc != nil)
437                self.hasVideoValue = YES;
438        else if(audioDesc != nil)
439                self.hasVideoValue = NO;
440}
441
442
443NSDictionary *fileMetaData(NSString *path, FileContainerType type)
444{
445        NSMutableDictionary *fileMeta = [NSMutableDictionary dictionary];
446        NSString *modifiedPath = path;
447        NSFileManager *fm = [NSFileManager defaultManager];
448       
449        if(type == FileContainerTypeVideoTS)
450        {
451                NSString *vtsPath = [path stringByAppendingPathComponent:@"VIDEO_TS"];
452                NSEnumerator *fileEnum = [[fm directoryContentsAtPath:vtsPath] objectEnumerator];
453                NSString *file;
454                while((file = [fileEnum nextObject]) != nil)
455                {
456                        NSString *lowerFile = [file lowercaseString];
457                        if([lowerFile hasSuffix:@".ifo"] && ![[lowerFile lastPathComponent] isEqualToString:@"video_ts.ifo"])
458                        {
459                                modifiedPath = [vtsPath stringByAppendingPathComponent:file];
460                                break;
461                        }
462                }
463        }
464        NSDictionary *props = [fm fileAttributesAtPath:modifiedPath traverseLink:YES];
465        int modTime = [[props objectForKey:NSFileModificationDate] timeIntervalSince1970];
466        /*Set modified, size, and version*/
467        [fileMeta setObject:[NSNumber numberWithInt:modTime] forKey:META_FILE_MODIFIED_KEY];
468        [fileMeta setObject:[props objectForKey:NSFileSize] forKey:META_FILE_SIZE_KEY];
469       
470        if(type == FileContainerTypeQTMovie)
471        {
472                /*Open the movie*/
473                NSError *error = nil;
474                QTMovie *movie = [QTMovie movieWithFile:path error:&error];
475                QTTime duration = [movie duration];
476                if(movie == nil)
477                        duration.timeValue = 0;
478                [fileMeta setObject:[NSNumber numberWithFloat:(float)duration.timeValue/(float)duration.timeScale] forKey:META_FILE_DURATION_KEY];
479                NSArray *audioTracks = [movie tracksOfMediaType:@"soun"];
480                NSNumber *audioSampleRate = nil;
481                int trackCount = [audioTracks count];
482                int i;
483                BOOL foundAC3 = NO;
484                for(i=0; i<trackCount; i++)
485                {
486                        /*Get the audio track*/
487                        QTTrack *track = [audioTracks objectAtIndex:i];
488                        QTMedia *media = [track media];
489                        if(media != nil)
490                        {
491                                /*Get the audio format*/
492                                Media qtMedia = [media quickTimeMedia];
493                                Handle sampleDesc = NewHandle(1);
494                                GetMediaSampleDescription(qtMedia, 1, (SampleDescriptionHandle)sampleDesc);
495                                AudioStreamBasicDescription asbd;
496                                ByteCount       propSize = 0;
497                                QTSoundDescriptionGetProperty((SoundDescriptionHandle)sampleDesc, kQTPropertyClass_SoundDescription, kQTSoundDescriptionPropertyID_AudioStreamBasicDescription, sizeof(asbd), &asbd, &propSize);
498                               
499                                if(propSize != 0 && !foundAC3)
500                                {
501                                        /*Set the format and sample rate*/
502                                        NSNumber *format = [NSNumber numberWithUnsignedInt:asbd.mFormatID];
503                                        [fileMeta setObject:format forKey:META_FILE_AUDIO_FORMAT_KEY];
504                                        audioSampleRate = [NSNumber numberWithDouble:asbd.mSampleRate];
505                                }
506                               
507                                CFStringRef userText = nil;
508                                propSize = 0;
509                                QTSoundDescriptionGetProperty((SoundDescriptionHandle)sampleDesc, kQTPropertyClass_SoundDescription, kQTSoundDescriptionPropertyID_UserReadableText, sizeof(userText), &userText, &propSize);
510                                if(userText != nil)
511                                {
512                                        if([(NSString *)userText hasPrefix:@"AC3"])
513                                                foundAC3 = YES;
514                                        /*Set the description*/
515                                        NSString *prevDesc = [fileMeta objectForKey:META_FILE_AUDIO_DESC_KEY];
516                                        NSString *newDesc;
517                                        if(prevDesc != nil)
518                                                newDesc = [prevDesc stringByAppendingFormat:@"\n%@", userText];
519                                        else
520                                                newDesc = (NSString *)userText;
521                                        [fileMeta setObject:newDesc forKey:META_FILE_AUDIO_DESC_KEY];
522                                        CFRelease(userText);
523                                }
524                                DisposeHandle(sampleDesc);
525                        }
526                }
527                /*Set the sample rate*/
528                if(audioSampleRate != nil)
529                        [fileMeta setObject:audioSampleRate forKey:META_FILE_SAMPLE_RATE_KEY];
530                NSArray *videoTracks = [movie tracksOfMediaType:@"vide"];
531                trackCount = [videoTracks count];
532                for(i=0; i<trackCount; i++)
533                {
534                        /*Get the video track*/
535                        QTTrack *track = [videoTracks objectAtIndex:i];
536                        QTMedia *media = [track media];
537                        if(media != nil)
538                        {
539                                /*Get the video description*/
540                                Media qtMedia = [media quickTimeMedia];
541                                Handle sampleDesc = NewHandle(1);
542                                GetMediaSampleDescription(qtMedia, 1, (SampleDescriptionHandle)sampleDesc);
543                                CFStringRef userText = nil;
544                                ByteCount propSize = 0;
545                                ICMImageDescriptionGetProperty((ImageDescriptionHandle)sampleDesc, kQTPropertyClass_ImageDescription, kICMImageDescriptionPropertyID_SummaryString, sizeof(userText), &userText, &propSize);
546                                DisposeHandle(sampleDesc);
547                               
548                                if(userText != nil)
549                                {
550                                        /*Set the description*/
551                                        NSString *prevDesc = [fileMeta objectForKey:META_FILE_VIDEO_DESC_KEY];
552                                        NSString *newDesc;
553                                        if(prevDesc != nil)
554                                                newDesc = [prevDesc stringByAppendingFormat:@"\n%@", userText];
555                                        else
556                                                newDesc = (NSString *)userText;
557                                        [fileMeta setObject:newDesc forKey:META_FILE_VIDEO_DESC_KEY];
558                                        CFRelease(userText);
559                                }
560                        }
561                }
562        } //QTMovie
563        else if(type == FileContainerTypeVideoTS)
564        {
565                SapphireVideoTsParser *dvd = [[SapphireVideoTsParser alloc] initWithPath:path];
566                id description = [dvd videoFormatsString];
567                if(description)
568                        [fileMeta setObject:description forKey:META_FILE_VIDEO_DESC_KEY];
569                description = [dvd audioFormatsString];
570                if(description)
571                        [fileMeta setObject:description forKey:META_FILE_AUDIO_DESC_KEY];
572                description = [dvd mainFeatureDuration];
573                if(description)
574                        [fileMeta setObject:description forKey:META_FILE_DURATION_KEY];
575                description = [dvd totalSize];
576                if(description)
577                        [fileMeta setObject:description forKey:META_FILE_SIZE_KEY];
578                description = [dvd subtitlesString];
579                if(description)
580                        [fileMeta setObject:description forKey:META_FILE_SUBTITLES_KEY ];
581               
582                [dvd release];
583        } // VIDEO_TS
584        return fileMeta;
585}
586
587- (BOOL)updateMetaData
588{
589        if([self needsUpdating])
590        {
591                NSDictionary *fileMeta = fileMetaData([self path], [self fileContainerTypeValue]);
592                [self addFileData:fileMeta];
593                return YES;
594        }
595        return NO;
596}
597
598NSString *sizeStringForSize(float size)
599{
600        if(size == 0)
601                return @"-";
602       
603        /*The letter for magnitude*/
604        char letter = ' ';
605        if(size >= 1024000)
606        {
607                if(size >= 1024*1024000)
608                {
609                        /*GB*/
610                        size /= 1024 * 1024 * 1024;
611                        letter = 'G';
612                }
613                else
614                {
615                        /*MB*/
616                        size /= 1024 * 1024;
617                        letter = 'M';
618                }
619        }
620        else if (size >= 1000)
621        {
622                /*KB*/
623                size /= 1024;
624                letter = 'K';
625        }
626        return [NSString stringWithFormat:@"%.1f%cB", size, letter];   
627}
628
629- (NSString *)sizeString
630{
631        /*Get size*/
632        float size = [self sizeValue];
633        NSString *sizeStr = sizeStringForSize(size);
634        SapphireJoinedFile *joined = self.joinedFile;
635        if(joined != nil)
636        {
637                NSEnumerator *joinedEnum = [joined.joinedFilesSet objectEnumerator];
638                SapphireFileMetaData *file;
639                while((file = [joinedEnum nextObject]) != nil)
640                {
641                        size += file.sizeValue;
642                }
643                if(size != 0)
644                        sizeStr = [sizeStr stringByAppendingFormat:@"/%@", sizeStringForSize(size)];
645        }
646        return sizeStr;
647}
648
649- (void)setToReimportFromMask:(NSNumber *)mask
650{
651        [self setToReimportFromMaskValue:[mask intValue]];
652}
653
654- (void)setToReimportFromMaskValue:(int)mask
655{
656        int currentMask = self.importTypeValue;
657        self.importTypeValue = currentMask & ~mask;
658        if(mask & ImportTypeMaskMovie)
659        {
660                SapphireMovie *movie = self.movie;
661                self.movie = nil;
662                if(movie != nil && [movie.filesSet count] == 0)
663                        [[self managedObjectContext] deleteObject:movie];
664        }
665        if(mask & ImportTypeMaskTVShow)
666        {
667                SapphireEpisode *ep = self.tvEpisode;
668                self.tvEpisode = nil;
669                if(ep != nil && [ep.filesSet count] == 0)
670                        [[self managedObjectContext] deleteObject:ep];
671        }
672}
673
674- (void)setToResetImportDecisions
675{
676        NSManagedObjectContext *moc = [self managedObjectContext];
677        NSString *lowerFileName = [[self fileName] lowercaseString];
678
679        SapphireEpisode *ep = self.tvEpisode;
680        if(ep != nil)
681        {
682                NSSet *translations = ep.tvShow.translationsSet;
683                SapphireTVTranslation *tran;
684                NSEnumerator *tranEnum = [translations objectEnumerator];
685                while((tran = [tranEnum nextObject]) != nil)
686                {
687                        if([lowerFileName hasPrefix:tran.name])
688                        {
689                                SapphireLog(SapphireLogTypeMetadataStore, SapphireLogLevelDetail, @"Deleting TV import translation for %@", tran.name);
690                                [moc deleteObject:tran];
691                        }
692                }
693        }
694        NSString *lookupName;
695        if([[SapphireSettings sharedSettings] dirLookup])
696                lookupName = [[[self.path stringByDeletingLastPathComponent] lastPathComponent] lowercaseString];
697        else
698                lookupName = lowerFileName;
699        SapphireMovieTranslation *movieTran = [SapphireMovieTranslation movieTranslationWithName:lookupName inContext:moc];
700        if(movieTran != nil)
701        {
702                SapphireLog(SapphireLogTypeMetadataStore, SapphireLogLevelDetail, @"Deleting Movie import translation for %@", movieTran.name);
703                [moc deleteObject:movieTran];
704        }
705        NSScanner *titleYearScanner = [NSScanner scannerWithString:lookupName];
706        NSString *normalTitle = nil;
707        int year = 0;
708        BOOL success = YES;
709        success &= [titleYearScanner scanUpToString:@"(" intoString:&normalTitle];
710        success &= [titleYearScanner scanString:@"(" intoString:nil];
711        success &= [titleYearScanner scanInt:&year];
712        success &= [titleYearScanner scanString:@")" intoString:nil];
713       
714        if(success)
715        {
716                if([normalTitle hasSuffix:@" "])
717                        normalTitle = [normalTitle substringToIndex:[normalTitle length]-1];
718                movieTran = [SapphireMovieTranslation movieTranslationWithName:normalTitle inContext:moc];
719                if(movieTran != nil)
720                {
721                        SapphireLog(SapphireLogTypeMetadataStore, SapphireLogLevelDetail, @"Deleting Movie import translation for %@", movieTran.name);
722                        [moc deleteObject:movieTran];
723                }
724        }       
725       
726        [self setToReimportFromMaskValue:ImportTypeMaskAll];
727}
728
729- (void)clearMetaData
730{
731        self.audioDescription = nil;
732        self.audioFormatID = nil;
733        self.duration = nil;
734        self.favoriteValue = 0;
735        self.fileClassValue = nil;
736        self.fileContainerType = nil;
737        self.hasVideo = nil;
738        self.importTypeValue = 0;
739        self.modified = nil;
740        self.resumeTime = nil;
741        self.sampleRate = nil;
742        self.size = nil;
743        self.subtitlesDescription = nil;
744        self.videoDescription = nil;
745        self.watchedValue = 0;
746        self.movie = nil;
747        self.tvEpisode = nil;
748        if(self.xmlData != nil)
749        {
750                [[self managedObjectContext] deleteObject:self.xmlData];
751        }
752}
753
754- (NSString *)coverArtPath
755{
756        /*Find cover art for the current file in the "Cover Art" dir */
757        NSString *subPath = [self extensionlessPath];
758       
759        NSString *fileName = [subPath lastPathComponent];
760        NSString * myArtPath=nil;
761       
762        if([self fileClassValue]==FileClassTVShow)
763                myArtPath=[[self tvEpisode] coverArtPath];
764        if([self fileClassValue]==FileClassMovie)
765                myArtPath=[[self movie] coverArtPath];
766       
767        /* Check the Collection Art location */
768        NSString *ret=searchCoverArtExtForPath(myArtPath);
769       
770        if(ret != nil)
771                return ret;
772       
773        /* Try Legacy Folders with the file */
774        ret=searchCoverArtExtForPath([[[subPath stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"Cover Art"] stringByAppendingPathComponent:fileName]);
775       
776        if(ret != nil)
777                return ret;
778       
779        /*Find cover art for the current file in the current dir*/
780        ret = searchCoverArtExtForPath(subPath);
781       
782        if(ret != nil)
783                return ret;
784       
785       
786        return nil;
787}
788
789- (NSString *)durationString
790{
791        /*Create duration string*/
792        return [NSString colonSeparatedTimeStringForSeconds:[self durationValue]];
793}
794
795static BOOL moving = NO;
796static BOOL moveSuccess = NO;
797static NSString *movingFromPath = @"From";
798static NSString *movingToPath = @"To";
799
800- (void)threadedMove:(NSDictionary *)pathInfo
801{
802        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
803        moveSuccess = [[NSFileManager defaultManager] movePath:[pathInfo objectForKey:movingFromPath] toPath:[pathInfo objectForKey:movingToPath] handler:nil];
804        moving = NO;
805        [pool drain];
806}
807
808- (NSString *)moveToPath:(NSString *)newPath pathForMoveError:(NSString *)errorPath inDir:(SapphireDirectoryMetaData *)newParent
809{
810        NSString *oldPath = [[[self path] retain] autorelease];
811        NSString *extLessPath = [self extensionlessPath];
812        NSFileManager *fm = [NSFileManager defaultManager];
813        if([fm fileExistsAtPath:newPath])
814                return [NSString stringWithFormat:BRLocalizedString(@"The name %@ is already taken", @"Name taken on a file/directory rename; parameter is name"), [newPath lastPathComponent]];
815        if(newParent != nil)
816        {
817                moving = YES;
818                [NSThread detachNewThreadSelector:@selector(threadedMove:) toTarget:self withObject:[NSDictionary dictionaryWithObjectsAndKeys:
819                                                                                                                                                                                         oldPath, movingFromPath,
820                                                                                                                                                                                         newPath, movingToPath,
821                                                                                                                                                                                         nil]];
822                while(moving)
823                        [[NSRunLoop currentRunLoop] runUntilDate:[[NSDate date] addTimeInterval:1]];           
824        }
825        else
826                moveSuccess = [fm movePath:oldPath toPath:newPath handler:nil];
827        NSLog(@"Move %d", moveSuccess);
828        if(!moveSuccess)
829                return [NSString stringWithFormat:BRLocalizedString(@"Could not move %@.  Is the filesystem read-only?", @"Unknown error renaming file/directory; parameter is name"), errorPath];
830        [self setPath:newPath];
831        NSLog(@"path set to %@", newPath);
832        if(newParent != nil)
833        {
834                SapphireDirectoryMetaData *oldParent = self.parent;
835                self.parent = newParent;
836                [oldParent clearPredicateCache];
837                [newParent clearPredicateCache];
838        }
839        NSLog(@"new parent set");
840        [SapphireMetaDataSupport save:[self managedObjectContext]];
841        NSLog(@"Save done");
842        NSEnumerator *secondaryExtEnum = [secondaryFiles objectEnumerator];
843        NSString *extension;
844        NSString *newExtlessPath = newPath;
845        if(self.fileContainerTypeValue != FileContainerTypeVideoTS)
846                newExtlessPath = [newExtlessPath stringByDeletingPathExtension];
847       
848        while((extension = [secondaryExtEnum nextObject]) != nil)
849        {
850                NSString *secondaryPath = [extLessPath stringByAppendingPathExtension:extension];
851                if([fm fileExistsAtPath:secondaryPath])
852                {
853                        NSString *newSecondaryPath = [newExtlessPath stringByAppendingPathExtension:extension];
854                        if(newParent != nil)
855                        {
856                                moving = YES;
857                                [NSThread detachNewThreadSelector:@selector(threadedMove:) toTarget:self withObject:[NSDictionary dictionaryWithObjectsAndKeys:
858                                                                                                                                                                                                         secondaryPath, movingFromPath,
859                                                                                                                                                                                                         newSecondaryPath, movingToPath,
860                                                                                                                                                                                                         nil]];
861                                while(moving)
862                                        [[NSRunLoop currentRunLoop] runUntilDate:[[NSDate date] addTimeInterval:1]];           
863                        }
864                        else
865                                moveSuccess = [fm movePath:secondaryPath toPath:newSecondaryPath handler:nil];                 
866                        if(!moveSuccess)
867                                return [NSString stringWithFormat:BRLocalizedString(@"Could not move %@ file for %@.  Is the filesystem read-only?", @"Unknown error renaming file/directory; parameter is extension, name"), extension, errorPath];
868                }
869        }
870        NSLog(@"Secondary files done");
871        NSString *coverArtPath = searchCoverArtExtForPath(extLessPath);
872        if(coverArtPath != nil)
873        {
874                NSString *newCoverArtPath = [newExtlessPath stringByAppendingPathExtension:[coverArtPath pathExtension]];
875                if(newParent != nil)
876                {
877                        moving = YES;
878                        [NSThread detachNewThreadSelector:@selector(threadedMove:) toTarget:self withObject:[NSDictionary dictionaryWithObjectsAndKeys:
879                                                                                                                                                                                                 coverArtPath, movingFromPath,
880                                                                                                                                                                                                 newCoverArtPath, movingToPath,
881                                                                                                                                                                                                 nil]];
882                        while(moving)
883                                [[NSRunLoop currentRunLoop] runUntilDate:[[NSDate date] addTimeInterval:1]];           
884                }
885                else
886                        moveSuccess = [fm movePath:coverArtPath toPath:newCoverArtPath handler:nil];
887                if(!moveSuccess)
888                        return [NSString stringWithFormat:BRLocalizedString(@"Could not move cover art for %@.  Is the filesystem read-only?", @"Unknown error renaming file/directory; parameter is name"), errorPath];
889        }
890        NSLog(@"Covert art done");
891        return nil;
892}
893
894- (NSString *)moveToDir:(SapphireDirectoryMetaData *)dir
895{
896        NSString *destination = [dir path];
897        NSString *newPath = [destination stringByAppendingPathComponent:[[self path] lastPathComponent]];
898        return [self moveToPath:newPath pathForMoveError:[newPath lastPathComponent] inDir:dir];
899}
900
901- (NSString *)rename:(NSString *)newFilename
902{
903        int componentCount = [[newFilename pathComponents] count];
904        if(componentCount != 1)
905                return BRLocalizedString(@"A File name should not contain any '/' characters", @"Error indicating that filenames cannot contain / characters");
906        NSString *oldPath = [self path];
907        if(self.fileContainerTypeValue != FileContainerTypeVideoTS)
908                newFilename = [newFilename stringByAppendingPathExtension:[oldPath pathExtension]];
909        NSString *newPath = [[oldPath stringByDeletingLastPathComponent] stringByAppendingPathComponent:newFilename];
910        if([oldPath isEqualToString:newPath])
911                return nil;
912        NSLog(@"Going to move path %@ to %@ with error %@", [self path], newPath, newFilename);
913        return [self moveToPath:newPath pathForMoveError:newFilename inDir:nil];
914}
915
916- (NSString *)prettyName
917{
918        if(self.tvEpisode != nil)
919        {
920                //TV Episode
921                SapphireEpisode *ep = self.tvEpisode;
922                NSString *tvShowName = ep.tvShow.name;
923                NSString *epName = [ep episodeTitle];
924                int season = ep.season.seasonNumberValue;
925                int firstEp = [ep episodeNumberValue];
926                int lastEp = [ep lastEpisodeNumberValue];
927               
928                NSString *SEString;
929                if(firstEp == 0)
930                        //Single Special Episode
931                        SEString = [NSString stringWithFormat:@"S%02dES1", season];
932                else if(lastEp == firstEp)
933                        //Single normal episode
934                        SEString = [NSString stringWithFormat:@"S%02dE%02d", season, firstEp];
935                else
936                        //Double episode
937                        SEString = [NSString stringWithFormat:@"S%02dE%02d-E%02d", season, firstEp, lastEp];
938               
939                return [NSString stringWithFormat:@"%@ %@ %@", tvShowName, SEString, epName];
940        }
941        else if(self.movie != nil)
942        {
943                //Movie
944                NSDate *releaseDate = [self.movie releaseDate];
945                NSCalendarDate *releaseCalDate = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate:[releaseDate timeIntervalSinceReferenceDate]];
946                return [NSString stringWithFormat:@"%@ (%d)", [self.movie title], [releaseCalDate yearOfCommonEra]];
947        }
948        return nil;
949}
950
951- (NSString *)renameToPrettyName
952{
953        NSString *prettyName = [self prettyName];
954        if(prettyName == nil)
955        {
956                return BRLocalizedString(@"No pretty name to construct", @"Error indicating there is no pretty name for this file");
957        }
958       
959        NSMutableString *mutStr = [prettyName mutableCopy];
960        [mutStr replaceOccurrencesOfString:@"/" withString:@"-" options:0 range:NSMakeRange(0, [mutStr length])];
961        NSLog(@"Going to rename %@ to %@", [self path], mutStr);
962       
963        return [self rename:[mutStr autorelease]];
964}
965
966- (NSString *)autoSortPath
967{
968        SapphireEpisode *episode = self.tvEpisode;
969        return [episode.season autoSortPath];
970}
971
972- (NSString *)moveToAutoSortName
973{
974        NSString *error = nil;
975        if(![[self fileName] isEqualToString:[self prettyName]])
976                error = [self renameToPrettyName];
977       
978        if(error != nil)
979                return error;
980       
981        NSString *autoSortSeasonPath = [self autoSortPath];
982        if(autoSortSeasonPath == nil)
983                return nil;
984       
985        NSString *autoSortShowPath = [autoSortSeasonPath stringByDeletingLastPathComponent];
986       
987        NSFileManager *fm = [NSFileManager defaultManager];
988        if(![fm isDirectory:autoSortShowPath])
989                return [NSString stringWithFormat:BRLocalizedString(@"Auto sort path %@ is not a directory", @"Error indicating auto sort path isn't a directory"), autoSortShowPath];
990       
991        if(![fm constructPath:autoSortSeasonPath])
992                return [NSString stringWithFormat:BRLocalizedString(@"Cannot create directory at path %@", @"Error indicating auto sort path can't be created"), autoSortSeasonPath];
993       
994        SapphireDirectoryMetaData *dirMeta = [SapphireDirectoryMetaData createDirectoryWithPath:autoSortSeasonPath inContext:[self managedObjectContext]];
995        return [self moveToDir:dirMeta];
996}
997
998- (NSString *)fileName
999{
1000        NSString *ret = [self.path lastPathComponent];
1001        if(self.fileContainerTypeValue != FileContainerTypeVideoTS)
1002                ret = [ret stringByDeletingPathExtension];
1003       
1004        return ret;
1005}
1006
1007- (NSString *)extensionlessPath
1008{
1009        NSString *ret = self.path;
1010        if(self.fileContainerTypeValue != FileContainerTypeVideoTS)
1011                ret = [ret stringByDeletingPathExtension];
1012       
1013        return ret;
1014}
1015
1016- (NSMutableDictionary *)getDisplayedMetaDataInOrder:(NSArray * *)order;
1017{
1018        NSString *name = [[self path] lastPathComponent];
1019        NSString *durationStr = [self durationString];
1020        /*Set the order*/
1021        if(order != nil)
1022                *order = displayedMetaDataOrder;
1023       
1024        NSMutableDictionary *ret = [[NSMutableDictionary alloc] init];
1025       
1026        SapphireMovie *movie = [self movie];
1027        SapphireEpisode *ep = [self tvEpisode];
1028        if(movie != nil)
1029        {
1030                [movie insertDisplayMetaData:ret];
1031        }
1032        else if (ep != nil)
1033        {
1034                [ep insertDisplayMetaData:ret];
1035        }
1036       
1037        id value = [self videoDescription];
1038        if(value != nil)
1039        {
1040                NSString *valueString = (NSString *)value;
1041                NSMutableArray *valueArray = [[valueString componentsSeparatedByString:@"\n"] mutableCopy];
1042                [valueArray uniqueObjects];
1043                int count = [valueArray count];
1044                if(count > 1)
1045                {
1046                        NSString *first = nil;
1047                        int i;
1048                        for(i=0; i<count; i++)
1049                        {
1050                                NSString *trackName = [valueArray objectAtIndex:i];
1051                                if(![trackName hasPrefix:@"VobSub"])
1052                                {
1053                                        first = [[trackName retain] autorelease];
1054                                        [valueArray removeObjectAtIndex:i];
1055                                        break;
1056                                }
1057                        }
1058                        if(first == nil)
1059                        {
1060                                first = [[[valueArray objectAtIndex:0] retain] autorelease];
1061                                [valueArray removeObjectAtIndex:0];
1062                        }
1063                        [ret setObject:first forKey:VIDEO_DESC_LABEL_KEY];
1064                        [ret setObject:[valueArray componentsJoinedByString:@"\n"] forKey:VIDEO2_DESC_LABEL_KEY];
1065                }
1066                else
1067                        [ret setObject:value forKey:VIDEO_DESC_LABEL_KEY];
1068                [valueArray release];
1069        }
1070        value = [self audioDescription];
1071        if(value != nil)
1072        {
1073                NSString *valueString = (NSString *)value;
1074                NSMutableArray *valueArray = [[valueString componentsSeparatedByString:@"\n"] mutableCopy];
1075                [valueArray uniqueObjects];
1076                if([valueArray count] > 1)
1077                {
1078                        [ret setObject:[valueArray objectAtIndex:0] forKey:AUDIO_DESC_LABEL_KEY];
1079                        [valueArray removeObjectAtIndex:0];
1080                        [ret setObject:[valueArray componentsJoinedByString:@"\n"] forKey:AUDIO2_DESC_LABEL_KEY];
1081                }
1082                else
1083                        [ret setObject:value forKey:AUDIO_DESC_LABEL_KEY];
1084                [valueArray release];
1085        }
1086        value = [self subtitlesDescription];
1087        if(value != nil)
1088                [ret setObject:value forKey:META_FILE_SUBTITLES_KEY];
1089        if([self durationValue])
1090        {
1091                if([self sizeValue])
1092                {
1093                        int resumeTime = [self resumeTimeValue];
1094                        NSString *fullDurationString = [NSString stringWithFormat:@"%@ (%@)", durationStr, [self sizeString]];
1095                        if(resumeTime != 0)
1096                                fullDurationString = [fullDurationString stringByAppendingFormat:BRLocalizedString(@" %@ Remaining", @"Time left to display in preview pane next to file runtime"), [NSString colonSeparatedTimeStringForSeconds:[self durationValue] - resumeTime]];
1097                        [ret setObject:fullDurationString forKey:META_FILE_DURATION_KEY];
1098                }
1099                else
1100                        [ret setObject:durationStr forKey:META_FILE_DURATION_KEY];
1101        }
1102        else
1103                [ret setObject:[self sizeString] forKey:META_FILE_SIZE_KEY];
1104       
1105        /*Set the title*/
1106        if([ret objectForKey:META_TITLE_KEY] == nil)
1107                [ret setObject:name forKey:META_TITLE_KEY];
1108        return [ret autorelease];
1109}
1110
1111- (NSString *)searchShowName
1112{
1113        return self.xmlData.searchShowName;
1114}
1115
1116- (int)searchSeasonNumber
1117{
1118        NSNumber *value = self.xmlData.searchSeasonNumber;
1119        if(value != nil)
1120                return [value intValue];
1121        return -1;
1122}
1123
1124- (int)searchEpisodeNumber
1125{
1126        NSNumber *value = self.xmlData.searchEpisode;
1127        if(value != nil)
1128                return [value intValue];
1129        return -1;
1130}
1131
1132- (int)searchLastEpisodeNumber
1133{
1134        NSNumber *value = self.xmlData.searchLastEpisodeNumber;
1135        if(value != nil)
1136                return [value intValue];
1137        return -1;
1138}
1139
1140- (int)searchIMDBNumber
1141{
1142        NSNumber *value = self.xmlData.searchIMDBNumber;
1143        if(value != nil)
1144                return [value intValue];
1145        return -1;
1146}
1147
1148- (FileContainerType)fileContainerTypeValue
1149{
1150        return super.fileContainerTypeValue;
1151}
1152
1153- (ImportTypeMask)importTypeValue
1154{
1155        return super.importTypeValue;
1156}
1157
1158- (long)importedTimeFromSource:(int)source
1159{
1160        if(source == ImportTypeMaskFile)
1161                return [self.modified timeIntervalSince1970];
1162        else if(source == ImportTypeMaskXML)
1163                return [self.xmlData.modified timeIntervalSince1970];
1164        return 0;
1165}
1166
1167- (void)didImportType:(ImportTypeMask)type
1168{
1169        self.importTypeValue |= type;
1170}
1171
1172- (void)setMovie:(SapphireMovie *)movie
1173{
1174        SapphireMovie *oldMovie = self.movie;
1175        super.movie = movie;
1176        if([self isDeleted])
1177                return;
1178        if(movie != nil)
1179        {
1180                [self setFileClassValue:FileClassMovie];
1181                self.importTypeValue |= ImportTypeMaskMovie;
1182        }
1183        if(movie != oldMovie)
1184                self.xmlData.movie = movie;
1185}
1186
1187- (void)setTvEpisode:(SapphireEpisode *)ep
1188{
1189        SapphireEpisode *oldEp = self.tvEpisode;
1190        super.tvEpisode = ep;
1191        if([self isDeleted])
1192                return;
1193        if(ep != nil)
1194        {
1195                [self setFileClassValue:FileClassTVShow];
1196                self.importTypeValue |= ImportTypeMaskTVShow;
1197        }
1198        if(ep != oldEp)
1199                self.xmlData.episode = ep;
1200}
1201
1202- (void)setXmlData:(SapphireXMLData *)data
1203{
1204        super.xmlData = data;
1205        if([self isDeleted])
1206                return;
1207        if(data != nil)
1208        {
1209                data.episode = self.tvEpisode;
1210                data.movie = self.movie;
1211        }
1212}
1213
1214- (FileClass)fileClassValue
1215{
1216        FileClass xmlClass = self.xmlData.fileClassValue;
1217        if(xmlClass != FileClassUnknown)
1218                return xmlClass;
1219        return super.fileClassValue;
1220}
1221
1222- (void)setFileClassValue:(FileClass)fileClass
1223{
1224        FileClass xmlClass = self.xmlData.fileClassValue;
1225        if(xmlClass != FileClassUnknown)
1226                self.xmlData.fileClassValue = FileClassUnknown;
1227        super.fileClassValue = fileClass;
1228}
1229
1230- (void)setWatched:(NSNumber*)value_ {
1231        NSNumber *oldValue = [self.watched retain];
1232        super.watched = value_;
1233        if(![oldValue isEqualToNumber:value_])
1234        {
1235                self.resumeTime = nil;
1236                [self.parent clearPredicateCache];
1237                [self.tvEpisode clearPredicateCache];
1238                [self.movie clearPredicateCache];
1239        }
1240}
1241
1242- (void)setFavorite:(NSNumber*)value_ {
1243        NSNumber *oldValue = [self.favorite retain];
1244        super.favorite = value_;
1245        if(![oldValue isEqualToNumber:value_])
1246        {
1247                [self.parent clearPredicateCache];
1248                [self.tvEpisode clearPredicateCache];
1249                [self.movie clearPredicateCache];
1250        }
1251}
1252
1253@end
Note: See TracBrowser for help on using the repository browser.