OSDN Git Service

MacGui: Picture window now uses a subclass of HBController to access the main controller.
[handbrake-jp/handbrake-jp-git.git] / macosx / PictureController.mm
1 /* $Id: PictureController.mm,v 1.11 2005/08/01 15:10:44 titer Exp $
2
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. */
6
7 #import "PictureController.h"
8 #import "Controller.h"
9
10 @interface PictureController (Private)
11
12 - (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize;
13 - (void)resizeSheetForViewSize: (NSSize)viewSize;
14 - (void)setViewSize: (NSSize)viewSize;
15 - (BOOL)viewNeedsToResizeToSize: (NSSize)newSize;
16
17 @end
18
19 @implementation PictureController
20
21 - (id)init
22 {
23         if (self = [super initWithWindowNibName:@"PictureSettings"])
24         {
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
29         // getter once.
30         //
31         // If/when we switch a lot of this stuff to bindings, this can probably
32         // go away.
33         [self window];
34         
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);
39         }
40         return self;
41 }
42
43 //------------------------------------------------------------------------------------
44 // Displays and brings the picture window to the front
45 //------------------------------------------------------------------------------------
46 - (IBAction) showPictureWindow: (id)sender
47 {
48     [self showWindow:sender];
49 }
50
51 - (void)setHBController: (HBController *)controller
52 {
53     fHBController = controller;
54 }
55
56 - (void)awakeFromNib
57 {
58     [fPictureWindow setDelegate:self];
59 }
60
61
62 - (void)windowWillClose:(NSNotification *)aNotification
63 {
64     /* Upon Closing the picture window, we make sure we clean up any
65      * preview movie that might be playing
66      */
67     play_movie = NO;
68     hb_stop( fPreviewLibhb );
69     [self pictureSliderChanged:nil];
70 }
71
72 - (BOOL)windowShouldClose:(id)fPictureWindow
73 {
74     return YES;
75 }
76
77 - (void) dealloc
78 {
79     hb_stop(fPreviewLibhb);
80     if (fPreviewMoviePath)
81     {
82         [[NSFileManager defaultManager] removeFileAtPath:fPreviewMoviePath handler:nil];
83         [fPreviewMoviePath release];
84     }    
85     
86     [fLibhbTimer invalidate];
87     [fLibhbTimer release];
88     
89     [fPicturePreviews release];
90     [super dealloc];
91 }
92
93 - (void) SetHandle: (hb_handle_t *) handle
94 {
95     fHandle = handle;
96     
97     [fWidthStepper  setValueWraps: NO];
98     [fWidthStepper  setIncrement: 16];
99     [fWidthStepper  setMinValue: 64];
100     [fHeightStepper setValueWraps: NO];
101     [fHeightStepper setIncrement: 16];
102     [fHeightStepper setMinValue: 64];
103     
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];
112     
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"];
127     
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];
133     
134     if ([[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewLength"])
135     {
136         [fPreviewMovieLengthPopUp selectItemWithTitle:[[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewLength"]];
137     }
138     else
139     {
140         /* currently hard set default to 10 seconds */
141         [fPreviewMovieLengthPopUp selectItemAtIndex: 1];
142     }
143 }
144
145 - (void) SetTitle: (hb_title_t *) title
146 {
147     hb_job_t * job = title->job;
148
149     fTitle = title;
150
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];
162
163     /* Populate the Anamorphic NSPopUp button here */
164     [fAnamorphicPopUp removeAllItems];
165     [fAnamorphicPopUp addItemWithTitle: @"None"];
166     [fAnamorphicPopUp addItemWithTitle: @"Strict"];
167     if (allowLooseAnamorphic)
168     {
169     [fAnamorphicPopUp addItemWithTitle: @"Loose"];
170     }
171     [fAnamorphicPopUp selectItemAtIndex: job->pixel_ratio];
172     
173     /* We initially set the previous state of keep ar to on */
174     keepAspectRatioPreviousState = 1;
175         if (!autoCrop)
176         {
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]];
187         }
188         else
189         {
190         [fCropMatrix  selectCellAtRow: 0 column:0];
191         }
192         
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];
199     
200     fPicture = 0;
201     MaxOutputWidth = title->width - job->crop[2] - job->crop[3];
202     MaxOutputHeight = title->height - job->crop[0] - job->crop[1];
203     [self SettingsChanged: nil];
204 }
205
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
209 {
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"];
216     
217         /* Set deinterlaces level according to the integer in the main window */
218         [fDeinterlacePopUp selectItemAtIndex: fPictureFilterSettings.deinterlace];
219
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];
228     
229
230 }
231
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
235 {
236
237     /* lets make sure that the still picture view is not hidden and that 
238      * the movie preview is 
239      */
240     [fMovieView pause:nil];
241     [fMovieView setHidden:YES];
242     [fMovieCreationProgressIndicator stopAnimation: nil];
243     [fMovieCreationProgressIndicator setHidden: YES];
244     
245     [fPictureView setHidden:NO];
246
247     [fPictureView setImage: [self imageForPicture: fPicture]];
248         
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
252     {
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 );   
260     }
261     else if (fTitle->job->pixel_ratio == 2) // Loose Anamorphic
262     {
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]];
267         
268         displaySize.width = display_width;
269     }
270     else // No Anamorphic
271     {
272         [fInfoField setStringValue: [NSString stringWithFormat:
273                                      @"Source: %dx%d, Output: %dx%d", fTitle->width, fTitle->height,
274                                      fTitle->job->width, fTitle->job->height]];
275     }
276
277     NSSize viewSize = [self optimalViewSizeForImageSize:displaySize];
278     if( [self viewNeedsToResizeToSize:viewSize] )
279     {
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)
282         {
283             [self resizeSheetForViewSize:viewSize];
284             [self setViewSize:viewSize];
285         }
286     }
287
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 )
291     {
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" ),
296                                  scale * 100.0];
297         [fInfoField setStringValue: [[fInfoField stringValue] stringByAppendingString:scaleString]];
298     }
299
300 }
301
302 - (IBAction) previewDurationPopUpChanged: (id) sender
303 {
304
305 [[NSUserDefaults standardUserDefaults] setObject:[fPreviewMovieLengthPopUp titleOfSelectedItem] forKey:@"PreviewLength"];
306
307 }    
308     
309     
310
311 - (IBAction) deblockSliderChanged: (id) sender
312 {
313     if ([fDeblockSlider floatValue] == 4.0)
314     {
315     [fDeblockField setStringValue: [NSString stringWithFormat: @"Off"]];
316     }
317     else
318     {
319     [fDeblockField setStringValue: [NSString stringWithFormat: @"%.0f", [fDeblockSlider floatValue]]];
320     }
321         [self SettingsChanged: sender];
322 }
323
324 - (IBAction) SettingsChanged: (id) sender
325 {
326     hb_job_t * job = fTitle->job;
327     
328     autoCrop = ( [fCropMatrix selectedRow] == 0 );
329     [fCropTopStepper    setEnabled: !autoCrop];
330     [fCropBottomStepper setEnabled: !autoCrop];
331     [fCropLeftStepper   setEnabled: !autoCrop];
332     [fCropRightStepper  setEnabled: !autoCrop];
333
334     if( autoCrop )
335     {
336         memcpy( job->crop, fTitle->crop, 4 * sizeof( int ) );
337     }
338     else
339     {
340         job->crop[0] = [fCropTopStepper    intValue];
341         job->crop[1] = [fCropBottomStepper intValue];
342         job->crop[2] = [fCropLeftStepper   intValue];
343         job->crop[3] = [fCropRightStepper  intValue];
344     }
345     
346         if( [fAnamorphicPopUp indexOfSelectedItem] > 0 )
347         {
348         if ([fAnamorphicPopUp indexOfSelectedItem] == 2) // Loose anamorphic
349         {
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
355              */
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.
358              */
359             if (sender == fAnamorphicPopUp)
360             {
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]];
363             }
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];
369             
370         }
371         else // must be "1" or strict anamorphic
372         {
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]];
375             
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];
382             
383             job->pixel_ratio = 1;
384             [fWidthStepper setEnabled: NO];
385             [fWidthField setEnabled: NO];
386         }
387         
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)
391         {
392             keepAspectRatioPreviousState = [fRatioCheck state];
393         }
394         [fRatioCheck setState:NSOffState];
395         [fRatioCheck setEnabled: NO];
396         
397         
398         [fHeightStepper setEnabled: NO];
399         [fHeightField setEnabled: NO];
400         
401     }
402     else
403         {
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)
415         {
416             [fRatioCheck setState:keepAspectRatioPreviousState];
417         }
418         
419         }
420         
421     job->keep_ratio  = ( [fRatioCheck state] == NSOnState );
422     
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)
428     {
429         job->deinterlace  = 1;
430     }
431     else
432     {
433         job->deinterlace  = 0;
434     }
435     fPictureFilterSettings.denoise     = [fDenoisePopUp indexOfSelectedItem];
436     
437     fPictureFilterSettings.detelecine  = [fDetelecineCheck state];
438     
439     if ([fDeblockField stringValue] == @"Off")
440     {
441     fPictureFilterSettings.deblock  = 0;
442     }
443     else
444     {
445     fPictureFilterSettings.deblock  = [fDeblockField intValue];
446     }
447     
448     fPictureFilterSettings.decomb = [fDecombCheck state];
449
450     if( job->keep_ratio )
451     {
452         if( sender == fWidthStepper || sender == fRatioCheck ||
453            sender == fCropTopStepper || sender == fCropBottomStepper )
454         {
455             hb_fix_aspect( job, HB_KEEP_WIDTH );
456             if( job->height > fTitle->height )
457             {
458                 job->height = fTitle->height;
459                 hb_fix_aspect( job, HB_KEEP_HEIGHT );
460             }
461         }
462         else
463         {
464             hb_fix_aspect( job, HB_KEEP_HEIGHT );
465             if( job->width > fTitle->width )
466             {
467                 job->width = fTitle->width;
468                 hb_fix_aspect( job, HB_KEEP_WIDTH );
469             }
470         }
471         // hb_get_preview can't handle sizes that are larger than the original title
472         // dimensions
473         if( job->width > fTitle->width )
474             job->width = fTitle->width;
475
476         if( job->height > fTitle->height )
477             job->height = fTitle->height;
478     }
479
480     [fWidthStepper      setIntValue: job->width];
481     [fWidthField        setIntValue: job->width];
482     if( [fAnamorphicPopUp indexOfSelectedItem] < 2 )
483         {
484         [fHeightStepper     setIntValue: job->height];
485         [fHeightField       setIntValue: job->height];
486     }
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)
500     {
501        
502          // Purge the existing picture previews so they get recreated the next time
503         // they are needed.
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
507          */
508         //[self displayPreview];
509         [self pictureSliderChanged:nil];
510
511     }
512
513     if (sender != nil)
514     {
515         [fHBController pictureSettingsDidChange];
516     }   
517     
518 }
519
520 - (IBAction) pictureSliderChanged: (id) sender
521 {
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];
528     
529     int newPicture = [fPictureSlider intValue];
530     if (newPicture != fPicture)
531     {
532         fPicture = newPicture;
533     }
534     [self displayPreview];
535     
536 }
537
538 #pragma mark Movie Preview
539 - (IBAction) createMoviePreview: (id) sender
540 {
541     
542     
543     /* Lets make sure the still picture previews are showing in case
544      * there is currently a movie showing */
545     [self pictureSliderChanged:nil];
546     
547     /* Rip or Cancel ? */
548     hb_state_t s;
549     hb_get_state2( fPreviewLibhb, &s );
550     
551     if(s.state == HB_STATE_WORKING || s.state == HB_STATE_PAUSED)
552         {
553         
554         play_movie = NO;
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"];
561         return;
562     }
563     
564     
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
568      * directory */
569     
570     hb_job_t * job = fTitle->job;
571     
572     /* We run our current setting through prepeareJob in Controller.mm
573      * just as if it were a regular encode */
574     
575     [fHBController prepareJobForPreview];
576     
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
580     {
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";
583     }
584     else if (fTitle->job->mux == HB_MUX_MKV) // MKV file
585     {
586         fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.mkv";
587     }
588     else if (fTitle->job->mux == HB_MUX_AVI) // AVI file
589     {
590         fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.avi";
591     }
592     else if (fTitle->job->mux == HB_MUX_OGM) // OGM file
593     {
594         fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.ogm";
595     }
596     
597     fPreviewMoviePath = [[fPreviewMoviePath stringByExpandingTildeInPath]retain];
598     
599     /* See if there is an existing preview file, if so, delete it */
600     if( ![[NSFileManager defaultManager] fileExistsAtPath:fPreviewMoviePath] )
601     {
602         [[NSFileManager defaultManager] removeFileAtPath:fPreviewMoviePath
603                                                  handler:nil];
604     }
605     
606     /* We now direct our preview encode to fPreviewMoviePath */
607     fTitle->job->file = [fPreviewMoviePath UTF8String];
608     
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;
613     
614     /* we use the preview duration popup to get the specified
615      * number of seconds for the preview encode.
616      */
617     
618     job->pts_to_stop = [[fPreviewMovieLengthPopUp titleOfSelectedItem] intValue] * 90000LL;
619     
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.
623      */
624     hb_add( fPreviewLibhb, job );
625     
626     [fPictureSlider setHidden:YES];
627     [fMovieCreationProgressIndicator setHidden: NO];
628     [fPreviewMovieStatusField setHidden: NO];
629     [self startReceivingLibhbNotifications];
630     
631     
632     [fCreatePreviewMovieButton setTitle: @"Cancel Preview"];
633     
634     play_movie = YES;
635     
636     /* Let fPreviewLibhb do the job */
637     hb_start( fPreviewLibhb );
638         
639 }
640
641 - (void) startReceivingLibhbNotifications
642 {
643     if (!fLibhbTimer)
644     {
645         fLibhbTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(libhbTimerFired:) userInfo:nil repeats:YES];
646         [fLibhbTimer retain];
647     }
648 }
649
650 - (void) stopReceivingLibhbNotifications
651 {
652     if (fLibhbTimer)
653     {
654         [fLibhbTimer invalidate];
655         [fLibhbTimer release];
656         fLibhbTimer = nil;
657     }
658 }
659 - (void) libhbTimerFired: (NSTimer*)theTimer
660 {
661     hb_state_t s;
662     hb_get_state( fPreviewLibhb, &s );
663     [self libhbStateChanged: s];
664 }
665 - (void) libhbStateChanged: (hb_state_t &)state
666 {
667     switch( state.state )
668     {
669         case HB_STATE_IDLE:
670         case HB_STATE_SCANNING:
671         case HB_STATE_SCANDONE:
672             break;
673             
674         case HB_STATE_WORKING:
675         {
676 #define p state.param.working
677             
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];
681             
682                         if( p.seconds > -1 )
683             {
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];
687             }
688             [fPreviewMovieStatusField setStringValue: string];
689             
690             [fMovieCreationProgressIndicator setIndeterminate: NO];
691             /* Update slider */
692                         [fMovieCreationProgressIndicator setDoubleValue: 100.0 * p.progress];
693             
694             [fCreatePreviewMovieButton setTitle: @"Cancel Preview"];
695             
696             break;
697             
698         }
699 #undef p
700             
701 #define p state.param.muxing            
702         case HB_STATE_MUXING:
703         {
704             // Update fMovieCreationProgressIndicator
705             [fMovieCreationProgressIndicator setIndeterminate: YES];
706             [fMovieCreationProgressIndicator startAnimation: nil];
707             [fPreviewMovieStatusField setStringValue: [NSString stringWithFormat:
708                                          NSLocalizedString( @"Muxing Preview ...", @"" )]];
709             break;
710         }
711 #undef p                        
712         case HB_STATE_PAUSED:
713             [fMovieCreationProgressIndicator stopAnimation: nil];
714             break;
715                         
716         case HB_STATE_WORKDONE:
717         {
718             // Delete all remaining jobs since libhb doesn't do this on its own.
719             hb_job_t * job;
720             while( ( job = hb_job(fPreviewLibhb, 0) ) )
721                 hb_rem( fHandle, job );
722             
723             [self stopReceivingLibhbNotifications];
724             [fPreviewMovieStatusField setStringValue: @""];
725             [fPreviewMovieStatusField setHidden: YES];
726             
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];
732             
733             // Show the movie view
734             if (play_movie)
735             {
736             [self showMoviePreview:fPreviewMoviePath];
737             }
738             
739             [fCreatePreviewMovieButton setTitle: @"Live Preview"];
740             
741             
742             break;
743         }
744     }
745         
746 }
747
748 - (IBAction) showMoviePreview: (NSString *) path
749 {
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
755      */
756     [fMovieView setHidden:NO];
757     
758     /* Load the new movie into fMovieView */
759     QTMovie * aMovie;
760     NSRect movieBounds;
761     if (path)
762     {
763         [fMovieView setControllerVisible: YES];
764         /* let's make sure there is no movie currently set */
765         [fMovieView setMovie:nil];
766         
767         aMovie = [QTMovie movieWithFile:path error:nil];
768         
769         /* we get some size information from the preview movie */
770         Rect movieBox;
771         GetMovieBox ([aMovie quickTimeMovie], &movieBox);
772         movieBounds = [fMovieView movieBounds];
773         movieBounds.size.height = movieBox.bottom - movieBox.top;
774         
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
779          */
780         movieBounds.size.height += 15;
781         
782         movieBounds.size.width = movieBox.right - movieBox.left;
783         
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 )
788         {
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
792              */
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] )
797             {
798                 
799                 [self resizeSheetForViewSize:viewSize];
800                 [self setViewSize:viewSize];
801                 
802             }
803             
804             [fMovieView setFrameSize:viewSize];
805         }
806         else
807         {
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];
811         }
812         
813         // lets reposition the movie if need be
814         
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)
820         {
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
824          */
825         origin.y += trunc((([fPictureViewArea frame].size.height -
826                             [fMovieView frame].size.height) / 2.0) - 7.5);
827         }
828         else
829         {
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
832          */
833         origin.y += trunc(([fPictureViewArea frame].size.height -
834                             [fMovieView frame].size.height) / 2.0);
835         }
836         [fMovieView setFrameOrigin:origin]; 
837         
838         [fMovieView setMovie:aMovie];
839         /// to actually play the movie
840         [fMovieView play:aMovie];
841     }
842     else
843     {
844         aMovie = nil;
845     }       
846     
847 }
848
849 #pragma mark -
850
851 - (BOOL) autoCrop
852 {
853     return autoCrop;
854 }
855 - (void) setAutoCrop: (BOOL) setting
856 {
857     autoCrop = setting;
858 }
859
860 - (BOOL) allowLooseAnamorphic
861 {
862     return allowLooseAnamorphic;
863 }
864
865 - (void) setAllowLooseAnamorphic: (BOOL) setting
866 {
867     allowLooseAnamorphic = setting;
868 }
869
870 - (int) detelecine
871 {
872     return fPictureFilterSettings.detelecine;
873 }
874
875 - (void) setDetelecine: (int) setting
876 {
877     fPictureFilterSettings.detelecine = setting;
878 }
879
880 - (int) deinterlace
881 {
882     return fPictureFilterSettings.deinterlace;
883 }
884
885 - (void) setDeinterlace: (int) setting {
886     fPictureFilterSettings.deinterlace = setting;
887 }
888 - (int) decomb
889 {
890     return fPictureFilterSettings.decomb;
891 }
892
893 - (void) setDecomb: (int) setting {
894     fPictureFilterSettings.decomb = setting;
895 }
896 - (int) denoise
897 {
898     return fPictureFilterSettings.denoise;
899 }
900
901 - (void) setDenoise: (int) setting
902 {
903     fPictureFilterSettings.denoise = setting;
904 }
905
906 - (int) deblock
907 {
908     return fPictureFilterSettings.deblock;
909 }
910
911 - (void) setDeblock: (int) setting
912 {
913     fPictureFilterSettings.deblock = setting;
914 }
915
916 - (IBAction)showPreviewPanel: (id)sender forTitle: (hb_title_t *)title
917 {
918     [self SetTitle:title];
919     [self showWindow:sender];
920
921 }
922
923
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
933 {
934     if (removeBorders)
935     {
936         //     |<---------- title->width ----------->|
937         //     |   |<---- title->job->width ---->|   |
938         //     |   |                             |   |
939         //     .......................................
940         //     ....+-----------------------------+....
941         //     ....|                             |....<-- gray border
942         //     ....|                             |....
943         //     ....|                             |....
944         //     ....|                             |<------- image
945         //     ....|                             |....
946         //     ....|                             |....
947         //     ....|                             |....
948         //     ....|                             |....
949         //     ....|                             |....
950         //     ....+-----------------------------+....
951         //     .......................................
952
953         static uint8_t * buffer;
954         static int bufferSize;
955
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;
961         int newSize;
962         newSize = srcWidth * srcHeight * 4;
963         if( bufferSize < newSize )
964         {
965             bufferSize = newSize;
966             buffer     = (uint8_t *) realloc( buffer, bufferSize );
967         }
968
969         hb_get_preview( handle, title, pictureIndex, buffer );
970
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.
974         
975         // The image data returned by hb_get_preview is 4 bytes per pixel, BGRA format.
976         // Alpha is ignored.
977         
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
983                 pixelsWide:dstWidth
984                 pixelsHigh:dstHeight
985                 bitsPerSample:8
986                 samplesPerPixel:3   // ignore alpha
987                 hasAlpha:NO
988                 isPlanar:NO
989                 colorSpaceName:NSCalibratedRGBColorSpace
990                 bitmapFormat:bitmapFormat
991                 bytesPerRow:dstWidth * 4
992                 bitsPerPixel:32] autorelease];
993
994         int borderTop = (srcHeight - dstHeight) / 2;
995         int borderLeft = (srcWidth - dstWidth) / 2;
996         
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++)
1002         {
1003             for (int c = 0; c < dstWidth; c++)
1004 #if TARGET_RT_LITTLE_ENDIAN
1005                 *dst++ = Endian32_Swap(*src++);
1006 #else
1007                 *dst++ = *src++;
1008 #endif
1009             src += (srcWidth - dstWidth);   // skip to next row in src
1010         }
1011
1012         NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(dstWidth, dstHeight)] autorelease];
1013         [img addRepresentation:imgrep];
1014
1015         return img;
1016     }
1017     else
1018     {
1019         // Make sure we have big enough buffer
1020         static uint8_t * buffer;
1021         static int bufferSize;
1022
1023         int newSize;
1024         newSize = ( title->width + 2 ) * (title->height + 2 ) * 4;
1025         if( bufferSize < newSize )
1026         {
1027             bufferSize = newSize;
1028             buffer     = (uint8_t *) realloc( buffer, bufferSize );
1029         }
1030
1031         hb_get_preview( handle, title, pictureIndex, buffer );
1032
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
1035         // ignored.
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
1042                 pixelsWide:width
1043                 pixelsHigh:height
1044                 bitsPerSample:8
1045                 samplesPerPixel:3   // ignore alpha
1046                 hasAlpha:NO
1047                 isPlanar:NO
1048                 colorSpaceName:NSCalibratedRGBColorSpace
1049                 bitmapFormat:bitmapFormat
1050                 bytesPerRow:width * 4
1051                 bitsPerPixel:32] autorelease];
1052
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++);
1058 #else
1059             *dst++ = *src++;
1060 #endif
1061
1062         NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(width, height)] autorelease];
1063         [img addRepresentation:imgrep];
1064
1065         return img;
1066     }
1067 }
1068
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
1074 {
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];
1079     if (!theImage)
1080     {
1081         theImage = [PictureController makeImageForPicture:pictureIndex libhb:fHandle title:fTitle removeBorders: NO];
1082         [fPicturePreviews setObject:theImage forKey:key];
1083     }
1084     return theImage;
1085 }
1086
1087 // Purges all images from the cache. The next call to imageForPicture will cause a new
1088 // image to be generated.
1089 - (void) purgeImageCache
1090 {
1091     [fPicturePreviews removeAllObjects];
1092 }
1093
1094 @end
1095
1096 @implementation PictureController (Private)
1097
1098 //
1099 // -[PictureController(Private) optimalViewSizeForImageSize:]
1100 //
1101 // Given the size of the preview image to be shown, returns the best possible
1102 // size for the view.
1103 //
1104 - (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize
1105 {
1106     // The min size is 320x240
1107     CGFloat minWidth = 320.0;
1108     CGFloat minHeight = 240.0;
1109
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.
1118      */
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;
1124     
1125     NSSize resultSize = imageSize;
1126     
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 )
1130     {
1131         resultSize.height *= (minWidth / resultSize.width);
1132         resultSize.width = minWidth;
1133     }
1134     if( resultSize.height < minHeight )
1135     {
1136         resultSize.width *= (minHeight / resultSize.height);
1137         resultSize.height = minHeight;
1138     }
1139     if( resultSize.width > maxWidth )
1140     {
1141         resultSize.height *= (maxWidth / resultSize.width);
1142         resultSize.width = maxWidth;
1143     }
1144     if( resultSize.height > maxHeight )
1145     {
1146         resultSize.width *= (maxHeight / resultSize.height);
1147         resultSize.height = maxHeight;
1148     }
1149     
1150     return resultSize;
1151 }
1152
1153 //
1154 // -[PictureController(Private) resizePanelForViewSize:animate:]
1155 //
1156 // Resizes the entire sheet to accomodate a view of a particular size.
1157 //
1158 - (void)resizeSheetForViewSize: (NSSize)viewSize
1159 {
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;
1164
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 )
1172     {
1173         frame.size.width = minSize.width;
1174     }
1175     if( frame.size.height < minSize.height )
1176     {
1177         frame.size.height = minSize.height;
1178     }
1179
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);
1184
1185     if( frame.size.height != [[self window] frame].size.height )
1186         frame.origin.y -= deltaY;
1187
1188     [[self window] setFrame:frame display:YES animate:YES];
1189 }
1190
1191 //
1192 // -[PictureController(Private) setViewSize:]
1193 //
1194 // Changes the view's size and centers it vertically inside of its area.
1195 // Assumes resizeSheetForViewSize: has already been called.
1196 //
1197 - (void)setViewSize: (NSSize)viewSize
1198 {
1199     [fPictureView setFrameSize:viewSize];
1200     
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];
1206 }
1207
1208 //
1209 // -[PictureController(Private) viewNeedsToResizeToSize:]
1210 //
1211 // Returns YES if the view will need to resize to match the given size.
1212 //
1213 - (BOOL)viewNeedsToResizeToSize: (NSSize)newSize
1214 {
1215     NSSize viewSize = [fPictureView frame].size;
1216     return (newSize.width != viewSize.width || newSize.height != viewSize.height);
1217 }
1218
1219 @end