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) -> 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.

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,
) -> 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.
    """
    from xil_pipeline.models import show_slug

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

    # project.json
    project_path = os.path.join(directory, "project.json")
    if not os.path.exists(project_path):
        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}"
        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)")

    # Subdirectories (new normalized layout — configs/{slug}/ for episode configs)
    for subdir in ("scripts", f"configs/{slug}", "parsed", "stems", "SFX", "daw", "masters", "cues"):
        path = os.path.join(directory, subdir)
        os.makedirs(path, exist_ok=True)

    # speakers.json — lives in configs/{slug}/ so each show has its own cast list
    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", 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)")

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",
    )
    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()

    if args.directory is None:
        if os.environ.get("XIL_PROJECTROOT"):
            from xil_pipeline.models import get_workspace_root
            directory = str(get_workspace_root())
        else:
            directory = os.path.abspath(".")
    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}\n")

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