diff --git a/Source/Frameworks/gyroflow/Cargo.toml b/Source/Frameworks/gyroflow/Cargo.toml index 2b582eee..2bb3edbd 100644 --- a/Source/Frameworks/gyroflow/Cargo.toml +++ b/Source/Frameworks/gyroflow/Cargo.toml @@ -21,3 +21,4 @@ lru = "0.10" nalgebra = { version = "0.32", features = ["serde-serialize"] } once_cell = "1.16.0" metal = { version = "0.25.0", git = "https://github.com/gfx-rs/metal-rs.git", rev = "a6a0446" } +block2 = "0.2.0" diff --git a/Source/Frameworks/gyroflow/inc/gyroflow.h b/Source/Frameworks/gyroflow/inc/gyroflow.h index 315de8ad..ce88cd55 100644 --- a/Source/Frameworks/gyroflow/inc/gyroflow.h +++ b/Source/Frameworks/gyroflow/inc/gyroflow.h @@ -49,7 +49,18 @@ const char* doesGyroflowProjectContainStabilisationData( const char* gyroflow_project_data ); +const char* isOfficialLensLoaded( + const char* gyroflow_project_data +); + const char* loadLensProfile( const char* gyroflow_project_data, const char* lens_profile_path ); + +// +// Just a test: +// +int32_t run_block(int32_t (^block)(int32_t, int32_t)); + +//double set_keyframe_provider(const char *, double (^block)(int32_t, double)); diff --git a/Source/Frameworks/gyroflow/src/lib.rs b/Source/Frameworks/gyroflow/src/lib.rs index c367243d..5612a139 100644 --- a/Source/Frameworks/gyroflow/src/lib.rs +++ b/Source/Frameworks/gyroflow/src/lib.rs @@ -21,6 +21,15 @@ use std::os::raw::c_char; // Allows us to use `*const c_uchar` use std::sync::Arc; // Adds Atomic Reference Count support use std::sync::atomic::AtomicBool; // The AtomicBool type is a type of atomic variable that can be used in concurrent (multi-threaded) contexts. use std::sync::Mutex; // A mutual exclusion primitive useful for protecting shared data +use block2::Block; + +//--------------------------------------------------------- +// Test Block: +//--------------------------------------------------------- +#[no_mangle] +unsafe extern "C" fn run_block(block: &Block<(i32, i32), i32>) -> i32 { + block.call((5, 8)) +} //--------------------------------------------------------- // We only want to setup the Gyroflow Manager once for @@ -30,6 +39,93 @@ lazy_static! { static ref MANAGER_CACHE: Mutex>> = Mutex::new(LruCache::new(std::num::NonZeroUsize::new(8).unwrap())); } +//--------------------------------------------------------- +// Set Keyframe Provider: +//--------------------------------------------------------- +/* +struct UnsafeBlock(Block<(*const c_char, f64), f64>); +unsafe impl Send for UnsafeBlock {} +unsafe impl Sync for UnsafeBlock {} +unsafe extern "C" fn set_keyframe_provider(cache_key: *const c_char, block: &UnsafeBlock) -> bool { + let block = block2::RcBlock::copy(block.0); + let cache_key = unsafe { CStr::from_ptr(cache_key) }.to_string_lossy().to_string(); + + let mut cache = MANAGER_CACHE.lock().unwrap(); + if let Some(manager) = cache.get(&cache_key) { + + log::error!("[Gyroflow Toolbox Rust] Got manager for key:: {cache_key}"); + + manager.keyframes.write().set_custom_provider(move |kf, typ, timestamp_ms| -> Option { + let use_gyroflow_internal_keyframes = false; // TODO: set from UI + if use_gyroflow_internal_keyframes && kf.is_keyframed_internally(typ) { return None; } + let keyframe_type_str = std::ffi::CString::new(format!("{typ:?}")).unwrap(); + let value_from_fcpx = block.0.call((keyframe_type_str.as_ptr(), timestamp_ms)); + + log::error!("[Gyroflow Toolbox Rust] Got keyframe value from FCPX:: {value_from_fcpx:?}"); + + Some(value_from_fcpx) + }); + true + } else { + log::error!("[Gyroflow Toolbox Rust] Didn't find cache key: {cache_key}, keyframe provider not set"); + false + } + } +*/ + +//--------------------------------------------------------- +// Is official lens loaded? +// Returns "YES" or "NO". +//--------------------------------------------------------- +#[no_mangle] +pub extern "C" fn isOfficialLensLoaded( + gyroflow_project_data: *const c_char, +) -> *const c_char { + //--------------------------------------------------------- + // Convert the Gyroflow Project data to a `&str`: + //--------------------------------------------------------- + let gyroflow_project_data_pointer = unsafe { CStr::from_ptr(gyroflow_project_data) }; + let gyroflow_project_data_string = gyroflow_project_data_pointer.to_string_lossy(); + + let stab = StabilizationManager::default(); + + //--------------------------------------------------------- + // Import the `gyroflow_project_data_string`: + //--------------------------------------------------------- + let blocking = true; + let cancel_flag = Arc::new(AtomicBool::new(false)); + let mut is_preset = false; + match stab.import_gyroflow_data( + gyroflow_project_data_string.as_bytes(), + blocking, + None, + |_|(), + cancel_flag, + &mut is_preset + ) { + Ok(_) => { + //--------------------------------------------------------- + // Is official lens loaded? + //--------------------------------------------------------- + let is_official_lens_loaded = stab.lens.read().official; + if is_official_lens_loaded { + let result = CString::new("YES").unwrap(); + return result.into_raw() + } else { + let result = CString::new("NO").unwrap(); + return result.into_raw() + } + }, + Err(e) => { + // Handle the error case + log::error!("[Gyroflow Toolbox Rust] Error importing gyroflow data: {:?}", e); + + let result = CString::new("NO").unwrap(); + return result.into_raw() + }, + } +} + //--------------------------------------------------------- // Does the Gyroflow Project contain Stabilisation Data? // Returns "PASS" or "FAIL". diff --git a/Source/Gyroflow/Plugin/CustomDropZoneView.m b/Source/Gyroflow/Plugin/CustomDropZoneView.m index 570f39db..94702ce2 100644 --- a/Source/Gyroflow/Plugin/CustomDropZoneView.m +++ b/Source/Gyroflow/Plugin/CustomDropZoneView.m @@ -8,8 +8,6 @@ #import "CustomDropZoneView.h" #import -static NSString *const kFinalCutProUTI = @"com.apple.flexo.proFFPasteboardUTI"; - @interface CustomDropZoneView () @property (nonatomic) bool dragIsOver; @@ -38,8 +36,7 @@ - (instancetype)initWithAPIManager:(id)apiManager { _apiManager = apiManager; - //[self registerForDraggedTypes:@[kFinalCutProUTI]]; - NSArray *sortedPasteboardTypes = @[@"com.apple.finalcutpro.xml.v1-11", @"com.apple.finalcutpro.xml.v1-10", @"com.apple.finalcutpro.xml.v1-9", @"com.apple.finalcutpro.xml", kFinalCutProUTI, NSPasteboardTypeFileURL]; + NSArray *sortedPasteboardTypes = @[NSPasteboardTypeFileURL, @"com.apple.finalcutpro.xml.v1-11", @"com.apple.finalcutpro.xml.v1-10", @"com.apple.finalcutpro.xml.v1-9", @"com.apple.finalcutpro.xml", @"com.apple.flexo.proFFPasteboardUTI"]; [self registerForDraggedTypes:sortedPasteboardTypes]; self.wantsLayer = YES; @@ -62,7 +59,7 @@ - (instancetype)initWithAPIManager:(id)apiManager //--------------------------------------------------------- - (NSDragOperation)draggingEntered:(id )sender { - NSArray *sortedPasteboardTypes = @[@"com.apple.finalcutpro.xml.v1-11", @"com.apple.finalcutpro.xml.v1-10", @"com.apple.finalcutpro.xml.v1-9", @"com.apple.finalcutpro.xml", NSPasteboardTypeFileURL]; + NSArray *sortedPasteboardTypes = @[NSPasteboardTypeFileURL, @"com.apple.finalcutpro.xml.v1-11", @"com.apple.finalcutpro.xml.v1-10", @"com.apple.finalcutpro.xml.v1-9", @"com.apple.finalcutpro.xml"]; for (NSPasteboardType pasteboardType in sortedPasteboardTypes) { if ( [[[sender draggingPasteboard] types] containsObject:pasteboardType] ) { _dragIsOver = true; @@ -92,21 +89,38 @@ - (BOOL)performDragOperation:(id)sender { NSDictionary *options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:NSPasteboardURLReadingFileURLsOnlyKey]; NSArray *fileURLs = [pasteboard readObjectsForClasses:classArray options:options]; for (NSURL *fileURL in fileURLs) { - // Do something with fileURL - NSLog(@"[Gyroflow Toolbox Renderer] Dropped file: %@", [fileURL path]); + //--------------------------------------------------------- + // Trigger Dropped File Method: + //--------------------------------------------------------- + + NSLog(@"[Gyroflow Toolbox Renderer] Dropped file path: %@", [fileURL path]); + + //--------------------------------------------------------- + // Create a new security-scoped bookmark: + //--------------------------------------------------------- + NSError *bookmarkError = nil; + NSURLBookmarkCreationOptions bookmarkOptions = NSURLBookmarkCreationWithSecurityScope | NSURLBookmarkCreationSecurityScopeAllowOnlyReadAccess; + NSData *bookmarkData = [fileURL bookmarkDataWithOptions:bookmarkOptions + includingResourceValuesForKeys:nil + relativeToURL:nil + error:&bookmarkError]; - if ([fileURL startAccessingSecurityScopedResource]) { - NSLog(@"[Gyroflow Toolbox Renderer] SUCCESSFUL startAccessingSecurityScopedResource"); - } else { - NSLog(@"[Gyroflow Toolbox Renderer] FAILED to startAccessingSecurityScopedResource"); + if (bookmarkError != nil) { + NSLog(@"[Gyroflow Toolbox Renderer] ERROR - Unable to create security-scoped bookmark for dragged file ('%@') due to: %@", fileURL, bookmarkError); + return NO; + } + + if (bookmarkData == nil) { + NSLog(@"[Gyroflow Toolbox Renderer] ERROR - Unable to create security-scoped bookmark for dragged file ('%@') due to: Bookmark is nil.", fileURL); + return NO; } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wobjc-method-access" - [_parentPlugin importDroppedMedia:fileURL]; + BOOL result = [_parentPlugin importDroppedMedia:bookmarkData]; #pragma clang diagnostic pop - - return YES; + + return result; } } else { diff --git a/Source/Gyroflow/Plugin/GyroflowConstants.h b/Source/Gyroflow/Plugin/GyroflowConstants.h index 68c9c911..8841bc64 100644 --- a/Source/Gyroflow/Plugin/GyroflowConstants.h +++ b/Source/Gyroflow/Plugin/GyroflowConstants.h @@ -91,9 +91,13 @@ enum { // Hidden Metadata: //--------------------------------------------------------- kCB_UniqueIdentifier = 500, + kCB_GyroflowProjectPath = 60, kCB_GyroflowProjectBookmarkData = 70, kCB_GyroflowProjectData = 80, + + kCB_MediaPath = 510, + kCB_MediaBookmarkData = 520, }; //--------------------------------------------------------- diff --git a/Source/Gyroflow/Plugin/GyroflowPlugIn.m b/Source/Gyroflow/Plugin/GyroflowPlugIn.m index 4a976002..95aca48a 100644 --- a/Source/Gyroflow/Plugin/GyroflowPlugIn.m +++ b/Source/Gyroflow/Plugin/GyroflowPlugIn.m @@ -78,6 +78,16 @@ - (nullable instancetype)initWithAPIManager:(id)newApiManager; NSString *build = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]; NSLog(@"[Gyroflow Toolbox Renderer] Version: %@ (%@)", version, build); + //--------------------------------------------------------- + // Test Rust Block: + //--------------------------------------------------------- + int32_t (^myBlock)(int32_t, int32_t) = ^int32_t(int32_t a, int32_t b) { + return a + b; + }; + + int32_t result = run_block(myBlock); + NSLog(@"[Gyroflow Toolbox Renderer] Result from Rust: %d", result); + _apiManager = newApiManager; } return self; @@ -231,7 +241,7 @@ - (NSView*)createViewForParameterID:(UInt32)parameterID return view; } else if (parameterID == kCB_Header) { - NSRect frameRect = NSMakeRect(0, 0, 200, 184); // x y w h + NSRect frameRect = NSMakeRect(0, 0, 200, 234); // x y w h NSView* view = [[HeaderView alloc] initWithFrame:frameRect]; headerView = view; @@ -1003,6 +1013,40 @@ - (BOOL)addParametersWithError:(NSError**)error } return NO; } + + //--------------------------------------------------------- + // ADD PARAMETER: 'Media Path' Text Box + //--------------------------------------------------------- + if (![paramAPI addStringParameterWithName:@"Media Path" + parameterID:kCB_MediaPath + defaultValue:@"" + parameterFlags:kFxParameterFlag_HIDDEN | kFxParameterFlag_NOT_ANIMATABLE]) + { + if (error != NULL) { + NSDictionary* userInfo = @{NSLocalizedDescriptionKey : @"[Gyroflow Toolbox Renderer] Unable to add parameter: kCB_MediaPath"}; + *error = [NSError errorWithDomain:FxPlugErrorDomain + code:kFxError_InvalidParameter + userInfo:userInfo]; + } + return NO; + } + + //--------------------------------------------------------- + // ADD PARAMETER: 'Media Bookmark Data' Text Box + //--------------------------------------------------------- + if (![paramAPI addStringParameterWithName:@"Media Bookmark Data" + parameterID:kCB_MediaBookmarkData + defaultValue:@"" + parameterFlags:kFxParameterFlag_HIDDEN | kFxParameterFlag_NOT_ANIMATABLE]) + { + if (error != NULL) { + NSDictionary* userInfo = @{NSLocalizedDescriptionKey : @"[Gyroflow Toolbox Renderer] Unable to add parameter: kCB_MediaBookmarkData"}; + *error = [NSError errorWithDomain:FxPlugErrorDomain + code:kFxError_InvalidParameter + userInfo:userInfo]; + } + return NO; + } return YES; } @@ -2182,7 +2226,113 @@ - (void)customButtonViewPressed:(UInt32)buttonID // BUTTON: 'Export Gyroflow Project' //--------------------------------------------------------- - (void)buttonExportGyroflowProject { - [self showAlertWithMessage:@"Export!" info:@"Coming soon!"]; + //--------------------------------------------------------- + // Load the Custom Parameter Action API: + //--------------------------------------------------------- + id actionAPI = [_apiManager apiForProtocol:@protocol(FxCustomParameterActionAPI_v4)]; + if (actionAPI == nil) { + [self showAlertWithMessage:@"An error has occurred." info:@"Unable to retrieve 'FxCustomParameterActionAPI_v4'. This shouldn't happen, so it's probably a bug."]; + return; + } + + //--------------------------------------------------------- + // Use the Action API to allow us to change the parameters: + //--------------------------------------------------------- + [actionAPI startAction:self]; + + //--------------------------------------------------------- + // Load the Parameter Retrieval API: + //--------------------------------------------------------- + id paramGetAPI = [_apiManager apiForProtocol:@protocol(FxParameterRetrievalAPI_v6)]; + if (paramGetAPI == nil) { + //--------------------------------------------------------- + // Stop Action API: + //--------------------------------------------------------- + [actionAPI endAction:self]; + + [self showAlertWithMessage:@"An error has occurred." info:@"Unable to retrieve 'FxParameterRetrievalAPI_v6'.\n\nThis shouldn't happen, so it's probably a bug."]; + return; + } + + //--------------------------------------------------------- + // Get the existing Gyroflow Project Data: + //--------------------------------------------------------- + NSString *gyroflowProjectData = nil; + [paramGetAPI getStringParameterValue:&gyroflowProjectData fromParameter:kCB_GyroflowProjectData]; + + //--------------------------------------------------------- + // Check that the Gyroflow Project is valid: + //--------------------------------------------------------- + if (gyroflowProjectData == nil || [gyroflowProjectData isEqualToString:@""]) { + [actionAPI endAction:self]; + NSString *errorMessage = @"There is currently no Gyroflow Project data loaded.\n\nPlease load a Gyroflow Project or Media File and try again."; + NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); + [self showAlertWithMessage:@"No Gyroflow Project Found" info:errorMessage]; + return; + } + + //--------------------------------------------------------- + // Get the existing Gyroflow Project Path: + //--------------------------------------------------------- + NSString *gyroflowProjectPath = nil; + [paramGetAPI getStringParameterValue:&gyroflowProjectPath fromParameter:kCB_GyroflowProjectPath]; + + //--------------------------------------------------------- + // Get the existing Gyroflow Project Path: + //--------------------------------------------------------- + NSString *gyroflowProjectName = nil; + [paramGetAPI getStringParameterValue:&gyroflowProjectName fromParameter:kCB_LoadedGyroflowProject]; + + if (gyroflowProjectName != nil || [gyroflowProjectName isEqualToString:@""]) { + gyroflowProjectName = [gyroflowProjectName stringByAppendingString:@".gyroflow"]; + } + + //--------------------------------------------------------- + // Stop the Action API: + //--------------------------------------------------------- + [actionAPI endAction:self]; + + //--------------------------------------------------------- + // Display Save Panel: + //--------------------------------------------------------- + NSSavePanel *savePanel = [NSSavePanel savePanel]; + + [savePanel setTitle:@"Choose a Location to Save Your Gyroflow Project"]; + [savePanel setPrompt:@"Save"]; + [savePanel setNameFieldStringValue:gyroflowProjectName]; + [savePanel setDirectoryURL:[NSURL fileURLWithPath:gyroflowProjectPath]]; + + //--------------------------------------------------------- + // Limit the file type to Gyroflow supported media files: + //--------------------------------------------------------- + UTType *gyroflow = [UTType typeWithFilenameExtension:@"gyroflow"]; + + NSArray *allowedContentTypes = [NSArray arrayWithObjects:gyroflow, nil]; + [savePanel setAllowedContentTypes:allowedContentTypes]; + + //--------------------------------------------------------- + // Show the Save Panel: + //--------------------------------------------------------- + [savePanel beginWithCompletionHandler:^(NSInteger result){ + if (result == NSModalResponseOK) { + NSURL *fileURL = [savePanel URL]; + + NSError *error = nil; + BOOL succeeded = [gyroflowProjectData writeToURL:fileURL + atomically:YES + encoding:NSUTF8StringEncoding + error:&error]; + if (!succeeded) { + //--------------------------------------------------------- + // Failed to save: + //--------------------------------------------------------- + [actionAPI endAction:self]; + NSString *errorMessage = [NSString stringWithFormat:@"Failed to write the Gyroflow Project to '%@', due to:\n\n%@", [fileURL path], error.localizedDescription]; + NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); + [self showAlertWithMessage:@"An error has occurred" info:errorMessage]; + } + } + }]; } //--------------------------------------------------------- @@ -2410,7 +2560,7 @@ - (void)buttonLaunchGyroflow { [self showAlertWithMessage:@"An error has occurred." info:@"Unable to retrieve 'FxCustomParameterActionAPI_v4'. This shouldn't happen, so it's probably a bug."]; return; } - + //--------------------------------------------------------- // Use the Action API to allow us to change the parameters: //--------------------------------------------------------- @@ -2430,14 +2580,27 @@ - (void)buttonLaunchGyroflow { return; } + NSString *pathToOpen = nil; + //--------------------------------------------------------- - // Get the existing Gyroflow project path: + // Get the existing Media path: //--------------------------------------------------------- - NSString *existingProjectPath = nil; - [paramGetAPI getStringParameterValue:&existingProjectPath fromParameter:kCB_GyroflowProjectPath]; + NSString *existingMediaPath = nil; + BOOL isMediaFile = NO; + [paramGetAPI getStringParameterValue:&existingMediaPath fromParameter:kCB_MediaPath]; + + if (existingMediaPath != nil) { + isMediaFile = YES; + pathToOpen = [NSString stringWithString:existingMediaPath]; + } else { + //--------------------------------------------------------- + // Get the existing Gyroflow project path: + //--------------------------------------------------------- + NSString *existingProjectPath = nil; + [paramGetAPI getStringParameterValue:&existingProjectPath fromParameter:kCB_GyroflowProjectPath]; + pathToOpen = [NSString stringWithString:existingProjectPath]; + } - NSURL *existingProjectURL = [NSURL fileURLWithPath:existingProjectPath]; - //--------------------------------------------------------- // Open Gyroflow or the current Gyroflow Project: //--------------------------------------------------------- @@ -2445,68 +2608,79 @@ - (void)buttonLaunchGyroflow { NSURL *appURL = [[NSWorkspace sharedWorkspace] URLForApplicationWithBundleIdentifier:bundleIdentifier]; if (appURL == nil) { + //--------------------------------------------------------- + // Gyroflow is not installed: + //--------------------------------------------------------- NSLog(@"[Gyroflow Toolbox Renderer] Could not find Gyroflow Installation"); [actionAPI endAction:self]; - [self showAlertWithMessage:@"Failed to launch Gyroflow." info:@"Please check that Gyroflow is installed and try again."]; + [self showAlertWithMessage:@"Gyroflow Application Not Found" info:@"The Gyroflow application could not be found on your system.\n\nGyroflow is a free and open-source application, that is separate and independent of this Gyroflow Toolbox plugin.\n\nYou can download the latest version of Gyroflow from:\n\nhttps://gyroflow.xyz"]; return; } - if (existingProjectPath == nil || [existingProjectPath isEqualToString:@""] || existingProjectURL == nil) { - NSLog(@"[Gyroflow Toolbox Renderer] Could not find existing project."); + if (pathToOpen == nil || [pathToOpen isEqualToString:@""]) { + //--------------------------------------------------------- + // No Media file or Gyroflow Project loaded: + //--------------------------------------------------------- + NSLog(@"[Gyroflow Toolbox Renderer] WARNING - No existing media or Gyroflow project was found so loading blank Gyroflow."); [[NSWorkspace sharedWorkspace] openURL:appURL]; } else { - //--------------------------------------------------------- - // Get the encoded bookmark string: - //--------------------------------------------------------- - NSString *encodedBookmark; - [paramGetAPI getStringParameterValue:&encodedBookmark fromParameter:kCB_GyroflowProjectBookmarkData]; - - //--------------------------------------------------------- - // Make sure there's actually encoded bookmark data: - //--------------------------------------------------------- - if ([encodedBookmark isEqualToString:@""]) { - NSLog(@"[Gyroflow Toolbox Renderer] Encoded Bookmark is empty."); - [[NSWorkspace sharedWorkspace] openURL:appURL]; - [actionAPI endAction:self]; - return; - } - - //--------------------------------------------------------- - // Decode the Base64 bookmark data: - //--------------------------------------------------------- - NSData *decodedBookmark = [[[NSData alloc] initWithBase64EncodedString:encodedBookmark - options:0] autorelease]; - - //--------------------------------------------------------- - // Resolve the decoded bookmark data into a - // security-scoped URL: - //--------------------------------------------------------- - NSError *bookmarkError = nil; - BOOL isStale = NO; - - NSURL *url = [NSURL URLByResolvingBookmarkData:decodedBookmark - options:NSURLBookmarkResolutionWithSecurityScope - relativeToURL:nil - bookmarkDataIsStale:&isStale - error:&bookmarkError]; - - if (bookmarkError != nil) { - NSLog(@"[Gyroflow Toolbox Renderer] Bookmark error: %@", bookmarkError.localizedDescription); - [[NSWorkspace sharedWorkspace] openURL:appURL]; - [actionAPI endAction:self]; - return; - } - - //--------------------------------------------------------- - // Read the Gyroflow Project Data from File: - //--------------------------------------------------------- + //--------------------------------------------------------- + // Get the encoded bookmark string: + //--------------------------------------------------------- + NSString *encodedBookmark; + + if (isMediaFile) { + [paramGetAPI getStringParameterValue:&encodedBookmark fromParameter:kCB_MediaBookmarkData]; + } else { + [paramGetAPI getStringParameterValue:&encodedBookmark fromParameter:kCB_GyroflowProjectBookmarkData]; + } + + //--------------------------------------------------------- + // Make sure there's actually encoded bookmark data: + //--------------------------------------------------------- + if ([encodedBookmark isEqualToString:@""]) { + NSLog(@"[Gyroflow Toolbox Renderer] ERROR - Encoded security-scoped bookmark is empty when trying to launch Gyroflow."); + [[NSWorkspace sharedWorkspace] openURL:appURL]; + [actionAPI endAction:self]; + return; + } + + //--------------------------------------------------------- + // Decode the Base64 bookmark data: + //--------------------------------------------------------- + NSData *decodedBookmark = [[[NSData alloc] initWithBase64EncodedString:encodedBookmark + options:0] autorelease]; + + //--------------------------------------------------------- + // Resolve the decoded bookmark data into a + // security-scoped URL: + //--------------------------------------------------------- + NSError *bookmarkError = nil; + BOOL isStale = NO; + + NSURL *url = [NSURL URLByResolvingBookmarkData:decodedBookmark + options:NSURLBookmarkResolutionWithSecurityScope + relativeToURL:nil + bookmarkDataIsStale:&isStale + error:&bookmarkError]; + + if (bookmarkError != nil) { + NSLog(@"[Gyroflow Toolbox Renderer] ERROR - Failed to resolve security-scoped bookmark when trying to launch Gyroflow due to: %@", bookmarkError.localizedDescription); + [[NSWorkspace sharedWorkspace] openURL:appURL]; + [actionAPI endAction:self]; + return; + } + + //--------------------------------------------------------- + // Read the Gyroflow Project Data from File: + //--------------------------------------------------------- if (![url startAccessingSecurityScopedResource]) { NSLog(@"[Gyroflow Toolbox Renderer] Failed to start security scoped resource: %@", url); [[NSWorkspace sharedWorkspace] openURL:appURL]; [actionAPI endAction:self]; return; } - + //--------------------------------------------------------- // There is an existing project path, so load Gyroflow // with that path: @@ -2607,7 +2781,7 @@ - (void)buttonReloadGyroflowProject { //--------------------------------------------------------- [actionAPI endAction:self]; - [self showAlertWithMessage:@"An error has occurred." info:@"There's no previous security-scoped bookmark data found.\n\nPlease make sure you import a Gyroflow Project before attempting to reload."]; + [self showAlertWithMessage:@"No active Gyroflow Project" info:@"There is currently no active Gyroflow Project loaded.\n\nEither you haven't imported a Gyroflow Project yet, or you imported a Media File that doesn't have a corresponding Gyroflow Project.\n\nIf you imported a Media File, you can use the 'Export Gyroflow Project' to save a Gyroflow Project locally."]; return; } @@ -2707,13 +2881,42 @@ - (void)buttonReloadGyroflowProject { //--------------------------------------------------------- // Import Dropped Media: //--------------------------------------------------------- -- (void)importDroppedMedia:(NSURL*)url { +- (BOOL)importDroppedMedia:(NSData*)bookmarkData { - if (url == nil) { - [self showAlertWithMessage:@"An error has occurred" info:@"The dropped item contains no data."]; - return; + /* + if ([[NSFileManager defaultManager] isReadableFileAtPath:[fileURL path]]) { + NSLog(@"[Gyroflow Toolbox Renderer] Can read file: %@", [fileURL path]); + } else { + NSLog(@"[Gyroflow Toolbox Renderer] Failed to read file: %@", [fileURL path]); } + */ + + //--------------------------------------------------------- + // Resolve the security-scope bookmark: + //--------------------------------------------------------- + NSError *bookmarkError = nil; + BOOL isStale = NO; + + NSURL *url = [NSURL URLByResolvingBookmarkData:bookmarkData + options:NSURLBookmarkResolutionWithSecurityScope + relativeToURL:nil + bookmarkDataIsStale:&isStale + error:&bookmarkError]; + if (bookmarkError != nil) { + NSLog(@"[Gyroflow Toolbox Renderer] ERROR - Failed to read security-scoped bookmark due to: %@", bookmarkError); + return NO; + } + + if (isStale) { + NSLog(@"[Gyroflow Toolbox Renderer] WARNING - Bookmark is stale!"); + } + + if (![url startAccessingSecurityScopedResource]) { + NSLog(@"[Gyroflow Toolbox Renderer] ERROR - Could not start accessing the security-scoped bookmark."); + return NO; + } + NSLog(@"[Gyroflow Toolbox Renderer] importDroppedMedia URL: %@", [url path]); NSString *extension = [[url pathExtension] lowercaseString]; @@ -2741,6 +2944,13 @@ - (void)importDroppedMedia:(NSURL*)url { [self importMediaWithOptionalURL:url]; } } + + //--------------------------------------------------------- + // Stop accessing the security scoped resource: + //--------------------------------------------------------- + [url stopAccessingSecurityScopedResource]; + + return YES; } //--------------------------------------------------------- @@ -2994,14 +3204,14 @@ - (void)readLastProjectFromGyroflowPreferencesFile { //--------------------------------------------------------- //--------------------------------------------------------- -// Import Gyroflow Project with Optional URL: +// Import Media with Optional URL: //--------------------------------------------------------- -- (void)importMediaWithOptionalURL:(NSURL*)optionalURL { +- (BOOL)importMediaWithOptionalURL:(NSURL*)optionalURL { NSLog(@"[Gyroflow Toolbox Renderer] Import Media File!"); NSURL *url = nil; - BOOL isAccessible = [optionalURL startAccessingSecurityScopedResource]; + BOOL isAccessible = [[NSFileManager defaultManager] isReadableFileAtPath:[optionalURL path]]; //[optionalURL startAccessingSecurityScopedResource]; if (isAccessible) { NSLog(@"[Gyroflow Toolbox Renderer] importMediaWithOptionalURL has an accessible NSURL!"); @@ -3035,7 +3245,7 @@ - (void)importMediaWithOptionalURL:(NSURL*)optionalURL { //--------------------------------------------------------- NSModalResponse result = [panel runModal]; if (result != NSModalResponseOK) { - return; + return NO; } url = [panel URL]; @@ -3046,7 +3256,7 @@ - (void)importMediaWithOptionalURL:(NSURL*)optionalURL { BOOL startedOK = [url startAccessingSecurityScopedResource]; if (startedOK == NO) { [self showAlertWithMessage:@"An error has occurred." info:@"Failed to startAccessingSecurityScopedResource. This shouldn't happen."]; - return; + return NO; } } @@ -3059,11 +3269,11 @@ - (void)importMediaWithOptionalURL:(NSURL*)optionalURL { ); NSString *resultString = [NSString stringWithUTF8String: importResult]; - NSLog(@"[Gyroflow Toolbox Renderer] resultString: %@", resultString); + //NSLog(@"[Gyroflow Toolbox Renderer] resultString: %@", resultString); if (resultString == nil || [resultString isEqualToString:@"FAIL"]) { [self showAlertWithMessage:@"An error has occurred" info:@"Failed to generate a Gyroflow Project from the Media File."]; - return; + return NO; } //--------------------------------------------------------- @@ -3072,11 +3282,11 @@ - (void)importMediaWithOptionalURL:(NSURL*)optionalURL { id actionAPI = [_apiManager apiForProtocol:@protocol(FxCustomParameterActionAPI_v4)]; if (actionAPI == nil) { [self showAlertWithMessage:@"An error has occurred." info:@"Unable to retrieve 'FxCustomParameterActionAPI_v4' in ImportGyroflowProjectView's 'buttonPressed'. This shouldn't happen."]; - return; + return NO; } NSString *selectedGyroflowProjectFile = [[url lastPathComponent] stringByDeletingPathExtension]; - NSString *selectedGyroflowProjectPath = [[[url lastPathComponent] stringByDeletingPathExtension] stringByAppendingString:@".gyroflow"]; + NSString *selectedGyroflowProjectPath = [[url path] stringByAppendingString:@".gyroflow"]; NSString *selectedGyroflowProjectBookmarkData = @""; //--------------------------------------------------------- @@ -3090,7 +3300,7 @@ - (void)importMediaWithOptionalURL:(NSURL*)optionalURL { NSString *errorMessage = [NSString stringWithFormat:@"There was an unexpected error reading the JSON data:\n\n%@", jsonError.localizedDescription]; NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); [self showAlertWithMessage:@"Failed to open Gyroflow Project" info:errorMessage]; - return; + return NO; } //--------------------------------------------------------- @@ -3100,21 +3310,49 @@ - (void)importMediaWithOptionalURL:(NSURL*)optionalURL { NSString *hasDataResult = [NSString stringWithUTF8String: hasData]; NSLog(@"[Gyroflow Toolbox Renderer] hasDataResult: %@", hasDataResult); if (hasDataResult == nil || ![hasDataResult isEqualToString:@"PASS"]) { - NSString *errorMessage = @"The Gyroflow file you imported doesn't seem to contain any gyro data.\n\nPlease try exporting from Gyroflow again using the 'Export project file (including gyro data)' option."; + NSString *errorMessage = @"The file you imported doesn't seem to contain any gyroscope data.\n\nPlease check the media file to make sure it's correct and valid."; NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); - [self showAlertWithMessage:@"Gyro Data Not Found." info:errorMessage]; - return; + [self showAlertWithMessage:@"Missing Gyroscope Data" info:errorMessage]; + return NO; } + //--------------------------------------------------------- + // Create a new security-scoped bookmark: + //--------------------------------------------------------- + NSError *bookmarkError = nil; + NSURLBookmarkCreationOptions bookmarkOptions = NSURLBookmarkCreationWithSecurityScope | NSURLBookmarkCreationSecurityScopeAllowOnlyReadAccess; + NSData *bookmarkData = [url bookmarkDataWithOptions:bookmarkOptions + includingResourceValuesForKeys:nil + relativeToURL:nil + error:&bookmarkError]; + + if (bookmarkError != nil) { + NSString *errorMessage = [NSString stringWithFormat:@"Unable to create security-scoped bookmark in importMediaWithOptionalURL ('%@') due to: %@", url, bookmarkError]; + NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); + [self showAlertWithMessage:@"An error has occurred" info:errorMessage]; + return NO; + } + + if (bookmarkData == nil) { + NSString *errorMessage = [NSString stringWithFormat:@"Unable to create security-scoped bookmark in importMediaWithOptionalURL ('%@') due to: Bookmark is nil.", url]; + NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); + [self showAlertWithMessage:@"An error has occurred" info:errorMessage]; + return NO; + } + + NSString *mediaBookmarkData = [bookmarkData base64EncodedStringWithOptions:0]; + //--------------------------------------------------------- // Get the current 'FOV' value: //--------------------------------------------------------- + // TODO: This should be using Rust API. NSDictionary *stabilizationData = [jsonData objectForKey:@"stabilization"]; NSNumber *fov = [stabilizationData objectForKey:@"fov"]; //--------------------------------------------------------- // Get the current 'Smoothness' value: //--------------------------------------------------------- + // TODO: This should be using Rust API. NSArray *smoothnessParams = [stabilizationData objectForKey:@"smoothing_params"]; NSNumber *smoothness = nil; for (NSDictionary *param in smoothnessParams) { @@ -3127,6 +3365,7 @@ - (void)importMediaWithOptionalURL:(NSURL*)optionalURL { //--------------------------------------------------------- // Get the current 'Lens Correction' value: //--------------------------------------------------------- + // TODO: This should be using Rust API. NSNumber *lensCorrection = [stabilizationData objectForKey:@"lens_correction_amount"]; //--------------------------------------------------------- @@ -3143,25 +3382,40 @@ - (void)importMediaWithOptionalURL:(NSURL*)optionalURL { NSString *errorMessage = @"Unable to retrieve FxParameterSettingAPI_v5 in 'selectFileButtonPressed'. This shouldn't happen."; NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); [self showAlertWithMessage:@"An error has occurred." info:errorMessage]; - return; + return NO; } + //--------------------------------------------------------- + // Update 'Media Path': + //--------------------------------------------------------- + [paramSetAPI setStringParameterValue:path toParameter:kCB_MediaPath]; + NSLog(@"[Gyroflow Toolbox Renderer] mediaPath: %@", path); + + //--------------------------------------------------------- + // Update 'Media Bookmark Data': + //--------------------------------------------------------- + [paramSetAPI setStringParameterValue:mediaBookmarkData toParameter:kCB_MediaBookmarkData]; + //NSLog(@"[Gyroflow Toolbox Renderer] mediaBookmarkData: %@", mediaBookmarkData); + //--------------------------------------------------------- // Generate a unique identifier: //--------------------------------------------------------- NSUUID *uuid = [NSUUID UUID]; NSString *uniqueIdentifier = uuid.UUIDString; [paramSetAPI setStringParameterValue:uniqueIdentifier toParameter:kCB_UniqueIdentifier]; - + NSLog(@"[Gyroflow Toolbox Renderer] uniqueIdentifier: %@", uniqueIdentifier); + //--------------------------------------------------------- // Update 'Gyroflow Project Path': //--------------------------------------------------------- [paramSetAPI setStringParameterValue:selectedGyroflowProjectPath toParameter:kCB_GyroflowProjectPath]; + NSLog(@"[Gyroflow Toolbox Renderer] selectedGyroflowProjectPath: %@", selectedGyroflowProjectPath); //--------------------------------------------------------- // Update 'Gyroflow Project Bookmark Data': //--------------------------------------------------------- [paramSetAPI setStringParameterValue:selectedGyroflowProjectBookmarkData toParameter:kCB_GyroflowProjectBookmarkData]; + NSLog(@"[Gyroflow Toolbox Renderer] selectedGyroflowProjectBookmarkData: %@", selectedGyroflowProjectBookmarkData); //--------------------------------------------------------- // Update 'Gyroflow Project Data': @@ -3172,6 +3426,7 @@ - (void)importMediaWithOptionalURL:(NSURL*)optionalURL { // Update 'Loaded Gyroflow Project' Text Box: //--------------------------------------------------------- [paramSetAPI setStringParameterValue:selectedGyroflowProjectFile toParameter:kCB_LoadedGyroflowProject]; + NSLog(@"[Gyroflow Toolbox Renderer] selectedGyroflowProjectFile: %@", selectedGyroflowProjectFile); //--------------------------------------------------------- // Set parameters from Gyroflow Project file: @@ -3201,6 +3456,7 @@ - (void)importMediaWithOptionalURL:(NSURL*)optionalURL { //--------------------------------------------------------- [self showAlertWithMessage:@"Success!" info:@"The Gyroflow Project has been successfully imported.\n\nYou can now adjust the parameters as required."]; + return YES; } //--------------------------------------------------------- diff --git a/Source/Gyroflow/Plugin/HeaderView.xib b/Source/Gyroflow/Plugin/HeaderView.xib index 203ae657..2cf71540 100644 --- a/Source/Gyroflow/Plugin/HeaderView.xib +++ b/Source/Gyroflow/Plugin/HeaderView.xib @@ -14,52 +14,62 @@ - + - + - - + + - - + + - - + + - + - - + + - + + + + + + + + + + - - + + - + + GoPro, DJI and Insta360 will automatically select a lens profile. For other cameras, you'll need to select a lens profile manually. - + diff --git a/Source/Gyroflow/Plugin/SandboxEntitlements.entitlements b/Source/Gyroflow/Plugin/SandboxEntitlements.entitlements index db28c6a8..9bddc6a7 100644 --- a/Source/Gyroflow/Plugin/SandboxEntitlements.entitlements +++ b/Source/Gyroflow/Plugin/SandboxEntitlements.entitlements @@ -6,6 +6,8 @@ com.apple.security.files.bookmarks.app-scope + com.apple.security.files.bookmarks.document-scope + com.apple.security.files.user-selected.read-write