OSDN Git Service

cc0e7382617939bc76d73279ff77c760c1b4dafd
[handbrake-jp/handbrake-jp-git.git] / macosx / HBPreviewController.m
1 /* $Id: HBPreviewController.mm,v 1.11 2005/08/01 15:10:44 titer Exp $
2
3    This file is part of the HandBrake source code.
4    Homepage: <http://handbrake.fr/>.
5    It may be used under the terms of the GNU General Public License. */
6
7 #import "HBPreviewController.h"
8 #import "Controller.h"
9
10 @implementation QTMovieView ( HBQTkitExt )
11 - (void) mouseMoved:(NSEvent *)theEvent
12 {
13     [super mouseMoved:theEvent];
14 }
15 @end
16
17 @interface PreviewController (Private)
18
19 - (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize;
20 - (void)resizeSheetForViewSize: (NSSize)viewSize;
21 - (void)setViewSize: (NSSize)viewSize;
22 - (BOOL)viewNeedsToResizeToSize: (NSSize)newSize;
23
24 @end
25
26 @implementation PreviewController
27
28 - (id)init
29 {
30         if (self = [super initWithWindowNibName:@"PicturePreview"])
31         {
32         // NSWindowController likes to lazily load its window. However since
33         // this controller tries to set all sorts of outlets before the window
34         // is displayed, we need it to load immediately. The correct way to do
35         // this, according to the documentation, is simply to invoke the window
36         // getter once.
37         //
38         // If/when we switch a lot of this stuff to bindings, this can probably
39         // go away.
40         [self window];
41         
42                 fPicturePreviews = [[NSMutableDictionary dictionaryWithCapacity: HB_NUM_HBLIB_PICTURES] retain];
43         /* Init libhb with check for updates libhb style set to "0" so its ignored and lets sparkle take care of it */
44         int loggingLevel = [[[NSUserDefaults standardUserDefaults] objectForKey:@"LoggingLevel"] intValue];
45         fPreviewLibhb = hb_init(loggingLevel, 0);
46         
47         }
48         return self;
49 }
50
51
52
53 //------------------------------------------------------------------------------------
54 // Displays and brings the picture window to the front
55 //------------------------------------------------------------------------------------
56 - (IBAction) showPreviewWindow: (id)sender
57 {
58     [self showWindow:sender];
59     [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"PreviewWindowIsOpen"];
60     
61     /* lets set the preview window to accept mouse moved events */
62     [fPreviewWindow setAcceptsMouseMovedEvents:YES];
63     hudTimerSeconds = 0;
64     [self pictureSliderChanged:nil];
65     [self startReceivingLibhbNotifications];
66 }
67
68 - (void)setHBController: (HBController *)controller
69 {
70     fHBController = controller;
71 }
72
73 - (void)awakeFromNib
74 {
75     [fPreviewWindow setDelegate:self];
76     if( ![[self window] setFrameUsingName:@"Preview"] )
77         [[self window] center];
78     [self setWindowFrameAutosaveName:@"Preview"];
79     [[self window] setExcludedFromWindowsMenu:YES];
80     
81     /* lets set the preview window to accept mouse moved events */
82     [fPreviewWindow setAcceptsMouseMovedEvents:YES];
83     //[self pictureSliderChanged:nil];
84     [self startReceivingLibhbNotifications];
85     
86     hudTimerSeconds = 0;
87     /* we set the progress indicator to not use threaded animation
88      * as it causes a conflict with the qtmovieview's controllerbar
89     */
90     [fMovieCreationProgressIndicator setUsesThreadedAnimation:NO];
91     
92     /* Setup our layers for core animation */
93     [fPictureViewArea setWantsLayer:YES];
94     [fPictureView setWantsLayer:YES];
95
96     [fCancelPreviewMovieButton setWantsLayer:YES];
97     [fMovieCreationProgressIndicator setWantsLayer:YES];
98
99     [fPictureControlBox setWantsLayer:YES];
100     [fEncodingControlBox setWantsLayer:YES];
101         [fMovieView setWantsLayer:YES];
102         [fMovieView setHidden:YES];
103     [fMovieView setDelegate:self];
104
105     /* Since the xib has everything off center for easy acess
106      * we align our views and windows here we an align to anything
107      * since it will actually change later upon source load, but
108      * for convenience we will use the fPictureViewArea
109      */
110      
111      /* Align the still preview image view to the picture box */
112      [fPictureView setFrameSize:[fPictureViewArea frame].size];
113      [fMovieView setFrameSize:[fPictureViewArea frame].size];
114      //[fPreviewWindow setFrameSize:[fPictureViewArea frame].size];
115     
116     
117 }
118 - (BOOL)acceptsMouseMovedEvents
119 {
120     return YES;
121 }
122
123 - (void)windowWillClose:(NSNotification *)aNotification
124 {
125     /* Upon Closing the picture window, we make sure we clean up any
126      * preview movie that might be playing
127      */
128     hb_stop( fPreviewLibhb );
129     isEncoding = NO;
130     // Show the picture view
131     [fPictureView setHidden:NO];
132     [fMovieView pause:nil];
133     [fMovieView setHidden:YES];
134         [fMovieView setMovie:nil];
135
136     hudTimerSeconds = 0;
137     [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"PreviewWindowIsOpen"];
138 }
139
140 - (BOOL)windowShouldClose:(id)fPictureWindow
141 {
142      return YES;
143 }
144
145 - (void) dealloc
146 {
147     hb_stop(fPreviewLibhb);
148     if (fPreviewMoviePath)
149     {
150         [[NSFileManager defaultManager] removeFileAtPath:fPreviewMoviePath handler:nil];
151         [fPreviewMoviePath release];
152     }    
153     
154     [fLibhbTimer invalidate];
155     [fLibhbTimer release];
156     
157     [fHudTimer invalidate];
158     [fHudTimer release];
159     
160     [fPicturePreviews release];
161     [fFullScreenWindow release];
162
163     [super dealloc];
164 }
165
166 - (void) SetHandle: (hb_handle_t *) handle
167 {
168     fHandle = handle;
169     
170
171     
172     /* we set the preview length popup in seconds */
173     [fPreviewMovieLengthPopUp removeAllItems];
174     [fPreviewMovieLengthPopUp addItemWithTitle: @"5"];
175     [fPreviewMovieLengthPopUp addItemWithTitle: @"10"];
176     [fPreviewMovieLengthPopUp addItemWithTitle: @"15"];
177     [fPreviewMovieLengthPopUp addItemWithTitle: @"20"];
178     [fPreviewMovieLengthPopUp addItemWithTitle: @"25"];
179     [fPreviewMovieLengthPopUp addItemWithTitle: @"30"];
180     [fPreviewMovieLengthPopUp addItemWithTitle: @"35"];
181     [fPreviewMovieLengthPopUp addItemWithTitle: @"40"];
182     [fPreviewMovieLengthPopUp addItemWithTitle: @"45"];
183     [fPreviewMovieLengthPopUp addItemWithTitle: @"50"];
184     [fPreviewMovieLengthPopUp addItemWithTitle: @"55"];
185     [fPreviewMovieLengthPopUp addItemWithTitle: @"60"];
186     
187     /* adjust the preview slider length */
188     /* We use our advance pref to determine how many previews we scanned */
189     int hb_num_previews = [[[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewsNumber"] intValue];
190     [fPictureSlider setMaxValue: hb_num_previews - 1.0];
191     [fPictureSlider setNumberOfTickMarks: hb_num_previews];
192     
193     if ([[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewLength"])
194     {
195         [fPreviewMovieLengthPopUp selectItemWithTitle:[[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewLength"]];
196     }
197     else
198     {
199         /* currently hard set default to 10 seconds */
200         [fPreviewMovieLengthPopUp selectItemAtIndex: 1];
201     }
202 }
203
204 - (void) SetTitle: (hb_title_t *) title
205 {
206     hb_job_t * job = title->job;
207     
208     fTitle = title;
209     fPicture = 0;
210     MaxOutputWidth = title->width - job->crop[2] - job->crop[3];
211     MaxOutputHeight = title->height - job->crop[0] - job->crop[1];
212     [self SettingsChanged: nil];
213
214 }
215
216
217
218 // Adjusts the window to draw the current picture (fPicture) adjusting its size as
219 // necessary to display as much of the picture as possible.
220 - (void) displayPreview
221 {
222     hb_job_t * job = fTitle->job;
223     /* lets make sure that the still picture view is not hidden and that 
224      * the movie preview is 
225      */
226     [fMovieView pause:nil];
227     [fMovieView setHidden:YES];
228         [fMovieView setMovie:nil];
229     [fMovieCreationProgressIndicator stopAnimation: nil];
230     [fMovieCreationProgressIndicator setHidden: YES];
231     
232     [fPictureView setHidden:NO];
233     
234     NSImage *fPreviewImage = [self imageForPicture: fPicture];
235     NSSize imageScaledSize = [fPreviewImage size];
236     [fPictureView setImage: fPreviewImage];
237     
238     NSSize displaySize = NSMakeSize( ( CGFloat )fTitle->width, ( CGFloat )fTitle->height );
239     NSString *sizeInfoString;
240     /* Set the picture size display fields below the Preview Picture*/
241     if( fTitle->job->anamorphic.mode == 1 ) // Original PAR Implementation
242     {
243         output_width = fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3];
244         output_height = fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1];
245         display_width = output_width * fTitle->job->anamorphic.par_width / fTitle->job->anamorphic.par_height;
246         sizeInfoString = [NSString stringWithFormat:
247                           @"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d Strict",
248                           fTitle->width, fTitle->height, output_width, output_height, display_width, output_height];
249         
250         displaySize.width = display_width;
251         displaySize.height = fTitle->height;
252         imageScaledSize.width = display_width;
253         imageScaledSize.height = output_height;   
254     }
255     else if (fTitle->job->anamorphic.mode == 2) // Loose Anamorphic
256     {
257         hb_set_anamorphic_size(job, &output_width, &output_height, &output_par_width, &output_par_height);
258         display_width = output_width * output_par_width / output_par_height;
259         sizeInfoString = [NSString stringWithFormat:
260                           @"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d Loose",
261                           fTitle->width, fTitle->height, output_width, output_height, display_width, output_height];
262         
263         displaySize.width = display_width;
264         displaySize.height = fTitle->height;
265         imageScaledSize.width = display_width;
266         imageScaledSize.height = output_height;
267     }
268     else if (fTitle->job->anamorphic.mode == 3) // Custom Anamorphic
269     {
270         hb_set_anamorphic_size(job, &output_width, &output_height, &output_par_width, &output_par_height);
271         display_width = output_width * output_par_width / output_par_height;
272         sizeInfoString = [NSString stringWithFormat:
273                           @"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d Custom",
274                           fTitle->width, fTitle->height, output_width, output_height, fTitle->job->anamorphic.dar_width, fTitle->job->anamorphic.dar_height];
275         
276         displaySize.width = fTitle->job->anamorphic.dar_width + fTitle->job->crop[2] + fTitle->job->crop[3];
277         displaySize.height = fTitle->job->anamorphic.dar_height + fTitle->job->crop[0] + fTitle->job->crop[1];
278         imageScaledSize.width = (int)fTitle->job->anamorphic.dar_width;
279         imageScaledSize.height = (int)fTitle->job->height;   
280     } 
281     else // No Anamorphic
282     {
283         sizeInfoString = [NSString stringWithFormat:
284                           @"Source: %dx%d, Output: %dx%d", fTitle->width, fTitle->height,
285                           fTitle->job->width, fTitle->job->height];
286         /* original
287         displaySize.width = fTitle->width;
288         displaySize.height = fTitle->height;
289         imageScaledSize.width = fTitle->job->width;
290         imageScaledSize.height = fTitle->job->height;
291         */
292         /* Test ... */
293         displaySize.width = fTitle->width;
294         displaySize.height = fTitle->height;
295         imageScaledSize.width = fTitle->job->width + fTitle->job->crop[2] + fTitle->job->crop[3];
296         imageScaledSize.height = fTitle->job->height + fTitle->job->crop[0] - fTitle->job->crop[1];
297     }
298     
299     NSSize viewSize = [self optimalViewSizeForImageSize:displaySize];
300     
301         /* we also need to take into account scaling to full screen to activate switching the view size */
302     if( [self viewNeedsToResizeToSize:viewSize])
303     {
304         if (fTitle->job->anamorphic.mode != 2 || (fTitle->job->anamorphic.mode == 2 && fTitle->width == fTitle->job->width))
305         {
306             [self resizeSheetForViewSize:viewSize];
307             [self setViewSize:viewSize];
308             
309         }
310     }   
311     
312     viewSize.width = viewSize.width - (viewSize.width - imageScaledSize.width);
313     viewSize.height = viewSize.height - (viewSize.height - imageScaledSize.height);
314     [self setViewSize:viewSize];
315     
316     /* special case for scaleToScreen */
317     if (scaleToScreen == YES)
318     {
319         [fPreviewImage setSize: viewSize];
320         [fPictureView setImage: fPreviewImage];
321     }
322     
323     NSString *scaleString;
324     CGFloat scale = ( ( CGFloat )[fPictureView frame].size.width) / ( ( CGFloat )imageScaledSize.width);
325     if (scale * 100.0 != 100)
326     {
327         //CGFloat scale = ( ( CGFloat )[fPictureView frame].size.width) / ( ( CGFloat )imageScaledSize.width);        
328         scaleString = [NSString stringWithFormat:
329                        NSLocalizedString( @" (%.0f%% actual size)",
330                                          @"String shown when a preview is scaled" ), scale * 100.0];
331     }
332     else
333     {
334         scaleString = @"(Actual size)";
335     }
336     /* Set the info fields in the hud controller */
337     [fInfoField setStringValue: [NSString stringWithFormat:
338                                  @"%@", sizeInfoString]];
339     
340     [fscaleInfoField setStringValue: [NSString stringWithFormat:
341                                       @"%@", scaleString]];
342     /* Set the info field in the window title bar */
343     [[self window] setTitle:[NSString stringWithFormat: @"Preview - %@ %@",sizeInfoString, scaleString]];
344 }
345
346 - (IBAction) previewDurationPopUpChanged: (id) sender
347 {
348     
349     [[NSUserDefaults standardUserDefaults] setObject:[fPreviewMovieLengthPopUp titleOfSelectedItem] forKey:@"PreviewLength"];
350     
351 }    
352     
353 - (IBAction) SettingsChanged: (id) sender
354 {
355          // Purge the existing picture previews so they get recreated the next time
356         // they are needed.
357         [self purgeImageCache];
358         [self pictureSliderChanged:nil];
359 }
360
361 - (IBAction) pictureSliderChanged: (id) sender
362 {
363     // Show the picture view
364     [fPictureView setHidden:NO];
365     [fMovieView pause:nil];
366     [fMovieView setHidden:YES];
367         [fMovieView setMovie:nil];
368     [fEncodingControlBox setHidden: YES];
369     
370     int newPicture = [fPictureSlider intValue];
371     if (newPicture != fPicture)
372     {
373         fPicture = newPicture;
374     }
375     [self displayPreview];
376     
377 }
378
379 - (IBAction)showPreviewPanel: (id)sender forTitle: (hb_title_t *)title
380 {
381     if ([fPreviewWindow isVisible])
382     {
383         [fPreviewWindow close];
384     }
385     else
386     {
387         [self showWindow:sender];
388         [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"PreviewWindowIsOpen"];
389         [fPreviewWindow setAcceptsMouseMovedEvents:YES];
390         scaleToScreen = NO;
391         [self pictureSliderChanged:nil];
392     }
393     
394 }
395
396 - (NSString*) pictureSizeInfoString
397 {
398     return [fInfoField stringValue];
399 }
400
401 - (IBAction)showPictureSettings:(id)sender
402 {
403     [fHBController showPicturePanel:self];
404 }
405
406 #pragma mark Hud Control Overlay
407 - (void) mouseMoved:(NSEvent *)theEvent
408 {
409     [super mouseMoved:theEvent];
410     NSPoint mouseLoc = [theEvent locationInWindow];
411     
412     /* Test for mouse location to show/hide hud controls */
413     if( isEncoding == NO ) {
414         if( NSPointInRect( mouseLoc, [fPictureControlBox frame] ) )
415         {
416             [[fPictureControlBox animator] setHidden: NO];
417             [self stopHudTimer];
418         }
419                 else if( NSPointInRect( mouseLoc, [fPictureViewArea frame] ) )
420         {
421             [[fPictureControlBox animator] setHidden: NO];
422             [self startHudTimer];
423         }
424         else
425             [[fPictureControlBox animator] setHidden: YES];
426         }
427 }
428
429 - (void) startHudTimer
430 {
431         if( fHudTimer ) {
432                 [fHudTimer invalidate];
433                 [fHudTimer release];
434         }
435     fHudTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(hudTimerFired:) userInfo:nil repeats:YES];
436     [fHudTimer retain];
437 }
438
439 - (void) stopHudTimer
440 {
441     if( fHudTimer )
442     {
443         [fHudTimer invalidate];
444         [fHudTimer release];
445         fHudTimer = nil;
446         hudTimerSeconds = 0;
447     }
448 }
449
450 - (void) hudTimerFired: (NSTimer*)theTimer
451 {
452     hudTimerSeconds++;
453     if( hudTimerSeconds >= 10 ) {
454         [[fPictureControlBox animator] setHidden: YES];
455         [self stopHudTimer];
456     }
457 }
458
459
460
461 - (IBAction)toggleScaleToScreen:(id)sender
462 {
463     if (scaleToScreen == YES)
464     {
465         scaleToScreen = NO;
466         /* make sure we are set to a still preview */
467         [self pictureSliderChanged:nil];
468         [fScaleToScreenToggleButton setTitle:@"Scale To Screen"];
469     }
470     else
471     {
472         scaleToScreen = YES;
473         /* make sure we are set to a still preview */
474         [self pictureSliderChanged:nil];
475         [fScaleToScreenToggleButton setTitle:@"Actual Scale"];
476     }
477     
478 }
479
480
481
482 // Title-less windows normally don't receive key presses, override this
483 - (BOOL)canBecomeKeyWindow
484 {
485     return YES;
486 }
487
488 // Title-less windows normally can't become main which means that another
489 // non-fullscreen window will have the "active" titlebar in expose. Bad, fix it.
490 - (BOOL)canBecomeMainWindow
491 {
492     return YES;
493 }
494
495
496
497
498
499 #pragma mark Still Preview Image Processing
500
501
502 // This function converts an image created by libhb (specified via pictureIndex) into
503 // an NSImage suitable for the GUI code to use. If removeBorders is YES,
504 // makeImageForPicture crops the image generated by libhb stripping off the gray
505 // border around the content. This is the low-level method that generates the image.
506 // -imageForPicture calls this function whenever it can't find an image in its cache.
507 + (NSImage *) makeImageForPicture: (int)pictureIndex
508                 libhb:(hb_handle_t*)handle
509                 title:(hb_title_t*)title
510 {
511     static uint8_t * buffer;
512     static int bufferSize;
513
514     // Make sure we have a big enough buffer to receive the image from libhb. libhb
515     int dstWidth = title->job->width;
516     int dstHeight = title->job->height;
517         
518     int newSize;
519     newSize = dstWidth * dstHeight * 4;
520     if( bufferSize < newSize )
521     {
522         bufferSize = newSize;
523         buffer     = (uint8_t *) realloc( buffer, bufferSize );
524     }
525
526     hb_get_preview( handle, title, pictureIndex, buffer );
527
528     // Create an NSBitmapImageRep and copy the libhb image into it, converting it from
529     // libhb's format to one suitable for NSImage. Along the way, we'll strip off the
530     // border around libhb's image.
531         
532     // The image data returned by hb_get_preview is 4 bytes per pixel, BGRA format.
533     // Alpha is ignored.
534         
535     NSBitmapFormat bitmapFormat = (NSBitmapFormat)NSAlphaFirstBitmapFormat;
536     NSBitmapImageRep * imgrep = [[[NSBitmapImageRep alloc]
537             initWithBitmapDataPlanes:nil
538             pixelsWide:dstWidth
539             pixelsHigh:dstHeight
540             bitsPerSample:8
541             samplesPerPixel:3   // ignore alpha
542             hasAlpha:NO
543             isPlanar:NO
544             colorSpaceName:NSCalibratedRGBColorSpace
545             bitmapFormat:bitmapFormat
546             bytesPerRow:dstWidth * 4
547             bitsPerPixel:32] autorelease];
548
549     UInt32 * src = (UInt32 *)buffer;
550     UInt32 * dst = (UInt32 *)[imgrep bitmapData];
551     int r, c;
552     for (r = 0; r < dstHeight; r++)
553     {
554         for (c = 0; c < dstWidth; c++)
555 #if TARGET_RT_LITTLE_ENDIAN
556             *dst++ = Endian32_Swap(*src++);
557 #else
558             *dst++ = *src++;
559 #endif
560     }
561
562     NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(dstWidth, dstHeight)] autorelease];
563     [img addRepresentation:imgrep];
564
565     return img;
566 }
567
568 // Returns the preview image for the specified index, retrieving it from its internal
569 // cache or by calling makeImageForPicture if it is not cached. Generally, you should
570 // use imageForPicture so that images are cached. Calling makeImageForPicture will
571 // always generate a new copy of the image.
572 - (NSImage *) imageForPicture: (int) pictureIndex
573 {
574     // The preview for the specified index may not currently exist, so this method
575     // generates it if necessary.
576     NSString * key = [NSString stringWithFormat:@"%d", pictureIndex];
577     NSImage * theImage = [fPicturePreviews objectForKey:key];
578     if (!theImage)
579     {
580         theImage = [PreviewController makeImageForPicture:pictureIndex libhb:fHandle title:fTitle];
581         [fPicturePreviews setObject:theImage forKey:key];
582     }
583     return theImage;
584 }
585
586 // Purges all images from the cache. The next call to imageForPicture will cause a new
587 // image to be generated.
588 - (void) purgeImageCache
589 {
590     [fPicturePreviews removeAllObjects];
591 }
592
593  
594
595 #pragma mark Movie Preview
596 - (IBAction) createMoviePreview: (id) sender
597 {
598     
599     
600     /* Lets make sure the still picture previews are showing in case
601      * there is currently a movie showing */
602     [self pictureSliderChanged:nil];
603     
604     /* Rip or Cancel ? */
605     hb_state_t s;
606     hb_get_state2( fPreviewLibhb, &s );
607     
608     if(sender == fCancelPreviewMovieButton && (s.state == HB_STATE_WORKING || s.state == HB_STATE_PAUSED))
609         {
610         hb_stop( fPreviewLibhb );
611         [fPictureView setHidden:NO];
612         [fMovieView pause:nil];
613         [fMovieView setHidden:YES];
614                 [fMovieView setMovie:nil];
615         [fPictureSlider setHidden:NO];
616         isEncoding = NO;
617         
618         return;
619     }
620     
621     
622     /* we use controller.mm's prepareJobForPreview to go ahead and set all of our settings
623      * however, we want to use a temporary destination field of course
624      * so that we do not put our temp preview in the users chosen
625      * directory */
626     
627     hb_job_t * job = fTitle->job;
628     
629     /* We run our current setting through prepeareJob in Controller.mm
630      * just as if it were a regular encode */
631     
632     [fHBController prepareJobForPreview];
633     
634     /* Destination file. We set this to our preview directory
635      * changing the extension appropriately.*/
636     if (fTitle->job->mux == HB_MUX_MP4) // MP4 file
637     {
638         /* we use .m4v for our mp4 files so that ac3 and chapters in mp4 will play properly */
639         fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.m4v";
640     }
641     else if (fTitle->job->mux == HB_MUX_MKV) // MKV file
642     {
643         fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.mkv";
644     }
645     else if (fTitle->job->mux == HB_MUX_AVI) // AVI file
646     {
647         fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.avi";
648     }
649     else if (fTitle->job->mux == HB_MUX_OGM) // OGM file
650     {
651         fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.ogm";
652     }
653     
654     fPreviewMoviePath = [[fPreviewMoviePath stringByExpandingTildeInPath]retain];
655     
656     /* See if there is an existing preview file, if so, delete it */
657     if( ![[NSFileManager defaultManager] fileExistsAtPath:fPreviewMoviePath] )
658     {
659         [[NSFileManager defaultManager] removeFileAtPath:fPreviewMoviePath
660                                                  handler:nil];
661     }
662     
663     /* We now direct our preview encode to fPreviewMoviePath */
664     fTitle->job->file = [fPreviewMoviePath UTF8String];
665     
666     /* We use our advance pref to determine how many previews to scan */
667     int hb_num_previews = [[[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewsNumber"] intValue];
668     job->start_at_preview = fPicture + 1;
669     job->seek_points = hb_num_previews;
670     
671     /* we use the preview duration popup to get the specified
672      * number of seconds for the preview encode.
673      */
674     
675     job->pts_to_stop = [[fPreviewMovieLengthPopUp titleOfSelectedItem] intValue] * 90000LL;
676     
677     /* lets go ahead and send it off to libhb
678      * Note: unlike a full encode, we only send 1 pass regardless if the final encode calls for 2 passes.
679      * this should suffice for a fairly accurate short preview and cuts our preview generation time in half.
680      * However we also need to take into account the indepth scan for subtitles.
681      */
682     /*
683      * If scanning we need to do some extra setup of the job.
684      */
685     if( job->indepth_scan == 1 )
686     {
687         char *x264opts_tmp;
688         
689         /*
690          * When subtitle scan is enabled do a fast pre-scan job
691          * which will determine which subtitles to enable, if any.
692          */
693         job->pass = -1;
694         x264opts_tmp = job->x264opts;
695         
696         job->x264opts = NULL;
697         job->indepth_scan = 1;  
698         /*
699          * Add the pre-scan job
700          */
701         hb_add( fPreviewLibhb, job );
702         job->x264opts = x264opts_tmp;
703     }                  
704     /* Go ahead and perform the actual encoding preview scan */
705     job->indepth_scan = 0;
706     job->pass = 0;
707     hb_add( fPreviewLibhb, job );
708     
709     [fEncodingControlBox setHidden: NO];
710     [fPictureControlBox setHidden: YES];
711     
712     [fMovieCreationProgressIndicator setHidden: NO];
713     [fPreviewMovieStatusField setHidden: NO];
714     
715     isEncoding = YES;
716
717     /* Let fPreviewLibhb do the job */
718     hb_start( fPreviewLibhb );
719         
720 }
721
722 - (void) startReceivingLibhbNotifications
723 {
724     if (!fLibhbTimer)
725     {
726         fLibhbTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(libhbTimerFired:) userInfo:nil repeats:YES];
727         [fLibhbTimer retain];
728     }
729 }
730
731 - (void) stopReceivingLibhbNotifications
732 {
733     if (fLibhbTimer)
734     {
735         [fLibhbTimer invalidate];
736         [fLibhbTimer release];
737         fLibhbTimer = nil;
738     }
739 }
740 - (void) libhbTimerFired: (NSTimer*)theTimer
741 {
742     hb_state_t s;
743     hb_get_state( fPreviewLibhb, &s );
744     [self libhbStateChanged: s];
745     
746 }
747
748 - (void) libhbStateChanged: (hb_state_t)state
749 {
750     switch( state.state )
751     {
752         case HB_STATE_IDLE:
753         case HB_STATE_SCANNING:
754         case HB_STATE_SCANDONE:
755             break;
756             
757         case HB_STATE_WORKING:
758         {
759 #define p state.param.working
760             
761             NSMutableString * string;
762                         /* Update text field */
763                         string = [NSMutableString stringWithFormat: NSLocalizedString( @"Encoding preview:  %.2f %%", @"" ), 100.0 * p.progress];
764             
765                         if( p.seconds > -1 )
766             {
767                 [string appendFormat:
768                  NSLocalizedString( @" (%.2f fps, avg %.2f fps, ETA %02dh%02dm%02ds)", @"" ),
769                  p.rate_cur, p.rate_avg, p.hours, p.minutes, p.seconds];
770             }
771             [fPreviewMovieStatusField setStringValue: string];
772             
773             [fMovieCreationProgressIndicator setIndeterminate: NO];
774             /* Update slider */
775                         [fMovieCreationProgressIndicator setDoubleValue: 100.0 * p.progress];
776             
777             [fCreatePreviewMovieButton setTitle: @"Cancel Preview"];
778             
779             break;
780             
781         }
782 #undef p
783             
784 #define p state.param.muxing            
785         case HB_STATE_MUXING:
786         {
787             // Update fMovieCreationProgressIndicator
788             [fMovieCreationProgressIndicator setIndeterminate: YES];
789             [fMovieCreationProgressIndicator startAnimation: nil];
790             [fPreviewMovieStatusField setStringValue: [NSString stringWithFormat:
791                                          NSLocalizedString( @"Muxing Preview ...", @"" )]];
792             break;
793         }
794 #undef p                        
795         case HB_STATE_PAUSED:
796             [fMovieCreationProgressIndicator stopAnimation: nil];
797             break;
798                         
799         case HB_STATE_WORKDONE:
800         {
801             // Delete all remaining jobs since libhb doesn't do this on its own.
802             hb_job_t * job;
803             while( ( job = hb_job(fPreviewLibhb, 0) ) )
804                 hb_rem( fHandle, job );
805             
806             [fPreviewMovieStatusField setStringValue: @""];
807             [fPreviewMovieStatusField setHidden: YES];
808             
809             [fMovieCreationProgressIndicator stopAnimation: nil];
810             [fMovieCreationProgressIndicator setHidden: YES];
811             [fEncodingControlBox setHidden: YES];
812             isEncoding = NO;
813             /* we make sure the picture slider and preview match */
814             [self pictureSliderChanged:nil];
815
816             // Show the movie view
817             [self showMoviePreview:fPreviewMoviePath];
818             [fCreatePreviewMovieButton setTitle: @"Live Preview"];
819
820             break;
821         }
822     }
823 }
824
825 - (IBAction) showMoviePreview: (NSString *) path
826 {
827     /* Since the gray background for the still images is part of
828      * fPictureView, lets leave the picture view visible and postion
829      * the fMovieView over the image portion of fPictureView so
830      * we retain the gray cropping border  we have already established
831      * with the still previews
832      */
833     
834     /* Load the new movie into fMovieView */
835     if (path) 
836     {
837                 QTMovie * aMovie;
838                 NSError  *outError;
839                 NSURL *movieUrl = [NSURL fileURLWithPath:path];
840                 NSDictionary *movieAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
841                                                                                  movieUrl, QTMovieURLAttribute,
842                                                                                  [NSNumber numberWithBool:NO], QTMovieAskUnresolvedDataRefsAttribute,
843                                                                                  [NSNumber numberWithBool:YES], @"QTMovieOpenForPlaybackAttribute",
844                                                                                  [NSNumber numberWithBool:NO], @"QTMovieOpenAsyncRequiredAttribute",                                                            
845                                                                                  [NSNumber numberWithBool:NO], @"QTMovieOpenAsyncOKAttribute",
846                                                                                  QTMovieApertureModeClean, QTMovieApertureModeAttribute,
847                                                                                  nil];
848         
849         aMovie = [[[QTMovie alloc] initWithAttributes:movieAttributes error:&outError] autorelease];
850         
851                 if (!aMovie) 
852         {
853                         NSLog(@"Unable to open movie");
854                 }
855         else 
856         {
857             NSRect movieBounds;
858             /* we get some size information from the preview movie */
859             NSSize movieSize= [[aMovie attributeForKey:QTMovieNaturalSizeAttribute] sizeValue];
860             movieBounds = [fMovieView movieBounds];
861             movieBounds.size.height = movieSize.height;
862             
863             if ([fMovieView isControllerVisible]) 
864             {
865                 CGFloat controllerBarHeight = [fMovieView controllerBarHeight];
866                 if ( controllerBarHeight != 0 ) //Check if QTKit return a real value or not.
867                     movieBounds.size.height += controllerBarHeight;
868                 else
869                     movieBounds.size.height += 15;
870             }
871             
872             movieBounds.size.width = movieSize.width;
873             
874             /* We need to find out if the preview movie needs to be scaled down so
875              * that it doesn't overflow our available viewing container (just like for image
876              * in -displayPreview) for HD sources, etc. [fPictureViewArea frame].size.height*/
877             if( (movieBounds.size.height) > [fPictureViewArea frame].size.height || scaleToScreen == YES )
878             {
879                 /* The preview movie would be larger than the available viewing area
880                  * in the preview movie, so we go ahead and scale it down to the same size
881                  * as the still preview  or we readjust our window to allow for the added height if need be
882                  */
883                 NSSize displaySize = NSMakeSize( ( CGFloat ) movieBounds.size.width, ( CGFloat ) movieBounds.size.height );
884                 NSSize viewSize = [self optimalViewSizeForImageSize:displaySize];
885                 if( [self viewNeedsToResizeToSize:viewSize] ) 
886                 {
887                     [self resizeSheetForViewSize:viewSize];
888                     [self setViewSize:viewSize];
889                 }
890                 [fMovieView setFrameSize:viewSize];
891             }
892             else
893             {
894                 /* Since the preview movie is smaller than the available viewing area
895                  * we can go ahead and use the preview movies native size */
896                 [fMovieView setFrameSize:movieBounds.size];
897             }
898             
899             //lets reposition the movie if need be
900             
901             NSPoint origin = [fPictureViewArea frame].origin;
902             origin.x += trunc( ( [fPictureViewArea frame].size.width -
903                                 [fMovieView frame].size.width ) / 2.0 );
904             /* We need to detect whether or not we are currently less than the available height.*/
905             if( movieBounds.size.height < [fPictureView frame].size.height )
906             {
907                 /* If we are, we are adding 15 to the height to allow for the controller bar so
908                  * we need to subtract half of that for the origin.y to get the controller bar
909                  * below the movie to it lines up vertically with where our still preview was
910                  */
911                 origin.y += trunc( ( ( [fPictureViewArea frame].size.height -
912                                       [fMovieView frame].size.height ) / 2.0 ) - 7.5 );
913             }
914             else
915             {
916                 /* if we are >= to the height of the picture view area, the controller bar
917                  * gets taken care of with picture resizing, so we do not want to offset the height
918                  */
919                 origin.y += trunc( ( [fPictureViewArea frame].size.height -
920                                     [fMovieView frame].size.height ) / 2.0 );
921             }
922             [fMovieView setFrameOrigin:origin];
923             [fMovieView setMovie:aMovie];
924             [fMovieView setHidden:NO];
925             // to actually play the movie
926             [fMovieView play:aMovie];
927         }
928     }
929     isEncoding = NO;
930 }
931
932 @end
933
934 @implementation PreviewController (Private)
935
936 //
937 // -[PictureController(Private) optimalViewSizeForImageSize:]
938 //
939 // Given the size of the preview image to be shown, returns the best possible
940 // size for the view.
941 //
942 - (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize
943 {
944     // The min size is 320x240
945     CGFloat minWidth = 480.0;
946     CGFloat minHeight = 360.0;
947
948     NSSize screenSize = [[NSScreen mainScreen] visibleFrame].size;
949     NSSize sheetSize = [[self window] frame].size;
950     NSSize viewAreaSize = [fPictureViewArea frame].size;
951     CGFloat paddingX = sheetSize.width - viewAreaSize.width;
952     CGFloat paddingY = sheetSize.height - viewAreaSize.height;
953     CGFloat maxWidth;
954     CGFloat maxHeight;
955     
956     maxWidth =  screenSize.width - paddingX;
957     maxHeight = screenSize.height - paddingY;
958     
959     NSSize resultSize = imageSize;
960     CGFloat resultPar = resultSize.width / resultSize.height;
961
962     //note, a mbp 15" at 1440 x 900 is a 1.6 ar
963     CGFloat screenAspect = screenSize.width / screenSize.height;
964     // Note, a standard dvd will use 720 x 480 which is a 1.5
965     CGFloat viewAreaAspect = viewAreaSize.width / viewAreaSize.height;
966
967     if (scaleToScreen == YES)
968     {
969         
970         if (screenAspect < viewAreaAspect)
971         {
972             resultSize.width = screenSize.width;
973             resultSize.height = (screenSize.width / viewAreaAspect);
974         }
975         else
976         {
977             resultSize.height = screenSize.height;
978             resultSize.width = resultSize.height * viewAreaAspect;
979         }
980         
981     }
982     else if ( resultSize.width > maxWidth || resultSize.height > maxHeight )
983     {
984         // Source is larger than screen in one or more dimensions
985         if ( resultPar > screenAspect )
986         {
987             // Source aspect wider than screen aspect, snap to max width and vary height
988             resultSize.width = maxWidth;
989             resultSize.height = (maxWidth / resultPar);
990         }
991         else
992         {
993             // Source aspect narrower than screen aspect, snap to max height vary width
994             resultSize.height = maxHeight;
995             resultSize.width = (maxHeight * resultPar);
996         }
997     }
998
999     // If necessary, grow to minimum dimensions to ensure controls overlay is not obstructed
1000     if ( resultSize.width < minWidth )
1001     {
1002         resultSize.width = minWidth;
1003     }
1004     if ( resultSize.height < minHeight )
1005     {
1006         resultSize.height = minHeight;
1007     }
1008
1009       return resultSize;
1010
1011     
1012 }
1013
1014 //
1015 // -[PictureController(Private) resizePanelForViewSize:animate:]
1016 //
1017 // Resizes the entire window to accomodate a view of a particular size.
1018 //
1019 - (void)resizeSheetForViewSize: (NSSize)viewSize
1020 {
1021     // Figure out the deltas for the new frame area
1022     NSSize currentSize = [fPictureViewArea frame].size;
1023     CGFloat deltaX = viewSize.width - currentSize.width;
1024     CGFloat deltaY = viewSize.height - currentSize.height;
1025     
1026     // Now resize the whole panel by those same deltas, but don't exceed the min
1027     NSRect frame = [[self window] frame];
1028     NSSize maxSize = [[[self window] screen] visibleFrame].size;
1029     NSSize minSize = [[self window] minSize];
1030     
1031     frame.size.width += deltaX;
1032     frame.size.height += deltaY;
1033     if( frame.size.width < minSize.width )
1034     {
1035         frame.size.width = minSize.width;
1036     }
1037     
1038     if( frame.size.height < minSize.height )
1039     {
1040         frame.size.height = minSize.height;
1041     }
1042     /* compare frame to max size of screen */
1043     if( frame.size.width > maxSize.width )
1044     {
1045         frame.size.width = maxSize.width;
1046     }
1047     
1048     if( frame.size.height > maxSize.height )
1049     {
1050         frame.size.height = maxSize.height;
1051     }
1052     
1053     
1054
1055     
1056     // But now the sheet is off-center, so also shift the origin to center it and
1057     // keep the top aligned.
1058     if( frame.size.width != [[self window] frame].size.width )
1059         frame.origin.x -= (deltaX / 2.0);
1060     
1061         
1062         /* Since upon launch we can open up the preview window if it was open
1063          * the last time we quit (and at the size it was) we want to make
1064          * sure that upon resize we do not have the window off the screen
1065          * So check the origin against the screen origin and adjust if
1066          * necessary.
1067          */
1068         NSSize screenSize = [[[self window] screen] visibleFrame].size;
1069         NSPoint screenOrigin = [[[self window] screen] frame].origin;
1070         if (screenSize.height < frame.size.height)
1071         {
1072             frame.size.height = screenSize.height;
1073         }
1074         if (screenSize.width < frame.size.width)
1075         {
1076             frame.size.width = screenSize.width;
1077         }
1078         
1079         
1080         /* our origin is off the screen to the left*/
1081         if (frame.origin.x < screenOrigin.x)
1082         {
1083             /* so shift our origin to the right */
1084             frame.origin.x = screenOrigin.x;
1085         }
1086         else if ((frame.origin.x + frame.size.width) > (screenOrigin.x + screenSize.width))
1087         {
1088             /* the right side of the preview is off the screen, so shift to the left */
1089             frame.origin.x = (screenOrigin.x + screenSize.width) - frame.size.width;
1090         }
1091         
1092         [[self window] setFrame:frame display:YES animate:YES];
1093     
1094     
1095 }
1096
1097 //
1098 // -[PictureController(Private) setViewSize:]
1099 //
1100 // Changes the view's size and centers it vertically inside of its area.
1101 // Assumes resizeSheetForViewSize: has already been called.
1102 //
1103 - (void)setViewSize: (NSSize)viewSize
1104 {   
1105     /* special case for scaleToScreen */
1106     NSSize screenSize = [[[self window] screen] visibleFrame].size;
1107     NSSize areaSize = [fPictureViewArea frame].size;
1108     if (scaleToScreen == YES || viewSize.width > areaSize.width || viewSize.height > areaSize.height)
1109     {
1110         /* for scaleToScreen, we expand the fPictureView to fit the entire screen */
1111         CGFloat viewSizeAspect = viewSize.width / viewSize.height;
1112         if (viewSizeAspect > 1.0) // we are wider than taller, so expand the width to fill the area and scale the height
1113         {
1114             viewSize.width = areaSize.width;
1115             viewSize.height = viewSize.width / viewSizeAspect;
1116         }
1117         else
1118         {
1119             viewSize.height = areaSize.height;
1120             viewSize.width = viewSize.height * viewSizeAspect;
1121         }
1122         
1123     }
1124     
1125     
1126     [fPictureView setFrameSize:viewSize];
1127
1128     // center it vertically and horizontally
1129     NSPoint origin = [fPictureViewArea frame].origin;
1130     origin.y += ([fPictureViewArea frame].size.height -
1131                  [fPictureView frame].size.height) / 2.0;
1132     
1133     origin.x += ([fPictureViewArea frame].size.width -
1134                  [fPictureView frame].size.width) / 2.0; 
1135
1136     origin.x = floor( origin.x );
1137     origin.y = floor( origin.y );
1138     
1139     [fPictureView setFrameOrigin:origin];
1140     
1141     /* set the top of the hud controller boxes centered vertically with the origin of our window */
1142     NSPoint hudControlBoxOrigin = [fPictureControlBox frame].origin;
1143     hudControlBoxOrigin.y = ([[self window] frame].size.height / 2) - [fPictureControlBox frame].size.height;
1144     [fPictureControlBox setFrameOrigin:hudControlBoxOrigin];
1145     [fEncodingControlBox setFrameOrigin:hudControlBoxOrigin];
1146 }
1147
1148
1149 - (BOOL)viewNeedsToResizeToSize: (NSSize)newSize
1150 {
1151     NSSize viewSize = [fPictureViewArea frame].size;
1152     return (newSize.width != viewSize.width || newSize.height != viewSize.height);
1153 }
1154
1155 @end