/* HBCore.m $
This file is part of the HandBrake source code.
Homepage: .
It may be used under the terms of the GNU General Public License. */
#import "HBCore.h"
#import "HBJob.h"
#import "HBJob+HBJobConversion.h"
#import "HBDVDDetector.h"
#import "HBUtilities.h"
#include
/**
* Private methods of HBCore.
*/
@interface HBCore ()
/// Pointer to a hb_state_s struct containing the detailed state information of libhb.
@property (nonatomic, readonly) hb_state_t *hb_state;
/// Current state of HBCore.
@property (nonatomic, readwrite) HBState state;
/// Timer used to poll libhb for state changes.
@property (nonatomic, readwrite, retain) NSTimer *updateTimer;
/// Current scanned titles.
@property (nonatomic, readwrite, retain) NSArray *titles;
/// Progress handler.
@property (nonatomic, readwrite, copy) HBCoreProgressHandler progressHandler;
/// Completation handler.
@property (nonatomic, readwrite, copy) HBCoreCompletationHandler completationHandler;
/// User cancelled.
@property (nonatomic, readwrite, getter=isCancelled) BOOL cancelled;
- (void)stateUpdateTimer:(NSTimer *)timer;
@end
@implementation HBCore
+ (void)setDVDNav:(BOOL)enabled
{
hb_dvd_set_dvdnav(enabled);
}
+ (void)closeGlobal
{
hb_global_close();
}
/**
* Initializes HBCore.
*/
- (instancetype)init
{
return [self initWithLoggingLevel:0];
}
/**
* Opens low level HandBrake library. This should be called once before other
* functions HBCore are used.
*
* @param debugMode If set to YES, libhb will print verbose debug output.
*
* @return YES if libhb was opened, NO if there was an error.
*/
- (instancetype)initWithLoggingLevel:(int)loggingLevel
{
self = [super init];
if (self)
{
_name = @"HBCore";
_state = HBStateIdle;
_hb_state = malloc(sizeof(struct hb_state_s));
_hb_handle = hb_init(loggingLevel, 0);
if (!_hb_handle)
{
[self release];
return nil;
}
}
return self;
}
/**
* Releases resources.
*/
- (void)dealloc
{
[self stopUpdateTimer];
hb_close(&_hb_handle);
_hb_handle = NULL;
free(_hb_state);
[super dealloc];
}
#pragma mark - Scan
- (BOOL)canScan:(NSURL *)url error:(NSError **)error
{
if (![[NSFileManager defaultManager] fileExistsAtPath:url.path]) {
if (*error) {
*error = [NSError errorWithDomain:@"HBErrorDomain"
code:100
userInfo:@{ NSLocalizedDescriptionKey: @"Unable to find the file at the specified URL" }];
}
return NO;
}
HBDVDDetector *detector = [HBDVDDetector detectorForPath:url.path];
if (detector.isVideoDVD)
{
// The chosen path was actually on a DVD, so use the raw block
// device path instead.
[HBUtilities writeToActivityLog:"%s trying to open a physical dvd at: %s", self.name.UTF8String, url.path.UTF8String];
// Notify the user that we don't support removal of copy protection.
void *dvdcss = dlopen("libdvdcss.2.dylib", RTLD_LAZY);
if (dvdcss)
{
// libdvdcss was found so all is well
[HBUtilities writeToActivityLog:"%s libdvdcss.2.dylib found for decrypting physical dvd", self.name.UTF8String];
dlclose(dvdcss);
}
else
{
// compatible libdvdcss not found
[HBUtilities writeToActivityLog:"%s, libdvdcss.2.dylib not found for decrypting physical dvd", self.name.UTF8String];
if (error) {
*error = [NSError errorWithDomain:@"HBErrorDomain"
code:101
userInfo:@{ NSLocalizedDescriptionKey: @"libdvdcss.2.dylib not found for decrypting physical dvd" }];
}
}
}
return YES;
}
- (void)scanURL:(NSURL *)url titleIndex:(NSUInteger)titleNum previews:(NSUInteger)previewsNum minDuration:(NSUInteger)minTitleDuration progressHandler:(HBCoreProgressHandler)progressHandler completationHandler:(HBCoreCompletationHandler)completationHandler
{
// Copy the progress/completation blocks
self.progressHandler = progressHandler;
self.completationHandler = completationHandler;
// Start the timer to handle libhb state changes
[self startUpdateTimerWithInterval:0.2];
NSString *path = url.path;
HBDVDDetector *detector = [HBDVDDetector detectorForPath:path];
if (detector.isVideoDVD)
{
// The chosen path was actually on a DVD, so use the raw block
// device path instead.
path = detector.devicePath;
}
// convert minTitleDuration from seconds to the internal HB time
uint64_t min_title_duration_ticks = 90000LL * minTitleDuration;
// If there is no title number passed to scan, we use 0
// which causes the default behavior of a full source scan
if (titleNum > 0)
{
[HBUtilities writeToActivityLog:"%s scanning specifically for title: %d", self.name.UTF8String, titleNum];
}
else
{
// minimum title duration doesn't apply to title-specific scan
// it doesn't apply to batch scan either, but we can't tell it apart from DVD & BD folders here
[HBUtilities writeToActivityLog:"%s scanning titles with a duration of %d seconds or more", self.name.UTF8String, minTitleDuration];
}
hb_system_sleep_prevent(_hb_handle);
hb_scan(_hb_handle, path.fileSystemRepresentation,
(int)titleNum, (int)previewsNum,
1, min_title_duration_ticks);
// Set the state, so the UI can be update
// to reflect the current state instead of
// waiting for libhb to set it in a background thread.
self.state = HBStateScanning;
}
/**
* Creates an array of lightweight HBTitles instances.
*/
- (BOOL)scanDone
{
hb_title_set_t *title_set = hb_get_title_set(_hb_handle);
NSMutableArray *titles = [NSMutableArray array];
for (int i = 0; i < hb_list_count(title_set->list_title); i++)
{
hb_title_t *title = (hb_title_t *) hb_list_item(title_set->list_title, i);
[titles addObject:[[[HBTitle alloc] initWithTitle:title featured:(title->index == title_set->feature)] autorelease]];
}
self.titles = [[titles copy] autorelease];
[HBUtilities writeToActivityLog:"%s scan done", self.name.UTF8String];
return (self.titles.count > 0);
}
- (void)cancelScan
{
hb_scan_stop(_hb_handle);
[HBUtilities writeToActivityLog:"%s scan cancelled", self.name.UTF8String];
}
#pragma mark - Encodes
- (void)encodeJob:(HBJob *)job progressHandler:(HBCoreProgressHandler)progressHandler completationHandler:(HBCoreCompletationHandler)completationHandler;
{
// Copy the progress/completation blocks
self.progressHandler = progressHandler;
self.completationHandler = completationHandler;
// Start the timer to handle libhb state changes
[self startUpdateTimerWithInterval:0.5];
// Add the job to libhb
hb_job_t *hb_job = job.hb_job;
hb_job_set_file(hb_job, job.destURL.path.fileSystemRepresentation);
hb_add(self.hb_handle, hb_job);
// Free the job
hb_job_close(&hb_job);
hb_system_sleep_prevent(_hb_handle);
hb_start(_hb_handle);
// Set the state, so the UI can be update
// to reflect the current state instead of
// waiting for libhb to set it in a background thread.
self.state = HBStateWorking;
[HBUtilities writeToActivityLog:"%s started encoding %s", self.name.UTF8String, job.destURL.lastPathComponent.UTF8String];
}
- (BOOL)workDone
{
// HB_STATE_WORKDONE happpens as a result of libhb finishing all its jobs
// or someone calling hb_stop. In the latter case, hb_stop does not clear
// out the remaining passes/jobs in the queue. We'll do that here.
hb_job_t *job;
while ((job = hb_job(_hb_handle, 0)))
{
hb_rem(_hb_handle, job);
}
[HBUtilities writeToActivityLog:"%s work done", self.name.UTF8String];
if (self.isCancelled)
{
self.cancelled = NO;
return NO;
}
else
{
return YES;
}
}
- (void)cancelEncode
{
self.cancelled = YES;
hb_stop(_hb_handle);
hb_system_sleep_allow(_hb_handle);
[HBUtilities writeToActivityLog:"%s stop", self.name.UTF8String];
}
- (void)pause
{
hb_pause(_hb_handle);
hb_system_sleep_allow(_hb_handle);
}
- (void)resume
{
hb_resume(_hb_handle);
hb_system_sleep_prevent(_hb_handle);
}
#pragma mark - State updates
/**
* Starts the timer used to polls libhb for state changes.
*
* @param seconds The number of seconds between firings of the timer.
*/
- (void)startUpdateTimerWithInterval:(NSTimeInterval)seconds
{
if (!self.updateTimer)
{
self.updateTimer = [NSTimer scheduledTimerWithTimeInterval:seconds
target:self
selector:@selector(stateUpdateTimer:)
userInfo:NULL
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.updateTimer forMode:NSEventTrackingRunLoopMode];
}
}
/**
* Stops the update timer.
*/
- (void)stopUpdateTimer
{
[self.updateTimer invalidate];
self.updateTimer = nil;
}
/**
* Transforms a libhb state constant to a matching HBCore selector.
*/
- (const SEL)selectorForState:(HBState)stateValue
{
switch (stateValue)
{
case HB_STATE_WORKING:
case HB_STATE_SCANNING:
case HB_STATE_MUXING:
case HB_STATE_PAUSED:
case HB_STATE_SEARCHING:
return @selector(handleProgress);
case HB_STATE_SCANDONE:
return @selector(handleScanCompletation);
case HB_STATE_WORKDONE:
return @selector(handleWorkCompletation);
default:
NSAssert1(NO, @"[HBCore selectorForState:] unknown state %lu", stateValue);
return NULL;
}
}
/**
* This method polls libhb continuously for state changes and processes them.
* Additional processing for each state is performed in methods that start
* with 'handle' (e.g. handleHBStateScanning).
*/
- (void)stateUpdateTimer:(NSTimer *)timer
{
if (!_hb_handle)
{
// Libhb is not open so we cannot do anything.
return;
}
hb_get_state(_hb_handle, _hb_state);
if (_hb_state->state == HB_STATE_IDLE)
{
// Libhb reported HB_STATE_IDLE, so nothing interesting has happened.
return;
}
// Update HBCore state to reflect the current state of libhb
self.state = _hb_state->state;
// Determine name of the method that does further processing for this state.
SEL sel = [self selectorForState:self.state];
if (_hb_state->state == HB_STATE_WORKDONE || _hb_state->state == HB_STATE_SCANDONE)
{
// Libhb reported HB_STATE_WORKDONE or HB_STATE_SCANDONE,
// so nothing interesting will happen after this point, stop the timer.
[self stopUpdateTimer];
// Set the state to idle, because the update timer won't fire again.
self.state = HBStateIdle;
hb_system_sleep_allow(_hb_handle);
}
// Call the determined selector.
[self performSelector:sel];
}
#pragma mark - Notifications
/**
* Processes HBStateSearching state information. Current implementation just
* sends HBCoreSearchingNotification.
*/
- (void)handleProgress
{
if (self.progressHandler)
{
self.progressHandler(self.state, *(self.hb_state));
}
}
/**
* Processes HBStateScanDone state information. Current implementation just
* sends HBCoreScanDoneNotification.
*/
- (void)handleScanCompletation
{
BOOL success = [self scanDone];
if (self.completationHandler)
{
HBCoreCompletationHandler completationHandler = [self.completationHandler retain];
self.progressHandler = nil;
self.completationHandler = nil;
completationHandler(success);
[completationHandler release];
}
}
/**
* Processes HBStateWorkDone state information. Current implementation just
* sends HBCoreWorkDoneNotification.
*/
- (void)handleWorkCompletation
{
BOOL success = [self workDone];
if (self.completationHandler)
{
HBCoreCompletationHandler completationHandler = [self.completationHandler retain];
self.progressHandler = nil;
self.completationHandler = nil;
completationHandler(success);
[completationHandler release];
}
}
@end