[Script] Convert Absolute to Relative Paths - AX Relativity V4.0

User avatar
SecondMan
Site Admin
Posts: 3757
Joined: Thu Jul 31, 2014 5:31 pm
Answers: 7
Location: Vancouver, Canada
Been thanked: 146 times
Contact:

[Script] Convert Absolute to Relative Paths - AX Relativity V4.0

#1

Post by SecondMan » Sat Nov 02, 2019 11:54 pm

AX Relativity V4.0.lua
This is an update of the AX Relativity script by @Protean which I dug up from the Pigsfly forum.

Works in Fu9/Fu16 - the script looks for any file paths inside a comp file and re-formats them so they are relative to the comp file location on disk. Very handy for sharing comps across different setups, or making them portable.

Rather than targeting specific tools, the script parses the comp file itself, it will also work with any tool that uses external files, such as Suck Less Audio, which didn't even exist when the script was written. Clever and relatively (hah!) future proof :)

Needless to say this works for Fusion Standalone only, since Resolve doesn't store Fusion comp files as ASCII. If anyone feels like writing a workaround for that, by all means be my guest.

I'm attaching it to this post, please test it for your needs and give feedback. If all is well I will submit it to Reactor.
You do not have the required permissions to view the files attached to this post.

Tags:

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

Re: [Script] Convert Absolute to Relative Paths - AX Relativity V4.1

#2

Post by AndrewHazelden » Sun Nov 03, 2019 2:53 am

SecondMan wrote:
Sat Nov 02, 2019 11:54 pm
I'm attaching it to this post, please test it for your needs and give feedback. If all is well I will submit it to Reactor.
@SecondMan here's a quick code review and cleanup pass on the AX Relativity.lua script.

Note: I think the way the relative filepaths are scanned in the script means the script likely runs best in a Windows created Fusion .comp usecase. Possibly an extra bit of support code would be needed to process comps that have a mix of UNIX and Windows formatted folder separators sprinkled across several Loader nodes in the same .comp document... I did test this script at 6:49 AM in the morning so I will have to think about this more once I fully wake up for the day. :)

Code: [Select all] [Expand/Collapse] [Download] (AX Relativity.lua)
  1. _VERSION = "v4.1 2019-11-03"
  2. --[[--
  3. AX Relativity - v4.1 2019-11-03
  4. by Protean
  5.  
  6. Overview:
  7. The AX Relativity script converts all absolute paths in your comp to relative ones for easy sharing across different machines and/or users.
  8.  
  9. Installation:
  10. Copy the Lua comp script to your "Scripts:/Comp/" PathMap folder. If you downloaded this script from the web make sure the script has a .lua file extension.
  11.  
  12. Disclaimer:
  13. AX Relativity isn't a foolproof way of backing up your work. Please be sure to double-check your work
  14.  
  15.  
  16. Version History:
  17. v3.2 2009 by Protean
  18. - Original script "AX Relativity V3.2" by protean
  19.  
  20. v4 2019-11-03 by Pieter Van Houte
  21. - Compatibility with Fusion 9+ and additional error handling by Pieter Van Houte, a decade later - https://www.steakunderwater.com/wesuckless/viewtopic.php?p=26676#p26676
  22.  
  23. v4.1 2019-11-03 by Andrew Hazelden <andrew@andrewhazelden.com>
  24. - Code re-formatting
  25. - Proof-read the script comments
  26. - Script cleanup for spaces vs tabs
  27. - Removed several bmd.scriptlib dependencies to improve Reactor packaging script reliability down the road in a post-Fu/ReFu v16+ world.
  28.  
  29. --]]--
  30.  
  31.  
  32. --------------- bmd.scriptlib Dependencies -------------------------------------------------------------------------
  33. -- These functions were shamelessly copied from "bmd.scriptlib" to reduce the patchwork of risks that exist in Fu/ReFu v16.x+ where scriptlib support changes impact Lua script compatibility for Reactor based installations:
  34.  
  35. ------------------------------------------------------------------------------
  36. -- ParseFilename()
  37. --
  38. -- This is a great function for ripping a filepath into little bits
  39. -- returns a table with the following
  40. --
  41. -- FullPath : The raw, original path sent to the function
  42. -- Path : The path, without filename
  43. -- FullName : The name of the clip w\ extension
  44. -- Name : The name without extension
  45. -- CleanName: The name of the clip, without extension or sequence
  46. -- SNum : The original sequence string, or "" if no sequence
  47. -- Number : The sequence as a numeric value, or nil if no sequence
  48. -- Extension : The raw extension of the clip
  49. -- Padding : Amount of padding in the sequence, or nil if no sequence
  50. -- UNC : A true or false value indicating whether the path is a UNC path or not
  51. ------------------------------------------------------------------------------
  52. function ParseFilename(filename)
  53.     local seq = {}
  54.     seq.FullPath = filename
  55.     string.gsub(seq.FullPath, "^(.+[/\\])(.+)", function(path, name) seq.Path = path seq.FullName = name end)
  56.     string.gsub(seq.FullName, "^(.+)(%..+)$", function(name, ext) seq.Name = name seq.Extension = ext end)
  57.  
  58.     if not seq.Name then -- no extension?
  59.         seq.Name = seq.FullName
  60.     end
  61.  
  62.     string.gsub(seq.Name, "^(.-)(%d+)$", function(name, SNum) seq.CleanName = name seq.SNum = SNum end)
  63.  
  64.     if seq.SNum then
  65.         seq.Number = tonumber(seq.SNum)
  66.         seq.Padding = string.len(seq.SNum)
  67.     else
  68.         seq.SNum = ""
  69.         seq.CleanName = seq.Name
  70.     end
  71.  
  72.     if seq.Extension == nil then seq.Extension = "" end
  73.     seq.UNC = (string.sub(seq.Path, 1, 2) == [[\\]])
  74.  
  75.     return seq
  76. end
  77.  
  78. --------------- GLOBALS --------------------------------------------------------------------------------------------
  79. -- Find out the current operating system platform. The platform variable should be set to either 'Windows', 'Mac', or 'Linux'.
  80. local platform = (FuPLATFORM_WINDOWS and 'Windows') or (FuPLATFORM_MAC and 'Mac') or (FuPLATFORM_LINUX and 'Linux')
  81.  
  82. -- Add the platform specific folder slash character
  83. local os_separator = package.config:sub(1,1)
  84.  
  85. local COMPNAME, COMPFILENAME, PATTERN, PARSECOMP, NEWCOMPNAME, NEWCOMPPATH, NEWCOMPEXT, NEWCOMP
  86.  
  87. COMPNAME = composition:GetAttrs().COMPS_Name
  88. -- print(COMPNAME)
  89.  
  90. COMPFILENAME = composition:GetAttrs().COMPS_FileName
  91. -- print(COMPFILENAME)
  92.  
  93. if COMPFILENAME == "" then
  94.     print "[Error] Please save your comp first..."
  95.     return
  96. else
  97.     PATTERN = "[%w%=%:%_%s%-.%.]+"
  98.     PARSECOMP = ParseFilename(COMPFILENAME)
  99.     NEWCOMPNAME = PARSECOMP.Name .. "_relative"
  100.     NEWCOMPPATH = PARSECOMP.Path
  101.     NEWCOMPEXT = PARSECOMP.Extension
  102.     NEWCOMP = NEWCOMPPATH .. NEWCOMPNAME .. NEWCOMPEXT
  103. end
  104. ----------------- END GLOBALS --------------------------------------------------------------------------------------
  105.  
  106. ----------------- START FUNCTIONS ----------------------------------------------------------------------------------
  107. function literalize(str)
  108.     return str:gsub("[%(%)%.%%%+%-%*%?%[%]%^%$]", function(c) return "%" .. c end)
  109. end
  110.  
  111. -- Run gsub in a case insensitive way
  112. function insensGsub(s, pat, repl) -- Case insensitive gsub
  113.     pat = string.gsub(pat, '(%a)',
  114.     function (v) return '[' .. string.upper(v) .. string.lower(v) .. ']' end)
  115.     return string.gsub(s, pat, repl)
  116. end
  117.  
  118. -- Expand a filepath string into separate elements in a new Lua table
  119. function splitPath(str, pat)
  120.     local pathTable = {} -- To store original path
  121.     local x = 1
  122.     for i in string.gmatch(str, pat) do -- Iterate every word in filename
  123.         pathTable[x] = i
  124.         x = x + 1
  125.     end
  126.     return pathTable
  127. end
  128.  
  129. -- Covert the capitalization of entries in a Lua table to lowercase
  130. function lowerCase(table)
  131.     local lowTable = {}
  132.     local x = 1
  133.     for i, v in ipairs(table) do
  134.         lowTable[x] = string.lower(v)
  135.         x = x + 1
  136.     end
  137.     return lowTable
  138. end
  139.  
  140. -- Rewrite all filepaths to be relative
  141. function relative(file1,file2)
  142.     local file1Lwr = string.lower(file1)
  143.     local file2Lwr = string.lower(file2)
  144.     local file1_Path = ParseFilename(file1).Path
  145.     local file2_Path = ParseFilename(file2).Path
  146.     local newPath = file2_Path -- Will be overwritten by a new string if relative path found
  147.     local filePathTable = {}
  148.     print("\t\t[Comp Path] " .. file1_Path)
  149.     print("\t\t[Filepath] " .. file2_Path)
  150.     local file1_Path_lit = literalize(file1_Path) -- gsub doesn't do literal string comparison so...
  151.  
  152.     if file2_Path:find(file1_Path, 1, true) ~= nil then -- First check if footage downstream
  153.         print("[Footage Dowstream]")
  154.         newPath = "COMP:\\\\" .. insensGsub(file2, file1_Path_lit, "")
  155.     else
  156.         local filePathTbl = splitPath(file2_Path, PATTERN)
  157.         local compPathTbl = splitPath(file1_Path, PATTERN)
  158.         local filePathTbl_lwr = lowerCase(filePathTbl)
  159.         local compPathTbl_lwr = lowerCase(compPathTbl)
  160.         local sharedPath = ""
  161.         local pathBuilder = ""
  162.         local sharePoint = 0
  163.         local fork = 0
  164.         -- dump(filePathTbl_lwr)
  165.         for i, v in ipairs(compPathTbl_lwr) do
  166.             -- dump(filePathTbl)
  167.             -- print("\t[File Entry] " .. filePathTbl[i])
  168.             -- print("\t[Comp Entry] " .. v)
  169.             if v == filePathTbl_lwr[i] and fork == 0 then -- Compare file and comp tables // 'fork' checks if there has already been a non-match
  170.                 sharedPath = sharedPath .. compPathTbl[i] .. "\\\\"
  171.                 sharePoint = i
  172.             elseif sharePoint ~= 0 then -- If the comp/file tables return no match but there is a shared path
  173.                 fork = 1
  174.                 pathBuilder = pathBuilder .. "..\\\\"
  175.             elseif sharePoint == 0 then -- If no initial match so no relative path
  176.                 break
  177.             end
  178.         end
  179.  
  180.         if sharePoint == 0 then
  181.             newPath = file2
  182.             print("[No relative path found]")
  183.         else
  184.             print("[Footage Upstream]")
  185.  
  186.             sharedPath_lit = literalize(sharedPath)
  187.             print("[SharedPath] " .. sharedPath)
  188.             -- print("\t\t[file2] " .. file2)
  189.  
  190.             newPath = insensGsub(file2, sharedPath_lit, "COMP:\\\\" .. pathBuilder)
  191.             -- print("\t\t[Newpath] " .. newpath)
  192.         end
  193.  
  194.     end
  195.     -- print("\t\t[Comp File] " .. COMPFILENAME)
  196.     -- print("\t\t[New Path] " .. newPath)
  197.     -- print("\n")
  198.     return newPath
  199. end
  200.  
  201. -- Use a Lua native approach to check if a file exists on disk
  202. function fileExists(file)
  203.     local f = io.open(file, "r")
  204.     if f ~= nil then io.close(f) return true else return false end
  205. end
  206.  
  207. -- Find out the current directory from a file path
  208. function dirname(mediaDirName)
  209.     return mediaDirName:match('(.*' .. os_separator .. ')')
  210. end
  211.  
  212. -- Open a folder window up using your desktop file browser
  213. function openDirectory(mediaDirName)
  214.     command = nil
  215.     dir = dirname(mediaDirName)
  216.  
  217.     if platform == "Windows" then
  218.         -- Running on Windows
  219.         command = 'explorer "' .. dir .. '"'
  220.        
  221.         print("[Launch Command] ", command)
  222.         os.execute(command)
  223.     elseif platform == "Mac" then
  224.         -- Running on Mac
  225.         command = 'open "' .. dir .. '" &'
  226.  
  227.         print("[Launch Command] ", command)
  228.         os.execute(command)
  229.     elseif platform == "Linux" then
  230.         -- Running on Linux
  231.         command = 'nautilus "' .. dir .. '" &'
  232.  
  233.         print("[Launch Command] ", command)
  234.         os.execute(command)
  235.     else
  236.         print("[Platform] ", platform)
  237.         print("There is an invalid platform defined in the local platform variable at the top of the code.")
  238.     end
  239. end
  240.  
  241. ----------------- END FUNCTIONS -----------------------------------------------------------------------------------
  242.  
  243. ----------------- BEGIN SCRIPT ------------------------------------------------------------------------------------
  244. print("AX Relativity - " .. tostring(_VERSION))
  245. print("-------------------------------")
  246. print("[Start Time] " .. tostring(os.date('%I:%M:%S %p')) .. "\n")
  247.  
  248. print("[Note] Fusion's built-in Lua v5.1 based interpreter may have difficulty handing Unicode extended + multibyte characters in filepaths.\n")
  249.  
  250. ----- Test Destination Comp --------------------------------------------------------------------------------------
  251. destComp, err = io.open(NEWCOMP, "w+")
  252. if not destComp then
  253.     print("[Error] Could not create output composition " .. NEWCOMP .. " The system returned the following error : \n" .. err)
  254.     exit()
  255. else
  256.     destComp:close()
  257.     destComp = nil
  258. end
  259. -----------------------------------------------------------------------------------------------------------------
  260. print("[Processing Comp]")
  261. print("\t[Comp File] " .. string.gsub(COMPFILENAME, "\\", "\\\\"))
  262. print("\t[Comp Dest] " .. NEWCOMP .. "\n")
  263.  
  264. -- COMPFILENAME file reading
  265. compFile = io.open(COMPFILENAME, "r+")
  266.  
  267. local lines = {}
  268. for line in compFile:lines() do
  269.     table.insert(lines, line)
  270. end
  271.  
  272. for i, v in ipairs(lines) do
  273.     findPattern = "%\"(.-)%\"" ---- Look for a string in quotes (any text entry basically)
  274.     s, e = v:find(findPattern)
  275.     if s ~= nil then
  276.         cleanLine = string.gsub(v:sub(s,e), "%\"" ,"") ---- Remove the quotation marks
  277.         if cleanLine:find("%a%:%\\%\\") ~= nil then ---- Look for a drive path <letter>:\\
  278.             local relativePath = relative(string.gsub(COMPFILENAME, "\\", "\\\\"), cleanLine)
  279.             local cleanLine_lit = literalize(cleanLine)
  280.             local newLine = v:gsub(cleanLine_lit, relativePath)
  281.             print("\t[Old Line] " .. v)
  282.             print("\t[New Line] " .. newLine .. "\n")
  283.             lines[i] = newLine
  284.         end
  285.     end
  286. end
  287.  
  288. compFile:close()
  289.  
  290. -- NEWCOMP file writing
  291. destComp = io.open(NEWCOMP, "w")
  292.  
  293. for i, l in ipairs(lines) do
  294.     -- print(l)
  295.     destComp:write(l, "\n")
  296. end
  297.  
  298. destComp:close()
  299. print("[Comp Saved] Relative comp file saved as:\n\"" .. NEWCOMP .. "\"\n")
  300.  
  301.  
  302. -- Open up the reative comp's containing folder in a desktop folder browsing window
  303. print("[Open Containing Folder]")
  304. openDirectory(NEWCOMP)
  305. print("\n")
  306.  
  307. -- Script complete
  308. print("[End Time] " .. tostring(os.date('%I:%M:%S %p')) .. "")
  309. print("-------------------------------")
  310. print("My work here is done.\n")
  311.  

AX Relativity Console Screenshot


User avatar
danell
Fusioneer
Posts: 58
Joined: Mon Dec 12, 2016 6:32 am
Been thanked: 3 times

Re: [Script] Convert Absolute to Relative Paths - AX Relativity V4.0

#3

Post by danell » Tue Dec 03, 2019 2:45 am

Lovely script! Thanks for updating it!
Will this be posted to reactor? My sugestion would be to update the name to tell more what it acutally does. I would never guess AX Relativity would mean to convert absolut paths to relative paths.

User avatar
SecondMan
Site Admin
Posts: 3757
Joined: Thu Jul 31, 2014 5:31 pm
Answers: 7
Location: Vancouver, Canada
Been thanked: 146 times
Contact:

Re: [Script] Convert Absolute to Relative Paths - AX Relativity V4.0

#4

Post by SecondMan » Tue Dec 03, 2019 6:20 am

You're welcome! Agreed about the name for sure.
danell wrote:
Tue Dec 03, 2019 2:45 am
Will this be posted to reactor?
SecondMan wrote:
Sat Nov 02, 2019 11:54 pm
please test it for your needs and give feedback. If all is well I will submit it to Reactor.
:)

User avatar
danell
Fusioneer
Posts: 58
Joined: Mon Dec 12, 2016 6:32 am
Been thanked: 3 times

Re: [Script] Convert Absolute to Relative Paths - AX Relativity V4.0

#5

Post by danell » Tue Dec 03, 2019 8:37 am

Oh, missed that :D

sepu66
Posts: 23
Joined: Tue Apr 23, 2019 12:18 pm
Location: San Francisco, CA
Contact:

Re: [Script] Convert Absolute to Relative Paths - AX Relativity V4.0

#6

Post by sepu66 » Wed Dec 04, 2019 8:44 pm

Oh man this awesome - such a time saver! Thanks - so far working great! Just tested on Window 10