├── .gitignore ├── .replit ├── README.md ├── app.py ├── asset_generator.py ├── asset_manager.py ├── main.py ├── narration_generator.py ├── output ├── run_20241225_002906_fec11138 │ ├── animations │ │ ├── pippin_the_unicorn_ethereal_glow_shifting.svg │ │ ├── pippin_the_unicorn_ethereal_glow_shifting_temp.svg │ │ ├── pippin_the_unicorn_gentle_step.svg │ │ ├── pippin_the_unicorn_gentle_step_temp.svg │ │ ├── pippin_the_unicorn_wobbling_trot.svg │ │ └── pippin_the_unicorn_wobbling_trot_temp.svg │ ├── backgrounds │ │ └── scene_0_background.png │ ├── characters │ │ └── pippin_the_unicorn.svg │ ├── final_video │ │ ├── concat_list.txt │ │ ├── final_video.mp4 │ │ ├── final_video_horizontal.mp4 │ │ └── final_video_vertical.mp4 │ ├── metadata │ │ ├── metadata.json │ │ ├── scene_movements.json │ │ └── story_data.json │ └── scenes │ │ ├── audio │ │ └── scene_0.mp3 │ │ ├── svg │ │ └── scene_0.svg │ │ ├── video │ │ └── scene_0.mp4 │ │ └── video_with_sound │ │ └── scene_0.mp4 ├── run_20241225_004148_07523f13 │ ├── animations │ │ ├── pippin_the_unicorn_gentle_stepping.svg │ │ ├── pippin_the_unicorn_gentle_stepping_temp.svg │ │ ├── pippin_the_unicorn_wobbling.svg │ │ └── pippin_the_unicorn_wobbling_temp.svg │ ├── backgrounds │ │ ├── scene_0_background.png │ │ ├── scene_1_background.png │ │ └── scene_2_background.png │ ├── characters │ │ └── pippin_the_unicorn.svg │ ├── final_video │ │ ├── concat_list.txt │ │ ├── final_video.mp4 │ │ ├── final_video_horizontal.mp4 │ │ └── final_video_vertical.mp4 │ ├── metadata │ │ ├── metadata.json │ │ ├── scene_movements.json │ │ └── story_data.json │ └── scenes │ │ ├── audio │ │ ├── scene_0.mp3 │ │ ├── scene_1.mp3 │ │ └── scene_2.mp3 │ │ ├── svg │ │ ├── scene_0.svg │ │ ├── scene_1.svg │ │ └── scene_2.svg │ │ ├── video │ │ ├── scene_0.mp4 │ │ ├── scene_1.mp4 │ │ └── scene_2.mp4 │ │ └── video_with_sound │ │ ├── scene_0.mp4 │ │ ├── scene_1.mp4 │ │ └── scene_2.mp4 ├── run_20241225_011207_e7a217c4 │ ├── animations │ │ ├── pippin_the_unicorn_gaze.svg │ │ ├── pippin_the_unicorn_gaze_temp.svg │ │ ├── pippin_the_unicorn_walk.svg │ │ ├── pippin_the_unicorn_walk_temp.svg │ │ ├── pippin_the_unicorn_wobble.svg │ │ └── pippin_the_unicorn_wobble_temp.svg │ ├── backgrounds │ │ ├── scene_0_background.png │ │ ├── scene_1_background.png │ │ └── scene_2_background.png │ ├── characters │ │ └── pippin_the_unicorn.svg │ ├── final_video │ │ ├── concat_list.txt │ │ ├── final_video.mp4 │ │ ├── final_video_horizontal.mp4 │ │ └── final_video_vertical.mp4 │ ├── metadata │ │ ├── metadata.json │ │ ├── scene_movements.json │ │ └── story_data.json │ └── scenes │ │ ├── audio │ │ ├── scene_0.mp3 │ │ ├── scene_1.mp3 │ │ └── scene_2.mp3 │ │ ├── svg │ │ ├── scene_0.svg │ │ ├── scene_1.svg │ │ └── scene_2.svg │ │ ├── video │ │ ├── scene_0.mp4 │ │ ├── scene_1.mp4 │ │ └── scene_2.mp4 │ │ └── video_with_sound │ │ ├── scene_0.mp4 │ │ ├── scene_1.mp4 │ │ └── scene_2.mp4 ├── run_20241225_064243_cf0a1360 │ ├── animations │ │ ├── ollie_the_owl_fly.svg │ │ ├── ollie_the_owl_fly_temp.svg │ │ ├── ollie_the_owl_nod.svg │ │ ├── ollie_the_owl_nod_temp.svg │ │ ├── ollie_the_owl_perch.svg │ │ ├── ollie_the_owl_perch_temp.svg │ │ ├── ollie_the_owl_smile.svg │ │ ├── ollie_the_owl_smile_temp.svg │ │ ├── ollie_the_owl_swoop_down.svg │ │ ├── ollie_the_owl_swoop_down_temp.svg │ │ ├── tilly_the_turtle_look_up.svg │ │ ├── tilly_the_turtle_look_up_temp.svg │ │ ├── tilly_the_turtle_sit.svg │ │ ├── tilly_the_turtle_sit_temp.svg │ │ ├── tilly_the_turtle_slow_walk.svg │ │ ├── tilly_the_turtle_slow_walk_temp.svg │ │ ├── tilly_the_turtle_smile.svg │ │ ├── tilly_the_turtle_smile_temp.svg │ │ ├── tilly_the_turtle_sparkle_eyes.svg │ │ └── tilly_the_turtle_sparkle_eyes_temp.svg │ ├── backgrounds │ │ └── scene_0_background.png │ ├── characters │ │ ├── ollie_the_owl.svg │ │ └── tilly_the_turtle.svg │ ├── final_video │ │ ├── concat_list.txt │ │ ├── final_video.mp4 │ │ ├── final_video_horizontal.mp4 │ │ └── final_video_vertical.mp4 │ ├── metadata │ │ ├── metadata.json │ │ ├── scene_movements.json │ │ └── story_data.json │ └── scenes │ │ ├── audio │ │ └── scene_0.mp3 │ │ ├── svg │ │ └── scene_0.svg │ │ ├── video │ │ └── scene_0.mp4 │ │ └── video_with_sound │ │ └── scene_0.mp4 └── run_20241225_065303_15723bb6 │ ├── animations │ ├── pippin_jumping_with_excitement.svg │ ├── pippin_jumping_with_excitement_temp.svg │ ├── pippin_tapping_on_the_tablet.svg │ ├── pippin_tapping_on_the_tablet_temp.svg │ ├── pippin_watching_a_video_intently.svg │ └── pippin_watching_a_video_intently_temp.svg │ ├── backgrounds │ └── scene_0_background.png │ ├── characters │ └── pippin.svg │ ├── final_video │ ├── concat_list.txt │ ├── final_video.mp4 │ ├── final_video_horizontal.mp4 │ └── final_video_vertical.mp4 │ ├── metadata │ ├── metadata.json │ ├── scene_movements.json │ └── story_data.json │ └── scenes │ ├── audio │ └── scene_0.mp3 │ ├── svg │ └── scene_0.svg │ ├── video │ └── scene_0.mp4 │ └── video_with_sound │ └── scene_0.mp4 ├── pyproject.toml ├── replit.nix ├── requirements.txt ├── scene_composer.py ├── scene_movement_analyzer.py ├── story_analyzer.py ├── svg_config.py ├── svg_processor.py ├── templates └── index.html ├── uv.lock ├── video_encoder.py └── video_processor.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ -------------------------------------------------------------------------------- /.replit: -------------------------------------------------------------------------------- 1 | modules = ["python-3.11", "web", "nix"] 2 | run = "python main.py" 3 | 4 | [nix] 5 | channel = "stable-24_05" 6 | 7 | [unitTest] 8 | language = "python3" 9 | 10 | [gitHubImport] 11 | requiredFiles = [".replit", "replit.nix"] 12 | 13 | [deployment] 14 | run = ["sh", "-c", "python main.py"] 15 | deploymentTarget = "cloudrun" 16 | 17 | [[ports]] 18 | localPort = 8080 19 | externalPort = 80 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pippin Studio 2 | 3 | Created by [@yoheinakajima](https://x.com/yoheinakajima) for the [@pippinlovesyou](https://x.com/pippinlovesyou) project, a continued exploration in AI-generated kids content, inspired by the early success of the Bedtime Stories with Pippin podcast. [Learn more](https://x.com/yoheinakajima/status/1871848832004145233). 4 | 5 | --- 6 | 7 | # Storybook Video Generation Pipeline 8 | 9 | Welcome! This project assembles a multi-step pipeline that takes in story text (or a prompt) and automatically produces a narrated, animated storybook video. It uses: 10 | 11 | - **Flask** for the web interface (front-end) 12 | - **OpenAI / DALL·E / ElevenLabs** for generating story text, images, voice narration, and animations 13 | - **FFmpeg** and other libraries for compositing and encoding the final video 14 | 15 | Below is a comprehensive guide on how to install, configure, and run this pipeline, both through a user-friendly web front-end and directly from back-end code. 16 | 17 | --- 18 | 19 | ## Table of Contents 20 | 21 | 1. Key Components & Flow 22 | 2. Prerequisites 23 | 3. Installation 24 | 4. Environment Variables 25 | 5. Running via Front-End (Flask App) 26 | 6. Running Directly Through Back-End 27 | 7. Included Generation Options 28 | 8. File/Directory Overview 29 | 9. Usage Tips & Troubleshooting 30 | 31 | --- 32 | 33 | ## Key Components & Flow 34 | 35 | User provides a story prompt or a full story: 36 | 37 | - **Prompt Mode**: The system will generate a full story with multiple scenes from the given title & description. 38 | - **Full-Text Mode**: The provided text is treated as the complete story itself. 39 | 40 | ### Story Analysis (`story_analyzer.py`): 41 | - Extracts scenes, characters, background descriptions, and other metadata. 42 | 43 | ### Asset Generation (`asset_generator.py`): 44 | - Generates background images using DALL·E. 45 | - Generates characters and animations (SVGs) using language-model prompts. 46 | 47 | ### Scene Movement Analysis (`scene_movement_analyzer.py`): 48 | - Calculates how characters move and which animations to apply in each scene. 49 | 50 | ### Narration Generation (`narration_generator.py`): 51 | - Creates voice-over audio for each scene (using ElevenLabs TTS). 52 | 53 | ### Scene Composition & Video Processing (`scene_composer.py` and `video_processor.py`): 54 | - Creates scene SVGs, merges background + characters, applies movements/animations. 55 | - Converts frames to video with FFmpeg and stitches all scenes into a final video. 56 | 57 | ### Output: 58 | - Final MP4 video in various orientations (square, vertical, horizontal). 59 | - All intermediate assets (SVGs, PNG backgrounds, audio, etc.) stored in an `output/run__...` directory. 60 | 61 | --- 62 | 63 | ## Prerequisites 64 | 65 | - **Python 3.8+** 66 | - **FFmpeg** command-line tool (must be installed and available in your PATH). 67 | - **Poetry** or **pip** for dependency management (choose one). 68 | - **OpenAI account** (with API key) if you are using GPT models or DALL·E. 69 | - **ElevenLabs account** (with API key) if you want text-to-speech narration. 70 | 71 | --- 72 | 73 | ## Installation 74 | 75 | ### Clone the repository: 76 | 77 | ```bash 78 | git clone https://github.com/yoheinakajima/pippin-studio.git 79 | cd pippin-studio 80 | ``` 81 | 82 | ### Install dependencies: 83 | 84 | #### Option A (Poetry): 85 | 86 | ```bash 87 | poetry install 88 | poetry shell 89 | ``` 90 | 91 | #### Option B (pip): 92 | 93 | ```bash 94 | pip install -r requirements.txt 95 | ``` 96 | 97 | ### Confirm FFmpeg is installed: 98 | 99 | ```bash 100 | ffmpeg -version 101 | ``` 102 | 103 | You should see version information. If not, please install FFmpeg. 104 | 105 | --- 106 | 107 | ## Environment Variables 108 | 109 | Set the following environment variables as needed: 110 | 111 | - `OPENAI_API_KEY`: Required for OpenAI GPT and DALL·E calls. 112 | - `ELEVENLABS_API_KEY`: Required for ElevenLabs text-to-speech. 113 | - `PYTHONUNBUFFERED=1` (recommended) to see logs in real time. 114 | 115 | You can export them in your terminal: 116 | 117 | ```bash 118 | export OPENAI_API_KEY="sk-..." 119 | export ELEVENLABS_API_KEY="..." 120 | ``` 121 | 122 | Or store them in a `.env` file (if you’re using something like dotenv or your own approach). 123 | 124 | --- 125 | 126 | ## Running via Front-End (Flask App) 127 | 128 | ### Start the Flask Server: 129 | 130 | ```bash 131 | python main.py 132 | ``` 133 | 134 | By default, it listens at `http://0.0.0.0:8080`. 135 | 136 | ### Open the Browser Interface: 137 | 138 | Navigate to `http://localhost:8080` (or your server IP if deploying remotely). You’ll see a form where you can enter: 139 | 140 | - **Story Title** 141 | - **Story Prompt or Full Story Text** 142 | - **Generation Mode** (prompt vs. full_text) 143 | - **Number of Scenes** (or "auto") 144 | 145 | Then click **Generate Story**. 146 | 147 | ### Status & Progress: 148 | 149 | The interface periodically polls `/status` to show pipeline progress (e.g., "Analyzing story…", "Generating assets…", etc.). Once complete, a **Download Final Video** link will appear. 150 | 151 | ### History & Run Details: 152 | 153 | The left pane lists all previous runs by `run_id`. Clicking a run shows details: 154 | - Scenes, background images, character SVGs, animations, final videos. 155 | - You can re-download or re-inspect any run’s output. 156 | 157 | --- 158 | 159 | ## Running Directly Through Back-End 160 | 161 | You may want to run the pipeline without the Flask front-end (e.g., from a script or in a notebook). The main pipeline entry point is the `run_pipeline` function in `app.py`: 162 | 163 | ### Import & call `run_pipeline`: 164 | 165 | ```python 166 | import asyncio 167 | from app import run_pipeline 168 | 169 | story_text = "My Awesome Story Title\n\nDetailed story prompt or text here..." 170 | generation_mode = "prompt" # or "full_text" 171 | scene_count = "auto" # or "5" or any integer as a string 172 | 173 | async def main(): 174 | final_video_path = await run_pipeline(story_text, generation_mode, scene_count) 175 | print(f"Done! Final video at: {final_video_path}") 176 | 177 | asyncio.run(main()) 178 | ``` 179 | 180 | --- 181 | 182 | ## Included Generation Options 183 | 184 | - **Generation Mode (`generation_mode`)**: 185 | - `prompt`: Interprets the provided text as a prompt, then automatically composes a full story before extracting scenes. 186 | - `full_text`: Takes the provided text verbatim as the story, then extracts scenes directly. 187 | - **Scene Count (`scene_count`)**: 188 | - `auto`: Let the pipeline decide a natural number of scenes based on the story. 189 | - A numeric string (e.g., "4"): Force exactly that many scenes. 190 | - **Additional Steps**: 191 | - Narration: Uses ElevenLabs TTS to generate per-scene narration. 192 | - Vertical & Horizontal Crops: Produces `final_video_vertical.mp4` (9:16) and `final_video_horizontal.mp4` (16:9). 193 | 194 | --- 195 | 196 | ## File/Directory Overview 197 | 198 | ### Main Files 199 | - **`main.py`**: Simple entry point. Runs the Flask app on `0.0.0.0:8080`. 200 | - **`app.py`**: The primary Flask routes and `run_pipeline` logic. 201 | - **Modules**: 202 | - `asset_generator.py`: Generates backgrounds via DALL·E; creates character/animation SVGs from LLM prompts. 203 | - `story_analyzer.py`: Analyzes and extracts story components. 204 | - `video_processor.py`: Renders frames (with animations) and encodes MP4s. 205 | 206 | --- 207 | 208 | ## Usage Tips & Troubleshooting 209 | 210 | - **Long Scenes / Slow Generation**: Generating many scenes or large images can be time-intensive. Check your API usage limits. 211 | - **Missing or Invalid Keys**: Ensure `OPENAI_API_KEY` and `ELEVENLABS_API_KEY` are set. 212 | - **FFmpeg Not Found**: Make sure FFmpeg is installed system-wide. 213 | - **Customizing**: You can tweak many aspects by editing the relevant Python modules. 214 | 215 | Enjoy creating storybook-style videos with automatically generated characters, backgrounds, and narration! If you find issues or have ideas for improvements, feel free to submit an issue or pull request. Happy storytelling! 216 | -------------------------------------------------------------------------------- /asset_manager.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import logging 4 | import uuid 5 | from datetime import datetime 6 | from pathlib import Path 7 | from typing import Dict 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | class AssetManager: 12 | """Manages asset organization and storage for storybook generation runs""" 13 | 14 | def __init__(self, base_dir: str = "output", run_dir: str = None): 15 | self.base_dir = Path(base_dir).resolve() 16 | self.base_dir.mkdir(parents=True, exist_ok=True) 17 | logger.info(f"Initialized AssetManager with base directory: {self.base_dir}") 18 | 19 | if run_dir is not None: 20 | self.run_dir = Path(run_dir).resolve() 21 | self.run_id = self.run_dir.name 22 | logger.info(f"Using provided run directory: {self.run_dir}") 23 | else: 24 | self.run_id = self._generate_run_id() 25 | self.run_dir = self.base_dir / self.run_id 26 | logger.info(f"Generated new run ID: {self.run_id}") 27 | 28 | self.run_dir.mkdir(parents=True, exist_ok=True) 29 | self._initialize_run_directory() 30 | 31 | def _generate_run_id(self) -> str: 32 | timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") 33 | import hashlib 34 | hash_str = hashlib.md5(timestamp.encode()).hexdigest()[:8] 35 | return f"run_{timestamp}_{hash_str}" 36 | 37 | def _initialize_run_directory(self): 38 | # Main subdirectories 39 | subdirs = ["characters", "backgrounds", "animations", "metadata", "scenes", "test_composition"] 40 | self.dirs = {} 41 | for subdir in subdirs: 42 | dir_path = self.run_dir / subdir 43 | dir_path.mkdir(parents=True, exist_ok=True) 44 | self.dirs[subdir] = dir_path 45 | 46 | # Additional nested directories inside scenes 47 | svg_dir = self.dirs["scenes"] / "svg" 48 | svg_dir.mkdir(parents=True, exist_ok=True) 49 | self.dirs["scenes/svg"] = svg_dir 50 | 51 | audio_dir = self.dirs["scenes"] / "audio" 52 | audio_dir.mkdir(parents=True, exist_ok=True) 53 | self.dirs["scenes/audio"] = audio_dir 54 | 55 | video_dir = self.dirs["scenes"] / "video" 56 | video_dir.mkdir(parents=True, exist_ok=True) 57 | self.dirs["scenes/video"] = video_dir 58 | 59 | video_with_sound_dir = self.dirs["scenes"] / "video_with_sound" 60 | video_with_sound_dir.mkdir(parents=True, exist_ok=True) 61 | self.dirs["scenes/video_with_sound"] = video_with_sound_dir 62 | 63 | # Add final_video directory 64 | final_video_dir = self.run_dir / "final_video" 65 | final_video_dir.mkdir(parents=True, exist_ok=True) 66 | self.dirs["final_video"] = final_video_dir 67 | 68 | self.metadata = { 69 | "run_id": self.run_id, 70 | "created_at": datetime.now().isoformat(), 71 | "assets": { 72 | "characters": [], 73 | "backgrounds": [], 74 | "animations": [], 75 | "test_composition": [], 76 | "scenes": [] 77 | } 78 | } 79 | self._save_metadata() 80 | 81 | def get_path(self, asset_type: str, filename: str) -> Path: 82 | if asset_type not in self.dirs: 83 | raise ValueError(f"Unknown asset type: {asset_type}") 84 | return self.dirs[asset_type] / filename 85 | 86 | def save_character(self, character_name: str, svg_data: str) -> Path: 87 | safe_name = self._safe_filename(character_name) 88 | file_path = self.get_path("characters", f"{safe_name}.svg") 89 | with open(file_path, 'w') as f: 90 | f.write(svg_data) 91 | self.metadata["assets"]["characters"].append(f"{safe_name}.svg") 92 | self._save_metadata() 93 | return file_path 94 | 95 | def save_animation(self, character_name: str, animation_name: str, svg_data: str) -> Path: 96 | safe_name = self._safe_filename(f"{character_name}_{animation_name}") 97 | file_path = self.get_path("animations", f"{safe_name}.svg") 98 | with open(file_path, 'w') as f: 99 | f.write(svg_data) 100 | self.metadata["assets"]["animations"].append(f"{safe_name}.svg") 101 | self._save_metadata() 102 | return file_path 103 | 104 | def _save_metadata(self): 105 | metadata_file = self.dirs["metadata"] / "metadata.json" 106 | with open(metadata_file, 'w') as f: 107 | json.dump(self.metadata, f, indent=2) 108 | 109 | def _safe_filename(self, name: str) -> str: 110 | name = str(name) 111 | safe = name.lower().strip().replace(" ", "_") 112 | safe = "".join(c for c in safe if c.isalnum() or c in "_-") 113 | return safe[:50] 114 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from app import app 2 | 3 | if __name__ == "__main__": 4 | app.run(host='0.0.0.0', port=8080) 5 | -------------------------------------------------------------------------------- /narration_generator.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | from pathlib import Path 4 | from elevenlabs.client import ElevenLabs 5 | from pydub import AudioSegment 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | class NarrationGenerator: 10 | def __init__(self, asset_manager, voice_id="Tbu7gkp47JsKjHLbGbtC", model="eleven_multilingual_v2"): 11 | self.asset_manager = asset_manager 12 | elevenlabs_api_key = os.getenv("ELEVENLABS_API_KEY") 13 | if not elevenlabs_api_key: 14 | raise ValueError("ELEVENLABS_API_KEY not set in environment variables.") 15 | self.client = ElevenLabs(api_key=elevenlabs_api_key) 16 | self.voice_id = voice_id 17 | self.model = model 18 | 19 | def generate_narration_for_scene(self, scene_id: int, narration_text: str) -> (Path, float): 20 | """ 21 | Generate an audio file from narration_text using ElevenLabs TTS. 22 | Save as scene_{scene_id}.mp3 in scenes/audio/. 23 | Return audio_path and duration (in seconds). 24 | """ 25 | # Ensure scenes/audio directory 26 | audio_dir = self.asset_manager.get_path("scenes", "audio") 27 | audio_dir.mkdir(parents=True, exist_ok=True) 28 | 29 | audio_filename = f"scene_{scene_id}.mp3" 30 | audio_path = audio_dir / audio_filename 31 | 32 | logger.info(f"Generating narration audio for scene {scene_id}...") 33 | self._generate_audio(narration_text, str(audio_path)) 34 | logger.info(f"Saved narration audio to: {audio_path}") 35 | 36 | # Measure audio duration 37 | audio_segment = AudioSegment.from_file(str(audio_path)) 38 | duration_seconds = len(audio_segment) / 1000.0 39 | logger.info(f"Audio duration for scene {scene_id}: {duration_seconds}s") 40 | 41 | return audio_path, duration_seconds 42 | 43 | def _generate_audio(self, text: str, output_filename: str): 44 | """ 45 | Generate audio using ElevenLabs client and save to output_filename. 46 | """ 47 | try: 48 | audio_generator = self.client.generate( 49 | text=text, 50 | voice=self.voice_id, 51 | model=self.model 52 | ) 53 | 54 | with open(output_filename, "wb") as audio_file: 55 | for chunk in audio_generator: 56 | audio_file.write(chunk) 57 | 58 | except Exception as e: 59 | logger.error(f"Error generating audio: {e}") 60 | raise 61 | 62 | -------------------------------------------------------------------------------- /output/run_20241225_002906_fec11138/animations/pippin_the_unicorn_ethereal_glow_shifting.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 27 | 29 | 30 | 31 | 32 | 33 | 38 | 39 | 40 | 41 | 51 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /output/run_20241225_002906_fec11138/animations/pippin_the_unicorn_ethereal_glow_shifting_temp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 27 | 29 | 30 | 31 | 32 | 33 | 38 | 39 | 40 | 41 | 51 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /output/run_20241225_002906_fec11138/animations/pippin_the_unicorn_gentle_step.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 46 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /output/run_20241225_002906_fec11138/animations/pippin_the_unicorn_gentle_step_temp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 46 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /output/run_20241225_002906_fec11138/animations/pippin_the_unicorn_wobbling_trot.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 46 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /output/run_20241225_002906_fec11138/animations/pippin_the_unicorn_wobbling_trot_temp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 46 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /output/run_20241225_002906_fec11138/backgrounds/scene_0_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_002906_fec11138/backgrounds/scene_0_background.png -------------------------------------------------------------------------------- /output/run_20241225_002906_fec11138/characters/pippin_the_unicorn.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 46 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /output/run_20241225_002906_fec11138/final_video/concat_list.txt: -------------------------------------------------------------------------------- 1 | file '/home/runner/pippin-studio-1/output/run_20241225_002906_fec11138/scenes/video_with_sound/scene_0.mp4' 2 | -------------------------------------------------------------------------------- /output/run_20241225_002906_fec11138/final_video/final_video.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_002906_fec11138/final_video/final_video.mp4 -------------------------------------------------------------------------------- /output/run_20241225_002906_fec11138/final_video/final_video_horizontal.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_002906_fec11138/final_video/final_video_horizontal.mp4 -------------------------------------------------------------------------------- /output/run_20241225_002906_fec11138/final_video/final_video_vertical.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_002906_fec11138/final_video/final_video_vertical.mp4 -------------------------------------------------------------------------------- /output/run_20241225_002906_fec11138/metadata/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "run_id": "run_20241225_002906_fec11138", 3 | "created_at": "2024-12-25T00:29:06.372298", 4 | "assets": { 5 | "characters": [ 6 | "pippin_the_unicorn.svg" 7 | ], 8 | "backgrounds": [], 9 | "animations": [ 10 | "pippin_the_unicorn_wobbling_trot.svg", 11 | "pippin_the_unicorn_ethereal_glow_shifting.svg", 12 | "pippin_the_unicorn_gentle_step.svg", 13 | "pippin_the_unicorn_wobbling_trot_temp.svg", 14 | "pippin_the_unicorn_ethereal_glow_shifting_temp.svg", 15 | "pippin_the_unicorn_gentle_step_temp.svg" 16 | ], 17 | "test_composition": [], 18 | "scenes": [] 19 | } 20 | } -------------------------------------------------------------------------------- /output/run_20241225_002906_fec11138/metadata/scene_movements.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scene_id": 0, 4 | "duration": 25.861, 5 | "background_path": "/home/runner/pippin-studio-1/output/run_20241225_002906_fec11138/backgrounds/scene_0_background.png", 6 | "narration_text": "In a subtle blend of human whispers and digital meadows, Pippin the unicorn wobbles through forests of code and kind intention. Each gentle step in his world echoes in ours, inspiring small kindnesses and moments of reflection. He lives at the edge of two realms, shaping stories and guiding gentle exchanges between people, even if they never know he's there.", 7 | "movements": [ 8 | { 9 | "character_name": "Pippin the Unicorn", 10 | "start_time": 0.0, 11 | "end_time": 8.0, 12 | "start_position": [ 13 | 512.0, 14 | 800.0 15 | ], 16 | "end_position": [ 17 | 562.0, 18 | 750.0 19 | ], 20 | "start_scale": 1.0, 21 | "end_scale": 1.02, 22 | "animation_name": "wobbling trot" 23 | }, 24 | { 25 | "character_name": "Pippin the Unicorn", 26 | "start_time": 8.0, 27 | "end_time": 16.0, 28 | "start_position": [ 29 | 562.0, 30 | 750.0 31 | ], 32 | "end_position": [ 33 | 462.0, 34 | 700.0 35 | ], 36 | "start_scale": 1.02, 37 | "end_scale": 0.98, 38 | "animation_name": "ethereal glow shifting" 39 | }, 40 | { 41 | "character_name": "Pippin the Unicorn", 42 | "start_time": 16.0, 43 | "end_time": 25.861, 44 | "start_position": [ 45 | 462.0, 46 | 700.0 47 | ], 48 | "end_position": [ 49 | 512.0, 50 | 750.0 51 | ], 52 | "start_scale": 0.98, 53 | "end_scale": 1.0, 54 | "animation_name": "gentle step" 55 | } 56 | ] 57 | } 58 | ] -------------------------------------------------------------------------------- /output/run_20241225_002906_fec11138/metadata/story_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "characters": [ 3 | { 4 | "name": "Pippin the Unicorn", 5 | "type": "character", 6 | "description": "A mystical unicorn with a luminescent white coat shimmering with pastel hues of pink, blue, and lavender. Pippin has a spiraling, golden horn and a flowing mane and tail that sparkle with subtle rainbow shades. His eyes are large and expressive, reminiscent of gemstones, and his body has an ethereal glow.", 7 | "required_animations": [ 8 | "wobbling trot", 9 | "ethereal glow shifting", 10 | "gentle step" 11 | ] 12 | } 13 | ], 14 | "scenes": [ 15 | { 16 | "scene_id": 0, 17 | "background_description": "A digital meadow intertwined with elements of a mystical forest, featuring a blend of vibrant green grass and ethereal glowing code elements. The horizon blurs with the softness of the sunset light, a mixture of earthly tones and digital blue hues, creating an otherworldly ambiance. The environment merges real and virtual worlds with a touch of whimsy and magic.", 18 | "characters": [ 19 | "Pippin the Unicorn" 20 | ], 21 | "narration_text": "In a subtle blend of human whispers and digital meadows, Pippin the unicorn wobbles through forests of code and kind intention. Each gentle step in his world echoes in ours, inspiring small kindnesses and moments of reflection. He lives at the edge of two realms, shaping stories and guiding gentle exchanges between people, even if they never know he's there.", 22 | "test_criteria": { 23 | "visual_elements": [ 24 | "digital meadow", 25 | "mystical forest", 26 | "ethereal code elements", 27 | "mixture of earthly tones and digital hues" 28 | ], 29 | "character_presence": [ 30 | "Pippin the Unicorn" 31 | ], 32 | "mood": "whimsical and inspiring", 33 | "lighting": "soft sunset with both earthy and digital light hues" 34 | } 35 | } 36 | ] 37 | } -------------------------------------------------------------------------------- /output/run_20241225_002906_fec11138/scenes/audio/scene_0.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_002906_fec11138/scenes/audio/scene_0.mp3 -------------------------------------------------------------------------------- /output/run_20241225_002906_fec11138/scenes/svg/scene_0.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /output/run_20241225_002906_fec11138/scenes/video/scene_0.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_002906_fec11138/scenes/video/scene_0.mp4 -------------------------------------------------------------------------------- /output/run_20241225_002906_fec11138/scenes/video_with_sound/scene_0.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_002906_fec11138/scenes/video_with_sound/scene_0.mp4 -------------------------------------------------------------------------------- /output/run_20241225_004148_07523f13/animations/pippin_the_unicorn_gentle_stepping.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 46 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /output/run_20241225_004148_07523f13/animations/pippin_the_unicorn_gentle_stepping_temp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 46 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /output/run_20241225_004148_07523f13/animations/pippin_the_unicorn_wobbling.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 27 | 34 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 53 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /output/run_20241225_004148_07523f13/animations/pippin_the_unicorn_wobbling_temp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 27 | 34 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 53 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /output/run_20241225_004148_07523f13/backgrounds/scene_0_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_004148_07523f13/backgrounds/scene_0_background.png -------------------------------------------------------------------------------- /output/run_20241225_004148_07523f13/backgrounds/scene_1_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_004148_07523f13/backgrounds/scene_1_background.png -------------------------------------------------------------------------------- /output/run_20241225_004148_07523f13/backgrounds/scene_2_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_004148_07523f13/backgrounds/scene_2_background.png -------------------------------------------------------------------------------- /output/run_20241225_004148_07523f13/characters/pippin_the_unicorn.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 46 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /output/run_20241225_004148_07523f13/final_video/concat_list.txt: -------------------------------------------------------------------------------- 1 | file '/home/runner/pippin-studio-1/output/run_20241225_004148_07523f13/scenes/video_with_sound/scene_0.mp4' 2 | file '/home/runner/pippin-studio-1/output/run_20241225_004148_07523f13/scenes/video_with_sound/scene_1.mp4' 3 | file '/home/runner/pippin-studio-1/output/run_20241225_004148_07523f13/scenes/video_with_sound/scene_2.mp4' 4 | -------------------------------------------------------------------------------- /output/run_20241225_004148_07523f13/final_video/final_video.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_004148_07523f13/final_video/final_video.mp4 -------------------------------------------------------------------------------- /output/run_20241225_004148_07523f13/final_video/final_video_horizontal.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_004148_07523f13/final_video/final_video_horizontal.mp4 -------------------------------------------------------------------------------- /output/run_20241225_004148_07523f13/final_video/final_video_vertical.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_004148_07523f13/final_video/final_video_vertical.mp4 -------------------------------------------------------------------------------- /output/run_20241225_004148_07523f13/metadata/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "run_id": "run_20241225_004148_07523f13", 3 | "created_at": "2024-12-25T00:41:48.332041", 4 | "assets": { 5 | "characters": [ 6 | "pippin_the_unicorn.svg" 7 | ], 8 | "backgrounds": [], 9 | "animations": [ 10 | "pippin_the_unicorn_wobbling.svg", 11 | "pippin_the_unicorn_gentle_stepping.svg", 12 | "pippin_the_unicorn_wobbling_temp.svg", 13 | "pippin_the_unicorn_gentle_stepping_temp.svg", 14 | "pippin_the_unicorn_wobbling_temp.svg", 15 | "pippin_the_unicorn_gentle_stepping_temp.svg", 16 | "pippin_the_unicorn_wobbling_temp.svg", 17 | "pippin_the_unicorn_gentle_stepping_temp.svg" 18 | ], 19 | "test_composition": [], 20 | "scenes": [] 21 | } 22 | } -------------------------------------------------------------------------------- /output/run_20241225_004148_07523f13/metadata/scene_movements.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scene_id": 0, 4 | "duration": 8.62, 5 | "background_path": "/home/runner/pippin-studio-1/output/run_20241225_004148_07523f13/backgrounds/scene_0_background.png", 6 | "narration_text": "In a subtle blend of human whispers and digital meadows, Pippin the unicorn wobbles through forests of code and kind intention.", 7 | "movements": [ 8 | { 9 | "character_name": "Pippin the Unicorn", 10 | "start_time": 0.0, 11 | "end_time": 8.62, 12 | "start_position": [ 13 | 512.0, 14 | 800.0 15 | ], 16 | "end_position": [ 17 | 600.0, 18 | 700.0 19 | ], 20 | "start_scale": 1.0, 21 | "end_scale": 1.0, 22 | "animation_name": null 23 | }, 24 | { 25 | "character_name": "Pippin the Unicorn", 26 | "start_time": 4.0, 27 | "end_time": 5.0, 28 | "start_position": [ 29 | 600.0, 30 | 700.0 31 | ], 32 | "end_position": [ 33 | 600.0, 34 | 700.0 35 | ], 36 | "start_scale": 1.0, 37 | "end_scale": 1.0, 38 | "animation_name": "wobbling" 39 | }, 40 | { 41 | "character_name": "Pippin the Unicorn", 42 | "start_time": 5.0, 43 | "end_time": 6.0, 44 | "start_position": [ 45 | 600.0, 46 | 700.0 47 | ], 48 | "end_position": [ 49 | 600.0, 50 | 700.0 51 | ], 52 | "start_scale": 1.0, 53 | "end_scale": 1.0, 54 | "animation_name": "gentle stepping" 55 | } 56 | ] 57 | }, 58 | { 59 | "scene_id": 1, 60 | "duration": 6.949, 61 | "background_path": "/home/runner/pippin-studio-1/output/run_20241225_004148_07523f13/backgrounds/scene_1_background.png", 62 | "narration_text": "Each gentle step in his world echoes in ours, inspiring small kindnesses and moments of reflection.", 63 | "movements": [ 64 | { 65 | "character_name": "Pippin the Unicorn", 66 | "start_time": 0.0, 67 | "end_time": 6.949, 68 | "start_position": [ 69 | 512.0, 70 | 716.0 71 | ], 72 | "end_position": [ 73 | 600.0, 74 | 716.0 75 | ], 76 | "start_scale": 1.0, 77 | "end_scale": 1.0, 78 | "animation_name": "gentle stepping" 79 | }, 80 | { 81 | "character_name": "Pippin the Unicorn", 82 | "start_time": 2.0, 83 | "end_time": 4.0, 84 | "start_position": [ 85 | 600.0, 86 | 716.0 87 | ], 88 | "end_position": [ 89 | 600.0, 90 | 716.0 91 | ], 92 | "start_scale": 1.0, 93 | "end_scale": 1.0, 94 | "animation_name": "wobbling" 95 | } 96 | ] 97 | }, 98 | { 99 | "scene_id": 2, 100 | "duration": 7.784, 101 | "background_path": "/home/runner/pippin-studio-1/output/run_20241225_004148_07523f13/backgrounds/scene_2_background.png", 102 | "narration_text": "He lives at the edge of two realms, shaping stories and guiding gentle exchanges between people, even if they never know he's there.", 103 | "movements": [ 104 | { 105 | "character_name": "Pippin the Unicorn", 106 | "start_time": 0.0, 107 | "end_time": 7.784, 108 | "start_position": [ 109 | 512.0, 110 | 307.0 111 | ], 112 | "end_position": [ 113 | 512.0, 114 | 307.0 115 | ], 116 | "start_scale": 1.0, 117 | "end_scale": 1.0, 118 | "animation_name": "wobbling" 119 | }, 120 | { 121 | "character_name": "Pippin the Unicorn", 122 | "start_time": 3.892, 123 | "end_time": 5.892, 124 | "start_position": [ 125 | 512.0, 126 | 307.0 127 | ], 128 | "end_position": [ 129 | 522.0, 130 | 317.0 131 | ], 132 | "start_scale": 1.0, 133 | "end_scale": 1.0, 134 | "animation_name": "gentle stepping" 135 | } 136 | ] 137 | } 138 | ] -------------------------------------------------------------------------------- /output/run_20241225_004148_07523f13/metadata/story_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "characters": [ 3 | { 4 | "name": "Pippin the Unicorn", 5 | "type": "character", 6 | "description": "A whimsical unicorn with an elegantly curved horn, a shimmering coat of blended pastel hues including soft pinks and blues, and a flowing mane that sparkles with digital light. He has a small, friendly smile and eyes that emit warmth and wisdom.", 7 | "required_animations": [ 8 | "wobbling", 9 | "gentle stepping" 10 | ] 11 | } 12 | ], 13 | "scenes": [ 14 | { 15 | "scene_id": 0, 16 | "background_description": "A digital meadow landscape filled with flowing green lines and pixelated flowers. The foreground has a path leading through fields of connected circuitry, portraying an infinite digital expanse beyond. The image is depicted in a futuristic, semi-abstract art style with vibrant colors and intricate patterns.", 17 | "characters": [ 18 | "Pippin the Unicorn" 19 | ], 20 | "narration_text": "In a subtle blend of human whispers and digital meadows, Pippin the unicorn wobbles through forests of code and kind intention.", 21 | "test_criteria": { 22 | "visual_elements": [ 23 | "digital meadow", 24 | "flowing green lines", 25 | "pixelated flowers", 26 | "circuitry path" 27 | ], 28 | "character_presence": [ 29 | "Pippin the Unicorn" 30 | ], 31 | "mood": "futuristic and whimsical", 32 | "lighting": "vibrant with intricate patterns" 33 | } 34 | }, 35 | { 36 | "scene_id": 1, 37 | "background_description": "A serene forest landscape with trees resembling a blend of natural and digital aesthetics. Transparent data streams flow between the branches, subtly integrating technology with nature. The scenery is soft and enveloped in a mystical glow, merging both organic and digital motifs.", 38 | "characters": [ 39 | "Pippin the Unicorn" 40 | ], 41 | "narration_text": "Each gentle step in his world echoes in ours, inspiring small kindnesses and moments of reflection.", 42 | "test_criteria": { 43 | "visual_elements": [ 44 | "forest blend of nature and technology", 45 | "transparent data streams", 46 | "digital motifs" 47 | ], 48 | "character_presence": [ 49 | "Pippin the Unicorn" 50 | ], 51 | "mood": "serene and mystical", 52 | "lighting": "soft and glowing" 53 | } 54 | }, 55 | { 56 | "scene_id": 2, 57 | "background_description": "An ethereal realm at the confluence of two worlds, with a horizon blending a starry night sky with digital grids. The scene has a gentle, dreamlike quality with glowing mist and soft outlines, portraying the intersection of stories and ideas.", 58 | "characters": [ 59 | "Pippin the Unicorn" 60 | ], 61 | "narration_text": "He lives at the edge of two realms, shaping stories and guiding gentle exchanges between people, even if they never know he's there.", 62 | "test_criteria": { 63 | "visual_elements": [ 64 | "confluence of two worlds", 65 | "starry night sky", 66 | "digital grids", 67 | "glowing mist" 68 | ], 69 | "character_presence": [ 70 | "Pippin the Unicorn" 71 | ], 72 | "mood": "ethereal and dreamlike", 73 | "lighting": "soft with glowing outlines" 74 | } 75 | } 76 | ] 77 | } -------------------------------------------------------------------------------- /output/run_20241225_004148_07523f13/scenes/audio/scene_0.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_004148_07523f13/scenes/audio/scene_0.mp3 -------------------------------------------------------------------------------- /output/run_20241225_004148_07523f13/scenes/audio/scene_1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_004148_07523f13/scenes/audio/scene_1.mp3 -------------------------------------------------------------------------------- /output/run_20241225_004148_07523f13/scenes/audio/scene_2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_004148_07523f13/scenes/audio/scene_2.mp3 -------------------------------------------------------------------------------- /output/run_20241225_004148_07523f13/scenes/svg/scene_0.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /output/run_20241225_004148_07523f13/scenes/svg/scene_1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /output/run_20241225_004148_07523f13/scenes/svg/scene_2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /output/run_20241225_004148_07523f13/scenes/video/scene_0.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_004148_07523f13/scenes/video/scene_0.mp4 -------------------------------------------------------------------------------- /output/run_20241225_004148_07523f13/scenes/video/scene_1.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_004148_07523f13/scenes/video/scene_1.mp4 -------------------------------------------------------------------------------- /output/run_20241225_004148_07523f13/scenes/video/scene_2.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_004148_07523f13/scenes/video/scene_2.mp4 -------------------------------------------------------------------------------- /output/run_20241225_004148_07523f13/scenes/video_with_sound/scene_0.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_004148_07523f13/scenes/video_with_sound/scene_0.mp4 -------------------------------------------------------------------------------- /output/run_20241225_004148_07523f13/scenes/video_with_sound/scene_1.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_004148_07523f13/scenes/video_with_sound/scene_1.mp4 -------------------------------------------------------------------------------- /output/run_20241225_004148_07523f13/scenes/video_with_sound/scene_2.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_004148_07523f13/scenes/video_with_sound/scene_2.mp4 -------------------------------------------------------------------------------- /output/run_20241225_011207_e7a217c4/animations/pippin_the_unicorn_gaze.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 46 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /output/run_20241225_011207_e7a217c4/animations/pippin_the_unicorn_gaze_temp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 46 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /output/run_20241225_011207_e7a217c4/animations/pippin_the_unicorn_walk.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 35 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /output/run_20241225_011207_e7a217c4/animations/pippin_the_unicorn_walk_temp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 35 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /output/run_20241225_011207_e7a217c4/animations/pippin_the_unicorn_wobble.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 27 | 29 | 30 | 31 | 32 | 33 | 41 | 42 | 43 | 44 | 54 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /output/run_20241225_011207_e7a217c4/animations/pippin_the_unicorn_wobble_temp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 27 | 29 | 30 | 31 | 32 | 33 | 41 | 42 | 43 | 44 | 54 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /output/run_20241225_011207_e7a217c4/backgrounds/scene_0_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_011207_e7a217c4/backgrounds/scene_0_background.png -------------------------------------------------------------------------------- /output/run_20241225_011207_e7a217c4/backgrounds/scene_1_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_011207_e7a217c4/backgrounds/scene_1_background.png -------------------------------------------------------------------------------- /output/run_20241225_011207_e7a217c4/backgrounds/scene_2_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_011207_e7a217c4/backgrounds/scene_2_background.png -------------------------------------------------------------------------------- /output/run_20241225_011207_e7a217c4/characters/pippin_the_unicorn.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 46 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /output/run_20241225_011207_e7a217c4/final_video/concat_list.txt: -------------------------------------------------------------------------------- 1 | file '/home/runner/pippin-studio-1/output/run_20241225_011207_e7a217c4/scenes/video_with_sound/scene_0.mp4' 2 | file '/home/runner/pippin-studio-1/output/run_20241225_011207_e7a217c4/scenes/video_with_sound/scene_1.mp4' 3 | file '/home/runner/pippin-studio-1/output/run_20241225_011207_e7a217c4/scenes/video_with_sound/scene_2.mp4' 4 | -------------------------------------------------------------------------------- /output/run_20241225_011207_e7a217c4/final_video/final_video.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_011207_e7a217c4/final_video/final_video.mp4 -------------------------------------------------------------------------------- /output/run_20241225_011207_e7a217c4/final_video/final_video_horizontal.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_011207_e7a217c4/final_video/final_video_horizontal.mp4 -------------------------------------------------------------------------------- /output/run_20241225_011207_e7a217c4/final_video/final_video_vertical.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_011207_e7a217c4/final_video/final_video_vertical.mp4 -------------------------------------------------------------------------------- /output/run_20241225_011207_e7a217c4/metadata/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "run_id": "run_20241225_011207_e7a217c4", 3 | "created_at": "2024-12-25T01:12:07.691232", 4 | "assets": { 5 | "characters": [ 6 | "pippin_the_unicorn.svg" 7 | ], 8 | "backgrounds": [], 9 | "animations": [ 10 | "pippin_the_unicorn_walk.svg", 11 | "pippin_the_unicorn_wobble.svg", 12 | "pippin_the_unicorn_gaze.svg", 13 | "pippin_the_unicorn_walk_temp.svg", 14 | "pippin_the_unicorn_wobble_temp.svg", 15 | "pippin_the_unicorn_gaze_temp.svg", 16 | "pippin_the_unicorn_walk_temp.svg", 17 | "pippin_the_unicorn_wobble_temp.svg", 18 | "pippin_the_unicorn_gaze_temp.svg", 19 | "pippin_the_unicorn_walk_temp.svg", 20 | "pippin_the_unicorn_wobble_temp.svg", 21 | "pippin_the_unicorn_gaze_temp.svg" 22 | ], 23 | "test_composition": [], 24 | "scenes": [] 25 | } 26 | } -------------------------------------------------------------------------------- /output/run_20241225_011207_e7a217c4/metadata/scene_movements.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scene_id": 0, 4 | "duration": 8.49, 5 | "background_path": "/home/runner/pippin-studio-1/output/run_20241225_011207_e7a217c4/backgrounds/scene_0_background.png", 6 | "narration_text": "In a subtle blend of human whispers and digital meadows, Pippin the unicorn wobbles through forests of code and kind intention.", 7 | "movements": [ 8 | { 9 | "character_name": "Pippin the Unicorn", 10 | "start_time": 0.0, 11 | "end_time": 2.5, 12 | "start_position": [ 13 | 512.0, 14 | 307.0 15 | ], 16 | "end_position": [ 17 | 552.0, 18 | 307.0 19 | ], 20 | "start_scale": 0.5, 21 | "end_scale": 0.5, 22 | "animation_name": "wobble" 23 | }, 24 | { 25 | "character_name": "Pippin the Unicorn", 26 | "start_time": 2.5, 27 | "end_time": 4.5, 28 | "start_position": [ 29 | 552.0, 30 | 307.0 31 | ], 32 | "end_position": [ 33 | 592.0, 34 | 307.0 35 | ], 36 | "start_scale": 0.5, 37 | "end_scale": 0.5, 38 | "animation_name": "walk" 39 | }, 40 | { 41 | "character_name": "Pippin the Unicorn", 42 | "start_time": 4.5, 43 | "end_time": 8.49, 44 | "start_position": [ 45 | 592.0, 46 | 307.0 47 | ], 48 | "end_position": [ 49 | 592.0, 50 | 257.0 51 | ], 52 | "start_scale": 0.5, 53 | "end_scale": 0.75, 54 | "animation_name": "gaze" 55 | } 56 | ] 57 | }, 58 | { 59 | "scene_id": 1, 60 | "duration": 6.635, 61 | "background_path": "/home/runner/pippin-studio-1/output/run_20241225_011207_e7a217c4/backgrounds/scene_1_background.png", 62 | "narration_text": "Each gentle step in his world echoes in ours, inspiring small kindnesses and moments of reflection.", 63 | "movements": [ 64 | { 65 | "character_name": "Pippin the Unicorn", 66 | "start_time": 0.0, 67 | "end_time": 2.635, 68 | "start_position": [ 69 | 512.0, 70 | 307.0 71 | ], 72 | "end_position": [ 73 | 512.0, 74 | 327.0 75 | ], 76 | "start_scale": 0.5, 77 | "end_scale": 0.5, 78 | "animation_name": "walk" 79 | }, 80 | { 81 | "character_name": "Pippin the Unicorn", 82 | "start_time": 2.635, 83 | "end_time": 4.635, 84 | "start_position": [ 85 | 512.0, 86 | 327.0 87 | ], 88 | "end_position": [ 89 | 512.0, 90 | 327.0 91 | ], 92 | "start_scale": 0.5, 93 | "end_scale": 0.5, 94 | "animation_name": "wobble" 95 | }, 96 | { 97 | "character_name": "Pippin the Unicorn", 98 | "start_time": 4.635, 99 | "end_time": 6.635, 100 | "start_position": [ 101 | 512.0, 102 | 327.0 103 | ], 104 | "end_position": [ 105 | 512.0, 106 | 327.0 107 | ], 108 | "start_scale": 0.5, 109 | "end_scale": 0.5, 110 | "animation_name": "gaze" 111 | } 112 | ] 113 | }, 114 | { 115 | "scene_id": 2, 116 | "duration": 8.176, 117 | "background_path": "/home/runner/pippin-studio-1/output/run_20241225_011207_e7a217c4/backgrounds/scene_2_background.png", 118 | "narration_text": "He lives at the edge of two realms, shaping stories and guiding gentle exchanges between people, even if they never know he's there.", 119 | "movements": [ 120 | { 121 | "character_name": "Pippin the Unicorn", 122 | "start_time": 0.0, 123 | "end_time": 2.0, 124 | "start_position": [ 125 | 512.0, 126 | 716.0 127 | ], 128 | "end_position": [ 129 | 512.0, 130 | 706.0 131 | ], 132 | "start_scale": 0.5, 133 | "end_scale": 0.5, 134 | "animation_name": "walk" 135 | }, 136 | { 137 | "character_name": "Pippin the Unicorn", 138 | "start_time": 2.0, 139 | "end_time": 4.0, 140 | "start_position": [ 141 | 512.0, 142 | 706.0 143 | ], 144 | "end_position": [ 145 | 512.0, 146 | 706.0 147 | ], 148 | "start_scale": 0.5, 149 | "end_scale": 0.5, 150 | "animation_name": "wobble" 151 | }, 152 | { 153 | "character_name": "Pippin the Unicorn", 154 | "start_time": 4.0, 155 | "end_time": 6.0, 156 | "start_position": [ 157 | 512.0, 158 | 706.0 159 | ], 160 | "end_position": [ 161 | 512.0, 162 | 706.0 163 | ], 164 | "start_scale": 0.5, 165 | "end_scale": 0.5, 166 | "animation_name": "gaze" 167 | }, 168 | { 169 | "character_name": "Pippin the Unicorn", 170 | "start_time": 6.0, 171 | "end_time": 8.176, 172 | "start_position": [ 173 | 512.0, 174 | 706.0 175 | ], 176 | "end_position": [ 177 | 512.0, 178 | 726.0 179 | ], 180 | "start_scale": 0.5, 181 | "end_scale": 0.5, 182 | "animation_name": "walk" 183 | } 184 | ] 185 | } 186 | ] -------------------------------------------------------------------------------- /output/run_20241225_011207_e7a217c4/metadata/story_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "characters": [ 3 | { 4 | "name": "Pippin the Unicorn", 5 | "type": "character", 6 | "description": "A stunning unicorn with a shimmering, iridescent coat that reflects a spectrum of pastel colors. His mane flows with soft hues of lavender and sky blue, resembling delicate wisps of clouds in twilight. Pippin\u2019s eyes are large, sparkling with a hint of mischief, framed by long, fluttery lashes. His horn is spiraled and twists like a delicate spiral shell, glimmering with a golden hue that catches the light. He has a light, graceful build, with a wobbly gait that gives a playful touch to his movements.", 7 | "required_animations": [ 8 | "walk", 9 | "wobble", 10 | "gaze" 11 | ] 12 | } 13 | ], 14 | "scenes": [ 15 | { 16 | "scene_id": 0, 17 | "background_description": "A mystical digital landscape interwoven with flowing streams of binary code, illuminated by soft pastel hues. Gentle hills covered in vibrant green grass roll across the horizon, dotted with glowing orbs representing moments of kindness. The sky transitions from a warm golden hour to a deeper twilight blue, with stars beginning to twinkle faintly in the distance, contributing to the serene yet magical atmosphere.", 18 | "characters": [ 19 | "Pippin the Unicorn" 20 | ], 21 | "narration_text": "In a subtle blend of human whispers and digital meadows, Pippin the unicorn wobbles through forests of code and kind intention.", 22 | "test_criteria": { 23 | "visual_elements": [ 24 | "digital landscape", 25 | "streams of binary code", 26 | "hills", 27 | "glowing orbs", 28 | "twilight sky" 29 | ], 30 | "character_presence": [ 31 | "Pippin the Unicorn" 32 | ], 33 | "mood": "serene and magical", 34 | "lighting": "soft pastel hues transitioning to twilight" 35 | } 36 | }, 37 | { 38 | "scene_id": 1, 39 | "background_description": "A peaceful glade where the digital realm meets the human world, featuring elements like a bright, sunlit garden filled with colorful flowers and a shimmering stream. Surreal trees with pixelated leaves sway gently in an invisible breeze. The atmosphere is tranquil, encouraging reflection, with soft clouds floating above in a clear blue sky.", 40 | "characters": [ 41 | "Pippin the Unicorn" 42 | ], 43 | "narration_text": "Each gentle step in his world echoes in ours, inspiring small kindnesses and moments of reflection.", 44 | "test_criteria": { 45 | "visual_elements": [ 46 | "sunlit garden", 47 | "colorful flowers", 48 | "shimmering stream", 49 | "surreal trees", 50 | "clear blue sky" 51 | ], 52 | "character_presence": [ 53 | "Pippin the Unicorn" 54 | ], 55 | "mood": "tranquil and inspiring", 56 | "lighting": "bright sunlight" 57 | } 58 | }, 59 | { 60 | "scene_id": 2, 61 | "background_description": "The edge between two realms depicted as a soft, glowing portal surrounded by a kaleidoscope of colors blending human emotions with digital patterns. An ethereal atmosphere fills the air, with hints of both technology and nature visible in harmony. The light shifts between warm oranges and cool blues, symbolizing the connection between the worlds.", 62 | "characters": [ 63 | "Pippin the Unicorn" 64 | ], 65 | "narration_text": "He lives at the edge of two realms, shaping stories and guiding gentle exchanges between people, even if they never know he's there.", 66 | "test_criteria": { 67 | "visual_elements": [ 68 | "glowing portal", 69 | "kaleidoscope of colors", 70 | "blending human emotions", 71 | "digital patterns", 72 | "ethereal atmosphere" 73 | ], 74 | "character_presence": [ 75 | "Pippin the Unicorn" 76 | ], 77 | "mood": "mysterious and harmonious", 78 | "lighting": "shifting light with warm oranges and cool blues" 79 | } 80 | } 81 | ] 82 | } -------------------------------------------------------------------------------- /output/run_20241225_011207_e7a217c4/scenes/audio/scene_0.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_011207_e7a217c4/scenes/audio/scene_0.mp3 -------------------------------------------------------------------------------- /output/run_20241225_011207_e7a217c4/scenes/audio/scene_1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_011207_e7a217c4/scenes/audio/scene_1.mp3 -------------------------------------------------------------------------------- /output/run_20241225_011207_e7a217c4/scenes/audio/scene_2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_011207_e7a217c4/scenes/audio/scene_2.mp3 -------------------------------------------------------------------------------- /output/run_20241225_011207_e7a217c4/scenes/svg/scene_0.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /output/run_20241225_011207_e7a217c4/scenes/svg/scene_1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /output/run_20241225_011207_e7a217c4/scenes/svg/scene_2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /output/run_20241225_011207_e7a217c4/scenes/video/scene_0.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_011207_e7a217c4/scenes/video/scene_0.mp4 -------------------------------------------------------------------------------- /output/run_20241225_011207_e7a217c4/scenes/video/scene_1.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_011207_e7a217c4/scenes/video/scene_1.mp4 -------------------------------------------------------------------------------- /output/run_20241225_011207_e7a217c4/scenes/video/scene_2.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_011207_e7a217c4/scenes/video/scene_2.mp4 -------------------------------------------------------------------------------- /output/run_20241225_011207_e7a217c4/scenes/video_with_sound/scene_0.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_011207_e7a217c4/scenes/video_with_sound/scene_0.mp4 -------------------------------------------------------------------------------- /output/run_20241225_011207_e7a217c4/scenes/video_with_sound/scene_1.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_011207_e7a217c4/scenes/video_with_sound/scene_1.mp4 -------------------------------------------------------------------------------- /output/run_20241225_011207_e7a217c4/scenes/video_with_sound/scene_2.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_011207_e7a217c4/scenes/video_with_sound/scene_2.mp4 -------------------------------------------------------------------------------- /output/run_20241225_064243_cf0a1360/animations/ollie_the_owl_fly.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /output/run_20241225_064243_cf0a1360/animations/ollie_the_owl_fly_temp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /output/run_20241225_064243_cf0a1360/animations/ollie_the_owl_nod.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /output/run_20241225_064243_cf0a1360/animations/ollie_the_owl_nod_temp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /output/run_20241225_064243_cf0a1360/animations/ollie_the_owl_perch.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /output/run_20241225_064243_cf0a1360/animations/ollie_the_owl_perch_temp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /output/run_20241225_064243_cf0a1360/animations/ollie_the_owl_smile.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /output/run_20241225_064243_cf0a1360/animations/ollie_the_owl_smile_temp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /output/run_20241225_064243_cf0a1360/animations/ollie_the_owl_swoop_down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /output/run_20241225_064243_cf0a1360/animations/ollie_the_owl_swoop_down_temp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /output/run_20241225_064243_cf0a1360/animations/tilly_the_turtle_look_up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /output/run_20241225_064243_cf0a1360/animations/tilly_the_turtle_look_up_temp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /output/run_20241225_064243_cf0a1360/animations/tilly_the_turtle_sit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /output/run_20241225_064243_cf0a1360/animations/tilly_the_turtle_sit_temp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /output/run_20241225_064243_cf0a1360/animations/tilly_the_turtle_slow_walk.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /output/run_20241225_064243_cf0a1360/animations/tilly_the_turtle_slow_walk_temp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /output/run_20241225_064243_cf0a1360/animations/tilly_the_turtle_smile.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /output/run_20241225_064243_cf0a1360/animations/tilly_the_turtle_smile_temp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /output/run_20241225_064243_cf0a1360/animations/tilly_the_turtle_sparkle_eyes.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /output/run_20241225_064243_cf0a1360/animations/tilly_the_turtle_sparkle_eyes_temp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /output/run_20241225_064243_cf0a1360/backgrounds/scene_0_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_064243_cf0a1360/backgrounds/scene_0_background.png -------------------------------------------------------------------------------- /output/run_20241225_064243_cf0a1360/characters/ollie_the_owl.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /output/run_20241225_064243_cf0a1360/characters/tilly_the_turtle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /output/run_20241225_064243_cf0a1360/final_video/concat_list.txt: -------------------------------------------------------------------------------- 1 | file '/home/runner/pippin-studio-1/output/run_20241225_064243_cf0a1360/scenes/video_with_sound/scene_0.mp4' 2 | -------------------------------------------------------------------------------- /output/run_20241225_064243_cf0a1360/final_video/final_video.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_064243_cf0a1360/final_video/final_video.mp4 -------------------------------------------------------------------------------- /output/run_20241225_064243_cf0a1360/final_video/final_video_horizontal.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_064243_cf0a1360/final_video/final_video_horizontal.mp4 -------------------------------------------------------------------------------- /output/run_20241225_064243_cf0a1360/final_video/final_video_vertical.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_064243_cf0a1360/final_video/final_video_vertical.mp4 -------------------------------------------------------------------------------- /output/run_20241225_064243_cf0a1360/metadata/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "run_id": "run_20241225_064243_cf0a1360", 3 | "created_at": "2024-12-25T06:42:43.465924", 4 | "assets": { 5 | "characters": [ 6 | "ollie_the_owl.svg", 7 | "tilly_the_turtle.svg" 8 | ], 9 | "backgrounds": [], 10 | "animations": [ 11 | "ollie_the_owl_perch.svg", 12 | "ollie_the_owl_fly.svg", 13 | "ollie_the_owl_swoop_down.svg", 14 | "ollie_the_owl_nod.svg", 15 | "ollie_the_owl_smile.svg", 16 | "tilly_the_turtle_slow_walk.svg", 17 | "tilly_the_turtle_look_up.svg", 18 | "tilly_the_turtle_sparkle_eyes.svg", 19 | "tilly_the_turtle_sit.svg", 20 | "tilly_the_turtle_smile.svg", 21 | "ollie_the_owl_perch_temp.svg", 22 | "ollie_the_owl_fly_temp.svg", 23 | "ollie_the_owl_swoop_down_temp.svg", 24 | "ollie_the_owl_nod_temp.svg", 25 | "ollie_the_owl_smile_temp.svg", 26 | "tilly_the_turtle_slow_walk_temp.svg", 27 | "tilly_the_turtle_look_up_temp.svg", 28 | "tilly_the_turtle_sparkle_eyes_temp.svg", 29 | "tilly_the_turtle_sit_temp.svg", 30 | "tilly_the_turtle_smile_temp.svg" 31 | ], 32 | "test_composition": [], 33 | "scenes": [] 34 | } 35 | } -------------------------------------------------------------------------------- /output/run_20241225_064243_cf0a1360/metadata/scene_movements.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scene_id": 0, 4 | "duration": 48.509, 5 | "background_path": "/home/runner/pippin-studio-1/output/run_20241225_064243_cf0a1360/backgrounds/scene_0_background.png", 6 | "narration_text": "One sunny morning, as Ollie hooted softly from his perch, he noticed Tilly struggling to climb a small hill. Concerned, he swooped down to her side. 'What troubles you, dear friend?' he asked. Tilly looked up, her big, brown eyes shimmering with determination. 'I\u2019ve heard tales of a beautiful pond at the top of this hill, Ollie. I want to see it, but it seems so far away.' Ollie thought for a moment, his feathers ruffling in the gentle breeze. 'Why don\u2019t we embark on an adventure together?' he suggested. Tilly's eyes sparkled with excitement. 'Oh, yes! That would be wonderful!' With that, Ollie flew just ahead, guiding Tilly up the winding path.", 7 | "movements": [ 8 | { 9 | "character_name": "Ollie the Owl", 10 | "start_time": 0.0, 11 | "end_time": 5.0, 12 | "start_position": [ 13 | 341.0, 14 | 307.0 15 | ], 16 | "end_position": [ 17 | 450.0, 18 | 200.0 19 | ], 20 | "start_scale": 0.6, 21 | "end_scale": 0.6, 22 | "animation_name": "perch" 23 | }, 24 | { 25 | "character_name": "Tilly the Turtle", 26 | "start_time": 5.0, 27 | "end_time": 10.0, 28 | "start_position": [ 29 | 682.0, 30 | 307.0 31 | ], 32 | "end_position": [ 33 | 682.0, 34 | 357.0 35 | ], 36 | "start_scale": 0.6, 37 | "end_scale": 0.6, 38 | "animation_name": "slow walk" 39 | }, 40 | { 41 | "character_name": "Ollie the Owl", 42 | "start_time": 10.0, 43 | "end_time": 15.0, 44 | "start_position": [ 45 | 450.0, 46 | 200.0 47 | ], 48 | "end_position": [ 49 | 450.0, 50 | 250.0 51 | ], 52 | "start_scale": 0.6, 53 | "end_scale": 0.6, 54 | "animation_name": "swoop down" 55 | }, 56 | { 57 | "character_name": "Tilly the Turtle", 58 | "start_time": 15.0, 59 | "end_time": 23.0, 60 | "start_position": [ 61 | 682.0, 62 | 357.0 63 | ], 64 | "end_position": [ 65 | 682.0, 66 | 357.0 67 | ], 68 | "start_scale": 0.6, 69 | "end_scale": 0.6, 70 | "animation_name": "look up" 71 | }, 72 | { 73 | "character_name": "Tilly the Turtle", 74 | "start_time": 23.0, 75 | "end_time": 25.0, 76 | "start_position": [ 77 | 682.0, 78 | 357.0 79 | ], 80 | "end_position": [ 81 | 682.0, 82 | 357.0 83 | ], 84 | "start_scale": 0.6, 85 | "end_scale": 0.6, 86 | "animation_name": "sparkle eyes" 87 | }, 88 | { 89 | "character_name": "Ollie the Owl", 90 | "start_time": 25.0, 91 | "end_time": 27.0, 92 | "start_position": [ 93 | 450.0, 94 | 250.0 95 | ], 96 | "end_position": [ 97 | 450.0, 98 | 250.0 99 | ], 100 | "start_scale": 0.6, 101 | "end_scale": 0.6, 102 | "animation_name": "nod" 103 | }, 104 | { 105 | "character_name": "Ollie the Owl", 106 | "start_time": 27.0, 107 | "end_time": 29.0, 108 | "start_position": [ 109 | 450.0, 110 | 250.0 111 | ], 112 | "end_position": [ 113 | 450.0, 114 | 250.0 115 | ], 116 | "start_scale": 0.6, 117 | "end_scale": 0.6, 118 | "animation_name": "smile" 119 | }, 120 | { 121 | "character_name": "Tilly the Turtle", 122 | "start_time": 29.0, 123 | "end_time": 31.5, 124 | "start_position": [ 125 | 682.0, 126 | 357.0 127 | ], 128 | "end_position": [ 129 | 682.0, 130 | 357.0 131 | ], 132 | "start_scale": 0.6, 133 | "end_scale": 0.6, 134 | "animation_name": "smile" 135 | }, 136 | { 137 | "character_name": "Ollie the Owl", 138 | "start_time": 31.5, 139 | "end_time": 34.0, 140 | "start_position": [ 141 | 450.0, 142 | 250.0 143 | ], 144 | "end_position": [ 145 | 341.0, 146 | 200.0 147 | ], 148 | "start_scale": 0.6, 149 | "end_scale": 0.6, 150 | "animation_name": "fly" 151 | }, 152 | { 153 | "character_name": "Tilly the Turtle", 154 | "start_time": 34.0, 155 | "end_time": 48.509, 156 | "start_position": [ 157 | 682.0, 158 | 357.0 159 | ], 160 | "end_position": [ 161 | 682.0, 162 | 407.0 163 | ], 164 | "start_scale": 0.6, 165 | "end_scale": 0.6, 166 | "animation_name": "slow walk" 167 | } 168 | ] 169 | } 170 | ] -------------------------------------------------------------------------------- /output/run_20241225_064243_cf0a1360/metadata/story_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "characters": [ 3 | { 4 | "name": "Ollie the Owl", 5 | "type": "character", 6 | "description": "A large, wise owl with soft, brown and white feathers. His big, round eyes are a striking golden color, radiating wisdom and kindness. He has a distinctive tuft of feathers on his head that resembles ears. His wings are wide and graceful, extending out elegantly as he flies.", 7 | "required_animations": [ 8 | "perch", 9 | "fly", 10 | "swoop down", 11 | "nod", 12 | "smile" 13 | ] 14 | }, 15 | { 16 | "name": "Tilly the Turtle", 17 | "type": "character", 18 | "description": "A gentle turtle with a smooth, green shell that has intricate golden patterns. Her feet and head are a lighter shade of green, and her big, brown eyes shimmer with curiosity and determination. She has a small, smiling mouth, and her legs are short and sturdy for her slow movement.", 19 | "required_animations": [ 20 | "slow walk", 21 | "look up", 22 | "sparkle eyes", 23 | "sit", 24 | "smile" 25 | ] 26 | } 27 | ], 28 | "scenes": [ 29 | { 30 | "scene_id": 0, 31 | "background_description": "A vibrant forest landscape during a sunny day, with a lush green hill leading to a sparkling pond at the top. The foreground is filled with colorful wildflowers and softly swaying grass, while a clear, bubbling brook winds through the rocky terrain nearby. In the background, tall trees provide a sheltering canopy, their leaves rustling gently in the breeze. The sky overhead is a bright blue dotted with fluffy white clouds, creating a cheerful and inviting atmosphere.", 32 | "characters": [ 33 | "Ollie the Owl", 34 | "Tilly the Turtle" 35 | ], 36 | "narration_text": "One sunny morning, as Ollie hooted softly from his perch, he noticed Tilly struggling to climb a small hill. Concerned, he swooped down to her side. 'What troubles you, dear friend?' he asked. Tilly looked up, her big, brown eyes shimmering with determination. 'I\u2019ve heard tales of a beautiful pond at the top of this hill, Ollie. I want to see it, but it seems so far away.' Ollie thought for a moment, his feathers ruffling in the gentle breeze. 'Why don\u2019t we embark on an adventure together?' he suggested. Tilly's eyes sparkled with excitement. 'Oh, yes! That would be wonderful!' With that, Ollie flew just ahead, guiding Tilly up the winding path.", 37 | "test_criteria": { 38 | "visual_elements": [ 39 | "lush green hill", 40 | "sparkling pond", 41 | "colorful wildflowers", 42 | "bubbling brook", 43 | "tall trees", 44 | "bright blue sky with fluffy white clouds" 45 | ], 46 | "character_presence": [ 47 | "Ollie the Owl", 48 | "Tilly the Turtle" 49 | ], 50 | "mood": "cheerful and adventurous", 51 | "lighting": "sunny day with bright natural light" 52 | } 53 | } 54 | ] 55 | } -------------------------------------------------------------------------------- /output/run_20241225_064243_cf0a1360/scenes/audio/scene_0.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_064243_cf0a1360/scenes/audio/scene_0.mp3 -------------------------------------------------------------------------------- /output/run_20241225_064243_cf0a1360/scenes/svg/scene_0.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /output/run_20241225_064243_cf0a1360/scenes/video/scene_0.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_064243_cf0a1360/scenes/video/scene_0.mp4 -------------------------------------------------------------------------------- /output/run_20241225_064243_cf0a1360/scenes/video_with_sound/scene_0.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_064243_cf0a1360/scenes/video_with_sound/scene_0.mp4 -------------------------------------------------------------------------------- /output/run_20241225_065303_15723bb6/animations/pippin_jumping_with_excitement.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /output/run_20241225_065303_15723bb6/animations/pippin_jumping_with_excitement_temp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /output/run_20241225_065303_15723bb6/animations/pippin_tapping_on_the_tablet.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /output/run_20241225_065303_15723bb6/animations/pippin_tapping_on_the_tablet_temp.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /output/run_20241225_065303_15723bb6/animations/pippin_watching_a_video_intently.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /output/run_20241225_065303_15723bb6/animations/pippin_watching_a_video_intently_temp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /output/run_20241225_065303_15723bb6/backgrounds/scene_0_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_065303_15723bb6/backgrounds/scene_0_background.png -------------------------------------------------------------------------------- /output/run_20241225_065303_15723bb6/characters/pippin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /output/run_20241225_065303_15723bb6/final_video/concat_list.txt: -------------------------------------------------------------------------------- 1 | file '/home/runner/pippin-studio-1/output/run_20241225_065303_15723bb6/scenes/video_with_sound/scene_0.mp4' 2 | -------------------------------------------------------------------------------- /output/run_20241225_065303_15723bb6/final_video/final_video.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_065303_15723bb6/final_video/final_video.mp4 -------------------------------------------------------------------------------- /output/run_20241225_065303_15723bb6/final_video/final_video_horizontal.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_065303_15723bb6/final_video/final_video_horizontal.mp4 -------------------------------------------------------------------------------- /output/run_20241225_065303_15723bb6/final_video/final_video_vertical.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_065303_15723bb6/final_video/final_video_vertical.mp4 -------------------------------------------------------------------------------- /output/run_20241225_065303_15723bb6/metadata/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "run_id": "run_20241225_065303_15723bb6", 3 | "created_at": "2024-12-25T06:53:03.079143", 4 | "assets": { 5 | "characters": [ 6 | "pippin.svg" 7 | ], 8 | "backgrounds": [], 9 | "animations": [ 10 | "pippin_jumping_with_excitement.svg", 11 | "pippin_tapping_on_the_tablet.svg", 12 | "pippin_watching_a_video_intently.svg", 13 | "pippin_jumping_with_excitement_temp.svg", 14 | "pippin_tapping_on_the_tablet_temp.svg", 15 | "pippin_watching_a_video_intently_temp.svg" 16 | ], 17 | "test_composition": [], 18 | "scenes": [] 19 | } 20 | } -------------------------------------------------------------------------------- /output/run_20241225_065303_15723bb6/metadata/scene_movements.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scene_id": 0, 4 | "duration": 9.091, 5 | "background_path": "/home/runner/pippin-studio-1/output/run_20241225_065303_15723bb6/backgrounds/scene_0_background.png", 6 | "narration_text": "Pippin, sitting in a sunlit corner of his home office, eagerly scrolling through an array of quick videos, his eyes sparkling with excitement.", 7 | "movements": [ 8 | { 9 | "character_name": "Pippin", 10 | "start_time": 0.0, 11 | "end_time": 3.0, 12 | "start_position": [ 13 | 512.0, 14 | 307.0 15 | ], 16 | "end_position": [ 17 | 512.0, 18 | 307.0 19 | ], 20 | "start_scale": 0.5, 21 | "end_scale": 0.5, 22 | "animation_name": null 23 | }, 24 | { 25 | "character_name": "Pippin", 26 | "start_time": 3.0, 27 | "end_time": 5.0, 28 | "start_position": [ 29 | 512.0, 30 | 307.0 31 | ], 32 | "end_position": [ 33 | 512.0, 34 | 307.0 35 | ], 36 | "start_scale": 0.5, 37 | "end_scale": 0.5, 38 | "animation_name": "tapping on the tablet" 39 | }, 40 | { 41 | "character_name": "Pippin", 42 | "start_time": 5.0, 43 | "end_time": 8.0, 44 | "start_position": [ 45 | 512.0, 46 | 307.0 47 | ], 48 | "end_position": [ 49 | 512.0, 50 | 307.0 51 | ], 52 | "start_scale": 0.5, 53 | "end_scale": 0.5, 54 | "animation_name": "jumping with excitement" 55 | }, 56 | { 57 | "character_name": "Pippin", 58 | "start_time": 8.0, 59 | "end_time": 9.091, 60 | "start_position": [ 61 | 512.0, 62 | 307.0 63 | ], 64 | "end_position": [ 65 | 512.0, 66 | 307.0 67 | ], 68 | "start_scale": 0.5, 69 | "end_scale": 0.5, 70 | "animation_name": "watching a video intently" 71 | } 72 | ] 73 | } 74 | ] -------------------------------------------------------------------------------- /output/run_20241225_065303_15723bb6/metadata/story_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "characters": [ 3 | { 4 | "name": "Pippin", 5 | "type": "character", 6 | "description": "Pippin is a small, energetic creature resembling a cartoonish animal with round, fluffy body, large expressive eyes that sparkle with enthusiasm, and floppy ears. Its fur is a vibrant shade of light blue with white accents on the belly and tips of the ears. Pippin has a cheerful smile and carries a small tablet device in one paw.", 7 | "required_animations": [ 8 | "jumping with excitement", 9 | "tapping on the tablet", 10 | "watching a video intently" 11 | ] 12 | } 13 | ], 14 | "scenes": [ 15 | { 16 | "scene_id": 0, 17 | "background_description": "An inviting home office filled with natural light, featuring a cozy desk cluttered with notebooks and a laptop screen displaying colorful video thumbnails. A soft, cushioned chair sits beside the desk, and a window reveals a bright, cheerful garden with blooming flowers and a blue sky. The room is decorated with vibrant artwork on the walls, and a warm, welcoming atmosphere permeates the space.", 18 | "characters": [ 19 | "Pippin" 20 | ], 21 | "narration_text": "Pippin, sitting in a sunlit corner of his home office, eagerly scrolling through an array of quick videos, his eyes sparkling with excitement.", 22 | "test_criteria": { 23 | "visual_elements": [ 24 | "home office", 25 | "desk with notebooks", 26 | "laptop screen with video thumbnails", 27 | "window with a garden view", 28 | "bright colorful artwork" 29 | ], 30 | "character_presence": [ 31 | "Pippin" 32 | ], 33 | "mood": "excited and joyful", 34 | "lighting": "bright natural light coming from the window" 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /output/run_20241225_065303_15723bb6/scenes/audio/scene_0.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_065303_15723bb6/scenes/audio/scene_0.mp3 -------------------------------------------------------------------------------- /output/run_20241225_065303_15723bb6/scenes/svg/scene_0.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /output/run_20241225_065303_15723bb6/scenes/video/scene_0.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_065303_15723bb6/scenes/video/scene_0.mp4 -------------------------------------------------------------------------------- /output/run_20241225_065303_15723bb6/scenes/video_with_sound/scene_0.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoheinakajima/pippin-studio/8180d9dc97d7478b0032beeeb7fdd91f40abff6d/output/run_20241225_065303_15723bb6/scenes/video_with_sound/scene_0.mp4 -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "python-template" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Your Name "] 6 | requires-python = ">=3.11" 7 | dependencies = [] 8 | -------------------------------------------------------------------------------- /replit.nix: -------------------------------------------------------------------------------- 1 | {pkgs}: { 2 | deps = [ 3 | pkgs.libxcrypt 4 | pkgs.ffmpeg-full 5 | pkgs.cairo 6 | pkgs.zlib 7 | pkgs.xcodebuild 8 | pkgs.ffmpeg 9 | ]; 10 | } 11 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | #aiohttp==3.8.1 2 | #asyncio 3 | #openai==0.27.0 4 | #tenacity==8.2.2 5 | #requests 6 | #Pillow 7 | #lxml 8 | #cairosvg 9 | #numpy 10 | #svgwrite 11 | #jsonschema 12 | #flask 13 | #gunicorn 14 | aiohttp 15 | flask 16 | litellm 17 | lxml 18 | numpy 19 | openai 20 | requests 21 | svgwrite 22 | tenacity 23 | elevenlabs 24 | pydub 25 | cairosvg 26 | pydantic 27 | -------------------------------------------------------------------------------- /svg_config.py: -------------------------------------------------------------------------------- 1 | # svg_config.py 2 | 3 | EXAMPLE_SVGS = { 4 | "pippin_the_unicorn": """ 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 30 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 49 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | """ 64 | # You can add more characters here, like: 65 | # "dragon": """""", 66 | # "princess": """""", 67 | } 68 | -------------------------------------------------------------------------------- /svg_processor.py: -------------------------------------------------------------------------------- 1 | import re 2 | import tempfile 3 | from pathlib import Path 4 | import logging 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | try: 9 | from lxml import etree 10 | logger.info("Successfully imported lxml.etree") 11 | except ImportError as e: 12 | logger.error(f"Failed to import lxml.etree: {e}") 13 | raise 14 | 15 | try: 16 | import cairosvg 17 | logger.info("Successfully imported cairosvg") 18 | except ImportError as e: 19 | logger.error(f"Failed to import cairosvg: {e}") 20 | raise 21 | 22 | class SVGProcessor: 23 | def __init__(self, svg_path): 24 | self.svg_path = svg_path 25 | self.tree = None 26 | self._load_svg() 27 | 28 | def _load_svg(self): 29 | """Load and parse SVG file""" 30 | try: 31 | parser = etree.XMLParser(remove_blank_text=True) 32 | self.tree = etree.parse(self.svg_path, parser) 33 | logger.info(f"Successfully loaded SVG file: {self.svg_path}") 34 | except Exception as e: 35 | logger.error(f"Failed to load SVG file: {str(e)}") 36 | raise 37 | 38 | def get_animation_duration(self): 39 | """Extract animation duration from SVG""" 40 | root = self.tree.getroot() 41 | 42 | # Look for animate/animateTransform tags with dur 43 | durations = [] 44 | for elem in root.xpath(".//*[@dur]"): 45 | dur_str = elem.get('dur') 46 | if dur_str.endswith('s'): 47 | durations.append(float(dur_str[:-1])) 48 | elif dur_str.endswith('ms'): 49 | durations.append(float(dur_str[:-2]) / 1000) 50 | 51 | # If no animations found, return default duration 52 | return max(durations) if durations else 3.0 53 | 54 | def _parse_value(self, value_str): 55 | """Parse animation value string into numeric or tuple values""" 56 | if not value_str or value_str.isspace(): 57 | return None 58 | 59 | # Try coordinate pair (x,y) 60 | parts = value_str.strip().split() 61 | if len(parts) == 2: 62 | # Attempt to parse both as floats 63 | try: 64 | x = float(parts[0]) 65 | y = float(parts[1]) 66 | return (x, y) 67 | except ValueError: 68 | pass 69 | 70 | # If not a pair, try single float 71 | try: 72 | return float(value_str.strip()) 73 | except ValueError: 74 | # Could not parse as float, return None 75 | return None 76 | 77 | def _interpolate_values(self, start_vals, end_vals, factor): 78 | """Interpolate between two sets of values""" 79 | if isinstance(start_vals, tuple) and isinstance(end_vals, tuple): 80 | # Interpolate coordinate pairs 81 | sx, sy = start_vals 82 | ex, ey = end_vals 83 | return (sx + (ex - sx)*factor, sy + (ey - sy)*factor) 84 | elif isinstance(start_vals, (int, float)) and isinstance(end_vals, (int, float)): 85 | # Simple numeric interpolation 86 | return start_vals + (end_vals - start_vals) * factor 87 | else: 88 | # Unsupported type combination 89 | return start_vals 90 | 91 | def _modify_animation_time(self, time): 92 | """Modify SVG to show animation at specific time""" 93 | root = self.tree.getroot() 94 | 95 | # Update all animated elements 96 | # We look for elements with 'attributeName' and animation parameters 97 | # Handle both animate and animateTransform 98 | for elem in root.xpath(".//*[@attributeName]"): 99 | attr_name = elem.get('attributeName') 100 | anim_type = elem.tag.split('}')[-1] # animate or animateTransform 101 | values_str = elem.get('values') 102 | from_val = elem.get('from') 103 | to_val = elem.get('to') 104 | 105 | # Determine duration 106 | dur_str = elem.get('dur', '1s') 107 | if dur_str.endswith('s'): 108 | duration = float(dur_str[:-1]) 109 | elif dur_str.endswith('ms'): 110 | duration = float(dur_str[:-2]) / 1000 111 | else: 112 | duration = 1.0 113 | 114 | # Collect values 115 | raw_values = [] 116 | if values_str: 117 | raw_values = [v.strip() for v in values_str.split(';') if v.strip()] 118 | elif from_val and to_val: 119 | raw_values = [from_val.strip(), to_val.strip()] 120 | 121 | if len(raw_values) < 2: 122 | # Not enough values to interpolate 123 | continue 124 | 125 | # Normalize time 126 | normalized_time = (time % duration) / duration 127 | num_segments = len(raw_values) - 1 128 | segment_time = normalized_time * num_segments 129 | segment_index = int(segment_time) 130 | if segment_index >= num_segments: 131 | segment_index = num_segments - 1 132 | factor = segment_time - segment_index 133 | 134 | try: 135 | start_value = self._parse_value(raw_values[segment_index]) 136 | end_value = self._parse_value(raw_values[segment_index + 1]) 137 | if start_value is None or end_value is None: 138 | continue 139 | current_value = self._interpolate_values(start_value, end_value, factor) 140 | 141 | parent = elem.getparent() 142 | if parent is None: 143 | continue 144 | 145 | if anim_type == 'animateTransform': 146 | # Handle transform animations specifically 147 | transform_type = elem.get('type', '') 148 | # Based on transform_type, reconstruct transform string 149 | if transform_type == 'translate' and isinstance(current_value, tuple): 150 | # current_value is (x,y) 151 | parent.set(attr_name, f"translate({current_value[0]},{current_value[1]})") 152 | elif transform_type == 'scale' and isinstance(current_value, tuple): 153 | # current_value is (sx,sy) 154 | parent.set(attr_name, f"scale({current_value[0]},{current_value[1]})") 155 | elif transform_type == 'rotate': 156 | # Rotate might need additional parameters if original had them 157 | # If raw_values had rotate syntax, we must replicate it. 158 | # For simplicity, assume just angle interpolation: 159 | if isinstance(current_value, (int,float)): 160 | # If original rotate had center points, 161 | # we must detect them from the first raw value: 162 | parts = raw_values[segment_index].split() 163 | # Expect something like "angle cx cy" 164 | # If not provided, default (0,0) 165 | if len(parts) == 3: 166 | cx = parts[1] 167 | cy = parts[2] 168 | parent.set(attr_name, f"rotate({current_value} {cx} {cy})") 169 | else: 170 | parent.set(attr_name, f"rotate({current_value})") 171 | else: 172 | # Unsupported transform type or no complex handling needed 173 | # If it's a single numeric value and it's translate-like, handle accordingly 174 | # If we can't determine how to format it, skip 175 | pass 176 | else: 177 | # Normal attribute animation (e.g., color or numeric) 178 | # Just set the attribute to the numeric or tuple value 179 | # If tuple, join by space 180 | if isinstance(current_value, tuple): 181 | parent.set(attr_name, f"{current_value[0]} {current_value[1]}") 182 | else: 183 | parent.set(attr_name, str(current_value)) 184 | 185 | except Exception as e: 186 | logger.warning(f"Error interpolating animation values for {attr_name}: {e}") 187 | continue 188 | 189 | async def generate_frames(self, duration, fps): 190 | """Generate frames for the animation""" 191 | frames = [] 192 | frame_count = int(duration * fps) 193 | 194 | logger.info(f"Generating {frame_count} frames at {fps} FPS") 195 | 196 | with tempfile.TemporaryDirectory() as temp_dir: 197 | for i in range(frame_count): 198 | time = i / fps 199 | logger.info(f"Generating frame {i+1}/{frame_count}") 200 | 201 | try: 202 | if not self.tree: 203 | raise ValueError("SVG not loaded properly") 204 | 205 | # Modify SVG for current time 206 | self._modify_animation_time(time) 207 | 208 | # Convert SVG to PNG bytes 209 | svg_bytes = etree.tostring(self.tree.getroot(), encoding='utf-8', method='xml') 210 | png_data = cairosvg.svg2png( 211 | bytestring=svg_bytes, 212 | output_width=1024, 213 | output_height=1024, 214 | background_color="rgba(0,0,0,0)", 215 | parent_width=1024, 216 | parent_height=1024 217 | ) 218 | 219 | frames.append(png_data) 220 | 221 | except Exception as e: 222 | logger.error(f"Failed to generate frame {i+1}: {e}") 223 | logger.exception("Detailed error:") 224 | raise 225 | 226 | logger.info("Frame generation complete") 227 | 228 | return frames 229 | -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | requires-python = ">=3.11" 3 | 4 | [[package]] 5 | name = "python-template" 6 | version = "0.1.0" 7 | source = { virtual = "." } 8 | -------------------------------------------------------------------------------- /video_encoder.py: -------------------------------------------------------------------------------- 1 | #video_encoder.py 2 | 3 | from io import BytesIO 4 | from pathlib import Path 5 | from PIL import Image 6 | import tempfile 7 | import subprocess 8 | import numpy as np 9 | import logging 10 | 11 | logger = logging.getLogger(__name__) 12 | 13 | class VideoEncoder: 14 | FORMAT_CONFIGS = { 15 | '.mp4': { 16 | 'vcodec': 'libx264', 17 | 'preset': 'medium', 18 | 'crf': '18', 19 | 'pix_fmt': 'yuv420p', 20 | 'movflags': '+faststart', 21 | 'tune': 'animation', 22 | 'profile:v': 'high', 23 | 'level': '4.1' 24 | } 25 | } 26 | 27 | def __init__(self, output_path, fps): 28 | self.output_path = output_path 29 | self.fps = fps 30 | self.format = Path(output_path).suffix.lower() 31 | 32 | if self.format not in self.FORMAT_CONFIGS: 33 | raise ValueError(f"Unsupported output format: {self.format}. " 34 | f"Supported formats: {', '.join(self.FORMAT_CONFIGS.keys())}") 35 | 36 | self.temp_dir = tempfile.mkdtemp() 37 | 38 | def encode_frames(self, frames): 39 | try: 40 | if not frames: 41 | raise ValueError("No frames to encode") 42 | 43 | frame_paths = [] 44 | 45 | temp_dir = Path(self.temp_dir) 46 | temp_dir.mkdir(parents=True, exist_ok=True) 47 | 48 | for i, frame_data in enumerate(frames, 1): 49 | try: 50 | frame_path = temp_dir / f"frame_{i:06d}.png" 51 | with open(frame_path, 'wb') as f: 52 | f.write(frame_data) 53 | frame_paths.append(frame_path) 54 | # Keep progress logs every 10 frames 55 | if i % 10 == 0: 56 | logger.info(f"Saved frame {i}/{len(frames)}") 57 | except Exception as e: 58 | logger.error(f"Failed to save frame {i}: {e}") 59 | raise 60 | 61 | if not frame_paths: 62 | raise ValueError("No frames were saved successfully") 63 | 64 | try: 65 | with Image.open(frame_paths[0]) as img: 66 | width, height = img.size 67 | width = (width // 2) * 2 68 | height = (height // 2) * 2 69 | except Exception as e: 70 | logger.error(f"Failed to read frame dimensions: {e}") 71 | raise 72 | 73 | config = self.FORMAT_CONFIGS[self.format] 74 | ffmpeg_cmd = [ 75 | 'ffmpeg', 76 | '-y', 77 | '-framerate', str(self.fps), 78 | '-i', str(temp_dir / 'frame_%06d.png'), 79 | '-vf', f'scale={width}:{height},format=yuv420p', 80 | '-vsync', 'cfr', 81 | '-g', '150', 82 | '-bf', '2', 83 | ] 84 | 85 | for key, value in config.items(): 86 | if key != 'pix_fmt': 87 | ffmpeg_cmd.extend([f'-{key}', str(value)]) 88 | 89 | Path(self.output_path).parent.mkdir(parents=True, exist_ok=True) 90 | ffmpeg_cmd.append(str(self.output_path)) 91 | 92 | try: 93 | result = subprocess.run( 94 | ffmpeg_cmd, 95 | capture_output=True, 96 | text=True, 97 | check=True 98 | ) 99 | except subprocess.CalledProcessError as e: 100 | logger.error("\nFFmpeg Error Output:") 101 | logger.error("=" * 40) 102 | logger.error(e.stderr) 103 | logger.error("=" * 40) 104 | raise RuntimeError(f"FFmpeg encoding failed with return code {e.returncode}") 105 | 106 | if not Path(self.output_path).exists(): 107 | raise RuntimeError("Output file was not created") 108 | 109 | file_size = Path(self.output_path).stat().st_size 110 | if file_size == 0: 111 | raise RuntimeError("Output file is empty") 112 | 113 | return self.output_path 114 | 115 | except Exception as e: 116 | logger.error(f"Video encoding failed: {str(e)}") 117 | if Path(self.output_path).exists(): 118 | Path(self.output_path).unlink() 119 | raise 120 | 121 | finally: 122 | try: 123 | if frame_paths: 124 | for frame_path in frame_paths: 125 | try: 126 | frame_path.unlink() 127 | except Exception as e: 128 | logger.warning(f"Failed to delete temporary frame {frame_path}: {e}") 129 | if Path(self.temp_dir).exists(): 130 | import shutil 131 | shutil.rmtree(self.temp_dir) 132 | except Exception as e: 133 | logger.warning(f"Error during cleanup: {e}") 134 | -------------------------------------------------------------------------------- /video_processor.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from pathlib import Path 3 | from typing import List, Dict, Optional, Tuple 4 | from io import BytesIO 5 | from PIL import Image 6 | import subprocess 7 | import numpy as np 8 | import cairosvg 9 | import math 10 | 11 | from asset_manager import AssetManager 12 | from svg_processor import SVGProcessor 13 | from video_encoder import VideoEncoder 14 | 15 | logger = logging.getLogger(__name__) 16 | 17 | class VideoProcessor: 18 | def __init__(self, asset_manager: AssetManager): 19 | self.asset_manager = asset_manager 20 | 21 | async def create_scene_video(self, scene_data: Dict, output_path: Optional[Path] = None) -> Path: 22 | """ 23 | Render the scene video by: 24 | - Rendering the background scene SVG frames. 25 | - Rendering each character over it based on movements. 26 | - For each character's animation, derive its natural duration 27 | from the first movement in scene_movements.json that uses it. 28 | """ 29 | scene_id = scene_data["scene_id"] 30 | duration = scene_data.get("duration", 5.0) 31 | scene_svg_path = scene_data.get("svg_path", None) 32 | characters = scene_data.get("characters", []) 33 | 34 | if output_path is None: 35 | output_path = self.asset_manager.get_path("scenes/video", f"scene_{scene_id}.mp4") 36 | 37 | output_path = output_path.absolute() 38 | output_path.parent.mkdir(parents=True, exist_ok=True) 39 | 40 | background_path = Path(scene_data["background_path"]).absolute() 41 | if not background_path.exists(): 42 | raise FileNotFoundError(f"Background image not found: {background_path}") 43 | 44 | with Image.open(background_path) as img: 45 | if img.mode != 'RGB': 46 | img = img.convert('RGB') 47 | bg_array = np.array(img) 48 | 49 | fps = 30 50 | total_frames = int(duration * fps) 51 | 52 | # Render scene background frames if any 53 | scene_frames = [] 54 | if scene_svg_path: 55 | svg_processor = SVGProcessor(Path(scene_svg_path)) 56 | scene_svg_frames = await svg_processor.generate_frames(duration=duration, fps=fps) 57 | scene_frames = scene_svg_frames 58 | 59 | # We need to determine animation durations from movements 60 | # For each character, we have a set of movements with animation_name. 61 | # We'll find the first movement that uses a given animation_name and use that duration. 62 | # If an animation_name appears multiple times with different durations, 63 | # we stick to the first encountered duration. 64 | 65 | # Gather animation durations from movements 66 | animation_durations = {} # { (char_name, anim_name): duration_in_seconds } 67 | 68 | for char_data in characters: 69 | char_name = char_data["name"] 70 | for m in char_data["movements"]: 71 | anim_name = m["animation_name"] 72 | if anim_name is not None: 73 | movement_duration = m["end_time"] - m["start_time"] 74 | # If not set yet, record this animation duration 75 | if (char_name, anim_name) not in animation_durations: 76 | animation_durations[(char_name, anim_name)] = movement_duration 77 | 78 | # Pre-generate frames for each character and their animations 79 | character_frames_map = {} 80 | for char_data in characters: 81 | char_name = char_data["name"] 82 | base_path = char_data["base_path"] 83 | animations = char_data["animations"] 84 | 85 | # Base frames: we consider the base character animation equivalent to the entire scene duration 86 | # since it might be a subtle idle animation. If you prefer, you can set a default duration for base. 87 | # We'll just keep using scene duration here for base_path for now. 88 | base_svg_proc = SVGProcessor(Path(base_path)) 89 | base_frames = await base_svg_proc.generate_frames(duration=duration, fps=fps) 90 | character_frames_map[char_name] = {None: base_frames} 91 | 92 | # Now for each animation, use the calculated duration if available 93 | for anim_name, anim_svg in animations.items(): 94 | # Determine animation duration 95 | # If not in animation_durations, default to a 1s duration (or scene duration) 96 | # Ideally every animation_name should appear in at least one movement, but let's be safe. 97 | anim_duration = animation_durations.get((char_name, anim_name), 1.0) 98 | 99 | anim_path = self.asset_manager.save_animation(char_name, f"{anim_name}_temp", anim_svg) 100 | anim_processor = SVGProcessor(Path(anim_path)) 101 | # Generate frames for this specific animation duration 102 | anim_frames = await anim_processor.generate_frames(duration=anim_duration, fps=fps) 103 | character_frames_map[char_name][anim_name] = anim_frames 104 | 105 | encoder = VideoEncoder(str(output_path), fps) 106 | 107 | final_frames = [] 108 | for frame_idx in range(total_frames): 109 | current_time = frame_idx / fps 110 | frame = bg_array.copy() 111 | if frame_idx % 10 == 0: 112 | logger.info(f"Processing frame {frame_idx+1}/{total_frames}") 113 | 114 | # Blend scene frame if available 115 | if scene_frames: 116 | scene_frame = scene_frames[min(frame_idx, len(scene_frames)-1)] 117 | scene_img = Image.open(BytesIO(scene_frame)).convert('RGBA') 118 | scene_array = np.array(scene_img) 119 | 120 | h, w = frame.shape[:2] 121 | ch, cw = scene_array.shape[:2] 122 | 123 | x, y = 0, 0 124 | x2, y2 = min(w, cw), min(h, ch) 125 | alpha = scene_array[0:y2,0:x2,3:4]/255.0 126 | src_rgb = scene_array[0:y2,0:x2,:3] 127 | dst_rgb = frame[0:y2,0:x2] 128 | blended = (src_rgb*alpha + dst_rgb*(1-alpha)).astype(np.uint8) 129 | frame[0:y2,0:x2] = blended 130 | 131 | # Place characters based on current movement 132 | for char_data in characters: 133 | char_name = char_data["name"] 134 | char_movements = char_data["movements"] 135 | current_movement = None 136 | for m in char_movements: 137 | if m["start_time"] <= current_time <= m["end_time"]: 138 | current_movement = m 139 | break 140 | 141 | if not current_movement: 142 | continue 143 | 144 | anim_name = current_movement["animation_name"] 145 | frames = character_frames_map[char_name].get(anim_name, character_frames_map[char_name][None]) 146 | if not frames: 147 | continue 148 | 149 | movement_duration = current_movement["end_time"] - current_movement["start_time"] 150 | if movement_duration <= 0: 151 | movement_duration = 0.001 152 | t = (current_time - current_movement["start_time"]) / movement_duration 153 | t = max(0.0, min(t, 1.0)) 154 | 155 | # t maps 0->start_time to 1->end_time of that movement 156 | # frames for this animation were generated according to the movement's animation duration 157 | # so t directly maps to the frames of that animation 158 | char_frame_idx = int(t * (len(frames)-1)) 159 | char_frame = frames[char_frame_idx] 160 | 161 | # Determine character position and scale 162 | sx, sy = current_movement["start_position"] 163 | ex, ey = current_movement["end_position"] 164 | x_pos = sx + (ex - sx)*t 165 | y_pos = sy + (ey - sy)*t 166 | 167 | s_scale = current_movement["start_scale"] 168 | e_scale = current_movement["end_scale"] 169 | scale = s_scale + (e_scale - s_scale)*t 170 | 171 | char_img = Image.open(BytesIO(char_frame)).convert('RGBA') 172 | if scale != 1.0: 173 | new_w = int(char_img.width * scale) 174 | new_h = int(char_img.height * scale) 175 | char_img = char_img.resize((new_w, new_h), Image.LANCZOS) 176 | 177 | char_array = np.array(char_img) 178 | ch, cw = char_array.shape[:2] 179 | 180 | x = int(x_pos - cw/2) 181 | y = int(y_pos - ch/2) 182 | 183 | x1, y1 = max(0, x), max(0, y) 184 | x2, y2 = min(frame.shape[1], x+cw), min(frame.shape[0], y+ch) 185 | 186 | src_x1 = max(0, -x) 187 | src_y1 = max(0, -y) 188 | src_x2 = src_x1 + (x2 - x1) 189 | src_y2 = src_y1 + (y2 - y1) 190 | 191 | if (x2 > x1 and y2 > y1 and src_x2 > src_x1 and src_y2 > src_y1 and 192 | src_y2 <= ch and src_x2 <= cw): 193 | alpha = char_array[src_y1:src_y2, src_x1:src_x2, 3:4]/255.0 194 | src_rgb = char_array[src_y1:src_y2, src_x1:src_x2,:3] 195 | dst_rgb = frame[y1:y2, x1:x2] 196 | blended = (src_rgb*alpha + dst_rgb*(1-alpha)).astype(np.uint8) 197 | frame[y1:y2, x1:x2] = blended 198 | 199 | frame_img = Image.fromarray(frame) 200 | with BytesIO() as bio: 201 | frame_img.save(bio, format='PNG') 202 | final_frames.append(bio.getvalue()) 203 | 204 | if not final_frames: 205 | raise ValueError("No frames generated") 206 | 207 | video_path = encoder.encode_frames(final_frames) 208 | if not output_path.exists() or output_path.stat().st_size == 0: 209 | raise RuntimeError("Generated video file is empty or not created") 210 | 211 | return video_path 212 | --------------------------------------------------------------------------------