Direct Fuse Media I/O with GetFrame and PutFrame

User avatar
AndrewHazelden
Fusionator
Posts: 1261
Joined: Fri Apr 03, 2015 3:20 pm
Location: West Dover, Nova Scotia, Canada
Been thanked: 28 times
Contact:

Direct Fuse Media I/O with GetFrame and PutFrame

#1

Post by AndrewHazelden » Wed May 15, 2019 3:58 pm

For the Fusion scripting TDs out there, here are two sample fuses that can be used to explore workflows for direct fuse based media I/O using the Fuse API's clip:GetFrame() and clip:PutFrame() functions.

These sample fuses allow you to read and write out jpg/exr/png/bmp/raw/fusepic images. This workflow supports a wider range of image formats and is much simpler to master then the more complex approach of creating an EXRIO based fuse like the LifeSaver node.

An example use case for needing both the GetFrame and PutFrame functions inside a single fuse node would be to run media from your compositing timeline on the fly through an external command line graphics tool (like Torch, Imagemagick, OpenCV, potrace, etc...) and then to be able to load the content automatically back into a Fusion comp node flow. All of the Fusion related code would work inside a single self-contained fuse node and be transparent to the end user.

Note: Fusion 16 and Resolve 16 are required to use the clip:PutFrame() function since the clip() open and close commands were inoperable in earlier Fusion versions.

Edit: Added REG_Unpredictable = true, to the fuses based upon PeterLoveday's suggestions.

GetFrame Fuse

GetFrame.png

A Fusion 9+/Resolve 15+ compatible fuse example that loads an image from disk using GetFrame(). The Filename control supports the use of PathMaps. The "Static Frame" control allows you to load either a (static) single image, or an image sequence.

Code: [Select all] [Expand/Collapse] [Download] (GetFrame.fuse)
  1. --[[--
  2. GetFrame - v1 2019-05-15
  3. By Andrew Hazelden <andrew@andrewhazelden.com>
  4.  
  5. A Fusion 9+/Resolve 15+ compatible fuse example that loads an image from disk using GetFrame().
  6.  
  7. The Filename control supports the use of PathMaps.
  8.  
  9. The "Static Frame" control allows you to load either a (static) single image, or an image sequence.
  10.  
  11. --]]--
  12.  
  13. FuRegisterClass('GetFrame', CT_Tool, {
  14.     REGS_Name = 'GetFrame',
  15.     REGS_Category = 'I/O',
  16.     REGS_OpIconString = 'Get',
  17.     REGS_OpDescription = 'Loads a jpg/png/bmp/exr/fusepic/raw/tga/tiff/mp4/mov format image.',
  18.     REGS_Company = 'Andrew Hazelden',
  19.     REGS_URL = 'http://www.andrewhazelden.com',
  20.     REGS_HelpTopic = 'http://www.andrewhazelden.com',
  21.    
  22. -- Should the current time setting be cached?
  23. REG_TimeVariant = true,
  24. REG_Unpredictable = true,
  25.  
  26. REG_OpNoMask = true,
  27. REG_NoBlendCtrls = true,
  28. REG_NoObjMatCtrls = true,
  29. REG_NoMotionBlurCtrls = true,
  30. REG_SupportsDoD = true,
  31.  
  32. -- Should the Edit and Reload buttons be hidden?
  33. REG_Fuse_NoEdit = false,
  34. REG_Fuse_NoReload = false,
  35.  
  36. -- Version number where 110 = v1.1
  37. REG_Version = 110,
  38. })
  39.  
  40. function Create()
  41.     -- Remove the default "Controls" page from the Fuse GUI
  42.     self:RemoveControlPage('Controls')
  43.  
  44. -- Add a new "File" page to the Fuse GUI
  45. self:AddControlPage('File')
  46.  
  47. InFilename = self:AddInput("Filename", "Filename", {
  48.     LINKID_DataType = "Text",
  49.     INPID_InputControl = "FileControl",
  50.     FC_IsSaver = false,
  51.     FC_ClipBrowse = true,
  52.     ICS_ControlPage = "File",
  53.     })
  54.  
  55. InStaticFrame = self:AddInput("Static Frame", "StaticFrame", {
  56.     LINKID_DataType = "Number",
  57.     INPID_InputControl = "CheckboxControl",
  58.     INP_DoNotifyChanged = true,
  59.     INP_Integer = true,
  60.     INP_Default = 1.0,
  61.     ICD_Width = 1,
  62.     ICS_ControlPage = 'File',
  63.     })
  64.  
  65. -- The output node connection where imagery is pushed out of the fuse
  66. OutImage = self:AddOutput('Output', 'Output', {
  67.     LINKID_DataType = 'Image',
  68.     LINK_Main = 1,
  69.     })
  70. end
  71.  
  72. function NotifyChanged(inp, param, time)
  73. end
  74.  
  75. -- The OnAddToFlow() function is automatically run when the a new fuse node is added to the comp, or when the .comp file is opened up.
  76. function OnAddToFlow()
  77. end
  78.  
  79. function Process(req)
  80.     -- Read the filename
  81.     local filename = self.Comp:MapPath(InFilename:GetValue(req).Value)
  82.  
  83. -- Read the frame mode
  84. local staticFrame = InStaticFrame:GetValue(req).Value
  85.  
  86. -- Verify the file exists
  87. if bmd.fileexists(filename) == true then
  88.     -- Get the image clip
  89.     local clip = Clip(filename)
  90.  
  91.     -- Is this a static frame or an image sequence
  92.     if staticFrame == 1 then
  93.         -- Read the first frame of the image sequence
  94.         out = clip:GetFrame(0)
  95.     else
  96.         -- Read the image from disk
  97.         out = clip:GetFrame(req.Time)
  98.  
  99.         -- Read the first frame of a movie
  100.         -- out = clip:GetFrame(self.Comp.CurrentTime - self.Comp.GlobalStart)
  101.     end
  102.  
  103.     -- print('[GetFrame Filename]', clip.Filename)
  104.  
  105.     -- Push the result to the node's output connection
  106.     OutImage:Set(req, out)
  107. else
  108.     -- Fallback to a blank canvas when no image is found
  109.     local compWidth = self.Comp:GetPrefs('Comp.FrameFormat.Width') or 1920
  110.     local compHeight = self.Comp:GetPrefs('Comp.FrameFormat.Height') or 1080
  111.    
  112.     emptyImage = Image({
  113.         IMG_Width = compWidth,
  114.         IMG_Height = compHeight,
  115.         })
  116.    
  117.     -- Pixel defaults to black/clear
  118.     emptyImage:Fill(Pixel())
  119.    
  120.     -- Push the empty result to the node's output connection
  121.     OutImage:Set(req, emptyImage)
  122.     print('[GetFrame Filename] Missing Image: ' .. tostring(filename))
  123. end
  124. end
  125.  

PutFrame Fuse

PutFrame.png

A Fusion 16+/Resolve 16+ compatible fuse example that saves an image to disk using PutFrame(). The Filename control supports the use of PathMaps.

Code: [Select all] [Expand/Collapse] [Download] (PutFrame.fuse)
  1. --[[
  2. PutFrame - v1.0 2019-05-15
  3. By Andrew Hazelden <andrew@andrewhazelden.com>
  4.  
  5. A Fusion 16+/Resolve 16+ compatible fuse example that saves an image to disk using PutFrame().
  6.  
  7. The Filename control supports the use of PathMaps.
  8.  
  9. --]]--
  10.  
  11. FuRegisterClass("PutFrame", CT_Tool, {
  12.     REGS_Name = "PutFrame",
  13.     REGS_Category = "I/O",
  14.     REGS_OpIconString = "Put",
  15.     REGS_OpDescription = "Save a jpg/exr/png/bmp/raw/fusepic image to disk using Clip:PutFrame().",
  16.     REGS_Company = 'Andrew Hazelden',
  17.     REGS_URL = 'http://www.andrewhazelden.com',
  18.     REG_SupportsDoD = true,
  19.     REG_OpNoMask = true,
  20.     REG_NoMotionBlurCtrls = true,
  21.  
  22. -- Should the current time setting be cached?
  23. REG_TimeVariant = true,
  24. REG_Unpredictable = true,
  25.  
  26. -- Should the Edit and Reload buttons be hidden?
  27. REG_Fuse_NoEdit = false,
  28. REG_Fuse_NoReload = false,
  29.  
  30. -- Sets the fuse version number (110 means v1.1) so newer fuses override older versions
  31. REG_Version = 110,
  32. })
  33.  
  34. function Create()
  35.     self:RemoveControlPage("Controls")
  36.     self:AddControlPage("File")
  37.    
  38. InFilename = self:AddInput("Filename", "Filename", {
  39.     LINKID_DataType = "Text",
  40.     INPID_InputControl = "FileControl",
  41.     FC_IsSaver = true,
  42.     FC_ClipBrowse = true,
  43.     ICS_ControlPage = "File",
  44.     })
  45. InImage = self:AddInput("Input", "Input", {
  46.     LINKID_DataType = "Image",
  47.     LINK_Main = 1,
  48. })
  49. OutImage = self:AddOutput("Output", "Output", {
  50.     LINKID_DataType = "Image",
  51.     LINK_Main = 1,
  52.     })
  53. end
  54.  
  55. function NotifyChanged(inp, param, time)
  56. end
  57.  
  58. -- The OnAddToFlow() function is automatically run when the a new fuse node is added to the comp, or when the .comp file is opened up.
  59. function OnAddToFlow()
  60. end
  61.  
  62.  
  63. function Process(req)
  64.     -- Grab an image from the input connection on the node
  65.     local img = InImage:GetValue(req)
  66.    
  67. -- Crop (with no offset, ie. Copy) handles images having no data, so we don't need to put this within if/then/end
  68. local result = Image({IMG_Like = img, IMG_NoData = req:IsPreCalc()})
  69. img:Crop(result, { })
  70.  
  71. -- Read the filename
  72. local filename = self.Comp:MapPath(InFilename:GetValue(req).Value)
  73.  
  74. -- Print a warning message when the filename field is empty
  75. if filename == nil or filename == '' then
  76.     print('\n[PutFrame Error] The "Filename" field is empty!\n\n')
  77. else
  78.     -- Get the 'final' frame number that will be written into the filename
  79.     local currentFrame = tonumber(req.Time)
  80.  
  81.     -- Create a new clip with a filename
  82.     local saveBool = true
  83.     local clip = Clip(filename, saveBool)
  84.    
  85.     -- Write an image to disk
  86.     clip:Open()
  87.     local putResult = clip:PutFrame(currentFrame, result)
  88.     clip:Close()
  89.    
  90.     -- print('[PutFrame Filename]', clip.Filename)
  91.     -- print('[PutFrame Result]', putResult)
  92. end
  93.  
  94. -- Pass the result image to the output connection
  95. OutImage:Set(req, result)
  96. end
  97.  
You do not have the required permissions to view the files attached to this post.
Last edited by AndrewHazelden on Thu May 16, 2019 5:40 am, edited 1 time in total.

User avatar
AndrewHazelden
Fusionator
Posts: 1261
Joined: Fri Apr 03, 2015 3:20 pm
Location: West Dover, Nova Scotia, Canada
Been thanked: 28 times
Contact:

Re: Direct Fuse Media I/O with GetFrame and PutFrame

#2

Post by AndrewHazelden » Wed May 15, 2019 4:03 pm

Hi @JPDoc.

I wanted to mention that the GetFrame Fuse's "Static Frame" option would give you a solution for your 2015 era WSL wishlist request in the "any way to override frame numbering in loader?" thread.

Also, if someone had the time and wanted to do some bmd.parseFilename() function based scripting work inside the PutFrame fuse code, you could easily take care of your 2016 era "[SV] I/O single frames without having frame numbers added" wishlist request. :)

Cheers. :cheers:


User avatar
intelligent machine
Fusionista
Posts: 399
Joined: Fri May 13, 2016 10:01 pm
Answers: 2
Location: Austin, Texas, USA
Been thanked: 28 times
Contact:

Re: Direct Fuse Media I/O with GetFrame and PutFrame

#3

Post by intelligent machine » Wed May 15, 2019 7:27 pm

Great! Maybe this could be combined into the LifeSaver fuse with a multi-button / dropdown selection of EXR or Everything Else.

BTW. I wanted to report a bug with the LifeSaver fuse (at least in Fu16) There seems to be an off-by-one or order-of-operations bug which isn't completely reproducible unless the LifeSaver node is active in one of the views. Try saving a single frame from the tool and it saves the next frame (console shows a from:to range as the current frame to the next frame but only the next frame is rendered). Try rendering a sequence and random memcached frames (with that tool active in the view) may be skipped.


User avatar
AndrewHazelden
Fusionator
Posts: 1261
Joined: Fri Apr 03, 2015 3:20 pm
Location: West Dover, Nova Scotia, Canada
Been thanked: 28 times
Contact:

Re: Direct Fuse Media I/O with GetFrame and PutFrame

#4

Post by AndrewHazelden » Wed May 15, 2019 7:58 pm

intelligent machine wrote:
Wed May 15, 2019 7:27 pm

BTW. I wanted to report a bug with the LifeSaver fuse (at least in Fu16)

TBH I haven't done any QA testing yet in the Fu 16 betas for the LifeSaver fuse.

Fu 16 is really Resolve backported into Fusion so a lot has changed internally with the timeline, caching, and other functionality in FU as far as where strange things can happen compared to Fu 9.0.2.


JPDoc
Fusioneer
Posts: 181
Joined: Tue Sep 02, 2014 8:26 am
Been thanked: 1 time

Re: Direct Fuse Media I/O with GetFrame and PutFrame

#5

Post by JPDoc » Wed May 15, 2019 11:29 pm

Many thanks, Andrew, I'll look into it - a good excuse to try and improve my woefully inadequate fuse writing skills as well. Still annoying that the in-box loader/saver nodes are so whacky, even after all these years (and complaints), though.

And nice to see you around again, as well. Your contributions are always fascinating, and valuable too!


User avatar
PeterLoveday
Fusioneer
Posts: 143
Joined: Sun Sep 14, 2014 6:09 pm
Answers: 6
Been thanked: 13 times

Re: Direct Fuse Media I/O with GetFrame and PutFrame

#6

Post by PeterLoveday » Thu May 16, 2019 12:24 am

I would suggest making the fuse REG_Unpredictable = true in the FuRegisterClass. It should prevent it caching and perhaps improve the issues with frames being skipped.


User avatar
AndrewHazelden
Fusionator
Posts: 1261
Joined: Fri Apr 03, 2015 3:20 pm
Location: West Dover, Nova Scotia, Canada
Been thanked: 28 times
Contact:

Re: Direct Fuse Media I/O with GetFrame and PutFrame

#7

Post by AndrewHazelden » Thu May 16, 2019 5:56 am

PeterLoveday wrote:
Thu May 16, 2019 12:24 am

I would suggest making the fuse REG_Unpredictable = true in the FuRegisterClass. It should prevent it caching and perhaps improve the issues with frames being skipped.

Thanks! I've just made the changes to the PutFrame and GetFrame fuses on this page, and to the LifeSaver and NetLoader fuses in Reactor. :)