/* 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 "HBDVDDetector.h"
#import "HBUtilities.h"
#include
// These constants specify various status notifications sent by HBCore
/// Notification sent to update status while scanning. Matches HB_STATE_SCANNING constant in libhb.
NSString *HBCoreScanningNotification = @"HBCoreScanningNotification";
/// Notification sent after scanning is complete. Matches HB_STATE_SCANDONE constant in libhb.
NSString *HBCoreScanDoneNotification = @"HBCoreScanDoneNotification";
/// Notification sent to update status while searching. Matches HB_STATE_SEARCHING constant in libhb.
NSString *HBCoreSearchingNotification = @"HBCoreSearchingNotification";
/// Notification sent to update status while encoding. Matches HB_STATE_WORKING constant in libhb.
NSString *HBCoreWorkingNotification = @"HBCoreWorkingNotification";
/// Notification sent when encoding is paused. Matches HB_STATE_PAUSED constant in libhb.
NSString *HBCorePausedNotification = @"HBCorePausedNotification";
/// Notification sent after encoding is complete. Matches HB_STATE_WORKDONE constant in libhb.
NSString *HBCoreWorkDoneNotification = @"HBCoreWorkDoneNotification";
/// Notification sent to update status while muxing. Matches HB_STATE_MUXING constant in libhb.
NSString *HBCoreMuxingNotification = @"HBCoreMuxingNotification";
/**
* Private methods of HBCore.
*/
@interface HBCore ()
/// 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;
- (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)scan:(NSURL *)url titleNum:(NSUInteger)titleNum previewsNum:(NSUInteger)previewsNum minTitleDuration:(NSUInteger)minTitleDuration;
{
// Start the timer to handle libhb state changes
[self startUpdateTimer];
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.
*/
- (void)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];
}
- (void)cancelScan
{
hb_scan_stop(_hb_handle);
[HBUtilities writeToActivityLog:"%s scan cancelled", self.name.UTF8String];
}
#pragma mark - Encodes
- (void)encodeJob:(HBJob *)job
{
hb_job_t *hb_job = job.hb_job;
[HBUtilities writeToActivityLog: "processNewQueueEncode number of passes expected is: %d", (job.video.twoPass + 1)];
hb_job_set_file(hb_job, job.destURL.path.fileSystemRepresentation);
// If scanning we need to do some extra setup of the job.
if (hb_job->indepth_scan == 1)
{
char *encoder_preset_tmp = hb_job->encoder_preset != NULL ? strdup(hb_job->encoder_preset) : NULL;
char *encoder_tune_tmp = hb_job->encoder_tune != NULL ? strdup(hb_job->encoder_tune) : NULL;
char *encoder_options_tmp = hb_job->encoder_options != NULL ? strdup(hb_job->encoder_options) : NULL;
char *encoder_profile_tmp = hb_job->encoder_profile != NULL ? strdup(hb_job->encoder_profile) : NULL;
char *encoder_level_tmp = hb_job->encoder_level != NULL ? strdup(hb_job->encoder_level) : NULL;
/*
* When subtitle scan is enabled do a fast pre-scan job
* which will determine which subtitles to enable, if any.
*/
hb_job_set_encoder_preset (hb_job, NULL);
hb_job_set_encoder_tune (hb_job, NULL);
hb_job_set_encoder_options(hb_job, NULL);
hb_job_set_encoder_profile(hb_job, NULL);
hb_job_set_encoder_level (hb_job, NULL);
hb_job->pass = -1;
hb_add(self.hb_handle, hb_job);
/*
* reset the advanced settings
*/
hb_job_set_encoder_preset (hb_job, encoder_preset_tmp);
hb_job_set_encoder_tune (hb_job, encoder_tune_tmp);
hb_job_set_encoder_options(hb_job, encoder_options_tmp);
hb_job_set_encoder_profile(hb_job, encoder_profile_tmp);
hb_job_set_encoder_level (hb_job, encoder_level_tmp);
free(encoder_preset_tmp);
free(encoder_tune_tmp);
free(encoder_options_tmp);
free(encoder_profile_tmp);
free(encoder_level_tmp);
}
if (job.video.twoPass)
{
hb_job->indepth_scan = 0;
hb_job->pass = 1;
hb_add(self.hb_handle, hb_job);
hb_job->pass = 2;
hb_add(self.hb_handle, hb_job);
}
else
{
hb_job->indepth_scan = 0;
hb_job->pass = 0;
hb_add(self.hb_handle, hb_job);
}
// Free the job
hb_job_close(&hb_job);
[self start];
}
- (void)start
{
// Start the timer to handle libhb state changes
[self startUpdateTimer];
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 work started", self.name.UTF8String];
}
- (void)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];
}
- (void)stop
{
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.
*/
- (void)startUpdateTimer
{
if (!self.updateTimer)
{
self.updateTimer = [NSTimer scheduledTimerWithTimeInterval:0.5
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:
return @selector(handleHBStateWorking);
case HB_STATE_SCANNING:
return @selector(handleHBStateScanning);
case HB_STATE_MUXING:
return @selector(handleHBStateMuxing);
case HB_STATE_PAUSED:
return @selector(handleHBStatePaused);
case HB_STATE_SEARCHING:
return @selector(handleHBStateSearching);
case HB_STATE_SCANDONE:
return @selector(handleHBStateScanDone);
case HB_STATE_WORKDONE:
return @selector(handleHBStateWorkDone);
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 HBStateScanning state information. Current implementation just
* sends HBCoreScanningNotification.
*/
- (void)handleHBStateScanning
{
[[NSNotificationCenter defaultCenter] postNotificationName:HBCoreScanningNotification object:self];
}
/**
* Processes HBStateScanDone state information. Current implementation just
* sends HBCoreScanDoneNotification.
*/
- (void)handleHBStateScanDone
{
[self scanDone];
[[NSNotificationCenter defaultCenter] postNotificationName:HBCoreScanDoneNotification object:self];
}
/**
* Processes HBStateWorking state information. Current implementation just
* sends HBCoreWorkingNotification.
*/
- (void)handleHBStateWorking
{
[[NSNotificationCenter defaultCenter] postNotificationName:HBCoreWorkingNotification object:self];
}
/**
* Processes HBStatePaused state information. Current implementation just
* sends HBCorePausedNotification.
*/
- (void)handleHBStatePaused
{
[[NSNotificationCenter defaultCenter] postNotificationName:HBCorePausedNotification object:self];
}
/**
* Processes HBStateWorkDone state information. Current implementation just
* sends HBCoreWorkDoneNotification.
*/
- (void)handleHBStateWorkDone
{
[self workDone];
[[NSNotificationCenter defaultCenter] postNotificationName:HBCoreWorkDoneNotification object:self];
}
/**
* Processes HBStateMuxing state information. Current implementation just
* sends HBCoreMuxingNotification.
*/
- (void)handleHBStateMuxing
{
[[NSNotificationCenter defaultCenter] postNotificationName:HBCoreMuxingNotification object:self];
}
/**
* Processes HBStateSearching state information. Current implementation just
* sends HBCoreSearchingNotification.
*/
- (void)handleHBStateSearching
{
[[NSNotificationCenter defaultCenter] postNotificationName:HBCoreSearchingNotification object:self];
}
@end