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"
9 @interface PictureController (Private)
11 - (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize;
12 - (void)resizeSheetForViewSize: (NSSize)viewSize;
13 - (void)setViewSize: (NSSize)viewSize;
14 - (BOOL)viewNeedsToResizeToSize: (NSSize)newSize;
18 @implementation PictureController
20 - (id)initWithDelegate:(id)del
22 if (self = [super initWithWindowNibName:@"PictureSettings"])
24 // NSWindowController likes to lazily load its window. However since
25 // this controller tries to set all sorts of outlets before the window
26 // is displayed, we need it to load immediately. The correct way to do
27 // this, according to the documentation, is simply to invoke the window
30 // 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);
45 hb_stop(fPreviewLibhb);
46 if (fPreviewMoviePath)
48 [[NSFileManager defaultManager] removeFileAtPath:fPreviewMoviePath handler:nil];
49 [fPreviewMoviePath release];
52 [fLibhbTimer invalidate];
53 [fLibhbTimer release];
55 [fPicturePreviews release];
59 - (void) SetHandle: (hb_handle_t *) handle
63 [fWidthStepper setValueWraps: NO];
64 [fWidthStepper setIncrement: 16];
65 [fWidthStepper setMinValue: 64];
66 [fHeightStepper setValueWraps: NO];
67 [fHeightStepper setIncrement: 16];
68 [fHeightStepper setMinValue: 64];
70 [fCropTopStepper setIncrement: 2];
71 [fCropTopStepper setMinValue: 0];
72 [fCropBottomStepper setIncrement: 2];
73 [fCropBottomStepper setMinValue: 0];
74 [fCropLeftStepper setIncrement: 2];
75 [fCropLeftStepper setMinValue: 0];
76 [fCropRightStepper setIncrement: 2];
77 [fCropRightStepper setMinValue: 0];
79 /* we set the preview length popup in seconds */
80 [fPreviewMovieLengthPopUp removeAllItems];
81 [fPreviewMovieLengthPopUp addItemWithTitle: @"5"];
82 [fPreviewMovieLengthPopUp addItemWithTitle: @"10"];
83 [fPreviewMovieLengthPopUp addItemWithTitle: @"15"];
84 [fPreviewMovieLengthPopUp addItemWithTitle: @"20"];
85 [fPreviewMovieLengthPopUp addItemWithTitle: @"25"];
86 [fPreviewMovieLengthPopUp addItemWithTitle: @"30"];
87 [fPreviewMovieLengthPopUp addItemWithTitle: @"35"];
88 [fPreviewMovieLengthPopUp addItemWithTitle: @"40"];
89 [fPreviewMovieLengthPopUp addItemWithTitle: @"45"];
90 [fPreviewMovieLengthPopUp addItemWithTitle: @"50"];
91 [fPreviewMovieLengthPopUp addItemWithTitle: @"55"];
92 [fPreviewMovieLengthPopUp addItemWithTitle: @"60"];
94 /* adjust the preview slider length */
95 /* We use our advance pref to determine how many previews we scanned */
96 int hb_num_previews = [[[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewsNumber"] intValue];
97 [fPictureSlider setMaxValue: hb_num_previews - 1.0];
98 [fPictureSlider setNumberOfTickMarks: hb_num_previews];
100 if ([[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewLength"])
102 [fPreviewMovieLengthPopUp selectItemWithTitle:[[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewLength"]];
106 /* currently hard set default to 10 seconds */
107 [fPreviewMovieLengthPopUp selectItemAtIndex: 1];
111 - (void) SetTitle: (hb_title_t *) title
113 hb_job_t * job = title->job;
117 [fWidthStepper setMaxValue: title->width];
118 [fWidthStepper setIntValue: job->width];
119 [fWidthField setIntValue: job->width];
120 [fHeightStepper setMaxValue: title->height];
121 [fHeightStepper setIntValue: job->height];
122 [fHeightField setIntValue: job->height];
123 [fRatioCheck setState: job->keep_ratio ? NSOnState : NSOffState];
124 [fCropTopStepper setMaxValue: title->height/2-2];
125 [fCropBottomStepper setMaxValue: title->height/2-2];
126 [fCropLeftStepper setMaxValue: title->width/2-2];
127 [fCropRightStepper setMaxValue: title->width/2-2];
129 /* Populate the Anamorphic NSPopUp button here */
130 [fAnamorphicPopUp removeAllItems];
131 [fAnamorphicPopUp addItemWithTitle: @"None"];
132 [fAnamorphicPopUp addItemWithTitle: @"Strict"];
133 if (allowLooseAnamorphic)
135 [fAnamorphicPopUp addItemWithTitle: @"Loose"];
137 [fAnamorphicPopUp selectItemAtIndex: job->pixel_ratio];
139 /* We initially set the previous state of keep ar to on */
140 keepAspectRatioPreviousState = 1;
143 [fCropMatrix selectCellAtRow: 1 column:0];
144 /* If auto, lets set the crop steppers according to current job->crop values */
145 [fCropTopStepper setIntValue: job->crop[0]];
146 [fCropTopField setIntValue: job->crop[0]];
147 [fCropBottomStepper setIntValue: job->crop[1]];
148 [fCropBottomField setIntValue: job->crop[1]];
149 [fCropLeftStepper setIntValue: job->crop[2]];
150 [fCropLeftField setIntValue: job->crop[2]];
151 [fCropRightStepper setIntValue: job->crop[3]];
152 [fCropRightField setIntValue: job->crop[3]];
156 [fCropMatrix selectCellAtRow: 0 column:0];
159 /* Set filters widgets according to the filters struct */
160 [fDetelecineCheck setState:fPictureFilterSettings.detelecine];
161 [fDeinterlacePopUp selectItemAtIndex: fPictureFilterSettings.deinterlace];
162 [fDenoisePopUp selectItemAtIndex: fPictureFilterSettings.denoise];
163 [fDeblockCheck setState: fPictureFilterSettings.deblock];
164 [fDecombCheck setState: fPictureFilterSettings.decomb];
167 MaxOutputWidth = title->width - job->crop[2] - job->crop[3];
168 MaxOutputHeight = title->height - job->crop[0] - job->crop[1];
169 [self SettingsChanged: nil];
172 /* we use this to setup the initial picture filters upon first launch, after that their states
173 are maintained across different sources */
174 - (void) setInitialPictureFilters
176 /* we use a popup to show the deinterlace settings */
177 [fDeinterlacePopUp removeAllItems];
178 [fDeinterlacePopUp addItemWithTitle: @"None"];
179 [fDeinterlacePopUp addItemWithTitle: @"Fast"];
180 [fDeinterlacePopUp addItemWithTitle: @"Slow"];
181 [fDeinterlacePopUp addItemWithTitle: @"Slower"];
183 /* Set deinterlaces level according to the integer in the main window */
184 [fDeinterlacePopUp selectItemAtIndex: fPictureFilterSettings.deinterlace];
186 /* we use a popup to show the denoise settings */
187 [fDenoisePopUp removeAllItems];
188 [fDenoisePopUp addItemWithTitle: @"None"];
189 [fDenoisePopUp addItemWithTitle: @"Weak"];
190 [fDenoisePopUp addItemWithTitle: @"Medium"];
191 [fDenoisePopUp addItemWithTitle: @"Strong"];
192 /* Set denoises level according to the integer in the main window */
193 [fDenoisePopUp selectItemAtIndex: fPictureFilterSettings.denoise];
198 // Adjusts the window to draw the current picture (fPicture) adjusting its size as
199 // necessary to display as much of the picture as possible.
200 - (void) displayPreview
203 /* lets make sure that the still picture view is not hidden and that
204 * the movie preview is
206 [fMovieView pause:nil];
207 [fMovieView setHidden:YES];
208 [fMovieCreationProgressIndicator stopAnimation: nil];
209 [fMovieCreationProgressIndicator setHidden: YES];
211 [fPictureView setHidden:NO];
213 [fPictureView setImage: [self imageForPicture: fPicture]];
215 NSSize displaySize = NSMakeSize( ( CGFloat )fTitle->width, ( CGFloat )fTitle->height );
216 /* Set the picture size display fields below the Preview Picture*/
217 if( fTitle->job->pixel_ratio == 1 ) // Original PAR Implementation
219 output_width = fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3];
220 output_height = fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1];
221 display_width = output_width * fTitle->job->pixel_aspect_width / fTitle->job->pixel_aspect_height;
222 [fInfoField setStringValue:[NSString stringWithFormat:
223 @"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d",
224 fTitle->width, fTitle->height, output_width, output_height, display_width, output_height]];
225 displaySize.width *= ( ( CGFloat )fTitle->job->pixel_aspect_width ) / ( ( CGFloat )fTitle->job->pixel_aspect_height );
227 else if (fTitle->job->pixel_ratio == 2) // Loose Anamorphic
229 display_width = output_width * output_par_width / output_par_height;
230 [fInfoField setStringValue:[NSString stringWithFormat:
231 @"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d",
232 fTitle->width, fTitle->height, output_width, output_height, display_width, output_height]];
234 displaySize.width = display_width;
236 else // No Anamorphic
238 [fInfoField setStringValue: [NSString stringWithFormat:
239 @"Source: %dx%d, Output: %dx%d", fTitle->width, fTitle->height,
240 fTitle->job->width, fTitle->job->height]];
243 NSSize viewSize = [self optimalViewSizeForImageSize:displaySize];
244 if( [self viewNeedsToResizeToSize:viewSize] )
246 /* In the case of loose anamorphic, do not resize the window when scaling down */
247 if (fTitle->job->pixel_ratio != 2 || [fWidthField intValue] == fTitle->width)
249 [self resizeSheetForViewSize:viewSize];
250 [self setViewSize:viewSize];
254 // Show the scaled text (use the height to check since the width can vary
255 // with anamorphic video).
256 if( ( ( int )viewSize.height ) != fTitle->height )
258 CGFloat scale = viewSize.width / ( ( CGFloat ) fTitle->width );
259 NSString *scaleString = [NSString stringWithFormat:
260 NSLocalizedString( @" (Preview scaled to %.0f%% actual size)",
261 @"String shown when a preview is scaled" ),
263 [fInfoField setStringValue: [[fInfoField stringValue] stringByAppendingString:scaleString]];
268 - (IBAction) previewDurationPopUpChanged: (id) sender
271 [[NSUserDefaults standardUserDefaults] setObject:[fPreviewMovieLengthPopUp titleOfSelectedItem] forKey:@"PreviewLength"];
277 - (IBAction) deblockSliderChanged: (id) sender
279 if ([fDeblockSlider floatValue] == 4.0)
281 [fDeblockField setStringValue: [NSString stringWithFormat: @"Off"]];
285 [fDeblockField setStringValue: [NSString stringWithFormat: @"%.0f", [fDeblockSlider floatValue]]];
287 [self SettingsChanged: sender];
290 - (IBAction) SettingsChanged: (id) sender
292 hb_job_t * job = fTitle->job;
294 autoCrop = ( [fCropMatrix selectedRow] == 0 );
295 [fCropTopStepper setEnabled: !autoCrop];
296 [fCropBottomStepper setEnabled: !autoCrop];
297 [fCropLeftStepper setEnabled: !autoCrop];
298 [fCropRightStepper setEnabled: !autoCrop];
302 memcpy( job->crop, fTitle->crop, 4 * sizeof( int ) );
306 job->crop[0] = [fCropTopStepper intValue];
307 job->crop[1] = [fCropBottomStepper intValue];
308 job->crop[2] = [fCropLeftStepper intValue];
309 job->crop[3] = [fCropRightStepper intValue];
312 if( [fAnamorphicPopUp indexOfSelectedItem] > 0 )
314 if ([fAnamorphicPopUp indexOfSelectedItem] == 2) // Loose anamorphic
316 job->pixel_ratio = 2;
317 [fWidthStepper setEnabled: YES];
318 [fWidthField setEnabled: YES];
319 /* We set job->width and call hb_set_anamorphic_size in libhb to do a "dry run" to get
320 * the values to be used by libhb for loose anamorphic
322 /* if the sender is the anamorphic popup, then we know that loose anamorphic has just
323 * been turned on, so snap the width to full width for the source.
325 if (sender == fAnamorphicPopUp)
327 [fWidthStepper setIntValue: fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3]];
328 [fWidthField setIntValue: fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3]];
330 job->width = [fWidthStepper intValue];
331 hb_set_anamorphic_size(job, &output_width, &output_height, &output_par_width, &output_par_height);
332 [fHeightStepper setIntValue: output_height];
333 [fHeightField setIntValue: output_height];
334 job->height = [fHeightStepper intValue];
337 else // must be "1" or strict anamorphic
339 [fWidthStepper setIntValue: fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3]];
340 [fWidthField setIntValue: fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3]];
342 /* This will show correct anamorphic height values, but
343 show distorted preview picture ratio */
344 [fHeightStepper setIntValue: fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1]];
345 [fHeightField setIntValue: fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1]];
346 job->width = [fWidthStepper intValue];
347 job->height = [fHeightStepper intValue];
349 job->pixel_ratio = 1;
350 [fWidthStepper setEnabled: NO];
351 [fWidthField setEnabled: NO];
354 /* if the sender is the Anamorphic checkbox, record the state
355 of KeepAspect Ratio so it can be reset if Anamorphic is unchecked again */
356 if (sender == fAnamorphicPopUp)
358 keepAspectRatioPreviousState = [fRatioCheck state];
360 [fRatioCheck setState:NSOffState];
361 [fRatioCheck setEnabled: NO];
364 [fHeightStepper setEnabled: NO];
365 [fHeightField setEnabled: NO];
370 job->width = [fWidthStepper intValue];
371 job->height = [fHeightStepper intValue];
372 job->pixel_ratio = 0;
373 [fWidthStepper setEnabled: YES];
374 [fWidthField setEnabled: YES];
375 [fHeightStepper setEnabled: YES];
376 [fHeightField setEnabled: YES];
377 [fRatioCheck setEnabled: YES];
378 /* if the sender is the Anamorphic checkbox, we return the
379 keep AR checkbox to its previous state */
380 if (sender == fAnamorphicPopUp)
382 [fRatioCheck setState:keepAspectRatioPreviousState];
387 job->keep_ratio = ( [fRatioCheck state] == NSOnState );
389 fPictureFilterSettings.deinterlace = [fDeinterlacePopUp indexOfSelectedItem];
390 /* if the gui deinterlace settings are fast through slowest, the job->deinterlace
391 value needs to be set to one, for the job as well as the previews showing deinterlacing
392 otherwise set job->deinterlace to 0 or "off" */
393 if (fPictureFilterSettings.deinterlace > 0)
395 job->deinterlace = 1;
399 job->deinterlace = 0;
401 fPictureFilterSettings.denoise = [fDenoisePopUp indexOfSelectedItem];
403 fPictureFilterSettings.detelecine = [fDetelecineCheck state];
405 if ([fDeblockField stringValue] == @"Off")
407 fPictureFilterSettings.deblock = 0;
411 fPictureFilterSettings.deblock = [fDeblockField intValue];
414 fPictureFilterSettings.decomb = [fDecombCheck state];
416 if( job->keep_ratio )
418 if( sender == fWidthStepper || sender == fRatioCheck ||
419 sender == fCropTopStepper || sender == fCropBottomStepper )
421 hb_fix_aspect( job, HB_KEEP_WIDTH );
422 if( job->height > fTitle->height )
424 job->height = fTitle->height;
425 hb_fix_aspect( job, HB_KEEP_HEIGHT );
430 hb_fix_aspect( job, HB_KEEP_HEIGHT );
431 if( job->width > fTitle->width )
433 job->width = fTitle->width;
434 hb_fix_aspect( job, HB_KEEP_WIDTH );
437 // hb_get_preview can't handle sizes that are larger than the original title
439 if( job->width > fTitle->width )
440 job->width = fTitle->width;
442 if( job->height > fTitle->height )
443 job->height = fTitle->height;
446 [fWidthStepper setIntValue: job->width];
447 [fWidthField setIntValue: job->width];
448 if( [fAnamorphicPopUp indexOfSelectedItem] < 2 )
450 [fHeightStepper setIntValue: job->height];
451 [fHeightField setIntValue: job->height];
453 [fCropTopStepper setIntValue: job->crop[0]];
454 [fCropTopField setIntValue: job->crop[0]];
455 [fCropBottomStepper setIntValue: job->crop[1]];
456 [fCropBottomField setIntValue: job->crop[1]];
457 [fCropLeftStepper setIntValue: job->crop[2]];
458 [fCropLeftField setIntValue: job->crop[2]];
459 [fCropRightStepper setIntValue: job->crop[3]];
460 [fCropRightField setIntValue: job->crop[3]];
461 /* Sanity Check Here for < 16 px preview to avoid
462 crashing hb_get_preview. In fact, just for kicks
463 lets getting previews at a min limit of 32, since
464 no human can see any meaningful detail below that */
465 if (job->width >= 64 && job->height >= 64)
468 // Purge the existing picture previews so they get recreated the next time
470 [self purgeImageCache];
471 /* We actually call displayPreview now from pictureSliderChanged which keeps
472 * our picture preview slider in sync with the previews being shown
474 //[self displayPreview];
475 [self pictureSliderChanged:nil];
481 if ([delegate respondsToSelector:@selector(pictureSettingsDidChange)])
482 [delegate pictureSettingsDidChange];
487 - (IBAction) pictureSliderChanged: (id) sender
489 // Show the picture view
490 [fCreatePreviewMovieButton setTitle: @"Live Preview"];
491 [fPictureView setHidden:NO];
492 [fMovieView pause:nil];
493 [fMovieView setHidden:YES];
494 [fPreviewMovieStatusField setHidden: YES];
496 int newPicture = [fPictureSlider intValue];
497 if (newPicture != fPicture)
499 fPicture = newPicture;
501 [self displayPreview];
505 #pragma mark Movie Preview
506 - (IBAction) createMoviePreview: (id) sender
508 /* Lets make sure the still picture previews are showing in case
509 * there is currently a movie showing */
510 [self pictureSliderChanged:nil];
512 /* Rip or Cancel ? */
514 hb_get_state2( fPreviewLibhb, &s );
516 if(s.state == HB_STATE_WORKING || s.state == HB_STATE_PAUSED)
520 hb_stop( fPreviewLibhb );
521 [fPictureView setHidden:NO];
522 [fMovieView pause:nil];
523 [fMovieView setHidden:YES];
524 [fPictureSlider setHidden:NO];
525 [fCreatePreviewMovieButton setTitle: @"Live Preview"];
529 /* we use controller.mm's prepareJobForPreview to go ahead and set all of our settings
530 * however, we want to use a temporary destination field of course
531 * so that we do not put our temp preview in the users chosen
534 hb_job_t * job = fTitle->job;
535 /* We run our current setting through prepeareJob in Controller.mm
536 * just as if it were a regular encode */
537 if ([delegate respondsToSelector:@selector(prepareJobForPreview)])
539 [delegate prepareJobForPreview];
542 /* Destination file. We set this to our preview directory
543 * changing the extension appropriately.*/
544 if (fTitle->job->mux == HB_MUX_MP4) // MP4 file
546 /* we use .m4v for our mp4 files so that ac3 and chapters in mp4 will play properly */
547 fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.m4v";
549 else if (fTitle->job->mux == HB_MUX_MKV) // MKV file
551 fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.mkv";
553 else if (fTitle->job->mux == HB_MUX_AVI) // AVI file
555 fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.avi";
557 else if (fTitle->job->mux == HB_MUX_OGM) // OGM file
559 fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.ogm";
562 fPreviewMoviePath = [[fPreviewMoviePath stringByExpandingTildeInPath]retain];
564 /* See if there is an existing preview file, if so, delete it */
565 if( ![[NSFileManager defaultManager] fileExistsAtPath:fPreviewMoviePath] )
567 [[NSFileManager defaultManager] removeFileAtPath:fPreviewMoviePath
571 /* We now direct our preview encode to fPreviewMoviePath */
572 fTitle->job->file = [fPreviewMoviePath UTF8String];
574 /* We use our advance pref to determine how many previews to scan */
575 int hb_num_previews = [[[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewsNumber"] intValue];
576 job->start_at_preview = fPicture + 1;
577 job->seek_points = hb_num_previews;
579 /* we use the preview duration popup to get the specified
580 * number of seconds for the preview encode.
583 job->pts_to_stop = [[fPreviewMovieLengthPopUp titleOfSelectedItem] intValue] * 90000LL;
585 /* lets go ahead and send it off to libhb
586 * Note: unlike a full encode, we only send 1 pass regardless if the final encode calls for 2 passes.
587 * this should suffice for a fairly accurate short preview and cuts our preview generation time in half.
589 hb_add( fPreviewLibhb, job );
591 [fPictureSlider setHidden:YES];
592 [fMovieCreationProgressIndicator setHidden: NO];
593 [fPreviewMovieStatusField setHidden: NO];
594 [self startReceivingLibhbNotifications];
597 [fCreatePreviewMovieButton setTitle: @"Cancel Preview"];
601 /* Let fPreviewLibhb do the job */
602 hb_start( fPreviewLibhb );
606 - (void) startReceivingLibhbNotifications
610 fLibhbTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(libhbTimerFired:) userInfo:nil repeats:YES];
611 [fLibhbTimer retain];
615 - (void) stopReceivingLibhbNotifications
619 [fLibhbTimer invalidate];
620 [fLibhbTimer release];
624 - (void) libhbTimerFired: (NSTimer*)theTimer
627 hb_get_state( fPreviewLibhb, &s );
628 [self libhbStateChanged: s];
630 - (void) libhbStateChanged: (hb_state_t &)state
632 switch( state.state )
635 case HB_STATE_SCANNING:
636 case HB_STATE_SCANDONE:
639 case HB_STATE_WORKING:
641 #define p state.param.working
643 NSMutableString * string;
644 /* Update text field */
645 string = [NSMutableString stringWithFormat: NSLocalizedString( @"Encoding %d seconds of preview %d: %.2f %%", @"" ), [[fPreviewMovieLengthPopUp titleOfSelectedItem] intValue], fPicture + 1, 100.0 * p.progress];
649 [string appendFormat:
650 NSLocalizedString( @" (%.2f fps, avg %.2f fps, ETA %02dh%02dm%02ds)", @"" ),
651 p.rate_cur, p.rate_avg, p.hours, p.minutes, p.seconds];
653 [fPreviewMovieStatusField setStringValue: string];
655 [fMovieCreationProgressIndicator setIndeterminate: NO];
657 [fMovieCreationProgressIndicator setDoubleValue: 100.0 * p.progress];
659 [fCreatePreviewMovieButton setTitle: @"Cancel Preview"];
666 #define p state.param.muxing
667 case HB_STATE_MUXING:
669 // Update fMovieCreationProgressIndicator
670 [fMovieCreationProgressIndicator setIndeterminate: YES];
671 [fMovieCreationProgressIndicator startAnimation: nil];
672 [fPreviewMovieStatusField setStringValue: [NSString stringWithFormat:
673 NSLocalizedString( @"Muxing Preview ...", @"" )]];
677 case HB_STATE_PAUSED:
678 [fMovieCreationProgressIndicator stopAnimation: nil];
681 case HB_STATE_WORKDONE:
683 // Delete all remaining jobs since libhb doesn't do this on its own.
685 while( ( job = hb_job(fPreviewLibhb, 0) ) )
686 hb_rem( fHandle, job );
688 [self stopReceivingLibhbNotifications];
689 [fPreviewMovieStatusField setStringValue: @""];
690 [fPreviewMovieStatusField setHidden: YES];
692 [fMovieCreationProgressIndicator stopAnimation: nil];
693 [fMovieCreationProgressIndicator setHidden: YES];
694 /* we make sure the picture slider and preview match */
695 [self pictureSliderChanged:nil];
696 [fPictureSlider setHidden:NO];
698 // Show the movie view
701 [self showMoviePreview:fPreviewMoviePath];
704 [fCreatePreviewMovieButton setTitle: @"Live Preview"];
713 - (IBAction) showMoviePreview: (NSString *) path
715 /* Since the gray background for the still images is part of
716 * fPictureView, lets leave the picture view visible and postion
717 * the fMovieView over the image portion of fPictureView so
718 * we retain the gray cropping border we have already established
719 * with the still previews
721 [fMovieView setHidden:NO];
723 /* Load the new movie into fMovieView */
728 [fMovieView setControllerVisible: YES];
729 /* let's make sure there is no movie currently set */
730 [fMovieView setMovie:nil];
732 aMovie = [QTMovie movieWithFile:path error:nil];
734 /* we get some size information from the preview movie */
736 GetMovieBox ([aMovie quickTimeMovie], &movieBox);
737 movieBounds = [fMovieView movieBounds];
738 movieBounds.size.height = movieBox.bottom - movieBox.top;
740 if ([fMovieView isControllerVisible])
741 movieBounds.size.height += [fMovieView controllerBarHeight];
742 /* since for whatever the reason I cannot seem to get the [fMovieView controllerBarHeight]
743 * For now just use 15 for additional height as it seems to line up well
745 movieBounds.size.height += 15;
747 movieBounds.size.width = movieBox.right - movieBox.left;
749 /* We need to find out if the preview movie needs to be scaled down so
750 * that it doesn't overflow our available viewing container (just like for image
751 * in -displayPreview) for HD sources, etc. [fPictureViewArea frame].size.height*/
752 if( ((int)movieBounds.size.height) > [fPictureView frame].size.height )
754 /* The preview movie would be larger than the available viewing area
755 * in the preview movie, so we go ahead and scale it down to the same size
756 * as the still preview or we readjust our window to allow for the added height if need be
758 NSSize displaySize = NSMakeSize( (float)movieBounds.size.width, (float)movieBounds.size.height );
759 //NSSize displaySize = NSMakeSize( (float)fTitle->width, (float)fTitle->height );
760 NSSize viewSize = [self optimalViewSizeForImageSize:displaySize];
761 if( [self viewNeedsToResizeToSize:viewSize] )
764 [self resizeSheetForViewSize:viewSize];
765 [self setViewSize:viewSize];
769 [fMovieView setFrameSize:viewSize];
773 /* Since the preview movie is smaller than the available viewing area
774 * we can go ahead and use the preview movies native size */
775 [fMovieView setFrameSize:movieBounds.size];
778 // lets reposition the movie if need be
779 NSPoint origin = [fPictureViewArea frame].origin;
780 origin.x += trunc(([fPictureViewArea frame].size.width -
781 [fMovieView frame].size.width) / 2.0);
782 /* Since we are adding 15 to the height to allow for the controller bar
783 * we need to subtract half of that for the origin.y to get the controller bar
784 * below the movie to it lines up vertically with where our still preview was
786 origin.y += trunc((([fPictureViewArea frame].size.height -
787 [fMovieView frame].size.height) / 2.0) - 7.5);
788 [fMovieView setFrameOrigin:origin];
790 [fMovieView setMovie:aMovie];
791 /// to actually play the movie
792 [fMovieView play:aMovie];
803 - (IBAction) ClosePanel: (id) sender
805 if ([delegate respondsToSelector:@selector(pictureSettingsDidChange)])
806 [delegate pictureSettingsDidChange];
808 [NSApp endSheet:[self window]];
809 [[self window] orderOut:self];
816 - (void) setAutoCrop: (BOOL) setting
821 - (BOOL) allowLooseAnamorphic
823 return allowLooseAnamorphic;
826 - (void) setAllowLooseAnamorphic: (BOOL) setting
828 allowLooseAnamorphic = setting;
833 return fPictureFilterSettings.detelecine;
836 - (void) setDetelecine: (int) setting
838 fPictureFilterSettings.detelecine = setting;
843 return fPictureFilterSettings.deinterlace;
846 - (void) setDeinterlace: (int) setting {
847 fPictureFilterSettings.deinterlace = setting;
851 return fPictureFilterSettings.decomb;
854 - (void) setDecomb: (int) setting {
855 fPictureFilterSettings.decomb = setting;
859 return fPictureFilterSettings.denoise;
862 - (void) setDenoise: (int) setting
864 fPictureFilterSettings.denoise = setting;
869 return fPictureFilterSettings.deblock;
872 - (void) setDeblock: (int) setting
874 fPictureFilterSettings.deblock = setting;
877 - (IBAction)showPreviewPanel: (id)sender forTitle: (hb_title_t *)title
879 [self SetTitle:title];
880 [self showWindow:sender];
885 // This function converts an image created by libhb (specified via pictureIndex) into
886 // an NSImage suitable for the GUI code to use. If removeBorders is YES,
887 // makeImageForPicture crops the image generated by libhb stripping off the gray
888 // border around the content. This is the low-level method that generates the image.
889 // -imageForPicture calls this function whenever it can't find an image in its cache.
890 + (NSImage *) makeImageForPicture: (int)pictureIndex
891 libhb:(hb_handle_t*)handle
892 title:(hb_title_t*)title
893 removeBorders:(BOOL)removeBorders
897 // |<---------- title->width ----------->|
898 // | |<---- title->job->width ---->| |
900 // .......................................
901 // ....+-----------------------------+....
902 // ....| |....<-- gray border
905 // ....| |<------- image
911 // ....+-----------------------------+....
912 // .......................................
914 static uint8_t * buffer;
915 static int bufferSize;
917 // Make sure we have a big enough buffer to receive the image from libhb. libhb
918 // creates images with a one-pixel border around the original content. Hence we
919 // add 2 pixels horizontally and vertically to the buffer size.
920 int srcWidth = title->width + 2;
921 int srcHeight= title->height + 2;
923 newSize = srcWidth * srcHeight * 4;
924 if( bufferSize < newSize )
926 bufferSize = newSize;
927 buffer = (uint8_t *) realloc( buffer, bufferSize );
930 hb_get_preview( handle, title, pictureIndex, buffer );
932 // Create an NSBitmapImageRep and copy the libhb image into it, converting it from
933 // libhb's format to one suitable for NSImage. Along the way, we'll strip off the
934 // border around libhb's image.
936 // The image data returned by hb_get_preview is 4 bytes per pixel, BGRA format.
939 int dstWidth = title->job->width;
940 int dstHeight = title->job->height;
941 NSBitmapFormat bitmapFormat = (NSBitmapFormat)NSAlphaFirstBitmapFormat;
942 NSBitmapImageRep * imgrep = [[[NSBitmapImageRep alloc]
943 initWithBitmapDataPlanes:nil
947 samplesPerPixel:3 // ignore alpha
950 colorSpaceName:NSCalibratedRGBColorSpace
951 bitmapFormat:bitmapFormat
952 bytesPerRow:dstWidth * 4
953 bitsPerPixel:32] autorelease];
955 int borderTop = (srcHeight - dstHeight) / 2;
956 int borderLeft = (srcWidth - dstWidth) / 2;
958 UInt32 * src = (UInt32 *)buffer;
959 UInt32 * dst = (UInt32 *)[imgrep bitmapData];
960 src += borderTop * srcWidth; // skip top rows in src to get to first row of dst
961 src += borderLeft; // skip left pixels in src to get to first pixel of dst
962 for (int r = 0; r < dstHeight; r++)
964 for (int c = 0; c < dstWidth; c++)
965 #if TARGET_RT_LITTLE_ENDIAN
966 *dst++ = Endian32_Swap(*src++);
970 src += (srcWidth - dstWidth); // skip to next row in src
973 NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(dstWidth, dstHeight)] autorelease];
974 [img addRepresentation:imgrep];
980 // Make sure we have big enough buffer
981 static uint8_t * buffer;
982 static int bufferSize;
985 newSize = ( title->width + 2 ) * (title->height + 2 ) * 4;
986 if( bufferSize < newSize )
988 bufferSize = newSize;
989 buffer = (uint8_t *) realloc( buffer, bufferSize );
992 hb_get_preview( handle, title, pictureIndex, buffer );
994 // The image data returned by hb_get_preview is 4 bytes per pixel, BGRA format.
995 // We'll copy that into an NSImage swapping it to ARGB in the process. Alpha is
997 int width = title->width + 2; // hblib adds a one-pixel border to the image
998 int height = title->height + 2;
999 int numPixels = width * height;
1000 NSBitmapFormat bitmapFormat = (NSBitmapFormat)NSAlphaFirstBitmapFormat;
1001 NSBitmapImageRep * imgrep = [[[NSBitmapImageRep alloc]
1002 initWithBitmapDataPlanes:nil
1006 samplesPerPixel:3 // ignore alpha
1009 colorSpaceName:NSCalibratedRGBColorSpace
1010 bitmapFormat:bitmapFormat
1011 bytesPerRow:width * 4
1012 bitsPerPixel:32] autorelease];
1014 UInt32 * src = (UInt32 *)buffer;
1015 UInt32 * dst = (UInt32 *)[imgrep bitmapData];
1016 for (int i = 0; i < numPixels; i++)
1017 #if TARGET_RT_LITTLE_ENDIAN
1018 *dst++ = Endian32_Swap(*src++);
1023 NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(width, height)] autorelease];
1024 [img addRepresentation:imgrep];
1030 // Returns the preview image for the specified index, retrieving it from its internal
1031 // cache or by calling makeImageForPicture if it is not cached. Generally, you should
1032 // use imageForPicture so that images are cached. Calling makeImageForPicture will
1033 // always generate a new copy of the image.
1034 - (NSImage *) imageForPicture: (int) pictureIndex
1036 // The preview for the specified index may not currently exist, so this method
1037 // generates it if necessary.
1038 NSString * key = [NSString stringWithFormat:@"%d", pictureIndex];
1039 NSImage * theImage = [fPicturePreviews objectForKey:key];
1042 theImage = [PictureController makeImageForPicture:pictureIndex libhb:fHandle title:fTitle removeBorders: NO];
1043 [fPicturePreviews setObject:theImage forKey:key];
1048 // Purges all images from the cache. The next call to imageForPicture will cause a new
1049 // image to be generated.
1050 - (void) purgeImageCache
1052 [fPicturePreviews removeAllObjects];
1057 @implementation PictureController (Private)
1060 // -[PictureController(Private) optimalViewSizeForImageSize:]
1062 // Given the size of the preview image to be shown, returns the best possible
1063 // size for the view.
1065 - (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize
1067 // The min size is 320x240
1068 CGFloat minWidth = 320.0;
1069 CGFloat minHeight = 240.0;
1071 NSSize screenSize = [[NSScreen mainScreen] frame].size;
1072 NSSize sheetSize = [[self window] frame].size;
1073 NSSize viewAreaSize = [fPictureViewArea frame].size;
1074 CGFloat paddingX = sheetSize.width - viewAreaSize.width;
1075 CGFloat paddingY = sheetSize.height - viewAreaSize.height;
1076 /* Since we are now non-modal, lets go ahead and allow the mac size to
1077 * go up to the full screen height or width below. Am leaving the original
1078 * code here that blindjimmy setup for 85% in case we don't like it.
1080 // The max size of the view is when the sheet is taking up 85% of the screen.
1081 //CGFloat maxWidth = (0.85 * screenSize.width) - paddingX;
1082 //CGFloat maxHeight = (0.85 * screenSize.height) - paddingY;
1083 CGFloat maxWidth = screenSize.width - paddingX;
1084 CGFloat maxHeight = screenSize.height - paddingY;
1086 NSSize resultSize = imageSize;
1088 // Its better to have a view that's too small than a view that's too big, so
1089 // apply the maximum constraints last.
1090 if( resultSize.width < minWidth )
1092 resultSize.height *= (minWidth / resultSize.width);
1093 resultSize.width = minWidth;
1095 if( resultSize.height < minHeight )
1097 resultSize.width *= (minHeight / resultSize.height);
1098 resultSize.height = minHeight;
1100 if( resultSize.width > maxWidth )
1102 resultSize.height *= (maxWidth / resultSize.width);
1103 resultSize.width = maxWidth;
1105 if( resultSize.height > maxHeight )
1107 resultSize.width *= (maxHeight / resultSize.height);
1108 resultSize.height = maxHeight;
1115 // -[PictureController(Private) resizePanelForViewSize:animate:]
1117 // Resizes the entire sheet to accomodate a view of a particular size.
1119 - (void)resizeSheetForViewSize: (NSSize)viewSize
1121 // Figure out the deltas for the new frame area
1122 NSSize currentSize = [fPictureViewArea frame].size;
1123 CGFloat deltaX = viewSize.width - currentSize.width;
1124 CGFloat deltaY = viewSize.height - currentSize.height;
1126 // Now resize the whole panel by those same deltas, but don't exceed the min
1127 NSRect frame = [[self window] frame];
1128 NSSize maxSize = [[self window] maxSize];
1129 NSSize minSize = [[self window] minSize];
1130 frame.size.width += deltaX;
1131 frame.size.height += deltaY;
1132 if( frame.size.width < minSize.width )
1134 frame.size.width = minSize.width;
1136 if( frame.size.height < minSize.height )
1138 frame.size.height = minSize.height;
1141 // But now the sheet is off-center, so also shift the origin to center it and
1142 // keep the top aligned.
1143 if( frame.size.width != [[self window] frame].size.width )
1144 frame.origin.x -= (deltaX / 2.0);
1146 if( frame.size.height != [[self window] frame].size.height )
1147 frame.origin.y -= deltaY;
1149 [[self window] setFrame:frame display:YES animate:YES];
1153 // -[PictureController(Private) setViewSize:]
1155 // Changes the view's size and centers it vertically inside of its area.
1156 // Assumes resizeSheetForViewSize: has already been called.
1158 - (void)setViewSize: (NSSize)viewSize
1160 [fPictureView setFrameSize:viewSize];
1162 // center it vertically
1163 NSPoint origin = [fPictureViewArea frame].origin;
1164 origin.y += ([fPictureViewArea frame].size.height -
1165 [fPictureView frame].size.height) / 2.0;
1166 [fPictureView setFrameOrigin:origin];
1170 // -[PictureController(Private) viewNeedsToResizeToSize:]
1172 // Returns YES if the view will need to resize to match the given size.
1174 - (BOOL)viewNeedsToResizeToSize: (NSSize)newSize
1176 NSSize viewSize = [fPictureView frame].size;
1177 return (newSize.width != viewSize.width || newSize.height != viewSize.height);