OSDN Git Service

Update ffmpeg to svn r15974, fixed up minor incompatiblities in the ffmpeg LATM diff...
[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
9 @interface PictureController (Private)
10
11 - (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize;
12 - (void)resizeSheetForViewSize: (NSSize)viewSize;
13 - (void)setViewSize: (NSSize)viewSize;
14 - (BOOL)viewNeedsToResizeToSize: (NSSize)newSize;
15
16 @end
17
18 @implementation PictureController
19
20 - (id)initWithDelegate:(id)del
21 {
22         if (self = [super initWithWindowNibName:@"PictureSettings"])
23         {
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
28         // getter once.
29         //
30         // If/when we switch a lot of this stuff to bindings, this can probably
31         // go away.
32         [self window];
33         
34                 delegate = del;
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 - (void) dealloc
44 {
45     hb_stop(fPreviewLibhb);
46     if (fPreviewMoviePath)
47     {
48         [[NSFileManager defaultManager] removeFileAtPath:fPreviewMoviePath handler:nil];
49         [fPreviewMoviePath release];
50     }    
51     
52     [fLibhbTimer invalidate];
53     [fLibhbTimer release];
54     
55     [fPicturePreviews release];
56     [super dealloc];
57 }
58
59 - (void) SetHandle: (hb_handle_t *) handle
60 {
61     fHandle = handle;
62     
63     [fWidthStepper  setValueWraps: NO];
64     [fWidthStepper  setIncrement: 16];
65     [fWidthStepper  setMinValue: 64];
66     [fHeightStepper setValueWraps: NO];
67     [fHeightStepper setIncrement: 16];
68     [fHeightStepper setMinValue: 64];
69     
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];
78     
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"];
93     
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];
99     
100     if ([[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewLength"])
101     {
102         [fPreviewMovieLengthPopUp selectItemWithTitle:[[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewLength"]];
103     }
104     else
105     {
106         /* currently hard set default to 10 seconds */
107         [fPreviewMovieLengthPopUp selectItemAtIndex: 1];
108     }
109 }
110
111 - (void) SetTitle: (hb_title_t *) title
112 {
113     hb_job_t * job = title->job;
114
115     fTitle = title;
116
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];
128
129     /* Populate the Anamorphic NSPopUp button here */
130     [fAnamorphicPopUp removeAllItems];
131     [fAnamorphicPopUp addItemWithTitle: @"None"];
132     [fAnamorphicPopUp addItemWithTitle: @"Strict"];
133     if (allowLooseAnamorphic)
134     {
135     [fAnamorphicPopUp addItemWithTitle: @"Loose"];
136     }
137     [fAnamorphicPopUp selectItemAtIndex: job->pixel_ratio];
138     
139     /* We initially set the previous state of keep ar to on */
140     keepAspectRatioPreviousState = 1;
141         if (!autoCrop)
142         {
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]];
153         }
154         else
155         {
156         [fCropMatrix  selectCellAtRow: 0 column:0];
157         }
158         
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];
165     
166     fPicture = 0;
167     MaxOutputWidth = title->width - job->crop[2] - job->crop[3];
168     MaxOutputHeight = title->height - job->crop[0] - job->crop[1];
169     [self SettingsChanged: nil];
170 }
171
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
175 {
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"];
182     
183         /* Set deinterlaces level according to the integer in the main window */
184         [fDeinterlacePopUp selectItemAtIndex: fPictureFilterSettings.deinterlace];
185
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];
194     
195
196 }
197
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
201 {
202
203     /* lets make sure that the still picture view is not hidden and that 
204      * the movie preview is 
205      */
206     [fMovieView pause:nil];
207     [fMovieView setHidden:YES];
208     [fMovieCreationProgressIndicator stopAnimation: nil];
209     [fMovieCreationProgressIndicator setHidden: YES];
210     
211     [fPictureView setHidden:NO];
212
213     [fPictureView setImage: [self imageForPicture: fPicture]];
214         
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
218     {
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 );   
226     }
227     else if (fTitle->job->pixel_ratio == 2) // Loose Anamorphic
228     {
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]];
233         
234         displaySize.width = display_width;
235     }
236     else // No Anamorphic
237     {
238         [fInfoField setStringValue: [NSString stringWithFormat:
239                                      @"Source: %dx%d, Output: %dx%d", fTitle->width, fTitle->height,
240                                      fTitle->job->width, fTitle->job->height]];
241     }
242
243     NSSize viewSize = [self optimalViewSizeForImageSize:displaySize];
244     if( [self viewNeedsToResizeToSize:viewSize] )
245     {
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)
248         {
249             [self resizeSheetForViewSize:viewSize];
250             [self setViewSize:viewSize];
251         }
252     }
253
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 )
257     {
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" ),
262                                  scale * 100.0];
263         [fInfoField setStringValue: [[fInfoField stringValue] stringByAppendingString:scaleString]];
264     }
265
266 }
267
268 - (IBAction) previewDurationPopUpChanged: (id) sender
269 {
270
271 [[NSUserDefaults standardUserDefaults] setObject:[fPreviewMovieLengthPopUp titleOfSelectedItem] forKey:@"PreviewLength"];
272
273 }    
274     
275     
276
277 - (IBAction) deblockSliderChanged: (id) sender
278 {
279     if ([fDeblockSlider floatValue] == 4.0)
280     {
281     [fDeblockField setStringValue: [NSString stringWithFormat: @"Off"]];
282     }
283     else
284     {
285     [fDeblockField setStringValue: [NSString stringWithFormat: @"%.0f", [fDeblockSlider floatValue]]];
286     }
287         [self SettingsChanged: sender];
288 }
289
290 - (IBAction) SettingsChanged: (id) sender
291 {
292     hb_job_t * job = fTitle->job;
293     
294     autoCrop = ( [fCropMatrix selectedRow] == 0 );
295     [fCropTopStepper    setEnabled: !autoCrop];
296     [fCropBottomStepper setEnabled: !autoCrop];
297     [fCropLeftStepper   setEnabled: !autoCrop];
298     [fCropRightStepper  setEnabled: !autoCrop];
299
300     if( autoCrop )
301     {
302         memcpy( job->crop, fTitle->crop, 4 * sizeof( int ) );
303     }
304     else
305     {
306         job->crop[0] = [fCropTopStepper    intValue];
307         job->crop[1] = [fCropBottomStepper intValue];
308         job->crop[2] = [fCropLeftStepper   intValue];
309         job->crop[3] = [fCropRightStepper  intValue];
310     }
311     
312         if( [fAnamorphicPopUp indexOfSelectedItem] > 0 )
313         {
314         if ([fAnamorphicPopUp indexOfSelectedItem] == 2) // Loose anamorphic
315         {
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
321              */
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.
324              */
325             if (sender == fAnamorphicPopUp)
326             {
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]];
329             }
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];
335             
336         }
337         else // must be "1" or strict anamorphic
338         {
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]];
341             
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];
348             
349             job->pixel_ratio = 1;
350             [fWidthStepper setEnabled: NO];
351             [fWidthField setEnabled: NO];
352         }
353         
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)
357         {
358             keepAspectRatioPreviousState = [fRatioCheck state];
359         }
360         [fRatioCheck setState:NSOffState];
361         [fRatioCheck setEnabled: NO];
362         
363         
364         [fHeightStepper setEnabled: NO];
365         [fHeightField setEnabled: NO];
366         
367     }
368     else
369         {
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)
381         {
382             [fRatioCheck setState:keepAspectRatioPreviousState];
383         }
384         
385         }
386         
387     job->keep_ratio  = ( [fRatioCheck state] == NSOnState );
388     
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)
394     {
395         job->deinterlace  = 1;
396     }
397     else
398     {
399         job->deinterlace  = 0;
400     }
401     fPictureFilterSettings.denoise     = [fDenoisePopUp indexOfSelectedItem];
402     
403     fPictureFilterSettings.detelecine  = [fDetelecineCheck state];
404     
405     if ([fDeblockField stringValue] == @"Off")
406     {
407     fPictureFilterSettings.deblock  = 0;
408     }
409     else
410     {
411     fPictureFilterSettings.deblock  = [fDeblockField intValue];
412     }
413     
414     fPictureFilterSettings.decomb = [fDecombCheck state];
415
416     if( job->keep_ratio )
417     {
418         if( sender == fWidthStepper || sender == fRatioCheck ||
419            sender == fCropTopStepper || sender == fCropBottomStepper )
420         {
421             hb_fix_aspect( job, HB_KEEP_WIDTH );
422             if( job->height > fTitle->height )
423             {
424                 job->height = fTitle->height;
425                 hb_fix_aspect( job, HB_KEEP_HEIGHT );
426             }
427         }
428         else
429         {
430             hb_fix_aspect( job, HB_KEEP_HEIGHT );
431             if( job->width > fTitle->width )
432             {
433                 job->width = fTitle->width;
434                 hb_fix_aspect( job, HB_KEEP_WIDTH );
435             }
436         }
437         // hb_get_preview can't handle sizes that are larger than the original title
438         // dimensions
439         if( job->width > fTitle->width )
440             job->width = fTitle->width;
441
442         if( job->height > fTitle->height )
443             job->height = fTitle->height;
444     }
445
446     [fWidthStepper      setIntValue: job->width];
447     [fWidthField        setIntValue: job->width];
448     if( [fAnamorphicPopUp indexOfSelectedItem] < 2 )
449         {
450         [fHeightStepper     setIntValue: job->height];
451         [fHeightField       setIntValue: job->height];
452     }
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)
466     {
467        
468          // Purge the existing picture previews so they get recreated the next time
469         // they are needed.
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
473          */
474         //[self displayPreview];
475         [self pictureSliderChanged:nil];
476
477     }
478
479     if (sender != nil)
480     {
481         if ([delegate respondsToSelector:@selector(pictureSettingsDidChange)])
482             [delegate pictureSettingsDidChange];
483     }   
484     
485 }
486
487 - (IBAction) pictureSliderChanged: (id) sender
488 {
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];
495     
496     int newPicture = [fPictureSlider intValue];
497     if (newPicture != fPicture)
498     {
499         fPicture = newPicture;
500     }
501     [self displayPreview];
502     
503 }
504
505 #pragma mark Movie Preview
506 - (IBAction) createMoviePreview: (id) sender
507 {
508     /* Lets make sure the still picture previews are showing in case
509      * there is currently a movie showing */
510     [self pictureSliderChanged:nil];
511     
512     /* Rip or Cancel ? */
513     hb_state_t s;
514     hb_get_state2( fPreviewLibhb, &s );
515     
516     if(s.state == HB_STATE_WORKING || s.state == HB_STATE_PAUSED)
517         {
518         
519         play_movie = NO;
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"];
526         return;
527     }
528     
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
532      * directory */
533     
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)])
538     {
539         [delegate prepareJobForPreview];
540     }
541     
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
545     {
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";
548     }
549     else if (fTitle->job->mux == HB_MUX_MKV) // MKV file
550     {
551         fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.mkv";
552     }
553     else if (fTitle->job->mux == HB_MUX_AVI) // AVI file
554     {
555         fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.avi";
556     }
557     else if (fTitle->job->mux == HB_MUX_OGM) // OGM file
558     {
559         fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.ogm";
560     }
561     
562     fPreviewMoviePath = [[fPreviewMoviePath stringByExpandingTildeInPath]retain];
563     
564     /* See if there is an existing preview file, if so, delete it */
565     if( ![[NSFileManager defaultManager] fileExistsAtPath:fPreviewMoviePath] )
566     {
567         [[NSFileManager defaultManager] removeFileAtPath:fPreviewMoviePath
568                                                  handler:nil];
569     }
570     
571     /* We now direct our preview encode to fPreviewMoviePath */
572     fTitle->job->file = [fPreviewMoviePath UTF8String];
573     
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;
578     
579     /* we use the preview duration popup to get the specified
580      * number of seconds for the preview encode.
581      */
582     
583     job->pts_to_stop = [[fPreviewMovieLengthPopUp titleOfSelectedItem] intValue] * 90000LL;
584     
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.
588      */
589     hb_add( fPreviewLibhb, job );
590     
591     [fPictureSlider setHidden:YES];
592     [fMovieCreationProgressIndicator setHidden: NO];
593     [fPreviewMovieStatusField setHidden: NO];
594     [self startReceivingLibhbNotifications];
595     
596     
597     [fCreatePreviewMovieButton setTitle: @"Cancel Preview"];
598     
599     play_movie = YES;
600     
601     /* Let fPreviewLibhb do the job */
602     hb_start( fPreviewLibhb );
603         
604 }
605
606 - (void) startReceivingLibhbNotifications
607 {
608     if (!fLibhbTimer)
609     {
610         fLibhbTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(libhbTimerFired:) userInfo:nil repeats:YES];
611         [fLibhbTimer retain];
612     }
613 }
614
615 - (void) stopReceivingLibhbNotifications
616 {
617     if (fLibhbTimer)
618     {
619         [fLibhbTimer invalidate];
620         [fLibhbTimer release];
621         fLibhbTimer = nil;
622     }
623 }
624 - (void) libhbTimerFired: (NSTimer*)theTimer
625 {
626     hb_state_t s;
627     hb_get_state( fPreviewLibhb, &s );
628     [self libhbStateChanged: s];
629 }
630 - (void) libhbStateChanged: (hb_state_t &)state
631 {
632     switch( state.state )
633     {
634         case HB_STATE_IDLE:
635         case HB_STATE_SCANNING:
636         case HB_STATE_SCANDONE:
637             break;
638             
639         case HB_STATE_WORKING:
640         {
641 #define p state.param.working
642             
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];
646             
647                         if( p.seconds > -1 )
648             {
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];
652             }
653             [fPreviewMovieStatusField setStringValue: string];
654             
655             [fMovieCreationProgressIndicator setIndeterminate: NO];
656             /* Update slider */
657                         [fMovieCreationProgressIndicator setDoubleValue: 100.0 * p.progress];
658             
659             [fCreatePreviewMovieButton setTitle: @"Cancel Preview"];
660             
661             break;
662             
663         }
664 #undef p
665             
666 #define p state.param.muxing            
667         case HB_STATE_MUXING:
668         {
669             // Update fMovieCreationProgressIndicator
670             [fMovieCreationProgressIndicator setIndeterminate: YES];
671             [fMovieCreationProgressIndicator startAnimation: nil];
672             [fPreviewMovieStatusField setStringValue: [NSString stringWithFormat:
673                                          NSLocalizedString( @"Muxing Preview ...", @"" )]];
674             break;
675         }
676 #undef p                        
677         case HB_STATE_PAUSED:
678             [fMovieCreationProgressIndicator stopAnimation: nil];
679             break;
680                         
681         case HB_STATE_WORKDONE:
682         {
683             // Delete all remaining jobs since libhb doesn't do this on its own.
684             hb_job_t * job;
685             while( ( job = hb_job(fPreviewLibhb, 0) ) )
686                 hb_rem( fHandle, job );
687             
688             [self stopReceivingLibhbNotifications];
689             [fPreviewMovieStatusField setStringValue: @""];
690             [fPreviewMovieStatusField setHidden: YES];
691             
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];
697             
698             // Show the movie view
699             if (play_movie)
700             {
701             [self showMoviePreview:fPreviewMoviePath];
702             }
703             
704             [fCreatePreviewMovieButton setTitle: @"Live Preview"];
705             
706             
707             break;
708         }
709     }
710         
711 }
712
713 - (IBAction) showMoviePreview: (NSString *) path
714 {
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
720      */
721     [fMovieView setHidden:NO];
722     
723     /* Load the new movie into fMovieView */
724     QTMovie * aMovie;
725     NSRect movieBounds;
726     if (path)
727     {
728         [fMovieView setControllerVisible: YES];
729         /* let's make sure there is no movie currently set */
730         [fMovieView setMovie:nil];
731         
732         aMovie = [QTMovie movieWithFile:path error:nil];
733         
734         /* we get some size information from the preview movie */
735         Rect movieBox;
736         GetMovieBox ([aMovie quickTimeMovie], &movieBox);
737         movieBounds = [fMovieView movieBounds];
738         movieBounds.size.height = movieBox.bottom - movieBox.top;
739         
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
744          */
745         movieBounds.size.height += 15;
746         
747         movieBounds.size.width = movieBox.right - movieBox.left;
748         
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 )
753         {
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
757              */
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] )
762             {
763                 
764                 [self resizeSheetForViewSize:viewSize];
765                 [self setViewSize:viewSize];
766                 
767             }
768             
769             [fMovieView setFrameSize:viewSize];
770         }
771         else
772         {
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];
776         }
777         
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
785          */
786         origin.y += trunc((([fPictureViewArea frame].size.height -
787                             [fMovieView frame].size.height) / 2.0) - 7.5);
788         [fMovieView setFrameOrigin:origin]; 
789         
790         [fMovieView setMovie:aMovie];
791         /// to actually play the movie
792         [fMovieView play:aMovie];
793     }
794     else
795     {
796         aMovie = nil;
797     }       
798     
799 }
800
801 #pragma mark -
802
803 - (IBAction) ClosePanel: (id) sender
804 {
805     if ([delegate respondsToSelector:@selector(pictureSettingsDidChange)])
806         [delegate pictureSettingsDidChange];
807
808     [NSApp endSheet:[self window]];
809     [[self window] orderOut:self];
810 }
811
812 - (BOOL) autoCrop
813 {
814     return autoCrop;
815 }
816 - (void) setAutoCrop: (BOOL) setting
817 {
818     autoCrop = setting;
819 }
820
821 - (BOOL) allowLooseAnamorphic
822 {
823     return allowLooseAnamorphic;
824 }
825
826 - (void) setAllowLooseAnamorphic: (BOOL) setting
827 {
828     allowLooseAnamorphic = setting;
829 }
830
831 - (int) detelecine
832 {
833     return fPictureFilterSettings.detelecine;
834 }
835
836 - (void) setDetelecine: (int) setting
837 {
838     fPictureFilterSettings.detelecine = setting;
839 }
840
841 - (int) deinterlace
842 {
843     return fPictureFilterSettings.deinterlace;
844 }
845
846 - (void) setDeinterlace: (int) setting {
847     fPictureFilterSettings.deinterlace = setting;
848 }
849 - (int) decomb
850 {
851     return fPictureFilterSettings.decomb;
852 }
853
854 - (void) setDecomb: (int) setting {
855     fPictureFilterSettings.decomb = setting;
856 }
857 - (int) denoise
858 {
859     return fPictureFilterSettings.denoise;
860 }
861
862 - (void) setDenoise: (int) setting
863 {
864     fPictureFilterSettings.denoise = setting;
865 }
866
867 - (int) deblock
868 {
869     return fPictureFilterSettings.deblock;
870 }
871
872 - (void) setDeblock: (int) setting
873 {
874     fPictureFilterSettings.deblock = setting;
875 }
876
877 - (IBAction)showPreviewPanel: (id)sender forTitle: (hb_title_t *)title
878 {
879     [self SetTitle:title];
880     [self showWindow:sender];
881
882 }
883
884
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
894 {
895     if (removeBorders)
896     {
897         //     |<---------- title->width ----------->|
898         //     |   |<---- title->job->width ---->|   |
899         //     |   |                             |   |
900         //     .......................................
901         //     ....+-----------------------------+....
902         //     ....|                             |....<-- gray border
903         //     ....|                             |....
904         //     ....|                             |....
905         //     ....|                             |<------- image
906         //     ....|                             |....
907         //     ....|                             |....
908         //     ....|                             |....
909         //     ....|                             |....
910         //     ....|                             |....
911         //     ....+-----------------------------+....
912         //     .......................................
913
914         static uint8_t * buffer;
915         static int bufferSize;
916
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;
922         int newSize;
923         newSize = srcWidth * srcHeight * 4;
924         if( bufferSize < newSize )
925         {
926             bufferSize = newSize;
927             buffer     = (uint8_t *) realloc( buffer, bufferSize );
928         }
929
930         hb_get_preview( handle, title, pictureIndex, buffer );
931
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.
935         
936         // The image data returned by hb_get_preview is 4 bytes per pixel, BGRA format.
937         // Alpha is ignored.
938         
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
944                 pixelsWide:dstWidth
945                 pixelsHigh:dstHeight
946                 bitsPerSample:8
947                 samplesPerPixel:3   // ignore alpha
948                 hasAlpha:NO
949                 isPlanar:NO
950                 colorSpaceName:NSCalibratedRGBColorSpace
951                 bitmapFormat:bitmapFormat
952                 bytesPerRow:dstWidth * 4
953                 bitsPerPixel:32] autorelease];
954
955         int borderTop = (srcHeight - dstHeight) / 2;
956         int borderLeft = (srcWidth - dstWidth) / 2;
957         
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++)
963         {
964             for (int c = 0; c < dstWidth; c++)
965 #if TARGET_RT_LITTLE_ENDIAN
966                 *dst++ = Endian32_Swap(*src++);
967 #else
968                 *dst++ = *src++;
969 #endif
970             src += (srcWidth - dstWidth);   // skip to next row in src
971         }
972
973         NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(dstWidth, dstHeight)] autorelease];
974         [img addRepresentation:imgrep];
975
976         return img;
977     }
978     else
979     {
980         // Make sure we have big enough buffer
981         static uint8_t * buffer;
982         static int bufferSize;
983
984         int newSize;
985         newSize = ( title->width + 2 ) * (title->height + 2 ) * 4;
986         if( bufferSize < newSize )
987         {
988             bufferSize = newSize;
989             buffer     = (uint8_t *) realloc( buffer, bufferSize );
990         }
991
992         hb_get_preview( handle, title, pictureIndex, buffer );
993
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
996         // ignored.
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
1003                 pixelsWide:width
1004                 pixelsHigh:height
1005                 bitsPerSample:8
1006                 samplesPerPixel:3   // ignore alpha
1007                 hasAlpha:NO
1008                 isPlanar:NO
1009                 colorSpaceName:NSCalibratedRGBColorSpace
1010                 bitmapFormat:bitmapFormat
1011                 bytesPerRow:width * 4
1012                 bitsPerPixel:32] autorelease];
1013
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++);
1019 #else
1020             *dst++ = *src++;
1021 #endif
1022
1023         NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(width, height)] autorelease];
1024         [img addRepresentation:imgrep];
1025
1026         return img;
1027     }
1028 }
1029
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
1035 {
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];
1040     if (!theImage)
1041     {
1042         theImage = [PictureController makeImageForPicture:pictureIndex libhb:fHandle title:fTitle removeBorders: NO];
1043         [fPicturePreviews setObject:theImage forKey:key];
1044     }
1045     return theImage;
1046 }
1047
1048 // Purges all images from the cache. The next call to imageForPicture will cause a new
1049 // image to be generated.
1050 - (void) purgeImageCache
1051 {
1052     [fPicturePreviews removeAllObjects];
1053 }
1054
1055 @end
1056
1057 @implementation PictureController (Private)
1058
1059 //
1060 // -[PictureController(Private) optimalViewSizeForImageSize:]
1061 //
1062 // Given the size of the preview image to be shown, returns the best possible
1063 // size for the view.
1064 //
1065 - (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize
1066 {
1067     // The min size is 320x240
1068     CGFloat minWidth = 320.0;
1069     CGFloat minHeight = 240.0;
1070
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.
1079      */
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;
1085     
1086     NSSize resultSize = imageSize;
1087     
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 )
1091     {
1092         resultSize.height *= (minWidth / resultSize.width);
1093         resultSize.width = minWidth;
1094     }
1095     if( resultSize.height < minHeight )
1096     {
1097         resultSize.width *= (minHeight / resultSize.height);
1098         resultSize.height = minHeight;
1099     }
1100     if( resultSize.width > maxWidth )
1101     {
1102         resultSize.height *= (maxWidth / resultSize.width);
1103         resultSize.width = maxWidth;
1104     }
1105     if( resultSize.height > maxHeight )
1106     {
1107         resultSize.width *= (maxHeight / resultSize.height);
1108         resultSize.height = maxHeight;
1109     }
1110     
1111     return resultSize;
1112 }
1113
1114 //
1115 // -[PictureController(Private) resizePanelForViewSize:animate:]
1116 //
1117 // Resizes the entire sheet to accomodate a view of a particular size.
1118 //
1119 - (void)resizeSheetForViewSize: (NSSize)viewSize
1120 {
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;
1125
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 )
1133     {
1134         frame.size.width = minSize.width;
1135     }
1136     if( frame.size.height < minSize.height )
1137     {
1138         frame.size.height = minSize.height;
1139     }
1140
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);
1145
1146     if( frame.size.height != [[self window] frame].size.height )
1147         frame.origin.y -= deltaY;
1148
1149     [[self window] setFrame:frame display:YES animate:YES];
1150 }
1151
1152 //
1153 // -[PictureController(Private) setViewSize:]
1154 //
1155 // Changes the view's size and centers it vertically inside of its area.
1156 // Assumes resizeSheetForViewSize: has already been called.
1157 //
1158 - (void)setViewSize: (NSSize)viewSize
1159 {
1160     [fPictureView setFrameSize:viewSize];
1161     
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];
1167 }
1168
1169 //
1170 // -[PictureController(Private) viewNeedsToResizeToSize:]
1171 //
1172 // Returns YES if the view will need to resize to match the given size.
1173 //
1174 - (BOOL)viewNeedsToResizeToSize: (NSSize)newSize
1175 {
1176     NSSize viewSize = [fPictureView frame].size;
1177     return (newSize.width != viewSize.width || newSize.height != viewSize.height);
1178 }
1179
1180 @end