local targetFPS = 12 local uiWidth, uiHeight = 400, 240 local playerRoot = "gif_player" local framesSub = playerRoot .. "/frames" local copiedGifName = playerRoot .. "/current.gif" local maxAutoFrames = 5000 local tryAutoFFmpeg = true local CoreGui = game:GetService("CoreGui") local RunService = game:GetService("RunService") local function hasfn(fn) return type(fn) == "function" end local function safe_isfile(p) if hasfn(isfile) then local ok,res = pcall(isfile,p) if ok then return res end end return false end local function safe_readfile(p) if hasfn(readfile) then local ok,res = pcall(readfile,p) if ok then return res end end return nil end local function safe_writefile(p,d) if hasfn(writefile) then local ok,res = pcall(writefile,p,d) return ok end return false end local function safe_isfolder(p) if hasfn(isfolder) then local ok,res = pcall(isfolder,p) if ok then return res end end return false end local function safe_makefolder(p) if hasfn(makefolder) then pcall(makefolder,p) end end local screen = Instance.new("ScreenGui") screen.Name = "GifExecutorPlayer" screen.ResetOnSpawn = false screen.Parent = CoreGui local main = Instance.new("Frame", screen) main.Size = UDim2.new(0, uiWidth, 0, uiHeight) main.Position = UDim2.new(0, 10, 0, 10) main.BackgroundColor3 = Color3.fromRGB(30,30,30) main.BorderSizePixel = 0 local mainCorner = Instance.new("UICorner", main) mainCorner.CornerRadius = UDim.new(0,12) local title = Instance.new("TextLabel", main) title.Size = UDim2.new(1, 0, 0, 24) title.Position = UDim2.new(0,0,0,0) title.BackgroundTransparency = 1 title.Text = "GIF Player — filename in executor folder" title.TextColor3 = Color3.fromRGB(255,255,255) title.Font = Enum.Font.SourceSansBold title.TextSize = 16 local titleCorner = Instance.new("UICorner", title) titleCorner.CornerRadius = UDim.new(0,8) local input = Instance.new("TextBox", main) input.Size = UDim2.new(1, -20, 0, 28) input.Position = UDim2.new(0, 10, 0, 34) input.PlaceholderText = "example.gif or relative/path/to/example.gif" input.Text = "" input.ClearTextOnFocus = false input.Font = Enum.Font.SourceSans input.TextSize = 16 input.BackgroundColor3 = Color3.fromRGB(40,40,40) input.TextColor3 = Color3.fromRGB(230,230,230) input.BorderSizePixel = 0 local inputCorner = Instance.new("UICorner", input) inputCorner.CornerRadius = UDim.new(0,8) local btn = Instance.new("TextButton", main) btn.Size = UDim2.new(0, 140, 0, 30) btn.Position = UDim2.new(0, 10, 0, 72) btn.Text = "Load & Prepare" btn.Font = Enum.Font.SourceSansSemibold btn.TextSize = 16 btn.BackgroundColor3 = Color3.fromRGB(80,160,80) btn.TextColor3 = Color3.fromRGB(255,255,255) btn.BorderSizePixel = 0 local btnCorner = Instance.new("UICorner", btn) btnCorner.CornerRadius = UDim.new(0,8) local playBtn = Instance.new("TextButton", main) playBtn.Size = UDim2.new(0, 100, 0, 30) playBtn.Position = UDim2.new(0, 160, 0, 72) playBtn.Text = "Play" playBtn.Font = Enum.Font.SourceSansSemibold playBtn.TextSize = 16 playBtn.BackgroundColor3 = Color3.fromRGB(60,120,220) playBtn.TextColor3 = Color3.fromRGB(255,255,255) playBtn.BorderSizePixel = 0 local playCorner = Instance.new("UICorner", playBtn) playCorner.CornerRadius = UDim.new(0,8) local stopBtn = Instance.new("TextButton", main) stopBtn.Size = UDim2.new(0, 100, 0, 30) stopBtn.Position = UDim2.new(0, 270, 0, 72) stopBtn.Text = "Stop" stopBtn.Font = Enum.Font.SourceSansSemibold stopBtn.TextSize = 16 stopBtn.BackgroundColor3 = Color3.fromRGB(200,60,60) stopBtn.TextColor3 = Color3.fromRGB(255,255,255) stopBtn.BorderSizePixel = 0 local stopCorner = Instance.new("UICorner", stopBtn) stopCorner.CornerRadius = UDim.new(0,8) local status = Instance.new("TextLabel", main) status.Size = UDim2.new(1, -20, 0, 44) status.Position = UDim2.new(0, 10, 0, 110) status.TextWrapped = true status.BackgroundTransparency = 0.6 status.BackgroundColor3 = Color3.fromRGB(15,15,15) status.TextColor3 = Color3.fromRGB(220,220,220) status.Font = Enum.Font.SourceSans status.TextSize = 14 status.Text = "Status: waiting for filename..." local statusCorner = Instance.new("UICorner", status) statusCorner.CornerRadius = UDim.new(0,8) local img = Instance.new("ImageLabel", screen) local squareSize = math.min(600, math.max(160, math.floor(math.min(workspace.CurrentCamera.ViewportSize.X, workspace.CurrentCamera.ViewportSize.Y) * 0.5))) img.Size = UDim2.new(0, squareSize, 0, squareSize) img.Position = UDim2.new(0.5, -squareSize/2, 0.5, -squareSize/2) img.BackgroundTransparency = 1 img.ScaleType = Enum.ScaleType.Fit img.Image = "" local imgCorner = Instance.new("UICorner", img) imgCorner.CornerRadius = UDim.new(0,10) local function run_ffmpeg_extract(gifPath, outFolder, fps, w, h) local cmd = string.format('ffmpeg -y -i "%s" -vf "fps=%d,scale=%d:%d" "%s/frame_%%04d.png"', gifPath, fps, w, h, outFolder) if not safe_isfolder(outFolder) then safe_makefolder(outFolder) end if type(os) == "table" and type(os.execute) == "function" then local ok, res = pcall(function() return os.execute(cmd) end) if ok then return true, "ffmpeg executed (check frames in " .. outFolder .. ")" else return false, "os.execute or ffmpeg not available. Try extracting frames manually with ffmpeg:\n" .. cmd end else return false, "os.execute not available in this environment." end end local function build_frames_list(folder) local list = {} local framesTxtPath = folder .. "/frames.txt" if safe_isfile(framesTxtPath) then local content = safe_readfile(framesTxtPath) if content then for line in content:gmatch("[^\r\n]+") do line = line:match("^%s*(.-)%s*$") if line ~= "" then table.insert(list, line) end end return list end end for i = 1, maxAutoFrames do local png = string.format("%s/frame_%04d.png", folder, i) local jpg = string.format("%s/frame_%04d.jpg", folder, i) if safe_isfile(png) then table.insert(list, png) elseif safe_isfile(jpg) then table.insert(list, jpg) else if i == 1 then break end break end end return list end local playing = false local stopRequested = false local playbackThread local function stop_playback() playing = false stopRequested = true if playbackThread then playbackThread:Disconnect() playbackThread = nil end end local function play_loop(frames, fps) if not frames or #frames == 0 then status.Text = "Status: No frames to play." return end playing = true stopRequested = false local delay = 1 / (fps or targetFPS) if playbackThread then playbackThread:Disconnect() playbackThread = nil end playbackThread = RunService.Heartbeat:Connect(function(dt) end) spawn(function() while playing and not stopRequested do for i = 1, #frames do if stopRequested then break end local src = frames[i] local ok = pcall(function() img.Image = src end) if not ok then pcall(function() img.Image = "file://" .. src end) end local t0 = tick() while tick() - t0 < delay do if stopRequested then break end wait(0.01) end end if not playing or stopRequested then break end end playing = false stopRequested = false end) end local function handle_load(filename) if not filename or filename:match("^%s*$") then status.Text = "Status: Please enter a filename." return end status.Text = "Status: preparing..." if not safe_isfolder(playerRoot) then safe_makefolder(playerRoot) end if not safe_isfolder(framesSub) then safe_makefolder(framesSub) end local given = filename local found = false local sourcePath = nil if safe_isfile(given) then found = true sourcePath = given else local tryList = { given, "./" .. given, "C:/" .. given } for _,p in ipairs(tryList) do if safe_isfile(p) then found = true sourcePath = p break end end end if not found then status.Text = "Status: GIF not found at given path. Make sure the file exists in your executor's file area and try again." return end status.Text = "Status: GIF found. Copying..." local data = safe_readfile(sourcePath) if not data then status.Text = "Status: Failed to read GIF file contents." return end local ok = safe_writefile(copiedGifName, data) if not ok then status.Text = "Status: Failed to write GIF into player folder." return end status.Text = "Status: GIF copied." if tryAutoFFmpeg then status.Text = "Status: Attempting ffmpeg extraction..." local okff, msg = run_ffmpeg_extract(copiedGifName, framesSub, targetFPS, squareSize, squareSize) if okff then status.Text = "Status: ffmpeg started. Waiting..." wait(1.2) else status.Text = "Status: Auto ffmpeg unavailable. Use ffmpeg manually:\nffmpeg -i \"" .. copiedGifName .. "\" -vf \"fps=" .. targetFPS .. ",scale=" .. squareSize .. ":" .. squareSize .. "\" \"" .. framesSub .. "/frame_%04d.png\"\nThen put frames in " .. framesSub return end end wait(1.0) local frames = build_frames_list(framesSub) if #frames == 0 then status.Text = "Status: No frames detected. Extract frames with ffmpeg and place them in: " .. framesSub return end status.Text = "Status: Found " .. tostring(#frames) .. " frames. Ready." stop_playback() img.Image = frames[1] play_loop(frames, targetFPS) end btn.MouseButton1Click:Connect(function() handle_load(input.Text) end) input.FocusLost:Connect(function(enterPressed) if enterPressed then handle_load(input.Text) end end) playBtn.MouseButton1Click:Connect(function() if playing then return end local frames = build_frames_list(framesSub) if #frames == 0 then status.Text = "Status: No frames ready. Load & Prepare first." return end play_loop(frames, targetFPS) end) stopBtn.MouseButton1Click:Connect(function() stop_playback() status.Text = "Status: Stopped." end) if not hasfn(readfile) then status.Text = "Warning: readfile not available in this environment." end