Рубрики
Без рубрики

Основные сценарии Python для автоматизации повседневных задач: Добавить текст на изображения с помощью imageMagick и палочкой

В предыдущем посте мы увидели, как использовать Bash, чтобы построить простой сценарий, который копирует некоторые фотографии Organi … Помечено Python, Linux, Ubuntu.

В предыдущий пост Мы увидели, как использовать Bash, чтобы построить простой скрипт, который копирует несколько изображений, организованных в подпапки в одну папку, одновременно переименовывая их и добавляя некоторые текст на них, чтобы вы могли знать, где он был предпринят на основе папки. в. Я рекомендую вам посмотреть на это сначала, потому что я объясняю решаемую конкретную проблему, структуру каталогов и предложил простое решение проблемы. Если вам нельзя беспокоить, чтобы прочитать это, вот резюме.

В предыдущем опухе я предполагал, что проблема, которую кто-то мог бы иметь, что может быть действительно легко решить, используя компьютеры для того, что они лучше всего: выполнение повторяющихся, предсказуемых задач быстро и без необходимости пользователей делать что-либо, пока программное обеспечение делает то, что он предназначен для делать.

Проблема была организация фотографий из коллекции туриста, который, возвращающийся из поездки в Италию, стремится показать друзьям и родственникам фотографии, сделанные из многих великих достопримечательностей Италии. Для этого все они должны быть в одной папке, чтобы их можно было легко проявить в последовательности, но они также должны быть распознаны как для их имени файла, так и для некоторого текста, показанного на них, чтобы турист не обязательно должен помнить где была сделана каждая картинка.

У нас есть дерево каталогов, которое выглядит так:

italy_pics/
├── Center
│   ├── Assisi
│   ├── Florence
│   ├── Marche
│   ├── Pisa
│   ├── Rome_Lazio
│   └── Siena
├── North
│   ├── EmiliaRomagna
│   ├── Genoa_CinqueTerre
│   ├── Milan_Lombardy
│   ├── Trentino
│   ├── Turin
│   └── Venice
└── South
    ├── Bari_Apulia
    ├── Basilicata
    ├── Calabria
    ├── Campobasso
    ├── Naples_Campania
    └── Sicily

С каждой из подпапок ( rome_lazio , Флоренция , Сицилия , и т.д.) содержащий один или несколько изображений.

Вывод будет в другом каталоге, называемом Италия_Pics_organaged , быть созданным внутри Italy_pics родительский каталог, если оно не существует там уже.

Как я уже сказал, этот пост изложил процесс написания решения этой проблемы с использованием Bash и ImageMagick’s Конвертировать CLI Tool, но этот пост здесь, чтобы выйти за пределы этого и написать что-то намного лучше с Python.

Раствор Bash имел несколько вопросов: не было портативным, учитывая, что он работает на определенной оболочке и интерпретатере, который не очень приятно использовать, если вы не используете ОС, которая поддерживает ее в родом (например, поддержку Windows ISN ‘ T Very) И что он работает только для очень конкретной структуры каталогов без файлов в дереве каталога, которые не являются изображениями в подпапке подпапки текущего каталога.

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

Основное улучшение возможности использования будет способность указывать каталог входных данных и выходных данных при запуске команды вместо того, чтобы они зависят от фиксированных относительных путей от рабочего каталога ( . и ../italy_pics_organaged В нашем случае).

Что тебе нужно знать

Этот пост не предполагает, что у вас есть какой-либо опыт работы с программированием Python, но не предназначен для всеобъемлющего введения Python (на этом есть много материалов в Интернете, нет необходимости быть избыточным здесь). Однако то, что я полагаю, что у вас есть некоторые базовые знания о структуре кода, написанного на объектно-ориентированном языке программирования высокого уровня (выбор, итерацию, классы, объекты, функции, библиотеки и т. Д.).

Прежде всего, давайте начнем с изложении необходимых инструментов. Очевидное требование – Python 3 вместе с Pypi Пип CLI Tool Установлен и доступный из вашего терминала или командной строки. В дополнение к этому, мы все еще понадобится ImageMagick, но мы не собираемся использовать Конвертировать CLI Command, потому что у нас есть несколько более приятных интерфейсов, с которыми мы можем работать, чтобы построить наш инструмент, поскольку мы работаем Python.

Более конкретно, мы собираемся использовать Палочка Пакет из PYPI, который обеспечивает простой в использовании интерфейс для библиотек ImageMagick.

Что касается установки этих инструментов, я рекомендую вам установить Python, используя менеджер пакета распределения в Linux, используя домень на Mac или загрузки Последняя версия Python для Windows и следовать за Инструкции по установке для imageMagick и палочке на сайте палочки Отказ

После того, как вы установили ImageMagick и палочку, мы можем обойти использование Конвертировать Который, в сценарии Python, потребует нереста подпроцесс, и это то, что большинство людей действительно не хотят делать, если не требуют строго необходимо: использовать библиотеку, когда она существует, почти всегда лучший выбор.

Мы собираемся использовать два класса из палочка упаковка: Изображение от палочка .Image и Рисование от WAND.Drawing И импортируйте их в наш код, придавая следующее в начале нашего сценария Python:

from wand.image import Image
from wand.drawing import Drawing

Если вы написали пакеты Python в прошлом, вы бы знаете палочка .Image и WAND.Drawing Имена связаны с внутренней структурой пакета, тогда как Изображение и Рисование Названия классов, которые мы импортируем.

Два класса, когда используются вместе, позволяют загружать изображения, рисовать на них, а затем сохранить их.

Получение объекта изображения

Изображение класс предназначен для использования в с Блок, как это

with Image(filename=filename) as image:
    # do something with the image, which exists here
    # as long as you keep indenting the code
# the image doesn't exist here
# because the indented block is over

Если путь к файлу (либо относительно или абсолютно) сохраняется на Имя файла Переменная, это создаст переменную под названием изображение Это существует только внутри блока с отступом, показанным в примере выше. Вы можете нарисовать на Изображение используя Рисование класс.

Мы будем вызывать методы непосредственно на нашем изображении только позже, когда мы сохраняем его, используя image.save.save. .

Класс рисунка

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

Команда используется так:

convert input.png -fill "textcolor" -pointsize textsize -gravity WhereTheTextWillBe -annotate +offsetHorizontal+offsetVertical "watermark text" output.png

Более конкретно, мы дали следующие аргументы этой команды в скрипте Bash:

convert ./${picture} -fill "white" -pointsize 90 -gravity SouthEast -annotate +30+30 "${watermarktext}" "${saveto}"

и $ {variable_name} – это то значение переменной вставлено в строку в bash.

-наполнять , -Очки и -Гравитация Аргументы Расскажите конвертировать Некоторые из атрибутов вещи, которую мы хотим рисовать: какого цвета, чтобы заполнить его, какой размер шрифта для использования и где поставить новый объект по сравнению с исходным изображением, в то время как -нотата рассказывает конвертировать Что делать: аннотировать некоторые текст по смещению изображения на 30 пикселей горизонтально и 30 пикселей вертикально. Первый аргумент и последний аргумент – это просто изображение, на которой мы рисуем текст, и путь, где сохранить отредактированное изображение.

То, как мы делаем это в Python, используя палочку, это инициализация нового Рисование объект

draw = Drawing()

Установка его fill_color Свойство в строку:

draw.fill_color = "white"

Установка font_size :

draw.font_size = 90

и гравитация аннотации со строкой, похожей на тот, который мы использовали для Конвертировать Cli command:

draw.gravity = "south_east"

Наконец, нарисуйте аннотацию на Изображение объект:

draw.draw(image)

И сохранить отредактированное изображение:

image.save(filename=f'{saveto}')

Упаковка, эквивалент команды мы видели выше в Python с помощью палочки является

 with Image(filename=picture) as image:
     draw = Drawing()

     draw.fill_color = "white"
     draw.font_size = 90
     draw.gravity = "south_east"
     draw.text(30, 30, text)
     draw.draw(image)
     image.save(filename=f'{saveto}')

Мы установим ценность картина и Saveto позже.

Стандартная библиотека Python содержит модуль, который можно использовать, среди прочего, для просмотра файлов и каталогов и изменение текущего каталога (пути передавались на Image Contrustor и на Image.save () Также могут быть относительные пути).

Этот модуль можно импортировать добавление

import os

ниже строк

from wand.image import Image
from wand.drawing import Drawing

Самые важные функции, которые мы собираемся использовать из ОС Модуль – это три:

OS.CHDIR , который изменяет текущий рабочий каталог на путь, который вы передаете к нему (который может быть относительным или абсолютным, так же, как CD команда Shell; os.listdir , который возвращает список имен Файлы и каталоги в текущем каталоге; os.scandir , который возвращает список файлов и каталогов, каждый из которых является ОС. Дирентри , который является типом данных, которые хранит, наряду с именем файла/каталога другие атрибуты, такие как полный путь или информация о том, является ли это каталог или файл.

Если бы мы застряли с поведением сценария Bash, и пришлось бы искать каталоги в начальном каталоге, мы могли бы использовать

os.scandir(".")

И тогда только сохранить его результаты в списке, если они являются каталогами.

Теперь мы хотим, чтобы пользователь мог указать любой каталог в качестве аргумента и посмотреть там для картинок, учитываемые, что мы рассматривали только в текущем рабочем каталоге ограничением сценария Bash.

Мы просто собираемся принять аргументы из аргв Интерфейс, который вы могли бы использовать в C (или в Python) и использовать первый аргумент в качестве каталога, из которого можно взять неотеданные, структурированные изображения и второе в качестве выходной папки, где хранить отредактированные изображения.

Когда вы запускаете команду, как CD На интерфейсе командной строки необходимо указать аргумент: какой каталог для изменения рабочего каталога.

Как вы знаете, эти аргументы указываются после имени команды следующим образом:

commandname argument1 argument2 argument3 ...

При написании сценариев Python эти аргументы доступны с помощью sys.argv список:

from sys import argv

# argv[0] = commandname
# argv[1] = argument1
# argv[2] = argument2
# argv[3] = argument3
# ...

Независимо от того, запустите ли вы свой скрипт в качестве исполняемого или используя Python Команда, ARGV [0] всегда будет именем файла вашего сценария.

Это означает, что, если мы хотим, чтобы первый аргумент, где можно было сделать изображения, нам нужно написать

os.scandir(argv[1])

Вы можете проверить, если Операционные системы. Дирентри Объект указывает на каталог, проверив значение, возвращаемое его IS_DIR () метод. Мы собираемся использовать понимание списка, чтобы сделать это в очень аккуратном и компактном способе:

areas = [file for file in os.scandir(argv[1]) if file.is_dir()]

Для каждой области:

for area in areas:

Мы собираемся перейти в каталог, соответствующий площади:

os.chdir(area)

И получить список папок города в текущем каталоге:

cities = [file for file in os.scandir(".") if file.is_dir()]

и искать фотографии внутри каждого из этих каталогов

for city in cities:
    os.chdir(city)
    pics = [file for file in os.scandir(".") if file.is_file()]
    # deal with the pictures
    os.chdir("..")

И вернитесь к родительскому каталогу для каждой области:

os.chdir(os.pardir)

Все вообще, до сих пор мы написали следующее:

areas = [file for file in os.scandir(argv[1]) if file.is_dir()]

for area in areas:
    os.chdir(area)
    cities = [file for file in os.scandir(".") if file.is_dir()]
    for city in cities:
        os.chdir(city)
        pics = [file for file in os.scandir(".") if file.is_file()]
        for pic in pics:
            # deal with each picture
        os.chdir("..")
    os.chdir("..")

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

Наличие того, как глобальная переменная, не то, что мы собираемся сделать, потому что это шанс перейти на Ооп в Python и, потому что реализует все в классе, улучшает использование кода и будет полезен в следующем посте, когда мы опираемся на инструмент Мы строим в этом посте.

Создание ImageProcessor класса

Прежде всего, вот как вы определяете класс и переменную участника:

class ImageProcessor:

   i = 0

Мы собираемся позвонить в функцию, которую мы построили в предыдущей главе, которая берет пути от Аргв , пересекает дерево каталога и запускает другую функцию на каждом изображении Процесс_argv Отказ

Методы Python (если они не должны быть статичными), возьмите объект, на котором они бегают, как аргумент под названием Я , который используется очень похоже на Это В классах в JavaScript: Self.i Используется ли синтаксис для доступа к Я Переменная участника, тогда как self.process (args) Используется это синтаксис для вызова Процесс () Метод с args аргументы.

Окончательная версия функции, которую мы построили в предыдущем абзаце, вместе с несколькими попробуйтепоймать Блоки, чтобы убедиться, что пользовательский ввод – это то, с чем мы можем работать, и звонок до еще определенного Процесс () Функция и несколько строк, которые занимаются выходным каталогом, я объясню после этого, следующее:

class ImageProcessor:

   i = 0

   def process_argv(self):
       if len(argv) < 3:
           print("Some arguments are missing.")
           print("Usage: picorganizer.py input_directory output_directory")
           return

       areas = []

       try:
           areas = [file for file in os.scandir(argv[1]) if file.is_dir()]
           if len(areas) == 0:
               raise Exception("No subdiretories in input path")
       except:
           print("The provided input path is not valid")

       save_dir = []

       try:
           save_dir = [dir for dir in os.scandir(f"{argv[2]}/..") if dir.name == os.path.split(argv[2])[1] and dir.is_dir()][0]
       except:
           print("The provided output path doesn't exist or is invalid")

       for area in areas:
           os.chdir(area)
           cities = [file for file in os.scandir(".") if file.is_dir()]
           for city in cities:
               os.chdir(city)
               pics = [file for file in os.scandir(".") if file.is_file()]
               for pic in pics:
                   print("    Pic:" + str(pic))
                   self.process(area, city, pic, os.path.abspath(save_dir))
               os.chdir(os.pardir)
           os.chdir(os.pardir)

Линия

save_dir = [dir for dir in os.scandir(f"{argv[2]}/..") if dir.name == os.path.split(argv[2])[1] and dir.is_dir()][0]]

Сканирует второй аргумент (выходной каталог) S Родительский каталог И, если сущность, который пользователь дал нам путь к фактическому существу, мы назначаем его Save_Dir Переменная. Это внутри попробуйте - CATH` Block, который сообщает пользователю ошибки и завершает выполнение, если второй аргумент указывает на несуществующий каталог, учитывая, что он будет вызвать исключение.

OS.PATH.SPLIT (ARGV [2]) [1] используется для доступа к подстроке ARGV [2] После последнего / персонаж. Другими словами, он используется для доступа к имени выходного каталога, без пути, приводящего к нему.

‘f “{argv [2]}/..”‘ называется F-string И как строковая интерполяция сделана из Python 3 6 Вперед: это строка состоит из значения ARGV [2] сопровождаемый /.. символы. Например, если значение ARGV [2] были путь/к/dir , значение этой ф-строки будет путь/к/dir/.. , что такое же, как путь/к , который является путь к каталогу, где мы должны иметь возможность найти каталог, называемый «|» дир Если мы перечислим содержимое этого каталога.

OS.Path.abspath (Save_Dir)) Возвращает абсолютный путь к Save_Dir Отказ

Написание процесса () Метод

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

def process(self, area: os.DirEntry, city: os.DirEntry, pic: os.DirEntry, save_dir: str):

Мы уже видели, как обработать изображения, нам просто нужно сделать это работать с этими аргументами.

Первое, что нужно учитывать, это то, что Процесс_argv Уже изменит рабочий каталог в каталог, в котором находится изображение для обработки, поэтому нам нужно только пройти имя файла для Изображение Конструктор, чтобы получить правильное изображение:

filename = pic.name

Чтобы не запутаться, прежде чем мы изменим рабочий каталог, давайте сохраним абсолютный путь к текущему каталогу в переменной, чтобы мы могли вернуться к нему после того, как мы закончимся с нашей обработкой:

return_dir = os.path.abspath(os.curdir)

Последняя часть данных, которые нам нужен для извлечения из аргумента, – это расширение изображения, которое мы можем получить с OS.Path.splitext () :

(name, extension) = os.path.splitext(filename)

Здесь мы распаковываем кортеж , которая представляет собой пару значений, возвращаемых вызываемой функцией, каждая из которых мы можем назначить переменной.

Остальная часть функции процесса является то, что мы видели в начале поста, когда мы рассмотрели, как использовать палочку для замены Конвертировать команда:

with Image(filename=filename) as image:
           text = f'{city.name}({area.name})'
           draw = Drawing()
           draw.fill_color = "white"
           draw.font_size = 90
           draw.gravity = "south_east"
           draw.text(30, 30, text)
           draw.draw(image)
           os.chdir(save_dir)
           image.save(filename=f'{self.i}-{text}{extension}')
       self.i=self.i+1
       os.chdir(return_dir)

Весь Процесс () Функция, с несколькими пояснительными комментариями, заканчивается следующим:

   def process(self, area: os.DirEntry, city: os.DirEntry, pic: os.DirEntry, save_dir: str):
       # os.DirEntry.name is the picture's filename
       filename = pic.name

       # Store the absolute path of the current directory
       # so that we can return to it when we're done,
       # so that the calling function doesn't end up
       # in a working directory that is not the same
       # as the one it was in before the call to process()
       return_dir = os.path.abspath(os.curdir)

       # os.path.splitext gives us both the file name and
       # the extension of the picture. We need the extension
       # because we're going to use it to tell Wand
       # (and, in turn, ImageMagick) what extension
       # to give to the image and we want to retain
       # the original.
       (name, extension) = os.path.splitext(filename)

       with Image(filename=filename) as image:
           text = f'{city.name}({area.name})'
           draw = Drawing()
           draw.fill_color = "white"
           draw.font_size = 90
           draw.gravity = "south_east"
           draw.text(30, 30, text)
           draw.draw(image)
           os.chdir(save_dir)
           image.save(filename=f'{self.i}-{text}{extension}')
       self.i=self.i+1
       os.chdir(return_dir)

Если вам понравилось этот пост, Следуй за мной в Twitter (ручка @CarminezaCC) и проверить моя книга трепетания или Мой блог на Carmine.dev Отказ

Весь скрипт Python, включая звонок на process_argv () , заканчивается следующим образом:

from wand.image import Image
from wand.drawing import Drawing
import os
from sys import argv


class ImageProcessor:

   i = 0

   def process(self, area: os.DirEntry, city: os.DirEntry, pic: os.DirEntry, save_dir: str):
       # os.DirEntry.name is the picture's filename
       filename = pic.name

       # Store the absolute path of the current directory
       # so that we can return to it when we're done,
       # so that the calling function doesn't end up
       # in a working directory that is not the same
       # as the one it was in before the call to process()
       return_dir = os.path.abspath(os.curdir)

       # os.path.splitext gives us both the file name and
       # the extension of the picture. We need the extension
       # because we're going to use it to tell Wand
       # (and, in turn, ImageMagick) what extension
       # to give to the image and we want to retain
       # the original one
       (name, extension) = os.path.splitext(pic)

       with Image(filename=filename) as image:
           text = f'{city.name}({area.name})'
           draw = Drawing()
           draw.fill_color = "white"
           draw.font_size = 90
           draw.gravity = "south_east"
           draw.text(30, 30, text)
           draw.draw(image)
           os.chdir(save_dir)
           image.save(filename=f'{self.i}-{text}{extension}')
       self.i=self.i+1
       os.chdir(return_dir)

   def process_argv(self):
       if len(argv) < 3:
           print("Some arguments are missing.")
           print("Usage: picorganizer.py input_directory output_directory")
           return

       areas = []

       try:
           areas = [file for file in os.scandir(argv[1]) if file.is_dir()]
           if len(areas) == 0:
               raise Exception("No subdiretories in input path")
       except:
           print("The provided input path is not valid")

       save_dir = []

       try:
           save_dir = [dir for dir in os.scandir(f"{argv[2]}/..") if dir.name == os.path.split(argv[2])[1] and dir.is_dir()][0]
       except:
           print("The provided output path doesn't exist or is invalid")

       for area in areas:
           os.chdir(area)
           cities = [file for file in os.scandir(".") if file.is_dir()]
           for city in cities:
               os.chdir(city)
               pics = [file for file in os.scandir(".") if file.is_file()]
               for pic in pics:
                   print("    Pic:" + str(pic))
                   self.process(area, city, pic, os.path.abspath(save_dir))
               os.chdir(os.pardir)
           os.chdir(os.pardir)


ImageProcessor().process_argv()

Оригинал: “https://dev.to/carminezacc/basic-python-scripting-to-automate-everyday-tasks-add-text-to-images-using-imagemagick-and-wand-5fib”