← Back to Library Pop-Up Tools
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
- Place your items CSV (for example
items.csv) in this folder. - Open Terminal and run:
cd ~/Desktop/library_pop_up_tools - Then run:
python spine_label_formatter.py items.csv labels_output.csv - Open
labels_output.csvand 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
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))