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 /* lets set the preview window to accept mouse moved events */
53 [fPreviewWindow setAcceptsMouseMovedEvents:YES];
55 [self pictureSliderChanged:nil];
56 [self startReceivingLibhbNotifications];
59 - (void)setHBController: (HBController *)controller
61 fHBController = controller;
66 [fPreviewWindow setDelegate:self];
67 /* lets set the preview window to accept mouse moved events */
68 [fPreviewWindow setAcceptsMouseMovedEvents:YES];
69 //[self pictureSliderChanged:nil];
70 [self startReceivingLibhbNotifications];
75 /* Setup our layers for core animation */
76 [fPictureViewArea setWantsLayer:YES];
77 [fPictureView setWantsLayer:YES];
79 [fMovieView setWantsLayer:YES];
81 [fCancelPreviewMovieButton setWantsLayer:YES];
82 [fMovieCreationProgressIndicator setWantsLayer:YES];
84 [fPictureControlBox setWantsLayer:YES];
85 [fPictureSlider setWantsLayer:YES];
86 [fFullScreenToggleButton setWantsLayer:YES];
87 [fPictureSettingsToggleButton setWantsLayer:YES];
88 [fScaleToScreenToggleButton setWantsLayer:YES];
89 [fCreatePreviewMovieButton setWantsLayer:YES];
91 [fEncodingControlBox setWantsLayer:YES];
93 [fShowPreviewMovieButton setWantsLayer:YES];
97 - (BOOL)acceptsMouseMovedEvents
102 - (void)windowWillClose:(NSNotification *)aNotification
104 /* Upon Closing the picture window, we make sure we clean up any
105 * preview movie that might be playing
108 hb_stop( fPreviewLibhb );
110 // Show the picture view
111 [fPictureView setHidden:NO];
112 [fMovieView pause:nil];
113 [fMovieView setHidden:YES];
116 [self goWindowedScreen:nil];
123 - (BOOL)windowShouldClose:(id)fPictureWindow
130 hb_stop(fPreviewLibhb);
131 if (fPreviewMoviePath)
133 [[NSFileManager defaultManager] removeFileAtPath:fPreviewMoviePath handler:nil];
134 [fPreviewMoviePath release];
137 [fLibhbTimer invalidate];
138 [fLibhbTimer release];
140 [fHudTimer invalidate];
143 [fPicturePreviews release];
144 [fFullScreenWindow release];
149 - (void) SetHandle: (hb_handle_t *) handle
155 /* we set the preview length popup in seconds */
156 [fPreviewMovieLengthPopUp removeAllItems];
157 [fPreviewMovieLengthPopUp addItemWithTitle: @"5"];
158 [fPreviewMovieLengthPopUp addItemWithTitle: @"10"];
159 [fPreviewMovieLengthPopUp addItemWithTitle: @"15"];
160 [fPreviewMovieLengthPopUp addItemWithTitle: @"20"];
161 [fPreviewMovieLengthPopUp addItemWithTitle: @"25"];
162 [fPreviewMovieLengthPopUp addItemWithTitle: @"30"];
163 [fPreviewMovieLengthPopUp addItemWithTitle: @"35"];
164 [fPreviewMovieLengthPopUp addItemWithTitle: @"40"];
165 [fPreviewMovieLengthPopUp addItemWithTitle: @"45"];
166 [fPreviewMovieLengthPopUp addItemWithTitle: @"50"];
167 [fPreviewMovieLengthPopUp addItemWithTitle: @"55"];
168 [fPreviewMovieLengthPopUp addItemWithTitle: @"60"];
170 /* adjust the preview slider length */
171 /* We use our advance pref to determine how many previews we scanned */
172 int hb_num_previews = [[[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewsNumber"] intValue];
173 [fPictureSlider setMaxValue: hb_num_previews - 1.0];
174 [fPictureSlider setNumberOfTickMarks: hb_num_previews];
176 if ([[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewLength"])
178 [fPreviewMovieLengthPopUp selectItemWithTitle:[[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewLength"]];
182 /* currently hard set default to 10 seconds */
183 [fPreviewMovieLengthPopUp selectItemAtIndex: 1];
187 - (void) SetTitle: (hb_title_t *) title
189 hb_job_t * job = title->job;
193 MaxOutputWidth = title->width - job->crop[2] - job->crop[3];
194 MaxOutputHeight = title->height - job->crop[0] - job->crop[1];
195 [self SettingsChanged: nil];
200 // Adjusts the window to draw the current picture (fPicture) adjusting its size as
201 // necessary to display as much of the picture as possible.
202 - (void) displayPreview
204 hb_job_t * job = fTitle->job;
205 /* lets make sure that the still picture view is not hidden and that
206 * the movie preview is
208 [fMovieView pause:nil];
209 [fMovieView setHidden:YES];
210 [fMovieCreationProgressIndicator stopAnimation: nil];
211 [fMovieCreationProgressIndicator setHidden: YES];
213 [fPictureView setHidden:NO];
215 [fPictureView setImage: [self imageForPicture: fPicture]];
219 NSSize displaySize = NSMakeSize( ( CGFloat )fTitle->width, ( CGFloat )fTitle->height );
220 /* Set the picture size display fields below the Preview Picture*/
221 if( fTitle->job->anamorphic.mode == 1 ) // Original PAR Implementation
223 output_width = fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3];
224 output_height = fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1];
225 display_width = output_width * fTitle->job->anamorphic.par_width / fTitle->job->anamorphic.par_height;
226 [fInfoField setStringValue:[NSString stringWithFormat:
227 @"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d",
228 fTitle->width, fTitle->height, output_width, output_height, display_width, output_height]];
229 displaySize.width *= ( ( CGFloat )fTitle->job->anamorphic.par_width ) / ( ( CGFloat )fTitle->job->anamorphic.par_height );
231 else if (fTitle->job->anamorphic.mode == 2) // Loose Anamorphic
233 hb_set_anamorphic_size(job, &output_width, &output_height, &output_par_width, &output_par_height);
234 display_width = output_width * output_par_width / output_par_height;
235 [fInfoField setStringValue:[NSString stringWithFormat:
236 @"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d",
237 fTitle->width, fTitle->height, output_width, output_height, display_width, output_height]];
239 displaySize.width = display_width;
241 else // No Anamorphic
243 [fInfoField setStringValue: [NSString stringWithFormat:
244 @"Source: %dx%d, Output: %dx%d", fTitle->width, fTitle->height,
245 fTitle->job->width, fTitle->job->height]];
248 NSSize viewSize = [self optimalViewSizeForImageSize:displaySize];
249 /* we also need to take into account scaling to full screen to activate switching the view size */
250 if( [self viewNeedsToResizeToSize:viewSize])
252 /* In the case of loose anamorphic, do not resize the window when scaling down */
253 // FIX ME: we need a new way to do this as we do not havefWidthField anymore
254 //if (fTitle->job->anamorphic.mode != 2 || [fWidthField intValue] == fTitle->width)
255 if (fTitle->job->anamorphic.mode != 2 || (fTitle->job->anamorphic.mode == 2 && output_width == fTitle->width))
257 [self resizeSheetForViewSize:viewSize];
258 [self setViewSize:viewSize];
263 // Show the scaled text (use the height to check since the width can vary
264 // with anamorphic video).
265 if( ( ( int )viewSize.height ) != fTitle->height )
267 CGFloat scale = viewSize.width / ( ( CGFloat ) fTitle->width );
268 NSString *scaleString = [NSString stringWithFormat:
269 NSLocalizedString( @" (Preview scaled to %.0f%% actual size)",
270 @"String shown when a preview is scaled" ),
272 [fscaleInfoField setStringValue: [NSString stringWithFormat:
273 @"%@", scaleString]];
278 [fscaleInfoField setStringValue: @""];
283 - (IBAction) previewDurationPopUpChanged: (id) sender
286 [[NSUserDefaults standardUserDefaults] setObject:[fPreviewMovieLengthPopUp titleOfSelectedItem] forKey:@"PreviewLength"];
290 - (IBAction) SettingsChanged: (id) sender
292 // Purge the existing picture previews so they get recreated the next time
294 [self purgeImageCache];
295 /* We actually call displayPreview now from pictureSliderChanged which keeps
296 * our picture preview slider in sync with the previews being shown
298 //[self displayPreview];
299 [self pictureSliderChanged:nil];
302 - (IBAction) pictureSliderChanged: (id) sender
304 // Show the picture view
305 [fPictureView setHidden:NO];
306 [fMovieView pause:nil];
307 [fMovieView setHidden:YES];
308 [fEncodingControlBox setHidden: YES];
310 int newPicture = [fPictureSlider intValue];
311 if (newPicture != fPicture)
313 fPicture = newPicture;
315 [self displayPreview];
319 - (IBAction)showPreviewPanel: (id)sender forTitle: (hb_title_t *)title
321 [self SetTitle:title];
322 [self showWindow:sender];
323 [fPreviewWindow setAcceptsMouseMovedEvents:YES];
327 [self startHudTimer];
331 - (IBAction)showPictureSettings:(id)sender
333 [fHBController showPicturePanel:self];
336 #pragma mark Hud Control Overlay
337 - (void) mouseMoved:(NSEvent *)theEvent
339 [super mouseMoved:theEvent];
341 if (isEncoding == NO)
343 if (hudTimerSeconds == 0)
346 [self startHudTimer];
349 if (hudTimerSeconds > 20)
354 [self showHideHudControls];
360 - (void) startHudTimer
364 fHudTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(hudTimerFired:) userInfo:nil repeats:YES];
369 - (void) stopHudTimer
373 [fHudTimer invalidate];
380 - (void) hudTimerFired: (NSTimer*)theTimer
383 [self showHideHudControls];
387 - (void) showHideHudControls
389 /* Test for mouse location to show/hide hud controls */
392 NSRect controlBoxFrame;
393 targetFrame = [fPictureViewArea frame];
394 controlBoxFrame = [fPictureControlBox frame];
398 mouseLoc = [fFullScreenWindow mouseLocationOutsideOfEventStream];
399 [fScaleToScreenToggleButton setHidden:NO];
403 mouseLoc = [fPreviewWindow mouseLocationOutsideOfEventStream];
404 [fScaleToScreenToggleButton setHidden:YES];
407 /* if the pointer is inside the picture view areas but not
408 * in the controlbox, check the hudTimerSeconds to see if
409 * its in the allowable time span
411 if ( hudTimerSeconds > 0 && hudTimerSeconds < 20)
414 if (isEncoding == NO)
416 if (NSPointInRect (mouseLoc, controlBoxFrame))
418 /* Mouse is over the preview area so show hud controls so just
419 * reset the timer to keep the control box visible
421 [fPictureControlBox setHidden: NO];
425 /* Re-verify we are within the target frame */
426 if (NSPointInRect (mouseLoc, targetFrame))
428 /* Mouse is over the preview area so show hud controls */
429 [[fPictureControlBox animator] setHidden: NO];
430 /* increment our timer by one */
435 [[fPictureControlBox animator] setHidden: YES];
443 [[fPictureControlBox animator] setHidden: YES];
449 #pragma mark Fullscreen Mode
451 - (IBAction)toggleScreenMode:(id)sender
455 [self goFullScreen:nil];
459 [self goWindowedScreen:nil];
463 - (IBAction)toggleScaleToScreen:(id)sender
465 if (scaleToScreen == YES)
468 /* make sure we are set to a still preview */
469 [self pictureSliderChanged:nil];
470 [fScaleToScreenToggleButton setTitle:@"<->"];
475 /* make sure we are set to a still preview */
476 [self pictureSliderChanged:nil];
477 [fScaleToScreenToggleButton setTitle:@">-<"];
480 /* Actually perform the scaling */
482 NSSize displaySize = NSMakeSize( ( CGFloat )fTitle->width, ( CGFloat )fTitle->height );
483 NSSize viewSize = [self optimalViewSizeForImageSize:displaySize];
484 [self resizeSheetForViewSize:viewSize];
485 [self setViewSize:viewSize];
494 - (IBAction)goFullScreen:(id)sender
496 // Get the screen information.
497 NSScreen* mainScreen = [NSScreen mainScreen];
498 NSDictionary* screenInfo = [mainScreen deviceDescription];
499 NSNumber* screenID = [screenInfo objectForKey:@"NSScreenNumber"];
500 // Capture the screen.
501 CGDirectDisplayID displayID = (CGDirectDisplayID)[screenID longValue];
502 CGDisplayErr err = CGDisplayCapture(displayID);
504 if (err == CGDisplayNoErr)
507 /* make sure we are set to a still preview and not scaled to screen */
509 [self pictureSliderChanged:nil];
511 // Create the full-screen window.
512 //NSRect winRect = [mainScreen frame];
514 NSRect winRect = [fPictureViewArea frame];
516 fFullScreenWindow = [[NSWindow alloc] initWithContentRect:winRect
517 styleMask:NSBorderlessWindowMask
518 backing:NSBackingStoreBuffered
520 screen:[NSScreen mainScreen]];
522 // Establish the window attributes.
523 [fFullScreenWindow setReleasedWhenClosed:NO];
524 [fFullScreenWindow setDisplaysWhenScreenProfileChanges:YES];
525 [fFullScreenWindow setDelegate:self];
527 /* insert a view into the new window */
528 [fFullScreenWindow setContentView:fPictureViewArea];
529 [fPictureViewArea setNeedsDisplay:YES];
533 /* Better to center the window using the screen's frame
534 * and the windows origin. Note that we should take into
535 * account the auto sizing and alignment that occurs in
536 * setViewSize each time the preview changes.
539 NSSize screenSize = [[NSScreen mainScreen] frame].size;
540 NSSize windowSize = [fFullScreenWindow frame].size;
541 NSPoint windowOrigin = [fFullScreenWindow frame].origin;
543 /* Adjust our origin y (vertical) based on the screen height */
544 windowOrigin.y = (screenSize.height - windowSize.height) / 2.0;
545 windowOrigin.x = (screenSize.width - windowSize.width) / 2.0;
547 [fFullScreenWindow setFrameOrigin:windowOrigin];
551 /* lets kill the timer for now */
552 [self stopReceivingLibhbNotifications];
554 /* We need to retain the fPreviewWindow */
555 [fPreviewWindow retain];
557 [self setWindow:fFullScreenWindow];
559 // The window has to be above the level of the shield window.
560 int32_t shieldLevel = CGShieldingWindowLevel();
562 [fFullScreenWindow setLevel:shieldLevel];
565 [fFullScreenWindow makeKeyAndOrderFront:self];
567 /* Change the name of fFullScreenToggleButton appropriately */
568 [fFullScreenToggleButton setTitle: @"Windowed"];
570 /* Lets fire the timer back up for the hud controls, etc. */
571 [self startReceivingLibhbNotifications];
574 [fScaleToScreenToggleButton setHidden:NO];
576 /* make sure we are set to a still preview */
577 [self pictureSliderChanged:nil];
579 //[fPreviewWindow setAcceptsMouseMovedEvents:NO];
580 [fFullScreenWindow setAcceptsMouseMovedEvents:YES];
584 [self startHudTimer];
588 - (IBAction)goWindowedScreen:(id)sender
591 /* Get the screen info to release the display but don't actually do
592 * it until the windowed screen is setup.
595 [self pictureSliderChanged:nil];
596 [fScaleToScreenToggleButton setTitle:@"<->"];
598 NSScreen* mainScreen = [NSScreen mainScreen];
599 NSDictionary* screenInfo = [mainScreen deviceDescription];
600 NSNumber* screenID = [screenInfo objectForKey:@"NSScreenNumber"];
601 CGDirectDisplayID displayID = (CGDirectDisplayID)[screenID longValue];
603 [fFullScreenWindow dealloc];
604 [fFullScreenWindow release];
607 [fPreviewWindow setContentView:fPictureViewArea];
608 [fPictureViewArea setNeedsDisplay:YES];
609 [self setWindow:fPreviewWindow];
612 [fPreviewWindow makeKeyAndOrderFront:self];
614 /* Set the window back to regular level */
615 [fPreviewWindow setLevel:NSNormalWindowLevel];
617 /* Set the isFullScreen flag back to NO */
620 /* make sure we are set to a still preview */
621 [self pictureSliderChanged:nil];
622 [self showPreviewWindow:nil];
624 /* Change the name of fFullScreenToggleButton appropriately */
625 [fFullScreenToggleButton setTitle: @"Full Screen"];
626 // [fScaleToScreenToggleButton setHidden:YES];
627 /* set the picture settings pallete back to normal level */
628 [fHBController picturePanelWindowed];
630 /* Release the display now that the we are back in windowed mode */
631 CGDisplayRelease(displayID);
633 [fPreviewWindow setAcceptsMouseMovedEvents:YES];
634 //[fFullScreenWindow setAcceptsMouseMovedEvents:NO];
637 [self startHudTimer];
642 #pragma mark Still Preview Image Processing
645 // This function converts an image created by libhb (specified via pictureIndex) into
646 // an NSImage suitable for the GUI code to use. If removeBorders is YES,
647 // makeImageForPicture crops the image generated by libhb stripping off the gray
648 // border around the content. This is the low-level method that generates the image.
649 // -imageForPicture calls this function whenever it can't find an image in its cache.
650 + (NSImage *) makeImageForPicture: (int)pictureIndex
651 libhb:(hb_handle_t*)handle
652 title:(hb_title_t*)title
653 removeBorders:(BOOL)removeBorders
657 // |<---------- title->width ----------->|
658 // | |<---- title->job->width ---->| |
660 // .......................................
661 // ....+-----------------------------+....
662 // ....| |....<-- gray border
665 // ....| |<------- image
671 // ....+-----------------------------+....
672 // .......................................
674 static uint8_t * buffer;
675 static int bufferSize;
677 // Make sure we have a big enough buffer to receive the image from libhb. libhb
678 // creates images with a one-pixel border around the original content. Hence we
679 // add 2 pixels horizontally and vertically to the buffer size.
680 int srcWidth = title->width + 2;
681 int srcHeight= title->height + 2;
683 newSize = srcWidth * srcHeight * 4;
684 if( bufferSize < newSize )
686 bufferSize = newSize;
687 buffer = (uint8_t *) realloc( buffer, bufferSize );
690 hb_get_preview( handle, title, pictureIndex, buffer );
692 // Create an NSBitmapImageRep and copy the libhb image into it, converting it from
693 // libhb's format to one suitable for NSImage. Along the way, we'll strip off the
694 // border around libhb's image.
696 // The image data returned by hb_get_preview is 4 bytes per pixel, BGRA format.
699 int dstWidth = title->job->width;
700 int dstHeight = title->job->height;
701 NSBitmapFormat bitmapFormat = (NSBitmapFormat)NSAlphaFirstBitmapFormat;
702 NSBitmapImageRep * imgrep = [[[NSBitmapImageRep alloc]
703 initWithBitmapDataPlanes:nil
707 samplesPerPixel:3 // ignore alpha
710 colorSpaceName:NSCalibratedRGBColorSpace
711 bitmapFormat:bitmapFormat
712 bytesPerRow:dstWidth * 4
713 bitsPerPixel:32] autorelease];
715 int borderTop = (srcHeight - dstHeight) / 2;
716 int borderLeft = (srcWidth - dstWidth) / 2;
718 UInt32 * src = (UInt32 *)buffer;
719 UInt32 * dst = (UInt32 *)[imgrep bitmapData];
720 src += borderTop * srcWidth; // skip top rows in src to get to first row of dst
721 src += borderLeft; // skip left pixels in src to get to first pixel of dst
722 for (int r = 0; r < dstHeight; r++)
724 for (int c = 0; c < dstWidth; c++)
725 #if TARGET_RT_LITTLE_ENDIAN
726 *dst++ = Endian32_Swap(*src++);
730 src += (srcWidth - dstWidth); // skip to next row in src
733 NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(dstWidth, dstHeight)] autorelease];
734 [img addRepresentation:imgrep];
740 // Make sure we have big enough buffer
741 static uint8_t * buffer;
742 static int bufferSize;
745 newSize = ( title->width + 2 ) * (title->height + 2 ) * 4;
746 if( bufferSize < newSize )
748 bufferSize = newSize;
749 buffer = (uint8_t *) realloc( buffer, bufferSize );
752 hb_get_preview( handle, title, pictureIndex, buffer );
754 // The image data returned by hb_get_preview is 4 bytes per pixel, BGRA format.
755 // We'll copy that into an NSImage swapping it to ARGB in the process. Alpha is
757 int width = title->width + 2; // hblib adds a one-pixel border to the image
758 int height = title->height + 2;
759 int numPixels = width * height;
760 NSBitmapFormat bitmapFormat = (NSBitmapFormat)NSAlphaFirstBitmapFormat;
761 NSBitmapImageRep * imgrep = [[[NSBitmapImageRep alloc]
762 initWithBitmapDataPlanes:nil
766 samplesPerPixel:3 // ignore alpha
769 colorSpaceName:NSCalibratedRGBColorSpace
770 bitmapFormat:bitmapFormat
771 bytesPerRow:width * 4
772 bitsPerPixel:32] autorelease];
774 UInt32 * src = (UInt32 *)buffer;
775 UInt32 * dst = (UInt32 *)[imgrep bitmapData];
776 for (int i = 0; i < numPixels; i++)
777 #if TARGET_RT_LITTLE_ENDIAN
778 *dst++ = Endian32_Swap(*src++);
783 NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(width, height)] autorelease];
784 [img addRepresentation:imgrep];
790 // Returns the preview image for the specified index, retrieving it from its internal
791 // cache or by calling makeImageForPicture if it is not cached. Generally, you should
792 // use imageForPicture so that images are cached. Calling makeImageForPicture will
793 // always generate a new copy of the image.
794 - (NSImage *) imageForPicture: (int) pictureIndex
796 // The preview for the specified index may not currently exist, so this method
797 // generates it if necessary.
798 NSString * key = [NSString stringWithFormat:@"%d", pictureIndex];
799 NSImage * theImage = [fPicturePreviews objectForKey:key];
802 theImage = [PreviewController makeImageForPicture:pictureIndex libhb:fHandle title:fTitle removeBorders: NO];
803 [fPicturePreviews setObject:theImage forKey:key];
808 // Purges all images from the cache. The next call to imageForPicture will cause a new
809 // image to be generated.
810 - (void) purgeImageCache
812 [fPicturePreviews removeAllObjects];
817 #pragma mark Movie Preview
818 - (IBAction) createMoviePreview: (id) sender
822 /* Lets make sure the still picture previews are showing in case
823 * there is currently a movie showing */
824 [self pictureSliderChanged:nil];
826 /* Rip or Cancel ? */
828 hb_get_state2( fPreviewLibhb, &s );
830 if(sender == fCancelPreviewMovieButton && (s.state == HB_STATE_WORKING || s.state == HB_STATE_PAUSED))
834 hb_stop( fPreviewLibhb );
835 [fPictureView setHidden:NO];
836 [fMovieView pause:nil];
837 [fMovieView setHidden:YES];
838 [fPictureSlider setHidden:NO];
845 /* we use controller.mm's prepareJobForPreview to go ahead and set all of our settings
846 * however, we want to use a temporary destination field of course
847 * so that we do not put our temp preview in the users chosen
850 hb_job_t * job = fTitle->job;
852 /* We run our current setting through prepeareJob in Controller.mm
853 * just as if it were a regular encode */
855 [fHBController prepareJobForPreview];
857 /* Destination file. We set this to our preview directory
858 * changing the extension appropriately.*/
859 if (fTitle->job->mux == HB_MUX_MP4) // MP4 file
861 /* we use .m4v for our mp4 files so that ac3 and chapters in mp4 will play properly */
862 fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.m4v";
864 else if (fTitle->job->mux == HB_MUX_MKV) // MKV file
866 fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.mkv";
868 else if (fTitle->job->mux == HB_MUX_AVI) // AVI file
870 fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.avi";
872 else if (fTitle->job->mux == HB_MUX_OGM) // OGM file
874 fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.ogm";
877 fPreviewMoviePath = [[fPreviewMoviePath stringByExpandingTildeInPath]retain];
879 /* See if there is an existing preview file, if so, delete it */
880 if( ![[NSFileManager defaultManager] fileExistsAtPath:fPreviewMoviePath] )
882 [[NSFileManager defaultManager] removeFileAtPath:fPreviewMoviePath
886 /* We now direct our preview encode to fPreviewMoviePath */
887 fTitle->job->file = [fPreviewMoviePath UTF8String];
889 /* We use our advance pref to determine how many previews to scan */
890 int hb_num_previews = [[[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewsNumber"] intValue];
891 job->start_at_preview = fPicture + 1;
892 job->seek_points = hb_num_previews;
894 /* we use the preview duration popup to get the specified
895 * number of seconds for the preview encode.
898 job->pts_to_stop = [[fPreviewMovieLengthPopUp titleOfSelectedItem] intValue] * 90000LL;
900 /* lets go ahead and send it off to libhb
901 * Note: unlike a full encode, we only send 1 pass regardless if the final encode calls for 2 passes.
902 * this should suffice for a fairly accurate short preview and cuts our preview generation time in half.
904 hb_add( fPreviewLibhb, job );
906 [fEncodingControlBox setHidden: NO];
907 [fPictureControlBox setHidden: YES];
909 [fMovieCreationProgressIndicator setHidden: NO];
910 [fPreviewMovieStatusField setHidden: NO];
916 /* Let fPreviewLibhb do the job */
917 hb_start( fPreviewLibhb );
921 - (void) startReceivingLibhbNotifications
925 fLibhbTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(libhbTimerFired:) userInfo:nil repeats:YES];
926 [fLibhbTimer retain];
930 - (void) stopReceivingLibhbNotifications
934 [fLibhbTimer invalidate];
935 [fLibhbTimer release];
939 - (void) libhbTimerFired: (NSTimer*)theTimer
942 hb_get_state( fPreviewLibhb, &s );
943 [self libhbStateChanged: s];
947 - (void) libhbStateChanged: (hb_state_t &)state
949 switch( state.state )
952 case HB_STATE_SCANNING:
953 case HB_STATE_SCANDONE:
956 case HB_STATE_WORKING:
958 #define p state.param.working
960 NSMutableString * string;
961 /* Update text field */
962 string = [NSMutableString stringWithFormat: NSLocalizedString( @"Encoding preview: %.2f %%", @"" ), 100.0 * p.progress];
966 [string appendFormat:
967 NSLocalizedString( @" (%.2f fps, avg %.2f fps, ETA %02dh%02dm%02ds)", @"" ),
968 p.rate_cur, p.rate_avg, p.hours, p.minutes, p.seconds];
970 [fPreviewMovieStatusField setStringValue: string];
972 [fMovieCreationProgressIndicator setIndeterminate: NO];
974 [fMovieCreationProgressIndicator setDoubleValue: 100.0 * p.progress];
976 [fCreatePreviewMovieButton setTitle: @"Cancel Preview"];
983 #define p state.param.muxing
984 case HB_STATE_MUXING:
986 // Update fMovieCreationProgressIndicator
987 [fMovieCreationProgressIndicator setIndeterminate: YES];
988 [fMovieCreationProgressIndicator startAnimation: nil];
989 [fPreviewMovieStatusField setStringValue: [NSString stringWithFormat:
990 NSLocalizedString( @"Muxing Preview ...", @"" )]];
994 case HB_STATE_PAUSED:
995 [fMovieCreationProgressIndicator stopAnimation: nil];
998 case HB_STATE_WORKDONE:
1000 // Delete all remaining jobs since libhb doesn't do this on its own.
1002 while( ( job = hb_job(fPreviewLibhb, 0) ) )
1003 hb_rem( fHandle, job );
1005 [fPreviewMovieStatusField setStringValue: @""];
1006 [fPreviewMovieStatusField setHidden: YES];
1008 [fMovieCreationProgressIndicator stopAnimation: nil];
1009 [fMovieCreationProgressIndicator setHidden: YES];
1010 [fEncodingControlBox setHidden: YES];
1012 /* we make sure the picture slider and preview match */
1013 [self pictureSliderChanged:nil];
1016 // Show the movie view
1019 [self showMoviePreview:fPreviewMoviePath];
1022 [fCreatePreviewMovieButton setTitle: @"Live Preview"];
1031 - (IBAction) showMoviePreview: (NSString *) path
1033 /* Since the gray background for the still images is part of
1034 * fPictureView, lets leave the picture view visible and postion
1035 * the fMovieView over the image portion of fPictureView so
1036 * we retain the gray cropping border we have already established
1037 * with the still previews
1039 [fMovieView setHidden:NO];
1041 /* Load the new movie into fMovieView */
1046 [fMovieView setControllerVisible: YES];
1047 /* let's make sure there is no movie currently set */
1048 [fMovieView setMovie:nil];
1050 aMovie = [QTMovie movieWithFile:path error:nil];
1052 /* we get some size information from the preview movie */
1054 GetMovieBox ([aMovie quickTimeMovie], &movieBox);
1055 movieBounds = [fMovieView movieBounds];
1056 movieBounds.size.height = movieBox.bottom - movieBox.top;
1058 if ([fMovieView isControllerVisible])
1059 movieBounds.size.height += [fMovieView controllerBarHeight];
1060 /* since for whatever the reason I cannot seem to get the [fMovieView controllerBarHeight]
1061 * For now just use 15 for additional height as it seems to line up well
1063 movieBounds.size.height += 15;
1065 movieBounds.size.width = movieBox.right - movieBox.left;
1067 /* We need to find out if the preview movie needs to be scaled down so
1068 * that it doesn't overflow our available viewing container (just like for image
1069 * in -displayPreview) for HD sources, etc. [fPictureViewArea frame].size.height*/
1070 if( ((int)movieBounds.size.height) > [fPictureView frame].size.height || scaleToScreen == YES)
1072 /* The preview movie would be larger than the available viewing area
1073 * in the preview movie, so we go ahead and scale it down to the same size
1074 * as the still preview or we readjust our window to allow for the added height if need be
1076 NSSize displaySize = NSMakeSize( (float)movieBounds.size.width, (float)movieBounds.size.height );
1077 NSSize viewSize = [self optimalViewSizeForImageSize:displaySize];
1078 if( [self viewNeedsToResizeToSize:viewSize] )
1081 [self resizeSheetForViewSize:viewSize];
1082 [self setViewSize:viewSize];
1086 [fMovieView setPreservesAspectRatio:YES];
1087 [fMovieView setFrameSize:viewSize];
1091 /* Since the preview movie is smaller than the available viewing area
1092 * we can go ahead and use the preview movies native size */
1093 [fMovieView setFrameSize:movieBounds.size];
1100 // lets reposition the movie if need be
1102 NSPoint origin = [fPictureViewArea frame].origin;
1103 origin.x += trunc(([fPictureViewArea frame].size.width -
1104 [fMovieView frame].size.width) / 2.0);
1105 /* We need to detect whether or not we are currently less than the available height.*/
1106 if (movieBounds.size.height < [fPictureView frame].size.height)
1108 /* If we are, we are adding 15 to the height to allow for the controller bar so
1109 * we need to subtract half of that for the origin.y to get the controller bar
1110 * below the movie to it lines up vertically with where our still preview was
1112 origin.y += trunc((([fPictureViewArea frame].size.height -
1113 [fMovieView frame].size.height) / 2.0) - 7.5);
1117 /* if we are >= to the height of the picture view area, the controller bar
1118 * gets taken care of with picture resizing, so we do not want to offset the height
1120 origin.y += trunc(([fPictureViewArea frame].size.height -
1121 [fMovieView frame].size.height) / 2.0);
1123 [fMovieView setFrameOrigin:origin];
1125 [fMovieView setMovie:aMovie];
1126 /// to actually play the movie
1127 [fMovieView play:aMovie];
1139 @implementation PreviewController (Private)
1142 // -[PictureController(Private) optimalViewSizeForImageSize:]
1144 // Given the size of the preview image to be shown, returns the best possible
1145 // size for the view.
1147 - (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize
1149 // The min size is 320x240
1150 CGFloat minWidth = 480.0;
1151 CGFloat minHeight = 360.0;
1153 NSSize screenSize = [[NSScreen mainScreen] frame].size;
1154 NSSize sheetSize = [[self window] frame].size;
1155 NSSize viewAreaSize = [fPictureViewArea frame].size;
1156 CGFloat paddingX = sheetSize.width - viewAreaSize.width;
1157 CGFloat paddingY = sheetSize.height - viewAreaSize.height;
1163 /* We are in full screen mode so lets use the full screen if we need to */
1164 maxWidth = screenSize.width - paddingX;
1165 maxHeight = screenSize.height - paddingY;
1169 // The max size of the view is when the sheet is taking up 85% of the screen.
1170 maxWidth = (0.85 * screenSize.width) - paddingX;
1171 maxHeight = (0.85 * screenSize.height) - paddingY;
1174 NSSize resultSize = imageSize;
1176 // Its better to have a view that's too small than a view that's too big, so
1177 // apply the maximum constraints last.
1178 if( resultSize.width < minWidth )
1180 resultSize.height *= (minWidth / resultSize.width);
1181 resultSize.width = minWidth;
1183 if( resultSize.height < minHeight )
1185 resultSize.width *= (minHeight / resultSize.height);
1186 resultSize.height = minHeight;
1188 if( resultSize.width > maxWidth )
1190 resultSize.height *= (maxWidth / resultSize.width);
1191 resultSize.width = maxWidth;
1193 if( resultSize.height > maxHeight )
1195 resultSize.width *= (maxHeight / resultSize.height);
1196 resultSize.height = maxHeight;
1199 if (scaleToScreen == YES)
1201 //CGFloat scaleToScreenWidth;
1202 //CGFloat scaleToScreenHeight;
1203 CGFloat screenAspect;
1204 CGFloat viewAreaAspect;
1205 //note, a mbp 15" at 1440 x 900 is a 1.6 ar
1206 screenAspect = screenSize.width / screenSize.height;
1208 // Note, a standard dvd will use 720 x 480 which is a 1.5
1209 viewAreaAspect = viewAreaSize.width / viewAreaSize.height;
1211 if (screenAspect < viewAreaAspect)
1213 resultSize.width = screenSize.width;
1214 resultSize.height = (screenSize.width / viewAreaAspect);
1218 resultSize.height = screenSize.height;
1219 resultSize.width = resultSize.height * viewAreaAspect;
1230 // -[PictureController(Private) resizePanelForViewSize:animate:]
1232 // Resizes the entire window to accomodate a view of a particular size.
1234 - (void)resizeSheetForViewSize: (NSSize)viewSize
1236 // Figure out the deltas for the new frame area
1237 NSSize currentSize = [fPictureViewArea frame].size;
1238 CGFloat deltaX = viewSize.width - currentSize.width;
1239 CGFloat deltaY = viewSize.height - currentSize.height;
1241 // Now resize the whole panel by those same deltas, but don't exceed the min
1242 NSRect frame = [[self window] frame];
1243 NSSize maxSize = [[self window] maxSize];
1244 NSSize minSize = [[self window] minSize];
1245 frame.size.width += deltaX;
1246 frame.size.height += deltaY;
1247 if( frame.size.width < minSize.width )
1249 frame.size.width = minSize.width;
1252 if( frame.size.height < minSize.height )
1254 frame.size.height = minSize.height;
1258 // But now the sheet is off-center, so also shift the origin to center it and
1259 // keep the top aligned.
1260 if( frame.size.width != [[self window] frame].size.width )
1261 frame.origin.x -= (deltaX / 2.0);
1265 if( frame.size.height != [[self window] frame].size.height )
1267 frame.origin.y -= (deltaY / 2.0);
1271 if( frame.size.height != [[self window] frame].size.height )
1272 frame.origin.y -= deltaY;
1275 [[self window] setFrame:frame display:YES animate:NO];
1279 [[self window] setFrame:frame display:YES animate:YES];
1285 // -[PictureController(Private) setViewSize:]
1287 // Changes the view's size and centers it vertically inside of its area.
1288 // Assumes resizeSheetForViewSize: has already been called.
1290 - (void)setViewSize: (NSSize)viewSize
1292 [fPictureView setFrameSize:viewSize];
1294 // center it vertically
1295 NSPoint origin = [fPictureViewArea frame].origin;
1296 origin.y += ([fPictureViewArea frame].size.height -
1297 [fPictureView frame].size.height) / 2.0;
1298 [fPictureView setFrameOrigin:origin];
1300 NSPoint controlboxorigin = [fPictureView frame].origin;
1302 /* for now, put the origin.y 100 above the bottom of the fPictureView */
1303 controlboxorigin.y += 100;
1305 controlboxorigin.x += ([fPictureViewArea frame].size.width -
1306 [fPictureControlBox frame].size.width) / 2.0;
1307 /* requires that thefPictureControlBox and the fEncodingControlBox
1308 * are the same width to line up.
1310 [fPictureControlBox setFrameOrigin:controlboxorigin];
1311 [fEncodingControlBox setFrameOrigin:controlboxorigin];
1316 - (BOOL)viewNeedsToResizeToSize: (NSSize)newSize
1318 NSSize viewSize = [fPictureView frame].size;
1319 return (newSize.width != viewSize.width || newSize.height != viewSize.height);