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];
323 if ([fPreviewWindow isVisible])
326 [fPreviewWindow close];
331 [self showWindow:sender];
332 [fPreviewWindow setAcceptsMouseMovedEvents:YES];
336 [self startHudTimer];
341 - (IBAction)showPictureSettings:(id)sender
343 [fHBController showPicturePanel:self];
346 #pragma mark Hud Control Overlay
347 - (void) mouseMoved:(NSEvent *)theEvent
349 [super mouseMoved:theEvent];
351 if (isEncoding == NO)
353 if (hudTimerSeconds == 0)
356 [self startHudTimer];
359 if (hudTimerSeconds > 20)
364 [self showHideHudControls];
370 - (void) startHudTimer
374 fHudTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(hudTimerFired:) userInfo:nil repeats:YES];
379 - (void) stopHudTimer
383 [fHudTimer invalidate];
390 - (void) hudTimerFired: (NSTimer*)theTimer
393 [self showHideHudControls];
397 - (void) showHideHudControls
399 /* Test for mouse location to show/hide hud controls */
402 NSRect controlBoxFrame;
403 targetFrame = [fPictureViewArea frame];
404 controlBoxFrame = [fPictureControlBox frame];
408 mouseLoc = [fFullScreenWindow mouseLocationOutsideOfEventStream];
409 [fScaleToScreenToggleButton setHidden:NO];
413 mouseLoc = [fPreviewWindow mouseLocationOutsideOfEventStream];
414 [fScaleToScreenToggleButton setHidden:YES];
417 /* if the pointer is inside the picture view areas but not
418 * in the controlbox, check the hudTimerSeconds to see if
419 * its in the allowable time span
421 if ( hudTimerSeconds > 0 && hudTimerSeconds < 20)
424 if (isEncoding == NO)
426 if (NSPointInRect (mouseLoc, controlBoxFrame))
428 /* Mouse is over the preview area so show hud controls so just
429 * reset the timer to keep the control box visible
431 [fPictureControlBox setHidden: NO];
435 /* Re-verify we are within the target frame */
436 if (NSPointInRect (mouseLoc, targetFrame))
438 /* Mouse is over the preview area so show hud controls */
439 [[fPictureControlBox animator] setHidden: NO];
440 /* increment our timer by one */
445 [[fPictureControlBox animator] setHidden: YES];
453 [[fPictureControlBox animator] setHidden: YES];
459 #pragma mark Fullscreen Mode
461 - (IBAction)toggleScreenMode:(id)sender
465 [self goFullScreen:nil];
469 [self goWindowedScreen:nil];
473 - (IBAction)toggleScaleToScreen:(id)sender
475 if (scaleToScreen == YES)
478 /* make sure we are set to a still preview */
479 [self pictureSliderChanged:nil];
480 [fScaleToScreenToggleButton setTitle:@"<->"];
485 /* make sure we are set to a still preview */
486 [self pictureSliderChanged:nil];
487 [fScaleToScreenToggleButton setTitle:@">-<"];
490 /* Actually perform the scaling */
492 NSSize displaySize = NSMakeSize( ( CGFloat )fTitle->width, ( CGFloat )fTitle->height );
493 NSSize viewSize = [self optimalViewSizeForImageSize:displaySize];
494 [self resizeSheetForViewSize:viewSize];
495 [self setViewSize:viewSize];
504 - (IBAction)goFullScreen:(id)sender
506 // Get the screen information.
507 NSScreen* mainScreen = [NSScreen mainScreen];
508 NSDictionary* screenInfo = [mainScreen deviceDescription];
509 NSNumber* screenID = [screenInfo objectForKey:@"NSScreenNumber"];
510 // Capture the screen.
511 CGDirectDisplayID displayID = (CGDirectDisplayID)[screenID longValue];
512 CGDisplayErr err = CGDisplayCapture(displayID);
514 if (err == CGDisplayNoErr)
517 /* make sure we are set to a still preview and not scaled to screen */
519 [self pictureSliderChanged:nil];
521 // Create the full-screen window.
522 //NSRect winRect = [mainScreen frame];
524 NSRect winRect = [fPictureViewArea frame];
526 fFullScreenWindow = [[NSWindow alloc] initWithContentRect:winRect
527 styleMask:NSBorderlessWindowMask
528 backing:NSBackingStoreBuffered
530 screen:[NSScreen mainScreen]];
532 // Establish the window attributes.
533 [fFullScreenWindow setReleasedWhenClosed:NO];
534 [fFullScreenWindow setDisplaysWhenScreenProfileChanges:YES];
535 [fFullScreenWindow setDelegate:self];
537 /* insert a view into the new window */
538 [fFullScreenWindow setContentView:fPictureViewArea];
539 [fPictureViewArea setNeedsDisplay:YES];
543 /* Better to center the window using the screen's frame
544 * and the windows origin. Note that we should take into
545 * account the auto sizing and alignment that occurs in
546 * setViewSize each time the preview changes.
549 NSSize screenSize = [[NSScreen mainScreen] frame].size;
550 NSSize windowSize = [fFullScreenWindow frame].size;
551 NSPoint windowOrigin = [fFullScreenWindow frame].origin;
553 /* Adjust our origin y (vertical) based on the screen height */
554 windowOrigin.y = (screenSize.height - windowSize.height) / 2.0;
555 windowOrigin.x = (screenSize.width - windowSize.width) / 2.0;
557 [fFullScreenWindow setFrameOrigin:windowOrigin];
561 /* lets kill the timer for now */
562 [self stopReceivingLibhbNotifications];
564 /* We need to retain the fPreviewWindow */
565 [fPreviewWindow retain];
567 [self setWindow:fFullScreenWindow];
569 // The window has to be above the level of the shield window.
570 int32_t shieldLevel = CGShieldingWindowLevel();
572 [fFullScreenWindow setLevel:shieldLevel];
575 [fFullScreenWindow makeKeyAndOrderFront:self];
577 /* Change the name of fFullScreenToggleButton appropriately */
578 [fFullScreenToggleButton setTitle: @"Windowed"];
580 /* Lets fire the timer back up for the hud controls, etc. */
581 [self startReceivingLibhbNotifications];
584 [fScaleToScreenToggleButton setHidden:NO];
586 /* make sure we are set to a still preview */
587 [self pictureSliderChanged:nil];
589 //[fPreviewWindow setAcceptsMouseMovedEvents:NO];
590 [fFullScreenWindow setAcceptsMouseMovedEvents:YES];
594 [self startHudTimer];
598 - (IBAction)goWindowedScreen:(id)sender
601 /* Get the screen info to release the display but don't actually do
602 * it until the windowed screen is setup.
605 [self pictureSliderChanged:nil];
606 [fScaleToScreenToggleButton setTitle:@"<->"];
608 NSScreen* mainScreen = [NSScreen mainScreen];
609 NSDictionary* screenInfo = [mainScreen deviceDescription];
610 NSNumber* screenID = [screenInfo objectForKey:@"NSScreenNumber"];
611 CGDirectDisplayID displayID = (CGDirectDisplayID)[screenID longValue];
613 [fFullScreenWindow dealloc];
614 [fFullScreenWindow release];
617 [fPreviewWindow setContentView:fPictureViewArea];
618 [fPictureViewArea setNeedsDisplay:YES];
619 [self setWindow:fPreviewWindow];
622 [fPreviewWindow makeKeyAndOrderFront:self];
624 /* Set the window back to regular level */
625 [fPreviewWindow setLevel:NSNormalWindowLevel];
627 /* Set the isFullScreen flag back to NO */
630 /* make sure we are set to a still preview */
631 [self pictureSliderChanged:nil];
632 [self showPreviewWindow:nil];
634 /* Change the name of fFullScreenToggleButton appropriately */
635 [fFullScreenToggleButton setTitle: @"Full Screen"];
636 // [fScaleToScreenToggleButton setHidden:YES];
637 /* set the picture settings pallete back to normal level */
638 [fHBController picturePanelWindowed];
640 /* Release the display now that the we are back in windowed mode */
641 CGDisplayRelease(displayID);
643 [fPreviewWindow setAcceptsMouseMovedEvents:YES];
644 //[fFullScreenWindow setAcceptsMouseMovedEvents:NO];
647 [self startHudTimer];
652 #pragma mark Still Preview Image Processing
655 // This function converts an image created by libhb (specified via pictureIndex) into
656 // an NSImage suitable for the GUI code to use. If removeBorders is YES,
657 // makeImageForPicture crops the image generated by libhb stripping off the gray
658 // border around the content. This is the low-level method that generates the image.
659 // -imageForPicture calls this function whenever it can't find an image in its cache.
660 + (NSImage *) makeImageForPicture: (int)pictureIndex
661 libhb:(hb_handle_t*)handle
662 title:(hb_title_t*)title
663 removeBorders:(BOOL)removeBorders
667 // |<---------- title->width ----------->|
668 // | |<---- title->job->width ---->| |
670 // .......................................
671 // ....+-----------------------------+....
672 // ....| |....<-- gray border
675 // ....| |<------- image
681 // ....+-----------------------------+....
682 // .......................................
684 static uint8_t * buffer;
685 static int bufferSize;
687 // Make sure we have a big enough buffer to receive the image from libhb. libhb
688 // creates images with a one-pixel border around the original content. Hence we
689 // add 2 pixels horizontally and vertically to the buffer size.
690 int srcWidth = title->width + 2;
691 int srcHeight= title->height + 2;
693 newSize = srcWidth * srcHeight * 4;
694 if( bufferSize < newSize )
696 bufferSize = newSize;
697 buffer = (uint8_t *) realloc( buffer, bufferSize );
700 hb_get_preview( handle, title, pictureIndex, buffer );
702 // Create an NSBitmapImageRep and copy the libhb image into it, converting it from
703 // libhb's format to one suitable for NSImage. Along the way, we'll strip off the
704 // border around libhb's image.
706 // The image data returned by hb_get_preview is 4 bytes per pixel, BGRA format.
709 int dstWidth = title->job->width;
710 int dstHeight = title->job->height;
711 NSBitmapFormat bitmapFormat = (NSBitmapFormat)NSAlphaFirstBitmapFormat;
712 NSBitmapImageRep * imgrep = [[[NSBitmapImageRep alloc]
713 initWithBitmapDataPlanes:nil
717 samplesPerPixel:3 // ignore alpha
720 colorSpaceName:NSCalibratedRGBColorSpace
721 bitmapFormat:bitmapFormat
722 bytesPerRow:dstWidth * 4
723 bitsPerPixel:32] autorelease];
725 int borderTop = (srcHeight - dstHeight) / 2;
726 int borderLeft = (srcWidth - dstWidth) / 2;
728 UInt32 * src = (UInt32 *)buffer;
729 UInt32 * dst = (UInt32 *)[imgrep bitmapData];
730 src += borderTop * srcWidth; // skip top rows in src to get to first row of dst
731 src += borderLeft; // skip left pixels in src to get to first pixel of dst
732 for (int r = 0; r < dstHeight; r++)
734 for (int c = 0; c < dstWidth; c++)
735 #if TARGET_RT_LITTLE_ENDIAN
736 *dst++ = Endian32_Swap(*src++);
740 src += (srcWidth - dstWidth); // skip to next row in src
743 NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(dstWidth, dstHeight)] autorelease];
744 [img addRepresentation:imgrep];
750 // Make sure we have big enough buffer
751 static uint8_t * buffer;
752 static int bufferSize;
755 newSize = ( title->width + 2 ) * (title->height + 2 ) * 4;
756 if( bufferSize < newSize )
758 bufferSize = newSize;
759 buffer = (uint8_t *) realloc( buffer, bufferSize );
762 hb_get_preview( handle, title, pictureIndex, buffer );
764 // The image data returned by hb_get_preview is 4 bytes per pixel, BGRA format.
765 // We'll copy that into an NSImage swapping it to ARGB in the process. Alpha is
767 int width = title->width + 2; // hblib adds a one-pixel border to the image
768 int height = title->height + 2;
769 int numPixels = width * height;
770 NSBitmapFormat bitmapFormat = (NSBitmapFormat)NSAlphaFirstBitmapFormat;
771 NSBitmapImageRep * imgrep = [[[NSBitmapImageRep alloc]
772 initWithBitmapDataPlanes:nil
776 samplesPerPixel:3 // ignore alpha
779 colorSpaceName:NSCalibratedRGBColorSpace
780 bitmapFormat:bitmapFormat
781 bytesPerRow:width * 4
782 bitsPerPixel:32] autorelease];
784 UInt32 * src = (UInt32 *)buffer;
785 UInt32 * dst = (UInt32 *)[imgrep bitmapData];
786 for (int i = 0; i < numPixels; i++)
787 #if TARGET_RT_LITTLE_ENDIAN
788 *dst++ = Endian32_Swap(*src++);
793 NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(width, height)] autorelease];
794 [img addRepresentation:imgrep];
800 // Returns the preview image for the specified index, retrieving it from its internal
801 // cache or by calling makeImageForPicture if it is not cached. Generally, you should
802 // use imageForPicture so that images are cached. Calling makeImageForPicture will
803 // always generate a new copy of the image.
804 - (NSImage *) imageForPicture: (int) pictureIndex
806 // The preview for the specified index may not currently exist, so this method
807 // generates it if necessary.
808 NSString * key = [NSString stringWithFormat:@"%d", pictureIndex];
809 NSImage * theImage = [fPicturePreviews objectForKey:key];
812 theImage = [PreviewController makeImageForPicture:pictureIndex libhb:fHandle title:fTitle removeBorders: NO];
813 [fPicturePreviews setObject:theImage forKey:key];
818 // Purges all images from the cache. The next call to imageForPicture will cause a new
819 // image to be generated.
820 - (void) purgeImageCache
822 [fPicturePreviews removeAllObjects];
827 #pragma mark Movie Preview
828 - (IBAction) createMoviePreview: (id) sender
832 /* Lets make sure the still picture previews are showing in case
833 * there is currently a movie showing */
834 [self pictureSliderChanged:nil];
836 /* Rip or Cancel ? */
838 hb_get_state2( fPreviewLibhb, &s );
840 if(sender == fCancelPreviewMovieButton && (s.state == HB_STATE_WORKING || s.state == HB_STATE_PAUSED))
844 hb_stop( fPreviewLibhb );
845 [fPictureView setHidden:NO];
846 [fMovieView pause:nil];
847 [fMovieView setHidden:YES];
848 [fPictureSlider setHidden:NO];
855 /* we use controller.mm's prepareJobForPreview to go ahead and set all of our settings
856 * however, we want to use a temporary destination field of course
857 * so that we do not put our temp preview in the users chosen
860 hb_job_t * job = fTitle->job;
862 /* We run our current setting through prepeareJob in Controller.mm
863 * just as if it were a regular encode */
865 [fHBController prepareJobForPreview];
867 /* Destination file. We set this to our preview directory
868 * changing the extension appropriately.*/
869 if (fTitle->job->mux == HB_MUX_MP4) // MP4 file
871 /* we use .m4v for our mp4 files so that ac3 and chapters in mp4 will play properly */
872 fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.m4v";
874 else if (fTitle->job->mux == HB_MUX_MKV) // MKV file
876 fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.mkv";
878 else if (fTitle->job->mux == HB_MUX_AVI) // AVI file
880 fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.avi";
882 else if (fTitle->job->mux == HB_MUX_OGM) // OGM file
884 fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.ogm";
887 fPreviewMoviePath = [[fPreviewMoviePath stringByExpandingTildeInPath]retain];
889 /* See if there is an existing preview file, if so, delete it */
890 if( ![[NSFileManager defaultManager] fileExistsAtPath:fPreviewMoviePath] )
892 [[NSFileManager defaultManager] removeFileAtPath:fPreviewMoviePath
896 /* We now direct our preview encode to fPreviewMoviePath */
897 fTitle->job->file = [fPreviewMoviePath UTF8String];
899 /* We use our advance pref to determine how many previews to scan */
900 int hb_num_previews = [[[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewsNumber"] intValue];
901 job->start_at_preview = fPicture + 1;
902 job->seek_points = hb_num_previews;
904 /* we use the preview duration popup to get the specified
905 * number of seconds for the preview encode.
908 job->pts_to_stop = [[fPreviewMovieLengthPopUp titleOfSelectedItem] intValue] * 90000LL;
910 /* lets go ahead and send it off to libhb
911 * Note: unlike a full encode, we only send 1 pass regardless if the final encode calls for 2 passes.
912 * this should suffice for a fairly accurate short preview and cuts our preview generation time in half.
914 hb_add( fPreviewLibhb, job );
916 [fEncodingControlBox setHidden: NO];
917 [fPictureControlBox setHidden: YES];
919 [fMovieCreationProgressIndicator setHidden: NO];
920 [fPreviewMovieStatusField setHidden: NO];
926 /* Let fPreviewLibhb do the job */
927 hb_start( fPreviewLibhb );
931 - (void) startReceivingLibhbNotifications
935 fLibhbTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(libhbTimerFired:) userInfo:nil repeats:YES];
936 [fLibhbTimer retain];
940 - (void) stopReceivingLibhbNotifications
944 [fLibhbTimer invalidate];
945 [fLibhbTimer release];
949 - (void) libhbTimerFired: (NSTimer*)theTimer
952 hb_get_state( fPreviewLibhb, &s );
953 [self libhbStateChanged: s];
957 - (void) libhbStateChanged: (hb_state_t &)state
959 switch( state.state )
962 case HB_STATE_SCANNING:
963 case HB_STATE_SCANDONE:
966 case HB_STATE_WORKING:
968 #define p state.param.working
970 NSMutableString * string;
971 /* Update text field */
972 string = [NSMutableString stringWithFormat: NSLocalizedString( @"Encoding preview: %.2f %%", @"" ), 100.0 * p.progress];
976 [string appendFormat:
977 NSLocalizedString( @" (%.2f fps, avg %.2f fps, ETA %02dh%02dm%02ds)", @"" ),
978 p.rate_cur, p.rate_avg, p.hours, p.minutes, p.seconds];
980 [fPreviewMovieStatusField setStringValue: string];
982 [fMovieCreationProgressIndicator setIndeterminate: NO];
984 [fMovieCreationProgressIndicator setDoubleValue: 100.0 * p.progress];
986 [fCreatePreviewMovieButton setTitle: @"Cancel Preview"];
993 #define p state.param.muxing
994 case HB_STATE_MUXING:
996 // Update fMovieCreationProgressIndicator
997 [fMovieCreationProgressIndicator setIndeterminate: YES];
998 [fMovieCreationProgressIndicator startAnimation: nil];
999 [fPreviewMovieStatusField setStringValue: [NSString stringWithFormat:
1000 NSLocalizedString( @"Muxing Preview ...", @"" )]];
1004 case HB_STATE_PAUSED:
1005 [fMovieCreationProgressIndicator stopAnimation: nil];
1008 case HB_STATE_WORKDONE:
1010 // Delete all remaining jobs since libhb doesn't do this on its own.
1012 while( ( job = hb_job(fPreviewLibhb, 0) ) )
1013 hb_rem( fHandle, job );
1015 [fPreviewMovieStatusField setStringValue: @""];
1016 [fPreviewMovieStatusField setHidden: YES];
1018 [fMovieCreationProgressIndicator stopAnimation: nil];
1019 [fMovieCreationProgressIndicator setHidden: YES];
1020 [fEncodingControlBox setHidden: YES];
1022 /* we make sure the picture slider and preview match */
1023 [self pictureSliderChanged:nil];
1026 // Show the movie view
1029 [self showMoviePreview:fPreviewMoviePath];
1032 [fCreatePreviewMovieButton setTitle: @"Live Preview"];
1041 - (IBAction) showMoviePreview: (NSString *) path
1043 /* Since the gray background for the still images is part of
1044 * fPictureView, lets leave the picture view visible and postion
1045 * the fMovieView over the image portion of fPictureView so
1046 * we retain the gray cropping border we have already established
1047 * with the still previews
1049 [fMovieView setHidden:NO];
1051 /* Load the new movie into fMovieView */
1056 [fMovieView setControllerVisible: YES];
1057 /* let's make sure there is no movie currently set */
1058 [fMovieView setMovie:nil];
1060 aMovie = [QTMovie movieWithFile:path error:nil];
1062 /* we get some size information from the preview movie */
1064 GetMovieBox ([aMovie quickTimeMovie], &movieBox);
1065 movieBounds = [fMovieView movieBounds];
1066 movieBounds.size.height = movieBox.bottom - movieBox.top;
1068 if ([fMovieView isControllerVisible])
1069 movieBounds.size.height += [fMovieView controllerBarHeight];
1070 /* since for whatever the reason I cannot seem to get the [fMovieView controllerBarHeight]
1071 * For now just use 15 for additional height as it seems to line up well
1073 movieBounds.size.height += 15;
1075 movieBounds.size.width = movieBox.right - movieBox.left;
1077 /* We need to find out if the preview movie needs to be scaled down so
1078 * that it doesn't overflow our available viewing container (just like for image
1079 * in -displayPreview) for HD sources, etc. [fPictureViewArea frame].size.height*/
1080 if( ((int)movieBounds.size.height) > [fPictureView frame].size.height || scaleToScreen == YES)
1082 /* The preview movie would be larger than the available viewing area
1083 * in the preview movie, so we go ahead and scale it down to the same size
1084 * as the still preview or we readjust our window to allow for the added height if need be
1086 NSSize displaySize = NSMakeSize( (float)movieBounds.size.width, (float)movieBounds.size.height );
1087 NSSize viewSize = [self optimalViewSizeForImageSize:displaySize];
1088 if( [self viewNeedsToResizeToSize:viewSize] )
1091 [self resizeSheetForViewSize:viewSize];
1092 [self setViewSize:viewSize];
1096 [fMovieView setPreservesAspectRatio:YES];
1097 [fMovieView setFrameSize:viewSize];
1101 /* Since the preview movie is smaller than the available viewing area
1102 * we can go ahead and use the preview movies native size */
1103 [fMovieView setFrameSize:movieBounds.size];
1110 // lets reposition the movie if need be
1112 NSPoint origin = [fPictureViewArea frame].origin;
1113 origin.x += trunc(([fPictureViewArea frame].size.width -
1114 [fMovieView frame].size.width) / 2.0);
1115 /* We need to detect whether or not we are currently less than the available height.*/
1116 if (movieBounds.size.height < [fPictureView frame].size.height)
1118 /* If we are, we are adding 15 to the height to allow for the controller bar so
1119 * we need to subtract half of that for the origin.y to get the controller bar
1120 * below the movie to it lines up vertically with where our still preview was
1122 origin.y += trunc((([fPictureViewArea frame].size.height -
1123 [fMovieView frame].size.height) / 2.0) - 7.5);
1127 /* if we are >= to the height of the picture view area, the controller bar
1128 * gets taken care of with picture resizing, so we do not want to offset the height
1130 origin.y += trunc(([fPictureViewArea frame].size.height -
1131 [fMovieView frame].size.height) / 2.0);
1133 [fMovieView setFrameOrigin:origin];
1135 [fMovieView setMovie:aMovie];
1136 /// to actually play the movie
1137 [fMovieView play:aMovie];
1149 @implementation PreviewController (Private)
1152 // -[PictureController(Private) optimalViewSizeForImageSize:]
1154 // Given the size of the preview image to be shown, returns the best possible
1155 // size for the view.
1157 - (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize
1159 // The min size is 320x240
1160 CGFloat minWidth = 480.0;
1161 CGFloat minHeight = 360.0;
1163 NSSize screenSize = [[NSScreen mainScreen] frame].size;
1164 NSSize sheetSize = [[self window] frame].size;
1165 NSSize viewAreaSize = [fPictureViewArea frame].size;
1166 CGFloat paddingX = sheetSize.width - viewAreaSize.width;
1167 CGFloat paddingY = sheetSize.height - viewAreaSize.height;
1173 /* We are in full screen mode so lets use the full screen if we need to */
1174 maxWidth = screenSize.width - paddingX;
1175 maxHeight = screenSize.height - paddingY;
1179 // The max size of the view is when the sheet is taking up 85% of the screen.
1180 maxWidth = (0.85 * screenSize.width) - paddingX;
1181 maxHeight = (0.85 * screenSize.height) - paddingY;
1184 NSSize resultSize = imageSize;
1186 // Its better to have a view that's too small than a view that's too big, so
1187 // apply the maximum constraints last.
1188 if( resultSize.width < minWidth )
1190 resultSize.height *= (minWidth / resultSize.width);
1191 resultSize.width = minWidth;
1193 if( resultSize.height < minHeight )
1195 resultSize.width *= (minHeight / resultSize.height);
1196 resultSize.height = minHeight;
1198 if( resultSize.width > maxWidth )
1200 resultSize.height *= (maxWidth / resultSize.width);
1201 resultSize.width = maxWidth;
1203 if( resultSize.height > maxHeight )
1205 resultSize.width *= (maxHeight / resultSize.height);
1206 resultSize.height = maxHeight;
1209 if (scaleToScreen == YES)
1211 //CGFloat scaleToScreenWidth;
1212 //CGFloat scaleToScreenHeight;
1213 CGFloat screenAspect;
1214 CGFloat viewAreaAspect;
1215 //note, a mbp 15" at 1440 x 900 is a 1.6 ar
1216 screenAspect = screenSize.width / screenSize.height;
1218 // Note, a standard dvd will use 720 x 480 which is a 1.5
1219 viewAreaAspect = viewAreaSize.width / viewAreaSize.height;
1221 if (screenAspect < viewAreaAspect)
1223 resultSize.width = screenSize.width;
1224 resultSize.height = (screenSize.width / viewAreaAspect);
1228 resultSize.height = screenSize.height;
1229 resultSize.width = resultSize.height * viewAreaAspect;
1240 // -[PictureController(Private) resizePanelForViewSize:animate:]
1242 // Resizes the entire window to accomodate a view of a particular size.
1244 - (void)resizeSheetForViewSize: (NSSize)viewSize
1246 // Figure out the deltas for the new frame area
1247 NSSize currentSize = [fPictureViewArea frame].size;
1248 CGFloat deltaX = viewSize.width - currentSize.width;
1249 CGFloat deltaY = viewSize.height - currentSize.height;
1251 // Now resize the whole panel by those same deltas, but don't exceed the min
1252 NSRect frame = [[self window] frame];
1253 NSSize maxSize = [[self window] maxSize];
1254 NSSize minSize = [[self window] minSize];
1255 frame.size.width += deltaX;
1256 frame.size.height += deltaY;
1257 if( frame.size.width < minSize.width )
1259 frame.size.width = minSize.width;
1262 if( frame.size.height < minSize.height )
1264 frame.size.height = minSize.height;
1268 // But now the sheet is off-center, so also shift the origin to center it and
1269 // keep the top aligned.
1270 if( frame.size.width != [[self window] frame].size.width )
1271 frame.origin.x -= (deltaX / 2.0);
1275 if( frame.size.height != [[self window] frame].size.height )
1277 frame.origin.y -= (deltaY / 2.0);
1281 if( frame.size.height != [[self window] frame].size.height )
1282 frame.origin.y -= deltaY;
1285 [[self window] setFrame:frame display:YES animate:NO];
1289 [[self window] setFrame:frame display:YES animate:YES];
1295 // -[PictureController(Private) setViewSize:]
1297 // Changes the view's size and centers it vertically inside of its area.
1298 // Assumes resizeSheetForViewSize: has already been called.
1300 - (void)setViewSize: (NSSize)viewSize
1302 [fPictureView setFrameSize:viewSize];
1304 // center it vertically
1305 NSPoint origin = [fPictureViewArea frame].origin;
1306 origin.y += ([fPictureViewArea frame].size.height -
1307 [fPictureView frame].size.height) / 2.0;
1308 [fPictureView setFrameOrigin:origin];
1310 NSPoint controlboxorigin = [fPictureView frame].origin;
1312 /* for now, put the origin.y 100 above the bottom of the fPictureView */
1313 controlboxorigin.y += 100;
1315 controlboxorigin.x += ([fPictureViewArea frame].size.width -
1316 [fPictureControlBox frame].size.width) / 2.0;
1317 /* requires that thefPictureControlBox and the fEncodingControlBox
1318 * are the same width to line up.
1320 [fPictureControlBox setFrameOrigin:controlboxorigin];
1321 [fEncodingControlBox setFrameOrigin:controlboxorigin];
1326 - (BOOL)viewNeedsToResizeToSize: (NSSize)newSize
1328 NSSize viewSize = [fPictureView frame].size;
1329 return (newSize.width != viewSize.width || newSize.height != viewSize.height);