WinGui: Fix another instance of the Caliburn vs Json.net sillyness where objects...
[HandBrake.git] / macosx / HBCore.m
blob13344f8b7e8e72effa6e9dd6f28f9e57fca5e0e3
1 /*  HBCore.m $
3  This file is part of the HandBrake source code.
4  Homepage: <http://handbrake.fr/>.
5  It may be used under the terms of the GNU General Public License. */
7 #import "HBCore.h"
8 #import "HBJob.h"
9 #import "HBJob+HBJobConversion.h"
10 #import "HBDVDDetector.h"
11 #import "HBUtilities.h"
13 #import "HBTitlePrivate.h"
15 #include <dlfcn.h>
17 static BOOL globalInitialized = NO;
19 static void (^errorHandler)(NSString *error) = NULL;
20 static void hb_error_handler(const char *errmsg)
22     NSString *error = @(errmsg);
23     if (error)
24     {
25         errorHandler(error);
26     }
29 /**
30  * Private methods of HBCore.
31  */
32 @interface HBCore ()
34 /// Pointer to a hb_state_s struct containing the detailed state information of libhb.
35 @property (nonatomic, readonly) hb_state_t *hb_state;
37 /// Pointer to a libhb handle used by this HBCore instance.
38 @property (nonatomic, readonly) hb_handle_t *hb_handle;
40 /// Current state of HBCore.
41 @property (nonatomic, readwrite) HBState state;
43 /// Timer used to poll libhb for state changes.
44 @property (nonatomic, readwrite) dispatch_source_t updateTimer;
45 @property (nonatomic, readonly) dispatch_queue_t updateTimerQueue;
47 /// Current scanned titles.
48 @property (nonatomic, readwrite, strong) NSArray *titles;
50 /// Progress handler.
51 @property (nonatomic, readwrite, copy) HBCoreProgressHandler progressHandler;
53 /// Completion handler.
54 @property (nonatomic, readwrite, copy) HBCoreCompletionHandler completionHandler;
56 /// User cancelled.
57 @property (nonatomic, readwrite, getter=isCancelled) BOOL cancelled;
59 @end
61 @implementation HBCore
63 + (void)setDVDNav:(BOOL)enabled
65     hb_dvd_set_dvdnav(enabled);
68 + (void)initGlobal
70     hb_global_init();
71     globalInitialized = YES;
74 + (void)closeGlobal
76     NSAssert(globalInitialized, @"[HBCore closeGlobal] global closed but not initialized");
77     hb_global_close();
80 + (void)registerErrorHandler:(void (^)(NSString *error))handler
82     errorHandler = [handler copy];
83     hb_register_error_handler(&hb_error_handler);
86 - (instancetype)init
88     return [self initWithLogLevel:0];
91 - (instancetype)initWithLogLevel:(int)level
93     self = [super init];
94     if (self)
95     {
96         _name = @"HBCore";
97         _state = HBStateIdle;
98         _updateTimerQueue = dispatch_queue_create("fr.handbrake.coreQueue", DISPATCH_QUEUE_SERIAL);
99         _hb_state = malloc(sizeof(struct hb_state_s));
101         _hb_handle = hb_init(level, 0);
102         if (!_hb_handle)
103         {
104             return nil;
105         }
106     }
108     return self;
111 - (instancetype)initWithLogLevel:(int)level name:(NSString *)name
113     self = [self initWithLogLevel:level];
114     if (self)
115     {
116         _name = [name copy];
117     }
118     return self;
122  * Releases resources.
123  */
124 - (void)dealloc
126     [self stopUpdateTimer];
128     dispatch_release(_updateTimerQueue);
130     hb_close(&_hb_handle);
131     _hb_handle = NULL;
132     free(_hb_state);
135 #pragma mark - Scan
137 - (BOOL)canScan:(NSURL *)url error:(NSError * __autoreleasing *)error
139     if (![[NSFileManager defaultManager] fileExistsAtPath:url.path]) {
140         if (error) {
141             *error = [NSError errorWithDomain:@"HBErrorDomain"
142                                          code:100
143                                      userInfo:@{ NSLocalizedDescriptionKey: @"Unable to find the file at the specified URL" }];
144         }
146         return NO;
147     }
149     HBDVDDetector *detector = [HBDVDDetector detectorForPath:url.path];
151     if (detector.isVideoDVD)
152     {
153         // The chosen path was actually on a DVD, so use the raw block
154         // device path instead.
156         [HBUtilities writeToActivityLog:"%s trying to open a physical dvd at: %s", self.name.UTF8String, url.path.UTF8String];
158         // Notify the user that we don't support removal of copy protection.
159         void *dvdcss = dlopen("libdvdcss.2.dylib", RTLD_LAZY);
160         if (dvdcss)
161         {
162             // libdvdcss was found so all is well
163             [HBUtilities writeToActivityLog:"%s libdvdcss.2.dylib found for decrypting physical dvd", self.name.UTF8String];
164             dlclose(dvdcss);
165         }
166         else
167         {
168             // compatible libdvdcss not found
169             [HBUtilities writeToActivityLog:"%s, libdvdcss.2.dylib not found for decrypting physical dvd", self.name.UTF8String];
171             if (error) {
172                 *error = [NSError errorWithDomain:@"HBErrorDomain"
173                                              code:101
174                                          userInfo:@{ NSLocalizedDescriptionKey: @"libdvdcss.2.dylib not found for decrypting physical dvd" }];
175             }
176         }
177     }
179     return YES;
182 - (void)scanURL:(NSURL *)url titleIndex:(NSUInteger)index previews:(NSUInteger)previewsNum minDuration:(NSUInteger)seconds progressHandler:(HBCoreProgressHandler)progressHandler completionHandler:(HBCoreCompletionHandler)completionHandler
184     NSAssert(self.state == HBStateIdle, @"[HBCore scanURL:] called while another scan or encode already in progress");
185     NSAssert(url, @"[HBCore scanURL:] called with nil url.");
187     // Reset the titles array
188     self.titles = nil;
190     // Copy the progress/completion blocks
191     self.progressHandler = progressHandler;
192     self.completionHandler = completionHandler;
194     // Start the timer to handle libhb state changes
195     [self startUpdateTimerWithInterval:0.2];
197     NSString *path = url.path;
198     HBDVDDetector *detector = [HBDVDDetector detectorForPath:path];
200     if (detector.isVideoDVD)
201     {
202         // The chosen path was actually on a DVD, so use the raw block
203         // device path instead.
204         path = detector.devicePath;
205     }
207     // convert minTitleDuration from seconds to the internal HB time
208     uint64_t min_title_duration_ticks = 90000LL * seconds;
210     // If there is no title number passed to scan, we use 0
211     // which causes the default behavior of a full source scan
212     if (index > 0)
213     {
214         [HBUtilities writeToActivityLog:"%s scanning specifically for title: %d", self.name.UTF8String, index];
215     }
216     else
217     {
218         // minimum title duration doesn't apply to title-specific scan
219         // it doesn't apply to batch scan either, but we can't tell it apart from DVD & BD folders here
220         [HBUtilities writeToActivityLog:"%s scanning titles with a duration of %d seconds or more", self.name.UTF8String, seconds];
221     }
223     hb_system_sleep_prevent(_hb_handle);
225     hb_scan(_hb_handle, path.fileSystemRepresentation,
226             (int)index, (int)previewsNum,
227             1, min_title_duration_ticks);
229     // Set the state, so the UI can be update
230     // to reflect the current state instead of
231     // waiting for libhb to set it in a background thread.
232     self.state = HBStateScanning;
236  *  Creates an array of lightweight HBTitles instances.
237  */
238 - (BOOL)scanDone
240     if (self.isCancelled)
241     {
242         return NO;
243     }
245     hb_title_set_t *title_set = hb_get_title_set(_hb_handle);
246     NSMutableArray *titles = [NSMutableArray array];
248     for (int i = 0; i < hb_list_count(title_set->list_title); i++)
249     {
250         hb_title_t *title = (hb_title_t *) hb_list_item(title_set->list_title, i);
251         [titles addObject:[[HBTitle alloc] initWithTitle:title featured:(title->index == title_set->feature)]];
252     }
254     self.titles = [titles copy];
256     [HBUtilities writeToActivityLog:"%s scan done", self.name.UTF8String];
258     return (self.titles.count > 0);
261 - (void)cancelScan
263     self.cancelled = YES;
264     hb_scan_stop(_hb_handle);
265     [HBUtilities writeToActivityLog:"%s scan cancelled", self.name.UTF8String];
268 #pragma mark - Preview images
270 - (CGImageRef)copyImageAtIndex:(NSUInteger)index
271                       forTitle:(HBTitle *)title
272                   pictureFrame:(HBPicture *)frame
273                    deinterlace:(BOOL)deinterlace
275     CGImageRef img = NULL;
277     hb_geometry_settings_t geo;
278     memset(&geo, 0, sizeof(geo));
279     geo.geometry.width = frame.displayWidth;
280     geo.geometry.height = frame.height;
281     // ignore the par.
282     geo.geometry.par.num = 1;
283     geo.geometry.par.den = 1;
284     int crop[4] = {frame.cropTop, frame.cropBottom, frame.cropLeft, frame.cropRight};
285     memcpy(geo.crop, crop, sizeof(int[4]));
287     hb_image_t *image = hb_get_preview2(_hb_handle, title.index, (int)index, &geo, deinterlace);
289     if (image)
290     {
291         // Create an CGImageRef and copy the libhb image into it.
292         // The image data returned by hb_get_preview2 is 4 bytes per pixel, BGRA format.
293         // Alpha is ignored.
294         CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaNone;
295         CFMutableDataRef imgData = CFDataCreateMutable(kCFAllocatorDefault, 3 * image->width * image->height);
296         CGDataProviderRef provider = CGDataProviderCreateWithCFData(imgData);
297         CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
298         img = CGImageCreate(image->width,
299                             image->height,
300                             8,
301                             24,
302                             image->width * 3,
303                             colorSpace,
304                             bitmapInfo,
305                             provider,
306                             NULL,
307                             NO,
308                             kCGRenderingIntentDefault);
309         CGColorSpaceRelease(colorSpace);
310         CGDataProviderRelease(provider);
311         CFRelease(imgData);
313         UInt8 *src_line = image->data;
314         UInt8 *dst = CFDataGetMutableBytePtr(imgData);
315         for (int r = 0; r < image->height; r++)
316         {
317             UInt8 *src = src_line;
318             for (int c = 0; c < image->width; c++)
319             {
320                 *dst++ = src[2];
321                 *dst++ = src[1];
322                 *dst++ = src[0];
323                 src += 4;
324             }
325             src_line += image->plane[0].stride;
326         }
328         hb_image_close(&image);
329     }
330     
331     return img;
334 #pragma mark - Encodes
336 - (void)encodeJob:(HBJob *)job progressHandler:(HBCoreProgressHandler)progressHandler completionHandler:(HBCoreCompletionHandler)completionHandler;
338     NSAssert(self.state == HBStateIdle, @"[HBCore encodeJob:] called while another scan or encode already in progress");
339     NSAssert(job, @"[HBCore encodeJob:] called with nil job");
341     // Copy the progress/completion blocks
342     self.progressHandler = progressHandler;
343     self.completionHandler = completionHandler;
345     // Start the timer to handle libhb state changes
346     [self startUpdateTimerWithInterval:0.5];
348     // Add the job to libhb
349     hb_job_t *hb_job = job.hb_job;
350     hb_job_set_file(hb_job, job.destURL.path.fileSystemRepresentation);
351     hb_add(_hb_handle, hb_job);
353     // Free the job
354     hb_job_close(&hb_job);
356     hb_system_sleep_prevent(_hb_handle);
357     hb_start(_hb_handle);
359     // Set the state, so the UI can be update
360     // to reflect the current state instead of
361     // waiting for libhb to set it in a background thread.
362     self.state = HBStateWorking;
364     [HBUtilities writeToActivityLog:"%s started encoding %s", self.name.UTF8String, job.destURL.lastPathComponent.UTF8String];
367 - (BOOL)workDone
369     // HB_STATE_WORKDONE happpens as a result of libhb finishing all its jobs
370     // or someone calling hb_stop. In the latter case, hb_stop does not clear
371     // out the remaining passes/jobs in the queue. We'll do that here.
372     hb_job_t *job;
373     while ((job = hb_job(_hb_handle, 0)))
374     {
375         hb_rem(_hb_handle, job);
376     }
378     [HBUtilities writeToActivityLog:"%s work done", self.name.UTF8String];
380     return self.isCancelled || (_hb_state->param.workdone.error == 0);
383 - (void)cancelEncode
385     self.cancelled = YES;
386     hb_stop(_hb_handle);
387     [HBUtilities writeToActivityLog:"%s encode cancelled", self.name.UTF8String];
390 - (void)pause
392     hb_pause(_hb_handle);
393     hb_system_sleep_allow(_hb_handle);
394     self.state = HBStatePaused;
397 - (void)resume
399     hb_resume(_hb_handle);
400     hb_system_sleep_prevent(_hb_handle);
401     self.state = HBStateWorking;
404 #pragma mark - State updates
407  *  Starts the timer used to polls libhb for state changes.
409  *  @param seconds The number of seconds between firings of the timer.
410  */
411 - (void)startUpdateTimerWithInterval:(NSTimeInterval)seconds
413     if (!self.updateTimer)
414     {
415         dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
416         if (timer)
417         {
418             dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), (uint64_t)(seconds * NSEC_PER_SEC), (uint64_t)(seconds * NSEC_PER_SEC / 10));
419             dispatch_source_set_event_handler(timer, ^{
420                 [self updateState];
421             });
422             dispatch_resume(timer);
423         }
424         self.updateTimer = timer;
425     }
429  * Stops the update timer.
430  */
431 - (void)stopUpdateTimer
433     if (self.updateTimer)
434     {
435         dispatch_source_cancel(self.updateTimer);
436         dispatch_release(self.updateTimer);
437         self.updateTimer = NULL;
438     }
442  * This method polls libhb continuously for state changes and processes them.
443  * Additional processing for each state is performed in methods that start
444  * with 'handle'.
445  */
446 - (void)updateState
448     hb_get_state(_hb_handle, _hb_state);
450     if (_hb_state->state == HB_STATE_IDLE)
451     {
452         // Libhb reported HB_STATE_IDLE, so nothing interesting has happened.
453         return;
454     }
456     // Update HBCore state to reflect the current state of libhb
457     self.state = _hb_state->state;
459     // Call the handler for the current state
460     if (_hb_state->state == HB_STATE_WORKDONE || _hb_state->state == HB_STATE_SCANDONE)
461     {
462         [self handleCompletion];
463     }
464     else
465     {
466         [self handleProgress];
467     }
470 #pragma mark - Blocks callbacks
473  * Processes progress state information.
474  */
475 - (void)handleProgress
477     if (self.progressHandler)
478     {
479         self.progressHandler(self.state, *(self.hb_state));
480     }
484  * Processes completion state information.
485  */
486 - (void)handleCompletion
488     // Libhb reported HB_STATE_WORKDONE or HB_STATE_SCANDONE,
489     // so nothing interesting will happen after this point, stop the timer.
490     [self stopUpdateTimer];
492     // Set the state to idle, because the update timer won't fire again.
493     self.state = HBStateIdle;
494     // Reallow system sleep.
495     hb_system_sleep_allow(_hb_handle);
497     // Call the completion block and clean ups the handlers
498     self.progressHandler = nil;
499     if (_hb_state->state == HB_STATE_WORKDONE)
500     {
501         [self handleWorkCompletion];
502     }
503     else
504     {
505         [self handleScanCompletion];
506     }
508     // Reset the cancelled state.
509     self.cancelled = NO;
513  *  Runs the completion block and clean ups the internal blocks.
515  *  @param result the result to pass to the completion block.
516  */
517 - (void)runCompletionBlockAndCleanUpWithResult:(BOOL)result
519     if (self.completionHandler)
520     {
521         // Retain the completion block, because it could be replaced
522         // inside the same block.
523         HBCoreCompletionHandler completionHandler = self.completionHandler;
524         self.completionHandler = nil;
525         completionHandler(result);
526     }
530  * Processes scan completion.
531  */
532 - (void)handleScanCompletion
534     BOOL result = [self scanDone];
535     [self runCompletionBlockAndCleanUpWithResult:result];
539  * Processes work completion.
540  */
541 - (void)handleWorkCompletion
543     BOOL result = [self workDone];
544     [self runCompletionBlockAndCleanUpWithResult:result];
547 @end