0

I needed to write a solution to write data on and then print RFID labels en-masse, each generated as .png images from a template python script and data taken from a database or excel file.

To print the program simply calls the relative system utility (CUPS on unix systems) using subprocess.check_call(print_cmd) passing the image file (saved on a ram-mounted file system for minimal disk usage)

Now, it also needs to run on Windows systems, but there is not really a decent system utility for that, and solutions under a similar question command line tool for print picture? don't account for print-job completion or if the job results in an error, the margins are all screwed and the image is always rotated 90 degrees for some reason.

How can I sanely print an image using a command or a script in Windows and wait for it to complete successfully or return an error if the job results in an error? Possibly with no dependencies

Jiulia
  • 305
  • 2
  • 10
  • why are you using scripting to print a batch of images? there are MANY utils for that ... for instance, IrfanView can do batch printing and can print one-per-page OR merge them into sheets & print that OR merge them into PDF sheets & print those. – Lee_Dailey Apr 16 '22 at 16:36
  • This solution differs from "just use IrfanView" by the fact that it does not require any dependency, and uses native windows programs. If there's a native windows utility that can do that, please add an answer. – Jiulia Apr 16 '22 at 17:24
  • ah! so you cannot do the sensible thing and use a util that has all this built in. [*sigh ...*] you have my sympathy ... and i wish you good luck with this ... unfortunate limitation. – Lee_Dailey Apr 16 '22 at 17:35
  • 1
    I mean i already solved it, this was an attempt at sharing a solution i found for a stupid problem. – Jiulia Apr 16 '22 at 17:37
  • that is good to know ... sorry for the misunderstanding ... [*blush*] – Lee_Dailey Apr 16 '22 at 18:05

2 Answers2

1

If you can install dependencies, there are many programs that offer a solution out-of-the-box.


The only sane way i could find to solve this issue with no dependencies is by creating a powershell script to account for this

[CmdletBinding()]
param (
    [string]    $file = $(throw "parameter is mandatory"),
    [string]    $printer = "EXACT PRINTER NAME HERE"
)

$ERR = "UserIntervention|Error|Jammed"

$status = (Get-Printer -Name $printer).PrinterStatus.ToString()
if ($status -match $ERR){ exit 1 }

# https://stackoverflow.com/a/20402656/17350905
# only sends the print job to the printer
rundll32 C:\Windows\System32\shimgvw.dll,ImageView_PrintTo $file $printer

# wait until printer is in printing status
do {
    $status = (Get-Printer -Name $printer).PrinterStatus.ToString()
    if ($status -match $ERR){ exit 1 }
    Start-Sleep -Milliseconds 100
} until ( $status -eq "Printing" )

# wait until printing is done
do {
    $status = (Get-Printer -Name $printer).PrinterStatus.ToString()
    if ($status -match $ERR){ exit 1 }
    Start-Sleep -Milliseconds 100
} until ( $status -eq "Normal" )

I would then need to slightly modify the print subprocess call to

powershell -File "path\to\print.ps1" "C:\absolute\path\to\file.png"

Then there are a couple of necessary setup steps:

(discaimer, I don't use windows in english so i don't know how the english thigs are supposed to be called. i will use cursive for those)

  1. create an example image, right click and then select Print

    • from the print dialog that opens then set up all the default options you want, like orientation, margins, paper type, etc etc for the specific printer you're gonna use.
  2. Go to printer settings, under tools then edit Printer Status Monitoring

    • edit monitoring frequency to "only during print jobs". it should be disabled by default
    • in the next tab, modify polling frequency to the minimum available, 100ms during print jobs (you can use a lower one for the while not printing option

Assuming the following:

  • only your program is running this script
  • theres always only 1 printing job at a time for a given printer
  • the printer drivers were not written by a monkey and they actually report the current, correct printer status

This little hack will allow to print an image from a command and await job completion, with error management; and uses only windows preinstalled software

Further optimization could be done by keeping powershell subprocess active and only passing it scripts in the & "path\to\print.ps1" "C:\absolute\path\to\file.png" format, waiting for standard output to report an OK or a KO; but only if mass printing is required.

Jiulia
  • 305
  • 2
  • 10
0

Having had to work on this again, just wanted to add a simpler solution in "pure" python using the pywin32 package

import time
import subprocess
from typing import List
try:
    import win32print as wprint

    PRINTERS: List[str] = [p[2] for p in wprint.EnumPrinters(wprint.PRINTER_ENUM_LOCAL)]
    PRINTER_DEFAULT = wprint.GetDefaultPrinter()
    WIN32_SUPPORTED = True
except:
    print("[!!] an error occured while retrieving printers")
    # you could throw an exception or whatever

# bla bla do other stuff
if "WIN32_SUPPORTED" in globals():
  __printImg_win32(file, printer_name)

def __printImg_win32(file: str, printer: str = ""):
    if not printer:
      printer = PRINTER_DEFAULT
    # verify prerequisites here

    # i still do prefer to print calling rundll32 directly,
    #  because of the default printer settings shenaningans
    #  and also because i've reliably used it to spool millions of jobs
    subprocess.check_call(
        [
            "C:\\Windows\\System32\\rundll32",
            "C:\\Windows\\System32\\shimgvw.dll,ImageView_PrintTo",
            file,
            printer,
        ]
    )
    __monitorJob_win32(printer)
    pass

def __monitorJob_win32(printer: str, timeout=16.0):
    p = wprint.OpenPrinter(printer)

    # wait for job to be sheduled
    t0 = time.time()
    while (time.time()-t0) < timeout:
        ptrr = wprint.GetPrinter(p, 2)
        # unsure about those flags, but definitively not errors.
        #  it seems they are "moving paper forward"
        if ptrr["Status"] != 0 and ptrr["Status"] not in [1024,1048576]:
            raise Error("Printer is in error (status %d)!" % ptrr["Status"])
        if ptrr["cJobs"] > 0:
            break
        time.sleep(0.1)
    else:
        raise Error("Printer timeout sheduling job!")

    # await job completion
    t0 = time.time()
    while (time.time()-t0) < timeout:
        ptrr = wprint.GetPrinter(p, 2)
        if ptrr["Status"] != 0 and ptrr["Status"] not in [1024,1048576]:
            raise Error("Printer is in error (status %d)!" % ptrr["Status"])
        if ptrr["cJobs"] == 0 and ptrr["Status"] == 0:
            break
        time.sleep(0.1)
    else:
        raise Error("Printer timeout waiting for completion!")

    wprint.ClosePrinter(p)
    return


useful additional resources

Jiulia
  • 305
  • 2
  • 10