{"id":242,"date":"2026-03-24T00:38:21","date_gmt":"2026-03-24T00:38:21","guid":{"rendered":"https:\/\/www.visitnyc.store\/blog\/?p=242"},"modified":"2026-03-24T02:29:24","modified_gmt":"2026-03-24T02:29:24","slug":"wow-blackmagic-raw-sdk","status":"publish","type":"post","link":"https:\/\/www.visitnyc.store\/blog\/wow-blackmagic-raw-sdk\/","title":{"rendered":"Wow Blackmagic Raw SDK ++"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">I was wondering why a big chunk of the URSA manual was just APIs and now I think I&#8217;m understanding.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/www.blackmagicdesign.com\/developer\/products\/braw\/sdk-and-software\">https:\/\/www.blackmagicdesign.com\/developer\/products\/braw\/sdk-and-software<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Installed for Mac OS. Went to &#8220;\/Applications\/Blackmagic RAW\/Blackmagic RAW SDK\/Mac&#8221; and then took the &#8220;ExtractFrame.cpp&#8221; Xcodeproject and sprinkle some codex and boom now a command to extract frames from blackmagic raw into sbs<\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>itunes@ituness-MacBook-Pro Desktop % for f in \/Volumes\/Expansion2\/BLACKMAGIC\/2026-02-february\/*.braw; do &#91; -e \"$f\" ] || continue; .\/ExtractFrame \"$f\"; done\nCreated \/Users\/itunes\/Desktop\/ExtractFrame_SBS_A001_02121815_C001_frame0_221414_20260323.jpg\nCreated \/Users\/itunes\/Desktop\/ExtractFrame_SBS_A001_02121821_C002_frame0_221420_20260323.jpg\nCreated \/Users\/itunes\/Desktop\/ExtractFrame_SBS_A001_02121941_C007_frame0_221426_20260323.jpg\nCreated \/Users\/itunes\/Desktop\/ExtractFrame_SBS_A001_02122019_C012_frame0_221432_20260323.jpg\nCreated \/Users\/itunes\/Desktop\/ExtractFrame_SBS_A001_02122025_C013_frame0_221439_20260323.jpg\nitunes@ituness-MacBook-Pro Desktop % for f in \/Volumes\/Expansion2\/BLACKMAGIC\/2026-03-march\/*.braw; do &#91; -e \"$f\" ] || continue; .\/ExtractFrame \"$f\"; done   \nCreated \/Users\/itunes\/Desktop\/ExtractFrame_SBS_A002_03100815_C001_frame0_221540_20260323.jpg\nCreated \/Users\/itunes\/Desktop\/ExtractFrame_SBS_A002_03100900_C002_frame0_221546_20260323.jpg\nCreated \/Users\/itunes\/Desktop\/ExtractFrame_SBS_A002_03100904_C003_frame0_221553_20260323.jpg\nCreated \/Users\/itunes\/Desktop\/ExtractFrame_SBS_A002_03100925_C004_frame0_221559_20260323.jpg\nCreated \/Users\/itunes\/Desktop\/ExtractFrame_SBS_A002_03101036_C005_frame0_221605_20260323.jpg<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">And now you have left and right frame exported from .braw! Hooray!<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Why do this? <em>to investigate a suspicion of a stereo issue &#8230; ? <\/em><\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>#include \"BlackmagicRawAPI.h\"\n\n#include &lt;cerrno>\n#include &lt;cctype>\n#include &lt;cstring>\n#include &lt;cstdlib>\n#include &lt;ctime>\n#include &lt;iomanip>\n#include &lt;iostream>\n#include &lt;limits>\n#include &lt;mutex>\n#include &lt;pwd.h>\n#include &lt;string>\n#include &lt;unistd.h>\n#include &lt;vector>\n\n#include &lt;CoreServices\/CoreServices.h>\n#include &lt;ImageIO\/ImageIO.h>\n\n#ifdef DEBUG\n\t#include &lt;cassert>\n\t#define VERIFY(condition) assert(SUCCEEDED(condition))\n#else\n\t#define VERIFY(condition) condition\n#endif\n\nstatic const BlackmagicRawResourceFormat s_resourceFormat = blackmagicRawResourceFormatRGBAU8;\nstatic const CFStringRef s_blackmagicRawLibrariesPath = CFSTR(\"\/Applications\/Blackmagic RAW\/Blackmagic RAW SDK\/Mac\/Libraries\");\n\nstruct OutputFormat\n{\n\tconst char* cliName;\n\tconst char* fileExtension;\n\tCFStringRef imageType;\n\tbool isLossy;\n};\n\nenum class LayoutMode\n{\n\tSideBySide,\n\tSeparateEyes\n};\n\nenum class EyeIndex\n{\n\tLeft,\n\tRight\n};\n\nstruct EyeImageData\n{\n\tuint32_t width = 0;\n\tuint32_t height = 0;\n\tsize_t rowBytes = 0;\n\tstd::vector&lt;uint8_t> imageData;\n\tbool isReady = false;\n};\n\nstruct ExtractionState\n{\n\tLayoutMode layoutMode = LayoutMode::SideBySide;\n\tconst OutputFormat* outputFormat = nullptr;\n\tCFStringRef sideBySideOutputFileName = nullptr;\n\tCFStringRef leftOutputFileName = nullptr;\n\tCFStringRef rightOutputFileName = nullptr;\n\tEyeImageData leftEyeImage;\n\tEyeImageData rightEyeImage;\n\tbool hasError = false;\n\tstd::string errorMessage;\n\tstd::mutex mutex;\n};\n\nstruct EyeJobData\n{\n\tEyeIndex eye = EyeIndex::Left;\n\tExtractionState* extractionState = nullptr;\n};\n\nstruct CommandLineOptions\n{\n\tconst char* clipPath;\n\tconst OutputFormat* outputFormat;\n\tLayoutMode layoutMode;\n\tuint64_t frameIndex;\n\tbool showInfo;\n};\n\nenum class ParseArgumentsResult\n{\n\tSuccess,\n\tHelp,\n\tError\n};\n\nstatic const OutputFormat s_pngOutputFormat = { \"png\", \"png\", kUTTypePNG, false };\nstatic const OutputFormat s_jpegOutputFormat = { \"jpeg\", \"jpg\", kUTTypeJPEG, true };\nstatic const OutputFormat s_tiffOutputFormat = { \"tiff\", \"tiff\", kUTTypeTIFF, false };\n\nstatic void PrintUsage(std::ostream&amp; stream, const char* executableName)\n{\n\tstream\n\t\t&lt;&lt; \"Usage:\\n\"\n\t\t&lt;&lt; \"  \" &lt;&lt; executableName &lt;&lt; \" clipName.braw &#91;--frame frameIndex] &#91;--layout sbs|separate] &#91;--format png|jpeg|jpg|tiff]\\n\"\n\t\t&lt;&lt; \"  \" &lt;&lt; executableName &lt;&lt; \" clipName.braw &#91;sbs|separate] &#91;png|jpeg|jpg|tiff]\\n\"\n\t\t&lt;&lt; \"  \" &lt;&lt; executableName &lt;&lt; \" --info clipName.braw\\n\";\n}\n\nstatic bool ParseUInt64(const char* value, uint64_t* parsedValue)\n{\n\tif (value == nullptr || parsedValue == nullptr || value&#91;0] == '\\0' || value&#91;0] == '-')\n\t\treturn false;\n\n\tchar* end = nullptr;\n\terrno = 0;\n\tconst unsigned long long parsedUnsignedLongLong = std::strtoull(value, &amp;end, 10);\n\tif (errno != 0 || end == value || *end != '\\0')\n\t\treturn false;\n\n\t*parsedValue = static_cast&lt;uint64_t>(parsedUnsignedLongLong);\n\treturn true;\n}\n\nstatic std::string CFStringToUTF8String(CFStringRef value)\n{\n\tif (value == nullptr)\n\t\treturn std::string();\n\n\tconst char* directCString = CFStringGetCStringPtr(value, kCFStringEncodingUTF8);\n\tif (directCString != nullptr)\n\t\treturn std::string(directCString);\n\n\tconst CFIndex length = CFStringGetLength(value);\n\tconst CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;\n\tstd::vector&lt;char> buffer(static_cast&lt;size_t>(maxSize), '\\0');\n\tif (CFStringGetCString(value, buffer.data(), maxSize, kCFStringEncodingUTF8))\n\t\treturn std::string(buffer.data());\n\n\treturn std::string();\n}\n\nstatic std::string GetHomeDirectoryPath()\n{\n\tconst char* homeDirectory = std::getenv(\"HOME\");\n\tif (homeDirectory != nullptr &amp;&amp; homeDirectory&#91;0] != '\\0')\n\t\treturn std::string(homeDirectory);\n\n\tconst passwd* userInfo = getpwuid(getuid());\n\tif (userInfo != nullptr &amp;&amp; userInfo->pw_dir != nullptr &amp;&amp; userInfo->pw_dir&#91;0] != '\\0')\n\t\treturn std::string(userInfo->pw_dir);\n\n\treturn std::string();\n}\n\nstatic std::string GetClipStem(const char* clipPath)\n{\n\tstd::string fileName = clipPath != nullptr ? std::string(clipPath) : std::string(\"output\");\n\n\tconst std::string::size_type lastSeparator = fileName.find_last_of(\"\/\\\\\");\n\tif (lastSeparator != std::string::npos)\n\t\tfileName = fileName.substr(lastSeparator + 1);\n\n\tconst std::string::size_type lastDot = fileName.find_last_of('.');\n\tif (lastDot != std::string::npos)\n\t\tfileName = fileName.substr(0, lastDot);\n\n\tif (fileName.empty())\n\t\tfileName = \"output\";\n\n\treturn fileName;\n}\n\nstatic void SanitizeFileNameComponent(std::string&amp; fileNameComponent)\n{\n\tfor (char&amp; character : fileNameComponent)\n\t{\n\t\tconst unsigned char unsignedCharacter = static_cast&lt;unsigned char>(character);\n\t\tif (! std::isalnum(unsignedCharacter) &amp;&amp; character != '-' &amp;&amp; character != '_' &amp;&amp; character != '.')\n\t\t\tcharacter = '_';\n\t}\n}\n\nstatic std::string CreateTimeDateString()\n{\n\tconst std::time_t currentTime = std::time(nullptr);\n\tstd::tm localTime = {};\n\tif (localtime_r(&amp;currentTime, &amp;localTime) == nullptr)\n\t\treturn \"unknown_time\";\n\n\tchar timestamp&#91;32] = {};\n\tif (std::strftime(timestamp, sizeof(timestamp), \"%H%M%S_%Y%m%d\", &amp;localTime) == 0)\n\t\treturn \"unknown_time\";\n\n\treturn std::string(timestamp);\n}\n\nstatic std::string ToLowerASCII(const char* value)\n{\n\tstd::string lowerCaseValue = value != nullptr ? std::string(value) : std::string();\n\tfor (char&amp; character : lowerCaseValue)\n\t\tcharacter = static_cast&lt;char>(std::tolower(static_cast&lt;unsigned char>(character)));\n\n\treturn lowerCaseValue;\n}\n\nstatic const char* GetEyeLabel(EyeIndex eye)\n{\n\treturn eye == EyeIndex::Left ? \"left\" : \"right\";\n}\n\nstatic const char* GetEyeOutputName(EyeIndex eye)\n{\n\treturn eye == EyeIndex::Left ? \"LEFT\" : \"RIGHT\";\n}\n\nstatic bool ParseLayoutMode(const char* layoutName, LayoutMode* layoutMode)\n{\n\tif (layoutMode == nullptr)\n\t\treturn false;\n\n\tif (layoutName == nullptr)\n\t{\n\t\t*layoutMode = LayoutMode::SideBySide;\n\t\treturn true;\n\t}\n\n\tconst std::string normalizedLayoutName = ToLowerASCII(layoutName);\n\tif (normalizedLayoutName == \"sbs\" || normalizedLayoutName == \"side-by-side\" || normalizedLayoutName == \"sidebyside\")\n\t{\n\t\t*layoutMode = LayoutMode::SideBySide;\n\t\treturn true;\n\t}\n\n\tif (normalizedLayoutName == \"separate\" || normalizedLayoutName == \"eyes\" || normalizedLayoutName == \"separate-eyes\")\n\t{\n\t\t*layoutMode = LayoutMode::SeparateEyes;\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nstatic const OutputFormat* ParseOutputFormat(const char* formatName)\n{\n\tif (formatName == nullptr)\n\t\treturn &amp;s_jpegOutputFormat;\n\n\tconst std::string normalizedFormatName = ToLowerASCII(formatName);\n\tif (normalizedFormatName == \"png\")\n\t\treturn &amp;s_pngOutputFormat;\n\tif (normalizedFormatName == \"jpeg\" || normalizedFormatName == \"jpg\")\n\t\treturn &amp;s_jpegOutputFormat;\n\tif (normalizedFormatName == \"tiff\" || normalizedFormatName == \"tif\")\n\t\treturn &amp;s_tiffOutputFormat;\n\n\treturn nullptr;\n}\n\nstatic ParseArgumentsResult ParseArguments(int argc, const char* argv&#91;], CommandLineOptions* options, std::string* errorMessage)\n{\n\tif (options == nullptr || errorMessage == nullptr)\n\t\treturn ParseArgumentsResult::Error;\n\n\toptions->clipPath = nullptr;\n\toptions->outputFormat = &amp;s_jpegOutputFormat;\n\toptions->layoutMode = LayoutMode::SideBySide;\n\toptions->frameIndex = 0;\n\toptions->showInfo = false;\n\terrorMessage->clear();\n\n\tfor (int argumentIndex = 1; argumentIndex &lt; argc; ++argumentIndex)\n\t{\n\t\tconst std::string argument = argv&#91;argumentIndex];\n\n\t\tif (argument == \"--help\" || argument == \"-h\")\n\t\t\treturn ParseArgumentsResult::Help;\n\n\t\tif (argument == \"--info\")\n\t\t{\n\t\t\toptions->showInfo = true;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (argument == \"--frame\" || argument == \"--format\" || argument == \"--layout\")\n\t\t{\n\t\t\tif (argumentIndex + 1 >= argc)\n\t\t\t{\n\t\t\t\t*errorMessage = \"Missing value for \" + argument + \".\";\n\t\t\t\treturn ParseArgumentsResult::Error;\n\t\t\t}\n\n\t\t\tconst char* value = argv&#91;++argumentIndex];\n\t\t\tif (argument == \"--frame\")\n\t\t\t{\n\t\t\t\tif (! ParseUInt64(value, &amp;options->frameIndex))\n\t\t\t\t{\n\t\t\t\t\t*errorMessage = \"Invalid frame index: \" + std::string(value) + \".\";\n\t\t\t\t\treturn ParseArgumentsResult::Error;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (argument == \"--format\")\n\t\t\t{\n\t\t\t\toptions->outputFormat = ParseOutputFormat(value);\n\t\t\t\tif (options->outputFormat == nullptr)\n\t\t\t\t{\n\t\t\t\t\t*errorMessage = \"Unsupported output format: \" + std::string(value) + \".\";\n\t\t\t\t\treturn ParseArgumentsResult::Error;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (! ParseLayoutMode(value, &amp;options->layoutMode))\n\t\t\t{\n\t\t\t\t*errorMessage = \"Unsupported layout mode: \" + std::string(value) + \".\";\n\t\t\t\treturn ParseArgumentsResult::Error;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (argument.rfind(\"--frame=\", 0) == 0)\n\t\t{\n\t\t\tconst char* value = argument.c_str() + 8;\n\t\t\tif (! ParseUInt64(value, &amp;options->frameIndex))\n\t\t\t{\n\t\t\t\t*errorMessage = \"Invalid frame index: \" + std::string(value) + \".\";\n\t\t\t\treturn ParseArgumentsResult::Error;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (argument.rfind(\"--format=\", 0) == 0)\n\t\t{\n\t\t\tconst char* value = argument.c_str() + 9;\n\t\t\toptions->outputFormat = ParseOutputFormat(value);\n\t\t\tif (options->outputFormat == nullptr)\n\t\t\t{\n\t\t\t\t*errorMessage = \"Unsupported output format: \" + std::string(value) + \".\";\n\t\t\t\treturn ParseArgumentsResult::Error;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (argument.rfind(\"--layout=\", 0) == 0)\n\t\t{\n\t\t\tconst char* value = argument.c_str() + 9;\n\t\t\tif (! ParseLayoutMode(value, &amp;options->layoutMode))\n\t\t\t{\n\t\t\t\t*errorMessage = \"Unsupported layout mode: \" + std::string(value) + \".\";\n\t\t\t\treturn ParseArgumentsResult::Error;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (options->clipPath == nullptr)\n\t\t{\n\t\t\toptions->clipPath = argv&#91;argumentIndex];\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst OutputFormat* positionalOutputFormat = ParseOutputFormat(argument.c_str());\n\t\tif (positionalOutputFormat != nullptr)\n\t\t{\n\t\t\toptions->outputFormat = positionalOutputFormat;\n\t\t\tcontinue;\n\t\t}\n\n\t\tLayoutMode positionalLayoutMode = LayoutMode::SideBySide;\n\t\tif (ParseLayoutMode(argument.c_str(), &amp;positionalLayoutMode))\n\t\t{\n\t\t\toptions->layoutMode = positionalLayoutMode;\n\t\t\tcontinue;\n\t\t}\n\n\t\t*errorMessage = \"Unrecognized argument: \" + argument + \".\";\n\t\treturn ParseArgumentsResult::Error;\n\t}\n\n\tif (options->clipPath == nullptr)\n\t{\n\t\t*errorMessage = \"Missing clip path.\";\n\t\treturn ParseArgumentsResult::Error;\n\t}\n\n\treturn ParseArgumentsResult::Success;\n}\n\nstatic CFStringRef CreateOutputFileName(const char* clipPath, const char* outputName, uint64_t frameIndex, const std::string&amp; timeDateString, const OutputFormat&amp; outputFormat)\n{\n\tstd::string outputDirectory = GetHomeDirectoryPath();\n\tif (outputDirectory.empty())\n\t\treturn nullptr;\n\n\tif (outputDirectory.back() != '\/')\n\t\toutputDirectory += '\/';\n\n\tstd::string clipStem = GetClipStem(clipPath);\n\tSanitizeFileNameComponent(clipStem);\n\n\tstd::string outputFileName = outputDirectory + \"Desktop\/ExtractFrame_\" + outputName + \"_\" + clipStem + \"_frame\" + std::to_string(frameIndex) + \"_\" + timeDateString + \".\" + outputFormat.fileExtension;\n\treturn CFStringCreateWithCString(kCFAllocatorDefault, outputFileName.c_str(), kCFStringEncodingUTF8);\n}\n\nstatic void PrintClipInfo(const char* clipPath, IBlackmagicRawClip* clip)\n{\n\tif (clipPath != nullptr)\n\t\tstd::cout &lt;&lt; \"Clip: \" &lt;&lt; clipPath &lt;&lt; std::endl;\n\n\tuint32_t width = 0;\n\tuint32_t height = 0;\n\tif (clip->GetWidth(&amp;width) == S_OK &amp;&amp; clip->GetHeight(&amp;height) == S_OK)\n\t\tstd::cout &lt;&lt; \"Dimensions: \" &lt;&lt; width &lt;&lt; \"x\" &lt;&lt; height &lt;&lt; std::endl;\n\n\tfloat frameRate = 0.0f;\n\tconst bool hasFrameRate = (clip->GetFrameRate(&amp;frameRate) == S_OK);\n\tif (hasFrameRate)\n\t{\n\t\tconst std::streamsize originalPrecision = std::cout.precision();\n\t\tconst std::ios::fmtflags originalFlags = std::cout.flags();\n\t\tstd::cout &lt;&lt; std::fixed &lt;&lt; std::setprecision(3);\n\t\tstd::cout &lt;&lt; \"Frame rate: \" &lt;&lt; frameRate &lt;&lt; std::endl;\n\t\tstd::cout.flags(originalFlags);\n\t\tstd::cout.precision(originalPrecision);\n\t}\n\n\tuint64_t frameCount = 0;\n\tif (clip->GetFrameCount(&amp;frameCount) == S_OK)\n\t{\n\t\tstd::cout &lt;&lt; \"Frame count: \" &lt;&lt; frameCount &lt;&lt; std::endl;\n\t\tif (frameCount > 0)\n\t\t{\n\t\t\tstd::cout &lt;&lt; \"Valid frame range: 0-\" &lt;&lt; (frameCount - 1) &lt;&lt; std::endl;\n\n\t\t\tif (hasFrameRate &amp;&amp; frameRate > 0.0f)\n\t\t\t{\n\t\t\t\tconst std::streamsize originalPrecision = std::cout.precision();\n\t\t\t\tconst std::ios::fmtflags originalFlags = std::cout.flags();\n\t\t\t\tconst double durationSeconds = static_cast&lt;double>(frameCount) \/ static_cast&lt;double>(frameRate);\n\t\t\t\tstd::cout &lt;&lt; std::fixed &lt;&lt; std::setprecision(3);\n\t\t\t\tstd::cout &lt;&lt; \"Duration: \" &lt;&lt; durationSeconds &lt;&lt; \" seconds\" &lt;&lt; std::endl;\n\t\t\t\tstd::cout.flags(originalFlags);\n\t\t\t\tstd::cout.precision(originalPrecision);\n\t\t\t}\n\n\t\t\tCFStringRef firstTimecode = nullptr;\n\t\t\tif (clip->GetTimecodeForFrame(0, &amp;firstTimecode) == S_OK &amp;&amp; firstTimecode != nullptr)\n\t\t\t{\n\t\t\t\tstd::cout &lt;&lt; \"First frame timecode: \" &lt;&lt; CFStringToUTF8String(firstTimecode) &lt;&lt; std::endl;\n\t\t\t\tCFRelease(firstTimecode);\n\t\t\t}\n\n\t\t\tCFStringRef lastTimecode = nullptr;\n\t\t\tif (clip->GetTimecodeForFrame(frameCount - 1, &amp;lastTimecode) == S_OK &amp;&amp; lastTimecode != nullptr)\n\t\t\t{\n\t\t\t\tstd::cout &lt;&lt; \"Last frame timecode: \" &lt;&lt; CFStringToUTF8String(lastTimecode) &lt;&lt; std::endl;\n\t\t\t\tCFRelease(lastTimecode);\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic bool IsJPEGOutputFormat(const OutputFormat&amp; outputFormat)\n{\n\treturn CFEqual(outputFormat.imageType, kUTTypeJPEG);\n}\n\nstatic bool ComputeRGBABytesPerRow(uint32_t width, size_t* rowBytes)\n{\n\tif (rowBytes == nullptr || width == 0)\n\t\treturn false;\n\n\tconst size_t bytesPerPixel = 4U;\n\tif (static_cast&lt;size_t>(width) > (std::numeric_limits&lt;size_t>::max() \/ bytesPerPixel))\n\t\treturn false;\n\n\t*rowBytes = static_cast&lt;size_t>(width) * bytesPerPixel;\n\treturn true;\n}\n\nstatic bool ComputeImageSizeFromRowBytes(size_t rowBytes, uint32_t height, size_t* sizeBytes)\n{\n\tif (sizeBytes == nullptr || rowBytes == 0 || height == 0)\n\t\treturn false;\n\n\tif (static_cast&lt;size_t>(height) > (std::numeric_limits&lt;size_t>::max() \/ rowBytes))\n\t\treturn false;\n\n\t*sizeBytes = static_cast&lt;size_t>(height) * rowBytes;\n\treturn true;\n}\n\nstatic bool ComputeRGBAImageSizeBytes(uint32_t width, uint32_t height, size_t* sizeBytes)\n{\n\tsize_t rowBytes = 0;\n\tif (! ComputeRGBABytesPerRow(width, &amp;rowBytes))\n\t\treturn false;\n\n\treturn ComputeImageSizeFromRowBytes(rowBytes, height, sizeBytes);\n}\n\nstatic bool ComputeCapturedImageLayout(uint32_t width, uint32_t height, size_t sizeBytes, size_t* rowBytes, size_t* copySizeBytes)\n{\n\tif (rowBytes == nullptr || copySizeBytes == nullptr || width == 0 || height == 0 || sizeBytes == 0)\n\t\treturn false;\n\n\tsize_t packedRowBytes = 0;\n\tsize_t packedSizeBytes = 0;\n\tif (! ComputeRGBABytesPerRow(width, &amp;packedRowBytes) || ! ComputeRGBAImageSizeBytes(width, height, &amp;packedSizeBytes))\n\t\treturn false;\n\n\tif (sizeBytes &lt; packedSizeBytes)\n\t\treturn false;\n\n\tconst size_t heightAsSizeT = static_cast&lt;size_t>(height);\n\tif ((sizeBytes % heightAsSizeT) == 0)\n\t{\n\t\tconst size_t computedRowBytes = sizeBytes \/ heightAsSizeT;\n\t\tif (computedRowBytes >= packedRowBytes)\n\t\t{\n\t\t\t*rowBytes = computedRowBytes;\n\t\t\t*copySizeBytes = sizeBytes;\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t\/\/ Some clips appear to expose extra trailing bytes without padding every row.\n\t\/\/ Fall back to a tightly packed RGBA interpretation in that case.\n\t*rowBytes = packedRowBytes;\n\t*copySizeBytes = packedSizeBytes;\n\treturn true;\n}\n\nstatic void SetExtractionError(ExtractionState* extractionState, const std::string&amp; message)\n{\n\tif (extractionState == nullptr)\n\t{\n\t\tstd::cerr &lt;&lt; message &lt;&lt; std::endl;\n\t\treturn;\n\t}\n\n\tstd::lock_guard&lt;std::mutex> lock(extractionState->mutex);\n\tif (! extractionState->hasError)\n\t{\n\t\textractionState->hasError = true;\n\t\textractionState->errorMessage = message;\n\t}\n}\n\nstatic EyeJobData* GetEyeJobData(IBlackmagicRawJob* job)\n{\n\tif (job == nullptr)\n\t\treturn nullptr;\n\n\tvoid* userData = nullptr;\n\tif (job->GetUserData(&amp;userData) != S_OK || userData == nullptr)\n\t\treturn nullptr;\n\n\treturn static_cast&lt;EyeJobData*>(userData);\n}\n\nstatic std::string CreateEyeErrorMessage(const char* action, const EyeJobData* eyeJobData)\n{\n\tconst std::string prefix = action != nullptr ? std::string(action) : std::string(\"Failed to process\");\n\tif (eyeJobData == nullptr)\n\t\treturn prefix + \" the extracted image.\";\n\n\treturn prefix + \" the \" + std::string(GetEyeLabel(eyeJobData->eye)) + \" eye image.\";\n}\n\nstatic std::string CreateEyeCaptureFailureMessage(const EyeJobData* eyeJobData, uint32_t width, uint32_t height, size_t sizeBytes)\n{\n\tconst std::string eyeLabel = (eyeJobData != nullptr) ? std::string(GetEyeLabel(eyeJobData->eye)) : std::string(\"unknown\");\n\treturn \"Failed to capture the \" + eyeLabel + \" eye image. width=\" + std::to_string(width) + \", height=\" + std::to_string(height) + \", sizeBytes=\" + std::to_string(sizeBytes) + \".\";\n}\n\nstatic bool CaptureEyeImage(ExtractionState* extractionState, EyeIndex eye, uint32_t width, uint32_t height, size_t sizeBytes, const void* imageData)\n{\n\tif (extractionState == nullptr || imageData == nullptr)\n\t\treturn false;\n\n\tsize_t rowBytes = 0;\n\tsize_t copySizeBytes = 0;\n\tif (! ComputeCapturedImageLayout(width, height, sizeBytes, &amp;rowBytes, &amp;copySizeBytes))\n\t\treturn false;\n\n\tconst uint8_t* imageBytes = static_cast&lt;const uint8_t*>(imageData);\n\tstd::lock_guard&lt;std::mutex> lock(extractionState->mutex);\n\n\tEyeImageData&amp; eyeImage = (eye == EyeIndex::Left) ? extractionState->leftEyeImage : extractionState->rightEyeImage;\n\teyeImage.width = width;\n\teyeImage.height = height;\n\teyeImage.rowBytes = rowBytes;\n\teyeImage.imageData.assign(imageBytes, imageBytes + copySizeBytes);\n\teyeImage.isReady = true;\n\treturn true;\n}\n\nstatic bool ValidateEyeImage(const EyeImageData&amp; eyeImage, const char* eyeLabel, std::string* errorMessage)\n{\n\tif (! eyeImage.isReady)\n\t{\n\t\tif (errorMessage != nullptr)\n\t\t\t*errorMessage = \"The \" + std::string(eyeLabel) + \" eye image was not captured.\";\n\t\treturn false;\n\t}\n\n\tsize_t expectedSizeBytes = 0;\n\tif (! ComputeImageSizeFromRowBytes(eyeImage.rowBytes, eyeImage.height, &amp;expectedSizeBytes))\n\t{\n\t\tif (errorMessage != nullptr)\n\t\t\t*errorMessage = \"The \" + std::string(eyeLabel) + \" eye image dimensions are invalid.\";\n\t\treturn false;\n\t}\n\n\tsize_t minimumRowBytes = 0;\n\tif (! ComputeRGBABytesPerRow(eyeImage.width, &amp;minimumRowBytes) || eyeImage.rowBytes &lt; minimumRowBytes)\n\t{\n\t\tif (errorMessage != nullptr)\n\t\t\t*errorMessage = \"The \" + std::string(eyeLabel) + \" eye image row stride is invalid.\";\n\t\treturn false;\n\t}\n\n\tif (eyeImage.imageData.size() != expectedSizeBytes)\n\t{\n\t\tif (errorMessage != nullptr)\n\t\t\t*errorMessage = \"The \" + std::string(eyeLabel) + \" eye image buffer size does not match its row stride.\";\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nstatic bool OutputImage(CFStringRef outputFileName, const OutputFormat&amp; outputFormat, uint32_t width, uint32_t height, size_t bytesPerRow, size_t sizeBytes, const void* imageData)\n{\n\tbool success = false;\n\tconst std::string outputFileNameAsString = CFStringToUTF8String(outputFileName);\n\n\tCFURLRef file = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, outputFileName, kCFURLPOSIXPathStyle, false);\n\tif (file != nullptr)\n\t{\n\t\tconst size_t bitsPerComponent\t= 8;\n\t\tconst size_t bitsPerPixel\t\t= 32;\n\n\t\tCGColorSpaceRef space\t\t\t= CGColorSpaceCreateWithName(kCGColorSpaceSRGB);\n\t\tCGBitmapInfo bitmapInfo\t\t\t= kCGImageAlphaNoneSkipLast | kCGImageByteOrderDefault;\n\t\tCGDataProviderRef provider\t\t= CGDataProviderCreateWithData(nullptr, imageData, sizeBytes, nullptr);\n\t\tconst CGFloat* decode\t\t\t= nullptr;\n\t\tbool shouldInterpolate\t\t\t= false;\n\t\tCGColorRenderingIntent intent\t= kCGRenderingIntentDefault;\n\n\t\tif (space != nullptr &amp;&amp; provider != nullptr)\n\t\t{\n\t\t\tCGImageRef imageRef = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, space, bitmapInfo, provider, decode, shouldInterpolate, intent);\n\t\t\tif (imageRef != nullptr)\n\t\t\t{\n\t\t\t\tCGImageDestinationRef destination = CGImageDestinationCreateWithURL(file, outputFormat.imageType, 1, nullptr);\n\t\t\t\tif (destination)\n\t\t\t\t{\n\t\t\t\t\tCFDictionaryRef imageProperties = nullptr;\n\t\t\t\t\tCFNumberRef compressionQualityNumber = nullptr;\n\n\t\t\t\t\tif (outputFormat.isLossy)\n\t\t\t\t\t{\n\t\t\t\t\t\tconst float compressionQuality = 0.92f;\n\t\t\t\t\t\tcompressionQualityNumber = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &amp;compressionQuality);\n\t\t\t\t\t\tif (compressionQualityNumber != nullptr)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tconst void* propertyKeys&#91;] = { kCGImageDestinationLossyCompressionQuality };\n\t\t\t\t\t\t\tconst void* propertyValues&#91;] = { compressionQualityNumber };\n\t\t\t\t\t\t\timageProperties = CFDictionaryCreate(kCFAllocatorDefault, propertyKeys, propertyValues, 1, &amp;kCFTypeDictionaryKeyCallBacks, &amp;kCFTypeDictionaryValueCallBacks);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tCGImageDestinationAddImage(destination, imageRef, imageProperties);\n\t\t\t\t\tsuccess = CGImageDestinationFinalize(destination);\n\n\t\t\t\t\tif (imageProperties != nullptr)\n\t\t\t\t\t\tCFRelease(imageProperties);\n\n\t\t\t\t\tif (compressionQualityNumber != nullptr)\n\t\t\t\t\t\tCFRelease(compressionQualityNumber);\n\n\t\t\t\t\tCFRelease(destination);\n\n\t\t\t\t\tif (success)\n\t\t\t\t\t\tstd::cout &lt;&lt; \"Created \" &lt;&lt; outputFileNameAsString &lt;&lt; std::endl;\n\t\t\t\t}\n\n\t\t\t\tCGImageRelease(imageRef);\n\t\t\t}\n\t\t}\n\n\t\tif (provider != nullptr)\n\t\t\tCGDataProviderRelease(provider);\n\n\t\tif (space != nullptr)\n\t\t\tCGColorSpaceRelease(space);\n\n\t\tCFRelease(file);\n\t}\n\n\tif (! success)\n\t\tstd::cerr &lt;&lt; \"Failed to create \" &lt;&lt; outputFileNameAsString &lt;&lt; \"!\" &lt;&lt; std::endl;\n\n\treturn success;\n}\n\nstatic bool WriteCapturedOutputs(const ExtractionState&amp; extractionState)\n{\n\tif (extractionState.outputFormat == nullptr)\n\t{\n\t\tstd::cerr &lt;&lt; \"Failed to resolve output format!\" &lt;&lt; std::endl;\n\t\treturn false;\n\t}\n\n\tif (extractionState.hasError)\n\t{\n\t\tif (! extractionState.errorMessage.empty())\n\t\t\tstd::cerr &lt;&lt; extractionState.errorMessage &lt;&lt; std::endl;\n\t\treturn false;\n\t}\n\n\tstd::string validationError;\n\tif (! ValidateEyeImage(extractionState.leftEyeImage, \"left\", &amp;validationError) || ! ValidateEyeImage(extractionState.rightEyeImage, \"right\", &amp;validationError))\n\t{\n\t\tstd::cerr &lt;&lt; validationError &lt;&lt; std::endl;\n\t\treturn false;\n\t}\n\n\tconst EyeImageData&amp; leftEyeImage = extractionState.leftEyeImage;\n\tconst EyeImageData&amp; rightEyeImage = extractionState.rightEyeImage;\n\tif (leftEyeImage.width != rightEyeImage.width || leftEyeImage.height != rightEyeImage.height)\n\t{\n\t\tstd::cerr &lt;&lt; \"Left and right eye images have different dimensions.\" &lt;&lt; std::endl;\n\t\treturn false;\n\t}\n\n\tif (extractionState.layoutMode == LayoutMode::SeparateEyes)\n\t{\n\t\tif (extractionState.leftOutputFileName == nullptr || extractionState.rightOutputFileName == nullptr)\n\t\t{\n\t\t\tstd::cerr &lt;&lt; \"Failed to resolve separate-eye output file names!\" &lt;&lt; std::endl;\n\t\t\treturn false;\n\t\t}\n\n\t\tconst bool leftSuccess = OutputImage(extractionState.leftOutputFileName, *extractionState.outputFormat, leftEyeImage.width, leftEyeImage.height, leftEyeImage.rowBytes, leftEyeImage.imageData.size(), leftEyeImage.imageData.data());\n\t\tconst bool rightSuccess = OutputImage(extractionState.rightOutputFileName, *extractionState.outputFormat, rightEyeImage.width, rightEyeImage.height, rightEyeImage.rowBytes, rightEyeImage.imageData.size(), rightEyeImage.imageData.data());\n\t\treturn leftSuccess &amp;&amp; rightSuccess;\n\t}\n\n\tif (extractionState.sideBySideOutputFileName == nullptr)\n\t{\n\t\tstd::cerr &lt;&lt; \"Failed to resolve the side-by-side output file name!\" &lt;&lt; std::endl;\n\t\treturn false;\n\t}\n\n\tif (rightEyeImage.width > (std::numeric_limits&lt;uint32_t>::max() - leftEyeImage.width))\n\t{\n\t\tstd::cerr &lt;&lt; \"Side-by-side output width exceeds supported image dimensions.\" &lt;&lt; std::endl;\n\t\treturn false;\n\t}\n\n\tconst uint32_t combinedWidth = leftEyeImage.width + rightEyeImage.width;\n\tconst uint32_t combinedHeight = leftEyeImage.height;\n\tif (IsJPEGOutputFormat(*extractionState.outputFormat) &amp;&amp; (combinedWidth > 65535U || combinedHeight > 65535U))\n\t{\n\t\tstd::cerr &lt;&lt; \"Side-by-side JPEG dimensions exceed the 65535-pixel limit per axis.\" &lt;&lt; std::endl;\n\t\treturn false;\n\t}\n\n\tsize_t combinedSizeBytes = 0;\n\tif (! ComputeRGBAImageSizeBytes(combinedWidth, combinedHeight, &amp;combinedSizeBytes))\n\t{\n\t\tstd::cerr &lt;&lt; \"Side-by-side output size exceeds supported memory limits.\" &lt;&lt; std::endl;\n\t\treturn false;\n\t}\n\n\tstd::vector&lt;uint8_t> combinedImage(combinedSizeBytes);\n\tsize_t eyePixelBytesPerRow = 0;\n\tsize_t combinedBytesPerRow = 0;\n\tif (! ComputeRGBABytesPerRow(leftEyeImage.width, &amp;eyePixelBytesPerRow) || ! ComputeRGBABytesPerRow(combinedWidth, &amp;combinedBytesPerRow))\n\t{\n\t\tstd::cerr &lt;&lt; \"Side-by-side output row stride exceeds supported memory limits.\" &lt;&lt; std::endl;\n\t\treturn false;\n\t}\n\n\tfor (uint32_t row = 0; row &lt; combinedHeight; ++row)\n\t{\n\t\tuint8_t* combinedRow = combinedImage.data() + (static_cast&lt;size_t>(row) * combinedBytesPerRow);\n\t\tconst uint8_t* leftRow = leftEyeImage.imageData.data() + (static_cast&lt;size_t>(row) * leftEyeImage.rowBytes);\n\t\tconst uint8_t* rightRow = rightEyeImage.imageData.data() + (static_cast&lt;size_t>(row) * rightEyeImage.rowBytes);\n\t\tstd::memcpy(combinedRow, leftRow, eyePixelBytesPerRow);\n\t\tstd::memcpy(combinedRow + eyePixelBytesPerRow, rightRow, eyePixelBytesPerRow);\n\t}\n\n\treturn OutputImage(extractionState.sideBySideOutputFileName, *extractionState.outputFormat, combinedWidth, combinedHeight, combinedBytesPerRow, combinedImage.size(), combinedImage.data());\n}\n\nclass CameraCodecCallback : public IBlackmagicRawCallback\n{\npublic:\n\texplicit CameraCodecCallback() = default;\n\tvirtual ~CameraCodecCallback() = default;\n\n\tvirtual void ReadComplete(IBlackmagicRawJob* readJob, HRESULT result, IBlackmagicRawFrame* frame)\n\t{\n\t\tEyeJobData* eyeJobData = GetEyeJobData(readJob);\n\t\tIBlackmagicRawJob* decodeAndProcessJob = nullptr;\n\n\t\tif (result == S_OK)\n\t\t\tresult = (frame != nullptr) ? frame->SetResourceFormat(s_resourceFormat) : E_FAIL;\n\n\t\tif (result == S_OK)\n\t\t\tresult = frame->CreateJobDecodeAndProcessFrame(nullptr, nullptr, &amp;decodeAndProcessJob);\n\n\t\tif (result == S_OK)\n\t\t{\n\t\t\tif (eyeJobData != nullptr)\n\t\t\t\tresult = decodeAndProcessJob->SetUserData(eyeJobData);\n\t\t\telse\n\t\t\t\tresult = E_FAIL;\n\t\t}\n\n\t\tif (result == S_OK)\n\t\t\tresult = decodeAndProcessJob->Submit();\n\n\t\tif (result != S_OK)\n\t\t{\n\t\t\tif (decodeAndProcessJob)\n\t\t\t\tdecodeAndProcessJob->Release();\n\n\t\t\tSetExtractionError(eyeJobData != nullptr ? eyeJobData->extractionState : nullptr, CreateEyeErrorMessage(\"Failed to decode and process\", eyeJobData));\n\t\t}\n\n\t\treadJob->Release();\n\t}\n\n\tvirtual void ProcessComplete(IBlackmagicRawJob* job, HRESULT result, IBlackmagicRawProcessedImage* processedImage)\n\t{\n\t\tEyeJobData* eyeJobData = GetEyeJobData(job);\n\t\tunsigned int width = 0;\n\t\tunsigned int height = 0;\n\t\tunsigned int sizeBytes = 0;\n\t\tvoid* imageData = nullptr;\n\n\t\tif (result == S_OK)\n\t\t\tresult = (processedImage != nullptr) ? processedImage->GetWidth(&amp;width) : E_FAIL;\n\n\t\tif (result == S_OK)\n\t\t\tresult = processedImage->GetHeight(&amp;height);\n\n\t\tif (result == S_OK)\n\t\t\tresult = processedImage->GetResourceSizeBytes(&amp;sizeBytes);\n\n\t\tif (result == S_OK)\n\t\t\tresult = processedImage->GetResource(&amp;imageData);\n\n\t\tif (result == S_OK &amp;&amp; eyeJobData != nullptr)\n\t\t{\n\t\t\tif (! CaptureEyeImage(eyeJobData->extractionState, eyeJobData->eye, width, height, sizeBytes, imageData))\n\t\t\t\tresult = E_FAIL;\n\t\t}\n\t\telse if (result == S_OK)\n\t\t\tresult = E_FAIL;\n\n\t\tif (result != S_OK)\n\t\t\tSetExtractionError(eyeJobData != nullptr ? eyeJobData->extractionState : nullptr, CreateEyeCaptureFailureMessage(eyeJobData, width, height, sizeBytes));\n\n\t\tjob->Release();\n\t}\n\n\tvirtual void DecodeComplete(IBlackmagicRawJob*, HRESULT) {}\n\tvirtual void TrimProgress(IBlackmagicRawJob*, float) {}\n\tvirtual void TrimComplete(IBlackmagicRawJob*, HRESULT) {}\n\tvirtual void SidecarMetadataParseWarning(IBlackmagicRawClip*, CFStringRef, uint32_t, CFStringRef) {}\n\tvirtual void SidecarMetadataParseError(IBlackmagicRawClip*, CFStringRef, uint32_t, CFStringRef) {}\n\tvirtual void PreparePipelineComplete(void*, HRESULT) {}\n\n\tvirtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID*)\n\t{\n\t\treturn E_NOTIMPL;\n\t}\n\n\tvirtual ULONG STDMETHODCALLTYPE AddRef(void)\n\t{\n\t\treturn 0;\n\t}\n\n\tvirtual ULONG STDMETHODCALLTYPE Release(void)\n\t{\n\t\treturn 0;\n\t}\n};\n\nint main(int argc, const char* argv&#91;])\n{\n\tCommandLineOptions options = {};\n\tstd::string argumentError;\n\tconst ParseArgumentsResult parseArgumentsResult = ParseArguments(argc, argv, &amp;options, &amp;argumentError);\n\tif (parseArgumentsResult == ParseArgumentsResult::Help)\n\t{\n\t\tPrintUsage(std::cout, argv&#91;0]);\n\t\treturn 0;\n\t}\n\n\tif (parseArgumentsResult != ParseArgumentsResult::Success)\n\t{\n\t\tif (! argumentError.empty())\n\t\t\tstd::cerr &lt;&lt; argumentError &lt;&lt; std::endl;\n\t\tPrintUsage(std::cerr, argv&#91;0]);\n\t\treturn 1;\n\t}\n\n\tCFStringRef clipName = CFStringCreateWithCString(NULL, options.clipPath, kCFStringEncodingUTF8);\n\tif (clipName == nullptr)\n\t{\n\t\tstd::cerr &lt;&lt; \"Failed to create clip path.\" &lt;&lt; std::endl;\n\t\treturn 1;\n\t}\n\n\tHRESULT result = S_OK;\n\n\tIBlackmagicRawFactory* factory = nullptr;\n\tIBlackmagicRaw* codec = nullptr;\n\tIBlackmagicRawClip* clip = nullptr;\n\tIBlackmagicRawClipImmersiveVideo* immersiveClip = nullptr;\n\tIBlackmagicRawJob* leftReadJob = nullptr;\n\tIBlackmagicRawJob* rightReadJob = nullptr;\n\tCFStringRef sideBySideOutputFileName = nullptr;\n\tCFStringRef leftOutputFileName = nullptr;\n\tCFStringRef rightOutputFileName = nullptr;\n\n\tExtractionState extractionState = {};\n\textractionState.layoutMode = options.layoutMode;\n\textractionState.outputFormat = options.outputFormat;\n\n\tEyeJobData leftEyeJobData = { EyeIndex::Left, &amp;extractionState };\n\tEyeJobData rightEyeJobData = { EyeIndex::Right, &amp;extractionState };\n\n\tCameraCodecCallback callback;\n\tbool hasSubmittedJobs = false;\n\n\tdo\n\t{\n\t\tfactory = CreateBlackmagicRawFactoryInstanceFromPath(s_blackmagicRawLibrariesPath);\n\t\tif (factory == nullptr)\n\t\t{\n\t\t\tstd::cerr &lt;&lt; \"Failed to create IBlackmagicRawFactory!\" &lt;&lt; std::endl;\n\t\t\tbreak;\n\t\t}\n\n\t\tresult = factory->CreateCodec(&amp;codec);\n\t\tif (result != S_OK)\n\t\t{\n\t\t\tstd::cerr &lt;&lt; \"Failed to create IBlackmagicRaw!\" &lt;&lt; std::endl;\n\t\t\tbreak;\n\t\t}\n\n\t\t\tresult = codec->OpenClip(clipName, &amp;clip);\n\t\t\tif (result != S_OK)\n\t\t\t{\n\t\t\t\tstd::cerr &lt;&lt; \"Failed to open IBlackmagicRawClip!\" &lt;&lt; std::endl;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (options.showInfo)\n\t\t\t{\n\t\t\t\tPrintClipInfo(options.clipPath, clip);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tuint64_t frameCount = 0;\n\t\t\tresult = clip->GetFrameCount(&amp;frameCount);\n\t\t\tif (result != S_OK)\n\t\t\t{\n\t\t\t\tstd::cerr &lt;&lt; \"Failed to query frame count!\" &lt;&lt; std::endl;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (frameCount == 0)\n\t\t\t{\n\t\t\t\tstd::cerr &lt;&lt; \"Clip contains no frames.\" &lt;&lt; std::endl;\n\t\t\t\tresult = E_FAIL;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (options.frameIndex >= frameCount)\n\t\t\t{\n\t\t\t\tstd::cerr &lt;&lt; \"Requested frame index \" &lt;&lt; options.frameIndex &lt;&lt; \" is out of range. Valid range: 0-\" &lt;&lt; (frameCount - 1) &lt;&lt; \".\" &lt;&lt; std::endl;\n\t\t\t\tresult = E_INVALIDARG;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tresult = codec->SetCallback(&amp;callback);\n\t\t\tif (result != S_OK)\n\t\t\t{\n\t\t\t\tstd::cerr &lt;&lt; \"Failed to set IBlackmagicRawCallback!\" &lt;&lt; std::endl;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tresult = clip->QueryInterface(IID_IBlackmagicRawClipImmersiveVideo, reinterpret_cast&lt;void**>(&amp;immersiveClip));\n\t\t\tif (result != S_OK || immersiveClip == nullptr)\n\t\t\t{\n\t\t\t\tstd::cerr &lt;&lt; \"Clip does not expose IBlackmagicRawClipImmersiveVideo!\" &lt;&lt; std::endl;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tconst std::string timeDateString = CreateTimeDateString();\n\n\t\t\tif (options.layoutMode == LayoutMode::SideBySide)\n\t\t\t{\n\t\t\t\tsideBySideOutputFileName = CreateOutputFileName(options.clipPath, \"SBS\", options.frameIndex, timeDateString, *options.outputFormat);\n\t\t\t\tif (sideBySideOutputFileName == nullptr)\n\t\t\t\t{\n\t\t\t\t\tstd::cerr &lt;&lt; \"Failed to create side-by-side output file path.\" &lt;&lt; std::endl;\n\t\t\t\t\tresult = E_FAIL;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\textractionState.sideBySideOutputFileName = sideBySideOutputFileName;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tleftOutputFileName = CreateOutputFileName(options.clipPath, GetEyeOutputName(EyeIndex::Left), options.frameIndex, timeDateString, *options.outputFormat);\n\t\t\t\tif (leftOutputFileName == nullptr)\n\t\t\t\t{\n\t\t\t\t\tstd::cerr &lt;&lt; \"Failed to create left output file path.\" &lt;&lt; std::endl;\n\t\t\t\t\tresult = E_FAIL;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\trightOutputFileName = CreateOutputFileName(options.clipPath, GetEyeOutputName(EyeIndex::Right), options.frameIndex, timeDateString, *options.outputFormat);\n\t\t\t\tif (rightOutputFileName == nullptr)\n\t\t\t\t{\n\t\t\t\t\tstd::cerr &lt;&lt; \"Failed to create right output file path.\" &lt;&lt; std::endl;\n\t\t\t\t\tresult = E_FAIL;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\textractionState.leftOutputFileName = leftOutputFileName;\n\t\t\t\textractionState.rightOutputFileName = rightOutputFileName;\n\t\t\t}\n\n\t\t\tresult = immersiveClip->CreateJobImmersiveReadFrame(blackmagicRawImmersiveVideoTrackLeft, options.frameIndex, &amp;leftReadJob);\n\t\t\tif (result != S_OK)\n\t\t\t{\n\t\t\t\tstd::cerr &lt;&lt; \"Failed to create left immersive read job!\" &lt;&lt; std::endl;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\tresult = leftReadJob->SetUserData(&amp;leftEyeJobData);\n\t\tif (result != S_OK)\n\t\t{\n\t\t\tstd::cerr &lt;&lt; \"Failed to attach left eye job data!\" &lt;&lt; std::endl;\n\t\t\tbreak;\n\t\t}\n\n\t\tresult = leftReadJob->Submit();\n\t\t\tif (result != S_OK)\n\t\t\t{\n\t\t\t\tstd::cerr &lt;&lt; \"Failed to submit left immersive read job!\" &lt;&lt; std::endl;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tleftReadJob = nullptr;\n\t\t\thasSubmittedJobs = true;\n\n\t\t\tresult = immersiveClip->CreateJobImmersiveReadFrame(blackmagicRawImmersiveVideoTrackRight, options.frameIndex, &amp;rightReadJob);\n\t\t\tif (result != S_OK)\n\t\t\t{\n\t\t\t\tstd::cerr &lt;&lt; \"Failed to create right immersive read job!\" &lt;&lt; std::endl;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\tresult = rightReadJob->SetUserData(&amp;rightEyeJobData);\n\t\tif (result != S_OK)\n\t\t{\n\t\t\tstd::cerr &lt;&lt; \"Failed to attach right eye job data!\" &lt;&lt; std::endl;\n\t\t\tbreak;\n\t\t}\n\n\t\tresult = rightReadJob->Submit();\n\t\tif (result != S_OK)\n\t\t{\n\t\t\tstd::cerr &lt;&lt; \"Failed to submit right immersive read job!\" &lt;&lt; std::endl;\n\t\t\tbreak;\n\t\t}\n\t\trightReadJob = nullptr;\n\n\t\tcodec->FlushJobs();\n\t\thasSubmittedJobs = false;\n\n\t\tif (! WriteCapturedOutputs(extractionState))\n\t\t{\n\t\t\tresult = E_FAIL;\n\t\t\tbreak;\n\t\t}\n\n\t} while(0);\n\n\tif (hasSubmittedJobs &amp;&amp; codec != nullptr)\n\t\tcodec->FlushJobs();\n\n\tif (leftReadJob != nullptr)\n\t\tleftReadJob->Release();\n\n\tif (rightReadJob != nullptr)\n\t\trightReadJob->Release();\n\n\tif (immersiveClip != nullptr)\n\t\timmersiveClip->Release();\n\n\tif (clip != nullptr)\n\t\tclip->Release();\n\n\tif (codec != nullptr)\n\t\tcodec->Release();\n\n\tif (factory != nullptr)\n\t\tfactory->Release();\n\n\tif (sideBySideOutputFileName != nullptr)\n\t\tCFRelease(sideBySideOutputFileName);\n\n\tif (rightOutputFileName != nullptr)\n\t\tCFRelease(rightOutputFileName);\n\n\tif (leftOutputFileName != nullptr)\n\t\tCFRelease(leftOutputFileName);\n\n\tif (clipName != nullptr)\n\t\tCFRelease(clipName);\n\n\treturn result;\n}\n\n\/* -LICENSE-START-\n ** Copyright (c) 2018 Blackmagic Design\n **\n ** Permission is hereby granted, free of charge, to any person or organization\n ** obtaining a copy of the software and accompanying documentation covered by\n ** this license (the \"Software\") to use, reproduce, display, distribute,\n ** execute, and transmit the Software, and to prepare derivative works of the\n ** Software, and to permit third-parties to whom the Software is furnished to\n ** do so, all subject to the following:\n **\n ** The copyright notices in the Software and this entire statement, including\n ** the above license grant, this restriction and the following disclaimer,\n ** must be included in all copies of the Software, in whole or in part, and\n ** all derivative works of the Software, unless such copies or derivative\n ** works are solely in the form of machine-executable object code generated by\n ** a source language processor.\n **\n ** THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n ** FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\n ** SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\n ** FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\n ** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n ** DEALINGS IN THE SOFTWARE.\n ** -LICENSE-END-\n *\/\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>I was wondering why a big chunk of the URSA manual was just APIs and now I think I&#8217;m understanding. https:\/\/www.blackmagicdesign.com\/developer\/products\/braw\/sdk-and-software Installed for Mac OS. Went to &#8220;\/Applications\/Blackmagic RAW\/Blackmagic RAW SDK\/Mac&#8221; and then took the &#8220;ExtractFrame.cpp&#8221; Xcodeproject and sprinkle some codex and boom now a command to extract frames from blackmagic raw into sbs And [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-242","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/www.visitnyc.store\/blog\/wp-json\/wp\/v2\/posts\/242","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.visitnyc.store\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.visitnyc.store\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.visitnyc.store\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.visitnyc.store\/blog\/wp-json\/wp\/v2\/comments?post=242"}],"version-history":[{"count":7,"href":"https:\/\/www.visitnyc.store\/blog\/wp-json\/wp\/v2\/posts\/242\/revisions"}],"predecessor-version":[{"id":251,"href":"https:\/\/www.visitnyc.store\/blog\/wp-json\/wp\/v2\/posts\/242\/revisions\/251"}],"wp:attachment":[{"href":"https:\/\/www.visitnyc.store\/blog\/wp-json\/wp\/v2\/media?parent=242"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.visitnyc.store\/blog\/wp-json\/wp\/v2\/categories?post=242"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.visitnyc.store\/blog\/wp-json\/wp\/v2\/tags?post=242"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}