main 7d1053356aa8 cached
14 files
270.1 KB
105.2k tokens
28 symbols
1 requests
Download .txt
Showing preview only (279K chars total). Download the full file or copy to clipboard to get everything.
Repository: dmarx/video-killed-the-radio-star
Branch: main
Commit: 7d1053356aa8
Files: 14
Total size: 270.1 KB

Directory structure:
gitextract_j7_i1yfm/

├── .github/
│   └── workflows/
│       └── python-publish.yml
├── .gitignore
├── LICENSE
├── README.md
├── VERSION
├── Video_Killed_The_Radio_Star_Defusion.ipynb
├── pyproject.toml
└── vktrs/
    ├── __init__.py
    ├── api.py
    ├── asr.py
    ├── hf.py
    ├── tsp.py
    ├── utils.py
    └── youtube.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/python-publish.yml
================================================
# This workflow will upload a Python Package using Twine when a release is created
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.

name: Upload Python Package

on:
  release:
    types: [published]

permissions:
  contents: read

jobs:
  deploy:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3
    - name: Set up Python
      uses: actions/setup-python@v3
      with:
        python-version: '3.x'
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install build
    - name: Build package
      run: python -m build
    - name: Publish package
      uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
      with:
        user: __token__
        password: ${{ secrets.PYPI_TOKEN }}


================================================
FILE: .gitignore
================================================
_venv


*.srv2
*.vtt
*.webm
*.mp3
*.mp4
*.pyc
*.egg-info/
**/frames
**/archive
*.yaml
*.whl
*.tar.gz
dist

# local huggingface model directories and files
feature_extractor
safety_checker
scheduler
text_encoder
tokenizer
unet
vae
model_index.json


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2022 David Marx

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Video Killed The Radio Star [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/dmarx/video-killed-the-radio-star/blob/main/Video_Killed_The_Radio_Star_Defusion.ipynb)



## Requirements

* ffmpeg - https://ffmpeg.org/
* pytorch - https://pytorch.org/get-started/locally/
* vktrs - (this repo) - `pip install vktrs[api]`
* stability_sdk api token - https://beta.dreamstudio.ai/ > circular icon in top right > membership > API Key
* whisper - `pip install git+https://github.com/openai/whisper`

## FAQ

**What is this?**

TLDR: Automated music video maker, given an mp3 or a youtube URL

**How does this animation technique work?**

For each text prompt you provide, the notebook will...

1. Generate an image based on that text prompt (using stable diffusion)
2. Use the generated image as the `init_image` to recombine with the text prompt to generate variations similar to the first image. This produces a sequence of extremely similar images based on the original text prompt
3. Images are then intelligently reordered to find the smoothest animation sequence of those frames
3. This image sequence is then repeated to pad out the animation duration as needed

The technique demonstrated in this notebook was inspired by a [video](https://www.youtube.com/watch?v=WJaxFbdjm8c) created by Ben Gillin.

**How are lyrics transcribed?**

This notebook uses openai's recently released 'whisper' model for performing automatic speech recognition. 
OpenAI was kind of to offer several different sizes of this model which each have their own pros and cons. 
This notebook uses the largest whisper model for transcribing the actual lyrics. Additionally, we use the 
smallest model for performing the lyric segmentation. Neither of these models is perfect, but the results 
so far seem pretty decent.

The first draft of this notebook relied on subtitles from youtube videos to determine timing, which was
then aligned with user-provided lyrics. Youtube's automated captions are powerful and I'll update the
notebook shortly to leverage those again, but for the time being we're just using whisper for everything
and not referencing user-provided captions at all.

**Something didn't work quite right in the transcription process. How do fix the timing or the actual lyrics?**

The notebook is divided into several steps. Between each step, a "storyboard" file is updated. If you want to
make modifications, you can edit this file directly and those edits should be reflected when you next load the
file. Depending on what you changed and what step you run next, your changes may be ignored or even overwritten.
Still playing with different solutions here.

**Can I provide my own images to 'bring to life' and associate with certain lyrics/sequences?**

Yes, you can! As described above: you just need to modify the storyboard. Will describe this functionality in
greater detail after the implementation stabilizes a bit more.

**This gave me an idea and I'd like to use just a part of your process here. What's the best way to reuse just some of the machinery you've developed here?**

Most of the functionality in this notebook has been offloaded to library I published to pypi called `vktrs`. I strongly encourage you to import anything you need 
from there rather than cutting and pasting function into a notebook. Similarly, if you have ideas for improvements, please don't hesitate to submit a PR!

## Dev notes


```
!pip install --upgrade setuptools build
!git clone https://github.com/dmarx/video-killed-the-radio-star/
!cd video-killed-the-radio-star;  python -m build; python -m pip install -e .[api,hf]
!pip install ipykernel ipywidgets panel prefetch_generator
```


================================================
FILE: VERSION
================================================
0.1.8


================================================
FILE: Video_Killed_The_Radio_Star_Defusion.ipynb
================================================
{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "mgXxoDhMAiti"
      },
      "source": [
        "# $ \\text{Video Killed The Radio Star}$ $\\color{red}{...Diffusion}$\n",
        "\n",
        "\n",
        "\n",
        "![StabilityAi_Logo-06.png](data:image/png;base64,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)\n",
        "\n",
        "Notebook by David Marx ([@DigThatData](https://twitter.com/digthatdata))\n",
        "\n",
        "Shared under MIT license\n",
        "\n",
        "Last updated: 2023-06-15\n",
        "\n",
        "Latest stable notebook revision: [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/dmarx/video-killed-the-radio-star/blob/main/Video_Killed_The_Radio_Star_Defusion.ipynb)\n",
        "\n",
        "# Introduction\n",
        "\n",
        "VKTRS is a tool for planning and generating complex animations using audio with spoken/sung words as conditioning input. The planning work can be used independently from generating an animation: all of your work is saved to a human-readable and modifiable `storyboard.yaml` file. Parameter names and notation are also compatible with other AI animation tools, such as a1111-deforum.\n",
        "\n",
        "The inspiration for this notebook and the \"variations\" animation modes were  [this video](https://www.youtube.com/watch?v=WJaxFbdjm8c) created by Ben Gillin.\n",
        "\n",
        "# General Workflow\n",
        "\n",
        "If you are reading this in colab, open the \"Table of contents\" tab on the left sidebar for a more detailed enumeration of the steps described below.\n",
        "\n",
        "1. Pick an audio source, like a youtube video\n",
        "2. The notebook will download the video if necessary\n",
        "3. OpenAI's \"whisper\" model is used to transcribe the lyrics\n",
        "4. The timing of this transcription is used to segment the timeline into a sequence of \"scenes\"\n",
        "5. The musical structure of the audio is analyzed to group thematically similar themes\n",
        "6. Scene/theme specific settings (prompts, camera motion) can then be added\n",
        "7. The audio can be further processed to isolate signals for driving animation parameters for \"audioreactive\" effects. This includes isolating instruments (demucs) and chaining manipulations (librosa)\n",
        "8. Generate or specify starting images for each scene\n",
        "9. Generate the remaining frames to animate each scene\n",
        "\n",
        "**NB: the `img2img` animation mode is currently only supported when using the stability ai animation api (DreamStudio) to generate animations**. The audioreactivity features are currently only relevant in img2img mode. If you don't have a DreamStudio account, you can still use this notebook to parameterize an animation, you'll just need to cut-and-paste settings out of the \"export settings\" cell (deforum compatible) or the `storyboard.yaml` file.\n",
        "\n",
        "# The \"Storyboard\"\n",
        "\n",
        "Start at the top of the notebook and work your way down. Each decision you make contributes information to a file called \"the storyboard\", which persists project state. The notebook is designed to facilitate working iteratively: once you've made it past a given decision point, you should generally be able to jump back to that point to tweak whatever the decision was and return to where you were.\n",
        "\n",
        "The storyboard isn't just a logical abstraction, it's a physical file you can modify directly, and is reasonably human readable.\n",
        "\n",
        "Every project you start will create a new storyboard. If you set your project name to one you used previously, the notebook will attempt to find that project and load its storyboard.\n",
        "\n",
        "The original motivation behind VKTRS was to be a toy for automating generative music videos. It has evolved quite a bit since, and I now mostly think about it as a tool for building and manipulating these storyboards. The storyboard parameterizes an animation, but the actual animation doesn't need to be generated by this tool.\n",
        "\n",
        "\n",
        "# $\\text{FAQ}$\n",
        "\n",
        "**Why the name?**\n",
        "\n",
        "The notebook's name is an ironic homage to the first music video played on MTV: [The Bugles - Video Killed The Radio Star](https://www.youtube.com/watch?v=W8r-tXRLazs). Quoting [wikipedia](https://en.wikipedia.org/wiki/Video_Killed_the_Radio_Star):\n",
        "\n",
        "> \"The song relates to concerns about, and mixed attitudes toward 20th-century inventions and machines for the media arts.\"\n",
        "\n",
        "**Something didn't work quite right in the transcription process. How do fix the timing or the actual lyrics?**\n",
        "\n",
        "The notebook is divided into several steps. Between each step, a \"storyboard\" file is updated. If you want to\n",
        "make modifications, you can edit this file directly and those edits should be reflected when you next load the\n",
        "file. Depending on what you changed and what step you run next, your changes may be ignored or even overwritten.\n",
        "Still playing with different solutions here.\n",
        "\n",
        "**Can I provide my own images to 'bring to life' and associate with certain lyrics/sequences?**\n",
        "\n",
        "Yes, you can! As described above: you just need to modify the storyboard. Will describe this functionality in\n",
        "greater detail after the implementation stabilizes a bit more.\n",
        "\n",
        "**How can I support your work or work like it?**\n",
        "\n",
        "This notebook was made possible thanks to ongoing support from [stability.ai](https://stability.ai/). The best way to support my work is to share it with your friends, [report bugs](https://github.com/dmarx/video-killed-the-radio-star/issues/new), [suggest features](https://github.com/dmarx/video-killed-the-radio-star/discussions) or to donate to open source non-profits :)\n",
        "\n",
        "# Examples of content made with VKTRS\n",
        "\n",
        "[![The Sudden - Fine - Official Music Video](data:image/jpeg;base64,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)](https://www.youtube.com/watch?v=dx8LmqalrmU)\n",
        "\n",
        "**The Sudden - Fine (Official Music Video)** `variations` animation mode and theme prompts.\n",
        "\n",
        "<div><a href=\"https://www.youtube.com/watch?v=ScbXSLYyzq4\"><img src=\"https://i.ytimg.com/vi_webp/ScbXSLYyzq4/oar2.webp\" alt=\"Hi-Standard - Asian Pride\" width=\"300\"/></a></div>\n",
        "\n",
        " **Hi-Standard - Asian Pride** `img2img` animation mode (via the Stability.AI animation API), with camera movement and simple audioreactivity (vocals stem driving `strength_curve` and `noise_curve`). This was one of my test animations and doesn't do the notebook justice, but you'll get the idea.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "sM147HP4kAdY"
      },
      "source": [
        "## $0.$ Setup"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "ZnTe8clZuZuj",
        "tags": []
      },
      "outputs": [],
      "source": [
        "# @title # 📊 Check GPU Status\n",
        "\n",
        "import pandas as pd\n",
        "import subprocess\n",
        "\n",
        "def gpu_info():\n",
        "    outv = subprocess.run([\n",
        "        'nvidia-smi',\n",
        "            # these lines concatenate into a single query string\n",
        "            '--query-gpu='\n",
        "            'timestamp,'\n",
        "            'name,'\n",
        "            'utilization.gpu,'\n",
        "            'utilization.memory,'\n",
        "            'memory.used,'\n",
        "            'memory.free,'\n",
        "            ,\n",
        "        '--format=csv'\n",
        "        ],\n",
        "        stdout=subprocess.PIPE).stdout.decode('utf-8')\n",
        "\n",
        "    header, rec = outv.split('\\n')[:-1]\n",
        "    return pd.DataFrame({' '.join(k.strip().split('.')).capitalize():v for k,v in zip(header.split(','), rec.split(','))}, index=[0]).T\n",
        "\n",
        "gpu_info()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "FIJ-gPjcVXby",
        "tags": []
      },
      "outputs": [],
      "source": [
        "#%%capture\n",
        "\n",
        "# @title # 🛠️ Setup: Install Dependencies\n",
        "\n",
        "# Install dependencies\n",
        "\n",
        "try:\n",
        "    import google.colab\n",
        "    local=False\n",
        "except:\n",
        "    local=True\n",
        "\n",
        "# TODO: pin versions\n",
        "\n",
        "# local only additional dependencies\n",
        "if local:\n",
        "    %pip install pandas torch pillow beautifulsoup4 scipy toolz numpy lxml librosa scikit-learn rich\n",
        "\n",
        "# dependencies for both colab and local\n",
        "%pip install yt-dlp python-tsp stability-sdk[anim_ui] diffusers transformers ftfy accelerate omegaconf\n",
        "%pip install openai-whisper panel huggingface_hub ipywidgets safetensors keyframed demucs parse"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "OrHUOTwdgCfK",
        "tags": [],
        "cellView": "form"
      },
      "outputs": [],
      "source": [
        "#%%capture\n",
        "\n",
        "# @title # 🛠️ Setup: Imports and Definitions\n",
        "\n",
        "#  Definitions and imports\n",
        "\n",
        "from collections import defaultdict\n",
        "import copy\n",
        "import datetime as dt\n",
        "import gc\n",
        "import io\n",
        "from itertools import chain, cycle\n",
        "import json\n",
        "import os\n",
        "from pathlib import Path\n",
        "import random\n",
        "import re\n",
        "import shutil\n",
        "import string\n",
        "import subprocess\n",
        "from subprocess import Popen, PIPE\n",
        "import time\n",
        "import warnings\n",
        "\n",
        "from bokeh.models.widgets.tables import (\n",
        "    NumberFormatter,\n",
        "    BooleanFormatter,\n",
        "    CheckboxEditor,\n",
        ")\n",
        "from diffusers import (\n",
        "    StableDiffusionImg2ImgPipeline,\n",
        "    StableDiffusionPipeline,\n",
        ")\n",
        "from IPython.display import display\n",
        "import keyframed\n",
        "import keyframed as kf # TODO...\n",
        "import keyframed.dsl\n",
        "from keyframed.serialization import from_dict as load_curve\n",
        "import librosa\n",
        "import matplotlib.pyplot as plt\n",
        "import numpy as np\n",
        "from omegaconf import OmegaConf, DictConfig\n",
        "import pandas as pd\n",
        "import panel as pn\n",
        "import parse\n",
        "import PIL\n",
        "from PIL import Image\n",
        "from PIL import Image, ImageDraw, ImageFont\n",
        "from python_tsp.exact import solve_tsp_dynamic_programming\n",
        "import rich\n",
        "from safetensors.numpy import save_file as save_safetensors\n",
        "from safetensors.numpy import load_file as load_safetensors\n",
        "import scipy\n",
        "from scipy.spatial.distance import pdist, squareform\n",
        "import sklearn.cluster\n",
        "import textwrap\n",
        "from tqdm.autonotebook import tqdm\n",
        "import torch\n",
        "from torch import autocast\n",
        "import whisper\n",
        "\n",
        "from stability_sdk.api import Context\n",
        "from stability_sdk.animation import AnimationArgs, Animator\n",
        "\n",
        "from stability_sdk.animation import (\n",
        "    AnimationArgs,\n",
        "    Animator,\n",
        "    AnimationSettings,\n",
        "    BasicSettings,\n",
        "    CoherenceSettings,\n",
        "    ColorSettings,\n",
        "    DepthSettings,\n",
        "    InpaintingSettings,\n",
        "    Rendering3dSettings,\n",
        "    CameraSettings,\n",
        "    VideoInputSettings,\n",
        "    VideoOutputSettings,\n",
        ")\n",
        "\n",
        "try:\n",
        "    import google.colab\n",
        "    local=False\n",
        "except:\n",
        "    local=True\n",
        "\n",
        "\n",
        "def sanitize_folder_name(fp):\n",
        "    outv = ''\n",
        "    whitelist = string.ascii_letters + string.digits + '-_'\n",
        "    for token in str(fp):\n",
        "        if token not in whitelist:\n",
        "            token = '-'\n",
        "        outv += token\n",
        "    return outv\n",
        "\n",
        "# to do: is there a way to check if this is in the env already?\n",
        "#pn.extension('tabulator')\n",
        "\n",
        "\n",
        "def establish_workspace(\n",
        "    use_stability_api,\n",
        "    mount_gdrive,\n",
        "    application_name=\"VideoKilledTheRadioStar\",\n",
        "    active_project=None,\n",
        "):\n",
        "    \"\"\"\n",
        "    This function constructs a local file called `config.yaml` that maintains state that will be used elsewhere.\n",
        "    It mostly sets the names of project folders and a handful of settings. The reason for doing things this way\n",
        "    is to facilitate \"resume\" functionality and creating new projects without overwriting previously created assets.\n",
        "\n",
        "    By convention, when loaded the config.yaml is referred to as the `workspace` object.\n",
        "\n",
        "    Most project-specific content will be located in a project-specific config -- `storyboard.yaml` -- which should be\n",
        "    located in the folder path given by `workspace.project_root`. By convention, when loaded this is referred to as the\n",
        "    `storyboard` object.\n",
        "\n",
        "    If everything is set up correctly, you should be able to load the currently configured workspace and storyboard via:\n",
        "\n",
        "        workspace, storyboard = load_storyboard()\n",
        "    \"\"\"\n",
        "    # yeah... so... this shouldn't be necessary....\n",
        "    import os\n",
        "\n",
        "    # infer if we're on colab or not, since this impacts gdrive mounting\n",
        "    try:\n",
        "        import google.colab\n",
        "        local=False\n",
        "    except:\n",
        "        local=True\n",
        "\n",
        "    if local:\n",
        "        mount_gdrive=False\n",
        "\n",
        "    # Infer directory locations\n",
        "    os.environ['XDG_CACHE_HOME'] = os.environ.get(\n",
        "        'XDG_CACHE_HOME',\n",
        "        str(Path('~/.cache').expanduser())\n",
        "    )\n",
        "    if mount_gdrive:\n",
        "        from google.colab import drive\n",
        "        drive.mount('/content/drive')\n",
        "        Path('/content/drive/MyDrive/AI/models/.cache/').mkdir(parents=True, exist_ok=True)\n",
        "        os.environ['XDG_CACHE_HOME']='/content/drive/MyDrive/AI/models/.cache'\n",
        "\n",
        "    model_dir_str=str(Path(os.environ['XDG_CACHE_HOME']))\n",
        "    proj_root_str = '${active_project}'\n",
        "    application_root = str(Path('.').absolute())\n",
        "    if mount_gdrive:\n",
        "        application_root = '/content/drive/MyDrive/AI/VideoKilledTheRadioStar'\n",
        "\n",
        "\n",
        "    # Build config file that defines the \"workspace\" abstraction\n",
        "    workspace = OmegaConf.create({\n",
        "        'active_project': active_project if active_project else str(time.time()),\n",
        "        'application_root':application_root,\n",
        "        'project_root':\"${application_root}/${active_project}\",\n",
        "        'shared_assets_root':\"${application_root}/shared_assets\",\n",
        "        'gdrive_mounted':mount_gdrive,\n",
        "        'use_stability_api':use_stability_api,\n",
        "        'model_dir':model_dir_str,\n",
        "        'output_dir':'${project_root}/frames'\n",
        "    })\n",
        "\n",
        "    Path(workspace.project_root).mkdir(parents=True, exist_ok=True)\n",
        "    Path(workspace.model_dir).mkdir(parents=True, exist_ok=True)\n",
        "    Path(workspace.output_dir).mkdir(parents=True, exist_ok=True)\n",
        "\n",
        "    ###################\n",
        "\n",
        "    # Assign tracking locations for A/V assets and generally useful outputs\n",
        "\n",
        "    assets_dir = Path(workspace.shared_assets_root)\n",
        "    assets_dir.mkdir(parents=True, exist_ok=True)\n",
        "\n",
        "    # TODO: yaml -> jsonl ?\n",
        "    video_assets_meta_fname = assets_dir / 'video_assets_meta.yaml'\n",
        "    if not video_assets_meta_fname.exists():\n",
        "        video_assets_meta = OmegaConf.create()\n",
        "        video_assets_meta.videos = []\n",
        "        with video_assets_meta_fname.open('w') as fp:\n",
        "            OmegaConf.save(config=video_assets_meta, f=fp.name)\n",
        "    else:\n",
        "        video_assets_meta = OmegaConf.load(video_assets_meta_fname)\n",
        "\n",
        "    audio_assets_meta_fname = assets_dir / 'audio_assets_meta.yaml'\n",
        "    if not audio_assets_meta_fname.exists():\n",
        "        audio_assets_meta = OmegaConf.create()\n",
        "        audio_assets_meta.content = []\n",
        "        with audio_assets_meta_fname.open('w') as fp:\n",
        "            OmegaConf.save(config=audio_assets_meta, f=fp.name)\n",
        "    else:\n",
        "        audio_assets_meta = OmegaConf.load(audio_assets_meta_fname)\n",
        "\n",
        "    ###################\n",
        "\n",
        "    # Request user provide credentials as needed\n",
        "\n",
        "    # if use_stability_api:\n",
        "    #     import os, getpass\n",
        "    #     if not os.environ.get('STABILITY_KEY'):\n",
        "    #         os.environ['STABILITY_KEY'] = getpass.getpass('Enter your Stability API Key, then press enter to continue')\n",
        "    # else:\n",
        "    #     # TODO: check for HF token in environment\n",
        "    #     if not local:\n",
        "    #         from google.colab import output\n",
        "    #         output.enable_custom_widget_manager()\n",
        "\n",
        "    #     from huggingface_hub import notebook_login\n",
        "    #     notebook_login()\n",
        "\n",
        "    ###################\n",
        "\n",
        "    with open('config.yaml','w') as fp:\n",
        "        OmegaConf.save(config=workspace, f=fp.name)\n",
        "\n",
        "    return workspace\n",
        "\n",
        "########################\n",
        "\n",
        "# wrap some of the loading logic for portability\n",
        "\n",
        "\n",
        "def resolve_scene_ids_and_start_end_times(storyboard):\n",
        "    \"\"\"\n",
        "    1. Force first scene to start at frame 0\n",
        "    2. Force last scene to end in accordance with duration\n",
        "    3. Force each scene's `end` attr to correspond to the `start` attr of the subsequent scene\n",
        "    4. Force scene_id to correspond to scenes index position\n",
        "    \"\"\"\n",
        "    # nothing to see here, move along.\n",
        "    if not storyboard.params.get('video_duration'):\n",
        "        return storyboard\n",
        "\n",
        "    storyboard.prompt_starts[0]['start']=0\n",
        "    storyboard.prompt_starts[-1]['end']=max(storyboard.params.video_duration, storyboard.prompt_starts[-1]['end'])\n",
        "    for idx, rec in enumerate(storyboard.prompt_starts):\n",
        "        rec['scene_id']=idx\n",
        "        if idx==0:\n",
        "            prev_rec = rec\n",
        "            continue\n",
        "        prev_rec['end'] = rec['start']\n",
        "    for rec in storyboard.prompt_starts:\n",
        "        rec['duration_'] = rec['end'] - rec['start']\n",
        "    return storyboard\n",
        "\n",
        "def save_storyboard(storyboard):\n",
        "    #if storyboard.params.get('video_duration'):\n",
        "    if storyboard.prompt_starts:\n",
        "        storyboard = resolve_scene_ids_and_start_end_times(storyboard)\n",
        "    root = Path(load_workspace().project_root)\n",
        "    root.mkdir(parents=True, exist_ok=True)\n",
        "    storyboard_fname = root / 'storyboard.yaml'\n",
        "    with open(storyboard_fname, 'w') as fp:\n",
        "        OmegaConf.save(config=storyboard, f=fp.name)\n",
        "\n",
        "def load_workspace():\n",
        "    return OmegaConf.load('config.yaml')\n",
        "\n",
        "def load_storyboard():\n",
        "    workspace = load_workspace()\n",
        "    root = Path(workspace.project_root)\n",
        "    storyboard_fname = root / 'storyboard.yaml'\n",
        "    storyboard = OmegaConf.load(storyboard_fname)\n",
        "    return workspace, storyboard\n",
        "\n",
        "def load_audio_meta(workspace, storyboard):\n",
        "    assets_dir = Path(workspace.shared_assets_root)\n",
        "    audio_assets_meta_fname = assets_dir / 'audio_assets_meta.yaml'\n",
        "    audio_assets_meta = OmegaConf.load(audio_assets_meta_fname)\n",
        "    audio_meta=dict()\n",
        "    for idx, rec in enumerate(audio_assets_meta.content):\n",
        "        if rec.audio_fpath == storyboard.params.audio_fpath:\n",
        "            audio_meta = rec\n",
        "            break\n",
        "    return audio_meta\n",
        "\n",
        "#######################\n",
        "\n",
        "# EXTRA SEGMENTATION STUFF\n",
        "\n",
        "\n",
        "def calculate_interword_gaps(segment):\n",
        "    end_prev = -1\n",
        "    gaps = []\n",
        "    for word in segment['words']:\n",
        "        if end_prev < 0:\n",
        "            end_prev = word['end']\n",
        "            continue\n",
        "        gap = word['start'] - end_prev\n",
        "        gaps.append(gap)\n",
        "        end_prev = word['end']\n",
        "    return gaps\n",
        "\n",
        "def trivial_subsegmentation(segment, threshold=0, gaps=None):\n",
        "    \"\"\"\n",
        "    split on gaps in detected vocal activity.\n",
        "    Contiguity = gap between adjacent tokens is less than the input threshold.\n",
        "    \"\"\"\n",
        "    if gaps is None:\n",
        "        gaps = calculate_interword_gaps(seg)\n",
        "    out_segments = []\n",
        "    this_segment = [seg['words'][0]]\n",
        "    for word, preceding_pause in zip(seg['words'][1:], gaps):\n",
        "        if preceding_pause <= threshold:\n",
        "            this_segment.append(word)\n",
        "        else:\n",
        "            out_segments.append(this_segment)\n",
        "            this_segment = [word]\n",
        "    out_segments.append(this_segment)\n",
        "\n",
        "    outv = [dict(\n",
        "        start=seg[0]['start'],\n",
        "        end=seg[-1]['end'],\n",
        "        text=''.join([w['word'] for w in seg]).strip(),\n",
        "    ) for seg in out_segments]\n",
        "\n",
        "    return outv\n",
        "\n",
        "##############################################################\n",
        "\n",
        "# audio processing\n",
        "\n",
        "\n",
        "def analyze_audio_structure(\n",
        "    audio_fpath,\n",
        "    BINS_PER_OCTAVE = 12 * 3, # should be a multiple of twelve: https://github.com/MTG/essentia/blob/master/src/examples/python/tutorial_spectral_constantq-nsg.ipynb\n",
        "    N_OCTAVES = 7,\n",
        "):\n",
        "    \"\"\"\n",
        "    via librosa docs\n",
        "    https://librosa.org/doc/latest/auto_examples/plot_segmentation.html#sphx-glr-auto-examples-plot-segmentation-py\n",
        "    cites: McFee and Ellis, 2014 - https://brianmcfee.net/papers/ismir2014_spectral.pdf\n",
        "    \"\"\"\n",
        "    y, sr = librosa.load(audio_fpath)\n",
        "\n",
        "    C = librosa.amplitude_to_db(np.abs(librosa.cqt(y=y, sr=sr,\n",
        "                                            bins_per_octave=BINS_PER_OCTAVE,\n",
        "                                            n_bins=N_OCTAVES * BINS_PER_OCTAVE)),\n",
        "                                ref=np.max)\n",
        "\n",
        "    # reduce dimensionality via beat-synchronization\n",
        "    tempo, beats = librosa.beat.beat_track(y=y, sr=sr, trim=False)\n",
        "    Csync = librosa.util.sync(C, beats, aggregate=np.median)\n",
        "\n",
        "    # I have concerns about this frame fixing operation\n",
        "    beat_times = librosa.frames_to_time(librosa.util.fix_frames(beats, x_min=0), sr=sr)\n",
        "\n",
        "    # width=3 prevents links within the same bar\n",
        "    # mode=’affinity’ here implements S_rep (after Eq. 8)\n",
        "    R = librosa.segment.recurrence_matrix(Csync, width=3, mode='affinity', sym=True)\n",
        "    # Enhance diagonals with a median filter (Equation 2)\n",
        "    df = librosa.segment.timelag_filter(scipy.ndimage.median_filter)\n",
        "    Rf = df(R, size=(1, 7))\n",
        "    # build the sequence matrix (S_loc) using mfcc-similarity\n",
        "    mfcc = librosa.feature.mfcc(y=y, sr=sr)\n",
        "    Msync = librosa.util.sync(mfcc, beats)\n",
        "    path_distance = np.sum(np.diff(Msync, axis=1)**2, axis=0)\n",
        "    sigma = np.median(path_distance)\n",
        "    path_sim = np.exp(-path_distance / sigma)\n",
        "    R_path = np.diag(path_sim, k=1) + np.diag(path_sim, k=-1)\n",
        "    # compute the balanced combination\n",
        "    deg_path = np.sum(R_path, axis=1)\n",
        "    deg_rec = np.sum(Rf, axis=1)\n",
        "    mu = deg_path.dot(deg_path + deg_rec) / np.sum((deg_path + deg_rec)**2)\n",
        "    A = mu * Rf + (1 - mu) * R_path\n",
        "\n",
        "    # compute normalized laplacian and its spectrum\n",
        "    L = scipy.sparse.csgraph.laplacian(A, normed=True)\n",
        "    evals, evecs = scipy.linalg.eigh(L)\n",
        "    # clean this up with a median filter. can help smooth over discontinuities\n",
        "    evecs = scipy.ndimage.median_filter(evecs, size=(9, 1))\n",
        "    return dict(\n",
        "        y=y,\n",
        "        sr=np.array(sr).astype(np.uint32),\n",
        "        tempo=tempo,\n",
        "        beats=beats,\n",
        "        beat_times=beat_times,\n",
        "        evecs=evecs,\n",
        "    )\n",
        "\n",
        "\n",
        "def laplacian_segmentation(\n",
        "    audio_fpath=None,\n",
        "    evecs=None,\n",
        "    n_clusters = 5,\n",
        "    n_spectral_features = None,\n",
        "):\n",
        "    \"\"\"\n",
        "    segment audio by clustering a self-similarity matrix.\n",
        "    via librosa docs\n",
        "    https://librosa.org/doc/latest/auto_examples/plot_segmentation.html#sphx-glr-auto-examples-plot-segmentation-py\n",
        "    cites: McFee and Ellis, 2014 - https://brianmcfee.net/papers/ismir2014_spectral.pdf\n",
        "    \"\"\"\n",
        "    if evecs is None:\n",
        "        if audio_fpath is None:\n",
        "            raise Exception(\"One of `audio_fpath` or `evecs` must be provided\")\n",
        "        features = analyze_audio_structure(audio_fpath)\n",
        "        evecs = features['evecs']\n",
        "\n",
        "    if n_clusters < 2:\n",
        "        seg_ids = np.zeros(evecs.shape[0], dtype=int)\n",
        "        return seg_ids\n",
        "\n",
        "    if n_spectral_features is None:\n",
        "        n_spectral_features = n_clusters\n",
        "\n",
        "    # cumulative normalization is needed for symmetric normalize laplacian eigenvectors\n",
        "    Cnorm = np.cumsum(evecs**2, axis=1)**0.5\n",
        "    k = n_spectral_features\n",
        "    X = evecs[:, :k] / Cnorm[:, k-1:k]\n",
        "\n",
        "\n",
        "    # use these k components to cluster beats into segments\n",
        "    KM = sklearn.cluster.KMeans(n_clusters=n_clusters, n_init=\"auto\")\n",
        "    seg_ids = KM.fit_predict(X)\n",
        "\n",
        "    return seg_ids #, beat_times, tempo\n",
        "\n",
        "\n",
        "# for video duration\n",
        "def get_audio_duration_seconds(audio_fpath):\n",
        "    outv = subprocess.run([\n",
        "        'ffprobe'\n",
        "        ,'-i',audio_fpath\n",
        "        ,'-show_entries', 'format=duration'\n",
        "        ,'-v','quiet'\n",
        "        ,'-of','csv=p=0'\n",
        "        ],\n",
        "        stdout=subprocess.PIPE\n",
        "        ).stdout.decode('utf-8')\n",
        "    return float(outv.strip())\n",
        "\n",
        "\n",
        "##########################################\n",
        "\n",
        "# animation stuff\n",
        "\n",
        "# TODO: update this stuff to reflect updates to API/sdk\n",
        "def get_image_for_prompt_sai(prompt, max_retries=5, **kargs):\n",
        "    stability_api = client.StabilityInference(\n",
        "        key=os.environ['STABILITY_KEY'],\n",
        "        verbose=False,\n",
        "    )\n",
        "\n",
        "    # auto-retry if mitigation triggered\n",
        "    while max_retries:\n",
        "        try:\n",
        "            answers = stability_api.generate(prompt=prompt, **kargs)\n",
        "            response = process_response(answers)\n",
        "            for img in response:\n",
        "                yield img\n",
        "            break\n",
        "\n",
        "        # TODO: better regen handling\n",
        "        except RuntimeError:\n",
        "            print(\"runtime error\")\n",
        "            max_retries -= 1\n",
        "            warnings.warn(f\"mitigation triggered, retries remaining: {max_retries}\")\n",
        "\n",
        "def process_response(answers):\n",
        "    for resp in answers:\n",
        "        for artifact in resp.artifacts:\n",
        "            if artifact.finish_reason == generation.FILTER:\n",
        "                warnings.warn(\n",
        "                    \"Your request activated the API's safety filters and could not be processed.\"\n",
        "                    \"Please modify the prompt and try again.\")\n",
        "                raise RuntimeError\n",
        "            if artifact.type == generation.ARTIFACT_IMAGE:\n",
        "                img = Image.open(io.BytesIO(artifact.binary))\n",
        "                yield img\n",
        "\n",
        "\n",
        "########################################\n",
        "\n",
        "# misc utils\n",
        "\n",
        "def rand_str(n_char=5):\n",
        "    return ''.join(random.choice(string.ascii_lowercase) for i in range(n_char))\n",
        "\n",
        "def save_frame(\n",
        "    img: Image,\n",
        "    idx:int=0,\n",
        "    root_path=Path('./frames'),\n",
        "    name=None,\n",
        "):\n",
        "    root_path.mkdir(parents=True, exist_ok=True)\n",
        "    if name is None:\n",
        "        name = rand_str()\n",
        "    outpath = root_path / f\"{idx}-{name}.png\"\n",
        "    img.save(outpath)\n",
        "    return str(outpath)\n",
        "\n",
        "def get_image_sequence(idx, root, init_first=True):\n",
        "    root = Path(root)\n",
        "    images = (root / 'frames' ).glob(f'{idx}-*.png')\n",
        "    images = [str(fp) for fp in images]\n",
        "    if init_first:\n",
        "        init_image = None\n",
        "        images2 = []\n",
        "        for i, fp in enumerate(images):\n",
        "            if 'anchor' in fp:\n",
        "                init_image = fp\n",
        "            else:\n",
        "                images2.append(fp)\n",
        "        if not init_image:\n",
        "            try:\n",
        "                init_image, images2 = images2[0], images2[1:]\n",
        "                images = [init_image] + images2\n",
        "            except IndexError:\n",
        "                images = images2\n",
        "    return images\n",
        "\n",
        "def archive_images(idx, root, archive_root = None):\n",
        "    root = Path(root)\n",
        "    if archive_root is None:\n",
        "        archive_root = root / 'archive'\n",
        "    archive_root = Path(archive_root)\n",
        "    archive_root.mkdir(parents=True, exist_ok=True)\n",
        "    old_images = get_image_sequence(idx, root=root)\n",
        "    if not old_images:\n",
        "        return\n",
        "    print(f\"moving {len(old_images)} old images for scene {idx} to {archive_root}\")\n",
        "    for old_fp in old_images:\n",
        "        old_fp = Path(old_fp)\n",
        "        im_name = Path(old_fp.name)\n",
        "        new_path = archive_root / im_name\n",
        "        if new_path.exists():\n",
        "            im_name = f\"{im_name.stem}-{time.time()}{im_name.suffix}\"\n",
        "            new_path = archive_root / im_name\n",
        "        old_fp.rename(new_path)\n",
        "\n",
        "\n",
        "############################\n",
        "\n",
        "# video compilation stuff\n",
        "\n",
        "# TODO: Sorting algorithm that can tolerate more than 15-ish frames (GPU?)\n",
        "def tsp_sort(frames):\n",
        "    frames_m = np.array([np.array(f).ravel() for f in frames])\n",
        "    dmat = pdist(frames_m, metric='cosine')\n",
        "    dmat = squareform(dmat)\n",
        "    permutation, _ = solve_tsp_dynamic_programming(dmat)\n",
        "    return permutation\n",
        "\n",
        "def add_caption2image(\n",
        "      image,\n",
        "      caption,\n",
        "      text_font='LiberationSans-Regular.ttf',\n",
        "      font_size=20,\n",
        "      fill_color=(255, 255, 255),\n",
        "      stroke_color=(0, 0, 0), #stroke_fill\n",
        "      stroke_width=2,\n",
        "      align='center',\n",
        "      ):\n",
        "    # via https://stackoverflow.com/a/59104505/819544\n",
        "    wrapper = textwrap.TextWrapper(width=50)\n",
        "    word_list = wrapper.wrap(text=caption)\n",
        "    caption_new = ''\n",
        "    for ii in word_list[:-1]:\n",
        "        caption_new = caption_new + ii + '\\n'\n",
        "    caption_new += word_list[-1]\n",
        "\n",
        "    draw = ImageDraw.Draw(image)\n",
        "\n",
        "    # Download the Font and Replace the font with the font file.\n",
        "    font = ImageFont.truetype(text_font, size=font_size)\n",
        "    w,h = draw.textsize(caption_new, font=font, stroke_width=stroke_width)\n",
        "    W,H = image.size\n",
        "    x,y = 0.5*(W-w),0.90*H-h\n",
        "    draw.text(\n",
        "        (x,y),\n",
        "        caption_new,\n",
        "        font=font,\n",
        "        fill=fill_color,\n",
        "        stroke_fill=stroke_color,\n",
        "        stroke_width=stroke_width,\n",
        "        align=align,\n",
        "    )\n",
        "\n",
        "    return image\n",
        "\n",
        "##########################################################\n",
        "\n",
        "# audioreactivity stuff\n",
        "\n",
        "\n",
        "def full_width_plot():\n",
        "    ax = plt.gca()\n",
        "    ax.figure.set_figwidth(20)\n",
        "    plt.show()\n",
        "\n",
        "def display_signal(y, sr, show_spec=True, title=None, start_time=0, end_time=9999):\n",
        "\n",
        "#     if show_spec:\n",
        "#         frame_time = librosa.samples_to_time(np.arange(len(normalized_signal)), sr=sr)\n",
        "#     else:\n",
        "#         frame_time = librosa.frames_to_time(np.arange(len(normalized_signal)), sr=sr)\n",
        "\n",
        "    if show_spec:\n",
        "        #librosa.display.waveshow(y, sr=sr)\n",
        "        times = librosa.samples_to_time(np.arange(len(y)), sr=sr)\n",
        "    else:\n",
        "        #times = librosa.times_like(y, sr=sr).ravel()\n",
        "        times = librosa.frames_to_time(np.arange(len(y)), sr=sr).ravel()\n",
        "\n",
        "    start_idx = np.argmax(start_time <= times)\n",
        "    #end_idx = len(times) - np.argmax([end_time <= times][::-1])\n",
        "    end_idx = np.argmax(end_time <= times)\n",
        "    if start_idx >= end_idx:\n",
        "        end_idx = -1\n",
        "\n",
        "    times = times[start_idx:end_idx]\n",
        "    y = y[start_idx:end_idx]\n",
        "\n",
        "    plt.plot(times, y)\n",
        "    if title:\n",
        "        plt.title(title)\n",
        "    full_width_plot()\n",
        "\n",
        "    if show_spec:\n",
        "        try:\n",
        "            M = librosa.feature.melspectrogram(y=y, sr=sr)\n",
        "            librosa.display.specshow(librosa.power_to_db(M, ref=np.max),\n",
        "                             y_axis='mel', x_axis='time')\n",
        "            full_width_plot()\n",
        "\n",
        "        except:\n",
        "            pass\n",
        "\n",
        "    # plt.plot(frame_time, y)\n",
        "    # if title:\n",
        "    #     plt.title(title)\n",
        "    # full_width_plot()\n",
        "\n",
        "\n",
        "# https://github.com/pytti-tools/pytti-core/blob/9e8568365cfdc123d2d2fbc20d676ca0f8715341/src/pytti/AudioParse.py#L95\n",
        "from scipy.signal import butter, sosfilt, sosfreqz\n",
        "\n",
        "def butter_bandpass(lowcut, highcut, fs, order):\n",
        "    nyq = 0.5 * fs\n",
        "    low = lowcut / nyq\n",
        "    high = highcut / nyq\n",
        "    sos = butter(order, [low, high], analog=False, btype='bandpass', output='sos')\n",
        "    return sos\n",
        "\n",
        "def butter_bandpass_filter(y, sr, lowcut, highcut, order=10):\n",
        "    sos = butter_bandpass(lowcut, highcut, sr, order=order)\n",
        "    y = sosfilt(sos, y)\n",
        "    return y\n",
        "\n",
        "########################################################################\n",
        "\n",
        "def is_multi_valued_curve(curve_str):\n",
        "    try:\n",
        "        return (\": (\" in curve_str) and (\"), \" in curve_str)\n",
        "    except:\n",
        "        return False\n",
        "\n",
        "def show_storyboard(storyboard=None):\n",
        "    if storyboard is None:\n",
        "        workspace, storyboard = load_storyboard()\n",
        "    storyboard = resolve_scene_ids_and_start_end_times(storyboard)\n",
        "\n",
        "    # TODO: fix this...\n",
        "    reactive_signal_map = {}\n",
        "    if storyboard.get('audioreactive'):\n",
        "        reactive_signal_map = storyboard.audioreactive.get('reactive_signal_map')\n",
        "\n",
        "    # TODO: should just invoke compile for basically all of this.\n",
        "    try:\n",
        "        # ... borks on ifps...\n",
        "        settings = compile_storyboard(ignore_defaults=True)\n",
        "    except:\n",
        "        settings = []\n",
        "\n",
        "    for idx, rec in enumerate(storyboard.prompt_starts):\n",
        "        report = f\"scene: {idx}\\t start: {rec['start']:.2f}\"\n",
        "        if rec.get('duration_'):\n",
        "            report += f\"\\t duration: {rec.get('duration_'):.2f}\"\n",
        "        report += f\"\\nspoken text: {rec.get('text')}\\n\"\n",
        "\n",
        "        # TODO: wrap prompt construction logic in a function (better yet use omegaconf substitution variables)\n",
        "\n",
        "        #'_theme':'theme', 'structural_segmentation_label':\n",
        "        if rec.get('_theme'):\n",
        "            report += f\"theme prompt: {rec['_theme']}\\n\"\n",
        "        #f\"image prompt: {rec['_prompt']}\\n\"\n",
        "        prompt = rec.get('prompt')\n",
        "        #if not prompt:\n",
        "        #    prompt = ...\n",
        "        if prompt:\n",
        "            report += f\"image prompt: {rec['_prompt']}\\n\"\n",
        "\n",
        "        if rec.get('animation_mode'):\n",
        "            report += f\"animation mode: {rec['animation_mode']}\"\n",
        "        print(report)\n",
        "        im_path = rec.get('frame0_fpath')\n",
        "        if im_path and Path(im_path).exists():\n",
        "            display(Image.open(rec['frame0_fpath']))\n",
        "\n",
        "        #if reactive_signal_map:\n",
        "        n = rec['frames']\n",
        "        if n <1:\n",
        "          continue\n",
        "\n",
        "        if not settings:\n",
        "            continue\n",
        "        scene_settings = settings[idx]\n",
        "            #for signal_name in reactive_signal_map.keys():\n",
        "        for signal_name, signal_val in scene_settings.items():\n",
        "            if not is_multi_valued_curve(signal_val):\n",
        "                continue\n",
        "\n",
        "            curve = kf.dsl.curve_from_cn_string(signal_val)\n",
        "            xs = [i for i in range(n)]\n",
        "            ys = [curve[i] for i in xs]\n",
        "            plt.plot(xs, ys, label=signal_name)\n",
        "            plt.title(f\"scene {idx} - {signal_name}\")\n",
        "            plt.xlabel(\"frame index within scene\")\n",
        "            plt.legend()\n",
        "            plt.show()\n",
        "\n",
        "#########################################\n",
        "\n",
        "def get_path_to_stems():\n",
        "    workspace, storyboard = load_storyboard()\n",
        "    assets_root = Path(workspace.application_root) / 'shared_assets'\n",
        "    #stems_path = root / \"stems\"\n",
        "    stems_path = assets_root / \"stems\"\n",
        "    stems_outpath = stems_path / 'htdemucs_ft' / Path(storyboard.params.audio_fpath).stem\n",
        "    return stems_outpath\n",
        "\n",
        "def ensure_stems_separated():\n",
        "    stems_outpath = get_path_to_stems()\n",
        "    stems_path = str(stems_outpath.parent.parent)\n",
        "    if not stems_outpath.exists():\n",
        "        !demucs -n htdemucs_ft -o \"{stems_path}\" \"{storyboard.params.audio_fpath}\"\n",
        "\n",
        "def get_stem(instrument_name):\n",
        "    ensure_stems_separated()\n",
        "    stems_outpath = get_path_to_stems()\n",
        "    stem_fpaths  = list(stems_outpath.glob('*.wav'))\n",
        "\n",
        "    for stem_fpath in stem_fpaths:\n",
        "        if instrument_name in str(stem_fpath):\n",
        "            y, sr = librosa.load(stem_fpath)\n",
        "            return y, sr\n",
        "    raise ValueError(\n",
        "        f\"Unable to locate stem for instrument: {instrument_name}\\n\"\n",
        "        f\"in folder: {stems_outpath}\"\n",
        "    )\n",
        "\n",
        "\n",
        "##########################################################################################################\n",
        "\n",
        "# deforum compatibility sprint\n",
        "\n",
        "\n",
        "import math\n",
        "import numpy as np\n",
        "\n",
        "def build_eval_scope(storyboard):\n",
        "    # preload eval scope with math stuff\n",
        "    math_env = {\n",
        "        \"abs\": abs,\n",
        "        \"max\": max,\n",
        "        \"min\": min,\n",
        "        \"pow\": pow,\n",
        "        \"round\": round,\n",
        "        \"np\": np,\n",
        "        \"__builtins__\": None,\n",
        "    }\n",
        "    math_env.update(\n",
        "        {key: getattr(math, key) for key in dir(math) if \"_\" not in key}\n",
        "    )\n",
        "\n",
        "    # add signals to scope\n",
        "    for signal_name, sig_curve in storyboard.signals.items():\n",
        "        sig_curve = OmegaConf.to_container(sig_curve) # zomg...\n",
        "        curve = load_curve(sig_curve)\n",
        "        math_env[signal_name] = curve\n",
        "    return math_env\n",
        "\n",
        "\n",
        "#eval(signal_mappings['noise_curve'], math_env, t=0)\n",
        "#math_env['t']=0\n",
        "#eval(signal_mappings['noise_curve'], math_env)\n",
        "\n",
        "\n",
        "#################\n",
        "\n",
        "\n",
        "true=True\n",
        "false=False\n",
        "DEFORUM_DEFAULTS = {\n",
        "    \"W\": 512,\n",
        "    \"H\": 512,\n",
        "    \"show_info_on_ui\": true,\n",
        "    \"tiling\": false,\n",
        "    \"restore_faces\": false,\n",
        "    \"seed_resize_from_w\": 0,\n",
        "    \"seed_resize_from_h\": 0,\n",
        "    \"seed\": -1,\n",
        "    \"sampler\": \"Euler a\",\n",
        "    \"steps\": 25,\n",
        "    \"batch_name\": \"Deforum_{timestring}\",\n",
        "    \"seed_behavior\": \"iter\",\n",
        "    \"seed_iter_N\": 1,\n",
        "    \"use_init\": false,\n",
        "    \"strength\": 0.8,\n",
        "    \"strength_0_no_init\": true,\n",
        "    \"init_image\": \"https://deforum.github.io/a1/I1.png\",\n",
        "    \"use_mask\": false,\n",
        "    \"use_alpha_as_mask\": false,\n",
        "    \"mask_file\": \"https://deforum.github.io/a1/M1.jpg\",\n",
        "    \"invert_mask\": false,\n",
        "    \"mask_contrast_adjust\": 1.0,\n",
        "    \"mask_brightness_adjust\": 1.0,\n",
        "    \"overlay_mask\": true,\n",
        "    \"mask_overlay_blur\": 4,\n",
        "    \"fill\": 1,\n",
        "    \"full_res_mask\": true,\n",
        "    \"full_res_mask_padding\": 4,\n",
        "    \"reroll_blank_frames\": \"ignore\",\n",
        "    \"reroll_patience\": 10.0,\n",
        "    # \"prompts\": {\n",
        "    #     \"0\": \"tiny cute bunny, vibrant diffraction, highly detailed, intricate, ultra hd, sharp photo, crepuscular rays, in focus, by tomasz alen kopera\",\n",
        "    #     \"30\": \"anthropomorphic clean cat, surrounded by fractals, epic angle and pose, symmetrical, 3d, depth of field, ruan jia and fenghua zhong\",\n",
        "    #     \"60\": \"a beautiful coconut --neg photo, realistic\",\n",
        "    #     \"90\": \"a beautiful durian, trending on Artstation\"\n",
        "    # },\n",
        "    \"animation_prompts_positive\": \"\",\n",
        "    \"animation_prompts_negative\": \"nsfw, nude\",\n",
        "    \"animation_mode\": \"2D\",\n",
        "    \"max_frames\": 1,\n",
        "    \"border\": \"replicate\",\n",
        "    \"angle\": \"0: (0)\",\n",
        "    \"zoom\": \"0: (1.0025+0.002*sin(1.25*3.14*t/30))\",\n",
        "    \"translation_x\": \"0: (0)\",\n",
        "    \"translation_y\": \"0: (0)\",\n",
        "    \"translation_z\": \"0: (1.75)\",\n",
        "    \"transform_center_x\": \"0: (0.5)\",\n",
        "    \"transform_center_y\": \"0: (0.5)\",\n",
        "    \"rotation_3d_x\": \"0: (0)\",\n",
        "    \"rotation_3d_y\": \"0: (0)\",\n",
        "    \"rotation_3d_z\": \"0: (0)\",\n",
        "    \"enable_perspective_flip\": false,\n",
        "    \"perspective_flip_theta\": \"0: (0)\",\n",
        "    \"perspective_flip_phi\": \"0: (0)\",\n",
        "    \"perspective_flip_gamma\": \"0: (0)\",\n",
        "    \"perspective_flip_fv\": \"0: (53)\",\n",
        "    \"noise_schedule\": \"0: (0.065)\",\n",
        "    \"strength_schedule\": \"0: (0.65)\",\n",
        "    \"contrast_schedule\": \"0: (1.0)\",\n",
        "    \"cfg_scale_schedule\": \"0: (7)\",\n",
        "    \"enable_steps_scheduling\": false,\n",
        "    \"steps_schedule\": \"0: (25)\",\n",
        "    \"fov_schedule\": \"0: (70)\",\n",
        "    \"aspect_ratio_schedule\": \"0: (1)\",\n",
        "    \"aspect_ratio_use_old_formula\": false,\n",
        "    \"near_schedule\": \"0: (200)\",\n",
        "    \"far_schedule\": \"0: (10000)\",\n",
        "    \"seed_schedule\": \"0:(s), 1:(-1), \\\"max_f-2\\\":(-1), \\\"max_f-1\\\":(s)\",\n",
        "    \"pix2pix_img_cfg_scale_schedule\": \"0:(1.5)\",\n",
        "    \"enable_subseed_scheduling\": false,\n",
        "    \"subseed_schedule\": \"0: (1)\",\n",
        "    \"subseed_strength_schedule\": \"0: (0)\",\n",
        "    \"enable_sampler_scheduling\": false,\n",
        "    \"sampler_schedule\": \"0: (\\\"Euler a\\\")\",\n",
        "    \"use_noise_mask\": false,\n",
        "    \"mask_schedule\": \"0: (\\\"{video_mask}\\\")\",\n",
        "    \"noise_mask_schedule\": \"0: (\\\"{video_mask}\\\")\",\n",
        "    \"enable_checkpoint_scheduling\": false,\n",
        "    \"checkpoint_schedule\": \"0: (\\\"model1.ckpt\\\"), 100: (\\\"model2.safetensors\\\")\",\n",
        "    \"enable_clipskip_scheduling\": false,\n",
        "    \"clipskip_schedule\": \"0: (2)\",\n",
        "    \"enable_noise_multiplier_scheduling\": true,\n",
        "    \"noise_multiplier_schedule\": \"0: (1.05)\",\n",
        "    \"resume_from_timestring\": false,\n",
        "    \"resume_timestring\": \"20230621175028\",\n",
        "    \"enable_ddim_eta_scheduling\": false,\n",
        "    \"ddim_eta_schedule\": \"0: (0)\",\n",
        "    \"enable_ancestral_eta_scheduling\": false,\n",
        "    \"ancestral_eta_schedule\": \"0: (1)\",\n",
        "    \"amount_schedule\": \"0: (0.1)\",\n",
        "    \"kernel_schedule\": \"0: (5)\",\n",
        "    \"sigma_schedule\": \"0: (1)\",\n",
        "    \"threshold_schedule\": \"0: (0)\",\n",
        "    \"color_coherence\": \"LAB\",\n",
        "    \"color_coherence_image_path\": \"\",\n",
        "    \"color_coherence_video_every_N_frames\": 1,\n",
        "    \"color_force_grayscale\": false,\n",
        "    \"legacy_colormatch\": false,\n",
        "    \"diffusion_cadence\": 2,\n",
        "    \"optical_flow_cadence\": \"None\",\n",
        "    \"cadence_flow_factor_schedule\": \"0: (1)\",\n",
        "    \"optical_flow_redo_generation\": \"None\",\n",
        "    \"redo_flow_factor_schedule\": \"0: (1)\",\n",
        "    \"diffusion_redo\": \"0\",\n",
        "    \"noise_type\": \"perlin\",\n",
        "    \"perlin_octaves\": 4,\n",
        "    \"perlin_persistence\": 0.5,\n",
        "    \"use_depth_warping\": true,\n",
        "    \"depth_algorithm\": \"Midas-3-Hybrid\",\n",
        "    \"midas_weight\": 0.2,\n",
        "    \"padding_mode\": \"border\",\n",
        "    \"sampling_mode\": \"bicubic\",\n",
        "    \"save_depth_maps\": false,\n",
        "    \"video_init_path\": \"https://deforum.github.io/a1/V1.mp4\",\n",
        "    \"extract_nth_frame\": 1,\n",
        "    \"extract_from_frame\": 0,\n",
        "    \"extract_to_frame\": -1,\n",
        "    \"overwrite_extracted_frames\": false,\n",
        "    \"use_mask_video\": false,\n",
        "    \"video_mask_path\": \"https://deforum.github.io/a1/VM1.mp4\",\n",
        "    \"hybrid_comp_alpha_schedule\": \"0:(0.5)\",\n",
        "    \"hybrid_comp_mask_blend_alpha_schedule\": \"0:(0.5)\",\n",
        "    \"hybrid_comp_mask_contrast_schedule\": \"0:(1)\",\n",
        "    \"hybrid_comp_mask_auto_contrast_cutoff_high_schedule\": \"0:(100)\",\n",
        "    \"hybrid_comp_mask_auto_contrast_cutoff_low_schedule\": \"0:(0)\",\n",
        "    \"hybrid_flow_factor_schedule\": \"0:(1)\",\n",
        "    \"hybrid_generate_inputframes\": false,\n",
        "    \"hybrid_generate_human_masks\": \"None\",\n",
        "    \"hybrid_use_first_frame_as_init_image\": true,\n",
        "    \"hybrid_motion\": \"None\",\n",
        "    \"hybrid_motion_use_prev_img\": false,\n",
        "    \"hybrid_flow_consistency\": false,\n",
        "    \"hybrid_consistency_blur\": 2,\n",
        "    \"hybrid_flow_method\": \"RAFT\",\n",
        "    \"hybrid_composite\": \"None\",\n",
        "    \"hybrid_use_init_image\": false,\n",
        "    \"hybrid_comp_mask_type\": \"None\",\n",
        "    \"hybrid_comp_mask_inverse\": false,\n",
        "    \"hybrid_comp_mask_equalize\": \"None\",\n",
        "    \"hybrid_comp_mask_auto_contrast\": false,\n",
        "    \"hybrid_comp_save_extra_frames\": false,\n",
        "    \"parseq_manifest\": \"\",\n",
        "    \"parseq_use_deltas\": true,\n",
        "    \"use_looper\": false,\n",
        "    \"init_images\": \"{\\n    \\\"0\\\": \\\"https://deforum.github.io/a1/Gi1.png\\\",\\n    \\\"max_f/4-5\\\": \\\"https://deforum.github.io/a1/Gi2.png\\\",\\n    \\\"max_f/2-10\\\": \\\"https://deforum.github.io/a1/Gi3.png\\\",\\n    \\\"3*max_f/4-15\\\": \\\"https://deforum.github.io/a1/Gi4.jpg\\\",\\n    \\\"max_f-20\\\": \\\"https://deforum.github.io/a1/Gi1.png\\\"\\n}\",\n",
        "    \"image_strength_schedule\": \"0:(0.75)\",\n",
        "    \"blendFactorMax\": \"0:(0.35)\",\n",
        "    \"blendFactorSlope\": \"0:(0.25)\",\n",
        "    \"tweening_frames_schedule\": \"0:(20)\",\n",
        "    \"color_correction_factor\": \"0:(0.075)\",\n",
        "    \"skip_video_creation\": false,\n",
        "    \"fps\": 15,\n",
        "    \"make_gif\": false,\n",
        "    \"delete_imgs\": false,\n",
        "    \"add_soundtrack\": \"None\",\n",
        "    \"soundtrack_path\": \"https://deforum.github.io/a1/A1.mp3\",\n",
        "    \"r_upscale_video\": false,\n",
        "    \"r_upscale_factor\": \"x2\",\n",
        "    \"r_upscale_model\": \"realesr-animevideov3\",\n",
        "    \"r_upscale_keep_imgs\": true,\n",
        "    \"store_frames_in_ram\": false,\n",
        "    \"frame_interpolation_engine\": \"None\",\n",
        "    \"frame_interpolation_x_amount\": 2,\n",
        "    \"frame_interpolation_slow_mo_enabled\": false,\n",
        "    \"frame_interpolation_slow_mo_amount\": 2,\n",
        "    \"frame_interpolation_keep_imgs\": true,\n",
        "    \"frame_interpolation_use_upscaled\": false,\n",
        "    \"sd_model_name\": \"v1-5-pruned-emaonly.ckpt\",\n",
        "    \"sd_model_hash\": \"81761151\",\n",
        "    \"deforum_git_commit_id\": \"b58056f9\",\n",
        "}\n",
        "\n",
        "\n",
        "def resolve_prompt(idx, storyboard):\n",
        "    prompt_lag = storyboard.params.get(\"prompt_lag\",True)\n",
        "    rec = storyboard.prompt_starts[idx]\n",
        "    if not rec.get('_prompt'):\n",
        "        theme = rec.get('_theme')\n",
        "        prompt = rec.get('prompt')\n",
        "        if not prompt:\n",
        "            prompt = f\"{rec['text']}, {theme}\"\n",
        "\n",
        "            if prompt_lag and (idx > 0):\n",
        "                rec_prev = storyboard.prompt_starts[idx -1]\n",
        "                prev_text = rec_prev.get('text','')\n",
        "                if not prev_text:\n",
        "                    prev_text = rec_prev.get('prompt','').split(',')[0]\n",
        "                this_text = rec.get('text','')\n",
        "                if this_text:\n",
        "                    prompt = f\"{prev_text} {this_text}, {theme}\"\n",
        "                else:\n",
        "                    prompt = rec_prev['_prompt']\n",
        "        rec['_prompt'] = prompt\n",
        "\n",
        "\n",
        "def resolve_signals(scene_id=0, storyboard=None):\n",
        "    if storyboard is None:\n",
        "        _, storyboard = load_storyboard()\n",
        "    idx=scene_id\n",
        "    rec = storyboard.prompt_starts[scene_id]\n",
        "    # resolve signals\n",
        "    default_mappings = storyboard.get('signal_mappings',{})\n",
        "    signal_mappings = rec.get('signal_mappings', default_mappings)\n",
        "\n",
        "    math_env = build_eval_scope(storyboard)\n",
        "    curves = {}\n",
        "    for param_name, param_meta in signal_mappings.items():\n",
        "        param_expr, attr_hi, attr_low = param_meta['parameter_value'], param_meta['param_max'], param_meta['param_min']\n",
        "        curve_chunks = []\n",
        "        start=rec['start']\n",
        "        for frame_idx in range(rec['frames']):\n",
        "            curr_time = start + frame_idx * ifps\n",
        "            # SUPPORTED VARIABLES\n",
        "            # TODO: describe this somewhere\n",
        "            math_env['t'] = curr_time\n",
        "            math_env['scene_id'] = idx\n",
        "            math_env['frame_id'] = frame_idx\n",
        "            math_env['theme_id'] = rec.get('structural_segmentation_label',0)\n",
        "            signal_value=eval(param_expr, math_env)\n",
        "            #if inverse_relationship:\n",
        "            if param_meta['invert_relationship_to_signal']:\n",
        "                signal_value = 1-signal_value\n",
        "            attr_value = signal_value*(attr_hi-attr_low)+attr_low\n",
        "            curve_chunks.append(f\"{frame_idx}: ({attr_value})\")\n",
        "        curve_str = ', '.join(curve_chunks)\n",
        "        #scene_args[param_name] = curve_str\n",
        "        #scene_args = OmegaConf.to_container(scene_args)\n",
        "        curves[param_name] = curve_str\n",
        "    return curves\n",
        "\n",
        "\n",
        "def compile_storyboard(storyboard=None, ignore_defaults=False):\n",
        "    if storyboard is None:\n",
        "        _, storyboard = load_storyboard()\n",
        "    scenes = []\n",
        "    story_defaults = storyboard.get('animation_args',{})\n",
        "    for idx, rec in enumerate(storyboard.prompt_starts):\n",
        "\n",
        "        # establish defaults\n",
        "        scene_args=copy.deepcopy(story_defaults)\n",
        "        if not ignore_defaults:\n",
        "            scene_args = rec.get('animation_args',{})\n",
        "        try:\n",
        "            scene_args = OmegaConf.to_container(scene_args)\n",
        "        except ValueError:\n",
        "            pass\n",
        "\n",
        "        # translate settings to deforum names\n",
        "        resolve_prompt(idx, storyboard)\n",
        "        #scene_args['prompts'] = {\"0\":rec['_prompt']}\n",
        "        #scene_args['max_frames'] = rec['frames']\n",
        "        scene_args['_prompt'] = rec['_prompt']\n",
        "        scene_args['frames'] = rec['frames']\n",
        "        scene_args['init_image'] = rec.get('frame0_fpath')\n",
        "\n",
        "        curves = resolve_signals(idx, storyboard)\n",
        "        scene_args.update(curves)\n",
        "        scenes.append(scene_args)\n",
        "    return scenes\n",
        "\n",
        "# TODO: copy init images into this folder, add to settings.txt's\n",
        "\n",
        "\n",
        "\n",
        "def make_compatible_for_deforum(settings, backfill_deforum_defaults = True):\n",
        "    fields_mapping={\n",
        "        #'prompts':'prompts',\n",
        "        'fps':'fs',\n",
        "        'extract_nth_frame':'extract_nth_frame',\n",
        "        'angle':'angle',\n",
        "        'zoom':'zoom',\n",
        "        'translation_x': 'translation_x',\n",
        "        'translation_y': 'translation_y',\n",
        "        'translation_z': 'translation_z',\n",
        "        'rotation_x': 'rotation_3d_x',\n",
        "        'rotation_y': 'rotation_3d_y',\n",
        "        'rotation_z': 'rotation_3d_z',\n",
        "        'frames':'max_frames',\n",
        "        #'noise_add_curve':'noise_schedule',\n",
        "        'noise_curve':'noise_schedule',\n",
        "        'noise_scale_curve':'noise_multiplier_curve',\n",
        "        'strength_curve':'strength_schedule',\n",
        "        'steps_curve':'steps_schedule',\n",
        "        'srength_curve': 'strength_schedule',\n",
        "        'steps_curve': 'steps_schedule',\n",
        "    }\n",
        "    # SAI_scalars_2_deforum_curves = {\n",
        "    #     'cfg_scale':'cfg_scale_schedule',\n",
        "    #     'seed':'seed_schedule',\n",
        "    # }\n",
        "    #keep_params = set(fields_mapping) + set(SAI_scalars_2_deforum_curves)\n",
        "\n",
        "    outv = {}\n",
        "    if backfill_deforum_defaults:\n",
        "        outv.update(DEFORUM_DEFAULTS )\n",
        "\n",
        "    for k,v in settings.items():\n",
        "        if k in fields_mapping:\n",
        "            outv[fields_mapping[k]] = v\n",
        "        elif k == '_prompt':\n",
        "            outv['prompts'] = {\"0\":v}\n",
        "        elif k == 'cfg_scale':\n",
        "            outv['cfg_scale_schedule'] = f\"0: ({v})\"\n",
        "        elif k == 'seed':\n",
        "            if v == -1:\n",
        "                outv['seed_schedule'] = \"0: (-1)\"\n",
        "            else:\n",
        "                seed_schedule_chunks = []\n",
        "                for i in range(settings['max_frames']):\n",
        "                    #if v == -1:\n",
        "                    #    v = random.randrange(0, 4294967295)\n",
        "                    seed_schedule_chunks.append(f\"{i}: ({v})\")\n",
        "                    v+=1\n",
        "                outv['seed_schedule'] = ', '.join(seed_schedule_chunks)\n",
        "    return outv\n",
        "\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PknSJ48jAmuP"
      },
      "source": [
        "## 1. 📋💬 Build Base Storyboard\n",
        "* Initial setup\n",
        "* Infer keyframes for scene segmentation"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "cM8cux9b7F4v",
        "tags": []
      },
      "outputs": [],
      "source": [
        "# @title ### 📋 Attach Storyboard (create or resume project)\n",
        "\n",
        "project_name = '' # @param {type:'string'}\n",
        "\n",
        "# TODO: make it so I can change this value without restarting the kernel....\n",
        "use_stability_api = False # @param {type:'boolean'}\n",
        "mount_gdrive = True # @param {type:'boolean'}\n",
        "resume=True # @param {type:'boolean'}\n",
        "\n",
        "# TODO: add support for whisper API\n",
        "\n",
        "# @markdown Enter a unique `project_name`.\n",
        "# @markdown If left blank, the current unix timestamp will be used\n",
        "# @markdown  (seconds since 1970-01-01 00:00).\n",
        "\n",
        "# @markdown If you use the name of an existing project, the workspace will switch to that project (even if `resume` is unchecked. Each project needs a unique name).\n",
        "\n",
        "# @markdown Non-alphanumeric characters (excluding '-' and '_') will be replaced with hyphens.\n",
        "\n",
        "# @markdown ---\n",
        "\n",
        "# @markdown # Detailed Discussion\n",
        "# @markdown In VKTRS, a \"project\" is encapsulated by a folder.\n",
        "# @markdown With google drive loaded, it will be the `<project_name>` subfolder under the path:\n",
        "# @markdown `/content/drive/MyDrive/AI/VideoKilledTheRadioStar/`\n",
        "\n",
        "# @markdown ---\n",
        "\n",
        "# @markdown Depending on your settings and environment, running this cell may prompt you to enter one or more API Keys below.\n",
        "# @markdown Don't forget to press \"enter\" after providing a requested key.\n",
        "\n",
        "\n",
        "##########################\n",
        "\n",
        "try:\n",
        "    import google.colab\n",
        "    local=False\n",
        "except:\n",
        "    local=True\n",
        "\n",
        "if local:\n",
        "    mount_gdrive=False\n",
        "\n",
        "\n",
        "##################################################################\n",
        "\n",
        "resuming = False\n",
        "if resume:\n",
        "    try:\n",
        "        workspace, storyboard = load_storyboard()\n",
        "        print(\"loading storyboard\")\n",
        "        resuming=True\n",
        "    except:\n",
        "        resuming = False\n",
        "\n",
        "if not resuming:\n",
        "    if not project_name:\n",
        "        project_name = str(time.time())\n",
        "    project_name = sanitize_folder_name(project_name)\n",
        "\n",
        "    print(\"creating workspace\")\n",
        "\n",
        "    workspace = establish_workspace(\n",
        "        use_stability_api=use_stability_api,\n",
        "        mount_gdrive=mount_gdrive,\n",
        "        application_name=\"VideoKilledTheRadioStar\",\n",
        "        active_project=project_name,\n",
        "    )\n",
        "\n",
        "    print(\"creating new storyboard\")\n",
        "    storyboard = OmegaConf.create()\n",
        "    storyboard.params = {}\n",
        "    storyboard.prompt_starts=[]\n",
        "\n",
        "\n",
        "# f.\n",
        "workspace.use_stability_api = use_stability_api\n",
        "with open('config.yaml','w') as fp:\n",
        "    OmegaConf.save(config=workspace, f=fp.name)\n",
        "\n",
        "#if workspace.use_stability_api:\n",
        "if use_stability_api:\n",
        "\n",
        "    import os, getpass\n",
        "    if not os.environ.get('STABILITY_KEY'):\n",
        "        os.environ['STABILITY_KEY'] = getpass.getpass('Enter your Stability API Key, then press enter to continue')\n",
        "\n",
        "\n",
        "    # @markdown To get your API key visit https://dreamstudio.ai/account\n",
        "    STABILITY_HOST = \"grpc.stability.ai:443\" #@param {type:\"string\"}\n",
        "\n",
        "\n",
        "\n",
        "    ###################################\n",
        "\n",
        "    STABILITY_KEY = os.environ.get('STABILITY_KEY')\n",
        "\n",
        "    # Connect to Stability API\n",
        "    context = Context(STABILITY_HOST, STABILITY_KEY)\n",
        "\n",
        "    # Test the connection\n",
        "    context.get_user_info()\n",
        "\n",
        "\n",
        "\n",
        "else:\n",
        "    # TODO: check for HF token in environment\n",
        "    if not local:\n",
        "        from google.colab import output\n",
        "        output.enable_custom_widget_manager()\n",
        "\n",
        "    from huggingface_hub import notebook_login\n",
        "    notebook_login()\n",
        "\n",
        "##################################################################\n",
        "\n",
        "root = Path(workspace.project_root)\n",
        "\n",
        "assets_dir = Path(workspace.shared_assets_root)\n",
        "\n",
        "video_assets_meta_fname = assets_dir / 'video_assets_meta.yaml'\n",
        "audio_assets_meta_fname = assets_dir / 'audio_assets_meta.yaml'\n",
        "\n",
        "video_assets_meta = OmegaConf.load( assets_dir/'video_assets_meta.yaml' )\n",
        "audio_assets_meta = OmegaConf.load( assets_dir/'audio_assets_meta.yaml' )\n",
        "\n",
        "# just to be safe\n",
        "save_storyboard(storyboard)\n",
        "workspace, storyboard = load_storyboard()\n",
        "\n",
        "import rich\n",
        "rich.print(dict(workspace))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "SyIJRMhEgCfL",
        "tags": []
      },
      "outputs": [],
      "source": [
        "# @title ⬇ Set Audio Source\n",
        "\n",
        "d_ = dict(\n",
        "    # all the underscore does is make it so each of the following lines can be preceded with a comma\n",
        "    # otw the first parameter would be offset from the other in the colab form\n",
        "    _=\"\"\n",
        "\n",
        "    , video_url = 'https://www.youtube.com/watch?v=REojIUxX4rw' # @param {type:'string'}\n",
        "    , audio_fpath = '' # @param {type:'string'}\n",
        ")\n",
        "d_.pop('_')\n",
        "\n",
        "# @markdown `video_url` - URL of a youtube video to download as a source for audio and potentially for text transcription as well.\n",
        "\n",
        "# @markdown `audio_fpath` - Optionally provide an audio file instead of relying on a youtube download. Name it something other than 'audio.mp3',\n",
        "# @markdown                 otherwise it might get overwritten accidentally.\n",
        "\n",
        "\n",
        "storyboard.params = d_\n",
        "\n",
        "storyboard_fname = root / 'storyboard.yaml'\n",
        "with open(storyboard_fname,'wb') as fp:\n",
        "    OmegaConf.save(config=storyboard, f=fp.name)\n",
        "\n",
        "\n",
        "###############################\n",
        "# Download audio from youtube #\n",
        "###############################\n",
        "\n",
        "# this should modify the existing record for the URL rather than creating a new one...\n",
        "force_redownload=False\n",
        "\n",
        "video_url = storyboard.params.video_url\n",
        "download_video=True\n",
        "\n",
        "if not force_redownload:\n",
        "    for rec in video_assets_meta.videos:\n",
        "        if rec.video_url == video_url:\n",
        "            if rec.get('video_fpath'):\n",
        "                print(\"previously downloaded video detected\")\n",
        "                download_video=False\n",
        "                # populate storyboard with previous processing results\n",
        "                if rec.get('audio_fpath'):\n",
        "                    storyboard.params.audio_fpath = rec.get('audio_fpath')\n",
        "            else:\n",
        "                download_video=True # should be redundant?\n",
        "            break\n",
        "\n",
        "\n",
        "\n",
        "if download_video:\n",
        "    # check if user provided an audio filepath (or we already have one from youtube) before attempting to download\n",
        "    video_assets_meta_record = {}\n",
        "    video_assets_meta_record['video_url'] = video_url\n",
        "\n",
        "    ytdl_prefix = \"DOWNLOADED__\"\n",
        "    ytdl_fname = f\"{str(assets_dir / ytdl_prefix)}%(title)s.%(ext)s\"\n",
        "\n",
        "    !yt-dlp -o \"{ytdl_fname}\" {video_url}\n",
        "\n",
        "    matched_files = assets_dir.glob(ytdl_prefix+\"*\")\n",
        "    most_recent_file = max(matched_files, key=os.path.getctime)\n",
        "    print(f\"downloaded: {most_recent_file}\")\n",
        "    ytdl_fname = most_recent_file\n",
        "\n",
        "    video_assets_meta_record['video_fpath'] = str(ytdl_fname.absolute())\n",
        "\n",
        "    # TODO: right here, we should be adding this to the audio meta\n",
        "    audio_fpath = ytdl_fname.with_suffix('.m4a')\n",
        "    input_audio = ytdl_fname\n",
        "    !ffmpeg -y -i \"{input_audio}\" -vn -c:a aac \"{audio_fpath}\"\n",
        "\n",
        "    storyboard.params.audio_fpath = audio_fpath\n",
        "    video_assets_meta_record['audio_fpath'] = str(audio_fpath.absolute())\n",
        "\n",
        "    video_assets_meta.videos.append(video_assets_meta_record)\n",
        "\n",
        "\n",
        "save_storyboard(storyboard)\n",
        "\n",
        "with open(video_assets_meta_fname, 'wb') as fp:\n",
        "    OmegaConf.save(config=video_assets_meta, f=fp.name)\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "9zT0u4-q_fMF",
        "tags": []
      },
      "outputs": [],
      "source": [
        "# @title ### 🔊 Initial Audio Processing\n",
        "\n",
        "# @markdown * Transcribe and segment speech using whisper\n",
        "\n",
        "# @markdown * Analyze major musical structure\n",
        "\n",
        "# @markdown If this audio source has been previoussly processed by this notebook, that should be detected and processing won't be repeated.\n",
        "\n",
        "##################################################\n",
        "# 💬 Transcribe and segment speech using whisper #\n",
        "##################################################\n",
        "\n",
        "audio_fpath = str(storyboard.params.audio_fpath)\n",
        "\n",
        "separate_stems = False # @param {type:'boolean'}\n",
        "\n",
        "# these are just confusing i think\n",
        "force_retranscription = False # #@#param {type:'boolean'}\n",
        "override_storyboard_transcription = False # #@#param {type:'boolean'}\n",
        "\n",
        "whisper_seg = None\n",
        "\n",
        "if audio_fpath in audio_assets_meta:\n",
        "    print(\"previously processed audio detected\")\n",
        "    audio_meta = audio_assets_meta[audio_fpath]\n",
        "else:\n",
        "    audio_assets_meta[audio_fpath] = {}\n",
        "    audio_meta = audio_assets_meta[audio_fpath]\n",
        "\n",
        "if (not force_retranscription) and audio_meta.get('whisper_segmentation') and Path(audio_meta.whisper_segmentation).exists():\n",
        "    print(\"Using pre-existing whisper transcription\")\n",
        "    whisper_seg_fpath = Path(audio_meta.whisper_segmentation)\n",
        "    with whisper_seg_fpath.open() as f:\n",
        "        timings = json.load(f)\n",
        "    whisper_seg = timings['segments']\n",
        "\n",
        "\n",
        "if force_retranscription or (whisper_seg is None):\n",
        "    print(\"Transcribing...\")\n",
        "    #audio_meta['audio_fpath'] = storyboard.params.audio_fpath redundant\n",
        "    # outputs text files as audio.* locally\n",
        "    !whisper --model large --word_timestamps True -o {str(assets_dir)} \"{storyboard.params.audio_fpath}\"\n",
        "\n",
        "    whisper_seg_fpath = Path(storyboard.params.audio_fpath).with_suffix('.json')\n",
        "    audio_meta['whisper_segmentation'] = str(whisper_seg_fpath)\n",
        "    audio_meta['duration'] = get_audio_duration_seconds(audio_fpath)\n",
        "\n",
        "    with whisper_seg_fpath.open() as f:\n",
        "        timings = json.load(f)\n",
        "    whisper_seg = timings['segments']\n",
        "\n",
        "    audio_assets_meta[audio_fpath] = audio_meta\n",
        "    with open(audio_assets_meta_fname, 'wb') as fp:\n",
        "        OmegaConf.save(config=audio_assets_meta, f=fp.name)\n",
        "\n",
        "if not storyboard.get('prompt_starts') or override_storyboard_transcription:\n",
        "\n",
        "    # Ta da!\n",
        "    storyboard.prompt_starts = [{k:rec[k] for k in ('start','end','text')} for rec in whisper_seg]\n",
        "\n",
        "    storyboard.params['video_duration'] = audio_meta['duration']\n",
        "    # unsure if below method is reliable.\n",
        "    #storyboard.params['video_duration'] = storyboard.prompt_starts[-1]['end']\n",
        "\n",
        "\n",
        "# TODO: enforce scene zero starts at t=0 and last scene ends at duration\n",
        "\n",
        "#storyboard.prompt_starts = prompt_starts\n",
        "save_storyboard(storyboard)\n",
        "\n",
        "# Music Structure Analysis\n",
        "\n",
        "# Music Structure Analysis\n",
        "# - beat and tempo detection\n",
        "# - Self-similarity graph\n",
        "\n",
        "audio_structure_features = analyze_audio_structure(audio_fpath=storyboard.params.audio_fpath)\n",
        "\n",
        "audio_features_fpath = Path(storyboard.params.audio_fpath).with_suffix('.audio_features.safetensors')\n",
        "save_safetensors(audio_structure_features, audio_features_fpath)\n",
        "\n",
        "# TODO: fix inconsistent variable naming\n",
        "structural_features = audio_structure_features\n",
        "\n",
        "# TODO: un-\"PosixPath\" `storyboard.params.audio_fpath`\n",
        "# TODO: this is a confusing af way to access this.\n",
        "audio_assets_meta[str(storyboard.params.audio_fpath)]['structural_features'] = audio_features_fpath\n",
        "\n",
        "\n",
        "with open(audio_assets_meta_fname, 'wb') as fp:\n",
        "    OmegaConf.save(config=audio_assets_meta, f=fp.name)\n",
        "\n",
        "if separate_stems:\n",
        "    ensure_stems_separated()\n",
        "\n",
        "# TODO: ~PLACEHOLDER~ Give user opportunity to correct the transcription at shared_asset level rather than project\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "IcKwmF1BUyup"
      },
      "outputs": [],
      "source": [
        "# @title ### 🎞️ Show Current Storyboard\n",
        "workspace, storyboard = load_storyboard() # just to ensure consistency\n",
        "show_storyboard()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "B3gw1cYNgCfL",
        "tags": []
      },
      "outputs": [],
      "source": [
        "# @title ### ✂️ Subdivide Unusually Long Scenes\n",
        "\n",
        "# @markdown Attempts to identify unusually long scenes and splits them into multiple shorter scenes.\n",
        "# @markdown Estimates the expected scene duration to identify outliers.\n",
        "\n",
        "# TODO: wrap these steps in functions for legibility/portability\n",
        "# TODO: make this threshold parameterizable via storyboard (and save analysis to storyboard)\n",
        "# TODO: use beat counts to estimate a smart scene duration\n",
        "\n",
        "subdivide_long_scenes = True # @param {'type':'boolean'}\n",
        "\n",
        "#TODO: merge short scenes\n",
        "\n",
        "# TODO: expose this to colab\n",
        "threshold_duration = None\n",
        "\n",
        "######################################################\n",
        "# estimate parameters of scene duration distribution #\n",
        "######################################################\n",
        "\n",
        "# TODO: use beat onsets/counts\n",
        "scene_durations = []\n",
        "scenes_ = []\n",
        "for idx, rec in enumerate(storyboard.prompt_starts):\n",
        "    rec=dict(rec)\n",
        "    if idx > 0:\n",
        "        # are we maybe doubling up 'start' time stamps? like there are more unique 'end's than 'start's?\n",
        "        duration = rec['start'] - prev['start']\n",
        "        prev['duration_'] = duration\n",
        "        scene_durations.append(duration)\n",
        "    prev = rec\n",
        "    scenes_.append(prev)\n",
        "\n",
        "# handle last record\n",
        "else:\n",
        "    duration = rec['end'] - rec['start']\n",
        "    rec['duration_'] = duration\n",
        "    scenes_.append(rec)\n",
        "    scene_durations.append(duration)\n",
        "\n",
        "# handle case where there's only one scene\n",
        "if idx == 0:\n",
        "    duration = rec['end'] - rec['start']\n",
        "    rec['duration_'] = duration\n",
        "    scenes_.append(rec)\n",
        "    scene_durations.append(duration)\n",
        "\n",
        "mu = sum(scene_durations)/len(scene_durations)\n",
        "sigma = np.std(scene_durations)\n",
        "\n",
        "# 1sd filter to concentrate on mode\n",
        "scene_durations2 = [s for s in scene_durations if (mu - sigma) < s < (mu+sigma)]\n",
        "try:\n",
        "    mu2 = sum(scene_durations2)/len(scene_durations2)\n",
        "    sigma2 = np.std(scene_durations2)\n",
        "    if mu2==0:\n",
        "        subdivide_long_scenes=False\n",
        "except ZeroDivisionError:\n",
        "    subdivide_long_scenes = False\n",
        "    warnings.warn(\n",
        "        \"Sorry, you've hit an unsupported edge case. \"\n",
        "        \"We need some sort of segmentation to start with, but right now \"\n",
        "        \"our initial heuristics have everything in one long scene. \"\n",
        "        \"If you're trying to animate an instrumental track, VKTRS \"\n",
        "        \"doesn't intelligent scene segmentation for that use case yet. \"\n",
        "        \"Scene segmentation needs lyrics at the moment.\"\n",
        "    )\n",
        "\n",
        "###########\n",
        "\n",
        "# break up \"outlier\" segments into smaller chunks\n",
        "# this heuristic could be improved with beat synchronization and onset detection.\n",
        "# ... also could probably leverage the 'end' time of the scene\n",
        "# TODO: hierarchical theme structure analysis\n",
        "# TODO: MSA segmentation for fully instrumental (i.e. arbitrary) audio\n",
        "\n",
        "if subdivide_long_scenes:\n",
        "\n",
        "    threshhold = mu2 + sigma\n",
        "    if threshold_duration is not None:\n",
        "        threshhold = threshold_duration\n",
        "\n",
        "    scenes = []\n",
        "    #for rec in storyboard.prompt_starts:\n",
        "    for rec in list(scenes_):\n",
        "        gap_remaining = rec['duration_']\n",
        "        while gap_remaining > threshhold:\n",
        "            #step = min(max(mu2-sigma, (np.random.normal() + mu2)*sigma), mu2+sigma)\n",
        "            step=mu2\n",
        "            step = float(step)\n",
        "            # TODO: move duration computation somewhere that it will happen necessarily\n",
        "            rec['duration_'] = step\n",
        "            new_rec = copy.deepcopy(rec)\n",
        "            new_rec['start'] = rec['start'] + step\n",
        "            # TODO: deal with new value here\n",
        "            #new_rec['end'] = ??\n",
        "            new_rec['duration_'] = step\n",
        "            ### maybe i could add a flag or something to clarify that this was an \"inferred\" subscene\n",
        "            #new_rec['parent_scene'] = rec.get('uid') # something like this?\n",
        "            new_rec['inferred_subscene'] = True # or this?\n",
        "\n",
        "            scenes.append(rec)\n",
        "            rec = new_rec\n",
        "            gap_remaining -= step\n",
        "        scenes.append(rec)\n",
        "\n",
        "    storyboard.prompt_starts = scenes\n",
        "\n",
        "# TODO: get last scene \"end\" from story duration\n",
        "\n",
        "# TODO: compute duration regardless of extra segmentation\n",
        "\n",
        "# TODO: add scene indices back to rec's to facilitate editing the text file\n",
        "\n",
        "save_storyboard(storyboard)\n",
        "show_storyboard(storyboard)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "5LrrcvX2gCfM",
        "tags": []
      },
      "outputs": [],
      "source": [
        "# @title ### 🤓 Math\n",
        "\n",
        "# @markdown Construct an initial estimate for how many frames will be needed for\n",
        "# @markdown each scene, based on the scene start times and animation framerate.\n",
        "\n",
        "# @markdown * `fps` - The desired framerate of the animation (frames per second).\n",
        "# @markdown  * `animation_mode=\"img2img\"` - We will generate an image for every frame, so higher fps will take longer but improves audioreactivity.\n",
        "# @markdown  * `animation_mode=\"variations\"` - We will only need to generate `n_variations` images per scene. The FPS setting only affects how the video will be compiled.\n",
        "# @markdown   The fps and n_variations settings will interact to define the frequency of a visual pulsing effect, e.g.\n",
        "# @markdown    * fps=12, n_variations=4 : the animation will appear to \"pulse\" 3 times per second as the sequence of image variations is cycled.\n",
        "\n",
        "#################################################\n",
        "# Math                                          #\n",
        "#                                               #\n",
        "#    This block computes how many frames are    #\n",
        "#    needed for each segment based on the start #\n",
        "#    times for each prompt                      #\n",
        "#################################################\n",
        "\n",
        "# TODO: move variations settings up here\n",
        "\n",
        "# TODO: leverage previous beat detection, onsets, etc. for frame timings\n",
        "# - TODO: isolated cells for calculating and suggesting parameters (fps, n_variations)\n",
        "\n",
        "# TODO: experiment with tying instantaneous framerate to a music attribute (i.e. so motion can change speed mid animation)\n",
        "\n",
        "fps = 30 # @param {type:'integer'}\n",
        "storyboard.params.fps = fps\n",
        "\n",
        "ifps = 1/fps\n",
        "\n",
        "# estimate video end\n",
        "if not storyboard.params.get('video_duration'):\n",
        "    storyboard.params['video_duration'] = get_audio_duration_seconds(storyboard.params.audio_fpath)\n",
        "video_duration = storyboard.params['video_duration']\n",
        "\n",
        "# dummy prompt for last scene duration\n",
        "prompt_starts = OmegaConf.to_container(storyboard.prompt_starts)\n",
        "prompt_starts.append({'start':video_duration})\n",
        "\n",
        "# TODO: do we still need anim_start? is that used in rendering?\n",
        "# TODO: add per-frame timings to incorporate beat information\n",
        "# make sure we respect the duration of the previous phrase\n",
        "frame_start=0\n",
        "prompt_starts[0]['anim_start']=frame_start\n",
        "for i, rec in enumerate(prompt_starts[1:], start=1):\n",
        "    rec_prev = prompt_starts[i-1]\n",
        "    k=0\n",
        "    while (rec_prev['anim_start'] + k*ifps) < rec['start']:\n",
        "        k+=1\n",
        "    k-=1\n",
        "    rec_prev['frames'] = k\n",
        "    rec_prev['anim_duration'] = k*ifps\n",
        "    frame_start+=k*ifps\n",
        "    rec['anim_start']=frame_start\n",
        "\n",
        "# drop the dummy frame\n",
        "prompt_starts = prompt_starts[:-1]\n",
        "\n",
        "# TODO: given a 0 duration prompt, assume its duration is captured in the next prompt\n",
        "#        and guesstimate a corrected prompt start time and duration\n",
        "#      - or rather.. why are there ever sero duration prompts?\n",
        "\n",
        "\n",
        "storyboard.prompt_starts = prompt_starts\n",
        "\n",
        "save_storyboard(storyboard)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "TcCOlx_vCo31"
      },
      "source": [
        "## $2.$ 🎥🌈 Define your animation's aesthetics"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Bu1FKypFO8C2"
      },
      "source": [
        "### 2.1 📋 Assign Themes to Scenes"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "SB0X2mXGgCfL",
        "tags": []
      },
      "outputs": [],
      "source": [
        "\n",
        "\n",
        "# @markdown `theme_prompt` - Text that will be appended to the end of each lyric, useful for e.g. applying a consistent aesthetic style.\n",
        "# @markdown  To provide multiple themes (only one will be used per scene), separate theme prompts with the `|` (pipe) symbol.\n",
        "\n",
        "# @markdown `infer_thematic_structure` - if False, themes will be rotated sequentially such that no two adjacent frames\n",
        "# @markdown will use the same theme prompt (if multiple theme prompts were provided). If True, song structure analysis will cluster related scenes into as many groups as there are theme prompts to attempt to associate a visual themes with respective musical themes.\n",
        "\n",
        "# @markdown The analysis runs quick. If you didn't understand that explanation, just try it both ways and you'll probably get the idea.\n",
        "\n",
        "theme_prompt = 'zombie wedding | zombie dance party | zombies in love' # @param {type:'string'}\n",
        "\n",
        "infer_thematic_structure = True # @param {type:'boolean'}\n",
        "\n",
        "storyboard.params.theme_prompt = theme_prompt\n",
        "themes = [prompt.strip() for prompt in theme_prompt.split('|') if prompt.strip()]\n",
        "\n",
        "# communicate theme_id mappings\n",
        "df_themes = pd.DataFrame({'theme prompt':themes})\n",
        "df_themes.index.name=\"theme_id\"\n",
        "\n",
        "rich.print(df_themes)\n",
        "print()\n",
        "\n",
        "if (len(themes) > 1):\n",
        "    if infer_thematic_structure:\n",
        "\n",
        "        # audio_assets_meta[storyboard.params.audio_fpath]['structural_features']\n",
        "        beat_times = audio_structure_features['beat_times']\n",
        "        evecs = audio_structure_features['evecs']\n",
        "        segment_labels = laplacian_segmentation(\n",
        "            evecs=evecs,\n",
        "\n",
        "            # TODO: publish these parameters to the user\n",
        "            n_clusters=len(themes), # be sure to explain this is an upper bound, stochastic\n",
        "            n_spectral_features=len(themes),\n",
        "        )\n",
        "\n",
        "        # beatsynch scene start times\n",
        "\n",
        "        # TODO: swap out rec['end'] -> rec_prev['start'] here\n",
        "        for rec in storyboard.prompt_starts:\n",
        "            beat_indices = np.where((beat_times >= rec['start']) & (beat_times <= rec['end']))[0]\n",
        "            segments_this_interval = segment_labels[beat_indices]\n",
        "            if len(segments_this_interval) == 0:\n",
        "                dominant_label = 0\n",
        "            else:\n",
        "                dominant_label = int(np.argmax(np.bincount(segments_this_interval)))\n",
        "            rec['structural_segmentation_label'] = dominant_label\n",
        "            rec['_theme'] = themes[dominant_label]\n",
        "    else:\n",
        "        for rec in storyboard.prompt_starts:\n",
        "            rec['_theme'] = themes[idx % len(themes)]\n",
        "else:\n",
        "    for rec in storyboard.prompt_starts:\n",
        "        rec['_theme'] = theme_prompt\n",
        "\n",
        "\n",
        "save_storyboard(storyboard)\n",
        "\n",
        "# TODO: show dataframe if no init images?\n",
        "show_storyboard(storyboard)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "zQvgGq3SgCfM",
        "tags": []
      },
      "outputs": [],
      "source": [
        "# @title #### 📝 (Optional) modify theme prompt without impacting structure label assignments\n",
        "\n",
        "# @markdown Themes will be assigned to the `structural_segmentation` label that maps to their ordering\n",
        "# @markdown in the theme prompt. To change which theme goes where, simply modify the order in which\n",
        "# @markdown they appear in your prompt.\n",
        "\n",
        "# @markdown **Why the second cell?** The procedure that assigns labels to themes\n",
        "# @markdown has some randomness, so re-running the cell above with the same\n",
        "# @markdown settings might produce a different order (i.e. a different theme-label assignment)\n",
        "\n",
        "theme_prompt = 'rusted industrial machinery | kaiju robot CGI | paperclips! paperclips! |  robotics for beginners | rusted industrial machinery' # @param {type:'string'}\n",
        "\n",
        "#####################################################\n",
        "\n",
        "storyboard.params.theme_prompt = theme_prompt\n",
        "themes = [prompt.strip() for prompt in theme_prompt.split('|') if prompt.strip()]\n",
        "\n",
        "for rec in storyboard.prompt_starts:\n",
        "    theme_idx = rec.get('structural_segmentation_label',0)\n",
        "    rec['_theme'] = themes[theme_idx]\n",
        "\n",
        "save_storyboard(storyboard)\n",
        "show_storyboard(storyboard)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "sYkcY1XPAFUF"
      },
      "source": [
        "### 2.2 🎛️🎚️ Animation Settings"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "edxBKv5TgCfM",
        "tags": []
      },
      "outputs": [],
      "source": [
        "# @title #### 🎚️ (Advanced) `img2img` Animation settings\n",
        "\n",
        "# @markdown NB: Skip this cell if you're not using the `img2img` animation mode. Go directly to the next cell, \"📌\".\n",
        "\n",
        "# TODO: let's move `variations` settings into their own dedicated area,\n",
        "#       call this \"`img2img` animation settings\" or something like that instead\n",
        "\n",
        "# @markdown Run This cell to reveal a multi-tab UI for specifying `img2img` animation settings. When you are happy with your settings,\n",
        "# @markdown run the cell which follows this one to attach the settings you have selected to one or more scenes/themes.\n",
        "\n",
        "# @markdown To reset values to default, simply re-run this cell.\n",
        "\n",
        "# @markdown The `img2img` animation mode is currently only supported through the Stability.AI Animation API (i.e. DreamStudio API).\n",
        "# @markdown The `img2img` animation parameters are mostly compatible with deforum: if you don't want to animate via the Stability.AI,\n",
        "# @markdown you can still use this notebook to configure your animation and then port the relevant settings from the `storyboard.yaml`\n",
        "# @markdown to your tool of choice.\n",
        "\n",
        "if not workspace.use_stability_api:\n",
        "    warnings.warn(\"img2img animation currently only supported if you're using the stability API\")\n",
        "    arg_objs=[]\n",
        "else:\n",
        "\n",
        "\n",
        "    show_documentation = True # @param {type:'boolean'}\n",
        "    # TODO: Move/add context to setup cell, and/or generation cells\n",
        "\n",
        "\n",
        "    ###################\n",
        "\n",
        "    args_generation = BasicSettings()\n",
        "    args_animation = AnimationSettings()\n",
        "    args_camera = CameraSettings()\n",
        "    args_coherence = CoherenceSettings()\n",
        "    args_color = ColorSettings()\n",
        "    args_depth = DepthSettings()\n",
        "    args_render_3d = Rendering3dSettings()\n",
        "    args_inpaint = InpaintingSettings()\n",
        "    args_vid_in = VideoInputSettings()\n",
        "    args_vid_out = VideoOutputSettings()\n",
        "    arg_objs = (\n",
        "        args_generation,\n",
        "        args_animation,\n",
        "        args_camera,\n",
        "        args_coherence,\n",
        "        args_color,\n",
        "        args_depth,\n",
        "        args_render_3d,\n",
        "        args_inpaint,\n",
        "        args_vid_in,\n",
        "        args_vid_out,\n",
        "    )\n",
        "\n",
        "    def _show_docs(component):\n",
        "        cols = []\n",
        "        for k, v in component.param.objects().items():\n",
        "            if k == 'name':\n",
        "                continue\n",
        "            col = pn.Column(v, v.doc)\n",
        "            cols.append(col)\n",
        "        return pn.Column(*cols)\n",
        "\n",
        "    def build(component):\n",
        "        if show_documentation:\n",
        "            component = _show_docs(component)\n",
        "        return pn.Row(component, width=1000)\n",
        "\n",
        "pn.extension()\n",
        "\n",
        "pn.Tabs(*[(a.name[:-5], build(a)) for a in arg_objs])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "BX3TBm3bFoY5"
      },
      "source": [
        "### 2.3 📌 Attach settings to scenes"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "efgDV40OVO9S",
        "tags": []
      },
      "outputs": [],
      "source": [
        "# @markdown If using img2img animation, use this cell to specify which scenes the settings you provided above should apply to.\n",
        "\n",
        "# @markdown Otherwise, you can ignore the cell above and just do everything here.\n",
        "\n",
        "# @markdown ---\n",
        "\n",
        "# @markdown `override_storyboard` - If settings conflict with values already set on the storyboard, the values on the storyboard will take priority.\n",
        "\n",
        "# @markdown `scene_ids` - Comma separated list of integers specifying scenes to attach to.\n",
        "\n",
        "# @markdown `theme_ids` - Comma separated list of integers specifying themes the animation parameters should be associated with.\n",
        "\n",
        "# @markdown NB: both `scene_ids` and `theme_ids` are zero-indexed.\n",
        "\n",
        "# @markdown If you're not sure what the appropriate scene/theme ids are that you want to reference, use the next cell to load and view the current storyboard state.\n",
        "\n",
        "# CURRENT ARGS APPLY TO...\n",
        "\n",
        "all_scenes = True # @param {'type':'boolean'}\n",
        "\n",
        "scene_ids = '' # @param {'type':'string'}\n",
        "theme_ids = '' # @param {'type':'string'}\n",
        "\n",
        "override_storyboard = True # @param {'type':'boolean'}\n",
        "\n",
        "# @markdown ---\n",
        "\n",
        "# @markdown ## Animation Modes.\n",
        "# @markdown * `static`: turns off animation, static image for duration of scene.\n",
        "# @markdown * `variations`: injects a small bit of \"life\" into the image. Cheap and fast.\n",
        "# @markdown * `variations tsp`: variations animation with frames reordered for smoother motion. Cheap and fast, but a tad slower.\n",
        "# @markdown * `img2img`: Fancy deforum-esque animation, only supported for stability api at present. Neither cheap nor fast.\n",
        "# #@markdown * `default`: `img2img` if stability api enabled,  `variations tsp` if not.\n",
        "\n",
        "# oh baby `jittered init`\n",
        "# markdown * `jittered init`: The VKTRS special! Sample a variation and use that as an init image for img2img. Basically built for audioreactivity\n",
        "# to do --> combine this with a referring expression mask\n",
        "# make sure animation is configured so we can curve prompts\n",
        "\n",
        "animation_mode = 'variations' # @param [\"static\", \"variations\", \"variations tsp\", \"img2img\"]\n",
        "\n",
        "# @markdown ---\n",
        "\n",
        "# @markdown ## Parameters for `variations` Animation Modes\n",
        "\n",
        "# TODO: these parameters should get set elsewhere. maybe with animation mode?\n",
        "\n",
        "# @markdown `n_variations` - How many unique variations to generate for a given text prompt. This determines the frequency of the visual \"pulsing\" effect\n",
        "\n",
        "# @markdown `image_consistency` - controls similarity between images generated by the prompt.\n",
        "# @markdown - 0: ignore the init image\n",
        "# @markdown - 1: true as possible to the init image\n",
        "\n",
        "## @markdown `max_video_duration_in_seconds` - Early stopping if you don't want to generate a video the full duration of the provided audio. Default = 5min.\n",
        "\n",
        "\n",
        "n_variations=8 # @param {type:'integer'}\n",
        "image_consistency=0.72 # @param {type:\"slider\", min:0, max:1, step:0.01}\n",
        "\n",
        "variations_settings = {\n",
        "    'n_variations':n_variations,\n",
        "    'image_consistency':image_consistency,\n",
        "}\n",
        "\n",
        "\n",
        "# let's try this:\n",
        "#   - user specifies animation stuff in cell above\n",
        "#   - then user comes down here and persists the animation settings either to every scene, or to specific scenes.\n",
        "#   - scene specification can just be a list of numbers\n",
        "#   - feels like this should be moved after init image generation. maybe push init image generation up to run right after theme prompts and scene count is finalized?\n",
        "\n",
        "# TODO: add storyboard -> parsec converter\n",
        "\n",
        "# TODO: PR to deforum for storyboard.yaml support\n",
        "\n",
        "# TODO: allow user to constrain attention to specific parameters or sets of parameters to set\n",
        "\n",
        "def param2json(args_obj):\n",
        "    args = args_obj.param.serialize_parameters()\n",
        "    args = json.loads(args)\n",
        "    args.pop('name')\n",
        "    return args\n",
        "\n",
        "def collect_animation_args():\n",
        "    if 'arg_objs' in globals():\n",
        "        args_d = {}\n",
        "        [args_d.update(a.param.values()) for a in arg_objs]\n",
        "        args=AnimationArgs(**args_d)\n",
        "    else:\n",
        "        import warnings\n",
        "        warnings.warn(\n",
        "            \"Looks like you're animating in img2img mode \"\n",
        "            \"without having specified any animation parameters. \"\n",
        "            \"Using SDK defaults. \"\n",
        "        )\n",
        "        args = AnimationArgs()\n",
        "    return param2json(args)\n",
        "\n",
        "\n",
        "##########################\n",
        "\n",
        "scene_ids = [int(v.strip()) for v in scene_ids.split(',') if v]\n",
        "theme_ids = [int(v.strip()) for v in theme_ids.split(',') if v]\n",
        "\n",
        "# build list of scenes args will apply to\n",
        "\n",
        "applicable_scenes = []\n",
        "if all_scenes:\n",
        "    applicable_scenes = [idx for idx, _ in enumerate(storyboard.prompt_starts)]\n",
        "else:\n",
        "    applicable_scenes += scene_ids\n",
        "    applicable_scenes += theme_ids\n",
        "\n",
        "###---------------------------------###\n",
        "\n",
        "animation_mode = animation_mode.lower()\n",
        "if animation_mode == 'default':\n",
        "    animation_mode = 'img2img' if workspace.use_stability_api else 'variations tsp'\n",
        "\n",
        "###---------------------------------###\n",
        "\n",
        "# apply args to appropriate scenes\n",
        "\n",
        "for idx, rec in enumerate(storyboard.prompt_starts):\n",
        "    if idx not in applicable_scenes:\n",
        "        continue\n",
        "\n",
        "    board_args = rec.get('animation_args')\n",
        "    if board_args:\n",
        "        board_args = OmegaConf.to_container(board_args) # coerce to dict\n",
        "\n",
        "    new_args = collect_animation_args()\n",
        "    if not override_storyboard:\n",
        "        new_args.update(board_args)\n",
        "        #variations_settings\n",
        "    rec['animation_args'] = new_args\n",
        "    rec.update(variations_settings)\n",
        "\n",
        "    if override_storyboard or (not rec.get('animation_mode')):\n",
        "        rec['animation_mode'] = animation_mode\n",
        "\n",
        "    # oooo lookat me being fancy with custom code!\n",
        "    #if (idx >=23) or idx ==16:\n",
        "    #    rec['animation_mode'] = 'img2img'\n",
        "\n",
        "save_storyboard(storyboard)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "zHJ5gPUOIhIY",
        "tags": []
      },
      "outputs": [],
      "source": [
        "# @title ### 🎞️ Show Current Storyboard\n",
        "workspace, storyboard = load_storyboard() # just to ensure consistency\n",
        "show_storyboard()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "fikaqNVJ-mmP"
      },
      "source": [
        "### 2.4 🎧📈 (Advanced) Audioreactivity"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "0twvUVRTgCfM",
        "tags": [],
        "cellView": "form"
      },
      "outputs": [],
      "source": [
        "# @title ### Choose a driving signal\n",
        "\n",
        "audio_assets_meta  = load_audio_meta(*load_storyboard())\n",
        "#structural_features = audio_assets_meta[str(storyboard.params.audio_fpath)]['structural_features']\n",
        "\n",
        "driving_signal_name = \"vocals stem\" #@param ['audio input','user specified','vocals stem','bass stem','other stem','drum stem']\n",
        "\n",
        "# TODO: test this\n",
        "custom_signal_fpath = '' # @param {'type':'string'}\n",
        "\n",
        "def get_user_specified_signal():\n",
        "    y, sr = librosa.load(custom_signal_fpath)\n",
        "    return y, sr\n",
        "\n",
        "y = structural_features['y']\n",
        "sr = structural_features['sr']\n",
        "\n",
        "driving_signals = {\n",
        "    'audio input': lambda: (y, sr),\n",
        "    'user specified': get_user_specified_signal,\n",
        "    'vocals stem':lambda: get_stem('vocals'),\n",
        "    'bass stem':lambda: get_stem('bass'),\n",
        "    'other stem':lambda: get_stem('other'),\n",
        "    'drum stem':lambda: get_stem('drum'),\n",
        "}\n",
        "\n",
        "raw_driving_signal, sr = driving_signals[driving_signal_name]()\n",
        "display_signal(raw_driving_signal, sr, title=driving_signal_name)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "NA2UEjQ6PYM6"
      },
      "source": [
        "#### Manipulate the signal"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "hFOIMefxgCfM",
        "tags": []
      },
      "outputs": [],
      "source": [
        "import numpy as np\n",
        "from scipy import signal\n",
        "from inspect import signature\n",
        "from functools import partial\n",
        "from scipy.signal import find_peaks\n",
        "from sklearn.cluster import KMeans\n",
        "#sklearn_extra.cluster.KMedoids\n",
        "\n",
        "# @markdown To apply multiple operations, separate manipulation names with a '|' or just re-run this cell.\n",
        "# @markdown When you're satisfied, run the next cell to replace the loaded driving signal with the manipulated signal.\n",
        "\n",
        "# @markdown **Tips**\n",
        "# @markdown * Add manipulations one at a time, re-running this cell each time to see what the effect of each subsequent manipulation is and to figure out if you need to change settings before adding others.\n",
        "# @markdown * Start by isolating the thing you're interested in, then finish by cleaning it up for animation.\n",
        "# @markdown * The outputs of this process will be saved in `storyboard.yaml` and can be used in other AI animation tools such as deforum.\n",
        "\n",
        "# @markdown ### Available Signal Manipulations\n",
        "# @markdown NB: if a signal's name is followed by an asterisk (`*`), this means it modifies the signal's time domain. Only one time domain transformation per sequence is supported.\n",
        "\n",
        "# @markdown NB: if a signal's name is followed by two asterisks (`**`), this means it can't be used after a time-domain modifying manipulation has been applied.\n",
        "# @markdown * `rms`* - Root mean squared. Converts raw signal to signal power\n",
        "# @markdown * `novelty`* - Estimates strength of sound event onsets.\n",
        "# @markdown * `predominant_pulse`* - Combined estimate of beat timing and strength\n",
        "# @markdown * `bandpass(low, high)`** - Isolate signal to frequencies between `[low, high]`\n",
        "# @markdown * `harmonic`** - Isolate the harmonic component.\n",
        "# @markdown * `percussive`** - Isolate the percussive component.\n",
        "# @markdown * `sustain(k)` - Treats signal as a self-exciting process that decays slowly over a window of `k`. Smooths asymmetrically (to the right)\n",
        "# @markdown * `stretch` - Increases gap between high and low amplitude signals. Alias for `pow(2)`\n",
        "# @markdown * `smoosh` - Reduces gap between high and low amplitude signals. Alias for `pow(1/2)`\n",
        "# @markdown * `pow(k)` - Raises signal to the power of k.\n",
        "# @markdown * `normalize` - Transform signal to `[0,1]` range. This will always be the last step, even if you don't specify it.\n",
        "# @markdown * `threshold(low)` - Zero the signal where amplitude is less than `low`\n",
        "# @markdown * `clamp(high)` - Clamp the signal such that no values have amplitude greater than `high`\n",
        "# @markdown * `modulo(k)` - Detects peaks in signal and returns only every Kth-peak\n",
        "# @markdown * `quantize(k)` - Discretizes the signal into K unique values after clustering amplitudes.\n",
        "\n",
        "# we can have some undocumented manipulations I think.\n",
        "# too cluttered as it is.\n",
        "# #@markdown * `raw` - no op\n",
        "# #@markdown * `pow2` - Square the signal. Increases gap between high and low amplitude signals\n",
        "# #@markdown * `sqrt` - Square root of signal. Reduces gap between high and low amplitude signals\n",
        "# #@markdown * `smooth(k)` - Smoothes the waveworm, using a symmetric smoothing window of `k`. Bigger `k` = flatter signal\n",
        "\n",
        "\n",
        "# signal_manipulations = 'bandpass(300, 500) | pow2 | pow2 | rms | pow2 | threshold(5e-6) ' # @param {'type':'string'}\n",
        "signal_manipulations = 'harmonic | bandpass(300, 500) | rms | smoosh ' # @param {'type':'string'}\n",
        "\n",
        "manipulations = [m.strip() for m in signal_manipulations.split('|') if m.strip()]\n",
        "\n",
        "\n",
        "# @markdown ---\n",
        "\n",
        "# @markdown Running this cell will produce a series of plots, one for each signal manipulation.\n",
        "# @markdown Use these options to zoom in on a particular time window.\n",
        "\n",
        "# TODO: add scene division markers to audio plots\n",
        "plotting_window_start_time = 0 # @param {'type': 'number'}\n",
        "plotting_window_end_time = 9999 # @param {'type': 'number'}\n",
        "\n",
        "# @markdown ---\n",
        "\n",
        "# @markdown ### Audio EQ cheat sheet\n",
        "\n",
        "# @markdown ![Audio EQ cheat sheet](https://i.pinimg.com/736x/4f/28/5e/4f285e3fbc5b6b6ea78638e58b2e3052.jpg)\n",
        "\n",
        "\n",
        "# TODO: specify which transforms are available in spectral space vs. not\n",
        "\n",
        "\n",
        "# trim/trim(q) -> y[y>quantile(y,.1)]\n",
        "# that 4part way of parameterizing a wave... hit, sustain, decay,..?\n",
        "\n",
        "\n",
Download .txt
gitextract_j7_i1yfm/

├── .github/
│   └── workflows/
│       └── python-publish.yml
├── .gitignore
├── LICENSE
├── README.md
├── VERSION
├── Video_Killed_The_Radio_Star_Defusion.ipynb
├── pyproject.toml
└── vktrs/
    ├── __init__.py
    ├── api.py
    ├── asr.py
    ├── hf.py
    ├── tsp.py
    ├── utils.py
    └── youtube.py
Download .txt
SYMBOL INDEX (28 symbols across 6 files)

FILE: vktrs/api.py
  function get_image_for_prompt (line 10) | def get_image_for_prompt(prompt, max_retries=3, **kargs):
  function process_response (line 36) | def process_response(answers):

FILE: vktrs/asr.py
  function whisper_transcribe (line 15) | def whisper_transcribe(
  function whisper_align (line 40) | def whisper_align(whispers):
  function whisper_transmit_meta_across_alignment (line 56) | def whisper_transmit_meta_across_alignment(
  function whisper_segment_transcription (line 115) | def whisper_segment_transcription(
  function whisper_lyrics (line 178) | def whisper_lyrics(audio_fpath="audio.mp3"):

FILE: vktrs/hf.py
  class HfHelper (line 19) | class HfHelper:
    method __init__ (line 20) | def __init__(
    method load_pipelines (line 41) | def load_pipelines(
    method get_image_for_prompt (line 74) | def get_image_for_prompt(

FILE: vktrs/tsp.py
  function tsp_permute_frames (line 9) | def tsp_permute_frames(frames, verbose=False):
  function batched_tsp_permute_frames (line 27) | def batched_tsp_permute_frames(frames, batch_size):

FILE: vktrs/utils.py
  function gpu_info (line 12) | def gpu_info():
  function get_audio_duration_seconds (line 32) | def get_audio_duration_seconds(audio_fpath):
  function rand_str (line 45) | def rand_str(n_char=5):
  function remove_punctuation (line 49) | def remove_punctuation(s):
  function sanitize_folder_name (line 54) | def sanitize_folder_name(fp):
  function add_caption2image (line 64) | def add_caption2image(
  function save_frame (line 102) | def save_frame(
  function get_image_sequence (line 116) | def get_image_sequence(idx, root, init_first=True):
  function archive_images (line 137) | def archive_images(idx, root, archive_root = None):

FILE: vktrs/youtube.py
  class YoutubeHelper (line 15) | class YoutubeHelper:
    method __init__ (line 16) | def __init__(
    method get_subtitles (line 32) | def get_subtitles(
  function parse_timestamp (line 49) | def parse_timestamp(ts):
  function vtt_to_token_timestamps (line 58) | def vtt_to_token_timestamps(captions):
  function srv2_to_token_timestamps (line 88) | def srv2_to_token_timestamps(srv2_xml):
Condensed preview — 14 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (294K chars).
[
  {
    "path": ".github/workflows/python-publish.yml",
    "chars": 1087,
    "preview": "# This workflow will upload a Python Package using Twine when a release is created\n# For more information see: https://h"
  },
  {
    "path": ".gitignore",
    "chars": 247,
    "preview": "_venv\n\n\n*.srv2\n*.vtt\n*.webm\n*.mp3\n*.mp4\n*.pyc\n*.egg-info/\n**/frames\n**/archive\n*.yaml\n*.whl\n*.tar.gz\ndist\n\n# local huggi"
  },
  {
    "path": "LICENSE",
    "chars": 1067,
    "preview": "MIT License\n\nCopyright (c) 2022 David Marx\n\nPermission is hereby granted, free of charge, to any person obtaining a copy"
  },
  {
    "path": "README.md",
    "chars": 3740,
    "preview": "# Video Killed The Radio Star [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab"
  },
  {
    "path": "VERSION",
    "chars": 6,
    "preview": "0.1.8\n"
  },
  {
    "path": "Video_Killed_The_Radio_Star_Defusion.ipynb",
    "chars": 251066,
    "preview": "{\n  \"cells\": [\n    {\n      \"cell_type\": \"markdown\",\n      \"metadata\": {\n        \"id\": \"mgXxoDhMAiti\"\n      },\n      \"sou"
  },
  {
    "path": "pyproject.toml",
    "chars": 886,
    "preview": "[build-system]\nrequires = [\"setuptools\", \"setuptools-scm\"]\nbuild-backend = \"setuptools.build_meta\"\n\n[project]\nname = \"vk"
  },
  {
    "path": "vktrs/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "vktrs/api.py",
    "chars": 1539,
    "preview": "import io\nimport os\nfrom PIL import Image, ImageDraw, ImageFont\nimport warnings\n\nfrom stability_sdk import client\nimport"
  },
  {
    "path": "vktrs/asr.py",
    "chars": 5933,
    "preview": "\"\"\"\n# Automatic Speech Recognition utilities\n\nCurrently uses openai/whisper. To install: \n  pip install git+https://gith"
  },
  {
    "path": "vktrs/hf.py",
    "chars": 2662,
    "preview": "from pathlib import Path\nimport torch\nfrom torch import autocast\nfrom diffusers import (\n    StableDiffusionImg2ImgPipel"
  },
  {
    "path": "vktrs/tsp.py",
    "chars": 1258,
    "preview": "import time\n\nimport numpy as np\nfrom scipy.spatial.distance import pdist, squareform\nfrom toolz.itertoolz import partiti"
  },
  {
    "path": "vktrs/utils.py",
    "chars": 4222,
    "preview": "from pathlib import Path\nimport random\nimport string\nimport subprocess\nimport textwrap\nimport time\n\nimport pandas as pd\n"
  },
  {
    "path": "vktrs/youtube.py",
    "chars": 2903,
    "preview": "import datetime as dt\nimport re\n\nfrom bs4 import BeautifulSoup\nimport yt_dlp\n\n\n# embedding yt-dlp instead of CLI let's u"
  }
]

About this extraction

This page contains the full source code of the dmarx/video-killed-the-radio-star GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 14 files (270.1 KB), approximately 105.2k tokens, and a symbol index with 28 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!