#include "pch.h" #include "objc_utils.h" #include "log.h" #include "app.h" #include #include #include #include #ifdef __OBJC__ #import @implementation PathWithModDate @end @implementation ObjcUtils + (NSArray*)getFilesAtPathSortedByModificationDate:(NSString*)folderPath { NSArray *allPaths = [NSFileManager.defaultManager contentsOfDirectoryAtPath:folderPath error:nil]; NSMutableArray *sortedPaths = [NSMutableArray new]; for (NSString *path in allPaths) { NSString *fullPath = [folderPath stringByAppendingPathComponent:path]; NSDictionary *attr = [NSFileManager.defaultManager attributesOfItemAtPath:fullPath error:nil]; NSDate *modDate = [attr objectForKey:NSFileModificationDate]; PathWithModDate *pathWithDate = [[PathWithModDate alloc] init]; pathWithDate.path = fullPath; pathWithDate.modDate = modDate; [sortedPaths addObject:pathWithDate]; } [sortedPaths sortUsingComparator:^(PathWithModDate *path1, PathWithModDate *path2) { // Descending (most recently modified first) return [path2.modDate compare:path1.modDate]; }]; return sortedPaths; } @end #endif std::string base_address; std::atomic_flag handler_executed; void save_recovery() { auto t = std::time(nullptr); auto tm = *std::localtime(&t); std::ostringstream oss; oss << std::put_time(&tm, "%d-%m-%Y %H-%M-%S"); auto path = App::I->data_path + "/" + App::I->doc_name + "-recovery (" + oss.str() + ").ppi"; if (App::I->canvas && App::I->canvas->m_canvas) App::I->canvas->m_canvas->project_save_thread(path, false); } void exception_handler(NSException *exception) { if (handler_executed.test_and_set()) exit(0); LOG("exception %s\n", [[exception name] cStringUsingEncoding:NSUTF8StringEncoding]); NSString* callstack = [[NSThread callStackSymbols] componentsJoinedByString:@"\n"]; LOG("base:\n%s\ncallstack:\n%s", base_address.c_str(), [callstack cStringUsingEncoding:NSUTF8StringEncoding]); save_recovery(); uinstall_global_handlers(); //exit(0); } static void signal_handler (int signo) { if (handler_executed.test_and_set()) exit(0); LOG("signal %d\n", signo); NSString* callstack = [[NSThread callStackSymbols] componentsJoinedByString:@"\n"]; LOG("base:\n%s\ncallstack:\n%s", base_address.c_str(), [callstack cStringUsingEncoding:NSUTF8StringEncoding]); save_recovery(); uinstall_global_handlers(); //exit(0); } void SignalHandler(int sig, siginfo_t *info, void *context) { save_recovery(); //exit(0); } void TerminateHandler() { save_recovery(); //exit(0); } void uinstall_global_handlers() { signal(SIGABRT, SIG_DFL); signal(SIGILL, SIG_DFL); signal(SIGSEGV, SIG_DFL); signal(SIGFPE, SIG_DFL); signal(SIGBUS, SIG_DFL); signal(SIGPIPE, SIG_DFL); } void install_global_handlers() { NSString* callstack = [[NSThread callStackSymbols] componentsJoinedByString:@"\n"]; base_address = [callstack cStringUsingEncoding:NSUTF8StringEncoding]; NSSetUncaughtExceptionHandler(exception_handler); signal(SIGABRT, signal_handler); signal(SIGILL, signal_handler); signal(SIGSEGV, signal_handler); signal(SIGFPE, signal_handler); signal(SIGBUS, signal_handler); signal(SIGPIPE, signal_handler); signal(SIGQUIT, signal_handler); signal(SIGTRAP, signal_handler); signal(SIGEMT, signal_handler); signal(SIGSYS, signal_handler); signal(SIGALRM, signal_handler); signal(SIGXCPU, signal_handler); signal(SIGXFSZ, signal_handler); /* struct sigaction mySigAction; mySigAction.sa_sigaction = SignalHandler; mySigAction.sa_flags = SA_SIGINFO; sigemptyset(&mySigAction.sa_mask); sigaction(SIGQUIT, &mySigAction, NULL); sigaction(SIGILL, &mySigAction, NULL); sigaction(SIGTRAP, &mySigAction, NULL); sigaction(SIGABRT, &mySigAction, NULL); sigaction(SIGEMT, &mySigAction, NULL); sigaction(SIGFPE, &mySigAction, NULL); sigaction(SIGBUS, &mySigAction, NULL); sigaction(SIGSEGV, &mySigAction, NULL); sigaction(SIGSYS, &mySigAction, NULL); sigaction(SIGPIPE, &mySigAction, NULL); sigaction(SIGALRM, &mySigAction, NULL); sigaction(SIGXCPU, &mySigAction, NULL); sigaction(SIGXFSZ, &mySigAction, NULL); std::set_terminate(TerminateHandler); */ } void delete_all_files_in_path(const std::string& source_path) { NSString *path = [NSString stringWithUTF8String:source_path.c_str()]; NSDirectoryEnumerator* en = [[NSFileManager defaultManager] enumeratorAtPath:path]; LOG("delete all files from %s", source_path.c_str()); while (NSString* file = [en nextObject]) { NSString* file_path = [path stringByAppendingPathComponent:file]; [[NSFileManager defaultManager] removeItemAtPath:file_path error:nil]; LOG("delete: %s", [file_path UTF8String]); } } static void stillImageDataReleaseCallback(void *releaseRefCon, const void *baseAddress) { free((void *)baseAddress); } void export_mp4(const std::string& rec_path, int width, int height, int tot, void(^progress_callback)(float)) { int progress = 0; NSString* mov_path = [NSString stringWithFormat:@"%s/out.mp4", rec_path.c_str()]; if ([[NSFileManager defaultManager] fileExistsAtPath:mov_path]) { NSLog(@"remove existing mp4"); [[NSFileManager defaultManager] removeItemAtPath:mov_path error:nil]; } NSURL* url = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%s/out.mp4", rec_path.c_str()]]; AVAssetWriter* writer = [AVAssetWriter assetWriterWithURL:url fileType:AVFileTypeMPEG4 error:nil]; writer.shouldOptimizeForNetworkUse = NO; NSDictionary *videoCompressionSettings = @{ AVVideoCodecKey : AVVideoCodecH264, AVVideoWidthKey : @(width), AVVideoHeightKey : @(height), AVVideoCompressionPropertiesKey : @{ AVVideoAverageBitRateKey : @(8<<20) } }; if (![writer canApplyOutputSettings:videoCompressionSettings forMediaType:AVMediaTypeVideo]) { NSLog(@"Couldn't add asset writer video input."); return; } AVAssetWriterInput* input = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:videoCompressionSettings sourceFormatHint:nil]; input.expectsMediaDataInRealTime = YES; NSDictionary *adaptorDict = @{ (id)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA), (id)kCVPixelBufferWidthKey : @(width), (id)kCVPixelBufferHeightKey : @(height) }; AVAssetWriterInputPixelBufferAdaptor* _pixelBufferAdaptor = [[AVAssetWriterInputPixelBufferAdaptor alloc] initWithAssetWriterInput:input sourcePixelBufferAttributes:adaptorDict]; // Add asset writer input to asset writer if (![writer canAddInput:input]) { NSLog(@"Couldn't add input to writer."); return; } [writer addInput:input]; CMTime t; t.timescale = 30; t.flags = kCMTimeFlags_Valid; t.epoch = 0; t.value = 0; //[writer startSessionAtSourceTime:t]; CVPixelBufferRef buff = NULL; uint8_t* data = (uint8_t*)calloc(1, width * height * 4); CVPixelBufferCreateWithBytes(kCFAllocatorDefault, width, height, kCVPixelFormatType_32RGBA, data, width * 4, stillImageDataReleaseCallback, nil, nil, &buff); OSStatus err = CVPixelBufferPoolCreatePixelBuffer(nil, _pixelBufferAdaptor.pixelBufferPool, &buff); if (writer.status == AVAssetWriterStatusUnknown) { // If the asset writer status is unknown, implies writing hasn't started yet, hence start writing with start time as the buffer's presentation timestamp if ([writer startWriting]) { [writer startSessionAtSourceTime:t]; } } if (writer.status == AVAssetWriterStatusWriting) { for (int i = 0; i < tot; i++) { // If the asset writer status is writing, append sample buffer to its corresponding asset writer input if (input.readyForMoreMediaData) { char path[256]; snprintf(path, sizeof(path), "%s/%04d.jpg", rec_path.c_str(), i); NSString* img_path = [NSString stringWithUTF8String:path]; NSLog(@"frame: %@", img_path); #if __OSX__ NSImage *image = [[NSImage alloc] initWithContentsOfFile:img_path]; if (!image) break; NSRect imageRect = NSMakeRect(0, 0, image.size.width, image.size.height); CGImageRef cgImage = [image CGImageForProposedRect:&imageRect context:NULL hints:nil]; #elif __IOS__ UIImage* image = [UIImage imageNamed:img_path]; if (!image) break; CGImageRef cgImage = image.CGImage; #endif NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool:YES], kCVPixelBufferCGImageCompatibilityKey, [NSNumber numberWithBool:YES], kCVPixelBufferCGBitmapContextCompatibilityKey, nil]; CGSize sz = image.size; CVPixelBufferRef pxbuffer = NULL; { CGImageRef image = cgImage; CGSize size = sz; CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, size.width, size.height, kCVPixelFormatType_32ARGB, (__bridge CFDictionaryRef) options, &pxbuffer); // CVReturn status = CVPixelBufferPoolCreatePixelBuffer(NULL, adaptor.pixelBufferPool, &pxbuffer); //NSParameterAssert(status == kCVReturnSuccess && pxbuffer != NULL); CVPixelBufferLockBaseAddress(pxbuffer, 0); void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer); //NSParameterAssert(pxdata != NULL); CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef context = CGBitmapContextCreate(pxdata, size.width, size.height, 8, 4*size.width, rgbColorSpace, kCGImageAlphaPremultipliedFirst); //NSParameterAssert(context); CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image)), image); CGColorSpaceRelease(rgbColorSpace); CGContextRelease(context); CVPixelBufferUnlockBaseAddress(pxbuffer, 0); } t.value = i; if (![_pixelBufferAdaptor appendPixelBuffer:pxbuffer withPresentationTime:t]) { NSLog(@"error %@", [writer.error localizedFailureReason]); } CFRelease(pxbuffer); progress++; progress_callback((float)progress / tot * 100.f); } } [input markAsFinished]; [writer finishWritingWithCompletionHandler:^{ NSString* path = [NSString stringWithFormat:@"%s/out.mp4", rec_path.c_str()]; NSLog(@"saved video %@", path); #if __IOS__ if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(path)) { NSLog(@"saving to camera roll"); UISaveVideoAtPathToSavedPhotosAlbum(path, nil, nil, nil); } #endif }]; } if (writer.status == AVAssetWriterStatusFailed) { NSLog(@"failed"); } } void save_image_library(const std::string& path) { #if __IOS__ [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{ NSURL* url = [NSURL fileURLWithPath:[NSString stringWithUTF8String:path.c_str()]]; PHAssetChangeRequest *changeRequest = [PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:url]; changeRequest.creationDate = [NSDate date]; } completionHandler:^(BOOL success, NSError *error) { if (success) { NSLog(@"successfully saved"); } else { NSLog(@"error saving to photos: %@", error); } }]; #endif }