AI Автоматизируем печать документов с помощью Python

AI

Редактор
Регистрация
23 Август 2023
Сообщения
3 045
Лучшие ответы
0
Реакции
0
Баллы
51
Offline
#1
Печать Word и PDF, используя двухсторонний принтер и автоматическая систематизация страниц в PDF, c помощью Python

Обзор


1) Начало
2) Задача
3) Решение
4) Вывод

Начало


Меня зовут, Матвеев Дмитрий, и я работаю в Синергии.

Каждый день, я готовлю однообразные документы, в которых нужно печатать страницы - одинаково (однообразно):
1 (ую) и 2 (ую) страницы, двойной печатью по длинному краю;
3 (ью) и 4 (ую) по короткому краю (эти листы горизонтальные);
5 (ую) страницу отдельно (только 1 лист).

Каждый день, из раза в раз, нужно было настраивать диапазон для печатати. И в один момент (спустя 3 дня) мне это надоело и я решил написать программу, с помощью которой можно будет распечатать этот документ - одним нажатием мыши.

Спойлер - мне удалось. Но пришлось поискать информацию, а информации на русском не очень много, поэтому искал преимущественно в английских источниках.

Задача


В начале у меня были Word документы и их нужно распечатать максимально экономно и этично, чтобы не просто печатать весь документ в разнобой, а используя свои подходы на печать.

Решение

Из Word в PDF



Для начала преобразуем Word документы в отдельные PDF файлы, для этого устанавливаем следящую библиотеку:

pip install docx2pdf

И пишем код для конвертации. Для этого собираем все ".docx" файлы в папке, преобразуем их в PDF и нам требуется условие на проверку файла.

Если ".docx" есть в названии файла, то конвертируем, иначе пропускаем файлы. Нужно для того, чтобы сами pdf файлы не конвертировались в pdf (вылезет ошибка).

from docx2pdf import convert
import os

def word_convert(arr):
out = []
for file in arr:
if '.docx' in file:
out.append(file)

return out

indir = '\\input' #ваша директория, где находятся .docx файлы
all_files = next(os.walk(indir))[2] # все файлы в вашей директории в массиве
input_files = word_convert(all_files) # входные .dock файлы

#проходимся циклом по директории и конвертируем
for file in input_files:
path_file = indir + file # путь до файла
convert(path_file)
Систематизация страниц


Далее устанавливаем библиотеку для редактирования страниц в pdf:

pip install pikepdf

Пишем функцию для чтения и систематизации документа на под файлы т.к используя эту библиотеку, мы будет удалять из нашего файла страницы, то пишем функцию для более удобного удаления:

def del_pages(pdf, arr, num_pages=16):
max = arr[1]
min = arr[0] - 1
del pdf.pages[max:num_pages]
del pdf.pages[0:min]

return pdf # -1 для того, чтобы вы писали с 1 страницы, а не с 0.

#В начале это не мешает, но если страниц 20, то в конце можно запутаться.
Сохранение новых файлов


# Теперь пишем функцию для чтения и систематизации:

outdir = '\\output'

def doc_crushing(doc_path, arr, outpdf):
with pikepdf.open(doc_path) as pdf:
# Смотрим сколько страниц в pdf
num_pages = len(pdf.pages)
# Удаляем страницы из pdf
del_pages(pdf, arr, num_pages)
pdf.save(outdir + outpdf)

#Пример
doc = 'Договор.pdf' # Название вашего файла (тут для примера без цикла)
doc_path = indir + doc # Путь до файла

#doc_crushing(входной файл, масив с нужными срезом страниц, новое название)
doc_crushing(doc_path, [1,2], 'Титульный лист.pdf')
doc_crushing(doc_path, [3,4], 'Экзаменационный лист с фото.pdf')
doc_crushing(doc_path, [5,6], 'Заявление на поступление.pdf')

Т.е из документа "doc_path", мы оставляем промежуток страниц с 1 по 2 и сохраняем с именем 'Титульный лист.pdf' и так для каждого файла


Печать документов


Для работы с принтерами установим следующую библиотеку

pip install pywin32

Далее пишем функцию для печати т.к мы ее будем вызывать для каждого нужного файла

import win32print
import win32api
#print_pdf (входной pdf, режим печати, какой принтер)
#режим печати: 1 - односторонняя, 2 двойная по длинному краю, 3 - по короткому
def print_pdf(input_pdf, mode=2, printer=1):
# тут мои принтеры, для своихузнаем имя дефолтного принтера через метод win32print.GetDefaultPrinter()
if int(printer) == 2:
name = "\\\\buh\\BUH DCP-L5500DN series (копия 1)" #win32print.GetDefaultPrinter()
elif int(printer) == 1:
name = "Brother DCP-L2540DN series Printer" #win32print.GetDefaultPrinter()
try:
# Устанавливаем дефолтный принтер
win32print.SetDefaultPrinterW(name)
win32print.SetDefaultPrinter(name)
finally:
# Если не получилось или получилось -> устанавливаем этот принтер стандартом
name = win32print.GetDefaultPrinter()

# оставляем без изменений
## тут нужные права на использование принтеров
printdefaults = {"DesiredAccess": win32print.PRINTER_ALL_ACCESS}
## начинаем работу с принтером ("открываем" его)
handle = win32print.OpenPrinter(name, printdefaults)
## Если изменить level на другое число, то не сработает
level = 2
## Получаем значения принтера
attributes = win32print.GetPrinter(handle, level)
## Настройка двухсторонней печати
attributes['pDevMode'].Duplex = mode #flip over 3 - это короткий 2 - это длинный край

## Передаем нужные значения в принтер
win32print.SetPrinter(handle, level, attributes, 0)
win32print.GetPrinter(handle, level)['pDevMode'].Duplex
## Предупреждаем принтер о старте печати
win32print.StartDocPrinter(handle, 1, [input_pdf, None, "raw"])
## 2 в начале для открытия pdf и его сворачивания, для открытия без сворачивания поменяйте на 1
win32api.ShellExecute(2,'print', input_pdf,'.','/manualstoprint',0)
## "Закрываем" принтер
win32print.ClosePrinter(handle)

## Меняем стандартный принтер на часто используемый
win32print.SetDefaultPrinterW("Brother DCP-L2540DN series Printer")
win32print.SetDefaultPrinter("Brother DCP-L2540DN series Printer")

# Пример
inputs_print = next(os.walk(outdir))[2] # Берем все файлы в папке outdir
# Печатаем документы
for input_print in inputs_print:
# Путь до файла, который нужно расспечатать
path_print = outdir + input_print
# Если в названии файла есть 'Экзаменационный', то печатаем по короткому краю
if 'Экзаменационный' in input_print:
print_pdf(path_print, 3, num)
else:
print_pdf(path_print, 2, num)
Полный код c переделкой на работу с php сервером:

from docx2pdf import convert
import win32print
import win32api

import pikepdf
import shutil

import time
import sys
import os

#Мой код запускается сразу с определными значениями
if sys.argv[7]:
lastname = sys.argv[1]
name = sys.argv[2]
fname = sys.argv[3]
name1 = sys.argv[4]
today = sys.argv[5]
num = sys.argv[6]
level_stydy = sys.argv[7]
else:
lastname = sys.argv[1]
name = sys.argv[2]
fname = sys.argv[3]
today = sys.argv[4]
num = sys.argv[5]
level_stydy = sys.argv[6]

#Если значения передавались, то изменяем директорию
if lastname:
dir = os.path.abspath(os.curdir) + "\\py\\" + f"{lastname} {name} {fname}"
else:
dir = os.path.abspath(os.curdir)
indir = dir + "\\input\\"
outdir = dir + "\\output\\"
#Получаем все файлы в дикетории -> выдаем docx файлы
def tackword(arr):
out = []
for file in arr:
if '.docx' in file:
out.append(file)

return out

#Функция для копирования сгенерированых файлов в мою директорию
def copy_in_input(indir, name1):
dir = os.path.abspath(os.curdir) + '\\iles\\'
input_files = tackword(next(os.walk('.\\files'))[2])
for file in input_files:
if name1 in file and today in file:
src = dir + file
ind = indir + file
shutil.copyfile(src, ind)

def del_pages(pdf, arr, num_pages=16):
max = arr[1]
min = arr[0] - 1
del pdf.pages[max:num_pages]
del pdf.pages[0:min]

return pdf

def doc_crushing(doc_path, arr, outpdf):
with pikepdf.open(doc_path) as pdf:
num_pages = len(pdf.pages)
#Удаляем страницы из pdf
del_pages(pdf, arr, num_pages)

pdf.save(outdir+outpdf)

def print_pdf(input_pdf, mode=2, printer=1):
if int(printer) == 2:
name = "\\buh\BUH DCP-L5500DN series (копия 1)" #win32print.GetDefaultPrinter()
elif int(printer) == 1:
name = "Brother DCP-L2540DN series Printer" #win32print.GetDefaultPrinter()
win32print.SetDefaultPrinterW(name)
win32print.SetDefaultPrinter(name)

name = win32print.GetDefaultPrinter()
print(name)
print("<bk>")
drivers = win32print.EnumPrinterDrivers(None, None, 2)
for drive in drivers:
print(drive['Name'])

printdefaults = {"DesiredAccess": win32print.PRINTER_ALL_ACCESS}
handle = win32print.OpenPrinter(name, printdefaults)
level = 2
attributes = win32print.GetPrinter(handle, level)

attributes['pDevMode'].Duplex = mode #flip over 3 - это короткий 2 - это длинный край

win32print.SetPrinter(handle, level, attributes, 0)
win32print.GetPrinter(handle, level)['pDevMode'].Duplex

r = win32print.StartDocPrinter(handle, 1, [input_pdf, None, "raw"])
print(r)
win32api.ShellExecute(2,'print', input_pdf,'.','/manualstoprint',0)

# win32print.ClosePrinter(handle)

# win32print.SetDefaultPrinterW("Brother DCP-L2540DN series Printer")
# win32print.SetDefaultPrinter("Brother DCP-L2540DN series Printer")

def main():
# проверяем есть ли директория, если нету, то делаем
if not os.path.exists(dir):
os.mkdir(dir)
os.mkdir(dir + '\\input')
os.mkdir(dir + '\\output')
# копируем из файлы имеющие имя в файле
copy_in_input(indir, name1)
# берем docx скопированные файлы
input_files = tackword(next(os.walk(indir))[2])
for file in input_files:
convert(indir+file)

input_pds = next(os.walk(indir))[2]

doc = ''
rec = ''
outs = []

# Если я хочу изменять pdf файл, то записываю его в переменную
for file in input_pds:
if '.pdf' in file:
if 'Договор' in file:
doc = file
elif 'Реквизиты' in file:
rec = file
else:
outs.append(file)

# Открываем договор pdf, если он есть
if doc != '':
doc_path = indir + doc

# 1 - БАК; 2 - МАГ; 3 - СПО
if level_stydy == 1:
doc_crushing(doc_path, [1,2], 'Титульный лист.pdf')
doc_crushing(doc_path, [3,4], 'Экзаменационный лист с фото.pdf')
doc_crushing(doc_path, [5,6], 'Заявление на поступление.pdf')
doc_crushing(doc_path, [7,7], 'Согласие на зачисление.pdf')
doc_crushing(doc_path, [8,9], 'Согласие на обработку персональных данных.pdf')
doc_crushing(doc_path, [10,16], 'Договор.pdf')

elif level_stydy == 2:
doc_crushing(doc_path, [1,2], 'Титульный лист.pdf')
doc_crushing(doc_path, [3,4], 'Экзаменационный лист с фото.pdf')
doc_crushing(doc_path, [5,6], 'Заявление на поступление.pdf')
doc_crushing(doc_path, [7,7], 'Согласие на зачисление.pdf')
doc_crushing(doc_path, [8,9], 'Согласие на обработку персональных данных.pdf')
doc_crushing(doc_path, [10,14], 'Договор.pdf')

elif level_stydy == 3:
doc_crushing(doc_path, [1,2], 'Титульный лист.pdf')
doc_crushing(doc_path, [3,4], 'Заявление на поступление.pdf')
doc_crushing(doc_path, [5,5], 'Согласие на зачисление.pdf')
doc_crushing(doc_path, [6,7], 'Согласие на обработку персональных данных.pdf')
doc_crushing(doc_path, [8,12], 'Договор.pdf')

if rec != '':
rec_path = indir + rec

with pikepdf.open(rec_path) as pdf:
num_pages = len(pdf.pages)
if num_pages != 1:
del_pages(pdf, [1,1], num_pages)

pdf.save(outdir+'Реквизиты на оплату.pdf')

if outs:
for out in outs:
with pikepdf.open(indir+out) as pdf:
pdf.save(outdir+out)

files_to_print = []
inputs_print = next(os.walk(outdir))[2]

#Печатаем документы
for input_print in inputs_print:

path_print = outdir + input_print

if 'Экзаменационный' in input_print:
print_pdf(path_print, 3, num)
else:
print_pdf(path_print, 2, num)

# #Удаляем файлы
# time.sleep(100)

# for file in os.scandir(indir):

# if file.name.endswith(".pdf") or file.name.endswith(".docx"):
# os.unlink(file.path)

# for file in os.scandir(outdir):

# if file.name.endswith(".pdf") or file.name.endswith(".docx"):
# os.unlink(file.path)

if name == 'main':
main()
Вывод


Сразу про минусы:
принтеры не всегда меняются (т.е печатает, только, 1 принтер), разбираюсь почему.

Плюсы т.к мне не нужно постоянно вбивать диапазон страниц, освобождается куча времени, на саморазвитие)

Прощу, раскритиковать мой говнокод и поделится своим мнением о автоматизации печати (как вы это делали) или была ли моя статья полезно для вас

Это моя первая статья, поэтому давайте пожестче

P.S Документы генерирую на php форме с php сервера, на котором запускаю Python скрипт, с помощью php формы
 
Сверху Снизу