Post subject: Reading and writing button presses from/to movie files
Player (80)
Joined: 8/5/2007
Posts: 865
I ran into a little hurdle in my input minimization script. I was unable to create a new GMV movie file from a series of button presses within a Lua script. This prompted me to create a small tool for reading/writing button presses directly from movie files. It's a pretty general tool, so I'm giving it its own thread. Purpose: To parse a movie file into a table of button presses or to reverse the process, turning a sequence of button presses into a movie file entirely within Lua. The table of button presses has three indices: the first index is the frame, the second is the player, and the third is the button pressed (as used in the Lua's input.set function). This is so that the movie can be edited quickly and intuitively using input.set, with the edited movie output directly to a (possibly new) file. Incidentally, because the format of the buttons table is essentially unchanged between the FM2 and GMV movie formats, this could allow for easy porting of a series of inputs from FM2 movie files to GMV movie files, or vice versa. I have no idea why anyone would want to do this, but hey, you can if you want. The script: Download RWmovies.lua
Language: lua

local function fromFM2(filename) local movie=io.open(filename) if not(movie) then print("File does not exist.") return end io.close(movie) local buttons={} --List of buttons in the order that they appear in the FM2 file. local buttonlist={"right","left","down","up","start","select","B","A"} local frame=1 for line in io.lines(filename) do if string.find(line,"|") then buttons[frame]={} for player=1,2 do buttons[frame][player]={} local start=9*player-6 for i=1,#buttonlist do if not(string.sub(line,start+i,start+i)==".") then buttons[frame][player][buttonlist[i]]=true end end end frame=frame+1 end end return buttons end local function fromGMV(filename) local movie=io.open(filename) if not(movie) then print("File does not exist.") return end local buttons={} --List of buttons in the order that they appear in the GMV file. local buttonlist={[0]="up","down","left","right","A","B","C","start"} movie:seek("set",64) str = movie:read("*a") for frame=1,math.floor(string.len(str)/3) do buttons[frame]={} for player=1,2 do buttons[frame][player]={} local binbuttons=string.byte(str,3*frame-3+player,3*frame-3+player) --This is really just NOT(binbuttons)... binbuttons=XOR(binbuttons,255) for i=0,#buttonlist do if AND(binbuttons,2^i)>0 then buttons[frame][player][buttonlist[i]]=true end end end end return buttons end --The headerfilename argument is optional. If it's there, the code plucks its header and creates a new file. If it's absent, the movie MUST replace an existing file. local function toFM2(buttons,filename,headerfilename) local buttonlist={"right","left","down","up","start","select","B","A"} local buttonlistFM2={"R","L","D","U","T","S","B","A"} if headerfilename then local from=io.open(headerfilename) local to=io.open(filename,"w+") local data=from:read("*a") to:write(data) to:flush() io.close(from) io.close(to) end local movie=io.open(filename,"r+b") local str=movie:read("*a") local starthere=string.find(str,"|") movie:seek("set") header=movie:read(starthere-1) io.close(movie) movie=io.open(filename,"w+") movie:write(header) for frame=1,#buttons do presses="|0|" for player=1,2 do for i=1,#buttonlistFM2 do if buttons[frame][player][buttonlist[i]] then presses=presses..buttonlistFM2[i] else presses=presses.."." end end presses=presses.."|" end presses=presses.."\n" movie:write(presses) end io.flush(movie) end local function toGMV(buttons,filename,headerfilename) local buttonlist={[0]="up","down","left","right","A","B","C","start"} local header="" if headerfilename then local from=io.open(headerfilename) header=from:read(64) io.close(from) else local from=io.open(filename) header=from:read(64) io.close(from) end local to=io.open(filename,"w+") to:write(header) for frame=1,#buttons do local presses="" for player=1,2 do local pressnum=0 for i=0,#buttonlist do if not(buttons[frame][player][buttonlist[i]]) then pressnum=pressnum+2^i end end presses=presses..string.char(pressnum) end presses=presses..string.char(255) to:write(presses) end io.flush(to) end --buttons=fromFM2("C:/someFM2file.fm2") --buttons=fromGMV("C:\\someGMVfile.gmv") --print(buttons) --buttons={{{},{}},{{["A"]=true},{["B"]=true}},{{["up"]=true},{["down"]=true}},{{["left"]=true},{["right"]=true}},{{["start"]=true},{["select"]=true}},{{},{}}} --buttons={{{},{}},{{["A"]=true},{["B"]=true}},{{["up"]=true},{["down"]=true}},{{["left"]=true},{["right"]=true}},{{["start"]=true},{["C"]=true}},{{},{}}} --toFM2(buttons,"test.fm2","C:/SomeDirectories/someFM2file.fm2") --toGMV(buttons,"test.txt","Test.gmv") --Format of buttons table: buttons[frame][player][button]
How to use the script: First, choose an FM2 movie or a GMV movie. Then, decide whether you would like to read the button presses from the movie file, write a series of button presses to the movie file, or both. I've commented out the basic features of the script at its end. You'll need to uncomment whatever you want to do. To read an FM2 movie file: • Uncomment the line starting "buttons=fromFM2(...". • Replace the default file path ("C:/someFM2file.fm2") with whatever movie you'd like to open. • Uncomment the print(buttons) line if you'd like to see the button presses in the Lua console. To read a GMV movie file: • Uncomment the line starting "buttons=fromGMV(...". • Replace the default file path ("C:\\someGMVfile.gmv") with whatever movie you'd like to open. • Uncomment the print(buttons) line if you'd like to see the button presses in the Lua console. To write an FM2 movie file: • Uncomment the line beginning "toFM2(...". • Pick a source for your buttons table. This means either using the fromFM2 function or uncommenting the first line with a sample buttons table ("buttons={{{},{}..."). This is the first argument. • Pick a destination file for your new FM2 movie. This is the second argument (default is "test.fm2"). If you do not have a third argument, this file must already exist. • Optionally, pick a source file from which to pluck the header information. This is the third argument. The third argument is used if you want to copy, instead of replace, a movie file and you still want to preserve things like the emulator settings and rerecord count. To write a GMV movie file: • Uncomment the line beginning "toGMV(...". • Pick a source for your buttons table. This means either using the fromGMV function or uncommenting the second line with a sample buttons table ("buttons={{{},{}..."). This is the first argument. • Pick a destination file for your new GMV movie. This is the second argument (default is "test.txt"). If you do not have a third argument, this file must already exist. • Optionally, pick a source file from which to pluck the header information. This is the third argument. The third argument is used if you want to copy, instead of replace, a movie file and you still want to preserve things like the emulator settings and rerecord count. Things to do: • Implement six-button controls in the GMV movie format (X, Y, Z, and mode are automatically presumed/set to false). • Implement commands and other formats specified here. • Expand to include other movie formats (BKM will likely be a priority). I probably won't be doing any of this, except by request or as I have need to, so just give the script a run, tell me how you like it, and make a request and I can probably add it in a couple of hours. Edit: Apparently T stands for sTart, not selecT. I've switched those two buttons (but I have not re-tested the script).