cai/tools/gif.py

123 lines
3.7 KiB
Python

#!/usr/bin/env python3
"""
Tool to create GIF recordings of JSONL replay files.
Usage:
cai-gif path/to/file.jsonl 0.5 output.gif
This tool wraps asciinema recording and agg to create GIF animations.
"""
import argparse
import os
import subprocess
import sys
import tempfile
def parse_arguments():
"""Parse command line arguments."""
parser = argparse.ArgumentParser(
description="Create GIF recordings of JSONL replay files.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
cai-gif path/to/file.jsonl 0.5 output.gif
cai-gif conversation.jsonl 1.0 demo.gif
""",
)
parser.add_argument("jsonl_file", help="Path to the JSONL file containing conversation history")
parser.add_argument("replay_delay", type=float, help="Time in seconds to wait between actions")
parser.add_argument("output_gif", help="Output GIF file path")
return parser.parse_args()
def check_dependencies():
"""Check if required tools are installed."""
missing_deps = []
# Check for asciinema
try:
subprocess.run(["asciinema", "--version"], check=True, capture_output=True)
except (subprocess.CalledProcessError, FileNotFoundError):
missing_deps.append("asciinema")
# Check for agg
try:
subprocess.run(["agg", "--version"], check=True, capture_output=True)
except (subprocess.CalledProcessError, FileNotFoundError):
missing_deps.append("agg")
if missing_deps:
print("Error: Missing required dependencies:", file=sys.stderr)
if "asciinema" in missing_deps:
print(" - asciinema: Install with 'pip install asciinema'", file=sys.stderr)
if "agg" in missing_deps:
print(" - agg: Install with 'npm install -g @asciinema/agg'", file=sys.stderr)
sys.exit(1)
def main():
"""Main function to create GIF recording."""
args = parse_arguments()
check_dependencies()
# Validate that the JSONL file exists
if not os.path.exists(args.jsonl_file):
print(f"Error: File {args.jsonl_file} not found", file=sys.stderr)
sys.exit(1)
# Create a temporary file for the asciinema cast
with tempfile.NamedTemporaryFile(suffix=".cast", delete=False) as temp_cast:
temp_cast_path = temp_cast.name
try:
# Build the command to record using the same Python interpreter
replay_command = f"{sys.executable} tools/replay.py {args.jsonl_file} {args.replay_delay}"
# Build asciinema command
asciinema_cmd = [
"asciinema",
"rec",
f"--command={replay_command}",
"--overwrite",
temp_cast_path,
]
print(
f"Recording asciinema session for {args.jsonl_file} with delay {args.replay_delay}s..."
)
print(f"Running: {' '.join(asciinema_cmd)}")
# Execute the asciinema command
subprocess.run(asciinema_cmd, check=True)
# Convert the cast file to GIF using agg
print(f"Converting recording to GIF: {args.output_gif}")
agg_cmd = ["agg", temp_cast_path, args.output_gif]
subprocess.run(agg_cmd, check=True)
print("GIF creation completed successfully!")
return 0
except subprocess.CalledProcessError as e:
print(f"Error: Command failed with exit code {e.returncode}", file=sys.stderr)
return e.returncode
except Exception as e: # pylint: disable=broad-except
print(f"Error: {str(e)}", file=sys.stderr)
return 1
finally:
# Clean up the temporary cast file
try:
os.unlink(temp_cast_path)
except OSError:
pass
if __name__ == "__main__":
sys.exit(main())