Skip to content

Xil Init

src.xil_pipeline.xil_init

Scaffold a new xil-pipeline project workspace.

Creates the directory structure, project.json, speakers.json, and a type-specific sample production script so a first-time user can immediately run the pipeline stages in dry-run mode.

Usage:

xil-init                              # scaffold in current directory
xil-init my-show                      # scaffold in ./my-show/
xil-init --show "Night Owls"          # custom show name
xil-init --type podcast               # podcast (default)
xil-init --type audiobook             # audiobook (V01C01 tags)
xil-init --type drama                 # drama short / audio drama
xil-init --type special               # special / one-off

logger module-attribute

logger = get_logger(__name__)

SAMPLE_SPEAKERS module-attribute

SAMPLE_SPEAKERS: list[dict] = [{'display': 'HOST', 'key': 'host'}, {'display': 'CO-HOST', 'key': 'co_host'}, {'display': 'CALLER', 'key': 'caller'}, {'display': 'NARRATOR', 'key': 'narrator'}]

SPEAKERS_BY_TYPE module-attribute

SPEAKERS_BY_TYPE: dict[str, list[dict]] = {'podcast': SAMPLE_SPEAKERS, 'audiobook': [{'display': 'NARRATOR', 'key': 'narrator'}], 'drama': [{'display': 'NARRATOR', 'key': 'narrator'}, {'display': 'ALICE', 'key': 'alice'}, {'display': 'BOB', 'key': 'bob'}, {'display': 'CHARLIE', 'key': 'charlie'}, {'display': 'DEZ', 'key': 'dez'}], 'special': [{'display': 'HOST', 'key': 'host'}, {'display': 'NARRATOR', 'key': 'narrator'}]}

SCRIPTS_BY_TYPE module-attribute

SCRIPTS_BY_TYPE: dict[str, str] = {'podcast': _PODCAST_SCRIPT, 'audiobook': _AUDIOBOOK_SCRIPT, 'drama': _DRAMA_SCRIPT, 'special': _SPECIAL_SCRIPT}

SAMPLE_TAG_BY_TYPE module-attribute

SAMPLE_TAG_BY_TYPE: dict[str, str] = {'podcast': 'S01E01', 'audiobook': 'V01C01', 'drama': 'S01E01', 'special': 'SP001'}

GETTING_STARTED_BY_TYPE module-attribute

GETTING_STARTED_BY_TYPE: dict[str, str] = {'podcast': 'S01E01', 'audiobook': 'V01C01', 'drama': 'S01E01', 'special': 'SP001'}

scaffold

scaffold(directory: str, show_name: str, content_type: str = 'podcast', season: int | None = None, season_title: str | None = None, flat_layout: bool = True) -> None

Create a new xil-pipeline workspace in directory.

Parameters:

  • directory (str) –

    Target directory (created if it doesn't exist).

  • show_name (str) –

    Human-readable show name for project.json.

  • content_type (str, default: 'podcast' ) –

    Content type — "podcast", "audiobook", "drama", or "special".

  • season (int | None, default: None ) –

    Optional season number for project.json and the sample script header.

  • season_title (str | None, default: None ) –

    Optional season/arc title for project.json.

  • flat_layout (bool, default: True ) –

    When True (default), use the multi-show flat layout: project.json is written to configs/{slug}/ and per-show subdirectories are created inside the shared category dirs (daw/{slug}/, stems/{slug}/, etc.). Set to False for a legacy single-show workspace where all dirs live at the root.

Source code in src/xil_pipeline/xil_init.py
def scaffold(
    directory: str,
    show_name: str,
    content_type: str = "podcast",
    season: int | None = None,
    season_title: str | None = None,
    flat_layout: bool = True,
) -> None:
    """Create a new xil-pipeline workspace in *directory*.

    Args:
        directory: Target directory (created if it doesn't exist).
        show_name: Human-readable show name for project.json.
        content_type: Content type — ``"podcast"``, ``"audiobook"``,
            ``"drama"``, or ``"special"``.
        season: Optional season number for project.json and the sample script header.
        season_title: Optional season/arc title for project.json.
        flat_layout: When ``True`` (default), use the multi-show flat layout:
            ``project.json`` is written to ``configs/{slug}/`` and per-show
            subdirectories are created inside the shared category dirs
            (``daw/{slug}/``, ``stems/{slug}/``, etc.).  Set to ``False`` for
            a legacy single-show workspace where all dirs live at the root.
    """
    from xil_pipeline.models import set_active_show, show_slug

    os.makedirs(directory, exist_ok=True)
    slug = show_slug(show_name)

    project_data: dict = {"show": show_name, "type": content_type}
    if season is not None:
        project_data["season"] = season
    if season_title is not None:
        project_data["season_title"] = season_title
    if content_type == "audiobook":
        project_data["tag_format"] = "V{volume:02d}C{chapter:02d}"

    if flat_layout:
        # Shared dirs — one per workspace, no per-show subdir
        for shared in ("scripts", "SFX", "voice_samples"):
            os.makedirs(os.path.join(directory, shared), exist_ok=True)
        # Per-show subdirs under scripts/ and SFX/ to match the rest of the workspace layout
        for slug_dir in (f"scripts/{slug}", f"SFX/{slug}"):
            os.makedirs(os.path.join(directory, slug_dir), exist_ok=True)
        # Per-show category subdirs
        os.makedirs(os.path.join(directory, "configs", slug), exist_ok=True)
        for category in ("daw", "stems", "masters", "parsed", "cues", "posts"):
            os.makedirs(os.path.join(directory, category, slug), exist_ok=True)
        # project.json lives in configs/{slug}/
        project_path = os.path.join(directory, "configs", slug, "project.json")
    else:
        # Legacy single-show layout
        for subdir in ("scripts", f"configs/{slug}", "parsed", "stems", "SFX", "daw", "masters", "cues"):
            os.makedirs(os.path.join(directory, subdir), exist_ok=True)
        project_path = os.path.join(directory, "project.json")

    if not os.path.exists(project_path):
        with open(project_path, "w", encoding="utf-8") as f:
            json.dump(project_data, f, indent=2)
            f.write("\n")
        logger.info(f"  Created {project_path}")
    else:
        logger.info(f"  Skipped {project_path} (already exists)")

    # speakers.json — lives in configs/{slug}/
    speakers_path = os.path.join(directory, "configs", slug, "speakers.json")
    if not os.path.exists(speakers_path):
        speakers = SPEAKERS_BY_TYPE.get(content_type, SPEAKERS_BY_TYPE["podcast"])
        with open(speakers_path, "w", encoding="utf-8") as f:
            json.dump(speakers, f, indent=2)
            f.write("\n")
        logger.info(f"  Created {speakers_path}")
    else:
        logger.info(f"  Skipped {speakers_path} (already exists)")

    # Sample script
    tag = SAMPLE_TAG_BY_TYPE.get(content_type, "S01E01")
    season_part = f" Season {season}:" if season is not None else ""
    arc_part = f' Arc: "{season_title}"' if season_title else ""
    script_path = os.path.join(directory, "scripts", slug, f"sample_{tag}.md") if flat_layout else os.path.join(directory, "scripts", f"sample_{tag}.md")
    if not os.path.exists(script_path):
        template = SCRIPTS_BY_TYPE.get(content_type, SCRIPTS_BY_TYPE["podcast"])
        with open(script_path, "w", encoding="utf-8") as f:
            f.write(template.format(
                show=show_name,
                season_part=season_part,
                arc_part=arc_part,
            ))
        logger.info(f"  Created {script_path}")
    else:
        logger.info(f"  Skipped {script_path} (already exists)")

    if flat_layout:
        set_active_show(slug)
        logger.info(f"  Active show set to: {slug}")

print_getting_started

print_getting_started(directory: str, content_type: str = 'podcast') -> None

Print a getting-started guide after scaffolding.

Source code in src/xil_pipeline/xil_init.py
def print_getting_started(directory: str, content_type: str = "podcast") -> None:
    """Print a getting-started guide after scaffolding."""
    cd_prefix = f"cd {directory} && " if directory != "." else ""
    tag = SAMPLE_TAG_BY_TYPE.get(content_type, "S01E01")
    logger.info(f"""
Getting Started
===============

1. Install the pipeline:
   pip install xil-pipeline

2. Scan the sample script (pre-flight check):
   {cd_prefix}xil-scan scripts/sample_{tag}.md

3. Parse the script into structured JSON:
   {cd_prefix}xil-parse scripts/sample_{tag}.md --episode {tag}

4. Preview voice generation (no API key needed):
   {cd_prefix}xil-produce --episode {tag} --dry-run

5. To use your own script:
   - Edit configs/<slug>/speakers.json with your cast
   - Write your script in scripts/
   - Set your ElevenLabs API key: export ELEVENLABS_API_KEY=your-key
   - Run the pipeline stages in order (see README.md)
""")

get_parser

get_parser() -> argparse.ArgumentParser
Source code in src/xil_pipeline/xil_init.py
def get_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        prog="xil-init",
        description="Scaffold a new xil-pipeline project workspace",
    )
    parser.add_argument(
        "directory", nargs="?", default=None,
        help="Target directory (default: XIL_PROJECTROOT if set, else current directory)",
    )
    parser.add_argument(
        "--show", default="Sample Show",
        help='Show name for project.json (default: "Sample Show")',
    )
    parser.add_argument(
        "--type", dest="content_type",
        choices=["podcast", "audiobook", "drama", "special"],
        default="podcast",
        help="Content type: podcast (default), audiobook, drama, special",
    )
    parser.add_argument(
        "--season", type=int, default=None,
        help="Season number for project.json and the sample script header",
    )
    parser.add_argument(
        "--season-title", default=None, metavar="TITLE",
        help="Season/arc title for project.json and the sample script header",
    )
    parser.add_argument(
        "--flat", dest="flat_layout", action="store_true", default=True,
        help="Use multi-show flat layout: project.json in configs/{slug}/, per-show category subdirs (default)",
    )
    parser.add_argument(
        "--no-flat", dest="flat_layout", action="store_false",
        help="Use legacy single-show layout: all dirs at workspace root",
    )
    return parser

main

main() -> None

CLI entry point for project scaffolding.

Source code in src/xil_pipeline/xil_init.py
def main() -> None:
    """CLI entry point for project scaffolding."""
    configure_logging()
    args = get_parser().parse_args()

    from xil_pipeline.models import get_workspace_root
    if args.directory is None:
        directory = str(get_workspace_root())
    else:
        directory = os.path.abspath(args.directory)
    show_name = args.show
    content_type = args.content_type

    logger.info(f"\nScaffolding xil-pipeline workspace in: {directory}")
    logger.info(f"Show: {show_name}  Type: {content_type}  Flat layout: {args.flat_layout}\n")

    scaffold(directory, show_name, content_type=content_type,
             season=args.season, season_title=args.season_title,
             flat_layout=args.flat_layout)
    print_getting_started(args.directory, content_type=content_type)