OSDN Git Service

With live preview decmpeg2 can be running more than once simultaneously, so the crude...
[handbrake-jp/handbrake-jp-git.git] / macosx / HBPreviewController.mm
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 @interface PreviewController (Private)
11
12 - (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize;
13 - (void)resizeSheetForViewSize: (NSSize)viewSize;
14 - (void)setViewSize: (NSSize)viewSize;
15 - (BOOL)viewNeedsToResizeToSize: (NSSize)newSize;
16
17 @end
18
19 @implementation PreviewController
20
21 - (id)init
22 {
23         if (self = [super initWithWindowNibName:@"PicturePreview"])
24         {
25         // NSWindowController likes to lazily load its window. However since
26         // this controller tries to set all sorts of outlets before the window
27         // is displayed, we need it to load immediately. The correct way to do
28         // this, according to the documentation, is simply to invoke the window
29         // getter once.
30         //
31         // If/when we switch a lot of this stuff to bindings, this can probably
32         // go away.
33         [self window];
34         
35                 fPicturePreviews = [[NSMutableDictionary dictionaryWithCapacity: HB_NUM_HBLIB_PICTURES] retain];
36         /* Init libhb with check for updates libhb style set to "0" so its ignored and lets sparkle take care of it */
37         int loggingLevel = [[[NSUserDefaults standardUserDefaults] objectForKey:@"LoggingLevel"] intValue];
38         fPreviewLibhb = hb_init(loggingLevel, 0);
39         
40         }
41         return self;
42 }
43
44
45
46 //------------------------------------------------------------------------------------
47 // Displays and brings the picture window to the front
48 //------------------------------------------------------------------------------------
49 - (IBAction) showPreviewWindow: (id)sender
50 {
51     [self showWindow:sender];
52     [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"PreviewWindowIsOpen"];
53     
54     /* lets set the preview window to accept mouse moved events */
55     [fPreviewWindow setAcceptsMouseMovedEvents:YES];
56     hudTimerSeconds = 0;
57     [self pictureSliderChanged:nil];
58     [self startReceivingLibhbNotifications];
59 }
60
61 - (void)setHBController: (HBController *)controller
62 {
63     fHBController = controller;
64 }
65
66 - (void)awakeFromNib
67 {
68     [fPreviewWindow setDelegate:self];
69     if( ![[self window] setFrameUsingName:@"Preview"] )
70         [[self window] center];
71     [self setWindowFrameAutosaveName:@"Preview"];
72     [[self window] setExcludedFromWindowsMenu:YES];
73     
74     /* lets set the preview window to accept mouse moved events */
75     [fPreviewWindow setAcceptsMouseMovedEvents:YES];
76     //[self pictureSliderChanged:nil];
77     [self startReceivingLibhbNotifications];
78     
79     isFullScreen = NO;
80     hudTimerSeconds = 0;
81     
82     /* Setup our layers for core animation */
83     [fPictureViewArea setWantsLayer:YES];
84     [fPictureView setWantsLayer:YES];
85     
86     [fMovieView setWantsLayer:YES];
87     
88     [fCancelPreviewMovieButton setWantsLayer:YES];
89     [fMovieCreationProgressIndicator setWantsLayer:YES];
90     
91     [fPictureControlBox setWantsLayer:YES];
92     [fPictureSlider setWantsLayer:YES];
93     [fFullScreenToggleButton setWantsLayer:YES];
94     [fPictureSettingsToggleButton setWantsLayer:YES];
95     [fScaleToScreenToggleButton setWantsLayer:YES];
96     [fCreatePreviewMovieButton setWantsLayer:YES];
97     
98     [fEncodingControlBox setWantsLayer:YES];
99     
100     [fShowPreviewMovieButton setWantsLayer:YES];
101     
102     
103 }
104 - (BOOL)acceptsMouseMovedEvents
105 {
106 return YES;
107 }
108
109 - (void)windowWillClose:(NSNotification *)aNotification
110 {
111     /* Upon Closing the picture window, we make sure we clean up any
112      * preview movie that might be playing
113      */
114     play_movie = NO;
115     hb_stop( fPreviewLibhb );
116     isEncoding = NO;
117     // Show the picture view
118     [fPictureView setHidden:NO];
119     [fMovieView pause:nil];
120     [fMovieView setHidden:YES];
121     if (isFullScreen)
122     {
123         [self goWindowedScreen:nil];
124     }
125     
126     isFullScreen = NO;
127     hudTimerSeconds = 0;
128     [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"PreviewWindowIsOpen"];
129 }
130
131 - (BOOL)windowShouldClose:(id)fPictureWindow
132 {
133     return YES;
134 }
135
136 - (void) dealloc
137 {
138     hb_stop(fPreviewLibhb);
139     if (fPreviewMoviePath)
140     {
141         [[NSFileManager defaultManager] removeFileAtPath:fPreviewMoviePath handler:nil];
142         [fPreviewMoviePath release];
143     }    
144     
145     [fLibhbTimer invalidate];
146     [fLibhbTimer release];
147     
148     [fHudTimer invalidate];
149     [fHudTimer release];
150     
151     [fPicturePreviews release];
152     [fFullScreenWindow release];
153
154     [super dealloc];
155 }
156
157 - (void) SetHandle: (hb_handle_t *) handle
158 {
159     fHandle = handle;
160     
161
162     
163     /* we set the preview length popup in seconds */
164     [fPreviewMovieLengthPopUp removeAllItems];
165     [fPreviewMovieLengthPopUp addItemWithTitle: @"5"];
166     [fPreviewMovieLengthPopUp addItemWithTitle: @"10"];
167     [fPreviewMovieLengthPopUp addItemWithTitle: @"15"];
168     [fPreviewMovieLengthPopUp addItemWithTitle: @"20"];
169     [fPreviewMovieLengthPopUp addItemWithTitle: @"25"];
170     [fPreviewMovieLengthPopUp addItemWithTitle: @"30"];
171     [fPreviewMovieLengthPopUp addItemWithTitle: @"35"];
172     [fPreviewMovieLengthPopUp addItemWithTitle: @"40"];
173     [fPreviewMovieLengthPopUp addItemWithTitle: @"45"];
174     [fPreviewMovieLengthPopUp addItemWithTitle: @"50"];
175     [fPreviewMovieLengthPopUp addItemWithTitle: @"55"];
176     [fPreviewMovieLengthPopUp addItemWithTitle: @"60"];
177     
178     /* adjust the preview slider length */
179     /* We use our advance pref to determine how many previews we scanned */
180     int hb_num_previews = [[[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewsNumber"] intValue];
181     [fPictureSlider setMaxValue: hb_num_previews - 1.0];
182     [fPictureSlider setNumberOfTickMarks: hb_num_previews];
183     
184     if ([[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewLength"])
185     {
186         [fPreviewMovieLengthPopUp selectItemWithTitle:[[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewLength"]];
187     }
188     else
189     {
190         /* currently hard set default to 10 seconds */
191         [fPreviewMovieLengthPopUp selectItemAtIndex: 1];
192     }
193 }
194
195 - (void) SetTitle: (hb_title_t *) title
196 {
197     hb_job_t * job = title->job;
198     
199     fTitle = title;
200     fPicture = 0;
201     MaxOutputWidth = title->width - job->crop[2] - job->crop[3];
202     MaxOutputHeight = title->height - job->crop[0] - job->crop[1];
203     [self SettingsChanged: nil];
204 }
205
206
207
208 // Adjusts the window to draw the current picture (fPicture) adjusting its size as
209 // necessary to display as much of the picture as possible.
210 - (void) displayPreview
211 {
212      hb_job_t * job = fTitle->job;
213     /* lets make sure that the still picture view is not hidden and that 
214      * the movie preview is 
215      */
216     [fMovieView pause:nil];
217     [fMovieView setHidden:YES];
218     [fMovieCreationProgressIndicator stopAnimation: nil];
219     [fMovieCreationProgressIndicator setHidden: YES];
220     
221     [fPictureView setHidden:NO];
222
223     [fPictureView setImage: [self imageForPicture: fPicture]];
224     
225         
226         
227     NSSize displaySize = NSMakeSize( ( CGFloat )fTitle->width, ( CGFloat )fTitle->height );
228     /* Set the picture size display fields below the Preview Picture*/
229     if( fTitle->job->anamorphic.mode == 1 ) // Original PAR Implementation
230     {
231         output_width = fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3];
232         output_height = fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1];
233         display_width = output_width * fTitle->job->anamorphic.par_width / fTitle->job->anamorphic.par_height;
234         [fInfoField setStringValue:[NSString stringWithFormat:
235                                     @"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d",
236                                     fTitle->width, fTitle->height, output_width, output_height, display_width, output_height]];
237         displaySize.width *= ( ( CGFloat )fTitle->job->anamorphic.par_width ) / ( ( CGFloat )fTitle->job->anamorphic.par_height );   
238     }
239     else if (fTitle->job->anamorphic.mode == 2) // Loose Anamorphic
240     {
241     hb_set_anamorphic_size(job, &output_width, &output_height, &output_par_width, &output_par_height);
242         display_width = output_width * output_par_width / output_par_height;
243         [fInfoField setStringValue:[NSString stringWithFormat:
244                                     @"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d",
245                                     fTitle->width, fTitle->height, output_width, output_height, display_width, output_height]];
246         
247         displaySize.width = display_width;
248     }
249     else // No Anamorphic
250     {
251         [fInfoField setStringValue: [NSString stringWithFormat:
252                                      @"Source: %dx%d, Output: %dx%d", fTitle->width, fTitle->height,
253                                      fTitle->job->width, fTitle->job->height]];
254     }
255
256     NSSize viewSize = [self optimalViewSizeForImageSize:displaySize];
257     /* we also need to take into account scaling to full screen to activate switching the view size */
258     if( [self viewNeedsToResizeToSize:viewSize])
259     {
260         /* In the case of loose anamorphic, do not resize the window when scaling down */
261         // FIX ME: we need a new way to do this as we do not havefWidthField anymore
262         //if (fTitle->job->anamorphic.mode != 2 || [fWidthField intValue] == fTitle->width)
263         if (fTitle->job->anamorphic.mode != 2 || (fTitle->job->anamorphic.mode == 2 && output_width == fTitle->width))
264         {
265             [self resizeSheetForViewSize:viewSize];
266             [self setViewSize:viewSize];
267         }
268     }
269     
270     
271     // Show the scaled text (use the height to check since the width can vary
272     // with anamorphic video).
273     if( ( ( int )viewSize.height ) != fTitle->height )
274     {
275         CGFloat scale = viewSize.width / ( ( CGFloat ) fTitle->width );
276         NSString *scaleString = [NSString stringWithFormat:
277                                  NSLocalizedString( @" (Preview scaled to %.0f%% actual size)",
278                                                    @"String shown when a preview is scaled" ),
279                                  scale * 100.0];
280         [fscaleInfoField setStringValue: [NSString stringWithFormat:
281                                           @"%@", scaleString]];
282         
283     }
284     else
285     {
286         [fscaleInfoField setStringValue: @""];
287     }
288
289 }
290
291 - (IBAction) previewDurationPopUpChanged: (id) sender
292 {
293     
294     [[NSUserDefaults standardUserDefaults] setObject:[fPreviewMovieLengthPopUp titleOfSelectedItem] forKey:@"PreviewLength"];
295     
296 }    
297     
298 - (IBAction) SettingsChanged: (id) sender
299 {
300          // Purge the existing picture previews so they get recreated the next time
301         // they are needed.
302         [self purgeImageCache];
303         /* We actually call displayPreview now from pictureSliderChanged which keeps
304          * our picture preview slider in sync with the previews being shown
305          */
306         //[self displayPreview];
307         [self pictureSliderChanged:nil];
308 }
309
310 - (IBAction) pictureSliderChanged: (id) sender
311 {
312     // Show the picture view
313     [fPictureView setHidden:NO];
314     [fMovieView pause:nil];
315     [fMovieView setHidden:YES];
316     [fEncodingControlBox setHidden: YES];
317     
318     int newPicture = [fPictureSlider intValue];
319     if (newPicture != fPicture)
320     {
321         fPicture = newPicture;
322     }
323     [self displayPreview];
324     
325 }
326
327 - (IBAction)showPreviewPanel: (id)sender forTitle: (hb_title_t *)title
328 {
329     [self SetTitle:title];
330     
331     if ([fPreviewWindow isVisible])
332     {
333         
334         [fPreviewWindow close];
335         
336     }
337     else
338     {
339         [self showWindow:sender];
340         [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"PreviewWindowIsOpen"];
341         [fPreviewWindow setAcceptsMouseMovedEvents:YES];
342         isFullScreen = NO;
343         scaleToScreen = NO;
344         hudTimerSeconds = 0;
345         [self pictureSliderChanged:nil];
346         [self startHudTimer];
347         
348     }
349     
350 }
351
352 - (IBAction)showPictureSettings:(id)sender
353 {
354     [fHBController showPicturePanel:self];
355 }
356
357 #pragma mark Hud Control Overlay
358 - (void) mouseMoved:(NSEvent *)theEvent
359 {
360     [super mouseMoved:theEvent];
361     
362     if (isEncoding == NO)
363     {    
364         if (hudTimerSeconds == 0)
365         {
366             hudTimerSeconds ++;
367             [self startHudTimer];
368         }
369         
370         if (hudTimerSeconds > 20)
371         {
372             
373             
374             [self stopHudTimer];
375             [self showHideHudControls];
376         }
377         
378     }
379 }
380
381 - (void) startHudTimer
382 {
383     if (!fHudTimer)
384     {
385         fHudTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(hudTimerFired:) userInfo:nil repeats:YES];
386         [fHudTimer retain];
387     }
388 }
389
390 - (void) stopHudTimer
391 {
392     if (fHudTimer)
393     {
394         [fHudTimer invalidate];
395         [fHudTimer release];
396         fHudTimer = nil;
397         hudTimerSeconds = 0;
398     }
399 }
400
401 - (void) hudTimerFired: (NSTimer*)theTimer
402 {
403     hudTimerSeconds ++;
404     [self showHideHudControls];
405
406 }
407
408 - (void) showHideHudControls
409 {
410     /* Test for mouse location to show/hide hud controls */
411     NSPoint    mouseLoc;
412     NSRect     targetFrame;
413     NSRect     controlBoxFrame;
414     targetFrame = [fPictureViewArea frame];
415     controlBoxFrame = [fPictureControlBox frame];
416     
417     if (isFullScreen)
418     {
419         mouseLoc = [fFullScreenWindow mouseLocationOutsideOfEventStream];
420         [fScaleToScreenToggleButton setHidden:NO];
421     }
422     else
423     {
424         mouseLoc = [fPreviewWindow mouseLocationOutsideOfEventStream];
425         [fScaleToScreenToggleButton setHidden:YES];
426     }
427     
428     /* if the pointer is inside the picture view areas but not
429      * in the controlbox, check the hudTimerSeconds to see if
430      * its in the allowable time span
431      */
432     if ( hudTimerSeconds > 0 && hudTimerSeconds < 20)
433     {
434         
435         if (isEncoding == NO)
436         {
437             if (NSPointInRect (mouseLoc, controlBoxFrame))
438             {
439                 /* Mouse is over the preview area so show hud controls so just
440                  * reset the timer to keep the control box visible
441                 */
442                 [fPictureControlBox setHidden: NO];
443                 hudTimerSeconds = 1;
444                 return;
445             }
446             /* Re-verify we are within the target frame */
447             if (NSPointInRect (mouseLoc, targetFrame))
448             {
449                 /* Mouse is over the preview area so show hud controls */
450                 [[fPictureControlBox animator] setHidden: NO];
451                 /* increment our timer by one */
452                 hudTimerSeconds ++;
453             }
454             else
455             {
456                 [[fPictureControlBox animator] setHidden: YES];
457                 [self stopHudTimer];
458             }
459         }
460         
461     }
462     else
463     {
464         [[fPictureControlBox animator] setHidden: YES];
465     }
466     
467 }
468
469
470 #pragma mark Fullscreen Mode
471
472 - (IBAction)toggleScreenMode:(id)sender
473 {
474     if (!isFullScreen)
475     {
476         [self goFullScreen:nil];
477     }
478     else
479     {
480         [self goWindowedScreen:nil];
481     }
482 }
483
484 - (IBAction)toggleScaleToScreen:(id)sender
485 {
486     if (scaleToScreen == YES)
487     {
488         scaleToScreen = NO;
489         /* make sure we are set to a still preview */
490         [self pictureSliderChanged:nil];
491         [fScaleToScreenToggleButton setTitle:@"<->"];
492     }
493     else
494     {
495         scaleToScreen = YES;
496         /* make sure we are set to a still preview */
497         [self pictureSliderChanged:nil];
498         [fScaleToScreenToggleButton setTitle:@">-<"];
499     }
500     
501     /* Actually perform the scaling */
502     /*
503     NSSize displaySize = NSMakeSize( ( CGFloat )fTitle->width, ( CGFloat )fTitle->height );
504     NSSize viewSize = [self optimalViewSizeForImageSize:displaySize];
505     [self resizeSheetForViewSize:viewSize];
506     [self setViewSize:viewSize];
507     */
508 }
509
510 - (BOOL)fullScreen
511 {
512     return isFullScreen;
513 }
514
515 - (IBAction)goFullScreen:(id)sender 
516
517     // Get the screen information. 
518     NSScreen* mainScreen = [NSScreen mainScreen];
519     NSDictionary* screenInfo = [mainScreen deviceDescription]; 
520     NSNumber* screenID = [screenInfo objectForKey:@"NSScreenNumber"]; 
521     // Capture the screen. 
522     CGDirectDisplayID displayID = (CGDirectDisplayID)[screenID longValue]; 
523     CGDisplayErr err = CGDisplayCapture(displayID); 
524     
525     if (err == CGDisplayNoErr) 
526     { 
527         
528         /* make sure we are set to a still preview and not scaled to screen */
529         scaleToScreen = NO;
530         [self pictureSliderChanged:nil];
531         
532         // Create the full-screen window. 
533         //NSRect winRect = [mainScreen frame];
534         //fPictureViewArea
535         NSRect winRect = [fPictureViewArea frame];
536           
537         fFullScreenWindow = [[NSWindow alloc] initWithContentRect:winRect 
538                                                         styleMask:NSBorderlessWindowMask 
539                                                           backing:NSBackingStoreBuffered 
540                                                             defer:NO 
541                                                            screen:[NSScreen mainScreen]]; 
542         
543         // Establish the window attributes. 
544         [fFullScreenWindow setReleasedWhenClosed:NO]; 
545         [fFullScreenWindow setDisplaysWhenScreenProfileChanges:YES]; 
546         [fFullScreenWindow setDelegate:self]; 
547         
548         /* insert a view into the new window */
549         [fFullScreenWindow setContentView:fPictureViewArea]; 
550         [fPictureViewArea setNeedsDisplay:YES];
551         
552         // Center the window 
553         
554         /* Better to center the window using the screen's frame
555          * and the windows origin. Note that we should take into
556          * account the auto sizing and alignment that occurs in 
557          * setViewSize each time the preview changes.
558          */
559         
560         NSSize screenSize = [[NSScreen mainScreen] frame].size;
561         NSSize windowSize = [fFullScreenWindow frame].size;
562         NSPoint windowOrigin = [fFullScreenWindow frame].origin;
563         
564         /* Adjust our origin y (vertical) based on the screen height */
565         windowOrigin.y = (screenSize.height - windowSize.height) / 2.0;
566         windowOrigin.x = (screenSize.width - windowSize.width) / 2.0;
567         
568         [fFullScreenWindow setFrameOrigin:windowOrigin];
569         
570         
571         
572         /* lets kill the timer for now */
573         [self stopReceivingLibhbNotifications];
574         
575         /* We need to retain the fPreviewWindow */
576         [fPreviewWindow retain];
577         
578         [self setWindow:fFullScreenWindow];
579         
580         // The window has to be above the level of the shield window.
581         int32_t shieldLevel = CGShieldingWindowLevel(); 
582         
583         [fFullScreenWindow setLevel:shieldLevel]; 
584         
585         // Show the window. 
586         [fFullScreenWindow makeKeyAndOrderFront:self];
587         
588         /* Change the name of fFullScreenToggleButton appropriately */
589         [fFullScreenToggleButton setTitle: @"Windowed"];
590         
591         /* Lets fire the timer back up for the hud controls, etc. */
592         [self startReceivingLibhbNotifications];
593         
594         isFullScreen = YES;
595         [fScaleToScreenToggleButton setHidden:NO];
596         
597         /* make sure we are set to a still preview */
598         [self pictureSliderChanged:nil];
599         
600         //[fPreviewWindow setAcceptsMouseMovedEvents:NO];
601         [fFullScreenWindow setAcceptsMouseMovedEvents:YES];
602         
603         
604         hudTimerSeconds = 0;
605         [self startHudTimer];
606     } 
607
608
609 - (IBAction)goWindowedScreen:(id)sender
610 {
611     
612     /* Get the screen info to release the display but don't actually do
613      * it until the windowed screen is setup.
614      */
615     scaleToScreen = NO;
616     [self pictureSliderChanged:nil];
617     [fScaleToScreenToggleButton setTitle:@"<->"];
618         
619     NSScreen* mainScreen = [NSScreen mainScreen]; 
620     NSDictionary* screenInfo = [mainScreen deviceDescription]; 
621     NSNumber* screenID = [screenInfo objectForKey:@"NSScreenNumber"];
622     CGDirectDisplayID displayID = (CGDirectDisplayID)[screenID longValue]; 
623     
624     [fFullScreenWindow dealloc];
625     [fFullScreenWindow release];
626     
627     
628     [fPreviewWindow setContentView:fPictureViewArea]; 
629     [fPictureViewArea setNeedsDisplay:YES];
630     [self setWindow:fPreviewWindow];
631     
632     // Show the window. 
633     [fPreviewWindow makeKeyAndOrderFront:self];
634     
635     /* Set the window back to regular level */
636     [fPreviewWindow setLevel:NSNormalWindowLevel];
637     
638     /* Set the isFullScreen flag back to NO */
639     isFullScreen = NO;
640     scaleToScreen = NO;
641     /* make sure we are set to a still preview */
642     [self pictureSliderChanged:nil];
643     [self showPreviewWindow:nil];
644     
645     /* Change the name of fFullScreenToggleButton appropriately */
646     [fFullScreenToggleButton setTitle: @"Full Screen"];
647     // [fScaleToScreenToggleButton setHidden:YES];
648     /* set the picture settings pallete back to normal level */
649     [fHBController picturePanelWindowed];
650     
651     /* Release the display now that the we are back in windowed mode */
652     CGDisplayRelease(displayID);
653     
654     [fPreviewWindow setAcceptsMouseMovedEvents:YES];
655     //[fFullScreenWindow setAcceptsMouseMovedEvents:NO];
656     
657     hudTimerSeconds = 0;
658     [self startHudTimer];
659     
660 }
661
662
663 #pragma mark Still Preview Image Processing
664
665
666 // This function converts an image created by libhb (specified via pictureIndex) into
667 // an NSImage suitable for the GUI code to use. If removeBorders is YES,
668 // makeImageForPicture crops the image generated by libhb stripping off the gray
669 // border around the content. This is the low-level method that generates the image.
670 // -imageForPicture calls this function whenever it can't find an image in its cache.
671 + (NSImage *) makeImageForPicture: (int)pictureIndex
672                 libhb:(hb_handle_t*)handle
673                 title:(hb_title_t*)title
674                 removeBorders:(BOOL)removeBorders
675 {
676     if (removeBorders)
677     {
678         //     |<---------- title->width ----------->|
679         //     |   |<---- title->job->width ---->|   |
680         //     |   |                             |   |
681         //     .......................................
682         //     ....+-----------------------------+....
683         //     ....|                             |....<-- gray border
684         //     ....|                             |....
685         //     ....|                             |....
686         //     ....|                             |<------- image
687         //     ....|                             |....
688         //     ....|                             |....
689         //     ....|                             |....
690         //     ....|                             |....
691         //     ....|                             |....
692         //     ....+-----------------------------+....
693         //     .......................................
694
695         static uint8_t * buffer;
696         static int bufferSize;
697
698         // Make sure we have a big enough buffer to receive the image from libhb. libhb
699         // creates images with a one-pixel border around the original content. Hence we
700         // add 2 pixels horizontally and vertically to the buffer size.
701         int srcWidth = title->width + 2;
702         int srcHeight= title->height + 2;
703         int newSize;
704         newSize = srcWidth * srcHeight * 4;
705         if( bufferSize < newSize )
706         {
707             bufferSize = newSize;
708             buffer     = (uint8_t *) realloc( buffer, bufferSize );
709         }
710
711         hb_get_preview( handle, title, pictureIndex, buffer );
712
713         // Create an NSBitmapImageRep and copy the libhb image into it, converting it from
714         // libhb's format to one suitable for NSImage. Along the way, we'll strip off the
715         // border around libhb's image.
716         
717         // The image data returned by hb_get_preview is 4 bytes per pixel, BGRA format.
718         // Alpha is ignored.
719         
720         int dstWidth = title->job->width;
721         int dstHeight = title->job->height;
722         NSBitmapFormat bitmapFormat = (NSBitmapFormat)NSAlphaFirstBitmapFormat;
723         NSBitmapImageRep * imgrep = [[[NSBitmapImageRep alloc]
724                 initWithBitmapDataPlanes:nil
725                 pixelsWide:dstWidth
726                 pixelsHigh:dstHeight
727                 bitsPerSample:8
728                 samplesPerPixel:3   // ignore alpha
729                 hasAlpha:NO
730                 isPlanar:NO
731                 colorSpaceName:NSCalibratedRGBColorSpace
732                 bitmapFormat:bitmapFormat
733                 bytesPerRow:dstWidth * 4
734                 bitsPerPixel:32] autorelease];
735
736         int borderTop = (srcHeight - dstHeight) / 2;
737         int borderLeft = (srcWidth - dstWidth) / 2;
738         
739         UInt32 * src = (UInt32 *)buffer;
740         UInt32 * dst = (UInt32 *)[imgrep bitmapData];
741         src += borderTop * srcWidth;    // skip top rows in src to get to first row of dst
742         src += borderLeft;              // skip left pixels in src to get to first pixel of dst
743         for (int r = 0; r < dstHeight; r++)
744         {
745             for (int c = 0; c < dstWidth; c++)
746 #if TARGET_RT_LITTLE_ENDIAN
747                 *dst++ = Endian32_Swap(*src++);
748 #else
749                 *dst++ = *src++;
750 #endif
751             src += (srcWidth - dstWidth);   // skip to next row in src
752         }
753
754         NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(dstWidth, dstHeight)] autorelease];
755         [img addRepresentation:imgrep];
756
757         return img;
758     }
759     else
760     {
761         // Make sure we have big enough buffer
762         static uint8_t * buffer;
763         static int bufferSize;
764
765         int newSize;
766         newSize = ( title->width + 2 ) * (title->height + 2 ) * 4;
767         if( bufferSize < newSize )
768         {
769             bufferSize = newSize;
770             buffer     = (uint8_t *) realloc( buffer, bufferSize );
771         }
772
773         hb_get_preview( handle, title, pictureIndex, buffer );
774
775         // The image data returned by hb_get_preview is 4 bytes per pixel, BGRA format.
776         // We'll copy that into an NSImage swapping it to ARGB in the process. Alpha is
777         // ignored.
778         int width = title->width + 2;      // hblib adds a one-pixel border to the image
779         int height = title->height + 2;
780         int numPixels = width * height;
781         NSBitmapFormat bitmapFormat = (NSBitmapFormat)NSAlphaFirstBitmapFormat;
782         NSBitmapImageRep * imgrep = [[[NSBitmapImageRep alloc]
783                 initWithBitmapDataPlanes:nil
784                 pixelsWide:width
785                 pixelsHigh:height
786                 bitsPerSample:8
787                 samplesPerPixel:3   // ignore alpha
788                 hasAlpha:NO
789                 isPlanar:NO
790                 colorSpaceName:NSCalibratedRGBColorSpace
791                 bitmapFormat:bitmapFormat
792                 bytesPerRow:width * 4
793                 bitsPerPixel:32] autorelease];
794
795         UInt32 * src = (UInt32 *)buffer;
796         UInt32 * dst = (UInt32 *)[imgrep bitmapData];
797         for (int i = 0; i < numPixels; i++)
798 #if TARGET_RT_LITTLE_ENDIAN
799             *dst++ = Endian32_Swap(*src++);
800 #else
801             *dst++ = *src++;
802 #endif
803
804         NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(width, height)] autorelease];
805         [img addRepresentation:imgrep];
806
807         return img;
808     }
809 }
810
811 // Returns the preview image for the specified index, retrieving it from its internal
812 // cache or by calling makeImageForPicture if it is not cached. Generally, you should
813 // use imageForPicture so that images are cached. Calling makeImageForPicture will
814 // always generate a new copy of the image.
815 - (NSImage *) imageForPicture: (int) pictureIndex
816 {
817     // The preview for the specified index may not currently exist, so this method
818     // generates it if necessary.
819     NSString * key = [NSString stringWithFormat:@"%d", pictureIndex];
820     NSImage * theImage = [fPicturePreviews objectForKey:key];
821     if (!theImage)
822     {
823         theImage = [PreviewController makeImageForPicture:pictureIndex libhb:fHandle title:fTitle removeBorders: NO];
824         [fPicturePreviews setObject:theImage forKey:key];
825     }
826     return theImage;
827 }
828
829 // Purges all images from the cache. The next call to imageForPicture will cause a new
830 // image to be generated.
831 - (void) purgeImageCache
832 {
833     [fPicturePreviews removeAllObjects];
834 }
835
836  
837
838 #pragma mark Movie Preview
839 - (IBAction) createMoviePreview: (id) sender
840 {
841     
842     
843     /* Lets make sure the still picture previews are showing in case
844      * there is currently a movie showing */
845     [self pictureSliderChanged:nil];
846     
847     /* Rip or Cancel ? */
848     hb_state_t s;
849     hb_get_state2( fPreviewLibhb, &s );
850     
851     if(sender == fCancelPreviewMovieButton && (s.state == HB_STATE_WORKING || s.state == HB_STATE_PAUSED))
852         {
853         
854         play_movie = NO;
855         hb_stop( fPreviewLibhb );
856         [fPictureView setHidden:NO];
857         [fMovieView pause:nil];
858         [fMovieView setHidden:YES];
859         [fPictureSlider setHidden:NO];
860         isEncoding = NO;
861         
862         return;
863     }
864     
865     
866     /* we use controller.mm's prepareJobForPreview to go ahead and set all of our settings
867      * however, we want to use a temporary destination field of course
868      * so that we do not put our temp preview in the users chosen
869      * directory */
870     
871     hb_job_t * job = fTitle->job;
872     
873     /* We run our current setting through prepeareJob in Controller.mm
874      * just as if it were a regular encode */
875     
876     [fHBController prepareJobForPreview];
877     
878     /* Destination file. We set this to our preview directory
879      * changing the extension appropriately.*/
880     if (fTitle->job->mux == HB_MUX_MP4) // MP4 file
881     {
882         /* we use .m4v for our mp4 files so that ac3 and chapters in mp4 will play properly */
883         fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.m4v";
884     }
885     else if (fTitle->job->mux == HB_MUX_MKV) // MKV file
886     {
887         fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.mkv";
888     }
889     else if (fTitle->job->mux == HB_MUX_AVI) // AVI file
890     {
891         fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.avi";
892     }
893     else if (fTitle->job->mux == HB_MUX_OGM) // OGM file
894     {
895         fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.ogm";
896     }
897     
898     fPreviewMoviePath = [[fPreviewMoviePath stringByExpandingTildeInPath]retain];
899     
900     /* See if there is an existing preview file, if so, delete it */
901     if( ![[NSFileManager defaultManager] fileExistsAtPath:fPreviewMoviePath] )
902     {
903         [[NSFileManager defaultManager] removeFileAtPath:fPreviewMoviePath
904                                                  handler:nil];
905     }
906     
907     /* We now direct our preview encode to fPreviewMoviePath */
908     fTitle->job->file = [fPreviewMoviePath UTF8String];
909     
910     /* We use our advance pref to determine how many previews to scan */
911     int hb_num_previews = [[[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewsNumber"] intValue];
912     job->start_at_preview = fPicture + 1;
913     job->seek_points = hb_num_previews;
914     
915     /* we use the preview duration popup to get the specified
916      * number of seconds for the preview encode.
917      */
918     
919     job->pts_to_stop = [[fPreviewMovieLengthPopUp titleOfSelectedItem] intValue] * 90000LL;
920     
921     /* lets go ahead and send it off to libhb
922      * Note: unlike a full encode, we only send 1 pass regardless if the final encode calls for 2 passes.
923      * this should suffice for a fairly accurate short preview and cuts our preview generation time in half.
924      */
925     hb_add( fPreviewLibhb, job );
926     
927     [fEncodingControlBox setHidden: NO];
928     [fPictureControlBox setHidden: YES];
929     
930     [fMovieCreationProgressIndicator setHidden: NO];
931     [fPreviewMovieStatusField setHidden: NO];
932     
933     isEncoding = YES;
934     
935     play_movie = YES;
936     
937     /* Let fPreviewLibhb do the job */
938     hb_start( fPreviewLibhb );
939         
940 }
941
942 - (void) startReceivingLibhbNotifications
943 {
944     if (!fLibhbTimer)
945     {
946         fLibhbTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(libhbTimerFired:) userInfo:nil repeats:YES];
947         [fLibhbTimer retain];
948     }
949 }
950
951 - (void) stopReceivingLibhbNotifications
952 {
953     if (fLibhbTimer)
954     {
955         [fLibhbTimer invalidate];
956         [fLibhbTimer release];
957         fLibhbTimer = nil;
958     }
959 }
960 - (void) libhbTimerFired: (NSTimer*)theTimer
961 {
962     hb_state_t s;
963     hb_get_state( fPreviewLibhb, &s );
964     [self libhbStateChanged: s];
965     
966 }
967
968 - (void) libhbStateChanged: (hb_state_t &)state
969 {
970     switch( state.state )
971     {
972         case HB_STATE_IDLE:
973         case HB_STATE_SCANNING:
974         case HB_STATE_SCANDONE:
975             break;
976             
977         case HB_STATE_WORKING:
978         {
979 #define p state.param.working
980             
981             NSMutableString * string;
982                         /* Update text field */
983                         string = [NSMutableString stringWithFormat: NSLocalizedString( @"Encoding preview:  %.2f %%", @"" ), 100.0 * p.progress];
984             
985                         if( p.seconds > -1 )
986             {
987                 [string appendFormat:
988                  NSLocalizedString( @" (%.2f fps, avg %.2f fps, ETA %02dh%02dm%02ds)", @"" ),
989                  p.rate_cur, p.rate_avg, p.hours, p.minutes, p.seconds];
990             }
991             [fPreviewMovieStatusField setStringValue: string];
992             
993             [fMovieCreationProgressIndicator setIndeterminate: NO];
994             /* Update slider */
995                         [fMovieCreationProgressIndicator setDoubleValue: 100.0 * p.progress];
996             
997             [fCreatePreviewMovieButton setTitle: @"Cancel Preview"];
998             
999             break;
1000             
1001         }
1002 #undef p
1003             
1004 #define p state.param.muxing            
1005         case HB_STATE_MUXING:
1006         {
1007             // Update fMovieCreationProgressIndicator
1008             [fMovieCreationProgressIndicator setIndeterminate: YES];
1009             [fMovieCreationProgressIndicator startAnimation: nil];
1010             [fPreviewMovieStatusField setStringValue: [NSString stringWithFormat:
1011                                          NSLocalizedString( @"Muxing Preview ...", @"" )]];
1012             break;
1013         }
1014 #undef p                        
1015         case HB_STATE_PAUSED:
1016             [fMovieCreationProgressIndicator stopAnimation: nil];
1017             break;
1018                         
1019         case HB_STATE_WORKDONE:
1020         {
1021             // Delete all remaining jobs since libhb doesn't do this on its own.
1022             hb_job_t * job;
1023             while( ( job = hb_job(fPreviewLibhb, 0) ) )
1024                 hb_rem( fHandle, job );
1025             
1026             [fPreviewMovieStatusField setStringValue: @""];
1027             [fPreviewMovieStatusField setHidden: YES];
1028             
1029             [fMovieCreationProgressIndicator stopAnimation: nil];
1030             [fMovieCreationProgressIndicator setHidden: YES];
1031             [fEncodingControlBox setHidden: YES];
1032             isEncoding = NO;
1033             /* we make sure the picture slider and preview match */
1034             [self pictureSliderChanged:nil];
1035             
1036             
1037             // Show the movie view
1038             if (play_movie)
1039             {
1040             [self showMoviePreview:fPreviewMoviePath];
1041             }
1042             
1043             [fCreatePreviewMovieButton setTitle: @"Live Preview"];
1044             
1045             
1046             break;
1047         }
1048     }
1049         
1050 }
1051
1052 - (IBAction) showMoviePreview: (NSString *) path
1053 {
1054     /* Since the gray background for the still images is part of
1055      * fPictureView, lets leave the picture view visible and postion
1056      * the fMovieView over the image portion of fPictureView so
1057      * we retain the gray cropping border  we have already established
1058      * with the still previews
1059      */
1060     [fMovieView setHidden:NO];
1061     
1062     /* Load the new movie into fMovieView */
1063     QTMovie * aMovie;
1064     NSRect movieBounds;
1065     if (path)
1066     {
1067         [fMovieView setControllerVisible: YES];
1068         /* let's make sure there is no movie currently set */
1069         [fMovieView setMovie:nil];
1070         
1071         aMovie = [QTMovie movieWithFile:path error:nil];
1072         
1073         /* we get some size information from the preview movie */
1074         Rect movieBox;
1075         GetMovieBox ([aMovie quickTimeMovie], &movieBox);
1076         movieBounds = [fMovieView movieBounds];
1077         movieBounds.size.height = movieBox.bottom - movieBox.top;
1078         
1079         if ([fMovieView isControllerVisible])
1080             movieBounds.size.height += [fMovieView controllerBarHeight];
1081         /* since for whatever the reason I cannot seem to get the [fMovieView controllerBarHeight]
1082          * For now just use 15 for additional height as it seems to line up well
1083          */
1084         movieBounds.size.height += 15;
1085         
1086         movieBounds.size.width = movieBox.right - movieBox.left;
1087         
1088         /* We need to find out if the preview movie needs to be scaled down so
1089          * that it doesn't overflow our available viewing container (just like for image
1090          * in -displayPreview) for HD sources, etc. [fPictureViewArea frame].size.height*/
1091         if( ((int)movieBounds.size.height) > [fPictureView frame].size.height || scaleToScreen == YES)
1092         {
1093             /* The preview movie would be larger than the available viewing area
1094              * in the preview movie, so we go ahead and scale it down to the same size
1095              * as the still preview  or we readjust our window to allow for the added height if need be
1096              */
1097             NSSize displaySize = NSMakeSize( (float)movieBounds.size.width, (float)movieBounds.size.height );
1098             NSSize viewSize = [self optimalViewSizeForImageSize:displaySize];
1099             if( [self viewNeedsToResizeToSize:viewSize] )
1100             {
1101                 
1102                 [self resizeSheetForViewSize:viewSize];
1103                 [self setViewSize:viewSize];
1104                 
1105             }
1106             
1107             [fMovieView setPreservesAspectRatio:YES];
1108             [fMovieView setFrameSize:viewSize];
1109         }
1110         else
1111         {
1112             /* Since the preview movie is smaller than the available viewing area
1113              * we can go ahead and use the preview movies native size */
1114             [fMovieView setFrameSize:movieBounds.size];
1115
1116         }
1117         
1118
1119         
1120
1121         // lets reposition the movie if need be
1122         
1123         NSPoint origin = [fPictureViewArea frame].origin;
1124         origin.x += trunc(([fPictureViewArea frame].size.width -
1125                            [fMovieView frame].size.width) / 2.0);
1126         /* We need to detect whether or not we are currently less than the available height.*/
1127         if (movieBounds.size.height < [fPictureView frame].size.height)
1128         {
1129         /* If we are, we are adding 15 to the height to allow for the controller bar so
1130          * we need to subtract half of that for the origin.y to get the controller bar
1131          * below the movie to it lines up vertically with where our still preview was
1132          */
1133         origin.y += trunc((([fPictureViewArea frame].size.height -
1134                             [fMovieView frame].size.height) / 2.0) - 7.5);
1135         }
1136         else
1137         {
1138         /* if we are >= to the height of the picture view area, the controller bar
1139          * gets taken care of with picture resizing, so we do not want to offset the height
1140          */
1141         origin.y += trunc(([fPictureViewArea frame].size.height -
1142                             [fMovieView frame].size.height) / 2.0);
1143         }
1144         [fMovieView setFrameOrigin:origin]; 
1145         
1146         [fMovieView setMovie:aMovie];
1147         /// to actually play the movie
1148         [fMovieView play:aMovie];
1149     }
1150     else
1151     {
1152         aMovie = nil;
1153     }       
1154     isEncoding = NO;
1155 }
1156
1157
1158 @end
1159
1160 @implementation PreviewController (Private)
1161
1162 //
1163 // -[PictureController(Private) optimalViewSizeForImageSize:]
1164 //
1165 // Given the size of the preview image to be shown, returns the best possible
1166 // size for the view.
1167 //
1168 - (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize
1169 {
1170     // The min size is 320x240
1171     CGFloat minWidth = 480.0;
1172     CGFloat minHeight = 360.0;
1173
1174     NSSize screenSize = [[NSScreen mainScreen] frame].size;
1175     NSSize sheetSize = [[self window] frame].size;
1176     NSSize viewAreaSize = [fPictureViewArea frame].size;
1177     CGFloat paddingX = sheetSize.width - viewAreaSize.width;
1178     CGFloat paddingY = sheetSize.height - viewAreaSize.height;
1179     CGFloat maxWidth;
1180     CGFloat maxHeight;
1181     
1182     if (isFullScreen)
1183     {
1184         /* We are in full screen mode so lets use the full screen if we need to */
1185         maxWidth =  screenSize.width - paddingX;
1186         maxHeight = screenSize.height - paddingY;
1187     }
1188     else
1189     {
1190         // The max size of the view is when the sheet is taking up 85% of the screen.
1191         maxWidth = (0.85 * screenSize.width) - paddingX;
1192         maxHeight = (0.85 * screenSize.height) - paddingY;
1193     }
1194     
1195     NSSize resultSize = imageSize;
1196     
1197     // Its better to have a view that's too small than a view that's too big, so
1198     // apply the maximum constraints last.
1199     if( resultSize.width < minWidth )
1200     {
1201         resultSize.height *= (minWidth / resultSize.width);
1202         resultSize.width = minWidth;
1203     }
1204     if( resultSize.height < minHeight )
1205     {
1206         resultSize.width *= (minHeight / resultSize.height);
1207         resultSize.height = minHeight;
1208     }
1209     if( resultSize.width > maxWidth )
1210     {
1211         resultSize.height *= (maxWidth / resultSize.width);
1212         resultSize.width = maxWidth;
1213     }
1214     if( resultSize.height > maxHeight )
1215     {
1216         resultSize.width *= (maxHeight / resultSize.height);
1217         resultSize.height = maxHeight;
1218     }
1219     
1220     if (scaleToScreen == YES)
1221     {
1222         //CGFloat scaleToScreenWidth;
1223         //CGFloat scaleToScreenHeight;
1224         CGFloat screenAspect;
1225         CGFloat viewAreaAspect; 
1226         //note, a mbp 15" at 1440 x 900 is a 1.6 ar
1227         screenAspect = screenSize.width / screenSize.height;
1228         
1229         // Note, a standard dvd will use 720 x 480 which is a 1.5
1230         viewAreaAspect = viewAreaSize.width / viewAreaSize.height;
1231         
1232         if (screenAspect < viewAreaAspect)
1233         {
1234             resultSize.width = screenSize.width;
1235             resultSize.height = (screenSize.width / viewAreaAspect);
1236         }
1237         else
1238         {
1239             resultSize.height = screenSize.height;
1240             resultSize.width = resultSize.height * viewAreaAspect;
1241         }
1242         
1243     }
1244
1245       return resultSize;
1246
1247     
1248 }
1249
1250 //
1251 // -[PictureController(Private) resizePanelForViewSize:animate:]
1252 //
1253 // Resizes the entire window to accomodate a view of a particular size.
1254 //
1255 - (void)resizeSheetForViewSize: (NSSize)viewSize
1256 {
1257     // Figure out the deltas for the new frame area
1258     NSSize currentSize = [fPictureViewArea frame].size;
1259     CGFloat deltaX = viewSize.width - currentSize.width;
1260     CGFloat deltaY = viewSize.height - currentSize.height;
1261     
1262     // Now resize the whole panel by those same deltas, but don't exceed the min
1263     NSRect frame = [[self window] frame];
1264     NSSize maxSize = [[self window] maxSize];
1265     NSSize minSize = [[self window] minSize];
1266     frame.size.width += deltaX;
1267     frame.size.height += deltaY;
1268     if( frame.size.width < minSize.width )
1269     {
1270         frame.size.width = minSize.width;
1271     }
1272     
1273     if( frame.size.height < minSize.height )
1274     {
1275         frame.size.height = minSize.height;
1276     }
1277     
1278     
1279     // But now the sheet is off-center, so also shift the origin to center it and
1280     // keep the top aligned.
1281     if( frame.size.width != [[self window] frame].size.width )
1282         frame.origin.x -= (deltaX / 2.0);
1283     
1284     if (isFullScreen)
1285     {
1286         if( frame.size.height != [[self window] frame].size.height )
1287         {
1288             frame.origin.y -= (deltaY / 2.0);
1289         }
1290         else
1291         {
1292             if( frame.size.height != [[self window] frame].size.height )
1293                 frame.origin.y -= deltaY;
1294         }
1295         
1296         [[self window] setFrame:frame display:YES animate:NO];
1297     }
1298     else
1299     {
1300         [[self window] setFrame:frame display:YES animate:YES];
1301     }
1302     
1303 }
1304
1305 //
1306 // -[PictureController(Private) setViewSize:]
1307 //
1308 // Changes the view's size and centers it vertically inside of its area.
1309 // Assumes resizeSheetForViewSize: has already been called.
1310 //
1311 - (void)setViewSize: (NSSize)viewSize
1312 {
1313     [fPictureView setFrameSize:viewSize];
1314     
1315     // center it vertically
1316     NSPoint origin = [fPictureViewArea frame].origin;
1317     origin.y += ([fPictureViewArea frame].size.height -
1318                  [fPictureView frame].size.height) / 2.0;
1319     [fPictureView setFrameOrigin:origin];
1320     
1321     NSPoint controlboxorigin = [fPictureView frame].origin;
1322     
1323     /* for now, put the origin.y 100 above the bottom of the fPictureView */
1324     controlboxorigin.y += 100;
1325     
1326     controlboxorigin.x += ([fPictureViewArea frame].size.width -
1327                  [fPictureControlBox frame].size.width) / 2.0;
1328     /* requires that thefPictureControlBox and the fEncodingControlBox
1329      * are the same width to line up.
1330      */
1331     [fPictureControlBox setFrameOrigin:controlboxorigin];
1332     [fEncodingControlBox setFrameOrigin:controlboxorigin];
1333     
1334 }
1335
1336
1337 - (BOOL)viewNeedsToResizeToSize: (NSSize)newSize
1338 {
1339     NSSize viewSize = [fPictureView frame].size;
1340     return (newSize.width != viewSize.width || newSize.height != viewSize.height);
1341 }
1342
1343 @end