/* HBUtilities.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 "HBUtilities.h" #import #import "HBTitle.h" #import "HBJob.h" #include "common.h" #include "lang.h" static NSDateFormatter *_timeFormatter = nil; static NSDateFormatter *_dateFormatter = nil; static NSDateFormatter *_releaseDateFormatter = nil; @implementation HBUtilities + (void)initialize { if (self == [HBUtilities class]) { _dateFormatter = [[NSDateFormatter alloc] init]; [_dateFormatter setDateStyle:NSDateFormatterShortStyle]; [_dateFormatter setTimeStyle:NSDateFormatterNoStyle]; _timeFormatter = [[NSDateFormatter alloc] init]; [_timeFormatter setDateStyle:NSDateFormatterNoStyle]; [_timeFormatter setTimeStyle:NSDateFormatterShortStyle]; _releaseDateFormatter = [[NSDateFormatter alloc] init]; [_releaseDateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'Z'"]; } } + (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"]; if (appSupportURL && ![fileManager fileExistsAtPath:appSupportURL.path]) { [fileManager createDirectoryAtPath:appSupportURL.path withIntermediateDirectories:YES attributes:nil error:NULL]; } return appSupportURL; } + (NSURL *)documentationURL { return [NSURL URLWithString:@"https://handbrake.fr/docs/en/1.2.0/"]; } + (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); } + (nullable NSURL *)URLFromBookmark:(NSData *)bookmark { NSParameterAssert(bookmark); NSError *error; BOOL isStale; NSURL *url = [NSURL URLByResolvingBookmarkData:bookmark options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&error]; if (error) { NSString *error_message = [NSString stringWithFormat:@"Failed to resolved bookmark: %@", error]; [HBUtilities writeToActivityLog:"%s", error_message.UTF8String]; } 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 *)mediaURLFromURL:(NSURL *)URL { NSURL *mediaURL = URL; // We check to see if the chosen file at path is a package if ([[NSWorkspace sharedWorkspace] isFilePackageAtPath:URL.path]) { [HBUtilities writeToActivityLog:"trying to open a package at: %s", URL.path.UTF8String]; // We check to see if this is an .eyetv package if ([URL.pathExtension isEqualToString:@"eyetv"]) { [HBUtilities writeToActivityLog:"trying to open eyetv package"]; // We're looking at an EyeTV package - try to open its enclosed .mpg media file NSString *mpgname; NSUInteger n = [[URL.path stringByAppendingString: @"/"] completePathIntoString: &mpgname caseSensitive: YES matchesIntoArray: nil filterTypes: @[@"mpg"]]; if (n > 0) { // Found an mpeg inside the eyetv package, make it our scan path [HBUtilities writeToActivityLog:"found mpeg in eyetv package"]; mediaURL = [NSURL fileURLWithPath:mpgname]; } else { // We did not find an mpeg file in our package, so we do not call performScan [HBUtilities writeToActivityLog:"no valid mpeg in eyetv package"]; } } // We check to see if this is a .dvdmedia package else if ([URL.pathExtension isEqualToString:@"dvdmedia"]) { // path IS a package - but dvdmedia packages can be treaded like normal directories [HBUtilities writeToActivityLog:"trying to open dvdmedia package"]; } else { // The package is not an eyetv package, try to open it anyway [HBUtilities writeToActivityLog:"not a known to package"]; } } #ifndef __SANDBOX_ENABLED__ else { // path is not a package, so we call perform scan directly on our file if ([URL.lastPathComponent isEqualToString:@"VIDEO_TS"]) { [HBUtilities writeToActivityLog:"trying to open video_ts folder (video_ts folder chosen)"]; // If VIDEO_TS Folder is chosen, choose its parent folder for the source display name mediaURL = URL.URLByDeletingLastPathComponent; } else { [HBUtilities writeToActivityLog:"trying to open a folder or file"]; } } #endif return mediaURL; } + (nullable NSDate *)releaseDate:(HBTitle *)title { if ([title.metadata.releaseDate length] == 0) { return nil; } else { return [_releaseDateFormatter dateFromString:title.metadata.releaseDate]; } } + (NSString *)automaticNameForJob:(HBJob *)job { HBTitle *title = job.title; NSDate *releaseDate = [self releaseDate:title]; if (releaseDate == nil) { NSDictionary* fileAttribs = [[NSFileManager defaultManager] attributesOfItemAtPath:job.fileURL.path error:nil]; releaseDate = [fileAttribs objectForKey:NSFileCreationDate]; } // Generate a new file name NSString *fileName = [HBUtilities automaticNameForSource:title.name title:title.index chapters:NSMakeRange(job.range.chapterStart + 1, job.range.chapterStop + 1) quality:job.video.qualityType ? job.video.quality : 0 bitrate:!job.video.qualityType ? job.video.avgBitrate : 0 videoCodec:job.video.encoder creationDate:releaseDate]; return fileName; } + (NSString *)automaticExtForJob:(HBJob *)job { NSString *extension = @(hb_container_get_default_extension(job.container)); if (job.container & HB_MUX_MASK_MP4) { BOOL anyCodecAC3 = [job.audio anyCodecMatches:HB_ACODEC_AC3] || [job.audio anyCodecMatches:HB_ACODEC_AC3_PASS]; // Chapter markers are enabled if the checkbox is ticked and we are doing p2p or we have > 1 chapter BOOL chapterMarkers = (job.chaptersEnabled) && (job.range.type != HBRangeTypeChapters || job.range.chapterStart < job.range.chapterStop); NSString *defaultExtension = [[NSUserDefaults standardUserDefaults] objectForKey:@"DefaultMpegExtension"]; if ([defaultExtension isEqualToString:@".m4v"] || ((YES == anyCodecAC3 || YES == chapterMarkers) && [defaultExtension isEqualToString:@"Auto"])) { extension = @"m4v"; } } return extension; } + (NSString *)defaultNameForJob:(HBJob *)job { // Generate a new file name NSString *fileName = job.title.name; // If Auto Naming is on. We create an output filename by using the // format set int he preferences. if ([[NSUserDefaults standardUserDefaults] boolForKey:@"DefaultAutoNaming"]) { fileName = [self automaticNameForJob:job]; } // use the correct extension based on the container NSString *ext = [self automaticExtForJob:job]; fileName = [fileName stringByAppendingPathExtension:ext]; return fileName; } + (NSString *)automaticNameForSource:(NSString *)sourceName title:(NSUInteger)title chapters:(NSRange)chaptersRange quality:(double)quality bitrate:(int)bitrate videoCodec:(uint32_t)codec creationDate:(NSDate *)creationDate { NSMutableString *name = [[NSMutableString alloc] init]; // The format array contains the tokens as NSString NSArray *format = [[NSUserDefaults standardUserDefaults] objectForKey:@"HBAutoNamingFormat"]; for (NSString *formatKey in format) { if ([formatKey isEqualToString:@"{Source}"]) { if ([[NSUserDefaults standardUserDefaults] boolForKey:@"HBAutoNamingRemoveUnderscore"]) { sourceName = [sourceName stringByReplacingOccurrencesOfString:@"_" withString:@" "]; } if ([[NSUserDefaults standardUserDefaults] boolForKey:@"HBAutoNamingRemovePunctuation"]) { sourceName = [sourceName stringByReplacingOccurrencesOfString:@"-" withString:@""]; sourceName = [sourceName stringByReplacingOccurrencesOfString:@"." withString:@""]; sourceName = [sourceName stringByReplacingOccurrencesOfString:@"," withString:@""]; sourceName = [sourceName stringByReplacingOccurrencesOfString:@";" withString:@""]; } if ([[NSUserDefaults standardUserDefaults] boolForKey:@"HBAutoNamingTitleCase"]) { sourceName = [sourceName capitalizedString]; } [name appendString:sourceName]; } else if ([formatKey isEqualToString:@"{Title}"]) { [name appendFormat:@"%lu", (unsigned long)title]; } else if ([formatKey isEqualToString:@"{Date}"]) { NSDate *date = [NSDate date]; NSString *dateString = [[_dateFormatter stringFromDate:date] stringByReplacingOccurrencesOfString:@"/" withString:@"-"]; [name appendString:dateString]; } else if ([formatKey isEqualToString:@"{Time}"]) { NSDate *date = [NSDate date]; [name appendString:[_timeFormatter stringFromDate:date]]; } else if ([formatKey isEqualToString:@"{Creation-Date}"]) { NSString *dateString = [[_dateFormatter stringFromDate:creationDate] stringByReplacingOccurrencesOfString:@"/" withString:@"-"]; [name appendString:dateString]; } else if ([formatKey isEqualToString:@"{Creation-Time}"]) { [name appendString:[_timeFormatter stringFromDate:creationDate]]; } else if ([formatKey isEqualToString:@"{Chapters}"]) { if (chaptersRange.location == chaptersRange.length) { [name appendFormat:@"%lu", (unsigned long)chaptersRange.location]; } else { [name appendFormat:@"%lu-%lu", (unsigned long)chaptersRange.location, (unsigned long)chaptersRange.length]; } } else if ([formatKey isEqualToString:@"{Quality/Bitrate}"]) { if (bitrate) { [name appendString:@"abr"]; [name appendString:[NSString stringWithFormat:@"%d", bitrate]]; } else { // Append the right quality suffix for the selected codec (rf/qp) [name appendString:[@(hb_video_quality_get_name(codec)) lowercaseString]]; [name appendString:[NSString stringWithFormat:@"%0.2f", quality]]; } } else { [name appendString:formatKey]; } } return [name copy]; } + (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"; } @end