Compare commits
4 Commits
48a7016f72
...
5fdbcb7468
| Author | SHA1 | Date | |
|---|---|---|---|
| 5fdbcb7468 | |||
| 6e47e10a44 | |||
| 573d8574a2 | |||
| 8e69681fc3 |
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
img/
|
||||
img_fullwidth/
|
||||
junk/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
.env
|
||||
40
README.md
Normal file
40
README.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Receipt Maker
|
||||
|
||||
Prints random images from 4chan's /bant/ board to an Epson TM-T20II thermal receipt printer over Ethernet.
|
||||
|
||||
## How it works
|
||||
|
||||
1. `connect_recipt.sh` — configures the network interface and connects to the printer at `192.168.192.168:9100`
|
||||
2. `receipt.py` — CLI with subcommands to print random /bant/ images, text notes, or local images (resized to full printer width, 576px)
|
||||
|
||||
## Requirements
|
||||
|
||||
- Python 3
|
||||
- `python-escpos`
|
||||
- `Pillow`
|
||||
- `requests`
|
||||
- `nmap` (for connection script)
|
||||
- Epson TM-T20II connected via USB-to-Ethernet adapter
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Print a random image from /bant/
|
||||
python receipt.py image
|
||||
|
||||
# Print a text note
|
||||
python receipt.py note "your message here"
|
||||
|
||||
# Print a local image (with optional caption)
|
||||
python receipt.py print path/to/image.jpg
|
||||
python receipt.py print path/to/image.jpg "optional caption"
|
||||
```
|
||||
|
||||
The script will run `connect_recipt.sh` automatically to bring up the network interface before printing.
|
||||
|
||||
## Files
|
||||
|
||||
- `receipt.py` — main script
|
||||
- `connect_recipt.sh` — network setup for USB Ethernet adapter
|
||||
- `img/` — downloaded images cache
|
||||
- `img_fullwidth/` — resized images ready for printing
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Try to find the USB Ethernet interface first
|
||||
iface=$(ip -o link | awk -F': ' '/enx|enp.*u/{print $2}' | head -n 1)
|
||||
@@ -16,14 +17,17 @@ else
|
||||
echo "Using USB Ethernet interface: $iface"
|
||||
fi
|
||||
|
||||
# Assign IP address (only for USB interface, skip eth0)
|
||||
# if [[ $iface != "eth0" ]]; then
|
||||
sudo ip addr add 192.168.192.10/24 dev "$iface" 2>/dev/null
|
||||
# fi
|
||||
|
||||
# Bring interface up
|
||||
# Bring interface up before assigning IP
|
||||
sudo ip link set "$iface" up
|
||||
|
||||
# Scan port 9100 on the printer IP
|
||||
nmap -p 9100 192.168.192.168
|
||||
# Assign IP address if not already set
|
||||
if ! ip addr show dev "$iface" | grep -q "192.168.192.10"; then
|
||||
sudo ip addr add 192.168.192.10/24 dev "$iface"
|
||||
fi
|
||||
|
||||
# Scan port 9100 on the printer IP and verify it is open
|
||||
if ! nmap -p 9100 --open 192.168.192.168 | grep -q "9100/tcp open"; then
|
||||
echo "Printer port 9100 is not open."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
84
receipt.py
84
receipt.py
@@ -1,28 +1,29 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from escpos import *
|
||||
from escpos import printer
|
||||
from PIL import Image
|
||||
import os
|
||||
import subprocess
|
||||
import time
|
||||
import random
|
||||
import requests
|
||||
from urllib.parse import quote
|
||||
|
||||
|
||||
# Return T20II printer
|
||||
def get_T20II_usb():
|
||||
p = printer.Usb(0x04b8, 0x0202) #, profile="TM-T20II")
|
||||
p = printer.Usb(0x04B8, 0x0202) # , profile="TM-T20II")
|
||||
# p.set_with_default(align='center', font='a', bold=False, width=2, height=2, custom_size=True, smooth=True)
|
||||
return p
|
||||
|
||||
|
||||
def get_T20II_ethernet():
|
||||
p = printer.Network("192.168.192.168", port=9100)#, profile="TM-T20II")
|
||||
p = printer.Network("192.168.192.168", port=9100) # , profile="TM-T20II")
|
||||
# p.set_with_default(align='center', font='a', bold=False, width=2, height=2, custom_size=True, smooth=True)
|
||||
return p
|
||||
|
||||
|
||||
def resize_image_to_fullwidth(img_src_name, target_width=576):
|
||||
# Make fullwidth dir
|
||||
os.makedirs('img_fullwidth', exist_ok=True)
|
||||
os.makedirs("img_fullwidth", exist_ok=True)
|
||||
|
||||
# Open the image
|
||||
img = Image.open(img_src_name)
|
||||
@@ -38,7 +39,7 @@ def resize_image_to_fullwidth(img_src_name, target_width=576):
|
||||
resized_img = img.resize((target_width, new_height), Image.LANCZOS)
|
||||
|
||||
# Save the resized image
|
||||
name, ext = os.path.splitext(img_src_name)
|
||||
_, ext = os.path.splitext(img_src_name)
|
||||
filename_no_ext = os.path.splitext(os.path.basename(img_src_name))[0]
|
||||
|
||||
# Build destination filename
|
||||
@@ -49,7 +50,7 @@ def resize_image_to_fullwidth(img_src_name, target_width=576):
|
||||
|
||||
def get_random_bant_image():
|
||||
# Create img directory if it doesn't exist
|
||||
os.makedirs('img', exist_ok=True)
|
||||
os.makedirs("img", exist_ok=True)
|
||||
|
||||
# Fetch the catalog JSON for /bant/
|
||||
url = "https://a.4cdn.org/bant/catalog.json"
|
||||
@@ -58,12 +59,15 @@ def get_random_bant_image():
|
||||
catalog = response.json()
|
||||
|
||||
# Flatten the list of threads from all pages
|
||||
threads = [thread for page in catalog for thread in page['threads']]
|
||||
threads = [thread for page in catalog for thread in page["threads"]]
|
||||
|
||||
# Filter threads that have images (tim and ext fields)
|
||||
image_threads = [
|
||||
thread for thread in threads
|
||||
if 'tim' in thread and 'ext' in thread and thread['ext'].lower() not in ['.gif', '.mp4', '.webm']
|
||||
thread
|
||||
for thread in threads
|
||||
if "tim" in thread
|
||||
and "ext" in thread
|
||||
and thread["ext"].lower() not in [".gif", ".mp4", ".webm"]
|
||||
]
|
||||
|
||||
if not image_threads:
|
||||
@@ -80,31 +84,79 @@ def get_random_bant_image():
|
||||
img_response = requests.get(image_url)
|
||||
img_response.raise_for_status()
|
||||
|
||||
with open(filename, 'wb') as f:
|
||||
with open(filename, "wb") as f:
|
||||
f.write(img_response.content)
|
||||
|
||||
return filename
|
||||
|
||||
|
||||
def print_random_bant_image(p, sh_file):
|
||||
img = get_random_bant_image()
|
||||
if img is None:
|
||||
print("No images found on /bant/.")
|
||||
return
|
||||
filename_no_ext = os.path.splitext(os.path.basename(img))[0]
|
||||
p.textln(filename_no_ext)
|
||||
fimg = resize_image_to_fullwidth(img)
|
||||
try:
|
||||
p.image(fimg, impl='bitImageColumn');
|
||||
except:
|
||||
p.image(fimg, impl="bitImageColumn")
|
||||
except Exception as e:
|
||||
print(f"Print failed, reconnecting: {e}")
|
||||
subprocess.run(["bash", sh_file], check=True)
|
||||
p = get_T20II_ethernet()
|
||||
p.cut()
|
||||
|
||||
|
||||
def print_note(p, text):
|
||||
p.textln(text)
|
||||
p.cut()
|
||||
|
||||
|
||||
def print_local_image(p, sh_file, img_path, message=None):
|
||||
if not os.path.isfile(img_path):
|
||||
print(f"File not found: {img_path}")
|
||||
return
|
||||
if message:
|
||||
p.textln(message)
|
||||
fimg = resize_image_to_fullwidth(img_path)
|
||||
try:
|
||||
p.image(fimg, impl="bitImageColumn")
|
||||
except Exception as e:
|
||||
print(f"Print failed, reconnecting: {e}")
|
||||
subprocess.run(["bash", sh_file], check=True)
|
||||
p = get_T20II_ethernet()
|
||||
p.cut()
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="Receipt printer CLI")
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
subparsers.add_parser("image", help="Print a random /bant/ image")
|
||||
|
||||
note_p = subparsers.add_parser("note", help="Print a text note")
|
||||
note_p.add_argument("text", help="The note to print")
|
||||
|
||||
print_p = subparsers.add_parser("print", help="Print a local image")
|
||||
print_p.add_argument("path", help="Path to the image file")
|
||||
print_p.add_argument("message", nargs="?", default=None, help="Optional caption")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
sh_file = os.path.join(script_dir, "connect_recipt.sh")
|
||||
subprocess.run(["bash", sh_file], check=True)
|
||||
p = get_T20II_ethernet()
|
||||
|
||||
print_random_bant_image(p, sh_file)
|
||||
return
|
||||
if args.command == "image":
|
||||
print_random_bant_image(p, sh_file)
|
||||
elif args.command == "note":
|
||||
print_note(p, args.text)
|
||||
elif args.command == "print":
|
||||
print_local_image(p, sh_file, args.path, args.message)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user