├── .github └── workflows │ └── package.yml ├── .gitignore ├── Deprecated ├── Fusion │ ├── Config │ │ ├── MediaOutContextualMenuFixRCMColorShift.fu │ │ └── RCMFusionDisplayViewOn.fu │ ├── Macros │ │ ├── RCM Color Space Display.setting │ │ ├── RCM Color Space Transform.setting │ │ ├── Rec709 Color Space Display.setting │ │ ├── Rec709 Color Space Transform.setting │ │ ├── Wide Gamut Color Space Display.setting │ │ └── Wide Gamut Color Space Transform.setting │ └── Scripts │ │ ├── Comp │ │ └── Fusion Hotkey Manager.lua │ │ ├── Tool │ │ ├── RCM Color Shift Fix Old.lua │ │ └── RCM Color Shift Fix.lua │ │ └── Utility │ │ ├── RCM Color Space Match.py │ │ ├── RCM Fusion Fix.lua │ │ ├── Subtitle Conversion.py │ │ └── Subtitle Tool.lua └── lib │ └── get_resolve.pyc ├── Fusion ├── Config │ └── RCMColorSpaceMatchHotkey.fu ├── Fuses │ └── Flow │ │ └── Switch.fuse ├── Modules │ └── Lua │ │ ├── json.lua │ │ ├── resolve-metadata-amd64.dylib │ │ ├── resolve-metadata-arm64.dylib │ │ └── resolve-metadata.dll ├── Scripts │ ├── Comp │ │ └── Sony MILC.lua │ └── Utility │ │ ├── Lua │ │ ├── Metadata Parser.lua │ │ ├── RCM Color Space Match.lua │ │ └── Script Installer.lua │ │ └── Python │ │ ├── Color Grading Tool.py │ │ ├── DRX Management.py │ │ ├── Metadata Parser.py │ │ └── get_resolve.py └── Templates │ └── Edit │ └── Effects │ └── XiaoLi │ ├── Sony MILC.png │ └── Sony MILC.setting ├── LICENSE ├── README-EN.md ├── README.md └── assets ├── RCM色彩匹配.png ├── 压缩包目录结构.png └── 脚本安装界面.png /.github/workflows/package.yml: -------------------------------------------------------------------------------- 1 | name: package 2 | 3 | on: 4 | push: 5 | workflow_dispatch: 6 | 7 | jobs: 8 | my-job: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: checkout 12 | uses: actions/checkout@v4 13 | 14 | - run: | 15 | mkdir dist target 16 | cp -r Fusion/* dist/ 17 | rm -rf dist/Scripts/Utility/* 18 | rm -rf dist/Config/RCMColorSpaceMatchHotkey.fu 19 | cp -r Fusion/Scripts/Utility/Lua/* dist/Scripts/Utility/ 20 | 7z a target/script.zip ./dist/* 21 | cp Fusion/Scripts/Utility/Lua/Script\ Installer.lua target/ 22 | 23 | - uses: actions/upload-artifact@v4 24 | with: 25 | name: my-artifact 26 | path: target 27 | 28 | - name: Release 29 | uses: softprops/action-gh-release@v2 30 | if: startsWith(github.ref, 'refs/tags/') 31 | with: 32 | files: target/* 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[od] 4 | *$py.class 5 | 6 | # Distribution / packaging 7 | .Python 8 | build/ 9 | develop-eggs/ 10 | dist/ 11 | downloads/ 12 | eggs/ 13 | .eggs/ 14 | lib64/ 15 | parts/ 16 | sdist/ 17 | var/ 18 | wheels/ 19 | pip-wheel-metadata/ 20 | share/python-wheels/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | MANIFEST 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .nox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *.cover 46 | *.py,cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | db.sqlite3-journal 59 | 60 | # Flask stuff: 61 | instance/ 62 | .webassets-cache 63 | 64 | # Scrapy stuff: 65 | .scrapy 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # Jupyter Notebook 74 | .ipynb_checkpoints 75 | 76 | # IPython 77 | profile_default/ 78 | ipython_config.py 79 | 80 | # pyenv 81 | .python-version 82 | 83 | # pipenv 84 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 85 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 86 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 87 | # install all needed dependencies. 88 | #Pipfile.lock 89 | 90 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 91 | __pypackages__/ 92 | 93 | # Celery stuff 94 | celerybeat-schedule 95 | celerybeat.pid 96 | 97 | # SageMath parsed files 98 | *.sage.py 99 | 100 | # Environments 101 | .env 102 | .venv 103 | env/ 104 | venv/ 105 | ENV/ 106 | env.bak/ 107 | venv.bak/ 108 | 109 | # Spyder project settings 110 | .spyderproject 111 | .spyproject 112 | 113 | # Rope project settings 114 | .ropeproject 115 | 116 | # mkdocs documentation 117 | /site 118 | 119 | # mypy 120 | .mypy_cache/ 121 | .dmypy.json 122 | dmypy.json 123 | 124 | # Pyre type checker 125 | .pyre/ 126 | 127 | .idea 128 | 129 | *.json -------------------------------------------------------------------------------- /Deprecated/Fusion/Config/MediaOutContextualMenuFixRCMColorShift.fu: -------------------------------------------------------------------------------- 1 | --[[-- 2 | 3 | Deprecated from DaVinci Resolve 17.3 4 | 5 | --]]-- 6 | { 7 | Action{ 8 | ID = "Fix_RCM_Rec709_Color_Shift", 9 | Category = "Utilities", 10 | Name = "Fix RCM Rec709 Color Shift", 11 | 12 | Targets = { 13 | Composition = { 14 | Execute = [=[ 15 | comp = obj:Comp() 16 | 17 | mediaOutNode = comp.ActiveTool 18 | mediaInNode = comp:FindToolByID("MediaIn") 19 | 20 | if mediaOutNode then 21 | local addTransformNode = true 22 | local addDisplayNode = true 23 | if mediaInNode and mediaInNode:GetData('MediaProps').MEDIA_IS_SOURCE_RES then 24 | addTransformNode = false 25 | if mediaInNode:GetData('MediaProps').MEDIA_FORMAT_TYPE == 'DNG' then 26 | addDisplayNode = false 27 | end 28 | end 29 | -- Add Color Space Transform 30 | if addDisplayNode and not comp:FindTool("Rec709ColorSpaceDisplay") then 31 | comp:DoAction("AddSetting", { filename = "Macros:/Rec709 Color Space Display.setting" }) 32 | end 33 | 34 | -- Add MediaOut Display Color Space Transform 35 | if addTransformNode and not comp:FindTool("Rec709ColorSpaceTransform") then 36 | if mediaOutNode:FindMainInput(1) and mediaOutNode:FindMainInput(1):GetConnectedOutput() then 37 | connectedNode = mediaOutNode:FindMainInput(1):GetConnectedOutput():GetTool() 38 | while connectedNode.ParentTool do 39 | connectedNode = connectedNode.ParentTool 40 | end 41 | comp:SetActiveTool(connectedNode) 42 | comp:DoAction("AddSetting", { filename = "Macros:/Rec709 Color Space Transform.setting" }) 43 | else 44 | comp:SetActiveTool() 45 | comp:DoAction("AddSetting", { filename = "Macros:/Rec709 Color Space Transform.setting" }) 46 | mediaOutNode.Input:ConnectTo(comp.ActiveTool.Output) 47 | end 48 | end 49 | end 50 | 51 | -- Disable Viewer LUT 52 | if comp:GetPreviewList().LeftView.View.CurrentViewer then 53 | comp:GetPreviewList().LeftView.View.CurrentViewer:EnableLUT(false) 54 | end 55 | if comp:FindTool("Rec709ColorSpaceDisplay") then 56 | comp:GetPreviewList().RightView:ViewOn(comp:FindTool("Rec709ColorSpaceDisplay")) 57 | if comp:GetPreviewList().RightView.View.CurrentViewer then 58 | comp:GetPreviewList().RightView.View.CurrentViewer:EnableLUT(false) 59 | end 60 | end 61 | ]=], 62 | }, 63 | }, 64 | }, 65 | 66 | Action{ 67 | ID = "Fix_RCM_WG_Color_Shift", 68 | Category = "Utilities", 69 | Name = "Fix RCM WG Color Shift", 70 | 71 | Targets = { 72 | Composition = { 73 | Execute = [=[ 74 | comp = obj:Comp() 75 | 76 | mediaOutNode = comp.ActiveTool 77 | mediaInNode = comp:FindToolByID("MediaIn") 78 | 79 | if mediaOutNode then 80 | local addTransformNode = true 81 | local addDisplayNode = true 82 | if mediaInNode and mediaInNode:GetData('MediaProps').MEDIA_IS_SOURCE_RES then 83 | addTransformNode = false 84 | if mediaInNode:GetData('MediaProps').MEDIA_FORMAT_TYPE == 'DNG' then 85 | addDisplayNode = false 86 | end 87 | end 88 | -- Add Color Space Transform 89 | if addDisplayNode and not comp:FindTool("WideGamutColorSpaceDisplay") then 90 | comp:DoAction("AddSetting", { filename = "Macros:/Wide Gamut Color Space Display.setting" }) 91 | end 92 | 93 | -- Add MediaOut Display Color Space Transform 94 | if addTransformNode and not comp:FindTool("WideGamutColorSpaceTransform") then 95 | if mediaOutNode:FindMainInput(1) and mediaOutNode:FindMainInput(1):GetConnectedOutput() then 96 | connectedNode = mediaOutNode:FindMainInput(1):GetConnectedOutput():GetTool() 97 | while connectedNode.ParentTool do 98 | connectedNode = connectedNode.ParentTool 99 | end 100 | comp:SetActiveTool(connectedNode) 101 | comp:DoAction("AddSetting", { filename = "Macros:/Wide Gamut Color Space Transform.setting" }) 102 | else 103 | comp:SetActiveTool() 104 | comp:DoAction("AddSetting", { filename = "Macros:/Wide Gamut Color Space Transform.setting" }) 105 | mediaOutNode.Input:ConnectTo(comp.ActiveTool.Output) 106 | end 107 | end 108 | end 109 | 110 | -- Disable Viewer LUT 111 | if comp:GetPreviewList().LeftView.View.CurrentViewer then 112 | comp:GetPreviewList().LeftView.View.CurrentViewer:EnableLUT(false) 113 | end 114 | if comp:FindTool("WideGamutColorSpaceDisplay") then 115 | comp:GetPreviewList().RightView:ViewOn(comp:FindTool("WideGamutColorSpaceDisplay")) 116 | if comp:GetPreviewList().RightView.View.CurrentViewer then 117 | comp:GetPreviewList().RightView.View.CurrentViewer:EnableLUT(false) 118 | end 119 | end 120 | ]=], 121 | }, 122 | }, 123 | }, 124 | 125 | Menus{ 126 | -- Add the menu entries to the MediaOut's node right-click based contextual menus. 127 | Target = "MediaOut", 128 | 129 | Append{ 130 | "_", 131 | "Fix_RCM_Rec709_Color_Shift{}", 132 | "Fix_RCM_WG_Color_Shift{}", 133 | }, 134 | }, 135 | } 136 | -------------------------------------------------------------------------------- /Deprecated/Fusion/Config/RCMFusionDisplayViewOn.fu: -------------------------------------------------------------------------------- 1 | { 2 | Event { 3 | Action = "Tool_ViewOn", 4 | Targets = { 5 | Fusion = { 6 | Execute = [[ 7 | 8 | -- Run the Action as the first step 9 | rets = self:Default(ctx, args) 10 | 11 | -- Get the pointer for the current foreground comp 12 | cmp = fusion.CurrentComp 13 | if args.__flags == nil and cmp then 14 | displayNode = cmp:FindTool("RCMColorSpaceDisplay") 15 | if not displayNode then 16 | displayNode = cmp:FindTool("RCMFusionDisplay2") 17 | end 18 | 19 | if displayNode then 20 | cmp:GetPreviewList().RightView:ViewOn(displayNode) 21 | if cmp:GetPreviewList().RightView.View.CurrentViewer then 22 | cmp:GetPreviewList().RightView.View.CurrentViewer:EnableLUT(false) 23 | end 24 | end 25 | end 26 | ]] 27 | }, 28 | }, 29 | } 30 | } -------------------------------------------------------------------------------- /Deprecated/Fusion/Macros/RCM Color Space Display.setting: -------------------------------------------------------------------------------- 1 | { 2 | Tools = ordered() { 3 | RCMColorSpaceDisplay = GroupOperator { 4 | CtrlWZoom = false, 5 | NameSet = true, 6 | Inputs = ordered() { 7 | Input1 = InstanceInput { 8 | SourceOp = "ColorSpaceTransform3", 9 | Source = "Source", 10 | } 11 | }, 12 | Outputs = { 13 | Output1 = InstanceOutput { 14 | SourceOp = "ColorSpaceTransform4", 15 | Source = "Output", 16 | } 17 | }, 18 | ViewInfo = GroupInfo { 19 | Pos = { 1045, 40.8636 }, 20 | Flags = { 21 | AllowPan = false, 22 | GridSnap = true, 23 | AutoSnap = true, 24 | RemoveRouters = true 25 | }, 26 | Size = { 236, 66.3636, 118, 24.2424 }, 27 | Direction = "Horizontal", 28 | PipeStyle = "Direct", 29 | Scale = 1, 30 | Offset = { -1045, -40.8636 } 31 | }, 32 | Tools = ordered() { 33 | ColorSpaceTransform3 = ofx.com.blackmagicdesign.resolvefx.ColorSpaceTransformV2 { 34 | Inputs = { 35 | Source = Input { 36 | SourceOp = "MediaOut1", 37 | Source = "Output", 38 | }, 39 | colorSpaceTransformGroup = Input { Value = 1, }, 40 | inputColorSpace = Input { Value = FuID { "TIMELINE_COLORSPACE" }, }, 41 | inputGamma = Input { Value = FuID { "LINEAR_GAMMA" }, }, 42 | outputColorSpace = Input { Value = FuID { "TIMELINE_COLORSPACE" }, }, 43 | outputGamma = Input { Value = FuID { "AUTO_GAMMA" }, }, 44 | isRec2390ScalingEnabled = Input { Value = 0, }, 45 | toneMappingGroup = Input { Value = 1, }, 46 | tmType = Input { Value = FuID { "TM_NONE" }, }, 47 | isSrcLumMaxCustomEnabled = Input { 48 | Value = 0, 49 | Disabled = true, 50 | }, 51 | srcLumMax = Input { 52 | Value = 100, 53 | Disabled = true, 54 | }, 55 | isDstLumMaxCustomEnabled = Input { 56 | Value = 0, 57 | Disabled = true, 58 | }, 59 | dstLumMax = Input { 60 | Value = 100, 61 | Disabled = true, 62 | }, 63 | srcLumAvg = Input { 64 | Value = 9, 65 | Disabled = true, 66 | }, 67 | satRolloffStart = Input { Value = 100, }, 68 | satRolloffLimit = Input { Value = 10000, }, 69 | gamutMappingGroup = Input { Value = 1, }, 70 | gmType = Input { Value = FuID { "GM_NONE" }, }, 71 | satKnee = Input { 72 | Value = 0.899999976158142, 73 | Disabled = true, 74 | }, 75 | satMax = Input { 76 | Value = 1, 77 | Disabled = true, 78 | }, 79 | advancedGroup = Input { Value = 1, }, 80 | doFwdOOTF = Input { Value = 0, }, 81 | doInvOOTF = Input { Value = 0, }, 82 | doCAT = Input { Value = 1, }, 83 | blendGroup = Input { Value = 0, }, 84 | blendIn = Input { Value = 1, }, 85 | blend = Input { Value = 0, }, 86 | ignoreContentShape = Input { Value = 0, }, 87 | legacyIsProcessRGBOnly = Input { Value = 0, }, 88 | refreshTrigger = Input { Value = 1, }, 89 | resolvefxVersion = Input { Value = "1.1", }, 90 | }, 91 | ViewInfo = OperatorInfo { Pos = { 990, 49.5 } }, 92 | }, 93 | ColorSpaceTransform4 = ofx.com.blackmagicdesign.resolvefx.ColorSpaceTransformV2 { 94 | Inputs = { 95 | Source = Input { 96 | SourceOp = "ColorSpaceTransform3", 97 | Source = "Output", 98 | }, 99 | colorSpaceTransformGroup = Input { Value = 1, }, 100 | inputColorSpace = Input { Value = FuID { "TIMELINE_COLORSPACE" }, }, 101 | inputGamma = Input { Value = FuID { "AUTO_GAMMA" }, }, 102 | outputColorSpace = Input { Value = FuID { "REC709_COLORSPACE" }, }, 103 | outputGamma = Input { Value = FuID { "TWOPOINTFOUR_GAMMA" }, }, 104 | isRec2390ScalingEnabled = Input { 105 | Value = 0, 106 | Disabled = true, 107 | }, 108 | toneMappingGroup = Input { Value = 1, }, 109 | tmType = Input { Value = FuID { "TM_DRT_V2" }, }, 110 | isSrcLumMaxCustomEnabled = Input { Value = 1, }, 111 | srcLumMax = Input { Value = 100, }, 112 | isDstLumMaxCustomEnabled = Input { Value = 0, }, 113 | dstLumMax = Input { 114 | Value = 100, 115 | Disabled = true, 116 | }, 117 | srcLumAvg = Input { Value = 9, }, 118 | satRolloffStart = Input { Value = 100, }, 119 | satRolloffLimit = Input { Value = 10000, }, 120 | gamutMappingGroup = Input { Value = 1, }, 121 | gmType = Input { Value = FuID { "GM_NONE" }, }, 122 | satKnee = Input { 123 | Value = 0.899999976158142, 124 | Disabled = true, 125 | }, 126 | satMax = Input { 127 | Value = 1, 128 | Disabled = true, 129 | }, 130 | advancedGroup = Input { Value = 1, }, 131 | doFwdOOTF = Input { Value = 1, }, 132 | doInvOOTF = Input { Value = 0, }, 133 | doCAT = Input { Value = 1, }, 134 | blendGroup = Input { Value = 0, }, 135 | blendIn = Input { Value = 1, }, 136 | blend = Input { Value = 0, }, 137 | ignoreContentShape = Input { Value = 0, }, 138 | legacyIsProcessRGBOnly = Input { Value = 0, }, 139 | refreshTrigger = Input { Value = 1, }, 140 | resolvefxVersion = Input { Value = "1.1", }, 141 | }, 142 | ViewInfo = OperatorInfo { Pos = { 1100, 49.5 } }, 143 | } 144 | }, 145 | } 146 | } 147 | } -------------------------------------------------------------------------------- /Deprecated/Fusion/Macros/RCM Color Space Transform.setting: -------------------------------------------------------------------------------- 1 | { 2 | Tools = ordered() { 3 | RCMColorSpaceTransform = GroupOperator { 4 | CtrlWZoom = false, 5 | NameSet = true, 6 | Inputs = ordered() { 7 | Comments = Input { Value = "1、注意适当增删RCM Color Space Transform节点,一般情况是Fusion颜色需要添加修正,相机输入画面不需要添加修正节点,复杂情况请自行判断;\n2、有合并节点请注意Transform节点放置位置,原则就是只修正需要修正的节点(组)!\n3、脚本没有那么智能,目前也只是解决官方颜色不匹配的过度方案,等到官方更新版本解决RCM下Fusion颜色问题才是最优解!", }, 8 | Input1 = InstanceInput { 9 | SourceOp = "ColorSpaceTransform1", 10 | Source = "Source", 11 | } 12 | }, 13 | Outputs = { 14 | Output1 = InstanceOutput { 15 | SourceOp = "ColorSpaceTransform2", 16 | Source = "Output", 17 | } 18 | }, 19 | ViewInfo = GroupInfo { 20 | Pos = { 330, 40.8636 }, 21 | Flags = { 22 | AllowPan = false, 23 | GridSnap = true, 24 | AutoSnap = true, 25 | RemoveRouters = true 26 | }, 27 | Size = { 236, 66.3636, 118, 24.2424 }, 28 | Direction = "Horizontal", 29 | PipeStyle = "Direct", 30 | Scale = 1, 31 | Offset = { -330, -40.8636 } 32 | }, 33 | Tools = ordered() { 34 | ColorSpaceTransform2 = ofx.com.blackmagicdesign.resolvefx.ColorSpaceTransformV2 { 35 | Inputs = { 36 | Source = Input { 37 | SourceOp = "ColorSpaceTransform1", 38 | Source = "Output", 39 | }, 40 | colorSpaceTransformGroup = Input { Value = 1, }, 41 | inputColorSpace = Input { Value = FuID { "TIMELINE_COLORSPACE" }, }, 42 | inputGamma = Input { Value = FuID { "AUTO_GAMMA" }, }, 43 | outputColorSpace = Input { Value = FuID { "TIMELINE_COLORSPACE" }, }, 44 | outputGamma = Input { Value = FuID { "LINEAR_GAMMA" }, }, 45 | isRec2390ScalingEnabled = Input { Value = 0, }, 46 | toneMappingGroup = Input { Value = 1, }, 47 | tmType = Input { Value = FuID { "TM_NONE" }, }, 48 | isSrcLumMaxCustomEnabled = Input { 49 | Value = 0, 50 | Disabled = true, 51 | }, 52 | srcLumMax = Input { 53 | Value = 100, 54 | Disabled = true, 55 | }, 56 | isDstLumMaxCustomEnabled = Input { 57 | Value = 0, 58 | Disabled = true, 59 | }, 60 | dstLumMax = Input { 61 | Value = 100, 62 | Disabled = true, 63 | }, 64 | srcLumAvg = Input { 65 | Value = 9, 66 | Disabled = true, 67 | }, 68 | satRolloffStart = Input { Value = 100, }, 69 | satRolloffLimit = Input { Value = 10000, }, 70 | gamutMappingGroup = Input { Value = 1, }, 71 | gmType = Input { Value = FuID { "GM_NONE" }, }, 72 | satKnee = Input { 73 | Value = 0.899999976158142, 74 | Disabled = true, 75 | }, 76 | satMax = Input { 77 | Value = 1, 78 | Disabled = true, 79 | }, 80 | advancedGroup = Input { Value = 1, }, 81 | doFwdOOTF = Input { Value = 0, }, 82 | doInvOOTF = Input { Value = 0, }, 83 | doCAT = Input { Value = 1, }, 84 | blendGroup = Input { Value = 0, }, 85 | blendIn = Input { Value = 1, }, 86 | blend = Input { Value = 0, }, 87 | ignoreContentShape = Input { Value = 0, }, 88 | legacyIsProcessRGBOnly = Input { Value = 0, }, 89 | refreshTrigger = Input { Value = 1, }, 90 | resolvefxVersion = Input { Value = "1.1", }, 91 | }, 92 | ViewInfo = OperatorInfo { Pos = { 385, 49.5 } }, 93 | }, 94 | ColorSpaceTransform1 = ofx.com.blackmagicdesign.resolvefx.ColorSpaceTransformV2 { 95 | Inputs = { 96 | Source = Input { 97 | SourceOp = "KeyframeStretcher1", 98 | Source = "Result", 99 | }, 100 | colorSpaceTransformGroup = Input { Value = 1, }, 101 | inputColorSpace = Input { Value = FuID { "REC709_COLORSPACE" }, }, 102 | inputGamma = Input { Value = FuID { "TWOPOINTFOUR_GAMMA" }, }, 103 | outputColorSpace = Input { Value = FuID { "TIMELINE_COLORSPACE" }, }, 104 | outputGamma = Input { Value = FuID { "AUTO_GAMMA" }, }, 105 | isRec2390ScalingEnabled = Input { 106 | Value = 0, 107 | Disabled = true, 108 | }, 109 | toneMappingGroup = Input { Value = 1, }, 110 | tmType = Input { Value = FuID { "TM_DRT_V2" }, }, 111 | isSrcLumMaxCustomEnabled = Input { Value = 0, }, 112 | srcLumMax = Input { 113 | Value = 100, 114 | Disabled = true, 115 | }, 116 | isDstLumMaxCustomEnabled = Input { Value = 1, }, 117 | dstLumMax = Input { Value = 100, }, 118 | srcLumAvg = Input { Value = 9, }, 119 | satRolloffStart = Input { Value = 100, }, 120 | satRolloffLimit = Input { Value = 10000, }, 121 | gamutMappingGroup = Input { Value = 1, }, 122 | gmType = Input { Value = FuID { "GM_NONE" }, }, 123 | satKnee = Input { 124 | Value = 0.899999976158142, 125 | Disabled = true, 126 | }, 127 | satMax = Input { 128 | Value = 1, 129 | Disabled = true, 130 | }, 131 | advancedGroup = Input { Value = 1, }, 132 | doFwdOOTF = Input { Value = 0, }, 133 | doInvOOTF = Input { Value = 1, }, 134 | doCAT = Input { Value = 1, }, 135 | blendGroup = Input { Value = 0, }, 136 | blendIn = Input { Value = 1, }, 137 | blend = Input { Value = 0, }, 138 | ignoreContentShape = Input { Value = 0, }, 139 | legacyIsProcessRGBOnly = Input { Value = 0, }, 140 | refreshTrigger = Input { Value = 1, }, 141 | resolvefxVersion = Input { Value = "1.1", }, 142 | }, 143 | ViewInfo = OperatorInfo { Pos = { 275, 49.5 } }, 144 | } 145 | }, 146 | } 147 | } 148 | } -------------------------------------------------------------------------------- /Deprecated/Fusion/Macros/Rec709 Color Space Display.setting: -------------------------------------------------------------------------------- 1 | { 2 | Tools = ordered() { 3 | Rec709ColorSpaceDisplay = ofx.com.blackmagicdesign.resolvefx.ColorSpaceTransformV2 { 4 | CtrlWZoom = false, 5 | NameSet = true, 6 | Inputs = { 7 | Source = Input { 8 | SourceOp = "MediaOut1", 9 | Source = "Output", 10 | }, 11 | colorSpaceTransformGroup = Input { Value = 1, }, 12 | inputColorSpace = Input { Value = FuID { "TIMELINE_COLORSPACE" }, }, 13 | inputGamma = Input { Value = FuID { "LINEAR_GAMMA" }, }, 14 | outputColorSpace = Input { Value = FuID { "TIMELINE_COLORSPACE" }, }, 15 | outputGamma = Input { Value = FuID { "AUTO_GAMMA" }, }, 16 | isRec2390ScalingEnabled = Input { Value = 0, }, 17 | toneMappingGroup = Input { Value = 1, }, 18 | tmType = Input { Value = FuID { "TM_NONE" }, }, 19 | isSrcLumMaxCustomEnabled = Input { 20 | Value = 0, 21 | Disabled = true, 22 | }, 23 | srcLumMax = Input { 24 | Value = 100, 25 | Disabled = true, 26 | }, 27 | isDstLumMaxCustomEnabled = Input { 28 | Value = 0, 29 | Disabled = true, 30 | }, 31 | dstLumMax = Input { 32 | Value = 100, 33 | Disabled = true, 34 | }, 35 | srcLumAvg = Input { 36 | Value = 9, 37 | Disabled = true, 38 | }, 39 | satRolloffStart = Input { Value = 100, }, 40 | satRolloffLimit = Input { Value = 10000, }, 41 | gamutMappingGroup = Input { Value = 1, }, 42 | gmType = Input { Value = FuID { "GM_NONE" }, }, 43 | satKnee = Input { 44 | Value = 0.899999976158142, 45 | Disabled = true, 46 | }, 47 | satMax = Input { 48 | Value = 1, 49 | Disabled = true, 50 | }, 51 | advancedGroup = Input { Value = 1, }, 52 | doFwdOOTF = Input { Value = 0, }, 53 | doInvOOTF = Input { Value = 0, }, 54 | doCAT = Input { Value = 1, }, 55 | blendGroup = Input { Value = 0, }, 56 | blendIn = Input { Value = 1, }, 57 | blend = Input { Value = 0, }, 58 | ignoreContentShape = Input { Value = 0, }, 59 | legacyIsProcessRGBOnly = Input { Value = 0, }, 60 | refreshTrigger = Input { Value = 1, }, 61 | resolvefxVersion = Input { Value = "1.1", }, 62 | }, 63 | ViewInfo = OperatorInfo { Pos = { 1018, 88.2879 } }, 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /Deprecated/Fusion/Macros/Rec709 Color Space Transform.setting: -------------------------------------------------------------------------------- 1 | { 2 | Tools = ordered() { 3 | Rec709ColorSpaceTransform = ofx.com.blackmagicdesign.resolvefx.ColorSpaceTransformV2 { 4 | CtrlWZoom = false, 5 | NameSet = true, 6 | Inputs = { 7 | Source = Input { 8 | SourceOp = "Background1", 9 | Source = "Output", 10 | }, 11 | colorSpaceTransformGroup = Input { Value = 1, }, 12 | inputColorSpace = Input { Value = FuID { "TIMELINE_COLORSPACE" }, }, 13 | inputGamma = Input { Value = FuID { "AUTO_GAMMA" }, }, 14 | outputColorSpace = Input { Value = FuID { "TIMELINE_COLORSPACE" }, }, 15 | outputGamma = Input { Value = FuID { "LINEAR_GAMMA" }, }, 16 | isRec2390ScalingEnabled = Input { Value = 0, }, 17 | toneMappingGroup = Input { Value = 1, }, 18 | tmType = Input { Value = FuID { "TM_NONE" }, }, 19 | isSrcLumMaxCustomEnabled = Input { 20 | Value = 0, 21 | Disabled = true, 22 | }, 23 | srcLumMax = Input { 24 | Value = 100, 25 | Disabled = true, 26 | }, 27 | isDstLumMaxCustomEnabled = Input { 28 | Value = 0, 29 | Disabled = true, 30 | }, 31 | dstLumMax = Input { 32 | Value = 100, 33 | Disabled = true, 34 | }, 35 | srcLumAvg = Input { 36 | Value = 9, 37 | Disabled = true, 38 | }, 39 | satRolloffStart = Input { Value = 100, }, 40 | satRolloffLimit = Input { Value = 10000, }, 41 | gamutMappingGroup = Input { Value = 1, }, 42 | gmType = Input { Value = FuID { "GM_NONE" }, }, 43 | satKnee = Input { 44 | Value = 0.899999976158142, 45 | Disabled = true, 46 | }, 47 | satMax = Input { 48 | Value = 1, 49 | Disabled = true, 50 | }, 51 | advancedGroup = Input { Value = 1, }, 52 | doFwdOOTF = Input { Value = 0, }, 53 | doInvOOTF = Input { Value = 0, }, 54 | doCAT = Input { Value = 1, }, 55 | blendGroup = Input { Value = 0, }, 56 | blendIn = Input { Value = 1, }, 57 | blend = Input { Value = 0, }, 58 | ignoreContentShape = Input { Value = 0, }, 59 | legacyIsProcessRGBOnly = Input { Value = 0, }, 60 | refreshTrigger = Input { Value = 1, }, 61 | resolvefxVersion = Input { Value = "1.1", }, 62 | }, 63 | ViewInfo = OperatorInfo { Pos = { 838.667, 92.2121 } }, 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /Deprecated/Fusion/Macros/Wide Gamut Color Space Display.setting: -------------------------------------------------------------------------------- 1 | { 2 | Tools = ordered() { 3 | WideGamutColorSpaceDisplay = GroupOperator { 4 | CtrlWZoom = false, 5 | NameSet = true, 6 | Inputs = ordered() { 7 | Input1 = InstanceInput { 8 | SourceOp = "ColorSpaceTransform1", 9 | Source = "Source", 10 | } 11 | }, 12 | Outputs = { 13 | Output1 = InstanceOutput { 14 | SourceOp = "ColorSpaceTransform2", 15 | Source = "Output", 16 | } 17 | }, 18 | ViewInfo = GroupInfo { 19 | Pos = { 1170, 82.6818 }, 20 | Flags = { 21 | AllowPan = false, 22 | AutoSnap = true, 23 | RemoveRouters = true 24 | }, 25 | Size = { 251.333, 66.9697, 125.667, 24.2424 }, 26 | Direction = "Horizontal", 27 | PipeStyle = "Direct", 28 | Scale = 1, 29 | Offset = { -1166.67, -71.7727 } 30 | }, 31 | Tools = ordered() { 32 | ColorSpaceTransform2 = ofx.com.blackmagicdesign.resolvefx.ColorSpaceTransformV2 { 33 | Inputs = { 34 | Source = Input { 35 | SourceOp = "ColorSpaceTransform1", 36 | Source = "Output", 37 | }, 38 | colorSpaceTransformGroup = Input { Value = 1, }, 39 | inputColorSpace = Input { Value = FuID { "TIMELINE_COLORSPACE" }, }, 40 | inputGamma = Input { Value = FuID { "AUTO_GAMMA" }, }, 41 | outputColorSpace = Input { Value = FuID { "REC709_COLORSPACE" }, }, 42 | outputGamma = Input { Value = FuID { "TWOPOINTFOUR_GAMMA" }, }, 43 | isRec2390ScalingEnabled = Input { 44 | Value = 0, 45 | Disabled = true, 46 | }, 47 | toneMappingGroup = Input { Value = 1, }, 48 | tmType = Input { Value = FuID { "TM_DRT_V2" }, }, 49 | isSrcLumMaxCustomEnabled = Input { Value = 1, }, 50 | srcLumMax = Input { Value = 4000, }, 51 | isDstLumMaxCustomEnabled = Input { Value = 0, }, 52 | dstLumMax = Input { 53 | Value = 100, 54 | Disabled = true, 55 | }, 56 | srcLumAvg = Input { Value = 9, }, 57 | satRolloffStart = Input { Value = 100, }, 58 | satRolloffLimit = Input { Value = 10000, }, 59 | gamutMappingGroup = Input { Value = 1, }, 60 | gmType = Input { Value = FuID { "GM_NONE" }, }, 61 | satKnee = Input { 62 | Value = 0.899999976158142, 63 | Disabled = true, 64 | }, 65 | satMax = Input { 66 | Value = 1, 67 | Disabled = true, 68 | }, 69 | advancedGroup = Input { Value = 1, }, 70 | doFwdOOTF = Input { Value = 1, }, 71 | doInvOOTF = Input { Value = 0, }, 72 | doCAT = Input { Value = 1, }, 73 | blendGroup = Input { Value = 0, }, 74 | blendIn = Input { Value = 1, }, 75 | blend = Input { Value = 0, }, 76 | ignoreContentShape = Input { Value = 0, }, 77 | legacyIsProcessRGBOnly = Input { Value = 0, }, 78 | refreshTrigger = Input { Value = 1, }, 79 | resolvefxVersion = Input { Value = "1.1", }, 80 | }, 81 | ViewInfo = OperatorInfo { Pos = { 1229.33, 80.4091 } }, 82 | }, 83 | ColorSpaceTransform1 = ofx.com.blackmagicdesign.resolvefx.ColorSpaceTransformV2 { 84 | Inputs = { 85 | Source = Input { 86 | SourceOp = "MediaOut1", 87 | Source = "Output", 88 | }, 89 | colorSpaceTransformGroup = Input { Value = 1, }, 90 | inputColorSpace = Input { Value = FuID { "TIMELINE_COLORSPACE" }, }, 91 | inputGamma = Input { Value = FuID { "LINEAR_GAMMA" }, }, 92 | outputColorSpace = Input { Value = FuID { "TIMELINE_COLORSPACE" }, }, 93 | outputGamma = Input { Value = FuID { "AUTO_GAMMA" }, }, 94 | isRec2390ScalingEnabled = Input { Value = 0, }, 95 | toneMappingGroup = Input { Value = 1, }, 96 | tmType = Input { Value = FuID { "TM_NONE" }, }, 97 | isSrcLumMaxCustomEnabled = Input { 98 | Value = 0, 99 | Disabled = true, 100 | }, 101 | srcLumMax = Input { 102 | Value = 100, 103 | Disabled = true, 104 | }, 105 | isDstLumMaxCustomEnabled = Input { 106 | Value = 0, 107 | Disabled = true, 108 | }, 109 | dstLumMax = Input { 110 | Value = 100, 111 | Disabled = true, 112 | }, 113 | srcLumAvg = Input { 114 | Value = 9, 115 | Disabled = true, 116 | }, 117 | satRolloffStart = Input { Value = 100, }, 118 | satRolloffLimit = Input { Value = 10000, }, 119 | gamutMappingGroup = Input { Value = 1, }, 120 | gmType = Input { Value = FuID { "GM_NONE" }, }, 121 | satKnee = Input { 122 | Value = 0.899999976158142, 123 | Disabled = true, 124 | }, 125 | satMax = Input { 126 | Value = 1, 127 | Disabled = true, 128 | }, 129 | advancedGroup = Input { Value = 1, }, 130 | doFwdOOTF = Input { Value = 0, }, 131 | doInvOOTF = Input { Value = 0, }, 132 | doCAT = Input { Value = 1, }, 133 | blendGroup = Input { Value = 0, }, 134 | blendIn = Input { Value = 1, }, 135 | blend = Input { Value = 0, }, 136 | ignoreContentShape = Input { Value = 0, }, 137 | legacyIsProcessRGBOnly = Input { Value = 0, }, 138 | refreshTrigger = Input { Value = 1, }, 139 | resolvefxVersion = Input { Value = "1.1", }, 140 | }, 141 | ViewInfo = OperatorInfo { Pos = { 1104, 81.0152 } }, 142 | } 143 | }, 144 | } 145 | } 146 | } -------------------------------------------------------------------------------- /Deprecated/Fusion/Macros/Wide Gamut Color Space Transform.setting: -------------------------------------------------------------------------------- 1 | { 2 | Tools = ordered() { 3 | WideGamutColorSpaceTransform = GroupOperator { 4 | CtrlWZoom = false, 5 | NameSet = true, 6 | Inputs = ordered() { 7 | Input1 = InstanceInput { 8 | SourceOp = "ColorSpaceTransform1", 9 | Source = "Source", 10 | } 11 | }, 12 | Outputs = { 13 | Output1 = InstanceOutput { 14 | SourceOp = "ColorSpaceTransform2", 15 | Source = "Output", 16 | } 17 | }, 18 | ViewInfo = GroupInfo { 19 | Pos = { 817.333, 86.6061 }, 20 | Flags = { 21 | AllowPan = false, 22 | AutoSnap = true, 23 | RemoveRouters = true 24 | }, 25 | Size = { 264.667, 67.5758, 132.333, 24.2424 }, 26 | Direction = "Horizontal", 27 | PipeStyle = "Direct", 28 | Scale = 1, 29 | Offset = { -817.333, -78.1212 } 30 | }, 31 | Tools = ordered() { 32 | ColorSpaceTransform2 = ofx.com.blackmagicdesign.resolvefx.ColorSpaceTransformV2 { 33 | Inputs = { 34 | Source = Input { 35 | SourceOp = "ColorSpaceTransform1", 36 | Source = "Output", 37 | }, 38 | colorSpaceTransformGroup = Input { Value = 1, }, 39 | inputColorSpace = Input { Value = FuID { "TIMELINE_COLORSPACE" }, }, 40 | inputGamma = Input { Value = FuID { "AUTO_GAMMA" }, }, 41 | outputColorSpace = Input { Value = FuID { "TIMELINE_COLORSPACE" }, }, 42 | outputGamma = Input { Value = FuID { "LINEAR_GAMMA" }, }, 43 | isRec2390ScalingEnabled = Input { Value = 0, }, 44 | toneMappingGroup = Input { Value = 1, }, 45 | tmType = Input { Value = FuID { "TM_NONE" }, }, 46 | isSrcLumMaxCustomEnabled = Input { 47 | Value = 0, 48 | Disabled = true, 49 | }, 50 | srcLumMax = Input { 51 | Value = 100, 52 | Disabled = true, 53 | }, 54 | isDstLumMaxCustomEnabled = Input { 55 | Value = 0, 56 | Disabled = true, 57 | }, 58 | dstLumMax = Input { 59 | Value = 100, 60 | Disabled = true, 61 | }, 62 | srcLumAvg = Input { 63 | Value = 9, 64 | Disabled = true, 65 | }, 66 | satRolloffStart = Input { Value = 100, }, 67 | satRolloffLimit = Input { Value = 10000, }, 68 | gamutMappingGroup = Input { Value = 1, }, 69 | gmType = Input { Value = FuID { "GM_NONE" }, }, 70 | satKnee = Input { 71 | Value = 0.899999976158142, 72 | Disabled = true, 73 | }, 74 | satMax = Input { 75 | Value = 1, 76 | Disabled = true, 77 | }, 78 | advancedGroup = Input { Value = 1, }, 79 | doFwdOOTF = Input { Value = 0, }, 80 | doInvOOTF = Input { Value = 0, }, 81 | doCAT = Input { Value = 1, }, 82 | blendGroup = Input { Value = 0, }, 83 | blendIn = Input { Value = 1, }, 84 | blend = Input { Value = 0, }, 85 | ignoreContentShape = Input { Value = 0, }, 86 | legacyIsProcessRGBOnly = Input { Value = 0, }, 87 | refreshTrigger = Input { Value = 1, }, 88 | resolvefxVersion = Input { Value = "1.1", }, 89 | }, 90 | ViewInfo = OperatorInfo { Pos = { 886.667, 87.9697 } }, 91 | }, 92 | ColorSpaceTransform1 = ofx.com.blackmagicdesign.resolvefx.ColorSpaceTransformV2 { 93 | Inputs = { 94 | Source = Input { 95 | SourceOp = "Background1", 96 | Source = "Output", 97 | }, 98 | colorSpaceTransformGroup = Input { Value = 1, }, 99 | inputColorSpace = Input { Value = FuID { "REC709_COLORSPACE" }, }, 100 | inputGamma = Input { Value = FuID { "TWOPOINTFOUR_GAMMA" }, }, 101 | outputColorSpace = Input { Value = FuID { "TIMELINE_COLORSPACE" }, }, 102 | outputGamma = Input { Value = FuID { "AUTO_GAMMA" }, }, 103 | isRec2390ScalingEnabled = Input { 104 | Value = 0, 105 | Disabled = true, 106 | }, 107 | toneMappingGroup = Input { Value = 1, }, 108 | tmType = Input { Value = FuID { "TM_DRT_V2" }, }, 109 | isSrcLumMaxCustomEnabled = Input { Value = 1, }, 110 | srcLumMax = Input { Value = 100, }, 111 | isDstLumMaxCustomEnabled = Input { Value = 1, }, 112 | dstLumMax = Input { Value = 4000, }, 113 | srcLumAvg = Input { Value = 9, }, 114 | satRolloffStart = Input { Value = 100, }, 115 | satRolloffLimit = Input { Value = 10000, }, 116 | gamutMappingGroup = Input { Value = 1, }, 117 | gmType = Input { Value = FuID { "GM_NONE" }, }, 118 | satKnee = Input { 119 | Value = 0.899999976158142, 120 | Disabled = true, 121 | }, 122 | satMax = Input { 123 | Value = 1, 124 | Disabled = true, 125 | }, 126 | advancedGroup = Input { Value = 1, }, 127 | doFwdOOTF = Input { Value = 0, }, 128 | doInvOOTF = Input { Value = 1, }, 129 | doCAT = Input { Value = 1, }, 130 | blendGroup = Input { Value = 0, }, 131 | blendIn = Input { Value = 1, }, 132 | blend = Input { Value = 0, }, 133 | ignoreContentShape = Input { Value = 0, }, 134 | legacyIsProcessRGBOnly = Input { Value = 0, }, 135 | refreshTrigger = Input { Value = 1, }, 136 | resolvefxVersion = Input { Value = "1.1", }, 137 | }, 138 | ViewInfo = OperatorInfo { Pos = { 748, 86.7576 } }, 139 | } 140 | }, 141 | } 142 | } 143 | } -------------------------------------------------------------------------------- /Deprecated/Fusion/Scripts/Comp/Fusion Hotkey Manager.lua: -------------------------------------------------------------------------------- 1 | app:DoAction("App_CustomizeHotkeys", {}) 2 | -------------------------------------------------------------------------------- /Deprecated/Fusion/Scripts/Tool/RCM Color Shift Fix Old.lua: -------------------------------------------------------------------------------- 1 | --deprecated 2 | --only support rec.709 & DaVinci Wide Gamut 3 | project = resolve:GetProjectManager():GetCurrentProject() 4 | color_science_mode = project:GetSetting('colorScienceMode') 5 | color_space_timeline = project:GetSetting('colorSpaceTimeline') 6 | 7 | if color_science_mode == 'davinciYRGBColorManagedv2' then 8 | mediaOutNode = comp:FindToolByID("MediaOut") 9 | mediaInNode = comp:FindToolByID("MediaIn") 10 | comp:SetActiveTool(mediaOutNode) 11 | 12 | local addTransformNode = true 13 | local addDisplayNode = true 14 | if mediaInNode and mediaInNode:GetData('MediaProps').MEDIA_IS_SOURCE_RES then 15 | addTransformNode = false 16 | if mediaInNode:GetData('MediaProps').MEDIA_FORMAT_TYPE == 'DNG' then 17 | addDisplayNode = false 18 | end 19 | end 20 | 21 | if mediaOutNode then 22 | if color_space_timeline == 'Rec.709 Gamma 2.4' then 23 | -- Add Color Space Transform 24 | if addDisplayNode and not comp:FindTool("Rec709ColorSpaceDisplay") then 25 | comp:DoAction("AddSetting", { filename = "Macros:/Rec709 Color Space Display.setting" }) 26 | end 27 | 28 | -- Add MediaOut Display Color Space Transform 29 | if addTransformNode and not comp:FindTool("Rec709ColorSpaceTransform") then 30 | if mediaOutNode:FindMainInput(1) and mediaOutNode:FindMainInput(1):GetConnectedOutput() then 31 | connectedNode = mediaOutNode:FindMainInput(1):GetConnectedOutput():GetTool() 32 | while connectedNode.ParentTool do 33 | connectedNode = connectedNode.ParentTool 34 | end 35 | comp:SetActiveTool(connectedNode) 36 | comp:DoAction("AddSetting", { filename = "Macros:/Rec709 Color Space Transform.setting" }) 37 | else 38 | comp:SetActiveTool() 39 | comp:DoAction("AddSetting", { filename = "Macros:/Rec709 Color Space Transform.setting" }) 40 | mediaOutNode.Input:ConnectTo(comp.ActiveTool.Output) 41 | end 42 | end 43 | elseif color_space_timeline == 'DaVinci WG' then 44 | -- Add Color Space Transform 45 | if addDisplayNode and not comp:FindTool("WideGamutColorSpaceDisplay") then 46 | comp:DoAction("AddSetting", { filename = "Macros:/Wide Gamut Color Space Display.setting" }) 47 | end 48 | 49 | -- Add MediaOut Display Color Space Transform 50 | if addTransformNode and not comp:FindTool("WideGamutColorSpaceTransform") then 51 | if mediaOutNode:FindMainInput(1) and mediaOutNode:FindMainInput(1):GetConnectedOutput() then 52 | connectedNode = mediaOutNode:FindMainInput(1):GetConnectedOutput():GetTool() 53 | while connectedNode.ParentTool do 54 | connectedNode = connectedNode.ParentTool 55 | end 56 | comp:SetActiveTool(connectedNode) 57 | comp:DoAction("AddSetting", { filename = "Macros:/Wide Gamut Color Space Transform.setting" }) 58 | else 59 | comp:SetActiveTool() 60 | comp:DoAction("AddSetting", { filename = "Macros:/Wide Gamut Color Space Transform.setting" }) 61 | mediaOutNode.Input:ConnectTo(comp.ActiveTool.Output) 62 | end 63 | end 64 | end 65 | -- Disable Viewer LUT 66 | if addTransformNode and comp:GetPreviewList().LeftView.View.CurrentViewer then 67 | comp:GetPreviewList().LeftView.View.CurrentViewer:EnableLUT(false) 68 | end 69 | if comp:FindTool("Rec709ColorSpaceDisplay") then 70 | comp:GetPreviewList().RightView:ViewOn(comp:FindTool("Rec709ColorSpaceDisplay")) 71 | if comp:GetPreviewList().RightView.View.CurrentViewer then 72 | comp:GetPreviewList().RightView.View.CurrentViewer:EnableLUT(false) 73 | end 74 | elseif comp:FindTool("WideGamutColorSpaceDisplay") then 75 | comp:GetPreviewList().RightView:ViewOn(comp:FindTool("WideGamutColorSpaceDisplay")) 76 | if comp:GetPreviewList().RightView.View.CurrentViewer then 77 | comp:GetPreviewList().RightView.View.CurrentViewer:EnableLUT(false) 78 | end 79 | end 80 | 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /Deprecated/Fusion/Scripts/Tool/RCM Color Shift Fix.lua: -------------------------------------------------------------------------------- 1 | project = resolve:GetProjectManager():GetCurrentProject() 2 | project_settings = project:GetSetting() 3 | color_science_mode = project_settings['colorScienceMode'] 4 | timeline_working_luminance_mode = project_settings['timelineWorkingLuminanceMode'] 5 | timeline_working_luminance = project_settings['timelineWorkingLuminance'] 6 | separate_color_space_and_gamma = project_settings['separateColorSpaceAndGamma'] 7 | color_space_output_gamma = project_settings['colorSpaceOutputGamma'] 8 | color_space_output = project_settings['colorSpaceOutput'] 9 | 10 | source_color_space_rec709 = "Rec.709" 11 | source_color_space_rec2020 = "Rec.2020" 12 | source_color_space_dw = "DaVinci WG" 13 | 14 | source_gamma_twoPointTwo = "Gamma 2.2" 15 | source_gamma_twoPointFour = "Gamma 2.4" 16 | source_gamma_hlg = "Rec.2100 HLG" 17 | source_gamma_pq = "Rec.2100 ST2084" 18 | source_gamma_di = "DaVinci Intermediate" 19 | 20 | target_color_space_default = "REC709_COLORSPACE" 21 | target_color_space_rec2020 = "REC2020_COLORSPACE" 22 | target_color_space_dw = "DWG_COLORSPACE" 23 | 24 | target_gamma_default = "TWOPOINTFOUR_GAMMA" 25 | target_gamma_2_2 = "TWOPOINTTWO_GAMMA" 26 | target_gamma_hlg = "GAMMA_REC2100_HLG_EOTF" 27 | target_gamma_pq = "GAMMA_REC2100_PQ_EOTF" 28 | target_gamma_di = "DAV_INTER_OETF_GAMMA" 29 | 30 | version_greater_equal_than_18_1 = false 31 | local versions = resolve:GetVersion() 32 | if versions[1] > 18 or (versions[1] == 18 and versions[2] >= 1) then 33 | version_greater_equal_than_18_1 = true 34 | end 35 | 36 | local function GetLuminance() 37 | if timeline_working_luminance_mode == 'Custom' then 38 | timeline_luminance = timeline_working_luminance 39 | else 40 | if string.find(timeline_working_luminance_mode, '/') then 41 | timeline_luminance = string.match(timeline_working_luminance_mode, '.*%/(%d+)') 42 | else 43 | timeline_luminance = string.match(timeline_working_luminance_mode, '[SH]DR (%d+)') 44 | end 45 | end 46 | end 47 | 48 | local function GetOutputColorSpaceAndGamma() 49 | if tonumber(separate_color_space_and_gamma) == 1 then 50 | source_output_color_space = color_space_output 51 | source_output_gamma = color_space_output_gamma 52 | elseif tonumber(separate_color_space_and_gamma) == 0 then 53 | if color_space_output == "Rec.709 Gamma 2.4" then 54 | source_output_color_space = source_color_space_rec709 55 | source_output_gamma = source_gamma_twoPointFour 56 | elseif color_space_output == "Rec.709 Gamma 2.2" then 57 | source_output_color_space = source_color_space_rec709 58 | source_output_gamma = source_gamma_twoPointTwo 59 | elseif color_space_output == "Rec.2020 Gamma 2.4" then 60 | source_output_color_space = source_color_space_rec2020 61 | source_output_gamma = source_gamma_twoPointFour 62 | elseif color_space_output == "Rec.2100 HLG" then 63 | source_output_color_space = source_color_space_rec2020 64 | source_output_gamma = source_gamma_hlg 65 | elseif color_space_output == "Rec.2100 ST2084" then 66 | source_output_color_space = source_color_space_rec2020 67 | source_output_gamma = source_gamma_pq 68 | elseif color_space_output == "DaVinci WG" then 69 | source_output_color_space = source_color_space_dw 70 | source_output_gamma = source_gamma_di 71 | end 72 | end 73 | 74 | if source_output_color_space == source_color_space_rec709 then 75 | target_color_space = target_color_space_default 76 | elseif source_output_color_space == source_color_space_rec2020 then 77 | target_color_space = target_color_space_rec2020 78 | elseif source_output_color_space == source_color_space_dw then 79 | target_color_space = target_color_space_dw 80 | end 81 | 82 | if source_output_gamma == source_gamma_twoPointFour then 83 | target_gamma = target_gamma_default 84 | elseif source_output_gamma == source_gamma_twoPointTwo then 85 | target_gamma = target_gamma_2_2 86 | elseif source_output_gamma == source_gamma_hlg then 87 | target_gamma = target_gamma_hlg 88 | elseif source_output_gamma == source_gamma_pq then 89 | target_gamma = target_gamma_pq 90 | elseif source_output_gamma == source_gamma_di then 91 | target_gamma = target_gamma_di 92 | end 93 | end 94 | 95 | local function ChangeTransform1(tool) 96 | if version_greater_equal_than_18_1 then 97 | tool.tmType = "TM_NONE" 98 | else 99 | tool.dstLumMax = tonumber(timeline_luminance) 100 | end 101 | tool.inputColorSpace = target_color_space 102 | tool.inputGamma = target_gamma 103 | tool.doInvOOTF = 1 104 | end 105 | 106 | local function change_display2(tool) 107 | if version_greater_equal_than_18_1 then 108 | tool.tmType = "TM_NONE" 109 | else 110 | tool.srcLumMax = tonumber(timeline_luminance) 111 | end 112 | tool.outputColorSpace = target_color_space 113 | tool.outputGamma = target_gamma 114 | tool.doFwdOOTF = 1 115 | end 116 | 117 | if color_science_mode == 'davinciYRGBColorManagedv2' then 118 | mediaOutNode = comp:FindToolByID("MediaOut") 119 | mediaInNode = comp:FindToolByID("MediaIn") 120 | 121 | if mediaOutNode then 122 | -- Step1. Add Color Space Transform Macro 123 | if not comp:FindTool("RCMColorSpaceTransform") and not comp:FindTool("RCMFusionTransform1") then 124 | if mediaOutNode:FindMainInput(1) and mediaOutNode:FindMainInput(1):GetConnectedOutput() and mediaOutNode:FindMainInput(1):GetConnectedOutput():GetTool() ~= mediaInNode then 125 | connectedNode = mediaOutNode:FindMainInput(1):GetConnectedOutput():GetTool() 126 | while connectedNode.ParentTool do 127 | connectedNode = connectedNode.ParentTool 128 | end 129 | comp:SetActiveTool(connectedNode) 130 | comp:DoAction("AddSetting", { filename = "Macros:/RCM Color Space Transform.setting" }) 131 | else 132 | comp:SetActiveTool() 133 | comp:DoAction("AddSetting", { filename = "Macros:/RCM Color Space Transform.setting" }) 134 | end 135 | end 136 | 137 | -- Step2. Add Display Fix Macro 138 | if not comp:FindTool("RCMColorSpaceDisplay") and not comp:FindTool("RCMFusionDisplay1") then 139 | comp:SetActiveTool(mediaOutNode) 140 | comp:DoAction("AddSetting", { filename = "Macros:/RCM Color Space Display.setting" }) 141 | end 142 | 143 | -- Step3. Change Params 144 | GetLuminance() 145 | GetOutputColorSpaceAndGamma() 146 | group_transform = comp:FindTool("RCMColorSpaceTransform") 147 | group_display = comp:FindTool("RCMColorSpaceDisplay") 148 | if group_transform then 149 | for _, child in ipairs(group_transform:GetChildrenList()) do 150 | if child:GetInput('inputColorSpace') ~= "TIMELINE_COLORSPACE" then 151 | ChangeTransform1(child) 152 | end 153 | end 154 | end 155 | if group_display then 156 | for _, child in ipairs(group_display:GetChildrenList()) do 157 | if child:GetInput('outputColorSpace') ~= "TIMELINE_COLORSPACE" then 158 | change_display2(child) 159 | end 160 | end 161 | end 162 | if comp:FindTool("RCMFusionTransform1") then 163 | ChangeTransform1(comp:FindTool("RCMFusionTransform1")) 164 | end 165 | if comp:FindTool("RCMFusionDisplay2") then 166 | change_display2(comp:FindTool("RCMFusionDisplay2")) 167 | end 168 | 169 | -- Step4. Disable Viewer LUT 170 | if needTransformNode and comp:GetPreviewList().LeftView.View.CurrentViewer then 171 | comp:GetPreviewList().LeftView.View.CurrentViewer:EnableLUT(false) 172 | end 173 | displayNode = comp:FindTool("RCMColorSpaceDisplay") 174 | if not displayNode then 175 | displayNode = comp:FindTool("RCMFusionDisplay2") 176 | end 177 | if displayNode then 178 | comp:GetPreviewList().RightView:ViewOn(displayNode) 179 | if comp:GetPreviewList().RightView.View.CurrentViewer then 180 | comp:GetPreviewList().RightView.View.CurrentViewer:EnableLUT(false) 181 | end 182 | end 183 | 184 | end 185 | end 186 | -------------------------------------------------------------------------------- /Deprecated/Fusion/Scripts/Utility/RCM Color Space Match.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """For DaVinci Resolve Space Match""" 3 | __author__ = "Michael" 4 | __version__ = "0.5.0" 5 | __license__ = "MIT" 6 | 7 | import logging 8 | import os 9 | import re 10 | 11 | from get_resolve import get_bmd 12 | 13 | metadata_parser = __import__("Metadata Parser") 14 | 15 | gui_mode = 1 16 | 17 | 18 | class ColorSpaceMatchRule: 19 | def __init__(self, manufacturer, gamma_notes, color_space_notes, input_color_space): 20 | self.manufacturer = manufacturer 21 | self.gamma_notes = gamma_notes 22 | self.color_space_notes = color_space_notes 23 | self.input_color_space = input_color_space 24 | 25 | 26 | # create logger 27 | logger = logging.getLogger("RCM_Color_Space_Match") 28 | logger.setLevel(logging.DEBUG) 29 | 30 | # create console handler and set level to debug 31 | ch = logging.StreamHandler() 32 | ch.setLevel(logging.DEBUG) 33 | 34 | # create formatter 35 | formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') 36 | 37 | # add formatter to ch 38 | ch.setFormatter(formatter) 39 | 40 | # add ch to logger 41 | logger.addHandler(ch) 42 | 43 | color_space_match_list = [ColorSpaceMatchRule("Atomos", "CLog", "Cinema", "Canon Cinema Gamut/Canon Log"), 44 | ColorSpaceMatchRule("Atomos", "CLog2", "Cinema", "Canon Cinema Gamut/Canon Log2"), 45 | ColorSpaceMatchRule("Atomos", "CLog3", "Cinema", "Canon Cinema Gamut/Canon Log3"), 46 | ColorSpaceMatchRule("Atomos", "F-Log", "F-Gamut", "FujiFilm F-Log"), 47 | ColorSpaceMatchRule("Atomos", "V-Log", "V-Gamut", "Panasonic V-Gamut/V-Log"), 48 | ColorSpaceMatchRule("Atomos", "SLog3", "SGamut3", "S-Gamut3/S-Log3"), 49 | ColorSpaceMatchRule("Atomos", "SLog3", "SGamut3Cine", "S-Gamut3.Cine/S-Log3"), 50 | ColorSpaceMatchRule("Atomos", "N-Log", "BT.2020", "Nikon N-Log"), 51 | ColorSpaceMatchRule("Atomos", "HLG", "BT.2020", "Rec.2100 HLG"), 52 | 53 | ColorSpaceMatchRule("Fujifilm", "F-log", "", "FujiFilm F-Log"), 54 | 55 | ColorSpaceMatchRule("Panasonic", "V-Log", "V-Gamut", "Panasonic V-Gamut/V-Log"), 56 | 57 | ColorSpaceMatchRule("Sony", "s-log2", "s-gamut", "S-Gamut/S-Log2"), 58 | ColorSpaceMatchRule("Sony", "s-log3-cine", "s-gamut3-cine", "S-Gamut3.Cine/S-Log3"), 59 | ColorSpaceMatchRule("Sony", "s-log3", "s-gamut3", "S-Gamut3/S-Log3"), 60 | ] 61 | color_space_match_map = {} 62 | for item in color_space_match_list: 63 | color_space_match_map[(item.gamma_notes, item.color_space_notes)] = item.input_color_space 64 | 65 | match_rules = {"rules": []} 66 | manufacturers = [] 67 | for item in color_space_match_list: 68 | if item.manufacturer in manufacturers: 69 | index = manufacturers.index(item.manufacturer) 70 | match_rules["rules"][index]["details"].append( 71 | {"Gamma Notes": item.gamma_notes, "Color Space Notes": item.color_space_notes, 72 | "Input Color Space": item.input_color_space}) 73 | else: 74 | manufacturers.append(item.manufacturer) 75 | match_rules["rules"].append({"manufacturer": item.manufacturer, "details": [ 76 | {"Gamma Notes": item.gamma_notes, "Color Space Notes": item.color_space_notes, 77 | "Input Color Space": item.input_color_space}]}) 78 | 79 | 80 | def main_window(): 81 | # some element IDs 82 | win_id = "com.xiaoli.RCMColorSpaceMatch" # should be unique for single instancing 83 | tree_id = "MatchTree" 84 | gamma_notes = "Gamma Notes" 85 | color_space_notes = "Color Space Notes" 86 | input_color_space = "Input Color Space" 87 | 88 | # check for existing instance 89 | win = ui.FindWindow(win_id) 90 | if win: 91 | win.Show() 92 | win.Raise() 93 | exit() 94 | 95 | # define the window UI layout 96 | win = dispatcher.AddWindow({ 97 | 'ID': win_id, 98 | 'WindowTitle': "RCM Color Space Match", 99 | }, 100 | ui.VGroup([ 101 | # color space match rule 102 | ui.Tree({"ID": tree_id}), 103 | 104 | ui.VGap(2), 105 | 106 | ui.HGroup({"Weight": 0}, [ 107 | ui.CheckBox( 108 | {"ID": "EnableMetadataParser", "Text": "Enable Metadata Parser", "Weight": 0, "Checked": True}), 109 | ui.CheckBox( 110 | {"ID": "EnableDataLevelAdjustment", "Text": "Enable Assign Atomos Clips' Data Level", "Weight": 0}), 111 | ui.ComboBox({"ID": "DataLevelAdjustmentType", "Weight": 1}) 112 | ]), 113 | 114 | ui.VGap(2), 115 | 116 | ui.HGroup({"Weight": 0}, [ 117 | ui.Button({"Text": "Match", "ID": "ExecuteButton", "Weight": 0}), 118 | ui.HGap(5), 119 | ui.Label({'Font': ui.Font({'Family': "Times New Roman"}), "ID": "InfoLabel"}) 120 | ]), 121 | ]) 122 | ) 123 | win.Resize([700, 480]) 124 | win.RecalcLayout() 125 | 126 | def init_tree(): 127 | items = win.GetItems() 128 | 129 | # Add a header row. 130 | hdr = items[tree_id].NewItem() 131 | hdr["Text"][0] = gamma_notes 132 | hdr["Text"][1] = color_space_notes 133 | hdr["Text"][2] = input_color_space 134 | items[tree_id].SetHeaderItem(hdr) 135 | 136 | # Number of columns in the Tree list 137 | items[tree_id]["ColumnCount"] = 3 138 | 139 | # Resize the Columns 140 | items[tree_id]["ColumnWidth"][0] = 200 141 | items[tree_id]["ColumnWidth"][1] = 200 142 | items[tree_id]["ColumnWidth"][2] = 260 143 | 144 | def init_combo(): 145 | items = win.GetItems() 146 | 147 | items["DataLevelAdjustmentType"].AddItem('For Log and Legal Clips') 148 | items["DataLevelAdjustmentType"].AddItem('For All Clips') 149 | 150 | def show_message(message, t=0): 151 | if t == 0: 152 | win.GetItems()["InfoLabel"]["Text"] = f"{message}" 153 | elif t == 1: 154 | win.GetItems()["InfoLabel"]["Text"] = f"{message}" 155 | 156 | def load_color_space_match_rule(): 157 | for manufacturerRule in match_rules["rules"]: 158 | items = win.GetItems() 159 | item = items[tree_id].NewItem() 160 | item["Text"][0] = manufacturerRule["manufacturer"] 161 | items[tree_id].AddTopLevelItem(item) 162 | for rule in manufacturerRule["details"]: 163 | item_child = items[tree_id].NewItem() 164 | item_child["Text"][0] = rule[gamma_notes] 165 | item_child["Text"][1] = rule[color_space_notes] 166 | item_child["Text"][2] = rule[input_color_space] 167 | item.AddChild(item_child) 168 | item["Expanded"] = True 169 | 170 | def click_execute_button(ev): 171 | logger.info("Start Processing.") 172 | show_message("Processing...") 173 | items = win.GetItems() 174 | if execute(assign_data_level_enabled=items["EnableDataLevelAdjustment"]["Checked"], 175 | assign_type=items["DataLevelAdjustmentType"]["CurrentIndex"], 176 | parse_metadata_enabled=items["EnableMetadataParser"]["Checked"]): 177 | show_message("All Down. Have Fun!") 178 | else: 179 | show_message("Some process failed, please check console log details.", 1) 180 | 181 | def close(ev): 182 | dispatcher.ExitLoop() 183 | 184 | init_tree() 185 | init_combo() 186 | load_color_space_match_rule() 187 | 188 | # assign event handlers 189 | win.On[win_id].Close = close 190 | win.On["ExecuteButton"].Clicked = click_execute_button 191 | win.Show() 192 | dispatcher.RunLoop() 193 | win.Hide() 194 | 195 | 196 | def get_clips(folder, result): 197 | result.extend(folder.GetClipList()) 198 | sub_folders = folder.GetSubFolders() 199 | for sub_folder in sub_folders.values(): 200 | get_clips(sub_folder, result) 201 | 202 | 203 | def assign_data_level(clip, metadata, assign_type): 204 | if metadata.get("Camera Manufacturer") == "Atomos": 205 | gamma_notes = metadata.get("Gamma Notes") if metadata.get("Gamma Notes") else "" 206 | camera_notes = metadata.get("Camera Notes") if metadata.get("Camera Notes") else "" 207 | if assign_type == 0 and ( 208 | re.search("LOG", gamma_notes, re.IGNORECASE) or "Range: Legal" in camera_notes) or assign_type == 1: 209 | if clip.SetClipProperty("Data Level", "Full"): 210 | logger.debug(f"Assign {clip.GetName()} data level full successfully.") 211 | else: 212 | logger.error(f"Assign {clip.GetName()} data level full failed.") 213 | return False 214 | return True 215 | 216 | 217 | def parse_metadata(clip, lib): 218 | file_path = clip.GetClipProperty("File Path") 219 | if len(file_path) > 0: 220 | resolve_meta_dict = lib.DRProcessMediaFile(file_path.encode("utf-8")).get_dict() 221 | if resolve_meta_dict: 222 | if not resolve_meta_dict["IsSupportMedia"]: 223 | logger.warning(f"{os.path.basename(file_path)} Not Supported.") 224 | else: 225 | del resolve_meta_dict["IsSupportMedia"] 226 | meta = {k: v.decode("utf-8") for k, v in resolve_meta_dict.items() if v} 227 | if not meta: 228 | logger.debug(f"Ignore clip {os.path.basename(file_path)}.") 229 | else: 230 | if clip.SetMetadata(meta): 231 | logger.debug(f"Process {os.path.basename(file_path)} Successfully.") 232 | return meta 233 | else: 234 | logger.error(f"Failed to set {os.path.basename(file_path)} metadata!") 235 | else: 236 | logger.error(f"Failed to parse clip {clip.GetName()}") 237 | return None 238 | 239 | 240 | def execute(assign_data_level_enabled=True, assign_type=0, parse_metadata_enabled=True): 241 | logger.info("Start match input color space and apply custom grading rules.") 242 | resolve = bmd.scriptapp("Resolve") 243 | project_manager = resolve.GetProjectManager() 244 | project = project_manager.GetCurrentProject() 245 | media_pool = project.GetMediaPool() 246 | root_folder = media_pool.GetRootFolder() 247 | success = True 248 | is_rcm = "davinciYRGBColorManaged" in project.GetSetting("colorScienceMode") 249 | clips = [] 250 | 251 | get_clips(root_folder, clips) 252 | lib = {} 253 | if parse_metadata_enabled: 254 | lib = metadata_parser.get_cdll_lib() 255 | for clip in clips: 256 | metadata = parse_metadata(clip, lib) if parse_metadata_enabled else clip.GetMetadata() 257 | codec = clip.GetClipProperty('Video Codec') 258 | if not metadata or 'RED' == codec.upper() or 'RAW' in codec.upper(): 259 | continue 260 | if is_rcm: 261 | gamma_notes = metadata.get("Gamma Notes") if metadata.get("Gamma Notes") else "" 262 | color_space_rotes = metadata.get("Color Space Notes") if metadata.get("Color Space Notes") else "" 263 | input_color_space = color_space_match_map.get((gamma_notes, color_space_rotes)) 264 | if input_color_space: 265 | if clip.GetClipProperty("Input Color Space") == input_color_space: 266 | logger.debug(f"Already Set {clip.GetName()} Input Color Space") 267 | continue 268 | if clip.SetClipProperty("Input Color Space", input_color_space): 269 | logger.debug(f"{clip.GetName()} Set Input Color Space {input_color_space} Successfully.") 270 | else: 271 | success = False 272 | logger.error(f"{clip.GetName()} Set Input Color Space {input_color_space} Failed!") 273 | else: 274 | logger.warning(f"{clip.GetName()} Does Not Found Input Color Space Match Rule!") 275 | if assign_data_level_enabled: 276 | if not assign_data_level(clip, metadata, assign_type): 277 | success = False 278 | if success: 279 | logger.info("All Done, Have Fun!") 280 | return True 281 | else: 282 | logger.warning("Some error happened, please check console details.") 283 | return False 284 | 285 | 286 | if __name__ == '__main__': 287 | bmd = get_bmd() 288 | if "gui_mode" in locals().keys() and gui_mode: 289 | fusion = bmd.scriptapp("Fusion") 290 | ui = fusion.UIManager 291 | dispatcher = bmd.UIDispatcher(ui) 292 | main_window() 293 | else: 294 | execute() 295 | -------------------------------------------------------------------------------- /Deprecated/Fusion/Scripts/Utility/RCM Fusion Fix.lua: -------------------------------------------------------------------------------- 1 | project = resolve:GetProjectManager():GetCurrentProject() 2 | project_settings = project:GetSetting() 3 | color_science_mode = project_settings['colorScienceMode'] 4 | timeline_working_luminance_mode = project_settings['timelineWorkingLuminanceMode'] 5 | timeline_working_luminance = project_settings['timelineWorkingLuminance'] 6 | separate_color_space_and_gamma = project_settings['separateColorSpaceAndGamma'] 7 | color_space_output_gamma = project_settings['colorSpaceOutputGamma'] 8 | color_space_output = project_settings['colorSpaceOutput'] 9 | 10 | source_color_space_rec709 = "Rec.709" 11 | source_color_space_rec2020 = "Rec.2020" 12 | source_color_space_dw = "DaVinci WG" 13 | 14 | source_gamma_twoPointFour = "Gamma 2.4" 15 | source_gamma_hlg = "Rec.2100 HLG" 16 | source_gamma_pq = "Rec.2100 ST2084" 17 | source_gamma_di = "DaVinci Intermediate" 18 | 19 | target_color_space_default = "REC709_COLORSPACE" 20 | target_color_space_rec2020 = "REC2020_COLORSPACE" 21 | target_color_space_dw = "DWG_COLORSPACE" 22 | 23 | target_gamma_default = "TWOPOINTFOUR_GAMMA" 24 | target_gamma_hlg = "GAMMA_REC2100_HLG_EOTF" 25 | target_gamma_pq = "GAMMA_REC2100_PQ_EOTF" 26 | target_gamma_di = "DAV_INTER_OETF_GAMMA" 27 | 28 | local function getLuminance() 29 | if timeline_working_luminance_mode == 'Custom' then 30 | timeline_luminance = timeline_working_luminance 31 | else 32 | if string.find(timeline_working_luminance_mode, '/') then 33 | timeline_luminance = string.match(timeline_working_luminance_mode, '.*%/(%d+)') 34 | else 35 | timeline_luminance = string.match(timeline_working_luminance_mode, '[SH]DR (%d+)') 36 | end 37 | end 38 | end 39 | 40 | local function getOutputColorSpaceAndGamma() 41 | if tonumber(separate_color_space_and_gamma) == 1 then 42 | source_output_color_space = color_space_output 43 | source_output_gamma = color_space_output_gamma 44 | elseif tonumber(separate_color_space_and_gamma) == 0 then 45 | if color_space_output == "Rec.709 Gamma 2.4" then 46 | source_output_color_space = source_color_space_rec709 47 | source_output_gamma = source_gamma_twoPointFour 48 | elseif color_space_output == "Rec.2020 Gamma 2.4" then 49 | source_output_color_space = source_color_space_rec2020 50 | source_output_gamma = source_gamma_twoPointFour 51 | elseif color_space_output == "Rec.2100 HLG" then 52 | source_output_color_space = source_color_space_rec2020 53 | source_output_gamma = source_gamma_hlg 54 | elseif color_space_output == "Rec.2100 ST2084" then 55 | source_output_color_space = source_color_space_rec2020 56 | source_output_gamma = source_gamma_pq 57 | elseif color_space_output == "DaVinci WG" then 58 | source_output_color_space = source_color_space_dw 59 | source_output_gamma = source_gamma_di 60 | end 61 | end 62 | 63 | if source_output_color_space == source_color_space_rec709 then 64 | target_color_space = target_color_space_default 65 | elseif source_output_color_space == source_color_space_rec2020 then 66 | target_color_space = target_color_space_rec2020 67 | elseif source_output_color_space == source_color_space_dw then 68 | target_color_space = target_color_space_dw 69 | end 70 | 71 | if source_output_gamma == source_gamma_twoPointFour then 72 | target_gamma = target_gamma_default 73 | elseif source_output_gamma == source_gamma_hlg then 74 | target_gamma = target_gamma_hlg 75 | elseif source_output_gamma == source_gamma_pq then 76 | target_gamma = target_gamma_pq 77 | elseif source_output_gamma == source_gamma_di then 78 | target_gamma = target_gamma_di 79 | end 80 | end 81 | 82 | function rcm_fusion_fix(videoItem, comp) 83 | local changed = false 84 | mediaOutNode = comp:FindToolByID("MediaOut") 85 | mediaInNode = comp:FindToolByID("MediaIn") 86 | --DaVinci Resolve can not set ActiveTool when a comp has never been opened in fusion 87 | comp:SetActiveTool(mediaOutNode) 88 | runInFusion = false 89 | if comp.ActiveTool then 90 | runInFusion = true 91 | end 92 | 93 | getLuminance() 94 | getOutputColorSpaceAndGamma() 95 | 96 | if mediaOutNode then 97 | local addTransformNode = true 98 | local addDisplayNode = true 99 | if videoItem:GetMediaPoolItem() then 100 | addTransformNode = false 101 | if mediaInNode and mediaInNode:GetData('MediaProps').MEDIA_FORMAT_TYPE == 'DNG' then 102 | addDisplayNode = false 103 | end 104 | end 105 | 106 | -- Add Color Space Transform 107 | if addTransformNode and not comp:FindTool("RCMColorSpaceTransform") then 108 | if not comp:FindTool("RCMFusionTransform1") then 109 | if mediaOutNode:FindMainInput(1) and mediaOutNode:FindMainInput(1):GetConnectedOutput() then 110 | connectedOutput = mediaOutNode:FindMainInput(1):GetConnectedOutput() 111 | connectedNode = connectedOutput:GetTool() 112 | while connectedNode.ParentTool do 113 | connectedNode = connectedNode.ParentTool 114 | end 115 | comp:SetActiveTool(connectedNode) 116 | end 117 | if runInFusion then 118 | tool1 = comp:AddTool('ofx.com.blackmagicdesign.resolvefx.ColorSpaceTransformV2', -32768, -32768) 119 | else 120 | tool1 = comp:AddTool('ofx.com.blackmagicdesign.resolvefx.ColorSpaceTransformV2') 121 | tool1.Source:ConnectTo(connectedOutput) 122 | end 123 | tool1:SetAttrs({ TOOLS_Name = 'RCMFusionTransform1' }) 124 | tool1.inputColorSpace = target_color_space 125 | tool1.inputGamma = target_gamma 126 | tool1.isDstLumMaxCustomEnabled = 1 127 | tool1.dstLumMax = tonumber(timeline_luminance) 128 | tool1.doInvOOTF = 1 129 | changed = true 130 | end 131 | 132 | if not comp:FindTool("RCMFusionTransform2") then 133 | if runInFusion then 134 | tool2 = comp:AddTool('ofx.com.blackmagicdesign.resolvefx.ColorSpaceTransformV2', -32768, -32768) 135 | else 136 | tool2 = comp:AddTool('ofx.com.blackmagicdesign.resolvefx.ColorSpaceTransformV2') 137 | tool2.Source:ConnectTo(tool1.Output) 138 | mediaOutNode.Input:ConnectTo(tool2.Output) 139 | end 140 | tool2:SetAttrs({ TOOLS_Name = 'RCMFusionTransform2' }) 141 | tool2.outputGamma = "LINEAR_GAMMA" 142 | tool2.tmType = "TM_NONE" 143 | changed = true 144 | end 145 | end 146 | 147 | -- Add MediaOut Display Color Space Transform 148 | comp:SetActiveTool(mediaOutNode) 149 | if addDisplayNode and not comp:FindTool("RCMColorSpaceDisplay") then 150 | if not comp:FindTool('RCMFusionDisplay1') then 151 | if runInFusion then 152 | tool3 = comp:AddTool('ofx.com.blackmagicdesign.resolvefx.ColorSpaceTransformV2', -32768, -32768) 153 | else 154 | tool3 = comp:AddTool('ofx.com.blackmagicdesign.resolvefx.ColorSpaceTransformV2') 155 | tool3.Source:ConnectTo(mediaOutNode.Output) 156 | end 157 | tool3:SetAttrs({ TOOLS_Name = 'RCMFusionDisplay1' }) 158 | tool3.inputGamma = "LINEAR_GAMMA" 159 | tool3.tmType = "TM_NONE" 160 | changed = true 161 | end 162 | 163 | if not comp:FindTool('RCMFusionDisplay2') then 164 | if runInFusion then 165 | tool4 = comp:AddTool('ofx.com.blackmagicdesign.resolvefx.ColorSpaceTransformV2', -32768, -32768) 166 | else 167 | tool4 = comp:AddTool('ofx.com.blackmagicdesign.resolvefx.ColorSpaceTransformV2') 168 | tool4.Source:ConnectTo(tool3.Output) 169 | end 170 | tool4:SetAttrs({ TOOLS_Name = 'RCMFusionDisplay2' }) 171 | tool4.outputColorSpace = target_color_space 172 | tool4.outputGamma = target_gamma 173 | tool4.isSrcLumMaxCustomEnabled = 1 174 | tool4.srcLumMax = tonumber(timeline_luminance) 175 | tool4.doFwdOOTF = 1 176 | changed = true 177 | end 178 | end 179 | end 180 | return changed 181 | end 182 | 183 | project = resolve:GetProjectManager():GetCurrentProject() 184 | color_science_mode = project:GetSetting('colorScienceMode') 185 | color_space_timeline = project:GetSetting('colorSpaceTimeline') 186 | n = 0 187 | if color_science_mode == 'davinciYRGBColorManagedv2' then 188 | timeline = project:GetCurrentTimeline() 189 | track_count = timeline:GetTrackCount("video") 190 | 191 | for index = 1, track_count do 192 | items = timeline:GetItemListInTrack("video", index) 193 | if items and table.getn(items) > 0 then 194 | for _, v in ipairs(items) do 195 | count = v:GetFusionCompCount() 196 | if count == 1 then 197 | comp = v:GetFusionCompByIndex(1) 198 | if rcm_fusion_fix(v, comp) then 199 | n = n + 1 200 | end 201 | elseif count > 1 then 202 | print(v:GetName() .. " Fusion comp count more than 1") 203 | break 204 | end 205 | end 206 | end 207 | end 208 | end 209 | 210 | local ui = fu.UIManager 211 | local disp = bmd.UIDispatcher(ui) 212 | 213 | win = disp:AddWindow({ 214 | ID = 'MyWin', 215 | WindowTitle = 'Notification', 216 | Spacing = 10, 217 | 218 | ui:VGroup { 219 | ID = 'root', 220 | 221 | -- Add your GUI elements here: 222 | ui:HGroup { 223 | ui:Label { 224 | Text = n .. ' fusion compositions has been fixed!', 225 | Alignment = { AlignHCenter = true, AlignVCenter = true }, 226 | }, 227 | }, 228 | 229 | ui:HGroup { 230 | Weight = 0, 231 | ui:Button { 232 | ID = 'B', 233 | Text = 'OK', 234 | }, 235 | } 236 | }, 237 | }) 238 | win:Resize({ 400, 120 }); 239 | win:RecalcLayout(); 240 | 241 | -- The window was closed 242 | function win.On.MyWin.Close(ev) 243 | disp:ExitLoop() 244 | end 245 | 246 | -- Add your GUI element based event functions here: 247 | itm = win:GetItems() 248 | 249 | function win.On.B.Clicked(ev) 250 | disp:ExitLoop() 251 | end 252 | 253 | win:Show() 254 | disp:RunLoop() 255 | win:Hide() 256 | -------------------------------------------------------------------------------- /Deprecated/Fusion/Scripts/Utility/Subtitle Conversion.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | __author__ = "Michael" 4 | __version__ = "0.1.0" 5 | __license__ = "MIT" 6 | 7 | import codecs 8 | import datetime 9 | import json 10 | import os 11 | 12 | from get_resolve import get_bmd 13 | 14 | bmd = get_bmd() 15 | fusion = bmd.scriptapp("Fusion") 16 | ui = fusion.UIManager 17 | dispatcher = bmd.UIDispatcher(ui) 18 | 19 | # some element IDs 20 | winID = "com.xiaoli.SubtitleConversion" # should be unique for single instancing 21 | tabsID = "MyTabs" 22 | exec1ID = "Step1ExecuteButton" 23 | exec2ID = "Step2ExecuteButton" 24 | stackID = "MyStack" 25 | fileLabelId = "FileText" 26 | fileButtonId = "FileButton" 27 | folderLabelId = "FolderText" 28 | folderButtonId = "FolderButton" 29 | 30 | # check for existing instance 31 | win = ui.FindWindow(winID) 32 | if win: 33 | win.Show() 34 | win.Raise() 35 | exit() 36 | 37 | # define the window UI layout 38 | win = dispatcher.AddWindow({ 39 | 'ID': winID, 40 | 'Geometry': [600, 300, 600, 200], 41 | 'WindowTitle': "Subtitle Conversion", 42 | }, 43 | ui.VGroup([ 44 | ui.TabBar({ 45 | 'CurrentIndex': 0, 46 | 'ID': 'MyTabs', 47 | }), 48 | 49 | ui.Stack({"Weight": 10, "ID": stackID, }, [ 50 | ui.VGroup([ 51 | ui.VGap(0, 5), 52 | ui.Label({"Text": "In Developing!", 'Font': ui.Font({'Family': "Times New Roman"}), 53 | "Alignment": {"AlignHCenter": True}, "Weight": 0}), 54 | ui.VGap(0, 5) 55 | ]), 56 | 57 | ui.VGroup([ 58 | ui.VGap(0, 10), 59 | 60 | ui.HGroup([ 61 | ui.Label({"Text": 'File:', "Weight": 0.25, 62 | 'Font': ui.Font({'Family': "Times New Roman"})}), 63 | ui.Label( 64 | {"ID": fileLabelId, 'Text': "Please Select JianYing Subtitle Json File Location", 'Weight': 1.5, 65 | 'Font': ui.Font({'Family': "Times New Roman", 'PixelSize': 12})}), 66 | ui.Button({"Text": "Select a File", "ID": fileButtonId, "Weight": 0.25}), 67 | ]), 68 | 69 | ui.HGroup([ 70 | ui.Label({"Text": 'Folder:', "Weight": 0.25, 71 | 'Font': ui.Font({'Family': "Times New Roman"})}), 72 | ui.Label( 73 | {"ID": folderLabelId, 'Text': "Please Select Output Subtitle File Path", 'Weight': 1.5, 74 | 'Font': ui.Font({'Family': "Times New Roman", 'PixelSize': 12})}), 75 | ui.Button({"Text": "Select a Folder", "ID": folderButtonId, "Weight": 0.25}), 76 | ]), 77 | 78 | ui.VGap(0, 10), 79 | 80 | ui.HGroup([ 81 | ui.Button({"Text": "Execute", "ID": "Step2ExecuteButton", "Weight": 0.25}), 82 | ]), 83 | ]), 84 | ]), 85 | 86 | ]) 87 | ) 88 | 89 | # Add your GUI element based event functions here: 90 | items = win.GetItems() 91 | 92 | items[stackID].CurrentIndex = 0 93 | 94 | # Add the items to the ComboBox menu 95 | items[tabsID].AddTab('Export Media') 96 | items[tabsID].AddTab('Generate SRT') 97 | 98 | 99 | # items[] 100 | 101 | # Event handlers 102 | def close(ev): 103 | dispatcher.ExitLoop() 104 | 105 | 106 | def change_tab(ev): 107 | items[stackID].CurrentIndex = ev["Index"] 108 | 109 | 110 | def click_file_button(ev): 111 | selected_path = fusion.RequestFile('') 112 | 113 | print('[File] ', selected_path) 114 | items[fileLabelId]["Text"] = selected_path 115 | 116 | 117 | def click_folder_button(ev): 118 | target_path = fusion.RequestDir() 119 | 120 | print('[folder] ', target_path) 121 | items[folderLabelId]["Text"] = target_path 122 | 123 | 124 | def resolve_handle_subtitle(srt_file): 125 | resolve = bmd.scriptapp("Resolve") 126 | project_manager = resolve.GetProjectManager() 127 | project = project_manager.GetCurrentProject() 128 | media_pool = project.GetMediaPool() 129 | root_folder = media_pool.GetRootFolder() 130 | sub_folders = root_folder.GetSubFolderList() 131 | subtitle_folder = "" 132 | for subFolder in sub_folders: 133 | if subFolder.GetName() == "SubTitles": 134 | subtitle_folder = subFolder 135 | if not subtitle_folder: 136 | subtitle_folder = media_pool.AddSubFolder(root_folder, "SubTitles") 137 | # mediaPool.SetCurrentFolder(subtitleFolder) 138 | # mediaPoolItem = mediaPool.ImportFile([{"FilePath": srt_file}]) 139 | timeline = project.GetCurrentTimeline() 140 | if timeline.GetTrackCount("subtitle") <= 0: 141 | timeline.AddTrack("subtitle") 142 | 143 | 144 | def convert_jianying_json_to_srt(json_file, srt_folder): 145 | f = codecs.open(json_file, mode="r", encoding="utf-8") 146 | data = json.load(f) 147 | time_dict = {} 148 | for track in data["tracks"]: 149 | if track["type"] == "text": 150 | for segment in track["segments"]: 151 | time_dict[segment["material_id"]] = segment["target_timerange"] 152 | f.close() 153 | project = bmd.scriptapp("Resolve").GetProjectManager().GetCurrentProject() 154 | project_name = project.GetName() 155 | time_str = datetime.datetime.now().strftime("%Y%m%d%H%M%S") 156 | 157 | srt_file = os.path.join(srt_folder, "{}_{}.srt".format(project_name, time_str)) 158 | f = codecs.open(srt_file, "w+", encoding="utf-8") 159 | i = 0 160 | for text in data["materials"]["texts"]: 161 | start_and_duration = time_dict[text["id"]] 162 | start_time = str(datetime.timedelta(microseconds=start_and_duration["start"]))[:-3].replace(".", ",") 163 | end_time = str(datetime.timedelta(microseconds=start_and_duration["start"] + start_and_duration["duration"]))[ 164 | :-3].replace(".", ",") 165 | lines = [str(i) + "\n", "{} --> {}\n".format(start_time, end_time), text["content"] + "\n", "\n"] 166 | f.writelines(lines) 167 | i += 1 168 | f.close() 169 | # mediaPool ImportFile can not import srt file 170 | # resolve_handle_subtitle(srt_file) 171 | dispatcher.ExitLoop() 172 | 173 | 174 | def process_srt(ev): 175 | source_json = items[fileLabelId]["Text"] 176 | target_path = items[folderLabelId]["Text"] 177 | if os.path.exists(source_json) and os.path.exists(target_path): 178 | convert_jianying_json_to_srt(source_json, target_path) 179 | else: 180 | if not source_json: 181 | print('[File] is Empty!') 182 | items[fileLabelId]["Text"] = "Please set File Path!" 183 | if not target_path: 184 | print('[Folder] is Empty!') 185 | items[folderLabelId]["Text"] = "Please set Folder Path!" 186 | return 187 | 188 | 189 | # assign event handlers 190 | win.On[winID].Close = close 191 | win.On[tabsID].CurrentChanged = change_tab 192 | win.On[exec2ID].Clicked = process_srt 193 | win.On[fileButtonId].Clicked = click_file_button 194 | win.On[folderButtonId].Clicked = click_folder_button 195 | 196 | # Main dispatcher loop 197 | if __name__ == '__main__': 198 | win.Show() 199 | dispatcher.RunLoop() 200 | win.Hide() 201 | -------------------------------------------------------------------------------- /Deprecated/Fusion/Scripts/Utility/Subtitle Tool.lua: -------------------------------------------------------------------------------- 1 | --MIT License 2 | -- 3 | --Copyright (c) 2021 Michael, https://github.com/fukco 4 | -- 5 | --Permission is hereby granted, free of charge, to any person obtaining a copy 6 | --of this software and associated documentation files (the "Software"), to deal 7 | --in the Software without restriction, including without limitation the rights 8 | --to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | --copies of the Software, and to permit persons to whom the Software is 10 | --furnished to do so, subject to the following conditions: 11 | -- 12 | --The above copyright notice and this permission notice shall be included in all 13 | --copies or substantial portions of the Software. 14 | -- 15 | --THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | --IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | --FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | --AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | --LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | --OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | --SOFTWARE. 22 | 23 | if ffi.os == "Windows" then 24 | --如果修改过安装路径,请修改此处路径,修改为 25 | --jianyingProjectLocation = “C:/your/path” 26 | --bcutProjectLocation = “C:/your/path” 27 | --注意windows拷贝路径请将右斜杠(\)修改为左斜杠(/) 28 | jianyingProjectLocation = os.getenv("userprofile") .. "/AppData/Local/JianyingPro/User Data/Projects/com.lveditor.draft" 29 | bcutProjectLocation = os.getenv("userprofile") .. "/Documents/MYVideoProject" 30 | elseif ffi.os == "OSX" then 31 | jianyingProjectLocation = os.getenv("HOME") .. "/Movies/JianyingPro/User Data/Projects/com.lveditor.draft" 32 | end 33 | 34 | json = require "json" 35 | 36 | local lib 37 | if ffi.os == "Windows" then 38 | lib = ffi.load(fusion:MapPath('LuaModules:/subtitle-tool.dll')) 39 | elseif ffi.os == "OSX" then 40 | if ffi.arch == "x64" then 41 | lib = ffi.load(fusion:MapPath('LuaModules:/subtitle-tool-amd64.dylib')) 42 | elseif ffi.arch == "arm64" then 43 | lib = ffi.load(fusion:MapPath('LuaModules:/subtitle-tool-arm64.dylib')) 44 | end 45 | end 46 | 47 | ffi.cdef [[ 48 | extern __declspec(dllexport) _Bool JExportByProjectName(char* name, char* basePath, char* outputPath); 49 | extern __declspec(dllexport) _Bool BExportById(char* name, char* id, char* basePath, char* outputPath); 50 | ]] 51 | 52 | local jianyingFile = io.open(jianyingProjectLocation .. "/root_meta_info.json", "r") 53 | local contents = "" 54 | local jianyingTable = {} 55 | local bcutTable = {} 56 | if jianyingFile then 57 | contents = jianyingFile:read("*a") 58 | jianyingTable = json.decode(contents); 59 | io.close(jianyingFile) 60 | end 61 | 62 | if bcutProjectLocation then 63 | local bcutFile = io.open(bcutProjectLocation .. "/draftInfo.json", "r") 64 | if bcutFile then 65 | contents = bcutFile:read("*a") 66 | bcutTable = json.decode(contents); 67 | io.close(bcutFile) 68 | end 69 | end 70 | 71 | local ui = fu.UIManager 72 | local disp = bmd.UIDispatcher(ui) 73 | 74 | resolve = Resolve() 75 | projectManager = resolve:GetProjectManager() 76 | project = projectManager:GetCurrentProject() 77 | mediaPool = project:GetMediaPool() 78 | 79 | -- some element IDs 80 | tabsID = "MyTabs" 81 | stackID = "MyStack" 82 | 83 | function MainWindow() 84 | win = disp:AddWindow({ 85 | ID = 'SubtitleTool', 86 | WindowTitle = '字幕工具By小黎', 87 | Spacing = 0, 88 | 89 | ui:VGroup { 90 | -- Add your GUI elements here: 91 | ui:TabBar { 92 | CurrentIndex = 0, 93 | ID = tabsID, 94 | Weight = 0 95 | }, 96 | 97 | ui.VGap(2), 98 | 99 | ui:Stack { 100 | Weight = 10, 101 | ID = stackID, 102 | ui:VGroup { 103 | Weight = 0, 104 | ui:VGroup { 105 | Weight = 0, 106 | ID = "JList" 107 | }, 108 | }, 109 | 110 | ui:VGroup { 111 | Weight = 0, 112 | ui:VGroup { 113 | Weight = 0, 114 | ID = "BList" 115 | }, 116 | } 117 | } 118 | } 119 | }) 120 | 121 | -- Add your GUI element based event functions here: 122 | itm = win:GetItems() 123 | 124 | itm[stackID].CurrentIndex = 0 125 | 126 | -- Add the items to the ComboBox menu 127 | itm[tabsID]:AddTab('剪映') 128 | itm[tabsID]:AddTab('必剪') 129 | 130 | if jianyingTable.all_draft_store then 131 | for i = 1, #jianyingTable.all_draft_store do 132 | local draft = jianyingTable.all_draft_store[#jianyingTable.all_draft_store + 1 - i] 133 | local min = math.floor(draft.tm_duration / 1000000 / 60) 134 | local sec = math.ceil(draft.tm_duration % (1000000 * 60) / 1000000) 135 | local date = os.date("%y/%m/%d %X", draft.tm_draft_modified / 1000000) 136 | local nameId = string.format("jianying_%s", i) 137 | local buttonId = string.format("j_export_%s", i) 138 | child = ui:HGroup { 139 | ui:TextEdit { 140 | Weight = 0, 141 | ReadOnly = true, 142 | HTML = string.format("= math.huge then 109 | error("unexpected number value '" .. tostring(val) .. "'") 110 | end 111 | return string.format("%.14g", val) 112 | end 113 | 114 | 115 | local type_func_map = { 116 | [ "nil" ] = encode_nil, 117 | [ "table" ] = encode_table, 118 | [ "string" ] = encode_string, 119 | [ "number" ] = encode_number, 120 | [ "boolean" ] = tostring, 121 | } 122 | 123 | 124 | encode = function(val, stack) 125 | local t = type(val) 126 | local f = type_func_map[t] 127 | if f then 128 | return f(val, stack) 129 | end 130 | error("unexpected type '" .. t .. "'") 131 | end 132 | 133 | 134 | function json.encode(val) 135 | return ( encode(val) ) 136 | end 137 | 138 | 139 | ------------------------------------------------------------------------------- 140 | -- Decode 141 | ------------------------------------------------------------------------------- 142 | 143 | local parse 144 | 145 | local function create_set(...) 146 | local res = {} 147 | for i = 1, select("#", ...) do 148 | res[ select(i, ...) ] = true 149 | end 150 | return res 151 | end 152 | 153 | local space_chars = create_set(" ", "\t", "\r", "\n") 154 | local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") 155 | local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") 156 | local literals = create_set("true", "false", "null") 157 | 158 | local literal_map = { 159 | [ "true" ] = true, 160 | [ "false" ] = false, 161 | [ "null" ] = nil, 162 | } 163 | 164 | 165 | local function next_char(str, idx, set, negate) 166 | for i = idx, #str do 167 | if set[str:sub(i, i)] ~= negate then 168 | return i 169 | end 170 | end 171 | return #str + 1 172 | end 173 | 174 | 175 | local function decode_error(str, idx, msg) 176 | local line_count = 1 177 | local col_count = 1 178 | for i = 1, idx - 1 do 179 | col_count = col_count + 1 180 | if str:sub(i, i) == "\n" then 181 | line_count = line_count + 1 182 | col_count = 1 183 | end 184 | end 185 | error( string.format("%s at line %d col %d", msg, line_count, col_count) ) 186 | end 187 | 188 | 189 | local function codepoint_to_utf8(n) 190 | -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa 191 | local f = math.floor 192 | if n <= 0x7f then 193 | return string.char(n) 194 | elseif n <= 0x7ff then 195 | return string.char(f(n / 64) + 192, n % 64 + 128) 196 | elseif n <= 0xffff then 197 | return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) 198 | elseif n <= 0x10ffff then 199 | return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, 200 | f(n % 4096 / 64) + 128, n % 64 + 128) 201 | end 202 | error( string.format("invalid unicode codepoint '%x'", n) ) 203 | end 204 | 205 | 206 | local function parse_unicode_escape(s) 207 | local n1 = tonumber( s:sub(1, 4), 16 ) 208 | local n2 = tonumber( s:sub(7, 10), 16 ) 209 | -- Surrogate pair? 210 | if n2 then 211 | return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) 212 | else 213 | return codepoint_to_utf8(n1) 214 | end 215 | end 216 | 217 | 218 | local function parse_string(str, i) 219 | local res = "" 220 | local j = i + 1 221 | local k = j 222 | 223 | while j <= #str do 224 | local x = str:byte(j) 225 | 226 | if x < 32 then 227 | decode_error(str, j, "control character in string") 228 | 229 | elseif x == 92 then -- `\`: Escape 230 | res = res .. str:sub(k, j - 1) 231 | j = j + 1 232 | local c = str:sub(j, j) 233 | if c == "u" then 234 | local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) 235 | or str:match("^%x%x%x%x", j + 1) 236 | or decode_error(str, j - 1, "invalid unicode escape in string") 237 | res = res .. parse_unicode_escape(hex) 238 | j = j + #hex 239 | else 240 | if not escape_chars[c] then 241 | decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") 242 | end 243 | res = res .. escape_char_map_inv[c] 244 | end 245 | k = j + 1 246 | 247 | elseif x == 34 then -- `"`: End of string 248 | res = res .. str:sub(k, j - 1) 249 | return res, j + 1 250 | end 251 | 252 | j = j + 1 253 | end 254 | 255 | decode_error(str, i, "expected closing quote for string") 256 | end 257 | 258 | 259 | local function parse_number(str, i) 260 | local x = next_char(str, i, delim_chars) 261 | local s = str:sub(i, x - 1) 262 | local n = tonumber(s) 263 | if not n then 264 | decode_error(str, i, "invalid number '" .. s .. "'") 265 | end 266 | return n, x 267 | end 268 | 269 | 270 | local function parse_literal(str, i) 271 | local x = next_char(str, i, delim_chars) 272 | local word = str:sub(i, x - 1) 273 | if not literals[word] then 274 | decode_error(str, i, "invalid literal '" .. word .. "'") 275 | end 276 | return literal_map[word], x 277 | end 278 | 279 | 280 | local function parse_array(str, i) 281 | local res = {} 282 | local n = 1 283 | i = i + 1 284 | while 1 do 285 | local x 286 | i = next_char(str, i, space_chars, true) 287 | -- Empty / end of array? 288 | if str:sub(i, i) == "]" then 289 | i = i + 1 290 | break 291 | end 292 | -- Read token 293 | x, i = parse(str, i) 294 | res[n] = x 295 | n = n + 1 296 | -- Next token 297 | i = next_char(str, i, space_chars, true) 298 | local chr = str:sub(i, i) 299 | i = i + 1 300 | if chr == "]" then break end 301 | if chr ~= "," then decode_error(str, i, "expected ']' or ','") end 302 | end 303 | return res, i 304 | end 305 | 306 | 307 | local function parse_object(str, i) 308 | local res = {} 309 | i = i + 1 310 | while 1 do 311 | local key, val 312 | i = next_char(str, i, space_chars, true) 313 | -- Empty / end of object? 314 | if str:sub(i, i) == "}" then 315 | i = i + 1 316 | break 317 | end 318 | -- Read key 319 | if str:sub(i, i) ~= '"' then 320 | decode_error(str, i, "expected string for key") 321 | end 322 | key, i = parse(str, i) 323 | -- Read ':' delimiter 324 | i = next_char(str, i, space_chars, true) 325 | if str:sub(i, i) ~= ":" then 326 | decode_error(str, i, "expected ':' after key") 327 | end 328 | i = next_char(str, i + 1, space_chars, true) 329 | -- Read value 330 | val, i = parse(str, i) 331 | -- Set 332 | res[key] = val 333 | -- Next token 334 | i = next_char(str, i, space_chars, true) 335 | local chr = str:sub(i, i) 336 | i = i + 1 337 | if chr == "}" then break end 338 | if chr ~= "," then decode_error(str, i, "expected '}' or ','") end 339 | end 340 | return res, i 341 | end 342 | 343 | 344 | local char_func_map = { 345 | [ '"' ] = parse_string, 346 | [ "0" ] = parse_number, 347 | [ "1" ] = parse_number, 348 | [ "2" ] = parse_number, 349 | [ "3" ] = parse_number, 350 | [ "4" ] = parse_number, 351 | [ "5" ] = parse_number, 352 | [ "6" ] = parse_number, 353 | [ "7" ] = parse_number, 354 | [ "8" ] = parse_number, 355 | [ "9" ] = parse_number, 356 | [ "-" ] = parse_number, 357 | [ "t" ] = parse_literal, 358 | [ "f" ] = parse_literal, 359 | [ "n" ] = parse_literal, 360 | [ "[" ] = parse_array, 361 | [ "{" ] = parse_object, 362 | } 363 | 364 | 365 | parse = function(str, idx) 366 | local chr = str:sub(idx, idx) 367 | local f = char_func_map[chr] 368 | if f then 369 | return f(str, idx) 370 | end 371 | decode_error(str, idx, "unexpected character '" .. chr .. "'") 372 | end 373 | 374 | 375 | function json.decode(str) 376 | if type(str) ~= "string" then 377 | error("expected argument of type string, got " .. type(str)) 378 | end 379 | local res, idx = parse(str, next_char(str, 1, space_chars, true)) 380 | idx = next_char(str, idx, space_chars, true) 381 | if idx <= #str then 382 | decode_error(str, idx, "trailing garbage") 383 | end 384 | return res 385 | end 386 | 387 | 388 | return json 389 | -------------------------------------------------------------------------------- /Fusion/Modules/Lua/resolve-metadata-amd64.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fukco/DaVinciResolveScript/02a5cfa9140b11cbc282520d8ffeea620d3e7972/Fusion/Modules/Lua/resolve-metadata-amd64.dylib -------------------------------------------------------------------------------- /Fusion/Modules/Lua/resolve-metadata-arm64.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fukco/DaVinciResolveScript/02a5cfa9140b11cbc282520d8ffeea620d3e7972/Fusion/Modules/Lua/resolve-metadata-arm64.dylib -------------------------------------------------------------------------------- /Fusion/Modules/Lua/resolve-metadata.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fukco/DaVinciResolveScript/02a5cfa9140b11cbc282520d8ffeea620d3e7972/Fusion/Modules/Lua/resolve-metadata.dll -------------------------------------------------------------------------------- /Fusion/Scripts/Comp/Sony MILC.lua: -------------------------------------------------------------------------------- 1 | local lib 2 | if ffi.os == "Windows" then 3 | lib = ffi.load(fusion:MapPath('LuaModules:/resolve-metadata.dll')) 4 | elseif ffi.os == "OSX" then 5 | if ffi.arch == "x64" then 6 | lib = ffi.load(fusion:MapPath('LuaModules:/resolve-metadata-amd64.dylib')) 7 | elseif ffi.arch == "arm64" then 8 | lib = ffi.load(fusion:MapPath('LuaModules:/resolve-metadata-arm64.dylib')) 9 | end 10 | end 11 | 12 | ffi.cdef [[ 13 | typedef struct 14 | { 15 | bool IsSupportMedia; 16 | char *Manufacturer; 17 | char *FileFormatAndRecFrameRate; 18 | char *ModelName; 19 | char *FormatFPS; 20 | char *CaptureFPS; 21 | char *VideoBitrate; 22 | char *Profile; 23 | char *RecordingMode; 24 | bool IsProxyOn; 25 | long long int CreationTimestamp; 26 | int TimecodeSecs; 27 | int TimecodeFrame; 28 | } DRSonyNrtmd; 29 | 30 | typedef struct 31 | { 32 | int Frame; 33 | char *Data; 34 | } DRFrameData; 35 | 36 | typedef struct 37 | { 38 | DRFrameData array[1000]; 39 | int len; 40 | } DRFrameDataArray; 41 | 42 | typedef struct 43 | { 44 | DRFrameDataArray WhiteBalanceModeArray; 45 | DRFrameDataArray ExposureModeArray; 46 | DRFrameDataArray AutoFocusSensingAreaArray; 47 | DRFrameDataArray ShutterSpeedArray; 48 | DRFrameDataArray ApertureArray; 49 | DRFrameDataArray ISOArray; 50 | DRFrameDataArray FocalLengthArray; 51 | DRFrameDataArray FocalLength35mmArray; 52 | DRFrameDataArray FocusPositionArray; 53 | DRFrameDataArray CaptureGammaEquationArray; 54 | DRFrameDataArray CameraMasterGainAdjustmentArray; 55 | } DRSonyRtmdDisp; 56 | 57 | extern __declspec(dllexport) DRSonyNrtmd DRSonyNrtmdDisp(char* absPath); 58 | extern __declspec(dllexport) DRSonyRtmdDisp DrSonyRtmdDisp(char* absPath, int start, int count); 59 | ]] 60 | 61 | local function log_line(message) 62 | print(string.format("[%s] %s", os.date("%Y-%m-%d %X"), message)) 63 | end 64 | 65 | local function isSonyXAVC() 66 | local filePath = mediaIn:GetData("MediaProps.MEDIA_PATH") 67 | if filePath ~= "" then 68 | local c_str = ffi.new("char[?]", #filePath + 1) 69 | ffi.copy(c_str, filePath) 70 | local res = lib.DRSonyNrtmdDisp(c_str) 71 | if res == nil or not res.IsSupportMedia then 72 | MyWindow("未找到媒体文件,或非SONY XAVC格式媒体文件") 73 | return false 74 | else 75 | return true 76 | end 77 | else 78 | MyWindow("无法找到媒体路径!") 79 | return false 80 | end 81 | end 82 | 83 | local function getSonyNrtmd(filePath) 84 | if filePath ~= "" then 85 | local c_str = ffi.new("char[?]", #filePath + 1) 86 | ffi.copy(c_str, filePath) 87 | local res = lib.DRSonyNrtmdDisp(c_str) 88 | if res == nil then 89 | log_line("Failed to parse clip" .. filePath) 90 | return 91 | end 92 | comp.FileFormatAndFrameRate.StyledText = ffi.string(res.FileFormatAndRecFrameRate) 93 | comp.BitrateText.StyledText = string.gsub(ffi.string(res.VideoBitrate), "Mbps", "") 94 | comp.Profile.StyledText = ffi.string(res.Profile) 95 | comp.ModelName.StyledText = ffi.string(res.ModelName) 96 | comp.CaptureFramerate.StyledText = string.gsub(ffi.string(res.CaptureFPS), "p", "fps") 97 | comp.ModelName.StyledText = ffi.string(res.ModelName) 98 | comp.DateTime.Timestamp = tostring(tonumber(res.CreationTimestamp)) 99 | comp.Timecode.FirstFrameTimeInSec = res.TimecodeSecs 100 | comp.Timecode.FirstFrameFrameNum = res.TimecodeFrame 101 | local fps = ffi.string(res.FormatFPS):gsub("p", "") 102 | comp.Timecode.RecFrameRate = tonumber(fps) 103 | 104 | if ffi.string(res.RecordingMode) == "normal" then 105 | comp.RecordModeTransform.RecordMode = 0 106 | elseif ffi.string(res.RecordingMode) == "S&Q" then 107 | comp.RecordModeTransform.RecordMode = 1 108 | end 109 | if res.IsProxyOn then 110 | comp.ProxyIconTx.ProxyOn = 1 111 | else 112 | comp.ProxyIconTx.ProxyOn = 0 113 | end 114 | end 115 | end 116 | 117 | function MyWindow(text) 118 | local ui = fu.UIManager 119 | local disp = bmd.UIDispatcher(ui) 120 | 121 | win = disp:AddWindow({ 122 | ID = 'MyWin', 123 | WindowTitle = 'Notification', 124 | Spacing = 10, 125 | 126 | ui:VGroup { 127 | ID = 'root', 128 | 129 | -- Add your GUI elements here: 130 | ui:HGroup { 131 | ui:Label { 132 | Text = text, 133 | Alignment = { AlignHCenter = true, AlignVCenter = true }, 134 | }, 135 | }, 136 | 137 | ui:HGroup { 138 | Weight = 0, 139 | ui:Button { 140 | ID = 'B', 141 | Text = 'OK', 142 | }, 143 | } 144 | }, 145 | }) 146 | win:Resize({ 400, 120 }); 147 | win:RecalcLayout(); 148 | 149 | -- The window was closed 150 | function win.On.MyWin.Close(ev) 151 | disp:ExitLoop() 152 | end 153 | 154 | -- Add your GUI element based event functions here: 155 | itm = win:GetItems() 156 | 157 | function win.On.B.Clicked(ev) 158 | disp:ExitLoop() 159 | end 160 | 161 | win:Show() 162 | disp:RunLoop() 163 | win:Hide() 164 | end 165 | 166 | function handleRtmdByFrame(res) 167 | local whiteBalanceLen = res.WhiteBalanceModeArray.len 168 | for i = 0, whiteBalanceLen - 1 do 169 | if ffi.string(res.WhiteBalanceModeArray.array[i].Data) == lastWhiteBalance then 170 | goto continue 171 | end 172 | lastWhiteBalance = ffi.string(res.WhiteBalanceModeArray.array[i].Data) 173 | if lastWhiteBalance == "Auto" then 174 | comp.WBTransform.WBSelector[res.WhiteBalanceModeArray.array[i].Frame] = "0" 175 | elseif lastWhiteBalance == "SunLight" then 176 | comp.WBTransform.WBSelector[res.WhiteBalanceModeArray.array[i].Frame] = "1" 177 | elseif lastWhiteBalance == "Cloudy" then 178 | comp.WBTransform.WBSelector[res.WhiteBalanceModeArray.array[i].Frame] = "2" 179 | elseif lastWhiteBalance == "Incandescent" then 180 | comp.WBTransform.WBSelector[res.WhiteBalanceModeArray.array[i].Frame] = "3" 181 | elseif lastWhiteBalance == "Fluorescent" then 182 | comp.WBTransform.WBSelector[res.WhiteBalanceModeArray.array[i].Frame] = "4" 183 | elseif lastWhiteBalance == "Other" then 184 | comp.WBTransform.WBSelector[res.WhiteBalanceModeArray.array[i].Frame] = "5" 185 | elseif lastWhiteBalance == "Custom" then 186 | comp.WBTransform.WBSelector[res.WhiteBalanceModeArray.array[i].Frame] = "6" 187 | elseif lastWhiteBalance == "Unknown" then 188 | comp.WBTransform.WBSelector[res.WhiteBalanceModeArray.array[i].Frame] = "7" 189 | end 190 | :: continue :: 191 | end 192 | local shutterSpeedLen = res.ShutterSpeedArray.len 193 | for i = 0, shutterSpeedLen - 1 do 194 | if ffi.string(res.ShutterSpeedArray.array[i].Data) == lastShutterSpeed then 195 | goto continue 196 | end 197 | lastShutterSpeed = ffi.string(res.ShutterSpeedArray.array[i].Data) 198 | comp.ShutterSpeed.StyledText[res.ShutterSpeedArray.array[i].Frame] = lastShutterSpeed 199 | :: continue :: 200 | end 201 | local exposureModeLen = res.ExposureModeArray.len 202 | for i = 0, exposureModeLen - 1 do 203 | if ffi.string(res.ExposureModeArray.array[i].Data) == lastExposureMode then 204 | goto continue 205 | end 206 | lastExposureMode = ffi.string(res.ExposureModeArray.array[i].Data) 207 | --P档也是Manual 208 | if lastExposureMode == "Manual" then 209 | comp.ExposureMode.StyledText[res.ExposureModeArray.array[i].Frame] = "M" 210 | elseif lastExposureMode == "A Mode" then 211 | comp.ExposureMode.StyledText[res.ExposureModeArray.array[i].Frame] = "A" 212 | elseif lastExposureMode == "S Mode" then 213 | comp.ExposureMode.StyledText[res.ExposureModeArray.array[i].Frame] = "S" 214 | else 215 | comp.ExposureMode.StyledText[res.ExposureModeArray.array[i].Frame] = "" 216 | end 217 | :: continue :: 218 | end 219 | local autoFocusSensingAreaLen = res.AutoFocusSensingAreaArray.len 220 | for i = 0, autoFocusSensingAreaLen - 1 do 221 | if ffi.string(res.AutoFocusSensingAreaArray.array[i].Data) == lastAutoFocusSensingArea then 222 | goto continue 223 | end 224 | lastAutoFocusSensingArea = ffi.string(res.AutoFocusSensingAreaArray.array[i].Data) 225 | if lastAutoFocusSensingArea == "AF Whole" then 226 | comp.FocusAreaTransform.FocusAreaSelector[res.AutoFocusSensingAreaArray.array[i].Frame] = "0" 227 | elseif lastAutoFocusSensingArea == "AF Multi" then 228 | comp.FocusAreaTransform.FocusAreaSelector[res.AutoFocusSensingAreaArray.array[i].Frame] = "1" 229 | elseif lastAutoFocusSensingArea == "AF Center" then 230 | comp.FocusAreaTransform.FocusAreaSelector[res.AutoFocusSensingAreaArray.array[i].Frame] = "2" 231 | elseif lastAutoFocusSensingArea == "AF Spot" then 232 | comp.FocusAreaTransform.FocusAreaSelector[res.AutoFocusSensingAreaArray.array[i].Frame] = "3" 233 | elseif lastAutoFocusSensingArea == "MF" then 234 | comp.FocusAreaTransform.FocusAreaSelector[res.AutoFocusSensingAreaArray.array[i].Frame] = "4" 235 | else 236 | comp.FocusAreaTransform.FocusAreaSelector[res.AutoFocusSensingAreaArray.array[i].Frame] = "5" 237 | end 238 | :: continue :: 239 | end 240 | local apertureLen = res.ApertureArray.len 241 | for i = 0, apertureLen - 1 do 242 | if ffi.string(res.ApertureArray.array[i].Data) == lastAperture then 243 | goto continue 244 | end 245 | lastAperture = ffi.string(res.ApertureArray.array[i].Data) 246 | comp.Aperture.StyledText[res.ApertureArray.array[i].Frame] = lastAperture 247 | :: continue :: 248 | end 249 | local isoLen = res.ISOArray.len 250 | for i = 0, isoLen - 1 do 251 | if ffi.string(res.ISOArray.array[i].Data) == lastISO then 252 | goto continue 253 | end 254 | lastISO = ffi.string(res.ISOArray.array[i].Data) 255 | comp.ISO.StyledText[res.ISOArray.array[i].Frame] = lastISO 256 | :: continue :: 257 | end 258 | local focalLengthLen = res.FocalLengthArray.len 259 | for i = 0, focalLengthLen - 1 do 260 | if ffi.string(res.FocalLengthArray.array[i].Data) == lastFocalLength then 261 | goto continue 262 | end 263 | lastFocalLength = ffi.string(res.FocalLengthArray.array[i].Data) 264 | comp.FocalLength.StyledText[res.FocalLengthArray.array[i].Frame] = lastFocalLength 265 | :: continue :: 266 | end 267 | local focalLength35mmLen = res.FocalLength35mmArray.len 268 | for i = 0, focalLength35mmLen - 1 do 269 | if ffi.string(res.FocalLength35mmArray.array[i].Data) == lastFocalLength35mm then 270 | goto continue 271 | end 272 | lastFocalLength35mm = ffi.string(res.FocalLength35mmArray.array[i].Data) 273 | comp.FocalLength_35mm.StyledText[res.FocalLength35mmArray.array[i].Frame] = lastFocalLength35mm 274 | :: continue :: 275 | end 276 | local focusPositionLen = res.FocusPositionArray.len 277 | for i = 0, focusPositionLen - 1 do 278 | if ffi.string(res.FocusPositionArray.array[i].Data) == lastFocusPosition then 279 | goto continue 280 | end 281 | lastFocusPosition = ffi.string(res.FocusPositionArray.array[i].Data) 282 | comp.FocusPosition.StyledText[res.FocusPositionArray.array[i].Frame] = lastFocusPosition 283 | :: continue :: 284 | end 285 | local gammaEquationLen = res.CaptureGammaEquationArray.len 286 | for i = 0, gammaEquationLen - 1 do 287 | if ffi.string(res.CaptureGammaEquationArray.array[i].Data) == lastGammaEquation then 288 | goto continue 289 | end 290 | lastGammaEquation = ffi.string(res.CaptureGammaEquationArray.array[i].Data) 291 | comp.GammaEquation.StyledText[res.CaptureGammaEquationArray.array[i].Frame] = lastGammaEquation 292 | if lastGammaEquation == "rec709/Still" then 293 | comp.PPTransform.PPSelector[res.CaptureGammaEquationArray.array[i].Frame] = "2" 294 | elseif lastGammaEquation == "rec709/Cine1" then 295 | comp.PPTransform.PPSelector[res.CaptureGammaEquationArray.array[i].Frame] = "5" 296 | elseif lastGammaEquation == "rec709/Cine2" then 297 | comp.PPTransform.PPSelector[res.CaptureGammaEquationArray.array[i].Frame] = "6" 298 | elseif lastGammaEquation == "S-Gamut/S-Log2" then 299 | comp.PPTransform.PPSelector[res.CaptureGammaEquationArray.array[i].Frame] = "7" 300 | elseif lastGammaEquation == "S-Gamut3.Cine/S-Log3-Cine" then 301 | comp.PPTransform.PPSelector[res.CaptureGammaEquationArray.array[i].Frame] = "8" 302 | elseif lastGammaEquation == "S-Gamut3/S-Log3" then 303 | comp.PPTransform.PPSelector[res.CaptureGammaEquationArray.array[i].Frame] = "9" 304 | elseif lastGammaEquation == "rec2020/Rec2100-HLG" then 305 | comp.PPTransform.PPSelector[res.CaptureGammaEquationArray.array[i].Frame] = "10" 306 | elseif lastGammaEquation == "rec709/S-Cinetone" then 307 | comp.PPTransform.PPSelector[res.CaptureGammaEquationArray.array[i].Frame] = "11" 308 | else 309 | comp.PPTransform.PPSelector[res.CaptureGammaEquationArray.array[i].Frame] = "12" 310 | end 311 | :: continue :: 312 | end 313 | local masterGainAdjustmentLen = res.CameraMasterGainAdjustmentArray.len 314 | for i = 0, masterGainAdjustmentLen - 1 do 315 | if ffi.string(res.CameraMasterGainAdjustmentArray.array[i].Data) == lastGainAdjustment then 316 | goto continue 317 | end 318 | lastGainAdjustment = ffi.string(res.CameraMasterGainAdjustmentArray.array[i].Data) 319 | comp.GainAdjustment.StyledText[res.CameraMasterGainAdjustmentArray.array[i].Frame] = lastGainAdjustment 320 | :: continue :: 321 | end 322 | end 323 | 324 | function getSonyRtmdByFrame(filePath, start, batch_count) 325 | log_line(string.format("处理帧:%d至%d", start, start + batch_count - 1)) 326 | local c_str = ffi.new("char[?]", #filePath + 1) 327 | ffi.copy(c_str, filePath) 328 | local res = lib.DrSonyRtmdDisp(c_str, start, batch_count) 329 | handleRtmdByFrame(res) 330 | end 331 | 332 | function setBezier() 333 | comp.CurrentTime = comp:GetAttrs("COMPN_RenderStart") 334 | comp.ShutterSpeed.StyledText = comp:BezierSpline() 335 | comp.ExposureMode.StyledText = comp:BezierSpline() 336 | comp.Aperture.StyledText = comp:BezierSpline() 337 | comp.ISO.StyledText = comp:BezierSpline() 338 | comp.FocalLength.StyledText = comp:BezierSpline() 339 | comp.FocalLength_35mm.StyledText = comp:BezierSpline() 340 | comp.FocusPosition.StyledText = comp:BezierSpline() 341 | comp.GammaEquation.StyledText = comp:BezierSpline() 342 | comp.GainAdjustment.StyledText = comp:BezierSpline() 343 | comp.WBTransform.WBSelector = comp:BezierSpline() 344 | comp.FocusAreaTransform.FocusAreaSelector = comp:BezierSpline() 345 | comp.PPTransform.PPSelector = comp:BezierSpline() 346 | end 347 | 348 | function Main() 349 | log_line("Sony MILC开始") 350 | if comp == nil then 351 | MyWindow("请在Fusion页面中打开此脚本") 352 | return 353 | end 354 | mediaIn = comp:FindToolByID("MediaIn") 355 | if mediaIn == nil then 356 | MyWindow("您的fusion效果中没有MediaIn节点!") 357 | return 358 | end 359 | if isSonyXAVC() then 360 | if comp:FindTool("SonyMILC") ~= nil then 361 | comp.SonyMILC:Delete() 362 | end 363 | comp:SetActiveTool(mediaIn) 364 | comp:DoAction("AddSetting", { filename = "Templates:/Edit/Effects/XiaoLi/Sony MILC.setting" }) 365 | else 366 | return 367 | end 368 | if comp:FindTool("SonyMILC") == nil then 369 | return 370 | end 371 | 372 | comp.SonyMILC:SetAttrs({ TOOLB_PassThrough = true }) 373 | local filePath = mediaIn:GetData("MediaProps.MEDIA_PATH") 374 | log_line("获取静态数据") 375 | getSonyNrtmd(filePath) 376 | 377 | renderStart = comp:GetAttrs("COMPN_RenderStart") 378 | renderEnd = comp:GetAttrs("COMPN_RenderEnd") 379 | 380 | setBezier() 381 | local batch_count = 1000 382 | local start 383 | local i = 0 384 | 385 | log_line("开始获取帧数据") 386 | while (true) 387 | do 388 | start = renderStart + batch_count * i 389 | if start + batch_count >= renderEnd + 1 then 390 | getSonyRtmdByFrame(filePath, start, renderEnd - start + 1) 391 | break 392 | end 393 | getSonyRtmdByFrame(filePath, start, batch_count) 394 | i = i + 1 395 | end 396 | 397 | comp.SonyMILC:SetAttrs({ TOOLB_PassThrough = false }) 398 | log_line("完成!") 399 | end 400 | 401 | Main() 402 | -------------------------------------------------------------------------------- /Fusion/Scripts/Utility/Lua/Metadata Parser.lua: -------------------------------------------------------------------------------- 1 | local lib 2 | if ffi.os == "Windows" then 3 | lib = ffi.load(fusion:MapPath('LuaModules:/resolve-metadata.dll')) 4 | elseif ffi.os == "OSX" then 5 | if ffi.arch == "x64" then 6 | lib = ffi.load(fusion:MapPath('LuaModules:/resolve-metadata-amd64.dylib')) 7 | elseif ffi.arch == "arm64" then 8 | lib = ffi.load(fusion:MapPath('LuaModules:/resolve-metadata-arm64.dylib')) 9 | end 10 | end 11 | 12 | ffi.cdef [[ 13 | typedef struct 14 | { 15 | bool IsSupportMedia; 16 | char *DateRecorded; 17 | char *CameraType; 18 | char *CameraManufacturer; 19 | char *CameraSerial; 20 | char *CameraId; 21 | char *CameraNotes; 22 | char *CameraFormat; 23 | char *MediaType; 24 | char *TimeLapseInterval; 25 | char *CameraFps; 26 | char *ShutterType; 27 | char *ShutterAngle; 28 | char *Shutter; 29 | char *ISO; 30 | char *WhitePoint; 31 | char *WhiteBalanceTint; 32 | char *CameraFirmware; 33 | char *LUTUsed; 34 | char *LensType; 35 | char *LensNumber; 36 | char *LensNotes; 37 | char *CameraApertureType; 38 | char *CameraAperture; 39 | char *FocalPoint; 40 | char *Distance; 41 | char *Filter; 42 | char *NDFilter; 43 | char *CompressionRatio; 44 | char *CodecBitrate; 45 | char *SensorAreaCaptured; 46 | char *PARNotes; 47 | char *AspectRatioNotes; 48 | char *GammaNotes; 49 | char *ColorSpaceNotes; 50 | } DRMetadata; 51 | extern __declspec(dllexport) DRMetadata DRProcessMediaFile(char* absPath); 52 | ]] 53 | 54 | local function log_line(message) 55 | print(string.format("[%s] %s", os.date("%Y-%m-%d %X"), message)) 56 | end 57 | 58 | local function processClip(clip) 59 | local filePath = clip:GetClipProperty("File Path") 60 | if filePath ~= "" then 61 | local c_str = ffi.new("char[?]", #filePath+1) 62 | ffi.copy(c_str, filePath) 63 | local res = lib.DRProcessMediaFile(c_str) 64 | if res == nil then 65 | log_line("Failed to parse clip" .. filePath) 66 | return 67 | end 68 | if not res.IsSupportMedia then 69 | log_line("Clip " .. filePath .. " Not Support.") 70 | return 71 | end 72 | local metadata = {} 73 | local returnVal = {} 74 | returnVal["Date Recorded"] = ffi.string(res.DateRecorded) 75 | returnVal["Camera Type"] = ffi.string(res.CameraType) 76 | returnVal["Camera Manufacturer"] = ffi.string(res.CameraManufacturer) 77 | returnVal["Camera Serial #"] = ffi.string(res.CameraSerial) 78 | returnVal["Camera ID"] = ffi.string(res.CameraId) 79 | returnVal["Camera Notes"] = ffi.string(res.CameraNotes) 80 | returnVal["Camera Format"] = ffi.string(res.CameraFormat) 81 | returnVal["Media Type"] = ffi.string(res.MediaType) 82 | returnVal["Time-Lapse Interval"] = ffi.string(res.TimeLapseInterval) 83 | returnVal["Camera FPS"] = ffi.string(res.CameraFps) 84 | returnVal["Shutter Type"] = ffi.string(res.ShutterType) 85 | returnVal["Shutter Angle"] = ffi.string(res.ShutterAngle) 86 | returnVal["ISO"] = ffi.string(res.ISO) 87 | returnVal["White Point (Kelvin)"] = ffi.string(res.WhitePoint) 88 | returnVal["White Balance Tint"] = ffi.string(res.WhiteBalanceTint) 89 | returnVal["Camera Firmware"] = ffi.string(res.CameraFirmware) 90 | returnVal["LUT Used"] = ffi.string(res.LUTUsed) 91 | returnVal["Lens Type"] = ffi.string(res.LensType) 92 | returnVal["Lens Number"] = ffi.string(res.LensNumber) 93 | returnVal["Lens Notes"] = ffi.string(res.LensNotes) 94 | returnVal["Camera Aperture Type"] = ffi.string(res.CameraApertureType) 95 | returnVal["Camera Aperture"] = ffi.string(res.CameraAperture) 96 | returnVal["Focal Point (mm)"] = ffi.string(res.FocalPoint) 97 | returnVal["Distance"] = ffi.string(res.Distance) 98 | returnVal["Filter"] = ffi.string(res.Filter) 99 | returnVal["ND Filter"] = ffi.string(res.NDFilter) 100 | returnVal["Compression Ratio"] = ffi.string(res.CompressionRatio) 101 | returnVal["Codec Bitrate"] = ffi.string(res.CodecBitrate) 102 | returnVal["Sensor Area Captured"] = ffi.string(res.SensorAreaCaptured) 103 | returnVal["PAR Notes"] = ffi.string(res.PARNotes) 104 | returnVal["Aspect Ratio Notes"] = ffi.string(res.AspectRatioNotes) 105 | returnVal["Gamma Notes"] = ffi.string(res.GammaNotes) 106 | returnVal["Color Space Notes"] = ffi.string(res.ColorSpaceNotes) 107 | for key, value in pairs(returnVal) do 108 | if value ~= "" then 109 | metadata[key] = value 110 | end 111 | end 112 | if next(metadata) == nil then 113 | log_line("Ignore Clip " .. filePath) 114 | return 115 | end 116 | if clip:SetMetadata(metadata) then 117 | --18.5开始Shutter修改为Shutter Speed 118 | if not clip:SetMetadata("Shutter", ffi.string(res.Shutter)) then 119 | clip:SetMetadata("Shutter Speed", ffi.string(res.Shutter)) 120 | end 121 | log_line("Process clip " .. filePath .. " successfully.") 122 | return true 123 | else 124 | log_line("Failed to set clip " .. filePath .. " metadata") 125 | end 126 | end 127 | end 128 | 129 | local function processClips(folder) 130 | local clips = folder:GetClipList() 131 | for i = 1, #clips do 132 | if processClip(clips[i]) then 133 | n = n + 1 134 | end 135 | end 136 | local subFolders = folder:GetSubFolderList() 137 | for i = 1, #subFolders do 138 | processClips(subFolders[i]) 139 | end 140 | end 141 | 142 | resolve = Resolve() 143 | projectManager = resolve:GetProjectManager() 144 | project = projectManager:GetCurrentProject() 145 | mediaPool = project:GetMediaPool() 146 | rootFolder = mediaPool:GetRootFolder() 147 | n = 0 148 | processClips(rootFolder) 149 | 150 | local ui = fu.UIManager 151 | local disp = bmd.UIDispatcher(ui) 152 | 153 | win = disp:AddWindow({ 154 | ID = 'MyWin', 155 | WindowTitle = 'Notification', 156 | Spacing = 10, 157 | 158 | ui:VGroup { 159 | ID = 'root', 160 | 161 | -- Add your GUI elements here: 162 | ui:HGroup { 163 | ui:Label { 164 | Text = n .. ' clips in media pool has been parsed.\nMore details in console.', 165 | Alignment = { AlignHCenter = true, AlignVCenter = true }, 166 | }, 167 | }, 168 | 169 | ui:HGroup { 170 | Weight = 0, 171 | ui:Button { 172 | ID = 'B', 173 | Text = 'OK', 174 | }, 175 | } 176 | }, 177 | }) 178 | win:Resize({ 400, 120 }); 179 | win:RecalcLayout(); 180 | 181 | -- The window was closed 182 | function win.On.MyWin.Close(ev) 183 | disp:ExitLoop() 184 | end 185 | 186 | -- Add your GUI element based event functions here: 187 | itm = win:GetItems() 188 | 189 | function win.On.B.Clicked(ev) 190 | disp:ExitLoop() 191 | end 192 | 193 | win:Show() 194 | disp:RunLoop() 195 | win:Hide() 196 | -------------------------------------------------------------------------------- /Fusion/Scripts/Utility/Lua/RCM Color Space Match.lua: -------------------------------------------------------------------------------- 1 | gui_mode = 1 2 | 3 | local color_space_match_list = { 4 | { manufacturer = "Atomos", details = { 5 | { gamma_notes = "CLog", color_science_mode = "Cinema", input_color_space = "Canon Cinema Gamut/Canon Log" }, 6 | { gamma_notes = "CLog2", color_science_mode = "Cinema", input_color_space = "Canon Cinema Gamut/Canon Log2" }, 7 | { gamma_notes = "CLog3", color_science_mode = "Cinema", input_color_space = "Canon Cinema Gamut/Canon Log3" }, 8 | { gamma_notes = "F-Log", color_science_mode = "F-Gamut", input_color_space = "FujiFilm F-Log" }, 9 | { gamma_notes = "V-Log", color_science_mode = "V-Gamut", input_color_space = "Panasonic V-Gamut/V-Log" }, 10 | { gamma_notes = "SLog3", color_science_mode = "SGamut3", input_color_space = "S-Gamut3/S-Log3" }, 11 | { gamma_notes = "SLog3", color_science_mode = "SGamut3Cine", input_color_space = "S-Gamut3.Cine/S-Log3" }, 12 | { gamma_notes = "N-Log", color_science_mode = "BT.2020", input_color_space = "Nikon N-Log" }, 13 | { gamma_notes = "HLG", color_science_mode = "BT.2020", input_color_space = "Rec.2100 HLG" } 14 | } }, 15 | { manufacturer = "Canon", details = { 16 | { gamma_notes = "Canon Log", color_science_mode = "", input_color_space = "Canon Cinema Gamut/Canon Log" }, 17 | { gamma_notes = "Canon Log 2", color_science_mode = "", input_color_space = "Canon Cinema Gamut/Canon Log2" }, 18 | { gamma_notes = "Canon Log 3", color_science_mode = "", input_color_space = "Canon Cinema Gamut/Canon Log3" }, 19 | } }, 20 | { manufacturer = "Fujifilm", details = { 21 | { gamma_notes = "F-log", color_science_mode = "", input_color_space = "FujiFilm F-Log" } 22 | } }, 23 | { manufacturer = "Nikon", details = { 24 | { gamma_notes = "N-LOG", color_science_mode = "", input_color_space = "Nikon N-Log" } 25 | } }, 26 | { manufacturer = "Panasonic", details = { 27 | { gamma_notes = "V-Log", color_science_mode = "V-Gamut", input_color_space = "Panasonic V-Gamut/V-Log" } 28 | } }, 29 | { manufacturer = "Sony", details = { 30 | { gamma_notes = "s-log2", color_science_mode = "", input_color_space = "S-Gamut/S-Log2" }, 31 | { gamma_notes = "s-log2", color_science_mode = "s-gamut", input_color_space = "S-Gamut/S-Log2" }, 32 | { gamma_notes = "s-log3-cine", color_science_mode = "s-gamut3-cine", input_color_space = "S-Gamut3.Cine/S-Log3" }, 33 | { gamma_notes = "s-log3", color_science_mode = "s-gamut3", input_color_space = "S-Gamut3/S-Log3" }, 34 | { gamma_notes = "rec2100-hlg", color_science_mode = "rec2020", input_color_space = "Rec.2100 HLG" }, 35 | } 36 | } 37 | } 38 | 39 | local color_space_match_map = {} 40 | 41 | for _, value in ipairs(color_space_match_list) do 42 | for _, detail in ipairs(value["details"]) do 43 | if color_space_match_map[detail["gamma_notes"]:lower()] then 44 | local exist = color_space_match_map[detail["gamma_notes"]:lower()] 45 | if exist[detail["color_science_mode"]:lower()] == nil then 46 | exist[detail["color_science_mode"]:lower()] = detail["input_color_space"] 47 | end 48 | else 49 | local child = {} 50 | child[detail["color_science_mode"]:lower()] = detail["input_color_space"] 51 | color_space_match_map[detail["gamma_notes"]:lower()] = child 52 | end 53 | end 54 | end 55 | 56 | local lib 57 | if ffi.os == "Windows" then 58 | lib = ffi.load(fusion:MapPath('LuaModules:/resolve-metadata.dll')) 59 | elseif ffi.os == "OSX" then 60 | if ffi.arch == "x64" then 61 | lib = ffi.load(fusion:MapPath('LuaModules:/resolve-metadata-amd64.dylib')) 62 | elseif ffi.arch == "arm64" then 63 | lib = ffi.load(fusion:MapPath('LuaModules:/resolve-metadata-arm64.dylib')) 64 | end 65 | end 66 | 67 | ffi.cdef [[ 68 | typedef struct 69 | { 70 | bool IsSupportMedia; 71 | char *DateRecorded; 72 | char *CameraType; 73 | char *CameraManufacturer; 74 | char *CameraSerial; 75 | char *CameraId; 76 | char *CameraNotes; 77 | char *CameraFormat; 78 | char *MediaType; 79 | char *TimeLapseInterval; 80 | char *CameraFps; 81 | char *ShutterType; 82 | char *ShutterAngle; 83 | char *Shutter; 84 | char *ISO; 85 | char *WhitePoint; 86 | char *WhiteBalanceTint; 87 | char *CameraFirmware; 88 | char *LUTUsed; 89 | char *LensType; 90 | char *LensNumber; 91 | char *LensNotes; 92 | char *CameraApertureType; 93 | char *CameraAperture; 94 | char *FocalPoint; 95 | char *Distance; 96 | char *Filter; 97 | char *NDFilter; 98 | char *CompressionRatio; 99 | char *CodecBitrate; 100 | char *SensorAreaCaptured; 101 | char *PARNotes; 102 | char *AspectRatioNotes; 103 | char *GammaNotes; 104 | char *ColorSpaceNotes; 105 | } DRMetadata; 106 | extern __declspec(dllexport) DRMetadata DRProcessMediaFile(char* absPath); 107 | ]] 108 | 109 | local function LogLine(message) 110 | print(string.format("[%s] %s", os.date("%Y-%m-%d %X"), message)) 111 | end 112 | 113 | local function ProcessClip(clip) 114 | local filePath = clip:GetClipProperty("File Path") 115 | if filePath ~= "" then 116 | local c_str = ffi.new("char[?]", #filePath+1) 117 | ffi.copy(c_str, filePath) 118 | local res = lib.DRProcessMediaFile(c_str) 119 | if res == nil then 120 | LogLine("Failed to parse clip" .. filePath) 121 | return 122 | end 123 | if not res.IsSupportMedia then 124 | LogLine("Clip " .. filePath .. " Not Support.") 125 | return 126 | end 127 | local metadata = {} 128 | local returnVal = {} 129 | returnVal["Date Recorded"] = ffi.string(res.DateRecorded) 130 | returnVal["Camera Type"] = ffi.string(res.CameraType) 131 | returnVal["Camera Manufacturer"] = ffi.string(res.CameraManufacturer) 132 | returnVal["Camera Serial #"] = ffi.string(res.CameraSerial) 133 | returnVal["Camera ID"] = ffi.string(res.CameraId) 134 | returnVal["Camera Notes"] = ffi.string(res.CameraNotes) 135 | returnVal["Camera Format"] = ffi.string(res.CameraFormat) 136 | returnVal["Media Type"] = ffi.string(res.MediaType) 137 | returnVal["Time-Lapse Interval"] = ffi.string(res.TimeLapseInterval) 138 | returnVal["Camera FPS"] = ffi.string(res.CameraFps) 139 | returnVal["Shutter Type"] = ffi.string(res.ShutterType) 140 | returnVal["Shutter Angle"] = ffi.string(res.ShutterAngle) 141 | returnVal["ISO"] = ffi.string(res.ISO) 142 | returnVal["White Point (Kelvin)"] = ffi.string(res.WhitePoint) 143 | returnVal["White Balance Tint"] = ffi.string(res.WhiteBalanceTint) 144 | returnVal["Camera Firmware"] = ffi.string(res.CameraFirmware) 145 | returnVal["LUT Used"] = ffi.string(res.LUTUsed) 146 | returnVal["Lens Type"] = ffi.string(res.LensType) 147 | returnVal["Lens Number"] = ffi.string(res.LensNumber) 148 | returnVal["Lens Notes"] = ffi.string(res.LensNotes) 149 | returnVal["Camera Aperture Type"] = ffi.string(res.CameraApertureType) 150 | returnVal["Camera Aperture"] = ffi.string(res.CameraAperture) 151 | returnVal["Focal Point (mm)"] = ffi.string(res.FocalPoint) 152 | returnVal["Distance"] = ffi.string(res.Distance) 153 | returnVal["Filter"] = ffi.string(res.Filter) 154 | returnVal["ND Filter"] = ffi.string(res.NDFilter) 155 | returnVal["Compression Ratio"] = ffi.string(res.CompressionRatio) 156 | returnVal["Codec Bitrate"] = ffi.string(res.CodecBitrate) 157 | returnVal["Sensor Area Captured"] = ffi.string(res.SensorAreaCaptured) 158 | returnVal["PAR Notes"] = ffi.string(res.PARNotes) 159 | returnVal["Aspect Ratio Notes"] = ffi.string(res.AspectRatioNotes) 160 | returnVal["Gamma Notes"] = ffi.string(res.GammaNotes) 161 | returnVal["Color Space Notes"] = ffi.string(res.ColorSpaceNotes) 162 | for key, value in pairs(returnVal) do 163 | if value ~= "" then 164 | metadata[key] = value 165 | end 166 | end 167 | if next(metadata) == nil then 168 | LogLine("Ignore Clip " .. filePath) 169 | return 170 | end 171 | if clip:SetMetadata(metadata) then 172 | --18.5开始Shutter修改为Shutter Speed 173 | if not clip:SetMetadata("Shutter", ffi.string(res.Shutter)) then 174 | clip:SetMetadata("Shutter Speed", ffi.string(res.Shutter)) 175 | end 176 | LogLine("Process clip " .. filePath .. " successfully.") 177 | return true, metadata 178 | else 179 | LogLine("Failed to set clip " .. filePath .. " metadata") 180 | end 181 | end 182 | end 183 | 184 | local tree_id = "MatchTree" 185 | 186 | local function GetClips(folder, result) 187 | for _, v in ipairs(folder:GetClipList()) do 188 | table.insert(result, v) 189 | end 190 | sub_folders = folder:GetSubFolders() 191 | for _, sub_folder in ipairs(sub_folders) do 192 | GetClips(sub_folder, result) 193 | end 194 | end 195 | 196 | local function AssignDataLevel(clip, metadata, assign_type) 197 | if metadata and metadata["Camera Manufacturer"] == "Atomos" then 198 | gamma_notes = metadata["Gamma Notes"] ~= nil and metadata["Gamma Notes"] or "" 199 | camera_notes = metadata["Camera Notes"] ~= nil and metadata["Camera Notes"] or "" 200 | if assign_type == 0 and (string.find(string.upper(gamma_notes), "LOG") or string.find(camera_notes, "Range: Legal")) or assign_type == 1 then 201 | if clip:SetClipProperty("Data Level", "Full") then 202 | LogLine(string.format("Assign %s data level full successfully.", clip:GetName())) 203 | else 204 | LogLine(string.format("Assign %s data level full failed.", clip:GetName())) 205 | return False 206 | end 207 | end 208 | end 209 | return true 210 | end 211 | 212 | local function ParseMetadata(clip) 213 | local result, metadata = ProcessClip(clip) 214 | if result then 215 | return metadata 216 | else 217 | return nil 218 | end 219 | end 220 | 221 | local function Execute(assign_data_level_enabled, assign_type, parse_metadata_enabled) 222 | if assign_data_level_enabled == nil then 223 | assign_data_level_enabled = true 224 | end 225 | if assign_type == nil then 226 | assign_type = 0 227 | end 228 | if parse_metadata_enabled == nil then 229 | parse_metadata_enabled = true 230 | end 231 | LogLine("Start match input color space and apply custom grading rules.") 232 | resolve = bmd.scriptapp("Resolve") 233 | project_manager = resolve:GetProjectManager() 234 | project = project_manager:GetCurrentProject() 235 | media_pool = project:GetMediaPool() 236 | root_folder = media_pool:GetRootFolder() 237 | local success = true 238 | is_rcm = string.find(project:GetSetting("colorScienceMode"), "davinciYRGBColorManaged") 239 | 240 | if not is_rcm then 241 | LogLine("RCM Not Enabled!") 242 | return false, "RCM Not Enabled!" 243 | end 244 | 245 | clips = {} 246 | 247 | GetClips(root_folder, clips) 248 | for _, clip in ipairs(clips) do 249 | metadata = parse_metadata_enabled and ParseMetadata(clip) or clip:GetMetadata() 250 | codec = clip:GetClipProperty('Video Codec') 251 | if not metadata or 'RED' == string.upper(codec) or string.find(string.upper(codec), 'RAW') then 252 | goto continue 253 | end 254 | if is_rcm then 255 | gamma_notes = metadata["Gamma Notes"] and metadata["Gamma Notes"]:lower() or "" 256 | color_space_notes = metadata["Color Space Notes"] and metadata["Color Space Notes"]:lower() or "" 257 | if gamma_notes == "" and color_space_notes == "" then 258 | goto continue 259 | end 260 | input_color_space = "" 261 | if color_space_match_map[gamma_notes] then 262 | input_color_space = color_space_match_map[gamma_notes][""] 263 | if input_color_space == nil then 264 | input_color_space = color_space_match_map[gamma_notes][color_space_notes] 265 | end 266 | end 267 | if input_color_space ~= nil and input_color_space ~= "" then 268 | if clip:GetClipProperty("Input Color Space") == input_color_space then 269 | LogLine(string.format("%s Input Color Space is already set", clip:GetName())) 270 | goto continue 271 | end 272 | if clip:SetClipProperty("Input Color Space", input_color_space) then 273 | LogLine(string.format("%s Set Input Color Space %s Successfully.", clip:GetName(), input_color_space)) 274 | else 275 | LogLine(string.format("%s Set Input Color Space %s Failed!", clip:GetName(), input_color_space)) 276 | end 277 | else 278 | success = false 279 | LogLine(string.format("%s Does Not Found Input Color Space Match Rule!", clip:GetName())) 280 | end 281 | end 282 | ::continue:: 283 | if assign_data_level_enabled then 284 | if not AssignDataLevel(clip, metadata, assign_type) then 285 | success = false 286 | end 287 | end 288 | end 289 | if success then 290 | LogLine("All Done, Have Fun!") 291 | return true 292 | else 293 | LogLine("Some error happened, please check console details.") 294 | return false 295 | end 296 | end 297 | 298 | local function MainWindow() 299 | local ui = fu.UIManager 300 | local disp = bmd.UIDispatcher(ui) 301 | 302 | --define the window UI layout 303 | win = disp:AddWindow({ 304 | ID = "RCMColorSpaceMatchWin", 305 | WindowTitle = "RCM Color Space Match", 306 | Spacing = 0, 307 | 308 | ui:VGroup { 309 | --color space match rule 310 | ui:Tree { ID = tree_id }, 311 | 312 | ui:VGap(2), 313 | 314 | ui:HGroup { 315 | Weight = 0, 316 | ui:CheckBox { ID = "EnableMetadataParser", Text = "Enable Metadata Parser", Weight = 0, Checked = true }, 317 | ui:CheckBox { ID = "EnableDataLevelAdjustment", Text = "Enable Assign Atomos Clips' Data Level", Weight = 0 }, 318 | ui:ComboBox { ID = "DataLevelAdjustmentType", Weight = 1 } 319 | }, 320 | 321 | ui:VGap(2), 322 | 323 | ui:HGroup { 324 | Weight = 0, 325 | ui:Button { Text = "Match", ID = "ExecuteButton", Weight = 0 }, 326 | ui:HGap(5), 327 | ui:Label { Font = ui:Font { Family = "Times New Roman" }, ID = "InfoLabel" } 328 | } 329 | } 330 | }) 331 | 332 | items = win:GetItems() 333 | 334 | --Add a header row. 335 | hdr = items[tree_id]:NewItem() 336 | hdr.Text[0] = "Gamma Notes" 337 | hdr.Text[1] = "Color Space Notes" 338 | hdr.Text[2] = "Input Color Space" 339 | items[tree_id]:SetHeaderItem(hdr) 340 | 341 | --Number of columns in the Tree list 342 | items[tree_id].ColumnCount = 3 343 | 344 | --Resize the Columns 345 | items[tree_id].ColumnWidth[0] = 200 346 | items[tree_id].ColumnWidth[1] = 200 347 | items[tree_id].ColumnWidth[2] = 260 348 | 349 | items.DataLevelAdjustmentType:AddItem('For Log and Legal Clips') 350 | items.DataLevelAdjustmentType:AddItem('For All Clips') 351 | 352 | for _, value in ipairs(color_space_match_list) do 353 | item = items[tree_id]:NewItem() 354 | item.Text[0] = value["manufacturer"] 355 | items[tree_id]:AddTopLevelItem(item) 356 | for _, detail in ipairs(value["details"]) do 357 | item_child = items[tree_id]:NewItem() 358 | item_child.Text[0] = detail["gamma_notes"] 359 | item_child.Text[1] = detail["color_science_mode"] 360 | item_child.Text[2] = detail["input_color_space"] 361 | item:AddChild(item_child) 362 | end 363 | item.Expanded = true 364 | end 365 | 366 | win:Resize({ 700, 480 }); 367 | win:RecalcLayout(); 368 | 369 | local function ShowMessage(message, t) 370 | t = t or 0 371 | if t == 0 then 372 | items.InfoLabel.Text = string.format("%s", message) 373 | elseif t == 1 then 374 | items.InfoLabel.Text = string.format("%s", message) 375 | end 376 | end 377 | 378 | function win.On.ExecuteButton.Clicked(ev) 379 | LogLine("Start Processing.") 380 | ShowMessage("Processing...") 381 | local success, message = Execute(items["EnableDataLevelAdjustment"]["Checked"], 382 | items["DataLevelAdjustmentType"]["CurrentIndex"], 383 | items["EnableMetadataParser"]["Checked"]) 384 | if success then 385 | ShowMessage("All Down. Have Fun!") 386 | else 387 | if message then 388 | ShowMessage(message, 1) 389 | else 390 | ShowMessage("Some processes failed, please check console details.", 1) 391 | end 392 | end 393 | end 394 | 395 | -- The window was closed 396 | function win.On.RCMColorSpaceMatchWin.Close(ev) 397 | disp:ExitLoop() 398 | end 399 | 400 | win:Show() 401 | disp:RunLoop() 402 | win:Hide() 403 | end 404 | 405 | if gui_mode == 1 then 406 | MainWindow() 407 | else 408 | Execute() 409 | end -------------------------------------------------------------------------------- /Fusion/Scripts/Utility/Lua/Script Installer.lua: -------------------------------------------------------------------------------- 1 | --MIT License 2 | -- 3 | --Copyright (c) 2021 Michael, https://github.com/fukco 4 | -- 5 | --Permission is hereby granted, free of charge, to any person obtaining a copy 6 | --of this software and associated documentation files (the "Software"), to deal 7 | --in the Software without restriction, including without limitation the rights 8 | --to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | --copies of the Software, and to permit persons to whom the Software is 10 | --furnished to do so, subject to the following conditions: 11 | -- 12 | --The above copyright notice and this permission notice shall be included in all 13 | --copies or substantial portions of the Software. 14 | -- 15 | --THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | --IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | --FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | --AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | --LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | --OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | --SOFTWARE. 22 | 23 | local ui = fu.UIManager 24 | local disp = bmd.UIDispatcher(ui) 25 | 26 | win = disp:AddWindow({ 27 | ID = 'ScriptInstaller', 28 | WindowTitle = 'Script Installer', 29 | Spacing = 10, 30 | 31 | ui:VGroup { 32 | ID = 'root', 33 | 34 | -- Add your GUI elements here: 35 | ui:HGroup { 36 | ui:Label { 37 | ID = 'FileLabel', 38 | Text = 'Zip File:', 39 | Weight = 0, 40 | }, 41 | ui:HGap(2), 42 | ui:Label { 43 | ID = 'FileTxt', 44 | Text = 'Please enter a zip file path.', 45 | Weight = 1.5, 46 | }, 47 | ui:Button { 48 | ID = 'FileButton', 49 | Text = 'Select', 50 | Weight = 0.25, 51 | }, 52 | }, 53 | 54 | ui:HGroup { 55 | ui:Label { 56 | Text = 'Path:', 57 | Weight = 0 58 | }, 59 | ui:HGap(12), 60 | ui:ComboBox { 61 | ID = 'MyCombo', 62 | Weight = 1.5 63 | }, 64 | ui:Button { 65 | ID = 'FolderButton', 66 | Text = 'Open', 67 | Weight = 0.25, 68 | }, 69 | }, 70 | 71 | ui:VGap(10), 72 | 73 | ui:HGroup { 74 | ui:Button { 75 | ID = 'Execute', 76 | Text = 'Execute', 77 | Weight = 0.5, 78 | }, 79 | 80 | ui:Label { 81 | ID = 'InfoLabel', 82 | Weight = 1, 83 | } 84 | }, 85 | }, 86 | }) 87 | win:Resize({ 500, 120 }); 88 | win:RecalcLayout(); 89 | 90 | -- The window was closed 91 | function win.On.ScriptInstaller.Close(ev) 92 | disp:ExitLoop() 93 | end 94 | 95 | -- Add your GUI element based event functions here: 96 | itm = win:GetItems() 97 | 98 | -- Add the items to the ComboBox menu 99 | itm.MyCombo:AddItem('for all users') 100 | itm.MyCombo:AddItem('for specific user') 101 | 102 | -- The Open File button was clicked 103 | function win.On.FileButton.Clicked(ev) 104 | selectedPath = fu:RequestFile() 105 | itm.FileTxt.Text = selectedPath 106 | FilePath = selectedPath 107 | end 108 | 109 | function win.On.FolderButton.Clicked(ev) 110 | -- Add the platform specific folder slash character 111 | osSeparator = package.config:sub(1,1) 112 | 113 | local path 114 | if itm.MyCombo.CurrentIndex == 0 then 115 | path = fusion:MapPath('AllData:') 116 | elseif itm.MyCombo.CurrentIndex == 1 then 117 | path = fusion:MapPath('UserData:') 118 | end 119 | -- Convert the PathMap and extract just the foldername from the filepath 120 | path = path:match('(.*' .. osSeparator .. ')') 121 | 122 | -- Open the folder view 123 | if bmd.fileexists(path) then 124 | bmd.openfileexternal('Open', path) 125 | end 126 | 127 | end 128 | 129 | function win.On.Execute.Clicked(ev) 130 | itm.InfoLabel.Text = '' 131 | if FilePath == nil then 132 | print("please select file path first!") 133 | return 134 | end 135 | local destinationPath 136 | if itm.MyCombo.CurrentIndex == 0 then 137 | destinationPath = fusion:MapPath('AllData:') 138 | elseif itm.MyCombo.CurrentIndex == 1 then 139 | destinationPath = fusion:MapPath('UserData:') 140 | end 141 | if ffi.os == "Windows" then 142 | script = 'Expand-Archive -Force -Path "' .. FilePath .. '" -DestinationPath "' .. destinationPath .. '"' 143 | local pipe = io.popen("powershell -command -", "w") 144 | 145 | pipe:write(script) 146 | pipe:close() 147 | elseif ffi.os == 'OSX' then 148 | os.execute('unzip -oq "' .. FilePath .. '" -d "' .. destinationPath .. '"') 149 | end 150 | itm.InfoLabel.Text = 'Execute Finished!' 151 | end 152 | 153 | win:Show() 154 | disp:RunLoop() 155 | win:Hide() 156 | -------------------------------------------------------------------------------- /Fusion/Scripts/Utility/Python/Color Grading Tool.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """For DaVinci Resolve Color Grading""" 3 | __author__ = "Michael" 4 | __version__ = "0.2.0" 5 | __license__ = "MIT" 6 | 7 | import collections 8 | import json 9 | import logging 10 | import os 11 | import pathlib 12 | import sys 13 | from datetime import datetime 14 | 15 | from get_resolve import get_bmd 16 | 17 | # create logger 18 | logger = logging.getLogger("color_grading_tool") 19 | logger.setLevel(logging.DEBUG) 20 | 21 | # create console handler and set level to debug 22 | ch = logging.StreamHandler() 23 | ch.setLevel(logging.DEBUG) 24 | 25 | # create formatter 26 | formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') 27 | 28 | # add formatter to ch 29 | ch.setFormatter(formatter) 30 | 31 | # add ch to logger 32 | logger.addHandler(ch) 33 | 34 | pathname = os.path.dirname(sys.argv[0]) 35 | filename = "conf.json" 36 | json_file = os.path.join(pathname, filename) 37 | 38 | custom_rules = "Custom Rules" 39 | default_color_version_name = "Auto Generate Color Version" 40 | 41 | data = {} 42 | if pathlib.Path(json_file).is_file(): 43 | f = open(json_file, mode="r", encoding="utf-8") 44 | logger.debug(f"Open Json File {json_file}") 45 | data = json.load(f) 46 | f.close() 47 | 48 | input_color_space_list = [] 49 | if data.get("Color Space Match Rules") and data.get("Color Space Match Rules").get("rules"): 50 | for item in data.get("Color Space Match Rules").get("rules"): 51 | for detail in item.get("details"): 52 | if detail.get("Input Color Space") not in input_color_space_list: 53 | input_color_space_list.append(detail.get("Input Color Space")) 54 | drx_lists = [] 55 | if data.get("DRX") and data.get("DRX").get("lists"): 56 | drx_lists = data.get("DRX").get("lists") 57 | drx_map = dict((x.get("name"), x.get("path")) for x in drx_lists) if drx_lists else {} 58 | copyTypeOptions = ['All', 'Same Clip', 'Same Camera Type', 'Same Camera Serial #', 'Same Keywords', 59 | 'Same Input Color Space', 60 | 'Same Clip Color', 'Same Flags'] 61 | 62 | 63 | def get_all_timeline_item(): 64 | track_count = timeline.GetTrackCount("video") 65 | result = [] 66 | for index in range(track_count): 67 | items = timeline.GetItemListInTrack("video", index) 68 | if items and len(items) > 0: 69 | result.extend(items) 70 | return result 71 | 72 | 73 | def handle_color_version(timeline_items, assign_color_version, color_version_name): 74 | if assign_color_version: 75 | for item in timeline_items: 76 | if not item.LoadVersionByName(color_version_name, 0): 77 | item.AddVersion(color_version_name, 0) 78 | 79 | 80 | def copy_grading(timeline_item, option, assign_color_version, color_version_name): 81 | logger.debug( 82 | "Current timelineItem is {}, Option is {}, Assign color version is {}, Color version name is {}".format( 83 | timeline_item.GetName(), option, assign_color_version, color_version_name)) 84 | timeline_items = get_all_timeline_item() 85 | target_items = [] 86 | if option == copyTypeOptions[0]: # All 87 | target_items = timeline_items 88 | elif option == copyTypeOptions[1]: # Same Clip 89 | for item in timeline_items: 90 | if item.GetMediaPoolItem().GetClipProperty( 91 | "File Path") == timeline_item.GetMediaPoolItem().GetClipProperty("File Path"): 92 | target_items.append(item) 93 | elif option == copyTypeOptions[5]: # Same Input Color Space 94 | for item in timeline_items: 95 | if item.GetMediaPoolItem().GetClipProperty( 96 | "Input Color Space") == timeline_item.GetMediaPoolItem().GetClipProperty( 97 | "Input Color Space"): 98 | target_items.append(item) 99 | elif option == copyTypeOptions[6]: # Clip Color 100 | for item in timeline_items: 101 | if item.GetClipColor() == timeline_item.GetClipColor(): 102 | target_items.append(item) 103 | elif option == copyTypeOptions[7]: # Flags 104 | for item in timeline_items: 105 | if item.GetFlags() == timeline_item.GetFlags(): 106 | target_items.append(item) 107 | else: 108 | param = option.lstrip("Same ") 109 | if param == "Keywords": 110 | for item in timeline_items: 111 | if collections.Counter(item.GetMediaPoolItem().GetMetadata(param).split(",")) == collections.Counter( 112 | timeline_item.GetMediaPoolItem().GetMetadata(param).split(",")): 113 | target_items.append(item) 114 | else: 115 | for item in timeline_items: 116 | if item.GetMediaPoolItem().GetMetadata(param) == timeline_item.GetMediaPoolItem().GetMetadata( 117 | param): 118 | target_items.append(item) 119 | handle_color_version(target_items, assign_color_version, color_version_name) 120 | if len(target_items) > 0 and not timeline_item.CopyGrades(target_items): 121 | logger.error("copy failed {}".format(target_items)) 122 | return False 123 | logger.info("Copy Grading Execute finished.") 124 | return True 125 | 126 | 127 | def main_window(): 128 | # some element IDs 129 | win_id = "com.xiaoli.ColorGradingTool" # should be unique for single instancing 130 | option_combo = "OptionCombo" 131 | save_button_id = "SaveButton" 132 | copy_button_id = "CopyButton" 133 | 134 | # check for existing instance 135 | win = ui.FindWindow(win_id) 136 | if win: 137 | win.Show() 138 | win.Raise() 139 | exit() 140 | 141 | # define the window UI layout 142 | win = dispatcher.AddWindow({ 143 | 'ID': win_id, 144 | 'Geometry': [600, 100, 700, 800], 145 | 'WindowTitle': "Color Grading Tool" 146 | }, 147 | ui.VGroup([ 148 | # timeline color grading copy 149 | ui.HGroup({"Weight": 0.05}, [ 150 | ui.Label({"Text": 'Timeline Color Grading Copy:', 'Font': ui.Font({'Family': "Times New Roman"}), 151 | "Alignment": {"AlignHCenter": True, "AlignTop": True}})]), 152 | ui.HGroup({"Weight": 0}, [ 153 | ui.HGap(70), 154 | 155 | ui.Label({ 156 | "Text": 'Copy To Timeline:', 157 | "Weight": 0 158 | }), 159 | 160 | ui.ComboBox({ 161 | "ID": 'CopyTypesCombo' 162 | }), 163 | 164 | ]), 165 | ui.HGroup({"Weight": 0}, [ 166 | ui.CheckBox({ 167 | "ID": 'CopyToAssignColorVersionCheckBox', 168 | "Weight": 0, 169 | "Text": 'Assign Color Version Name' 170 | }), 171 | 172 | ui.LineEdit({ 173 | "ID": 'CopyToColorVersionTxt', 174 | "PlaceholderText": default_color_version_name, 175 | }), 176 | ]), 177 | 178 | ui.VGap(15), 179 | 180 | # custom rule 181 | ui.VGroup({"ID": "CustomRuleId", "Weight": 5}, [ 182 | # option 183 | ui.HGroup({"Weight": 0}, [ 184 | ui.Label({"Text": 'Custom Grading Rules:', 'Font': ui.Font({'Family': "Times New Roman"}), 185 | "Alignment": {"AlignHCenter": True, "AlignTop": True}})]), 186 | 187 | ui.HGroup({"Weight": 0}, [ 188 | ui.Label({"Text": 'Option:', 'Font': ui.Font({'Family': "Times New Roman"}), "Weight": 0}), 189 | ui.HGap(5), 190 | ui.ComboBox({"ID": option_combo, "Weight": 1.5}), 191 | ui.Button({"ID": "OptionAddButton", "Text": "Add", "Weight": 0}), 192 | ui.Button({"ID": "OptionDeleteButton", "Text": "Delete", "Weight": 0})]), 193 | 194 | # details 195 | ui.HGroup({"Weight": 0}, [ 196 | ui.Button({"ID": "EntryAddButton", "Text": "Add Entry", "Weight": 0.25}), 197 | ui.HGap(0, 10) 198 | ]), 199 | 200 | ui.VGroup({"ID": "CustomRuleEntriesContainer", "Weight": 10}, [ 201 | ui.VGroup({"ID": "CustomRules"}), 202 | ]), 203 | ]), 204 | 205 | ui.HGroup({"Weight": 0}, [ 206 | ui.Button({"Text": "Copy Grades", "ID": copy_button_id, "Weight": 0}), 207 | ui.HGap(2), 208 | ui.Button({"Text": "Custom Grading", "ID": "ExecuteButton", "Weight": 0}), 209 | ui.HGap(2), 210 | ui.Button({"Text": "Save Rules", "ID": save_button_id, "Weight": 0}), 211 | ui.HGap(5), 212 | ui.Label({'Font': ui.Font({'Family': "Times New Roman"}), "ID": "InfoLabel"}) 213 | ]), 214 | ]) 215 | ) 216 | items = win.GetItems() 217 | 218 | # Add the items to the ComboBox menu 219 | for option in copyTypeOptions: 220 | items["CopyTypesCombo"].AddItem(option) 221 | 222 | items["CopyTypesCombo"]["CurrentIndex"] = 1 223 | 224 | def add_custom_rules_table(options): 225 | if len(options) <= 0: 226 | return 227 | entries = [] 228 | option_selected = {} 229 | for option in options: 230 | if option["selected"]: 231 | option_selected = option 232 | if "entries" in option: 233 | entries = option["entries"] 234 | # assign color version name 235 | win.GetItems()["CustomRules"].AddChild(ui.HGroup({"Weight": 0}, [ 236 | ui.CheckBox( 237 | {"Text": 'Assign Color Version Name', "Checked": option_selected.get("Assign Color Version Name"), 238 | "ID": "AssignColorVersionNameCheckBox", "Weight": 0}), 239 | ui.LineEdit({"ID": "ColorVersionName", "Weight": 5, "Text": option_selected.get("Color Version Name"), 240 | "PlaceholderText": default_color_version_name}) 241 | ])) 242 | win.GetItems()["CustomRules"].AddChild(ui.VGap(0, 10)) 243 | for index in range(len(entries) - 1, -1, -1): 244 | entry = entries[index] 245 | conditions = [] 246 | if "conditions" in entry: 247 | conditions = entry["conditions"] 248 | condition_tables = [] 249 | for j in range(len(conditions)): 250 | condition_row = [ui.ComboBox({"ID": f"ConditionKeyCombo_{index}_{j}", "Weight": 0.5}), 251 | ui.HGroup({"ID": f"ConditionContainer_{index}_{j}", "Weight": 1}), 252 | ui.Button({"ID": f"DeleteConditionButton_{index}_{j}", "Text": "Delete", "Weight": 0})] 253 | condition_tables.append(ui.HGroup(condition_row)) 254 | 255 | if drx_lists: 256 | drx_element = ui.ComboBox({"ID": f"drxFile_{index}"}) 257 | else: 258 | drx_element = ui.Label({"Text": "Please update DRX file lists first!", 259 | 'Font': ui.Font({'Family': "Times New Roman"}), "Weight": 2}) 260 | 261 | option_element = ui.HGroup([ 262 | ui.VGroup([ 263 | ui.HGroup([ 264 | ui.HGap(2), 265 | ui.Label( 266 | {"Text": f"Entry # {index + 1}", 'Font': ui.Font({'Family': "Times New Roman"}), 267 | "Weight": 0}), 268 | ui.HGap(140), 269 | drx_element, 270 | ui.HGap(100), 271 | ui.Button({"ID": f"EntryDeleteButton_{index}", "Text": "Delete Entry", "Weight": 0}), 272 | ]), 273 | ui.HGroup({"Weight": 0}, [ 274 | ui.VGroup({"Weight": 0}, [ 275 | ui.Button( 276 | {"ID": f"ConditionAddButton_{index}", "Text": "Add Condition", "Weight": 0})]), 277 | ui.VGroup({"ID": f"conditionRows_{index}"}, condition_tables) 278 | ]) 279 | ]), 280 | ]) 281 | win.GetItems()["CustomRules"].AddChild(option_element) 282 | 283 | def delete_entry(ev): 284 | index = int(ev["who"].split("_")[-1]) 285 | option_selected_index = win.GetItems()[option_combo]["CurrentIndex"] 286 | del data.get(custom_rules)["options"][option_selected_index]["entries"][index] 287 | repaint_custom_rules_table() 288 | 289 | def update_drx(ev): 290 | index = int(ev["who"].split("_")[-1]) 291 | option_selected_index = win.GetItems()[option_combo]["CurrentIndex"] 292 | entries = data.get(custom_rules)["options"][option_selected_index]["entries"] 293 | entries[index]["drx"] = win.GetItems()[f"drxFile_{index}"]["CurrentText"] 294 | 295 | def add_condition(ev): 296 | index = int(ev["who"].split("_")[-1]) 297 | option_selected_index = win.GetItems()[option_combo]["CurrentIndex"] 298 | data.get(custom_rules)["options"][option_selected_index]["entries"][index]["conditions"].append( 299 | {"key": "", "value": ""}) 300 | repaint_custom_rules_table() 301 | 302 | def delete_condition(ev): 303 | i = int(ev["who"].split("_")[-2]) 304 | j = int(ev["who"].split("_")[-1]) 305 | option_selected_index = win.GetItems()[option_combo]["CurrentIndex"] 306 | del data.get(custom_rules)["options"][option_selected_index]["entries"][i]["conditions"][j] 307 | repaint_custom_rules_table() 308 | 309 | def change_condition_key(ev): 310 | i = int(ev["who"].split("_")[-2]) 311 | j = int(ev["who"].split("_")[-1]) 312 | items = win.GetItems() 313 | option_selected_index = win.GetItems()[option_combo]["CurrentIndex"] 314 | value = data.get(custom_rules)["options"][option_selected_index]["entries"][i]["conditions"][j]["value"] 315 | data.get(custom_rules)["options"][option_selected_index]["entries"][i]["conditions"][j]["key"] = \ 316 | items[f"ConditionKeyCombo_{i}_{j}"]["CurrentText"] 317 | items[f"ConditionContainer_{i}_{j}"].RemoveChild(f"ConditionValue_{i}_{j}") 318 | items[f"ConditionContainer_{i}_{j}"].RemoveChild(f"ConditionValueColor_{i}_{j}") 319 | if items[f"ConditionKeyCombo_{i}_{j}"]["CurrentText"] == "Clip Color": 320 | items[f"ConditionContainer_{i}_{j}"].AddChild(ui.ComboBox({"ID": f"ConditionValue_{i}_{j}"})) 321 | items[f"ConditionContainer_{i}_{j}"].AddChild( 322 | ui.LineEdit({"ID": f"ConditionValueColor_{i}_{j}", "ReadOnly": True, "Weight": 0.2})) 323 | clip_colors = ["Orange", "Apricot", "Yellow", "Lime", "Olive", "Green", "Teal", "Navy", "Blue", 324 | "Purple", "Violet", "Pink", "Tan", "Beige", "Brown", "Chocolate"] 325 | for color in clip_colors: 326 | win.GetItems()[f"ConditionValue_{i}_{j}"].AddItem(color) 327 | if value and value in clip_colors: 328 | win.GetItems()[f"ConditionValue_{i}_{j}"]["CurrentIndex"] = clip_colors.index(value) 329 | win.On[f"ConditionValue_{i}_{j}"].CurrentIndexChanged = change_clip_color 330 | elif items[f"ConditionKeyCombo_{i}_{j}"]["CurrentText"] == "Flag": 331 | items[f"ConditionContainer_{i}_{j}"].AddChild(ui.ComboBox({"ID": f"ConditionValue_{i}_{j}"})) 332 | items[f"ConditionContainer_{i}_{j}"].AddChild( 333 | ui.LineEdit({"ID": f"ConditionValueColor_{i}_{j}", "ReadOnly": True, "Weight": 0.2})) 334 | flag_colors = ["Blue", "Cyan", "Green", "Yellow", "Red", "Pink", "Purple", "Fuchsia", "Rose", 335 | "Lavender", "Sky", "Mint", "Lemon", "Sand", "Cocoa", "Cream"] 336 | for color in flag_colors: 337 | win.GetItems()[f"ConditionValue_{i}_{j}"].AddItem(color) 338 | if value and value in flag_colors: 339 | win.GetItems()[f"ConditionValue_{i}_{j}"]["CurrentIndex"] = flag_colors.index(value) 340 | win.On[f"ConditionValue_{i}_{j}"].CurrentIndexChanged = change_flag 341 | elif items[f"ConditionKeyCombo_{i}_{j}"]["CurrentText"] == "Input Color Space": 342 | items[f"ConditionContainer_{i}_{j}"].AddChild(ui.ComboBox({"ID": f"ConditionValue_{i}_{j}"})) 343 | for input_color_space in input_color_space_list: 344 | win.GetItems()[f"ConditionValue_{i}_{j}"].AddItem(input_color_space) 345 | win.On[f"ConditionValue_{i}_{j}"].CurrentIndexChanged = change_input_color_space 346 | elif items[f"ConditionKeyCombo_{i}_{j}"]["CurrentText"] == "All": 347 | items[f"ConditionContainer_{i}_{j}"].AddChild( 348 | ui.LineEdit({"ID": f"ConditionValue_{i}_{j}", "Weight": 1, "ReadOnly": True, 349 | "PlaceholderText": "No need to input anything."})) 350 | else: 351 | items[f"ConditionContainer_{i}_{j}"].AddChild( 352 | ui.LineEdit({"ID": f"ConditionValue_{i}_{j}", "Weight": 1, "Text": value, 353 | "PlaceholderText": "Please Enter Condition Value."})) 354 | win.On[f"ConditionValue_{i}_{j}"].TextChanged = update_condition_value_text 355 | win.RecalcLayout() 356 | 357 | def update_condition_value_text(ev): 358 | i = int(ev["who"].split("_")[-2]) 359 | j = int(ev["who"].split("_")[-1]) 360 | items = win.GetItems() 361 | option_selected_index = items[option_combo]["CurrentIndex"] 362 | data.get(custom_rules)["options"][option_selected_index]["entries"][i]["conditions"][j]["value"] = \ 363 | items[f"ConditionValue_{i}_{j}"]["Text"] 364 | 365 | def change_clip_color(ev): 366 | condition_item = ev["who"].replace("ConditionValue_", "ConditionValueColor_") 367 | clip_colors = [{"R": 0.9215, "G": 0.4313, "B": 0.0039, "A": 1}, {"R": 1, "G": 0.6588, "B": 0.2, "A": 1}, 368 | {"R": 0.8313, "G": 0.6784, "B": 0.1215, "A": 1}, 369 | {"R": 0.6235, "G": 0.7764, "B": 0.0823, "A": 1}, 370 | {"R": 0.3725, "G": 0.6, "B": 0.1294, "A": 1}, {"R": 0.2666, "G": 0.5607, "B": 0.396, "A": 1}, 371 | {"R": 0.039, "G": 0.596, "B": 0.6, "A": 1}, {"R": 0, "G": 0.3215, "B": 0.4705, "A": 1}, 372 | {"R": 0.2627, "G": 0.4627, "B": 0.6313, "A": 1}, {"R": 0.6, "G": 0.447, "B": 0.6274, "A": 1}, 373 | {"R": 0.8156, "G": 0.3372, "B": 0.5529, "A": 1}, 374 | {"R": 0.9137, "G": 0.549, "B": 0.7098, "A": 1}, 375 | {"R": 0.7254, "G": 0.6862, "B": 0.5921, "A": 1}, 376 | {"R": 0.7686, "G": 0.6274, "B": 0.0039, "A": 1}, 377 | {"R": 0.6, "G": 0.4, "B": 0.0039, "A": 1}, {"R": 0.549, "G": 0.3529, "B": 0.247, "A": 1}] 378 | items = win.GetItems() 379 | current_index = items[ev["who"]]["CurrentIndex"] 380 | items[condition_item].SetPaletteColor('All', 'Base', clip_colors[current_index]) 381 | i = int(ev["who"].split("_")[-2]) 382 | j = int(ev["who"].split("_")[-1]) 383 | option_selected_index = win.GetItems()[option_combo]["CurrentIndex"] 384 | data.get(custom_rules)["options"][option_selected_index]["entries"][i]["conditions"][j]["value"] = \ 385 | items[f"ConditionValue_{i}_{j}"]["CurrentText"] 386 | 387 | def change_flag(ev): 388 | condition_item = ev["who"].replace("ConditionValue_", "ConditionValueColor_") 389 | flag_colors = [{"R": 0, "G": 0.498, "B": 0.8901, "A": 1}, {"R": 0, "G": 0.8078, "B": 0.8156, "A": 1}, 390 | {"R": 0, "G": 0.6784, "B": 0, "A": 1}, {"R": 0.9411, "G": 0.6156, "B": 0, "A": 1}, 391 | {"R": 0.8823, "G": 0.1411, "B": 0.0039, "A": 1}, {"R": 1, "G": 0.2666, "B": 0.7843, "A": 1}, 392 | {"R": 0.5647, "G": 0.0745, "B": 0.6, "A": 1}, 393 | {"R": 0.7529, "G": 0.1803, "B": 0.4352, "A": 1}, 394 | {"R": 1, "G": 0.6313, "B": 0.7254, "A": 1}, {"R": 0.6313, "G": 0.5764, "B": 0.7843, "A": 1}, 395 | {"R": 0.5725, "G": 0.8862, "B": 0.9921, "A": 1}, {"R": 0.447, "G": 0.8588, "B": 0, "A": 1}, 396 | {"R": 0.8627, "G": 0.9137, "B": 0.3529, "A": 1}, 397 | {"R": 0.745, "G": 0.5686, "B": 0.3686, "A": 1}, 398 | {"R": 0.4313, "G": 0.3176, "B": 0.2627, "A": 1}, 399 | {"R": 0.9607, "G": 0.9615, "B": 0.8823, "A": 1}] 400 | items = win.GetItems() 401 | current_index = items[ev["who"]]["CurrentIndex"] 402 | items[condition_item].SetPaletteColor('All', 'Base', flag_colors[current_index]) 403 | i = int(ev["who"].split("_")[-2]) 404 | j = int(ev["who"].split("_")[-1]) 405 | option_selected_index = win.GetItems()[option_combo]["CurrentIndex"] 406 | data.get(custom_rules)["options"][option_selected_index]["entries"][i]["conditions"][j]["value"] = \ 407 | items[f"ConditionValue_{i}_{j}"]["CurrentText"] 408 | 409 | def change_input_color_space(ev): 410 | i = int(ev["who"].split("_")[-2]) 411 | j = int(ev["who"].split("_")[-1]) 412 | items = win.GetItems() 413 | option_selected_index = win.GetItems()[option_combo]["CurrentIndex"] 414 | data.get(custom_rules)["options"][option_selected_index]["entries"][i]["conditions"][j]["value"] = \ 415 | items[f"ConditionValue_{i}_{j}"]["CurrentText"] 416 | 417 | def click_assign_color_version_name_check_box(ev): 418 | option_selected = get_selected_option() 419 | if win.GetItems()["AssignColorVersionNameCheckBox"]["Checked"]: 420 | option_selected["Assign Color Version Name"] = True 421 | else: 422 | option_selected["Assign Color Version Name"] = False 423 | 424 | def update_color_version_name(ev): 425 | option_selected = get_selected_option() 426 | if option_selected: 427 | option_selected.update({"Color Version Name": win.GetItems()["ColorVersionName"]["Text"]}) 428 | 429 | win.On["AssignColorVersionNameCheckBox"].Clicked = click_assign_color_version_name_check_box 430 | win.On["ColorVersionName"].TextChanged = update_color_version_name 431 | condition_keys = ["All", "Camera Type", "Camera Serial #", "Keyword", "Input Color Space", "Clip Color", "Flag"] 432 | for index in range(len(entries)): 433 | entry = entries[index] 434 | if win.GetItems().get(f"drxFile_{index}"): 435 | i = 0 436 | for drx_element in drx_lists: 437 | win.GetItems().get(f"drxFile_{index}").AddItem(drx_element.get("name")) 438 | if drx_element.get("name") == entry.get("drx"): 439 | win.GetItems().get(f"drxFile_{index}")["CurrentIndex"] = i 440 | i = i + 1 441 | win.On[f"drxFile_{index}"].CurrentIndexChanged = update_drx 442 | conditions = [] 443 | if "conditions" in entry: 444 | conditions = entry["conditions"] 445 | win.On[f"ConditionAddButton_{index}"].Clicked = add_condition 446 | win.On[f"EntryDeleteButton_{index}"].Clicked = delete_entry 447 | for j in range(len(conditions)): 448 | key = conditions[j]["key"] 449 | win.On[f"DeleteConditionButton_{index}_{j}"].Clicked = delete_condition 450 | win.On[f"ConditionKeyCombo_{index}_{j}"].CurrentIndexChanged = change_condition_key 451 | for i in range(len(condition_keys)): 452 | win.GetItems()[f"ConditionKeyCombo_{index}_{j}"].AddItem(condition_keys[i]) 453 | if condition_keys[i] == key: 454 | win.GetItems()[f"ConditionKeyCombo_{index}_{j}"]["CurrentIndex"] = i 455 | 456 | def show_message(message, t=0): 457 | if t == 0: 458 | win.GetItems()["InfoLabel"]["Text"] = f"{message}" 459 | elif t == 1: 460 | win.GetItems()["InfoLabel"]["Text"] = f"{message}" 461 | 462 | def add_entry(ev): 463 | selected_option = get_selected_option() 464 | if selected_option: 465 | if "entries" not in selected_option: 466 | selected_option["entries"] = [] 467 | if len(selected_option["entries"]) < 10: 468 | selected_option["entries"].append({"drx": "", "conditions": []}) 469 | repaint_custom_rules_table() 470 | show_message("") 471 | else: 472 | show_message("Do Not Add Entry More Than 10!", 1) 473 | else: 474 | show_message("Please Add Option First!", 1) 475 | 476 | def repaint_custom_rules_table(): 477 | if "options" in data.get(custom_rules): 478 | items = win.GetItems() 479 | items["CustomRuleEntriesContainer"].RemoveChild("CustomRules") 480 | items["CustomRuleEntriesContainer"].AddChild(ui.VGroup({"ID": "CustomRules"})) 481 | add_custom_rules_table(data[custom_rules]["options"]) 482 | else: 483 | add_custom_rules_table([]) 484 | win.RecalcLayout() 485 | 486 | def new_option_win(ev): 487 | new_option_win = dispatcher.AddWindow({ 488 | 'ID': "new_option_win", 489 | "WindowFlags": {"Window": True, "WindowStaysOnTopHint": True}, 490 | 'Geometry': [850, 450, 300, 100], 491 | 'WindowTitle': "Color Grading Tool", 492 | }, 493 | ui.VGroup([ 494 | ui.Label({"Text": "Please input new option name.", 'Font': ui.Font({'Family': "Times New Roman"}), 495 | "ID": "NewOptionLabelId", "Alignment": {"AlignHCenter": True, "AlignTop": True}, 496 | "Weight": 0}), 497 | 498 | ui.HGroup([ui.LineEdit( 499 | {"ID": "MyLineTxt", "Text": "My Option", "PlaceholderText": "Please Enter a option name."}), ]), 500 | 501 | # execute button 502 | ui.HGroup({"Weight": 0}, [ 503 | ui.Button({"Text": "Add", "ID": "OptionAddExecuteButton", "Weight": 0.25}) 504 | ]), 505 | ]) 506 | ) 507 | 508 | new_option_items = new_option_win.GetItems() 509 | 510 | def close_new_option_win(ev): 511 | dispatcher.ExitLoop() 512 | 513 | def add_option_to_config(option_name): 514 | if custom_rules not in data or "options" not in data.get(custom_rules): 515 | data.update({custom_rules: {"options": [{"name": option_name, "selected": True}]}}) 516 | else: 517 | if len(data[custom_rules]["options"]) >= 20: 518 | new_option_items["NewOptionLabelId"][ 519 | "Text"] = "Your Options is more than 20!" 520 | return False 521 | for option in data.get(custom_rules)["options"]: 522 | if option["name"] == option_name: 523 | new_option_items["NewOptionLabelId"][ 524 | "Text"] = "Your option name is exist! Please use another one!" 525 | new_option_items["MyLineTxt"].SetPaletteColor('All', 'Base', 526 | {"R": 1, "G": 0.125, "B": 0.125, 527 | "A": 1}) 528 | return False 529 | for option in data.get(custom_rules)["options"]: 530 | option["selected"] = False 531 | data.get(custom_rules)["options"].append({"name": option_name, "selected": True}) 532 | return True 533 | 534 | def new_option_execute(ev): 535 | if add_option_to_config(new_option_items["MyLineTxt"]["Text"]): 536 | combo_add_option(new_option_items["MyLineTxt"]["Text"]) 537 | dispatcher.ExitLoop() 538 | 539 | # assign event handlers 540 | new_option_win.On["new_option_win"].Close = close_new_option_win 541 | new_option_win.On["OptionAddExecuteButton"].Clicked = new_option_execute 542 | 543 | new_option_win.Show() 544 | dispatcher.RunLoop() 545 | new_option_win.Hide() 546 | return new_option_win, new_option_items 547 | 548 | def load_option_combo(): 549 | if data.get(custom_rules) and "options" in data.get(custom_rules) and data.get(custom_rules)["options"]: 550 | i = 0 551 | items = win.GetItems() 552 | for option in data.get(custom_rules)["options"]: 553 | items[option_combo].AddItem(option["name"]) 554 | if option["selected"]: 555 | items[option_combo]["CurrentIndex"] = i 556 | i += 1 557 | 558 | def combo_add_option(option): 559 | items = win.GetItems() 560 | items[option_combo].AddItem(option) 561 | items[option_combo]["CurrentIndex"] = items[option_combo].Count() - 1 562 | 563 | def combo_delete_option(index): 564 | win.GetItems()[option_combo].RemoveItem(index) 565 | 566 | def delete_option(ev): 567 | if win.GetItems()[option_combo]["CurrentText"]: 568 | i = 0 569 | for option in data.get(custom_rules)["options"]: 570 | if option["name"] == win.GetItems()[option_combo]["CurrentText"]: 571 | data.get(custom_rules)["options"].remove(option) 572 | combo_delete_option(i) 573 | break 574 | i += 1 575 | 576 | def combo_change(ev): 577 | options = data.get(custom_rules)["options"] 578 | for index in range(len(options)): 579 | if index == win.GetItems()[option_combo]["CurrentIndex"]: 580 | options[index]["selected"] = True 581 | else: 582 | options[index]["selected"] = False 583 | repaint_custom_rules_table() 584 | 585 | def save_config(): 586 | write_file = open(json_file, mode="w", encoding="utf-8") 587 | json.dump(data, write_file, indent=2, ensure_ascii=False) 588 | write_file.close() 589 | 590 | def click_save_button(ev): 591 | save_config() 592 | show_message(f"Config Updated At {datetime.now().strftime('%H:%M:%S.%f')[:-3]}.") 593 | 594 | def click_copy_button(ev): 595 | current_timeline_item = timeline.GetCurrentVideoItem() 596 | if not current_timeline_item: 597 | show_message("Please open [Edit] or [Color] page to choose one timeline item!", 1) 598 | logger.warning("Please open [Edit] or [Color] page to choose one timeline item!") 599 | return 600 | option = items["CopyTypesCombo"]["CurrentText"] 601 | assign_color_version = items["CopyToAssignColorVersionCheckBox"]["Checked"] 602 | color_version_name = items["CopyToColorVersionTxt"]["Text"] 603 | if not color_version_name: 604 | color_version_name = default_color_version_name 605 | if copy_grading(current_timeline_item, option, assign_color_version, color_version_name): 606 | show_message("Finished.") 607 | else: 608 | show_message("Some error occurred, Please check log details!") 609 | 610 | def click_execute_button(ev): 611 | logger.info("Start Processing.") 612 | show_message("Processing...") 613 | save_config() 614 | if quick_grading_execute(): 615 | show_message("All Down. Have Fun!") 616 | else: 617 | show_message("Some process failed, please check console log details.", 1) 618 | 619 | def close(ev): 620 | dispatcher.ExitLoop() 621 | 622 | load_option_combo() 623 | 624 | # assign event handlers 625 | win.On[win_id].Close = close 626 | win.On["OptionDeleteButton"].Clicked = delete_option 627 | win.On["OptionAddButton"].Clicked = new_option_win 628 | win.On[option_combo].CurrentIndexChanged = combo_change 629 | win.On["ExecuteButton"].Clicked = click_execute_button 630 | win.On[save_button_id].Clicked = click_save_button 631 | win.On[copy_button_id].Clicked = click_copy_button 632 | win.On["EntryAddButton"].Clicked = add_entry 633 | win.Show() 634 | dispatcher.RunLoop() 635 | win.Hide() 636 | 637 | 638 | def get_selected_option(): 639 | if custom_rules in data and "options" in data.get(custom_rules): 640 | for option in data.get(custom_rules)["options"]: 641 | if option["selected"]: 642 | return option 643 | return data.get(custom_rules)["options"][0] 644 | return None 645 | 646 | 647 | def get_clips(folder, result): 648 | result.extend(folder.GetClipList()) 649 | sub_folders = folder.GetSubFolders() 650 | for sub_folder in sub_folders.values(): 651 | get_clips(sub_folder, result) 652 | 653 | 654 | def is_timeline_item_match_conditions(timeline_item, conditions): 655 | for condition in conditions: 656 | if not condition.get("key") or not condition.get("value"): 657 | conditions.remove(condition) 658 | if len(conditions) <= 0: 659 | return False 660 | clip = timeline_item.GetMediaPoolItem() 661 | metadata = clip.GetMetadata() 662 | for condition in conditions: 663 | key = condition.get("key") 664 | value = condition.get("value") 665 | if key == "All": 666 | continue 667 | elif key == "keyword": 668 | if value not in metadata.get("Keywords"): 669 | return False 670 | elif key == "Input Color Space": 671 | if clip.GetClipProperty("Input Color Space") != value: 672 | return False 673 | elif key == "Clip Color": 674 | if timeline_item.GetClipColor() != value: 675 | return False 676 | elif key == "Flag": 677 | flag_dict = clip.GetFlags() 678 | if flag_dict and value not in flag_dict.values(): 679 | return False 680 | else: 681 | if metadata.get(key) != value: 682 | return False 683 | return True 684 | 685 | 686 | def quick_grading_execute(): 687 | logger.info("Start match input color space and apply custom grading rules.") 688 | success = True 689 | 690 | logger.debug("Apply custom color grading rules begin") 691 | option_selected = get_selected_option() 692 | if option_selected: 693 | entries = option_selected.get("entries") if option_selected else [] 694 | track_count = timeline.GetTrackCount("video") 695 | logger.debug(f"Total track count: {track_count}") 696 | if len(entries) > 0: 697 | version_name = option_selected.get("Color Version Name") if option_selected.get( 698 | "Color Version Name") else default_color_version_name 699 | for index in range(1, int(track_count) + 1): 700 | timeline_items = timeline.GetItemListInTrack("video", index) 701 | for entry in entries: 702 | conditions = entry.get("conditions") 703 | drx_name = entry.get("drx") 704 | drx_path = drx_map.get(drx_name) 705 | target_items = [] 706 | for item in timeline_items: 707 | if is_timeline_item_match_conditions(item, conditions): 708 | if option_selected.get("Assign Color Version Name"): 709 | if not item.LoadVersionByName(version_name, 0): 710 | item.AddVersion(version_name, 0) 711 | logger.debug( 712 | f"{item.GetName()} apply drx [{drx_name}] to color version [{version_name}].") 713 | else: 714 | logger.debug(f"{item.GetName()} apply drx [{drx_name}] to current color version.") 715 | target_items.append(item) 716 | if len(target_items) and not timeline.ApplyGradeFromDRX(drx_path, 0, target_items): 717 | success = False 718 | logger.error(f"Unable to apply a still from {drx_path} to target items.") 719 | if success: 720 | logger.info("All Done, Have Fun!") 721 | return True 722 | else: 723 | logger.warning("Some error happened, please check console details.") 724 | return False 725 | 726 | 727 | if __name__ == '__main__': 728 | bmd = get_bmd() 729 | fusion = bmd.scriptapp("Fusion") 730 | ui = fusion.UIManager 731 | dispatcher = bmd.UIDispatcher(ui) 732 | resolve = bmd.scriptapp("Resolve") 733 | project_manager = resolve.GetProjectManager() 734 | project = project_manager.GetCurrentProject() 735 | mediaPool = project.GetMediaPool() 736 | rootFolder = mediaPool.GetRootFolder() 737 | timeline = project.GetCurrentTimeline() 738 | main_window() 739 | -------------------------------------------------------------------------------- /Fusion/Scripts/Utility/Python/DRX Management.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """For DRX File Management""" 4 | __author__ = "Michael" 5 | __version__ = "0.1.0" 6 | __license__ = "MIT" 7 | 8 | import json 9 | import logging 10 | import os 11 | import pathlib 12 | import sys 13 | from json import JSONDecodeError 14 | 15 | from get_resolve import get_bmd 16 | 17 | gui_mode = 1 18 | 19 | # create logger 20 | logger = logging.getLogger("color_grading_tool") 21 | logger.setLevel(logging.DEBUG) 22 | 23 | # create console handler and set level to debug 24 | ch = logging.StreamHandler() 25 | ch.setLevel(logging.DEBUG) 26 | 27 | # create formatter 28 | formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') 29 | 30 | # add formatter to ch 31 | ch.setFormatter(formatter) 32 | 33 | # add ch to logger 34 | logger.addHandler(ch) 35 | 36 | pathname = os.path.dirname(sys.argv[0]) 37 | filename = "conf.json" 38 | json_file = os.path.join(pathname, filename) 39 | data = {} 40 | folder = "" 41 | lists = [] 42 | 43 | if pathlib.Path(json_file).is_file(): 44 | try: 45 | f = open(json_file, mode="r", encoding="utf-8") 46 | logger.debug(f"Open Json File {json_file}") 47 | data = json.load(f) 48 | if data.get("DRX") and data.get("DRX").get("folder"): 49 | folder = data.get("DRX").get("folder") 50 | if data.get("DRX") and data.get("DRX").get("lists"): 51 | lists = data.get("DRX").get("lists") 52 | f.close() 53 | except JSONDecodeError: 54 | logger.error("Invalid json file!") 55 | 56 | 57 | def main_window(): 58 | win = dispatcher.AddWindow({ 59 | "ID": 'MyWin', 60 | "WindowTitle": 'DRX Management', 61 | "Geometry": [600, 300, 800, 500] 62 | }, [ 63 | ui.VGroup({"ID": 'root'}, [ 64 | # Add your GUI elements here: 65 | ui.HGroup({"Weight": 0}, [ 66 | ui.Label({"Text": "Folder:", "Weight": 0.25}), 67 | 68 | ui.Label( 69 | {"ID": "fileLabelId", 'Text': folder if folder else "Please Select DRX Folder Location", 70 | 'Weight': 1.5, 71 | 'Font': ui.Font({'Family': "Times New Roman", 'PixelSize': 12})}), 72 | 73 | ui.Button({"Text": "Select a Folder", "ID": "SelectButton", "Weight": 0}), 74 | 75 | ui.Button({"Text": "Update Lists", "ID": "UpdateButton", "Weight": 0}), 76 | ]), 77 | 78 | ui.VGap(5), 79 | 80 | ui.HGroup([ 81 | ui.Tree({"ID": "MyTree", "Weight": 1}) 82 | ]), 83 | 84 | ]), 85 | ]) 86 | # Add your GUI element based event functions here: 87 | itms = win.GetItems() 88 | # Add a header row. 89 | hdr = itms["MyTree"].NewItem() 90 | hdr["Text"][0] = "Name" 91 | hdr["Text"][1] = "Path" 92 | itms["MyTree"].SetHeaderItem(hdr) 93 | 94 | # Number of columns in the Tree list 95 | itms["MyTree"]["ColumnCount"] = 2 96 | 97 | # Resize the Columns 98 | itms["MyTree"]["ColumnWidth"][0] = 300 99 | itms["MyTree"]["ColumnWidth"][1] = 300 100 | 101 | def refresh_tree(): 102 | itms["MyTree"].Clear() 103 | if lists: 104 | for item in lists: 105 | detail = itms["MyTree"].NewItem() 106 | detail["Text"][0] = item.get("name") 107 | detail["Text"][1] = item.get("path") 108 | itms["MyTree"].AddTopLevelItem(detail) 109 | 110 | def click_folder_button(ev): 111 | target_path = fusion.RequestDir() 112 | logger.info('[folder] ', target_path) 113 | itms["fileLabelId"]["Text"] = target_path 114 | global folder 115 | folder = target_path 116 | 117 | def close_win(ev): 118 | dispatcher.ExitLoop() 119 | 120 | def execute(ev): 121 | refresh_lists() 122 | refresh_tree() 123 | 124 | win.On.MyWin.Close = close_win 125 | win.On.SelectButton.Clicked = click_folder_button 126 | win.On.UpdateButton.Clicked = execute 127 | 128 | refresh_tree() 129 | win.Show() 130 | dispatcher.RunLoop() 131 | win.Hide() 132 | 133 | 134 | def refresh_lists(): 135 | if folder: 136 | lists.clear() 137 | for root, dirs, files in os.walk(folder): 138 | for file in files: 139 | if file.endswith(".drx"): 140 | drx_path = os.path.join(root, file) 141 | relative_path = os.path.relpath(drx_path, folder) 142 | drx_name = os.path.splitext(relative_path)[0].replace(os.sep, "_") 143 | lists.append({"name": drx_name, "path": drx_path}) 144 | data.update({"DRX": {"lists": lists, "folder": folder}}) 145 | write_file = open(json_file, mode="w", encoding="utf-8") 146 | json.dump(data, write_file, indent=2, ensure_ascii=False) 147 | write_file.close() 148 | logger.info("DRX file list saved!") 149 | 150 | 151 | if __name__ == '__main__': 152 | bmd = get_bmd() 153 | print(dir(bmd)) 154 | resolve = bmd.scriptapp("Resolve") 155 | projectManager = resolve.GetProjectManager() 156 | project = projectManager.GetCurrentProject() 157 | mediaPool = project.GetMediaPool() 158 | rootFolder = mediaPool.GetRootFolder() 159 | timeline = project.GetCurrentTimeline() 160 | if "gui_mode" in locals().keys() and gui_mode: 161 | fusion = bmd.scriptapp("Fusion") 162 | ui = fusion.UIManager 163 | dispatcher = bmd.UIDispatcher(ui) 164 | main_window() 165 | else: 166 | refresh_lists() 167 | -------------------------------------------------------------------------------- /Fusion/Scripts/Utility/Python/Metadata Parser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import logging 3 | import os 4 | import platform 5 | import sys 6 | from ctypes import cdll, c_char_p, c_bool, Structure 7 | 8 | from get_resolve import get_bmd 9 | 10 | __author__ = "Michael" 11 | __version__ = "0.5.0" 12 | __license__ = "MIT" 13 | 14 | fields_list = [ 15 | ("IsSupportMedia", c_bool), 16 | ("Camera Type", c_char_p), 17 | ("Camera Manufacturer", c_char_p), 18 | ("Camera Serial #", c_char_p), 19 | ("Camera ID", c_char_p), 20 | ("Camera Notes", c_char_p), 21 | ("Camera Format", c_char_p), 22 | ("Media Type", c_char_p), 23 | ("Time-Lapse Interval", c_char_p), 24 | ("Camera FPS", c_char_p), 25 | ("Shutter Type", c_char_p), 26 | # ("Shutter", c_char_p), 27 | ("ISO", c_char_p), 28 | ("White Point (Kelvin)", c_char_p), 29 | ("White Balance Tint", c_char_p), 30 | ("Camera Firmware", c_char_p), 31 | ("Lens Type", c_char_p), 32 | ("Lens Number", c_char_p), 33 | ("Lens Notes", c_char_p), 34 | ("Camera Aperture Type", c_char_p), 35 | ("Camera Aperture", c_char_p), 36 | ("Focal Point (mm)", c_char_p), 37 | ("Distance", c_char_p), 38 | ("Filter", c_char_p), 39 | ("ND Filter", c_char_p), 40 | ("Compression Ratio", c_char_p), 41 | ("Codec Bitrate", c_char_p), 42 | ("Sensor Area Captured", c_char_p), 43 | ("PAR Notes", c_char_p), 44 | ("Aspect Ratio Notes", c_char_p), 45 | ("Gamma Notes", c_char_p), 46 | ("Color Space Notes", c_char_p)] 47 | 48 | 49 | class DRMetadata(Structure): 50 | _fields_ = fields_list 51 | 52 | def get_dict(self): 53 | return dict((f, getattr(self, f)) for f, _ in self._fields_) 54 | 55 | 56 | def get_clips(folder, result): 57 | result.extend(folder.GetClipList()) 58 | sub_folders = folder.GetSubFolders() 59 | for sub_folder in sub_folders.values(): 60 | get_clips(sub_folder, result) 61 | 62 | 63 | def get_cdll_lib(): 64 | path = os.path.dirname(sys.argv[0]) 65 | if sys.platform.startswith("win") or sys.platform.startswith("cygwin"): 66 | library = os.path.join(path, "resolve-metadata.dll") 67 | else: 68 | if platform.machine() == "x86_64": 69 | library = os.path.join(path, "resolve-metadata-amd64.dylib") 70 | else: 71 | library = os.path.join(path, "resolve-metadata-arm64.dylib") 72 | lib = cdll.LoadLibrary(library) 73 | lib.DRProcessMediaFile.argtypes = [c_char_p] 74 | lib.DRProcessMediaFile.restype = DRMetadata 75 | return lib 76 | 77 | 78 | def success_message(num): 79 | bmd = get_bmd() 80 | fusion = bmd.scriptapp("Fusion") 81 | ui = fusion.UIManager 82 | disp = bmd.UIDispatcher(ui) 83 | win = disp.AddWindow({ 84 | 'ID': 'MyWin', 85 | 'WindowTitle': 'Notification', 86 | 'Spacing': 10, }, [ 87 | 88 | ui.VGroup({'ID': 'root'}, [ 89 | 90 | ui.HGroup([ 91 | ui.Label({ 92 | 'Text': f"{num} clips in media pool has been parsed.\nMore details in console.", 93 | 'Alignment': {'AlignHCenter': True, 'AlignVCenter': True}, 94 | }) 95 | ]), 96 | 97 | ui.HGroup({ 98 | 'Weight': 0, 99 | }, [ 100 | ui.Button({ 101 | 'ID': 'B', 102 | 'Text': 'OK', 103 | }) 104 | ]), 105 | ]), 106 | ]) 107 | win.Resize([400, 120]) 108 | win.RecalcLayout() 109 | 110 | def close(ev): 111 | disp.ExitLoop() 112 | 113 | win.On['MyWin'].Close = close 114 | win.On['B'].Clicked = close 115 | 116 | win.Show() 117 | disp.RunLoop() 118 | win.Hide() 119 | 120 | 121 | if __name__ == "__main__": 122 | # create logger 123 | logger = logging.getLogger("metadata_parser") 124 | logger.setLevel(logging.DEBUG) 125 | # create console handler and set level to debug 126 | ch = logging.StreamHandler() 127 | ch.setLevel(logging.DEBUG) 128 | # create formatter 129 | formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') 130 | # add formatter to ch 131 | ch.setFormatter(formatter) 132 | # add ch to logger 133 | logger.addHandler(ch) 134 | 135 | logger.info("Processing...") 136 | resolve = get_bmd().scriptapp("Resolve") 137 | projectManager = resolve.GetProjectManager() 138 | project = projectManager.GetCurrentProject() 139 | mediaPool = project.GetMediaPool() 140 | rootFolder = mediaPool.GetRootFolder() 141 | 142 | clips = [] 143 | get_clips(rootFolder, clips) 144 | 145 | lib = get_cdll_lib() 146 | n = 0 147 | for clip in clips: 148 | file_path = clip.GetClipProperty("File Path") 149 | if len(file_path) > 0: 150 | resolve_meta_dict = lib.DRProcessMediaFile(file_path.encode("utf-8")).get_dict() 151 | if resolve_meta_dict: 152 | if not resolve_meta_dict["IsSupportMedia"]: 153 | logger.warning(f"{os.path.basename(file_path)} Not Supported.") 154 | continue 155 | else: 156 | del resolve_meta_dict["IsSupportMedia"] 157 | meta = {k: v for k, v in resolve_meta_dict.items() if v} 158 | if not meta: 159 | logger.debug(f"Ignore clip {os.path.basename(file_path)}.") 160 | else: 161 | if clip.SetMetadata(meta): 162 | logger.debug(f"Process {os.path.basename(file_path)} Successfully.") 163 | n += 1 164 | else: 165 | logger.error(f"Failed to set {os.path.basename(file_path)} metadata!") 166 | else: 167 | logger.error(f"Failed to parse clip {clip.GetName()}") 168 | logger.info("Done.") 169 | success_message(n) 170 | -------------------------------------------------------------------------------- /Fusion/Scripts/Utility/Python/get_resolve.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | 4 | def get_bmd(): 5 | try: 6 | # The PYTHONPATH needs to be set correctly for this import statement to work. 7 | # An alternative is to import the DaVinciResolveScript by specifying absolute path (see ExceptionHandler logic) 8 | import DaVinciResolveScript as bmd 9 | 10 | except ImportError: 11 | if sys.platform.startswith("darwin"): 12 | expectedPath = "/Library/Application Support/Blackmagic Design/DaVinci Resolve/Developer/Scripting/Modules/" 13 | elif sys.platform.startswith("win") or sys.platform.startswith("cygwin"): 14 | import os 15 | expectedPath = os.getenv( 16 | 'PROGRAMDATA') + "\\Blackmagic Design\\DaVinci Resolve\\Support\\Developer\\Scripting\\Modules\\" 17 | elif sys.platform.startswith("linux"): 18 | expectedPath = "/opt/resolve/libs/Fusion/Modules/" 19 | 20 | # check if the default path has it... 21 | # print("Unable to find module DaVinciResolveScript from $PYTHONPATH - trying default locations") 22 | try: 23 | import imp 24 | bmd = imp.load_source('DaVinciResolveScript', expectedPath + "DaVinciResolveScript.py") 25 | except ImportError: 26 | # No fallbacks ... report error: 27 | print( 28 | "Unable to find module DaVinciResolveScript - please ensure that the module DaVinciResolveScript is discoverable by python") 29 | print( 30 | "For a default DaVinci Resolve installation, the module is expected to be located in: " + expectedPath) 31 | sys.exit() 32 | 33 | return bmd -------------------------------------------------------------------------------- /Fusion/Templates/Edit/Effects/XiaoLi/Sony MILC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fukco/DaVinciResolveScript/02a5cfa9140b11cbc282520d8ffeea620d3e7972/Fusion/Templates/Edit/Effects/XiaoLi/Sony MILC.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Michael, https://github.com/fukco 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README-EN.md: -------------------------------------------------------------------------------- 1 | ### Coming Soon! 2 | If you want an english version of README, you can email me. [Contact Me](mailto:greatgeeklee@gmail.com) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GitHub release](https://img.shields.io/github/release/fukco/DaVinciResolveScript?&style=flat-square)](https://github.com/fukco/DaVinciResolveScript/releases/latest) 2 | [![Paypal Donate](https://img.shields.io/badge/donate-paypal-00457c.svg?logo=paypal&style=flat-square)](https://www.paypal.com/donate/?business=9BGFEVJPEFZAQ&no_recurring=0¤cy_code=USD&source=qr) 3 | [![Bilibili](https://img.shields.io/badge/dynamic/json?label=Bilibili&query=%24.data.follower&url=https%3A%2F%2Fapi.bilibili.com%2Fx%2Frelation%2Fstat%3Fvmid%3D26755389&style=social&logo=Bilibili)](https://space.bilibili.com/26755389) 4 | [![Youtube](https://img.shields.io/youtube/channel/subscribers/UCb7NsYnLmtPTn-yddNTcVKA?style=social&label=Youtube)](https://www.youtube.com/@geek-lee) 5 | 6 | [English Version](README-EN.md) 7 | 8 | ## 支持范围 9 | ### 达芬奇版本 10 | 17+
11 | Sony MILC使用了Multi Merge节点,需要18.5+版本
12 | 从达芬奇19开始,BMD修复了开启RCM,色差偏移的bug,所以移除了相关修复脚本,如果需要请从Deprecated目录手动下载并放置指定目录 13 | 14 | ### 操作系统 15 | * Windows 16 | * Mac OS(Intel/Apple Silicon均可) 17 | 18 | ## 使用方法 19 | 20 | ### 安装 21 | ![脚本安装](assets/脚本安装界面.png) 22 | 1. 将下载好的`Script Installer.lua`拖拽到Fusion界面中释放会出现UI界面或者复制粘贴代码到控制台运行
23 | 2. 文件选择框选择下载的压缩包
24 | 3. 下拉框选择`for all users` Or `for specfic user`
25 | 4. 点击执行 26 | 5. 安装压缩包后,后续可以通过点击【工作区/脚本/Script Installer】执行 27 | 28 | ### 使用 29 | 30 | 1. 元数据解析
31 | 在达芬奇内,打开 工作区->脚本->Metadata Parser 执行日志可以在 工作区->控制台 内查看 32 | 33 | 34 | 2. RCM色彩空间匹配
35 | 在达芬奇内,打开 工作区->脚本->RCM Color Space Match 执行日志可以在 工作区->控制台 内查看
36 | ![脚本安装](assets/RCM色彩匹配.png)
37 | a.视图中展示了RCM(色彩科学DaVinci YRGB Color Managed)色彩空间匹配规则
38 | b.支持开启元数据解析,如果未单独解析元数据,需要勾选此选项,默认勾选
39 | c.支持Atomos录机LOG素材以及Legal素材Data Level批量修改为Full,同时支持所有Atomos素材Data 40 | Level批量修改为Full(后者的操作你最好能弄明白原理是否需要使用此功能,错误操作会导致素材还原与预期不一致)
41 | d.点击执行,将媒体池中所有元数据符合a中规则的片段,按照规则指定其input color space,Atomos素材按照指定规则修改其Data Level
42 | e.如需使用使用无视图模式:编辑源代码,修改`gui_mode = 1`为`gui_mode = 0`,无视图模式默认开启元数据解析以及Atomos的LOG素材和Legal素材Data Level修改
43 | f.佳能相机暂无法解析到拍摄使用的LOG格式,暂时无法自动匹配,如需批量匹配佳能素材可以使用智能媒体夹过滤佳能素材并全选设置对应输入色彩空间 44 | 45 | 46 | 3. ~~RCM下色彩偏移修复~~【达芬奇19开始移除】
47 | Fusion页面任意节点右键脚本->RCM Color Shift Fix 48 | 18.1版本开始,Text+在RCM下做了一定的修改,请更新脚本到0.9.0版本 49 | 50 | 51 | 4. ~~DRX文件管理~~【暂未正式推出】
52 | 在达芬奇内,打开 工作区->脚本->DRX Management 执行日志可以在 工作区->控制台 内查看
53 | a.选择需要查找DRX文件的根目录
54 | b.点击刷新按钮,更新目录以及递归目录所有DRX文件列表
55 | c.如需使用无视图模式:编辑源代码,修改`gui_mode = 1`为`gui_mode = 0` 56 | 57 | 58 | 5. ~~调色工具~~【暂未正式推出】
59 | 在达芬奇内,打开 工作区->脚本->Color Grading Tool 执行日志可以在 工作区->控制台 内查看

60 | I.调色拷贝工具
61 | a.选择拷贝至所有时间线项/同一片段的时间线项/相同摄像机类型/相同摄像机序列号/相同关键词/相同输入色彩空间/相同片段色彩/相同旗标
62 | b.选择是否指定调色版本,如果指定颜色版本不存在,将自动创建相应的调色版本,未指定则使用当前调色版本
63 | c.点击"拷贝调色"执行

64 | II.自定义调色
65 | a.新增/修改选项(方案),提前设定
66 | b.为单个方案新增/修改预定义调色方案,需要选择DRX文件名(由DRX Management自动生成),匹配条件(条件支持所有、摄像机类型、摄像机序列号、关键词、输入色彩空间(RCM) 67 | 、片段色彩、旗标),多条件时匹配规则为“同时满足”,如果需要满足“或”的场景,可以使用“添加条目”来实现
68 | c.选择是否指定调色版本,如果指定颜色版本不存在,将自动创建相应的调色版本,未指定则使用当前调色版本
69 | d.点击"保存配置"进行保存配置或"自定义调色"直接执行,执行会自动保存配置
70 | 71 | 72 | ## 文件说明 73 | ![目录结构](assets/压缩包目录结构.png) 74 | 压缩包分为:全量版本以及仅Lua版本,前者脚本更全但是需要额外安装Python环境,后者无需安装Python环境使用达芬奇内置的Lua解释器,M1芯片MAC暂时不支持Python3.6,但是相对全量版本,脚本没有那么全面,如有需要我会尽力补全Lua版本脚本 75 | 76 | ### 脚本说明 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 |
文件夹文件名作用
Scripts/CompSony MILC.lua索尼微单参数界面
Scripts/UtilityMetadata Parser.lua元数据解析
RCM Color Space Match.luaRCM色彩空间匹配
Script Installer.lua脚本安装助手
102 | 103 | 达芬奇19移除的脚本 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 |
文件夹文件名作用
ConfigRCMColorSpaceMatchHotkey.fuFusion快捷键注册,快速调色启动
RCMFusionDisplayViewOn.fuFusion中RCM色彩修正显示节点
MacrosRCM Color Space Display.settingRCM Fusion颜色偏移显示修正
RCM Color Space Transform.settingRCM Fusion颜色偏移输出修正
Scripts/ToolRCM Color Shift Fix.luaRCM色彩修正Tool脚本
134 | 135 | 附加的Python脚本 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 |
文件夹文件名作用
Scripts/UtilityMetadata parser.py元数据解析
RCM Color Space Match.pyRCM色彩空间匹配
DRX Management.pyDRX管理
Color Grading Tool.py调色工具
160 | 161 | 补充说明 162 | * Metadata Parser(无视图脚本) 借助动态链接库解析视频文件元数据 163 | 164 | 165 | * RCM Color Space Match(支持有、无视图模式)
166 | 1、使用RCM时支持通过视频文件元数据自动匹配输入色彩空间
167 | 168 | 169 | * DRX Management(支持有、无视图模式)
170 | 1、整理DRX文件,并按照目录拼接生成DRX文件显示名,供Color Grading Tool使用 171 | 172 | 173 | * Color Grading Tool
174 | 1、单次调色可快速复制到时间线上其他项,支持全部或按照条件复制
175 | 2、基于DRX文件按条件匹配应用调色,支持配置持久化 (DRX文件是达芬奇能够识别的保存节点以及调色信息的文件) 176 | 177 | 178 | * .dll .dylib后缀文件为动态链接库,源码使用Golang编写,参见另外一个项目 179 | 180 | ## 关于源码 181 | 182 | 1. ~~源码基于Python3.6开发,并不适配Mac OS自带的2.7版本,特别标注的除外~~ 183 | 2. ~~Mac OS当前版本自带2.7以及高于3.6版本的Python,非M1芯片Mac建议安装Python3.6~~ 184 | 185 | ### 源码贡献 186 | 187 | * 如果你知道各种LOG格式对应的RCM的输入色彩空间,欢迎与我联系或者直接贡献源码。 188 | * 如果你有各相机厂商的Tag标签定义或者元数据相关的白皮书什么的也欢迎联系我。 189 | 190 | ## 更新注意事项 191 | 192 | ### 色彩空间匹配规则 193 | 194 | * 色彩空间匹配规则如果需要添加,可以与我联系,或者自己摸索代码依葫芦画瓢即可。 195 | 196 | ## Q&A 197 | 198 | **Q: Apple Silicon支持情况?**
199 | 支持 200 | 201 | **Q: 喂!有BUG啊,如何反馈?**
202 | A: 在你能找到我的方式内联系我,或者按照规范提交ISSUE。 203 | 204 | -------------------------------------------------------------------------------- /assets/RCM色彩匹配.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fukco/DaVinciResolveScript/02a5cfa9140b11cbc282520d8ffeea620d3e7972/assets/RCM色彩匹配.png -------------------------------------------------------------------------------- /assets/压缩包目录结构.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fukco/DaVinciResolveScript/02a5cfa9140b11cbc282520d8ffeea620d3e7972/assets/压缩包目录结构.png -------------------------------------------------------------------------------- /assets/脚本安装界面.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fukco/DaVinciResolveScript/02a5cfa9140b11cbc282520d8ffeea620d3e7972/assets/脚本安装界面.png --------------------------------------------------------------------------------