1 /* $Id: PictureController.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 "PictureController.h"
10 @interface PictureController (Private)
12 - (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize;
13 - (void)resizeSheetForViewSize: (NSSize)viewSize;
14 - (void)setViewSize: (NSSize)viewSize;
15 - (BOOL)viewNeedsToResizeToSize: (NSSize)newSize;
19 @implementation PictureController
23 if (self = [super initWithWindowNibName:@"PictureSettings"])
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);
43 //------------------------------------------------------------------------------------
44 // Displays and brings the picture window to the front
45 //------------------------------------------------------------------------------------
46 - (IBAction) showPictureWindow: (id)sender
48 [self showWindow:sender];
51 - (void)setHBController: (HBController *)controller
53 fHBController = controller;
58 [fPictureWindow setDelegate:self];
62 - (void)windowWillClose:(NSNotification *)aNotification
64 /* Upon Closing the picture window, we make sure we clean up any
65 * preview movie that might be playing
68 hb_stop( fPreviewLibhb );
69 [self pictureSliderChanged:nil];
72 - (BOOL)windowShouldClose:(id)fPictureWindow
79 hb_stop(fPreviewLibhb);
80 if (fPreviewMoviePath)
82 [[NSFileManager defaultManager] removeFileAtPath:fPreviewMoviePath handler:nil];
83 [fPreviewMoviePath release];
86 [fLibhbTimer invalidate];
87 [fLibhbTimer release];
89 [fPicturePreviews release];
93 - (void) SetHandle: (hb_handle_t *) handle
97 [fWidthStepper setValueWraps: NO];
98 [fWidthStepper setIncrement: 16];
99 [fWidthStepper setMinValue: 64];
100 [fHeightStepper setValueWraps: NO];
101 [fHeightStepper setIncrement: 16];
102 [fHeightStepper setMinValue: 64];
104 [fCropTopStepper setIncrement: 2];
105 [fCropTopStepper setMinValue: 0];
106 [fCropBottomStepper setIncrement: 2];
107 [fCropBottomStepper setMinValue: 0];
108 [fCropLeftStepper setIncrement: 2];
109 [fCropLeftStepper setMinValue: 0];
110 [fCropRightStepper setIncrement: 2];
111 [fCropRightStepper setMinValue: 0];
113 /* we set the preview length popup in seconds */
114 [fPreviewMovieLengthPopUp removeAllItems];
115 [fPreviewMovieLengthPopUp addItemWithTitle: @"5"];
116 [fPreviewMovieLengthPopUp addItemWithTitle: @"10"];
117 [fPreviewMovieLengthPopUp addItemWithTitle: @"15"];
118 [fPreviewMovieLengthPopUp addItemWithTitle: @"20"];
119 [fPreviewMovieLengthPopUp addItemWithTitle: @"25"];
120 [fPreviewMovieLengthPopUp addItemWithTitle: @"30"];
121 [fPreviewMovieLengthPopUp addItemWithTitle: @"35"];
122 [fPreviewMovieLengthPopUp addItemWithTitle: @"40"];
123 [fPreviewMovieLengthPopUp addItemWithTitle: @"45"];
124 [fPreviewMovieLengthPopUp addItemWithTitle: @"50"];
125 [fPreviewMovieLengthPopUp addItemWithTitle: @"55"];
126 [fPreviewMovieLengthPopUp addItemWithTitle: @"60"];
128 /* adjust the preview slider length */
129 /* We use our advance pref to determine how many previews we scanned */
130 int hb_num_previews = [[[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewsNumber"] intValue];
131 [fPictureSlider setMaxValue: hb_num_previews - 1.0];
132 [fPictureSlider setNumberOfTickMarks: hb_num_previews];
134 if ([[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewLength"])
136 [fPreviewMovieLengthPopUp selectItemWithTitle:[[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewLength"]];
140 /* currently hard set default to 10 seconds */
141 [fPreviewMovieLengthPopUp selectItemAtIndex: 1];
145 - (void) SetTitle: (hb_title_t *) title
147 hb_job_t * job = title->job;
151 [fWidthStepper setMaxValue: title->width];
152 [fWidthStepper setIntValue: job->width];
153 [fWidthField setIntValue: job->width];
154 [fHeightStepper setMaxValue: title->height];
155 [fHeightStepper setIntValue: job->height];
156 [fHeightField setIntValue: job->height];
157 [fRatioCheck setState: job->keep_ratio ? NSOnState : NSOffState];
158 [fCropTopStepper setMaxValue: title->height/2-2];
159 [fCropBottomStepper setMaxValue: title->height/2-2];
160 [fCropLeftStepper setMaxValue: title->width/2-2];
161 [fCropRightStepper setMaxValue: title->width/2-2];
163 /* Populate the Anamorphic NSPopUp button here */
164 [fAnamorphicPopUp removeAllItems];
165 [fAnamorphicPopUp addItemWithTitle: @"None"];
166 [fAnamorphicPopUp addItemWithTitle: @"Strict"];
167 if (allowLooseAnamorphic)
169 [fAnamorphicPopUp addItemWithTitle: @"Loose"];
171 [fAnamorphicPopUp selectItemAtIndex: job->pixel_ratio];
173 /* We initially set the previous state of keep ar to on */
174 keepAspectRatioPreviousState = 1;
177 [fCropMatrix selectCellAtRow: 1 column:0];
178 /* If auto, lets set the crop steppers according to current job->crop values */
179 [fCropTopStepper setIntValue: job->crop[0]];
180 [fCropTopField setIntValue: job->crop[0]];
181 [fCropBottomStepper setIntValue: job->crop[1]];
182 [fCropBottomField setIntValue: job->crop[1]];
183 [fCropLeftStepper setIntValue: job->crop[2]];
184 [fCropLeftField setIntValue: job->crop[2]];
185 [fCropRightStepper setIntValue: job->crop[3]];
186 [fCropRightField setIntValue: job->crop[3]];
190 [fCropMatrix selectCellAtRow: 0 column:0];
193 /* Set filters widgets according to the filters struct */
194 [fDetelecineCheck setState:fPictureFilterSettings.detelecine];
195 [fDeinterlacePopUp selectItemAtIndex: fPictureFilterSettings.deinterlace];
196 [fDenoisePopUp selectItemAtIndex: fPictureFilterSettings.denoise];
197 [fDeblockCheck setState: fPictureFilterSettings.deblock];
198 [fDecombCheck setState: fPictureFilterSettings.decomb];
201 MaxOutputWidth = title->width - job->crop[2] - job->crop[3];
202 MaxOutputHeight = title->height - job->crop[0] - job->crop[1];
203 [self SettingsChanged: nil];
206 /* we use this to setup the initial picture filters upon first launch, after that their states
207 are maintained across different sources */
208 - (void) setInitialPictureFilters
210 /* we use a popup to show the deinterlace settings */
211 [fDeinterlacePopUp removeAllItems];
212 [fDeinterlacePopUp addItemWithTitle: @"None"];
213 [fDeinterlacePopUp addItemWithTitle: @"Fast"];
214 [fDeinterlacePopUp addItemWithTitle: @"Slow"];
215 [fDeinterlacePopUp addItemWithTitle: @"Slower"];
217 /* Set deinterlaces level according to the integer in the main window */
218 [fDeinterlacePopUp selectItemAtIndex: fPictureFilterSettings.deinterlace];
220 /* we use a popup to show the denoise settings */
221 [fDenoisePopUp removeAllItems];
222 [fDenoisePopUp addItemWithTitle: @"None"];
223 [fDenoisePopUp addItemWithTitle: @"Weak"];
224 [fDenoisePopUp addItemWithTitle: @"Medium"];
225 [fDenoisePopUp addItemWithTitle: @"Strong"];
226 /* Set denoises level according to the integer in the main window */
227 [fDenoisePopUp selectItemAtIndex: fPictureFilterSettings.denoise];
232 // Adjusts the window to draw the current picture (fPicture) adjusting its size as
233 // necessary to display as much of the picture as possible.
234 - (void) displayPreview
237 /* lets make sure that the still picture view is not hidden and that
238 * the movie preview is
240 [fMovieView pause:nil];
241 [fMovieView setHidden:YES];
242 [fMovieCreationProgressIndicator stopAnimation: nil];
243 [fMovieCreationProgressIndicator setHidden: YES];
245 [fPictureView setHidden:NO];
247 [fPictureView setImage: [self imageForPicture: fPicture]];
249 NSSize displaySize = NSMakeSize( ( CGFloat )fTitle->width, ( CGFloat )fTitle->height );
250 /* Set the picture size display fields below the Preview Picture*/
251 if( fTitle->job->pixel_ratio == 1 ) // Original PAR Implementation
253 output_width = fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3];
254 output_height = fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1];
255 display_width = output_width * fTitle->job->pixel_aspect_width / fTitle->job->pixel_aspect_height;
256 [fInfoField setStringValue:[NSString stringWithFormat:
257 @"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d",
258 fTitle->width, fTitle->height, output_width, output_height, display_width, output_height]];
259 displaySize.width *= ( ( CGFloat )fTitle->job->pixel_aspect_width ) / ( ( CGFloat )fTitle->job->pixel_aspect_height );
261 else if (fTitle->job->pixel_ratio == 2) // Loose Anamorphic
263 display_width = output_width * output_par_width / output_par_height;
264 [fInfoField setStringValue:[NSString stringWithFormat:
265 @"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d",
266 fTitle->width, fTitle->height, output_width, output_height, display_width, output_height]];
268 displaySize.width = display_width;
270 else // No Anamorphic
272 [fInfoField setStringValue: [NSString stringWithFormat:
273 @"Source: %dx%d, Output: %dx%d", fTitle->width, fTitle->height,
274 fTitle->job->width, fTitle->job->height]];
277 NSSize viewSize = [self optimalViewSizeForImageSize:displaySize];
278 if( [self viewNeedsToResizeToSize:viewSize] )
280 /* In the case of loose anamorphic, do not resize the window when scaling down */
281 if (fTitle->job->pixel_ratio != 2 || [fWidthField intValue] == fTitle->width)
283 [self resizeSheetForViewSize:viewSize];
284 [self setViewSize:viewSize];
288 // Show the scaled text (use the height to check since the width can vary
289 // with anamorphic video).
290 if( ( ( int )viewSize.height ) != fTitle->height )
292 CGFloat scale = viewSize.width / ( ( CGFloat ) fTitle->width );
293 NSString *scaleString = [NSString stringWithFormat:
294 NSLocalizedString( @" (Preview scaled to %.0f%% actual size)",
295 @"String shown when a preview is scaled" ),
297 [fInfoField setStringValue: [[fInfoField stringValue] stringByAppendingString:scaleString]];
302 - (IBAction) previewDurationPopUpChanged: (id) sender
305 [[NSUserDefaults standardUserDefaults] setObject:[fPreviewMovieLengthPopUp titleOfSelectedItem] forKey:@"PreviewLength"];
311 - (IBAction) deblockSliderChanged: (id) sender
313 if ([fDeblockSlider floatValue] == 4.0)
315 [fDeblockField setStringValue: [NSString stringWithFormat: @"Off"]];
319 [fDeblockField setStringValue: [NSString stringWithFormat: @"%.0f", [fDeblockSlider floatValue]]];
321 [self SettingsChanged: sender];
324 - (IBAction) SettingsChanged: (id) sender
326 hb_job_t * job = fTitle->job;
328 autoCrop = ( [fCropMatrix selectedRow] == 0 );
329 [fCropTopStepper setEnabled: !autoCrop];
330 [fCropBottomStepper setEnabled: !autoCrop];
331 [fCropLeftStepper setEnabled: !autoCrop];
332 [fCropRightStepper setEnabled: !autoCrop];
336 memcpy( job->crop, fTitle->crop, 4 * sizeof( int ) );
340 job->crop[0] = [fCropTopStepper intValue];
341 job->crop[1] = [fCropBottomStepper intValue];
342 job->crop[2] = [fCropLeftStepper intValue];
343 job->crop[3] = [fCropRightStepper intValue];
346 if( [fAnamorphicPopUp indexOfSelectedItem] > 0 )
348 if ([fAnamorphicPopUp indexOfSelectedItem] == 2) // Loose anamorphic
350 job->pixel_ratio = 2;
351 [fWidthStepper setEnabled: YES];
352 [fWidthField setEnabled: YES];
353 /* We set job->width and call hb_set_anamorphic_size in libhb to do a "dry run" to get
354 * the values to be used by libhb for loose anamorphic
356 /* if the sender is the anamorphic popup, then we know that loose anamorphic has just
357 * been turned on, so snap the width to full width for the source.
359 if (sender == fAnamorphicPopUp)
361 [fWidthStepper setIntValue: fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3]];
362 [fWidthField setIntValue: fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3]];
364 job->width = [fWidthStepper intValue];
365 hb_set_anamorphic_size(job, &output_width, &output_height, &output_par_width, &output_par_height);
366 [fHeightStepper setIntValue: output_height];
367 [fHeightField setIntValue: output_height];
368 job->height = [fHeightStepper intValue];
371 else // must be "1" or strict anamorphic
373 [fWidthStepper setIntValue: fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3]];
374 [fWidthField setIntValue: fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3]];
376 /* This will show correct anamorphic height values, but
377 show distorted preview picture ratio */
378 [fHeightStepper setIntValue: fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1]];
379 [fHeightField setIntValue: fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1]];
380 job->width = [fWidthStepper intValue];
381 job->height = [fHeightStepper intValue];
383 job->pixel_ratio = 1;
384 [fWidthStepper setEnabled: NO];
385 [fWidthField setEnabled: NO];
388 /* if the sender is the Anamorphic checkbox, record the state
389 of KeepAspect Ratio so it can be reset if Anamorphic is unchecked again */
390 if (sender == fAnamorphicPopUp)
392 keepAspectRatioPreviousState = [fRatioCheck state];
394 [fRatioCheck setState:NSOffState];
395 [fRatioCheck setEnabled: NO];
398 [fHeightStepper setEnabled: NO];
399 [fHeightField setEnabled: NO];
404 job->width = [fWidthStepper intValue];
405 job->height = [fHeightStepper intValue];
406 job->pixel_ratio = 0;
407 [fWidthStepper setEnabled: YES];
408 [fWidthField setEnabled: YES];
409 [fHeightStepper setEnabled: YES];
410 [fHeightField setEnabled: YES];
411 [fRatioCheck setEnabled: YES];
412 /* if the sender is the Anamorphic checkbox, we return the
413 keep AR checkbox to its previous state */
414 if (sender == fAnamorphicPopUp)
416 [fRatioCheck setState:keepAspectRatioPreviousState];
421 job->keep_ratio = ( [fRatioCheck state] == NSOnState );
423 fPictureFilterSettings.deinterlace = [fDeinterlacePopUp indexOfSelectedItem];
424 /* if the gui deinterlace settings are fast through slowest, the job->deinterlace
425 value needs to be set to one, for the job as well as the previews showing deinterlacing
426 otherwise set job->deinterlace to 0 or "off" */
427 if (fPictureFilterSettings.deinterlace > 0)
429 job->deinterlace = 1;
433 job->deinterlace = 0;
435 fPictureFilterSettings.denoise = [fDenoisePopUp indexOfSelectedItem];
437 fPictureFilterSettings.detelecine = [fDetelecineCheck state];
439 if ([fDeblockField stringValue] == @"Off")
441 fPictureFilterSettings.deblock = 0;
445 fPictureFilterSettings.deblock = [fDeblockField intValue];
448 fPictureFilterSettings.decomb = [fDecombCheck state];
450 if( job->keep_ratio )
452 if( sender == fWidthStepper || sender == fRatioCheck ||
453 sender == fCropTopStepper || sender == fCropBottomStepper )
455 hb_fix_aspect( job, HB_KEEP_WIDTH );
456 if( job->height > fTitle->height )
458 job->height = fTitle->height;
459 hb_fix_aspect( job, HB_KEEP_HEIGHT );
464 hb_fix_aspect( job, HB_KEEP_HEIGHT );
465 if( job->width > fTitle->width )
467 job->width = fTitle->width;
468 hb_fix_aspect( job, HB_KEEP_WIDTH );
471 // hb_get_preview can't handle sizes that are larger than the original title
473 if( job->width > fTitle->width )
474 job->width = fTitle->width;
476 if( job->height > fTitle->height )
477 job->height = fTitle->height;
480 [fWidthStepper setIntValue: job->width];
481 [fWidthField setIntValue: job->width];
482 if( [fAnamorphicPopUp indexOfSelectedItem] < 2 )
484 [fHeightStepper setIntValue: job->height];
485 [fHeightField setIntValue: job->height];
487 [fCropTopStepper setIntValue: job->crop[0]];
488 [fCropTopField setIntValue: job->crop[0]];
489 [fCropBottomStepper setIntValue: job->crop[1]];
490 [fCropBottomField setIntValue: job->crop[1]];
491 [fCropLeftStepper setIntValue: job->crop[2]];
492 [fCropLeftField setIntValue: job->crop[2]];
493 [fCropRightStepper setIntValue: job->crop[3]];
494 [fCropRightField setIntValue: job->crop[3]];
495 /* Sanity Check Here for < 16 px preview to avoid
496 crashing hb_get_preview. In fact, just for kicks
497 lets getting previews at a min limit of 32, since
498 no human can see any meaningful detail below that */
499 if (job->width >= 64 && job->height >= 64)
502 // Purge the existing picture previews so they get recreated the next time
504 [self purgeImageCache];
505 /* We actually call displayPreview now from pictureSliderChanged which keeps
506 * our picture preview slider in sync with the previews being shown
508 //[self displayPreview];
509 [self pictureSliderChanged:nil];
515 [fHBController pictureSettingsDidChange];
520 - (IBAction) pictureSliderChanged: (id) sender
522 // Show the picture view
523 [fCreatePreviewMovieButton setTitle: @"Live Preview"];
524 [fPictureView setHidden:NO];
525 [fMovieView pause:nil];
526 [fMovieView setHidden:YES];
527 [fPreviewMovieStatusField setHidden: YES];
529 int newPicture = [fPictureSlider intValue];
530 if (newPicture != fPicture)
532 fPicture = newPicture;
534 [self displayPreview];
538 #pragma mark Movie Preview
539 - (IBAction) createMoviePreview: (id) sender
543 /* Lets make sure the still picture previews are showing in case
544 * there is currently a movie showing */
545 [self pictureSliderChanged:nil];
547 /* Rip or Cancel ? */
549 hb_get_state2( fPreviewLibhb, &s );
551 if(s.state == HB_STATE_WORKING || s.state == HB_STATE_PAUSED)
555 hb_stop( fPreviewLibhb );
556 [fPictureView setHidden:NO];
557 [fMovieView pause:nil];
558 [fMovieView setHidden:YES];
559 [fPictureSlider setHidden:NO];
560 [fCreatePreviewMovieButton setTitle: @"Live Preview"];
565 /* we use controller.mm's prepareJobForPreview to go ahead and set all of our settings
566 * however, we want to use a temporary destination field of course
567 * so that we do not put our temp preview in the users chosen
570 hb_job_t * job = fTitle->job;
572 /* We run our current setting through prepeareJob in Controller.mm
573 * just as if it were a regular encode */
575 [fHBController prepareJobForPreview];
577 /* Destination file. We set this to our preview directory
578 * changing the extension appropriately.*/
579 if (fTitle->job->mux == HB_MUX_MP4) // MP4 file
581 /* we use .m4v for our mp4 files so that ac3 and chapters in mp4 will play properly */
582 fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.m4v";
584 else if (fTitle->job->mux == HB_MUX_MKV) // MKV file
586 fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.mkv";
588 else if (fTitle->job->mux == HB_MUX_AVI) // AVI file
590 fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.avi";
592 else if (fTitle->job->mux == HB_MUX_OGM) // OGM file
594 fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.ogm";
597 fPreviewMoviePath = [[fPreviewMoviePath stringByExpandingTildeInPath]retain];
599 /* See if there is an existing preview file, if so, delete it */
600 if( ![[NSFileManager defaultManager] fileExistsAtPath:fPreviewMoviePath] )
602 [[NSFileManager defaultManager] removeFileAtPath:fPreviewMoviePath
606 /* We now direct our preview encode to fPreviewMoviePath */
607 fTitle->job->file = [fPreviewMoviePath UTF8String];
609 /* We use our advance pref to determine how many previews to scan */
610 int hb_num_previews = [[[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewsNumber"] intValue];
611 job->start_at_preview = fPicture + 1;
612 job->seek_points = hb_num_previews;
614 /* we use the preview duration popup to get the specified
615 * number of seconds for the preview encode.
618 job->pts_to_stop = [[fPreviewMovieLengthPopUp titleOfSelectedItem] intValue] * 90000LL;
620 /* lets go ahead and send it off to libhb
621 * Note: unlike a full encode, we only send 1 pass regardless if the final encode calls for 2 passes.
622 * this should suffice for a fairly accurate short preview and cuts our preview generation time in half.
624 hb_add( fPreviewLibhb, job );
626 [fPictureSlider setHidden:YES];
627 [fMovieCreationProgressIndicator setHidden: NO];
628 [fPreviewMovieStatusField setHidden: NO];
629 [self startReceivingLibhbNotifications];
632 [fCreatePreviewMovieButton setTitle: @"Cancel Preview"];
636 /* Let fPreviewLibhb do the job */
637 hb_start( fPreviewLibhb );
641 - (void) startReceivingLibhbNotifications
645 fLibhbTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(libhbTimerFired:) userInfo:nil repeats:YES];
646 [fLibhbTimer retain];
650 - (void) stopReceivingLibhbNotifications
654 [fLibhbTimer invalidate];
655 [fLibhbTimer release];
659 - (void) libhbTimerFired: (NSTimer*)theTimer
662 hb_get_state( fPreviewLibhb, &s );
663 [self libhbStateChanged: s];
665 - (void) libhbStateChanged: (hb_state_t &)state
667 switch( state.state )
670 case HB_STATE_SCANNING:
671 case HB_STATE_SCANDONE:
674 case HB_STATE_WORKING:
676 #define p state.param.working
678 NSMutableString * string;
679 /* Update text field */
680 string = [NSMutableString stringWithFormat: NSLocalizedString( @"Encoding %d seconds of preview %d: %.2f %%", @"" ), [[fPreviewMovieLengthPopUp titleOfSelectedItem] intValue], fPicture + 1, 100.0 * p.progress];
684 [string appendFormat:
685 NSLocalizedString( @" (%.2f fps, avg %.2f fps, ETA %02dh%02dm%02ds)", @"" ),
686 p.rate_cur, p.rate_avg, p.hours, p.minutes, p.seconds];
688 [fPreviewMovieStatusField setStringValue: string];
690 [fMovieCreationProgressIndicator setIndeterminate: NO];
692 [fMovieCreationProgressIndicator setDoubleValue: 100.0 * p.progress];
694 [fCreatePreviewMovieButton setTitle: @"Cancel Preview"];
701 #define p state.param.muxing
702 case HB_STATE_MUXING:
704 // Update fMovieCreationProgressIndicator
705 [fMovieCreationProgressIndicator setIndeterminate: YES];
706 [fMovieCreationProgressIndicator startAnimation: nil];
707 [fPreviewMovieStatusField setStringValue: [NSString stringWithFormat:
708 NSLocalizedString( @"Muxing Preview ...", @"" )]];
712 case HB_STATE_PAUSED:
713 [fMovieCreationProgressIndicator stopAnimation: nil];
716 case HB_STATE_WORKDONE:
718 // Delete all remaining jobs since libhb doesn't do this on its own.
720 while( ( job = hb_job(fPreviewLibhb, 0) ) )
721 hb_rem( fHandle, job );
723 [self stopReceivingLibhbNotifications];
724 [fPreviewMovieStatusField setStringValue: @""];
725 [fPreviewMovieStatusField setHidden: YES];
727 [fMovieCreationProgressIndicator stopAnimation: nil];
728 [fMovieCreationProgressIndicator setHidden: YES];
729 /* we make sure the picture slider and preview match */
730 [self pictureSliderChanged:nil];
731 [fPictureSlider setHidden:NO];
733 // Show the movie view
736 [self showMoviePreview:fPreviewMoviePath];
739 [fCreatePreviewMovieButton setTitle: @"Live Preview"];
748 - (IBAction) showMoviePreview: (NSString *) path
750 /* Since the gray background for the still images is part of
751 * fPictureView, lets leave the picture view visible and postion
752 * the fMovieView over the image portion of fPictureView so
753 * we retain the gray cropping border we have already established
754 * with the still previews
756 [fMovieView setHidden:NO];
758 /* Load the new movie into fMovieView */
763 [fMovieView setControllerVisible: YES];
764 /* let's make sure there is no movie currently set */
765 [fMovieView setMovie:nil];
767 aMovie = [QTMovie movieWithFile:path error:nil];
769 /* we get some size information from the preview movie */
771 GetMovieBox ([aMovie quickTimeMovie], &movieBox);
772 movieBounds = [fMovieView movieBounds];
773 movieBounds.size.height = movieBox.bottom - movieBox.top;
775 if ([fMovieView isControllerVisible])
776 movieBounds.size.height += [fMovieView controllerBarHeight];
777 /* since for whatever the reason I cannot seem to get the [fMovieView controllerBarHeight]
778 * For now just use 15 for additional height as it seems to line up well
780 movieBounds.size.height += 15;
782 movieBounds.size.width = movieBox.right - movieBox.left;
784 /* We need to find out if the preview movie needs to be scaled down so
785 * that it doesn't overflow our available viewing container (just like for image
786 * in -displayPreview) for HD sources, etc. [fPictureViewArea frame].size.height*/
787 if( ((int)movieBounds.size.height) > [fPictureView frame].size.height )
789 /* The preview movie would be larger than the available viewing area
790 * in the preview movie, so we go ahead and scale it down to the same size
791 * as the still preview or we readjust our window to allow for the added height if need be
793 NSSize displaySize = NSMakeSize( (float)movieBounds.size.width, (float)movieBounds.size.height );
794 //NSSize displaySize = NSMakeSize( (float)fTitle->width, (float)fTitle->height );
795 NSSize viewSize = [self optimalViewSizeForImageSize:displaySize];
796 if( [self viewNeedsToResizeToSize:viewSize] )
799 [self resizeSheetForViewSize:viewSize];
800 [self setViewSize:viewSize];
804 [fMovieView setFrameSize:viewSize];
808 /* Since the preview movie is smaller than the available viewing area
809 * we can go ahead and use the preview movies native size */
810 [fMovieView setFrameSize:movieBounds.size];
813 // lets reposition the movie if need be
815 NSPoint origin = [fPictureViewArea frame].origin;
816 origin.x += trunc(([fPictureViewArea frame].size.width -
817 [fMovieView frame].size.width) / 2.0);
818 /* We need to detect whether or not we are currently less than the available height.*/
819 if (movieBounds.size.height < [fPictureView frame].size.height)
821 /* If we are, we are adding 15 to the height to allow for the controller bar so
822 * we need to subtract half of that for the origin.y to get the controller bar
823 * below the movie to it lines up vertically with where our still preview was
825 origin.y += trunc((([fPictureViewArea frame].size.height -
826 [fMovieView frame].size.height) / 2.0) - 7.5);
830 /* if we are >= to the height of the picture view area, the controller bar
831 * gets taken care of with picture resizing, so we do not want to offset the height
833 origin.y += trunc(([fPictureViewArea frame].size.height -
834 [fMovieView frame].size.height) / 2.0);
836 [fMovieView setFrameOrigin:origin];
838 [fMovieView setMovie:aMovie];
839 /// to actually play the movie
840 [fMovieView play:aMovie];
855 - (void) setAutoCrop: (BOOL) setting
860 - (BOOL) allowLooseAnamorphic
862 return allowLooseAnamorphic;
865 - (void) setAllowLooseAnamorphic: (BOOL) setting
867 allowLooseAnamorphic = setting;
872 return fPictureFilterSettings.detelecine;
875 - (void) setDetelecine: (int) setting
877 fPictureFilterSettings.detelecine = setting;
882 return fPictureFilterSettings.deinterlace;
885 - (void) setDeinterlace: (int) setting {
886 fPictureFilterSettings.deinterlace = setting;
890 return fPictureFilterSettings.decomb;
893 - (void) setDecomb: (int) setting {
894 fPictureFilterSettings.decomb = setting;
898 return fPictureFilterSettings.denoise;
901 - (void) setDenoise: (int) setting
903 fPictureFilterSettings.denoise = setting;
908 return fPictureFilterSettings.deblock;
911 - (void) setDeblock: (int) setting
913 fPictureFilterSettings.deblock = setting;
916 - (IBAction)showPreviewPanel: (id)sender forTitle: (hb_title_t *)title
918 [self SetTitle:title];
919 [self showWindow:sender];
924 // This function converts an image created by libhb (specified via pictureIndex) into
925 // an NSImage suitable for the GUI code to use. If removeBorders is YES,
926 // makeImageForPicture crops the image generated by libhb stripping off the gray
927 // border around the content. This is the low-level method that generates the image.
928 // -imageForPicture calls this function whenever it can't find an image in its cache.
929 + (NSImage *) makeImageForPicture: (int)pictureIndex
930 libhb:(hb_handle_t*)handle
931 title:(hb_title_t*)title
932 removeBorders:(BOOL)removeBorders
936 // |<---------- title->width ----------->|
937 // | |<---- title->job->width ---->| |
939 // .......................................
940 // ....+-----------------------------+....
941 // ....| |....<-- gray border
944 // ....| |<------- image
950 // ....+-----------------------------+....
951 // .......................................
953 static uint8_t * buffer;
954 static int bufferSize;
956 // Make sure we have a big enough buffer to receive the image from libhb. libhb
957 // creates images with a one-pixel border around the original content. Hence we
958 // add 2 pixels horizontally and vertically to the buffer size.
959 int srcWidth = title->width + 2;
960 int srcHeight= title->height + 2;
962 newSize = srcWidth * srcHeight * 4;
963 if( bufferSize < newSize )
965 bufferSize = newSize;
966 buffer = (uint8_t *) realloc( buffer, bufferSize );
969 hb_get_preview( handle, title, pictureIndex, buffer );
971 // Create an NSBitmapImageRep and copy the libhb image into it, converting it from
972 // libhb's format to one suitable for NSImage. Along the way, we'll strip off the
973 // border around libhb's image.
975 // The image data returned by hb_get_preview is 4 bytes per pixel, BGRA format.
978 int dstWidth = title->job->width;
979 int dstHeight = title->job->height;
980 NSBitmapFormat bitmapFormat = (NSBitmapFormat)NSAlphaFirstBitmapFormat;
981 NSBitmapImageRep * imgrep = [[[NSBitmapImageRep alloc]
982 initWithBitmapDataPlanes:nil
986 samplesPerPixel:3 // ignore alpha
989 colorSpaceName:NSCalibratedRGBColorSpace
990 bitmapFormat:bitmapFormat
991 bytesPerRow:dstWidth * 4
992 bitsPerPixel:32] autorelease];
994 int borderTop = (srcHeight - dstHeight) / 2;
995 int borderLeft = (srcWidth - dstWidth) / 2;
997 UInt32 * src = (UInt32 *)buffer;
998 UInt32 * dst = (UInt32 *)[imgrep bitmapData];
999 src += borderTop * srcWidth; // skip top rows in src to get to first row of dst
1000 src += borderLeft; // skip left pixels in src to get to first pixel of dst
1001 for (int r = 0; r < dstHeight; r++)
1003 for (int c = 0; c < dstWidth; c++)
1004 #if TARGET_RT_LITTLE_ENDIAN
1005 *dst++ = Endian32_Swap(*src++);
1009 src += (srcWidth - dstWidth); // skip to next row in src
1012 NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(dstWidth, dstHeight)] autorelease];
1013 [img addRepresentation:imgrep];
1019 // Make sure we have big enough buffer
1020 static uint8_t * buffer;
1021 static int bufferSize;
1024 newSize = ( title->width + 2 ) * (title->height + 2 ) * 4;
1025 if( bufferSize < newSize )
1027 bufferSize = newSize;
1028 buffer = (uint8_t *) realloc( buffer, bufferSize );
1031 hb_get_preview( handle, title, pictureIndex, buffer );
1033 // The image data returned by hb_get_preview is 4 bytes per pixel, BGRA format.
1034 // We'll copy that into an NSImage swapping it to ARGB in the process. Alpha is
1036 int width = title->width + 2; // hblib adds a one-pixel border to the image
1037 int height = title->height + 2;
1038 int numPixels = width * height;
1039 NSBitmapFormat bitmapFormat = (NSBitmapFormat)NSAlphaFirstBitmapFormat;
1040 NSBitmapImageRep * imgrep = [[[NSBitmapImageRep alloc]
1041 initWithBitmapDataPlanes:nil
1045 samplesPerPixel:3 // ignore alpha
1048 colorSpaceName:NSCalibratedRGBColorSpace
1049 bitmapFormat:bitmapFormat
1050 bytesPerRow:width * 4
1051 bitsPerPixel:32] autorelease];
1053 UInt32 * src = (UInt32 *)buffer;
1054 UInt32 * dst = (UInt32 *)[imgrep bitmapData];
1055 for (int i = 0; i < numPixels; i++)
1056 #if TARGET_RT_LITTLE_ENDIAN
1057 *dst++ = Endian32_Swap(*src++);
1062 NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(width, height)] autorelease];
1063 [img addRepresentation:imgrep];
1069 // Returns the preview image for the specified index, retrieving it from its internal
1070 // cache or by calling makeImageForPicture if it is not cached. Generally, you should
1071 // use imageForPicture so that images are cached. Calling makeImageForPicture will
1072 // always generate a new copy of the image.
1073 - (NSImage *) imageForPicture: (int) pictureIndex
1075 // The preview for the specified index may not currently exist, so this method
1076 // generates it if necessary.
1077 NSString * key = [NSString stringWithFormat:@"%d", pictureIndex];
1078 NSImage * theImage = [fPicturePreviews objectForKey:key];
1081 theImage = [PictureController makeImageForPicture:pictureIndex libhb:fHandle title:fTitle removeBorders: NO];
1082 [fPicturePreviews setObject:theImage forKey:key];
1087 // Purges all images from the cache. The next call to imageForPicture will cause a new
1088 // image to be generated.
1089 - (void) purgeImageCache
1091 [fPicturePreviews removeAllObjects];
1096 @implementation PictureController (Private)
1099 // -[PictureController(Private) optimalViewSizeForImageSize:]
1101 // Given the size of the preview image to be shown, returns the best possible
1102 // size for the view.
1104 - (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize
1106 // The min size is 320x240
1107 CGFloat minWidth = 320.0;
1108 CGFloat minHeight = 240.0;
1110 NSSize screenSize = [[NSScreen mainScreen] frame].size;
1111 NSSize sheetSize = [[self window] frame].size;
1112 NSSize viewAreaSize = [fPictureViewArea frame].size;
1113 CGFloat paddingX = sheetSize.width - viewAreaSize.width;
1114 CGFloat paddingY = sheetSize.height - viewAreaSize.height;
1115 /* Since we are now non-modal, lets go ahead and allow the mac size to
1116 * go up to the full screen height or width below. Am leaving the original
1117 * code here that blindjimmy setup for 85% in case we don't like it.
1119 // The max size of the view is when the sheet is taking up 85% of the screen.
1120 //CGFloat maxWidth = (0.85 * screenSize.width) - paddingX;
1121 //CGFloat maxHeight = (0.85 * screenSize.height) - paddingY;
1122 CGFloat maxWidth = screenSize.width - paddingX;
1123 CGFloat maxHeight = screenSize.height - paddingY;
1125 NSSize resultSize = imageSize;
1127 // Its better to have a view that's too small than a view that's too big, so
1128 // apply the maximum constraints last.
1129 if( resultSize.width < minWidth )
1131 resultSize.height *= (minWidth / resultSize.width);
1132 resultSize.width = minWidth;
1134 if( resultSize.height < minHeight )
1136 resultSize.width *= (minHeight / resultSize.height);
1137 resultSize.height = minHeight;
1139 if( resultSize.width > maxWidth )
1141 resultSize.height *= (maxWidth / resultSize.width);
1142 resultSize.width = maxWidth;
1144 if( resultSize.height > maxHeight )
1146 resultSize.width *= (maxHeight / resultSize.height);
1147 resultSize.height = maxHeight;
1154 // -[PictureController(Private) resizePanelForViewSize:animate:]
1156 // Resizes the entire sheet to accomodate a view of a particular size.
1158 - (void)resizeSheetForViewSize: (NSSize)viewSize
1160 // Figure out the deltas for the new frame area
1161 NSSize currentSize = [fPictureViewArea frame].size;
1162 CGFloat deltaX = viewSize.width - currentSize.width;
1163 CGFloat deltaY = viewSize.height - currentSize.height;
1165 // Now resize the whole panel by those same deltas, but don't exceed the min
1166 NSRect frame = [[self window] frame];
1167 NSSize maxSize = [[self window] maxSize];
1168 NSSize minSize = [[self window] minSize];
1169 frame.size.width += deltaX;
1170 frame.size.height += deltaY;
1171 if( frame.size.width < minSize.width )
1173 frame.size.width = minSize.width;
1175 if( frame.size.height < minSize.height )
1177 frame.size.height = minSize.height;
1180 // But now the sheet is off-center, so also shift the origin to center it and
1181 // keep the top aligned.
1182 if( frame.size.width != [[self window] frame].size.width )
1183 frame.origin.x -= (deltaX / 2.0);
1185 if( frame.size.height != [[self window] frame].size.height )
1186 frame.origin.y -= deltaY;
1188 [[self window] setFrame:frame display:YES animate:YES];
1192 // -[PictureController(Private) setViewSize:]
1194 // Changes the view's size and centers it vertically inside of its area.
1195 // Assumes resizeSheetForViewSize: has already been called.
1197 - (void)setViewSize: (NSSize)viewSize
1199 [fPictureView setFrameSize:viewSize];
1201 // center it vertically
1202 NSPoint origin = [fPictureViewArea frame].origin;
1203 origin.y += ([fPictureViewArea frame].size.height -
1204 [fPictureView frame].size.height) / 2.0;
1205 [fPictureView setFrameOrigin:origin];
1209 // -[PictureController(Private) viewNeedsToResizeToSize:]
1211 // Returns YES if the view will need to resize to match the given size.
1213 - (BOOL)viewNeedsToResizeToSize: (NSSize)newSize
1215 NSSize viewSize = [fPictureView frame].size;
1216 return (newSize.width != viewSize.width || newSize.height != viewSize.height);