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];
222 [fPictureView setImage: [self imageForPicture: fPicture]];
224 NSSize displaySize = NSMakeSize( ( CGFloat )fTitle->width, ( CGFloat )fTitle->height );
225 /* Set the picture size display fields below the Preview Picture*/
226 if( fTitle->job->anamorphic.mode == 1 ) // Original PAR Implementation
228 output_width = fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3];
229 output_height = fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1];
230 display_width = output_width * fTitle->job->anamorphic.par_width / fTitle->job->anamorphic.par_height;
231 [fInfoField setStringValue:[NSString stringWithFormat:
232 @"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d Strict",
233 fTitle->width, fTitle->height, output_width, output_height, display_width, output_height]];
234 displaySize.width *= ( ( CGFloat )fTitle->job->anamorphic.par_width ) / ( ( CGFloat )fTitle->job->anamorphic.par_height );
236 else if (fTitle->job->anamorphic.mode == 2) // Loose Anamorphic
238 hb_set_anamorphic_size(job, &output_width, &output_height, &output_par_width, &output_par_height);
239 display_width = output_width * output_par_width / output_par_height;
240 [fInfoField setStringValue:[NSString stringWithFormat:
241 @"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d Loose",
242 fTitle->width, fTitle->height, output_width, output_height, display_width, output_height]];
244 displaySize.width = display_width;
246 else // No Anamorphic
248 [fInfoField setStringValue: [NSString stringWithFormat:
249 @"Source: %dx%d, Output: %dx%d", fTitle->width, fTitle->height,
250 fTitle->job->width, fTitle->job->height]];
254 NSSize viewSize = [self optimalViewSizeForImageSize:displaySize];
255 /* we also need to take into account scaling to full screen to activate switching the view size */
256 if( [self viewNeedsToResizeToSize:viewSize])
258 /* In the case of loose anamorphic, do not resize the window when scaling down */
259 // FIX ME: we need a new way to do this as we do not havefWidthField anymore
260 //if (fTitle->job->anamorphic.mode != 2 || [fWidthField intValue] == fTitle->width)
261 if (fTitle->job->anamorphic.mode != 2 || (fTitle->job->anamorphic.mode == 2 && output_width == fTitle->width))
263 [self resizeSheetForViewSize:viewSize];
264 [self setViewSize:viewSize];
269 // Show the scaled text (use the height to check since the width can vary
270 // with anamorphic video).
271 if( ( ( int )viewSize.height ) != fTitle->height )
273 CGFloat scale = viewSize.width / ( ( CGFloat ) fTitle->width );
274 NSString *scaleString = [NSString stringWithFormat:
275 NSLocalizedString( @" (Preview scaled to %.0f%% actual size)",
276 @"String shown when a preview is scaled" ),
278 [fscaleInfoField setStringValue: [NSString stringWithFormat:
279 @"%@", scaleString]];
284 [fscaleInfoField setStringValue: @""];
289 - (IBAction) previewDurationPopUpChanged: (id) sender
292 [[NSUserDefaults standardUserDefaults] setObject:[fPreviewMovieLengthPopUp titleOfSelectedItem] forKey:@"PreviewLength"];
296 - (IBAction) SettingsChanged: (id) sender
298 // Purge the existing picture previews so they get recreated the next time
300 [self purgeImageCache];
301 /* We actually call displayPreview now from pictureSliderChanged which keeps
302 * our picture preview slider in sync with the previews being shown
304 //[self displayPreview];
305 [self pictureSliderChanged:nil];
308 - (IBAction) pictureSliderChanged: (id) sender
310 // Show the picture view
311 [fPictureView setHidden:NO];
312 [fMovieView pause:nil];
313 [fMovieView setHidden:YES];
314 [fEncodingControlBox setHidden: YES];
316 int newPicture = [fPictureSlider intValue];
317 if (newPicture != fPicture)
319 fPicture = newPicture;
321 [self displayPreview];
325 - (IBAction)showPreviewPanel: (id)sender forTitle: (hb_title_t *)title
327 [self SetTitle:title];
329 if ([fPreviewWindow isVisible])
332 [fPreviewWindow close];
337 [self showWindow:sender];
338 [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"PreviewWindowIsOpen"];
339 [fPreviewWindow setAcceptsMouseMovedEvents:YES];
343 [self pictureSliderChanged:nil];
344 [self startHudTimer];
350 - (NSString*) pictureSizeInfoString
352 return [fInfoField stringValue];
355 - (IBAction)showPictureSettings:(id)sender
357 [fHBController showPicturePanel:self];
360 #pragma mark Hud Control Overlay
361 - (void) mouseMoved:(NSEvent *)theEvent
363 [super mouseMoved:theEvent];
365 if (isEncoding == NO)
367 if (hudTimerSeconds == 0)
370 [self startHudTimer];
373 if (hudTimerSeconds > 20)
378 [self showHideHudControls];
384 - (void) startHudTimer
388 fHudTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(hudTimerFired:) userInfo:nil repeats:YES];
393 - (void) stopHudTimer
397 [fHudTimer invalidate];
404 - (void) hudTimerFired: (NSTimer*)theTimer
407 [self showHideHudControls];
411 - (void) showHideHudControls
413 /* Test for mouse location to show/hide hud controls */
416 NSRect controlBoxFrame;
417 targetFrame = [fPictureViewArea frame];
418 controlBoxFrame = [fPictureControlBox frame];
422 mouseLoc = [fFullScreenWindow mouseLocationOutsideOfEventStream];
423 [fScaleToScreenToggleButton setHidden:NO];
427 mouseLoc = [fPreviewWindow mouseLocationOutsideOfEventStream];
428 [fScaleToScreenToggleButton setHidden:YES];
431 /* if the pointer is inside the picture view areas but not
432 * in the controlbox, check the hudTimerSeconds to see if
433 * its in the allowable time span
435 if ( hudTimerSeconds > 0 && hudTimerSeconds < 20)
438 if (isEncoding == NO)
440 if (NSPointInRect (mouseLoc, controlBoxFrame))
442 /* Mouse is over the preview area so show hud controls so just
443 * reset the timer to keep the control box visible
445 [fPictureControlBox setHidden: NO];
449 /* Re-verify we are within the target frame */
450 if (NSPointInRect (mouseLoc, targetFrame))
452 /* Mouse is over the preview area so show hud controls */
453 [[fPictureControlBox animator] setHidden: NO];
454 /* increment our timer by one */
459 [[fPictureControlBox animator] setHidden: YES];
467 [[fPictureControlBox animator] setHidden: YES];
473 #pragma mark Fullscreen Mode
475 - (IBAction)toggleScreenMode:(id)sender
479 [self goFullScreen:nil];
483 [self goWindowedScreen:nil];
487 - (IBAction)toggleScaleToScreen:(id)sender
489 if (scaleToScreen == YES)
492 /* make sure we are set to a still preview */
493 [self pictureSliderChanged:nil];
494 [fScaleToScreenToggleButton setTitle:@"<->"];
499 /* make sure we are set to a still preview */
500 [self pictureSliderChanged:nil];
501 [fScaleToScreenToggleButton setTitle:@">-<"];
504 /* Actually perform the scaling */
506 NSSize displaySize = NSMakeSize( ( CGFloat )fTitle->width, ( CGFloat )fTitle->height );
507 NSSize viewSize = [self optimalViewSizeForImageSize:displaySize];
508 [self resizeSheetForViewSize:viewSize];
509 [self setViewSize:viewSize];
518 - (IBAction)goFullScreen:(id)sender
520 // Get the screen information.
521 NSScreen* mainScreen = [NSScreen mainScreen];
522 NSDictionary* screenInfo = [mainScreen deviceDescription];
523 NSNumber* screenID = [screenInfo objectForKey:@"NSScreenNumber"];
524 // Capture the screen.
525 CGDirectDisplayID displayID = (CGDirectDisplayID)[screenID longValue];
526 CGDisplayErr err = CGDisplayCapture(displayID);
528 if (err == CGDisplayNoErr)
531 /* make sure we are set to a still preview and not scaled to screen */
533 [self pictureSliderChanged:nil];
535 // Create the full-screen window.
536 //NSRect winRect = [mainScreen frame];
538 NSRect winRect = [fPictureViewArea frame];
540 fFullScreenWindow = [[NSWindow alloc] initWithContentRect:winRect
541 styleMask:NSBorderlessWindowMask
542 backing:NSBackingStoreBuffered
544 screen:[NSScreen mainScreen]];
546 // Establish the window attributes.
547 [fFullScreenWindow setReleasedWhenClosed:NO];
548 [fFullScreenWindow setDisplaysWhenScreenProfileChanges:YES];
549 [fFullScreenWindow setDelegate:self];
551 /* insert a view into the new window */
552 [fFullScreenWindow setContentView:fPictureViewArea];
553 [fPictureViewArea setNeedsDisplay:YES];
557 /* Better to center the window using the screen's frame
558 * and the windows origin. Note that we should take into
559 * account the auto sizing and alignment that occurs in
560 * setViewSize each time the preview changes.
563 NSSize screenSize = [[NSScreen mainScreen] frame].size;
564 NSSize windowSize = [fFullScreenWindow frame].size;
565 NSPoint windowOrigin = [fFullScreenWindow frame].origin;
567 /* Adjust our origin y (vertical) based on the screen height */
568 windowOrigin.y = (screenSize.height - windowSize.height) / 2.0;
569 windowOrigin.x = (screenSize.width - windowSize.width) / 2.0;
571 [fFullScreenWindow setFrameOrigin:windowOrigin];
575 /* lets kill the timer for now */
576 [self stopReceivingLibhbNotifications];
578 /* We need to retain the fPreviewWindow */
579 [fPreviewWindow retain];
581 [self setWindow:fFullScreenWindow];
583 // The window has to be above the level of the shield window.
584 int32_t shieldLevel = CGShieldingWindowLevel();
586 [fFullScreenWindow setLevel:shieldLevel];
589 [fFullScreenWindow makeKeyAndOrderFront:self];
591 /* Change the name of fFullScreenToggleButton appropriately */
592 [fFullScreenToggleButton setTitle: @"Windowed"];
594 /* Lets fire the timer back up for the hud controls, etc. */
595 [self startReceivingLibhbNotifications];
598 [fScaleToScreenToggleButton setHidden:NO];
600 /* make sure we are set to a still preview */
601 [self pictureSliderChanged:nil];
603 //[fPreviewWindow setAcceptsMouseMovedEvents:NO];
604 [fFullScreenWindow setAcceptsMouseMovedEvents:YES];
608 [self startHudTimer];
612 - (IBAction)goWindowedScreen:(id)sender
615 /* Get the screen info to release the display but don't actually do
616 * it until the windowed screen is setup.
619 [self pictureSliderChanged:nil];
620 [fScaleToScreenToggleButton setTitle:@"<->"];
622 NSScreen* mainScreen = [NSScreen mainScreen];
623 NSDictionary* screenInfo = [mainScreen deviceDescription];
624 NSNumber* screenID = [screenInfo objectForKey:@"NSScreenNumber"];
625 CGDirectDisplayID displayID = (CGDirectDisplayID)[screenID longValue];
627 [fFullScreenWindow dealloc];
628 [fFullScreenWindow release];
631 [fPreviewWindow setContentView:fPictureViewArea];
632 [fPictureViewArea setNeedsDisplay:YES];
633 [self setWindow:fPreviewWindow];
636 [fPreviewWindow makeKeyAndOrderFront:self];
638 /* Set the window back to regular level */
639 [fPreviewWindow setLevel:NSNormalWindowLevel];
641 /* Set the isFullScreen flag back to NO */
644 /* make sure we are set to a still preview */
645 [self pictureSliderChanged:nil];
646 [self showPreviewWindow:nil];
648 /* Change the name of fFullScreenToggleButton appropriately */
649 [fFullScreenToggleButton setTitle: @"Full Screen"];
650 // [fScaleToScreenToggleButton setHidden:YES];
651 /* set the picture settings pallete back to normal level */
652 [fHBController picturePanelWindowed];
654 /* Release the display now that the we are back in windowed mode */
655 CGDisplayRelease(displayID);
657 [fPreviewWindow setAcceptsMouseMovedEvents:YES];
658 //[fFullScreenWindow setAcceptsMouseMovedEvents:NO];
661 [self startHudTimer];
666 #pragma mark Still Preview Image Processing
669 // This function converts an image created by libhb (specified via pictureIndex) into
670 // an NSImage suitable for the GUI code to use. If removeBorders is YES,
671 // makeImageForPicture crops the image generated by libhb stripping off the gray
672 // border around the content. This is the low-level method that generates the image.
673 // -imageForPicture calls this function whenever it can't find an image in its cache.
674 + (NSImage *) makeImageForPicture: (int)pictureIndex
675 libhb:(hb_handle_t*)handle
676 title:(hb_title_t*)title
677 removeBorders:(BOOL)removeBorders
681 // |<---------- title->width ----------->|
682 // | |<---- title->job->width ---->| |
684 // .......................................
685 // ....+-----------------------------+....
686 // ....| |....<-- gray border
689 // ....| |<------- image
695 // ....+-----------------------------+....
696 // .......................................
698 static uint8_t * buffer;
699 static int bufferSize;
701 // Make sure we have a big enough buffer to receive the image from libhb. libhb
702 // creates images with a one-pixel border around the original content. Hence we
703 // add 2 pixels horizontally and vertically to the buffer size.
704 int srcWidth = title->width + 2;
705 int srcHeight= title->height + 2;
707 newSize = srcWidth * srcHeight * 4;
708 if( bufferSize < newSize )
710 bufferSize = newSize;
711 buffer = (uint8_t *) realloc( buffer, bufferSize );
714 hb_get_preview( handle, title, pictureIndex, buffer );
716 // Create an NSBitmapImageRep and copy the libhb image into it, converting it from
717 // libhb's format to one suitable for NSImage. Along the way, we'll strip off the
718 // border around libhb's image.
720 // The image data returned by hb_get_preview is 4 bytes per pixel, BGRA format.
723 int dstWidth = title->job->width;
724 int dstHeight = title->job->height;
725 NSBitmapFormat bitmapFormat = (NSBitmapFormat)NSAlphaFirstBitmapFormat;
726 NSBitmapImageRep * imgrep = [[[NSBitmapImageRep alloc]
727 initWithBitmapDataPlanes:nil
731 samplesPerPixel:3 // ignore alpha
734 colorSpaceName:NSCalibratedRGBColorSpace
735 bitmapFormat:bitmapFormat
736 bytesPerRow:dstWidth * 4
737 bitsPerPixel:32] autorelease];
739 int borderTop = (srcHeight - dstHeight) / 2;
740 int borderLeft = (srcWidth - dstWidth) / 2;
742 UInt32 * src = (UInt32 *)buffer;
743 UInt32 * dst = (UInt32 *)[imgrep bitmapData];
744 src += borderTop * srcWidth; // skip top rows in src to get to first row of dst
745 src += borderLeft; // skip left pixels in src to get to first pixel of dst
746 for (int r = 0; r < dstHeight; r++)
748 for (int c = 0; c < dstWidth; c++)
749 #if TARGET_RT_LITTLE_ENDIAN
750 *dst++ = Endian32_Swap(*src++);
754 src += (srcWidth - dstWidth); // skip to next row in src
757 NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(dstWidth, dstHeight)] autorelease];
758 [img addRepresentation:imgrep];
764 // Make sure we have big enough buffer
765 static uint8_t * buffer;
766 static int bufferSize;
769 newSize = ( title->width + 2 ) * (title->height + 2 ) * 4;
770 if( bufferSize < newSize )
772 bufferSize = newSize;
773 buffer = (uint8_t *) realloc( buffer, bufferSize );
776 hb_get_preview( handle, title, pictureIndex, buffer );
778 // The image data returned by hb_get_preview is 4 bytes per pixel, BGRA format.
779 // We'll copy that into an NSImage swapping it to ARGB in the process. Alpha is
781 int width = title->width + 2; // hblib adds a one-pixel border to the image
782 int height = title->height + 2;
783 int numPixels = width * height;
784 NSBitmapFormat bitmapFormat = (NSBitmapFormat)NSAlphaFirstBitmapFormat;
785 NSBitmapImageRep * imgrep = [[[NSBitmapImageRep alloc]
786 initWithBitmapDataPlanes:nil
790 samplesPerPixel:3 // ignore alpha
793 colorSpaceName:NSCalibratedRGBColorSpace
794 bitmapFormat:bitmapFormat
795 bytesPerRow:width * 4
796 bitsPerPixel:32] autorelease];
798 UInt32 * src = (UInt32 *)buffer;
799 UInt32 * dst = (UInt32 *)[imgrep bitmapData];
800 for (int i = 0; i < numPixels; i++)
801 #if TARGET_RT_LITTLE_ENDIAN
802 *dst++ = Endian32_Swap(*src++);
807 NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(width, height)] autorelease];
808 [img addRepresentation:imgrep];
814 // Returns the preview image for the specified index, retrieving it from its internal
815 // cache or by calling makeImageForPicture if it is not cached. Generally, you should
816 // use imageForPicture so that images are cached. Calling makeImageForPicture will
817 // always generate a new copy of the image.
818 - (NSImage *) imageForPicture: (int) pictureIndex
820 // The preview for the specified index may not currently exist, so this method
821 // generates it if necessary.
822 NSString * key = [NSString stringWithFormat:@"%d", pictureIndex];
823 NSImage * theImage = [fPicturePreviews objectForKey:key];
826 theImage = [PreviewController makeImageForPicture:pictureIndex libhb:fHandle title:fTitle removeBorders: NO];
827 [fPicturePreviews setObject:theImage forKey:key];
832 // Purges all images from the cache. The next call to imageForPicture will cause a new
833 // image to be generated.
834 - (void) purgeImageCache
836 [fPicturePreviews removeAllObjects];
841 #pragma mark Movie Preview
842 - (IBAction) createMoviePreview: (id) sender
846 /* Lets make sure the still picture previews are showing in case
847 * there is currently a movie showing */
848 [self pictureSliderChanged:nil];
850 /* Rip or Cancel ? */
852 hb_get_state2( fPreviewLibhb, &s );
854 if(sender == fCancelPreviewMovieButton && (s.state == HB_STATE_WORKING || s.state == HB_STATE_PAUSED))
858 hb_stop( fPreviewLibhb );
859 [fPictureView setHidden:NO];
860 [fMovieView pause:nil];
861 [fMovieView setHidden:YES];
862 [fPictureSlider setHidden:NO];
869 /* we use controller.mm's prepareJobForPreview to go ahead and set all of our settings
870 * however, we want to use a temporary destination field of course
871 * so that we do not put our temp preview in the users chosen
874 hb_job_t * job = fTitle->job;
876 /* We run our current setting through prepeareJob in Controller.mm
877 * just as if it were a regular encode */
879 [fHBController prepareJobForPreview];
881 /* Destination file. We set this to our preview directory
882 * changing the extension appropriately.*/
883 if (fTitle->job->mux == HB_MUX_MP4) // MP4 file
885 /* we use .m4v for our mp4 files so that ac3 and chapters in mp4 will play properly */
886 fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.m4v";
888 else if (fTitle->job->mux == HB_MUX_MKV) // MKV file
890 fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.mkv";
892 else if (fTitle->job->mux == HB_MUX_AVI) // AVI file
894 fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.avi";
896 else if (fTitle->job->mux == HB_MUX_OGM) // OGM file
898 fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.ogm";
901 fPreviewMoviePath = [[fPreviewMoviePath stringByExpandingTildeInPath]retain];
903 /* See if there is an existing preview file, if so, delete it */
904 if( ![[NSFileManager defaultManager] fileExistsAtPath:fPreviewMoviePath] )
906 [[NSFileManager defaultManager] removeFileAtPath:fPreviewMoviePath
910 /* We now direct our preview encode to fPreviewMoviePath */
911 fTitle->job->file = [fPreviewMoviePath UTF8String];
913 /* We use our advance pref to determine how many previews to scan */
914 int hb_num_previews = [[[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewsNumber"] intValue];
915 job->start_at_preview = fPicture + 1;
916 job->seek_points = hb_num_previews;
918 /* we use the preview duration popup to get the specified
919 * number of seconds for the preview encode.
922 job->pts_to_stop = [[fPreviewMovieLengthPopUp titleOfSelectedItem] intValue] * 90000LL;
924 /* lets go ahead and send it off to libhb
925 * Note: unlike a full encode, we only send 1 pass regardless if the final encode calls for 2 passes.
926 * this should suffice for a fairly accurate short preview and cuts our preview generation time in half.
928 hb_add( fPreviewLibhb, job );
930 [fEncodingControlBox setHidden: NO];
931 [fPictureControlBox setHidden: YES];
933 [fMovieCreationProgressIndicator setHidden: NO];
934 [fPreviewMovieStatusField setHidden: NO];
940 /* Let fPreviewLibhb do the job */
941 hb_start( fPreviewLibhb );
945 - (void) startReceivingLibhbNotifications
949 fLibhbTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(libhbTimerFired:) userInfo:nil repeats:YES];
950 [fLibhbTimer retain];
954 - (void) stopReceivingLibhbNotifications
958 [fLibhbTimer invalidate];
959 [fLibhbTimer release];
963 - (void) libhbTimerFired: (NSTimer*)theTimer
966 hb_get_state( fPreviewLibhb, &s );
967 [self libhbStateChanged: s];
971 - (void) libhbStateChanged: (hb_state_t &)state
973 switch( state.state )
976 case HB_STATE_SCANNING:
977 case HB_STATE_SCANDONE:
980 case HB_STATE_WORKING:
982 #define p state.param.working
984 NSMutableString * string;
985 /* Update text field */
986 string = [NSMutableString stringWithFormat: NSLocalizedString( @"Encoding preview: %.2f %%", @"" ), 100.0 * p.progress];
990 [string appendFormat:
991 NSLocalizedString( @" (%.2f fps, avg %.2f fps, ETA %02dh%02dm%02ds)", @"" ),
992 p.rate_cur, p.rate_avg, p.hours, p.minutes, p.seconds];
994 [fPreviewMovieStatusField setStringValue: string];
996 [fMovieCreationProgressIndicator setIndeterminate: NO];
998 [fMovieCreationProgressIndicator setDoubleValue: 100.0 * p.progress];
1000 [fCreatePreviewMovieButton setTitle: @"Cancel Preview"];
1007 #define p state.param.muxing
1008 case HB_STATE_MUXING:
1010 // Update fMovieCreationProgressIndicator
1011 [fMovieCreationProgressIndicator setIndeterminate: YES];
1012 [fMovieCreationProgressIndicator startAnimation: nil];
1013 [fPreviewMovieStatusField setStringValue: [NSString stringWithFormat:
1014 NSLocalizedString( @"Muxing Preview ...", @"" )]];
1018 case HB_STATE_PAUSED:
1019 [fMovieCreationProgressIndicator stopAnimation: nil];
1022 case HB_STATE_WORKDONE:
1024 // Delete all remaining jobs since libhb doesn't do this on its own.
1026 while( ( job = hb_job(fPreviewLibhb, 0) ) )
1027 hb_rem( fHandle, job );
1029 [fPreviewMovieStatusField setStringValue: @""];
1030 [fPreviewMovieStatusField setHidden: YES];
1032 [fMovieCreationProgressIndicator stopAnimation: nil];
1033 [fMovieCreationProgressIndicator setHidden: YES];
1034 [fEncodingControlBox setHidden: YES];
1036 /* we make sure the picture slider and preview match */
1037 [self pictureSliderChanged:nil];
1040 // Show the movie view
1043 [self showMoviePreview:fPreviewMoviePath];
1046 [fCreatePreviewMovieButton setTitle: @"Live Preview"];
1055 - (IBAction) showMoviePreview: (NSString *) path
1057 /* Since the gray background for the still images is part of
1058 * fPictureView, lets leave the picture view visible and postion
1059 * the fMovieView over the image portion of fPictureView so
1060 * we retain the gray cropping border we have already established
1061 * with the still previews
1063 [fMovieView setHidden:NO];
1065 /* Load the new movie into fMovieView */
1070 [fMovieView setControllerVisible: YES];
1071 /* let's make sure there is no movie currently set */
1072 [fMovieView setMovie:nil];
1074 aMovie = [QTMovie movieWithFile:path error:nil];
1076 /* we get some size information from the preview movie */
1078 GetMovieBox ([aMovie quickTimeMovie], &movieBox);
1079 movieBounds = [fMovieView movieBounds];
1080 movieBounds.size.height = movieBox.bottom - movieBox.top;
1082 if ([fMovieView isControllerVisible])
1083 movieBounds.size.height += [fMovieView controllerBarHeight];
1084 /* since for whatever the reason I cannot seem to get the [fMovieView controllerBarHeight]
1085 * For now just use 15 for additional height as it seems to line up well
1087 movieBounds.size.height += 15;
1089 movieBounds.size.width = movieBox.right - movieBox.left;
1091 /* We need to find out if the preview movie needs to be scaled down so
1092 * that it doesn't overflow our available viewing container (just like for image
1093 * in -displayPreview) for HD sources, etc. [fPictureViewArea frame].size.height*/
1094 if( ((int)movieBounds.size.height) > [fPictureView frame].size.height || scaleToScreen == YES)
1096 /* The preview movie would be larger than the available viewing area
1097 * in the preview movie, so we go ahead and scale it down to the same size
1098 * as the still preview or we readjust our window to allow for the added height if need be
1100 NSSize displaySize = NSMakeSize( (float)movieBounds.size.width, (float)movieBounds.size.height );
1101 NSSize viewSize = [self optimalViewSizeForImageSize:displaySize];
1102 if( [self viewNeedsToResizeToSize:viewSize] )
1105 [self resizeSheetForViewSize:viewSize];
1106 [self setViewSize:viewSize];
1110 [fMovieView setPreservesAspectRatio:YES];
1111 [fMovieView setFrameSize:viewSize];
1115 /* Since the preview movie is smaller than the available viewing area
1116 * we can go ahead and use the preview movies native size */
1117 [fMovieView setFrameSize:movieBounds.size];
1124 // lets reposition the movie if need be
1126 NSPoint origin = [fPictureViewArea frame].origin;
1127 origin.x += trunc(([fPictureViewArea frame].size.width -
1128 [fMovieView frame].size.width) / 2.0);
1129 /* We need to detect whether or not we are currently less than the available height.*/
1130 if (movieBounds.size.height < [fPictureView frame].size.height)
1132 /* If we are, we are adding 15 to the height to allow for the controller bar so
1133 * we need to subtract half of that for the origin.y to get the controller bar
1134 * below the movie to it lines up vertically with where our still preview was
1136 origin.y += trunc((([fPictureViewArea frame].size.height -
1137 [fMovieView frame].size.height) / 2.0) - 7.5);
1141 /* if we are >= to the height of the picture view area, the controller bar
1142 * gets taken care of with picture resizing, so we do not want to offset the height
1144 origin.y += trunc(([fPictureViewArea frame].size.height -
1145 [fMovieView frame].size.height) / 2.0);
1147 [fMovieView setFrameOrigin:origin];
1149 [fMovieView setMovie:aMovie];
1150 /// to actually play the movie
1151 [fMovieView play:aMovie];
1163 @implementation PreviewController (Private)
1166 // -[PictureController(Private) optimalViewSizeForImageSize:]
1168 // Given the size of the preview image to be shown, returns the best possible
1169 // size for the view.
1171 - (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize
1173 // The min size is 320x240
1174 CGFloat minWidth = 480.0;
1175 CGFloat minHeight = 360.0;
1177 NSSize screenSize = [[NSScreen mainScreen] frame].size;
1178 NSSize sheetSize = [[self window] frame].size;
1179 NSSize viewAreaSize = [fPictureViewArea frame].size;
1180 CGFloat paddingX = sheetSize.width - viewAreaSize.width;
1181 CGFloat paddingY = sheetSize.height - viewAreaSize.height;
1187 /* We are in full screen mode so lets use the full screen if we need to */
1188 maxWidth = screenSize.width - paddingX;
1189 maxHeight = screenSize.height - paddingY;
1193 // The max size of the view is when the sheet is taking up 85% of the screen.
1194 maxWidth = (0.85 * screenSize.width) - paddingX;
1195 maxHeight = (0.85 * screenSize.height) - paddingY;
1198 NSSize resultSize = imageSize;
1200 // Its better to have a view that's too small than a view that's too big, so
1201 // apply the maximum constraints last.
1202 if( resultSize.width < minWidth )
1204 resultSize.height *= (minWidth / resultSize.width);
1205 resultSize.width = minWidth;
1207 if( resultSize.height < minHeight )
1209 resultSize.width *= (minHeight / resultSize.height);
1210 resultSize.height = minHeight;
1212 if( resultSize.width > maxWidth )
1214 resultSize.height *= (maxWidth / resultSize.width);
1215 resultSize.width = maxWidth;
1217 if( resultSize.height > maxHeight )
1219 resultSize.width *= (maxHeight / resultSize.height);
1220 resultSize.height = maxHeight;
1223 if (scaleToScreen == YES)
1225 //CGFloat scaleToScreenWidth;
1226 //CGFloat scaleToScreenHeight;
1227 CGFloat screenAspect;
1228 CGFloat viewAreaAspect;
1229 //note, a mbp 15" at 1440 x 900 is a 1.6 ar
1230 screenAspect = screenSize.width / screenSize.height;
1232 // Note, a standard dvd will use 720 x 480 which is a 1.5
1233 viewAreaAspect = viewAreaSize.width / viewAreaSize.height;
1235 if (screenAspect < viewAreaAspect)
1237 resultSize.width = screenSize.width;
1238 resultSize.height = (screenSize.width / viewAreaAspect);
1242 resultSize.height = screenSize.height;
1243 resultSize.width = resultSize.height * viewAreaAspect;
1254 // -[PictureController(Private) resizePanelForViewSize:animate:]
1256 // Resizes the entire window to accomodate a view of a particular size.
1258 - (void)resizeSheetForViewSize: (NSSize)viewSize
1260 // Figure out the deltas for the new frame area
1261 NSSize currentSize = [fPictureViewArea frame].size;
1262 CGFloat deltaX = viewSize.width - currentSize.width;
1263 CGFloat deltaY = viewSize.height - currentSize.height;
1265 // Now resize the whole panel by those same deltas, but don't exceed the min
1266 NSRect frame = [[self window] frame];
1267 NSSize maxSize = [[self window] maxSize];
1268 NSSize minSize = [[self window] minSize];
1269 frame.size.width += deltaX;
1270 frame.size.height += deltaY;
1271 if( frame.size.width < minSize.width )
1273 frame.size.width = minSize.width;
1276 if( frame.size.height < minSize.height )
1278 frame.size.height = minSize.height;
1282 // But now the sheet is off-center, so also shift the origin to center it and
1283 // keep the top aligned.
1284 if( frame.size.width != [[self window] frame].size.width )
1285 frame.origin.x -= (deltaX / 2.0);
1289 if( frame.size.height != [[self window] frame].size.height )
1291 frame.origin.y -= (deltaY / 2.0);
1295 if( frame.size.height != [[self window] frame].size.height )
1296 frame.origin.y -= deltaY;
1299 [[self window] setFrame:frame display:YES animate:NO];
1303 [[self window] setFrame:frame display:YES animate:YES];
1309 // -[PictureController(Private) setViewSize:]
1311 // Changes the view's size and centers it vertically inside of its area.
1312 // Assumes resizeSheetForViewSize: has already been called.
1314 - (void)setViewSize: (NSSize)viewSize
1316 [fPictureView setFrameSize:viewSize];
1318 // center it vertically
1319 NSPoint origin = [fPictureViewArea frame].origin;
1320 origin.y += ([fPictureViewArea frame].size.height -
1321 [fPictureView frame].size.height) / 2.0;
1322 [fPictureView setFrameOrigin:origin];
1324 NSPoint controlboxorigin = [fPictureView frame].origin;
1326 /* for now, put the origin.y 100 above the bottom of the fPictureView */
1327 controlboxorigin.y += 100;
1329 controlboxorigin.x += ([fPictureViewArea frame].size.width -
1330 [fPictureControlBox frame].size.width) / 2.0;
1331 /* requires that thefPictureControlBox and the fEncodingControlBox
1332 * are the same width to line up.
1334 [fPictureControlBox setFrameOrigin:controlboxorigin];
1335 [fEncodingControlBox setFrameOrigin:controlboxorigin];
1340 - (BOOL)viewNeedsToResizeToSize: (NSSize)newSize
1342 NSSize viewSize = [fPictureView frame].size;
1343 return (newSize.width != viewSize.width || newSize.height != viewSize.height);