mirror of https://github.com/HandBrake/HandBrake
1657 lines
54 KiB
Objective-C
1657 lines
54 KiB
Objective-C
/* $Id: HBPreviewController.mm,v 1.11 2005/08/01 15:10:44 titer Exp $
|
|
|
|
This file is part of the HandBrake source code.
|
|
Homepage: <http://handbrake.fr/>.
|
|
It may be used under the terms of the GNU General Public License. */
|
|
|
|
#import "HBPreviewController.h"
|
|
#import "Controller.h"
|
|
|
|
@implementation QTMovieView ( HBQTkitExt )
|
|
- (void) mouseMoved:(NSEvent *)theEvent
|
|
{
|
|
[super mouseMoved:theEvent];
|
|
}
|
|
@end
|
|
|
|
|
|
|
|
@interface PreviewController (Private)
|
|
|
|
- (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize;
|
|
- (void)resizeSheetForViewSize: (NSSize)viewSize;
|
|
- (void)setViewSize: (NSSize)viewSize;
|
|
- (BOOL)viewNeedsToResizeToSize: (NSSize)newSize;
|
|
|
|
@end
|
|
|
|
@implementation PreviewController
|
|
|
|
- (id)init
|
|
{
|
|
if (self = [super initWithWindowNibName:@"PicturePreview"])
|
|
{
|
|
// NSWindowController likes to lazily load its window. However since
|
|
// this controller tries to set all sorts of outlets before the window
|
|
// is displayed, we need it to load immediately. The correct way to do
|
|
// this, according to the documentation, is simply to invoke the window
|
|
// getter once.
|
|
//
|
|
// If/when we switch a lot of this stuff to bindings, this can probably
|
|
// go away.
|
|
[self window];
|
|
|
|
fPicturePreviews = [[NSMutableDictionary dictionaryWithCapacity: HB_NUM_HBLIB_PICTURES] retain];
|
|
/* Init libhb with check for updates libhb style set to "0" so its ignored and lets sparkle take care of it */
|
|
int loggingLevel = [[[NSUserDefaults standardUserDefaults] objectForKey:@"LoggingLevel"] intValue];
|
|
fPreviewLibhb = hb_init(loggingLevel, 0);
|
|
|
|
|
|
|
|
}
|
|
return self;
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------------
|
|
// Displays and brings the picture window to the front
|
|
//------------------------------------------------------------------------------------
|
|
- (IBAction) showPreviewWindow: (id)sender
|
|
{
|
|
[self showWindow:sender];
|
|
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"PreviewWindowIsOpen"];
|
|
|
|
/* lets set the preview window to accept mouse moved events */
|
|
[fPreviewWindow setAcceptsMouseMovedEvents:YES];
|
|
hudTimerSeconds = 0;
|
|
[self pictureSliderChanged:nil];
|
|
[self startReceivingLibhbNotifications];
|
|
}
|
|
|
|
- (void)setHBController: (HBController *)controller
|
|
{
|
|
fHBController = controller;
|
|
}
|
|
|
|
- (void)awakeFromNib
|
|
{
|
|
[fPreviewWindow setDelegate:self];
|
|
if( ![[self window] setFrameUsingName:@"Preview"] )
|
|
[[self window] center];
|
|
[self setWindowFrameAutosaveName:@"Preview"];
|
|
[[self window] setExcludedFromWindowsMenu:YES];
|
|
|
|
/* lets set the preview window to accept mouse moved events */
|
|
[fPreviewWindow setAcceptsMouseMovedEvents:YES];
|
|
//[self pictureSliderChanged:nil];
|
|
[self startReceivingLibhbNotifications];
|
|
|
|
hudTimerSeconds = 0;
|
|
/* we set the progress indicator to not use threaded animation
|
|
* as it causes a conflict with the qtmovieview's controllerbar
|
|
*/
|
|
[fMovieCreationProgressIndicator setUsesThreadedAnimation:NO];
|
|
|
|
/* Setup our layers for core animation */
|
|
[fPictureViewArea setWantsLayer:YES];
|
|
[fPictureView setWantsLayer:YES];
|
|
|
|
[fCancelPreviewMovieButton setWantsLayer:YES];
|
|
[fMovieCreationProgressIndicator setWantsLayer:YES];
|
|
|
|
[fPictureControlBox setWantsLayer:YES];
|
|
[fEncodingControlBox setWantsLayer:YES];
|
|
[fMovieView setWantsLayer:YES];
|
|
[fMovieView setHidden:YES];
|
|
[fMovieView setDelegate:self];
|
|
|
|
/* Since the xib has everything off center for easy acess
|
|
* we align our views and windows here we an align to anything
|
|
* since it will actually change later upon source load, but
|
|
* for convenience we will use the fPictureViewArea
|
|
*/
|
|
|
|
/* Align the still preview image view to the picture box */
|
|
[fPictureView setFrameSize:[fPictureViewArea frame].size];
|
|
[fMovieView setFrameSize:[fPictureViewArea frame].size];
|
|
//[fPreviewWindow setFrameSize:[fPictureViewArea frame].size];
|
|
|
|
|
|
}
|
|
- (BOOL)acceptsMouseMovedEvents
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
- (void)windowWillClose:(NSNotification *)aNotification
|
|
{
|
|
/* Upon closing the preview window, we make sure we clean up any
|
|
* preview movie that might be playing or encoding. However, first
|
|
* make sure we have a preview picture before calling pictureSliderChanged
|
|
* to go back to still previews .. just in case nothing is loaded up like in
|
|
* a Launch, cancel new scan then quit type scenario.
|
|
*/
|
|
if (fPicture)
|
|
{
|
|
[self pictureSliderChanged:nil];
|
|
[fMovieTimer invalidate];
|
|
[fMovieTimer release];
|
|
}
|
|
|
|
hudTimerSeconds = 0;
|
|
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"PreviewWindowIsOpen"];
|
|
}
|
|
|
|
- (BOOL)windowShouldClose:(id)fPictureWindow
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
- (void) dealloc
|
|
{
|
|
hb_stop(fPreviewLibhb);
|
|
if (fPreviewMoviePath)
|
|
{
|
|
[[NSFileManager defaultManager] removeItemAtPath:fPreviewMoviePath error:nil];
|
|
[fPreviewMoviePath release];
|
|
}
|
|
|
|
[fLibhbTimer invalidate];
|
|
[fLibhbTimer release];
|
|
|
|
[fHudTimer invalidate];
|
|
[fHudTimer release];
|
|
|
|
[fMovieTimer invalidate];
|
|
[fMovieTimer release];
|
|
|
|
[fPicturePreviews release];
|
|
[fFullScreenWindow release];
|
|
|
|
hb_close(&fPreviewLibhb);
|
|
|
|
[self removeMovieCallbacks];
|
|
|
|
[super dealloc];
|
|
}
|
|
|
|
- (void) SetHandle: (hb_handle_t *) handle
|
|
{
|
|
fHandle = handle;
|
|
|
|
|
|
|
|
/* we set the preview length popup in seconds */
|
|
[fPreviewMovieLengthPopUp removeAllItems];
|
|
[fPreviewMovieLengthPopUp addItemWithTitle: @"15"];
|
|
[fPreviewMovieLengthPopUp addItemWithTitle: @"30"];
|
|
[fPreviewMovieLengthPopUp addItemWithTitle: @"45"];
|
|
[fPreviewMovieLengthPopUp addItemWithTitle: @"60"];
|
|
[fPreviewMovieLengthPopUp addItemWithTitle: @"90"];
|
|
[fPreviewMovieLengthPopUp addItemWithTitle: @"105"];
|
|
[fPreviewMovieLengthPopUp addItemWithTitle: @"120"];
|
|
|
|
/* adjust the preview slider length */
|
|
/* We use our advance pref to determine how many previews we scanned */
|
|
int hb_num_previews = [[[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewsNumber"] intValue];
|
|
[fPictureSlider setMaxValue: hb_num_previews - 1.0];
|
|
[fPictureSlider setNumberOfTickMarks: hb_num_previews];
|
|
|
|
if ([[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewLength"])
|
|
{
|
|
[fPreviewMovieLengthPopUp selectItemWithTitle:[[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewLength"]];
|
|
}
|
|
else
|
|
{
|
|
/* currently hard set default to 10 seconds */
|
|
[fPreviewMovieLengthPopUp selectItemAtIndex: 1];
|
|
}
|
|
}
|
|
|
|
- (void) SetTitle: (hb_title_t *) title
|
|
{
|
|
hb_job_t * job = title->job;
|
|
|
|
fTitle = title;
|
|
fPicture = 0;
|
|
MaxOutputWidth = title->width - job->crop[2] - job->crop[3];
|
|
MaxOutputHeight = title->height - job->crop[0] - job->crop[1];
|
|
|
|
[self SettingsChanged: nil];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Adjusts the window to draw the current picture (fPicture) adjusting its size as
|
|
// necessary to display as much of the picture as possible.
|
|
- (void) displayPreview
|
|
{
|
|
hb_job_t * job = fTitle->job;
|
|
/* lets make sure that the still picture view is not hidden and that
|
|
* the movie preview is
|
|
*/
|
|
aMovie = nil;
|
|
[fMovieView pause:nil];
|
|
[fMovieView setHidden:YES];
|
|
[fMovieView setMovie:nil];
|
|
[fMovieCreationProgressIndicator stopAnimation: nil];
|
|
[fMovieCreationProgressIndicator setHidden: YES];
|
|
[fMoviePlaybackControlBox setHidden: YES];
|
|
if( fMovieTimer )
|
|
{
|
|
[self stopMovieTimer];
|
|
}
|
|
[fPictureControlBox setHidden: NO];
|
|
|
|
[fPictureView setHidden:NO];
|
|
|
|
NSImage *fPreviewImage = [self imageForPicture: fPicture];
|
|
NSSize imageScaledSize = [fPreviewImage size];
|
|
[fPictureView setImage: fPreviewImage];
|
|
|
|
NSSize displaySize = NSMakeSize( ( CGFloat )fTitle->width, ( CGFloat )fTitle->height );
|
|
NSString *sizeInfoString;
|
|
/* Set the picture size display fields below the Preview Picture*/
|
|
if( fTitle->job->anamorphic.mode == 1 ) // Original PAR Implementation
|
|
{
|
|
output_width = fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3];
|
|
output_height = fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1];
|
|
display_width = output_width * fTitle->job->anamorphic.par_width / fTitle->job->anamorphic.par_height;
|
|
sizeInfoString = [NSString stringWithFormat:
|
|
@"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d Strict",
|
|
fTitle->width, fTitle->height, output_width, output_height, display_width, output_height];
|
|
|
|
displaySize.width = display_width;
|
|
displaySize.height = fTitle->height;
|
|
imageScaledSize.width = display_width;
|
|
imageScaledSize.height = output_height;
|
|
}
|
|
else if (fTitle->job->anamorphic.mode == 2) // Loose Anamorphic
|
|
{
|
|
hb_set_anamorphic_size(job, &output_width, &output_height, &output_par_width, &output_par_height);
|
|
display_width = output_width * output_par_width / output_par_height;
|
|
sizeInfoString = [NSString stringWithFormat:
|
|
@"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d Loose",
|
|
fTitle->width, fTitle->height, output_width, output_height, display_width, output_height];
|
|
|
|
displaySize.width = display_width;
|
|
displaySize.height = fTitle->height;
|
|
imageScaledSize.width = display_width;
|
|
imageScaledSize.height = output_height;
|
|
}
|
|
else if (fTitle->job->anamorphic.mode == 3) // Custom Anamorphic
|
|
{
|
|
hb_set_anamorphic_size(job, &output_width, &output_height, &output_par_width, &output_par_height);
|
|
display_width = output_width * output_par_width / output_par_height;
|
|
sizeInfoString = [NSString stringWithFormat:
|
|
@"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d Custom",
|
|
fTitle->width, fTitle->height, output_width, output_height, fTitle->job->anamorphic.dar_width, fTitle->job->anamorphic.dar_height];
|
|
|
|
displaySize.width = fTitle->job->anamorphic.dar_width + fTitle->job->crop[2] + fTitle->job->crop[3] ;
|
|
displaySize.height = fTitle->job->anamorphic.dar_height + fTitle->job->crop[0] + fTitle->job->crop[1];
|
|
imageScaledSize.width = (int)fTitle->job->anamorphic.dar_width;
|
|
imageScaledSize.height = (int)fTitle->job->height;
|
|
}
|
|
else // No Anamorphic
|
|
{
|
|
sizeInfoString = [NSString stringWithFormat:
|
|
@"Source: %dx%d, Output: %dx%d", fTitle->width, fTitle->height,
|
|
fTitle->job->width, fTitle->job->height];
|
|
|
|
displaySize.width = fTitle->width;
|
|
displaySize.height = fTitle->height;
|
|
imageScaledSize.width = fTitle->job->width;
|
|
imageScaledSize.height = fTitle->job->height;
|
|
}
|
|
|
|
|
|
|
|
NSSize viewSize = [self optimalViewSizeForImageSize:displaySize];
|
|
[self resizeSheetForViewSize:viewSize];
|
|
|
|
NSSize windowSize = [[self window] frame].size;
|
|
|
|
if (scaleToScreen == YES)
|
|
{
|
|
/* Note: this should probably become a utility function */
|
|
/* We are in Scale To Screen mode so, we have to get the ratio for height and width against the window
|
|
*size so we can scale from there.
|
|
*/
|
|
CGFloat deltaWidth = imageScaledSize.width / displaySize.width;
|
|
CGFloat deltaHeight = imageScaledSize.height /displaySize.height;
|
|
NSSize windowSize = [[self window] frame].size;
|
|
CGFloat pictureAspectRatio = imageScaledSize.width / imageScaledSize.height;
|
|
|
|
/* Set our min size to the storage size */
|
|
NSSize minSize;
|
|
minSize.width = fTitle->width;
|
|
minSize.height = fTitle->height;
|
|
|
|
/* Set delta's based on minimum size */
|
|
if (imageScaledSize.width < minSize.width)
|
|
{
|
|
deltaWidth = imageScaledSize.width / minSize.width;
|
|
}
|
|
else
|
|
{
|
|
deltaWidth = 1.0;
|
|
}
|
|
|
|
if (imageScaledSize.height < minSize.height)
|
|
{
|
|
deltaHeight = imageScaledSize.height / minSize.height;
|
|
}
|
|
else
|
|
{
|
|
deltaHeight = 1.0;
|
|
}
|
|
|
|
/* Now apply our deltas to the full screen view */
|
|
if (pictureAspectRatio > 1.0) // we are wider than taller, so expand the width to fill the area and scale the height
|
|
{
|
|
viewSize.width = windowSize.width * deltaWidth;
|
|
viewSize.height = viewSize.width / pictureAspectRatio;
|
|
|
|
}
|
|
else
|
|
{
|
|
viewSize.height = windowSize.height * deltaHeight;
|
|
viewSize.width = viewSize.height * pictureAspectRatio;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
viewSize.width = viewSize.width - (viewSize.width - imageScaledSize.width);
|
|
viewSize.height = viewSize.height - (viewSize.height - imageScaledSize.height);
|
|
|
|
if (fTitle->width > windowSize.width || fTitle->height > windowSize.height)
|
|
{
|
|
CGFloat viewSizeAspect = viewSize.width / viewSize.height;
|
|
if (viewSizeAspect > 1.0) // we are wider than taller, so expand the width to fill the area and scale the height
|
|
{
|
|
viewSize.width = viewSize.width * (windowSize.width / fTitle->width) ;
|
|
viewSize.height = viewSize.width / viewSizeAspect;
|
|
}
|
|
else
|
|
{
|
|
viewSize.height = viewSize.height * (windowSize.height / fTitle->height);
|
|
viewSize.width = viewSize.height * viewSizeAspect;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
[self setViewSize:viewSize];
|
|
|
|
/* relocate our hud origins as per setViewSize */
|
|
NSPoint hudControlBoxOrigin = [fPictureControlBox frame].origin;
|
|
hudControlBoxOrigin.y = ([[self window] frame].size.height / 2) - (viewSize.height / 2);
|
|
hudControlBoxOrigin.x = ([[self window] frame].size.width / 2) - ([fPictureControlBox frame].size.width / 2);
|
|
[fPictureControlBox setFrameOrigin:hudControlBoxOrigin];
|
|
[fEncodingControlBox setFrameOrigin:hudControlBoxOrigin];
|
|
[fMoviePlaybackControlBox setFrameOrigin:hudControlBoxOrigin];
|
|
|
|
|
|
NSString *scaleString;
|
|
CGFloat scale = ( ( CGFloat )[fPictureView frame].size.width) / ( ( CGFloat )imageScaledSize.width);
|
|
if (scale * 100.0 != 100)
|
|
{
|
|
scaleString = [NSString stringWithFormat:
|
|
NSLocalizedString( @" (%.0f%% actual size)",
|
|
@"String shown when a preview is scaled" ), scale * 100.0];
|
|
}
|
|
else
|
|
{
|
|
scaleString = @"(Actual size)";
|
|
}
|
|
|
|
if (scaleToScreen == YES)
|
|
{
|
|
scaleString = [scaleString stringByAppendingString:@" Scaled To Screen"];
|
|
}
|
|
/* Set the info fields in the hud controller */
|
|
[fInfoField setStringValue: [NSString stringWithFormat:
|
|
@"%@", sizeInfoString]];
|
|
|
|
[fscaleInfoField setStringValue: [NSString stringWithFormat:
|
|
@"%@", scaleString]];
|
|
/* Set the info field in the window title bar */
|
|
[[self window] setTitle:[NSString stringWithFormat: @"Preview - %@ %@",sizeInfoString, scaleString]];
|
|
}
|
|
|
|
- (IBAction) previewDurationPopUpChanged: (id) sender
|
|
{
|
|
|
|
[[NSUserDefaults standardUserDefaults] setObject:[fPreviewMovieLengthPopUp titleOfSelectedItem] forKey:@"PreviewLength"];
|
|
|
|
}
|
|
|
|
- (IBAction) SettingsChanged: (id) sender
|
|
{
|
|
// Purge the existing picture previews so they get recreated the next time
|
|
// they are needed.
|
|
[self purgeImageCache];
|
|
[self pictureSliderChanged:nil];
|
|
}
|
|
|
|
- (IBAction) pictureSliderChanged: (id) sender
|
|
{
|
|
/* Run cancelCreateMoviePreview in case a preview is being encoded and then cancel if so */
|
|
[self cancelCreateMoviePreview:nil];
|
|
|
|
// Show the picture view
|
|
[fPictureView setHidden:NO];
|
|
[fMovieView pause:nil];
|
|
[fMovieView setHidden:YES];
|
|
[fMovieView setMovie:nil];
|
|
[fEncodingControlBox setHidden: YES];
|
|
|
|
int newPicture = [fPictureSlider intValue];
|
|
if (newPicture != fPicture)
|
|
{
|
|
fPicture = newPicture;
|
|
}
|
|
[self displayPreview];
|
|
|
|
}
|
|
|
|
- (IBAction)showPreviewPanel: (id)sender forTitle: (hb_title_t *)title
|
|
{
|
|
if ([fPreviewWindow isVisible])
|
|
{
|
|
[fPreviewWindow close];
|
|
}
|
|
else
|
|
{
|
|
[self showWindow:sender];
|
|
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"PreviewWindowIsOpen"];
|
|
[fPreviewWindow setAcceptsMouseMovedEvents:YES];
|
|
scaleToScreen = NO;
|
|
[self pictureSliderChanged:nil];
|
|
}
|
|
|
|
}
|
|
|
|
- (NSString*) pictureSizeInfoString
|
|
{
|
|
return [fInfoField stringValue];
|
|
}
|
|
|
|
- (IBAction)showPictureSettings:(id)sender
|
|
{
|
|
[fHBController showPicturePanel:self];
|
|
}
|
|
|
|
#pragma mark Hud Control Overlay
|
|
/* enableHudControls and disableHudControls are used to sync enableUI
|
|
* in HBController so that during a scan we do not attempt to access source
|
|
* images, etc. which can cause a crash. In general this ui behavior will mirror
|
|
* the main window ui's enableUI method and in fact is called from there */
|
|
- (void) enableHudControls
|
|
{
|
|
[fPictureSlider setEnabled:YES];
|
|
[fScaleToScreenToggleButton setEnabled:YES];
|
|
[fCreatePreviewMovieButton setEnabled:YES];
|
|
[fGoToStillPreviewButton setEnabled:YES];
|
|
[fHBController writeToActivityLog: "Preview: Enabling HUD Controls"];
|
|
}
|
|
|
|
- (void) disableHudControls
|
|
{
|
|
[fPictureSlider setEnabled:NO];
|
|
[fScaleToScreenToggleButton setEnabled:NO];
|
|
[fCreatePreviewMovieButton setEnabled:NO];
|
|
[fGoToStillPreviewButton setEnabled:NO];
|
|
[fHBController writeToActivityLog: "Preview: Disabling HUD Controls"];
|
|
}
|
|
|
|
- (void) mouseMoved:(NSEvent *)theEvent
|
|
{
|
|
[super mouseMoved:theEvent];
|
|
NSPoint mouseLoc = [theEvent locationInWindow];
|
|
|
|
/* Test for mouse location to show/hide hud controls */
|
|
if( isEncoding == NO )
|
|
{
|
|
/* Since we are not encoding, verify which control hud to show
|
|
* or hide based on aMovie ( aMovie indicates we need movie controls )
|
|
*/
|
|
NSBox * hudBoxToShow;
|
|
if ( aMovie == nil ) // No movie loaded up
|
|
{
|
|
hudBoxToShow = fPictureControlBox;
|
|
}
|
|
else // We have a movie
|
|
{
|
|
hudBoxToShow = fMoviePlaybackControlBox;
|
|
}
|
|
|
|
if( NSPointInRect( mouseLoc, [fPictureControlBox frame] ) )
|
|
{
|
|
[[hudBoxToShow animator] setHidden: NO];
|
|
[self stopHudTimer];
|
|
}
|
|
else if( NSPointInRect( mouseLoc, [fPictureViewArea frame] ) )
|
|
{
|
|
[[hudBoxToShow animator] setHidden: NO];
|
|
[self startHudTimer];
|
|
}
|
|
else
|
|
{
|
|
[[hudBoxToShow animator] setHidden: YES];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void) startHudTimer
|
|
{
|
|
if( fHudTimer ) {
|
|
[fHudTimer invalidate];
|
|
[fHudTimer release];
|
|
}
|
|
fHudTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(hudTimerFired:) userInfo:nil repeats:YES];
|
|
[fHudTimer retain];
|
|
}
|
|
|
|
- (void) stopHudTimer
|
|
{
|
|
if( fHudTimer )
|
|
{
|
|
[fHudTimer invalidate];
|
|
[fHudTimer release];
|
|
fHudTimer = nil;
|
|
hudTimerSeconds = 0;
|
|
}
|
|
}
|
|
|
|
- (void) hudTimerFired: (NSTimer*)theTimer
|
|
{
|
|
hudTimerSeconds++;
|
|
if( hudTimerSeconds >= 10 )
|
|
{
|
|
/* Regardless which control box is active, after the timer
|
|
* period we want either one to fade to hidden.
|
|
*/
|
|
[[fPictureControlBox animator] setHidden: YES];
|
|
[[fMoviePlaybackControlBox animator] setHidden: YES];
|
|
[self stopHudTimer];
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- (IBAction)toggleScaleToScreen:(id)sender
|
|
{
|
|
if (scaleToScreen == YES)
|
|
{
|
|
scaleToScreen = NO;
|
|
/* make sure we are set to a still preview */
|
|
[self pictureSliderChanged:nil];
|
|
[fScaleToScreenToggleButton setTitle:@"Scale To Screen"];
|
|
}
|
|
else
|
|
{
|
|
scaleToScreen = YES;
|
|
/* make sure we are set to a still preview */
|
|
[self pictureSliderChanged:nil];
|
|
[fScaleToScreenToggleButton setTitle:@"Actual Scale"];
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Title-less windows normally don't receive key presses, override this
|
|
- (BOOL)canBecomeKeyWindow
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
// Title-less windows normally can't become main which means that another
|
|
// non-fullscreen window will have the "active" titlebar in expose. Bad, fix it.
|
|
- (BOOL)canBecomeMainWindow
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
|
|
- (IBAction)goWindowedScreen:(id)sender
|
|
{
|
|
|
|
/* Get the screen info to release the display but don't actually do
|
|
* it until the windowed screen is setup.
|
|
*/
|
|
scaleToScreen = NO;
|
|
[self pictureSliderChanged:nil];
|
|
[fScaleToScreenToggleButton setTitle:@"<->"];
|
|
|
|
NSScreen* mainScreen = [NSScreen mainScreen];
|
|
NSDictionary* screenInfo = [mainScreen deviceDescription];
|
|
NSNumber* screenID = [screenInfo objectForKey:@"NSScreenNumber"];
|
|
CGDirectDisplayID displayID = (CGDirectDisplayID)[screenID longValue];
|
|
|
|
[fFullScreenWindow dealloc];
|
|
[fFullScreenWindow release];
|
|
|
|
|
|
[fPreviewWindow setContentView:fPictureViewArea];
|
|
[fPictureViewArea setNeedsDisplay:YES];
|
|
[self setWindow:fPreviewWindow];
|
|
|
|
// Show the window.
|
|
[fPreviewWindow makeKeyAndOrderFront:self];
|
|
|
|
/* Set the window back to regular level */
|
|
[fPreviewWindow setLevel:NSNormalWindowLevel];
|
|
|
|
/* Set the isFullScreen flag back to NO */
|
|
//isFullScreen = NO;
|
|
scaleToScreen = NO;
|
|
/* make sure we are set to a still preview */
|
|
[self pictureSliderChanged:nil];
|
|
[self showPreviewWindow:nil];
|
|
|
|
/* Change the name of fFullScreenToggleButton appropriately */
|
|
//[fFullScreenToggleButton setTitle: @"Full Screen"];
|
|
// [fScaleToScreenToggleButton setHidden:YES];
|
|
/* set the picture settings pallete back to normal level */
|
|
[fHBController picturePanelWindowed];
|
|
|
|
/* Release the display now that the we are back in windowed mode */
|
|
CGDisplayRelease(displayID);
|
|
|
|
[fPreviewWindow setAcceptsMouseMovedEvents:YES];
|
|
//[fFullScreenWindow setAcceptsMouseMovedEvents:NO];
|
|
|
|
hudTimerSeconds = 0;
|
|
[self startHudTimer];
|
|
|
|
}
|
|
|
|
|
|
#pragma mark Still Preview Image Processing
|
|
|
|
|
|
// This function converts an image created by libhb (specified via pictureIndex) into
|
|
// an NSImage suitable for the GUI code to use. If removeBorders is YES,
|
|
// makeImageForPicture crops the image generated by libhb stripping off the gray
|
|
// border around the content. This is the low-level method that generates the image.
|
|
// -imageForPicture calls this function whenever it can't find an image in its cache.
|
|
+ (NSImage *) makeImageForPicture: (int)pictureIndex
|
|
libhb:(hb_handle_t*)handle
|
|
title:(hb_title_t*)title
|
|
{
|
|
static uint8_t * buffer;
|
|
static int bufferSize;
|
|
|
|
// Make sure we have a big enough buffer to receive the image from libhb. libhb
|
|
int dstWidth = title->job->width;
|
|
int dstHeight = title->job->height;
|
|
|
|
int newSize;
|
|
newSize = dstWidth * dstHeight * 4;
|
|
if( bufferSize < newSize )
|
|
{
|
|
bufferSize = newSize;
|
|
buffer = (uint8_t *) realloc( buffer, bufferSize );
|
|
}
|
|
|
|
hb_get_preview( handle, title, pictureIndex, buffer );
|
|
|
|
// Create an NSBitmapImageRep and copy the libhb image into it, converting it from
|
|
// libhb's format to one suitable for NSImage. Along the way, we'll strip off the
|
|
// border around libhb's image.
|
|
|
|
// The image data returned by hb_get_preview is 4 bytes per pixel, BGRA format.
|
|
// Alpha is ignored.
|
|
|
|
NSBitmapFormat bitmapFormat = (NSBitmapFormat)NSAlphaFirstBitmapFormat;
|
|
NSBitmapImageRep * imgrep = [[[NSBitmapImageRep alloc]
|
|
initWithBitmapDataPlanes:nil
|
|
pixelsWide:dstWidth
|
|
pixelsHigh:dstHeight
|
|
bitsPerSample:8
|
|
samplesPerPixel:3 // ignore alpha
|
|
hasAlpha:NO
|
|
isPlanar:NO
|
|
colorSpaceName:NSCalibratedRGBColorSpace
|
|
bitmapFormat:bitmapFormat
|
|
bytesPerRow:dstWidth * 4
|
|
bitsPerPixel:32] autorelease];
|
|
|
|
UInt32 * src = (UInt32 *)buffer;
|
|
UInt32 * dst = (UInt32 *)[imgrep bitmapData];
|
|
int r, c;
|
|
for (r = 0; r < dstHeight; r++)
|
|
{
|
|
for (c = 0; c < dstWidth; c++)
|
|
#if TARGET_RT_LITTLE_ENDIAN
|
|
*dst++ = Endian32_Swap(*src++);
|
|
#else
|
|
*dst++ = *src++;
|
|
#endif
|
|
}
|
|
|
|
NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(dstWidth, dstHeight)] autorelease];
|
|
[img addRepresentation:imgrep];
|
|
|
|
return img;
|
|
}
|
|
|
|
// Returns the preview image for the specified index, retrieving it from its internal
|
|
// cache or by calling makeImageForPicture if it is not cached. Generally, you should
|
|
// use imageForPicture so that images are cached. Calling makeImageForPicture will
|
|
// always generate a new copy of the image.
|
|
- (NSImage *) imageForPicture: (int) pictureIndex
|
|
{
|
|
// The preview for the specified index may not currently exist, so this method
|
|
// generates it if necessary.
|
|
NSString * key = [NSString stringWithFormat:@"%d", pictureIndex];
|
|
NSImage * theImage = [fPicturePreviews objectForKey:key];
|
|
if (!theImage)
|
|
{
|
|
theImage = [PreviewController makeImageForPicture:pictureIndex libhb:fHandle title:fTitle];
|
|
[fPicturePreviews setObject:theImage forKey:key];
|
|
}
|
|
return theImage;
|
|
}
|
|
|
|
// Purges all images from the cache. The next call to imageForPicture will cause a new
|
|
// image to be generated.
|
|
- (void) purgeImageCache
|
|
{
|
|
[fPicturePreviews removeAllObjects];
|
|
}
|
|
|
|
|
|
|
|
#pragma mark Movie Preview
|
|
|
|
- (IBAction) cancelCreateMoviePreview: (id) sender
|
|
{
|
|
|
|
hb_state_t s;
|
|
hb_get_state2( fPreviewLibhb, &s );
|
|
|
|
if(isEncoding && (s.state == HB_STATE_WORKING || s.state == HB_STATE_PAUSED))
|
|
{
|
|
hb_stop( fPreviewLibhb );
|
|
[fPictureView setHidden:NO];
|
|
[fMovieView pause:nil];
|
|
[fMovieView setHidden:YES];
|
|
[fMovieView setMovie:nil];
|
|
[fPictureSlider setHidden:NO];
|
|
isEncoding = NO;
|
|
|
|
[self pictureSliderChanged:nil];
|
|
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
- (IBAction) createMoviePreview: (id) sender
|
|
{
|
|
|
|
|
|
/* Lets make sure the still picture previews are showing in case
|
|
* there is currently a movie showing */
|
|
[self pictureSliderChanged:nil];
|
|
|
|
/* Rip or Cancel ? */
|
|
hb_state_t s;
|
|
hb_get_state2( fPreviewLibhb, &s );
|
|
|
|
if(sender == fCancelPreviewMovieButton && (s.state == HB_STATE_WORKING || s.state == HB_STATE_PAUSED))
|
|
{
|
|
hb_stop( fPreviewLibhb );
|
|
[fPictureView setHidden:NO];
|
|
[fMovieView pause:nil];
|
|
[fMovieView setHidden:YES];
|
|
[fMovieView setMovie:nil];
|
|
[fPictureSlider setHidden:NO];
|
|
isEncoding = NO;
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* we use controller.mm's prepareJobForPreview to go ahead and set all of our settings
|
|
* however, we want to use a temporary destination field of course
|
|
* so that we do not put our temp preview in the users chosen
|
|
* directory */
|
|
|
|
hb_job_t * job = fTitle->job;
|
|
|
|
/* We run our current setting through prepeareJob in Controller.mm
|
|
* just as if it were a regular encode */
|
|
|
|
[fHBController prepareJobForPreview];
|
|
|
|
/* Make sure we have a Preview sub directory with our pidnum attached */
|
|
NSString *PreviewDirectory = [NSString stringWithFormat:@"~/Library/Application Support/HandBrake/Previews/%d", [fHBController getPidnum]];
|
|
PreviewDirectory = [PreviewDirectory stringByExpandingTildeInPath];
|
|
if( ![[NSFileManager defaultManager] fileExistsAtPath:PreviewDirectory] )
|
|
{
|
|
[[NSFileManager defaultManager] createDirectoryAtPath:PreviewDirectory
|
|
withIntermediateDirectories:NO
|
|
attributes:nil
|
|
error:nil];
|
|
}
|
|
/* Destination file. We set this to our preview directory
|
|
* changing the extension appropriately.*/
|
|
if (fTitle->job->mux == HB_MUX_MP4) // MP4 file
|
|
{
|
|
/* we use .m4v for our mp4 files so that ac3 and chapters in mp4 will play properly */
|
|
fPreviewMoviePath = [PreviewDirectory stringByAppendingString:@"/preview_temp.m4v"];
|
|
}
|
|
else if (fTitle->job->mux == HB_MUX_MKV) // MKV file
|
|
{
|
|
fPreviewMoviePath = [PreviewDirectory stringByAppendingString:@"/preview_temp.mkv"];
|
|
}
|
|
|
|
fPreviewMoviePath = [[fPreviewMoviePath stringByExpandingTildeInPath]retain];
|
|
|
|
/* See if there is an existing preview file, if so, delete it */
|
|
if( ![[NSFileManager defaultManager] fileExistsAtPath:fPreviewMoviePath] )
|
|
{
|
|
[[NSFileManager defaultManager] removeItemAtPath:fPreviewMoviePath error:nil];
|
|
}
|
|
|
|
/* We now direct our preview encode to fPreviewMoviePath */
|
|
fTitle->job->file = [fPreviewMoviePath UTF8String];
|
|
|
|
/* We use our advance pref to determine how many previews to scan */
|
|
int hb_num_previews = [[[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewsNumber"] intValue];
|
|
job->start_at_preview = fPicture + 1;
|
|
job->seek_points = hb_num_previews;
|
|
|
|
/* we use the preview duration popup to get the specified
|
|
* number of seconds for the preview encode.
|
|
*/
|
|
|
|
job->pts_to_stop = [[fPreviewMovieLengthPopUp titleOfSelectedItem] intValue] * 90000LL;
|
|
|
|
/* lets go ahead and send it off to libhb
|
|
* Note: unlike a full encode, we only send 1 pass regardless if the final encode calls for 2 passes.
|
|
* this should suffice for a fairly accurate short preview and cuts our preview generation time in half.
|
|
* However we also need to take into account the indepth scan for subtitles.
|
|
*/
|
|
/*
|
|
* If scanning we need to do some extra setup of the job.
|
|
*/
|
|
if( job->indepth_scan == 1 )
|
|
{
|
|
char *x264opts_tmp;
|
|
|
|
/*
|
|
* When subtitle scan is enabled do a fast pre-scan job
|
|
* which will determine which subtitles to enable, if any.
|
|
*/
|
|
job->pass = -1;
|
|
x264opts_tmp = job->advanced_opts;
|
|
|
|
job->advanced_opts = NULL;
|
|
job->indepth_scan = 1;
|
|
/*
|
|
* Add the pre-scan job
|
|
*/
|
|
hb_add( fPreviewLibhb, job );
|
|
job->advanced_opts = x264opts_tmp;
|
|
}
|
|
/* Go ahead and perform the actual encoding preview scan */
|
|
job->indepth_scan = 0;
|
|
job->pass = 0;
|
|
hb_add( fPreviewLibhb, job );
|
|
|
|
[fEncodingControlBox setHidden: NO];
|
|
[fPictureControlBox setHidden: YES];
|
|
|
|
[fMovieCreationProgressIndicator setHidden: NO];
|
|
[fPreviewMovieStatusField setHidden: NO];
|
|
|
|
isEncoding = YES;
|
|
|
|
/* Let fPreviewLibhb do the job */
|
|
hb_start( fPreviewLibhb );
|
|
|
|
}
|
|
|
|
- (void) startReceivingLibhbNotifications
|
|
{
|
|
if (!fLibhbTimer)
|
|
{
|
|
fLibhbTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(libhbTimerFired:) userInfo:nil repeats:YES];
|
|
[fLibhbTimer retain];
|
|
}
|
|
}
|
|
|
|
- (void) stopReceivingLibhbNotifications
|
|
{
|
|
if (fLibhbTimer)
|
|
{
|
|
[fLibhbTimer invalidate];
|
|
[fLibhbTimer release];
|
|
fLibhbTimer = nil;
|
|
}
|
|
}
|
|
- (void) libhbTimerFired: (NSTimer*)theTimer
|
|
{
|
|
hb_state_t s;
|
|
hb_get_state( fPreviewLibhb, &s );
|
|
[self libhbStateChanged: s];
|
|
|
|
}
|
|
|
|
- (void) libhbStateChanged: (hb_state_t)state
|
|
{
|
|
switch( state.state )
|
|
{
|
|
case HB_STATE_IDLE:
|
|
case HB_STATE_SCANNING:
|
|
case HB_STATE_SCANDONE:
|
|
break;
|
|
|
|
case HB_STATE_WORKING:
|
|
{
|
|
#define p state.param.working
|
|
|
|
NSMutableString * string;
|
|
/* Update text field */
|
|
string = [NSMutableString stringWithFormat: NSLocalizedString( @"Encoding preview: %.2f %%", @"" ), 100.0 * p.progress];
|
|
|
|
if( p.seconds > -1 )
|
|
{
|
|
[string appendFormat:
|
|
NSLocalizedString( @" (%.2f fps, avg %.2f fps, ETA %02dh%02dm%02ds)", @"" ),
|
|
p.rate_cur, p.rate_avg, p.hours, p.minutes, p.seconds];
|
|
}
|
|
[fPreviewMovieStatusField setStringValue: string];
|
|
|
|
[fMovieCreationProgressIndicator setIndeterminate: NO];
|
|
/* Update slider */
|
|
[fMovieCreationProgressIndicator setDoubleValue: 100.0 * p.progress];
|
|
|
|
[fCreatePreviewMovieButton setTitle: @"Cancel Preview"];
|
|
|
|
break;
|
|
|
|
}
|
|
#undef p
|
|
|
|
#define p state.param.muxing
|
|
case HB_STATE_MUXING:
|
|
{
|
|
// Update fMovieCreationProgressIndicator
|
|
[fMovieCreationProgressIndicator setIndeterminate: YES];
|
|
[fMovieCreationProgressIndicator startAnimation: nil];
|
|
[fPreviewMovieStatusField setStringValue: [NSString stringWithFormat:
|
|
NSLocalizedString( @"Muxing Preview ...", @"" )]];
|
|
break;
|
|
}
|
|
#undef p
|
|
case HB_STATE_PAUSED:
|
|
[fMovieCreationProgressIndicator stopAnimation: nil];
|
|
break;
|
|
|
|
case HB_STATE_WORKDONE:
|
|
{
|
|
// Delete all remaining jobs since libhb doesn't do this on its own.
|
|
hb_job_t * job;
|
|
while( ( job = hb_job(fPreviewLibhb, 0) ) )
|
|
hb_rem( fHandle, job );
|
|
|
|
[fPreviewMovieStatusField setStringValue: @""];
|
|
[fPreviewMovieStatusField setHidden: YES];
|
|
|
|
[fMovieCreationProgressIndicator stopAnimation: nil];
|
|
[fMovieCreationProgressIndicator setHidden: YES];
|
|
[fEncodingControlBox setHidden: YES];
|
|
[fPictureControlBox setHidden: YES];
|
|
isEncoding = NO;
|
|
|
|
// Show the movie view
|
|
[self showMoviePreview:fPreviewMoviePath];
|
|
[fCreatePreviewMovieButton setTitle: @"Live Preview"];
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (IBAction) toggleMoviePreviewPlayPause: (id) sender
|
|
{
|
|
/* make sure a movie is even loaded up */
|
|
if (aMovie != nil)
|
|
{
|
|
/* For some stupid reason there is no "isPlaying" method for a QTMovie
|
|
* object, given that, we detect the rate to determine whether the movie
|
|
* is playing or not.
|
|
*/
|
|
if ([aMovie rate] != 0) // we are playing
|
|
{
|
|
[fMovieView pause:aMovie];
|
|
[fPlayPauseButton setTitle: @">"];
|
|
}
|
|
else // we are paused or stopped
|
|
{
|
|
[fMovieView play:aMovie];
|
|
[fPlayPauseButton setTitle: @"||"];
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
- (IBAction) moviePlaybackGoToBeginning: (id) sender
|
|
{
|
|
/* make sure a movie is even loaded up */
|
|
if (aMovie != nil)
|
|
{
|
|
[fMovieView gotoBeginning:aMovie];
|
|
}
|
|
|
|
}
|
|
|
|
- (IBAction) moviePlaybackGoToEnd: (id) sender
|
|
{
|
|
/* make sure a movie is even loaded up */
|
|
if (aMovie != nil)
|
|
{
|
|
[fMovieView gotoEnd:aMovie];
|
|
}
|
|
|
|
}
|
|
|
|
- (IBAction) moviePlaybackGoBackwardOneFrame: (id) sender
|
|
{
|
|
/* make sure a movie is even loaded up */
|
|
if (aMovie != nil)
|
|
{
|
|
[fMovieView pause:aMovie]; // Pause the movie
|
|
[fMovieView stepBackward:aMovie];
|
|
}
|
|
|
|
}
|
|
|
|
- (IBAction) moviePlaybackGoForwardOneFrame: (id) sender
|
|
{
|
|
/* make sure a movie is even loaded up */
|
|
if (aMovie != nil)
|
|
{
|
|
[fMovieView pause:aMovie]; // Pause the movie
|
|
[fMovieView stepForward:aMovie];
|
|
}
|
|
|
|
}
|
|
|
|
|
|
- (void) startMovieTimer
|
|
{
|
|
if( fMovieTimer ) {
|
|
[fMovieTimer invalidate];
|
|
[fMovieTimer release];
|
|
}
|
|
fMovieTimer = [NSTimer scheduledTimerWithTimeInterval:0.10 target:self selector:@selector(movieTimerFired:) userInfo:nil repeats:YES];
|
|
[fMovieTimer retain];
|
|
}
|
|
|
|
- (void) stopMovieTimer
|
|
{
|
|
if( fMovieTimer )
|
|
{
|
|
[fMovieTimer invalidate];
|
|
[fMovieTimer release];
|
|
fMovieTimer = nil;
|
|
}
|
|
}
|
|
|
|
- (void) movieTimerFired: (NSTimer*)theTimer
|
|
{
|
|
if (aMovie != nil)
|
|
{
|
|
[self adjustPreviewScrubberForCurrentMovieTime];
|
|
[fMovieInfoField setStringValue: [NSString stringWithFormat:NSLocalizedString( @"%@", @"" ),[self calculatePlaybackSMTPETimecodeForDisplay]]];
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- (IBAction) showMoviePreview: (NSString *) path
|
|
{
|
|
/* Since the gray background for the still images is part of
|
|
* fPictureView, lets leave the picture view visible and postion
|
|
* the fMovieView over the image portion of fPictureView so
|
|
* we retain the gray cropping border we have already established
|
|
* with the still previews
|
|
*/
|
|
|
|
/* Load the new movie into fMovieView */
|
|
if (path)
|
|
{
|
|
//QTMovie * aMovie;
|
|
NSError *outError;
|
|
NSURL *movieUrl = [NSURL fileURLWithPath:path];
|
|
NSDictionary *movieAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
|
|
movieUrl, QTMovieURLAttribute,
|
|
[NSNumber numberWithBool:NO], QTMovieAskUnresolvedDataRefsAttribute,
|
|
[NSNumber numberWithBool:YES], @"QTMovieOpenForPlaybackAttribute",
|
|
[NSNumber numberWithBool:NO], @"QTMovieOpenAsyncRequiredAttribute",
|
|
[NSNumber numberWithBool:NO], @"QTMovieOpenAsyncOKAttribute",
|
|
[NSNumber numberWithBool:YES], @"QTMovieIsSteppableAttribute",
|
|
QTMovieApertureModeClean, QTMovieApertureModeAttribute,
|
|
nil];
|
|
|
|
aMovie = [[[QTMovie alloc] initWithAttributes:movieAttributes error:&outError] autorelease];
|
|
|
|
|
|
if (!aMovie)
|
|
{
|
|
NSLog(@"Unable to open movie");
|
|
}
|
|
else
|
|
{
|
|
NSRect movieBounds;
|
|
/* we get some size information from the preview movie */
|
|
NSSize movieSize= [[aMovie attributeForKey:QTMovieNaturalSizeAttribute] sizeValue];
|
|
movieBounds = [fMovieView movieBounds];
|
|
movieBounds.size.height = movieSize.height;
|
|
/* We also get our view size to use for scaling fMovieView's size */
|
|
NSSize scaledMovieViewSize = [fPictureView frame].size;
|
|
[fMovieView setControllerVisible:FALSE];
|
|
if ([fMovieView isControllerVisible])
|
|
{
|
|
CGFloat controllerBarHeight = [fMovieView controllerBarHeight];
|
|
if ( controllerBarHeight != 0 ) //Check if QTKit return a real value or not.
|
|
{
|
|
movieBounds.size.height += controllerBarHeight;
|
|
scaledMovieViewSize.height += controllerBarHeight;
|
|
}
|
|
else
|
|
{
|
|
movieBounds.size.height += 15;
|
|
scaledMovieViewSize.height += 15;
|
|
}
|
|
}
|
|
|
|
movieBounds.size.width = movieSize.width;
|
|
|
|
/* we need to account for an issue where the scaledMovieViewSize > the window size */
|
|
if (scaledMovieViewSize.height > [[self window] frame].size.height)
|
|
{
|
|
[fHBController writeToActivityLog: "showMoviePreview: Our window is not tall enough to show the controller bar ..."];
|
|
}
|
|
|
|
|
|
|
|
/* Scale the fMovieView to scaledMovieViewSize */
|
|
[fMovieView setFrameSize:scaledMovieViewSize];
|
|
|
|
/*set our origin try using fPictureViewArea or fPictureView */
|
|
NSPoint origin = [fPictureView frame].origin;
|
|
origin.x += trunc( ( [fPictureView frame].size.width -
|
|
[fMovieView frame].size.width ) / 2.0 );
|
|
origin.y += trunc( ( ( [fPictureView frame].size.height -
|
|
[fMovieView frame].size.height ) / 2.0 ) - 7.5 );
|
|
|
|
[fMovieView setFrameOrigin:origin];
|
|
[fMovieView setMovie:aMovie];
|
|
[fMovieView setHidden:NO];
|
|
[fMoviePlaybackControlBox setHidden: NO];
|
|
[fPictureControlBox setHidden: YES];
|
|
|
|
// get and enable subtitles
|
|
NSArray *subtitlesArray;
|
|
subtitlesArray = [aMovie tracksOfMediaType: @"sbtl"];
|
|
if( subtitlesArray && [subtitlesArray count] )
|
|
{
|
|
// enable the first TX3G subtitle track
|
|
[[subtitlesArray objectAtIndex: 0] setEnabled: YES];
|
|
}
|
|
else
|
|
{
|
|
// Perian subtitles
|
|
subtitlesArray = [aMovie tracksOfMediaType: QTMediaTypeVideo];
|
|
if( subtitlesArray && ( [subtitlesArray count] >= 2 ) )
|
|
{
|
|
// track 0 should be video, other video tracks should
|
|
// be subtitles; force-enable the first subs track
|
|
[[subtitlesArray objectAtIndex: 1] setEnabled: YES];
|
|
}
|
|
}
|
|
|
|
// to actually play the movie
|
|
|
|
[self initPreviewScrubberForMovie];
|
|
[self startMovieTimer];
|
|
/* Install amovie notifications */
|
|
[aMovie setDelegate:self];
|
|
[self installMovieCallbacks];
|
|
[fMovieView play:aMovie];
|
|
|
|
}
|
|
}
|
|
isEncoding = NO;
|
|
}
|
|
#pragma mark *** Movie Playback Scrubber and time code methods ***
|
|
|
|
/* Since MacOSX Leopard QTKit has taken over some responsibility for assessing movie playback
|
|
* information from the old QuickTime carbon api ( time code information as well as fps, etc.).
|
|
* However, the QTKit devs at apple were not really big on documentation and further ...
|
|
* QuickTimes ability to playback HB's largely variable framerate output makes perfectly frame
|
|
* accurate information at best convoluted. Still, for the purpose of a custom hud based custom
|
|
* playback scrubber slider this has so far proven to be as accurate as I have found. To say it
|
|
* could use some better accuracy is not understating it enough probably.
|
|
* Most of this was gleaned from this obscure Apple Mail list thread:
|
|
* http://www.mailinglistarchive.com/quicktime-api@lists.apple.com/msg05642.html
|
|
* Now as we currently do not show a QTKit control bar with scrubber for display sizes > container
|
|
* size, this seems to facilitate playback control from the HB custom HUD controller fairly close
|
|
* to the built in controller bar.
|
|
* Further work needs to be done to try to get accurate frame by frame playback display if we want it.
|
|
* Note that the keyboard commands for frame by frame step through etc. work as always.
|
|
*/
|
|
|
|
// Returns a human readable string from the currentTime of movie playback
|
|
- (NSString*) calculatePlaybackSMTPETimecodeForDisplay
|
|
{
|
|
QTTime time = [aMovie currentTime];
|
|
|
|
NSString *smtpeTimeCodeString;
|
|
int days, hour, minute, second, frame;
|
|
long long result;
|
|
|
|
result = time.timeValue / time.timeScale; // second
|
|
frame = (time.timeValue % time.timeScale) / 100;
|
|
|
|
second = result % 60;
|
|
|
|
result = result / 60; // minute
|
|
minute = result % 60;
|
|
|
|
result = result / 60; // hour
|
|
hour = result % 24;
|
|
days = result;
|
|
|
|
smtpeTimeCodeString = [NSString stringWithFormat:@"Time: %02d:%02d:%02d", hour, minute, second]; // hh:mm:ss
|
|
return smtpeTimeCodeString;
|
|
|
|
}
|
|
|
|
|
|
// Initialize the preview scrubber min/max to appropriate values for the current movie
|
|
-(void) initPreviewScrubberForMovie
|
|
{
|
|
if (aMovie)
|
|
{
|
|
|
|
QTTime duration = [aMovie duration];
|
|
float result = duration.timeValue / duration.timeScale;
|
|
|
|
[fMovieScrubberSlider setMinValue:0.0];
|
|
[fMovieScrubberSlider setMaxValue: (float)result];
|
|
[fMovieScrubberSlider setFloatValue: 0.0];
|
|
}
|
|
}
|
|
|
|
|
|
-(void) adjustPreviewScrubberForCurrentMovieTime
|
|
{
|
|
if (aMovie)
|
|
{
|
|
QTTime time = [aMovie currentTime];
|
|
|
|
float result = (float)time.timeValue / (float)time.timeScale;;
|
|
[fMovieScrubberSlider setFloatValue:result];
|
|
}
|
|
}
|
|
|
|
- (IBAction) previewScrubberChanged: (id) sender
|
|
{
|
|
if (aMovie)
|
|
{
|
|
[fMovieView pause:aMovie]; // Pause the movie
|
|
QTTime time = [aMovie currentTime];
|
|
[self setTime: time.timeScale * [fMovieScrubberSlider floatValue]];
|
|
[self calculatePlaybackSMTPETimecodeForDisplay];
|
|
}
|
|
}
|
|
#pragma mark *** Movie Notifications ***
|
|
|
|
- (void) installMovieCallbacks
|
|
{
|
|
|
|
|
|
/*Notification for any time the movie rate changes */
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(movieRateDidChange:)
|
|
name:@"QTMovieRateDidChangeNotification"
|
|
object:aMovie];
|
|
/*Notification for when the movie ends */
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(movieDidEnd:)
|
|
name:@"QTMovieDidEndNotification"
|
|
object:aMovie];
|
|
}
|
|
|
|
- (void)removeMovieCallbacks
|
|
{
|
|
if (aMovie)
|
|
{
|
|
/*Notification for any time the movie rate changes */
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self
|
|
name:@"QTMovieRateDidChangeNotification"
|
|
object:aMovie];
|
|
/*Notification for when the movie ends */
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self
|
|
name:@"QTMovieDidEndNotification"
|
|
object:aMovie];
|
|
}
|
|
}
|
|
|
|
- (void)movieRateDidChange:(NSNotification *)notification
|
|
{
|
|
if (aMovie != nil)
|
|
{
|
|
/* For some stupid reason there is no "isPlaying" method for a QTMovie
|
|
* object, given that, we detect the rate to determine whether the movie
|
|
* is playing or not.
|
|
*/
|
|
//[self adjustPreviewScrubberForCurrentMovieTime];
|
|
if ([aMovie rate] != 0) // we are playing
|
|
{
|
|
[fPlayPauseButton setTitle: @"||"];
|
|
}
|
|
else // we are paused or stopped
|
|
{
|
|
[fPlayPauseButton setTitle: @">"];
|
|
}
|
|
}
|
|
}
|
|
/* This notification is not currently used. However we should keep it "just in case" as
|
|
* live preview playback is enhanced.
|
|
*/
|
|
- (void)movieDidEnd:(NSNotification *)notification
|
|
{
|
|
|
|
//[fHBController writeToActivityLog: "Movie DidEnd Notification Received"];
|
|
}
|
|
|
|
|
|
#pragma mark *** QTTime Utilities ***
|
|
|
|
// convert a time value (long) to a QTTime structure
|
|
-(void)timeToQTTime:(long)timeValue resultTime:(QTTime *)aQTTime
|
|
{
|
|
NSNumber *timeScaleObj;
|
|
long timeScaleValue;
|
|
|
|
timeScaleObj = [aMovie attributeForKey:QTMovieTimeScaleAttribute];
|
|
timeScaleValue = [timeScaleObj longValue];
|
|
|
|
*aQTTime = QTMakeTime(timeValue, timeScaleValue);
|
|
}
|
|
|
|
// set the movie's current time
|
|
-(void)setTime:(int)timeValue
|
|
{
|
|
QTTime movieQTTime;
|
|
NSValue *valueForQTTime;
|
|
|
|
[self timeToQTTime:timeValue resultTime:&movieQTTime];
|
|
|
|
valueForQTTime = [NSValue valueWithQTTime:movieQTTime];
|
|
|
|
[aMovie setAttribute:valueForQTTime forKey:QTMovieCurrentTimeAttribute];
|
|
}
|
|
|
|
|
|
@end
|
|
|
|
@implementation PreviewController (Private)
|
|
|
|
//
|
|
// -[PictureController(Private) optimalViewSizeForImageSize:]
|
|
//
|
|
// Given the size of the preview image to be shown, returns the best possible
|
|
// size for the view.
|
|
//
|
|
- (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize
|
|
{
|
|
// The min size is 480x360
|
|
CGFloat minWidth = 480.0;
|
|
CGFloat minHeight = 360.0;
|
|
|
|
NSSize screenSize = [[[self window] screen] visibleFrame].size;
|
|
NSSize sheetSize = [[self window] frame].size;
|
|
NSSize viewAreaSize = [fPictureViewArea frame].size;
|
|
CGFloat paddingX = 0.00;
|
|
CGFloat paddingY = 0.00;
|
|
|
|
if (fTitle->width > screenSize.width || fTitle->height > screenSize.height)
|
|
{
|
|
if (scaleToScreen == YES)
|
|
{
|
|
paddingX = screenSize.width - imageSize.width;
|
|
paddingY = screenSize.height - imageSize.height;
|
|
}
|
|
|
|
else
|
|
{
|
|
paddingX = sheetSize.width - viewAreaSize.width;
|
|
paddingY = sheetSize.height - viewAreaSize.height;
|
|
}
|
|
|
|
}
|
|
|
|
CGFloat maxWidth;
|
|
CGFloat maxHeight;
|
|
maxWidth = screenSize.width - paddingX;
|
|
maxHeight = screenSize.height - paddingY;
|
|
|
|
NSSize resultSize = imageSize;
|
|
CGFloat resultPar = resultSize.width / resultSize.height;
|
|
|
|
//note, a mbp 15" at 1440 x 900 is a 1.6 ar
|
|
CGFloat screenAspect = screenSize.width / screenSize.height;
|
|
// Note, a standard dvd will use 720 x 480 which is a 1.5
|
|
CGFloat viewAreaAspect = viewAreaSize.width / viewAreaSize.height;
|
|
|
|
if (scaleToScreen == YES)
|
|
{
|
|
|
|
if (screenAspect < viewAreaAspect)
|
|
{
|
|
resultSize.width = screenSize.width;
|
|
resultSize.height = (screenSize.width / viewAreaAspect);
|
|
}
|
|
else
|
|
{
|
|
resultSize.height = screenSize.height;
|
|
resultSize.width = resultSize.height * viewAreaAspect;
|
|
}
|
|
|
|
}
|
|
else if ( resultSize.width > maxWidth || resultSize.height > maxHeight )
|
|
{
|
|
// Source is larger than screen in one or more dimensions
|
|
if ( resultPar > screenAspect )
|
|
{
|
|
// Source aspect wider than screen aspect, snap to max width and vary height
|
|
resultSize.width = maxWidth;
|
|
resultSize.height = (maxWidth / resultPar);
|
|
}
|
|
else
|
|
{
|
|
// Source aspect narrower than screen aspect, snap to max height vary width
|
|
resultSize.height = maxHeight;
|
|
resultSize.width = (maxHeight * resultPar);
|
|
}
|
|
}
|
|
|
|
// If necessary, grow to minimum dimensions to ensure controls overlay is not obstructed
|
|
if ( resultSize.width < minWidth )
|
|
{
|
|
resultSize.width = minWidth;
|
|
}
|
|
if ( resultSize.height < minHeight )
|
|
{
|
|
resultSize.height = minHeight;
|
|
}
|
|
|
|
return resultSize;
|
|
|
|
|
|
}
|
|
|
|
//
|
|
// -[PictureController(Private) resizePanelForViewSize:animate:]
|
|
//
|
|
// Resizes the entire window to accomodate a view of a particular size.
|
|
//
|
|
- (void)resizeSheetForViewSize: (NSSize)viewSize
|
|
{
|
|
// Figure out the deltas for the new frame area
|
|
NSSize currentSize = [fPictureViewArea frame].size;
|
|
CGFloat deltaX = viewSize.width - currentSize.width;
|
|
CGFloat deltaY = viewSize.height - currentSize.height;
|
|
|
|
// Now resize the whole panel by those same deltas, but don't exceed the min
|
|
NSRect frame = [[self window] frame];
|
|
NSSize maxSize = [[[self window] screen] visibleFrame].size;
|
|
/* if we are not Scale To Screen, put an 85% of visible screen on the window */
|
|
if (scaleToScreen == NO )
|
|
{
|
|
maxSize.width = maxSize.width * 0.85;
|
|
maxSize.height = maxSize.height * 0.85;
|
|
}
|
|
|
|
/* Set our min size to the storage size */
|
|
NSSize minSize;
|
|
minSize.width = fTitle->width;
|
|
minSize.height = fTitle->height;
|
|
|
|
frame.size.width += deltaX;
|
|
frame.size.height += deltaY;
|
|
if( frame.size.width < minSize.width )
|
|
{
|
|
frame.size.width = minSize.width;
|
|
}
|
|
|
|
if( frame.size.height < minSize.height )
|
|
{
|
|
frame.size.height = minSize.height;
|
|
}
|
|
/* compare frame to max size of screen */
|
|
|
|
if( frame.size.width > maxSize.width )
|
|
{
|
|
frame.size.width = maxSize.width;
|
|
}
|
|
|
|
if( frame.size.height > maxSize.height )
|
|
{
|
|
frame.size.height = maxSize.height;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// But now the sheet is off-center, so also shift the origin to center it and
|
|
// keep the top aligned.
|
|
if( frame.size.width != [[self window] frame].size.width )
|
|
frame.origin.x -= (deltaX / 2.0);
|
|
|
|
|
|
/* Since upon launch we can open up the preview window if it was open
|
|
* the last time we quit (and at the size it was) we want to make
|
|
* sure that upon resize we do not have the window off the screen
|
|
* So check the origin against the screen origin and adjust if
|
|
* necessary.
|
|
*/
|
|
NSSize screenSize = [[[self window] screen] visibleFrame].size;
|
|
NSPoint screenOrigin = [[[self window] screen] frame].origin;
|
|
if (screenSize.height < frame.size.height)
|
|
{
|
|
frame.size.height = screenSize.height;
|
|
}
|
|
if (screenSize.width < frame.size.width)
|
|
{
|
|
frame.size.width = screenSize.width;
|
|
}
|
|
|
|
|
|
/* our origin is off the screen to the left*/
|
|
if (frame.origin.x < screenOrigin.x)
|
|
{
|
|
/* so shift our origin to the right */
|
|
frame.origin.x = screenOrigin.x;
|
|
}
|
|
else if ((frame.origin.x + frame.size.width) > (screenOrigin.x + screenSize.width))
|
|
{
|
|
/* the right side of the preview is off the screen, so shift to the left */
|
|
frame.origin.x = (screenOrigin.x + screenSize.width) - frame.size.width;
|
|
}
|
|
|
|
[[self window] setFrame:frame display:YES animate:YES];
|
|
|
|
|
|
}
|
|
|
|
//
|
|
// -[PictureController(Private) setViewSize:]
|
|
//
|
|
// Changes the view's size and centers it vertically inside of its area.
|
|
// Assumes resizeSheetForViewSize: has already been called.
|
|
//
|
|
- (void)setViewSize: (NSSize)viewSize
|
|
{
|
|
|
|
/* special case for scaleToScreen */
|
|
NSSize areaSize = [fPictureViewArea frame].size;
|
|
CGFloat viewSizeAspect = viewSize.width / viewSize.height;
|
|
|
|
if (viewSize.width > areaSize.width || viewSize.height > areaSize.height)
|
|
{
|
|
|
|
if (viewSizeAspect > 1.0) // we are wider than taller, so expand the width to fill the area and scale the height
|
|
{
|
|
viewSize.width = areaSize.width;
|
|
viewSize.height = viewSize.width / viewSizeAspect;
|
|
}
|
|
else
|
|
{
|
|
viewSize.height = areaSize.height;
|
|
viewSize.width = viewSize.height * viewSizeAspect;
|
|
}
|
|
|
|
}
|
|
|
|
[fPictureView setFrameSize:viewSize];
|
|
|
|
|
|
// center it vertically and horizontally
|
|
NSPoint origin = [fPictureViewArea frame].origin;
|
|
origin.y += ([fPictureViewArea frame].size.height -
|
|
[fPictureView frame].size.height) / 2.0;
|
|
|
|
origin.x += ([fPictureViewArea frame].size.width -
|
|
[fPictureView frame].size.width) / 2.0;
|
|
|
|
origin.x = floor( origin.x );
|
|
origin.y = floor( origin.y );
|
|
|
|
[fPictureView setFrameOrigin:origin];
|
|
|
|
}
|
|
|
|
|
|
- (BOOL)viewNeedsToResizeToSize: (NSSize)newSize
|
|
{
|
|
NSSize viewSize = [fPictureViewArea frame].size;
|
|
return (newSize.width != viewSize.width || newSize.height != viewSize.height);
|
|
}
|
|
|
|
@end
|