Jump to content

All my products and services are free. All my costs are met by donations I receive from my users. If you enjoy using any of my products, please donate to support me. My bare hosting costs are currently not met so please consider donating by either clicking this text or the Patreon link on the right.

Patreon

Recommended Posts

Posted

Also, here you can see the fruits of my labour.... video of Future Pinball captures of all 3 screens using OBS MP in Pinball X.

 

 

 

While not every backglass had some kind of animation, they were still recorded. If it didn't have any change in the backglass, I would just use a still pic of the video for the backglass instead of playing a video.

 

PC Games:

 

 

Pinball FX 2 (using other pre-made backglass videos. I had to create the DMD / flaming PBFX 2 logos myself):

 

 

 

The Pinball Arcade (using other pre-made videos for backglass and playfield):

 

 

 

 

Now, I'm in the middle of getting over 150 Visual Pinball tables all setup before recording any media for them. There's lots already on the FTP, but they don't all match the newer versions of the tables and backglass that I have.

 

Then. its onto MAME....

Posted

Thanks... already have those, but I still have to make custom "marquee / dmd" pics for each game....  :(

 

Wish I could find a way to batch edit pictures in a way that would allow me to overlay a marquee pic into another pic (Mame logo), for each game....that would be lovely!

 

Unless Pinball X would support a "4th screen"... then that would make my life sooooooo much easier.

Posted
8 hours ago, TerryRed said:

Good to see it working for you!

I used multiple installations / instances because I think I was having issues with the profiles not quite allowing the screen rotation to work properly everytime, etc.... I can't remember for sure, but this method works everytime without issue. No need to change anything this way once setup for each screen / layout.  I do find that sometimes you will just get a still picture instead of a video capture in FP playfield..... just close EVERYTHING (OBS, FP,  FDMD, etc) and re-opening them should take care of that.... and you need to wait a few seconds after the table first appears to record sometimes as well.

 

For full colour range video and gameplay, make sure you have full colour range set for your screen resolution AND for your video output for each screen. Its a separate setting in the NVidia control Panel.  DVI connected screens (my 19 inch screen), may not give you the option for Full Range on your resolution section, but you can still change it in the video.  Also make sure your TV / monitors are set correctly as well in their setup options (HDMI black level) as well.     God, colour range can be a pain...

Doesn't look like OBS Studio has support for designating different profiles to run. Then you would have to have multiple installations to manage simultaneous captures. I would not have much of an issue doing that for myself, but it's not a solution suitable to publish I think. Appears to be a low priority for the developers, as well:

https://obsproject.com/forum/threads/are-they-any-shortcut-parameters.33385/#post-153345

The older classic version of OBS can load multiple instances with different profiles, but again, my playfield capture was black using Win10. So, going back to 0.657 is a no go. It would be possible to swap out ffmpeg with OBS Studio for the playfield capture engine and keep ffmpeg for backglass and DMD, but I'm not sure that I can get the output synchronized. At least not easily. That may not matter to most users anyway, so if people want to see it published then that's fine. 

Posted

Hi, thanks to gtxjoe for generously making his source code available. This is a version of PBXRecorder that supports capture of Future Pinball tables. It has all the features of 1.3beta except that it supports only Future Pinball captures via swapping ffmpeg with OBS Studio for the playfield capture engine. Backglass and DMD captures still require ffmpeg 

 

Latest OBS Studio for Windows:

https://obsproject.com/download#mp

Install OBS Studio in the same folder where you will run the script or executable. The installation contains both 32bit and 64bit executables. The script assumes 64bit.

Unzip the following file into the obs-studio folder, RETAIN THE FOLDER STRUCTURE WHEN YOU COPY

https://dl.dropboxusercontent.com/u/45430846/config.zip

Open a command prompt and navigate to obs-studio\bin\64bit

execute obs64.exe --portable

Agree to the license. You should see in the title bar that the profile and scene loaded are both named playfield.

Right click on  Sources: Display Capture and confirm that the screen that is being previewed is your playfield screen. Change the display in the pulldown if needed.

I think that's it.

The 64bit compiled executable is here:

https://dl.dropboxusercontent.com/u/45430846/PBXrecorder_1.3betaFP_x64.exe

As always, run as admin.

This can co-exist in the same folder as PBXRecorder for VP. The log will get overwritten but it creates an ini file with a different file name.

Output will look best and work trouble free with the LAV filters that are linked on the PinballX home page.

Because of the length of time it takes to load some of the more elaborate tables in my cab, I've increased the wait time from 30 seconds to 60 seconds.

I'm running FP at 3K using NVidea DSR. Some of the more elaborate tables (SLAMT1LT) has some slowdown while capturing on my system, so this may require some beefy hardware, or you might try capturing the different screens in separate sessions.

I'm assuming if you are capturing three screens that you are using FutureDMD. I'm not aware of another solution.

Capture settings are read from the registry and FutureDMD.ini. No sense trying to capture anything until you are sure the tables are playing correctly.

Works with or without BAM. Why would you not use BAM, though?

Source:

https://dl.dropboxusercontent.com/u/45430846/PBXrecorder_1.3betaFP.ahk

Changes from 1.3beta

; 1.3FP         Future Pinball support with playfield capture via OBS Studio
;                Add support for ™ and recognition of * (i.e, *) in Table and Description names
;               Some minor clean up for unused ErrorLevel parameters

Consider this beta as well. I've done some basic functionality tests in Win 7 x64 and some more thorough testing and catching up on my captures in Win 10. 

Again thanks to gtxjoe for providing such a great tool. 

  • Like 3
Posted

Hi Carney,

I was just curious as to the advantages of using OBS over ffmpeg? I tried OBS and didn't think the video quality was as good as ffmpeg. I might be wrong, I've only been into this for a few weeks, but i didn't care for OBS (as far as this project goes). I do like PBXRecorder very much - it was a blessing after thinking I'd have to do each table manually. Many thanks go to you, gtxjoe, terry, and others who have worked hard to create these utilities!

Posted

Ffmpeg does not capture the FP playfield, rendered with OpenGL. At least not for me running in full screen.

My mod only supports Future Pinball. For Visual Pinball continue using PBXRecorder.

Sent from my iPad using Tapatalk

Posted

As far as OBS quality, you have to get the settings for your specific codec right to make it look good. I originally was using NVidia's encoder which required a much higher data rate, but then eventually was able to get a great quality recording using the standard x264 (not ffmpeg) with just the right settings.

 

I used OBS MP (multi-platform), and was able to get better results than normal OBS.  You can capture at a very high quality large file format and use a program like Rip-bot or Tom's excellent Convert-it tool to do a batch conversion of your files afterward to a smaller file size with similar quality.

I originally captured all my videos at 60fps, but then used the above tools to convert them to a 30fps smaller file once I realised that Pinball X can only play "one" video at 60fps. When you have 2 or more videos playing at once, they will NOT play at a proper 60fps.... so having your videos at 30 fps is the better option.

 

I did all my videos for Visual Pinball using OBS MP (manually) with the exact same settings as I did for Future Pinball (except at 30 fps), and it worked great.

 

Using PBX Recorder would have made my job MUCH easier, but I wanted to record my videos at very specific times, so I had to do it manually. I'm too picky for my own good.

 

Now I have to figure out exactly how I want to do my MAME setup... videos are easy..... custom middle screen with a MAME / marquee for each game.... that would take a long time... trying to figure out my options here.... and what time I have for it.

Posted

The settings used for obs-studio are comparable to those used in the original PBXRecorder:

Open source H.264 lossless encoding

30 fps

YUV444 color format

Full color range

Identical code for transcode and downscale for the output file

In other words, it is the same quality results as the PBXRecorder for VP.

Sent from my iPad using Tapatalk

  • 4 weeks later...
Posted

Does pbxrecorder record sound as well?  It did a great job recording backglass and playfield videos. Ran overnight and generated 265 vids. However, there is no sound.  I examined settings and searched forum(s) but could not locate references related to sound being included / not included. 

Thank you very much for this fantastic tool!!!!!

Posted
Does pbxrecorder record sound as well?  It did a great job recording backglass and playfield videos. Ran overnight and generated 265 vids. However, there is no sound.  I examined settings and searched forum(s) but could not locate references related to sound being included / not included. 

Thank you very much for this fantastic tool!!!!!

Have you seen this?

Table sound when browsing tables

http://www.gameex.info/forums/index.php?/topic/13893-Table-sound-when-browsing-tables

These work really well because they're good representative sounds and highlights of the game being played, not just the sounds that may or may not exist when the table is turned on.

  • Like 1
Posted

ScottyVH, these sounds are very nice and will work well in my PinballX configuration.  Subscribed to site and and now downloading sounds from FTP site.  Thanks for the tip.

gStav,  Thank you for all your efforts to upload these sound files.  Very much appreciated. 

Posted

Thanks for the support guys. Don't miss the new launch audio feature in the new PinballX. Have made 100+ sounds all ready on the FTP :D

  • 3 weeks later...
Posted (edited)

Hi, the latest OBS studio added command line support for multiple profiles, so I ported gtxjoe's version 1.4 to support capturing Future Pinball tables. It has all the features of 1.4 except that it supports only Future Pinball and uses OBS Studio as the capture engline for playfield, backglass, DMD. Ffmpeg is still required to complete the transcode to generate the final output. 

Latest OBS Studio for Windows (portable zip):

https://github.com/jp9000/obs-studio/releases/download/0.14.1/OBS-Studio-0.14.1.zip

The installation contains both 32bit and 64bit executables. The script assumes 64bit and assumes that you will extract directly to your C: drive.

Unzip the following file into the obs-studio folder, RETAIN THE FOLDER STRUCTURE WHEN YOU COPY

https://dl.dropboxusercontent.com/u/45430846/config.zip

Open a command prompt and navigate to obs-studio\bin\64bit

execute obs64.exe --portable

Agree to the license. 

There are pre-existing profiles, scene collections, and sources for playfield, bg, and dmd. Each source captures an entire screen. You may want to confirm that each profile is capturing the correct screen. Click the settings icon for display capture in each profile and see that the preview is displaying the correct screen. You can change the screen using the pulldown menu. The script assumes that full screens are used for playfield and bg. Ffmpeg will automatically crop the DMD video based on your FutureDMD settings. I think that's it.

The 64bit compiled executable is here:

https://dl.dropboxusercontent.com/u/45430846/PBXrecorder_x64_1.4FP.exe

As always, run as admin.

This can co-exist in the same folder as PBXRecorder for VP. The log will get overwritten but it creates an ini file with a different file name.

Output will look best and work trouble free with the LAV filters that are linked on the PinballX home page.

Because of the length of time it takes to load some of the more elaborate tables in my cab, I've increased the wait time from 15 seconds to 60 seconds. It's simply what it takes on my system to load SlamT1lt's Nightmare on Elm Street and Robocop.

I'm running FP at 3K using NVidea DSR on a GTX 660. Using the same capturing engine, the videos are synced up better than when I was using a mix of OBS and ffmpeg. But my hardware is three years old now and running all of these capture simultaneously causes slowdown, so this may require some beefy hardware, or you might try capturing the different screens in separate sessions.

I'm assuming if you are capturing three screens that you are using FutureDMD. I'm not aware of another solution.

Capture settings are read from the registry and FutureDMD.ini. No sense trying to capture anything until you are sure the tables are playing correctly.

Works with or without BAM. Why would you not use BAM, though?

Source:

Spoiler

;PBX recorder 1.4FP (May 2, 2016) - Mod by Carny_Priest from original 1.4 by GTXJoe

LoadTime:= 60         ;How long to allow table to load/start (in sec)


;*****Start log file
FileDelete %A_ScriptDir%\PBXrecorder.log
FileAppend, %A_MMMM% %A_DD%`,%A_YYYY% %A_Hour%:%A_Min%:%A_Sec%`n, %A_ScriptDir%\PBXrecorder.log
FileAppend, Version 1.4betaFP`n, %A_ScriptDir%\PBXrecorder.log

;*******If table drag and drop use, record only that table *************************
Loop %0% 
{
    GivenPath := %A_Index%
    Loop %GivenPath%, 1
    {
        DragAndDropFile = %A_LoopFileLongPath%
    }
    SplitPath, DragAndDropFile, DragAndDropFileName, DragAndDropFileDir, DragAndDropFileExtOnly, DragAndDropFileNameOnly
    If ((InStr(DragAndDropFileExtOnly, "fpt")) = 1)
        DragAndDrop := 1
;    Else If ((InStr(DragAndDropFileExtOnly, "vpx")) = 1)
;        DragAndDrop := 1
    Else
    {
        Msgbox Error:       %DragAndDropFileName% is not a Future Pinball table
        ExitApp
    }
        
    FileAppend, Drag and Drop used:  %DragAndDropFile%`n, %A_ScriptDir%\PBXrecorder.log
    DragText = ---------------------------  Drag and drop mode  ---------------------------`n%DragAndDropFileName%`n------------------------------------------------------------------------------
    Break
}

;***************Monitor setup details
SysGet, MonitorCount, MonitorCount
SysGet, MonitorPrimary, MonitorPrimary
FileAppend, `nMonitor Count: %MonitorCount%`,  Primary Monitor: %MonitorPrimary%`n, %A_ScriptDir%\PBXrecorder.log
Loop, %MonitorCount%
{
    SysGet, MonitorName, MonitorName, %A_Index%
    SysGet, Monitor, Monitor, %A_Index%
    SysGet, MonitorWorkArea, MonitorWorkArea, %A_Index%
    ;MsgBox, Monitor %A_Index%: %MonitorRight%x%MonitorBottom% (%MonitorName%)
    FileAppend, Monitor %A_Index%: %MonitorRight%x%MonitorBottom% (%MonitorName%)`n, %A_ScriptDir%\PBXrecorder.log
}

;************************************************************************************************
;************************************************************************************************
;Create/Load PinballX recorder settings 
;************************************************************************************************
;************************************************************************************************
;************************************************************************************************
;************************************************************************************************
Version:=5
NumOfSettings:=12
LoadTimeX:=LoadTime*10
PBXRPauseMode:=0
BetaMode:=0           ;0=Normal mode,   1=Beta mode will store recorded media in a Beta folder
                    
;Load PBXrecorder settings if they exist
IfExist, %A_ScriptDir%\PBXrecorderFP.ini
{
    ;Load Existing settings from file
    ;*************************
    Loopcount:=0
    Loop, Read, %A_ScriptDir%\PBXrecorderFP.ini
    {
            If (Loopcount = 0)
                VersionRead:= A_LoopReadLine    
            Else If (Loopcount=1)                
                PinballXPath := A_LoopReadLine
            Else If (Loopcount=2)
                OnlyRecordMissingVideos := A_LoopReadLine    
            Else If (Loopcount=3)
                PFVideoOnly := A_LoopReadLine                
            Else If (Loopcount=4)
                BGVideoOnly := A_LoopReadLine                
            Else If (Loopcount=5)
                DMDVideoOnly := A_LoopReadLine                
            Else If (Loopcount=6)
                PFImageOnly := A_LoopReadLine                
            Else If (Loopcount=7)
                BGImageOnly := A_LoopReadLine                
            Else If (Loopcount=8)
                DMDImageOnly := A_LoopReadLine
            Else If (Loopcount=9)
                RecFormat := A_LoopReadLine
            Else If (Loopcount=10)
                RecTime := A_LoopReadLine            
            Else If (Loopcount=11)
                MediaFileNaming := A_LoopReadLine
                
            Loopcount:=Loopcount + 1
    }

    ;Check Version of ini file
    ;*************************
    If ((Version=VersionRead) and (LoopCount=NumOfSettings))    ;If ini version matches, process, else force user to update settings
    {
        ;Display ini settings
        ;*************************
        If (OnlyRecordMissingVideos = 2)
            TempStr2 := "`nFind and record missing media files only`n(Existing media files are kept)"
        Else If (OnlyRecordMissingVideos = 1)
            TempStr2 := "`nRecord complete media set for new and incomplete tables`n(Incomplete media sets are deleted/re-recorded)"
        Else If (OnlyRecordMissingVideos = 3)
            TempStr2 := "`nRecord complete media set for new tables only`n(If any media is found, the table is skipped. Tables with no media are recorded)"
        Else
            TempStr2 := "`nStart over and record complete media sets for all tables`n(All existing media is replaced)"
        
        If RecFormat = 1
            RecExt=mp4
        Else
            RecExt=f4v
            
        TempStr1 = `nMedia to Record (%RecTime% sec %RecExt% videos):`n
        If (PFVideoOnly=1)
            TempStr1 = %TempStr1%Playfield Videos`n
        If (BGVideoOnly=1)
            TempStr1 = %TempStr1%Backglass Videos`n
        If (DMDVideoOnly=1)
            TempStr1 = %TempStr1%DMD Videos`n
        If (PFImageOnly=1)
            TempStr1 = %TempStr1%Playfield Images`n
        If (BGImageOnly=1)
            TempStr1 = %TempStr1%Backglass Images`n
        If (DMDImageOnly=1)
            TempStr1 = %TempStr1%DMD Images`n
            
        If (MediaFileNaming=0)
            TempStr1 = %TempStr1%`nMedia filename based on Description tags`n
        Else
            TempStr1 = %TempStr1%`nMedia filename based on FP table filename`n
            
        If (DragAndDrop<>1)    ; if drag and drop not used, display settings
        {
            ;Prompt user if change to settings desired
            ;*************************
            SetTimer, CurrSettingsButtonNames, 50 
            Msgbox, 4, Current Recorder Settings, %DragText%`n`nPinballX Path: %PinballXPath%`n%TempStr2%`n%TempStr1%`n`n`nPress ESC to abort script            
            IfMsgBox, NO 
                Change := 1
        }
    }
    Else
    {
        Msgbox, New version of the video recorder detected.  Please update settings
        Change := 1
    }
    
}
Else    ;No PBXRecorderFP.ini found.  Need to Create the ini file
{
    ;******* PBXR settings needed ******************
    Change := 1
    PinballXPath = C:\PinballX
}

;*****Create Pinballx.net FTP login file if not exist
IfNotExist, %A_ScriptDir%\FTPLoginInfo.txt
{
    FileAppend, **** To enabled Wheel Image downloads - Replace USERNAME and PASSWORD with your Pinballx FTP login info (requires subscription)`n, %A_ScriptDir%\FTPLoginInfo.txt
    FileAppend, USERNAME`n, %A_ScriptDir%\FTPLoginInfo.txt
    FileAppend, PASSWORD`n, %A_ScriptDir%\FTPLoginInfo.txt
}

; User needs to update PBX Recorder ini settings
;*************************
If (Change = 1)        
{
    ;Pinballx folder
    Msgbox, 4,PBXrecorder Settings, PinballX folder: %PinballXPath%`n`n Do you want to change the Pinballx path?       `n
    IfMsgBox Yes    ;display folder selection dialog
    {
        FileSelectFolder, PinballXPath,,0,Select the Pinballx Folder
        PinballXPath := RegExReplace(PinballXPath, "\\$")
    }
    
    ;Pic Record Mode
    Gui, Show, W810 H170, Record Mode
    gui, font, s10, Arial
    Gui, Add, Radio, checked vMRadioGroup1, Find and record missing media files only (Existing media files are kept. No files deleted)
    Gui, Add, Radio, vMRadioGroup4, Record complete media set for new tables only (If any media is found, the table is skipped. Only tables with no media are recorded)
    Gui, Add, Radio, vMRadioGroup2, Record complete media set for new and incomplete tables (Missing media is recorded. Incomplete media sets are deleted/re-recorded)
    Gui, Add, Radio, vMRadioGroup3, Start over and record complete media sets for all tables (All existing media is deleted and re-recorded)
    Gui, Add, Text,, `n
    Gui, Add, Button, Default gMediaMode, Next
    Gui, Show
    WinWaitClose, Record Mode        

    ;Choose what media to record
    Gui, Show, W400 H280, Choose Media to Record
    gui, font, s10, Arial
    Gui, Add, Text,,Videos
    Gui, Add, Checkbox, vPFVid, Playfield Videos
    Gui, Add, Checkbox, vBGVid, Backglass Videos
    Gui, Add, Checkbox, vDMDVid, DMD Videos (Req 3 Monitor setup/FutureDMD)
    Gui, Add, Text,,`nImages
    Gui, Add, Checkbox, vPFImage, Playfield Image
    Gui, Add, Checkbox, vBGImage, Backglass Image
    Gui, Add, Checkbox, vDMDImage, DMD Image (Req 3 Monitor setup/FutureDMD)
    Gui, Add, Text,, `n
    Gui, Add, Button, Default gConfirm, Next
    Gui, Show
    WinWaitClose, Choose Media to Record

    ;Set the video recording time
    Gui, Show, W400 H260, Video Recording Time
    gui, font, s10, Arial
    Gui, Add, Text,,Select duration of video recordings:`n
    Gui, Add, Radio, vRadioGroup1, 5 seconds
    Gui, Add, Radio, vRadioGroup2, 15 seconds
    Gui, Add, Radio, vRadioGroup3, 30 seconds
    Gui, Add, Radio, Checked vRadioGroup4, 60 seconds
    Gui, Add, Radio, vRadioGroup5, 120 seconds
    Gui, Add, Radio, vRadioGroup6, 300 seconds
    Gui, Add, Text,, `n
    Gui, Add, Button, Default gSelect, Next
    Gui, Show
    WinWaitClose, Video Recording Time
    
    ;Recording Format
    Gui, Show, W500 H170, Recording Format
    gui, font, s10, Arial
    Gui, Add, Text,,Record all videos in:`n
    Gui, Add, Radio, checked vRadioGroupRF1, .f4v format 
    Gui, Add, Radio, vRadioGroupRF2, .mp4 format
    Gui, Add, Text,, `n
    Gui, Add, Button, Default gMediaR, Next
    Gui, Show
    WinWaitClose, Recording Format
    
    ;File naming convention
    Gui, Show, W500 H170, Media Naming
    gui, font, s10, Arial
    Gui, Add, Text,,Media filenames should be based on:`n
    Gui, Add, Radio, checked vRadioGroupMF1, Description tag            Example:  Medieval Madness (Williams 1997).%RecExt% 
    Gui, Add, Radio, vRadioGroupMF2, FP table filename        Example:  Medieval Madness ZED.%RecExt%
    Gui, Add, Text,, `n
    Gui, Add, Button, Default gMediaF, Next
    Gui, Show
    WinWaitClose, Media Naming
    
    ;******Save Settings*****
    FileDelete %A_ScriptDir%\PBXrecorderFP.ini
    FileAppend,%Version%`n%PinballXPath%`n%OnlyRecordMissingVideos%`n%PFVideoOnly%`n%BGVideoOnly%`n%DMDVideoOnly%`n%PFImageOnly%`n%BGImageOnly%`n%DMDImageOnly%`n%RecFormat%`n%RecTime%`n%MediaFileNaming%`n, %A_ScriptDir%\PBXrecorderFP.ini

    ;Display ini settings
    If (OnlyRecordMissingVideos = 2)
            TempStr2 := "`nFind and record missing media files only`n(Existing media files are kept)"
        Else If (OnlyRecordMissingVideos = 1)
            TempStr2 := "`nRecord complete media set for new and incomplete tables`n(Incomplete media sets are deleted/re-recorded)"
        Else If (OnlyRecordMissingVideos = 3)
            TempStr2 := "`nRecord complete media set for new tables only`n(If any media is found, the table is skipped. Tables with no media are recorded)"
        Else
            TempStr2 := "`nStart over and record complete media sets for all tables`n(All existing media is replaced)"
            
    If RecordingFormat = 1
        RecExt=mp4
    Else
        RecExt=f4v
            
    TempStr1 = `nMedia to Record (%RecTime% sec %RecExt% videos):`n
    If (PFVideoOnly=1)
        TempStr1 = %TempStr1%Playfield Videos`n
    If (BGVideoOnly=1)
        TempStr1 = %TempStr1%Backglass Videos`n
    If (DMDVideoOnly=1)
        TempStr1 = %TempStr1%DMD Videos`n
    If (PFImageOnly=1)
        TempStr1 = %TempStr1%Playfield Images`n
    If (BGImageOnly=1)
        TempStr1 = %TempStr1%Backglass Images`n
    If (DMDImageOnly=1)
        TempStr1 = %TempStr1%DMD Images`n

    If (MediaFileNaming=0)
        TempStr1 = %TempStr1%`nMedia filename based on Description tags`n
    Else
        TempStr1 = %TempStr1%`nMedia filename based on FP table filename`n

    Msgbox, 0, Current Recorder Settings, PinballX Path: %PinballXPath%`n%TempStr2%`n%TempStr1%`n(Wheel Image download support. See FTPLoginInfo.txt)`n`n`nPress ESC to abort script

}
FileAppend,`nPinballx.ini`n%Version%`n%PinballXPath%`n%OnlyRecordMissingVideos%`n%PFVideoOnly%`n%BGVideoOnly%`n%DMDVideoOnly%`n%PFImageOnly%`n%BGImageOnly%`n%DMDImageOnly%`n%RecFormat%`n%RecTime%`n%MediaFileNaming%`n`nIdentify all FP XML files...`n, %A_ScriptDir%\PBXrecorder.log

;Set FFMpeg, OBS and MediaOut folder locations
OBSPath = C:\obs-studio
FFMpegPath = %A_ScriptDir%\FFMpeg\bin
MediaOutPath = %PinballXPath%\Media

;************************************************************************************************
;************************************************************************************************
;2. Time to walk find all the FP xml files
;************************************************************************************************
;************************************************************************************************
;************************************************************************************************
;Arrays for finding and storing FP xml files and path information
XMLPathArray := Object()
XMLFileNameArray := Object()
WorkingPathArray := Object()
TablePathArray := Object()
ExecutableArray := Object()
;Array[j] := A_LoopField
; Array of xml search strings
Array := ["[FuturePinball]", "Enabled=", "WorkingPath=", "TablePath=", "Executable=", "[System_",  "Name=", "WorkingPath=", "TablePath=", "Executable=", "Enabled=", "SystemType=", "XXXX END XXXX"]
;                1                2           3               4             5             6           7            8            9             10              11          12                13

FPXMLCount=1
FPSearchIndex:=1
FPActiveFlag=0
FPKeepFlag = 1 ;Assume System is enabled
FPSystemFlag = 0
FPFirstSystemDone = 0

If (DragAndDrop<>1)
    PauseTime:=200
Else
    PauseTime:=0
    
;Open PinballX.ini file and identify FP xml files
;=================================================
Loop, Read, %PinballXPath%\Config\PinballX.ini
{
    ;****** Find 1st System ******
    If (FPFirstSystemDone=0 and FPActiveFlag=0)        ;Search for 1st System start
    {
        StringGetPos, Position, A_LoopReadLine, [FuturePinball]    
        If Position = 0 
        {
            FPActiveFlag = 1
            TempStr = %PinballXPath%\Databases\Future Pinball
            XMLPathArray[FPXMLCount] := TempStr
            XMLFileNameArray[FPXMLCount] := "Future Pinball"
            TempStr1 := XMLPathArray[FPXMLCount]
            TempStr2 := XMLFileNameArray[FPXMLCount]
            ;Msgbox, XMLPathArray[%FPXMLCount%]=%TempStr1%\%TempStr2%
        }
        else        
        {
            continue    ;Read next line
        }
    }
    else if (FPFirstSystemDone==0 and FPActiveFlag==1)    ;Collect info for 1st system
    {
        StringGetPos, Position, A_LoopReadLine, WorkingPath
        If Position = 0 
        {
            TempStr1 := SubStr(A_LoopReadLine, 13)
            WorkingPathArray[FPXMLCount] := TempStr1
            TempStr2 := WorkingPathArray[FPXMLCount]
            ;Msgbox WorkingPathArray[%FPXMLCount%]=%TempStr2%        
        }
        StringGetPos, Position, A_LoopReadLine, Tablepath
        If Position = 0 
        {
            TempStr1 := SubStr(A_LoopReadLine, 11)
            TablePathArray[FPXMLCount] := TempStr1
            TempStr2 := TablePathArray[FPXMLCount]
            ;Msgbox TablePathArray[%FPXMLCount%]=%TempStr2%        
        }
        StringGetPos, Position, A_LoopReadLine, Executable
        If Position = 0 
        {
            TempStr1 := SubStr(A_LoopReadLine, 12)
            ExecutableArray[FPXMLCount] := TempStr1
            TempStr2 := ExecutableArray[FPXMLCount]
            ;Msgbox ExecutableArray[%FPXMLCount%]=%TempStr2%
        }
        StringGetPos, Position, A_LoopReadLine, Enabled=True
        If Position = 0 
        {
            FPKeepFlag = 1
            ;MsgBox, enabled is true
        }
        StringGetPos, Position, A_LoopReadLine, Enabled=False
        If Position = 0 
        {
            FPKeepFlag = 0
            ;MsgBox, enabled is false
        }
        StringGetPos, Position, A_LoopReadLine, [     ;End of section found
        If Position = 0 
        {
            FPFirstSystemDone=1
            StringGetPos, pos, A_LoopReadLine, [
            if pos = 0
            {
                If FPKeepFlag = 1    ;Keep System info
                {
                    TempStr1 := XMLPathArray[FPXMLCount]
                    TempStr2 := XMLFileNameArray[FPXMLCount]
                    TempStr3 := WorkingPathArray[FPXMLCount]
                    TempStr4 := TablePathArray[FPXMLCount]
                    TempStr5 := ExecutableArray[FPXMLCount]
                    ;Msgbox, XMLPathArray[%FPXMLCount%]=%TempStr1%\%TempStr2%`n WorkingPathArray[%FPXMLCount%]=%TempStr3%`n TablePathArray[%FPXMLCount%]=%TempStr4%`n ExecutableArray[%FPXMLCount%]=%TempStr5%
                    FileAppend, FP System #%FPXMLCount%:`n%TempStr1%\%TempStr2%.xml`n%TempStr3%`n%TempStr4%`n%TempStr5%`n`n, %A_ScriptDir%\PBXrecorder.log

                    FPXMLCount++
                }
                Else
                {
                    FileAppend, `nFuture Pinball System is disabled in the XML file`n`n, %A_ScriptDir%\PBXrecorder.log
                    ;Msgbox, System Disabled (%FPSystemFlag% %FPKeepFlag%)
                }
                FPActiveFlag = 0
                FPKeepFlag = 1    ;Assume System is enabled        
            }
            
            ; Need to check this line if it is the start of a new system
            StringGetPos, Position, A_LoopReadLine, [System_
            If Position = 0 
            {
                FPActiveFlag = 1
            }

        }
    }
    ;****** Find Extra Systems ******
    Else If (FPFirstSystemDone=1 and FPActiveFlag = 0)        ;Search for start extra [System...] sections
    {
        StringGetPos, Position, A_LoopReadLine, [System_
        If Position = 0 
        {
            FPActiveFlag = 1
        }
        else        
        {
            continue    ;Read next line
        }
    }
    else if (FPFirstSystemDone=1 and FPActiveFlag = 1)    ;Collect info for the additional [System... sections
    {
        StringGetPos, Position, A_LoopReadLine, Name
        If Position = 0 
        {
            TempStr1 := SubStr(A_LoopReadLine, 6)
            TempStr = %PinballXPath%\Databases\%TempStr1%
            XMLPathArray[FPXMLCount] := TempStr
            XMLFileNameArray[FPXMLCount] := TempStr1
            ;Msgbox, XMLPathArray[%FPXMLCount%]=%TempStr%\%TempStr1%
        }
        StringGetPos, Position, A_LoopReadLine, WorkingPath
        If Position = 0 
        {
            TempStr1 := SubStr(A_LoopReadLine, 13)
            WorkingPathArray[FPXMLCount] := TempStr1
            TempStr2 := WorkingPathArray[FPXMLCount]
            ;Msgbox %TempStr2%        
        }
        StringGetPos, Position, A_LoopReadLine, Tablepath
        If Position = 0 
        {
            TempStr1 := SubStr(A_LoopReadLine, 11)
            TablePathArray[FPXMLCount] := TempStr1
            TempStr2 := TablePathArray[FPXMLCount]
            ;Msgbox %TempStr2%        
        }
        StringGetPos, Position, A_LoopReadLine, Executable
        If Position = 0 
        {
            TempStr1 := SubStr(A_LoopReadLine, 12)
            ExecutableArray[FPXMLCount] := TempStr1
            TempStr2 := ExecutableArray[FPXMLCount]
            ;Msgbox %TempStr2%
        }
        StringGetPos, Position, A_LoopReadLine, SystemType=2
        If Position = 0 
        {
            FPSystemFlag = 1
            ;MsgBox, System is FP
        }
        StringGetPos, Position, A_LoopReadLine, Enabled=True
        If Position = 0 
        {
            FPKeepFlag = 1
            ;MsgBox, System Enabled
        }
        StringGetPos, Position, A_LoopReadLine, Enabled=False
        If Position = 0 
        {
            FPKeepFlag = 0
            ;MsgBox, System Disabled

        }
        StringGetPos, Position, A_LoopReadLine, [
        If Position = 0 
        {
            If (FPSystemFlag = 1 and FPKeepFlag = 1)       ;Keep System info
            {
                TempStr1 := XMLPathArray[FPXMLCount]
                TempStr2 := XMLFileNameArray[FPXMLCount]
                TempStr3 := WorkingPathArray[FPXMLCount]
                TempStr4 := TablePathArray[FPXMLCount]
                TempStr5 := ExecutableArray[FPXMLCount]
                ;Msgbox, XMLPathArray[%FPXMLCount%]=%TempStr1%\%TempStr2%`n WorkingPathArray[%FPXMLCount%]=%TempStr3%`n TablePathArray[%FPXMLCount%]=%TempStr4%`n ExecutableArray[%FPXMLCount%]=%TempStr5%
                FileAppend, FP System #%FPXMLCount%:`n%TempStr1%\%TempStr2%.xml`n%TempStr3%`n%TempStr4%`n%TempStr5%`n`n, %A_ScriptDir%\PBXrecorder.log
            
                FPXMLCount++
            }
            Else
            {
                TempStr2 := XMLFileNameArray[FPXMLCount]
                FileAppend, Skipping this system: %TempStr2%.xml`n`n, %A_ScriptDir%\PBXrecorder.log
                ;Msgbox, System Disabled (%FPSystemFlag% %FPKeepFlag%)
            }                
            FPActiveFlag = 0
            FPKeepFlag = 1    ;Assume System is enabled
            FPSystemFlag = 0
            
            ; Need to check this line if it is the start of a new system
            StringGetPos, Position, A_LoopReadLine, [System_
            If Position = 0 
            {
                FPActiveFlag = 1
            }
        }
    }
}

;End of file found, process last system found
If (FPSystemFlag = 1 and FPKeepFlag = 1)       ;Keep System info
{
    TempStr1 := XMLPathArray[FPXMLCount]
    TempStr2 := XMLFileNameArray[FPXMLCount]
    TempStr3 := WorkingPathArray[FPXMLCount]
    TempStr4 := TablePathArray[FPXMLCount]
    TempStr5 := ExecutableArray[FPXMLCount]
    ;Msgbox, XMLPathArray[%FPXMLCount%]=%TempStr1%\%TempStr2%`n WorkingPathArray[%FPXMLCount%]=%TempStr3%`n TablePathArray[%FPXMLCount%]=%TempStr4%`n ExecutableArray[%FPXMLCount%]=%TempStr5%
    FileAppend, FP System #%FPXMLCount%:`n%TempStr1%\%TempStr2%.xml`n%TempStr3%`n%TempStr4%`n%TempStr5%`n`n, %A_ScriptDir%\PBXrecorder.log

    FPXMLCount++
}
Else
{
    TempStr2 := XMLFileNameArray[FPXMLCount]
    FileAppend, Skipping this system: %TempStr2%.xml`n`n, %A_ScriptDir%\PBXrecorder.log
    ;Msgbox, System Disabled (%FPSystemFlag% %FPKeepFlag%)
}    
FPActiveFlag = 0
FPKeepFlag = 0    
FPSystemFlag = 0
FPXMLCount--
FileAppend, Total number of FP systems found: %FPXMLCount%`n, %A_ScriptDir%\PBXrecorder.log
If FPXMLCount = 0
{
    Msgbox No XML files were found.  Check PinballX path.  
}

;************************************************************************************************
;************************************************************************************************
;3. If table selected via drag drop or right click, Allow user to add table to XML file
;************************************************************************************************
;************************************************************************************************
;************************************************************************************************
If (DragAndDrop=1)
{
    Loop, %FPXMLCount%        ;Search if table exists in XML files already
    {
        XMLPath:=XMLPathArray[A_Index]
        XMLFileName:=XMLFileNameArray[A_Index]
        WorkingPath:=WorkingPathArray[A_Index]
        TablePath:=TablePathArray[A_Index]
        Executable:=ExecutableArray[A_Index]
        FileAppend, `nAdding table to %XMLFileName%.xml (%A_Hour%:%A_Min%:%A_Sec%)`n`n, %A_ScriptDir%\PBXrecorder.log
            
        ;Open XML file and walk through every table
        ;==========================
        Loop, Read, %XMLPath%\%XMLFilename%.xml
        {
            IfInString, A_LoopReadLine, game name=
            {
                TableEnabled := 1
                StringGetPos, start, A_LoopReadLine, =
                StringGetPos, end, A_LoopReadLine, >
                XTable := SubStr(A_LoopReadLine, start+3, end-start-3)
                
                ;-----------------------------------------------------
                ;Convert special chars in Table Name so they are used correctly
                ;-----------------------------------------------------        
                ;Special handling for & in the Table Name.  Change &amp; to & 
                IfInString, XTable, &amp;
                {
                    StringGetPos, start, XTable, &amp;
                    TestTable := SubStr(XTable, 1, start)
                    TestTable2 := SubStr(XTable, start+6)
                    XTable = %TestTable%&%TestTable2%
                }
                ;Special handling for italian ' in the Table Name.  Change ’ to ' 
                IfInString, XTable, ’
                {
                    StringGetPos, start, XTable, ’
                    TestTable := SubStr(XTable, 1, start)
                    TestTable2 := SubStr(XTable, start+4)
                    XTable = %TestTable%'%TestTable2%
                }    
                ;Special handling for ™ in the Table Name.  Change &#8482; to ™ 
                IfInString, XTable, &#8482;
                {
                    StringGetPos, start, XTable, &#8482;
                    TestTable := SubStr(XTable, 1, start)
                    TestTable2 := SubStr(XTable, start+8)
                    XTable = %TestTable%™%TestTable2%
                }
                
                If inStr(XTable, DragAndDropFileNameOnly)
                {
                    XMLTableFound := 1    ;Table found in XML files, go ahead and record media
                    break
                }
            } ;End of Else IfInString, A_LoopReadLine, /game                
        } ;End of Loop, Read, %XMLPath%\%XMLFilename%.xml
        
        If (XMLTableFound=1) ;Table was found in XML files, go ahead and record media
        {
            break ;exit loop to record media
        }    
    } ;End of Loop, %FPXMLCount%

    If (XMLTableFound<>1)    ; XML table not found, so display XML dialog to add table to XML file
    {
        IPDBArray := Object()
        IPDBNameArray := Object()
        IPDBMfrArray := Object()
        IPDBYearArray := Object()
        IPDBTypeArray := Object()
        IPDBStart:=0
        XMLAllStart:=0
        XMLMatchFound:=0
        
        Loop, %FPXMLCount%    ;Create XML system list for the dialog
        {    
            theXMLString2:=XMLPathArray[A_Index]
            SplitPath, theXMLString2,,,, theXMLString
                        
            If (XMLAllStart = 0) ;Creating XML system list
            {
                XMLAllFPSysString=%theXMLString%|
                XMLAllStart := 1
            }
            Else
            {
                XMLAllFPSysString = %XMLAllFPSysString%%theXMLString%|
            }
            
            ;Check if table path matches the current FP system path, if so mark xml system as default by adding extra |
            If (XMLMatchFound=0)
            {
                xmltpath:=TablePathArray[A_Index]
                If (xmltpath = DragAndDropFileDir)
                {
                    XMLMatchFound:=1
                    XMLAllFPSysString = %XMLAllFPSysString%|
                    
                    ;also store executable for launch button
                    LaunchExecutable:=ExecutableArray[A_Index]

                    ;msgbox match%xmltpath% : %DragAndDropFileDir%
                }
            }
        }

        IfNotExist, %A_ScriptDir%\ipdb list.txt
            Msgbox, Please re-download PBX Recorder - ipdb list.txt file is missing
                
        Loop, Read, %A_ScriptDir%\ipdb list.txt         ; Create the ipdb pinball table list for the dialog
        {
            StringSplit, TempWordArray, A_LoopReadLine,|, 
            IPDBNameArray.Insert(TempWordArray1) ; Append this line to the array.
            IPDBMfrArray.Insert(TempWordArray4) ; Append this line to the array.
            IPDBYearArray.Insert(TempWordArray7) ; Append this line to the array.
            IPDBtypeArray.Insert(TempWordArray10) ; Append this line to the array.
            
            tempIPDB = %TempWordArray1% (%TempWordArray4% %TempWordArray7%)
            IPDBArray.Insert(tempIPDB) ; Append this line to the array.
            
            If (IPDBStart = 0)
            {
                allIPDBString = %tempIPDB%
                IPDBStart := 1
            }
            Else
                allIPDBString = %allIPDBString%|%tempIPDB%
        }

        ; File drag and drop - store filename
        Loop %0% 
        {
            GivenPath := %A_Index%
            Loop %GivenPath%, 1
                DragAndDropFile = %A_LoopFileLongPath%
            DragAndDrop := 1
            Break
        }
        SplitPath, DragAndDropFile,,,, DragAndDropFileNameOnly

        ;****** Display the XML Table entry dialog bpx ******
        Gui, Font, S11 CDefault, Verdana
        Gui, Add, Text, x42 y22 w90 h20 +right , XML List
        Gui, Add, Text, x42 y52 w90 h20 +right, Game
        Gui, Add, Text, x42 y80 w90 h20 +right, Description

        Gui, Add, DropDownList, x142 y20 w280 h25 r10 altSubmit vXMLFPSystem, %XMLAllFPSysString%  
        Gui, Add, Button, x442 y18 w100 h29 gXMLLaunch , Launch
        Gui, Add, Edit, x142 y50 w400 h25 vXMLTableFileName , %DragAndDropFileNameOnly%

        Gui, Add, Edit, x142 y80 w400 h20 vXMLDescText, 
        Gui, Add, DropDownList, x146 y102 w396 h80 r10 sort altSubmit vXMLDescSelected gGetXMLDescSelected, %allIPDBString%
;        IfExist, %DragAndDropFileDir%\%DragAndDropFileNameOnly%.directB2S
;            Gui, Add, Button, x22 y120 w70 h60 vXDB2S gXMLDB2S, DB2S Found
;        Else IfExist, %DragAndDropFileDir%\%DragAndDropFileNameOnly%.exe
;            Gui, Add, Button, x22 y120 w70 h60 vXDB2S gXMLDB2S, B2S.exe Found
;        Else
;            Gui, Add, Button, x22 y120 w70 h60 vXDB2S gXMLDB2S, DB2S Rename

        Gui, Add, Text, x142 y130 w220 h20 +center , Manufacturer
        Gui, Add, Text, x372 y130 w80 h20 +center , Year
        Gui, Add, Text, x462 y130 w80 h20 +center , Type

        Gui, Add, Edit, x142 y150 w220 h25 vXMLMfr, 
        Gui, Add, Edit, x372 y150 w80 h25 vXMLYear, 
        Gui, Add, Edit, x462 y150 w80 h25 vXMLType, 

        Gui, Add, CheckBox, x142 y180 w120 h30 Checked vXMLEnabled, Table Enabled
        Gui, Add, CheckBox, x302 y180 w100 h30 Checked vXMLHideDMD, Hide DMD
        Gui, Add, CheckBox, x412 y180 w150 h30 Checked vXMLHideBG, Hide Backglass

        Gui, Add, DropDownList, x142 y215 w120 h25 r10 +center altSubmit vXMLAltExe, No Exe Tag||AlternateExe|     Exe
        Gui, Add, Edit, x262 y216 w170 h25 vXMLAltExeName, 
        Gui, Add, DropDownList, x442 y215 w100 h25 r10 +center altSubmit vXMLRating, No Rating||1 out of 5|2 out of 5|3 out of 5|4 out of 5|5 out of 5
        
        Gui, Add, Button, x22 y280 w140 h30 gXMLCancel, Cancel
        Gui, Add, Button, x212 y280 w140 h30 gXMLSaveExit, Save && Exit
        Gui, Add, Button, x402 y280 w140 h30 gXMLSave, Save && Record

        GuiControl, Focus, XMLDescSelected
        Gui, Show, x127 y87 h327 w574,Add Table to XML - Start typing to perform Description auto-complete
        WinWaitClose, Add Table to XML - Start typing to perform Description auto-complete
        ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx        
    }
    Else
    {
        MsgBox, 4,, Table exists in XML file already.  Begin recording? (press Yes or No)
        IfMsgBox No
            ExitApp
    }
    
        
}    
    
;************************************************************************************************
;************************************************************************************************
;4. Time to walk through the tables and record videos
;************************************************************************************************
;************************************************************************************************
;************************************************************************************************
; Disabling "Application has stopped Working" dialog
;(https://www.raymond.cc/blog/disable-program-has-stopped-working-error-dialog-in-windows-server-2008/)
RegWrite, REG_DWORD, HKEY_CURRENT_USER, Software\Microsoft\Windows\Windows Error Reporting, DontShowUI, 0x00000001

Rectime := Rectime + 10        ;Pad record time by 10 seconds
Loopcount:=0
Recordcount:=0
Loop, %FPXMLCount%
{
    XMLPath:=XMLPathArray[A_Index]
    XMLFileName:=XMLFileNameArray[A_Index]
    WorkingPath:=WorkingPathArray[A_Index]
    TablePath:=TablePathArray[A_Index]
    Executable:=ExecutableArray[A_Index]
    FileAppend, `nWorking on %XMLFileName%.xml (%A_Hour%:%A_Min%:%A_Sec%)`n`n, %A_ScriptDir%\PBXrecorder.log
    
    ;Read FP Registry and FutureDMD Settings - may need to be even number values or it may not record
    ;=======================
    RegRead, PF_width, HKCU, Software\Future Pinball\GamePlayer, Width
    RegRead, PF_height, HKCU, Software\Future Pinball\GamePlayer, Height
    RegRead, BG_width, HKCU, Software\Future Pinball\GamePlayer, SecondMonitorWidth
    RegRead, BG_height, HKCU, Software\Future Pinball\GamePlayer, SecondMonitorHeight

    IniRead, DMD_width, %WorkingPath%\FutureDMD.ini, default, SizeW
    IniRead, DMD_height, %WorkingPath%\FutureDMD.ini, default, SizeH
    IniRead, DMD_X, %WorkingPath%\FutureDMD.ini, default, PosX
    IniRead, DMD_Y, %WorkingPath%\FutureDMD.ini, default, PosY

    ;Save FP Registry and FutureDMD Settings to PBX recorder log
    FileAppend, FP Registry and FutureDMD Settings`n, %A_ScriptDir%\PBXrecorder.log
    FileAppend, %PF_width%`n, %A_ScriptDir%\PBXrecorder.log
    FileAppend, %PF_height%`n, %A_ScriptDir%\PBXrecorder.log
    FileAppend, %BG_width%`n, %A_ScriptDir%\PBXrecorder.log
    FileAppend, %BG_height%`n, %A_ScriptDir%\PBXrecorder.log
    FileAppend, %DMD_width%`n, %A_ScriptDir%\PBXrecorder.log
    FileAppend, %DMD_height%`n, %A_ScriptDir%\PBXrecorder.log
    FileAppend, %DMD_X%`n, %A_ScriptDir%\PBXrecorder.log
    FileAppend, %DMD_Y%`n, %A_ScriptDir%\PBXrecorder.log

    ;OBS file check
    IfNotExist, %OBSPath%\bin\64bit\obs64.exe
        {
            FileAppend, `nWARNING:  The file obs64.exe could not be found`n, %A_ScriptDir%\PBXrecorder.log
            FileAppend, WARNING:  The file obs64.exe could not be found`n, %A_ScriptDir%\PBXrecorder.log
            FileAppend, WARNING:  The file obs64.exe could not be found`n`n, %A_ScriptDir%\PBXrecorder.log
        }
    
    ;FFMPEG file check
    IfNotExist, %FFMpegPath%\ffmpeg.exe
    {
        FFMpegPath = %FFMpegPath%\bin
        IfNotExist, %FFMpegPath%\ffmpeg.exe
            FileAppend, `nWARNING:  The file ffmpeg.exe could not be found`n, %A_ScriptDir%\PBXrecorder.log
            FileAppend, WARNING:  The file ffmpeg.exe could not be found`n, %A_ScriptDir%\PBXrecorder.log
            FileAppend, WARNING:  The file ffmpeg.exe could not be found`n`n, %A_ScriptDir%\PBXrecorder.log
    }

    ;Create Media directories if needed
    ;==========================

    MediaSubDir:=XMLFileName
    MediaSubDirOut:=XMLFileName

    If (BetaMode=1)
    {
        MediaSubDirOut = %MediaSubDirOut% Beta
        Msgbox, 0,, Beta Mode. All new media stored in %MediaOutPath%\%MediaSubDirOut%, 20
    }
    
    FileCreateDir, %MediaOutPath%\%MediaSubDirOut%\Backglass Images
    FileCreateDir, %MediaOutPath%\%MediaSubDirOut%\Backglass Videos
    FileCreateDir, %MediaOutPath%\%MediaSubDirOut%\DMD Images
    FileCreateDir, %MediaOutPath%\%MediaSubDirOut%\DMD Videos
    FileCreateDir, %MediaOutPath%\%MediaSubDirOut%\Table Images
    FileCreateDir, %MediaOutPath%\%MediaSubDirOut%\Table Videos
    FileCreateDir, %MediaOutPath%\%MediaSubDirOut%\Wheel Images

    ;Start of new XML file
    if (DragAndDrop<>1)
    {
        Progress, x200 y200 zh0 M h200 w600 FS10, `n%WorkingPath%`n %TablePath%`n %Executable%`n`n`n(Press ESC anytime to abort script)  , `nWorking on %XMLFileName%.xml, System XML #%A_Index% of %FPXMLCount% ... , Arial
        Sleep 2000
    }
    
    ; Clean up
    ;==========================
    FileDelete, %A_ScriptDir%\playfield.mkv
    FileDelete, %A_ScriptDir%\bg.mkv
    FileDelete, %A_ScriptDir%\dmd.mkv
    Run, taskkill /T /IM obs64.exe ,,UseErrorLevel
    WinKill, Error
    Progress, Off

    ;*************** Screensize check - How good are the settings ****************
    ;==========================
    XDMD_X:=DMD_X-PF_width ;Reference to Top Left of Playfield screen
    SysGet, VirtualScreenWidth, 78
    TotalScreenWidth := DMD_X + DMD_width
    DMD_oldwidth := DMD_Width
    PFBGWidth := PF_width+BG_width
    DMDScreenWidth := VirtualScreenWidth - PFBGWidth
    DMDScreenHeight := MonitorBottom
    DMDXCrop := DMD_X - PFBGWidth
    
    ;Convert all width and height to even values for best recording success
    PF_width    := (floor(PF_width/2))*2          
    PF_height    := (floor(PF_height/2))*2          
    BG_width    := (floor(BG_width/2))*2           
    BG_height    := (floor(BG_height/2))*2          
    DMD_width    := (floor(DMD_width/2))*2          
    DMD_height    := (floor(DMD_height/2))*2
    
    FileAppend, `nValues used for media capture (height/width forced to even values)`n, %A_ScriptDir%\PBXrecorder.log
    FileAppend, VirtualScreenWidth = %VirtualScreenWidth%`n, %A_ScriptDir%\PBXrecorder.log
    FileAppend, TotalScreenWidth   = %TotalScreenWidth%  `n, %A_ScriptDir%\PBXrecorder.log
    FileAppend, PF_width           = %PF_width%          `n, %A_ScriptDir%\PBXrecorder.log
    FileAppend, PF_height          = %PF_height%         `n, %A_ScriptDir%\PBXrecorder.log
    FileAppend, BG_width           = %BG_width%          `n, %A_ScriptDir%\PBXrecorder.log
    FileAppend, BG_height          = %BG_height%         `n, %A_ScriptDir%\PBXrecorder.log
    FileAppend, DMD_width          = %DMD_width%      `n, %A_ScriptDir%\PBXrecorder.log
    FileAppend, DMD_height         = %DMD_height%        `n, %A_ScriptDir%\PBXrecorder.log
    FileAppend, DMD_X_offset       = %XDMD_X%            `n, %A_ScriptDir%\PBXrecorder.log
    FileAppend, DMD_Y_offset       = %DMD_Y%             `n, %A_ScriptDir%\PBXrecorder.log
    FileAppend, -----------------------------------------`n, %A_ScriptDir%\PBXrecorder.log
    FileAppend, DMD_tot_offset     = %DMD_X%             `n, %A_ScriptDir%\PBXrecorder.log
    FileAppend, DMD_orig_width     = %DMD_oldwidth%         `n, %A_ScriptDir%\PBXrecorder.log
    FileAppend, DMDScreenWidth     = %DMDScreenWidth%    `n, %A_ScriptDir%\PBXrecorder.log
    FileAppend, DMDScreenHeight    = %DMDScreenHeight%   `n, %A_ScriptDir%\PBXrecorder.log
    FileAppend, DMDScreenX         = %DMDXCrop%          `n, %A_ScriptDir%\PBXrecorder.log

    ;PF width check
    If ((PF_width > VirtualScreenWidth) and (PFVideoOnly = 1 or PFImageOnly = 1))
    {
        Msgbox, 0, FP Settings: Playfield size check, Warning: FP Playfield(%PF_width%) width exceeds the actual size of the windows desktop(%VirtualScreenWidth%).  Review FP settings`n`nPlayfield recording may not work, 60 
        FileAppend, WARNING: FP Settings Playfield width exceeds the actual size of windows desktop.  Review FP settings`n, %A_ScriptDir%\PBXrecorder.log
    }
    
    ;PF and BG width check
    If ((PFBGWidth > VirtualScreenWidth) and (BGVideoOnly = 1 or BGImageOnly = 1))
    {
        Msgbox, 0, FP Settings: Backglass size check, Warning: FP Playfield(%PF_width%) plus Backglass(%BG_width%) width exceeds the actual size of windows desktop (%VirtualScreenWidth%).  Review FP settings`n`nBackglass recording may not work, 60 
        FileAppend, WARNING: FP Settings Playfield(%PF_width%) plus Backglass(%BG_width%) width exceeds the actual size of windows desktop (%VirtualScreenWidth%).  Review FP settings`n, %A_ScriptDir%\PBXrecorder.log
    }

    ;PF and BG and DMD width check
    If ((TotalScreenWidth > VirtualScreenWidth) and (DMDVideoOnly = 1 or DMDImageOnly = 1))
    {
        DMD_Width := (floor((DMD_oldwidth - (TotalScreenWidth - VirtualScreenWidth))/2))*2
        Msgbox, 0, FutureDMD: DMD size check, Warning: FutureDMD width/offset is not correct`nReducing DMD width from %DMD_oldwidth% to %DMD_width%.  Review FutureDMD.ini settings`n`nDMD recording may not work, 60
        FileAppend, WARNING: DMD window settings incorrect - auto-resizing (%DMD_oldwidth% to %DMD_width%).  Review FutureDMD settings`n, %A_ScriptDir%\PBXrecorder.log
    }
    
    
    ;Write modified settings to OBS config files
    IniWrite, %PF_width%, %OBSPath%\config\obs-studio\basic\profiles\playfield\basic.ini, Video, OutputCX 
    IniWrite, %PF_height%, %OBSPath%\config\obs-studio\basic\profiles\playfield\basic.ini, Video, OutputCY 
    IniWrite, %A_ScriptDir%, %OBSPath%\config\obs-studio\basic\profiles\playfield\basic.ini, AdvOut, RecFilePath 

    IniWrite, %BG_width%, %OBSPath%\config\obs-studio\basic\profiles\bg\basic.ini, Video, OutputCX 
    IniWrite, %BG_height%, %OBSPath%\config\obs-studio\basic\profiles\bg\basic.ini, Video, OutputCY 
    IniWrite, %A_ScriptDir%, %OBSPath%\config\obs-studio\basic\profiles\bg\basic.ini, AdvOut, RecFilePath 

    IniWrite, %DMDScreenWidth%, %OBSPath%\config\obs-studio\basic\profiles\dmd\basic.ini, Video, OutputCX 
    IniWrite, %DMDScreenHeight%, %OBSPath%\config\obs-studio\basic\profiles\dmd\basic.ini, Video, OutputCY 
    IniWrite, %A_ScriptDir%, %OBSPath%\config\obs-studio\basic\profiles\dmd\basic.ini, AdvOut, RecFilePath 
    
    ;Open XML file and walk through every table
    ;==========================
    ExecutableBackup:=Executable
    PrintFFMPEGExamples:=1
    Loop, Read, %XMLPath%\%XMLFilename%.xml
    {
        IfInString, A_LoopReadLine, game name=
        {
            TableEnabled := 1
            Executable:=ExecutableBackup
            StringGetPos, start, A_LoopReadLine, =
            StringGetPos, end, A_LoopReadLine, >
            XTable := SubStr(A_LoopReadLine, start+3, end-start-3)
            ;Msgbox, what%XTable%`n%A_LoopReadLine%`n%start%`n%end%
            
            ;-----------------------------------------------------
            ;Convert special chars in Table Name so they are used correctly
            ;-----------------------------------------------------
            ;Special handling for ® in the Table Name.  Change ® to ®
            IfInString, XTable, ®
            {
                StringGetPos, start, XTable, ®
                TestTable := SubStr(XTable, 1, start)
                TestTable2 := SubStr(XTable, start+2)
                XTable = %TestTable%%TestTable2%
                ;msgbox %XTable%
            }            
            ;Special handling for & in the Table Name.  Change &amp; to & 
            IfInString, XTable, &amp;
            {
                StringGetPos, start, XTable, &amp;
                TestTable := SubStr(XTable, 1, start)
                TestTable2 := SubStr(XTable, start+6)
                XTable = %TestTable%&%TestTable2%
            }
            ;Special handling for Italian ® in the Table Name.  Change &#174; to ® 
            IfInString, XTable, &#174;
            {
                StringGetPos, start, XTable, &amp;
                TestTable := SubStr(XTable, 1, start)
                TestTable2 := SubStr(XTable, start+7)
                XTable = %TestTable%�%TestTable2%
            }
            ;Special handling for italian ' in the Table Name.  Change ’ to ' 
            IfInString, XTable, ’
            {
                StringGetPos, start, XTable, ’
                TestTable := SubStr(XTable, 1, start)
                TestTable2 := SubStr(XTable, start+4)
                XTable = %TestTable%'%TestTable2%
            }    
            ;Special handling for ™ in the Table Name.  Change &#8482; to ™ 
            IfInString, XTable, &#8482;
            {
                StringGetPos, start, XTable, &#8482;
                TestTable := SubStr(XTable, 1, start)
                TestTable2 := SubStr(XTable, start+8)
                XTable = %TestTable%™%TestTable2%
            }
            
            ;Identify if fpt
            IfExist, %TablePath%\%Xtable%.fpt
            {
                Table = %XTable%.fpt
                TableExists := 1
            }
            ;Else IfExist, %TablePath%\%Xtable%.vpx   
            ;{
            ;    Table = %XTable%.vpx 
            ;    TableExists := 1
            ;}
            Else      ;must be vpx file
            {
                ;Table = %XTable%.vpx 
                TableExists := 0
            }
        }
        Else IfInString, A_LoopReadLine, /description
        {                                         
            StringGetPos, start, A_LoopReadLine, <description>
            StringGetPos, end, A_LoopReadLine, </description>
            Description := SubStr(A_LoopReadLine, start+14, end-start-13)
            
            ;----------------------------------------------------
            ;Remove invalid chars from Description name like  : / \ * ? " < > |
            ;----------------------------------------------------
            IfInString, Description, :
            {
                StringGetPos, start, Description, :
                TestTable := SubStr(Description, 1, start)
                TestTable2 := SubStr(Description, start+2)
                Description = %TestTable%%TestTable2%
            }
            IfInString, Description, /
            {
                StringGetPos, start, Description, /
                TestTable := SubStr(Description, 1, start)
                TestTable2 := SubStr(Description, start+2)
                Description = %TestTable%%TestTable2%
            }
            IfInString, Description, \
            {
                StringGetPos, start, Description, \
                TestTable := SubStr(Description, 1, start)
                TestTable2 := SubStr(Description, start+2)
                Description = %TestTable%%TestTable2%
            }
            IfInString, Description, *
            {
                StringGetPos, start, Description, *
                TestTable := SubStr(Description, 1, start)
                TestTable2 := SubStr(Description, start+2)
                Description = %TestTable%%TestTable2%
            }            
            IfInString, Description, &#42;
            {
                StringGetPos, start, Description, &#42;
                TestTable := SubStr(Description, 1, start)
                TestTable2 := SubStr(Description, start+6)
                Description = %TestTable%%TestTable2%
            }            
            IfInString, Description, ?
            {
                StringGetPos, start, Description, ?
                TestTable := SubStr(Description, 1, start)
                TestTable2 := SubStr(Description, start+2)
                Description = %TestTable%%TestTable2%
            }            
            IfInString, Description, "
            {
                StringGetPos, start, Description, "
                TestTable := SubStr(Description, 1, start)
                TestTable2 := SubStr(Description, start+2)
                Description = %TestTable%%TestTable2%
            }            
            IfInString, Description, <
            {
                StringGetPos, start, Description, <
                TestTable := SubStr(Description, 1, start)
                TestTable2 := SubStr(Description, start+2)
                Description = %TestTable%%TestTable2%
            }            
            IfInString, Description, >
            {
                StringGetPos, start, Description, >
                TestTable := SubStr(Description, 1, start)
                TestTable2 := SubStr(Description, start+2)
                Description = %TestTable%%TestTable2%
            }            
            IfInString, Description, |
            {
                StringGetPos, start, Description, |
                TestTable := SubStr(Description, 1, start)
                TestTable2 := SubStr(Description, start+2)
                Description = %TestTable%%TestTable2%
            }
            
            ;-----------------------------------------------------
            ;Convert special chars in Description so they are used correctly
            ;-----------------------------------------------------
            ;Special handling for ® in the Description.  Change ® to ®
            IfInString, Description, ®
            {
                StringGetPos, start, Description, ®
                TestTable := SubStr(Description, 1, start)
                TestTable2 := SubStr(Description, start+2)
                Description = %TestTable%%TestTable2%
            }            
            ;Special handling for & in the Description.  Change &amp; to & 
            IfInString, Description, &amp;
            {
                StringGetPos, start, Description, &amp;
                TestTable := SubStr(Description, 1, start)
                TestTable2 := SubStr(Description, start+6)
                Description = %TestTable%&%TestTable2%
            }
            ;Special handling for &#174; in the Description.  Change &#174; to ®
            IfInString, Description, &#174;
            {
                StringGetPos, start, Description, &#174;
                TestTable := SubStr(Description, 1, start)
                TestTable2 := SubStr(Description, start+7)
                Description = %TestTable%�%TestTable2%
            }
            ;Special handling for italian ' in the Description.  Change ’ to ' 
            IfInString, Description, ’
            {
                StringGetPos, start, Description, ’
                TestTable := SubStr(Description, 1, start)
                TestTable2 := SubStr(Description, start+4)
                Description = %TestTable%'%TestTable2%
            }
            ;Special handling for ™ in the Description.  Change &#8482; to ™ 
            IfInString, Description, &#8482;
            {
                StringGetPos, start, Description, &#8482;
                TestTable := SubStr(Description, 1, start)
                TestTable2 := SubStr(Description, start+8)
                Description = %TestTable%™%TestTable2%
            }

            FileAppend, `n%Description%`n, %A_ScriptDir%\PBXrecorder.log                                
            If TableExists = 0
                FileAppend,  Table file not found: %TablePath%\%XTable%.fpt`n, %A_ScriptDir%\PBXrecorder.log
        }
        Else IfInString, A_LoopReadLine, /alternateExe
        {            
            StringGetPos, start, A_LoopReadLine, <alternateExe>
            StringGetPos, end, A_LoopReadLine, </alternateExe>
            Executable := SubStr(A_LoopReadLine, start+15, end-start-14)
            ;Msgbox, %Executable%
            FileAppend, AlternateExe found in xml: %Executable%`n, %A_ScriptDir%\PBXrecorder.log
        }
        Else IfInString, A_LoopReadLine, /exe
        {            
            StringGetPos, start, A_LoopReadLine, <exe>
            StringGetPos, end, A_LoopReadLine, </exe>
            Executable := SubStr(A_LoopReadLine, start+6, end-start-5)
            ;Msgbox, %Executable%
            FileAppend, Exe found in xml: %Executable%`n, %A_ScriptDir%\PBXrecorder.log
        }
        Else IfInString, A_LoopReadLine, enabled>False
        {            
            ;Msgbox, %A_LoopReadLine%
            FileAppend, Table disabled in xml: %TablePath%\%Table%`n, %A_ScriptDir%\PBXrecorder.log
            TableEnabled := 0
        }
        Else IfInString, A_LoopReadLine, enabled>True
        {            
            ;Msgbox, %A_LoopReadLine% 
            TableEnabled := 1
        }
        Else IfInString, A_LoopReadLine, /game
        {
            If TableEnabled = 1
            {
                If TableExists = 1
                {
                    Loopcount:=Loopcount + 1
                    ;Msgbox, recording
                    ;Check if Media Files exist and skip if skip is enabled
                    ;===================================================
                    MediaFoundFlag:=0
                    If (MediaFileNaming=0)
                        SearchString:=Description   
                    Else
                        SearchString:=Xtable 

                    If (PrintFFMPEGExamples=1)
                    {
                        PrintFFMPEGExamples:=0
                        FileAppend, `nEXAMPLE of all ffmpeg.exe commands used`n, %A_ScriptDir%\PBXrecorder.log
;                        FileAppend, "%FFMpegPath%\ffmpeg" -y -t 1 -f gdigrab -framerate 1 -offset_x 0 -offset_y 0 -video_size %PF_width%x%PF_Height% -i desktop -vf "rotate=PI:bilinear=0" "%MediaOutPath%\%MediaSubDirOut%\Table Images\%SearchString%.png"`n, %A_ScriptDir%\PBXrecorder.log
;                        FileAppend, "%FFMpegPath%\ffmpeg" -y -t 1 -f gdigrab -framerate 1 -offset_x %PF_width% -offset_y 0 -video_size %BG_width%x%BG_Height% -i desktop "%MediaOutPath%\%MediaSubDirOut%\Backglass Images\%SearchString%.png"`n, %A_ScriptDir%\PBXrecorder.log
;                        FileAppend, "%FFMpegPath%\ffmpeg" -y -t 1 -f gdigrab -framerate 1 -offset_x %DMD_X% -offset_y %DMD_Y% -video_size %DMD_width%x%DMD_Height% -i desktop "%MediaOutPath%\%MediaSubDirOut%\DMD Images\%SearchString%.png"`n, %A_ScriptDir%\PBXrecorder.log
;                        FileAppend, "%FFMpegPath%\ffmpeg" -y -t %RecTime% -rtbufsize 1500M -f gdigrab -framerate 30 -offset_x 0 -offset_y 0 -video_size %PF_width%x%PF_Height% -i desktop -vcodec libx264 -preset ultrafast -qp 0 -threads 8 "%A_ScriptDir%\playfield.mkv"`n, %A_ScriptDir%\PBXrecorder.log
;                        FileAppend, "%FFMpegPath%\ffmpeg" -y -t %RecTime% -rtbufsize 1500M -f gdigrab -framerate 30 -offset_x %PF_width% -offset_y 0 -video_size %BG_width%x%BG_Height% -i desktop -vcodec libx264 -preset ultrafast  -qp 0 -threads 8 "%A_ScriptDir%\bg.mkv"`n, %A_ScriptDir%\PBXrecorder.log
;                        FileAppend, "%FFMpegPath%\ffmpeg" -y -t %RecTime% -rtbufsize 1500M -f gdigrab -framerate 30 -offset_x %DMD_X% -offset_y %DMD_Y% -video_size %DMD_width%x%DMD_Height% -i desktop -vcodec libx264 -preset ultrafast  -qp 0 -threads 8 "%A_ScriptDir%\dmd.mkv"`n, %A_ScriptDir%\PBXrecorder.log
                        FileAppend, "%FFMpegPath%\ffmpeg" -y -i "%A_ScriptDir%\playfield.mkv" -ss 10 -to 1000 -vf [in]rotate=PI:bilinear=0[middle];[middle]scale=1920:-1[out] -map 0:0 -c:v libx264 -crf 26 "%MediaOutPath%\%MediaSubDirOut%\Table Videos\%SearchString%.%RecExt%"`n, %A_ScriptDir%\PBXrecorder.log
                        FileAppend, "%FFMpegPath%\ffmpeg" -y -i "%A_ScriptDir%\bg.mkv" -ss 10 -to 1000 -map 0:0 -c:v libx264 -crf 26 "%MediaOutPath%\%MediaSubDirOut%\Backglass Videos\%SearchString%.%RecExt%"`n, %A_ScriptDir%\PBXrecorder.log
                        FileAppend, "%FFMpegPath%\ffmpeg" -y -i "%A_ScriptDir%\dmd.mkv" -ss 10 -to 1000 -vf "crop=w=%DMD_width%:h=%DMD_height%:x=%DMDXCrop%:y=%DMD_Y%" -map 0:0 -c:v libx264 -crf 26 "%MediaOutPath%\%MediaSubDirOut%\DMD Videos\%SearchString%.%RecExt%"`n`n, %A_ScriptDir%\PBXrecorder.log                        
                    }
                                        
                    If ((OnlyRecordMissingVideos = 1) or (OnlyRecordMissingVideos = 2) or (OnlyRecordMissingVideos = 3))
                    {
                        ;Perform media check for missing files
                        MediaFoundFlag:=1
                        MediaAtLeastOneFound:=0
                        
                        If (PFVideoOnly=1)
                        {
                            IfNotExist, %MediaOutPath%\%MediaSubDir%\Table Videos\%SearchString%.*
                            {
                                MediaFoundFlag:=0
                                FileAppend, MISSING: Table Video`n, %A_ScriptDir%\PBXrecorder.log                        
                            }
                            Else
                            {
                                MediaAtLeastOneFound:=1
                            }
                        }
                        If (BGVideoOnly=1)
                        {
                            IfNotExist, %MediaOutPath%\%MediaSubDir%\BackGlass Videos\%SearchString%.*
                            {
                                MediaFoundFlag:=0
                                FileAppend, MISSING: BackGlass Video`n, %A_ScriptDir%\PBXrecorder.log                        
                            }
                            Else
                            {
                                MediaAtLeastOneFound:=1
                            }
                        }
                        If (DMDVideoOnly=1)
                        {
                            IfNotExist, %MediaOutPath%\%MediaSubDir%\DMD Videos\%SearchString%.*
                            {
                                FileAppend, MISSING: DMD Video`n, %A_ScriptDir%\PBXrecorder.log                        
                                If DMD_width > 0 
                                {
                                    MediaFoundFlag:=0
                                }
                            }
                            Else
                            {
                                MediaAtLeastOneFound:=1
                            }
                        }
                        If (PFImageOnly=1)
                        {
                            IfNotExist, %MediaOutPath%\%MediaSubDir%\Table Images\%SearchString%.*
                            {
                                MediaFoundFlag:=0
                                FileAppend, MISSING: Table Image`n, %A_ScriptDir%\PBXrecorder.log                        
                            }
                            Else
                            {
                                MediaAtLeastOneFound:=1
                            }
                        }
                        If (BGImageOnly=1)
                        {
                            IfNotExist, %MediaOutPath%\%MediaSubDir%\BackGlass Images\%SearchString%.*
                            {
                                MediaFoundFlag:=0
                                FileAppend, MISSING: BackGlass Image`n, %A_ScriptDir%\PBXrecorder.log                        
                            }
                            Else
                            {
                                MediaAtLeastOneFound:=1
                            }
                        }
                        If (DMDImageOnly=1)
                        {
                            IfNotExist, %MediaOutPath%\%MediaSubDir%\DMD Images\%SearchString%.*
                            {
                                FileAppend, MISSING: DMD Image`n, %A_ScriptDir%\PBXrecorder.log                        
                                If DMD_width > 0                 
                                {
                                    MediaFoundFlag:=0
                                }
                            }
                            Else
                            {
                                MediaAtLeastOneFound:=1
                            }
                        }
                        
                    } ;end of If ((OnlyRecordMissingVideos = 1) or (OnlyRecordMissingVideos = 2) or (OnlyRecordMissingVideos = 3))
                    Else
                    {
                        FileAppend, PBXrecorder set to record complete media set`n, %A_ScriptDir%\PBXrecorder.log                        
                    }
                
                    ;===========================================================================
                    ; Extra FP file checks - Wheel images (FTP download supported)
                    ;===========================================================================
                    ;Print out message to log file if DirectB2s file is missing
                    ;IfNotExist, %TablePath%\%XTable%.directb2s
                    ;{
                    ;    IfNotExist, %TablePath%\%XTable%.exe
                    ;    {
                    ;        FileAppend, FYI: No DirectB2S/B2S.exe File.  %TablePath%\%XTable%.directb2s`n, %A_ScriptDir%\PBXrecorder.log                        
                    ;    }
                    ;    Else
                    ;    {
                    ;        FileAppend, FYI: No DirectB2S but B2S was found.  %TablePath%\%XTable%.directb2s`n, %A_ScriptDir%\PBXrecorder.log                        
                    ;    }
                    ;}
                    ;Print out message to log file if Wheel image is missing - Perform FTP
                    IfNotExist, %MediaOutPath%\%MediaSubDir%\Wheel Images\%SearchString%.png
                    {
                        ;=============================================================
                        ; Download Wheel from PinballX FTP if user has filled in FTP login info
                        ;=============================================================
                        IfExist, %A_ScriptDir%\FTPLoginInfo.txt                            
                        {                        
                            FileReadLine, tempStr, %A_ScriptDir%\FTPLoginInfo.txt, 2
                            IfNotInString, tempStr, USERNAME
                            {
                                Progress, x200 y200 zh0 M h200 w600 FS10, `n`Wheel Image missing... Check PinballX FTP site`n`n(Press ESC anytime to abort script)  ,`n`n%Description%, Attempting PinballX FTP download , Arial
                                ;Strip out the {...} from the description if found
                                StringSplit, SearchStringArray, Description, {} ;drop the 2nd part
                                SearchStringArray1:=Trim(SearchStringArray1)
                                SearchStringArray3:=Trim(SearchStringArray3)
                                If (StrLen(SearchStringArray3)>0)
                                    SearchStringArray1=%SearchStringArray1% %SearchStringArray3%

                                FileToGet = %SearchStringArray1%.png
                                FileDelete %A_ScriptDir%\FTPtemp.bat
                                FileAppend, open online.gameex.com`n, %A_ScriptDir%\FTPtemp.bat
                                FileReadLine, tempStr, %A_ScriptDir%\FTPLoginInfo.txt, 2
                                FileAppend, %tempStr%`n, %A_ScriptDir%\FTPtemp.bat
                                FileReadLine, tempStr, %A_ScriptDir%\FTPLoginInfo.txt, 3
                                FileAppend, %tempStr%`n, %A_ScriptDir%\FTPtemp.bat
                                FileAppend,
                                (
                                binary
                                hash
                                cd "/-PinballX-/Media/Future Pinball/Wheel Images"
                                get "%FileToGet%"
                                bye
                                ), %A_ScriptDir%\FTPtemp.bat
                                RunWait %comspec% /c ftp.exe -s:"%A_ScriptDir%\FTPtemp.bat"
                                FileDelete %A_ScriptDir%\FTPtemp.bat
                                FileMove %A_ScriptDir%\%FileToGet%, %MediaOutPath%\%MediaSubDirOut%\Wheel Images\%Description%.png, 1
                                
                                IfNotExist, %MediaOutPath%\%MediaSubDirOut%\Wheel Images\%Description%.png
                                {
                                    FileAppend, FYI (Wheel not found in FTP): No Wheel Image - %Description%.png`n, %A_ScriptDir%\PBXrecorder.log
                                }
                                Else
                                {
                                    FileAppend, Downloaded via FTP: Wheel Image - %Description%.png`n, %A_ScriptDir%\PBXrecorder.log
                                }
                            }
                            Else
                            {
                                FileAppend, FYI (FTP not enabled): No Wheel Image - %Description%.png`n, %A_ScriptDir%\PBXrecorder.log
                            }
                        }
                        Else
                        {
                            FileAppend, FYI (FTP not enabled): No Wheel Image - %SearchString%.png`n, %A_ScriptDir%\PBXrecorder.log
                        }                            
                        
                    }
                    ; END OF Extra FP file checks - Wheel images (FTP download supported)
                    ;===========================================================================
                    
                    
                    ; Media checked - Decide whether to record or skip table
                    ;===========================================================================
                    IfEqual MediaFoundFlag, 1    ;If all media exists then skip table
                    {
                        If PauseTime>=10
                            PauseTime:=PauseTime-5
                        TempLongPath = %TablePath%\%Table%
                        if (DragAndDrop<>1)
                        {
                            Progress, x200 y200 zh0 M h200 w600 FS10, `nMedia already exists.  Skipping...`n`n(Press ESC anytime to abort script)  ,`n`n%Description%, Capturing Videos... #%Loopcount% , Arial                                
                            Sleep %PauseTime%
                            FileAppend, Skipping table... %TablePath%\%Table%`n, %A_ScriptDir%\PBXrecorder.log
                        }
                        Else If inStr(TempLongPath, DragAndDropFile)  ;Check if drag and drop table already has media
                        {
                            DragAndDropTableFound := -1
                            Progress, x200 y200 zh0 M h200 w600 FS10, `nDrag and Drop table media already exists.  Skipping...`n`n(Press ESC anytime to abort script)  ,`n`n%Description%, Drag and Drop... #%Loopcount% , Arial                                
                            Sleep 3000    
                            FileAppend, Drag and drop table already has Media.  Skipping... %TablePath%\%Table%`n, %A_ScriptDir%\PBXrecorder.log                            
                        }
                        continue ;Skipping table!!!
                    }
                    Else If (DragAndDrop=1) ;and MediaFoundFlag = 0     ;Handle Drag and drop table recording
                    {
                        ;Check if current table matches the drag and drop table
                        TempLongPath = %TablePath%\%Table%
                        If inStr(TempLongPath, DragAndDropFile)    ;Match - go ahead and record
                        {
                            DragAndDropTableFound := 1
                            Progress, x200 y200 M h240 w600 FS10 CB0000FF, `n`nLoading "%Table%"`n`nDrag and Drop recording will start in %LoadTime% seconds...`n(Press PAUSE if you need to edit/setup table)`n(Press ESC anytime to abort script) ,`n`n%Description%`n, Capturing Videos... #%Loopcount% , Arial
                            FileAppend, Launching table: %Executable% /STAYINRAM /open %TablePath%\%Table% /play /exit`n, %A_ScriptDir%\PBXrecorder.log
                        }
                        Else    ;No match - skip this table
                        {
                            FileAppend, Skipping table due to Drag and Drop mode... %TablePath%\%Table%`n, %A_ScriptDir%\PBXrecorder.log
                            continue ;Skipping table
                        }
                    }
                    Else If ((OnlyRecordMissingVideos=3) and (MediaAtLeastOneFound=1)) ;check if Mode = Record ONLY new tables(Ignore partial media set) AND any if media was found
                    {
                            FileAppend, Skipping table - Recording only new tables. Partial media set exists... %TablePath%\%Table%`n, %A_ScriptDir%\PBXrecorder.log
                            continue ;Skipping table
                    }
                    Else ;MediaFoundFlag = 0    ;Launch table to record
                    {
                        Progress, x200 y200 M h240 w600 FS10 CB0000FF, `n`nLoading "%Table%"`n`nRecording will start in %LoadTime% seconds...`n(Press PAUSE if you need to edit/setup table)`n(Press ESC anytime to abort script) ,`n`n%Description%`n, Capturing Videos... #%Loopcount% , Arial
                        FileAppend, Launching table: %Executable% /STAYINRAM /open %TablePath%\%Table% /play /exit`n, %A_ScriptDir%\PBXrecorder.log
                    }        

                    ;=================================================
                    ;Start Table
                    ;=================================================
                    Recordcount:=Recordcount + 1
                    WinHide, ahk_class Button
                    WinHide, ahk_class Shell_TrayWnd
                    Run, "%WorkingPath%\%Executable%" /STAYINRAM /open "%TablePath%\%Table%" /play /exit
                                        LoadingTable:=1
                    Process, wait, Future Pinball.exe
                    If DMD_width > 0
                        Run, %WorkingPath%\FutureDMD.exe table="%Table%" close=1

                    WinWaitActive, ahk_class FuturePinballOpenGL
                    If (TestMode=1) ;debug speed up of recording
                    {
                        Loop, 50 ;DEBUG
                        {            
                            j := A_Index/1.0
                            Progress,%j%
                            Sleep 100    
                            If (PBXRPauseMode=1)
                            {
                                Goto, PBRXPaused
                            }
                        }        ; Allow time for table score display to get well beyond loading and boot images
                    }
                    Else
                    {
                        Loop, % LoadTimeX ; 
                        {            
                            j := A_Index/LoadTimeX*100
                            Progress,%j%
                            Sleep 100    
                            If (PBXRPauseMode=1)
                            {
                                Goto, PBRXPaused
                            }
                        }        ; Allow time for table score display to get well beyond loading and boot images
                    }                    
                    Progress, Off
                    LoadingTable:=0
                    
PBRXPaused:
                    If (LoadingTable=1)    ;User has paused PBX Recorder
                    {
                        Progress, Off
                        WindowX:=PF_width/2 -200
                        Progress, x%WindowX% y0 h25 w400 FS10 CB0000FF, Paused.  Press Pause key to start recording  , Paused.  Press Pause key to start recording, Press Pause to Resume , Arial
                        WinWaitClose, Press Pause to Resume
                    }
                    LoadingTable:=0
                    PBXRPauseMode:=0
                        
                    ; Record Video as .mkv - Fast Capture and take screenshots
                    ;============================================================
                    FileAppend, Video and screenshot capture (%A_Hour%:%A_Min%:%A_Sec%)`n, %A_ScriptDir%\PBXrecorder.log
                    If (OnlyRecordMissingVideos = 2)    ;Mode = RECORD MISSING MEDIA ONLY
                    ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                    {
                        If (PFImageOnly=1)
                        {
                            IfNotExist, %MediaOutPath%\%MediaSubDir%\Table Images\%SearchString%.*
                            {
                                RunWait, %comspec% /k  cd "%OBSPath%\bin\64bit" && start obs64.exe --portable --collection playfield --profile playfield --scene playfield --startrecording && exit,, Hide
                                FileAppend, Screenshot "%MediaOutPath%\%MediaSubDirOut%\Table Images\%SearchString%.png"`n, %A_ScriptDir%\PBXrecorder.log
                            }
                        }
                        If (BGImageOnly=1)
                        {
                            IfNotExist, %MediaOutPath%\%MediaSubDir%\BackGlass Images\%SearchString%.*
                            {
                                RunWait, %comspec% /k  cd "%OBSPath%\bin\64bit" && start obs64.exe --portable --collection bg --profile bg --scene bg --startrecording && exit,, Hide
                                FileAppend, Screenshot "%MediaOutPath%\%MediaSubDirOut%\Backglass Images\%SearchString%.png"`n, %A_ScriptDir%\PBXrecorder.log
                            }
                        }
                        If (DMDImageOnly=1)
                        {
                            If DMD_width > 0
                            {
                                IfNotExist, %MediaOutPath%\%MediaSubDir%\DMD Images\%SearchString%.*
                                {
                                    RunWait, %comspec% /k  cd "%OBSPath%\bin\64bit" && start obs64.exe --portable --collection dmd --profile dmd --scene dmd --startrecording && exit,, Hide
                                    FileAppend, Screenshot "%MediaOutPath%\%MediaSubDirOut%\DMD Images\%SearchString%.png"`n, %A_ScriptDir%\PBXrecorder.log
                                }
                            }
                            Else
                            {
                                FileAppend, Screenshot skipped (Bad DMD size) "%MediaOutPath%\%MediaSubDirOut%\DMD Images\%SearchString%.png"`n, %A_ScriptDir%\PBXrecorder.log
                            }
                        }

;                        Sleep,3000

                        If (PFVideoOnly=1)
                        {
                            IfNotExist, %MediaOutPath%\%MediaSubDir%\Table Videos\%SearchString%.*
                            {
                                RunWait, %comspec% /k  cd "%OBSPath%\bin\64bit" && start obs64.exe --portable --collection playfield --profile playfield --scene playfield --startrecording && exit,, Hide
                                FileAppend, Recording "%A_ScriptDir%\playfield\playfield.mkv"`n, %A_ScriptDir%\PBXrecorder.log
                            }
                        }
                        If (BGVideoOnly=1)
                        {
                            IfNotExist, %MediaOutPath%\%MediaSubDir%\BackGlass Videos\%SearchString%.*
                            {
                                RunWait, %comspec% /k  cd "%OBSPath%\bin\64bit" && start obs64.exe --portable --collection bg --profile bg --scene bg --startrecording && exit,, Hide
                                FileAppend, Recording "%A_ScriptDir%\bg.mkv"`n, %A_ScriptDir%\PBXrecorder.log
                            }
                        }
                        If (DMDVideoOnly=1)
                        {
                            If DMD_width > 0 
                            {
                                IfNotExist, %MediaOutPath%\%MediaSubDir%\DMD Videos\%SearchString%.*
                                {
                                    RunWait, %comspec% /k  cd "%OBSPath%\bin\64bit" && start obs64.exe --portable --collection dmd --profile dmd --scene dmd --startrecording && exit,, Hide
                                    FileAppend, Recording "%A_ScriptDir%\dmd.mkv"`n, %A_ScriptDir%\PBXrecorder.log
                                }
                            }
                            Else
                            {
                                FileAppend, Recording skipped (Bad DMD size) "%A_ScriptDir%\dmd.mkv"`n, %A_ScriptDir%\PBXrecorder.log
                            }
                        }
                    }
                    Else If ((OnlyRecordMissingVideos=3) and (MediaAtLeastOneFound=1))
                    ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                    {
                        ;Safety check for Mode = Record only new tables (Ignore partial media set).
                        FileAppend, Skipping table - XXX Recording only new tables. Partial media set exists... %TablePath%\%Table%`n, %A_ScriptDir%\PBXrecorder.log
                    }
                    Else     ;Record everything for this table (Mode = Re-record all, Record if incomplete table, Record if new table)
                    ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                    {                    
                        If (PFImageOnly=1)
                        {
                            RunWait, %comspec% /k  cd "%OBSPath%\bin\64bit" && start obs64.exe --portable --collection playfield --profile playfield --scene playfield --startrecording && exit,, Hide
                            FileAppend, Screenshot "%MediaOutPath%\%MediaSubDirOut%\Table Images\%SearchString%.png"`n, %A_ScriptDir%\PBXrecorder.log
                        }
                        If (BGImageOnly=1)
                        {
                            RunWait, %comspec% /k  cd "%OBSPath%\bin\64bit" && start obs64.exe --portable --collection bg --profile bg --scene bg --startrecording && exit,, Hide
                            FileAppend, Screenshot "%MediaOutPath%\%MediaSubDirOut%\Backglass Images\%SearchString%.png"`n, %A_ScriptDir%\PBXrecorder.log
                        }
                        If (DMDImageOnly=1)
                        {
                            If DMD_width > 0 
                            {
                                RunWait, %comspec% /k  cd "%OBSPath%\bin\64bit" && start obs64.exe --portable --collection dmd --profile dmd --scene dmd --startrecording && exit,, Hide
                                FileAppend, Screenshot "%MediaOutPath%\%MediaSubDirOut%\DMD Images\%SearchString%.png"`n, %A_ScriptDir%\PBXrecorder.log
                            }
                            Else
                            {
                                FileAppend, Screenshot skipped (Bad DMD size) "%MediaOutPath%\%MediaSubDirOut%\DMD Images\%SearchString%.png"`n, %A_ScriptDir%\PBXrecorder.log
                            }
                        }
;                        Sleep,3000

                        If (PFVideoOnly=1)
                        {
                            RunWait, %comspec% /k  cd "%OBSPath%\bin\64bit" && start obs64.exe --portable --collection playfield --profile playfield --scene playfield --startrecording && exit,, Hide
                            FileAppend, Recording "%A_ScriptDir%\playfield\playfield.mkv"`n, %A_ScriptDir%\PBXrecorder.log
                        }
                        If (BGVideoOnly=1)
                        {
                            RunWait, %comspec% /k  cd "%OBSPath%\bin\64bit" && start obs64.exe --portable --collection bg --profile bg --scene bg --startrecording && exit,, Hide
                            FileAppend, Recording "%A_ScriptDir%\bg.mkv"`n, %A_ScriptDir%\PBXrecorder.log
                        }
                        If (DMDVideoOnly=1)
                        {
                            If DMD_width > 0 
                            {
                                RunWait, %comspec% /k  cd "%OBSPath%\bin\64bit" && start obs64.exe --portable --collection dmd --profile dmd --scene dmd --startrecording && exit,, Hide
                                FileAppend, Recording "%A_ScriptDir%\dmd.mkv"`n, %A_ScriptDir%\PBXrecorder.log
                            }
                            Else
                            {
                                FileAppend, Recording skipped (Bad DMD size) "%A_ScriptDir%\dmd.mkv"`n, %A_ScriptDir%\PBXrecorder.log
                            }
                        }
                    }
                                 
                    WinHide, ahk_class FuturePinball
                    WinMinimize, ahk_class FuturePinball
                    WinActivate, ahk_class FuturePinballOpenGL
                    WinWaitActive, ahk_class FuturePinballOpenGL

                    
                    ;Timer if capturing any videos
                    If (PFVideoOnly=1 OR BGVideoOnly=1 OR DMDVideoOnly=1)
                        {
                            OBSRecTime:=RecTime * 1000
                            Sleep,%OBSRecTime%
                        }
                    Else
                        Sleep,3000

                    ; Clean up
                    ;==========================
                    Send ^{U down}^{U up}
                    WinClose, ahk_class Qt5QWindowIcon
                    Send {Enter}
                    WinClose, ahk_class Qt5QWindowIcon
                    Send {Enter}
                    WinClose, ahk_class Qt5QWindowIcon
                    Send {Enter}
                    Run, taskkill /T /IM obs64.exe ,,UseErrorLevel
                    WinHide, ahk_class FuturePinball
                    WinMinimize, ahk_class FuturePinball
                    WinActivate, ahk_class FuturePinballOpenGL
                    WinWaitActive, ahk_class FuturePinballOpenGL
                    Send {Esc}
                    WinWaitClose, ahk_class FuturePinballOpenGL
                    Process, close, FutureDMD.exe
                    WinClose, ahk_class FuturePinball
                    Run, taskkill /T /IM "Future Pinball.exe" ,,UseErrorLevel
                    Run, taskkill /T /IM "FutureDMD.exe" ,,UseErrorLevel
                    Run, taskkill /IM obs64.exe /F,,UseErrorLevel                
                    Sleep,5000
                    Run, taskkill /F /IM "Future Pinball.exe",,UseErrorLevel
                    WinKill, Error

                    WinShow, ahk_class Button
                    WinShow, ahk_class Shell_TrayWnd

                    
                    ;PIN2DMD related - Not ready for primetime yet
                    ;==========================
                    ;IfExist, %A_ScriptDir%\pin2dmd\pin2dmd.exe
                    ;{
                    ;    Run, %A_ScriptDir%\pin2dmd\Pin2DMD.exe /r,,UseErrorLevel
                    ;}
                    ;IfExist, %A_ScriptDir%\pin2dmd\blank.ppm
                    ;{
                    ;    Run, %A_ScriptDir%\pin2dmd\Pin2DMD.exe /i %A_ScriptDir%\pin2dmd\blank.ppm,,UseErrorLevel
                    ;}

                    ;Convert Videos to .f4v/.mp4 if any mkv exist  - Post Capture Trim and Transcode
                    ;============================================================================
                    Progress, x200 y200 zh0 M h200 w600 FS10, `n`nConvert Videos to %RecExt% if video was captured`n`n(Press ESC anytime to abort script) ,`n`n%Description%, Capturing Videos... #%Loopcount% , Arial
                    
                    ConvertStatusStr= (
                    If (PFVideoOnly=1)
                        ConvertStatusStr= %ConvertStatusStr% PF 
                    If (BGVideoOnly=1)
                        ConvertStatusStr= %ConvertStatusStr% BG     
                    If (DMDVideoOnly=1)
                        ConvertStatusStr= %ConvertStatusStr% DMD
                    ConvertStatusStr= %ConvertStatusStr%  )
                    
                    FileAppend, Convert Videos to %RecExt% if video was captured (%A_Hour%:%A_Min%:%A_Sec%)`n, %A_ScriptDir%\PBXrecorder.log
                    IfExist, %A_ScriptDir%\playfield.mkv ;If (PFVideoOnly=1) OR (PFImageOnly=1);
                    {
                        If (PFImageOnly=1)
                            Run, "%FFMpegPath%\ffmpeg" -y -ss 1 -i "%A_ScriptDir%\playfield.mkv" -vframes 1 -vf "rotate=PI:bilinear=0" "%MediaOutPath%\%MediaSubDirOut%\Table Images\%SearchString%.png"
                        Else
                            {
                                Run, "%FFMpegPath%\ffmpeg" -y -i "%A_ScriptDir%\playfield.mkv" -ss 10 -to 1000 -vf [in]rotate=PI:bilinear=0[middle];[middle]scale=1920:-1[out] -map 0:0 -c:v libx264 -crf 26 "%MediaOutPath%\%MediaSubDirOut%\Table Videos\%SearchString%.%RecExt%"
                                FileAppend, Converting to "%MediaOutPath%\%MediaSubDirOut%\Table Videos\%SearchString%.%RecExt%"`n, %A_ScriptDir%\PBXrecorder.log
                            }
                    }
                    IfExist, %A_ScriptDir%\bg.mkv ;If (BGVideoOnly=1) OR (BGImageOnly=1);
                    {
                        If (BGImageOnly=1)
                            Run, "%FFMpegPath%\ffmpeg" -y -ss 1 -i "%A_ScriptDir%\bg.mkv" -vframes 1 "%MediaOutPath%\%MediaSubDirOut%\Backglass Images\%SearchString%.png"
                        Else
                            {
                                Run, "%FFMpegPath%\ffmpeg" -y -i "%A_ScriptDir%\bg.mkv" -ss 10 -to 1000 -map 0:0 -c:v libx264 -crf 26 "%MediaOutPath%\%MediaSubDirOut%\Backglass Videos\%SearchString%.%RecExt%"
                                FileAppend, Converting to "%MediaOutPath%\%MediaSubDirOut%\Backglass Videos\%SearchString%.%RecExt%"`n, %A_ScriptDir%\PBXrecorder.log
                            }
                    }
                    IfExist, %A_ScriptDir%\dmd.mkv ;If (DMDVideoOnly=1)  OR (DMDImageOnly=1);
                    {        
                        If (DMDImageOnly=1)
                            Run, "%FFMpegPath%\ffmpeg" -y -ss 1 -i "%A_ScriptDir%\dmd.mkv" -vframes 1 -vf "crop=w=%DMD_width%:h=%DMD_height%:x=%DMDXCrop%:y=%DMD_Y%" "%MediaOutPath%\%MediaSubDirOut%\DMD Images\%SearchString%.png"
                        Else
                            {
                                Run, "%FFMpegPath%\ffmpeg" -y -i "%A_ScriptDir%\dmd.mkv" -ss 10 -to 1000 -vf "crop=w=%DMD_width%:h=%DMD_height%:x=%DMDXCrop%:y=%DMD_Y%" -map 0:0 -c:v libx264 -crf 26 "%MediaOutPath%\%MediaSubDirOut%\DMD Videos\%SearchString%.%RecExt%"
                                FileAppend, Converting to "%MediaOutPath%\%MediaSubDirOut%\DMD Videos\%SearchString%.%RecExt%"`n, %A_ScriptDir%\PBXrecorder.log
                            }
                    }
                    ; Clean up
                    ;==========================
                    Process, WaitClose, ffmpeg.exe, 500
                    Run, taskkill /IM ffmpeg.exe /F
                    Run, taskkill /IM ffmpeg.exe /F
                    Run, taskkill /IM ffmpeg.exe /F
                    Progress, Off
                    Sleep,3000
                    ;SoundBeep, 400, 200 
                    FileDelete, %A_ScriptDir%\playfield.mkv
                    FileDelete, %A_ScriptDir%\bg.mkv
                    FileDelete, %A_ScriptDir%\dmd.mkv        
                    FileAppend, Table done (%A_Hour%:%A_Min%:%A_Sec%)`n, %A_ScriptDir%\PBXrecorder.log
                    
                } ;End of If TableExists = 1
            } ;End of If TableEnabled = 1
        } ;End of Else IfInString, A_LoopReadLine, /game
    } ;End of Loop, Read, %XMLPath%\%XMLFilename%.xml
}

; All done!
;===============
Progress, Off
If (DragAndDrop<>1)
{
    FileAppend, `n%Recordcount% new recordings out of %Loopcount% tables. Finished (%A_Hour%:%A_Min%:%A_Sec%)`n, %A_ScriptDir%\PBXrecorder.log
    Msgbox,0, Recording Finished,Recording is complete.  %Recordcount% new recordings out of %Loopcount% tables
}
Else ;Drag and drop done
{
    If (DragAndDropTableFound=1)
    {
        FileAppend, `nDrag and Drop recording finished (%DragAndDropFile%). Finished (%A_Hour%:%A_Min%:%A_Sec%)`n, %A_ScriptDir%\PBXrecorder.log
        Msgbox,0, Drag and Drop, Recording is complete`n`n(%DragAndDropFile%).
    }
    Else If (DragAndDropTableFound=-1)
    {
        FileAppend, `nDrag and Drop skipped.  Media already exists (%DragAndDropFile%). Finished (%A_Hour%:%A_Min%:%A_Sec%)`n, %A_ScriptDir%\PBXrecorder.log
        Msgbox,0, Drag and Drop, No Recording. Media already exists`n`n(%DragAndDropFile%).
    }
    Else
    {
        FileAppend, `nDrag and Drop table was not found in the FP XML files (%DragAndDropFile%). Finished (%A_Hour%:%A_Min%:%A_Sec%)`n, %A_ScriptDir%\PBXrecorder.log
        Msgbox,0, Drag and Drop, Table was not found in the FP XML files.  No recording performed`n`n(%DragAndDropFile%).
    }
}
ExitApp


;*******************************************
;*******************************************
; Misc Subroutines for Msgbox and Gui windows and Exit
;*******************************************
;*******************************************
RecordModeButtonNames: 
IfWinNotExist, Record Mode
    return  ; Keep waiting.
SetTimer, RecordModeButtonNames, off 
WinActivate 
ControlSetText, Button1, &Videos Only
ControlSetText, Button2, &Both 
return    

MissingVideosButtonNames: 
IfWinNotExist, Record Mode
    return  ; Keep waiting.
SetTimer, MissingVideosButtonNames, off 
WinActivate 
ControlSetText, Button1, &Missing Only
ControlSetText, Button2, &All Tables
return    

CurrSettingsButtonNames: 
IfWinNotExist, Current Recorder Settings
    return  ; Keep waiting.
SetTimer, CurrSettingsButtonNames, off 
WinActivate 
ControlSetText, Button1, &Continue
ControlSetText, Button2, &Change
return    


StoreMediaButtonNames: 
IfWinNotExist, Storing Media
    return  ; Keep waiting.
SetTimer, StoreMediaButtonNames, off 
WinActivate 
ControlSetText, Button1, &Combined
ControlSetText, Button2, &Separate
return    

GuiClose:
ExitApp

;Pressing the ESC key will abort the script
Esc::
IfWinNotExist,Add Table to XML    
    IfWinNotExist,Press Pause to Resume
        {
            WinShow, ahk_class Button
            WinShow, ahk_class Shell_TrayWnd
            ExitApp
        }    
Else
    Return   ; XML dialog present, do not exit

Pause::
    If (LoadingTable=1)
    {
        If (PBXRPauseMode=0)
        {
            PBXRPauseMode:=1
        }
        Else
        {
            Progress, Off
        }
    }
    Return
    
    
Confirm:
    Gui, Submit
    PFVideoOnly=0
    BGVideoOnly=0
    DMDVideoOnly=0
    PFImageOnly=0
    BGImageOnly=0
    DMDImageOnly=0

    if (PFVid = 1) 
        PFVideoOnly=1
    if (BGVid = 1) 
        BGVideoOnly=1
    if (DMDVid = 1) 
        DMDVideoOnly=1
    if (PFImage = 1) 
        PFImageOnly=1
    if (BGImage = 1) 
        BGImageOnly=1
    if (DMDImage = 1) 
        DMDImageOnly=1    

    Gui, Destroy
return
        
Select:
    Gui, Submit
    if (RadioGroup1) 
        RecTime:=5
    else if (RadioGroup2) 
        RecTime:=15
    else if (RadioGroup3) 
        RecTime:=30
    else if (RadioGroup4) 
        RecTime:=60
    else if (RadioGroup5) 
        RecTime:=120
    else if (RadioGroup6) 
        RecTime:=300    
        
    Gui, Destroy
Return

MediaMode:
    Gui, Submit
    if (MRadioGroup1) 
        OnlyRecordMissingVideos:=2
    else if (MRadioGroup2) 
        OnlyRecordMissingVideos:=1
    else if (MRadioGroup3) 
        OnlyRecordMissingVideos:=0
    else if (MRadioGroup4) 
        OnlyRecordMissingVideos:=3
    Gui, Destroy
Return

MediaF:
    Gui, Submit
    if (RadioGroupMF1) 
        MediaFileNaming:=0
    else if (RadioGroupMF2) 
        MediaFileNaming:=1

    Gui, Destroy
Return

MediaR:
    Gui, Submit
    if (RadioGroupRF1) 
    {
        RecFormat:=0
        RecExt=f4v
    }
    else if (RadioGroupRF2) 
    {
        RecFormat:=1
        RecExt=mp4
    }
    Gui, Destroy
Return

; Subroutines for Add Table to XML files
GetXMLDescSelected:
GuiControlGet, XMLDescSelected 
x:=IPDBArray[XMLDescSelected]
GuiControl,, XMLDescText,%x%
x:=IPDBMfrArray[XMLDescSelected]
GuiControl,, XMLMfr,%x%
x:=IPDBYearArray[XMLDescSelected]
GuiControl,, XMLYear,%x%
x:=IPDBTypeArray[XMLDescSelected]
GuiControl,, XMLType,%x%
Return

XMLLaunch:
GuiControlGet, XMLAltExe
GuiControlGet, XMLAltExeName
If (XMLAltExe = 1)
    IfExist, %WorkingPath%\%LaunchExecutable%
    {
        Run, "%WorkingPath%\%LaunchExecutable%" /STAYINRAM /open "%DragAndDropFile%" /play /exit
    }
    Else
    {
        Msgbox, %WorkingPath%\%LaunchExecutable% could not be found
        Return
    }
Else
    IfExist, %WorkingPath%\%XMLAltExeName%
        Run, "%WorkingPath%\%XMLAltExeName%" /STAYINRAM /open "%DragAndDropFile%" /play /exit
    Else
    {
        Msgbox, %WorkingPath%\%XMLAltExeName% could not be found
        Return
    }
Return

XMLCancel:
Gui, Destroy
ExitApp

XMLSaveExit:
gosub XMLSave
;Msgbox XML Saved
ExitApp

XMLSave:
;Figure out which XML file to update
GuiControlGet, XMLFPSystem
tempXMLString1:=XMLPathArray[XMLFPSystem]
tempXMLString2:=XMLFileNameArray[XMLFPSystem]
tempXMLStringOrig=%tempXMLString1%\%tempXMLString2%.xml
tempXMLStringBak=%tempXMLString1%\%tempXMLString2%.bak
;FileAppend, `t TEST %tempXMLStringOrig%`n, %A_ScriptDir%\XMLtest.txt

FileCopy, %tempXMLStringOrig%, %tempXMLStringBak%, 1
FileRead, tempXMLfilestring, %tempXMLStringOrig%
StringReplace, tempXMLfilestring, tempXMLfilestring, `r`n</menu>
StringReplace, tempXMLfilestring, tempXMLfilestring, `n</menu>
FileDelete, %tempXMLStringOrig%
FileAppend, %tempXMLfilestring%, %tempXMLStringOrig%

GuiControlGet, XMLTableFileName
FileAppend, `t<game name="%XMLTableFileName%">`n, %tempXMLStringOrig%

GuiControlGet, XMLDescText 
FileAppend, `t`t<description>%XMLDescText%</description>`n, %tempXMLStringOrig%
FileAppend, `t`t<rom></rom>`n, %tempXMLStringOrig%

GuiControlGet, XMLMfr
FileAppend, `t`t<manufacturer>%XMLMfr%</manufacturer>`n, %tempXMLStringOrig%

GuiControlGet, XMLYear
FileAppend, `t`t<year>%XMLYear%</year>`n, %tempXMLStringOrig%

GuiControlGet, XMLType
FileAppend, `t`t<type>%XMLType%</type>`n, %tempXMLStringOrig%

GuiControlGet, XMLHideDMD
If (XMLHideDMD=1)
    FileAppend, `t`t<hidedmd>True</hidedmd>`n, %tempXMLStringOrig%
Else
    FileAppend, `t`t<hidedmd>False</hidedmd>`n, %tempXMLStringOrig%

GuiControlGet, XMLHideBG
If (XMLHideBG=1)
    FileAppend, `t`t<hidebackglass>True</hidebackglass>`n, %tempXMLStringOrig%
Else
    FileAppend, `t`t<hidebackglass>False</hidebackglass>`n, %tempXMLStringOrig%

GuiControlGet, XMLEnabled
If (XMLEnabled=1)
    FileAppend, `t`t<enabled>True</enabled>`n, %tempXMLStringOrig%
Else
    FileAppend, `t`t<enabled>False</enabled>`n, %tempXMLStringOrig%

GuiControlGet, XMLRating
XMLRating := XMLRating-1    
FileAppend, `t`t<rating>%XMLRating%</rating>`n, %tempXMLStringOrig%

GuiControlGet, XMLAltExe
GuiControlGet, XMLAltExeName
If (XMLAltExe = 2)
    FileAppend, `t`t<alternateExe>%XMLAltExeName%</alternateExe>`n, %tempXMLStringOrig%
Else If (XMLAltExe = 3)
    FileAppend, `t`t<Exe>%XMLAltExeName%</alternateExe>`n, %tempXMLStringOrig%

FileAppend, `t</game>`n, %tempXMLStringOrig%
FileAppend, </menu>`n, %tempXMLStringOrig%
Gui, Destroy
Return

XMLDB2S:
IfExist, %DragAndDropFileDir%\%DragAndDropFileNameOnly%.directB2S
    Return
Else IfExist, %DragAndDropFileDir%\%DragAndDropFileNameOnly%.exe
    Return
Else ;Let user select DirectB2S file to rename
    FileSelectFile, XMLFilePicked,,%DragAndDropFileDir%,,*.directb2s

IfExist, %XMLFilePicked%
{
    SetTimer, DB2SButtonNames, 50 
    Msgbox, 3, DirectB2S File Rename, Create a copy of the DirectB2S file and rename it?            
    IfMsgBox, Yes ; Copy and rename the file
    {
        GuiControl ,, XDB2S, DB2S Found
        FileCopy, %XMLFilePicked%, %DragAndDropFileDir%\%DragAndDropFileNameOnly%.directB2S, 1
    }
    Else IfMsgBox, No ; Just rename the file
    {
        FileMove, %XMLFilePicked%, %DragAndDropFileDir%\%DragAndDropFileNameOnly%.directB2S, 1
        GuiControl ,, XDB2S, DB2S Found
    }
}
Return

DB2SButtonNames: 
IfWinNotExist, DirectB2S File Rename
    return  ; Keep waiting.
SetTimer, DB2SButtonNames, off 
WinActivate 
ControlSetText, Button1, &Copy&&Rename
ControlSetText, Button2, &Just Rename 
return        
        
; Release 1.0    Initial release Nov 11, 2015
;                 Create playfield, backglass, dmd videos and/or images automatically based on PinballX setup
; Release 1.1    Fix bug if VisualPinball system is disabled  (Dec 2015)
; Release 1.2    Skip table if table is disabled in Game Manager/XML (Jan 9, 2016)
;                Add <AlternateExe> and <exe> xml tag support
;                Add support for & and ® and ' in Table and Description names
;               Check if vpt or vpx file exists, skip if not found
;                Detects if Wheel Images or DirectB2S/B2S.exe are missing
;                PinballX FTP users can enable automatic Wheel download support
;                If PIN2DMD.exe found, it will reset PIN2DMD between tables
; 1.3 changes   Feb 7, 2016            
;                 New recording mode added:  "Record complete media set for new tables only"  If any media is found, the table is skipped. Only tables with no media are recorded.   This might eliminate the need for the drag and drop feature, since this leaves your existing Pinballx Media set unchanged, i.e if you do not have DMD images for some existing tables, PBX Recorder will not try to create them
;                 Drag and drop .vpt/.vpx file onto PBXrecorder to record only one table (Table must be exist in the PinballX xml files)
;                 New option to record video in .mp4 or existing .f4v.  Now checks for existing .f4v or .mp4 or any file.
;                 New option to save Media filenames based on description name or table name
;                 Speed up the scanning of tables for people with larger libraries
;                 Improve DMD recording success by forcing dvd dimensions to an even pixel value
;                 Bug fix: Enable recordings for VP xml systems missing the enabled xml tag        
; 1.4 changes   Apr 7, 2016
;                New dialog to add table to Pinballx XML file
;                PAUSE and RESUME support to allow table script changes before recording.  Double tap PAUSE to record immediately
; 1.4FP         May 2, 2016
;                Future Pinball support with playfield capture via OBS Studio
;                Add support for ™ and recognition of &#42; (i.e, *) in Table and Description names
;                Some minor clean up for unused ErrorLevel parameters
                

;                Add right click support (Play with VP, Record with PBXRecorder)
;todo: create reg entries
;todo: detect xml file to default to
;todo list duplicates xmls entries. 
;todo support [xxx] for wheel download.
;todo list tables not in the xml file
;todo switch ini parsing to use split string function
; possible bug.  recext not correct after modifying ini 

Changes from 1.4

; 1.4FP         Future Pinball support with playfield capture via OBS Studio
;                Add support for ™ and recognition of &#42; (i.e, *) in Table and Description names
;               Some minor clean up for unused ErrorLevel parameters

Again thanks to gtxjoe for allowing mods to his source code. 

Edited by Carny_Priest
source embedded in a spoiler
  • Like 1
  • 2 weeks later...
Posted

Hi, I installed Unit3D Pinball last year, but I just now finally got around to coding a launcher for myself. The timing is pretty good as it seems the French Pinball Team is pretty close to releasing Roadshow. In any case, I need media so I ported gtxjoe's PBX Recorder to capture Unit3D. Works pretty much the same way as PBX Recorder. It uses ffmpeg as the capture engine.

The 64bit compiled executable is here:

https://dl.dropboxusercontent.com/u/45430846/PBXrecorder_1.4Unit3D.exe

As always, run as admin.

This can co-exist in the same folder as PBXRecorder for VP. The log will get overwritten but it creates an ini file with a different file name.

Output will look best and work trouble free with the LAV filters that are linked on the PinballX home page.

Unit3D only supports two screens by spanning the game window over to the backglass display while running cab calibration. DMD settings are still part of the ini, but any selections will be ignored.

The script assumes that "Unit3D" is contained in the name assigned to your Unit3D system (e.g., the databases have file names such as "Unit3D.xml" or "Unit3D Pinball.xml").   

The script assumes that your displays will be set up in a standard fashion with playfield screen in landscape orientation and backglass to the right in landscape orientation. The tops of the screens aligned on the same axis. If you do have Unit3D setup with the playfield screen in portrait orientation then you will probably need to make a minor tweak to the script so that the output will be saved in the correct orientation for display in PinballX.

Capture settings are read from Config\CabCalibration.xml. No sense trying to capture anything until you are sure the tables are playing correctly.

Script still checks for missing wheels and will direct to Visual Pinball\Wheel Images on the FTP if you enter in your account information.

Source:

Spoiler

;PBX recorder 1.4Unit3D (May 12, 2016)

LoadTime:= 15         ;How long to allow table to load/start (in sec)


;*****Start log file
FileDelete %A_ScriptDir%\PBXrecorder.log
FileAppend, %A_MMMM% %A_DD%`,%A_YYYY% %A_Hour%:%A_Min%:%A_Sec%`n, %A_ScriptDir%\PBXrecorder.log
FileAppend, Version 1.4Unit3D`n, %A_ScriptDir%\PBXrecorder.log

;*******If table drag and drop use, record only that table *************************
Loop %0% 
{
    GivenPath := %A_Index%
    Loop %GivenPath%, 1
    {
        DragAndDropFile = %A_LoopFileLongPath%
    }
    SplitPath, DragAndDropFile, DragAndDropFileName, DragAndDropFileDir, DragAndDropFileExtOnly, DragAndDropFileNameOnly
    If ((InStr(DragAndDropFileExtOnly, "upt")) = 1)
        DragAndDrop := 1
;    Else If ((InStr(DragAndDropFileExtOnly, "vpx")) = 1)
;        DragAndDrop := 1
    Else
    {
        Msgbox Error:       %DragAndDropFileName% is not a Unit3D table
        ExitApp
    }
        
    FileAppend, Drag and Drop used:  %DragAndDropFile%`n, %A_ScriptDir%\PBXrecorder.log
    DragText = ---------------------------  Drag and drop mode  ---------------------------`n%DragAndDropFileName%`n------------------------------------------------------------------------------
    Break
}

;***************Monitor setup details
SysGet, MonitorCount, MonitorCount
SysGet, MonitorPrimary, MonitorPrimary
FileAppend, `nMonitor Count: %MonitorCount%`,  Primary Monitor: %MonitorPrimary%`n, %A_ScriptDir%\PBXrecorder.log
Loop, %MonitorCount%
{
    SysGet, MonitorName, MonitorName, %A_Index%
    SysGet, Monitor, Monitor, %A_Index%
    SysGet, MonitorWorkArea, MonitorWorkArea, %A_Index%
    ;MsgBox, Monitor %A_Index%: %MonitorRight%x%MonitorBottom% (%MonitorName%)
    FileAppend, Monitor %A_Index%: %MonitorRight%x%MonitorBottom% (%MonitorName%)`n, %A_ScriptDir%\PBXrecorder.log
}

;************************************************************************************************
;************************************************************************************************
;Create/Load PinballX recorder settings 
;************************************************************************************************
;************************************************************************************************
;************************************************************************************************
;************************************************************************************************
Version:=4
NumOfSettings:=12
LoadTimeX:=LoadTime*10
PBXRPauseMode:=0
BetaMode:=0           ;0=Normal mode,   1=Beta mode will store recorded media in a Beta folder
                    
;Load PBXrecorder settings if they exist
IfExist, %A_ScriptDir%\PBXrecorderUnit3D.ini
{
    ;Load Existing settings from file
    ;*************************
    Loopcount:=0
    Loop, Read, %A_ScriptDir%\PBXrecorderUnit3D.ini
    {
            If (Loopcount = 0)
                VersionRead:= A_LoopReadLine    
            Else If (Loopcount=1)                
                PinballXPath := A_LoopReadLine
            Else If (Loopcount=2)
                OnlyRecordMissingVideos := A_LoopReadLine    
            Else If (Loopcount=3)
                PFVideoOnly := A_LoopReadLine                
            Else If (Loopcount=4)
                BGVideoOnly := A_LoopReadLine                
            Else If (Loopcount=5)
                DMDVideoOnly := A_LoopReadLine                
            Else If (Loopcount=6)
                PFImageOnly := A_LoopReadLine                
            Else If (Loopcount=7)
                BGImageOnly := A_LoopReadLine                
            Else If (Loopcount=8)
                DMDImageOnly := A_LoopReadLine
            Else If (Loopcount=9)
                RecFormat := A_LoopReadLine
            Else If (Loopcount=10)
                RecTime := A_LoopReadLine            
            Else If (Loopcount=11)
                MediaFileNaming := A_LoopReadLine
                
            Loopcount:=Loopcount + 1
    }

    ;Check Version of ini file
    ;*************************
    If ((Version=VersionRead) and (LoopCount=NumOfSettings))    ;If ini version matches, process, else force user to update settings
    {
        ;Display ini settings
        ;*************************
        If (OnlyRecordMissingVideos = 2)
            TempStr2 := "`nFind and record missing media files only`n(Existing media files are kept)"
        Else If (OnlyRecordMissingVideos = 1)
            TempStr2 := "`nRecord complete media set for new and incomplete tables`n(Incomplete media sets are deleted/re-recorded)"
        Else If (OnlyRecordMissingVideos = 3)
            TempStr2 := "`nRecord complete media set for new tables only`n(If any media is found, the table is skipped. Tables with no media are recorded)"
        Else
            TempStr2 := "`nStart over and record complete media sets for all tables`n(All existing media is replaced)"
        
        If RecFormat = 1
            RecExt=mp4
        Else
            RecExt=f4v
            
        TempStr1 = `nMedia to Record (%RecTime% sec %RecExt% videos):`n
        If (PFVideoOnly=1)
            TempStr1 = %TempStr1%Playfield Videos`n
        If (BGVideoOnly=1)
            TempStr1 = %TempStr1%Backglass Videos`n
        If (DMDVideoOnly=1)
            TempStr1 = %TempStr1%DMD Videos`n
        If (PFImageOnly=1)
            TempStr1 = %TempStr1%Playfield Images`n
        If (BGImageOnly=1)
            TempStr1 = %TempStr1%Backglass Images`n
        If (DMDImageOnly=1)
            TempStr1 = %TempStr1%DMD Images`n
            
        If (MediaFileNaming=0)
            TempStr1 = %TempStr1%`nMedia filename based on Description tags`n
        Else
            TempStr1 = %TempStr1%`nMedia filename based on Unit3D table filename`n
            
        If (DragAndDrop<>1)    ; if drag and drop not used, display settings
        {
            ;Prompt user if change to settings desired
            ;*************************
            SetTimer, CurrSettingsButtonNames, 50 
            Msgbox, 4, Current Recorder Settings, %DragText%`n`nPinballX Path: %PinballXPath%`n%TempStr2%`n%TempStr1%`n`n`nPress ESC to abort script            
            IfMsgBox, NO 
                Change := 1
        }
    }
    Else
    {
        Msgbox, New version of the video recorder detected.  Please update settings
        Change := 1
    }
    
}
Else    ;No PBXRecorderUnit3D.ini found.  Need to Create the ini file
{
    ;******* PBXR settings needed ******************
    Change := 1
    PinballXPath = C:\PinballX
}

;*****Create Pinballx.net FTP login file if not exist
IfNotExist, %A_ScriptDir%\FTPLoginInfo.txt
{
    FileAppend, **** To enabled Wheel Image downloads - Replace USERNAME and PASSWORD with your Pinballx FTP login info (requires subscription)`n, %A_ScriptDir%\FTPLoginInfo.txt
    FileAppend, USERNAME`n, %A_ScriptDir%\FTPLoginInfo.txt
    FileAppend, PASSWORD`n, %A_ScriptDir%\FTPLoginInfo.txt
}

; User needs to update PBX Recorder ini settings
;*************************
If (Change = 1)        
{
    ;Pinballx folder
    Msgbox, 4,PBXrecorder Settings, PinballX folder: %PinballXPath%`n`n Do you want to change the Pinballx path?       `n
    IfMsgBox Yes    ;display folder selection dialog
    {
        FileSelectFolder, PinballXPath,,0,Select the Pinballx Folder
        PinballXPath := RegExReplace(PinballXPath, "\\$")
    }
    
    ;Pic Record Mode
    Gui, Show, W810 H170, Record Mode
    gui, font, s10, Arial
    Gui, Add, Radio, checked vMRadioGroup1, Find and record missing media files only (Existing media files are kept. No files deleted)
    Gui, Add, Radio, vMRadioGroup4, Record complete media set for new tables only (If any media is found, the table is skipped. Only tables with no media are recorded)
    Gui, Add, Radio, vMRadioGroup2, Record complete media set for new and incomplete tables (Missing media is recorded. Incomplete media sets are deleted/re-recorded)
    Gui, Add, Radio, vMRadioGroup3, Start over and record complete media sets for all tables (All existing media is deleted and re-recorded)
    Gui, Add, Text,, `n
    Gui, Add, Button, Default gMediaMode, Next
    Gui, Show
    WinWaitClose, Record Mode        

    ;Choose what media to record
    Gui, Show, W400 H280, Choose Media to Record
    gui, font, s10, Arial
    Gui, Add, Text,,Videos
    Gui, Add, Checkbox, vPFVid, Playfield Videos
    Gui, Add, Checkbox, vBGVid, Backglass Videos
    Gui, Add, Checkbox, vDMDVid, DMD Videos (Req 3 Monitor setup)
    Gui, Add, Text,,`nImages
    Gui, Add, Checkbox, vPFImage, Playfield Image
    Gui, Add, Checkbox, vBGImage, Backglass Image
    Gui, Add, Checkbox, vDMDImage, DMD Image (Req 3 Monitor setup)
    Gui, Add, Text,, `n
    Gui, Add, Button, Default gConfirm, Next
    Gui, Show
    WinWaitClose, Choose Media to Record

    ;Set the video recording time
    Gui, Show, W400 H260, Video Recording Time
    gui, font, s10, Arial
    Gui, Add, Text,,Select duration of video recordings:`n
    Gui, Add, Radio, vRadioGroup1, 5 seconds
    Gui, Add, Radio, vRadioGroup2, 15 seconds
    Gui, Add, Radio, vRadioGroup3, 30 seconds
    Gui, Add, Radio, Checked vRadioGroup4, 60 seconds
    Gui, Add, Radio, vRadioGroup5, 120 seconds
    Gui, Add, Radio, vRadioGroup6, 300 seconds
    Gui, Add, Text,, `n
    Gui, Add, Button, Default gSelect, Next
    Gui, Show
    WinWaitClose, Video Recording Time
    
    ;Recording Format
    Gui, Show, W500 H170, Recording Format
    gui, font, s10, Arial
    Gui, Add, Text,,Record all videos in:`n
    Gui, Add, Radio, checked vRadioGroupRF1, .f4v format 
    Gui, Add, Radio, vRadioGroupRF2, .mp4 format
    Gui, Add, Text,, `n
    Gui, Add, Button, Default gMediaR, Next
    Gui, Show
    WinWaitClose, Recording Format
    
    ;File naming convention
    Gui, Show, W500 H170, Media Naming
    gui, font, s10, Arial
    Gui, Add, Text,,Media filenames should be based on:`n
    Gui, Add, Radio, checked vRadioGroupMF1, Description tag            Example:  Dr. Dude (Bally 1990).%RecExt% 
    Gui, Add, Radio, vRadioGroupMF2, Unit3D table filename        Example:  Dr Dude.%RecExt%
    Gui, Add, Text,, `n
    Gui, Add, Button, Default gMediaF, Next
    Gui, Show
    WinWaitClose, Media Naming
    
    ;******Save Settings*****
    FileDelete %A_ScriptDir%\PBXrecorderUnit3D.ini
    FileAppend,%Version%`n%PinballXPath%`n%OnlyRecordMissingVideos%`n%PFVideoOnly%`n%BGVideoOnly%`n%DMDVideoOnly%`n%PFImageOnly%`n%BGImageOnly%`n%DMDImageOnly%`n%RecFormat%`n%RecTime%`n%MediaFileNaming%`n, %A_ScriptDir%\PBXrecorderUnit3D.ini

    ;Display ini settings
    If (OnlyRecordMissingVideos = 2)
            TempStr2 := "`nFind and record missing media files only`n(Existing media files are kept)"
        Else If (OnlyRecordMissingVideos = 1)
            TempStr2 := "`nRecord complete media set for new and incomplete tables`n(Incomplete media sets are deleted/re-recorded)"
        Else If (OnlyRecordMissingVideos = 3)
            TempStr2 := "`nRecord complete media set for new tables only`n(If any media is found, the table is skipped. Tables with no media are recorded)"
        Else
            TempStr2 := "`nStart over and record complete media sets for all tables`n(All existing media is replaced)"
            
    If RecordingFormat = 1
        RecExt=mp4
    Else
        RecExt=f4v
            
    TempStr1 = `nMedia to Record (%RecTime% sec %RecExt% videos):`n
    If (PFVideoOnly=1)
        TempStr1 = %TempStr1%Playfield Videos`n
    If (BGVideoOnly=1)
        TempStr1 = %TempStr1%Backglass Videos`n
    If (DMDVideoOnly=1)
        TempStr1 = %TempStr1%DMD Videos`n
    If (PFImageOnly=1)
        TempStr1 = %TempStr1%Playfield Images`n
    If (BGImageOnly=1)
        TempStr1 = %TempStr1%Backglass Images`n
    If (DMDImageOnly=1)
        TempStr1 = %TempStr1%DMD Images`n

    If (MediaFileNaming=0)
        TempStr1 = %TempStr1%`nMedia filename based on Description tags`n
    Else
        TempStr1 = %TempStr1%`nMedia filename based on VP table filename`n

    Msgbox, 0, Current Recorder Settings, PinballX Path: %PinballXPath%`n%TempStr2%`n%TempStr1%`n(Wheel Image download support. See FTPLoginInfo.txt)`n`n`nPress ESC to abort script

}
FileAppend,`nPinballx.ini`n%Version%`n%PinballXPath%`n%OnlyRecordMissingVideos%`n%PFVideoOnly%`n%BGVideoOnly%`n%DMDVideoOnly%`n%PFImageOnly%`n%BGImageOnly%`n%DMDImageOnly%`n%RecFormat%`n%RecTime%`n%MediaFileNaming%`n`nIdentify all Unit3D XML files...`n, %A_ScriptDir%\PBXrecorder.log

;Set FFMpeg and MediaOut folder locations
FFMpegPath = %A_ScriptDir%\FFMpeg\bin
MediaOutPath = %PinballXPath%\Media

;************************************************************************************************
;************************************************************************************************
;2. Time to walk find all the Unit3D xml files
;************************************************************************************************
;************************************************************************************************
;************************************************************************************************
;Arrays for finding and storing Unit3D xml files and path information
XMLPathArray := Object()
XMLFileNameArray := Object()
WorkingPathArray := Object()
TablePathArray := Object()
ExecutableArray := Object()
;Array[j] := A_LoopField
; Array of xml search strings
Array := ["[VisualPinball]", "Enabled=", "WorkingPath=", "TablePath=", "Executable=", "[System_",  "Name=", "WorkingPath=", "TablePath=", "Executable=", "Enabled=", "SystemType=", "XXXX END XXXX"]
;                1                2           3               4             5             6           7            8            9             10              11          12                13

VPXMLCount=1
VPSearchIndex:=1
VPActiveFlag=0
VPKeepFlag = 1 ;Assume System is enabled
VPSystemFlag = 0
VPFirstSystemDone = 0

If (DragAndDrop<>1)
    PauseTime:=200
Else
    PauseTime:=0
    
;Open PinballX.ini file and identify Unit3D xml files
;=================================================
Loop, Read, %PinballXPath%\Config\PinballX.ini
{
    ;****** Find 1st System ******
    If (VPFirstSystemDone=0 and VPActiveFlag=0)        ;Search for 1st System start
    {
        StringGetPos, Position, A_LoopReadLine, [VisualPinball]    
        If Position = 0 
        {
            VPActiveFlag = 1
            TempStr = %PinballXPath%\Databases\Visual Pinball
            XMLPathArray[VPXMLCount] := TempStr
            XMLFileNameArray[VPXMLCount] := "Visual Pinball"
            TempStr1 := XMLPathArray[VPXMLCount]
            TempStr2 := XMLFileNameArray[VPXMLCount]
            ;Msgbox, XMLPathArray[%VPXMLCount%]=%TempStr1%\%TempStr2%
        }
        else        
        {
            continue    ;Read next line
        }
    }
    else if (VPFirstSystemDone==0 and VPActiveFlag==1)    ;Collect info for 1st system
    {
        StringGetPos, Position, A_LoopReadLine, WorkingPath
        If Position = 0 
        {
            TempStr1 := SubStr(A_LoopReadLine, 13)
            WorkingPathArray[VPXMLCount] := TempStr1
            TempStr2 := WorkingPathArray[VPXMLCount]
            ;Msgbox WorkingPathArray[%VPXMLCount%]=%TempStr2%        
        }
        StringGetPos, Position, A_LoopReadLine, Tablepath
        If Position = 0 
        {
            TempStr1 := SubStr(A_LoopReadLine, 11)
            TablePathArray[VPXMLCount] := TempStr1
            TempStr2 := TablePathArray[VPXMLCount]
            ;Msgbox TablePathArray[%VPXMLCount%]=%TempStr2%        
        }
        StringGetPos, Position, A_LoopReadLine, Executable
        If Position = 0 
        {
            TempStr1 := SubStr(A_LoopReadLine, 12)
            ExecutableArray[VPXMLCount] := TempStr1
            TempStr2 := ExecutableArray[VPXMLCount]
            ;Msgbox ExecutableArray[%VPXMLCount%]=%TempStr2%
        }
        StringGetPos, Position, A_LoopReadLine, Enabled=True
        If Position = 0 
        {
            VPKeepFlag = 1
            ;MsgBox, enabled is true
        }
        StringGetPos, Position, A_LoopReadLine, Enabled=False
        If Position = 0 
        {
            VPKeepFlag = 0
            ;MsgBox, enabled is false
        }
        StringGetPos, Position, A_LoopReadLine, [     ;End of section found
        If Position = 0 
        {
            VPFirstSystemDone=1
            StringGetPos, pos, A_LoopReadLine, [
            if pos = 0
            {
                If VPKeepFlag = 1    ;Keep System info
                {
                    TempStr1 := XMLPathArray[VPXMLCount]
                    TempStr2 := XMLFileNameArray[VPXMLCount]
                    TempStr3 := WorkingPathArray[VPXMLCount]
                    TempStr4 := TablePathArray[VPXMLCount]
                    TempStr5 := ExecutableArray[VPXMLCount]
                    ;Msgbox, XMLPathArray[%VPXMLCount%]=%TempStr1%\%TempStr2%`n WorkingPathArray[%VPXMLCount%]=%TempStr3%`n TablePathArray[%VPXMLCount%]=%TempStr4%`n ExecutableArray[%VPXMLCount%]=%TempStr5%
;                    FileAppend, VP System #%VPXMLCount%:`n%TempStr1%\%TempStr2%.xml`n%TempStr3%`n%TempStr4%`n%TempStr5%`n`n, %A_ScriptDir%\PBXrecorder.log

;                    VPXMLCount++
                }
                Else
                {
;                    FileAppend, `nVisual Pinball System is disabled in the XML file`n`n, %A_ScriptDir%\PBXrecorder.log
                    ;Msgbox, System Disabled (%VPSystemFlag% %VPKeepFlag%)
                }
                VPActiveFlag = 0
                VPKeepFlag = 1    ;Assume System is enabled        
            }
            
            ; Need to check this line if it is the start of a new system
            StringGetPos, Position, A_LoopReadLine, [System_
            If Position = 0 
            {
                VPActiveFlag = 1
            }

        }
    }
    ;****** Find Extra Systems ******
    Else If (VPFirstSystemDone=1 and VPActiveFlag = 0)        ;Search for start extra [System...] sections
    {
        StringGetPos, Position, A_LoopReadLine, [System_
        If Position = 0 
        {
            VPActiveFlag = 1
        }
        else        
        {
            continue    ;Read next line
        }
    }
    else if (VPFirstSystemDone=1 and VPActiveFlag = 1)    ;Collect info for the additional [System... sections
    {
        StringGetPos, Position, A_LoopReadLine, Name
        If Position = 0 
        {
            TempStr1 := SubStr(A_LoopReadLine, 6)
            TempStr = %PinballXPath%\Databases\%TempStr1%
            XMLPathArray[VPXMLCount] := TempStr
            XMLFileNameArray[VPXMLCount] := TempStr1
            ;Msgbox, XMLPathArray[%VPXMLCount%]=%TempStr%\%TempStr1%
        }
        StringGetPos, Position, A_LoopReadLine, WorkingPath
        If Position = 0 
        {
            TempStr1 := SubStr(A_LoopReadLine, 13)
            WorkingPathArray[VPXMLCount] := TempStr1
            TempStr2 := WorkingPathArray[VPXMLCount]
            ;Msgbox %TempStr2%        
        }
        StringGetPos, Position, A_LoopReadLine, Tablepath
        If Position = 0 
        {
            TempStr1 := SubStr(A_LoopReadLine, 11)
            TablePathArray[VPXMLCount] := TempStr1
            TempStr2 := TablePathArray[VPXMLCount]
            ;Msgbox %TempStr2%        
        }
        StringGetPos, Position, A_LoopReadLine, Executable
        If Position = 0 
        {
            TempStr1 := SubStr(A_LoopReadLine, 12)
            ExecutableArray[VPXMLCount] := TempStr1
            TempStr2 := ExecutableArray[VPXMLCount]
            ;Msgbox %TempStr2%
        }
        StringGetPos, Position, A_LoopReadLine, SystemType=0
        If Position = 0 
        {
            VPSystemFlag = 1
            ;MsgBox, System is Custom
        }
        StringGetPos, Position, A_LoopReadLine, Enabled=True
        If Position = 0 
        {
            VPKeepFlag = 1
            ;MsgBox, System Enabled
        }
        StringGetPos, Position, A_LoopReadLine, Enabled=False
        If Position = 0 
        {
            VPKeepFlag = 0
            ;MsgBox, System Disabled

        }
        StringGetPos, Position, A_LoopReadLine, [
        If Position = 0 
        {
            TempStr := XMLFileNameArray[VPXMLCount]
            StringGetPos, Position, TempStr, Unit3D   ;Name contains Unit3D
            If (VPSystemFlag = 1 and VPKeepFlag = 1 and Position >= 0)       ;Keep System info
            {
                TempStr1 := XMLPathArray[VPXMLCount]
                TempStr2 := XMLFileNameArray[VPXMLCount]
                TempStr3 := WorkingPathArray[VPXMLCount]
                TempStr4 := TablePathArray[VPXMLCount]
                TempStr5 := ExecutableArray[VPXMLCount]
                ;Msgbox, XMLPathArray[%VPXMLCount%]=%TempStr1%\%TempStr2%`n WorkingPathArray[%VPXMLCount%]=%TempStr3%`n TablePathArray[%VPXMLCount%]=%TempStr4%`n ExecutableArray[%VPXMLCount%]=%TempStr5%
                FileAppend, Unit3D System #%VPXMLCount%:`n%TempStr1%\%TempStr2%.xml`n%TempStr3%`n%TempStr4%`n%TempStr5%`n`n, %A_ScriptDir%\PBXrecorder.log
    
                VPXMLCount++
            }
            Else
            {
                TempStr2 := XMLFileNameArray[VPXMLCount]
                FileAppend, Skipping this system: %TempStr2%.xml`n`n, %A_ScriptDir%\PBXrecorder.log
                ;Msgbox, System Disabled (%VPSystemFlag% %VPKeepFlag%)
            }                
            VPActiveFlag = 0
            VPKeepFlag = 1    ;Assume System is enabled
            VPSystemFlag = 0
            
            ; Need to check this line if it is the start of a new system
            StringGetPos, Position, A_LoopReadLine, [System_
            If Position = 0 
            {
                VPActiveFlag = 1
            }
        }
    }
}

;End of file found, process last system found
TempStr := XMLFileNameArray[VPXMLCount]
StringGetPos, Position, TempStr, Unit3D   ;Name contains Unit3D
If (VPSystemFlag = 1 and VPKeepFlag = 1 and Position >= 0)       ;Keep System info
{
    TempStr1 := XMLPathArray[VPXMLCount]
    TempStr2 := XMLFileNameArray[VPXMLCount]
    TempStr3 := WorkingPathArray[VPXMLCount]
    TempStr4 := TablePathArray[VPXMLCount]
    TempStr5 := ExecutableArray[VPXMLCount]
    ;Msgbox, XMLPathArray[%VPXMLCount%]=%TempStr1%\%TempStr2%`n WorkingPathArray[%VPXMLCount%]=%TempStr3%`n TablePathArray[%VPXMLCount%]=%TempStr4%`n ExecutableArray[%VPXMLCount%]=%TempStr5%
    FileAppend, VP System #%VPXMLCount%:`n%TempStr1%\%TempStr2%.xml`n%TempStr3%`n%TempStr4%`n%TempStr5%`n`n, %A_ScriptDir%\PBXrecorder.log

    VPXMLCount++
}
Else
{
    TempStr2 := XMLFileNameArray[VPXMLCount]
    FileAppend, Skipping this system: %TempStr2%.xml`n`n, %A_ScriptDir%\PBXrecorder.log
    ;Msgbox, System Disabled (%VPSystemFlag% %VPKeepFlag%)
}    
VPActiveFlag = 0
VPKeepFlag = 0    
VPSystemFlag = 0
VPXMLCount--
FileAppend, Total number of Unit3D systems found: %VPXMLCount%`n, %A_ScriptDir%\PBXrecorder.log
If VPXMLCount = 0
{
    Msgbox No XML files were found.  Check PinballX path.  
}

;************************************************************************************************
;************************************************************************************************
;3. If table selected via drag drop or right click, Allow user to add table to XML file
;************************************************************************************************
;************************************************************************************************
;************************************************************************************************
If (DragAndDrop=1)
{
    Loop, %VPXMLCount%        ;Search if table exists in XML files already
    {
        XMLPath:=XMLPathArray[A_Index]
        XMLFileName:=XMLFileNameArray[A_Index]
        WorkingPath:=WorkingPathArray[A_Index]
        TablePath:=TablePathArray[A_Index]
        Executable:=ExecutableArray[A_Index]
        FileAppend, `nAdding table to %XMLFileName%.xml (%A_Hour%:%A_Min%:%A_Sec%)`n`n, %A_ScriptDir%\PBXrecorder.log
            
        ;Open XML file and walk through every table
        ;==========================
        Loop, Read, %XMLPath%\%XMLFilename%.xml
        {
            IfInString, A_LoopReadLine, game name=
            {
                TableEnabled := 1
                StringGetPos, start, A_LoopReadLine, =
                StringGetPos, end, A_LoopReadLine, >
                XTable := SubStr(A_LoopReadLine, start+3, end-start-3)
                
                ;-----------------------------------------------------
                ;Convert special chars in Table Name so they are used correctly
                ;-----------------------------------------------------        
                ;Special handling for & in the Table Name.  Change &amp; to & 
                IfInString, XTable, &amp;
                {
                    StringGetPos, start, XTable, &amp;
                    TestTable := SubStr(XTable, 1, start)
                    TestTable2 := SubStr(XTable, start+6)
                    XTable = %TestTable%&%TestTable2%
                }
                ;Special handling for italian ' in the Table Name.  Change ’ to ' 
                IfInString, XTable, ’
                {
                    StringGetPos, start, XTable, ’
                    TestTable := SubStr(XTable, 1, start)
                    TestTable2 := SubStr(XTable, start+4)
                    XTable = %TestTable%'%TestTable2%
                }    
                ;Special handling for ™ in the Table Name.  Change &#8482; to ™ 
                IfInString, XTable, &#8482;
                {
                    StringGetPos, start, XTable, &#8482;
                    TestTable := SubStr(XTable, 1, start)
                    TestTable2 := SubStr(XTable, start+8)
                    XTable = %TestTable%™%TestTable2%
                }
                
                If inStr(XTable, DragAndDropFileNameOnly)
                {
                    XMLTableFound := 1    ;Table found in XML files, go ahead and record media
                    break
                }
            } ;End of Else IfInString, A_LoopReadLine, /game                
        } ;End of Loop, Read, %XMLPath%\%XMLFilename%.xml
        
        If (XMLTableFound=1) ;Table was found in XML files, go ahead and record media
        {
            break ;exit loop to record media
        }    
    } ;End of Loop, %VPXMLCount%

    If (XMLTableFound<>1)    ; XML table not found, so display XML dialog to add table to XML file
    {
        IPDBArray := Object()
        IPDBNameArray := Object()
        IPDBMfrArray := Object()
        IPDBYearArray := Object()
        IPDBTypeArray := Object()
        IPDBStart:=0
        XMLAllStart:=0
        XMLMatchFound:=0
        
        Loop, %VPXMLCount%    ;Create XML system list for the dialog
        {    
            theXMLString2:=XMLPathArray[A_Index]
            SplitPath, theXMLString2,,,, theXMLString
                        
            If (XMLAllStart = 0) ;Creating XML system list
            {
                XMLAllVPSysString=%theXMLString%|
                XMLAllStart := 1
            }
            Else
            {
                XMLAllVPSysString = %XMLAllVPSysString%%theXMLString%|
            }
            
            ;Check if table path matches the current Unit3D system path, if so mark xml system as default by adding extra |
            If (XMLMatchFound=0)
            {
                xmltpath:=TablePathArray[A_Index]
                If (xmltpath = DragAndDropFileDir)
                {
                    XMLMatchFound:=1
                    XMLAllVPSysString = %XMLAllVPSysString%|
                    
                    ;also store executable for launch button
                    LaunchExecutable:=ExecutableArray[A_Index]

                    ;msgbox match%xmltpath% : %DragAndDropFileDir%
                }
            }
        }

        IfNotExist, %A_ScriptDir%\ipdb list.txt
            Msgbox, Please re-download PBX Recorder - ipdb list.txt file is missing
                
        Loop, Read, %A_ScriptDir%\ipdb list.txt         ; Create the ipdb pinball table list for the dialog
        {
            StringSplit, TempWordArray, A_LoopReadLine,|, 
            IPDBNameArray.Insert(TempWordArray1) ; Append this line to the array.
            IPDBMfrArray.Insert(TempWordArray4) ; Append this line to the array.
            IPDBYearArray.Insert(TempWordArray7) ; Append this line to the array.
            IPDBtypeArray.Insert(TempWordArray10) ; Append this line to the array.
            
            tempIPDB = %TempWordArray1% (%TempWordArray4% %TempWordArray7%)
            IPDBArray.Insert(tempIPDB) ; Append this line to the array.
            
            If (IPDBStart = 0)
            {
                allIPDBString = %tempIPDB%
                IPDBStart := 1
            }
            Else
                allIPDBString = %allIPDBString%|%tempIPDB%
        }

        ; File drag and drop - store filename
        Loop %0% 
        {
            GivenPath := %A_Index%
            Loop %GivenPath%, 1
                DragAndDropFile = %A_LoopFileLongPath%
            DragAndDrop := 1
            Break
        }
        SplitPath, DragAndDropFile,,,, DragAndDropFileNameOnly

        ;****** Display the XML Table entry dialog bpx ******
        Gui, Font, S11 CDefault, Verdana
        Gui, Add, Text, x42 y22 w90 h20 +right , XML List
        Gui, Add, Text, x42 y52 w90 h20 +right, Game
        Gui, Add, Text, x42 y80 w90 h20 +right, Description

        Gui, Add, DropDownList, x142 y20 w280 h25 r10 altSubmit vXMLVPSystem, %XMLAllVPSysString%  
        Gui, Add, Button, x442 y18 w100 h29 gXMLLaunch , Launch
        Gui, Add, Edit, x142 y50 w400 h25 vXMLTableFileName , %DragAndDropFileNameOnly%

        Gui, Add, Edit, x142 y80 w400 h20 vXMLDescText, 
        Gui, Add, DropDownList, x146 y102 w396 h80 r10 sort altSubmit vXMLDescSelected gGetXMLDescSelected, %allIPDBString%
;        IfExist, %DragAndDropFileDir%\%DragAndDropFileNameOnly%.directB2S
;            Gui, Add, Button, x22 y120 w70 h60 vXDB2S gXMLDB2S, DB2S Found
;        Else IfExist, %DragAndDropFileDir%\%DragAndDropFileNameOnly%.exe
;            Gui, Add, Button, x22 y120 w70 h60 vXDB2S gXMLDB2S, B2S.exe Found
;        Else
;            Gui, Add, Button, x22 y120 w70 h60 vXDB2S gXMLDB2S, DB2S Rename

        Gui, Add, Text, x142 y130 w220 h20 +center , Manufacturer
        Gui, Add, Text, x372 y130 w80 h20 +center , Year
        Gui, Add, Text, x462 y130 w80 h20 +center , Type

        Gui, Add, Edit, x142 y150 w220 h25 vXMLMfr, 
        Gui, Add, Edit, x372 y150 w80 h25 vXMLYear, 
        Gui, Add, Edit, x462 y150 w80 h25 vXMLType, 

        Gui, Add, CheckBox, x142 y180 w120 h30 Checked vXMLEnabled, Table Enabled
        Gui, Add, CheckBox, x302 y180 w100 h30 Checked vXMLHideDMD, Hide DMD
        Gui, Add, CheckBox, x412 y180 w150 h30 Checked vXMLHideBG, Hide Backglass

;        Gui, Add, DropDownList, x142 y215 w120 h25 r10 +center altSubmit vXMLAltExe, No Exe Tag||AlternateExe|     Exe
;        Gui, Add, Edit, x262 y216 w170 h25 vXMLAltExeName, 
        Gui, Add, DropDownList, x442 y215 w100 h25 r10 +center altSubmit vXMLRating, No Rating||1 out of 5|2 out of 5|3 out of 5|4 out of 5|5 out of 5
        
        Gui, Add, Button, x22 y280 w140 h30 gXMLCancel, Cancel
        Gui, Add, Button, x212 y280 w140 h30 gXMLSaveExit, Save && Exit
        Gui, Add, Button, x402 y280 w140 h30 gXMLSave, Save && Record

        GuiControl, Focus, XMLDescSelected
        Gui, Show, x127 y87 h327 w574,Add Table to XML - Start typing to perform Description auto-complete
        WinWaitClose, Add Table to XML - Start typing to perform Description auto-complete
        ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx        
    }
    Else
    {
        MsgBox, 4,, Table exists in XML file already.  Begin recording? (press Yes or No)
        IfMsgBox No
            ExitApp
    }
    
        
}    
    
;************************************************************************************************
;************************************************************************************************
;4. Time to walk through the tables and record videos
;************************************************************************************************
;************************************************************************************************
;************************************************************************************************
; Disabling "Application has stopped Working" dialog
;(https://www.raymond.cc/blog/disable-program-has-stopped-working-error-dialog-in-windows-server-2008/)
RegWrite, REG_DWORD, HKEY_CURRENT_USER, Software\Microsoft\Windows\Windows Error Reporting, DontShowUI, 0x00000001

Rectime := Rectime + 5        ;Pad record time by 5 seconds
Loopcount:=0
Recordcount:=0
Loop, %VPXMLCount%
{
    XMLPath:=XMLPathArray[A_Index]
    XMLFileName:=XMLFileNameArray[A_Index]
    WorkingPath:=WorkingPathArray[A_Index]
    TablePath:=TablePathArray[A_Index]
    Executable:=ExecutableArray[A_Index]
    Position := InStr(TablePath,"\TABLES", false) - 1
    StringLeft, XTablePath, TablePath, Position
    
    
    FileAppend, `nWorking on %XMLFileName%.xml (%A_Hour%:%A_Min%:%A_Sec%)`n`n, %A_ScriptDir%\PBXrecorder.log
    
    ;Save CabCalibration.xml to PBX recorder log
    FileAppend, CabCalibration.xml`n, %A_ScriptDir%\PBXrecorder.log
    IfNotExist, %XTablePath%\Config\CabCalibration.xml
        FileAppend, WARNING:  CabCalibration.xml is missing.  Will default Playfield size to 1920x1080`n, %A_ScriptDir%\PBXrecorder.log
        
    Loop, Read, %XTablePath%\Config\CabCalibration.xml, %A_ScriptDir%\PBXrecorder.log
    {
        FileAppend, %A_LoopReadLine%`n
    }
    
    ;FFMPEG file check
    IfNotExist, %FFMpegPath%\ffmpeg.exe
    {
        FFMpegPath = %FFMpegPath%\bin
        IfNotExist, %FFMpegPath%\ffmpeg.exe
            FileAppend, `nWARNING:  The file ffmpeg.exe could not be found`n, %A_ScriptDir%\PBXrecorder.log
            FileAppend, WARNING:  The file ffmpeg.exe could not be found`n, %A_ScriptDir%\PBXrecorder.log
            FileAppend, WARNING:  The file ffmpeg.exe could not be found`n`n, %A_ScriptDir%\PBXrecorder.log
    }
    
    
    ;Open CabCalibration.XML file - may need to be even number values or it may not record
    ;==========================
    IfExist, %XTablePath%\Config\CabCalibration.xml
    {
        Loop, Read, %XTablePath%\Config\CabCalibration.xml
        {
            IfInString, A_LoopReadLine, name="Cabinet"
                Screen := 1
            IfInString, A_LoopReadLine, name="TRANSLITE"
                Screen := 2
            IfInString, A_LoopReadLine, <topleft>
                Coord := 1
            IfInString, A_LoopReadLine, <bottomright>
                Coord := 2
            IfInString, A_LoopReadLine, <screenpos>
            {
                If (Screen=1 AND Coord=1)
                {
                    StringGetPos, start, A_LoopReadLine, >
                    StringGetPos, end, A_LoopReadLine, `,
                    PFTLx := SubStr(A_LoopReadLine, start+2, end-start-1)
                    StringGetPos, start, A_LoopReadLine, `,
                    StringGetPos, end, A_LoopReadLine, <, L2
                    PFTLy := SubStr(A_LoopReadLine, start+2, end-start-1)
                }
                If (Screen=1 AND Coord=2)
                {
                    StringGetPos, start, A_LoopReadLine, >
                    StringGetPos, end, A_LoopReadLine, `,
                    PFBRx := SubStr(A_LoopReadLine, start+2, end-start-1)
                    StringGetPos, start, A_LoopReadLine, `,
                    StringGetPos, end, A_LoopReadLine, <, L2
                    PFBRy := SubStr(A_LoopReadLine, start+2, end-start-1)
                }
                If (Screen=2 AND Coord=1)
                {
                    StringGetPos, start, A_LoopReadLine, >
                    StringGetPos, end, A_LoopReadLine, `,
                    BGTLx := SubStr(A_LoopReadLine, start+2, end-start-1)
                    StringGetPos, start, A_LoopReadLine, `,
                    StringGetPos, end, A_LoopReadLine, <, L2
                    BGTLy := SubStr(A_LoopReadLine, start+2, end-start-1)
                }
                If (Screen=2 AND Coord=2)
                {
                    StringGetPos, start, A_LoopReadLine, >
                    StringGetPos, end, A_LoopReadLine, `,
                    BGBRx := SubStr(A_LoopReadLine, start+2, end-start-1)
                    StringGetPos, start, A_LoopReadLine, `,
                    StringGetPos, end, A_LoopReadLine, <, L2
                    BGBRy := SubStr(A_LoopReadLine, start+2, end-start-1)
                }
            } ;End of Else IfInString, A_LoopReadLine, /CabCalibration                
        } ;End of Loop, Read, %XTablePath%\Config\CabCalibration.xml
        PF_Width:=PFBRx-PFTLx
        PF_Height:=PFTLy-PFBRy
        BG_Width:=BGBRx-BGTLx
        BG_Height:=BGBRy-BGTLy
    }
    IfNotExist, %XTablePath%\Config\CabCalibration.xml
    {
        PF_Width:=1920
        PF_Height:=1080        
    }
    
    ;Create Media directories if needed
    ;==========================

    MediaSubDir:=XMLFileName
    MediaSubDirOut:=XMLFileName

    If (BetaMode=1)
    {
        MediaSubDirOut = %MediaSubDirOut% Beta
        Msgbox, 0,, Beta Mode. All new media stored in %MediaOutPath%\%MediaSubDirOut%, 20
    }
    
    FileCreateDir, %MediaOutPath%\%MediaSubDirOut%\Backglass Images
    FileCreateDir, %MediaOutPath%\%MediaSubDirOut%\Backglass Videos
    FileCreateDir, %MediaOutPath%\%MediaSubDirOut%\DMD Images
    FileCreateDir, %MediaOutPath%\%MediaSubDirOut%\DMD Videos
    FileCreateDir, %MediaOutPath%\%MediaSubDirOut%\Table Images
    FileCreateDir, %MediaOutPath%\%MediaSubDirOut%\Table Videos
    FileCreateDir, %MediaOutPath%\%MediaSubDirOut%\Wheel Images

    ;Start of new XML file
    if (DragAndDrop<>1)
    {
        Progress, x200 y200 zh0 M h200 w600 FS10, `n%WorkingPath%`n %TablePath%`n %Executable%`n`n`n(Press ESC anytime to abort script)  , `nWorking on %XMLFileName%.xml, System XML #%A_Index% of %VPXMLCount% ... , Arial
        Sleep 2000
    }
    
    ; Clean up
    ;==========================
    FileDelete, %A_ScriptDir%\playfield.mkv
    FileDelete, %A_ScriptDir%\bg.mkv
    FileDelete, %A_ScriptDir%\dmd.mkv
    Run, taskkill /IM ffmpeg.exe /F,,UseErrorLevel
    Run, taskkill /IM ffmpeg.exe /F,,UseErrorLevel
    Run, taskkill /IM ffmpeg.exe /F,,UseErrorLevel
    Progress, Off

    ;*************** Screensize check - How good is your screenres.txt ****************
    ;==========================
    SysGet, VirtualScreenWidth, 78
    TotalScreenWidth := DMD_X + DMD_width
    DMD_oldwidth := DMD_Width
    PFBGWidth := PF_width+BG_width
    
    ;Convert all width and height to even values for best recording success
    PF_width    := (floor(PF_width/2))*2          
    PF_height    := (floor(PF_height/2))*2          
    BG_width    := (floor(BG_width/2))*2           
    BG_height    := (floor(BG_height/2))*2          
    DMD_width    := (floor(DMD_width/2))*2          
    DMD_height    := (floor(DMD_height/2))*2
    
    FileAppend, `nValues used for media capture (height/width forced to even values)`n, %A_ScriptDir%\PBXrecorder.log
    FileAppend, VirtualScreenWidth = %VirtualScreenWidth%`n, %A_ScriptDir%\PBXrecorder.log
;    FileAppend, TotalScreenWidth   = %TotalScreenWidth%  `n, %A_ScriptDir%\PBXrecorder.log
    FileAppend, PF_width           = %PF_width%          `n, %A_ScriptDir%\PBXrecorder.log
    FileAppend, PF_height          = %PF_height%         `n, %A_ScriptDir%\PBXrecorder.log
    FileAppend, BG_width           = %BG_width%          `n, %A_ScriptDir%\PBXrecorder.log
    FileAppend, BG_height          = %BG_height%         `n, %A_ScriptDir%\PBXrecorder.log
;    FileAppend, DMD_width          = %DMD_width%      `n, %A_ScriptDir%\PBXrecorder.log
;    FileAppend, DMD_height         = %DMD_height%        `n, %A_ScriptDir%\PBXrecorder.log
;    FileAppend, DMD_X_offset       = %XDMD_X%            `n, %A_ScriptDir%\PBXrecorder.log
;    FileAppend, DMD_Y_offset       = %DMD_Y%             `n, %A_ScriptDir%\PBXrecorder.log
    FileAppend, -----------------------------------------`n, %A_ScriptDir%\PBXrecorder.log
;    FileAppend, DMD_tot_offset     = %DMD_X%             `n, %A_ScriptDir%\PBXrecorder.log
;    FileAppend, DMD_orig_width     = %DMD_oldwidth%         `n, %A_ScriptDir%\PBXrecorder.log

    ;PF width check
    If ((PF_width > VirtualScreenWidth) and (PFVideoOnly = 1 or PFImageOnly = 1))
    {
        Msgbox, 0, CabCalibration.xml: Playfield size check, Warning: CabCalibration.xml Playfield(%PF_width%) width exceeds the actual size of the windows desktop(%VirtualScreenWidth%).  Review Cab Calibration settings`n`nPlayfield recording may not work, 60 
        FileAppend, WARNING: CabCalibration.xml Playfield width exceeds the actual size of windows desktop.  Review Cab Calibration settings`n, %A_ScriptDir%\PBXrecorder.log
    }
    
    ;PF and BG width check
    If ((PFBGWidth > VirtualScreenWidth) and (BGVideoOnly = 1 or BGImageOnly = 1))
    {
        Msgbox, 0, CabCalibration.xml: Backglass size check, Warning: CabCalibration.xml Playfield(%PF_width%) plus Backglass(%BG_width%) width exceeds the actual size of windows desktop (%VirtualScreenWidth%).  Review Cab Calibration settings`n`nBackglass recording may not work, 60 
        FileAppend, WARNING: CabCalibration.xml Playfield(%PF_width%) plus Backglass(%BG_width%) width exceeds the actual size of windows desktop (%VirtualScreenWidth%).  Review Cab Calibration settings`n, %A_ScriptDir%\PBXrecorder.log
    }

    ;PF and BG and DMD width check
;    If ((TotalScreenWidth > VirtualScreenWidth) and (DMDVideoOnly = 1 or DMDImageOnly = 1))
;    {
;        DMD_Width := (floor((DMD_oldwidth - (TotalScreenWidth - VirtualScreenWidth))/2))*2
;        Msgbox, 0, ScreenRes.txt: DMD size check, Warning: Screenres.txt DMD width/offset is not correct`nReducing DMD width from %DMD_oldwidth% to %DMD_width%.  Review screenres.txt settings`n`nDMD recording may not work, 60
;        FileAppend, WARNING: DMD window settings incorrect - auto-resizing (%DMD_oldwidth% to %DMD_width%).  Review screenres.txt DMD settings`n, %A_ScriptDir%\PBXrecorder.log
;    }
    
    ;Open XML file and walk through every table
    ;==========================
    ExecutableBackup:=Executable
    PrintFFMPEGExamples:=1
    Loop, Read, %XMLPath%\%XMLFilename%.xml
    {
        IfInString, A_LoopReadLine, game name=
        {
            TableEnabled := 1
            Executable:=ExecutableBackup
            StringGetPos, start, A_LoopReadLine, =
            StringGetPos, end, A_LoopReadLine, >
            XTable := SubStr(A_LoopReadLine, start+3, end-start-3)
            ;Msgbox, what%XTable%`n%A_LoopReadLine%`n%start%`n%end%
            
            ;-----------------------------------------------------
            ;Convert special chars in Table Name so they are used correctly
            ;-----------------------------------------------------
            ;Special handling for ® in the Table Name.  Change ® to ®
            IfInString, XTable, ®
            {
                StringGetPos, start, XTable, ®
                TestTable := SubStr(XTable, 1, start)
                TestTable2 := SubStr(XTable, start+2)
                XTable = %TestTable%%TestTable2%
                ;msgbox %XTable%
            }            
            ;Special handling for & in the Table Name.  Change &amp; to & 
            IfInString, XTable, &amp;
            {
                StringGetPos, start, XTable, &amp;
                TestTable := SubStr(XTable, 1, start)
                TestTable2 := SubStr(XTable, start+6)
                XTable = %TestTable%&%TestTable2%
            }
            ;Special handling for Italian ® in the Table Name.  Change &#174; to ® 
            IfInString, XTable, &#174;
            {
                StringGetPos, start, XTable, &amp;
                TestTable := SubStr(XTable, 1, start)
                TestTable2 := SubStr(XTable, start+7)
                XTable = %TestTable%�%TestTable2%
            }
            ;Special handling for italian ' in the Table Name.  Change ’ to ' 
            IfInString, XTable, ’
            {
                StringGetPos, start, XTable, ’
                TestTable := SubStr(XTable, 1, start)
                TestTable2 := SubStr(XTable, start+4)
                XTable = %TestTable%'%TestTable2%
            }    
            ;Special handling for ™ in the Table Name.  Change &#8482; to ™ 
            IfInString, XTable, &#8482;
            {
                StringGetPos, start, XTable, &#8482;
                TestTable := SubStr(XTable, 1, start)
                TestTable2 := SubStr(XTable, start+8)
                XTable = %TestTable%™%TestTable2%
            }
            
            ;Identify if upt
            IfExist, %TablePath%\%Xtable%\%Xtable%.upt
            {
                Table = %XTable%.upt
                TableExists := 1
            }
;            Else IfExist, %TablePath%\%Xtable%.vpx   
;            {
;                Table = %XTable%.vpx 
;                TableExists := 1
;            }
            Else      ;must be vpx file
            {
;                Table = %XTable%.vpx 
                TableExists := 0
            }
        }
        Else IfInString, A_LoopReadLine, /description
        {                                         
            StringGetPos, start, A_LoopReadLine, <description>
            StringGetPos, end, A_LoopReadLine, </description>
            Description := SubStr(A_LoopReadLine, start+14, end-start-13)
            
            ;----------------------------------------------------
            ;Remove invalid chars from Description name like  : / \ * ? " < > |
            ;----------------------------------------------------
            IfInString, Description, :
            {
                StringGetPos, start, Description, :
                TestTable := SubStr(Description, 1, start)
                TestTable2 := SubStr(Description, start+2)
                Description = %TestTable%%TestTable2%
            }
            IfInString, Description, /
            {
                StringGetPos, start, Description, /
                TestTable := SubStr(Description, 1, start)
                TestTable2 := SubStr(Description, start+2)
                Description = %TestTable%%TestTable2%
            }
            IfInString, Description, \
            {
                StringGetPos, start, Description, \
                TestTable := SubStr(Description, 1, start)
                TestTable2 := SubStr(Description, start+2)
                Description = %TestTable%%TestTable2%
            }
            IfInString, Description, *
            {
                StringGetPos, start, Description, *
                TestTable := SubStr(Description, 1, start)
                TestTable2 := SubStr(Description, start+2)
                Description = %TestTable%%TestTable2%
            }            
            IfInString, Description, &#42;
            {
                StringGetPos, start, Description, &#42;
                TestTable := SubStr(Description, 1, start)
                TestTable2 := SubStr(Description, start+6)
                Description = %TestTable%%TestTable2%
            }            
            IfInString, Description, ?
            {
                StringGetPos, start, Description, ?
                TestTable := SubStr(Description, 1, start)
                TestTable2 := SubStr(Description, start+2)
                Description = %TestTable%%TestTable2%
            }            
            IfInString, Description, "
            {
                StringGetPos, start, Description, "
                TestTable := SubStr(Description, 1, start)
                TestTable2 := SubStr(Description, start+2)
                Description = %TestTable%%TestTable2%
            }            
            IfInString, Description, <
            {
                StringGetPos, start, Description, <
                TestTable := SubStr(Description, 1, start)
                TestTable2 := SubStr(Description, start+2)
                Description = %TestTable%%TestTable2%
            }            
            IfInString, Description, >
            {
                StringGetPos, start, Description, >
                TestTable := SubStr(Description, 1, start)
                TestTable2 := SubStr(Description, start+2)
                Description = %TestTable%%TestTable2%
            }            
            IfInString, Description, |
            {
                StringGetPos, start, Description, |
                TestTable := SubStr(Description, 1, start)
                TestTable2 := SubStr(Description, start+2)
                Description = %TestTable%%TestTable2%
            }
            
            ;-----------------------------------------------------
            ;Convert special chars in Description so they are used correctly
            ;-----------------------------------------------------
            ;Special handling for ® in the Description.  Change ® to ®
            IfInString, Description, ®
            {
                StringGetPos, start, Description, ®
                TestTable := SubStr(Description, 1, start)
                TestTable2 := SubStr(Description, start+2)
                Description = %TestTable%%TestTable2%
            }            
            ;Special handling for & in the Description.  Change &amp; to & 
            IfInString, Description, &amp;
            {
                StringGetPos, start, Description, &amp;
                TestTable := SubStr(Description, 1, start)
                TestTable2 := SubStr(Description, start+6)
                Description = %TestTable%&%TestTable2%
            }
            ;Special handling for &#174; in the Description.  Change &#174; to ®
            IfInString, Description, &#174;
            {
                StringGetPos, start, Description, &#174;
                TestTable := SubStr(Description, 1, start)
                TestTable2 := SubStr(Description, start+7)
                Description = %TestTable%�%TestTable2%
            }
            ;Special handling for italian ' in the Description.  Change ’ to ' 
            IfInString, Description, ’
            {
                StringGetPos, start, Description, ’
                TestTable := SubStr(Description, 1, start)
                TestTable2 := SubStr(Description, start+4)
                Description = %TestTable%'%TestTable2%
            }
            ;Special handling for ™ in the Description.  Change &#8482; to ™ 
            IfInString, Description, &#8482;
            {
                StringGetPos, start, Description, &#8482;
                TestTable := SubStr(Description, 1, start)
                TestTable2 := SubStr(Description, start+8)
                Description = %TestTable%™%TestTable2%
            }

            FileAppend, `n%Description%`n, %A_ScriptDir%\PBXrecorder.log                                
            If TableExists = 0
                FileAppend,  Table file not found: %TablePath%\%XTable%\%XTable%.upt `n, %A_ScriptDir%\PBXrecorder.log
        }
        Else IfInString, A_LoopReadLine, /alternateExe
        {            
            StringGetPos, start, A_LoopReadLine, <alternateExe>
            StringGetPos, end, A_LoopReadLine, </alternateExe>
            Executable := SubStr(A_LoopReadLine, start+15, end-start-14)
            ;Msgbox, %Executable%
            FileAppend, AlternateExe found in xml: %Executable%`n, %A_ScriptDir%\PBXrecorder.log
        }
        Else IfInString, A_LoopReadLine, /exe
        {            
            StringGetPos, start, A_LoopReadLine, <exe>
            StringGetPos, end, A_LoopReadLine, </exe>
            Executable := SubStr(A_LoopReadLine, start+6, end-start-5)
            ;Msgbox, %Executable%
            FileAppend, Exe found in xml: %Executable%`n, %A_ScriptDir%\PBXrecorder.log
        }
        Else IfInString, A_LoopReadLine, enabled>False
        {            
            ;Msgbox, %A_LoopReadLine%
            FileAppend, Table disabled in xml: %TablePath%\%Table%`n, %A_ScriptDir%\PBXrecorder.log
            TableEnabled := 0
        }
        Else IfInString, A_LoopReadLine, enabled>True
        {            
            ;Msgbox, %A_LoopReadLine% 
            TableEnabled := 1
        }
        Else IfInString, A_LoopReadLine, /game
        {
            If TableEnabled = 1
            {
                If TableExists = 1
                {
                    Loopcount:=Loopcount + 1
                    ;Msgbox, recording
                    ;Check if Media Files exist and skip if skip is enabled
                    ;===================================================
                    MediaFoundFlag:=0
                    If (MediaFileNaming=0)
                        SearchString:=Description   
                    Else
                        SearchString:=Xtable 

                    If (PrintFFMPEGExamples=1)
                    {
                        PrintFFMPEGExamples:=0
                        FileAppend, `nEXAMPLE of all ffmpeg.exe commands used`n, %A_ScriptDir%\PBXrecorder.log
                        FileAppend, "%FFMpegPath%\ffmpeg" -y -t 1 -f gdigrab -framerate 1 -offset_x 0 -offset_y 0 -video_size %PF_width%x%PF_Height% -i desktop -vf "rotate=PI:bilinear=0" "%MediaOutPath%\%MediaSubDirOut%\Table Images\%SearchString%.png"`n, %A_ScriptDir%\PBXrecorder.log
                        FileAppend, "%FFMpegPath%\ffmpeg" -y -t 1 -f gdigrab -framerate 1 -offset_x %PF_width% -offset_y 0 -video_size %BG_width%x%BG_Height% -i desktop "%MediaOutPath%\%MediaSubDirOut%\Backglass Images\%SearchString%.png"`n, %A_ScriptDir%\PBXrecorder.log
;                        FileAppend, "%FFMpegPath%\ffmpeg" -y -t 1 -f gdigrab -framerate 1 -offset_x %DMD_X% -offset_y %DMD_Y% -video_size %DMD_width%x%DMD_Height% -i desktop "%MediaOutPath%\%MediaSubDirOut%\DMD Images\%SearchString%.png"`n, %A_ScriptDir%\PBXrecorder.log
                        FileAppend, "%FFMpegPath%\ffmpeg" -y -t %RecTime% -rtbufsize 1500M -f gdigrab -framerate 30 -offset_x 0 -offset_y 0 -video_size %PF_width%x%PF_Height% -i desktop -vcodec libx264 -preset ultrafast -qp 0 -threads 8 "%A_ScriptDir%\playfield.mkv"`n, %A_ScriptDir%\PBXrecorder.log
                        FileAppend, "%FFMpegPath%\ffmpeg" -y -t %RecTime% -rtbufsize 1500M -f gdigrab -framerate 30 -offset_x %PF_width% -offset_y 0 -video_size %BG_width%x%BG_Height% -i desktop -vcodec libx264 -preset ultrafast  -qp 0 -threads 8 "%A_ScriptDir%\bg.mkv"`n, %A_ScriptDir%\PBXrecorder.log
;                        FileAppend, "%FFMpegPath%\ffmpeg" -y -t %RecTime% -rtbufsize 1500M -f gdigrab -framerate 30 -offset_x %DMD_X% -offset_y %DMD_Y% -video_size %DMD_width%x%DMD_Height% -i desktop -vcodec libx264 -preset ultrafast  -qp 0 -threads 8 "%A_ScriptDir%\dmd.mkv"`n, %A_ScriptDir%\PBXrecorder.log
                        FileAppend, "%FFMpegPath%\ffmpeg" -y -i "%A_ScriptDir%\playfield.mkv" -ss 5 -to 1000 -vf [in]rotate=PI:bilinear=0[middle];[middle]scale=1920:-1[out] -map 0:0 -c:v libx264 -crf 26 "%MediaOutPath%\%MediaSubDirOut%\Table Videos\%SearchString%.%RecExt%"`n, %A_ScriptDir%\PBXrecorder.log
                        FileAppend, "%FFMpegPath%\ffmpeg" -y -i "%A_ScriptDir%\bg.mkv" -ss 5 -to 1000 -c:v libx264 -crf 26 "%MediaOutPath%\%MediaSubDirOut%\Backglass Videos\%SearchString%.%RecExt%"`n, %A_ScriptDir%\PBXrecorder.log
;                        FileAppend, "%FFMpegPath%\ffmpeg" -y -i "%A_ScriptDir%\dmd.mkv" -ss 5 -to 1000 -c:v libx264 -crf 26 "%MediaOutPath%\%MediaSubDirOut%\DMD Videos\%SearchString%.%RecExt%"`n`n, %A_ScriptDir%\PBXrecorder.log                        
                    }
                                        
                    If ((OnlyRecordMissingVideos = 1) or (OnlyRecordMissingVideos = 2) or (OnlyRecordMissingVideos = 3))
                    {
                        ;Perform media check for missing files
                        MediaFoundFlag:=1
                        MediaAtLeastOneFound:=0
                        
                        If (PFVideoOnly=1)
                        {
                            IfNotExist, %MediaOutPath%\%MediaSubDir%\Table Videos\%SearchString%.*
                            {
                                MediaFoundFlag:=0
                                FileAppend, MISSING: Table Video`n, %A_ScriptDir%\PBXrecorder.log                        
                            }
                            Else
                            {
                                MediaAtLeastOneFound:=1
                            }
                        }
                        If (BGVideoOnly=1)
                        {
                            IfNotExist, %MediaOutPath%\%MediaSubDir%\BackGlass Videos\%SearchString%.*
                            {
                                MediaFoundFlag:=0
                                FileAppend, MISSING: BackGlass Video`n, %A_ScriptDir%\PBXrecorder.log                        
                            }
                            Else
                            {
                                MediaAtLeastOneFound:=1
                            }
                        }
;                        If (DMDVideoOnly=1)
;                        {
;                            IfNotExist, %MediaOutPath%\%MediaSubDir%\DMD Videos\%SearchString%.*
;                            {
;                                FileAppend, MISSING: DMD Video`n, %A_ScriptDir%\PBXrecorder.log                        
;                                If DMD_width > 0 
;                                {
;                                    MediaFoundFlag:=0
;                                }
;                            }
;                            Else
;                            {
;                                MediaAtLeastOneFound:=1
;                            }
;                        }
                        If (PFImageOnly=1)
                        {
                            IfNotExist, %MediaOutPath%\%MediaSubDir%\Table Images\%SearchString%.*
                            {
                                MediaFoundFlag:=0
                                FileAppend, MISSING: Table Image`n, %A_ScriptDir%\PBXrecorder.log                        
                            }
                            Else
                            {
                                MediaAtLeastOneFound:=1
                            }
                        }
                        If (BGImageOnly=1)
                        {
                            IfNotExist, %MediaOutPath%\%MediaSubDir%\BackGlass Images\%SearchString%.*
                            {
                                MediaFoundFlag:=0
                                FileAppend, MISSING: BackGlass Image`n, %A_ScriptDir%\PBXrecorder.log                        
                            }
                            Else
                            {
                                MediaAtLeastOneFound:=1
                            }
                        }
;                        If (DMDImageOnly=1)
;                        {
;                            IfNotExist, %MediaOutPath%\%MediaSubDir%\DMD Images\%SearchString%.*
;                            {
;                                FileAppend, MISSING: DMD Image`n, %A_ScriptDir%\PBXrecorder.log                        
;                                If DMD_width > 0                 
;                                {
;                                    MediaFoundFlag:=0
;                                }
;                            }
;                            Else
;                            {
;                                MediaAtLeastOneFound:=1
;                            }
;                        }
                        
                    } ;end of If ((OnlyRecordMissingVideos = 1) or (OnlyRecordMissingVideos = 2) or (OnlyRecordMissingVideos = 3))
                    Else
                    {
                        FileAppend, PBXrecorder set to record complete media set`n, %A_ScriptDir%\PBXrecorder.log                        
                    }
                
                    ;===========================================================================
                    ; Extra Unit3D file checks - DirectB2S and Wheel images (FTP download supported)
                    ;===========================================================================
                    ;Print out message to log file if DirectB2s file is missing
;                    IfNotExist, %TablePath%\%XTable%.directb2s
;                    {
;                        IfNotExist, %TablePath%\%XTable%.exe
;                        {
;                            FileAppend, FYI: No DirectB2S/B2S.exe File.  %TablePath%\%XTable%.directb2s`n, %A_ScriptDir%\PBXrecorder.log                        
;                        }
;                        Else
;                        {
;                            FileAppend, FYI: No DirectB2S but B2S was found.  %TablePath%\%XTable%.directb2s`n, %A_ScriptDir%\PBXrecorder.log                        
;                        }
;                    }
                    ;Print out message to log file if Wheel image is missing - Perform FTP
                    IfNotExist, %MediaOutPath%\%MediaSubDir%\Wheel Images\%Description%.png
                    {
                        ;=============================================================
                        ; Download Wheel from PinballX FTP if user has filled in FTP login info
                        ;=============================================================
                        IfExist, %A_ScriptDir%\FTPLoginInfo.txt                            
                        {                        
                            FileReadLine, tempStr, %A_ScriptDir%\FTPLoginInfo.txt, 2
                            IfNotInString, tempStr, USERNAME
                            {
                                Progress, x200 y200 zh0 M h200 w600 FS10, `n`Wheel Image missing... Check PinballX FTP site`n`n(Press ESC anytime to abort script)  ,`n`n%Description%, Attempting PinballX FTP download , Arial
                                ;Strip out the {...} from the description if found
                                StringSplit, SearchStringArray, Description, {} ;drop the 2nd part
                                SearchStringArray1:=Trim(SearchStringArray1)
                                SearchStringArray3:=Trim(SearchStringArray3)
                                If (StrLen(SearchStringArray3)>0)
                                    SearchStringArray1=%SearchStringArray1% %SearchStringArray3%

                                FileToGet = %SearchStringArray1%.png
                                FileDelete %A_ScriptDir%\FTPtemp.bat
                                FileAppend, open online.gameex.com`n, %A_ScriptDir%\FTPtemp.bat
                                FileReadLine, tempStr, %A_ScriptDir%\FTPLoginInfo.txt, 2
                                FileAppend, %tempStr%`n, %A_ScriptDir%\FTPtemp.bat
                                FileReadLine, tempStr, %A_ScriptDir%\FTPLoginInfo.txt, 3
                                FileAppend, %tempStr%`n, %A_ScriptDir%\FTPtemp.bat
                                FileAppend,
                                (
                                binary
                                hash
                                cd "/-PinballX-/Media/Visual Pinball/Wheel Images"
                                get "%FileToGet%"
                                bye
                                ), %A_ScriptDir%\FTPtemp.bat
                                RunWait %comspec% /c ftp.exe -s:"%A_ScriptDir%\FTPtemp.bat"
                                FileDelete %A_ScriptDir%\FTPtemp.bat
                                FileMove %A_ScriptDir%\%FileToGet%, %MediaOutPath%\%MediaSubDirOut%\Wheel Images\%Description%.png, 1
                                
                                IfNotExist, %MediaOutPath%\%MediaSubDirOut%\Wheel Images\%Description%.png
                                {
                                    FileAppend, FYI (Wheel not found in FTP): No Wheel Image - %Description%.png`n, %A_ScriptDir%\PBXrecorder.log
                                }
                                Else
                                {
                                    FileAppend, Downloaded via FTP: Wheel Image - %Description%.png`n, %A_ScriptDir%\PBXrecorder.log
                                }
                            }
                            Else
                            {
                                FileAppend, FYI (FTP not enabled): No Wheel Image - %Description%.png`n, %A_ScriptDir%\PBXrecorder.log
                            }
                        }
                        Else
                        {
                            FileAppend, FYI (FTP not enabled): No Wheel Image - %SearchString%.png`n, %A_ScriptDir%\PBXrecorder.log
                        }                            
                        
                    }
                    ; END OF Extra VP file checks - DirectB2S and Wheel images (FTP download supported)
                    ;===========================================================================
                    
                    
                    ; Media checked - Decide whether to record or skip table
                    ;===========================================================================
                    IfEqual MediaFoundFlag, 1    ;If all media exists then skip table
                    {
                        If PauseTime>=10
                            PauseTime:=PauseTime-5
                        TempLongPath = %TablePath%\%XTable%\%Table%
                        if (DragAndDrop<>1)
                        {
                            Progress, x200 y200 zh0 M h200 w600 FS10, `nMedia already exists.  Skipping...`n`n(Press ESC anytime to abort script)  ,`n`n%Description%, Capturing Videos... #%Loopcount% , Arial                                
                            Sleep %PauseTime%
                            FileAppend, Skipping table... %TablePath%\%XTable%\%Table%`n, %A_ScriptDir%\PBXrecorder.log
                        }
                        Else If inStr(TempLongPath, DragAndDropFile)  ;Check if drag and drop table already has media
                        {
                            DragAndDropTableFound := -1
                            Progress, x200 y200 zh0 M h200 w600 FS10, `nDrag and Drop table media already exists.  Skipping...`n`n(Press ESC anytime to abort script)  ,`n`n%Description%, Drag and Drop... #%Loopcount% , Arial                                
                            Sleep 3000    
                            FileAppend, Drag and drop table already has Media.  Skipping... %TablePath%\%XTable%\%Table%`n, %A_ScriptDir%\PBXrecorder.log                            
                        }
                        continue ;Skipping table!!!
                    }
                    Else If (DragAndDrop=1) ;and MediaFoundFlag = 0     ;Handle Drag and drop table recording
                    {
                        ;Check if current table matches the drag and drop table
                        TempLongPath = %TablePath%\%XTable%\%Table%
                        If inStr(TempLongPath, DragAndDropFile)    ;Match - go ahead and record
                        {
                            DragAndDropTableFound := 1
                            Progress, x200 y200 M h240 w600 FS10 CB0000FF, `n`nLoading "%Table%"`n`nDrag and Drop recording will start in %LoadTime% seconds...`n(Press PAUSE if you need to edit/setup table)`n(Press ESC anytime to abort script)  ,`n`n%Description%`n, Capturing Videos... #%Loopcount% , Arial
                            FileAppend, Launching table: %XTablePath%\Unit3D Pinball.exe %TablePath%\%XTable%\%Table%`n, %A_ScriptDir%\PBXrecorder.log
                        }
                        Else    ;No match - skip this table
                        {
                            FileAppend, Skipping table due to Drag and Drop mode... %TablePath%\%XTable%\%Table%`n, %A_ScriptDir%\PBXrecorder.log
                            continue ;Skipping table
                        }
                    }
                    Else If ((OnlyRecordMissingVideos=3) and (MediaAtLeastOneFound=1)) ;check if Mode = Record ONLY new tables(Ignore partial media set) AND any if media was found
                    {
                            FileAppend, Skipping table - Recording only new tables. Partial media set exists... %TablePath%\%XTable%\%Table%`n, %A_ScriptDir%\PBXrecorder.log
                            continue ;Skipping table
                    }
                    Else ;MediaFoundFlag = 0    ;Launch table to record
                    {
                        Progress, x200 y200 M h240 w600 FS10 CB0000FF, `n`nLoading "%Table%"`n`nRecording will start in %LoadTime% seconds...`n(Press PAUSE if you need to edit/setup table)`n(Press ESC anytime to abort script)  ,`n`n%Description%`n, Capturing Videos... #%Loopcount% , Arial
                        FileAppend, Launching table: %XTablePath%\Unit3D Pinball.exe %TablePath%\%XTable%\%Table%`n, %A_ScriptDir%\PBXrecorder.log
                    }        

                    ;=================================================
                    ;Start Table
                    ;=================================================
                    Recordcount:=Recordcount + 1
                    Run, "%XTablePath%\Unit3D Pinball.exe" "%TablePath%\%XTable%\%Table%"
                    Process, wait, Unit3D Pinball.exe

                    LoadingTable:=1
                    If (TestMode=1) ;debug speed up of recording
                    {
                        Loop, 50 ;DEBUG
                        {            
                            j := A_Index/1.0
                            Progress,%j%
                            Sleep 100    
                            If (PBXRPauseMode=1)
                            {
                                Goto, PBRXPaused
                            }
                        }        ; Allow time for table score display to get well beyond loading and boot images
                    }
                    Else
                    {
                        Loop, % LoadTimeX ; 
                        {            
                            j := A_Index/LoadTimeX*100
                            Progress,%j%
                            Sleep 100    
                            If (PBXRPauseMode=1)
                            {
                                Goto, PBRXPaused
                            }
                        }        ; Allow time for table score display to get well beyond loading and boot images
                    }                    
                    Progress, Off
                    LoadingTable:=0
                    
PBRXPaused:
                    If (LoadingTable=1)    ;User has paused PBX Recorder
                    {
                        Progress, Off
                        WindowX:=PF_width/2 -200
                        Progress, x%WindowX% y0 h25 w400 FS10 CB0000FF, Paused.  Press Pause key to start recording  , Paused.  Press Pause key to start recording, Press Pause to Resume , Arial
                        WinWaitClose, Press Pause to Resume
                    }
                    LoadingTable:=0
                    PBXRPauseMode:=0
                        
                    ; Record Video as .mkv - Fast Capture and take screenshots
                    ;============================================================
                    FileAppend, Video and screenshot capture (%A_Hour%:%A_Min%:%A_Sec%)`n, %A_ScriptDir%\PBXrecorder.log
                    If (OnlyRecordMissingVideos = 2)    ;Mode = RECORD MISSING MEDIA ONLY
                    ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                    {
                        If (PFImageOnly=1)
                        {
                            IfNotExist, %MediaOutPath%\%MediaSubDir%\Table Images\%SearchString%.*
                            {
                                Run, "%FFMpegPath%\ffmpeg" -y -t 1 -f gdigrab -framerate 1 -offset_x 0 -offset_y 0 -video_size %PF_width%x%PF_Height% -i desktop -vf "rotate=PI:bilinear=0" "%MediaOutPath%\%MediaSubDirOut%\Table Images\%SearchString%.png",,Hide
                                FileAppend, Screenshot "%MediaOutPath%\%MediaSubDirOut%\Table Images\%SearchString%.png"`n, %A_ScriptDir%\PBXrecorder.log
                            }
                        }
                        If (BGImageOnly=1)
                        {
                            IfNotExist, %MediaOutPath%\%MediaSubDir%\BackGlass Images\%SearchString%.*
                            {
                                Run, "%FFMpegPath%\ffmpeg" -y -t 1 -f gdigrab -framerate 1 -offset_x %PF_width% -offset_y 0 -video_size %BG_width%x%BG_Height% -i desktop "%MediaOutPath%\%MediaSubDirOut%\Backglass Images\%SearchString%.png",,Hide
                                FileAppend, Screenshot "%MediaOutPath%\%MediaSubDirOut%\Backglass Images\%SearchString%.png"`n, %A_ScriptDir%\PBXrecorder.log
                            }
                        }
;                        If (DMDImageOnly=1)
;                        {
;                            If DMD_width > 0
;                            {
;                                IfNotExist, %MediaOutPath%\%MediaSubDir%\DMD Images\%SearchString%.*
;                                {
;                                    Run, "%FFMpegPath%\ffmpeg" -y -t 1 -f gdigrab -framerate 1 -offset_x %DMD_X% -offset_y %DMD_Y% -video_size %DMD_width%x%DMD_Height% -i desktop "%MediaOutPath%\%MediaSubDirOut%\DMD Images\%SearchString%.png",,Hide
;                                    FileAppend, Screenshot "%MediaOutPath%\%MediaSubDirOut%\DMD Images\%SearchString%.png"`n, %A_ScriptDir%\PBXrecorder.log
;                                }
;                            }
;                            Else
;                            {
;                                FileAppend, Screenshot skipped (Bad DMD size) "%MediaOutPath%\%MediaSubDirOut%\DMD Images\%SearchString%.png"`n, %A_ScriptDir%\PBXrecorder.log
;                            }
;                        }

                        Sleep,3000

                        If (PFVideoOnly=1)
                        {
                            IfNotExist, %MediaOutPath%\%MediaSubDir%\Table Videos\%SearchString%.*
                            {
                                ;Run, %FFMPEG_Path%\ffmpeg -y -t %RecTime% -rtbufsize 1500M -f dshow -i audio=%Audio_Device% -f gdigrab -framerate 30 -offset_x 0 -offset_y 0 -video_size %PF_width%x%PF_height% -i desktop -vcodec libx264 -preset ultrafast -qp 0 -acodec copy -threads 8 "%A_ScriptDir%\playfield.mkv",,Hide
                                Run, "%FFMpegPath%\ffmpeg" -y -t %RecTime% -rtbufsize 1500M -f gdigrab -framerate 30 -offset_x 0 -offset_y 0 -video_size %PF_width%x%PF_Height% -i desktop -vcodec libx264 -preset ultrafast -qp 0 -threads 8 "%A_ScriptDir%\playfield.mkv",,Hide
                                FileAppend, Recording "%A_ScriptDir%\playfield.mkv"`n, %A_ScriptDir%\PBXrecorder.log
                            }
                        }
                        If (BGVideoOnly=1)
                        {
                            IfNotExist, %MediaOutPath%\%MediaSubDir%\BackGlass Videos\%SearchString%.*
                            {
                                Run, "%FFMpegPath%\ffmpeg" -y -t %RecTime% -rtbufsize 1500M -f gdigrab -framerate 30 -offset_x %PF_width% -offset_y 0 -video_size %BG_width%x%BG_Height% -i desktop -vcodec libx264 -preset ultrafast  -qp 0 -threads 8 "%A_ScriptDir%\bg.mkv",,Hide
                                FileAppend, Recording "%A_ScriptDir%\bg.mkv"`n, %A_ScriptDir%\PBXrecorder.log
                            }
                        }
;                        If (DMDVideoOnly=1)
;                        {
;                            If DMD_width > 0 
;                            {
;                                IfNotExist, %MediaOutPath%\%MediaSubDir%\DMD Videos\%SearchString%.*
;                                {
;                                    Run, "%FFMpegPath%\ffmpeg" -y -t %RecTime% -rtbufsize 1500M -f gdigrab -framerate 30 -offset_x %DMD_X% -offset_y %DMD_Y% -video_size %DMD_width%x%DMD_Height% -i desktop -vcodec libx264 -preset ultrafast  -qp 0 -threads 8 "%A_ScriptDir%\dmd.mkv",,Hide
;                                    FileAppend, Recording "%A_ScriptDir%\dmd.mkv"`n, %A_ScriptDir%\PBXrecorder.log
;                                }
;                            }
;                            Else
;                            {
;                                FileAppend, Recording skipped (Bad DMD size) "%A_ScriptDir%\dmd.mkv"`n, %A_ScriptDir%\PBXrecorder.log
;                            }
;                        }
                    }
                    Else If ((OnlyRecordMissingVideos=3) and (MediaAtLeastOneFound=1))
                    ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                    {
                        ;Safety check for Mode = Record only new tables (Ignore partial media set).
                        FileAppend, Skipping table - XXX Recording only new tables. Partial media set exists... %TablePath%\%XTable%\%Table%`n, %A_ScriptDir%\PBXrecorder.log
                    }
                    Else     ;Record everything for this table (Mode = Re-record all, Record if incomplete table, Record if new table)
                    ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                    {                    
                        If (PFImageOnly=1)
                        {
                            Run, "%FFMpegPath%\ffmpeg" -y -t 1 -f gdigrab -framerate 1 -offset_x 0 -offset_y 0 -video_size %PF_width%x%PF_Height% -i desktop -vf "rotate=PI:bilinear=0" "%MediaOutPath%\%MediaSubDirOut%\Table Images\%SearchString%.png",,Hide
                            FileAppend, Screenshot "%MediaOutPath%\%MediaSubDirOut%\Table Images\%SearchString%.png"`n, %A_ScriptDir%\PBXrecorder.log
                        }
                        If (BGImageOnly=1)
                        {
                            Run, "%FFMpegPath%\ffmpeg" -y -t 1 -f gdigrab -framerate 1 -offset_x %PF_width% -offset_y 0 -video_size %BG_width%x%BG_Height% -i desktop "%MediaOutPath%\%MediaSubDirOut%\Backglass Images\%SearchString%.png",,Hide
                            FileAppend, Screenshot "%MediaOutPath%\%MediaSubDirOut%\Backglass Images\%SearchString%.png"`n, %A_ScriptDir%\PBXrecorder.log
                        }
;                        If (DMDImageOnly=1)
;                        {
;                            If DMD_width > 0 
;                            {
;                                Run, "%FFMpegPath%\ffmpeg" -y -t 1 -f gdigrab -framerate 1 -offset_x %DMD_X% -offset_y %DMD_Y% -video_size %DMD_width%x%DMD_Height% -i desktop "%MediaOutPath%\%MediaSubDirOut%\DMD Images\%SearchString%.png",,Hide
;                                FileAppend, Screenshot "%MediaOutPath%\%MediaSubDirOut%\DMD Images\%SearchString%.png"`n, %A_ScriptDir%\PBXrecorder.log
;                            }
;                            Else
;                            {
;                                FileAppend, Screenshot skipped (Bad DMD size) "%MediaOutPath%\%MediaSubDirOut%\DMD Images\%SearchString%.png"`n, %A_ScriptDir%\PBXrecorder.log
;                            }
;                        }
                        Sleep,3000

                        If (PFVideoOnly=1)
                        {
                            ;Run, %FFMPEG_Path%\ffmpeg -y -t %RecTime% -rtbufsize 1500M -f dshow -i audio=%Audio_Device% -f gdigrab -framerate 30 -offset_x 0 -offset_y 0 -video_size %PF_width%x%PF_height% -i desktop -vcodec libx264 -preset ultrafast -qp 0 -acodec copy -threads 8 "%A_ScriptDir%\playfield.mkv",,Hide
                            Run, "%FFMpegPath%\ffmpeg" -y -t %RecTime% -rtbufsize 1500M -f gdigrab -framerate 30 -offset_x 0 -offset_y 0 -video_size %PF_width%x%PF_Height% -i desktop -vcodec libx264 -preset ultrafast -qp 0 -threads 8 "%A_ScriptDir%\playfield.mkv",,Hide
                            FileAppend, Recording "%A_ScriptDir%\playfield.mkv"`n, %A_ScriptDir%\PBXrecorder.log
                        }
                        If (BGVideoOnly=1)
                        {
                            Run, "%FFMpegPath%\ffmpeg" -y -t %RecTime% -rtbufsize 1500M -f gdigrab -framerate 30 -offset_x %PF_width% -offset_y 0 -video_size %BG_width%x%BG_Height% -i desktop -vcodec libx264 -preset ultrafast  -qp 0 -threads 8 "%A_ScriptDir%\bg.mkv",,Hide
                            FileAppend, Recording "%A_ScriptDir%\bg.mkv"`n, %A_ScriptDir%\PBXrecorder.log
                        }
;                        If (DMDVideoOnly=1)
;                        {
;                            If DMD_width > 0 
;                            {
;                                Run, "%FFMpegPath%\ffmpeg" -y -t %RecTime% -rtbufsize 1500M -f gdigrab -framerate 30 -offset_x %DMD_X% -offset_y %DMD_Y% -video_size %DMD_width%x%DMD_Height% -i desktop -vcodec libx264 -preset ultrafast  -qp 0 -threads 8 "%A_ScriptDir%\dmd.mkv",,Hide
;                                FileAppend, Recording "%A_ScriptDir%\dmd.mkv"`n, %A_ScriptDir%\PBXrecorder.log
;                            }
;                            Else
;                            {
;                                FileAppend, Recording skipped (Bad DMD size) "%A_ScriptDir%\dmd.mkv"`n, %A_ScriptDir%\PBXrecorder.log
;                            }
;                        }
                    }
                                 
                    WinActivate, ahk_class UnityWndClass
                    WinWaitActive, ahk_class UnityWndClass,,5
                     
                    ; Clean up
                    ;==========================
                    Process, WaitClose, ffmpeg.exe, 500
                    WinMinimize, ahk_class UnityWndClass
                    WinClose, ahk_class UnityWndClass
                    WinWaitClose ahk_class UnityWndClass
                    Run, taskkill /T /IM "Unit3D Pinball.exe" ,,UseErrorLevel
;                    Run, taskkill /T /IM B2SBackglassServerEXE.exe,,UserErrorLevel
;                    Run, taskkill /F /IM %XTable%.exe,,UserErrorLevel
;                    IfInString, XTable, B2S.exe, WinKill, Form1
                    Run, taskkill /IM ffmpeg.exe /F,,UseErrorLevel
                    Run, taskkill /IM ffmpeg.exe /F,,UseErrorLevel
                    Run, taskkill /IM ffmpeg.exe /F,,UseErrorLevel                
;                    Run, taskkill /F /IM UltraDMD.exe,,UseErrorLevel
;                    Run, taskkill /F /IM Pin2dmd.exe,,UseErrorLevel
                    Sleep,5000
                    Run, taskkill /F /IM "Unit3D Pinball.exe",,UseErrorLevel
;                    Run, taskkill /F /IM B2SBackglassServerEXE.exe,,UserErrorLevel    
;                    WinKill, UltraDMD
                    WinKill, Error
                    
                    ;PIN2DMD related - Not ready for primetime yet
                    ;==========================
                    ;IfExist, %A_ScriptDir%\pin2dmd\pin2dmd.exe
                    ;{
                    ;    Run, %A_ScriptDir%\pin2dmd\Pin2DMD.exe /r,,UseErrorLevel
                    ;}
                    ;IfExist, %A_ScriptDir%\pin2dmd\blank.ppm
                    ;{
                    ;    Run, %A_ScriptDir%\pin2dmd\Pin2DMD.exe /i %A_ScriptDir%\pin2dmd\blank.ppm,,UseErrorLevel
                    ;}

                    ;Convert Videos to .f4v/.mp4 if any mkv exist  - Post Capture Trim and Transcode
                    ;============================================================================
                    Progress, x200 y200 zh0 M h200 w600 FS10, `n`nConvert Videos to %RecExt% if video was captured`n`n(Press ESC anytime to abort script) ,`n`n%Description%, Capturing Videos... #%Loopcount% , Arial
                    
                    ConvertStatusStr= (
                    If (PFVideoOnly=1)
                        ConvertStatusStr= %ConvertStatusStr% PF 
                    If (BGVideoOnly=1)
                        ConvertStatusStr= %ConvertStatusStr% BG     
;                    If (DMDVideoOnly=1)
;                        ConvertStatusStr= %ConvertStatusStr% DMD
                    ConvertStatusStr= %ConvertStatusStr%  )
                    
                    FileAppend, Convert Videos to %RecExt% if video was captured (%A_Hour%:%A_Min%:%A_Sec%)`n, %A_ScriptDir%\PBXrecorder.log
                    IfExist, %A_ScriptDir%\playfield.mkv ;If (PFVideoOnly=1) ;
                    {
                        Run, "%FFMpegPath%\ffmpeg" -y -i "%A_ScriptDir%\playfield.mkv" -ss 5 -to 1000 -vf [in]rotate=PI:bilinear=0[middle];[middle]scale=1920:-1[out] -map 0:0 -c:v libx264 -crf 26 "%MediaOutPath%\%MediaSubDirOut%\Table Videos\%SearchString%.%RecExt%"
                        FileAppend, Converting to "%MediaOutPath%\%MediaSubDirOut%\Table Videos\%SearchString%.%RecExt%"`n, %A_ScriptDir%\PBXrecorder.log
                    }
                    IfExist, %A_ScriptDir%\bg.mkv ;If (BGVideoOnly=1) ;
                    {
                        Run, "%FFMpegPath%\ffmpeg" -y -i "%A_ScriptDir%\bg.mkv" -ss 5 -to 1000 -c:v libx264 -crf 26 "%MediaOutPath%\%MediaSubDirOut%\Backglass Videos\%SearchString%.%RecExt%"
                        FileAppend, Converting to "%MediaOutPath%\%MediaSubDirOut%\Backglass Videos\%SearchString%.%RecExt%"`n, %A_ScriptDir%\PBXrecorder.log
                    }
;                    IfExist, %A_ScriptDir%\dmd.mkv ;If (DMDVideoOnly=1) ;
;                    {        
;                        Run, "%FFMpegPath%\ffmpeg" -y -i "%A_ScriptDir%\dmd.mkv" -ss 5 -to 1000 -c:v libx264 -crf 26 "%MediaOutPath%\%MediaSubDirOut%\DMD Videos\%SearchString%.%RecExt%"
;                        FileAppend, Converting to "%MediaOutPath%\%MediaSubDirOut%\DMD Videos\%SearchString%.%RecExt%"`n, %A_ScriptDir%\PBXrecorder.log
;                    }
                    ; Clean up
                    ;==========================
                    Process, WaitClose, ffmpeg.exe, 500
                    Run, taskkill /IM ffmpeg.exe /F
                    Run, taskkill /IM ffmpeg.exe /F
                    Run, taskkill /IM ffmpeg.exe /F
                    Progress, Off
                    Sleep,3000
                    ;SoundBeep, 400, 200 
                    FileDelete, %A_ScriptDir%\playfield.mkv
                    FileDelete, %A_ScriptDir%\bg.mkv
                    FileDelete, %A_ScriptDir%\dmd.mkv        
                    FileAppend, Table done (%A_Hour%:%A_Min%:%A_Sec%)`n, %A_ScriptDir%\PBXrecorder.log
                    
                } ;End of If TableExists = 1
            } ;End of If TableEnabled = 1
        } ;End of Else IfInString, A_LoopReadLine, /game
    } ;End of Loop, Read, %XMLPath%\%XMLFilename%.xml
}

; All done!
;===============
Progress, Off
If (DragAndDrop<>1)
{
    FileAppend, `n%Recordcount% new recordings out of %Loopcount% tables. Finished (%A_Hour%:%A_Min%:%A_Sec%)`n, %A_ScriptDir%\PBXrecorder.log
    Msgbox,0, Recording Finished,Recording is complete.  %Recordcount% new recordings out of %Loopcount% tables
}
Else ;Drag and drop done
{
    If (DragAndDropTableFound=1)
    {
        FileAppend, `nDrag and Drop recording finished (%DragAndDropFile%). Finished (%A_Hour%:%A_Min%:%A_Sec%)`n, %A_ScriptDir%\PBXrecorder.log
        Msgbox,0, Drag and Drop, Recording is complete`n`n(%DragAndDropFile%).
    }
    Else If (DragAndDropTableFound=-1)
    {
        FileAppend, `nDrag and Drop skipped.  Media already exists (%DragAndDropFile%). Finished (%A_Hour%:%A_Min%:%A_Sec%)`n, %A_ScriptDir%\PBXrecorder.log
        Msgbox,0, Drag and Drop, No Recording. Media already exists`n`n(%DragAndDropFile%).
    }
    Else
    {
        FileAppend, `nDrag and Drop table was not found in the Unit3D XML files (%DragAndDropFile%). Finished (%A_Hour%:%A_Min%:%A_Sec%)`n, %A_ScriptDir%\PBXrecorder.log
        Msgbox,0, Drag and Drop, Table was not found in the Unit3D XML files.  No recording performed`n`n(%DragAndDropFile%).
    }
}
ExitApp


;*******************************************
;*******************************************
; Misc Subroutines for Msgbox and Gui windows and Exit
;*******************************************
;*******************************************
RecordModeButtonNames: 
IfWinNotExist, Record Mode
    return  ; Keep waiting.
SetTimer, RecordModeButtonNames, off 
WinActivate 
ControlSetText, Button1, &Videos Only
ControlSetText, Button2, &Both 
return    

MissingVideosButtonNames: 
IfWinNotExist, Record Mode
    return  ; Keep waiting.
SetTimer, MissingVideosButtonNames, off 
WinActivate 
ControlSetText, Button1, &Missing Only
ControlSetText, Button2, &All Tables
return    

CurrSettingsButtonNames: 
IfWinNotExist, Current Recorder Settings
    return  ; Keep waiting.
SetTimer, CurrSettingsButtonNames, off 
WinActivate 
ControlSetText, Button1, &Continue
ControlSetText, Button2, &Change
return    


StoreMediaButtonNames: 
IfWinNotExist, Storing Media
    return  ; Keep waiting.
SetTimer, StoreMediaButtonNames, off 
WinActivate 
ControlSetText, Button1, &Combined
ControlSetText, Button2, &Separate
return    

GuiClose:
ExitApp

;Pressing the ESC key will abort the script
Esc::
IfWinNotExist,Add Table to XML    
    IfWinNotExist,Press Pause to Resume
        ExitApp
Else
    Return   ; XML dialog present, do not exit

Pause::
    If (LoadingTable=1)
    {
        If (PBXRPauseMode=0)
        {
            PBXRPauseMode:=1
        }
        Else
        {
            Progress, Off
        }
    }
    Return
    
    
Confirm:
    Gui, Submit
    PFVideoOnly=0
    BGVideoOnly=0
    DMDVideoOnly=0
    PFImageOnly=0
    BGImageOnly=0
    DMDImageOnly=0

    if (PFVid = 1) 
        PFVideoOnly=1
    if (BGVid = 1) 
        BGVideoOnly=1
    if (DMDVid = 1) 
        DMDVideoOnly=1
    if (PFImage = 1) 
        PFImageOnly=1
    if (BGImage = 1) 
        BGImageOnly=1
    if (DMDImage = 1) 
        DMDImageOnly=1    

    Gui, Destroy
return
        
Select:
    Gui, Submit
    if (RadioGroup1) 
        RecTime:=5
    else if (RadioGroup2) 
        RecTime:=15
    else if (RadioGroup3) 
        RecTime:=30
    else if (RadioGroup4) 
        RecTime:=60
    else if (RadioGroup5) 
        RecTime:=120
    else if (RadioGroup6) 
        RecTime:=300    
        
    Gui, Destroy
Return

MediaMode:
    Gui, Submit
    if (MRadioGroup1) 
        OnlyRecordMissingVideos:=2
    else if (MRadioGroup2) 
        OnlyRecordMissingVideos:=1
    else if (MRadioGroup3) 
        OnlyRecordMissingVideos:=0
    else if (MRadioGroup4) 
        OnlyRecordMissingVideos:=3
    Gui, Destroy
Return

MediaF:
    Gui, Submit
    if (RadioGroupMF1) 
        MediaFileNaming:=0
    else if (RadioGroupMF2) 
        MediaFileNaming:=1

    Gui, Destroy
Return

MediaR:
    Gui, Submit
    if (RadioGroupRF1) 
    {
        RecFormat:=0
        RecExt=f4v
    }
    else if (RadioGroupRF2) 
    {
        RecFormat:=1
        RecExt=mp4
    }
    Gui, Destroy
Return

; Subroutines for Add Table to XML files
GetXMLDescSelected:
GuiControlGet, XMLDescSelected 
x:=IPDBArray[XMLDescSelected]
GuiControl,, XMLDescText,%x%
x:=IPDBMfrArray[XMLDescSelected]
GuiControl,, XMLMfr,%x%
x:=IPDBYearArray[XMLDescSelected]
GuiControl,, XMLYear,%x%
x:=IPDBTypeArray[XMLDescSelected]
GuiControl,, XMLType,%x%
Return

XMLLaunch:
GuiControlGet, XMLAltExe
GuiControlGet, XMLAltExeName
If (XMLAltExe = 1)
    IfExist, %WorkingPath%\%LaunchExecutable%
    {
        Run, "%WorkingPath%\%LaunchExecutable%" -play "%DragAndDropFile%",,UseErrorLevel
    }
    Else
    {
        Msgbox, %WorkingPath%\%LaunchExecutable% could not be found
        Return
    }
Else
    IfExist, %WorkingPath%\%XMLAltExeName%
        Run, "%WorkingPath%\%XMLAltExeName%" -play "%DragAndDropFile%",,UseErrorLevel
    Else
    {
        Msgbox, %WorkingPath%\%XMLAltExeName% could not be found
        Return
    }
Return

XMLCancel:
Gui, Destroy
ExitApp

XMLSaveExit:
gosub XMLSave
;Msgbox XML Saved
ExitApp

XMLSave:
;Figure out which XML file to update
GuiControlGet, XMLVPSystem
tempXMLString1:=XMLPathArray[XMLVPSystem]
tempXMLString2:=XMLFileNameArray[XMLVPSystem]
tempXMLStringOrig=%tempXMLString1%\%tempXMLString2%.xml
tempXMLStringBak=%tempXMLString1%\%tempXMLString2%.bak
;FileAppend, `t TEST %tempXMLStringOrig%`n, %A_ScriptDir%\XMLtest.txt

FileCopy, %tempXMLStringOrig%, %tempXMLStringBak%, 1
FileRead, tempXMLfilestring, %tempXMLStringOrig%
StringReplace, tempXMLfilestring, tempXMLfilestring, `r`n</menu>
StringReplace, tempXMLfilestring, tempXMLfilestring, `n</menu>
FileDelete, %tempXMLStringOrig%
FileAppend, %tempXMLfilestring%, %tempXMLStringOrig%

GuiControlGet, XMLTableFileName
FileAppend, `t<game name="%XMLTableFileName%">`n, %tempXMLStringOrig%

GuiControlGet, XMLDescText 
FileAppend, `t`t<description>%XMLDescText%</description>`n, %tempXMLStringOrig%
FileAppend, `t`t<rom></rom>`n, %tempXMLStringOrig%

GuiControlGet, XMLMfr
FileAppend, `t`t<manufacturer>%XMLMfr%</manufacturer>`n, %tempXMLStringOrig%

GuiControlGet, XMLYear
FileAppend, `t`t<year>%XMLYear%</year>`n, %tempXMLStringOrig%

GuiControlGet, XMLType
FileAppend, `t`t<type>%XMLType%</type>`n, %tempXMLStringOrig%

GuiControlGet, XMLHideDMD
If (XMLHideDMD=1)
    FileAppend, `t`t<hidedmd>True</hidedmd>`n, %tempXMLStringOrig%
Else
    FileAppend, `t`t<hidedmd>False</hidedmd>`n, %tempXMLStringOrig%

GuiControlGet, XMLHideBG
If (XMLHideBG=1)
    FileAppend, `t`t<hidebackglass>True</hidebackglass>`n, %tempXMLStringOrig%
Else
    FileAppend, `t`t<hidebackglass>False</hidebackglass>`n, %tempXMLStringOrig%

GuiControlGet, XMLEnabled
If (XMLEnabled=1)
    FileAppend, `t`t<enabled>True</enabled>`n, %tempXMLStringOrig%
Else
    FileAppend, `t`t<enabled>False</enabled>`n, %tempXMLStringOrig%

GuiControlGet, XMLRating
XMLRating := XMLRating-1    
FileAppend, `t`t<rating>%XMLRating%</rating>`n, %tempXMLStringOrig%

GuiControlGet, XMLAltExe
GuiControlGet, XMLAltExeName
If (XMLAltExe = 2)
    FileAppend, `t`t<alternateExe>%XMLAltExeName%</alternateExe>`n, %tempXMLStringOrig%
Else If (XMLAltExe = 3)
    FileAppend, `t`t<Exe>%XMLAltExeName%</alternateExe>`n, %tempXMLStringOrig%

FileAppend, `t</game>`n, %tempXMLStringOrig%
FileAppend, </menu>`n, %tempXMLStringOrig%
Gui, Destroy
Return

XMLDB2S:
IfExist, %DragAndDropFileDir%\%DragAndDropFileNameOnly%.directB2S
    Return
Else IfExist, %DragAndDropFileDir%\%DragAndDropFileNameOnly%.exe
    Return
Else ;Let user select DirectB2S file to rename
    FileSelectFile, XMLFilePicked,,%DragAndDropFileDir%,,*.directb2s

IfExist, %XMLFilePicked%
{
    SetTimer, DB2SButtonNames, 50 
    Msgbox, 3, DirectB2S File Rename, Create a copy of the DirectB2S file and rename it?            
    IfMsgBox, Yes ; Copy and rename the file
    {
        GuiControl ,, XDB2S, DB2S Found
        FileCopy, %XMLFilePicked%, %DragAndDropFileDir%\%DragAndDropFileNameOnly%.directB2S, 1
    }
    Else IfMsgBox, No ; Just rename the file
    {
        FileMove, %XMLFilePicked%, %DragAndDropFileDir%\%DragAndDropFileNameOnly%.directB2S, 1
        GuiControl ,, XDB2S, DB2S Found
    }
}
Return

DB2SButtonNames: 
IfWinNotExist, DirectB2S File Rename
    return  ; Keep waiting.
SetTimer, DB2SButtonNames, off 
WinActivate 
ControlSetText, Button1, &Copy&&Rename
ControlSetText, Button2, &Just Rename 
return        
        
; Release 1.0    Initial release Nov 11, 2015
;                 Create playfield, backglass, dmd videos and/or images automatically based on PinballX setup
; Release 1.1    Fix bug if VisualPinball system is disabled  (Dec 2015)
; Release 1.2    Skip table if table is disabled in Game Manager/XML (Jan 9, 2016)
;                Add <AlternateExe> and <exe> xml tag support
;                Add support for & and ® and ' in Table and Description names
;               Check if vpt or vpx file exists, skip if not found
;                Detects if Wheel Images or DirectB2S/B2S.exe are missing
;                PinballX FTP users can enable automatic Wheel download support
;                If PIN2DMD.exe found, it will reset PIN2DMD between tables
; 1.3 changes   Feb 7, 2016            
;                 New recording mode added:  "Record complete media set for new tables only"  If any media is found, the table is skipped. Only tables with no media are recorded.   This might eliminate the need for the drag and drop feature, since this leaves your existing Pinballx Media set unchanged, i.e if you do not have DMD images for some existing tables, PBX Recorder will not try to create them
;                 Drag and drop .vpt/.vpx file onto PBXrecorder to record only one table (Table must be exist in the PinballX xml files)
;                 New option to record video in .mp4 or existing .f4v.  Now checks for existing .f4v or .mp4 or any file.
;                 New option to save Media filenames based on description name or table name
;                 Speed up the scanning of tables for people with larger libraries
;                 Improve DMD recording success by forcing dvd dimensions to an even pixel value
;                 Bug fix: Enable recordings for VP xml systems missing the enabled xml tag        
; 1.4 changes   Apr 7, 2016
;                New dialog to add table to Pinballx XML file
;                PAUSE and RESUME support to allow table script changes before recording.  Double tap PAUSE to record immediately
; 1.4Unit3D     May 12, 2016
;                Port for Unit3D capture - Unit3D beta does not support three screens - DMD settings will be ignored
;                Add support for ™ and recognition of &#42; (i.e, *) in Table and Description names
;                Some minor clean up for unused ErrorLevel parameters


;                Add right click support (Play with VP, Record with PBXRecorder)
;todo: create reg entries
;todo: detect xml file to default to
;todo list duplicates xmls entries. 
;todo support [xxx] for wheel download.
;todo list tables not in the xml file
;todo switch ini parsing to use split string function
; possible bug.  recext not correct after modifying ini 

Changes from 1.4

; 1.4Unit3D     May 12, 2016
;                Port for Unit3D capture - Unit3D beta does not support three screens - DMD settings will be ignored
;                Add support for ™ and recognition of &#42; (i.e, *) in Table and Description names
;                Some minor clean up for unused ErrorLevel parameters

Again thanks to gtxjoe for allowing mods to his source code. 

  • Like 1
Posted

I was wondering if there is a way to insert coin & start the game while this is recording unattended. The backglass doesnt do much for most games until a coin is inserted

Thanks

Posted

Well, somebody has to be there to insert a coin and play a game, so there is some manual aspect to doing what you are looking to do. I built on top of gtxjoe's work. I "think" that you can do this through the PAUSE function, but I have not tested that feature yet. I only changed the part of the code needed to read in table info and capture settings for Unit3D.

; 1.4 changes   Apr 7, 2016
;                New dialog to add table to Pinballx XML file
;                PAUSE and RESUME support to allow table script changes before recording.  Double tap PAUSE to record immediately
 

Posted

Thanks for the reply Carny_Priest. Its actually fine, I went back to look at the ones I had previously and they were about the same.

Question regarding the FP version. It is recording my tables and backglasses however they are small/cropped on the screen surrounded by black.

Did I screw something up? The VP one worked without issue.

thanks

May 15,2016 11:51:46
Version 1.4betaFP

Monitor Count: 2,  Primary Monitor: 1
Monitor 1: 1920x1080 (\\.\DISPLAY1)
Monitor 2: 3200x1024 (\\.\DISPLAY2)

Pinballx.ini
5
C:\PinballX-New
0
1
1
0
0
0
0
1
60
0

Identify all FP XML files...
FP System #1:
C:\PinballX-New\Databases\Future Pinball\Future Pinball.xml
C:\Games\Future Pinball\BAM
C:\Games\Future Pinball\Tables
FPLoader.exe

Skipping this system: MAME.xml

Skipping this system: Visual Pinball PM5.xml

Skipping this system: Visual Pinball 10.xml

Total number of FP systems found: 1

Working on Future Pinball.xml (11:52:00)

FP Registry and FutureDMD Settings
1920
1080
1280
1024
ERROR
ERROR
ERROR
ERROR

Values used for media capture (height/width forced to even values)
VirtualScreenWidth = 3200
TotalScreenWidth   =   
PF_width           = 1920          
PF_height          = 1080         
BG_width           = 1280          
BG_height          = 1024         
DMD_width          = 0      
DMD_height         = 0        
DMD_X_offset       =             
DMD_Y_offset       = ERROR             
-----------------------------------------
DMD_tot_offset     = ERROR             
DMD_orig_width     = ERROR         
DMDScreenWidth     = 0    
DMDScreenHeight    = 1024   
DMDScreenX         =           

2001 (Gottlieb 1971)
Table disabled in xml: C:\Games\Future Pinball\Tables\2001_V200.fpt

Buccaneer (Gottlieb 1948)
Table disabled in xml: C:\Games\Future Pinball\Tables\BUCCANEER-CPM-Cabinet.fpt

24 (2009)

EXAMPLE of all ffmpeg.exe commands used
"C:\PBXRecorder_x64\FFMpeg\bin\ffmpeg" -y -i "C:\PBXRecorder_x64\playfield.mkv" -ss 10 -to 1000 -vf [in]rotate=PI:bilinear=0[middle];[middle]scale=1920:-1[out] -map 0:0 -c:v libx264 -crf 26 "C:\PinballX-New\Media\Future Pinball\Table Videos\24 (2009).mp4"
"C:\PBXRecorder_x64\FFMpeg\bin\ffmpeg" -y -i "C:\PBXRecorder_x64\bg.mkv" -ss 10 -to 1000 -map 0:0 -c:v libx264 -crf 26 "C:\PinballX-New\Media\Future Pinball\Backglass Videos\24 (2009).mp4"
"C:\PBXRecorder_x64\FFMpeg\bin\ffmpeg" -y -i "C:\PBXRecorder_x64\dmd.mkv" -ss 10 -to 1000 -vf "crop=w=0:h=0:x=:y=ERROR" -map 0:0 -c:v libx264 -crf 26 "C:\PinballX-New\Media\Future Pinball\DMD Videos\24 (2009).mp4"

PBXrecorder set to record complete media set
Launching table: FPLoader.exe /STAYINRAM /open C:\Games\Future Pinball\Tables\24_CE.fpt /play /exit
Video and screenshot capture (11:53:16)
Recording "C:\PBXRecorder_x64\playfield\playfield.mkv"
Recording "C:\PBXRecorder_x64\bg.mkv"
Convert Videos to mp4 if video was captured (11:54:34)
Converting to "C:\PinballX-New\Media\Future Pinball\Table Videos\24 (2009).mp4"
Converting to "C:\PinballX-New\Media\Future Pinball\Backglass Videos\24 (2009).mp4"
Table done (11:55:50)

 

24 (2009).mp4

Capture.PNG

Posted

Did you run as admin? The compiled version needs permission to change OBS Studio profile xml files based on FP screen settings defined in the registry. The OBS executable needs to be run as admin. 

If worse comes to worse, and the program can not overwrite the capture parameters that I have in the existing profile configuration then you may need to set up Display Capture for the playfield and for the bg profiles yourself. It is actually not too hard. Go to command line and start OBS Studio

obs64.exe --portable --collection playfield --profile playfield --scene playfield

You will get the main OBS window with profile: playfield - Scenes: playfield in the Window title. 

Sources - right click Display Capture --> Properties. Make sure that the correct display for your playfield is defined. You can use the pulldown to change the display. 

Also

Click Settings button --> Video Change the Base (Canvas) Resolution to 1920x1080 if you need to using the pulldown.

Repeat for your bg profile except that the Base (Canvas) Resolution should be 1280x1024.

Let me know if you already had everything set up as admin and the program did not apparently copy over your registry settings. I may have some more tweaking to do to get this more automated. I wanted to avoid people having to get into the OBS settings dialog if I can.

Thanks for trying it out. The existing profile configuration were done manually so of course it works fine for me. I only generated media for a few tables that I had where I had some missing videos. I only used draganddrop mode, so I know that part of it works Ok.

Posted

Thanks, I tired setting obs as admin and running pbx recorder as admin and no luck. I manually set the canvas and it's working now. Thank you for your help!

Posted
On 5/15/2016 at 7:25 PM, bryhud said:

I was wondering if there is a way to insert coin & start the game while this is recording unattended. The backglass doesnt do much for most games until a coin is inserted

Thanks

You could modify the script to send the coin key and start key if you want to. But where do you stop :)  Should you plunge, should you hit the flipper buttons...

I dont know about FP but for VP, some tables require 1 coin while others require 4 coins so it gets tricky to do, how many coins to insert without leaving the table/rom fully loaded with coins.  Some people would complain about that.

Posted

Yeah. I hear ya, they turned out well. Almost done with the future pinball tables. Every once in a while it hangs on a table and needs to be restarted, otherwise working well

  • 2 months later...
Posted

Hello All,

 

Came across this great tool, but i'm having a heck of a time getting it to work.  I am using future pinball and using the latest PBS recorder

The instructions are a bit vague, can somebody share a shot of their folder structure?

Where do you put the actual script, inside of the OBS root folder?

When you extract the config file, its foldered inside of config twice?  Do you put the actual config folder in the OBS root file?

I'm also getting FFMPEG file not found in the log, i have installed this on the C: drive as suggested in the tutorial.

When i open the obs software "playfield" does not shot up at the top by default i have to physically select it, and sometimes the process gets held up as when the OBS software asks to accept the license again.

The log also says a screen shot was taken, but noting shows up in the folders.

I know this path is incorrect, not sure why the script generating this - Action: <c:\obs-studio\ffmpeg\bin\bin\ffmped

Any help would be really appreciated.

 

PBXrecorder.log

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...