File: //opt/resolve/Developer/Fusion Fuse/Example 8 Meta Time Burn.fuse
--[[--
A Timecode Burn in Fuse
by SR
--]]--
--******************************************************************************************************
-- This Registers the Fuse into Fusion, gives its Name, and Menu location. Location of Help. Can Fuse be edited.
FuRegisterClass("MetaTimeBurn", CT_Tool, {
REGS_Name = "Ex9 Meta Time Burn",
REGS_Category = "Fuses\\Examples",
REGS_OpIconString = "MTB",
REGS_OpDescription = "Example, Metadata Timecode Burning",
REGS_HelpTopic = "Example Location of Help", --This can be a URL
REGS_URL = "www.blackmagicdesign.com",
--REGS_IconID = "Icons.Tools.Icons.Example",-- This can be Inline as an array of Values
REG_OpNoMask = false,
REG_NoBlendCtrls = false,
REG_NoObjMatCtrls = false,
REG_NoMotionBlurCtrls = false,
REG_TimeVariant = true, -- this tool must be reprocessed for every frame
REG_Fuse_NoEdit = false,
REG_Fuse_NoReload = false,
}) -- End of RegisterClass
function Create()
InFps = self:AddInput(tr("FPS"), "FPS", {
LINKID_DataType = "Number",
INPID_InputControl = "MultiButtonControl",
MBTNC_StretchToFit = true,
{ MBTNC_AddButton = "24" },
{ MBTNC_AddButton = "25" },
{ MBTNC_AddButton = "30" },
{ MBTNC_AddButton = "48" },
{ MBTNC_AddButton = "50" },
{ MBTNC_AddButton = "60" },
})
InHours = self:AddInput(tr("Hours"), "TCHours", {
LINKID_DataType = "Number",
INPID_InputControl = "SliderControl",
INP_MaxScale = 23,
INP_Integer = true,
INP_Default = 0,
})
InMinutes = self:AddInput(tr("Minutes"), "TCMinutes", {
LINKID_DataType = "Number",
INPID_InputControl = "SliderControl",
INP_MaxScale = 59,
INP_Integer = true,
INP_Default = 0,
})
InSeconds = self:AddInput(tr("Seconds"), "TCSeconds", {
LINKID_DataType = "Number",
INPID_InputControl = "SliderControl",
INP_MaxScale = 59,
INP_Integer = true,
INP_Default = 0,
})
InFrames = self:AddInput(tr("Frames"), "TCFrames", {
LINKID_DataType = "Number",
INPID_InputControl = "SliderControl",
INP_MaxScale = 60,
INP_Integer = true,
INP_Default = 0,
})
self:BeginControlNest("Text Settings", "TextNest", false); -- Control Nests group controls with a togglable collapse/expand function
InFont = self:AddInput("Font", "Font", {
LINKID_DataType = "Text",
INPID_InputControl = "FontFileControl",
IC_ControlGroup = 2,
IC_ControlID = 0, -- Font name ID
INP_Level = 1,
INP_DoNotifyChanged = true,
})
InFontStyle = self:AddInput("Style", "Style", {
LINKID_DataType = "Text",
INPID_InputControl = "FontFileControl",
IC_ControlGroup = 2,
IC_ControlID = 1, -- Font style ID
INP_Level = 1,
INP_DoNotifyChanged = true,
})
InPosition = self:AddInput("Position", "Position", {
LINKID_DataType = "Point",
INPID_InputControl = "OffsetControl",
INPID_PreviewControl = "CrosshairControl",
INP_DefaultX = 0.1,
INP_DefaultY = 0.1,
})
InSize = self:AddInput("Size", "Size", {
LINKID_DataType = "Number",
INPID_InputControl = "SliderControl",
INP_MinScale = 0.0,
INP_MaxScale = 0.5,
INP_Default = 0.08,
})
InJustify = self:AddInput("Justification", "Justification", {
LINKID_DataType = "Number",
INPID_InputControl = "MultiButtonControl",
INP_Default = 0.0,
{ MBTNC_AddButton = "Left", MBTNCD_ButtonWidth = 1/3, },
{ MBTNC_AddButton = "Center", MBTNCD_ButtonWidth = 1/3, },
{ MBTNC_AddButton = "Right", MBTNCD_ButtonWidth = 1/3, },
INP_Integer = true,
})
InR = self:AddInput("Red", "Red", {
LINKID_DataType = "Number",
INPID_InputControl = "ColorControl",
INP_MinScale = 0.0,
INP_MaxScale = 1.0,
INP_Default = 1.0,
ICS_Name = "Color",
CLRC_ShowWheel = false,
IC_ControlGroup = 1,
IC_ControlID = 0, -- Red ID
})
InG = self:AddInput("Green", "Green", {
LINKID_DataType = "Number",
INPID_InputControl = "ColorControl",
INP_MinScale = 0.0,
INP_MaxScale = 1.0,
INP_Default = 1.0,
IC_ControlGroup = 1,
IC_ControlID = 1, -- Green ID
})
InB = self:AddInput("Blue", "Blue", {
LINKID_DataType = "Number",
INPID_InputControl = "ColorControl",
INP_MinScale = 0.0,
INP_MaxScale = 1.0,
INP_Default = 1.0,
IC_ControlGroup = 1,
IC_ControlID = 2, -- Blue ID
})
InA = self:AddInput("Alpha", "Alpha", {
LINKID_DataType = "Number",
INPID_InputControl = "ColorControl",
INP_MinScale = 0.0,
INP_MaxScale = 1.0,
INP_Default = 1.0,
IC_ControlGroup = 1,
IC_ControlID = 3, -- Alpha ID
})
self:EndControlNest()
InImage = self:AddInput("Input", "Input", {
LINKID_DataType = "Image",
LINK_Main = 1,
})
OutImage = self:AddOutput("Output", "Output", {
LINKID_DataType = "Image",
LINK_Main = 1,
})
end
function drawstring(img, font, style, size, justify, quality, x, y, colour, text)
local ic = ImageChannel(img, quality)
local fs = FillStyle()
local cs = ChannelStyle()
cs.Color = colour
ic:SetStyleFill(fs)
-- get the fonts metrics (see http://freetype.sourceforge.net/freetype2/docs/glyphs/index.html for a great guide)
local font = TextStyleFont(font, style)
local tfm = TextStyleFontMetrics(font)
-- This is the distance between this line and the next one.
local line_height = (tfm.TextAscent + tfm.TextDescent + tfm.TextExternalLeading) * 10 * size
local x_move = 0
local mat = Matrix4()
mat:Scale(1.0/tfm.Scale, 1.0/tfm.Scale, 1.0)
mat:Scale(size, size, 1)
-- set the initial baseline position of the text cursor
local sh, ch, prevch
local shape = Shape()
mat:Move(x, y, 0)
-- split the text into separate lines
for line in string.gmatch(text, "%C+") do
-- First pass, work out what the total width of this line is going to be
local line_width = 0
for i=1,#line do
ch = line:sub(i,i):byte()
-- is this ignoring kerning?
line_width = line_width + tfm:CharacterWidth(ch)*10*size
end
-- Now work out our initial cursor position, based on the justification
-- 0 = left justify,
-- 1 = centered
-- 2 = right justify
if justify == 0 then
--mat:Move(0, 0, 0)
elseif justify == 1 then
mat:Move(-line_width/2, 0, 0)
elseif justify == 2 then
mat:Move(-line_width, 0, 0)
end
-- Second pass, now we assemble the actual shape
for i=1,#line do
prevch = ch
-- get the character, or glyph
ch = line:sub(i,i):byte()
-- first we want to know what the width of the character is,
-- so we know where to start drawing this next character
-- not really sure why we multiply this by 10, we just do :-)
local cw = tfm:CharacterWidth(ch)*10*size
-- if there is a previous character, we need to get the kerning
-- between the current character and the last one.
if prevch then
x_offset = tfm:CharacterKerning(prevch, ch)*10*size
x_move = x_move + x_offset
mat:Move(x_offset, 0, 0)
end
-- move the cursor to the center of the character
mat:Move(cw/2, 0, 0)
-- I think this renders the shape we are interested in
sh = tfm:GetCharacterShape(ch, false)
if sh then
sh = sh:TransformOfShape(mat)
-- move the text cursor to the end of the glyph.
mat:Move(cw/2, 0, 0)
x_move = x_move + cw
shape:AddShape(sh)
end
end
-- line end, move the cursor back to the start
if justify == 0 then
mat:Move(-x_move, -line_height, 0)
elseif justify == 1 then
mat:Move(-x_move/2, -line_height, 0)
elseif justify == 2 then
mat:Move(0, -line_height, 0)
end
x_move = 0
end
ic:ShapeFill(shape)
ic:PutToImage("CM_Merge", cs)
end
function Process(req)
local img = InImage:GetValue(req)
local fps = InFps:GetValue(req).Value
local hours = InHours:GetValue(req).Value
local minutes = InMinutes:GetValue(req).Value
local seconds = InSeconds:GetValue(req).Value
local frames = InFrames:GetValue(req).Value
local currentframe = req.Time
local rates = { 24, 25, 30, 48, 50, 60 }
local fpsx = rates[math.min(math.max(fps, 0), 5)+1]
-- Calculate and format the time code strings
-- Calculate frames from Time code settings
local frames_o = currentframe + frames + fpsx * (seconds + 60*minutes + 3600*hours)
local frames_s = frames_o % fpsx
local seconds_s = math.floor(frames_o / fpsx) % 60
local minutes_s = math.floor(frames_o / (60*fpsx)) % 60
local hours_s = math.floor(frames_o / (3600*fpsx))
local timecode = string.format("%02d:%02d:%02d:%02d", hours_s, minutes_s, seconds_s, frames_s)
local font = InFont:GetValue(req).Value
local style = InFontStyle:GetValue(req).Value
local out = img:CopyOf()
local size = InSize:GetValue(req).Value
local center = InPosition:GetValue(req)
local justify = InJustify:GetValue(req).Value
local r = InR:GetValue(req).Value
local g = InG:GetValue(req).Value
local b = InB:GetValue(req).Value
local a = InA:GetValue(req).Value
local cx = center.X
local cy = center.Y * (out.Height * out.YScale) / (out.Width * out.XScale)
local quality = 32
-- if the FontManager list is empty, scan the font list
-- If the UI has never been shown, as would always be the case on a render node,
-- nothing will scan the font list for available fonts. So we check for that here,
-- and force a scan if needed.
if not next( FontManager:GetFontList() ) then
FontManager:ScanDir()
end
if req:IsQuick() then
quality = 1
end
-- the drawstring function is doing all the heavy lifting
drawstring(out, font, style, size, justify, quality, cx, cy, Pixel{R=r,G=g,B=b,A=a}, timecode)
OutImage:Set(req, out)
end
function NotifyChanged(inp, param, time)
-- when the tools FontFileControl is first created, the FontManager has not yet
-- provided a FontList, so we can't set a default value. Instead we do it here.
if inp == InFont then
local f = param.Value
if f == nil or string.len(f) == 0 then
InFont:SetSource(Text("Open Sans"), time)
end
elseif inp == InFontStyle then
local f = param.Value
if f == nil or string.len(f) == 0 then
InFontStyle:SetSource(Text("Regular"), time)
end
end
end