From 6850f3672e1a006b0eb85e6aa50ee93f319e55f0 Mon Sep 17 00:00:00 2001 From: gkonoplya Date: Sat, 21 Feb 2026 13:20:08 +0300 Subject: [PATCH] Implement CLI commands for batch extraction and PDF generation - Added command-line interface using Click for `batch_extractor.py` to handle extraction from ZIP and PDF files. - Enhanced `save_to_excel` function to create parent directories for output files. - Updated `build_pdf.py` to include a CLI for generating PDF labels from Excel data. - Improved README.md with detailed usage instructions for the new CLI commands. - Added `click` to requirements.txt for command-line functionality. --- README.md | 84 +++++++++++++++++++++++++++++----------------- batch_extractor.py | 64 +++++++++++++++++++++++++++++++---- build_pdf.py | 70 +++++++++++++++++++++++++++++--------- render_eps.py | 4 ++- requirements.txt | 3 +- 5 files changed, 170 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index 3dd939d..232b1e5 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,16 @@ # LabelExtractor (Честный ЗНАК / CRPT) -Утилита для автоматизированного извлечения кодов маркировки (DataMatrix) и массовой генерации готовых к печати PDF-этикеток (формат 58x40 мм) на основе данных из Excel-шаблона. Решение оптимизировано для работы с системой «Честный ЗНАК» и создания этикеток для термопринтеров. +Комплексное решение для работы с кодами маркировки «Честный ЗНАК». Состоит из двух мощных утилит: +1. **Batch Extractor:** Автоматически вытаскивает коды из выгрузок ЧЗ (PDF или ZIP-архивы) и собирает их в удобную Excel-таблицу. +2. **PDF Builder:** Генерирует промышленные макеты этикеток (58x40 мм) для термопринтеров, объединяя извлеченные коды и описания товаров. -## Ключевые возможности +## 🌟 Главная фича: Утилита массового извлечения кодов (`batch_extractor.py`) -* **Распознавание DataMatrix:** Точное чтение кодов из изображений с помощью `zxing-cpp` с сохранением непечатаемых спецсимволов GS1 (включая FNC1 / ASCII 29) посредством кодирования в Base64. -* **Интеграция с Excel:** Парсинг атрибутов товара (GTIN, EAN, Описание, Артикул, Цвет, Размер, Организация) из Excel-шаблонов с использованием `pandas`. Чтение строго по позициям колонок обеспечивает защиту от изменения заголовков. -* **PDF Генерация:** Автоматическая сборка промышленных макетов этикеток (включая логотипы EAC, Честный ЗНАК, штрихкоды Code128 и DataMatrix) с помощью `reportlab`. -* **Умное форматирование текста:** Автоматический перенос строк (Word Wrap), адаптивное уменьшение размера шрифта и умное усечение текста многоточием (при выходе за границы AABB). -* **Портативность:** Использование встроенной версии Ghostscript (`gswin32c`) для рендеринга EPS и штрихкодов без необходимости сложной системной настройки. +Вам больше не нужно вручную копировать коды или пытаться достать их из картинок. В проекте есть специальная утилита, которая сама "прочитает" исходные файлы от Честного ЗНАКа и создаст готовый Excel-файл (со списком текстовых значений кодов и их Base64-представлением, сохраняющим скрытые спецсимволы GS1/FNC1). + +Утилита поддерживает два формата исходников: +* **Многостраничные PDF-файлы** (где на каждой странице расположена этикетка). +* **ZIP-архивы**, внутри которых лежат векторные `EPS` файлы. ## Требования и установка @@ -19,38 +21,58 @@ pip install -r requirements.txt ``` **Зависимости:** -* `pillow` — работа с изображениями. -* `zxing-cpp` — быстрое и надежное чтение штрихкодов. -* `reportlab` — генерация векторных PDF-файлов. -* `pandas` & `openpyxl` — чтение и обработка Excel-таблиц. -* `treepoem` — генерация штрихкодов (Code128, DataMatrix). +* `pillow`, `zxing-cpp` — чтение штрихкодов из изображений. +* `reportlab`, `treepoem` — генерация векторных PDF-файлов и штрихкодов. +* `pandas`, `openpyxl` — чтение и создание Excel-таблиц. +* `click` — создание удобного интерфейса командной строки. -*Примечание: Убедитесь, что архив `Ghostscript.zip` распакован в папку `Ghostscript` в корне проекта для корректной работы `treepoem` и обработки EPS-файлов.* +*Примечание: Убедитесь, что архив `Ghostscript.zip` распакован в папку `Ghostscript` в корне проекта.* -## Структура проекта +## 📋 Порядок работы (Как получить готовые этикетки) -* `build_pdf.py` — Главный модуль и оркестратор бизнес-логики. Считывает базу данных Excel, группирует входные коды по GTIN, рендерит штрихкоды и генерирует готовые PDF-документы. -* `read_image.py` — Утилита для извлечения байтов DataMatrix из картинок и перевода их в безопасный Base64 формат. -* `render_eps.py` — Скрипт-конвертер EPS изображений в PNG с использованием портативного Ghostscript. -* `Resources/` — Папка с графическими ассетами (логотипы) и исходным файлом `ШАблон для загрузки этикеток.xlsx`. -* `data/` — Рабочая директория (содержит входные коды, картинки и генерируемые PDF в папке `output_pdfs`). +Процесс разделен на три простых шага в терминале: -## Использование +### ШАГ 1: Извлечение кодов в Excel (Создание списка КМ) -1. Подготовьте описания ваших товаров в файле `Resources/ШАблон для загрузки этикеток.xlsx`. -2. Подготовьте список КМ (кодов маркировки), предварительно закодировав сырые байты DataMatrix в формат Base64. (Для получения Base64 из изображений можно использовать скрипт `read_image.py`). -3. Запустите процесс генерации PDF: +Возьмите файл, который вы скачали из системы «Честный ЗНАК», и натравите на него утилиту-экстрактор. +**Если у вас ZIP-архив с этикетками:** ```bash -python build_pdf.py +python batch_extractor.py from-zip "C:\путь\к\архиву\ЧЗ.zip" "извлеченные_коды.xlsx" ``` -Готовые многостраничные PDF-файлы (сгруппированные по GTIN) будут сохранены в директории `data/output_pdfs/`. +**Если у вас многостраничный PDF-файл:** +```bash +python batch_extractor.py from-pdf "C:\путь\к\файлу\ЧЗ.pdf" "извлеченные_коды.xlsx" +``` -## Архитектура решения +*Результат:* Рядом появится файл `извлеченные_коды.xlsx`. В нем две колонки: текстовый код и его Base64 (именно он нужен для точной печати). -Код разбит на несколько логических слоев: -1. **Domain Layer:** Описание структур данных (`LabelData`). -2. **Data & Asset Layer:** Функции генерации и трансформации изображений и штрихкодов (`render_code128`, `create_datamatrix_in_memory`). Изображения генерируются без сглаживания (`NEAREST`) для идеальной печати на термопринтерах. -3. **Presentation Layer:** Низкоуровневая отрисовка PDF-холста через `reportlab`, позиционирование блоков и алгоритмы подгонки текста (`draw_label_page`, `place_text`). -4. **Business Logic Layer:** Оркестрация батчевой обработки данных (`process_batch`). Чтение тяжелых файлов происходит один раз для экономии ресурсов I/O диска. +### ШАГ 2: Подготовка описания товаров + +Откройте шаблон `Resources/ШАблон для загрузки этикеток.xlsx`. Внесите данные о товарах строго по порядку колонок (слева направо). Программа читает именно номера столбцов, а не их заголовки: +1. **GTIN** (14 цифр) +2. **EAN** (13 цифр) +3. **Описание** (название товара, до 200 символов, длинный текст автоматически перенесется или обрежется) +4. **Артикул** +5. **Цвет** +6. **Размер** +7. **Организация** + +### ШАГ 3: Генерация этикеток для печати (`build_pdf.py`) + +Теперь скрестим наши коды из Шага 1 и описания из Шага 2. Запустите генератор, указав три пути: файл с кодами, файл-шаблон с описаниями и папку для сохранения готовых этикеток. + +```bash +python build_pdf.py "извлеченные_коды.xlsx" "Resources/ШАблон для загрузки этикеток.xlsx" "готовые_этикетки/" +``` + +*Результат:* В папке `готовые_этикетки/` программа создаст аккуратные PDF-файлы (по одному на каждый GTIN). Формат 58х40 мм, все штрихкоды и шрифты оптимизированы для термопринтеров без размытия. Можно смело отправлять на печать! + +## Внутренняя структура проекта + +* `batch_extractor.py` — CLI утилита для извлечения кодов в Excel. +* `build_pdf.py` — Главный оркестратор. Группирует коды и генерирует макеты этикеток. +* `read_image.py` — Низкоуровневый движок чтения DataMatrix через `zxing-cpp`. +* `render_eps.py` — Вспомогательный модуль для работы с EPS. +* `Resources/` — Папка с графикой (логотипы EAC, ЧЗ) и Excel-шаблоном. \ No newline at end of file diff --git a/batch_extractor.py b/batch_extractor.py index cb7c6d5..c876b8e 100644 --- a/batch_extractor.py +++ b/batch_extractor.py @@ -3,6 +3,7 @@ import zipfile import tempfile import pandas as pd from read_image import read_datamatrix_zxing, extract_barcodes_from_pdf +from pathlib import Path import click import render_eps @@ -59,6 +60,14 @@ def save_to_excel(data: list, output_path: str): if not data: print("Нет данных для сохранения в Excel.") return + + # Преобразуем путь в объект Path для удобной работы с файловой системой + path_obj = Path(output_path) + + # Создаем родительскую директорию (вместе со всеми промежуточными), если ее нет + # Метод parent возвращает путь к папке, в которой должен лежать файл + # exist_ok=True гарантирует отсутствие ошибки, если папка уже существует + path_obj.parent.mkdir(parents=True, exist_ok=True) # Формируем DataFrame из списка кортежей df = pd.DataFrame(data, columns=["Текст", "Base64"]) @@ -68,11 +77,52 @@ def save_to_excel(data: list, output_path: str): print(f"Данные успешно сохранены в {output_path}") -if __name__ == "__main__": - # Тестовый пример оркестрации: - base_dir = os.path.dirname(os.path.abspath(__file__)) - zip_path = "data/0109 черный xxl 720 шт. d46349f7-148a-4301-b6b5-f9a3c70fdf19_begin_offset_2000_number_of_codes_720.zip" - #zip_data = extract_eps_from_zip(zip_path) +@click.group(help="Утилита для массового извлечения DataMatrix кодов и сохранения их в Excel.") +def cli(): + """Основная группа команд для CLI.""" + pass - pdf_path = 'data/0109, цвет синий, р.L 10шт_b3ba8577-7874-4dfa-a091-e6953fbe0ca7_gtin_04639970975214_quantity_10.pdf' - pdf_data = extract_dm_from_pdf(pdf_path) +@cli.command(help="Извлекает коды из EPS-файлов внутри ZIP-архива.") +@click.argument('input_zip', type=click.Path(exists=True, dir_okay=False, path_type=Path)) +@click.argument('output_xlsx', type=click.Path(dir_okay=False, writable=True, path_type=Path)) +def from_zip(input_zip: Path, output_xlsx: Path): + """ + Пакетная обработка ZIP-архива. + + INPUT_ZIP: Путь к исходному .zip файлу с EPS-этикетками. + OUTPUT_XLSX: Путь для сохранения результата (например, result.xlsx). + """ + click.echo(f"Начинаю обработку архива: {input_zip.name}") + try: + data = extract_eps_from_zip(str(input_zip)) + if data: + save_to_excel(data, str(output_xlsx)) + click.secho(f"Успех! Найдено кодов: {len(data)}. Файл сохранен: {output_xlsx.name}", fg="green") + else: + click.secho("Внимание: в архиве не найдено читаемых кодов.", fg="yellow") + except Exception as e: + click.secho(f"Ошибка при обработке ZIP: {e}", fg="red") + +@cli.command(help="Извлекает коды со всех страниц PDF-документа.") +@click.argument('input_pdf', type=click.Path(exists=True, dir_okay=False, path_type=Path)) +@click.argument('output_xlsx', type=click.Path(dir_okay=False, writable=True, path_type=Path)) +def from_pdf(input_pdf: Path, output_xlsx: Path): + """ + Пакетная обработка PDF-документа. + + INPUT_PDF: Путь к исходному многостраничному .pdf файлу. + OUTPUT_XLSX: Путь для сохранения результата (например, result.xlsx). + """ + click.echo(f"Начинаю обработку PDF-документа: {input_pdf.name}") + try: + data = extract_dm_from_pdf(str(input_pdf)) + if data: + save_to_excel(data, str(output_xlsx)) + click.secho(f"Успех! Найдено кодов: {len(data)}. Файл сохранен: {output_xlsx.name}", fg="green") + else: + click.secho("Внимание: в PDF-файле не найдено читаемых кодов.", fg="yellow") + except Exception as e: + click.secho(f"Ошибка при обработке PDF: {e}", fg="red") + +if __name__ == "__main__": + cli() diff --git a/build_pdf.py b/build_pdf.py index bf1caec..18b6e98 100644 --- a/build_pdf.py +++ b/build_pdf.py @@ -18,6 +18,9 @@ from read_image import read_datamatrix_zxing import render_eps import treepoem +import click +from pathlib import Path + # --------------------------------------------------------- # 1. СТРУКТУРЫ ДАННЫХ (DOMAIN LAYER) # --------------------------------------------------------- @@ -400,19 +403,56 @@ def process_batch(base64_codes: list[str], excel_path: str, output_dir: str): print(f" -> Сохранен файл: {pdf_path}") -if __name__ == "__main__": - # Тестовый пример оркестрации: - base_dir = os.path.dirname(os.path.abspath(__file__)) - image_path = "data/output.png" - data = read_datamatrix_zxing(image_path) - # Замените своими боевыми Base64 строками - mock_base64_list = [ - read_datamatrix_zxing(image_path)[1], - read_datamatrix_zxing(image_path)[1] - ] +@click.command(help="Генерирует PDF-этикетки на основе извлеченных Base64-кодов и Excel-шаблона.") +@click.argument('codes_xlsx', type=click.Path(exists=True, dir_okay=False, path_type=Path)) +@click.argument('template_xlsx', type=click.Path(exists=True, dir_okay=False, path_type=Path)) +@click.argument('output_dir', type=click.Path(file_okay=False, writable=True, path_type=Path)) +def cli(codes_xlsx: Path, template_xlsx: Path, output_dir: Path): + """ + Создает многостраничные PDF-файлы для термопринтера. - process_batch( - base64_codes=mock_base64_list, - excel_path=os.path.join(base_dir, "resources", "ШАблон для загрузки этикеток.xlsx"), - output_dir=os.path.join(base_dir, "data", "output_pdfs") - ) \ No newline at end of file + CODES_XLSX: Путь к XLSX-файлу со списком кодов (структура: Текст / Base64). + TEMPLATE_XLSX: Путь к XLSX-файлу с описанием товаров (GTIN, Артикул, Цвет и т.д.). + OUTPUT_DIR: Папка, куда будут сохранены готовые PDF-файлы. + """ + click.echo(f"Подготовка к генерации PDF...") + click.echo(f"Файл с кодами: {codes_xlsx.name}") + click.echo(f"Файл шаблона: {template_xlsx.name}") + + try: + # 1. Читаем файл с извлеченными кодами + codes_df = pd.read_excel(codes_xlsx, engine='openpyxl') + + # Проверяем наличие нужной колонки + if "Base64" not in codes_df.columns: + click.secho("Ошибка: В файле кодов отсутствует колонка 'Base64'.", fg="red") + return + + # Извлекаем все коды в список строк + base64_list = codes_df["Base64"].dropna().astype(str).tolist() + + if not base64_list: + click.secho("Предупреждение: Список Base64 кодов пуст.", fg="yellow") + return + + click.echo(f"Успешно загружено {len(base64_list)} кодов из {codes_xlsx.name}.") + + # 2. Создаем выходную директорию (если её нет) + # exist_ok=True предотвращает ошибку, если папка уже существует + output_dir.mkdir(parents=True, exist_ok=True) + + # 3. Запускаем основной оркестратор + # process_batch ожидает пути в виде строк (str), поэтому оборачиваем Path в str() + process_batch( + base64_codes=base64_list, + excel_path=str(template_xlsx), + output_dir=str(output_dir) + ) + + click.secho(f"Генерация успешно завершена! Файлы сохранены в: {output_dir.absolute()}", fg="green") + + except Exception as e: + click.secho(f"Критическая ошибка при генерации PDF: {e}", fg="red") + +if __name__ == "__main__": + cli() \ No newline at end of file diff --git a/render_eps.py b/render_eps.py index 4305310..e6a97af 100644 --- a/render_eps.py +++ b/render_eps.py @@ -12,6 +12,7 @@ os.environ['PATH'] = gs_path + os.pathsep + os.environ.get('PATH', '') # Мы устанавливаем имя бинарника ДО того, как Pillow начнет его искать PIL.EpsImagePlugin.gs_binary = "gswin32c" +''' from PIL import Image file_path = './data/d46349f7-148a-4301-b6b5-f9a3c70fdf19_04639970975115_2000.eps' @@ -22,4 +23,5 @@ try: img.save('./data/output.png', 'PNG') print("Успех! Файл сохранен в ./data/output.png") except Exception as e: - print(f"Опять ошибка: {e}") \ No newline at end of file + print(f"Опять ошибка: {e}") +''' \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index bffa9ca..0e3292a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,5 @@ reportlab pandas treepoem openpyxl -pymupdf \ No newline at end of file +pymupdf +click \ No newline at end of file