[an error occurred while processing this directive]

Spine Label Formatter (spine_label_formatter.py)

This tool takes an items CSV and adds label_line1, label_line2, and label_line3 for spine labels (call number on the first line, wrapped title text below).

How It Works (In Plain Language)

  • Reads your items CSV and looks for a "call" column (call number) and a "title" column.
  • Wraps long titles into shorter lines so the words fit on a small label (by default about 20 characters per line).
  • Fills three new columns: label_line1 = call number, label_line2 = first part of the title, label_line3 = next part of the title.
  • Writes a new CSV with both the original columns and the new label line columns for use in label-printing software.

How to Use

  1. Place your items CSV (for example items.csv) in this folder.
  2. Open Terminal and run: cd ~/Desktop/library_pop_up_tools
  3. Then run: python spine_label_formatter.py items.csv labels_output.csv
  4. Open labels_output.csv and map the label lines in your label software.
library_pop_up_tools % python spine_label_formatter.py items.csv labels_output.csv
Label-ready file written to: labels_output.csv

Optional: Adjust Label Line Length

By default, the title lines are wrapped at about 20 characters. You can tweak this at the top of the script:

MAX_TITLE_LINE_LENGTH = 20

To fit more or fewer characters per line, open spine_label_formatter.py, change this number, save, and run the tool again.

Full Python Source (Optional)

Click to show the full script
#!/usr/bin/env python3
"""
spine_label_formatter.py

Pop-up tool to prepare spine label text from a CSV export.

Expected columns (case-insensitive, flexible):
    - call_number  (or similar)
    - title

Output: same CSV plus `label_line1`, `label_line2`, `label_line3`.

Example:
    python spine_label_formatter.py items.csv labels_output.csv
"""

import csv
import sys
from pathlib import Path

MAX_TITLE_LINE_LENGTH = 20


def wrap_title(title: str) -> list[str]:
    words = (title or "").split()
    lines: list[str] = []
    current: list[str] = []
    for w in words:
        tentative = " ".join(current + [w]) if current else w
        if len(tentative) <= MAX_TITLE_LINE_LENGTH:
            current.append(w)
        else:
            if current:
                lines.append(" ".join(current))
            current = [w]
    if current:
        lines.append(" ".join(current))
    return lines[:2]


def format_labels(input_path: Path, output_path: Path) -> None:
    with input_path.open(newline="", encoding="utf-8-sig") as infile, output_path.open(
        "w", newline="", encoding="utf-8"
    ) as outfile:
        reader = csv.DictReader(infile)
        fieldnames = list(reader.fieldnames or [])
        for col in ["label_line1", "label_line2", "label_line3"]:
            if col not in fieldnames:
                fieldnames.append(col)
        writer = csv.DictWriter(outfile, fieldnames=fieldnames)
        writer.writeheader()

        for row in reader:
            for key in row:
                if row[key] is None:
                    row[key] = ""

            call_keys = [k for k in row if "call" in k.lower()]
            title_keys = [k for k in row if "title" in k.lower()]

            call_number = row[call_keys[0]] if call_keys else ""
            title = row[title_keys[0]] if title_keys else ""

            title_lines = wrap_title(title)

            row["label_line1"] = call_number.strip()
            row["label_line2"] = title_lines[0] if len(title_lines) > 0 else ""
            row["label_line3"] = title_lines[1] if len(title_lines) > 1 else ""

            writer.writerow(row)


def main(argv: list[str]) -> int:
    if len(argv) != 3:
        print("Usage: python spine_label_formatter.py items.csv labels_output.csv")
        return 1
    input_path = Path(argv[1]).expanduser()
    output_path = Path(argv[2]).expanduser()
    if not input_path.exists():
        print(f\'Input file not found: {input_path}")
        return 1
    format_labels(input_path, output_path)
    print(f\'Label-ready file written to: {output_path}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main(sys.argv))

← Back to all tools

[an error occurred while processing this directive]