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. */
9 #import "HBJob+HBJobConversion.h"
10 #import "HBDVDDetector.h"
11 #import "HBUtilities.h"
13 #import "HBTitlePrivate.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);
30 * Private methods of 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;
51 @property (nonatomic, readwrite, copy) HBCoreProgressHandler progressHandler;
53 /// Completion handler.
54 @property (nonatomic, readwrite, copy) HBCoreCompletionHandler completionHandler;
57 @property (nonatomic, readwrite, getter=isCancelled) BOOL cancelled;
61 @implementation HBCore
63 + (void)setDVDNav:(BOOL)enabled
65 hb_dvd_set_dvdnav(enabled);
71 globalInitialized = YES;
76 NSAssert(globalInitialized, @"[HBCore closeGlobal] global closed but not initialized");
80 + (void)registerErrorHandler:(void (^)(NSString *error))handler
82 errorHandler = [handler copy];
83 hb_register_error_handler(&hb_error_handler);
88 return [self initWithLogLevel:0];
91 - (instancetype)initWithLogLevel:(int)level
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);
111 - (instancetype)initWithLogLevel:(int)level name:(NSString *)name
113 self = [self initWithLogLevel:level];
122 * Releases resources.
126 [self stopUpdateTimer];
128 dispatch_release(_updateTimerQueue);
130 hb_close(&_hb_handle);
137 - (BOOL)canScan:(NSURL *)url error:(NSError * __autoreleasing *)error
139 if (![[NSFileManager defaultManager] fileExistsAtPath:url.path]) {
141 *error = [NSError errorWithDomain:@"HBErrorDomain"
143 userInfo:@{ NSLocalizedDescriptionKey: @"Unable to find the file at the specified URL" }];
149 HBDVDDetector *detector = [HBDVDDetector detectorForPath:url.path];
151 if (detector.isVideoDVD)
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);
162 // libdvdcss was found so all is well
163 [HBUtilities writeToActivityLog:"%s libdvdcss.2.dylib found for decrypting physical dvd", self.name.UTF8String];
168 // compatible libdvdcss not found
169 [HBUtilities writeToActivityLog:"%s, libdvdcss.2.dylib not found for decrypting physical dvd", self.name.UTF8String];
172 *error = [NSError errorWithDomain:@"HBErrorDomain"
174 userInfo:@{ NSLocalizedDescriptionKey: @"libdvdcss.2.dylib not found for decrypting physical dvd" }];
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
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)
202 // The chosen path was actually on a DVD, so use the raw block
203 // device path instead.
204 path = detector.devicePath;
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
214 [HBUtilities writeToActivityLog:"%s scanning specifically for title: %d", self.name.UTF8String, index];
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];
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.
240 if (self.isCancelled)
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++)
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)]];
254 self.titles = [titles copy];
256 [HBUtilities writeToActivityLog:"%s scan done", self.name.UTF8String];
258 return (self.titles.count > 0);
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;
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);
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.
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,
308 kCGRenderingIntentDefault);
309 CGColorSpaceRelease(colorSpace);
310 CGDataProviderRelease(provider);
313 UInt8 *src_line = image->data;
314 UInt8 *dst = CFDataGetMutableBytePtr(imgData);
315 for (int r = 0; r < image->height; r++)
317 UInt8 *src = src_line;
318 for (int c = 0; c < image->width; c++)
325 src_line += image->plane[0].stride;
328 hb_image_close(&image);
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);
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];
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.
373 while ((job = hb_job(_hb_handle, 0)))
375 hb_rem(_hb_handle, job);
378 [HBUtilities writeToActivityLog:"%s work done", self.name.UTF8String];
380 return self.isCancelled || (_hb_state->param.workdone.error == 0);
385 self.cancelled = YES;
387 [HBUtilities writeToActivityLog:"%s encode cancelled", self.name.UTF8String];
392 hb_pause(_hb_handle);
393 hb_system_sleep_allow(_hb_handle);
394 self.state = HBStatePaused;
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.
411 - (void)startUpdateTimerWithInterval:(NSTimeInterval)seconds
413 if (!self.updateTimer)
415 dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
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, ^{
422 dispatch_resume(timer);
424 self.updateTimer = timer;
429 * Stops the update timer.
431 - (void)stopUpdateTimer
433 if (self.updateTimer)
435 dispatch_source_cancel(self.updateTimer);
436 dispatch_release(self.updateTimer);
437 self.updateTimer = NULL;
442 * This method polls libhb continuously for state changes and processes them.
443 * Additional processing for each state is performed in methods that start
448 hb_get_state(_hb_handle, _hb_state);
450 if (_hb_state->state == HB_STATE_IDLE)
452 // Libhb reported HB_STATE_IDLE, so nothing interesting has happened.
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)
462 [self handleCompletion];
466 [self handleProgress];
470 #pragma mark - Blocks callbacks
473 * Processes progress state information.
475 - (void)handleProgress
477 if (self.progressHandler)
479 self.progressHandler(self.state, *(self.hb_state));
484 * Processes completion state information.
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)
501 [self handleWorkCompletion];
505 [self handleScanCompletion];
508 // Reset the cancelled state.
513 * Runs the completion block and clean ups the internal blocks.
515 * @param result the result to pass to the completion block.
517 - (void)runCompletionBlockAndCleanUpWithResult:(BOOL)result
519 if (self.completionHandler)
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);
530 * Processes scan completion.
532 - (void)handleScanCompletion
534 BOOL result = [self scanDone];
535 [self runCompletionBlockAndCleanUpWithResult:result];
539 * Processes work completion.
541 - (void)handleWorkCompletion
543 BOOL result = [self workDone];
544 [self runCompletionBlockAndCleanUpWithResult:result];