source: trunk/SapphireMovieImporter.m @ 226

Revision 226, 15.8 KB checked in by pmerrill, 7 years ago (diff)
  • Implemented IMPAwards cover art fetching - Automated only.
  • Initial framework for Movie support in the browser
  • Initial framework for Movie support in the media preview
Line 
1//
2//  SapphireMovieImporter.m
3//  Sapphire
4//
5//  Created by Patrick Merrill on 9/10/07.
6//  Copyright 2007 www.nanopi.net. All rights reserved.
7//
8
9#import "SapphireMovieImporter.h"
10#import "SapphireMetaData.h"
11#import "NSString-Extensions.h"
12#import "SapphireMovieChooser.h"
13
14 /* IMDB XPATHS */
15#define IMDB_SEARCH_XPATH                               @"//td[starts-with(a/@href,'/title')]"
16#define IMDB_RESULT_LINK_XPATH                  @"a/@href"
17#define IMDB_RESULT_NAME_XPATH                  @"normalize-space(string())"
18#define IMDB_RESULT_TITLE_YEAR_XPATH    @"//div[@id='tn15title']/h1/replace(string(), '\n', '')"
19 
20 
21#define TRANSLATIONS_KEY                @"Translations"
22#define LINK_KEY                                @"Link"
23
24/*Delegate class to download cover art*/
25@interface SapphireMovieDataMenuDownloadDelegate : NSObject
26{
27        NSString *destination;
28        NSArray *requestList ;
29       
30}
31- (id)initWithRequest:(NSArray*)reqList withDestination:(NSString *)dest;
32-(void)getCoverArt ;
33@end
34
35@implementation SapphireMovieDataMenuDownloadDelegate
36/*!
37* @brief Initialize a cover art downloader
38 *
39 * @param reqList The list of url requests to try
40 * @param dest The path to save the file
41 */
42- (id)initWithRequest:(NSArray*)reqList withDestination:(NSString *)dest;
43{
44        self = [super init];
45        if(!self)
46                return nil;
47       
48        destination = [dest retain];
49        requestList = [reqList retain];
50       
51        return self;
52       
53}
54
55/*!
56* @brief Fire the delegate to start downloading
57 *
58 */
59-(void)getCoverArt
60{
61        NSEnumerator *reqEnum= [requestList objectEnumerator] ;
62        NSURLRequest *req=nil ;
63        while((req=[reqEnum nextObject]) !=nil)
64        {
65                NSURLDownload *currentDownload=[[NSURLDownload alloc] initWithRequest:req delegate:self] ;
66                [currentDownload release] ;
67        //      if(currentDownload)break;/*The download is going, no need to try another URL */
68        }
69       
70}
71
72
73- (void)dealloc
74{
75        [destination release];
76        [requestList release];
77        [super dealloc];
78}
79
80/*!
81* @brief Delegate Method which prompts for location to save file.  Override and set new
82 * destination
83 *
84 * @param download The downloader
85 * @param filename The suggested filename
86 */
87- (void)download:(NSURLDownload *)download decideDestinationWithSuggestedFilename:(NSString *)filename
88{
89
90        [download setDestination:destination allowOverwrite:NO];
91       
92}
93
94- (void)download:(NSURLDownload *)download didFailWithError:(NSError *)error
95{
96        [download release];
97//      NSString *failed=[[error userInfo] objectForKey:NSErrorFailingURLStringKey] ;
98}
99
100@end
101
102@interface SapphireMovieImporter (private)
103- (void)writeSettings;
104@end
105
106@implementation SapphireMovieImporter
107
108- (id) initWithSavedSetting:(NSString *)path
109{
110        self = [super init];
111        if(!self)
112                return nil;
113       
114        /*Get the settings*/
115        settingsPath = [path retain];
116        NSDictionary *settings = [NSDictionary dictionaryWithContentsOfFile:path];
117        /*Get or create the show translation dict*/
118        movieTranslations = [[settings objectForKey:TRANSLATIONS_KEY] mutableCopy];
119        if(movieTranslations == nil)
120                movieTranslations = [NSMutableDictionary new];
121        /*Cached show info*/
122        movieInfo = [NSMutableDictionary new];
123       
124        return self;
125}
126
127- (void)dealloc
128{
129        [dataMenu release];
130        [movieTranslations release];
131        [movieInfo release];
132        [settingsPath release];
133        [super dealloc];
134}
135
136/*!
137* @brief Sets the importer's data menu
138 *
139 * @param theDataMenu The importer's menu
140 */
141- (void)setImporterDataMenu:(SapphireImporterDataMenu *)theDataMenu
142{
143        [dataMenu release];
144        dataMenu = [theDataMenu retain];
145}
146
147/*!
148* @brief Fetch information for a movie
149 *
150 * @param movieName The IMDB name (part of the show's URL)
151 * @return A cached dictionary of the movie info
152 */
153- (NSMutableDictionary *)getMetaForMovie:(NSString *)movieName withPath:(NSString*)moviePath
154{
155        NSError *error = nil;
156        NSMutableDictionary *ret = [NSMutableDictionary dictionary];
157        /*Get the movie html*/
158        NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://www.IMDB.com%@",movieName]];
159        NSXMLDocument *document = [[NSXMLDocument alloc] initWithContentsOfURL:url options:NSXMLDocumentTidyHTML error:&error];
160       
161        /* Get the movie title */
162        NSString *movieTitle= [[document objectsForXQuery:IMDB_RESULT_TITLE_YEAR_XPATH error:&error] objectAtIndex:0];
163        int titleYear= 0 ;
164        NSString *shortenedMovieTitle=nil ;
165        NSString *coverArtLinkA=nil ;
166        NSString *coverArtLinkB=nil ;
167        NSString *coverArtSavePath=nil ;
168        NSCharacterSet *decimalSet = [NSCharacterSet decimalDigitCharacterSet];
169        NSCharacterSet *skipSet = [NSCharacterSet characterSetWithCharactersInString:@"("];
170        NSScanner *titleScan= [NSScanner scannerWithString:movieTitle] ;
171       
172        /* Cover Art Processing */
173        [titleScan scanUpToCharactersFromSet:skipSet intoString:&shortenedMovieTitle];
174        [titleScan scanUpToCharactersFromSet:decimalSet intoString:nil];
175        [titleScan scanInt:&titleYear];
176       
177        /* Remove punctuation */
178        shortenedMovieTitle=[shortenedMovieTitle stringByReplacingAllOccurancesOf:@":" withString:@""];
179        shortenedMovieTitle=[shortenedMovieTitle stringByReplacingAllOccurancesOf:@"'" withString:@""];
180        shortenedMovieTitle=[shortenedMovieTitle stringByReplacingAllOccurancesOf:@"," withString:@""];
181        shortenedMovieTitle=[[shortenedMovieTitle stringByReplacingAllOccurancesOf:@" " withString:@"_"] lowercaseString];
182       
183        /*Convert roman to verbage*/
184        shortenedMovieTitle=[shortenedMovieTitle stringByReplacingAllOccurancesOf:@"_i_"        withString:@"_one_"] ;
185        shortenedMovieTitle=[shortenedMovieTitle stringByReplacingAllOccurancesOf:@"_ii_"       withString:@"_two_"] ;
186        shortenedMovieTitle=[shortenedMovieTitle stringByReplacingAllOccurancesOf:@"_iii_"      withString:@"_three_"] ;
187        shortenedMovieTitle=[shortenedMovieTitle stringByReplacingAllOccurancesOf:@"_iv_"       withString:@"_four_"] ;
188        shortenedMovieTitle=[shortenedMovieTitle stringByReplacingAllOccurancesOf:@"_v_"        withString:@"_five_"] ;
189        shortenedMovieTitle=[shortenedMovieTitle stringByReplacingAllOccurancesOf:@"_1_"        withString:@"_one_"] ;
190        shortenedMovieTitle=[shortenedMovieTitle stringByReplacingAllOccurancesOf:@"_2_"        withString:@"_two_"] ;
191        shortenedMovieTitle=[shortenedMovieTitle stringByReplacingAllOccurancesOf:@"_3_"        withString:@"_three_"] ;
192        shortenedMovieTitle=[shortenedMovieTitle stringByReplacingAllOccurancesOf:@"_4_"        withString:@"_four_"] ;
193        shortenedMovieTitle=[shortenedMovieTitle stringByReplacingAllOccurancesOf:@"_5_"        withString:@"_five_"] ;
194       
195        /* Symbol Replacements */
196        shortenedMovieTitle=[shortenedMovieTitle stringByReplacingAllOccurancesOf:@"&"  withString:@"_and_"] ;
197        shortenedMovieTitle=[shortenedMovieTitle stringByReplacingAllOccurancesOf:@"®"  withString:@"ae"] ;
198               
199        /* Remove leading 'the' and 'a' */
200        if([shortenedMovieTitle hasPrefix:@"the_"])shortenedMovieTitle=[shortenedMovieTitle substringFromIndex:4];
201        else if([shortenedMovieTitle hasPrefix:@"a_"])shortenedMovieTitle=[shortenedMovieTitle substringFromIndex:2];
202        coverArtLinkA=[NSString stringWithFormat:@"http://www.impawards.com/%d/posters/%@ver1.jpg",titleYear,shortenedMovieTitle] ;
203        coverArtLinkB=[coverArtLinkA stringByReplacingAllOccurancesOf:@"_ver1" withString:@""];
204        coverArtSavePath=[[[moviePath stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"Cover Art"] stringByAppendingPathComponent:[[[moviePath lastPathComponent] stringByDeletingPathExtension] stringByAppendingPathExtension:@"jpg"]];
205        error = nil;
206        BOOL isDir = NO;
207        BOOL imageExists = [[NSFileManager defaultManager] fileExistsAtPath:coverArtSavePath isDirectory:&isDir] && !isDir;
208        if(!imageExists)/*Get the screen cap*/
209        {
210                NSArray *requestURLList=[[NSArray alloc]  initWithObjects:
211                        [NSURLRequest requestWithURL:[NSURL URLWithString:coverArtLinkA]
212                                                         cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:5.0],
213                        [NSURLRequest requestWithURL:[NSURL URLWithString:coverArtLinkB]
214                                cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:5.0],
215                        nil];
216               
217
218//              SapphireMovieDataMenuDownloadDelegate *myDelegate= [[SapphireMovieDataMenuDownloadDelegate alloc] initWithRequest:requestURLList withDestination:coverArtSavePath];             
219//              [[NSFileManager defaultManager] createDirectoryAtPath:[coverArtSavePath stringByDeletingLastPathComponent] attributes:nil];
220//              [myDelegate getCoverArt];
221//              [[NSURLDownload alloc] initWithRequest:[requestURLList objectAtIndex:0] delegate:myDelegate];
222//              [myDelegate release];
223        }
224               
225        /* Dump XML document to disk (Dev Only) */
226//      NSString *documentPath =[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/Sapphire/XML"];
227//      [[document XMLDataWithOptions:NSXMLNodePrettyPrint] writeToFile:[NSString stringWithFormat:@"/%@/%@_title_result.xml",documentPath,movieTitle] atomically:YES] ;
228       
229       
230       
231        /* populate metadata to return */
232        [ret setObject:movieTitle forKey:META_MOVIE_TITLE_KEY];
233        return ret;
234}
235
236
237
238/*!
239* @brief Searches for a movie based on the filename
240 *
241 * @param searchStr Part of the filename to use in the show search
242 * @return An array of possible results
243 */
244- (NSArray *)searchResultsForMovie:(NSString *)searchStr
245{
246        /* prep the search string */
247        searchStr = [searchStr stringByDeletingPathExtension];
248        searchStr = [searchStr stringByReplacingAllOccurancesOf:@"_" withString:@" "];
249        searchStr = [searchStr stringByReplacingAllOccurancesOf:@"." withString:@" "];
250        searchStr = [searchStr stringByReplacingAllOccurancesOf:@"-" withString:@" "];
251        NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://www.imdb.com/find?s=all&q=%@", [searchStr URLEncode]]];
252        NSError *error = nil;
253        NSXMLDocument *document = [[NSXMLDocument alloc] initWithContentsOfURL:url options:NSXMLDocumentTidyHTML error:&error];
254        /* Dump XML document to disk (Dev Only) */
255        //NSString *documentPath =[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/Sapphire/XML"];
256        //[[document XMLDataWithOptions:NSXMLNodePrettyPrint] writeToFile:[NSString stringWithFormat:@"%@/%@_search_result.xml",documentPath,searchStr] atomically:YES] ;
257       
258        NSXMLElement *root = [document rootElement];
259       
260        NSString *resultTitle=[[[root objectsForXQuery:@"//title" error:&error]objectAtIndex:0] stringValue];
261       
262        if([resultTitle isEqualToString:@"IMDb Search"])/*Make sure we didn't get back a unique result */
263        {
264                /*Get the results list*/
265                NSArray *results = [root objectsForXQuery:IMDB_SEARCH_XPATH error:&error];
266
267                //Need to clean out (VG) entries <Video Game>
268                NSMutableArray *ret = [NSMutableArray arrayWithCapacity:[results count]];
269                if([results count])
270                {
271                        /*Get each result*/
272                        NSEnumerator *resultEnum = [results objectEnumerator];
273                        NSXMLElement *result = nil;
274                        while((result = [resultEnum nextObject]) != nil)
275                        {
276                                /*Add the result to the list*/
277
278                                NSURL *resultURL = [NSURL URLWithString:[[[result objectsForXQuery:IMDB_RESULT_LINK_XPATH error:&error] objectAtIndex:0] stringValue]] ;
279                       
280                                if(resultURL == nil)
281                                        continue;
282                                {
283                                        [ret addObject:[NSDictionary dictionaryWithObjectsAndKeys:
284                                        [[result objectsForXQuery:IMDB_RESULT_NAME_XPATH error:&error] objectAtIndex:0], @"name",
285                                        [resultURL path], @"link",
286                                        nil]];
287                                }
288                        }
289                        return ret;
290                }
291        }
292        else /* IMDB directly linked to a unique movie title */
293        {
294                NSMutableArray *ret = [NSMutableArray arrayWithCapacity:1];
295                [ret addObject:[NSDictionary dictionaryWithObjectsAndKeys:
296                resultTitle, @"name",
297                [[url relativeString] stringByReplacingAllOccurancesOf:@"http://www.imdb.com" withString:@""], @"link",
298                nil]];
299                return ret ;
300        }
301        /*No results found*/
302        return nil;
303}
304
305
306/*!
307* @brief Write our setings out
308 */
309- (void)writeSettings
310{
311        NSDictionary *settings = [NSDictionary dictionaryWithObjectsAndKeys:
312                movieTranslations, TRANSLATIONS_KEY,
313                nil];
314        [settings writeToFile:settingsPath atomically:YES];
315}
316
317/*!
318* @brief verify file extention of a file
319 *
320 * @param filePAth The file's path
321 * @return YES if candidate, NO otherwise
322 */
323- (BOOL)isMovieCandidate:(NSString*)fileExt
324{
325        if([[SapphireMetaData videoExtensions] member:fileExt])
326                return YES;
327        else return NO ;
328}
329
330/*!
331* @brief Import a single File
332 *
333 * @param metaData The file to import
334 * @return YES if imported, NO otherwise
335 */
336- (BOOL) importMetaData:(SapphireFileMetaData *)metaData
337{
338        currentData = metaData;
339        /*Check to see if it is already imported*/
340        if([metaData importedTimeFromSource:META_IMDB_IMPORT_KEY])
341                return NO;
342        /*Get path*/
343        NSString *path = [metaData path];
344        if(![self isMovieCandidate:[path pathExtension]])
345                return NO;
346        /*Get fineName*/
347        NSString *fileName = [path lastPathComponent];
348       
349        if([metaData fileClass]==FILE_CLASS_TV_SHOW) /* File is a TV Show - skip it */
350                return NO ;
351       
352        /*Get the movie title*/
353        NSString *searchStr = fileName;
354        /*Check to see if we know this movie*/
355        NSString *movie = [movieTranslations objectForKey:[searchStr lowercaseString]];
356        if(movie == nil)
357        {
358                /*Ask the user what movie this is*/
359                NSArray *movies = [self searchResultsForMovie:searchStr];
360                /*Pause for the user's input*/
361                [dataMenu pause];
362                /*Bring up the prompt*/
363                SapphireMovieChooser *chooser = [[SapphireMovieChooser alloc] initWithScene:[dataMenu scene]];
364                [chooser setMovies:movies];
365                [chooser setFileName:fileName];         
366                [chooser setListTitle:BRLocalizedString(@"Select Movie Title", @"Prompt the user for title of movie")];
367                [chooser setSearchStr:searchStr];
368                /*And display prompt*/
369                [[dataMenu stack] pushController:chooser];
370                [chooser release];
371                return NO ;
372                //Data will be ready for access on the next exe
373        }
374       
375        /*Import the info*/
376        NSMutableDictionary *info = nil;
377        info = [self getMetaForMovie:movie withPath:path];
378        if(!info)
379                return NO;     
380        [info removeObjectForKey:LINK_KEY];
381        [metaData importInfo:info fromSource:META_IMDB_IMPORT_KEY withTime:[[NSDate date] timeIntervalSince1970]];
382        [metaData setFileClass:FILE_CLASS_MOVIE];
383       
384        /*We imported something*/
385        return YES;
386}
387
388
389/*!
390* @brief The completion text to display
391 *
392 * @return The completion text to display
393 */
394- (NSString *)completionText
395{
396        return BRLocalizedString(@"All availble Movie data has been imported", @"The Movie import is complete");
397}
398
399/*!
400* @brief The initial text to display
401 *
402 * @return The initial text to display
403 */
404- (NSString *)initialText
405{
406        return BRLocalizedString(@"Movie Meta Data", @"Title");
407}
408
409/*!
410* @brief The informative text to display
411 *
412 * @return The informative text to display
413 */
414- (NSString *)informativeText
415{
416        return BRLocalizedString(@"This tool will attempt to fetch information about your Movie files from the Internet (IMDB/IMPAwards).  This procedure may take quite some time and could ask you questions.  You may cancel at any time.", @"Description of the movie import");
417}
418
419/*!
420* @brief The button title
421 *
422 * @return The button title
423 */
424- (NSString *)buttonTitle
425{
426        return BRLocalizedString(@"Start Fetching Data", @"Button");
427}
428
429/*!
430* @brief The data menu was exhumed
431 *
432 * @param controller The Controller which was on top
433 */
434- (void) wasExhumedByPoppingController: (BRLayerController *) controller
435{
436        /*See if it was a movie chooser*/
437        if(![controller isKindOfClass:[SapphireMovieChooser class]])
438                return;
439       
440        /*Get the user's selection*/
441        SapphireMovieChooser *chooser = (SapphireMovieChooser *)controller;
442        int selection = [chooser selection];
443        if(selection == MOVIE_CHOOSE_CANCEL)
444                /*They aborted, skip*/
445                [dataMenu skipNextItem];
446        else if(selection == MOVIE_CHOOSE_NOT_MOVIE)
447        {
448                /*They said it is not a movie, so put in empty data so they are not asked again*/
449                [currentData importInfo:[NSMutableDictionary dictionary] fromSource:META_IMDB_IMPORT_KEY withTime:[[NSDate date] timeIntervalSince1970]];
450                if ([currentData fileClass] != FILE_CLASS_TV_SHOW)
451                        [currentData setFileClass:FILE_CLASS_UNKNOWN];
452        }
453        else if(selection==MOVIE_CHOOSE_OTHER)
454        {
455                [currentData importInfo:[NSMutableDictionary dictionary] fromSource:META_IMDB_IMPORT_KEY withTime:[[NSDate date] timeIntervalSince1970]];
456                [currentData setFileClass:FILE_CLASS_OTHER] ;
457        }
458        else if(selection==MOVIE_CHOOSE_TV_SHOW)
459        {
460                [currentData importInfo:[NSMutableDictionary dictionary] fromSource:META_IMDB_IMPORT_KEY withTime:[[NSDate date] timeIntervalSince1970]];
461                [currentData setFileClass:FILE_CLASS_TV_SHOW] ;
462        }
463        else
464        {
465                /*They selected a movie title, save the translation and write it*/
466                NSDictionary *movie = [[chooser movies] objectAtIndex:selection];
467                [movieTranslations setObject:[movie objectForKey:@"link"] forKey:[[chooser searchStr] lowercaseString]];
468                [self writeSettings];
469        }
470        /*We can resume now*/
471        [dataMenu resume];
472}
473
474@end
Note: See TracBrowser for help on using the repository browser.