-- OBS File Collector Script -- This script collects all local files used in OBS sources and filters -- and copies them to a specified directory local obs = obslua local ffi = require("ffi") -- Windows-specific FFI declarations for file operations ffi.cdef[[ int CreateDirectoryA(const char* lpPathName, void* lpSecurityAttributes); int CopyFileA(const char* lpExistingFileName, const char* lpNewFileName, int bFailIfExists); ]] -- Mac-specific FFI declarations for file operations ffi.cdef[[ int mkdir(const char* pathname, int mode); ]] -- Global variables local collected_files = {} local target_directory = "" -- Helper function to check if a file exists function file_exists(file_path) local f = io.open(file_path, "r") if f then f:close() return true end return false end -- Helper function to process file path function process_file_path(file_path) if file_path and file_path ~= "" then -- Convert path separators to match the current OS local normalized_path = file_path:gsub([[\]], "/") -- Add to collection if not already present if not collected_files[normalized_path] then collected_files[normalized_path] = true print("Found file: " .. normalized_path) end end end -- Helper function to copy a file function copy_file(source_path, dest_path) -- Normalize paths for cross-platform compatibility source_path = source_path:gsub([[\]], "/") dest_path = dest_path:gsub([[\]], "/") if not file_exists(source_path) then print("Source file not found: " .. source_path) return false end -- Create target directory if it doesn't exist local dir = dest_path:match("(.*)/") if dir then -- Create all parent directories recursively local current_dir = "" for part in dir:gmatch("([^/]+)") do current_dir = current_dir .. part .. "/" if ffi.os == "Windows" then ffi.C.CreateDirectoryA(current_dir:gsub("/", "\\"), nil) else ffi.C.mkdir(current_dir, 0x1ED) end end end -- Copy the file using Lua's io facilities (works cross-platform) local success = false local source = io.open(source_path, "rb") if source then local dest = io.open(dest_path, "wb") if dest then success = true local chunk_size = 8192 while true do local chunk = source:read(chunk_size) if not chunk then break end dest:write(chunk) end dest:close() end source:close() end return success end -- Function to process source settings function process_source_settings(settings) if not settings then return end -- Get all properties of the source local properties = obs.obs_properties_create() if properties then -- Known file properties for different source types local common_file_properties = { "local_file", "file", "path", "script_path", "lua_script", -- Added for Lua script sources "python_script", -- Added for Python script sources "js_script" -- Added for JavaScript script sources } for _, prop_name in ipairs(common_file_properties) do local path = obs.obs_data_get_string(settings, prop_name) if path and path ~= "" then process_file_path(path) end end obs.obs_properties_destroy(properties) end end -- Function to process filters function process_filters(source, scene_name, source_name, scene_printed) local filters = obs.obs_source_enum_filters(source) for _, filter in ipairs(filters) do local filter_name = obs.obs_source_get_name(filter) local filter_id = obs.obs_source_get_id(filter) local settings = obs.obs_source_get_settings(filter) local filter_printed = false -- Common file properties to check for all filter types local file_properties = { "file", -- For image files "local_file", -- For media files "path", -- Generic path property "image_path", -- For image masks "mask_path", -- For mask filters "lut_file", -- For LUT/color correction filters "image_file" -- For image files } -- Check all possible file properties for any filter type for _, prop_name in ipairs(file_properties) do local file_path = obs.obs_data_get_string(settings, prop_name) if file_path and file_path ~= "" then if not scene_printed then print("\nScene: " .. scene_name) scene_printed = true end if not filter_printed then print("\n Source: " .. source_name) print(" Filter: " .. filter_name .. " (" .. filter_id .. ")") filter_printed = true end print(" File (" .. prop_name .. "): " .. tostring(file_path)) collected_files[file_path] = "filter" end end obs.obs_data_release(settings) end obs.source_list_release(filters) return scene_printed end -- Add this new function to process groups function process_group_sources(group_source) local scene = obs.obs_group_from_source(group_source) if scene then local items = obs.obs_scene_enum_items(scene) if items then for _, item in ipairs(items) do local source = obs.obs_sceneitem_get_source(item) if source then local source_id = obs.obs_source_get_id(source) local settings = obs.obs_source_get_settings(source) -- Process the source settings process_source_settings(settings) -- If this is another group, process it recursively if source_id == "group" then process_group_sources(source) end -- Process filters process_filters(source, "Group", obs.obs_source_get_name(source), false) obs.obs_data_release(settings) end end obs.sceneitem_list_release(items) end end end -- Function to collect files from all sources function collect_files() -- First get all scenes local scenes = obs.obs_frontend_get_scenes() if scenes then print("\n=== Collecting Files ===") for _, scene in ipairs(scenes) do local scene_name = obs.obs_source_get_name(scene) local scene_printed = false -- Get scene items in this scene local scene_items = obs.obs_scene_from_source(scene) if scene_items then local items = obs.obs_scene_enum_items(scene_items) if items then for _, item in ipairs(items) do local source = obs.obs_sceneitem_get_source(item) local source_name = obs.obs_source_get_name(source) local source_id = obs.obs_source_get_id(source) local has_files = false local settings = obs.obs_source_get_settings(source) -- Process based on source type if source_id == "group" then -- Process group sources process_group_sources(source) elseif source_id == "ffmpeg_source" then local local_file = obs.obs_data_get_string(settings, "local_file") if local_file and local_file ~= "" then if not scene_printed then print("\nScene: " .. scene_name) scene_printed = true end print("\n Source: " .. source_name) print(" Media file: " .. tostring(local_file)) process_file_path(local_file) has_files = true end elseif source_id == "image_source" then local file = obs.obs_data_get_string(settings, "file") if file and file ~= "" then if not scene_printed then print("\nScene: " .. scene_name) scene_printed = true end print("\n Source: " .. source_name) print(" Image file: " .. tostring(file)) process_file_path(file) has_files = true end elseif source_id == "browser_source" then local is_local_file = obs.obs_data_get_bool(settings, "is_local_file") local local_file = obs.obs_data_get_string(settings, "local_file") if is_local_file and local_file and local_file ~= "" then if not scene_printed then print("\nScene: " .. scene_name) scene_printed = true end print("\n Source: " .. source_name) print(" Browser file: " .. tostring(local_file)) -- Process the HTML file process_file_path(local_file) has_files = true -- Try to read the HTML file to find linked resources local f = io.open(local_file, "r") if f then local content = f:read("*all") f:close() -- Get the directory of the HTML file local dir = local_file:match("(.*[/\\])") -- Look for local resources in the HTML for resource in content:gmatch('src="([^"]+)"') do if not resource:match("^https?://") then -- This is a local resource local resource_path = dir .. resource print(" Found resource: " .. resource_path) process_file_path(resource_path) end end -- Also look for CSS files for css in content:gmatch('href="([^"]+%.css)"') do if not css:match("^https?://") then local css_path = dir .. css print(" Found CSS: " .. css_path) process_file_path(css_path) end end end end elseif source_id == "script_source" then local script_path = obs.obs_data_get_string(settings, "script_path") if script_path and script_path ~= "" then if not scene_printed then print("\nScene: " .. scene_name) scene_printed = true end print("\n Source: " .. source_name) print(" Script file: " .. tostring(script_path)) process_file_path(script_path) has_files = true end end -- Process filters scene_printed = process_filters(source, scene_name, source_name, scene_printed) obs.obs_data_release(settings) end obs.sceneitem_list_release(items) end end end obs.source_list_release(scenes) end end -- Function to copy collected files function copy_collected_files() local copied_count = 0 local failed_count = 0 -- Create media and filter_files directories with proper path separators local path_sep = ffi.os == "Windows" and "\\" or "/" local media_dir = target_directory .. path_sep .. "media" local filter_dir = target_directory .. path_sep .. "filter_files" -- Create base directories if ffi.os == "Windows" then ffi.C.CreateDirectoryA(media_dir:gsub("/", "\\"), nil) ffi.C.CreateDirectoryA(filter_dir:gsub("/", "\\"), nil) else ffi.C.mkdir(media_dir, 0x1ED) ffi.C.mkdir(filter_dir, 0x1ED) end for file_path, file_type in pairs(collected_files) do local file_name = file_path:match("([^/\\]+)$") local dest_path if file_type == "filter" then dest_path = filter_dir .. path_sep .. file_name else dest_path = media_dir .. path_sep .. file_name end if copy_file(file_path, dest_path) then copied_count = copied_count + 1 print("Copied: " .. file_path .. " -> " .. dest_path) else failed_count = failed_count + 1 print("Failed to copy: " .. file_path) end end return copied_count, failed_count end -- Script hook functions function script_description() return [[Collects and copies all local files used in OBS sources and filters to a specified directory. Author: Barathi Kasiraja Date : 2025-01-19]] end function script_properties() local props = obs.obs_properties_create() obs.obs_properties_add_path(props, "target_directory", "Target Directory", obs.OBS_PATH_DIRECTORY, "", nil) obs.obs_properties_add_button(props, "collect_button", "Collect Files", function() if target_directory == "" then print("Please select a target directory first!") return false end collected_files = {} collect_files() local copied, failed = copy_collected_files() print(string.format("Collection complete! Copied: %d, Failed: %d", copied, failed)) return true end) return props end function script_update(settings) target_directory = obs.obs_data_get_string(settings, "target_directory") end