2026-03-24 18:11:39 -06:00
2026-03-24 18:11:39 -06:00
2026-03-24 18:11:39 -06:00
2026-03-24 18:11:39 -06:00
2026-03-24 18:11:39 -06:00
2026-03-24 18:11:39 -06:00
2026-03-24 18:11:39 -06:00
2026-03-24 18:11:39 -06:00
2026-03-24 18:11:39 -06:00
2026-03-24 18:11:39 -06:00
2026-03-24 18:11:39 -06:00
2026-03-24 18:11:39 -06:00

SWF → MP4 Converter

Converts SWF (Flash) files to MP4 videos on Fedora, with:

  • Automatic loop detection — stops recording after 5 seconds of a repeated loop
  • Interactive branch capture — records separate MP4s for each clickable path
  • Batch processing — handles entire directories of SWF files
  • GUI interaction mapper — click-record tool to find interaction coordinates

File Structure

swf_converter/
├── convert.py          # Main entry point — run this
├── recorder.py         # FFmpeg + Ruffle orchestration
├── loop_detector.py    # Frame-hash loop detection + FFmpeg freeze detection
├── interaction.py      # xdotool click injection + interaction recording
├── config_manager.py   # JSON config loading and validation
├── swf_inspector.py    # Reads SWF binary headers (FPS, frame count, dimensions)
├── map_interactions.py # GUI tool to record interaction coordinates
├── install.sh          # Fedora dependency installer
├── tests.py            # Unit tests
└── configs/
    └── swf_config.json # Per-file settings and interaction definitions

Installation

bash install.sh

This installs: ffmpeg, xdotool, ImageMagick, scrot, python3-tkinter, Xvfb, and downloads the latest Ruffle binary to ~/.local/bin/ruffle.


Quick Start

1. Inspect your SWF files

python3 convert.py --inspect *.swf

Shows version, FPS, frame count, dimensions, and estimated duration for each file.

2. Generate a starter config

python3 convert.py --generate-config *.swf

Creates configs/swf_config.json pre-populated with each SWF's metadata. Edit it to add end_seconds and interaction points.

3. Map interaction points (optional)

For interactive SWFs, use the GUI tool to record where/when clicks happen:

python3 map_interactions.py my_interactive.swf
  1. Click Launch & Start — opens the SWF in Ruffle and starts a timer
  2. Watch the SWF — when an interactive moment appears, click Record Click
  3. Coordinates are auto-detected from your mouse position
  4. Click Save Config — writes interactions to configs/swf_config.json

4. Convert

# Single file
python3 convert.py my.swf

# Directory of SWF files
python3 convert.py ./swf_files/ -o ./output/

# Dry run (shows what would happen without recording)
python3 convert.py ./swf_files/ --dry-run

# Override loop grace period
python3 convert.py *.swf --loop-grace 8.0

Output Structure

For a SWF called lesson.swf with two interaction points:

output/
├── lesson_base.mp4          ← Linear content, no clicks
├── lesson_button_yes.mp4    ← Branch: click "Yes" at t=12.5s
└── lesson_button_no.mp4     ← Branch: click "No" at t=12.5s

Configuration Reference

{
  "_defaults": {
    "emulator": "ruffle",          // "ruffle" or "lightspark"
    "loop_detection": "hash",      // "hash", "freeze", or "none"
    "loop_grace": 5.0,             // Seconds after loop detected before stopping
    "max_duration": 600.0,         // Hard cap in seconds
    "fps": 30,                     // Capture frame rate
    "crf": 18,                     // FFmpeg quality (051, lower=better)
    "audio": true,                 // Capture audio via PulseAudio
    "startup_delay": 3.0           // Wait after launching emulator
  },

  "my_animation.swf": {
    "end_seconds": 42.0,           // Force-stop at this timestamp
    "interactions": [
      {
        "t": 12.5,                 // Seconds from SWF start
        "x": 320,                  // X coordinate in window
        "y": 240,                  // Y coordinate in window
        "label": "button_yes",     // Output filename suffix
        "button": 1,               // 1=left, 2=middle, 3=right
        "double": false            // Double-click?
      }
    ]
  }
}

Loop Detection Methods

Method How it works Best for
hash Compares MD5 hashes of downsampled screenshots every 0.4s. Detects repeated frame sequences. Most animations
freeze Runs FFmpeg freezedetect on the raw recording as post-processing. Catches visual stills. Animations that fade to a static frame
none No loop detection — relies on end_seconds or max_duration. When you know exact duration

All methods add a configurable grace period (default 5 seconds) after detection, so the loop point itself is visible in the output.


Headless / Server Use

If running without a desktop display:

# Start virtual display
Xvfb :99 -screen 0 1024x768x24 &
export DISPLAY=:99

# Start virtual audio sink
pulseaudio --start
pactl load-module module-null-sink sink_name=virtual

# Convert
python3 convert.py ./swf_files/ -o ./output/

Running Tests

python3 -m pytest tests.py -v
# or
python3 tests.py

Troubleshooting

"No Flash emulator found" → Download Ruffle from https://github.com/ruffle-rs/ruffle/releases and place at ~/.local/bin/ruffle

"Could not find emulator window" → Ensure a display is available (echo $DISPLAY should return :0 or :99) → Try increasing startup_delay in config (some SWFs load slowly)

Audio missing from output → Ensure PulseAudio is running: pulseaudio --start → Or disable audio: set "audio": false in config

Loop not detected → Try "loop_detection": "freeze" for animations that end on a static frame → Or set "end_seconds" manually after inspecting the SWF

SWF doesn't work in Ruffle → Set "emulator": "lightspark" in config for that file → Install Lightspark: sudo dnf install lightspark

xdotool clicks not registering → Check window focus — the Ruffle window must be in the foreground → Use map_interactions.py to verify coordinates match what you see on screen

S
Description
Convert SWF to MP4
Readme 59 KiB
Languages
Python 93.4%
Shell 6.6%