Python CLI tools for controlling the Brother VC-500W label printer from Linux, macOS, and other Unix-like systems.
Features:
- 🖨️ Print text labels with automatic image generation
- 🎨 Send custom JPEG images to printer
- 📊 Check printer status and tape remaining
- ⚙️ Configure via JSON config file
- 🔧 Horizontal and vertical text support
- 📬 Optional CUPS queue mode with job management
- 🚀 Easy installation with
uv toolor Nix
Fork from m7i.org/projects/labelprinter-linux-python-for-vc-500w
License: AGPLv3 (see LICENSE file)
This is an unofficial, open-source package with no warranty or support guarantees. Use at your own risk. The authors are not responsible for any damage to your printer or system.
Fast, isolated installation using uv:
# Install from local directory
uv tool install /path/to/labelprinter-vc500w
# Or install from git
uv tool install git+https://github.com/sgrimee/labelprinter-vc500w
# Commands now available system-wide:
label-text "Hello World"
label-raw --host 192.168.1.100 --print-jpeg image.jpgUpdate after changes:
uv tool install --force /path/to/labelprinter-vc500wnix profile install github:sgrimee/labelprinter-vc500w
# Or from local directory:
nix profile install .nix run github:sgrimee/labelprinter-vc500w -- "Hello World"{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
labelprinter.url = "github:sgrimee/labelprinter-vc500w";
};
outputs = { nixpkgs, labelprinter, ... }: {
nixosConfigurations.yourhostname = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
{
environment.systemPackages = [
labelprinter.packages.x86_64-linux.default
];
}
];
};
};
}{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
home-manager.url = "github:nix-community/home-manager";
labelprinter.url = "github:yourusername/labelprinter-vc500w";
};
outputs = { nixpkgs, home-manager, labelprinter, ... }: {
homeConfigurations.yourusername = home-manager.lib.homeManagerConfiguration {
pkgs = nixpkgs.legacyPackages.x86_64-linux;
modules = [
{
home.packages = [
labelprinter.packages.x86_64-linux.default
];
}
];
};
};
}{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
darwin.url = "github:lnl7/nix-darwin";
labelprinter.url = "github:sgrimee/labelprinter-vc500w";
};
outputs = { nixpkgs, darwin, labelprinter, ... }: {
darwinConfigurations.yourhostname = darwin.lib.darwinSystem {
system = "aarch64-darwin"; # or x86_64-darwin
modules = [
{
environment.systemPackages = [
labelprinter.packages.aarch64-darwin.default
];
}
];
};
};
}# With pipx (isolated environment)
pipx install /path/to/labelprinter-vc500w
# Or with pip
pip install /path/to/labelprinter-vc500w# Option A: Use the included script
just printer-ip
# Option B: Use nbtscan
nbtscan -v -s : 192.168.1.1/24 | grep "VC-500W"
# Option C: Check your router's DHCP leases
# Look for device named "VC-500W####"# Run the setup script to auto-detect and save printer hostname
just setup-printer
# Or manually edit ~/.config/labelprinter/config.jsonConfiguration file location: ~/.config/labelprinter/config.json
Example config:
{
"host": "VC-500W4188.local",
"label_width_mm": 25,
"font_size": 104,
"font": "/path/to/font.ttf",
"pixels_per_mm": 12.48,
"rotate": 0
}# Simple text label
label-text "Hello World"
# With options
label-text "My Label" --host VC-500W.local --width 25 --font-size 100
# Preview without printing
label-text "Test" --dry-run --preview
# Vertical text
label-text "Vertical" --rotate 90Configuration is stored in: ~/.config/labelprinter/config.json
The file is created automatically on first run. You can edit it manually or use just setup-printer for auto-configuration.
{
"host": "VC-500W4188.local",
"label_width_mm": 25,
"font_size": 104,
"font": "/path/to/font.ttf",
"padding": 50,
"rotate": 0,
"pixels_per_mm": 12.48,
"text_padding_pixels": 0,
"print_timeout": 120,
"avahi_timeout": 10,
"cups": {
"enabled": false,
"queue_name": "BrotherVC500W",
"auto_process": false
}
}Printer Settings:
host- Printer hostname or IP address (e.g., "VC-500W4188.local" or "192.168.1.100")label_width_mm- Tape width in millimeters (12, 19, 25, 29, 38, 50)
Font Settings:
font_size- Font size in points (80-150 typical; use ~104 for 25mm tape)font- Path to TrueType font file (or system font name)rotate- Text rotation: 0 (horizontal), 90, 180, or 270 degrees
Image Generation:
pixels_per_mm- Printer resolution (12.48 for VC-500W, ~317 DPI - don't change)padding- Image padding in pixels (deprecated, use text_padding_pixels)text_padding_pixels- Extra padding around text (0 for minimal waste)
Timeouts:
print_timeout- Maximum seconds to wait for printing (default: 120)avahi_timeout- Seconds to wait for mDNS resolution (default: 10)
CUPS Queue Mode (Optional):
cups.enabled- Set totrueto enable queue mode,falsefor direct printing (default: false)cups.queue_name- Name of CUPS printer queue (default: "BrotherVC500W")cups.auto_process- Reserved for future use (default: false)
| Tape Width | Recommended Font Size | Notes |
|---|---|---|
| 12mm | 40-60 | Small labels |
| 19mm | 70-90 | Medium labels |
| 25mm | 90-120 | Standard labels (104 works well) |
| 29mm | 100-130 | Large labels |
| 38mm | 120-160 | Extra large |
| 50mm | 150-200 | Maximum size |
Tip: Run label-text without --no-auto-detect to let it automatically detect tape width and suggest optimal font size.
Both label-text and label-raw support two printing modes:
How it works: Sends print jobs directly to the printer via TCP/IP socket connection
Speed: Immediate (blocks until printer is ready and completes printing)
Best for: Single labels, quick testing, simple scripts
Example: label-raw --host VC-500W.local --print-jpeg image.jpg
Characteristics:
- Synchronous operation - waits for print completion
- No dependencies beyond Python and Pillow
- Simple, straightforward printing
- Immediate feedback on success or failure
How it works: Jobs are submitted to a CUPS queue and processed by a background daemon
Speed: Fire-and-forget (returns immediately, prints later)
Best for: Multiple labels, batch printing, busy printers
Setup: Run label-queue-setup to configure
Example: label-raw --host VC-500W.local --print-jpeg image.jpg --queue
Characteristics:
- Asynchronous operation - submit and continue working
- Automatic retry when printer is busy
- Job persistence across system reboots
- Requires CUPS and pycups library
Both commands are config-aware and automatically use the mode specified in ~/.config/labelprinter/config.json:
{
"cups": {
"enabled": false // false = direct mode (default), true = queue mode
}
}Override with flags:
--direct- Force direct mode for this print (ignore config)--queue- Force queue mode for this print (ignore config)
Examples:
# Use mode from config (default behavior)
label-text "Hello World"
label-raw --host VC-500W.local --print-jpeg image.jpg
# Force direct mode (even if CUPS is enabled in config)
label-text "Urgent Label" --direct
label-raw --host VC-500W.local --print-jpeg image.jpg --direct
# Force queue mode (even if CUPS is disabled in config)
label-text "Batch Job" --queue
label-raw --host VC-500W.local --print-jpeg image.jpg --queueThe main command for printing text labels:
label-text "Your Text Here" [options]Options:
--host HOST- Override printer IP/hostname from config--width WIDTH- Label width in mm (default: 25)--font-size SIZE- Font size in points (default: 104)--rotate DEGREES- Rotate text: 0, 90, 180, or 270--dry-run- Create image but don't print--preview- Show preview in terminal (requires chafa/catimg/tiv)--debug- Show detailed debug output--no-auto-detect- Skip auto-detection of tape width from printer--direct- Force direct printing mode (ignore CUPS config)--queue- Force queue mode via CUPS (ignore config)
Auto-Detection:
By default, label-text automatically queries the printer to detect the installed tape width. If the detected width differs from your config file, it will:
- Use the detected width for printing
- Show a warning about the mismatch
- Suggest an appropriate font size if needed
Use --width to explicitly override, or --no-auto-detect to skip detection and use config values.
Examples:
# Basic usage
label-text "Storage Box A"
# Different tape width
label-text "Narrow Label" --width 12
# Vertical orientation
label-text "Side Label" --rotate 90
# Preview before printing
label-text "Test Label" --dry-run --preview
# Custom printer and size
label-text "Custom" --host 192.168.1.100 --font-size 80Send JPEG images to the printer (respects CUPS config, or use --direct/--queue to override):
label-raw --host HOST --print-jpeg IMAGE.jpg [options]Options:
-h HOST, --host HOST- Printer IP/hostname (required)-p PORT, --port PORT- Printer port (default: 9100)--print-mode {vivid,normal}- Print quality (default: vivid)--print-cut {none,half,full}- Cut mode (default: full)--wait-after-print- Wait for printer to finish before returning--get-status- Check printer status--release JOB_ID- Release stuck print job-j, --json- Output status in JSON format--direct- Force direct printing mode (ignore CUPS config)--queue- Force queue mode via CUPS (ignore config)
Examples:
# Print an image (uses config mode or default direct)
label-raw --host VC-500W.local --print-jpeg label.jpg
# Force direct mode (bypass CUPS even if enabled)
label-raw --host VC-500W.local --print-jpeg label.jpg --direct
# Force queue mode
label-raw --host VC-500W.local --print-jpeg label.jpg --queue
# Check printer status
label-raw --host VC-500W.local --get-status
# Get status as JSON
label-raw --host VC-500W.local --get-status --json
# Print with custom settings
label-raw --host VC-500W.local --print-jpeg image.jpg \
--print-mode normal --print-cut halfFor scenarios where the printer may be busy or you want to batch print jobs, you can enable CUPS queue mode. This allows jobs to be queued and processed when the printer is available, preventing lost print requests.
Problem: When the CLI blocks waiting for the printer and fails because the printer is busy, the print request is lost.
Solution: CUPS queue mode provides:
- Fire-and-forget printing: Commands return immediately after queuing
- Automatic retry: Jobs wait in queue until printer is available
- Job management: View, cancel, and manage pending print jobs
- Standard interface: Uses CUPS, the standard printing system on Unix/Linux
-
Install CUPS (if not already installed):
# Debian/Ubuntu sudo apt install cups # Fedora/RHEL sudo dnf install cups # Arch Linux sudo pacman -S cups # macOS - CUPS is pre-installed
-
Install with CUPS support:
# With uv tool uv tool install --with pycups /path/to/labelprinter-vc500w # Or if already installed uv tool install --force --with pycups /path/to/labelprinter-vc500w # With pip pip install pycups
Note:
pycupsrequires a C compiler and CUPS development libraries. On some systems you may need:# Debian/Ubuntu sudo apt install libcups2-dev gcc python3-dev # Fedora/RHEL sudo dnf install cups-devel gcc python3-devel
-
Configure CUPS queue:
label-queue-setup
This will:
- Create a CUPS printer queue named "BrotherVC500W"
- Configure it to hold jobs (not auto-print)
- Update your labelprinter config to enable CUPS mode
-
Verify setup:
label-queue-setup --check
Once CUPS mode is enabled, label-text will automatically queue jobs instead of printing directly:
# Start the worker daemon (in a separate terminal or as background service)
label-queue-worker
# Submit jobs (they'll be processed automatically)
label-text "Label 1"
label-text "Label 2"
label-text "Label 3"
# View pending jobs
label-queue list
# Jobs are processed automatically by the daemon!label-queue list - View pending jobs
label-queue list # Show pending jobs
label-queue list --all # Show all jobs (including completed)label-queue cancel - Cancel jobs
label-queue cancel 123 # Cancel job 123
label-queue cancel --all # Cancel all pending jobs
label-queue cancel 123 --purge # Cancel and delete job datalabel-queue-worker - Process queued jobs
By default, the worker runs as a daemon (keeps running and waits for new jobs):
label-queue-worker # Daemon mode (keeps running)
label-queue-worker --once # Process current batch and exit (for cron)
label-queue-worker --dry-run # Test mode (no actual printing)
label-queue-worker --verbose # Show detailed outputlabel-queue status - Show queue status
label-queue statusDaemon Mode (Default):
- Keeps running and monitors the queue
- Processes jobs immediately as they arrive
- Retries jobs if printer is busy
- Press Ctrl+C to stop
# Start worker as daemon
label-queue-worker
# With custom polling interval (default: 5s)
label-queue-worker --poll-interval 10
# Show what's happening
label-queue-worker --verboseOne-Shot Mode (--once):
- Process current batch and exit
- Useful for cron jobs or manual runs
- Exits when queue is empty or all jobs processed
# Process current jobs and exit
label-queue-worker --once
# For cron: process every 5 minutes
*/5 * * * * label-queue-worker --onceThe queue worker handles printer busy states automatically:
# Custom retry delay for busy printer (default: 30s)
label-queue-worker --retry-delay 60
# Test mode without printing
label-queue-worker --dry-runTo return to direct printing mode:
label-queue-setup --removeThis will:
- Remove the CUPS printer queue
- Disable CUPS mode in your config
- Return
label-textto direct printing behavior
When CUPS mode is enabled:
label-textcreates the label image and submits it to the CUPS queue (fire-and-forget)- Jobs remain in "held" state in CUPS
label-queue-workerprocesses held jobs:- Reads jobs from CUPS queue
- Sends them to the printer using existing
label-rawlogic - Handles "printer busy" errors with automatic retry
- Marks jobs as completed or failed
label-queueprovides job management (list, cancel, status)
This hybrid approach gives you CUPS benefits (standard queue interface, job persistence) while using your custom printer protocol for actual printing.
The project includes a justfile for common tasks:
# Show all available commands
just --list
# Get printer IP address
just printer-ip
# Setup printer configuration
just setup-printer
# Print text label
just print-text "Your Text"
# Preview label without printing
just preview-text "Your Text"
# Install dependencies
just install# Enter development environment
nix develop
# Or use direnv
echo "use flake" > .envrc
direnv allowThe dev shell includes:
- Python 3 with Pillow
- uv for package management
- just for task running
- chafa for terminal image preview
- black, flake8, pytest for code quality
# Run all tests
pytest labelprinter/test/
# Run specific test
pytest labelprinter/test/test_printer.py::TestPrinter::test_method_name
# Test printer connection
just test-printerThe project follows these conventions:
- Code formatting:
black - Linting:
flake8 - Import style: standard lib, then third-party, then local
- Naming: PascalCase for classes, snake_case for functions/variables
See AGENTS.md for detailed coding guidelines.
For horizontal text labels, the image generation follows these requirements:
-
Image HEIGHT = Full label width (312px for 25mm tape)
- Prevents printer auto-scaling that enlarges text
- Height must equal
label_width_mm * pixels_per_mm
-
Image WIDTH = Text width + padding
- Left padding: ~2 characters before text
- Right padding: ~2 characters after text
- Minimizes label waste while providing clean margins
-
Text height ≈ 1/3 of label width (~104px for 25mm)
- Vertically centered with white space above/below
- Adjust
font_sizein config (typically ~104 for 25mm tape)
Example for 25mm tape:
- Label width: 25mm = 312 pixels
- Image: 735×312 pixels (width varies with text)
- Text height: ~76-104 pixels (24-33% of label)
- Font size: ~104pt
- Left/right padding: ~124px each (~2 characters)
See AGENTS.md for complete image generation requirements.
- Check printer is on and connected to network
- Verify IP address:
just printer-ip - Try printer hostname:
VC-500W####.local(check printer display) - Check firewall allows port 9100
# Release stuck job
label-raw --host VC-500W.local --release JOB_ID
# Check printer status
label-raw --host VC-500W.local --get-status- Adjust
font_sizein config:~/.config/labelprinter/config.json - Or use
--font-sizeoption:label-text "Text" --font-size 80 - For 25mm tape: font size 80-120 works well
- For 12mm tape: try font size 40-60
- Ensure Pillow is installed:
pip install Pillow - Check font path in config file
- Try with default font (auto-selected if config font missing)
- Check
~/.local/binis in PATH - For uv:
export PATH="$HOME/.local/bin:$PATH" - For Nix: Commands should be in PATH automatically
labelprinter/
├── __init__.py
├── __main__.py # label-raw: Raw printer control (config-aware)
├── connection.py # TCP/IP connection handling
├── printer.py # Printer protocol implementation
├── print_text.py # label-text: Text-to-label main CLI (config-aware)
├── queue_worker.py # label-queue-worker: CUPS job processor
├── queue_manager.py # label-queue: CUPS job management
├── queue_setup.py # label-queue-setup: CUPS configuration
└── test/
└── test_printer.py # Unit tests
Configuration: ~/.config/labelprinter/config.json
Generated images: ./images/ (when using label-text)
Commands:
Main Commands (Config-Aware):
label-text- High-level: converts text to JPEG and prints (respects CUPS config)label-raw- Low-level: sends JPEG images to printer (respects CUPS config)- Both support
--directand--queueflags to override config
- Both support
Queue Management (CUPS):
label-queue-worker- Background daemon: processes queued jobs usinglabel-rawlabel-queue- Queue management: list, cancel, statuslabel-queue-setup- One-time setup: configure CUPS queue
Contributions welcome! Please:
- Follow the code style guidelines in
AGENTS.md - Add tests for new features
- Update documentation
- Test with actual hardware if possible
- Original implementation: Andrea Micheloni
- This fork: Enhanced with text printing, Nix packaging, and modern tooling
- brother_ql - For QL-series label printers
AGPLv3 - See LICENSE file for details.
This program comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under certain conditions. See the GNU Affero General Public License for more details.