diff --git a/Source/Frameworks/gyroflow/inc/gyroflow.h b/Source/Frameworks/gyroflow/inc/gyroflow.h index 5e7f8c4a..42c0f120 100644 --- a/Source/Frameworks/gyroflow/inc/gyroflow.h +++ b/Source/Frameworks/gyroflow/inc/gyroflow.h @@ -94,3 +94,11 @@ const char* loadLensProfile( const char* gyroflow_project_data, const char* lens_profile_path ); + +//--------------------------------------------------------- +// Load a Preset into a Gyroflow Project: +//--------------------------------------------------------- +const char* loadPreset( + const char* gyroflow_project_data, + const char* lens_profile_path +); diff --git a/Source/Frameworks/gyroflow/src/lib.rs b/Source/Frameworks/gyroflow/src/lib.rs index f81e9961..ce739120 100644 --- a/Source/Frameworks/gyroflow/src/lib.rs +++ b/Source/Frameworks/gyroflow/src/lib.rs @@ -19,10 +19,22 @@ use std::sync::Arc; // Adds Atomic Reference Count suppo 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 -//--------------------------------------------------------- -// We only want to setup the Gyroflow Manager once for -// each pixel format: -//--------------------------------------------------------- +// This code block defines a lazy static variable called `MANAGER_CACHE` that is a `Mutex`-protected LRU cache of `StabilizationManager` instances. +// +// The `lazy_static!` macro is used to ensure that the variable is initialized only once, and only when it is first accessed. +// +// The `Mutex` is used to ensure that the cache can be safely accessed from multiple threads. +// +// The `LruCache` is used to limit the size of the cache to 8 items. +// +// # Example +// +// ```rust +// use gyroflow::MANAGER_CACHE; +// +// let cache = MANAGER_CACHE.lock().unwrap(); +// let manager = cache.get("my_pixel_format").unwrap(); +// ``` lazy_static! { static ref MANAGER_CACHE: Mutex>> = Mutex::new(LruCache::new(std::num::NonZeroUsize::new(8).unwrap())); } @@ -416,7 +428,7 @@ pub extern "C" fn hasAccurateTimestamps( } } -/// Load a Lens Profile to a supplied Gyroflow Project. +/// Load a Lens Profile from a JSON to a supplied Gyroflow Project. /// /// # Arguments /// @@ -518,6 +530,107 @@ pub extern "C" fn loadLensProfile( } } +/// Load a Gyroflow Preset to a supplied Gyroflow Project. +/// +/// # Arguments +/// +/// * `gyroflow_project_data` - A pointer to a C-style string representing the Gyroflow Project data. +/// * `preset_path` - A pointer to a C-style string representing the Profile data. +/// +/// # Returns +/// +/// A new Gyroflow Project or "FAIL". +#[no_mangle] +pub extern "C" fn loadPreset( + gyroflow_project_data: *const c_char, + preset_path: *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(); + + //--------------------------------------------------------- + // Convert the Lens Profile data to a `&str`: + //--------------------------------------------------------- + let preset_path_pointer = unsafe { CStr::from_ptr(preset_path) }; + let preset_path_string = preset_path_pointer.to_string_lossy(); + + let mut stab = StabilizationManager::default(); + { + //--------------------------------------------------------- + // Find first lens profile database with loaded profiles: + //--------------------------------------------------------- + let lock = MANAGER_CACHE.lock().unwrap(); + for (_, v) in lock.iter() { + if v.lens_profile_db.read().loaded { + stab.lens_profile_db = v.lens_profile_db.clone(); + break; + } + } + } + + //--------------------------------------------------------- + // Import the `gyroflow_project_data_string`: + //--------------------------------------------------------- + let blocking = true; + let path = Some(std::path::PathBuf::from(&*gyroflow_project_data_string)); + 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, + path, + |_|(), + cancel_flag, + &mut is_preset + ) { + Ok(_) => { + //--------------------------------------------------------- + // Load Preset: + //--------------------------------------------------------- + let mut is_preset = false; + if let Err(e) = stab.import_gyroflow_data(preset_path_string.as_bytes(), true, None, |_|(), Arc::new(AtomicBool::new(false)), &mut is_preset) { + log::error!("[Gyroflow Toolbox Rust] Error loading Preset: {:?}", e); + let result = CString::new("FAIL").unwrap(); + return result.into_raw() + } + + //--------------------------------------------------------- + // Export Gyroflow data: + //--------------------------------------------------------- + let gyroflow_data: String; + match stab.export_gyroflow_data(false, false, "{}") { + Ok(data) => { + gyroflow_data = data; + log::info!("[Gyroflow Toolbox Rust] Gyroflow data exported successfully"); + }, + Err(e) => { + log::error!("[Gyroflow Toolbox Rust] An error occured: {:?}", e); + gyroflow_data = "FAIL".to_string(); + } + } + + //--------------------------------------------------------- + // Return Gyroflow Project data as string: + //--------------------------------------------------------- + let result = CString::new(gyroflow_data).unwrap(); + return result.into_raw() + }, + Err(e) => { + //--------------------------------------------------------- + // An error has occurred: + //--------------------------------------------------------- + log::error!("[Gyroflow Toolbox Rust] Error importing Preset: {:?}", e); + + let error_msg = format!("{}", e); + let result = CString::new(error_msg).unwrap(); + return result.into_raw() + }, + } +} + /// This function is called from Objective-C land and is responsible for clearing the cache. /// /// # Returns @@ -871,7 +984,7 @@ pub extern "C" fn processFrame( let output_stride: usize = output_width * 4 * number_of_bytes_value; //--------------------------------------------------------- - // Stabilization time! + // Prepare the Metal Texture Image Buffers: //--------------------------------------------------------- let mut buffers = Buffers { output: BufferDescription { @@ -890,6 +1003,9 @@ pub extern "C" fn processFrame( } }; + //--------------------------------------------------------- + // Get the Stabilization Result: + //--------------------------------------------------------- let _stabilization_result = match pixel_format_string.as_ref() { "BGRA8Unorm" => { manager.process_pixels::(timestamp, &mut buffers) diff --git a/Source/Gyroflow.xcodeproj/project.pbxproj b/Source/Gyroflow.xcodeproj/project.pbxproj index a8983da7..91488d87 100644 --- a/Source/Gyroflow.xcodeproj/project.pbxproj +++ b/Source/Gyroflow.xcodeproj/project.pbxproj @@ -31,6 +31,7 @@ 224321B829615C4400EA591A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 224321B729615C4400EA591A /* Assets.xcassets */; }; 22BED1C32A5D6805000562A3 /* CustomButtonView.m in Sources */ = {isa = PBXBuildFile; fileRef = 22BED1BF2A5D6805000562A3 /* CustomButtonView.m */; }; 22BED1C42A5D6805000562A3 /* CustomDropZoneView.m in Sources */ = {isa = PBXBuildFile; fileRef = 22BED1C22A5D6805000562A3 /* CustomDropZoneView.m */; }; + 22C25B1D2A7B4AB700BB135B /* BRAWToolboxXMLReader.m in Sources */ = {isa = PBXBuildFile; fileRef = 22C25B1C2A7B4AB700BB135B /* BRAWToolboxXMLReader.m */; }; 22DAC75F2953B1A7001F2E06 /* GyroflowDocument.icns in Resources */ = {isa = PBXBuildFile; fileRef = 22DAC75E2953B1A7001F2E06 /* GyroflowDocument.icns */; }; 22F73D712A6009AA00A6F326 /* HeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 22F73D702A6009AA00A6F326 /* HeaderView.xib */; }; 22F73D742A600B3A00A6F326 /* HeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 22F73D732A600B3A00A6F326 /* HeaderView.m */; }; @@ -122,6 +123,8 @@ 22BED1C02A5D6805000562A3 /* CustomButtonView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CustomButtonView.h; sourceTree = ""; }; 22BED1C12A5D6805000562A3 /* CustomDropZoneView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CustomDropZoneView.h; sourceTree = ""; }; 22BED1C22A5D6805000562A3 /* CustomDropZoneView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CustomDropZoneView.m; sourceTree = ""; }; + 22C25B1B2A7B4AB700BB135B /* BRAWToolboxXMLReader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BRAWToolboxXMLReader.h; sourceTree = ""; }; + 22C25B1C2A7B4AB700BB135B /* BRAWToolboxXMLReader.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BRAWToolboxXMLReader.m; sourceTree = ""; }; 22DAC75E2953B1A7001F2E06 /* GyroflowDocument.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = GyroflowDocument.icns; sourceTree = ""; }; 22E4989F29492E8100580F67 /* Cargo.toml */ = {isa = PBXFileReference; lastKnownFileType = text; name = Cargo.toml; path = Frameworks/gyroflow/Cargo.toml; sourceTree = SOURCE_ROOT; }; 22F73D6F2A5FF9A000A6F326 /* SandboxEntitlements.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SandboxEntitlements.entitlements; sourceTree = ""; }; @@ -308,8 +311,7 @@ 228E9E1929517DDA00B2571E /* GyroflowConstants.h */, 2239AB2E2943F8F600028B77 /* GyroflowPlugIn.h */, 2239AB312943F8F600028B77 /* GyroflowPlugIn.m */, - 2239AB58294400CE00028B77 /* GyroflowParameters.h */, - 2239AB56294400B500028B77 /* GyroflowParameters.m */, + 22C25B1E2A7B5CAF00BB135B /* Helpers */, 22DAC7632953D1CF001F2E06 /* Custom Views */, ); name = Code; @@ -328,6 +330,17 @@ name = Extras; sourceTree = ""; }; + 22C25B1E2A7B5CAF00BB135B /* Helpers */ = { + isa = PBXGroup; + children = ( + 22C25B1B2A7B4AB700BB135B /* BRAWToolboxXMLReader.h */, + 22C25B1C2A7B4AB700BB135B /* BRAWToolboxXMLReader.m */, + 2239AB58294400CE00028B77 /* GyroflowParameters.h */, + 2239AB56294400B500028B77 /* GyroflowParameters.m */, + ); + name = Helpers; + sourceTree = ""; + }; 22DAC7632953D1CF001F2E06 /* Custom Views */ = { isa = PBXGroup; children = ( @@ -515,6 +528,7 @@ 22F73D742A600B3A00A6F326 /* HeaderView.m in Sources */, 22F73D912A604BB100A6F326 /* TileableRemoteBRAW.metal in Sources */, 2239AB392943F8F600028B77 /* MetalDeviceCache.m in Sources */, + 22C25B1D2A7B4AB700BB135B /* BRAWToolboxXMLReader.m in Sources */, 22BED1C32A5D6805000562A3 /* CustomButtonView.m in Sources */, 2239AB452943F8F600028B77 /* main.m in Sources */, 22BED1C42A5D6805000562A3 /* CustomDropZoneView.m in Sources */, @@ -631,7 +645,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 11.0; + MACOSX_DEPLOYMENT_TARGET = 12.6; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -691,7 +705,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 11.0; + MACOSX_DEPLOYMENT_TARGET = 12.6; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; diff --git a/Source/Gyroflow/Plugin/BRAWToolboxXMLReader.h b/Source/Gyroflow/Plugin/BRAWToolboxXMLReader.h new file mode 100644 index 00000000..e59ac001 --- /dev/null +++ b/Source/Gyroflow/Plugin/BRAWToolboxXMLReader.h @@ -0,0 +1,19 @@ +// +// BRAWToolboxXMLReader.h +// Gyroflow Toolbox Renderer +// +// Created by Chris Hocking on 3/8/2023. +// + +#import + +@interface BRAWToolboxXMLReader : NSObject + +@property (strong, nonatomic) NSString *currentElement; +@property (strong, nonatomic) NSString *filePath; +@property (strong, nonatomic) NSString *bookmarkData; +@property (nonatomic) BOOL isBRAWToolbox; + +- (NSDictionary *)readXML:(NSString *)xmlString; + +@end diff --git a/Source/Gyroflow/Plugin/BRAWToolboxXMLReader.m b/Source/Gyroflow/Plugin/BRAWToolboxXMLReader.m new file mode 100644 index 00000000..485673d3 --- /dev/null +++ b/Source/Gyroflow/Plugin/BRAWToolboxXMLReader.m @@ -0,0 +1,72 @@ +// +// BRAWToolboxXMLReader.m +// Gyroflow Toolbox Renderer +// +// Created by Chris Hocking on 3/8/2023. +// + +#import "BRAWToolboxXMLReader.h" + +//--------------------------------------------------------- +// BRAW Toolbox XML Reader: +//--------------------------------------------------------- + +@implementation BRAWToolboxXMLReader + +//--------------------------------------------------------- +// Read XML file: +//--------------------------------------------------------- +- (NSDictionary *)readXML:(NSString *)xmlString { + self.isBRAWToolbox = NO; + NSData *data = [xmlString dataUsingEncoding:NSUTF8StringEncoding]; + NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data]; + [parser setDelegate:self]; + [parser parse]; + + if (self.filePath && self.bookmarkData) { + return @{@"File Path": self.filePath, @"Bookmark Data": self.bookmarkData}; + } + return nil; +} + +//--------------------------------------------------------- +// XML Parser: +//--------------------------------------------------------- +- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict { + self.currentElement = elementName; + + if ([elementName isEqualToString:@"filter-video"] && [[attributeDict objectForKey:@"name"] isEqualToString:@"BRAW Toolbox"]) { + self.isBRAWToolbox = YES; + } + + if (self.isBRAWToolbox && [elementName isEqualToString:@"param"]) { + if ([[attributeDict objectForKey:@"name"] isEqualToString:@"File Path"]) { + self.filePath = [attributeDict objectForKey:@"value"]; + } else if ([[attributeDict objectForKey:@"name"] isEqualToString:@"Bookmark Data"]) { + self.bookmarkData = [attributeDict objectForKey:@"value"]; + } + } +} + +//--------------------------------------------------------- +// XML Parser: +//--------------------------------------------------------- +- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { + self.currentElement = nil; + if ([elementName isEqualToString:@"filter-video"]) { + self.isBRAWToolbox = NO; + } +} + +//--------------------------------------------------------- +// Dealloc: +//--------------------------------------------------------- +- (void)dealloc { + [_currentElement release]; + [_filePath release]; + [_bookmarkData release]; + + [super dealloc]; +} + +@end diff --git a/Source/Gyroflow/Plugin/CustomDropZoneView.m b/Source/Gyroflow/Plugin/CustomDropZoneView.m index 94702ce2..7331f925 100644 --- a/Source/Gyroflow/Plugin/CustomDropZoneView.m +++ b/Source/Gyroflow/Plugin/CustomDropZoneView.m @@ -92,9 +92,8 @@ - (BOOL)performDragOperation:(id)sender { //--------------------------------------------------------- // Trigger Dropped File Method: //--------------------------------------------------------- + //NSLog(@"[Gyroflow Toolbox Renderer] Dropped file path: %@", [fileURL path]); - NSLog(@"[Gyroflow Toolbox Renderer] Dropped file path: %@", [fileURL path]); - //--------------------------------------------------------- // Create a new security-scoped bookmark: //--------------------------------------------------------- @@ -115,12 +114,17 @@ - (BOOL)performDragOperation:(id)sender { return NO; } - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wobjc-method-access" - BOOL result = [_parentPlugin importDroppedMedia:bookmarkData]; - #pragma clang diagnostic pop - - return result; + dispatch_async(dispatch_get_main_queue(), ^{ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wobjc-method-access" + [_parentPlugin importDroppedMedia:bookmarkData]; + #pragma clang diagnostic pop + }); + + _dragIsOver = false; + [self needsDisplay]; + + return YES; } } else { @@ -135,10 +139,12 @@ - (BOOL)performDragOperation:(id)sender { //NSLog(@"[Gyroflow Toolbox Renderer] Dropped Final Cut Pro data: %@", finalCutProData); - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wobjc-method-access" - [_parentPlugin importDroppedClip:finalCutProData]; - #pragma clang diagnostic pop + dispatch_async(dispatch_get_main_queue(), ^{ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wobjc-method-access" + [_parentPlugin importDroppedClip:finalCutProData]; + #pragma clang diagnostic pop + }); _dragIsOver = false; [self needsDisplay]; diff --git a/Source/Gyroflow/Plugin/GyroflowConstants.h b/Source/Gyroflow/Plugin/GyroflowConstants.h index 8841bc64..e8a01d11 100644 --- a/Source/Gyroflow/Plugin/GyroflowConstants.h +++ b/Source/Gyroflow/Plugin/GyroflowConstants.h @@ -38,6 +38,7 @@ enum { kCB_TopSection = 1, kCB_Header = 2, kCB_OpenUserGuide = 3, + kCB_Settings = 4, //--------------------------------------------------------- // Import Section: diff --git a/Source/Gyroflow/Plugin/GyroflowPlugIn.h b/Source/Gyroflow/Plugin/GyroflowPlugIn.h index 5bf21940..172bfd50 100644 --- a/Source/Gyroflow/Plugin/GyroflowPlugIn.h +++ b/Source/Gyroflow/Plugin/GyroflowPlugIn.h @@ -8,6 +8,56 @@ #import #import +#include "gyroflow.h" + +#import "GyroflowParameters.h" +#import "GyroflowConstants.h" +#import "BRAWToolboxXMLReader.h" + +#import "CustomButtonView.h" +#import "CustomDropZoneView.h" + +#import "HeaderView.h" + +#import "TileableRemoteBRAWShaderTypes.h" +#import "MetalDeviceCache.h" + +#import +#import + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#import +#import + +//--------------------------------------------------------- +// Metal Performance Shaders for scaling: +//--------------------------------------------------------- +#import +#import +#import + +#import + +//--------------------------------------------------------- +// NSMenu Addition: +//--------------------------------------------------------- +@interface NSMenu () +- (BOOL)popUpMenuPositioningItem:(nullable NSMenuItem *)item atLocation:(NSPoint)location inView:(nullable NSView *)view appearance:(nullable NSAppearance *)appearance NS_AVAILABLE_MAC(10_6); +@end + +//--------------------------------------------------------- +// Gyroflow Plugin: +//--------------------------------------------------------- @interface GyroflowPlugIn : NSObject { //--------------------------------------------------------- // Cached Custom Views: @@ -22,6 +72,8 @@ NSView* headerView; NSView* loadPresetLensProfileView; NSView* exportGyroflowProjectView; + NSView* openUserGuideView; + NSView* settingsView; } -@property (assign) id apiManager; +@property (assign) id _Nonnull apiManager; @end diff --git a/Source/Gyroflow/Plugin/GyroflowPlugIn.m b/Source/Gyroflow/Plugin/GyroflowPlugIn.m index 0edce690..d4959a29 100644 --- a/Source/Gyroflow/Plugin/GyroflowPlugIn.m +++ b/Source/Gyroflow/Plugin/GyroflowPlugIn.m @@ -8,42 +8,7 @@ //--------------------------------------------------------- // Import Headers: //--------------------------------------------------------- -#include "gyroflow.h" - #import "GyroflowPlugIn.h" -#import "GyroflowParameters.h" -#import "GyroflowConstants.h" - -#import "CustomButtonView.h" -#import "CustomDropZoneView.h" - -#import "HeaderView.h" - -#import "TileableRemoteBRAWShaderTypes.h" -#import "MetalDeviceCache.h" - -#import -#import - -#include -#include -#include - -#include -#include -#include -#include - -#include -#import -#import - -//--------------------------------------------------------- -// Metal Performance Shaders for scaling: -//--------------------------------------------------------- -#import -#import -#import //--------------------------------------------------------- // Gyroflow FxPlug4 Implementation: @@ -68,28 +33,20 @@ - (nullable instancetype)initWithAPIManager:(id)newApiManager; //--------------------------------------------------------- NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); NSString *applicationSupportDirectory = [paths firstObject]; - NSLog(@"applicationSupportDirectory: '%@'", applicationSupportDirectory); NSString* logPath = [applicationSupportDirectory stringByAppendingString:@"/FxPlug.log"]; freopen([logPath fileSystemRepresentation],"a+",stderr); - NSLog(@"[Gyroflow Toolbox Renderer] --------------------------------- START OF NEW SESSION ---------------------------------"); NSString *version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; NSString *build = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]; + + NSLog(@"[Gyroflow Toolbox Renderer] --------------------------------- START OF NEW SESSION ---------------------------------"); NSLog(@"[Gyroflow Toolbox Renderer] Version: %@ (%@)", version, build); - + NSLog(@"[Gyroflow Toolbox Renderer] applicationSupportDirectory: '%@'", applicationSupportDirectory); + //--------------------------------------------------------- - // Test Rust Block: + // Cache the API Manager: //--------------------------------------------------------- - /* - 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; @@ -105,65 +62,65 @@ - (BOOL)properties:(NSDictionary * _Nonnull *)properties error:(NSError * _Nullable *)error { *properties = @{ - //--------------------------------------------------------- - // Deprecated, and no longer required in FxPlug 4: - // - // * kFxPropertyKey_IsThreadSafe - // * kFxPropertyKey_MayRemapTime - // * kFxPropertyKey_PixelIndependent - // * kFxPropertyKey_PreservesAlpha - // * kFxPropertyKey_UsesLumaChroma - // * kFxPropertyKey_UsesNonmatchingTextureLayout - // * kFxPropertyKey_UsesRationalTime - //--------------------------------------------------------- - - //--------------------------------------------------------- - // @const kFxPropertyKey_NeedsFullBuffer - // @abstract A key that determines whether the plug-in needs the entire image to do its - // processing, and can't tile its rendering. - // @discussion This value of this key is a Boolean NSNumber indicating whether this plug-in - // needs the entire image to do its processing. Note that setting this value to - // YES incurs a significant performance penalty and makes your plug-in - // unable to render large input images. The default value is NO. - //--------------------------------------------------------- - kFxPropertyKey_NeedsFullBuffer : [NSNumber numberWithBool:YES], - - //--------------------------------------------------------- - // @const kFxPropertyKey_VariesWhenParamsAreStatic - // @abstract A key that determines whether your rendering varies even when - // the parameters remain the same. - // @discussion The value for this key is a Boolean NSNumber indicating whether this effect - // changes its rendering even when the parameters don't change. This can happen if - // your rendering is based on timing in addition to parameters, for example. Note that - // this property is only checked once when the filter is applied, so it - // should go in static properties rather than dynamic properties. - //--------------------------------------------------------- - kFxPropertyKey_VariesWhenParamsAreStatic : [NSNumber numberWithBool:YES], - - //--------------------------------------------------------- - // @const kFxPropertyKey_ChangesOutputSize - // @abstract A key that determines whether your filter has the ability to change the size - // of its output to be different than the size of its input. - // @discussion The value of this key is a Boolean NSNumber indicating whether your filter - // returns an output that has a different size than the input. If not, return "NO" - // and your filter's @c -destinationImageRect:sourceImages:pluginState:atTime:error: - // method will not be called. - //--------------------------------------------------------- - kFxPropertyKey_ChangesOutputSize : [NSNumber numberWithBool:YES], - - //--------------------------------------------------------- - // @const kFxPropertyKey_DesiredProcessingColorInfo - // @abstract Key for properties dictionary - // @discussion The value for this key is an NSNumber indicating the colorspace - // that the plug-in would like to process in. This color space is - // expressed as an FxImageColorInfo enum. If a plug-in specifies this, - // and the host supports it, all inputs will be in this colorspace, - // and the output must also be in this colorspace. This may not - // be supported by all hosts, so the plug-in should still check - // the colorInfo of its input and output images. - //--------------------------------------------------------- - kFxPropertyKey_DesiredProcessingColorInfo : [NSNumber numberWithInt:kFxImageColorInfo_RGB_LINEAR], - }; + //--------------------------------------------------------- + // Deprecated, and no longer required in FxPlug 4: + // + // * kFxPropertyKey_IsThreadSafe + // * kFxPropertyKey_MayRemapTime + // * kFxPropertyKey_PixelIndependent + // * kFxPropertyKey_PreservesAlpha + // * kFxPropertyKey_UsesLumaChroma + // * kFxPropertyKey_UsesNonmatchingTextureLayout + // * kFxPropertyKey_UsesRationalTime + //--------------------------------------------------------- + + //--------------------------------------------------------- + // @const kFxPropertyKey_NeedsFullBuffer + // @abstract A key that determines whether the plug-in needs the entire image to do its + // processing, and can't tile its rendering. + // @discussion This value of this key is a Boolean NSNumber indicating whether this plug-in + // needs the entire image to do its processing. Note that setting this value to + // YES incurs a significant performance penalty and makes your plug-in + // unable to render large input images. The default value is NO. + //--------------------------------------------------------- + kFxPropertyKey_NeedsFullBuffer : [NSNumber numberWithBool:YES], + + //--------------------------------------------------------- + // @const kFxPropertyKey_VariesWhenParamsAreStatic + // @abstract A key that determines whether your rendering varies even when + // the parameters remain the same. + // @discussion The value for this key is a Boolean NSNumber indicating whether this effect + // changes its rendering even when the parameters don't change. This can happen if + // your rendering is based on timing in addition to parameters, for example. Note that + // this property is only checked once when the filter is applied, so it + // should go in static properties rather than dynamic properties. + //--------------------------------------------------------- + kFxPropertyKey_VariesWhenParamsAreStatic : [NSNumber numberWithBool:YES], + + //--------------------------------------------------------- + // @const kFxPropertyKey_ChangesOutputSize + // @abstract A key that determines whether your filter has the ability to change the size + // of its output to be different than the size of its input. + // @discussion The value of this key is a Boolean NSNumber indicating whether your filter + // returns an output that has a different size than the input. If not, return "NO" + // and your filter's @c -destinationImageRect:sourceImages:pluginState:atTime:error: + // method will not be called. + //--------------------------------------------------------- + kFxPropertyKey_ChangesOutputSize : [NSNumber numberWithBool:YES], + + //--------------------------------------------------------- + // @const kFxPropertyKey_DesiredProcessingColorInfo + // @abstract Key for properties dictionary + // @discussion The value for this key is an NSNumber indicating the colorspace + // that the plug-in would like to process in. This color space is + // expressed as an FxImageColorInfo enum. If a plug-in specifies this, + // and the host supports it, all inputs will be in this colorspace, + // and the output must also be in this colorspace. This may not + // be supported by all hosts, so the plug-in should still check + // the colorInfo of its input and output images. + //--------------------------------------------------------- + kFxPropertyKey_DesiredProcessingColorInfo : [NSNumber numberWithInt:kFxImageColorInfo_RGB_LINEAR], + }; return YES; } @@ -223,8 +180,8 @@ - (NSView*)createViewForParameterID:(UInt32)parameterID } else if (parameterID == kCB_DropZone) { NSView* view = [[CustomDropZoneView alloc] initWithAPIManager:_apiManager parentPlugin:self - buttonID:kCB_DropZone - buttonTitle:@"Import Dropped Clip"]; + buttonID:kCB_DropZone + buttonTitle:@"Import Dropped Clip"]; dropZoneView = view; return view; } else if (parameterID == kCB_RevealInFinder) { @@ -243,7 +200,7 @@ - (NSView*)createViewForParameterID:(UInt32)parameterID return view; } else if (parameterID == kCB_Header) { - NSRect frameRect = NSMakeRect(0, 0, 200, 268); // x y w h + NSRect frameRect = NSMakeRect(0, 0, 200, 310); // x y w h NSView* view = [[HeaderView alloc] initWithFrame:frameRect]; headerView = view; @@ -253,7 +210,14 @@ - (NSView*)createViewForParameterID:(UInt32)parameterID parentPlugin:self buttonID:kCB_OpenUserGuide buttonTitle:@"Open User Guide"]; - launchGyroflowView = view; + openUserGuideView = view; + return view; + } else if (parameterID == kCB_Settings) { + NSView* view = [[CustomButtonView alloc] initWithAPIManager:_apiManager + parentPlugin:self + buttonID:kCB_Settings + buttonTitle:@"Settings"]; + settingsView = view; return view; } else { NSLog(@"[Gyroflow Toolbox Renderer] BUG - createViewForParameterID requested a parameterID that we haven't allowed for: %u", (unsigned int)parameterID); @@ -261,21 +225,6 @@ - (NSView*)createViewForParameterID:(UInt32)parameterID } } -//--------------------------------------------------------- -// Notifies your plug-in when it becomes part of user’s -// document. -// -// Called when a new plug-in instance is created or a -// document is loaded and an existing instance is -// deserialised. When the host calls this method, the -// plug-in is a part of the document and the various API -// objects work as expected. -//--------------------------------------------------------- -- (void)pluginInstanceAddedToDocument -{ - //NSLog(@"[Gyroflow Toolbox Renderer] pluginInstanceAddedToDocument!"); -} - //--------------------------------------------------------- // #pragma mark - Parameters @@ -347,9 +296,9 @@ - (BOOL)addParametersWithError:(NSError**)error // ADD PARAMETER: 'Open User Guide' Button //--------------------------------------------------------- if (![paramAPI addCustomParameterWithName:@"" - parameterID:kCB_OpenUserGuide - defaultValue:@0 - parameterFlags:kFxParameterFlag_CUSTOM_UI | kFxParameterFlag_NOT_ANIMATABLE]) + parameterID:kCB_OpenUserGuide + defaultValue:@0 + parameterFlags:kFxParameterFlag_CUSTOM_UI | kFxParameterFlag_NOT_ANIMATABLE]) { if (error != NULL) { NSDictionary* userInfo = @{NSLocalizedDescriptionKey : @"[Gyroflow Toolbox Renderer] Unable to add parameter: kCB_OpenUserGuide"}; @@ -400,9 +349,9 @@ - (BOOL)addParametersWithError:(NSError**)error // ADD PARAMETER: Drop Clip Here //--------------------------------------------------------- if (![paramAPI addCustomParameterWithName:@"Drop Clip Here ➡" - parameterID:kCB_DropZone - defaultValue:@0 - parameterFlags:kFxParameterFlag_CUSTOM_UI | kFxParameterFlag_NOT_ANIMATABLE]) + parameterID:kCB_DropZone + defaultValue:@0 + parameterFlags:kFxParameterFlag_CUSTOM_UI | kFxParameterFlag_NOT_ANIMATABLE]) { if (error != NULL) { NSDictionary* userInfo = @{NSLocalizedDescriptionKey : @"[Gyroflow Toolbox Renderer] Unable to add parameter: kCB_DropZone"}; @@ -417,9 +366,9 @@ - (BOOL)addParametersWithError:(NSError**)error // ADD PARAMETER: 'Import Gyroflow Project' Button //--------------------------------------------------------- if (![paramAPI addCustomParameterWithName:@"" - parameterID:kCB_ImportGyroflowProject - defaultValue:@0 - parameterFlags:kFxParameterFlag_CUSTOM_UI | kFxParameterFlag_NOT_ANIMATABLE]) + parameterID:kCB_ImportGyroflowProject + defaultValue:@0 + parameterFlags:kFxParameterFlag_CUSTOM_UI | kFxParameterFlag_NOT_ANIMATABLE]) { if (error != NULL) { NSDictionary* userInfo = @{NSLocalizedDescriptionKey : @"[Gyroflow Toolbox Renderer] Unable to add parameter: kCB_ImportGyroflowProject"}; @@ -434,9 +383,9 @@ - (BOOL)addParametersWithError:(NSError**)error // ADD PARAMETER: 'Import Last Gyroflow Project' Button //--------------------------------------------------------- if (![paramAPI addCustomParameterWithName:@"" - parameterID:kCB_LoadLastGyroflowProject - defaultValue:@0 - parameterFlags:kFxParameterFlag_CUSTOM_UI | kFxParameterFlag_NOT_ANIMATABLE]) + parameterID:kCB_LoadLastGyroflowProject + defaultValue:@0 + parameterFlags:kFxParameterFlag_CUSTOM_UI | kFxParameterFlag_NOT_ANIMATABLE]) { if (error != NULL) { NSDictionary* userInfo = @{NSLocalizedDescriptionKey : @"[Gyroflow Toolbox Renderer] Unable to add parameter: kCB_LoadLastGyroflowProject"}; @@ -451,9 +400,9 @@ - (BOOL)addParametersWithError:(NSError**)error // ADD PARAMETER: 'Import Media File' Button //--------------------------------------------------------- if (![paramAPI addCustomParameterWithName:@"" - parameterID:kCB_ImportMediaFile - defaultValue:@0 - parameterFlags:kFxParameterFlag_CUSTOM_UI | kFxParameterFlag_NOT_ANIMATABLE]) + parameterID:kCB_ImportMediaFile + defaultValue:@0 + parameterFlags:kFxParameterFlag_CUSTOM_UI | kFxParameterFlag_NOT_ANIMATABLE]) { if (error != NULL) { NSDictionary* userInfo = @{NSLocalizedDescriptionKey : @"[Gyroflow Toolbox Renderer] Unable to add parameter: kCB_ImportMediaFile"}; @@ -468,9 +417,9 @@ - (BOOL)addParametersWithError:(NSError**)error // ADD PARAMETER: 'Load Preset/Lens Profile' Button //--------------------------------------------------------- if (![paramAPI addCustomParameterWithName:@"" - parameterID:kCB_LoadPresetLensProfile - defaultValue:@0 - parameterFlags:kFxParameterFlag_CUSTOM_UI | kFxParameterFlag_NOT_ANIMATABLE]) + parameterID:kCB_LoadPresetLensProfile + defaultValue:@0 + parameterFlags:kFxParameterFlag_CUSTOM_UI | kFxParameterFlag_NOT_ANIMATABLE]) { if (error != NULL) { NSDictionary* userInfo = @{NSLocalizedDescriptionKey : @"[Gyroflow Toolbox Renderer] Unable to add parameter: kCB_LoadPresetLensProfile"}; @@ -501,7 +450,7 @@ - (BOOL)addParametersWithError:(NSError**)error // GYROFLOW PARAMETERS: // //--------------------------------------------------------- - + //--------------------------------------------------------- // START GROUP: 'Gyroflow Parameters' //--------------------------------------------------------- @@ -582,7 +531,7 @@ - (BOOL)addParametersWithError:(NSError**)error } return NO; } - + //--------------------------------------------------------- // ADD PARAMETER: 'Lens Correction' Slider // @@ -649,7 +598,7 @@ - (BOOL)addParametersWithError:(NSError**)error } return NO; } - + //--------------------------------------------------------- // ADD PARAMETER: 'Horizon Roll' Slider // @@ -897,7 +846,7 @@ - (BOOL)addParametersWithError:(NSError**)error return NO; } } - + //--------------------------------------------------------- // // FILE MANAGEMENT: @@ -923,9 +872,9 @@ - (BOOL)addParametersWithError:(NSError**)error // ADD PARAMETER: 'Reload Gyroflow Project' Button //--------------------------------------------------------- if (![paramAPI addCustomParameterWithName:@"" - parameterID:kCB_ReloadGyroflowProject - defaultValue:@0 - parameterFlags:kFxParameterFlag_CUSTOM_UI | kFxParameterFlag_NOT_ANIMATABLE]) + parameterID:kCB_ReloadGyroflowProject + defaultValue:@0 + parameterFlags:kFxParameterFlag_CUSTOM_UI | kFxParameterFlag_NOT_ANIMATABLE]) { if (error != NULL) { NSDictionary* userInfo = @{NSLocalizedDescriptionKey : @"[Gyroflow Toolbox Renderer] Unable to add parameter: kCB_ReloadGyroflowProject"}; @@ -940,9 +889,9 @@ - (BOOL)addParametersWithError:(NSError**)error // ADD PARAMETER: 'Open in Gyroflow' Button //--------------------------------------------------------- if (![paramAPI addCustomParameterWithName:@"" - parameterID:kCB_LaunchGyroflow - defaultValue:@0 - parameterFlags:kFxParameterFlag_CUSTOM_UI | kFxParameterFlag_NOT_ANIMATABLE]) + parameterID:kCB_LaunchGyroflow + defaultValue:@0 + parameterFlags:kFxParameterFlag_CUSTOM_UI | kFxParameterFlag_NOT_ANIMATABLE]) { if (error != NULL) { NSDictionary* userInfo = @{NSLocalizedDescriptionKey : @"[Gyroflow Toolbox Renderer] Unable to add parameter: kCB_LaunchGyroflow"}; @@ -957,9 +906,9 @@ - (BOOL)addParametersWithError:(NSError**)error // ADD PARAMETER: 'Export Gyroflow Project' Button //--------------------------------------------------------- if (![paramAPI addCustomParameterWithName:@"" - parameterID:kCB_ExportGyroflowProject - defaultValue:@0 - parameterFlags:kFxParameterFlag_CUSTOM_UI | kFxParameterFlag_NOT_ANIMATABLE]) + parameterID:kCB_ExportGyroflowProject + defaultValue:@0 + parameterFlags:kFxParameterFlag_CUSTOM_UI | kFxParameterFlag_NOT_ANIMATABLE]) { if (error != NULL) { NSDictionary* userInfo = @{NSLocalizedDescriptionKey : @"[Gyroflow Toolbox Renderer] Unable to add parameter: kCB_ExportGyroflowProject"}; @@ -974,9 +923,9 @@ - (BOOL)addParametersWithError:(NSError**)error // ADD PARAMETER: 'Reveal in Finder' Button //--------------------------------------------------------- if (![paramAPI addCustomParameterWithName:@"" - parameterID:kCB_RevealInFinder - defaultValue:@0 - parameterFlags:kFxParameterFlag_CUSTOM_UI | kFxParameterFlag_NOT_ANIMATABLE]) + parameterID:kCB_RevealInFinder + defaultValue:@0 + parameterFlags:kFxParameterFlag_CUSTOM_UI | kFxParameterFlag_NOT_ANIMATABLE]) { if (error != NULL) { NSDictionary* userInfo = @{NSLocalizedDescriptionKey : @"[Gyroflow Toolbox Renderer] Unable to add parameter: kCB_RevealInFinder"}; @@ -987,6 +936,23 @@ - (BOOL)addParametersWithError:(NSError**)error return NO; } + //--------------------------------------------------------- + // ADD PARAMETER: 'Settings' Button + //--------------------------------------------------------- + if (![paramAPI addCustomParameterWithName:@"" + parameterID:kCB_Settings + defaultValue:@0 + parameterFlags:kFxParameterFlag_CUSTOM_UI | kFxParameterFlag_NOT_ANIMATABLE]) + { + if (error != NULL) { + NSDictionary* userInfo = @{NSLocalizedDescriptionKey : @"[Gyroflow Toolbox Renderer] Unable to add parameter: kCB_Settings"}; + *error = [NSError errorWithDomain:FxPlugErrorDomain + code:kFxError_InvalidParameter + userInfo:userInfo]; + } + return NO; + } + //--------------------------------------------------------- // ADD PARAMETER: 'Loaded Gyroflow Project' Text Box //--------------------------------------------------------- @@ -1092,7 +1058,7 @@ - (BOOL)addParametersWithError:(NSError**)error } return NO; } - + //--------------------------------------------------------- // ADD PARAMETER: 'Media Path' Text Box //--------------------------------------------------------- @@ -1140,7 +1106,7 @@ - (BOOL)parameterChanged:(UInt32)paramID error:(NSError * _Nullable *)error { if (paramID == kCB_DisableGyroflowStretch) { - NSLog(@"[Gyroflow Toolbox Renderer] Disable Gyroflow Stretch Changed!"); + //NSLog(@"[Gyroflow Toolbox Renderer] Disable Gyroflow Stretch Changed!"); trashCache(); } return YES; @@ -1177,14 +1143,16 @@ - (BOOL)pluginState:(NSData**)pluginState //--------------------------------------------------------- id timingAPI = [_apiManager apiForProtocol:@protocol(FxTimingAPI_v4)]; if (timingAPI == nil) { - NSLog(@"[Gyroflow Toolbox Renderer] Unable to retrieve FxTimingAPI_v4 in pluginStateAtTime."); + //--------------------------------------------------------- + // Show error message: + //--------------------------------------------------------- + NSString *errorMessage = @"Unable to retrieve FxTimingAPI_v4 in pluginStateAtTime."; + NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); if (error != NULL) { *error = [NSError errorWithDomain:FxPlugErrorDomain code:kFxError_FailedToLoadTimingAPI userInfo:@{ - NSLocalizedDescriptionKey : - @"Unable to retrieve FxTimingAPI_v4 in \ - [-pluginStateAtTime:]" }]; + NSLocalizedDescriptionKey : errorMessage }]; } return NO; } @@ -1194,14 +1162,16 @@ - (BOOL)pluginState:(NSData**)pluginState //--------------------------------------------------------- id paramGetAPI = [_apiManager apiForProtocol:@protocol(FxParameterRetrievalAPI_v6)]; if (paramGetAPI == nil) { - NSLog(@"[Gyroflow Toolbox Renderer] Unable to retrieve FxParameterRetrievalAPI_v6 in pluginStateAtTime."); + //--------------------------------------------------------- + // Show error message: + //--------------------------------------------------------- + NSString *errorMessage = @"Unable to retrieve FxParameterRetrievalAPI_v6 in pluginStateAtTime."; + NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); if (error != NULL) { *error = [NSError errorWithDomain:FxPlugErrorDomain code:kFxError_FailedToLoadParameterGetAPI userInfo:@{ - NSLocalizedDescriptionKey : - @"Unable to retrieve FxParameterRetrievalAPI_v6 in \ - [-pluginStateAtTime:]" }]; + NSLocalizedDescriptionKey : errorMessage }]; } return NO; } @@ -1216,22 +1186,22 @@ - (BOOL)pluginState:(NSData**)pluginState //--------------------------------------------------------- CMTime timelineFrameDuration = kCMTimeZero; timelineFrameDuration = CMTimeMake( [timingAPI timelineFpsDenominatorForEffect:self], - (int)[timingAPI timelineFpsNumeratorForEffect:self] ); - + (int)[timingAPI timelineFpsNumeratorForEffect:self] ); + CMTime timelineTime = kCMTimeZero; [timingAPI timelineTime:&timelineTime fromInputTime:renderTime]; - + CMTime startTimeOfInputToFilter = kCMTimeZero; [timingAPI startTimeForEffect:&startTimeOfInputToFilter]; - + CMTime startTimeOfInputToFilterInTimelineTime = kCMTimeZero; [timingAPI timelineTime:&startTimeOfInputToFilterInTimelineTime fromInputTime:startTimeOfInputToFilter]; - + Float64 timelineTimeMinusStartTimeOfInputToFilterNumerator = (Float64)timelineTime.value * (Float64)startTimeOfInputToFilterInTimelineTime.timescale - (Float64)startTimeOfInputToFilterInTimelineTime.value * (Float64)timelineTime.timescale; Float64 timelineTimeMinusStartTimeOfInputToFilterDenominator = (Float64)timelineTime.timescale * (Float64)startTimeOfInputToFilterInTimelineTime.timescale; - + Float64 frame = ( ((Float64)timelineTimeMinusStartTimeOfInputToFilterNumerator / (Float64)timelineTimeMinusStartTimeOfInputToFilterDenominator) / ((Float64)timelineFrameDuration.value / (Float64)timelineFrameDuration.timescale) ); - + //--------------------------------------------------------- // Calculate the Timestamp: //--------------------------------------------------------- @@ -1242,20 +1212,20 @@ - (BOOL)pluginState:(NSData**)pluginState params.timestamp = [[[NSNumber alloc] initWithFloat:timestamp] autorelease]; /* - NSLog(@"---------------------------------"); - NSLog(@"timelineFrameDuration: %.2f seconds", CMTimeGetSeconds(timelineFrameDuration)); - NSLog(@"timelineTime: %.2f seconds", CMTimeGetSeconds(timelineTime)); - NSLog(@"startTimeOfInputToFilter: %.2f seconds", CMTimeGetSeconds(startTimeOfInputToFilter)); - NSLog(@"startTimeOfInputToFilterInTimelineTime: %.2f seconds", CMTimeGetSeconds(startTimeOfInputToFilterInTimelineTime)); - NSLog(@"timelineTimeMinusStartTimeOfInputToFilterNumerator: %f", timelineTimeMinusStartTimeOfInputToFilterNumerator); - NSLog(@"timelineTimeMinusStartTimeOfInputToFilterDenominator: %f", timelineTimeMinusStartTimeOfInputToFilterDenominator); - NSLog(@"frame: %f", frame); - NSLog(@"timelineFpsNumerator: %f", timelineFpsNumerator); - NSLog(@"timelineFpsDenominator: %f", timelineFpsDenominator); - NSLog(@"frameRate: %f", frameRate); - NSLog(@"timestamp: %f", timestamp); - NSLog(@"---------------------------------"); - */ + NSLog(@"---------------------------------"); + NSLog(@"timelineFrameDuration: %.2f seconds", CMTimeGetSeconds(timelineFrameDuration)); + NSLog(@"timelineTime: %.2f seconds", CMTimeGetSeconds(timelineTime)); + NSLog(@"startTimeOfInputToFilter: %.2f seconds", CMTimeGetSeconds(startTimeOfInputToFilter)); + NSLog(@"startTimeOfInputToFilterInTimelineTime: %.2f seconds", CMTimeGetSeconds(startTimeOfInputToFilterInTimelineTime)); + NSLog(@"timelineTimeMinusStartTimeOfInputToFilterNumerator: %f", timelineTimeMinusStartTimeOfInputToFilterNumerator); + NSLog(@"timelineTimeMinusStartTimeOfInputToFilterDenominator: %f", timelineTimeMinusStartTimeOfInputToFilterDenominator); + NSLog(@"frame: %f", frame); + NSLog(@"timelineFpsNumerator: %f", timelineFpsNumerator); + NSLog(@"timelineFpsDenominator: %f", timelineFpsDenominator); + NSLog(@"frameRate: %f", frameRate); + NSLog(@"timestamp: %f", timestamp); + NSLog(@"---------------------------------"); + */ //--------------------------------------------------------- // Unique Identifier: @@ -1300,7 +1270,7 @@ - (BOOL)pluginState:(NSData**)pluginState double lensCorrection; [paramGetAPI getFloatValue:&lensCorrection fromParameter:kCB_LensCorrection atTime:renderTime]; params.lensCorrection = [NSNumber numberWithDouble:lensCorrection]; - + //--------------------------------------------------------- // Horizon Lock: //--------------------------------------------------------- @@ -1314,7 +1284,7 @@ - (BOOL)pluginState:(NSData**)pluginState double horizonRoll; [paramGetAPI getFloatValue:&horizonRoll fromParameter:kCB_HorizonRoll atTime:renderTime]; params.horizonRoll = [NSNumber numberWithDouble:horizonRoll]; - + //--------------------------------------------------------- // Position Offset X: //--------------------------------------------------------- @@ -1328,7 +1298,7 @@ - (BOOL)pluginState:(NSData**)pluginState double positionOffsetY; [paramGetAPI getFloatValue:&positionOffsetY fromParameter:kCB_PositionOffsetY atTime:renderTime]; params.positionOffsetY = [NSNumber numberWithDouble:positionOffsetY]; - + //--------------------------------------------------------- // Input Rotation: //--------------------------------------------------------- @@ -1363,8 +1333,12 @@ - (BOOL)pluginState:(NSData**)pluginState NSError *newPluginStateError; NSData *newPluginState = [NSKeyedArchiver archivedDataWithRootObject:params requiringSecureCoding:YES error:&newPluginStateError]; if (newPluginState == nil) { + //--------------------------------------------------------- + // Show error message: + //--------------------------------------------------------- + NSString* errorMessage = [NSString stringWithFormat:@"ERROR - Failed to create newPluginState due to '%@'", [newPluginStateError localizedDescription]]; + NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); if (error != NULL) { - NSString* errorMessage = [NSString stringWithFormat:@"[Gyroflow Toolbox Renderer] ERROR - Failed to create newPluginState due to '%@'", [newPluginStateError localizedDescription]]; *error = [NSError errorWithDomain:FxPlugErrorDomain code:kFxError_FailedToCreatePluginState userInfo:@{ NSLocalizedDescriptionKey : errorMessage }]; @@ -1377,12 +1351,17 @@ - (BOOL)pluginState:(NSData**)pluginState if (*pluginState != nil) { succeeded = YES; } else { + //--------------------------------------------------------- + // Show error message: + //--------------------------------------------------------- + NSString* errorMessage = @"ERROR - pluginState is nil in pluginState method."; + NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); *error = [NSError errorWithDomain:FxPlugErrorDomain code:kFxError_PlugInStateIsNil - userInfo:@{ NSLocalizedDescriptionKey : @"[Gyroflow Toolbox Renderer] pluginState is nil in -pluginState." }]; + userInfo:@{ NSLocalizedDescriptionKey : errorMessage }]; succeeded = NO; } - + return succeeded; } @@ -1452,6 +1431,289 @@ - (BOOL)sourceTileRect:(FxRect *)sourceTileRect return YES; } +//--------------------------------------------------------- +// Render an Error Message: +//--------------------------------------------------------- +- (BOOL)renderErrorMessageWithID:(NSString*)errorMessageID correctedHeight:(float *)correctedHeight destinationImage:(FxImageTile * _Nonnull)destinationImage differenceBetweenHeights:(float *)differenceBetweenHeights fullHeight:(float)fullHeight fullWidth:(float)fullWidth outError:(NSError * _Nullable * _Nullable)outError outputHeight:(float)outputHeight outputWidth:(float)outputWidth sourceImages:(NSArray * _Nonnull)sourceImages { + MetalDeviceCache* deviceCache = [MetalDeviceCache deviceCache]; + MTLPixelFormat pixelFormat = [MetalDeviceCache MTLPixelFormatForImageTile:destinationImage]; + id commandQueue = [deviceCache commandQueueWithRegistryID:destinationImage.deviceRegistryID + pixelFormat:pixelFormat]; + if (commandQueue == nil) + { + NSString *errorMessage = @"FATAL ERROR: commandQueue was nil when attempting to show an error message."; + NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); + if (outError != NULL) { + *outError = [NSError errorWithDomain:FxPlugErrorDomain + code:kFxError_CommandQueueWasNilDuringShowErrorMessage + userInfo:@{ NSLocalizedDescriptionKey : errorMessage }]; + } + return NO; + } + + id commandBuffer = [commandQueue commandBuffer]; + commandBuffer.label = @"Gyroflow Toolbox Error Command Buffer"; + [commandBuffer enqueue]; + + //--------------------------------------------------------- + // Load the texture from our "Assets": + //--------------------------------------------------------- + MTKTextureLoader *loader = [[MTKTextureLoader alloc] initWithDevice:commandQueue.device]; + + NSDictionary *options = [[NSDictionary alloc] initWithObjectsAndKeys: + [NSNumber numberWithBool:YES], MTKTextureLoaderOptionSRGB, + nil]; + + id inputTexture = [loader newTextureWithName:errorMessageID scaleFactor:1.0 bundle:[NSBundle mainBundle] options:options error:nil]; + id outputTexture = [destinationImage metalTextureForDevice:[deviceCache deviceWithRegistryID:destinationImage.deviceRegistryID]]; + + //--------------------------------------------------------- + // If square pixels, we'll manipulate the height and y + // axis manually: + //--------------------------------------------------------- + if (fullHeight == outputHeight) { + *correctedHeight = ((float)inputTexture.height/(float)inputTexture.width) * outputWidth; + *differenceBetweenHeights = (outputHeight - *correctedHeight) / 2; + } + + //--------------------------------------------------------- + // Use a "Metal Performance Shader" to scale the texture + // to the correct size. Note, we're using the full width + // and height, to compensate for non-square pixels: + //--------------------------------------------------------- + id scaledInputTexture = nil; + if (fullHeight != outputHeight) { + + //--------------------------------------------------------- + // Create a new Command Buffer for scale transform: + //--------------------------------------------------------- + id scaleCommandBuffer = [commandQueue commandBuffer]; + scaleCommandBuffer.label = @"Gyroflow Toolbox Scale Command Buffer"; + [scaleCommandBuffer enqueue]; + + //--------------------------------------------------------- + // Create a new texture for the scaled image: + //--------------------------------------------------------- + MTLTextureDescriptor *scaleTextureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:inputTexture.pixelFormat + width:fullWidth + height:fullHeight + mipmapped:NO]; + + scaledInputTexture = [inputTexture.device newTextureWithDescriptor:scaleTextureDescriptor]; + + //--------------------------------------------------------- + // Work out how much to scale/re-position: + //--------------------------------------------------------- + float scaleX = (float)(fullWidth / inputTexture.width); + float scaleY = (float)(fullHeight / inputTexture.height); + + if (scaleX > scaleY) { + scaleX = scaleY; + } else { + scaleY = scaleX; + } + + float translateX = (float)((fullWidth - inputTexture.width * scaleX) / 2); + float translateY = (float)((fullHeight - inputTexture.height * scaleY) / 2); + + MPSScaleTransform transform; + transform.scaleX = scaleX; // The horizontal scale factor. + transform.scaleY = scaleY; // The vertical scale factor. + transform.translateX = translateX; // The horizontal translation factor. + transform.translateY = translateY; // The vertical translation factor. + + //--------------------------------------------------------- + // A filter that resizes and changes the aspect ratio of + // an image: + // + // NOTE: In v1.0.0 we use MPSImageLanczosScale, however + // I've changed to MPSImageBilinearScale as it's + // faster, and we probably prefer speed over + // quality for thumbnails. + //--------------------------------------------------------- + MPSImageBilinearScale *filter = [[[MPSImageBilinearScale alloc] initWithDevice:commandQueue.device] autorelease]; + [filter setScaleTransform:&transform]; + [filter encodeToCommandBuffer:scaleCommandBuffer sourceTexture:inputTexture destinationTexture:scaledInputTexture]; + + //--------------------------------------------------------- + // Commits the scale command buffer for execution: + //--------------------------------------------------------- + [scaleCommandBuffer commit]; + } + + //--------------------------------------------------------- + // Release the texture loader: + //--------------------------------------------------------- + [options release]; + [loader release]; + + MTLRenderPassColorAttachmentDescriptor* colorAttachmentDescriptor = [[MTLRenderPassColorAttachmentDescriptor alloc] init]; + colorAttachmentDescriptor.texture = outputTexture; + colorAttachmentDescriptor.clearColor = MTLClearColorMake(0.0, 0.0, 1.0, 1.0); + colorAttachmentDescriptor.loadAction = MTLLoadActionClear; + MTLRenderPassDescriptor* renderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor]; + renderPassDescriptor.colorAttachments [ 0 ] = colorAttachmentDescriptor; + id commandEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; + + //--------------------------------------------------------- + // Calculate the vertex coordinates and the texture + // coordinates: + //--------------------------------------------------------- + float textureLeft = (destinationImage.tilePixelBounds.left - destinationImage.imagePixelBounds.left) / fullWidth; + float textureRight = (destinationImage.tilePixelBounds.right - destinationImage.imagePixelBounds.left) / fullWidth; + float textureBottom = (destinationImage.tilePixelBounds.bottom - destinationImage.imagePixelBounds.bottom) / fullHeight; + float textureTop = (destinationImage.tilePixelBounds.top - destinationImage.imagePixelBounds.bottom) / fullHeight; + + Vertex2D vertices[] = { + { { outputWidth / 2.0f, -outputHeight / 2.0f }, { textureRight, textureTop } }, + { { -outputWidth / 2.0f, -outputHeight / 2.0f }, { textureLeft, textureTop } }, + { { outputWidth / 2.0f, outputHeight / 2.0f }, { textureRight, textureBottom } }, + { { -outputWidth / 2.0f, outputHeight / 2.0f }, { textureLeft, textureBottom } } + }; + + //--------------------------------------------------------- + // Setup our viewport: + // + // MTLViewport: A 3D rectangular region for the viewport + // clipping. + //--------------------------------------------------------- + MTLViewport viewport = { + 0, *differenceBetweenHeights, outputWidth, *correctedHeight, -1.0, 1.0 + }; + + //--------------------------------------------------------- + // Sets the viewport used for transformations and clipping: + //--------------------------------------------------------- + [commandEncoder setViewport:viewport]; + + //--------------------------------------------------------- + // Setup our Render Pipeline State. + // + // MTLRenderPipelineState: An object that contains graphics + // functions and configuration state to use in a render + // command. + //--------------------------------------------------------- + id pipelineState = [deviceCache pipelineStateWithRegistryID:sourceImages[0].deviceRegistryID + pixelFormat:pixelFormat]; + + //--------------------------------------------------------- + // Sets the current render pipeline state object: + //--------------------------------------------------------- + [commandEncoder setRenderPipelineState:pipelineState]; + + //--------------------------------------------------------- + // Sets a block of data for the vertex shader: + //--------------------------------------------------------- + [commandEncoder setVertexBytes:vertices + length:sizeof(vertices) + atIndex:BVI_Vertices]; + + //--------------------------------------------------------- + // Set the viewport size: + //--------------------------------------------------------- + simd_uint2 viewportSize = { + (unsigned int)(outputWidth), + (unsigned int)(outputHeight) + }; + + //--------------------------------------------------------- + // Sets a block of data for the vertex shader: + //--------------------------------------------------------- + [commandEncoder setVertexBytes:&viewportSize + length:sizeof(viewportSize) + atIndex:BVI_ViewportSize]; + + //--------------------------------------------------------- + // Sets a texture for the fragment function at an index + // in the texture argument table: + //--------------------------------------------------------- + if (scaledInputTexture != nil) { + //--------------------------------------------------------- + // Use our scaled input texture for non-square pixels: + //--------------------------------------------------------- + [commandEncoder setFragmentTexture:scaledInputTexture + atIndex:BTI_InputImage]; + } else { + //--------------------------------------------------------- + // Use the data straight from the MTLBuffer for square + // pixels to avoid any extra processing: + //--------------------------------------------------------- + [commandEncoder setFragmentTexture:inputTexture + atIndex:BTI_InputImage]; + } + + //--------------------------------------------------------- + // drawPrimitives: Encodes a command to render one instance + // of primitives using vertex data in contiguous array + // elements. + // + // MTLPrimitiveTypeTriangleStrip: For every three adjacent + // vertices, rasterize a triangle. + //--------------------------------------------------------- + [commandEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip + vertexStart:0 + vertexCount:4]; + + //--------------------------------------------------------- + // Declares that all command generation from the encoder + // is completed. After `endEncoding` is called, the + // command encoder has no further use. You cannot encode + // any other commands with this encoder. + //--------------------------------------------------------- + [commandEncoder endEncoding]; + + //--------------------------------------------------------- + // Commits the command buffer for execution. + // After you call the commit method, the MTLDevice schedules + // and executes the commands in the command buffer. If you + // haven’t already enqueued the command buffer with a call + // to enqueue, calling this function also enqueues the + // command buffer. The GPU executes the command buffer + // after any command buffers enqueued before it on the same + // command queue. + // + // You can only commit a command buffer once. You can’t + // commit a command buffer if the command buffer has an + // active command encoder. Once you commit a command buffer, + // you may not encode additional commands into it, nor can + // you add a schedule or completion handler. + //--------------------------------------------------------- + [commandBuffer commit]; + + //--------------------------------------------------------- + // Blocks execution of the current thread until execution + // of the command buffer is completed. + //--------------------------------------------------------- + [commandBuffer waitUntilCompleted]; + + //--------------------------------------------------------- + // Release the `colorAttachmentDescriptor` we created + // earlier: + //--------------------------------------------------------- + [colorAttachmentDescriptor release]; + + //--------------------------------------------------------- + // Release the Input Texture: + //--------------------------------------------------------- + if (inputTexture != nil) { + [inputTexture setPurgeableState:MTLPurgeableStateEmpty]; + [inputTexture release]; + inputTexture = nil; + } + if (scaledInputTexture != nil) { + [scaledInputTexture setPurgeableState:MTLPurgeableStateEmpty]; + [scaledInputTexture release]; + scaledInputTexture = nil; + } + + //--------------------------------------------------------- + // Return the Command Queue back to the cache: + //--------------------------------------------------------- + [deviceCache returnCommandQueueToCache:commandQueue]; + + return YES; +} + //--------------------------------------------------------- // renderDestinationImage:sourceImages:pluginState:atTime:error: // @@ -1477,9 +1739,11 @@ - (BOOL)renderDestinationImage:(FxImageTile *)destinationImage if ((pluginState == nil) || (sourceImages [ 0 ].ioSurface == nil) || (destinationImage.ioSurface == nil)) { if (outError != NULL) { + NSString *errorMessage = @"FATAL ERROR - Invalid plugin state received from host."; + NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); *outError = [NSError errorWithDomain:FxPlugErrorDomain code:kFxError_InvalidParameter - userInfo:@{ NSLocalizedDescriptionKey : @"[Gyroflow Toolbox Renderer] FATAL ERROR - Invalid plugin state received from host." }]; + userInfo:@{ NSLocalizedDescriptionKey : errorMessage }]; } return NO; } @@ -1491,7 +1755,8 @@ - (BOOL)renderDestinationImage:(FxImageTile *)destinationImage NSError *paramsError; GyroflowParameters *params = [NSKeyedUnarchiver unarchivedObjectOfClass:[GyroflowParameters class] fromData:pluginState error:¶msError]; if (params == nil) { - NSString *errorMessage = [NSString stringWithFormat:@"[Gyroflow Toolbox Renderer] FATAL ERROR - Parameters was nil in -renderDestinationImage due to '%@'.", [paramsError localizedDescription]]; + NSString *errorMessage = [NSString stringWithFormat:@"FATAL ERROR - Parameters was nil in -renderDestinationImage due to '%@'.", [paramsError localizedDescription]]; + NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); if (outError != NULL) { *outError = [NSError errorWithDomain:FxPlugErrorDomain code:kFxError_InvalidParameter @@ -1510,7 +1775,7 @@ - (BOOL)renderDestinationImage:(FxImageTile *)destinationImage NSNumber *fov = params.fov; NSNumber *smoothness = params.smoothness; NSNumber *lensCorrection = params.lensCorrection; - + NSNumber *horizonLock = params.horizonLock; NSNumber *horizonRoll = params.horizonRoll; NSNumber *positionOffsetX = params.positionOffsetX; @@ -1522,7 +1787,7 @@ - (BOOL)renderDestinationImage:(FxImageTile *)destinationImage NSNumber *disableGyroflowStretch = params.disableGyroflowStretch; //NSLog(@"[Gyroflow Toolbox Renderer] uniqueIdentifier: '%@'", uniqueIdentifier); - + //--------------------------------------------------------- // Calculate output width & height: //--------------------------------------------------------- @@ -1530,7 +1795,7 @@ - (BOOL)renderDestinationImage:(FxImageTile *)destinationImage float outputHeight = (destinationImage.tilePixelBounds.top - destinationImage.tilePixelBounds.bottom); float fullWidth = (destinationImage.imagePixelBounds.right - destinationImage.imagePixelBounds.left); float fullHeight = (destinationImage.imagePixelBounds.top - destinationImage.imagePixelBounds.bottom); - + //--------------------------------------------------------- // Prepare our aspect ratio correction objects: //--------------------------------------------------------- @@ -1538,297 +1803,16 @@ - (BOOL)renderDestinationImage:(FxImageTile *)destinationImage float differenceBetweenHeights = 0; //--------------------------------------------------------- - // - // HOUSTON WE HAVE A PROBLEM! - // - // There's no unique identifier, so let's abort: - // + // There's no unique identifier or Gyroflow Data, + // so let's abort: //--------------------------------------------------------- - if (uniqueIdentifier == nil || [uniqueIdentifier isEqualToString:@""]) { - - NSLog(@"[Gyroflow Toolbox Renderer] Showing early error message!"); - - MetalDeviceCache* deviceCache = [MetalDeviceCache deviceCache]; - MTLPixelFormat pixelFormat = [MetalDeviceCache MTLPixelFormatForImageTile:destinationImage]; - id commandQueue = [deviceCache commandQueueWithRegistryID:destinationImage.deviceRegistryID - pixelFormat:pixelFormat]; - if (commandQueue == nil) - { - NSString *errorMessage = @"[Gyroflow Toolbox Renderer] FATAL ERROR: commandQueue was nil when attempting to show an error message."; - NSLog(@"%@", errorMessage); - if (outError != NULL) { - *outError = [NSError errorWithDomain:FxPlugErrorDomain - code:kFxError_CommandQueueWasNilDuringShowErrorMessage - userInfo:@{ NSLocalizedDescriptionKey : errorMessage }]; - } - return NO; - } - - id commandBuffer = [commandQueue commandBuffer]; - commandBuffer.label = @"Gyroflow Toolbox Error Command Buffer"; - [commandBuffer enqueue]; - - //--------------------------------------------------------- - // Load the texture from our "Assets": - //--------------------------------------------------------- - MTKTextureLoader *loader = [[MTKTextureLoader alloc] initWithDevice:commandQueue.device]; - - NSDictionary *options = [[NSDictionary alloc] initWithObjectsAndKeys: - [NSNumber numberWithBool:YES], MTKTextureLoaderOptionSRGB, - nil]; - - id inputTexture = [loader newTextureWithName:@"NoGyroflowProjectLoaded" scaleFactor:1.0 bundle:[NSBundle mainBundle] options:options error:nil]; - id outputTexture = [destinationImage metalTextureForDevice:[deviceCache deviceWithRegistryID:destinationImage.deviceRegistryID]]; - - //--------------------------------------------------------- - // If square pixels, we'll manipulate the height and y - // axis manually: - //--------------------------------------------------------- - if (fullHeight == outputHeight) { - correctedHeight = ((float)inputTexture.height/(float)inputTexture.width) * outputWidth; - differenceBetweenHeights = (outputHeight - correctedHeight) / 2; - } - - //--------------------------------------------------------- - // Use a "Metal Performance Shader" to scale the texture - // to the correct size. Note, we're using the full width - // and height, to compensate for non-square pixels: - //--------------------------------------------------------- - id scaledInputTexture = nil; - if (fullHeight != outputHeight) { - - //--------------------------------------------------------- - // Create a new Command Buffer for scale transform: - //--------------------------------------------------------- - id scaleCommandBuffer = [commandQueue commandBuffer]; - scaleCommandBuffer.label = @"Gyroflow Toolbox Scale Command Buffer"; - [scaleCommandBuffer enqueue]; - - //--------------------------------------------------------- - // Create a new texture for the scaled image: - //--------------------------------------------------------- - MTLTextureDescriptor *scaleTextureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:inputTexture.pixelFormat - width:fullWidth - height:fullHeight - mipmapped:NO]; - - scaledInputTexture = [inputTexture.device newTextureWithDescriptor:scaleTextureDescriptor]; - - //--------------------------------------------------------- - // Work out how much to scale/re-position: - //--------------------------------------------------------- - float scaleX = (float)(fullWidth / inputTexture.width); - float scaleY = (float)(fullHeight / inputTexture.height); - - if (scaleX > scaleY) { - scaleX = scaleY; - } else { - scaleY = scaleX; - } - - float translateX = (float)((fullWidth - inputTexture.width * scaleX) / 2); - float translateY = (float)((fullHeight - inputTexture.height * scaleY) / 2); - - MPSScaleTransform transform; - transform.scaleX = scaleX; // The horizontal scale factor. - transform.scaleY = scaleY; // The vertical scale factor. - transform.translateX = translateX; // The horizontal translation factor. - transform.translateY = translateY; // The vertical translation factor. - - //--------------------------------------------------------- - // A filter that resizes and changes the aspect ratio of - // an image: - // - // NOTE: In v1.0.0 we use MPSImageLanczosScale, however - // I've changed to MPSImageBilinearScale as it's - // faster, and we probably prefer speed over - // quality for thumbnails. - //--------------------------------------------------------- - MPSImageBilinearScale *filter = [[[MPSImageBilinearScale alloc] initWithDevice:commandQueue.device] autorelease]; - [filter setScaleTransform:&transform]; - [filter encodeToCommandBuffer:scaleCommandBuffer sourceTexture:inputTexture destinationTexture:scaledInputTexture]; - - //--------------------------------------------------------- - // Commits the scale command buffer for execution: - //--------------------------------------------------------- - [scaleCommandBuffer commit]; - } - - //--------------------------------------------------------- - // Release the texture loader: - //--------------------------------------------------------- - [options release]; - [loader release]; - - MTLRenderPassColorAttachmentDescriptor* colorAttachmentDescriptor = [[MTLRenderPassColorAttachmentDescriptor alloc] init]; - colorAttachmentDescriptor.texture = outputTexture; - colorAttachmentDescriptor.clearColor = MTLClearColorMake(0.0, 0.0, 1.0, 1.0); - colorAttachmentDescriptor.loadAction = MTLLoadActionClear; - MTLRenderPassDescriptor* renderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor]; - renderPassDescriptor.colorAttachments [ 0 ] = colorAttachmentDescriptor; - id commandEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; - - //--------------------------------------------------------- - // Calculate the vertex coordinates and the texture - // coordinates: - //--------------------------------------------------------- - float textureLeft = (destinationImage.tilePixelBounds.left - destinationImage.imagePixelBounds.left) / fullWidth; - float textureRight = (destinationImage.tilePixelBounds.right - destinationImage.imagePixelBounds.left) / fullWidth; - float textureBottom = (destinationImage.tilePixelBounds.bottom - destinationImage.imagePixelBounds.bottom) / fullHeight; - float textureTop = (destinationImage.tilePixelBounds.top - destinationImage.imagePixelBounds.bottom) / fullHeight; - - Vertex2D vertices[] = { - { { outputWidth / 2.0f, -outputHeight / 2.0f }, { textureRight, textureTop } }, - { { -outputWidth / 2.0f, -outputHeight / 2.0f }, { textureLeft, textureTop } }, - { { outputWidth / 2.0f, outputHeight / 2.0f }, { textureRight, textureBottom } }, - { { -outputWidth / 2.0f, outputHeight / 2.0f }, { textureLeft, textureBottom } } - }; - - //--------------------------------------------------------- - // Setup our viewport: - // - // MTLViewport: A 3D rectangular region for the viewport - // clipping. - //--------------------------------------------------------- - MTLViewport viewport = { - 0, differenceBetweenHeights, outputWidth, correctedHeight, -1.0, 1.0 - }; - - //--------------------------------------------------------- - // Sets the viewport used for transformations and clipping: - //--------------------------------------------------------- - [commandEncoder setViewport:viewport]; - - //--------------------------------------------------------- - // Setup our Render Pipeline State. - // - // MTLRenderPipelineState: An object that contains graphics - // functions and configuration state to use in a render - // command. - //--------------------------------------------------------- - id pipelineState = [deviceCache pipelineStateWithRegistryID:sourceImages[0].deviceRegistryID - pixelFormat:pixelFormat]; - - //--------------------------------------------------------- - // Sets the current render pipeline state object: - //--------------------------------------------------------- - [commandEncoder setRenderPipelineState:pipelineState]; - - //--------------------------------------------------------- - // Sets a block of data for the vertex shader: - //--------------------------------------------------------- - [commandEncoder setVertexBytes:vertices - length:sizeof(vertices) - atIndex:BVI_Vertices]; - - //--------------------------------------------------------- - // Set the viewport size: - //--------------------------------------------------------- - simd_uint2 viewportSize = { - (unsigned int)(outputWidth), - (unsigned int)(outputHeight) - }; - - //--------------------------------------------------------- - // Sets a block of data for the vertex shader: - //--------------------------------------------------------- - [commandEncoder setVertexBytes:&viewportSize - length:sizeof(viewportSize) - atIndex:BVI_ViewportSize]; - - //--------------------------------------------------------- - // Sets a texture for the fragment function at an index - // in the texture argument table: - //--------------------------------------------------------- - if (scaledInputTexture != nil) { - //--------------------------------------------------------- - // Use our scaled input texture for non-square pixels: - //--------------------------------------------------------- - [commandEncoder setFragmentTexture:scaledInputTexture - atIndex:BTI_InputImage]; - } else { - //--------------------------------------------------------- - // Use the data straight from the MTLBuffer for square - // pixels to avoid any extra processing: - //--------------------------------------------------------- - [commandEncoder setFragmentTexture:inputTexture - atIndex:BTI_InputImage]; - } - - //--------------------------------------------------------- - // drawPrimitives: Encodes a command to render one instance - // of primitives using vertex data in contiguous array - // elements. - // - // MTLPrimitiveTypeTriangleStrip: For every three adjacent - // vertices, rasterize a triangle. - //--------------------------------------------------------- - [commandEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip - vertexStart:0 - vertexCount:4]; - - //--------------------------------------------------------- - // Declares that all command generation from the encoder - // is completed. After `endEncoding` is called, the - // command encoder has no further use. You cannot encode - // any other commands with this encoder. - //--------------------------------------------------------- - [commandEncoder endEncoding]; - - //--------------------------------------------------------- - // Commits the command buffer for execution. - // After you call the commit method, the MTLDevice schedules - // and executes the commands in the command buffer. If you - // haven’t already enqueued the command buffer with a call - // to enqueue, calling this function also enqueues the - // command buffer. The GPU executes the command buffer - // after any command buffers enqueued before it on the same - // command queue. - // - // You can only commit a command buffer once. You can’t - // commit a command buffer if the command buffer has an - // active command encoder. Once you commit a command buffer, - // you may not encode additional commands into it, nor can - // you add a schedule or completion handler. - //--------------------------------------------------------- - [commandBuffer commit]; - - //--------------------------------------------------------- - // Blocks execution of the current thread until execution - // of the command buffer is completed. - //--------------------------------------------------------- - [commandBuffer waitUntilCompleted]; - - //--------------------------------------------------------- - // Release the `colorAttachmentDescriptor` we created - // earlier: - //--------------------------------------------------------- - [colorAttachmentDescriptor release]; - - //--------------------------------------------------------- - // Release the Input Texture: - //--------------------------------------------------------- - if (inputTexture != nil) { - [inputTexture setPurgeableState:MTLPurgeableStateEmpty]; - [inputTexture release]; - inputTexture = nil; - } - if (scaledInputTexture != nil) { - [scaledInputTexture setPurgeableState:MTLPurgeableStateEmpty]; - [scaledInputTexture release]; - scaledInputTexture = nil; - } - - //--------------------------------------------------------- - // Return the Command Queue back to the cache: - //--------------------------------------------------------- - [deviceCache returnCommandQueueToCache:commandQueue]; - - return YES; + if (uniqueIdentifier == nil || [uniqueIdentifier isEqualToString:@""] || gyroflowData == nil || [gyroflowData isEqualToString:@""]) { + //NSLog(@"[Gyroflow Toolbox Renderer] Showing early error message!"); + return [self renderErrorMessageWithID:@"NoGyroflowProjectLoaded" correctedHeight:&correctedHeight destinationImage:destinationImage differenceBetweenHeights:&differenceBetweenHeights fullHeight:fullHeight fullWidth:fullWidth outError:outError outputHeight:outputHeight outputWidth:outputWidth sourceImages:sourceImages]; } //NSLog(@"[Gyroflow Toolbox Renderer] uniqueIdentifier during render: %@", uniqueIdentifier); - + //--------------------------------------------------------- // Set up the renderer, in this case we are using Metal. //--------------------------------------------------------- @@ -1844,16 +1828,21 @@ - (BOOL)renderDestinationImage:(FxImageTile *)destinationImage //--------------------------------------------------------- id commandQueue = [deviceCache commandQueueWithRegistryID:sourceImages[0].deviceRegistryID pixelFormat:pixelFormat]; - + //--------------------------------------------------------- // If the Command Queue wasn't created, abort: //--------------------------------------------------------- if (commandQueue == nil) { + //--------------------------------------------------------- + // Output error message to Console: + //--------------------------------------------------------- + NSString *errorMessage = @"FATAL ERROR - Unable to get command queue. May need to increase cache size."; + NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); if (outError != NULL) { *outError = [NSError errorWithDomain:FxPlugErrorDomain code:kFxError_InvalidParameter - userInfo:@{ NSLocalizedDescriptionKey : @"[Gyroflow Toolbox Renderer] FATAL ERROR - Unable to get command queue. May need to increase cache size." }]; + userInfo:@{ NSLocalizedDescriptionKey : errorMessage }]; } return NO; } @@ -1884,7 +1873,11 @@ - (BOOL)renderDestinationImage:(FxImageTile *)destinationImage inputPixelFormat = @"RGBAf"; numberOfBytes = 4; } else { - NSString *errorMessage = [NSString stringWithFormat:@"[Gyroflow Toolbox Renderer] BUG - Unsupported pixelFormat for inputTexture: %lu", (unsigned long)inputTexture.pixelFormat]; + //--------------------------------------------------------- + // Output error message to Console: + //--------------------------------------------------------- + NSString *errorMessage = [NSString stringWithFormat:@"BUG - Unsupported pixelFormat for inputTexture: %lu", (unsigned long)inputTexture.pixelFormat]; + NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); if (outError != NULL) { *outError = [NSError errorWithDomain:FxPlugErrorDomain code:kFxError_UnsupportedPixelFormat @@ -1916,342 +1909,60 @@ - (BOOL)renderDestinationImage:(FxImageTile *)destinationImage uint8_t xDisableGyroflowStretch = [disableGyroflowStretch unsignedCharValue]; //--------------------------------------------------------- - // Only trigger the Rust function if we have Gyroflow Data: - //--------------------------------------------------------- - if (![gyroflowData isEqualToString:@""]) { - //--------------------------------------------------------- - // Trigger the Gyroflow Rust Function: - //--------------------------------------------------------- - const char* result = processFrame( - xUniqueIdentifier, // const char* - xWidth, // uint32_t - xHeight, // uint32_t - xPixelFormat, // const char* - numberOfBytes, // int - xPath, // const char* - xData, // const char* - xTimestamp, // int64_t - xFOV, // double - xSmoothness, // double - xLensCorrection, // double - xHorizonLock, // double - xHorizonRoll, // double - xPositionOffsetX, // double - xPositionOffsetY, // double - xInputRotation, // double - xVideoRotation, // double - xFOVOverview, // uint8_t - xDisableGyroflowStretch, // uint8_t - inputTexture, // MTLTexture - outputTexture, // MTLTexture - commandQueue // MTLCommandQueue - ); - - NSString *resultString = [NSString stringWithUTF8String: result]; - //NSLog(@"[Gyroflow Toolbox Renderer] resultString: %@", resultString); - - if ([resultString isEqualToString:@"FAIL"]) { - - NSLog(@"[Gyroflow Toolbox Renderer] Showing fail error message!"); - - MetalDeviceCache* deviceCache = [MetalDeviceCache deviceCache]; - MTLPixelFormat pixelFormat = [MetalDeviceCache MTLPixelFormatForImageTile:destinationImage]; - id commandQueue = [deviceCache commandQueueWithRegistryID:destinationImage.deviceRegistryID - pixelFormat:pixelFormat]; - if (commandQueue == nil) - { - NSString *errorMessage = @"[Gyroflow Toolbox Renderer] FATAL ERROR: commandQueue was nil when attempting to show an error message."; - NSLog(@"%@", errorMessage); - if (outError != NULL) { - *outError = [NSError errorWithDomain:FxPlugErrorDomain - code:kFxError_CommandQueueWasNilDuringShowErrorMessage - userInfo:@{ NSLocalizedDescriptionKey : errorMessage }]; - } - return NO; - } - - id commandBuffer = [commandQueue commandBuffer]; - commandBuffer.label = @"Gyroflow Toolbox Error Command Buffer"; - [commandBuffer enqueue]; - - //--------------------------------------------------------- - // Load the texture from our "Assets": - //--------------------------------------------------------- - MTKTextureLoader *loader = [[MTKTextureLoader alloc] initWithDevice:commandQueue.device]; - - NSDictionary *options = [[NSDictionary alloc] initWithObjectsAndKeys: - [NSNumber numberWithBool:YES], MTKTextureLoaderOptionSRGB, - nil]; - - id inputTexture = [loader newTextureWithName:@"GyroflowCoreRenderError" scaleFactor:1.0 bundle:[NSBundle mainBundle] options:options error:nil]; - id outputTexture = [destinationImage metalTextureForDevice:[deviceCache deviceWithRegistryID:destinationImage.deviceRegistryID]]; - - //--------------------------------------------------------- - // If square pixels, we'll manipulate the height and y - // axis manually: - //--------------------------------------------------------- - if (fullHeight == outputHeight) { - correctedHeight = ((float)inputTexture.height/(float)inputTexture.width) * outputWidth; - differenceBetweenHeights = (outputHeight - correctedHeight) / 2; - } - - //--------------------------------------------------------- - // Use a "Metal Performance Shader" to scale the texture - // to the correct size. Note, we're using the full width - // and height, to compensate for non-square pixels: - //--------------------------------------------------------- - id scaledInputTexture = nil; - if (fullHeight != outputHeight) { - - //--------------------------------------------------------- - // Create a new Command Buffer for scale transform: - //--------------------------------------------------------- - id scaleCommandBuffer = [commandQueue commandBuffer]; - scaleCommandBuffer.label = @"Gyroflow Toolbox Scale Command Buffer"; - [scaleCommandBuffer enqueue]; - - //--------------------------------------------------------- - // Create a new texture for the scaled image: - //--------------------------------------------------------- - MTLTextureDescriptor *scaleTextureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:inputTexture.pixelFormat - width:fullWidth - height:fullHeight - mipmapped:NO]; - - scaledInputTexture = [inputTexture.device newTextureWithDescriptor:scaleTextureDescriptor]; - - //--------------------------------------------------------- - // Work out how much to scale/re-position: - //--------------------------------------------------------- - float scaleX = (float)(fullWidth / inputTexture.width); - float scaleY = (float)(fullHeight / inputTexture.height); - - if (scaleX > scaleY) { - scaleX = scaleY; - } else { - scaleY = scaleX; - } - - float translateX = (float)((fullWidth - inputTexture.width * scaleX) / 2); - float translateY = (float)((fullHeight - inputTexture.height * scaleY) / 2); - - MPSScaleTransform transform; - transform.scaleX = scaleX; // The horizontal scale factor. - transform.scaleY = scaleY; // The vertical scale factor. - transform.translateX = translateX; // The horizontal translation factor. - transform.translateY = translateY; // The vertical translation factor. - - //--------------------------------------------------------- - // A filter that resizes and changes the aspect ratio of - // an image: - // - // NOTE: In v1.0.0 we use MPSImageLanczosScale, however - // I've changed to MPSImageBilinearScale as it's - // faster, and we probably prefer speed over - // quality for thumbnails. - //--------------------------------------------------------- - MPSImageBilinearScale *filter = [[[MPSImageBilinearScale alloc] initWithDevice:commandQueue.device] autorelease]; - [filter setScaleTransform:&transform]; - [filter encodeToCommandBuffer:scaleCommandBuffer sourceTexture:inputTexture destinationTexture:scaledInputTexture]; - - //--------------------------------------------------------- - // Commits the scale command buffer for execution: - //--------------------------------------------------------- - [scaleCommandBuffer commit]; - } - - //--------------------------------------------------------- - // Release the texture loader: - //--------------------------------------------------------- - [options release]; - [loader release]; - - MTLRenderPassColorAttachmentDescriptor* colorAttachmentDescriptor = [[MTLRenderPassColorAttachmentDescriptor alloc] init]; - colorAttachmentDescriptor.texture = outputTexture; - colorAttachmentDescriptor.clearColor = MTLClearColorMake(1.0, 0.5, 0.0, 1.0); - colorAttachmentDescriptor.loadAction = MTLLoadActionClear; - MTLRenderPassDescriptor* renderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor]; - renderPassDescriptor.colorAttachments [ 0 ] = colorAttachmentDescriptor; - id commandEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; - - //--------------------------------------------------------- - // Calculate the vertex coordinates and the texture - // coordinates: - //--------------------------------------------------------- - float textureLeft = (destinationImage.tilePixelBounds.left - destinationImage.imagePixelBounds.left) / fullWidth; - float textureRight = (destinationImage.tilePixelBounds.right - destinationImage.imagePixelBounds.left) / fullWidth; - float textureBottom = (destinationImage.tilePixelBounds.bottom - destinationImage.imagePixelBounds.bottom) / fullHeight; - float textureTop = (destinationImage.tilePixelBounds.top - destinationImage.imagePixelBounds.bottom) / fullHeight; - - Vertex2D vertices[] = { - { { outputWidth / 2.0f, -outputHeight / 2.0f }, { textureRight, textureTop } }, - { { -outputWidth / 2.0f, -outputHeight / 2.0f }, { textureLeft, textureTop } }, - { { outputWidth / 2.0f, outputHeight / 2.0f }, { textureRight, textureBottom } }, - { { -outputWidth / 2.0f, outputHeight / 2.0f }, { textureLeft, textureBottom } } - }; - - //--------------------------------------------------------- - // Setup our viewport: - // - // MTLViewport: A 3D rectangular region for the viewport - // clipping. - //--------------------------------------------------------- - MTLViewport viewport = { - 0, differenceBetweenHeights, outputWidth, correctedHeight, -1.0, 1.0 - }; - - //--------------------------------------------------------- - // Sets the viewport used for transformations and clipping: - //--------------------------------------------------------- - [commandEncoder setViewport:viewport]; - - //--------------------------------------------------------- - // Setup our Render Pipeline State. - // - // MTLRenderPipelineState: An object that contains graphics - // functions and configuration state to use in a render - // command. - //--------------------------------------------------------- - id pipelineState = [deviceCache pipelineStateWithRegistryID:sourceImages[0].deviceRegistryID - pixelFormat:pixelFormat]; - - //--------------------------------------------------------- - // Sets the current render pipeline state object: - //--------------------------------------------------------- - [commandEncoder setRenderPipelineState:pipelineState]; - - //--------------------------------------------------------- - // Sets a block of data for the vertex shader: - //--------------------------------------------------------- - [commandEncoder setVertexBytes:vertices - length:sizeof(vertices) - atIndex:BVI_Vertices]; - - //--------------------------------------------------------- - // Set the viewport size: - //--------------------------------------------------------- - simd_uint2 viewportSize = { - (unsigned int)(outputWidth), - (unsigned int)(outputHeight) - }; - - //--------------------------------------------------------- - // Sets a block of data for the vertex shader: - //--------------------------------------------------------- - [commandEncoder setVertexBytes:&viewportSize - length:sizeof(viewportSize) - atIndex:BVI_ViewportSize]; - - //--------------------------------------------------------- - // Sets a texture for the fragment function at an index - // in the texture argument table: - //--------------------------------------------------------- - if (scaledInputTexture != nil) { - //--------------------------------------------------------- - // Use our scaled input texture for non-square pixels: - //--------------------------------------------------------- - [commandEncoder setFragmentTexture:scaledInputTexture - atIndex:BTI_InputImage]; - } else { - //--------------------------------------------------------- - // Use the data straight from the MTLBuffer for square - // pixels to avoid any extra processing: - //--------------------------------------------------------- - [commandEncoder setFragmentTexture:inputTexture - atIndex:BTI_InputImage]; - } - - //--------------------------------------------------------- - // drawPrimitives: Encodes a command to render one instance - // of primitives using vertex data in contiguous array - // elements. - // - // MTLPrimitiveTypeTriangleStrip: For every three adjacent - // vertices, rasterize a triangle. - //--------------------------------------------------------- - [commandEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip - vertexStart:0 - vertexCount:4]; - - //--------------------------------------------------------- - // Declares that all command generation from the encoder - // is completed. After `endEncoding` is called, the - // command encoder has no further use. You cannot encode - // any other commands with this encoder. - //--------------------------------------------------------- - [commandEncoder endEncoding]; - - //--------------------------------------------------------- - // Commits the command buffer for execution. - // After you call the commit method, the MTLDevice schedules - // and executes the commands in the command buffer. If you - // haven’t already enqueued the command buffer with a call - // to enqueue, calling this function also enqueues the - // command buffer. The GPU executes the command buffer - // after any command buffers enqueued before it on the same - // command queue. - // - // You can only commit a command buffer once. You can’t - // commit a command buffer if the command buffer has an - // active command encoder. Once you commit a command buffer, - // you may not encode additional commands into it, nor can - // you add a schedule or completion handler. - //--------------------------------------------------------- - [commandBuffer commit]; - - //--------------------------------------------------------- - // Blocks execution of the current thread until execution - // of the command buffer is completed. - //--------------------------------------------------------- - [commandBuffer waitUntilCompleted]; - - //--------------------------------------------------------- - // Release the `colorAttachmentDescriptor` we created - // earlier: - //--------------------------------------------------------- - [colorAttachmentDescriptor release]; - - //--------------------------------------------------------- - // Release the Input Texture: - //--------------------------------------------------------- - if (inputTexture != nil) { - [inputTexture setPurgeableState:MTLPurgeableStateEmpty]; - [inputTexture release]; - inputTexture = nil; - } - if (scaledInputTexture != nil) { - [scaledInputTexture setPurgeableState:MTLPurgeableStateEmpty]; - [scaledInputTexture release]; - scaledInputTexture = nil; - } - - //--------------------------------------------------------- - // Return the Command Queue back to the cache: - //--------------------------------------------------------- - [deviceCache returnCommandQueueToCache:commandQueue]; - - return YES; - - } + // Trigger the Gyroflow Rust Function: + //--------------------------------------------------------- + const char* result = processFrame( + xUniqueIdentifier, // const char* + xWidth, // uint32_t + xHeight, // uint32_t + xPixelFormat, // const char* + numberOfBytes, // int + xPath, // const char* + xData, // const char* + xTimestamp, // int64_t + xFOV, // double + xSmoothness, // double + xLensCorrection, // double + xHorizonLock, // double + xHorizonRoll, // double + xPositionOffsetX, // double + xPositionOffsetY, // double + xInputRotation, // double + xVideoRotation, // double + xFOVOverview, // uint8_t + xDisableGyroflowStretch, // uint8_t + inputTexture, // MTLTexture + outputTexture, // MTLTexture + commandQueue // MTLCommandQueue + ); + + NSString *resultString = [NSString stringWithUTF8String: result]; + //NSLog(@"[Gyroflow Toolbox Renderer] resultString: %@", resultString); + + //--------------------------------------------------------- + // Gyroflow Core had an error, so abort: + //--------------------------------------------------------- + if (![resultString isEqualToString:@"DONE"]) { + return [self renderErrorMessageWithID:@"GyroflowCoreRenderError" correctedHeight:&correctedHeight destinationImage:destinationImage differenceBetweenHeights:&differenceBetweenHeights fullHeight:fullHeight fullWidth:fullWidth outError:outError outputHeight:outputHeight outputWidth:outputWidth sourceImages:sourceImages]; } - + //--------------------------------------------------------- // Debugging: //--------------------------------------------------------- /* - NSString *debugMessage = [NSString stringWithFormat:@"[Gyroflow Toolbox Renderer] RENDERING A FRAME:\n"]; - debugMessage = [debugMessage stringByAppendingFormat:@"processFrame result: %@\n", resultString]; - debugMessage = [debugMessage stringByAppendingFormat:@"inputTexture.width: %lu\n", (unsigned long)inputTexture.width]; - debugMessage = [debugMessage stringByAppendingFormat:@"inputTexture.height: %lu\n", (unsigned long)inputTexture.height]; - debugMessage = [debugMessage stringByAppendingFormat:@"outputWidth: %f\n", outputWidth]; - debugMessage = [debugMessage stringByAppendingFormat:@"outputHeight: %f\n", outputHeight]; - debugMessage = [debugMessage stringByAppendingFormat:@"gyroflowPath: %@\n", gyroflowPath]; - debugMessage = [debugMessage stringByAppendingFormat:@"timestamp: %@\n", timestamp]; - debugMessage = [debugMessage stringByAppendingFormat:@"fov: %f\n", sourceFOV]; - debugMessage = [debugMessage stringByAppendingFormat:@"smoothness: %f\n", sourceSmoothness]; - debugMessage = [debugMessage stringByAppendingFormat:@"lensCorrection: %f\n", sourceLensCorrection]; - NSLog(@"%@", debugMessage); - */ + NSString *debugMessage = [NSString stringWithFormat:@"[Gyroflow Toolbox Renderer] RENDERING A FRAME:\n"]; + debugMessage = [debugMessage stringByAppendingFormat:@"processFrame result: %@\n", resultString]; + debugMessage = [debugMessage stringByAppendingFormat:@"inputTexture.width: %lu\n", (unsigned long)inputTexture.width]; + debugMessage = [debugMessage stringByAppendingFormat:@"inputTexture.height: %lu\n", (unsigned long)inputTexture.height]; + debugMessage = [debugMessage stringByAppendingFormat:@"outputWidth: %f\n", outputWidth]; + debugMessage = [debugMessage stringByAppendingFormat:@"outputHeight: %f\n", outputHeight]; + debugMessage = [debugMessage stringByAppendingFormat:@"gyroflowPath: %@\n", gyroflowPath]; + debugMessage = [debugMessage stringByAppendingFormat:@"timestamp: %@\n", timestamp]; + debugMessage = [debugMessage stringByAppendingFormat:@"fov: %f\n", sourceFOV]; + debugMessage = [debugMessage stringByAppendingFormat:@"smoothness: %f\n", sourceSmoothness]; + debugMessage = [debugMessage stringByAppendingFormat:@"lensCorrection: %f\n", sourceLensCorrection]; + NSLog(@"%@", debugMessage); + */ //NSLog(@"[Gyroflow Toolbox Renderer] inputTexture.debugDescription: %@", inputTexture.debugDescription); @@ -2260,11 +1971,6 @@ - (BOOL)renderDestinationImage:(FxImageTile *)destinationImage // so we can re-use it again: //--------------------------------------------------------- [deviceCache returnCommandQueueToCache:commandQueue]; - - //--------------------------------------------------------- - // Release the Input Textures: - //--------------------------------------------------------- - //[inputTexture setPurgeableState:MTLPurgeableStateEmpty]; return YES; } @@ -2298,9 +2004,85 @@ - (void)customButtonViewPressed:(UInt32)buttonID [self buttonLoadPresetLensProfile]; } else if (buttonID == kCB_OpenUserGuide) { [self buttonOpenUserGuide]; + } else if (buttonID == kCB_Settings) { + [self buttonSettings]; } } +//--------------------------------------------------------- +// BUTTON: 'Settings' +//--------------------------------------------------------- +-(void)buttonSettings { + dispatch_async(dispatch_get_main_queue(), ^{ + //--------------------------------------------------------- + // Get User Defaults: + //--------------------------------------------------------- + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + + //--------------------------------------------------------- + // Create the menu: + //--------------------------------------------------------- + NSMenu *settingsMenu = [[[NSMenu alloc] initWithTitle:@"Settings"] autorelease]; + + //--------------------------------------------------------- + // Create the "Show Alerts" sub-menu: + //--------------------------------------------------------- + NSMenu *disableAlertSubMenu = [[[NSMenu alloc] initWithTitle:@"Disable Alerts"] autorelease]; + + //--------------------------------------------------------- + // "Show Alerts" Sub Menu Items: + //--------------------------------------------------------- + { + //--------------------------------------------------------- + // Request Sandbox Access: + //--------------------------------------------------------- + NSMenuItem *suppressRequestSandboxAccessAlert = [[[NSMenuItem alloc] initWithTitle:@"Request Sandbox Access" action:@selector(toggleMenuItem:) keyEquivalent:@""] autorelease]; + suppressRequestSandboxAccessAlert.identifier = @"suppressRequestSandboxAccessAlert"; + suppressRequestSandboxAccessAlert.target = self; + suppressRequestSandboxAccessAlert.enabled = YES; + suppressRequestSandboxAccessAlert.state = [self boolToControlState:[userDefaults boolForKey:@"suppressRequestSandboxAccessAlert"]]; + [disableAlertSubMenu addItem:suppressRequestSandboxAccessAlert]; + + //--------------------------------------------------------- + // No Lens Profile Detected: + //--------------------------------------------------------- + NSMenuItem *suppressNoLensProfileDetected = [[[NSMenuItem alloc] initWithTitle:@"No Lens Profile Detected" action:@selector(toggleMenuItem:) keyEquivalent:@""] autorelease]; + suppressNoLensProfileDetected.identifier = @"suppressNoLensProfileDetected"; + suppressNoLensProfileDetected.target = self; + suppressNoLensProfileDetected.enabled = YES; + suppressNoLensProfileDetected.state = [self boolToControlState:[userDefaults boolForKey:@"suppressNoLensProfileDetected"]]; + [disableAlertSubMenu addItem:suppressNoLensProfileDetected]; + } + //--------------------------------------------------------- + // Add "Show Alerts" Sub Menu: + //--------------------------------------------------------- + NSMenuItem *disableAlertSubMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Disable Alerts" action:nil keyEquivalent:@""] autorelease]; + [disableAlertSubMenuItem setSubmenu:disableAlertSubMenu]; + + [settingsMenu addItem:disableAlertSubMenuItem]; + + //--------------------------------------------------------- + // Get the current mouse location: + //--------------------------------------------------------- + NSPoint mouseLocation = [NSEvent mouseLocation]; + + //--------------------------------------------------------- + // Show the menu at the current mouse location: + //--------------------------------------------------------- + [settingsMenu popUpMenuPositioningItem:nil atLocation:mouseLocation inView:nil appearance:[NSAppearance appearanceNamed:NSAppearanceNameVibrantDark]]; + }); +} + +//--------------------------------------------------------- +// Toggle Menu Item: +//--------------------------------------------------------- +- (void)toggleMenuItem:(id)sender { + NSMenuItem *menuItem = (NSMenuItem *)sender; + BOOL state = menuItem.state == NSControlStateValueOff; // NOTE: We want the opposite for the toggle. + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + [userDefaults setBool:state forKey:menuItem.identifier]; +} + //--------------------------------------------------------- // BUTTON: 'Export Gyroflow Project' //--------------------------------------------------------- @@ -2310,10 +2092,15 @@ - (void)buttonExportGyroflowProject { //--------------------------------------------------------- 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."]; + //--------------------------------------------------------- + // Show Error Message: + //--------------------------------------------------------- + NSString *errorMessage = @"Unable to retrieve 'FxCustomParameterActionAPI_v4'. This shouldn't happen, so it's probably a bug."; + NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); + [self showAlertWithMessage:@"An error has occurred." info:errorMessage]; return; } - + //--------------------------------------------------------- // Use the Action API to allow us to change the parameters: //--------------------------------------------------------- @@ -2329,9 +2116,14 @@ - (void)buttonExportGyroflowProject { //--------------------------------------------------------- [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; - } + //--------------------------------------------------------- + // Show Error Message: + //--------------------------------------------------------- + NSString *errorMessage = @"Unable to retrieve 'FxParameterRetrievalAPI_v6'.\n\nThis shouldn't happen, so it's probably a bug."; + NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); + [self showAlertWithMessage:@"An error has occurred." info:errorMessage]; + return; + } //--------------------------------------------------------- // Get the existing Gyroflow Project Data: @@ -2364,6 +2156,8 @@ - (void)buttonExportGyroflowProject { if (gyroflowProjectName != nil || [gyroflowProjectName isEqualToString:@""]) { gyroflowProjectName = [gyroflowProjectName stringByAppendingString:@".gyroflow"]; + } else { + gyroflowProjectName = @"Untitled.gyroflow"; } //--------------------------------------------------------- @@ -2371,31 +2165,30 @@ - (void)buttonExportGyroflowProject { //--------------------------------------------------------- [actionAPI endAction:self]; + //--------------------------------------------------------- + // Limit the file type to Gyroflow supported media files: + //--------------------------------------------------------- + UTType *gyroflow = [UTType typeWithFilenameExtension:@"gyroflow"]; + NSArray *allowedContentTypes = [NSArray arrayWithObjects:gyroflow, nil]; + //--------------------------------------------------------- // 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 @@ -2475,14 +2268,6 @@ - (void)buttonLoadPresetLensProfile { return; } - /* - const char* loadLensProfile( - const char* gyroflow_project_data, - const char* lens_profile_path - ); - */ - - //--------------------------------------------------------- // Load the Custom Parameter Action API: //--------------------------------------------------------- @@ -2491,7 +2276,7 @@ - (void)buttonLoadPresetLensProfile { [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: //--------------------------------------------------------- @@ -2517,6 +2302,9 @@ - (void)buttonLoadPresetLensProfile { id paramSetAPI = [_apiManager apiForProtocol:@protocol(FxParameterSettingAPI_v5)]; if (paramSetAPI == nil) { + //--------------------------------------------------------- + // Show Error Message: + //--------------------------------------------------------- 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]; @@ -2533,32 +2321,63 @@ - (void)buttonLoadPresetLensProfile { [self showAlertWithMessage:@"Failed to get Gyroflow Project" info:@"Please ensure you have a Gyroflow Project already loaded."]; return; } - - NSString *lensProfilePath = [url path]; - - const char* loadResult = loadLensProfile( - [gyroflowProjectData UTF8String], - [lensProfilePath UTF8String] - ); - [url stopAccessingSecurityScopedResource]; + //--------------------------------------------------------- + // Process the file depending on the file type: + //--------------------------------------------------------- + NSString *filePath = [url path]; + NSString *extension = [[url pathExtension] lowercaseString]; + NSString *loadResultString = nil; - NSString *loadResultString = [NSString stringWithUTF8String: loadResult]; + if ([extension isEqualToString:@"json"]) { + //--------------------------------------------------------- + // Attempt to load the JSON Lens Profile: + //--------------------------------------------------------- + const char* loadResult = loadLensProfile( + [gyroflowProjectData UTF8String], + [filePath UTF8String] + ); + loadResultString = [NSString stringWithUTF8String: loadResult]; + } else { + //--------------------------------------------------------- + // Attempt to load the Gyroflow Project Preset: + //--------------------------------------------------------- + const char* loadResult = loadPreset( + [gyroflowProjectData UTF8String], + [filePath UTF8String] + ); + loadResultString = [NSString stringWithUTF8String: loadResult]; + } + //--------------------------------------------------------- + // Stop Accessing File: + //--------------------------------------------------------- + [url stopAccessingSecurityScopedResource]; + + //--------------------------------------------------------- + // Abort is failed: + //--------------------------------------------------------- if (loadResultString == nil || [loadResultString isEqualToString:@"FAIL"]) { [self showAlertWithMessage:@"An error has occurred" info:@"Failed to load a Lens Profile or Preset."]; [actionAPI endAction:self]; return; } + //--------------------------------------------------------- + // Save the data to FxPlug: + //--------------------------------------------------------- [paramSetAPI setStringParameterValue:loadResultString toParameter:kCB_GyroflowProjectData]; //--------------------------------------------------------- // Trash the cache! //--------------------------------------------------------- trashCache(); - - [self showAlertWithMessage:@"VICTORY!" info:@""]; + + //--------------------------------------------------------- + // Show success message: + //--------------------------------------------------------- + // TODO: Make lens profile complete alert optional + [self showAlertWithMessage:@"Import Complete!" info:@"Your Lens Profile has been correctly applied."]; //--------------------------------------------------------- // Stop Action API: @@ -2575,10 +2394,15 @@ - (void)buttonRevealInFinder { //--------------------------------------------------------- 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."]; + //--------------------------------------------------------- + // Show Error Message: + //--------------------------------------------------------- + NSString *errorMessage = @"Unable to retrieve 'FxCustomParameterActionAPI_v4'. This shouldn't happen, so it's probably a bug."; + NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); + [self showAlertWithMessage:@"An error has occurred." info:errorMessage]; return; } - + //--------------------------------------------------------- // Use the Action API to allow us to change the parameters: //--------------------------------------------------------- @@ -2594,7 +2418,12 @@ - (void)buttonRevealInFinder { //--------------------------------------------------------- [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."]; + //--------------------------------------------------------- + // Show Error Message: + //--------------------------------------------------------- + NSString *errorMessage = @"Unable to retrieve 'FxParameterRetrievalAPI_v6'.\n\nThis shouldn't happen, so it's probably a bug."; + NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); + [self showAlertWithMessage:@"An error has occurred." info:errorMessage]; return; } @@ -2634,7 +2463,12 @@ - (void)buttonLaunchGyroflow { //--------------------------------------------------------- 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."]; + //--------------------------------------------------------- + // Show Error Message: + //--------------------------------------------------------- + NSString *errorMessage = @"Unable to retrieve 'FxCustomParameterActionAPI_v4'. This shouldn't happen, so it's probably a bug."; + NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); + [self showAlertWithMessage:@"An error has occurred." info:errorMessage]; return; } @@ -2653,7 +2487,12 @@ - (void)buttonLaunchGyroflow { //--------------------------------------------------------- [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."]; + //--------------------------------------------------------- + // Show Error Message: + //--------------------------------------------------------- + NSString *errorMessage = @"Unable to retrieve 'FxParameterRetrievalAPI_v6'.\n\nThis shouldn't happen, so it's probably a bug."; + NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); + [self showAlertWithMessage:@"An error has occurred." info:errorMessage]; return; } @@ -2700,12 +2539,13 @@ - (void)buttonLaunchGyroflow { //--------------------------------------------------------- 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; - + if (isMediaFile) { [paramGetAPI getStringParameterValue:&encodedBookmark fromParameter:kCB_MediaBookmarkData]; } else { @@ -2759,17 +2599,18 @@ - (void)buttonLaunchGyroflow { } //--------------------------------------------------------- - // There is an existing project path, so load Gyroflow - // with that path: + // This is depreciated, but there's no better way to do + // it in a sandbox sadly without prompting for access + // to the Gyroflow Application: //--------------------------------------------------------- - //NSURL *appURL = [[NSWorkspace sharedWorkspace] URLForApplicationWithBundleIdentifier:bundleIdentifier]; - //NSWorkspaceOpenConfiguration *config = [[[NSWorkspaceOpenConfiguration alloc] init] autorelease]; - //[[NSWorkspace sharedWorkspace] openURLs:@[url] withApplicationAtURL:appURL configuration:config completionHandler:nil]; - - // NOTE TO SELF: For some dumb reason the above fails with a sandboxing error, but the below works: - - [[NSWorkspace sharedWorkspace] openURL:url]; + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" + [[NSWorkspace sharedWorkspace] openFile:[url path] withApplication:@"Gyroflow"]; + #pragma GCC diagnostic pop + //--------------------------------------------------------- + // Stop Accessing Security Scoped Resource: + //--------------------------------------------------------- [url stopAccessingSecurityScopedResource]; } @@ -2790,22 +2631,22 @@ - (void)buttonImportGyroflowProject { // BUTTON: 'Load Last Gyroflow Project' //--------------------------------------------------------- - (void)buttonLoadLastGyroflowProject { - NSLog(@"[Gyroflow Toolbox Renderer] Load Last Gyroflow Project Pressed!"); + //NSLog(@"[Gyroflow Toolbox Renderer] Load Last Gyroflow Project Pressed!"); if ([self canReadGyroflowPreferencesFile]) { //--------------------------------------------------------- // We can read the Gyroflow Preferences file: //--------------------------------------------------------- - NSLog(@"[Gyroflow Toolbox Renderer] We can read the preferences file."); + //NSLog(@"[Gyroflow Toolbox Renderer] We can read the preferences file."); [self readLastProjectFromGyroflowPreferencesFile]; } else { //--------------------------------------------------------- // We can't read the Gyroflow Preferences file, so lets // try get sandbox access: //--------------------------------------------------------- - NSLog(@"[Gyroflow Toolbox Renderer] We can't read the preferences file."); + //NSLog(@"[Gyroflow Toolbox Renderer] We can't read the preferences file."); NSURL* gyroflowPlistURL = [self getGyroflowPreferencesFileURL]; - [self requestSandboxAccessWithURL:gyroflowPlistURL]; + [self requestSandboxAccessAlertWithURL:gyroflowPlistURL]; } } @@ -2814,23 +2655,29 @@ - (void)buttonLoadLastGyroflowProject { //--------------------------------------------------------- - (void)buttonReloadGyroflowProject { - NSLog(@"[Gyroflow Toolbox Renderer] BUTTON PRESSED: Reload Gyroflow Project"); + //NSLog(@"[Gyroflow Toolbox Renderer] BUTTON PRESSED: Reload Gyroflow Project"); //--------------------------------------------------------- // Trash all the caches in Rust land: //--------------------------------------------------------- - uint32_t cacheSize = trashCache(); - NSLog(@"[Gyroflow Toolbox Renderer] Rust MANAGER_CACHE size after trashing (should be zero): %u", cacheSize); + trashCache(); + //uint32_t cacheSize = trashCache(); + //NSLog(@"[Gyroflow Toolbox Renderer] Rust MANAGER_CACHE size after trashing (should be zero): %u", cacheSize); //--------------------------------------------------------- // 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."]; + //--------------------------------------------------------- + // Show error message: + //--------------------------------------------------------- + NSString *errorMessage = @"Unable to retrieve 'FxCustomParameterActionAPI_v4'. This shouldn't happen, so it's probably a bug."; + NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); + [self showAlertWithMessage:@"An error has occurred." info:errorMessage]; return; } - + //--------------------------------------------------------- // Use the Action API to allow us to change the parameters: //--------------------------------------------------------- @@ -2841,7 +2688,12 @@ - (void)buttonReloadGyroflowProject { //--------------------------------------------------------- id paramGetAPI = [_apiManager apiForProtocol:@protocol(FxParameterRetrievalAPI_v6)]; if (paramGetAPI == nil) { - [self showAlertWithMessage:@"An error has occurred." info:@"Unable to retrieve 'FxParameterRetrievalAPI_v6'.\n\nThis shouldn't happen, so it's probably a bug."]; + //--------------------------------------------------------- + // Show error message: + //--------------------------------------------------------- + NSString *errorMessage = @"Unable to retrieve 'FxParameterRetrievalAPI_v6'.\n\nThis shouldn't happen, so it's probably a bug."; + NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); + [self showAlertWithMessage:@"An error has occurred." info:errorMessage]; return; } @@ -2850,21 +2702,21 @@ - (void)buttonReloadGyroflowProject { //--------------------------------------------------------- NSString *encodedBookmark; [paramGetAPI getStringParameterValue:&encodedBookmark fromParameter:kCB_GyroflowProjectBookmarkData]; - NSLog(@"[Gyroflow Toolbox Renderer] encodedBookmark: %@", encodedBookmark); - + //NSLog(@"[Gyroflow Toolbox Renderer] encodedBookmark: %@", encodedBookmark); + //--------------------------------------------------------- // Make sure there's actually encoded bookmark data. If // there's no bookmark, lets try the file path instead: //--------------------------------------------------------- if ([encodedBookmark isEqualToString:@""]) { - NSLog(@"[Gyroflow Toolbox Renderer] encodedBookmark is empty, so lets try file path instead..."); + //NSLog(@"[Gyroflow Toolbox Renderer] encodedBookmark is empty, so lets try file path instead..."); //--------------------------------------------------------- // Get the File Path: //--------------------------------------------------------- NSString *gyroflowProjectPath; [paramGetAPI getStringParameterValue:&gyroflowProjectPath fromParameter:kCB_GyroflowProjectPath]; - NSLog(@"[Gyroflow Toolbox Renderer] gyroflowProjectPath: %@", gyroflowProjectPath); + //NSLog(@"[Gyroflow Toolbox Renderer] gyroflowProjectPath: %@", gyroflowProjectPath); //--------------------------------------------------------- // If there's no path, then abort: @@ -2881,8 +2733,8 @@ - (void)buttonReloadGyroflowProject { NSURL *gyroflowProjectURL = [NSURL fileURLWithPath:gyroflowProjectPath]; NSString *gyroflowProjectFilename = [gyroflowProjectURL lastPathComponent]; - NSLog(@"[Gyroflow Toolbox Renderer] gyroflowProjectURL: %@", gyroflowProjectURL); - NSLog(@"[Gyroflow Toolbox Renderer] gyroflowProjectFilename: %@", gyroflowProjectFilename); + //NSLog(@"[Gyroflow Toolbox Renderer] gyroflowProjectURL: %@", gyroflowProjectURL); + //NSLog(@"[Gyroflow Toolbox Renderer] gyroflowProjectFilename: %@", gyroflowProjectFilename); NSOpenPanel* panel = [NSOpenPanel openPanel]; [panel setCanChooseDirectories:NO]; @@ -2898,7 +2750,7 @@ - (void)buttonReloadGyroflowProject { UTType *gyroflowExtension = [UTType typeWithFilenameExtension:@"gyroflow"]; NSArray *allowedContentTypes = [NSArray arrayWithObject:gyroflowExtension]; [panel setAllowedContentTypes:allowedContentTypes]; - + //--------------------------------------------------------- // Open the panel: //--------------------------------------------------------- @@ -2907,7 +2759,7 @@ - (void)buttonReloadGyroflowProject { //--------------------------------------------------------- // Open panel cancelled: //--------------------------------------------------------- - NSLog(@"[Gyroflow Toolbox Renderer] Open Panel Cancelled"); + //NSLog(@"[Gyroflow Toolbox Renderer] Open Panel Cancelled"); //--------------------------------------------------------- // Stop Action API: @@ -2915,12 +2767,12 @@ - (void)buttonReloadGyroflowProject { [actionAPI endAction:self]; return; } - + //--------------------------------------------------------- // Start accessing security scoped resource: //--------------------------------------------------------- NSURL *openPanelURL = [panel URL]; - NSLog(@"[Gyroflow Toolbox Renderer] openPanelURL: %@", openPanelURL); + //NSLog(@"[Gyroflow Toolbox Renderer] openPanelURL: %@", openPanelURL); BOOL startedOK = [openPanelURL startAccessingSecurityScopedResource]; if (startedOK == NO) { @@ -2937,7 +2789,7 @@ - (void)buttonReloadGyroflowProject { [actionAPI endAction:self]; return; } - + //--------------------------------------------------------- // Create a Security Scope Bookmark, so we can reload // later: @@ -2989,16 +2841,16 @@ - (void)buttonReloadGyroflowProject { NSString *selectedGyroflowProjectPath = [openPanelURL path]; NSString *selectedGyroflowProjectBookmarkData = [bookmark base64EncodedStringWithOptions:0]; - NSLog(@"[Gyroflow Toolbox Renderer] selectedGyroflowProjectFile: %@", selectedGyroflowProjectFile); - NSLog(@"[Gyroflow Toolbox Renderer] selectedGyroflowProjectPath: %@", selectedGyroflowProjectPath); - NSLog(@"[Gyroflow Toolbox Renderer] selectedGyroflowProjectBookmarkData: %@", selectedGyroflowProjectBookmarkData); + //NSLog(@"[Gyroflow Toolbox Renderer] selectedGyroflowProjectFile: %@", selectedGyroflowProjectFile); + //NSLog(@"[Gyroflow Toolbox Renderer] selectedGyroflowProjectPath: %@", selectedGyroflowProjectPath); + //NSLog(@"[Gyroflow Toolbox Renderer] selectedGyroflowProjectBookmarkData: %@", selectedGyroflowProjectBookmarkData); //--------------------------------------------------------- // Read the Gyroflow Project Data from File: //--------------------------------------------------------- NSError *readError = nil; NSString *selectedGyroflowProjectData = [NSString stringWithContentsOfURL:openPanelURL encoding:NSUTF8StringEncoding error:&readError]; - NSLog(@"[Gyroflow Toolbox Renderer] selectedGyroflowProjectData: %@", selectedGyroflowProjectData); + //NSLog(@"[Gyroflow Toolbox Renderer] selectedGyroflowProjectData: %@", selectedGyroflowProjectData); //--------------------------------------------------------- // There was an error reading the Gyroflow Project file: @@ -3022,7 +2874,7 @@ - (void)buttonReloadGyroflowProject { //--------------------------------------------------------- NSString *mediaBookmarkDataString = nil; [paramGetAPI getStringParameterValue:&mediaBookmarkDataString fromParameter:kCB_MediaBookmarkData]; - NSLog(@"[Gyroflow Toolbox Renderer] mediaBookmarkDataString: %@", mediaBookmarkDataString); + //NSLog(@"[Gyroflow Toolbox Renderer] mediaBookmarkDataString: %@", mediaBookmarkDataString); //--------------------------------------------------------- // If there's a bookmark for the media, lets try load it: @@ -3033,8 +2885,8 @@ - (void)buttonReloadGyroflowProject { // Decode the Base64 bookmark data: //--------------------------------------------------------- NSData *decodedBookmark = [[[NSData alloc] initWithBase64EncodedString:mediaBookmarkDataString - options:0] autorelease]; - + options:0] autorelease]; + //--------------------------------------------------------- // Resolve the decoded bookmark data into a // security-scoped URL: @@ -3065,7 +2917,7 @@ - (void)buttonReloadGyroflowProject { //--------------------------------------------------------- const char* hasData = doesGyroflowProjectContainStabilisationData([selectedGyroflowProjectData UTF8String]); NSString *hasDataResult = [NSString stringWithUTF8String: hasData]; - NSLog(@"[Gyroflow Toolbox Renderer] doesGyroflowProjectContainStabilisationData: %@", hasDataResult); + //NSLog(@"[Gyroflow Toolbox Renderer] doesGyroflowProjectContainStabilisationData: %@", hasDataResult); if (hasDataResult == nil || ![hasDataResult isEqualToString:@"YES"]) { 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."; NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); @@ -3092,7 +2944,9 @@ - (void)buttonReloadGyroflowProject { id paramSetAPI = [_apiManager apiForProtocol:@protocol(FxParameterSettingAPI_v5)]; if (paramSetAPI == nil) { - [self showAlertWithMessage:@"An error has occurred." info:@"Unable to retrieve 'FxParameterSettingAPI_v5'.\n\nThis shouldn't happen, so it's probably a bug."]; + NSString *errorMessage = @"Unable to retrieve 'FxParameterSettingAPI_v5'.\n\nThis shouldn't happen, so it's probably a bug."; + NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); + [self showAlertWithMessage:@"An error has occurred." info:errorMessage]; //--------------------------------------------------------- // Stop Action API & Stop Accessing Resource: @@ -3113,13 +2967,13 @@ - (void)buttonReloadGyroflowProject { // Update 'Gyroflow Project Path': //--------------------------------------------------------- [paramSetAPI setStringParameterValue:selectedGyroflowProjectPath toParameter:kCB_GyroflowProjectPath]; - NSLog(@"[Gyroflow Toolbox Renderer] selectedGyroflowProjectPath: %@", selectedGyroflowProjectPath); + //NSLog(@"[Gyroflow Toolbox Renderer] selectedGyroflowProjectPath: %@", selectedGyroflowProjectPath); //--------------------------------------------------------- // Update 'Gyroflow Project Bookmark Data': //--------------------------------------------------------- [paramSetAPI setStringParameterValue:selectedGyroflowProjectBookmarkData toParameter:kCB_GyroflowProjectBookmarkData]; - NSLog(@"[Gyroflow Toolbox Renderer] selectedGyroflowProjectBookmarkData: %@", selectedGyroflowProjectBookmarkData); + //NSLog(@"[Gyroflow Toolbox Renderer] selectedGyroflowProjectBookmarkData: %@", selectedGyroflowProjectBookmarkData); //--------------------------------------------------------- // Update 'Gyroflow Project Data': @@ -3130,11 +2984,12 @@ - (void)buttonReloadGyroflowProject { // Update 'Loaded Gyroflow Project' Text Box: //--------------------------------------------------------- [paramSetAPI setStringParameterValue:selectedGyroflowProjectFile toParameter:kCB_LoadedGyroflowProject]; - NSLog(@"[Gyroflow Toolbox Renderer] selectedGyroflowProjectFile: %@", selectedGyroflowProjectFile); + //NSLog(@"[Gyroflow Toolbox Renderer] selectedGyroflowProjectFile: %@", selectedGyroflowProjectFile); //--------------------------------------------------------- // Show success message: //--------------------------------------------------------- + // TODO: Success message should be optional. [self showAlertWithMessage:@"Success!" info:@"The Gyroflow Project has been successfully reloaded from disk."]; //--------------------------------------------------------- @@ -3149,8 +3004,8 @@ - (void)buttonReloadGyroflowProject { // Decode the Base64 bookmark data: //--------------------------------------------------------- NSData *decodedBookmark = [[[NSData alloc] initWithBase64EncodedString:encodedBookmark - options:0] autorelease]; - + options:0] autorelease]; + //--------------------------------------------------------- // Resolve the decoded bookmark data into a // security-scoped URL: @@ -3173,6 +3028,7 @@ - (void)buttonReloadGyroflowProject { //--------------------------------------------------------- NSString *message = @"An error has occurred."; NSString *info = [NSString stringWithFormat:@"Failed to resolve bookmark due to:\n\n%@", [bookmarkError localizedDescription]]; + NSLog(@"[Gyroflow Toolbox Renderer] %@", info); [self showAlertWithMessage:message info:info]; //--------------------------------------------------------- @@ -3192,6 +3048,7 @@ - (void)buttonReloadGyroflowProject { //--------------------------------------------------------- NSString *message = @"An error has occurred."; NSString *info = @"Unable to retrieve 'FxParameterSettingAPI_v5'.\n\nThis shouldn't happen, so it's probably a bug."; + NSLog(@"[Gyroflow Toolbox Renderer] %@", info); [self showAlertWithMessage:message info:info]; //--------------------------------------------------------- @@ -3200,7 +3057,7 @@ - (void)buttonReloadGyroflowProject { [actionAPI endAction:self]; return; } - + //--------------------------------------------------------- // Read the Gyroflow Project Data from File: //--------------------------------------------------------- @@ -3223,6 +3080,7 @@ - (void)buttonReloadGyroflowProject { //--------------------------------------------------------- NSString *message = @"An error has occurred."; NSString *info = [NSString stringWithFormat:@"Failed to read Gyroflow Project file due to:\n\n%@", [readError localizedDescription]]; + NSLog(@"[Gyroflow Toolbox Renderer] %@", info); [self showAlertWithMessage:message info:info]; //--------------------------------------------------------- @@ -3236,7 +3094,7 @@ - (void)buttonReloadGyroflowProject { // Update 'Gyroflow Project Data': //--------------------------------------------------------- [paramSetAPI setStringParameterValue:selectedGyroflowProjectData toParameter:kCB_GyroflowProjectData]; - + //--------------------------------------------------------- // Generate a new unique identifier: //--------------------------------------------------------- @@ -3252,6 +3110,7 @@ - (void)buttonReloadGyroflowProject { //--------------------------------------------------------- // Show success message: //--------------------------------------------------------- + // TODO: This reload success message should be optional. [self showAlertWithMessage:@"Success!" info:@"The Gyroflow Project has been successfully reloaded from disk."]; } @@ -3267,12 +3126,12 @@ - (void)buttonReloadGyroflowProject { - (BOOL)importDroppedMedia:(NSData*)bookmarkData { /* - 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]); - } - */ + 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: @@ -3292,38 +3151,38 @@ - (BOOL)importDroppedMedia:(NSData*)bookmarkData { } if (isStale) { - NSLog(@"[Gyroflow Toolbox Renderer] WARNING - Bookmark is stale!"); + NSLog(@"[Gyroflow Toolbox Renderer] WARNING - Bookmark is stale: %@", [url path]); } 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]); + + //NSLog(@"[Gyroflow Toolbox Renderer] importDroppedMedia URL: %@", [url path]); NSString *extension = [[url pathExtension] lowercaseString]; NSFileManager *fileManager = [NSFileManager defaultManager]; - + if ([extension isEqualToString:@"gyroflow"]) { //--------------------------------------------------------- // If the dragged file is a Gyroflow file, try load it: //--------------------------------------------------------- - NSLog(@"[Gyroflow Toolbox Renderer] It's a Gyroflow Project!"); + //NSLog(@"[Gyroflow Toolbox Renderer] It's a Gyroflow Project!"); [self importGyroflowProjectWithOptionalURL:url]; } else { //--------------------------------------------------------- // If the dragged file is a Media file, check if there's // a Gyroflow Project next to it first: //--------------------------------------------------------- - NSLog(@"[Gyroflow Toolbox Renderer] It's a Media File!"); + //NSLog(@"[Gyroflow Toolbox Renderer] It's a Media File!"); NSURL *gyroflowUrl = [[url URLByDeletingPathExtension] URLByAppendingPathExtension:@"gyroflow"]; - NSLog(@"[Gyroflow Toolbox Renderer] importDroppedMedia Gyroflow URL: %@", [gyroflowUrl path]); + //NSLog(@"[Gyroflow Toolbox Renderer] importDroppedMedia Gyroflow URL: %@", [gyroflowUrl path]); if ([fileManager fileExistsAtPath:[gyroflowUrl path]]) { - NSLog(@"[Gyroflow Toolbox Renderer] It's a Media File, with a Gyroflow Project next to it!"); + //NSLog(@"[Gyroflow Toolbox Renderer] It's a Media File, with a Gyroflow Project next to it!"); [self importGyroflowProjectWithOptionalURL:gyroflowUrl]; } else { - NSLog(@"[Gyroflow Toolbox Renderer] It's a Media File, with no Gyroflow Project next to it!"); + //NSLog(@"[Gyroflow Toolbox Renderer] It's a Media File, with no Gyroflow Project next to it!"); [self importMediaWithOptionalURL:url]; } } @@ -3339,48 +3198,79 @@ - (BOOL)importDroppedMedia:(NSData*)bookmarkData { //--------------------------------------------------------- // Import Dropped Clip: //--------------------------------------------------------- -- (void)importDroppedClip:(NSString*)fcpxmlString { - +- (BOOL)importDroppedClip:(NSString*)fcpxmlString { + NSURL *url = nil; - - NSError *error; - NSXMLDocument *xmlDoc = [[NSXMLDocument alloc] initWithXMLString:fcpxmlString options:0 error:&error]; - - if (error) { - NSString *errorMessage = [NSString stringWithFormat:@"Failed to parse the FCPXML due to:\n\n%@", error.localizedDescription]; - [self showAlertWithMessage:@"An error has occurred" info:errorMessage]; - return; - } - NSArray *mediaRepNodes = [xmlDoc nodesForXPath:@"//media-rep" error:&error]; + NSError *error; + NSXMLDocument *xmlDoc = [[[NSXMLDocument alloc] initWithXMLString:fcpxmlString options:0 error:&error] autorelease]; if (error) { - NSString *errorMessage = [NSString stringWithFormat:@"Error extracting media-rep nodes:\n\n%@", error.localizedDescription]; - [self showAlertWithMessage:@"An error has occurred" info:errorMessage]; - return; + NSString *errorMessage = [NSString stringWithFormat:@"Failed to parse the FCPXML due to:\n\n%@", error.localizedDescription]; + [self showAsyncAlertWithMessage:@"An error has occurred" info:errorMessage]; + return NO; } - for (NSXMLElement *node in mediaRepNodes) { - NSString *src = [[node attributeForName:@"src"] stringValue]; - if (src) { - url = [NSURL URLWithString:src]; + if ([self isValidBRAWToolboxString:fcpxmlString]) { + //--------------------------------------------------------- + // It's a BRAW Toolbox Clip: + //--------------------------------------------------------- + //NSLog(@"[Gyroflow Toolbox Renderer] It's a BRAW clip!"); + + BRAWToolboxXMLReader *reader = [[[BRAWToolboxXMLReader alloc] init] autorelease]; + NSDictionary *result = [reader readXML:fcpxmlString]; + + if (!result) { + [self showAlertWithMessage:@"An error has occurred" info:@"Failed to get the media path from the BRAW Toolbox Clip."]; + return NO; } - } - - if (url == nil) { - [self showAlertWithMessage:@"An error has occurred" info:@"Failed to get the media path from the FCPXML."]; - return; - } + + //NSLog(@"[Gyroflow Toolbox Renderer] result: %@", result); - NSURL *gyroflowUrl = [[url URLByDeletingPathExtension] URLByAppendingPathExtension:@"gyroflow"]; - - NSFileManager *fileManager = [NSFileManager defaultManager]; - - if ([fileManager fileExistsAtPath:[gyroflowUrl path]]) { - [self importGyroflowProjectWithOptionalURL:gyroflowUrl]; + NSString *filePath = result[@"File Path"]; + NSString *bookmarkData = result[@"Bookmark Data"]; + [self importBRAWToolboxClipWithPath:filePath bookmarkDataString:bookmarkData]; + + return NO; } else { - [self importMediaWithOptionalURL:url]; + //--------------------------------------------------------- + // It's not a BRAW Toolbox Clip: + //--------------------------------------------------------- + //NSLog(@"[Gyroflow Toolbox Renderer] It's NOT a BRAW clip!"); + + NSArray *mediaRepNodes = [xmlDoc nodesForXPath:@"//media-rep" error:&error]; + + if (error) { + NSString *errorMessage = [NSString stringWithFormat:@"Error extracting media-rep nodes:\n\n%@", error.localizedDescription]; + NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); + [self showAsyncAlertWithMessage:@"An error has occurred" info:errorMessage]; + return NO; + } + + for (NSXMLElement *node in mediaRepNodes) { + NSString *src = [[node attributeForName:@"src"] stringValue]; + if (src) { + url = [NSURL URLWithString:src]; + } + } + + if (url == nil) { + [self showAsyncAlertWithMessage:@"An error has occurred" info:@"Failed to get the media path from the FCPXML."]; + return NO; + } + + NSURL *gyroflowUrl = [[url URLByDeletingPathExtension] URLByAppendingPathExtension:@"gyroflow"]; + + NSFileManager *fileManager = [NSFileManager defaultManager]; + + if ([fileManager fileExistsAtPath:[gyroflowUrl path]]) { + [self importGyroflowProjectWithOptionalURL:gyroflowUrl]; + } else { + [self importMediaWithOptionalURL:url]; + } } + + return YES; } //--------------------------------------------------------- @@ -3390,112 +3280,134 @@ - (void)importDroppedClip:(NSString*)fcpxmlString { //--------------------------------------------------------- //--------------------------------------------------------- -// Request Sandbox access to the Gyroflow Preferences file: +// Request Sandbox access alert: //--------------------------------------------------------- -- (void)requestSandboxAccessWithURL:(NSURL*)gyroflowPlistURL { +- (void)requestSandboxAccessAlertWithURL:(NSURL*)gyroflowPlistURL { //--------------------------------------------------------- // Show popup with instructions: //--------------------------------------------------------- - NSAlert *alert = [[[NSAlert alloc] init] autorelease]; - alert.icon = [NSImage imageNamed:@"GyroflowToolbox"]; - alert.alertStyle = NSAlertStyleInformational; - alert.messageText = @"Permission Required"; - alert.informativeText = @"Gyroflow Toolbox requires explicit permission to access your Gyroflow Preferences, so that it can determine the last opened project.\n\nPlease click 'Grant Access' on the next Open Folder window to continue."; - [alert beginSheetModalForWindow:loadLastGyroflowProjectView.window completionHandler:^(NSModalResponse result){ - //--------------------------------------------------------- - // Display an open panel: - //--------------------------------------------------------- - UTType *plistType = [UTType typeWithIdentifier:@"com.apple.property-list"]; - NSArray *allowedContentTypes = [NSArray arrayWithObject:plistType]; - - NSOpenPanel* panel = [NSOpenPanel openPanel]; - [panel setCanChooseDirectories:NO]; - [panel setCanCreateDirectories:NO]; - [panel setCanChooseFiles:YES]; - [panel setAllowsMultipleSelection:NO]; - [panel setDirectoryURL:gyroflowPlistURL]; - [panel setAllowedContentTypes:allowedContentTypes]; - [panel setPrompt:@"Grant Access"]; - [panel setMessage:@"Please click 'Grant Access' to allow access to the Gyroflow Preferences file:"]; - - [panel beginSheetModalForWindow:loadLastGyroflowProjectView.window completionHandler:^(NSModalResponse result){ - if (result != NSModalResponseOK) { - return; - } - - NSURL *url = [panel URL]; - [url startAccessingSecurityScopedResource]; + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + if (![defaults boolForKey:@"suppressRequestSandboxAccessAlert"]) { + NSAlert *alert = [[[NSAlert alloc] init] autorelease]; + alert.icon = [NSImage imageNamed:@"GyroflowToolbox"]; + alert.alertStyle = NSAlertStyleInformational; + alert.messageText = @"Permission Required"; + alert.informativeText = @"Gyroflow Toolbox requires explicit permission to access your Gyroflow Preferences, so that it can determine the last opened project.\n\nPlease click 'Grant Access' on the next Open Folder window to continue."; + alert.showsSuppressionButton = YES; + [alert beginSheetModalForWindow:loadLastGyroflowProjectView.window completionHandler:^(NSModalResponse result) { //--------------------------------------------------------- - // Create a new app-scope security-scoped bookmark for - // future sessions: + // Close the alert: //--------------------------------------------------------- - NSError *bookmarkError = nil; - NSURLBookmarkCreationOptions bookmarkOptions = NSURLBookmarkCreationWithSecurityScope; - NSData *bookmark = [url bookmarkDataWithOptions:bookmarkOptions - includingResourceValuesForKeys:nil - relativeToURL:nil - error:&bookmarkError]; - - if (bookmarkError != nil) { - NSString *errorMessage = [NSString stringWithFormat:@"Failed to create a security-scoped bookmark due to the following error:\n\n %@", [bookmarkError localizedDescription]]; - NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); - [self showAlertWithMessage:@"An error has occurred" info:errorMessage]; - [url stopAccessingSecurityScopedResource]; - return; - } + [alert.window orderOut:nil]; - if (bookmark == nil) { - [self showAlertWithMessage:@"An error has occurred" info:@"Failed to create a security-scoped bookmark due to the Bookmark being 'nil' and the error message is also being 'nil'"]; - [url stopAccessingSecurityScopedResource]; - return; + if ([alert suppressionButton].state == NSControlStateValueOn) { + [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"suppressRequestSandboxAccessAlert"]; } - - //NSLog(@"[Gyroflow Toolbox Renderer] Bookmark created successfully for: %@", [url path]); - - NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init]; - [userDefaults setObject:bookmark forKey:@"gyroFlowPreferencesBookmarkData"]; - [userDefaults release]; - - [url stopAccessingSecurityScopedResource]; - - [self readLastProjectFromGyroflowPreferencesFile]; + [self requestSandboxAccessWithURL:gyroflowPlistURL]; }]; - }]; + } else { + [self requestSandboxAccessWithURL:gyroflowPlistURL]; + } } //--------------------------------------------------------- -// Can we read the Gyroflow Preferences File? +// Request Sandbox access to the Gyroflow Preferences file: //--------------------------------------------------------- -- (BOOL)canReadGyroflowPreferencesFile { - NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init]; - NSData *gyroFlowPreferencesBookmarkData = [userDefaults dataForKey:@"gyroFlowPreferencesBookmarkData"]; - [userDefaults release]; - - if (gyroFlowPreferencesBookmarkData == nil) { - return NO; - } - - BOOL staleBookmark; - NSURL *url = nil; - NSError *bookmarkError = nil; - url = [NSURL URLByResolvingBookmarkData:gyroFlowPreferencesBookmarkData - options:NSURLBookmarkResolutionWithSecurityScope - relativeToURL:nil - bookmarkDataIsStale:&staleBookmark - error:&bookmarkError]; - - if (bookmarkError != nil) { - NSLog(@"[Gyroflow Toolbox Renderer] Failed to read Gyroflow Preferences Bookmark Data: %@", bookmarkError.localizedDescription); - return NO; - } +- (void)requestSandboxAccessWithURL:(NSURL*)gyroflowPlistURL { + //--------------------------------------------------------- + // Display an open panel: + //--------------------------------------------------------- + UTType *plistType = [UTType typeWithIdentifier:@"com.apple.property-list"]; + NSArray *allowedContentTypes = [NSArray arrayWithObject:plistType]; - if (staleBookmark) { - NSLog(@"[Gyroflow Toolbox Renderer] Stale Gyroflow Preferences Bookmark."); - return NO; - } + NSOpenPanel* panel = [NSOpenPanel openPanel]; + [panel setCanChooseDirectories:NO]; + [panel setCanCreateDirectories:NO]; + [panel setCanChooseFiles:YES]; + [panel setAllowsMultipleSelection:NO]; + [panel setDirectoryURL:gyroflowPlistURL]; + [panel setAllowedContentTypes:allowedContentTypes]; + [panel setPrompt:@"Grant Access"]; + [panel setMessage:@"Please click 'Grant Access' to allow access to the Gyroflow Preferences file:"]; - if (url == nil) { + [panel beginSheetModalForWindow:loadLastGyroflowProjectView.window completionHandler:^(NSModalResponse result){ + if (result != NSModalResponseOK) { + return; + } + + NSURL *url = [panel URL]; + [url startAccessingSecurityScopedResource]; + + //--------------------------------------------------------- + // Create a new app-scope security-scoped bookmark for + // future sessions: + //--------------------------------------------------------- + NSError *bookmarkError = nil; + NSURLBookmarkCreationOptions bookmarkOptions = NSURLBookmarkCreationWithSecurityScope; + NSData *bookmark = [url bookmarkDataWithOptions:bookmarkOptions + includingResourceValuesForKeys:nil + relativeToURL:nil + error:&bookmarkError]; + + if (bookmarkError != nil) { + NSString *errorMessage = [NSString stringWithFormat:@"Failed to create a security-scoped bookmark due to the following error:\n\n %@", [bookmarkError localizedDescription]]; + NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); + [self showAlertWithMessage:@"An error has occurred" info:errorMessage]; + [url stopAccessingSecurityScopedResource]; + return; + } + + if (bookmark == nil) { + [self showAlertWithMessage:@"An error has occurred" info:@"Failed to create a security-scoped bookmark due to the Bookmark being 'nil' and the error message is also being 'nil'"]; + [url stopAccessingSecurityScopedResource]; + return; + } + + //NSLog(@"[Gyroflow Toolbox Renderer] Bookmark created successfully for: %@", [url path]); + + NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init]; + [userDefaults setObject:bookmark forKey:@"gyroFlowPreferencesBookmarkData"]; + [userDefaults release]; + + [url stopAccessingSecurityScopedResource]; + + [self readLastProjectFromGyroflowPreferencesFile]; + }]; +} + +//--------------------------------------------------------- +// Can we read the Gyroflow Preferences File? +//--------------------------------------------------------- +- (BOOL)canReadGyroflowPreferencesFile { + NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init]; + NSData *gyroFlowPreferencesBookmarkData = [userDefaults dataForKey:@"gyroFlowPreferencesBookmarkData"]; + [userDefaults release]; + + if (gyroFlowPreferencesBookmarkData == nil) { + return NO; + } + + BOOL staleBookmark; + NSURL *url = nil; + NSError *bookmarkError = nil; + url = [NSURL URLByResolvingBookmarkData:gyroFlowPreferencesBookmarkData + options:NSURLBookmarkResolutionWithSecurityScope + relativeToURL:nil + bookmarkDataIsStale:&staleBookmark + error:&bookmarkError]; + + if (bookmarkError != nil) { + NSLog(@"[Gyroflow Toolbox Renderer] Failed to read Gyroflow Preferences Bookmark Data: %@", bookmarkError.localizedDescription); + return NO; + } + + if (staleBookmark) { + NSLog(@"[Gyroflow Toolbox Renderer] Stale Gyroflow Preferences Bookmark."); + return NO; + } + + if (url == nil) { NSLog(@"[Gyroflow Toolbox Renderer] Gyroflow Preferences Bookmark is nil."); return NO; } @@ -3591,13 +3503,17 @@ - (void)readLastProjectFromGyroflowPreferencesFile { //--------------------------------------------------------- - (BOOL)importMediaWithOptionalURL:(NSURL*)optionalURL { - NSLog(@"[Gyroflow Toolbox Renderer] FUNCTION: Import Media File with optional URL: %@", optionalURL); + //NSLog(@"[Gyroflow Toolbox Renderer] FUNCTION: Import Media File with optional URL: %@", optionalURL); NSURL *url = nil; - BOOL isAccessible = [[NSFileManager defaultManager] isReadableFileAtPath:[optionalURL path]]; + BOOL isAccessible = NO; + + if (optionalURL) { + isAccessible = [[NSFileManager defaultManager] isReadableFileAtPath:[optionalURL path]]; + } if (isAccessible) { - NSLog(@"[Gyroflow Toolbox Renderer] importMediaWithOptionalURL has an accessible NSURL!"); + //NSLog(@"[Gyroflow Toolbox Renderer] importMediaWithOptionalURL has an accessible NSURL!"); url = optionalURL; } else { //--------------------------------------------------------- @@ -3638,15 +3554,20 @@ - (BOOL)importMediaWithOptionalURL:(NSURL*)optionalURL { //--------------------------------------------------------- BOOL startedOK = [url startAccessingSecurityScopedResource]; if (startedOK == NO) { - [self showAlertWithMessage:@"An error has occurred." info:@"Failed to startAccessingSecurityScopedResource. This shouldn't happen."]; + NSString *errorMessage = @"Failed to startAccessingSecurityScopedResource. This shouldn't happen."; + NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); + [self showAlertWithMessage:@"An error has occurred." info:errorMessage]; return NO; } } NSString *path = [url path]; - NSLog(@"[Gyroflow Toolbox Renderer] Import Media File Path: %@", path); + //NSLog(@"[Gyroflow Toolbox Renderer] Import Media File Path: %@", path); + //--------------------------------------------------------- + // Use gyroflow_core to import the media file: + //--------------------------------------------------------- const char* importResult = importMediaFile( [path UTF8String] // const char* ); @@ -3707,90 +3628,6 @@ - (BOOL)importMediaWithOptionalURL:(NSURL*)optionalURL { NSString *info = @"The imported media file needs to be synchronized in Gyroflow before it can be used by Gyroflow Toolbox in Final Cut Pro.\n\nYou will be prompted to save the Gyroflow Project, and then we'll launch Gyroflow to open it.\n\nWhen you have finished synchronizing in Gyroflow, press COMMAND+S to save the project. Then back in Final Cut Pro press the 'Reload Gyroflow Project' button in the Inspector of the Gyroflow Toolbox effect."; [self showAlertWithMessage:message info:info]; requiresGyroflowLaunch = YES; - - /* - //--------------------------------------------------------- - // This is depreciated, but there's no better way to do - // it in a sandbox sadly: - //--------------------------------------------------------- - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wdeprecated-declarations" - [[NSWorkspace sharedWorkspace] openFile:[url path] withApplication:@"Gyroflow"]; - #pragma GCC diagnostic pop - - return YES; - - //--------------------------------------------------------- - // Display Save Panel: - //--------------------------------------------------------- - NSSavePanel *savePanel = [NSSavePanel savePanel]; - - [savePanel setTitle:@"Choose a Location to Save Your Gyroflow Project"]; - [savePanel setPrompt:@"Save"]; - [savePanel setNameFieldStringValue:selectedGyroflowProjectFile]; - [savePanel setDirectoryURL:[NSURL fileURLWithPath:selectedGyroflowProjectPath]]; - [savePanel setExtensionHidden:NO]; - [savePanel setCanSelectHiddenExtension:YES]; - - //--------------------------------------------------------- - // Limit the file type to Gyroflow supported media files: - //--------------------------------------------------------- - UTType *gyroflow = [UTType typeWithFilenameExtension:@"gyroflow"]; - - NSArray *allowedContentTypes = [NSArray arrayWithObjects:gyroflow, nil]; - [savePanel setAllowedContentTypes:allowedContentTypes]; - - NSModalResponse result = [savePanel runModal]; - - if (result == NSModalResponseOK) { - savePanelURL = [savePanel URL]; - - NSError *error = nil; - BOOL succeeded = [gyroflowProject writeToURL:savePanelURL - 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%@", [savePanelURL path], error.localizedDescription]; - NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); - [self showAlertWithMessage:@"An error has occurred" info:errorMessage]; - return NO; - } - - //--------------------------------------------------------- - // Create a new security-scoped bookmark: - //--------------------------------------------------------- - NSError *bookmarkError = nil; - NSURLBookmarkCreationOptions bookmarkOptions = NSURLBookmarkCreationWithSecurityScope | NSURLBookmarkCreationSecurityScopeAllowOnlyReadAccess; - NSData *bookmarkData = [savePanelURL 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: %@", savePanelURL, 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.", savePanelURL]; - NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); - [self showAlertWithMessage:@"An error has occurred" info:errorMessage]; - return NO; - } - - selectedGyroflowProjectPath = [savePanelURL path]; - selectedGyroflowProjectBookmarkData = [bookmarkData base64EncodedStringWithOptions:0]; - - requiresGyroflowLaunch = YES; - } - */ } //--------------------------------------------------------- @@ -3896,19 +3733,19 @@ - (BOOL)importMediaWithOptionalURL:(NSURL*)optionalURL { NSUUID *uuid = [NSUUID UUID]; NSString *uniqueIdentifier = uuid.UUIDString; [paramSetAPI setStringParameterValue:uniqueIdentifier toParameter:kCB_UniqueIdentifier]; - NSLog(@"[Gyroflow Toolbox Renderer] uniqueIdentifier: %@", uniqueIdentifier); + //NSLog(@"[Gyroflow Toolbox Renderer] uniqueIdentifier: %@", uniqueIdentifier); //--------------------------------------------------------- // Update 'Gyroflow Project Path': //--------------------------------------------------------- [paramSetAPI setStringParameterValue:selectedGyroflowProjectPath toParameter:kCB_GyroflowProjectPath]; - NSLog(@"[Gyroflow Toolbox Renderer] selectedGyroflowProjectPath: %@", selectedGyroflowProjectPath); + //NSLog(@"[Gyroflow Toolbox Renderer] selectedGyroflowProjectPath: %@", selectedGyroflowProjectPath); //--------------------------------------------------------- // Update 'Gyroflow Project Bookmark Data': //--------------------------------------------------------- [paramSetAPI setStringParameterValue:selectedGyroflowProjectBookmarkData toParameter:kCB_GyroflowProjectBookmarkData]; - NSLog(@"[Gyroflow Toolbox Renderer] selectedGyroflowProjectBookmarkData: %@", selectedGyroflowProjectBookmarkData); + //NSLog(@"[Gyroflow Toolbox Renderer] selectedGyroflowProjectBookmarkData: %@", selectedGyroflowProjectBookmarkData); //--------------------------------------------------------- // Update 'Gyroflow Project Data': @@ -3919,20 +3756,20 @@ - (BOOL)importMediaWithOptionalURL:(NSURL*)optionalURL { // Update 'Loaded Gyroflow Project' Text Box: //--------------------------------------------------------- [paramSetAPI setStringParameterValue:selectedGyroflowProjectFile toParameter:kCB_LoadedGyroflowProject]; - NSLog(@"[Gyroflow Toolbox Renderer] selectedGyroflowProjectFile: %@", selectedGyroflowProjectFile); + //NSLog(@"[Gyroflow Toolbox Renderer] selectedGyroflowProjectFile: %@", selectedGyroflowProjectFile); //--------------------------------------------------------- // Set parameters from Gyroflow Project file: //--------------------------------------------------------- if ([getDefaultValuesResultString isEqualToString:@"OK"]) { - NSLog(@"[Gyroflow Toolbox Renderer] defaultFOV: %f", defaultFOV); - NSLog(@"[Gyroflow Toolbox Renderer] defaultSmoothness: %f", defaultSmoothness); - NSLog(@"[Gyroflow Toolbox Renderer] defaultLensCorrection: %f", defaultLensCorrection); - NSLog(@"[Gyroflow Toolbox Renderer] defaultHorizonLock: %f", defaultHorizonLock); - NSLog(@"[Gyroflow Toolbox Renderer] defaultHorizonRoll: %f", defaultHorizonRoll); - NSLog(@"[Gyroflow Toolbox Renderer] defaultPositionOffsetX: %f", defaultPositionOffsetX); - NSLog(@"[Gyroflow Toolbox Renderer] defaultPositionOffsetY: %f", defaultPositionOffsetY); - NSLog(@"[Gyroflow Toolbox Renderer] defaultVideoRotation: %f", defaultVideoRotation); + //NSLog(@"[Gyroflow Toolbox Renderer] defaultFOV: %f", defaultFOV); + //NSLog(@"[Gyroflow Toolbox Renderer] defaultSmoothness: %f", defaultSmoothness); + //NSLog(@"[Gyroflow Toolbox Renderer] defaultLensCorrection: %f", defaultLensCorrection); + //NSLog(@"[Gyroflow Toolbox Renderer] defaultHorizonLock: %f", defaultHorizonLock); + //NSLog(@"[Gyroflow Toolbox Renderer] defaultHorizonRoll: %f", defaultHorizonRoll); + //NSLog(@"[Gyroflow Toolbox Renderer] defaultPositionOffsetX: %f", defaultPositionOffsetX); + //NSLog(@"[Gyroflow Toolbox Renderer] defaultPositionOffsetY: %f", defaultPositionOffsetY); + //NSLog(@"[Gyroflow Toolbox Renderer] defaultVideoRotation: %f", defaultVideoRotation); [paramSetAPI setFloatValue:defaultFOV toParameter:kCB_FOV atTime:kCMTimeZero]; [paramSetAPI setFloatValue:defaultSmoothness toParameter:kCB_Smoothness atTime:kCMTimeZero]; @@ -3977,31 +3814,35 @@ - (BOOL)importMediaWithOptionalURL:(NSURL*)optionalURL { //--------------------------------------------------------- const char* officialLensLoaded = isOfficialLensLoaded([gyroflowProject UTF8String]); NSString *isOfficialLensLoaded = [NSString stringWithUTF8String:officialLensLoaded]; - NSLog(@"[Gyroflow Toolbox Renderer] isOfficialLensLoaded: %@", isOfficialLensLoaded); + //NSLog(@"[Gyroflow Toolbox Renderer] isOfficialLensLoaded: %@", isOfficialLensLoaded); if (isOfficialLensLoaded == nil || ![isOfficialLensLoaded isEqualToString:@"YES"]) { - NSString *message = @"No Lens Profile Detected"; - NSString *info = @"A lens profile could not be automatically detected from the supplied video file.\n\nYou will be prompted to select an appropriate Lens Profile in the next panel."; - [self showAlertWithMessage:message info:info]; - [self buttonLoadPresetLensProfile]; - } - - //--------------------------------------------------------- - // Check to see if it has accurate timestamps: - //--------------------------------------------------------- - /* - const char* doesHaveAccurateTimestamps = hasAccurateTimestamps([gyroflowProject UTF8String]); - NSString *doesHaveAccurateTimestampsString = [NSString stringWithUTF8String:doesHaveAccurateTimestamps]; - NSLog(@"[Gyroflow Toolbox Renderer] doesHaveAccurateTimestamps: %@", doesHaveAccurateTimestampsString); - - if (doesHaveAccurateTimestampsString == nil || ![doesHaveAccurateTimestampsString isEqualToString:@"YES"]) { - [self showAlertWithMessage:@"Doesn't have accurate timestamps" info:@"need to load gyroflow"]; - } else { - //--------------------------------------------------------- - // Show Victory Message: //--------------------------------------------------------- - [self showAlertWithMessage:@"Success!" info:@"The Gyroflow Project has been successfully imported.\n\nYou can now adjust the parameters as required."]; + // Show popup with instructions: + //--------------------------------------------------------- + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + if (![defaults boolForKey:@"suppressNoLensProfileDetected"]) { + NSAlert *alert = [[[NSAlert alloc] init] autorelease]; + alert.icon = [NSImage imageNamed:@"GyroflowToolbox"]; + alert.alertStyle = NSAlertStyleInformational; + alert.messageText = @"No Lens Profile Detected"; + alert.informativeText = @"A lens profile could not be automatically detected from the supplied video file.\n\nYou will be prompted to select an appropriate Lens Profile in the next panel."; + alert.showsSuppressionButton = YES; + [alert beginSheetModalForWindow:loadLastGyroflowProjectView.window completionHandler:^(NSModalResponse result) { + + //--------------------------------------------------------- + // Close the alert: + //--------------------------------------------------------- + [alert.window orderOut:nil]; + + if ([alert suppressionButton].state == NSControlStateValueOn) { + [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"suppressNoLensProfileDetected"]; + } + [self buttonLoadPresetLensProfile]; + }]; + } else { + [self buttonLoadPresetLensProfile]; + } } - */ return YES; } @@ -4011,14 +3852,19 @@ - (BOOL)importMediaWithOptionalURL:(NSURL*)optionalURL { //--------------------------------------------------------- - (void)importGyroflowProjectWithOptionalURL:(NSURL*)optionalURL { - NSLog(@"[Gyroflow Toolbox Renderer] Import Gyroflow Project with Optional URL Triggered: %@", optionalURL); + //NSLog(@"[Gyroflow Toolbox Renderer] Import Gyroflow Project with Optional URL Triggered: %@", optionalURL); //--------------------------------------------------------- // 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' in ImportGyroflowProjectView's 'buttonPressed'. This shouldn't happen."]; + //--------------------------------------------------------- + // Show error message: + //--------------------------------------------------------- + NSString *errorMessage = @"Unable to retrieve 'FxCustomParameterActionAPI_v4' in ImportGyroflowProjectView's 'buttonPressed'. This shouldn't happen."; + NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); + [self showAlertWithMessage:@"An error has occurred." info:errorMessage]; return; } @@ -4053,7 +3899,12 @@ - (void)importGyroflowProjectWithOptionalURL:(NSURL*)optionalURL { NSURL *url = [panel URL]; BOOL startedOK = [url startAccessingSecurityScopedResource]; if (startedOK == NO) { - [self showAlertWithMessage:@"An error has occurred." info:@"Failed to startAccessingSecurityScopedResource. This shouldn't happen."]; + //--------------------------------------------------------- + // Show error message: + //--------------------------------------------------------- + NSString *errorMessage = @"Failed to startAccessingSecurityScopedResource. This shouldn't happen."; + NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); + [self showAlertWithMessage:@"An error has occurred." info:errorMessage]; return; } @@ -4103,7 +3954,7 @@ - (void)importGyroflowProjectWithOptionalURL:(NSURL*)optionalURL { //--------------------------------------------------------- const char* hasData = doesGyroflowProjectContainStabilisationData([selectedGyroflowProjectData UTF8String]); NSString *hasDataResult = [NSString stringWithUTF8String: hasData]; - NSLog(@"[Gyroflow Toolbox Renderer] hasDataResult: %@", hasDataResult); + //NSLog(@"[Gyroflow Toolbox Renderer] hasDataResult: %@", hasDataResult); if (hasDataResult == nil || ![hasDataResult isEqualToString:@"YES"]) { 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."; NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); @@ -4136,7 +3987,7 @@ - (void)importGyroflowProjectWithOptionalURL:(NSURL*)optionalURL { ); NSString *getDefaultValuesResultString = [NSString stringWithUTF8String:getDefaultValuesResult]; - NSLog(@"[Gyroflow Toolbox Renderer] getDefaultValuesResult: %@", getDefaultValuesResultString); + //NSLog(@"[Gyroflow Toolbox Renderer] getDefaultValuesResult: %@", getDefaultValuesResultString); //--------------------------------------------------------- // Use the Action API to allow us to change the parameters: @@ -4186,14 +4037,14 @@ - (void)importGyroflowProjectWithOptionalURL:(NSURL*)optionalURL { // Set parameters from Gyroflow Project file: //--------------------------------------------------------- if ([getDefaultValuesResultString isEqualToString:@"OK"]) { - NSLog(@"[Gyroflow Toolbox Renderer] defaultFOV: %f", defaultFOV); - NSLog(@"[Gyroflow Toolbox Renderer] defaultSmoothness: %f", defaultSmoothness); - NSLog(@"[Gyroflow Toolbox Renderer] defaultLensCorrection: %f", defaultLensCorrection); - NSLog(@"[Gyroflow Toolbox Renderer] defaultHorizonLock: %f", defaultHorizonLock); - NSLog(@"[Gyroflow Toolbox Renderer] defaultHorizonRoll: %f", defaultHorizonRoll); - NSLog(@"[Gyroflow Toolbox Renderer] defaultPositionOffsetX: %f", defaultPositionOffsetX); - NSLog(@"[Gyroflow Toolbox Renderer] defaultPositionOffsetY: %f", defaultPositionOffsetY); - NSLog(@"[Gyroflow Toolbox Renderer] defaultVideoRotation: %f", defaultVideoRotation); + //NSLog(@"[Gyroflow Toolbox Renderer] defaultFOV: %f", defaultFOV); + //NSLog(@"[Gyroflow Toolbox Renderer] defaultSmoothness: %f", defaultSmoothness); + //NSLog(@"[Gyroflow Toolbox Renderer] defaultLensCorrection: %f", defaultLensCorrection); + //NSLog(@"[Gyroflow Toolbox Renderer] defaultHorizonLock: %f", defaultHorizonLock); + //NSLog(@"[Gyroflow Toolbox Renderer] defaultHorizonRoll: %f", defaultHorizonRoll); + //NSLog(@"[Gyroflow Toolbox Renderer] defaultPositionOffsetX: %f", defaultPositionOffsetX); + //NSLog(@"[Gyroflow Toolbox Renderer] defaultPositionOffsetY: %f", defaultPositionOffsetY); + //NSLog(@"[Gyroflow Toolbox Renderer] defaultVideoRotation: %f", defaultVideoRotation); [paramSetAPI setFloatValue:defaultFOV toParameter:kCB_FOV atTime:kCMTimeZero]; [paramSetAPI setFloatValue:defaultSmoothness toParameter:kCB_Smoothness atTime:kCMTimeZero]; @@ -4220,9 +4071,352 @@ - (void)importGyroflowProjectWithOptionalURL:(NSURL*)optionalURL { //--------------------------------------------------------- // Show Victory Message: //--------------------------------------------------------- + // TODO: This import success alert should be optional. [self showAlertWithMessage:@"Success!" info:@"The Gyroflow Project has been successfully imported.\n\nYou can now adjust the parameters as required."]; } +//--------------------------------------------------------- +// Import BRAW Toolbox Clip with Path: +//--------------------------------------------------------- +- (void)importBRAWToolboxClipWithPath:(NSString*)path bookmarkDataString:(NSString*)bookmarkDataString { + + //NSLog(@"[Gyroflow Toolbox Renderer] importBRAWToolboxClipWithPath Triggered!"); + //NSLog(@"[Gyroflow Toolbox Renderer] path: %@", path); + //NSLog(@"[Gyroflow Toolbox Renderer] bookmarkDataString: %@", bookmarkDataString); + + NSUserDefaults *userDefaults = [[[NSUserDefaults alloc] init] autorelease]; + NSString *brawToolboxDocumentBookmarkData = [userDefaults stringForKey:@"brawToolboxDocumentBookmarkData"]; + + //NSLog(@"[Gyroflow Toolbox Renderer] brawToolboxDocumentBookmarkData: %@", brawToolboxDocumentBookmarkData); + + NSURL *urlToBRAWToolboxDocument = nil; + + BOOL needToRequestAccessToBRAWToolboxDocument = YES; + + //--------------------------------------------------------- + // Check if there's previously saved bookmark data: + //--------------------------------------------------------- + if (brawToolboxDocumentBookmarkData != nil && ![brawToolboxDocumentBookmarkData isEqualToString:@""]) { + + //NSLog(@"[Gyroflow Toolbox Renderer] brawToolboxDocumentBookmarkData is valid!"); + + //--------------------------------------------------------- + // Decode the Base64 bookmark data: + //--------------------------------------------------------- + NSData *decodedBookmark = [[[NSData alloc] initWithBase64EncodedString:brawToolboxDocumentBookmarkData + options:0] autorelease]; + + //--------------------------------------------------------- + // Resolve the decoded bookmark data into a + // security-scoped URL: + //--------------------------------------------------------- + NSError *bookmarkError = nil; + BOOL isStale = NO; + + urlToBRAWToolboxDocument = [NSURL URLByResolvingBookmarkData:decodedBookmark + options:NSURLBookmarkResolutionWithSecurityScope + relativeToURL:nil + bookmarkDataIsStale:&isStale + error:&bookmarkError]; + + //--------------------------------------------------------- + // Continue if there's no error... + //--------------------------------------------------------- + if (bookmarkError == nil || isStale == NO) { + if ([urlToBRAWToolboxDocument startAccessingSecurityScopedResource]) { + if ([[NSFileManager defaultManager] isReadableFileAtPath:[urlToBRAWToolboxDocument path]]) { + //NSLog(@"[Gyroflow Toolbox Renderer] We have access to the BRAW Toolbox Document. Yay!"); + needToRequestAccessToBRAWToolboxDocument = NO; + } else { + //NSLog(@"[Gyroflow Toolbox Renderer] We don't have access to the BRAW Toolbox Document. Yay!"); + [urlToBRAWToolboxDocument stopAccessingSecurityScopedResource]; + } + } + } + } + + //--------------------------------------------------------- + // If we need to request access to BRAW Toolbox document: + //--------------------------------------------------------- + if (needToRequestAccessToBRAWToolboxDocument) { + + //NSLog(@"[Gyroflow Toolbox Renderer] needToRequestAccessToBRAWToolboxDocument triggered!"); + + //NSLog(@"[Gyroflow Toolbox Renderer] importMediaFileView: %@", importMediaFileView); + //NSLog(@"[Gyroflow Toolbox Renderer] importMediaFileView.window: %@", importMediaFileView.window); + + //--------------------------------------------------------- + // Show an alert: + //--------------------------------------------------------- + NSAlert *alert = [[[NSAlert alloc] init] autorelease]; + alert.icon = [NSImage imageNamed:@"GyroflowToolbox"]; + alert.alertStyle = NSAlertStyleInformational; + alert.messageText = @"Gyroflow Toolbox Requires Permission"; + alert.informativeText = @"To make it easier to import BRAW Toolbox clips into Gyroflow Toolbox, you'll need to grant Gyroflow Toolbox sandbox access to a BRAW Toolbox helper file.\n\nOn the next panel, please select 'Grant Sandbox Access' to continue."; + [alert beginSheetModalForWindow:importMediaFileView.window completionHandler:^(NSModalResponse result){ + + //--------------------------------------------------------- + // Close the alert: + //--------------------------------------------------------- + [alert.window orderOut:nil]; + + //NSLog(@"[Gyroflow Toolbox Renderer] NSModalResponse result: %ld", (long)result); + + //--------------------------------------------------------- + // Attempt to get access to the BRAW Toolbox document: + //--------------------------------------------------------- + NSString *userHomePath = [self getUserHomeDirectoryPath]; + //NSLog(@"[Gyroflow Toolbox Renderer] userHomePath: %@", userHomePath); + + NSString *documentFilePath = [userHomePath stringByAppendingString:@"/Library/Group Containers/A5HDJTY9X5.com.latenitefilms.BRAWToolbox/Library/Application Support/BRAWToolbox.document"]; + //NSLog(@"[Gyroflow Toolbox Renderer] documentFilePath: %@", documentFilePath); + + NSURL *documentFileURL = [NSURL fileURLWithPath:documentFilePath]; + //NSLog(@"[Gyroflow Toolbox Renderer] documentFileURL: %@", documentFileURL); + + //--------------------------------------------------------- + // Limit the file type to a ".document": + //--------------------------------------------------------- + UTType *documentType = [UTType typeWithFilenameExtension:@"document"]; + NSArray *allowedContentTypes = [NSArray arrayWithObjects:documentType, nil]; + + //--------------------------------------------------------- + // Setup an NSOpenPanel: + //--------------------------------------------------------- + NSOpenPanel* panel = [NSOpenPanel openPanel]; + [panel setMessage:@"Please select the BRAW Toolbox.document file:"]; + [panel setPrompt:@"Grant Access"]; + [panel setCanChooseDirectories:NO]; + [panel setCanCreateDirectories:NO]; + [panel setCanChooseFiles:YES]; + [panel setAllowsMultipleSelection:NO]; + [panel setDirectoryURL:documentFileURL]; + [panel setAllowedContentTypes:allowedContentTypes]; + [panel setExtensionHidden:NO]; + [panel setCanSelectHiddenExtension:YES]; + + //--------------------------------------------------------- + // Open the panel: + //--------------------------------------------------------- + NSModalResponse openPanelResult = [panel runModal]; + if (openPanelResult != NSModalResponseOK) { + return; + } + + NSURL *openPanelURL = [panel URL]; + //NSLog(@"[Gyroflow Toolbox Renderer] openPanelURL: %@", [openPanelURL path]); + + //--------------------------------------------------------- + // Start accessing security scoped resource: + //--------------------------------------------------------- + if (![openPanelURL startAccessingSecurityScopedResource]) { + NSString *errorMessage = @"Failed to startAccessingSecurityScopedResource when attempting to access the BRAW Toolbox document.\n\nThis shouldn't happen and is most likely a bug."; + NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); + [self showAsyncAlertWithMessage:@"An error has occurred." info:errorMessage]; + return; + } else { + NSLog(@"[Gyroflow Toolbox Renderer] We have sandbox access to: %@", [openPanelURL path]); + } + + //--------------------------------------------------------- + // Create a Security Scope Bookmark, so we can reload + // later: + //--------------------------------------------------------- + NSError *bookmarkError = nil; + NSURLBookmarkCreationOptions bookmarkOptions = NSURLBookmarkCreationWithSecurityScope | NSURLBookmarkCreationSecurityScopeAllowOnlyReadAccess; + NSData *bookmark = [openPanelURL bookmarkDataWithOptions:bookmarkOptions + includingResourceValuesForKeys:nil + relativeToURL:nil + error:&bookmarkError]; + + //--------------------------------------------------------- + // There was an error creating the bookmark: + //--------------------------------------------------------- + if (bookmarkError != nil) { + NSString *errorMessage = [NSString stringWithFormat:@"Failed to create bookmark of the open panel file due to:\n\n%@", [bookmarkError localizedDescription]]; + NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); + [self showAsyncAlertWithMessage:@"An error has occurred." info:errorMessage]; + + //--------------------------------------------------------- + // Stop Accessing Resource: + //--------------------------------------------------------- + [openPanelURL stopAccessingSecurityScopedResource]; + return; + } + + //--------------------------------------------------------- + // The bookmark is nil: + //--------------------------------------------------------- + if (bookmark == nil) { + NSString *errorMessage = @"Bookmark data from the open panel is nil.\n\nThis shouldn't happen and is most likely a bug."; + NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); + [self showAsyncAlertWithMessage:@"An error has occurred." info:errorMessage]; + + //--------------------------------------------------------- + // Stop Accessing Resource: + //--------------------------------------------------------- + [openPanelURL stopAccessingSecurityScopedResource]; + return; + } + + //--------------------------------------------------------- + // Same the encoded bookmark for next time: + //--------------------------------------------------------- + NSString *base64EncodedBookmark = [bookmark base64EncodedStringWithOptions:0]; + [userDefaults setObject:base64EncodedBookmark forKey:@"brawToolboxDocumentBookmarkData"]; + + //--------------------------------------------------------- + // + // Lets now try and resolve the bookmark in the FCPXML: + // + //--------------------------------------------------------- + + //--------------------------------------------------------- + // Resolve the decoded bookmark data into a + // security-scoped URL: + //--------------------------------------------------------- + NSData *decodedBookmarkData = [[[NSData alloc] initWithBase64EncodedString:bookmarkDataString + options:0] autorelease]; + + NSError *brawBookmarkError = nil; + BOOL isStale = NO; + + NSURL* brawDecodedBookmarkURL = [NSURL URLByResolvingBookmarkData:decodedBookmarkData + options:NSURLBookmarkResolutionWithSecurityScope + relativeToURL:openPanelURL + bookmarkDataIsStale:&isStale + error:&brawBookmarkError]; + + //--------------------------------------------------------- + // If there was an error: + //--------------------------------------------------------- + if (brawBookmarkError != nil) { + NSString *errorMessage = [NSString stringWithFormat:@"Failed to resolve bookmark due to:\n\n%@", [brawBookmarkError localizedDescription]]; + NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); + [self showAsyncAlertWithMessage:@"An error has occurred." info:errorMessage]; + + + //--------------------------------------------------------- + // Stop Action API & Stop Accessing Resource: + //--------------------------------------------------------- + [openPanelURL stopAccessingSecurityScopedResource]; + return; + } + + if (![brawDecodedBookmarkURL startAccessingSecurityScopedResource]) { + NSString *errorMessage = @"Failed to startAccessingSecurityScopedResource when attempting to access the BRAW Toolbox clip.\n\nThis shouldn't happen."; + NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); + [self showAsyncAlertWithMessage:@"An error has occurred." info:errorMessage]; + return; + } + + //[self showAsyncAlertWithMessage:@"VICTORY!" info:[brawDecodedBookmarkURL path]]; + + //--------------------------------------------------------- + // Check to see if there's a Gyroflow project next to the + // BRAW file: + //--------------------------------------------------------- + NSURL *gyroflowUrl = [[brawDecodedBookmarkURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"gyroflow"]; + //NSLog(@"[Gyroflow Toolbox Renderer] importDroppedMedia Gyroflow URL: %@", [gyroflowUrl path]); + + NSFileManager *fileManager = [NSFileManager defaultManager]; + if ([fileManager fileExistsAtPath:[gyroflowUrl path]]) { + //NSLog(@"[Gyroflow Toolbox Renderer] It's a Media File, with a Gyroflow Project next to it: %@", gyroflowUrl); + [self importGyroflowProjectWithOptionalURL:gyroflowUrl]; + } else { + //NSLog(@"[Gyroflow Toolbox Renderer] It's a Media File, with no Gyroflow Project next to it: %@", brawDecodedBookmarkURL); + [self importMediaWithOptionalURL:brawDecodedBookmarkURL]; + } + + [openPanelURL stopAccessingSecurityScopedResource]; + [brawDecodedBookmarkURL stopAccessingSecurityScopedResource]; + }]; + return; + } + + //--------------------------------------------------------- + // + // We already have access to the BRAW Toolbox document: + // + //--------------------------------------------------------- + + //NSLog(@"[Gyroflow Toolbox Renderer] We already have access to: %@", urlToBRAWToolboxDocument); + + //--------------------------------------------------------- + // Resolve the decoded bookmark data into a + // security-scoped URL: + //--------------------------------------------------------- + NSData *decodedBookmarkData = [[[NSData alloc] initWithBase64EncodedString:bookmarkDataString + options:0] autorelease]; + + NSError *brawBookmarkError = nil; + BOOL isStale = NO; + + NSURL* brawDecodedBookmarkURL = [NSURL URLByResolvingBookmarkData:decodedBookmarkData + options:NSURLBookmarkResolutionWithSecurityScope + relativeToURL:urlToBRAWToolboxDocument + bookmarkDataIsStale:&isStale + error:&brawBookmarkError]; + + //--------------------------------------------------------- + // If there was an error: + //--------------------------------------------------------- + if (brawBookmarkError != nil) { + //--------------------------------------------------------- + // Show error message: + //--------------------------------------------------------- + NSString *errorMessage = [NSString stringWithFormat:@"Failed to resolve bookmark due to:\n\n%@", [brawBookmarkError localizedDescription]]; + NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); + [self showAsyncAlertWithMessage:@"An error has occurred." info:errorMessage]; + + //--------------------------------------------------------- + // Stop Action API & Stop Accessing Resource: + //--------------------------------------------------------- + [urlToBRAWToolboxDocument stopAccessingSecurityScopedResource]; + return; + } + + //--------------------------------------------------------- + // Failed to access resource due to sandbox: + //--------------------------------------------------------- + if (![brawDecodedBookmarkURL startAccessingSecurityScopedResource]) { + //--------------------------------------------------------- + // Show error message: + //--------------------------------------------------------- + NSString *errorMessage = @"Failed to startAccessingSecurityScopedResource when attempting to access the BRAW Toolbox clip.\n\nThis shouldn't happen."; + NSLog(@"[Gyroflow Toolbox Renderer] %@", errorMessage); + [self showAsyncAlertWithMessage:@"An error has occurred." info:errorMessage]; + + //--------------------------------------------------------- + // Stop Action API & Stop Accessing Resource: + //--------------------------------------------------------- + [urlToBRAWToolboxDocument stopAccessingSecurityScopedResource]; + return; + } + + //--------------------------------------------------------- + // Check to see if there's a Gyroflow project next to the + // BRAW file: + //--------------------------------------------------------- + NSURL *gyroflowUrl = [[brawDecodedBookmarkURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"gyroflow"]; + //NSLog(@"[Gyroflow Toolbox Renderer] importDroppedMedia Gyroflow URL: %@", [gyroflowUrl path]); + + NSFileManager *fileManager = [NSFileManager defaultManager]; + if ([fileManager fileExistsAtPath:[gyroflowUrl path]]) { + //NSLog(@"[Gyroflow Toolbox Renderer] It's a Media File, with a Gyroflow Project next to it: %@", gyroflowUrl); + [self importGyroflowProjectWithOptionalURL:gyroflowUrl]; + } else { + //NSLog(@"[Gyroflow Toolbox Renderer] It's a Media File, with no Gyroflow Project next to it: %@", brawDecodedBookmarkURL); + [self importMediaWithOptionalURL:brawDecodedBookmarkURL]; + } + + //--------------------------------------------------------- + // Stop accessing resources: + //--------------------------------------------------------- + [brawDecodedBookmarkURL stopAccessingSecurityScopedResource]; + [urlToBRAWToolboxDocument stopAccessingSecurityScopedResource]; +} + //--------------------------------------------------------- // #pragma mark - Helpers @@ -4230,23 +4424,105 @@ - (void)importGyroflowProjectWithOptionalURL:(NSURL*)optionalURL { //--------------------------------------------------------- //--------------------------------------------------------- -// Run on Main Queue Without Deadlocking: +// Converts a NSNumber to a Control State: //--------------------------------------------------------- -void runOnMainQueueWithoutDeadlocking(void (^block)(void)) { - if ([NSThread isMainThread]) { - block(); +- (NSInteger)boolToControlState:(BOOL)state { + if (state) { + return NSControlStateValueOn; } else { - dispatch_sync(dispatch_get_main_queue(), block); + return NSControlStateValueOff; } } +//--------------------------------------------------------- +// Is Valid BRAW Toolbox String? +//--------------------------------------------------------- +- (BOOL)isValidBRAWToolboxString:(NSString *)inputString { + //--------------------------------------------------------- + // Check for : + //--------------------------------------------------------- + NSRange filterVideoRange = [inputString rangeOfString:@"" options:NSCaseInsensitiveSearch]; + if (brawToolboxRange.location == NSNotFound) { + return NO; + } + + //--------------------------------------------------------- + // Check for pw_dir]; + NSString *userHomeDirectoryPath = [NSString stringWithUTF8String:pw->pw_dir]; + NSLog(@"[Gyroflow Toolbox] getUserHomeDirectoryPath - Method 2: %@", userHomeDirectoryPath); + return userHomeDirectoryPath; +} + +//--------------------------------------------------------- +// Run on Main Queue Without Deadlocking: +//--------------------------------------------------------- +void runOnMainQueueWithoutDeadlocking(void (^block)(void)) { + if ([NSThread isMainThread]) { + block(); + } else { + dispatch_sync(dispatch_get_main_queue(), block); + } } //--------------------------------------------------------- @@ -4273,4 +4549,18 @@ - (void)showAlertWithMessage:(NSString*)message info:(NSString*)info { }); } +//--------------------------------------------------------- +// Show Asynchronous Alert: +//--------------------------------------------------------- +- (void)showAsyncAlertWithMessage:(NSString*)message info:(NSString*)info { + dispatch_async(dispatch_get_main_queue(), ^{ + NSAlert *alert = [[[NSAlert alloc] init] autorelease]; + alert.icon = [NSImage imageNamed:@"GyroflowToolbox"]; + alert.alertStyle = NSAlertStyleInformational; + alert.messageText = message; + alert.informativeText = info; + [alert runModal]; + }); +} + @end diff --git a/Source/Gyroflow/Plugin/HeaderView.xib b/Source/Gyroflow/Plugin/HeaderView.xib index 9933b671..eac5100e 100644 --- a/Source/Gyroflow/Plugin/HeaderView.xib +++ b/Source/Gyroflow/Plugin/HeaderView.xib @@ -14,34 +14,36 @@ - + - + - + - + + To get started, drop your video clip from the Final Cut Pro Browser or Finder to the drop zone below. You can also drop a Gyroflow Project. - + - + + Sony, GoPro (Hero 8+), DJI, BRAW and Insta360 will be automatically synced when imported. Other formats, such as R3D, will require launching Gyroflow to sync. - + @@ -50,7 +52,7 @@ - + @@ -59,17 +61,17 @@ - + - GoPro (Hero 8+), DJI and Insta360 will automatically select a lens profile. For other cameras, you'll need to select a lens profile manually. + GoPro (Hero 8+), DJI and Insta360 will automatically select a lens profile. For all other cameras, you'll need to select a lens profile manually. - +