@@ -1,55 +0,0 @@
|
||||
|
||||
### DEBUG
|
||||
|
||||
This document shows some ways to help debugging the package brother\_ql.
|
||||
One way is to look into the binary raster instruction file, see the *Analyse* section.
|
||||
The other way is to send those instructions to the printer one by one and check how it reacts, see the *Debug* section.
|
||||
|
||||
#### Analyse
|
||||
|
||||
To analyse a binary file containing Brother QL Raster instructions:
|
||||
|
||||
brother_ql_analyse 720x300_monochrome.bin --loglevel DEBUG
|
||||
|
||||
The tool will dissect your file and print the opcodes to stdout.
|
||||
In addition, it creates PNG images of what the printer's output would look like.
|
||||
They are saved to page0001.png etc. (yes, one .bin file can contain more than one "page").
|
||||
|
||||
This tool also has the `--help` option.
|
||||
|
||||
(This specific tool doesn't work on Python 2.)
|
||||
|
||||
#### Debug
|
||||
|
||||
If your printer has problems printing the instructions file, it may blink its LED (green or red) depending on the model. This can have many reasons, eg.:
|
||||
|
||||
* The selected label doesn't match.
|
||||
* End of paper.
|
||||
* Unsupported opcode (some printers require a mode switching opcode, others fail if such an instruction is sent; some do support data compression, others don't)
|
||||
|
||||
To debug this situation and find out which command could be the culprit, connect your printer via USB. (You don't get any status information via network).
|
||||
You can use the supplied tool `brother_ql_debug` to send your problematic instructions file to the printer. It will be split into single instructions sent one after the other.
|
||||
After every instruction, the printer will be given a chance to send a status response containing error information. Here is an example:
|
||||
|
||||
philipp@lion ~> brother_ql_debug ./720x151_monochrome.bin /dev/usb/lp0
|
||||
INFO: CMD preamble FOUND. Instruction: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [...] 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
INFO: CMD init FOUND. Instruction: 1B 40
|
||||
INFO: CMD status request FOUND. Instruction: 1B 69 53
|
||||
INFO: Response from the device: 80 20 42 30 4F 30 00 00 00 00 3E 0A 00 00 15 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
INFO: Interpretation of the response: 'Reply to status request' (phase: Waiting to receive), 'Continuous length tape' 62x0 mm^2, errors: []
|
||||
INFO: CMD media/quality FOUND. Instruction: 1B 69 7A CE 0A 3E 00 97 00 00 00 01 00
|
||||
INFO: CMD margins FOUND. Instruction: 1B 69 64 23 00
|
||||
INFO: CMD raster FOUND. Instruction: 67 00 5A 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 FF FF FF 1F FF FF FF FF FF F0 00 00 00 00 00 0F FF FF 03 FF FF FF FF E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [...] 00 07 FF FF 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
INFO: Response from the device: 80 20 42 30 4F 30 00 00 00 00 3E 0A 00 00 15 00 00 00 06 01 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
INFO: Interpretation of the response: 'Phase change' (phase: Printing state), 'Continuous length tape' 62x0 mm^2, errors: []
|
||||
INFO: CMD print FOUND. Instruction: 1A
|
||||
TIME 1.60
|
||||
INFO: Interpretation of the response: 'Printing completed' (phase: Printing state), 'Continuous length tape' 62x0 mm^2, errors: []
|
||||
TIME 1.60
|
||||
INFO: Interpretation of the response: 'Phase change' (phase: Waiting to receive), 'Continuous length tape' 62x0 mm^2, errors: []
|
||||
|
||||
Here, a command file was successfully printed. The last response should state the *Waiting to receive* phase.
|
||||
If you want to confirm the sending of every single command individually, you can add the `--interactive` argument to the command line call.
|
||||
|
||||
If you're seeing any error there, open a new issue on Github containing the debugging output to get your device supported.
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
## Legacy User Interfaces
|
||||
|
||||
The following user interfaces of this package are still around but their use is now deprecated and they will
|
||||
be removed in a future release of the package. This documentation still lists the old UI and how it was used.
|
||||
|
||||
### Create
|
||||
|
||||
The command line tool `brother_ql_create` is possibly the most important piece of software in this package.
|
||||
It allows you to create a new instruction file in the label printers' raster language:
|
||||
|
||||
brother_ql_create --model QL-500 ./720x300_monochrome.png > 720x300_monochrome.bin
|
||||
|
||||
If you want to find out about its options, just call the tool with `--help`:
|
||||
|
||||
brother_ql_create --help
|
||||
|
||||
giving:
|
||||
|
||||
usage: brother_ql_create [-h] [--model MODEL] [--label-size LABEL_SIZE]
|
||||
[--rotate {0,90,180,270}] [--threshold THRESHOLD]
|
||||
[--dither] [--compress] [--red] [--600dpi] [--no-cut]
|
||||
[--loglevel LOGLEVEL]
|
||||
image [outfile]
|
||||
|
||||
positional arguments:
|
||||
image The image file to create a label from.
|
||||
outfile The file to write the instructions to. Defaults to
|
||||
stdout.
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--model MODEL, -m MODEL
|
||||
The printer model to use. Check available ones with
|
||||
`brother_ql_info list-models`.
|
||||
--label-size LABEL_SIZE, -s LABEL_SIZE
|
||||
The label size (and kind) to use. Check available ones
|
||||
with `brother_ql_info list-label-sizes`.
|
||||
--rotate {0,90,180,270}, -r {0,90,180,270}
|
||||
Rotate the image (counterclock-wise) by this amount of
|
||||
degrees.
|
||||
--threshold THRESHOLD, -t THRESHOLD
|
||||
The threshold value (in percent) to discriminate
|
||||
between black and white pixels.
|
||||
--dither, -d Enable dithering when converting the image to b/w. If
|
||||
set, --threshold is meaningless.
|
||||
--compress, -c Enable compression (if available with the model).
|
||||
Takes more time but results in smaller file size.
|
||||
--red Create a label to be printed on black/red/white tape
|
||||
(only with QL-8xx series on DK-22251 labels). You must
|
||||
use this option when printing on black/red tape, even
|
||||
when not printing red.
|
||||
--600dpi Print with 600x300 dpi available on some models.
|
||||
Provide your image as 600x600 dpi; perpendicular to
|
||||
the feeding the image will be resized to 300dpi.
|
||||
--no-cut Don't cut the tape after printing the label.
|
||||
--loglevel LOGLEVEL Set to DEBUG for verbose debugging output to stderr.
|
||||
|
||||
The image argument should be a PNG/GIF/JPEG image file.
|
||||
Here is the output of `brother_ql_info list-label-sizes` listing the available options for `--label-size`:
|
||||
|
||||
Supported label sizes:
|
||||
Name Printable px Description
|
||||
12 106 (12 mm endless)
|
||||
29 306 (29 mm endless)
|
||||
38 413 (38 mm endless)
|
||||
50 554 (50 mm endless)
|
||||
54 590 (54 mm endless)
|
||||
62 696 (62 mm endless)
|
||||
102 1164 (102 mm endless)
|
||||
17x54 165 x 566 (17 x 54 mm^2)
|
||||
17x87 165 x 956 (17 x 87 mm^2)
|
||||
23x23 202 x 202 (23 x 23 mm^2)
|
||||
29x42 306 x 425 (29 x 42 mm^2)
|
||||
29x90 306 x 991 (29 x 90 mm^2)
|
||||
39x90 413 x 991 (38 x 90 mm^2)
|
||||
39x48 425 x 495 (39 x 48 mm^2)
|
||||
52x29 578 x 271 (52 x 29 mm^2)
|
||||
62x29 696 x 271 (62 x 29 mm^2)
|
||||
62x100 696 x 1109 (62 x 100 mm^2)
|
||||
102x51 1164 x 526 (102 x 51 mm^2)
|
||||
102x152 1164 x 1660 (102 x 152 mm^2)
|
||||
d12 94 x 94 (12 mm diameter, round)
|
||||
d24 236 x 236 (24 mm diameter, round)
|
||||
d58 618 x 618 (58 mm diameter, round)
|
||||
|
||||
**Pro Tip™**:
|
||||
For the best results, use image files with the matching pixel dimensions. Die-cut labels have to be in the exact pixel dimensions stated above. For endless label rolls, you can provide image files with a pixel width as stated above. If you provide a file with different dimensions when creating an endless label file, it will be scaled to fit the width.
|
||||
|
||||
### Print
|
||||
|
||||
Once you have a Brother QL instruction file, you can send it to the printer like this:
|
||||
|
||||
cat my_label.bin > /dev/usb/lp1
|
||||
|
||||
Be sure to have permission to write to the device (usually adding yourself to the *lp* group is sufficient.
|
||||
|
||||
Or via network (if you have a LAN/WLAN enabled Brother QL):
|
||||
|
||||
nc 192.168.0.23 9100 < my_label.bin
|
||||
|
||||
You can also use the tool `brother_ql_print` (Py3 only) to send the instructions to your printer:
|
||||
|
||||
brother_ql_print 720x151_monochrome.bin /dev/usb/lp0
|
||||
# or
|
||||
brother_ql_print --backend network 720x151_monochrome.bin tcp://192.168.0.23:9100
|
||||
# or (requires PyUSB: `pip install pyusb`)
|
||||
brother_ql_print 720x151_monochrome.bin usb://0x04f9:0x2015
|
||||
# or if you have multiple ones connected:
|
||||
brother_ql_print 720x151_monochrome.bin usb://0x04f9:0x2015/000M6Z401370
|
||||
# where 000M6Z401370 is the serial number (see lsusb output).
|
||||
|
||||
If your printer has problems printing the instructions file, it may blink its LED (green or red) depending on the model. This can have many reasons, eg.:
|
||||
|
||||
* The selected label doesn't match (make sure `--red` has been passed to `brother_ql_create` if you're using black/red labels).
|
||||
* End of paper.
|
||||
* Unsupported opcode (wrong `--model` when using `brother_ql_create`?)
|
||||
|
||||
### DEBUG
|
||||
|
||||
In case of trouble printing an instruction file, there are some ways to help debugging.
|
||||
One way is to look into the binary raster instruction file, see the *Analyse* section.
|
||||
The other way is to send those instructions to the printer one by one and check how it reacts, see the *Debug* section.
|
||||
|
||||
#### Analyse
|
||||
|
||||
To analyse a binary file containing Brother QL Raster instructions:
|
||||
|
||||
brother_ql_analyse 720x300_monochrome.bin --loglevel DEBUG
|
||||
|
||||
The tool will dissect your file and print the opcodes to stdout.
|
||||
In addition, it creates PNG images of what the printer's output would look like.
|
||||
They are saved to page0001.png etc. (yes, one .bin file can contain more than one "page").
|
||||
|
||||
This tool also has the `--help` option.
|
||||
|
||||
(This specific tool doesn't work on Python 2.)
|
||||
|
||||
#### Debug
|
||||
|
||||
If your printer has problems printing the instructions file, it may blink its LED (green or red) depending on the model. This can have many reasons, eg.:
|
||||
|
||||
* The selected label doesn't match.
|
||||
* End of paper.
|
||||
* Unsupported opcode (some printers require a mode switching opcode, others fail if such an instruction is sent; some do support data compression, others don't)
|
||||
|
||||
To debug this situation and find out which command could be the culprit, connect your printer via USB. (You don't get any status information via network).
|
||||
You can use the supplied tool `brother_ql_debug` to send your problematic instructions file to the printer. It will be split into single instructions sent one after the other.
|
||||
After every instruction, the printer will be given a chance to send a status response containing error information. Here is an example:
|
||||
|
||||
philipp@lion ~> brother_ql_debug ./720x151_monochrome.bin /dev/usb/lp0
|
||||
INFO: CMD preamble FOUND. Instruction: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [...] 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
INFO: CMD init FOUND. Instruction: 1B 40
|
||||
INFO: CMD status request FOUND. Instruction: 1B 69 53
|
||||
INFO: Response from the device: 80 20 42 30 4F 30 00 00 00 00 3E 0A 00 00 15 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
INFO: Interpretation of the response: 'Reply to status request' (phase: Waiting to receive), 'Continuous length tape' 62x0 mm^2, errors: []
|
||||
INFO: CMD media/quality FOUND. Instruction: 1B 69 7A CE 0A 3E 00 97 00 00 00 01 00
|
||||
INFO: CMD margins FOUND. Instruction: 1B 69 64 23 00
|
||||
INFO: CMD raster FOUND. Instruction: 67 00 5A 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 FF FF FF 1F FF FF FF FF FF F0 00 00 00 00 00 0F FF FF 03 FF FF FF FF E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [...] 00 07 FF FF 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
INFO: Response from the device: 80 20 42 30 4F 30 00 00 00 00 3E 0A 00 00 15 00 00 00 06 01 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
INFO: Interpretation of the response: 'Phase change' (phase: Printing state), 'Continuous length tape' 62x0 mm^2, errors: []
|
||||
INFO: CMD print FOUND. Instruction: 1A
|
||||
TIME 1.60
|
||||
INFO: Interpretation of the response: 'Printing completed' (phase: Printing state), 'Continuous length tape' 62x0 mm^2, errors: []
|
||||
TIME 1.60
|
||||
INFO: Interpretation of the response: 'Phase change' (phase: Waiting to receive), 'Continuous length tape' 62x0 mm^2, errors: []
|
||||
|
||||
Here, a command file was successfully printed. The last response should state the *Waiting to receive* phase.
|
||||
If you want to confirm the sending of every single command individually, you can add the `--interactive` argument to the command line call.
|
||||
|
||||
If you're seeing any error there, open a new issue on Github containing the debugging output to get your device supported.
|
||||
|
||||
@@ -51,125 +51,81 @@ This package was mainly created for use with Python 3. The essential functionali
|
||||
|
||||
## Usage
|
||||
|
||||
The main user interface of this package are its command line tools.
|
||||
You can also use its functionality from your own Python code (yet, there is no dedicated API documentation).
|
||||
The following sections show how to use the most important CLI tools.
|
||||
The main user interface of this package is the command line tool `brother_ql`.
|
||||
|
||||
### Create
|
||||
|
||||
The command line tool `brother_ql_create` is possibly the most important piece of software in this package.
|
||||
It allows you to create a new instruction file in the label printers' raster language:
|
||||
|
||||
brother_ql_create --model QL-500 ./720x300_monochrome.png > 720x300_monochrome.bin
|
||||
|
||||
If you want to find out about its options, just call the tool with `--help`:
|
||||
|
||||
brother_ql_create --help
|
||||
|
||||
giving:
|
||||
|
||||
usage: brother_ql_create [-h] [--model MODEL] [--label-size LABEL_SIZE]
|
||||
[--rotate {0,90,180,270}] [--threshold THRESHOLD]
|
||||
[--dither] [--compress] [--red] [--600dpi] [--no-cut]
|
||||
[--loglevel LOGLEVEL]
|
||||
image [outfile]
|
||||
Usage: brother_ql [OPTIONS] COMMAND [ARGS]...
|
||||
|
||||
positional arguments:
|
||||
image The image file to create a label from.
|
||||
outfile The file to write the instructions to. Defaults to
|
||||
stdout.
|
||||
Command line interface for the brother_ql Python package.
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--model MODEL, -m MODEL
|
||||
The printer model to use. Check available ones with
|
||||
`brother_ql_info list-models`.
|
||||
--label-size LABEL_SIZE, -s LABEL_SIZE
|
||||
The label size (and kind) to use. Check available ones
|
||||
with `brother_ql_info list-label-sizes`.
|
||||
--rotate {0,90,180,270}, -r {0,90,180,270}
|
||||
Rotate the image (counterclock-wise) by this amount of
|
||||
degrees.
|
||||
--threshold THRESHOLD, -t THRESHOLD
|
||||
The threshold value (in percent) to discriminate
|
||||
between black and white pixels.
|
||||
--dither, -d Enable dithering when converting the image to b/w. If
|
||||
set, --threshold is meaningless.
|
||||
--compress, -c Enable compression (if available with the model).
|
||||
Takes more time but results in smaller file size.
|
||||
--red Create a label to be printed on black/red/white tape
|
||||
(only with QL-8xx series on DK-22251 labels). You must
|
||||
use this option when printing on black/red tape, even
|
||||
when not printing red.
|
||||
--600dpi Print with 600x300 dpi available on some models.
|
||||
Provide your image as 600x600 dpi; perpendicular to
|
||||
the feeding the image will be resized to 300dpi.
|
||||
--no-cut Don't cut the tape after printing the label.
|
||||
--loglevel LOGLEVEL Set to DEBUG for verbose debugging output to stderr.
|
||||
Options:
|
||||
-b, --backend [pyusb|network|linux_kernel]
|
||||
-m, --model [QL-500|QL-550|QL-560|QL-570|QL-580N|QL-650TD|QL-700|QL-710W|QL-720NW|QL-800|QL-810W|QL-820NWB|QL-1050|QL-1060N]
|
||||
-p, --printer PRINTER_IDENTIFIER
|
||||
The identifier for the printer. This could
|
||||
be a string like tcp://192.168.1.21:9100 for
|
||||
a networked printer or
|
||||
usb://0x04f9:0x2015/000M6Z401370 for a
|
||||
printer connected via USB.
|
||||
--debug
|
||||
--help Show this message and exit.
|
||||
|
||||
Commands:
|
||||
analyze interpret a binary file containing raster...
|
||||
discover find connected label printers
|
||||
info list available choices (for labels or models)
|
||||
print Print a label
|
||||
send send an instruction file to the printer
|
||||
|
||||
The image argument should be a PNG/GIF/JPEG image file.
|
||||
Here is the output of `brother_ql_info list-label-sizes` listing the available options for `--label-size`:
|
||||
There are some global options available such as --model and --printer.
|
||||
They can also be provided by environment variables (`BROTHER_QL_MODEL` and `BROTHER_QL_PRINTER`).
|
||||
|
||||
Supported label sizes:
|
||||
Name Printable px Description
|
||||
12 106 (12 mm endless)
|
||||
29 306 (29 mm endless)
|
||||
38 413 (38 mm endless)
|
||||
50 554 (50 mm endless)
|
||||
54 590 (54 mm endless)
|
||||
62 696 (62 mm endless)
|
||||
102 1164 (102 mm endless)
|
||||
17x54 165 x 566 (17 x 54 mm^2)
|
||||
17x87 165 x 956 (17 x 87 mm^2)
|
||||
23x23 202 x 202 (23 x 23 mm^2)
|
||||
29x42 306 x 425 (29 x 42 mm^2)
|
||||
29x90 306 x 991 (29 x 90 mm^2)
|
||||
39x90 413 x 991 (38 x 90 mm^2)
|
||||
39x48 425 x 495 (39 x 48 mm^2)
|
||||
52x29 578 x 271 (52 x 29 mm^2)
|
||||
62x29 696 x 271 (62 x 29 mm^2)
|
||||
62x100 696 x 1109 (62 x 100 mm^2)
|
||||
102x51 1164 x 526 (102 x 51 mm^2)
|
||||
102x152 1164 x 1660 (102 x 152 mm^2)
|
||||
d12 94 x 94 (12 mm diameter, round)
|
||||
d24 236 x 236 (24 mm diameter, round)
|
||||
d58 618 x 618 (58 mm diameter, round)
|
||||
The global options are followed by a command such as `info` or `print`.
|
||||
The most important command is the `print` command and here is its CLI signature:
|
||||
|
||||
**Pro Tip™**:
|
||||
For the best results, use image files with the matching pixel dimensions. Die-cut labels have to be in the exact pixel dimensions stated above. For endless label rolls, you can provide image files with a pixel width as stated above. If you provide a file with different dimensions when creating an endless label file, it will be scaled to fit the width.
|
||||
Usage: brother_ql print [OPTIONS] IMAGE [IMAGE] ...
|
||||
|
||||
Print a label of the provided IMAGE.
|
||||
|
||||
Options:
|
||||
-l, --label [12|29|38|50|54|62|102|17x54|17x87|23x23|29x42|29x90|39x90|39x48|52x29|62x29|62x100|102x51|102x152|d12|d24|d58]
|
||||
The label (size, type - die-cut or endless).
|
||||
Run `brother_ql info list-labels` for a full
|
||||
list including ideal pixel dimensions.
|
||||
-r, --rotate [auto|0|90|180|270]
|
||||
Rotate the image (counterclock-wise) by this
|
||||
amount of degrees.
|
||||
-t, --threshold FLOAT The threshold value (in percent) to
|
||||
discriminate between black and white pixels.
|
||||
-d, --dither Enable dithering when converting the image
|
||||
to b/w. If set, --threshold is meaningless.
|
||||
-c, --compress Enable compression (if available with the
|
||||
model). Label creation can take slightly
|
||||
longer but the resulting instruction size is
|
||||
normally considerably smaller.
|
||||
--red Create a label to be printed on
|
||||
black/red/white tape (only with QL-8xx
|
||||
series on DK-22251 labels). You must use
|
||||
this option when printing on black/red tape,
|
||||
even when not printing red.
|
||||
--600dpi Print with 600x300 dpi available on some
|
||||
models. Provide your image as 600x600 dpi;
|
||||
perpendicular to the feeding the image will
|
||||
be resized to 300dpi.
|
||||
--lq Print with low quality (faster). Default is
|
||||
high quality.
|
||||
--no-cut / --cut Don't cut the tape after printing the label.
|
||||
--help Show this message and exit.
|
||||
|
||||
### Print
|
||||
So, printing an image file onto 62mm endless tape on a QL-710W label printer can be as easy as:
|
||||
|
||||
Once you have a Brother QL instruction file, you can send it to the printer like this:
|
||||
export BROTHER_QL_PRINTER=tcp://192.168.1.21
|
||||
export BROTHER_QL_MODEL=QL-710W
|
||||
brother_ql print -l 62 my_image.png
|
||||
|
||||
cat my_label.bin > /dev/usb/lp1
|
||||
### Legacy command line tools
|
||||
|
||||
Be sure to have permission to write to the device (usually adding yourself to the *lp* group is sufficient.
|
||||
|
||||
Or via network (if you have a LAN/WLAN enabled Brother QL):
|
||||
|
||||
nc 192.168.0.23 9100 < my_label.bin
|
||||
|
||||
You can also use the tool `brother_ql_print` (Py3 only) to send the instructions to your printer:
|
||||
|
||||
brother_ql_print 720x151_monochrome.bin /dev/usb/lp0
|
||||
# or
|
||||
brother_ql_print --backend network 720x151_monochrome.bin tcp://192.168.0.23:9100
|
||||
# or (requires PyUSB: `pip install pyusb`)
|
||||
brother_ql_print 720x151_monochrome.bin usb://0x04f9:0x2015
|
||||
# or if you have multiple ones connected:
|
||||
brother_ql_print 720x151_monochrome.bin usb://0x04f9:0x2015/000M6Z401370
|
||||
# where 000M6Z401370 is the serial number (see lsusb output).
|
||||
|
||||
If your printer has problems printing the instructions file, it may blink its LED (green or red) depending on the model. This can have many reasons, eg.:
|
||||
|
||||
* The selected label doesn't match (make sure `--red` has been passed to `brother_ql_create` if you're using black/red labels).
|
||||
* End of paper.
|
||||
* Unsupported opcode (wrong `--model` when using `brother_ql_create`?)
|
||||
|
||||
## Debugging
|
||||
|
||||
More info on how to debug difficult situations is to be found in the [DEBUG doc](https://github.com/pklaus/brother_ql/blob/master/DEBUG.md).
|
||||
For a long time, this project provided multiple command line tools, such as `brother_ql_create`, `brother_ql_print`, `brother_ql_analyze`, and more.
|
||||
A documentation of their usage can be found in the [LEGACY](https://github.com/pklaus/brother_ql/blob/master/LEGACY.md).
|
||||
|
||||
## Links
|
||||
|
||||
|
||||
@@ -8,16 +8,16 @@ available_backends = [
|
||||
'linux_kernel',
|
||||
]
|
||||
|
||||
def guess_backend(descr):
|
||||
""" guess the backend from a given string descriptor for the device """
|
||||
if descr.startswith('usb://') or descr.startswith('0x'):
|
||||
def guess_backend(identifier):
|
||||
""" guess the backend from a given identifier string for the device """
|
||||
if identifier.startswith('usb://') or identifier.startswith('0x'):
|
||||
return 'pyusb'
|
||||
elif descr.startswith('file://') or descr.startswith('/dev/usb/') or descr.startswith('lp'):
|
||||
elif identifier.startswith('file://') or identifier.startswith('/dev/usb/') or identifier.startswith('lp'):
|
||||
return 'linux_kernel'
|
||||
elif descr.startswith('tcp://'):
|
||||
elif identifier.startswith('tcp://'):
|
||||
return 'network'
|
||||
else:
|
||||
raise ValueError('Cannot guess backend for given string descriptor: %s' % descr)
|
||||
raise ValueError('Cannot guess backend for given identifier: %s' % identifier)
|
||||
|
||||
|
||||
def backend_factory(backend_name):
|
||||
|
||||
@@ -5,8 +5,8 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
def list_available_devices():
|
||||
""" List all available devices for the respective backend """
|
||||
# returns a list of dictionaries with the keys 'string_descr' and 'instance':
|
||||
# [ {'string_descr': '/dev/usb/lp0', 'instance': os.open('/dev/usb/lp0', os.O_RDWR)}, ]
|
||||
# returns a list of dictionaries with the keys 'identifier' and 'instance':
|
||||
# [ {'identifier': '/dev/usb/lp0', 'instance': os.open('/dev/usb/lp0', os.O_RDWR)}, ]
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
|
||||
Executable
+88
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Helpers for the subpackage brother_ql.backends
|
||||
|
||||
* device discovery
|
||||
* printing
|
||||
"""
|
||||
|
||||
import logging, time
|
||||
|
||||
from brother_ql.backends import backend_factory, guess_backend
|
||||
from brother_ql.reader import interpret_response
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def discover(backend_identifier='linux_kernel'):
|
||||
|
||||
be = backend_factory(backend_identifier)
|
||||
list_available_devices = be['list_available_devices']
|
||||
BrotherQLBackend = be['backend_class']
|
||||
|
||||
available_devices = list_available_devices()
|
||||
return available_devices
|
||||
|
||||
def send(instructions, printer_identifier=None, backend_identifier=None, blocking=True):
|
||||
"""
|
||||
Send instruction bytes to a printer.
|
||||
|
||||
:param bytes instructions: The instructions to be sent to the printer.
|
||||
:param str printer_identifier: Identifier for the printer.
|
||||
:param str backend_identifier: Can enforce the use of a specific backend.
|
||||
:param bool blocking: Indicates whether the function call should block while waiting for the completion of the printing.
|
||||
"""
|
||||
|
||||
selected_backend = None
|
||||
if backend_identifier:
|
||||
selected_backend = backend_identifier
|
||||
else:
|
||||
try:
|
||||
selected_backend = guess_backend(printer_identifier)
|
||||
except:
|
||||
logger.info("No backend stated. Selecting the default linux_kernel backend.")
|
||||
selected_backend = 'linux_kernel'
|
||||
|
||||
be = backend_factory(selected_backend)
|
||||
list_available_devices = be['list_available_devices']
|
||||
BrotherQLBackend = be['backend_class']
|
||||
|
||||
printer = BrotherQLBackend(printer_identifier)
|
||||
|
||||
start = time.time()
|
||||
logger.info('Sending instructions to the printer. Total: %d bytes.', len(instructions))
|
||||
printer.write(instructions)
|
||||
if not blocking:
|
||||
return
|
||||
if selected_backend == 'network':
|
||||
""" No need to wait for completion. The network backend doesn't support readback. """
|
||||
return
|
||||
printing_completed = False
|
||||
waiting_to_receive = False
|
||||
while time.time() - start < 10:
|
||||
data = printer.read()
|
||||
if not data:
|
||||
time.sleep(0.005)
|
||||
continue
|
||||
try:
|
||||
result = interpret_response(data)
|
||||
except ValueError:
|
||||
logger.error("TIME %.3f - Couln't understand response: %s", time.time()-start, data)
|
||||
continue
|
||||
logger.debug('TIME %.3f - result: %s', time.time()-start, result)
|
||||
if result['errors']:
|
||||
logger.error('Errors occured: %s', result['errors'])
|
||||
if result['status_type'] == 'Printing completed':
|
||||
printing_completed = True
|
||||
if result['status_type'] == 'Phase change' and result['phase_type'] == 'Waiting to receive':
|
||||
waiting_to_receive = True
|
||||
if printing_completed and waiting_to_receive:
|
||||
break
|
||||
if not printing_completed:
|
||||
logger.warning("'printing completed' status not received.")
|
||||
if not waiting_to_receive:
|
||||
logger.warning("'waiting to receive' status not received.")
|
||||
if (not printing_completed) or (not waiting_to_receive):
|
||||
logger.warning('Printing potentially not successful?')
|
||||
if printing_completed and waiting_to_receive:
|
||||
logger.info("Printing was successful. Waiting for the next job.")
|
||||
@@ -13,14 +13,14 @@ def list_available_devices():
|
||||
"""
|
||||
List all available devices for the linux kernel backend
|
||||
|
||||
returns: devices: a list of dictionaries with the keys 'string_descr' and 'instance': \
|
||||
[ {'string_descr': 'file:///dev/usb/lp0', 'instance': None}, ] \
|
||||
returns: devices: a list of dictionaries with the keys 'identifier' and 'instance': \
|
||||
[ {'identifier': 'file:///dev/usb/lp0', 'instance': None}, ] \
|
||||
Instance is set to None because we don't want to open (and thus potentially block) the device here.
|
||||
"""
|
||||
|
||||
paths = glob.glob('/dev/usb/lp*')
|
||||
|
||||
return [{'string_descr': 'file://' + path, 'instance': None} for path in paths]
|
||||
return [{'identifier': 'file://' + path, 'instance': None} for path in paths]
|
||||
|
||||
class BrotherQLBackendLinuxKernel(BrotherQLBackendGeneric):
|
||||
"""
|
||||
@@ -29,7 +29,7 @@ class BrotherQLBackendLinuxKernel(BrotherQLBackendGeneric):
|
||||
|
||||
def __init__(self, device_specifier):
|
||||
"""
|
||||
device_specifier: string or os.open(): string descriptor in the \
|
||||
device_specifier: string or os.open(): identifier in the \
|
||||
format file:///dev/usb/lp0 or os.open() raw device handle.
|
||||
"""
|
||||
|
||||
|
||||
@@ -13,14 +13,14 @@ def list_available_devices():
|
||||
"""
|
||||
List all available devices for the network backend
|
||||
|
||||
returns: devices: a list of dictionaries with the keys 'string_descr' and 'instance': \
|
||||
[ {'string_descr': 'tcp://hostname[:port]', 'instance': None}, ] \
|
||||
returns: devices: a list of dictionaries with the keys 'identifier' and 'instance': \
|
||||
[ {'identifier': 'tcp://hostname[:port]', 'instance': None}, ] \
|
||||
Instance is set to None because we don't want to connect to the device here yet.
|
||||
"""
|
||||
|
||||
# We need some snmp request sent to 255.255.255.255 here
|
||||
raise NotImplementedError()
|
||||
return [{'string_descr': 'tcp://' + path, 'instance': None} for path in paths]
|
||||
return [{'identifier': 'tcp://' + path, 'instance': None} for path in paths]
|
||||
|
||||
class BrotherQLBackendNetwork(BrotherQLBackendGeneric):
|
||||
"""
|
||||
@@ -29,7 +29,7 @@ class BrotherQLBackendNetwork(BrotherQLBackendGeneric):
|
||||
|
||||
def __init__(self, device_specifier):
|
||||
"""
|
||||
device_specifier: string or os.open(): string descriptor in the \
|
||||
device_specifier: string or os.open(): identifier in the \
|
||||
format file:///dev/usb/lp0 or os.open() raw device handle.
|
||||
"""
|
||||
|
||||
|
||||
@@ -19,9 +19,9 @@ def list_available_devices():
|
||||
"""
|
||||
List all available devices for the respective backend
|
||||
|
||||
returns: devices: a list of dictionaries with the keys 'string_descr' and 'instance': \
|
||||
[ {'string_descr': 'usb://0x04f9:0x2015/C5Z315686', 'instance': pyusb.core.Device()}, ]
|
||||
The 'string_descr' is of the format idVendor:idProduct_iSerialNumber.
|
||||
returns: devices: a list of dictionaries with the keys 'identifier' and 'instance': \
|
||||
[ {'identifier': 'usb://0x04f9:0x2015/C5Z315686', 'instance': pyusb.core.Device()}, ]
|
||||
The 'identifier' is of the format idVendor:idProduct_iSerialNumber.
|
||||
"""
|
||||
|
||||
class find_class(object):
|
||||
@@ -42,14 +42,14 @@ def list_available_devices():
|
||||
# only Brother printers
|
||||
printers = usb.core.find(find_all=1, custom_match=find_class(7), idVendor=0x04f9)
|
||||
|
||||
def string_descr(dev):
|
||||
def identifier(dev):
|
||||
try:
|
||||
serial = usb.util.get_string(dev, 256, dev.iSerialNumber)
|
||||
return 'usb://0x{:04x}:0x{:04x}_{}'.format(dev.idVendor, dev.idProduct, serial)
|
||||
except:
|
||||
return 'usb://0x{:04x}:0x{:04x}'.format(dev.idVendor, dev.idProduct)
|
||||
|
||||
return [{'string_descr': string_descr(printer), 'instance': printer} for printer in printers]
|
||||
return [{'identifier': identifier(printer), 'instance': printer} for printer in printers]
|
||||
|
||||
class BrotherQLBackendPyUSB(BrotherQLBackendGeneric):
|
||||
"""
|
||||
@@ -58,7 +58,7 @@ class BrotherQLBackendPyUSB(BrotherQLBackendGeneric):
|
||||
|
||||
def __init__(self, device_specifier):
|
||||
"""
|
||||
device_specifier: string or pyusb.core.Device: string descriptor of the \
|
||||
device_specifier: string or pyusb.core.Device: identifier of the \
|
||||
format usb://idVendor:idProduct/iSerialNumber or pyusb.core.Device instance.
|
||||
"""
|
||||
|
||||
|
||||
@@ -1,27 +1,16 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import division
|
||||
|
||||
import sys, argparse, logging
|
||||
|
||||
from PIL import Image
|
||||
import PIL.ImageOps, PIL.ImageChops
|
||||
|
||||
from brother_ql.raster import BrotherQLRaster
|
||||
from brother_ql.devicedependent import models, label_type_specs, ENDLESS_LABEL, DIE_CUT_LABEL, ROUND_DIE_CUT_LABEL, right_margin_addition
|
||||
from brother_ql import BrotherQLError, BrotherQLUnsupportedCmd, BrotherQLUnknownModel
|
||||
from brother_ql.image_trafos import filtered_hsv
|
||||
from brother_ql.conversion import convert
|
||||
from brother_ql.devicedependent import label_type_specs
|
||||
|
||||
try:
|
||||
stdout = sys.stdout.buffer
|
||||
except:
|
||||
stdout = sys.stdout
|
||||
|
||||
try:
|
||||
unicode
|
||||
except:
|
||||
unicode = str
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def main():
|
||||
@@ -42,7 +31,6 @@ def main():
|
||||
args = parser.parse_args()
|
||||
|
||||
logging.basicConfig(level=args.loglevel)
|
||||
logging.getLogger("PIL.PngImagePlugin").setLevel(logging.WARNING)
|
||||
|
||||
args.model = args.model.upper()
|
||||
|
||||
@@ -63,145 +51,7 @@ def main():
|
||||
args.outfile.write(qlr.data)
|
||||
|
||||
def create_label(qlr, image, label_size, threshold=70, cut=True, dither=False, compress=False, red=False, **kwargs):
|
||||
|
||||
label_specs = label_type_specs[label_size]
|
||||
dots_printable = label_specs['dots_printable']
|
||||
right_margin_dots = label_specs['right_margin_dots']
|
||||
right_margin_dots += right_margin_addition.get(qlr.model, 0)
|
||||
device_pixel_width = qlr.get_pixel_width()
|
||||
rotate = kwargs.get('rotate', 'auto')
|
||||
if rotate != 'auto': rotate = int(rotate)
|
||||
dpi_600 = kwargs.get('dpi_600', False)
|
||||
hq = kwargs.get('hq', True)
|
||||
|
||||
threshold = 100.0 - threshold
|
||||
threshold = min(255, max(0, int(threshold/100.0 * 255))) # from percent to pixel val
|
||||
|
||||
if red and not qlr.two_color_support:
|
||||
raise BrotherQLUnsupportedCmd('Printing in red is not supported with the selected model.')
|
||||
|
||||
if isinstance(image, Image.Image):
|
||||
im = image
|
||||
elif isinstance(image, (unicode, str)):
|
||||
im = Image.open(image)
|
||||
else:
|
||||
raise NotImplementedError("The image argument needs to be an Image() instance or the filename to an image.")
|
||||
|
||||
if im.mode.endswith('A'):
|
||||
# place in front of white background and get red of transparency
|
||||
bg = Image.new("RGB", im.size, (255,255,255))
|
||||
bg.paste(im, im.split()[-1])
|
||||
im = bg
|
||||
elif im.mode == "P":
|
||||
# Convert GIF ("P") to RGB
|
||||
im = im.convert("RGB" if red else "L")
|
||||
elif im.mode == "L" and red:
|
||||
# Convert greyscale to RGB if printing on black/red tape
|
||||
im = im.convert("RGB")
|
||||
|
||||
if dpi_600:
|
||||
dots_expected = [el*2 for el in dots_printable]
|
||||
else:
|
||||
dots_expected = dots_printable
|
||||
|
||||
if label_specs['kind'] == ENDLESS_LABEL:
|
||||
if rotate not in ('auto', 0):
|
||||
im = im.rotate(rotate, expand=True)
|
||||
if dpi_600:
|
||||
im = im.resize((im.size[0]//2, im.size[1]))
|
||||
if im.size[0] != dots_printable[0]:
|
||||
hsize = int((dots_printable[0] / im.size[0]) * im.size[1])
|
||||
im = im.resize((dots_printable[0], hsize), Image.ANTIALIAS)
|
||||
logger.warning('Need to resize the image...')
|
||||
if im.size[0] < device_pixel_width:
|
||||
new_im = Image.new(im.mode, (device_pixel_width, im.size[1]), (255,)*len(im.mode))
|
||||
new_im.paste(im, (device_pixel_width-im.size[0]-right_margin_dots, 0))
|
||||
im = new_im
|
||||
elif label_specs['kind'] in (DIE_CUT_LABEL, ROUND_DIE_CUT_LABEL):
|
||||
if rotate == 'auto':
|
||||
if im.size[0] == dots_expected[1] and im.size[1] == dots_expected[0]:
|
||||
im = im.rotate(90, expand=True)
|
||||
elif rotate != 0:
|
||||
im = im.rotate(rotate, expand=True)
|
||||
if im.size[0] != dots_expected[0] or im.size[1] != dots_expected[1]:
|
||||
raise ValueError("Bad image dimensions: %s. Expecting: %s." % (im.size, dots_expected))
|
||||
if dpi_600:
|
||||
im = im.resize((im.size[0]//2, im.size[1]))
|
||||
new_im = Image.new(im.mode, (device_pixel_width, dots_expected[1]), (255,)*len(im.mode))
|
||||
new_im.paste(im, (device_pixel_width-im.size[0]-right_margin_dots, 0))
|
||||
im = new_im
|
||||
|
||||
if red:
|
||||
filter_h = lambda h: 255 if (h < 40 or h > 210) else 0
|
||||
filter_s = lambda s: 255 if s > 100 else 0
|
||||
filter_v = lambda v: 255 if v > 80 else 0
|
||||
red_im = filtered_hsv(im, filter_h, filter_s, filter_v)
|
||||
red_im = red_im.convert("L")
|
||||
red_im = PIL.ImageOps.invert(red_im)
|
||||
red_im = red_im.point(lambda x: 0 if x < threshold else 255, mode="1")
|
||||
|
||||
filter_h = lambda h: 255
|
||||
filter_s = lambda s: 255
|
||||
filter_v = lambda v: 255 if v < 80 else 0
|
||||
black_im = filtered_hsv(im, filter_h, filter_s, filter_v)
|
||||
black_im = black_im.convert("L")
|
||||
black_im = PIL.ImageOps.invert(black_im)
|
||||
black_im = black_im.point(lambda x: 0 if x < threshold else 255, mode="1")
|
||||
black_im = PIL.ImageChops.subtract(black_im, red_im)
|
||||
else:
|
||||
im = im.convert("L")
|
||||
im = PIL.ImageOps.invert(im)
|
||||
|
||||
if dither:
|
||||
im = im.convert("1", dither=Image.FLOYDSTEINBERG)
|
||||
else:
|
||||
im = im.point(lambda x: 0 if x < threshold else 255, mode="1")
|
||||
|
||||
try:
|
||||
qlr.add_switch_mode()
|
||||
except BrotherQLUnsupportedCmd:
|
||||
pass
|
||||
qlr.add_invalidate()
|
||||
qlr.add_initialize()
|
||||
try:
|
||||
qlr.add_switch_mode()
|
||||
except BrotherQLUnsupportedCmd:
|
||||
pass
|
||||
qlr.add_status_information()
|
||||
tape_size = label_specs['tape_size']
|
||||
if label_specs['kind'] in (DIE_CUT_LABEL, ROUND_DIE_CUT_LABEL):
|
||||
qlr.mtype = 0x0B
|
||||
qlr.mwidth = tape_size[0]
|
||||
qlr.mlength = tape_size[1]
|
||||
else:
|
||||
qlr.mtype = 0x0A
|
||||
qlr.mwidth = tape_size[0]
|
||||
qlr.mlength = 0
|
||||
qlr.pquality = int(hq)
|
||||
qlr.add_media_and_quality(im.size[1])
|
||||
try:
|
||||
if cut:
|
||||
qlr.add_autocut(True)
|
||||
qlr.add_cut_every(1)
|
||||
except BrotherQLUnsupportedCmd:
|
||||
pass
|
||||
try:
|
||||
qlr.dpi_600 = dpi_600
|
||||
qlr.cut_at_end = cut
|
||||
qlr.two_color_printing = True if red else False
|
||||
qlr.add_expanded_mode()
|
||||
except BrotherQLUnsupportedCmd:
|
||||
pass
|
||||
qlr.add_margins(label_specs['feed_margin'])
|
||||
try:
|
||||
if compress: qlr.add_compression(True)
|
||||
except BrotherQLUnsupportedCmd:
|
||||
pass
|
||||
if red:
|
||||
qlr.add_raster_data(black_im, red_im)
|
||||
else:
|
||||
qlr.add_raster_data(im)
|
||||
qlr.add_print()
|
||||
convert(qlr, [image], label_size, threshold=threshold, cut=cut, dither=dither, compress=compress, red=red, **kwargs)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -4,22 +4,23 @@
|
||||
Testing the packaged version of the Linux Kernel backend
|
||||
"""
|
||||
|
||||
import argparse, logging, sys, time
|
||||
from pprint import pprint
|
||||
import argparse, logging, sys
|
||||
|
||||
from brother_ql.backends import backend_factory, guess_backend, available_backends
|
||||
from brother_ql.reader import interpret_response
|
||||
from brother_ql.backends.helpers import discover, send
|
||||
from brother_ql.output_helpers import log_discovered_devices, textual_description_discovered_devices
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def main():
|
||||
|
||||
# Command line parsing...
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--backend', choices=available_backends, help='Forces the use of a specific backend')
|
||||
parser.add_argument('--list-printers', action='store_true', help='List the devices available with the selected --backend')
|
||||
parser.add_argument('--debug', action='store_true', help='Enable debugging output')
|
||||
parser.add_argument('instruction_file', nargs='?', help='file containing the instructions to be sent to the printer')
|
||||
parser.add_argument('device', metavar='DEVICE_STRING_DESCRIPTOR', nargs='?', help='String descriptor for specific device. If not specified, select first detected device')
|
||||
parser.add_argument('printer', metavar='PRINTER_IDENTIFIER', nargs='?', help='Identifier string specifying the printer. If not specified, selects the first detected device.')
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.list_printers and not args.backend:
|
||||
@@ -28,6 +29,7 @@ def main():
|
||||
if not args.list_printers and not args.instruction_file:
|
||||
parser.error("the following arguments are required: instruction_file")
|
||||
|
||||
# Reading the instruction input file into content variable
|
||||
if args.instruction_file == '-':
|
||||
try:
|
||||
content = sys.stdin.buffer.read()
|
||||
@@ -37,76 +39,47 @@ def main():
|
||||
with open(args.instruction_file, 'rb') as f:
|
||||
content = f.read()
|
||||
|
||||
# Setting up the requested level of logging.
|
||||
level = logging.DEBUG if args.debug else logging.INFO
|
||||
logging.basicConfig(level=level)
|
||||
|
||||
# State any shortcomings of this software early on.
|
||||
if args.backend == 'network':
|
||||
logger.warning("The network backend doesn't supply any 'readback' functionality. No status reports will be received.")
|
||||
|
||||
# Select the backend based: Either explicitly stated or derived from identifier. Otherwise: Default.
|
||||
selected_backend = None
|
||||
if args.backend:
|
||||
selected_backend = args.backend
|
||||
else:
|
||||
try:
|
||||
selected_backend = guess_backend(args.device)
|
||||
selected_backend = guess_backend(args.printer)
|
||||
except:
|
||||
logger.info("No backend stated. Selecting the default linux_kernel backend.")
|
||||
selected_backend = 'linux_kernel'
|
||||
|
||||
be = backend_factory(selected_backend)
|
||||
list_available_devices = be['list_available_devices']
|
||||
BrotherQLBackend = be['backend_class']
|
||||
|
||||
if args.list_printers or not args.device:
|
||||
available_devices = list_available_devices()
|
||||
for ad in available_devices:
|
||||
result = {'model': 'unknown'}
|
||||
result.update(ad)
|
||||
logger.info(" Found a label printer: {string_descr} (model: {model})".format(**result))
|
||||
# List any printers found, if explicitly asked to do so or if no identifier has been provided.
|
||||
if args.list_printers or not args.printer:
|
||||
available_devices = discover(backend_identifier=selected_backend)
|
||||
log_discovered_devices(available_devices)
|
||||
|
||||
if args.list_printers:
|
||||
for printer in available_devices:
|
||||
print(printer['string_descr'])
|
||||
print(textual_description_discovered_devices(available_devices))
|
||||
sys.exit(0)
|
||||
|
||||
string_descr = None
|
||||
if not args.device:
|
||||
# Determine the identifier. Either selecting the explicitly stated one or using the first found device.
|
||||
identifier = None
|
||||
if not args.printer:
|
||||
"We need to search for available devices and select the first."
|
||||
if not available_devices:
|
||||
sys.exit("No printer found")
|
||||
string_descr = available_devices[0]['string_descr']
|
||||
print("Selecting first device %s" % string_descr)
|
||||
identifier = available_devices[0]['identifier']
|
||||
print("Selecting first device %s" % identifier)
|
||||
else:
|
||||
"A string descriptor for the device was given, let's use it."
|
||||
string_descr = args.device
|
||||
"A string identifier for the device was given, let's use it."
|
||||
identifier = args.printer
|
||||
|
||||
printer = BrotherQLBackend(string_descr)
|
||||
|
||||
start = time.time()
|
||||
logger.info('Sending instructions to the printer. Total: %d bytes.', len(content))
|
||||
printer.write(content)
|
||||
if selected_backend == 'network':
|
||||
""" No need to wait for completion. The network backend doesn't support readback. """
|
||||
return
|
||||
printing_completed = False
|
||||
waiting_to_receive = False
|
||||
while time.time() - start < 10:
|
||||
data = printer.read()
|
||||
if not data:
|
||||
time.sleep(0.005)
|
||||
continue
|
||||
try:
|
||||
result = interpret_response(data)
|
||||
except ValueError:
|
||||
logger.error("TIME %.3f - Couln't understand response: %s", time.time()-start, data)
|
||||
continue
|
||||
logger.debug('TIME %.3f - result: %s', time.time()-start, result)
|
||||
if result['errors']:
|
||||
logger.error('Errors occured: %s', result['errors'])
|
||||
if result['status_type'] == 'Printing completed': printing_completed = True
|
||||
if result['status_type'] == 'Phase change' and result['phase_type'] == 'Waiting to receive': waiting_to_receive = True
|
||||
if printing_completed and waiting_to_receive:
|
||||
break
|
||||
if not (printing_completed and waiting_to_receive):
|
||||
logger.warning('Printing potentially not successful?')
|
||||
# Finally, do the actual printing.
|
||||
send(instructions=content, printer_identifier=identifier, backend_identifier=selected_backend, blocking=True)
|
||||
|
||||
if __name__ == "__main__": main()
|
||||
|
||||
Executable
+120
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Python standard library
|
||||
import logging
|
||||
|
||||
# external dependencies
|
||||
import click
|
||||
|
||||
# imports from this very package
|
||||
from brother_ql.devicedependent import models, label_sizes, label_type_specs, DIE_CUT_LABEL, ENDLESS_LABEL, ROUND_DIE_CUT_LABEL
|
||||
from brother_ql.backends import available_backends, backend_factory
|
||||
|
||||
|
||||
logger = logging.getLogger('brother_ql')
|
||||
|
||||
|
||||
printer_help = "The identifier for the printer. This could be a string like tcp://192.168.1.21:9100 for a networked printer or usb://0x04f9:0x2015/000M6Z401370 for a printer connected via USB."
|
||||
@click.group()
|
||||
@click.option('-b', '--backend', type=click.Choice(available_backends), envvar='BROTHER_QL_BACKEND')
|
||||
@click.option('-m', '--model', type=click.Choice(models), envvar='BROTHER_QL_MODEL')
|
||||
@click.option('-p', '--printer', metavar='PRINTER_IDENTIFIER', envvar='BROTHER_QL_PRINTER', help=printer_help)
|
||||
@click.option('--debug', is_flag=True)
|
||||
@click.pass_context
|
||||
def cli(ctx, *args, **kwargs):
|
||||
""" Command line interface for the brother_ql Python package. """
|
||||
|
||||
backend = kwargs.get('backend', None)
|
||||
model = kwargs.get('model', None)
|
||||
printer = kwargs.get('printer', None)
|
||||
debug = kwargs.get('debug')
|
||||
|
||||
# Store the general CLI options in the context meta dictionary.
|
||||
# The name corresponds to the second half of the respective envvar:
|
||||
ctx.meta['MODEL'] = model
|
||||
ctx.meta['BACKEND'] = backend
|
||||
ctx.meta['PRINTER'] = printer
|
||||
|
||||
logging.basicConfig(level='DEBUG' if debug else 'INFO')
|
||||
|
||||
@cli.command()
|
||||
@click.pass_context
|
||||
def discover(ctx):
|
||||
""" find connected label printers """
|
||||
backend = ctx.meta.get('BACKEND', 'pyusb')
|
||||
discover_and_list_available_devices(backend)
|
||||
|
||||
def discover_and_list_available_devices(backend):
|
||||
from brother_ql.backends.helpers import discover
|
||||
available_devices = discover(backend_identifier=backend)
|
||||
from brother_ql.output_helpers import log_discovered_devices, textual_description_discovered_devices
|
||||
log_discovered_devices(available_devices)
|
||||
print(textual_description_discovered_devices(available_devices))
|
||||
|
||||
@cli.command()
|
||||
@click.argument('info', click.Choice(('labels', 'models')))
|
||||
@click.pass_context
|
||||
def info(ctx, *args, **kwargs):
|
||||
""" list available choices (for labels or models) """
|
||||
if kwargs['info'] == 'models':
|
||||
"""List the models (choices for --model)
|
||||
|
||||
List the models that can be used with this software.
|
||||
Those are the choices avaiable for the --model option.
|
||||
"""
|
||||
print('Supported models:')
|
||||
for model in models: print(" " + model)
|
||||
|
||||
elif kwargs['info'] == 'labels':
|
||||
"""
|
||||
List labels (types and sizes).
|
||||
|
||||
This command lists all labels (label types and label sizes)
|
||||
that can be used with this software. """
|
||||
from brother_ql.output_helpers import textual_label_description
|
||||
print(textual_label_description(label_sizes))
|
||||
|
||||
@cli.command('print', short_help='Print a label')
|
||||
@click.argument('images', nargs=-1, metavar='IMAGE [IMAGE] ...')
|
||||
@click.option('-l', '--label', type=click.Choice(label_sizes), envvar='BROTHER_QL_LABEL', help='The label (size, type - die-cut or endless). Run `brother_ql info list-labels` for a full list including ideal pixel dimensions.')
|
||||
@click.option('-r', '--rotate', type=click.Choice(('auto', '0', '90', '180', '270')), default='auto', help='Rotate the image (counterclock-wise) by this amount of degrees.')
|
||||
@click.option('-t', '--threshold', type=float, default=70.0, help='The threshold value (in percent) to discriminate between black and white pixels.')
|
||||
@click.option('-d', '--dither', is_flag=True, help='Enable dithering when converting the image to b/w. If set, --threshold is meaningless.')
|
||||
@click.option('-c', '--compress', is_flag=True, help='Enable compression (if available with the model). Label creation can take slightly longer but the resulting instruction size is normally considerably smaller.')
|
||||
@click.option('--red', is_flag=True, help='Create a label to be printed on black/red/white tape (only with QL-8xx series on DK-22251 labels). You must use this option when printing on black/red tape, even when not printing red.')
|
||||
@click.option('--600dpi', 'dpi_600', is_flag=True, help='Print with 600x300 dpi available on some models. Provide your image as 600x600 dpi; perpendicular to the feeding the image will be resized to 300dpi.')
|
||||
@click.option('--lq', is_flag=True, help='Print with low quality (faster). Default is high quality.')
|
||||
@click.option('--no-cut/--cut', is_flag=True, help="Don't cut the tape after printing the label.")
|
||||
@click.pass_context
|
||||
def print_cmd(ctx, *args, **kwargs):
|
||||
""" Print a label of the provided IMAGE. """
|
||||
backend = ctx.meta.get('BACKEND', 'pyusb')
|
||||
model = ctx.meta.get('MODEL')
|
||||
printer = ctx.meta.get('PRINTER')
|
||||
from brother_ql.conversion import convert
|
||||
from brother_ql.backends.helpers import send
|
||||
from brother_ql.raster import BrotherQLRaster
|
||||
qlr = BrotherQLRaster(model)
|
||||
qlr.exception_on_warning = True
|
||||
instructions = convert(qlr=qlr, **kwargs)
|
||||
send(instructions=instructions, printer_identifier=printer, backend_identifier=backend, blocking=True)
|
||||
|
||||
@cli.command(name='analyze', help='interpret a binary file containing raster instructions for the Brother QL-Series printers')
|
||||
@click.argument('instructions', type=click.File('rb'))
|
||||
@click.option('-f', '--filename-format', help="Filename format string. Default is: label{counter:04d}.png.")
|
||||
@click.pass_context
|
||||
def analyze_cmd(ctx, *args, **kwargs):
|
||||
from brother_ql.reader import BrotherQLReader
|
||||
br = BrotherQLReader(kwargs.get('instructions'))
|
||||
if kwargs.get('filename_format'): br.filename_fmt = kwargs.get('filename_format')
|
||||
br.analyse()
|
||||
|
||||
@cli.command(name='send', short_help='send an instruction file to the printer')
|
||||
@click.argument('instructions', type=click.File('rb'))
|
||||
@click.pass_context
|
||||
def send_cmd(ctx, *args, **kwargs):
|
||||
from brother_ql.backends.helpers import send
|
||||
send(instructions=kwargs['instructions'].read(), printer_identifier=ctx.meta.get('PRINTER'), backend_identifier=ctx.meta.get('BACKEND'), blocking=True)
|
||||
|
||||
if __name__ == '__main__':
|
||||
cli()
|
||||
Executable
+197
@@ -0,0 +1,197 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import division
|
||||
|
||||
import logging
|
||||
|
||||
from PIL import Image
|
||||
import PIL.ImageOps, PIL.ImageChops
|
||||
|
||||
from brother_ql.raster import BrotherQLRaster
|
||||
from brother_ql.devicedependent import label_type_specs, ENDLESS_LABEL, DIE_CUT_LABEL, ROUND_DIE_CUT_LABEL, right_margin_addition
|
||||
from brother_ql import BrotherQLUnsupportedCmd
|
||||
from brother_ql.image_trafos import filtered_hsv
|
||||
|
||||
try:
|
||||
unicode
|
||||
except:
|
||||
unicode = str
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
logging.getLogger("PIL.PngImagePlugin").setLevel(logging.WARNING)
|
||||
|
||||
def convert(qlr, images, label, **kwargs):
|
||||
r"""Converts one or more images to a raster instruction file.
|
||||
|
||||
:param qlr:
|
||||
An instance of the BrotherQLRaster class
|
||||
:type qlr: :py:class:`brother_ql.raster.BrotherQLRaster`
|
||||
:param images:
|
||||
The images to be converted. They can be filenames or instances of Pillow's Image.
|
||||
:type images: list(PIL.Image.Image) or list(str) images
|
||||
:param str label:
|
||||
Type of label the printout should be on.
|
||||
:param \**kwargs:
|
||||
See below
|
||||
|
||||
:Keyword Arguments:
|
||||
* **cut** (``bool``) --
|
||||
Enable cutting after printing the labels.
|
||||
* **dither** (``bool``) --
|
||||
Instead of applying a threshold to the pixel values, approximate grey tones with dithering.
|
||||
* **compress**
|
||||
* **red**
|
||||
* **rotate**
|
||||
* **dpi_600**
|
||||
* **hq**
|
||||
* **threshold**
|
||||
"""
|
||||
label_specs = label_type_specs[label]
|
||||
|
||||
dots_printable = label_specs['dots_printable']
|
||||
right_margin_dots = label_specs['right_margin_dots']
|
||||
right_margin_dots += right_margin_addition.get(qlr.model, 0)
|
||||
device_pixel_width = qlr.get_pixel_width()
|
||||
|
||||
cut = kwargs.get('cut', True)
|
||||
dither = kwargs.get('dither', False)
|
||||
compress = kwargs.get('compress', False)
|
||||
red = kwargs.get('red', False)
|
||||
rotate = kwargs.get('rotate', 'auto')
|
||||
if rotate != 'auto': rotate = int(rotate)
|
||||
dpi_600 = kwargs.get('dpi_600', False)
|
||||
hq = kwargs.get('hq', True)
|
||||
threshold = kwargs.get('threshold', 70)
|
||||
threshold = 100.0 - threshold
|
||||
threshold = min(255, max(0, int(threshold/100.0 * 255)))
|
||||
|
||||
if red and not qlr.two_color_support:
|
||||
raise BrotherQLUnsupportedCmd('Printing in red is not supported with the selected model.')
|
||||
|
||||
try:
|
||||
qlr.add_switch_mode()
|
||||
except BrotherQLUnsupportedCmd:
|
||||
pass
|
||||
qlr.add_invalidate()
|
||||
qlr.add_initialize()
|
||||
try:
|
||||
qlr.add_switch_mode()
|
||||
except BrotherQLUnsupportedCmd:
|
||||
pass
|
||||
|
||||
for image in images:
|
||||
if isinstance(images, Image.Image):
|
||||
im = image
|
||||
elif isinstance(image, (unicode, str)):
|
||||
im = Image.open(image)
|
||||
else:
|
||||
raise NotImplementedError("The image argument needs to be an Image() instance or the filename to an image.")
|
||||
|
||||
if im.mode.endswith('A'):
|
||||
# place in front of white background and get red of transparency
|
||||
bg = Image.new("RGB", im.size, (255,255,255))
|
||||
bg.paste(im, im.split()[-1])
|
||||
im = bg
|
||||
elif im.mode == "P":
|
||||
# Convert GIF ("P") to RGB
|
||||
im = im.convert("RGB" if red else "L")
|
||||
elif im.mode == "L" and red:
|
||||
# Convert greyscale to RGB if printing on black/red tape
|
||||
im = im.convert("RGB")
|
||||
|
||||
if dpi_600:
|
||||
dots_expected = [el*2 for el in dots_printable]
|
||||
else:
|
||||
dots_expected = dots_printable
|
||||
|
||||
if label_specs['kind'] == ENDLESS_LABEL:
|
||||
if rotate not in ('auto', 0):
|
||||
im = im.rotate(rotate, expand=True)
|
||||
if dpi_600:
|
||||
im = im.resize((im.size[0]//2, im.size[1]))
|
||||
if im.size[0] != dots_printable[0]:
|
||||
hsize = int((dots_printable[0] / im.size[0]) * im.size[1])
|
||||
im = im.resize((dots_printable[0], hsize), Image.ANTIALIAS)
|
||||
logger.warning('Need to resize the image...')
|
||||
if im.size[0] < device_pixel_width:
|
||||
new_im = Image.new(im.mode, (device_pixel_width, im.size[1]), (255,)*len(im.mode))
|
||||
new_im.paste(im, (device_pixel_width-im.size[0]-right_margin_dots, 0))
|
||||
im = new_im
|
||||
elif label_specs['kind'] in (DIE_CUT_LABEL, ROUND_DIE_CUT_LABEL):
|
||||
if rotate == 'auto':
|
||||
if im.size[0] == dots_expected[1] and im.size[1] == dots_expected[0]:
|
||||
im = im.rotate(90, expand=True)
|
||||
elif rotate != 0:
|
||||
im = im.rotate(rotate, expand=True)
|
||||
if im.size[0] != dots_expected[0] or im.size[1] != dots_expected[1]:
|
||||
raise ValueError("Bad image dimensions: %s. Expecting: %s." % (im.size, dots_expected))
|
||||
if dpi_600:
|
||||
im = im.resize((im.size[0]//2, im.size[1]))
|
||||
new_im = Image.new(im.mode, (device_pixel_width, dots_expected[1]), (255,)*len(im.mode))
|
||||
new_im.paste(im, (device_pixel_width-im.size[0]-right_margin_dots, 0))
|
||||
im = new_im
|
||||
|
||||
if red:
|
||||
filter_h = lambda h: 255 if (h < 40 or h > 210) else 0
|
||||
filter_s = lambda s: 255 if s > 100 else 0
|
||||
filter_v = lambda v: 255 if v > 80 else 0
|
||||
red_im = filtered_hsv(im, filter_h, filter_s, filter_v)
|
||||
red_im = red_im.convert("L")
|
||||
red_im = PIL.ImageOps.invert(red_im)
|
||||
red_im = red_im.point(lambda x: 0 if x < threshold else 255, mode="1")
|
||||
|
||||
filter_h = lambda h: 255
|
||||
filter_s = lambda s: 255
|
||||
filter_v = lambda v: 255 if v < 80 else 0
|
||||
black_im = filtered_hsv(im, filter_h, filter_s, filter_v)
|
||||
black_im = black_im.convert("L")
|
||||
black_im = PIL.ImageOps.invert(black_im)
|
||||
black_im = black_im.point(lambda x: 0 if x < threshold else 255, mode="1")
|
||||
black_im = PIL.ImageChops.subtract(black_im, red_im)
|
||||
else:
|
||||
im = im.convert("L")
|
||||
im = PIL.ImageOps.invert(im)
|
||||
|
||||
if dither:
|
||||
im = im.convert("1", dither=Image.FLOYDSTEINBERG)
|
||||
else:
|
||||
im = im.point(lambda x: 0 if x < threshold else 255, mode="1")
|
||||
|
||||
qlr.add_status_information()
|
||||
tape_size = label_specs['tape_size']
|
||||
if label_specs['kind'] in (DIE_CUT_LABEL, ROUND_DIE_CUT_LABEL):
|
||||
qlr.mtype = 0x0B
|
||||
qlr.mwidth = tape_size[0]
|
||||
qlr.mlength = tape_size[1]
|
||||
else:
|
||||
qlr.mtype = 0x0A
|
||||
qlr.mwidth = tape_size[0]
|
||||
qlr.mlength = 0
|
||||
qlr.pquality = int(hq)
|
||||
qlr.add_media_and_quality(im.size[1])
|
||||
try:
|
||||
if cut:
|
||||
qlr.add_autocut(True)
|
||||
qlr.add_cut_every(1)
|
||||
except BrotherQLUnsupportedCmd:
|
||||
pass
|
||||
try:
|
||||
qlr.dpi_600 = dpi_600
|
||||
qlr.cut_at_end = cut
|
||||
qlr.two_color_printing = True if red else False
|
||||
qlr.add_expanded_mode()
|
||||
except BrotherQLUnsupportedCmd:
|
||||
pass
|
||||
qlr.add_margins(label_specs['feed_margin'])
|
||||
try:
|
||||
if compress: qlr.add_compression(True)
|
||||
except BrotherQLUnsupportedCmd:
|
||||
pass
|
||||
if red:
|
||||
qlr.add_raster_data(black_im, red_im)
|
||||
else:
|
||||
qlr.add_raster_data(im)
|
||||
qlr.add_print()
|
||||
|
||||
return qlr.data
|
||||
@@ -0,0 +1,37 @@
|
||||
import logging
|
||||
|
||||
from brother_ql.devicedependent import label_type_specs
|
||||
from brother_ql.devicedependent import DIE_CUT_LABEL, ENDLESS_LABEL, ROUND_DIE_CUT_LABEL
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def textual_label_description(labels_to_include):
|
||||
output = "Supported label sizes:\n"
|
||||
output = ""
|
||||
fmt = " {label_size:9s} {dots_printable:14s} {label_descr:26s}\n"
|
||||
output += fmt.format(label_size="Name", dots_printable="Printable px", label_descr="Description")
|
||||
#output += fmt.format(label_size="", dots_printable="width x height", label_descr="")
|
||||
for label_size in labels_to_include:
|
||||
s = label_type_specs[label_size]
|
||||
if s['kind'] in (DIE_CUT_LABEL, ROUND_DIE_CUT_LABEL):
|
||||
dp_fmt = "{0:4d} x {1:4d}"
|
||||
elif s['kind'] == ENDLESS_LABEL:
|
||||
dp_fmt = "{0:4d}"
|
||||
else:
|
||||
dp_fmt = " - unknown - "
|
||||
dots_printable = dp_fmt.format(*s['dots_printable'])
|
||||
label_descr = s['name']
|
||||
output += fmt.format(label_size=label_size, dots_printable=dots_printable, label_descr=label_descr)
|
||||
return output
|
||||
|
||||
def log_discovered_devices(available_devices, level=logging.INFO):
|
||||
for ad in available_devices:
|
||||
result = {'model': 'unknown'}
|
||||
result.update(ad)
|
||||
logger.log(level, " Found a label printer: {identifier} (model: {model})".format(**result))
|
||||
|
||||
def textual_description_discovered_devices(available_devices):
|
||||
output = ""
|
||||
for ad in available_devices:
|
||||
output += ad['identifier']
|
||||
return output
|
||||
@@ -227,6 +227,7 @@ def merge_specific_instructions(chunks, join_preamble=True, join_raster=True):
|
||||
return new_instructions
|
||||
|
||||
class BrotherQLReader(object):
|
||||
DEFAULT_FILENAME_FMT = 'label{counter:04d}.png'
|
||||
|
||||
def __init__(self, brother_file):
|
||||
if type(brother_file) in (str,):
|
||||
@@ -237,10 +238,11 @@ class BrotherQLReader(object):
|
||||
self.black_rows = []
|
||||
self.red_rows = []
|
||||
self.compression = False
|
||||
self.page = 1
|
||||
self.page_counter = 1
|
||||
self.two_color_printing = False
|
||||
self.cut_at_end = False
|
||||
self.high_resolution_printing = False
|
||||
self.filename_fmt = self.DEFAULT_FILENAME_FMT
|
||||
|
||||
def analyse(self):
|
||||
instructions = self.brother_file.read()
|
||||
@@ -297,8 +299,8 @@ class BrotherQLReader(object):
|
||||
fmt = " media width: {} mm, media length: {} mm, raster no: {} rows"
|
||||
logger.info(fmt.format(self.mwidth, self.mlength, self.raster_no))
|
||||
if opcode_def[0] == 'print':
|
||||
logger.info("Len of black rows: ", len(self.black_rows))
|
||||
logger.info("Len of red rows: ", len(self.red_rows))
|
||||
logger.info("Len of black rows: %d", len(self.black_rows))
|
||||
logger.info("Len of red rows: %d", len(self.red_rows))
|
||||
def get_im(rows):
|
||||
if not len(rows): return None
|
||||
size = (len(rows[0])*8, len(rows))
|
||||
@@ -324,7 +326,7 @@ class BrotherQLReader(object):
|
||||
im_red.paste(im_black, (0, 0), im_black)
|
||||
im = im_red
|
||||
im = im.transpose(Image.FLIP_LEFT_RIGHT)
|
||||
img_name = 'page{:04d}.png'.format(self.page)
|
||||
img_name = self.filename_fmt.format(counter=self.page_counter)
|
||||
im.save(img_name)
|
||||
print('Page saved as {}'.format(img_name))
|
||||
self.page += 1
|
||||
self.page_counter += 1
|
||||
|
||||
@@ -26,6 +26,7 @@ setup(name='brother_ql',
|
||||
'brother_ql.backends'],
|
||||
entry_points = {
|
||||
'console_scripts': [
|
||||
'brother_ql = brother_ql.cli:cli',
|
||||
'brother_ql_analyse = brother_ql.brother_ql_analyse:main',
|
||||
'brother_ql_create = brother_ql.brother_ql_create:main',
|
||||
'brother_ql_print = brother_ql.brother_ql_print:main',
|
||||
@@ -37,6 +38,7 @@ setup(name='brother_ql',
|
||||
zip_safe = True,
|
||||
platforms = 'any',
|
||||
install_requires = [
|
||||
"click",
|
||||
"future",
|
||||
"packbits",
|
||||
"pillow>=3.3.0",
|
||||
|
||||
Reference in New Issue
Block a user