Merge pull request #37 from pklaus/dev

New unified CLI: `brother_ql`
This commit is contained in:
Philipp Klaus
2018-08-08 19:01:01 +02:00
committed by GitHub
16 changed files with 737 additions and 396 deletions
-55
View File
@@ -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.
+171
View File
@@ -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.
+65 -109
View File
@@ -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
+6 -6
View File
@@ -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):
+2 -2
View File
@@ -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()
+88
View File
@@ -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.")
+4 -4
View File
@@ -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.
"""
+4 -4
View File
@@ -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.
"""
+6 -6
View File
@@ -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.
"""
+3 -153
View File
@@ -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()
+25 -52
View File
@@ -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()
+120
View File
@@ -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()
+197
View File
@@ -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
+37
View File
@@ -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
+7 -5
View File
@@ -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
+2
View File
@@ -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",