initial commit
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
# 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
|
||||
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
|
||||
|
||||
```bash
|
||||
python3 convert.py --inspect *.swf
|
||||
```
|
||||
|
||||
Shows version, FPS, frame count, dimensions, and estimated duration for each file.
|
||||
|
||||
### 2. Generate a starter config
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```json
|
||||
{
|
||||
"_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 (0–51, 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:
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```bash
|
||||
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
|
||||
Reference in New Issue
Block a user