Export Pipeline¶
The vexy-lines-cli export command exports .lines files to PDF, SVG, or PNG without interacting with save dialogs. This page explains how the command works.
The problem¶
Vexy Lines has no headless export mode. Exporting normally means opening the file, choosing File > Export, picking a format and destination, and clicking Save. That's fine for one file. For a hundred, you need automation.
The solution: plist injection¶
Instead of automating dialog clicks (fragile and slow), the CLI injects export settings directly into the macOS preferences domain (com.fontlab.vexy-lines) using defaults write, then triggers the menu item via AppleScript.
Five-stage pipeline¶
1. Discovery¶
Find all .lines files to export. Input can be a single file or a directory (searched recursively). Files with existing outputs are skipped unless --force is set.
2. Plist injection¶
PlistManager is a context manager that:
- Reads the current preferences via
defaults read - Saves a snapshot of the original values
- Writes the export format and destination path via
defaults write - On exit (success or failure), restores the original preferences
This ensures the app's preferences are never permanently modified.
3. App activation¶
The pipeline quits the running app (if any), waits for it to fully close, then relaunches with the injected preferences. AppleScriptBridge handles the quit/launch/wait cycle.
4. Per-file export loop¶
For each .lines file:
- Open the file via AppleScript (
opencommand) - Wait for the file window to appear (polls window title)
- Trigger File > Export via the menu item name (
"Export PDF File","Export SVG File", or"Export PNG File") - Wait for the export to complete
- Optionally validate the output file exists and has non-zero size
Failed files are retried up to --max-retries times (default 3).
5. Cleanup¶
PlistManager.__exit__ restores the original preferences. The app is left running with its original settings.
Timeout control¶
All internal timeouts scale with --timeout-multiplier:
| What | Base timeout | At 2.0x |
|---|---|---|
| App launch | 20s | 40s |
| File window | 20s | 40s |
| Post-action delay | 0.4s | 0.8s |
| Poll interval | 0.2s | 0.4s |
Use higher multipliers on slower machines or when exporting complex files.
Window watcher¶
WindowWatcher polls the app's window titles via AppleScript to detect when a file has opened and when an export has completed. It uses a configurable poll interval and timeout.
Error handling¶
Each file export is wrapped in retry logic:
- Transient failures (window didn't appear, menu item failed) are retried
- Persistent failures are logged and counted
- The pipeline continues to the next file on failure
- Final stats report: processed, succeeded, skipped, failed
Stats¶
The ExportStats dataclass tracks timing and counts:
stats = exporter.export(Path("./art/"))
print(stats.human_summary())
# "Exported 42 files (3 skipped, 1 failed) in 2m 15s"
macOS only¶
The export pipeline depends on:
defaultscommand (macOS preferences system)- AppleScript (
osascript) for app automation - The Vexy Lines macOS app
It does not work on Windows or Linux. For cross-platform export, use the MCP API directly via MCPClient.export_document().
Style export pipeline (job folders)¶
The style-transfer and style-video commands use a different pipeline from export. Instead of plist injection, they use the MCP API to apply styles programmatically.
Job folder¶
Every style export creates a persistent job folder alongside the output:
| Output type | Output path | Job folder |
|---|---|---|
| Video | ./06/styled.mp4 |
./06/styled-vljob/ |
| Frames | ./output/ |
./output-vljob/ |
| Images | ./results/ |
./results-vljob/ |
For video exports, the job folder stores the cached artifact chain used to render frames:
| Step | File pattern | Description |
|---|---|---|
| 1 | src/src--{stem}--{NNN}.png |
Raw decoded video frame (video only) |
| 2 | {stem}--{NNN}.lines |
Styled .lines document from Vexy Lines |
| 3 | {stem}--{NNN}.svg |
SVG export |
| 4 | {stem}--{NNN}.png |
Rasterized output |
Video exports first extract all requested raw frames into the job folder, then style or assemble from that cached source set. Frame numbers are 1-based and zero-padded from the source video's total frame count. For MP4 output, the cached PNG frames are assembled into the requested output path after step 4 rather than stored as {stem}.mp4 inside the job folder.
Resume¶
Re-running the same command skips items whose final output already exists in the job folder. Long video jobs therefore survive interruptions: a 4-hour render that fails at frame 147/192 resumes from frame 148.
Flags¶
--force: Delete the job folder and start from scratch.--cleanup: Delete the job folder after the final output is copied to its destination.
Environment¶
Set VEXY_LINES_JOB_FOLDER to override the computed job folder path.