mirror of https://github.com/HandBrake/HandBrake
527 lines
16 KiB
Objective-C
527 lines
16 KiB
Objective-C
/* HBUtilities.m $
|
|
|
|
This file is part of the HandBrake source code.
|
|
Homepage: <http://handbrake.fr/>.
|
|
It may be used under the terms of the GNU General Public License. */
|
|
|
|
#import "HBUtilities.h"
|
|
#import "HBDirectUtilities.h"
|
|
|
|
#import <Cocoa/Cocoa.h>
|
|
|
|
#include "handbrake/lang.h"
|
|
|
|
static BOOL hb_resolveBookmarks = YES;
|
|
|
|
HB_OBJC_DIRECT_MEMBERS
|
|
@interface HBURLPair : NSObject
|
|
@property (nonatomic) NSURL *URL;
|
|
@property (nonatomic) NSURL *volumeURL;
|
|
@end
|
|
|
|
HB_OBJC_DIRECT_MEMBERS
|
|
@implementation HBURLPair
|
|
@end
|
|
|
|
@implementation HBUtilities
|
|
|
|
+ (NSString *)handBrakeVersion
|
|
{
|
|
NSDictionary *infoDictionary = NSBundle.mainBundle.infoDictionary;
|
|
return [NSString stringWithFormat:@"Handbrake Version: %@ (%@)",
|
|
infoDictionary[@"CFBundleShortVersionString"],
|
|
infoDictionary[@"CFBundleVersion"]];
|
|
}
|
|
|
|
+ (NSURL *)appSupportURL
|
|
{
|
|
NSFileManager *fileManager = NSFileManager.defaultManager;
|
|
NSURL *appSupportURL = [[[fileManager URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask]
|
|
firstObject] URLByAppendingPathComponent:@"HandBrake" isDirectory:YES];
|
|
if (appSupportURL)
|
|
{
|
|
[fileManager createDirectoryAtURL:appSupportURL withIntermediateDirectories:YES attributes:nil error:NULL];
|
|
}
|
|
return appSupportURL;
|
|
}
|
|
|
|
+ (NSURL *)defaultDestinationFolderURL
|
|
{
|
|
return [[NSFileManager.defaultManager URLsForDirectory:NSMoviesDirectory inDomains:NSUserDomainMask] firstObject];
|
|
}
|
|
|
|
+ (NSURL *)documentationURL
|
|
{
|
|
return [NSURL URLWithString:@"https://handbrake.fr/docs/"];
|
|
}
|
|
|
|
+ (NSURL *)documentationBaseURL
|
|
{
|
|
return [NSURL URLWithString:@"https://handbrake.fr/docs/en/latest/"];
|
|
}
|
|
|
|
+ (void)writeToActivityLog:(const char *)format, ...
|
|
{
|
|
va_list args;
|
|
va_start(args, format);
|
|
if (format != nil)
|
|
{
|
|
char str[1024];
|
|
vsnprintf(str, 1024, format, args);
|
|
|
|
time_t _now = time(NULL);
|
|
struct tm *now = localtime(&_now);
|
|
fprintf(stderr, "[%02d:%02d:%02d] macgui: %s\n", now->tm_hour, now->tm_min, now->tm_sec, str);
|
|
}
|
|
va_end(args);
|
|
}
|
|
|
|
+ (void)writeErrorToActivityLog:(NSError *)error
|
|
{
|
|
[self writeToActivityLog:"Error domain: %s", error.domain.UTF8String];
|
|
[self writeToActivityLog:"Error code: %d", error.code];
|
|
if (error.localizedDescription)
|
|
{
|
|
[self writeToActivityLog:"Error description: %s", error.localizedDescription.UTF8String];
|
|
}
|
|
if (error.debugDescription)
|
|
{
|
|
[self writeToActivityLog:"Error debug description: %s", error.debugDescription.UTF8String];
|
|
}
|
|
}
|
|
|
|
+ (void)writeToActivityLogWithNoHeader:(NSString *)text
|
|
{
|
|
fprintf(stderr, "%s", text.UTF8String);
|
|
}
|
|
|
|
+ (void)setResolveBookmarks:(BOOL)resolveBookmarks
|
|
{
|
|
hb_resolveBookmarks = resolveBookmarks;
|
|
}
|
|
|
|
+ (BOOL)resolveBookmarks
|
|
{
|
|
return hb_resolveBookmarks;
|
|
}
|
|
|
|
+ (nullable NSURL *)URLFromBookmark:(NSData *)bookmark
|
|
{
|
|
if (hb_resolveBookmarks == NO)
|
|
{
|
|
return nil;
|
|
}
|
|
|
|
NSParameterAssert(bookmark);
|
|
|
|
NSError *error;
|
|
BOOL isStale;
|
|
|
|
NSURL *url = [NSURL URLByResolvingBookmarkData:bookmark
|
|
options:NSURLBookmarkResolutionWithSecurityScope | NSURLBookmarkResolutionWithoutMounting
|
|
relativeToURL:nil
|
|
bookmarkDataIsStale:&isStale error:&error];
|
|
|
|
if (error)
|
|
{
|
|
[HBUtilities writeErrorToActivityLog:error];
|
|
}
|
|
|
|
return isStale ? nil : url;
|
|
}
|
|
|
|
+ (nullable NSData *)bookmarkFromURL:(NSURL *)url options:(NSURLBookmarkCreationOptions)options
|
|
{
|
|
NSParameterAssert(url);
|
|
|
|
NSError *error;
|
|
|
|
NSData *bookmark = [url bookmarkDataWithOptions:options includingResourceValuesForKeys:nil relativeToURL:nil error:&error];
|
|
|
|
if (error)
|
|
{
|
|
NSString *error_message = [NSString stringWithFormat:@"Failed to create bookmark: %@", error];
|
|
[HBUtilities writeToActivityLog:"%s", error_message.UTF8String];
|
|
}
|
|
|
|
return bookmark;
|
|
}
|
|
|
|
+ (nullable NSData *)bookmarkFromURL:(NSURL *)url
|
|
{
|
|
return [HBUtilities bookmarkFromURL:url options:NSURLBookmarkCreationWithSecurityScope];
|
|
}
|
|
|
|
+ (NSURL *)commonParentOfURL:(NSURL *)a withURL:(NSURL *)b
|
|
{
|
|
if ([a isEqualTo:b])
|
|
{
|
|
return a;
|
|
}
|
|
|
|
if ([b.path hasPrefix:a.path])
|
|
{
|
|
return a;
|
|
}
|
|
|
|
NSArray<NSString *> *aComponents = a.pathComponents;
|
|
NSArray<NSString *> *bComponents = b.pathComponents;
|
|
|
|
NSMutableArray<NSString *> *commonComponents = [NSMutableArray array];
|
|
|
|
for (int idx = 0; idx < aComponents.count && idx < bComponents.count; idx++)
|
|
{
|
|
NSString *aComponent = aComponents[idx];
|
|
NSString *bComponent = bComponents[idx];
|
|
|
|
if ([aComponent isEqualToString:bComponent])
|
|
{
|
|
[commonComponents addObject:aComponent];
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return [NSURL fileURLWithPathComponents:commonComponents];
|
|
}
|
|
|
|
+ (NSArray<NSURL *> *)baseURLs:(NSArray<NSURL *> *)fileURLs
|
|
{
|
|
NSMutableArray<HBURLPair *> *pairs = [[NSMutableArray alloc] init];
|
|
NSMutableSet<NSURL *> *volumeURLs = [[NSMutableSet alloc] init];
|
|
|
|
for (NSURL *fileURL in fileURLs)
|
|
{
|
|
NSURL *volumeURL = nil;
|
|
[fileURL getResourceValue:&volumeURL forKey:NSURLVolumeURLKey error:nil];
|
|
|
|
if (volumeURL)
|
|
{
|
|
HBURLPair *pair = [[HBURLPair alloc] init];
|
|
pair.URL = fileURL.URLByDeletingLastPathComponent;
|
|
pair.volumeURL = volumeURL;
|
|
|
|
[pairs addObject:pair];
|
|
[volumeURLs addObject:volumeURL];
|
|
}
|
|
}
|
|
|
|
NSMutableArray<NSURL *> *baseURLs = [[NSMutableArray alloc] init];
|
|
|
|
for (NSURL *volumeURL in volumeURLs)
|
|
{
|
|
NSURL *baseURL = nil;
|
|
for (HBURLPair *pair in pairs)
|
|
{
|
|
if ([pair.volumeURL isEqualTo:volumeURL])
|
|
{
|
|
if (baseURL == nil)
|
|
{
|
|
baseURL = pair.URL;
|
|
}
|
|
else
|
|
{
|
|
baseURL = [HBUtilities commonParentOfURL:baseURL withURL:pair.URL];
|
|
}
|
|
}
|
|
}
|
|
if (baseURL)
|
|
{
|
|
[baseURLs addObject:baseURL];
|
|
}
|
|
}
|
|
|
|
return baseURLs;
|
|
}
|
|
|
|
+ (NSURL *)eyetvMediaURL:(NSURL *)url
|
|
{
|
|
// We're looking at an EyeTV package - try to open its enclosed .mpg or .ts media file
|
|
NSString *mediaName;
|
|
NSUInteger count = [[url.path stringByAppendingString:@"/"] completePathIntoString:&mediaName
|
|
caseSensitive:YES
|
|
matchesIntoArray:nil
|
|
filterTypes:@[@"mpg", @"ts"]];
|
|
if (count > 0)
|
|
{
|
|
// Found an mpeg inside the eyetv package, make it our scan path
|
|
return [NSURL fileURLWithPath:mediaName];
|
|
}
|
|
else
|
|
{
|
|
return nil;
|
|
}
|
|
}
|
|
|
|
+ (NSArray<NSURL *> *)expandURLs:(NSArray<NSURL *> *)fileURLs recursive:(BOOL)recursive
|
|
{
|
|
NSFileManager *manager = NSFileManager.defaultManager;
|
|
NSMutableArray<NSURL *> *mutableFileURLs = [NSMutableArray array];
|
|
|
|
NSDirectoryEnumerationOptions options = NSDirectoryEnumerationSkipsHiddenFiles |
|
|
NSDirectoryEnumerationSkipsPackageDescendants |
|
|
NSDirectoryEnumerationSkipsSubdirectoryDescendants;
|
|
|
|
// Check first if it's a DVD-Video or Bluray
|
|
if (fileURLs.count == 1)
|
|
{
|
|
NSURL *directoryURL = fileURLs.firstObject;
|
|
|
|
NSNumber *isDirectory = nil;
|
|
[directoryURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:nil];
|
|
|
|
if (isDirectory.boolValue == YES)
|
|
{
|
|
if ([directoryURL.pathExtension isEqualToString:@"eyetv"])
|
|
{
|
|
NSURL *eyetvMediaURL = [HBUtilities eyetvMediaURL:directoryURL];
|
|
if (eyetvMediaURL)
|
|
{
|
|
return @[eyetvMediaURL];
|
|
}
|
|
}
|
|
|
|
if ([directoryURL.pathExtension isEqualToString:@"dvdmedia"] ||
|
|
[directoryURL.lastPathComponent isEqualToString:@"VIDEO_TS"])
|
|
{
|
|
return fileURLs;
|
|
}
|
|
|
|
NSArray<NSURL *> *content = [manager contentsOfDirectoryAtURL:directoryURL
|
|
includingPropertiesForKeys:nil
|
|
options:options
|
|
error:NULL];
|
|
|
|
for (NSURL *url in content)
|
|
{
|
|
if ([url.lastPathComponent isEqualToString:@"VIDEO_TS"] ||
|
|
[url.lastPathComponent isEqualToString:@"BDMV"])
|
|
{
|
|
return fileURLs;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (recursive)
|
|
{
|
|
options &= ~NSDirectoryEnumerationSkipsSubdirectoryDescendants;
|
|
}
|
|
|
|
// If not, recursively enumerate all the files and directories
|
|
for (NSURL *url in fileURLs)
|
|
{
|
|
NSNumber *isDirectory = nil;
|
|
[url getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:nil];
|
|
|
|
if (isDirectory.boolValue == NO)
|
|
{
|
|
[mutableFileURLs addObject:url];
|
|
}
|
|
else if ([url.pathExtension isEqualToString:@"eyetv"])
|
|
{
|
|
NSURL *eyetvMediaURL = [HBUtilities eyetvMediaURL:url];
|
|
if (eyetvMediaURL)
|
|
{
|
|
[mutableFileURLs addObject:eyetvMediaURL];
|
|
}
|
|
}
|
|
else if ([url.pathExtension isEqualToString:@"dvdmedia"] ||
|
|
[url.lastPathComponent isEqualToString:@"VIDEO_TS"])
|
|
{
|
|
// Skip
|
|
}
|
|
else
|
|
{
|
|
NSDirectoryEnumerator<NSURL *> *enumerator = [manager enumeratorAtURL:url
|
|
includingPropertiesForKeys:@[NSURLIsDirectoryKey]
|
|
options:options
|
|
errorHandler:NULL];
|
|
|
|
for (NSURL *enumeratorURL in enumerator)
|
|
{
|
|
[enumeratorURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:nil];
|
|
|
|
if (isDirectory.boolValue == YES)
|
|
{
|
|
if ([enumeratorURL.pathExtension isEqualToString:@"eyetv"])
|
|
{
|
|
NSURL *eyetvMediaURL = [HBUtilities eyetvMediaURL:enumeratorURL];
|
|
if (eyetvMediaURL)
|
|
{
|
|
[mutableFileURLs addObject:enumeratorURL];
|
|
}
|
|
[enumerator skipDescendants];
|
|
}
|
|
else if ([enumeratorURL.pathExtension isEqualToString:@"dvdmedia"] ||
|
|
[enumeratorURL.lastPathComponent isEqualToString:@"VIDEO_TS"])
|
|
{
|
|
[enumerator skipDescendants];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
[mutableFileURLs addObject:enumeratorURL];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
[mutableFileURLs sortUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
|
|
return [[obj1 path] localizedStandardCompare:[obj2 path]];
|
|
}];
|
|
|
|
return mutableFileURLs;
|
|
}
|
|
|
|
+ (NSArray<NSString *> *)supportedExtensions
|
|
{
|
|
return @[@"srt", @"ssa", @"ass"];
|
|
}
|
|
|
|
+ (NSArray<NSURL *> *)extractURLs:(NSArray<NSURL *> *)fileURLs withExtension:(NSArray<NSString *> *)extensions
|
|
{
|
|
NSMutableArray<NSURL *> *extractedFileURLs = [NSMutableArray array];
|
|
|
|
for (NSURL *fileURL in fileURLs)
|
|
{
|
|
BOOL isMatch = NO;
|
|
for (NSString *extension in extensions)
|
|
{
|
|
if ([fileURL.pathExtension caseInsensitiveCompare:extension] == NSOrderedSame)
|
|
{
|
|
isMatch = YES;
|
|
break;
|
|
}
|
|
}
|
|
if (isMatch)
|
|
{
|
|
[extractedFileURLs addObject:fileURL];
|
|
}
|
|
}
|
|
|
|
return extractedFileURLs;
|
|
}
|
|
|
|
+ (NSArray<NSURL *> *)trimURLs:(NSArray<NSURL *> *)fileURLs withExtension:(NSArray<NSString *> *)excludedExtensions
|
|
{
|
|
NSMutableArray<NSURL *> *trimmedURLs = [NSMutableArray array];
|
|
|
|
for (NSURL *fileURL in fileURLs)
|
|
{
|
|
BOOL excluded = NO;
|
|
NSString *extension = fileURL.pathExtension;
|
|
|
|
if (extension)
|
|
{
|
|
for (NSString *excludedExtension in excludedExtensions)
|
|
{
|
|
if ([extension caseInsensitiveCompare:excludedExtension] == NSOrderedSame)
|
|
{
|
|
excluded = YES;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (excluded == NO)
|
|
{
|
|
[trimmedURLs addObject:fileURL];
|
|
}
|
|
}
|
|
return trimmedURLs;
|
|
}
|
|
|
|
+ (NSString *)isoCodeForNativeLang:(NSString *)language
|
|
{
|
|
const iso639_lang_t *lang = lang_get_next(NULL);
|
|
for (lang = lang_get_next(lang); lang != NULL; lang = lang_get_next(lang))
|
|
{
|
|
NSString *nativeLanguage = strlen(lang->native_name) ? @(lang->native_name) : @(lang->eng_name);
|
|
|
|
if ([language isEqualToString:nativeLanguage])
|
|
{
|
|
return @(lang->iso639_2);
|
|
}
|
|
}
|
|
|
|
return @"und";
|
|
}
|
|
|
|
+ (NSString *)iso6392CodeFor:(NSString *)aLanguage
|
|
{
|
|
iso639_lang_t *lang = lang_for_english(aLanguage.UTF8String);
|
|
if (lang)
|
|
{
|
|
return @(lang->iso639_2);
|
|
}
|
|
return @"und";
|
|
}
|
|
|
|
+ (NSString *)languageCodeForIso6392Code:(NSString *)aLanguage
|
|
{
|
|
iso639_lang_t *lang = lang_for_code2(aLanguage.UTF8String);
|
|
if (lang)
|
|
{
|
|
return @(lang->eng_name);
|
|
}
|
|
return @"Unknown";
|
|
}
|
|
|
|
+ (HBPrivacyConsentState)determinePermissionToAutomateTarget:(NSString *)bundleIdentifier promptIfNeeded:(BOOL)promptIfNeeded
|
|
{
|
|
if (@available(macOS 10.14, *))
|
|
{
|
|
const char *identifierCString = bundleIdentifier.UTF8String;
|
|
AEAddressDesc addressDesc;
|
|
|
|
if ([bundleIdentifier isEqualToString:@"com.apple.systemevents"])
|
|
{
|
|
// Mare sure system events is running, if not the consent alert will not be shown.
|
|
BOOL result = [NSWorkspace.sharedWorkspace launchAppWithBundleIdentifier:bundleIdentifier options:0 additionalEventParamDescriptor:nil launchIdentifier:NULL];
|
|
if (result == NO)
|
|
{
|
|
[HBUtilities writeToActivityLog:"Automation: couldn't launch %s.", bundleIdentifier.UTF8String];
|
|
}
|
|
}
|
|
|
|
OSErr descResult = AECreateDesc(typeApplicationBundleID, identifierCString, strlen(identifierCString), &addressDesc);
|
|
|
|
if (descResult == noErr)
|
|
{
|
|
OSStatus permission = AEDeterminePermissionToAutomateTarget(&addressDesc, typeWildCard, typeWildCard, promptIfNeeded);
|
|
AEDisposeDesc(&addressDesc);
|
|
|
|
HBPrivacyConsentState result;
|
|
|
|
switch (permission)
|
|
{
|
|
case -1744: //errAEEventWouldRequireUserConsent: 10.14 or later
|
|
[HBUtilities writeToActivityLog:"Automation: request user consent for %s.", bundleIdentifier.UTF8String];
|
|
result = HBPrivacyConsentStateUnknown;
|
|
break;
|
|
case noErr:
|
|
[HBUtilities writeToActivityLog:"Automation: permission granted for %s.", bundleIdentifier.UTF8String];
|
|
result = HBPrivacyConsentStateGranted;
|
|
break;
|
|
case errAEEventNotPermitted:
|
|
[HBUtilities writeToActivityLog:"Automation: permission not granted for %s.", bundleIdentifier.UTF8String];
|
|
result = HBPrivacyConsentStateDenied;
|
|
break;
|
|
case procNotFound:
|
|
default:
|
|
[HBUtilities writeToActivityLog:"Automation: permission unknown."];
|
|
result = HBPrivacyConsentStateUnknown;
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return HBPrivacyConsentStateGranted;
|
|
}
|
|
|
|
@end
|