1 /* $Id: HBPreviewController.mm,v 1.11 2005/08/01 15:10:44 titer Exp $
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. */
7 #import "HBPreviewController.h"
10 @interface PreviewController (Private)
12 - (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize;
13 - (void)resizeSheetForViewSize: (NSSize)viewSize;
14 - (void)setViewSize: (NSSize)viewSize;
15 - (BOOL)viewNeedsToResizeToSize: (NSSize)newSize;
19 @implementation PreviewController
23 if (self = [super initWithWindowNibName:@"PicturePreview"])
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
31 // If/when we switch a lot of this stuff to bindings, this can probably
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);
46 //------------------------------------------------------------------------------------
47 // Displays and brings the picture window to the front
48 //------------------------------------------------------------------------------------
49 - (IBAction) showPreviewWindow: (id)sender
51 [self showWindow:sender];
52 [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"PreviewWindowIsOpen"];
54 /* lets set the preview window to accept mouse moved events */
55 [fPreviewWindow setAcceptsMouseMovedEvents:YES];
57 [self pictureSliderChanged:nil];
58 [self startReceivingLibhbNotifications];
61 - (void)setHBController: (HBController *)controller
63 fHBController = controller;
68 [fPreviewWindow setDelegate:self];
69 if( ![[self window] setFrameUsingName:@"Preview"] )
70 [[self window] center];
71 [self setWindowFrameAutosaveName:@"Preview"];
72 [[self window] setExcludedFromWindowsMenu:YES];
74 /* lets set the preview window to accept mouse moved events */
75 [fPreviewWindow setAcceptsMouseMovedEvents:YES];
76 //[self pictureSliderChanged:nil];
77 [self startReceivingLibhbNotifications];
82 /* Setup our layers for core animation */
83 [fPictureViewArea setWantsLayer:YES];
84 [fPictureView setWantsLayer:YES];
86 [fMovieView setWantsLayer:YES];
88 [fCancelPreviewMovieButton setWantsLayer:YES];
89 [fMovieCreationProgressIndicator setWantsLayer:YES];
91 [fPictureControlBox setWantsLayer:YES];
92 [fPictureSlider setWantsLayer:YES];
93 [fFullScreenToggleButton setWantsLayer:YES];
94 [fPictureSettingsToggleButton setWantsLayer:YES];
95 [fScaleToScreenToggleButton setWantsLayer:YES];
96 [fCreatePreviewMovieButton setWantsLayer:YES];
98 [fEncodingControlBox setWantsLayer:YES];
100 [fShowPreviewMovieButton setWantsLayer:YES];
104 - (BOOL)acceptsMouseMovedEvents
109 - (void)windowWillClose:(NSNotification *)aNotification
111 /* Upon Closing the picture window, we make sure we clean up any
112 * preview movie that might be playing
115 hb_stop( fPreviewLibhb );
117 // Show the picture view
118 [fPictureView setHidden:NO];
119 [fMovieView pause:nil];
120 [fMovieView setHidden:YES];
123 [self goWindowedScreen:nil];
128 [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"PreviewWindowIsOpen"];
131 - (BOOL)windowShouldClose:(id)fPictureWindow
138 hb_stop(fPreviewLibhb);
139 if (fPreviewMoviePath)
141 [[NSFileManager defaultManager] removeFileAtPath:fPreviewMoviePath handler:nil];
142 [fPreviewMoviePath release];
145 [fLibhbTimer invalidate];
146 [fLibhbTimer release];
148 [fHudTimer invalidate];
151 [fPicturePreviews release];
152 [fFullScreenWindow release];
157 - (void) SetHandle: (hb_handle_t *) handle
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"];
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];
184 if ([[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewLength"])
186 [fPreviewMovieLengthPopUp selectItemWithTitle:[[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewLength"]];
190 /* currently hard set default to 10 seconds */
191 [fPreviewMovieLengthPopUp selectItemAtIndex: 1];
195 - (void) SetTitle: (hb_title_t *) title
197 hb_job_t * job = title->job;
201 MaxOutputWidth = title->width - job->crop[2] - job->crop[3];
202 MaxOutputHeight = title->height - job->crop[0] - job->crop[1];
203 [self SettingsChanged: nil];
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
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
216 [fMovieView pause:nil];
217 [fMovieView setHidden:YES];
218 [fMovieCreationProgressIndicator stopAnimation: nil];
219 [fMovieCreationProgressIndicator setHidden: YES];
221 [fPictureView setHidden:NO];
223 [fPictureView setImage: [self imageForPicture: fPicture]];
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
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 );
239 else if (fTitle->job->anamorphic.mode == 2) // Loose Anamorphic
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]];
247 displaySize.width = display_width;
249 else // No Anamorphic
251 [fInfoField setStringValue: [NSString stringWithFormat:
252 @"Source: %dx%d, Output: %dx%d", fTitle->width, fTitle->height,
253 fTitle->job->width, fTitle->job->height]];
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])
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))
265 [self resizeSheetForViewSize:viewSize];
266 [self setViewSize:viewSize];
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 )
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" ),
280 [fscaleInfoField setStringValue: [NSString stringWithFormat:
281 @"%@", scaleString]];
286 [fscaleInfoField setStringValue: @""];
291 - (IBAction) previewDurationPopUpChanged: (id) sender
294 [[NSUserDefaults standardUserDefaults] setObject:[fPreviewMovieLengthPopUp titleOfSelectedItem] forKey:@"PreviewLength"];
298 - (IBAction) SettingsChanged: (id) sender
300 // Purge the existing picture previews so they get recreated the next time
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
306 //[self displayPreview];
307 [self pictureSliderChanged:nil];
310 - (IBAction) pictureSliderChanged: (id) sender
312 // Show the picture view
313 [fPictureView setHidden:NO];
314 [fMovieView pause:nil];
315 [fMovieView setHidden:YES];
316 [fEncodingControlBox setHidden: YES];
318 int newPicture = [fPictureSlider intValue];
319 if (newPicture != fPicture)
321 fPicture = newPicture;
323 [self displayPreview];
327 - (IBAction)showPreviewPanel: (id)sender forTitle: (hb_title_t *)title
329 [self SetTitle:title];
331 if ([fPreviewWindow isVisible])
334 [fPreviewWindow close];
339 [self showWindow:sender];
340 [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"PreviewWindowIsOpen"];
341 [fPreviewWindow setAcceptsMouseMovedEvents:YES];
345 [self pictureSliderChanged:nil];
346 [self startHudTimer];
352 - (IBAction)showPictureSettings:(id)sender
354 [fHBController showPicturePanel:self];
357 #pragma mark Hud Control Overlay
358 - (void) mouseMoved:(NSEvent *)theEvent
360 [super mouseMoved:theEvent];
362 if (isEncoding == NO)
364 if (hudTimerSeconds == 0)
367 [self startHudTimer];
370 if (hudTimerSeconds > 20)
375 [self showHideHudControls];
381 - (void) startHudTimer
385 fHudTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(hudTimerFired:) userInfo:nil repeats:YES];
390 - (void) stopHudTimer
394 [fHudTimer invalidate];
401 - (void) hudTimerFired: (NSTimer*)theTimer
404 [self showHideHudControls];
408 - (void) showHideHudControls
410 /* Test for mouse location to show/hide hud controls */
413 NSRect controlBoxFrame;
414 targetFrame = [fPictureViewArea frame];
415 controlBoxFrame = [fPictureControlBox frame];
419 mouseLoc = [fFullScreenWindow mouseLocationOutsideOfEventStream];
420 [fScaleToScreenToggleButton setHidden:NO];
424 mouseLoc = [fPreviewWindow mouseLocationOutsideOfEventStream];
425 [fScaleToScreenToggleButton setHidden:YES];
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
432 if ( hudTimerSeconds > 0 && hudTimerSeconds < 20)
435 if (isEncoding == NO)
437 if (NSPointInRect (mouseLoc, controlBoxFrame))
439 /* Mouse is over the preview area so show hud controls so just
440 * reset the timer to keep the control box visible
442 [fPictureControlBox setHidden: NO];
446 /* Re-verify we are within the target frame */
447 if (NSPointInRect (mouseLoc, targetFrame))
449 /* Mouse is over the preview area so show hud controls */
450 [[fPictureControlBox animator] setHidden: NO];
451 /* increment our timer by one */
456 [[fPictureControlBox animator] setHidden: YES];
464 [[fPictureControlBox animator] setHidden: YES];
470 #pragma mark Fullscreen Mode
472 - (IBAction)toggleScreenMode:(id)sender
476 [self goFullScreen:nil];
480 [self goWindowedScreen:nil];
484 - (IBAction)toggleScaleToScreen:(id)sender
486 if (scaleToScreen == YES)
489 /* make sure we are set to a still preview */
490 [self pictureSliderChanged:nil];
491 [fScaleToScreenToggleButton setTitle:@"<->"];
496 /* make sure we are set to a still preview */
497 [self pictureSliderChanged:nil];
498 [fScaleToScreenToggleButton setTitle:@">-<"];
501 /* Actually perform the scaling */
503 NSSize displaySize = NSMakeSize( ( CGFloat )fTitle->width, ( CGFloat )fTitle->height );
504 NSSize viewSize = [self optimalViewSizeForImageSize:displaySize];
505 [self resizeSheetForViewSize:viewSize];
506 [self setViewSize:viewSize];
515 - (IBAction)goFullScreen:(id)sender
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);
525 if (err == CGDisplayNoErr)
528 /* make sure we are set to a still preview and not scaled to screen */
530 [self pictureSliderChanged:nil];
532 // Create the full-screen window.
533 //NSRect winRect = [mainScreen frame];
535 NSRect winRect = [fPictureViewArea frame];
537 fFullScreenWindow = [[NSWindow alloc] initWithContentRect:winRect
538 styleMask:NSBorderlessWindowMask
539 backing:NSBackingStoreBuffered
541 screen:[NSScreen mainScreen]];
543 // Establish the window attributes.
544 [fFullScreenWindow setReleasedWhenClosed:NO];
545 [fFullScreenWindow setDisplaysWhenScreenProfileChanges:YES];
546 [fFullScreenWindow setDelegate:self];
548 /* insert a view into the new window */
549 [fFullScreenWindow setContentView:fPictureViewArea];
550 [fPictureViewArea setNeedsDisplay:YES];
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.
560 NSSize screenSize = [[NSScreen mainScreen] frame].size;
561 NSSize windowSize = [fFullScreenWindow frame].size;
562 NSPoint windowOrigin = [fFullScreenWindow frame].origin;
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;
568 [fFullScreenWindow setFrameOrigin:windowOrigin];
572 /* lets kill the timer for now */
573 [self stopReceivingLibhbNotifications];
575 /* We need to retain the fPreviewWindow */
576 [fPreviewWindow retain];
578 [self setWindow:fFullScreenWindow];
580 // The window has to be above the level of the shield window.
581 int32_t shieldLevel = CGShieldingWindowLevel();
583 [fFullScreenWindow setLevel:shieldLevel];
586 [fFullScreenWindow makeKeyAndOrderFront:self];
588 /* Change the name of fFullScreenToggleButton appropriately */
589 [fFullScreenToggleButton setTitle: @"Windowed"];
591 /* Lets fire the timer back up for the hud controls, etc. */
592 [self startReceivingLibhbNotifications];
595 [fScaleToScreenToggleButton setHidden:NO];
597 /* make sure we are set to a still preview */
598 [self pictureSliderChanged:nil];
600 //[fPreviewWindow setAcceptsMouseMovedEvents:NO];
601 [fFullScreenWindow setAcceptsMouseMovedEvents:YES];
605 [self startHudTimer];
609 - (IBAction)goWindowedScreen:(id)sender
612 /* Get the screen info to release the display but don't actually do
613 * it until the windowed screen is setup.
616 [self pictureSliderChanged:nil];
617 [fScaleToScreenToggleButton setTitle:@"<->"];
619 NSScreen* mainScreen = [NSScreen mainScreen];
620 NSDictionary* screenInfo = [mainScreen deviceDescription];
621 NSNumber* screenID = [screenInfo objectForKey:@"NSScreenNumber"];
622 CGDirectDisplayID displayID = (CGDirectDisplayID)[screenID longValue];
624 [fFullScreenWindow dealloc];
625 [fFullScreenWindow release];
628 [fPreviewWindow setContentView:fPictureViewArea];
629 [fPictureViewArea setNeedsDisplay:YES];
630 [self setWindow:fPreviewWindow];
633 [fPreviewWindow makeKeyAndOrderFront:self];
635 /* Set the window back to regular level */
636 [fPreviewWindow setLevel:NSNormalWindowLevel];
638 /* Set the isFullScreen flag back to NO */
641 /* make sure we are set to a still preview */
642 [self pictureSliderChanged:nil];
643 [self showPreviewWindow:nil];
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];
651 /* Release the display now that the we are back in windowed mode */
652 CGDisplayRelease(displayID);
654 [fPreviewWindow setAcceptsMouseMovedEvents:YES];
655 //[fFullScreenWindow setAcceptsMouseMovedEvents:NO];
658 [self startHudTimer];
663 #pragma mark Still Preview Image Processing
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
678 // |<---------- title->width ----------->|
679 // | |<---- title->job->width ---->| |
681 // .......................................
682 // ....+-----------------------------+....
683 // ....| |....<-- gray border
686 // ....| |<------- image
692 // ....+-----------------------------+....
693 // .......................................
695 static uint8_t * buffer;
696 static int bufferSize;
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;
704 newSize = srcWidth * srcHeight * 4;
705 if( bufferSize < newSize )
707 bufferSize = newSize;
708 buffer = (uint8_t *) realloc( buffer, bufferSize );
711 hb_get_preview( handle, title, pictureIndex, buffer );
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.
717 // The image data returned by hb_get_preview is 4 bytes per pixel, BGRA format.
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
728 samplesPerPixel:3 // ignore alpha
731 colorSpaceName:NSCalibratedRGBColorSpace
732 bitmapFormat:bitmapFormat
733 bytesPerRow:dstWidth * 4
734 bitsPerPixel:32] autorelease];
736 int borderTop = (srcHeight - dstHeight) / 2;
737 int borderLeft = (srcWidth - dstWidth) / 2;
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++)
745 for (int c = 0; c < dstWidth; c++)
746 #if TARGET_RT_LITTLE_ENDIAN
747 *dst++ = Endian32_Swap(*src++);
751 src += (srcWidth - dstWidth); // skip to next row in src
754 NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(dstWidth, dstHeight)] autorelease];
755 [img addRepresentation:imgrep];
761 // Make sure we have big enough buffer
762 static uint8_t * buffer;
763 static int bufferSize;
766 newSize = ( title->width + 2 ) * (title->height + 2 ) * 4;
767 if( bufferSize < newSize )
769 bufferSize = newSize;
770 buffer = (uint8_t *) realloc( buffer, bufferSize );
773 hb_get_preview( handle, title, pictureIndex, buffer );
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
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
787 samplesPerPixel:3 // ignore alpha
790 colorSpaceName:NSCalibratedRGBColorSpace
791 bitmapFormat:bitmapFormat
792 bytesPerRow:width * 4
793 bitsPerPixel:32] autorelease];
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++);
804 NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(width, height)] autorelease];
805 [img addRepresentation:imgrep];
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
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];
823 theImage = [PreviewController makeImageForPicture:pictureIndex libhb:fHandle title:fTitle removeBorders: NO];
824 [fPicturePreviews setObject:theImage forKey:key];
829 // Purges all images from the cache. The next call to imageForPicture will cause a new
830 // image to be generated.
831 - (void) purgeImageCache
833 [fPicturePreviews removeAllObjects];
838 #pragma mark Movie Preview
839 - (IBAction) createMoviePreview: (id) sender
843 /* Lets make sure the still picture previews are showing in case
844 * there is currently a movie showing */
845 [self pictureSliderChanged:nil];
847 /* Rip or Cancel ? */
849 hb_get_state2( fPreviewLibhb, &s );
851 if(sender == fCancelPreviewMovieButton && (s.state == HB_STATE_WORKING || s.state == HB_STATE_PAUSED))
855 hb_stop( fPreviewLibhb );
856 [fPictureView setHidden:NO];
857 [fMovieView pause:nil];
858 [fMovieView setHidden:YES];
859 [fPictureSlider setHidden:NO];
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
871 hb_job_t * job = fTitle->job;
873 /* We run our current setting through prepeareJob in Controller.mm
874 * just as if it were a regular encode */
876 [fHBController prepareJobForPreview];
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
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";
885 else if (fTitle->job->mux == HB_MUX_MKV) // MKV file
887 fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.mkv";
889 else if (fTitle->job->mux == HB_MUX_AVI) // AVI file
891 fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.avi";
893 else if (fTitle->job->mux == HB_MUX_OGM) // OGM file
895 fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.ogm";
898 fPreviewMoviePath = [[fPreviewMoviePath stringByExpandingTildeInPath]retain];
900 /* See if there is an existing preview file, if so, delete it */
901 if( ![[NSFileManager defaultManager] fileExistsAtPath:fPreviewMoviePath] )
903 [[NSFileManager defaultManager] removeFileAtPath:fPreviewMoviePath
907 /* We now direct our preview encode to fPreviewMoviePath */
908 fTitle->job->file = [fPreviewMoviePath UTF8String];
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;
915 /* we use the preview duration popup to get the specified
916 * number of seconds for the preview encode.
919 job->pts_to_stop = [[fPreviewMovieLengthPopUp titleOfSelectedItem] intValue] * 90000LL;
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.
925 hb_add( fPreviewLibhb, job );
927 [fEncodingControlBox setHidden: NO];
928 [fPictureControlBox setHidden: YES];
930 [fMovieCreationProgressIndicator setHidden: NO];
931 [fPreviewMovieStatusField setHidden: NO];
937 /* Let fPreviewLibhb do the job */
938 hb_start( fPreviewLibhb );
942 - (void) startReceivingLibhbNotifications
946 fLibhbTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(libhbTimerFired:) userInfo:nil repeats:YES];
947 [fLibhbTimer retain];
951 - (void) stopReceivingLibhbNotifications
955 [fLibhbTimer invalidate];
956 [fLibhbTimer release];
960 - (void) libhbTimerFired: (NSTimer*)theTimer
963 hb_get_state( fPreviewLibhb, &s );
964 [self libhbStateChanged: s];
968 - (void) libhbStateChanged: (hb_state_t &)state
970 switch( state.state )
973 case HB_STATE_SCANNING:
974 case HB_STATE_SCANDONE:
977 case HB_STATE_WORKING:
979 #define p state.param.working
981 NSMutableString * string;
982 /* Update text field */
983 string = [NSMutableString stringWithFormat: NSLocalizedString( @"Encoding preview: %.2f %%", @"" ), 100.0 * p.progress];
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];
991 [fPreviewMovieStatusField setStringValue: string];
993 [fMovieCreationProgressIndicator setIndeterminate: NO];
995 [fMovieCreationProgressIndicator setDoubleValue: 100.0 * p.progress];
997 [fCreatePreviewMovieButton setTitle: @"Cancel Preview"];
1004 #define p state.param.muxing
1005 case HB_STATE_MUXING:
1007 // Update fMovieCreationProgressIndicator
1008 [fMovieCreationProgressIndicator setIndeterminate: YES];
1009 [fMovieCreationProgressIndicator startAnimation: nil];
1010 [fPreviewMovieStatusField setStringValue: [NSString stringWithFormat:
1011 NSLocalizedString( @"Muxing Preview ...", @"" )]];
1015 case HB_STATE_PAUSED:
1016 [fMovieCreationProgressIndicator stopAnimation: nil];
1019 case HB_STATE_WORKDONE:
1021 // Delete all remaining jobs since libhb doesn't do this on its own.
1023 while( ( job = hb_job(fPreviewLibhb, 0) ) )
1024 hb_rem( fHandle, job );
1026 [fPreviewMovieStatusField setStringValue: @""];
1027 [fPreviewMovieStatusField setHidden: YES];
1029 [fMovieCreationProgressIndicator stopAnimation: nil];
1030 [fMovieCreationProgressIndicator setHidden: YES];
1031 [fEncodingControlBox setHidden: YES];
1033 /* we make sure the picture slider and preview match */
1034 [self pictureSliderChanged:nil];
1037 // Show the movie view
1040 [self showMoviePreview:fPreviewMoviePath];
1043 [fCreatePreviewMovieButton setTitle: @"Live Preview"];
1052 - (IBAction) showMoviePreview: (NSString *) path
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
1060 [fMovieView setHidden:NO];
1062 /* Load the new movie into fMovieView */
1067 [fMovieView setControllerVisible: YES];
1068 /* let's make sure there is no movie currently set */
1069 [fMovieView setMovie:nil];
1071 aMovie = [QTMovie movieWithFile:path error:nil];
1073 /* we get some size information from the preview movie */
1075 GetMovieBox ([aMovie quickTimeMovie], &movieBox);
1076 movieBounds = [fMovieView movieBounds];
1077 movieBounds.size.height = movieBox.bottom - movieBox.top;
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
1084 movieBounds.size.height += 15;
1086 movieBounds.size.width = movieBox.right - movieBox.left;
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)
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
1097 NSSize displaySize = NSMakeSize( (float)movieBounds.size.width, (float)movieBounds.size.height );
1098 NSSize viewSize = [self optimalViewSizeForImageSize:displaySize];
1099 if( [self viewNeedsToResizeToSize:viewSize] )
1102 [self resizeSheetForViewSize:viewSize];
1103 [self setViewSize:viewSize];
1107 [fMovieView setPreservesAspectRatio:YES];
1108 [fMovieView setFrameSize:viewSize];
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];
1121 // lets reposition the movie if need be
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)
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
1133 origin.y += trunc((([fPictureViewArea frame].size.height -
1134 [fMovieView frame].size.height) / 2.0) - 7.5);
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
1141 origin.y += trunc(([fPictureViewArea frame].size.height -
1142 [fMovieView frame].size.height) / 2.0);
1144 [fMovieView setFrameOrigin:origin];
1146 [fMovieView setMovie:aMovie];
1147 /// to actually play the movie
1148 [fMovieView play:aMovie];
1160 @implementation PreviewController (Private)
1163 // -[PictureController(Private) optimalViewSizeForImageSize:]
1165 // Given the size of the preview image to be shown, returns the best possible
1166 // size for the view.
1168 - (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize
1170 // The min size is 320x240
1171 CGFloat minWidth = 480.0;
1172 CGFloat minHeight = 360.0;
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;
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;
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;
1195 NSSize resultSize = imageSize;
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 )
1201 resultSize.height *= (minWidth / resultSize.width);
1202 resultSize.width = minWidth;
1204 if( resultSize.height < minHeight )
1206 resultSize.width *= (minHeight / resultSize.height);
1207 resultSize.height = minHeight;
1209 if( resultSize.width > maxWidth )
1211 resultSize.height *= (maxWidth / resultSize.width);
1212 resultSize.width = maxWidth;
1214 if( resultSize.height > maxHeight )
1216 resultSize.width *= (maxHeight / resultSize.height);
1217 resultSize.height = maxHeight;
1220 if (scaleToScreen == YES)
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;
1229 // Note, a standard dvd will use 720 x 480 which is a 1.5
1230 viewAreaAspect = viewAreaSize.width / viewAreaSize.height;
1232 if (screenAspect < viewAreaAspect)
1234 resultSize.width = screenSize.width;
1235 resultSize.height = (screenSize.width / viewAreaAspect);
1239 resultSize.height = screenSize.height;
1240 resultSize.width = resultSize.height * viewAreaAspect;
1251 // -[PictureController(Private) resizePanelForViewSize:animate:]
1253 // Resizes the entire window to accomodate a view of a particular size.
1255 - (void)resizeSheetForViewSize: (NSSize)viewSize
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;
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 )
1270 frame.size.width = minSize.width;
1273 if( frame.size.height < minSize.height )
1275 frame.size.height = minSize.height;
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);
1286 if( frame.size.height != [[self window] frame].size.height )
1288 frame.origin.y -= (deltaY / 2.0);
1292 if( frame.size.height != [[self window] frame].size.height )
1293 frame.origin.y -= deltaY;
1296 [[self window] setFrame:frame display:YES animate:NO];
1300 [[self window] setFrame:frame display:YES animate:YES];
1306 // -[PictureController(Private) setViewSize:]
1308 // Changes the view's size and centers it vertically inside of its area.
1309 // Assumes resizeSheetForViewSize: has already been called.
1311 - (void)setViewSize: (NSSize)viewSize
1313 [fPictureView setFrameSize:viewSize];
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];
1321 NSPoint controlboxorigin = [fPictureView frame].origin;
1323 /* for now, put the origin.y 100 above the bottom of the fPictureView */
1324 controlboxorigin.y += 100;
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.
1331 [fPictureControlBox setFrameOrigin:controlboxorigin];
1332 [fEncodingControlBox setFrameOrigin:controlboxorigin];
1337 - (BOOL)viewNeedsToResizeToSize: (NSSize)newSize
1339 NSSize viewSize = [fPictureView frame].size;
1340 return (newSize.width != viewSize.width || newSize.height != viewSize.height);