Журнал LinuxFormat - перейти на главную

LXF84:Python

Материал из Linuxformat
Перейти к: навигация, поиск
Python
Python для профессионалов
Python + PyGame
Python + Web
Python + Clutter

Содержание

Python: Обработка графики и звука

ЧАСТЬ 4: Язык Python может многое, хотя, конечно, не всё. Но благодаря «привязкам» к таким мощным библиотекам, как GTK и модулям сторонних разработчиков его возможности становятся практически безграничными, пишет Сергей Супрунов.

Рассказывать о различных «расширениях» Python можно бесконечно. Чтобы все-таки завершить серию и дать вам возможность погрузиться в самостоятельное изучение этого языка, я решил коротко остановиться на двух сторонних библиотеках, значительно расширяющих возможности Python по работе с изображениями и звуком.

Где же кружка?…

Начнем с PIL — Python Image Library. Вам наверняка удастся без проблем инсталлировать ее с помощью менеджера пакетов вашего дистрибутива. Не исключено, что она уже установлена (как это имеет место быть в Ubuntu). Но если вам не очень повезло (мало ли, вдруг вы — фанат Slackware), то забрать архив с ее исходным кодом можно на http://www.Pythonware.com.

Итак, зачем она нужна? Представьте, что вы привезли с моря четыре «флешки», забитых замечательными фотографиями, и хотели бы згрузить их на свою домашнюю страничку… Начинание благое, но каково будет ее посетителям «тянуть» добродушно созданные вашей «Минолтой» файлы по 5 МБ каждый? Принципы гуманизма требуют предварительно «сжать» изображения до приемлемых размеров. Да и миниатюры создать было бы неплохо, чтобы можно было окинуть взглядом сразу несколько фотографий.

Если вы уже собрались запускать Gimp — одумайтесь! Ну на 10 изображений у вас терпения хватит, ну на 100… А если их 1000? Нет, ручная работа не для нас. Вот тут-то и пригодится PIL, где уже реализованы широчайшие возможности по обработке изображений.

Небольшой практикум

Листинг conv2.py
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. import os, sys, getopt, glob
  4. import PIL.Image as pim
  5. type = “JPEG”
  6. outdir = “.”
  7. width = height = 0
  8. opts, pattern = getopt.getopt(sys.argv[1:], “t:o:w:h:”)
  9. for opt in opts:
  10. if opt[0] == “-t”: type = opt[1]
  11. elif opt[0] == “-o”: outdir = opt[1]
  12. elif opt[0] == “-w”: width = int(opt[1])
  13. elif opt[0] == “-h”: height = int(opt[1])
  14. try:
  15. tmp = pim.new(“RGB”, (1,1))
  16. tmp.save(“/dev/null”, type)
  17. except:
  18. print >> sys.stderr, “ОШИБКА: формат %s не поддерживается” % type
  19. sys.exit()
  20. if not pattern: pattern = “.”
  21. filelist = []
  22. for entry in pattern:
  23. if entry[-1] == “/”: entry = entry[:-1]
  24. if os.path.isdir(entry): entry += “/*
  25. filelist.extend(glob.glob(entry))
  26. for file in filelist:
  27. if not os.path.isfile(file): continue
  28. try:
  29. im = pim.open(file)
  30. except IOError: continue
  31. if not (width and height):
  32. if width: height = width * im.size[1] / im.size[0]
  33. elif height: width = height * im.size[0] / im.size[1]
  34. else:
  35. width = im.size[0]
  36. height = im.size[1]
  37. resized = im.resize((width, height), pim.ANTIALIAS)
  38. name, ext = os.path.splitext(file)
  39. name = name.split(“/”)[-1]
  40. outfile = “%s/%s.%s” % (outdir, name, type.lower())
  41. resized.save(outfile)
  42. print%s (%s, %dx%d) -> %s (%s, %dx%d)% (file,
  43. im.format, im.size[0], im.size[1],
  44. outfile, type, width, height)

Итак, после установки библиотеки все ее модули вы, скорее всего, найдете в /usr/lib/Python2.4/site-packages/PIL. Кстати, обычно при инсталляции PIL добавляет в список sys.path также и указанный путь, так что для подключения, скажем, модуля ImageDraw, можно будет использовать не только import PIL.ImageDraw, но и просто import ImageDraw.

Сами модули не слишком щедро прокомментированы, но зато по адресу http://www.Pythonware.com/library/pil/handbook вы найдете превосходное руководство.

Несмотря на множество модулей, входящих в состав библиотеки, для большинства задач вполне хватает модуля Image, а он уже сам неявно задействует возможности остальных. Основой является объект-изображение, которое можно загрузить из файла (используя метод open()), либо создать с нуля (конструктор new()), причем при загрузке файла библиотека сама позаботится о распознавании формата изображения.

Для примера напишем небольшой конвертер, который мог бы брать указанные с помощью шаблона файлы и сохранять их измененные копии в другом каталоге. PIL предоставляет много методов для модификации изображений, в нашем примере мы остановимся на двух — изменении размера и формата. Заодно вспомним, как работать с файловой системой, и познакомимся с небольшим, но полезным модулем getopt для разбора параметров, передаваемых сценарию из командной строки. Код сценария представлен в листинге conv2.py.

В строках 8-13 мы используем функцию getopt() одноименного модуля для того, чтобы получить передаваемые в скрипт опции. Первым параметром функция получает список аргументов командной строки (без первого элемента, соответствующего имени самого скрипта), а вторым — список так называемых «коротких» опций, которые нужно найти. (Модуль поддерживает также и «длинные» опции в формате GNU — смотрите документацию или код самого модуля). Если опция задается как логическая (то есть важно лишь то, присутствует ли она в командной строке), то в списке просто указывается соответствующая ей буква. Если опция параметрическая (то есть должна сопровождаться каким-то значением), после ее буквы ставится двоеточие. В нашем примере все опции параметрические: -t (формат выходных файлов), -o (каталог для их размещения), -h (высота модифицированного изображения), -w (ширина изображения).

Возвращает getopt() два списка: список распознанных опций в формате [(опция, значение), (опция, значение), (. . .)] (значение указывается только для параметрический опций, для логических второй элемент кортежа остается пустым), и список оставшихся (не распознанных) аргументов. Наш скрипт будет ожидать во втором списке имена файлов (с указанием пути, если нужно), которые должны быть преобразованы. Пользователь может задать ширину результирующих изображений, их высоту или оба параметра сразу (если задан только один, второй вычисляется согласно пропорциям исходного файла, см. строки 31-36).

Поскольку в опции -t пользователь может ввести все, что угодно, в строках 14-19 мы проводим небольшую проверку — создаем изображение размером 1х1 и пытаемся записать его в /dev/null (чтобы нигде мусор не разводить) в указанном пользователем формате type (метод save() самостоятельно выполняет все нужные преобразования форматов). В случае неудачи сообщаем об этом пользователю (обратите внимание на синтаксис перенаправления вывода по десткриптору, отличному от stdout). Если же операция пройдет успешно — продолжаем.

Строки 20-25 отвечают за формирование списка файлов, соответствующих указанному шаблону. И, начиная с 26-й, мы приступаем собственно к обработке. Файлы, которые не могут быть открыты, например, текстовые, мы просто игнорируем — строка 30.

В строке 37 изменяем размер изображения (при этом создается новый объект). Флаг Image.ANTIALIAS задает способ интерполяции соседних точек (доступны и другие, например, LINEAR, NEAREST, BICUBIC). Наконец, рассчитав имя результирующего файла, выполняем сохранение. В данном случае мы не передаем методу save() второй параметр, указывающий формат файла — он будет определен автоматически по расширению.

Вот, собственно, и готово:

admin@toshiba:~/lxf/propy/l4/code$ ./conv2.py -t png -h 120 *.jpg
osf_t1.jpg (JPEG, 2272x1704) -> ./osf_t1.png (png, 160x120)
osf_t2.jpg (JPEG, 2272x1704) -> ./osf_t2.png (png, 160x120)
osf_t3.jpg (JPEG, 2272x1704) -> ./osf_t3.png (png, 160x120)
osf_t4.jpg (JPEG, 2272x1704) -> ./osf_t4.png (png, 160x120)

Небольшой совет: вместо метода resize() для создания миниатюр лучше использовать метод thumbnail() — он работает раза в два быстрее. Только имейте в виду, что thumbnail() изменяет текущий объект, то есть исполняется «по месту», в то время как resize() создает копию объекта, оставляя оригинал в неприкосновенном виде (речь, естественно, об объекте в памяти — файл на диске не будет изменен до тех пор, пока вы явно не выполните метод save()).

Не конвертированием единым…

Естественно, возможности PIL этим не ограничиваются: она позволяет изменять цветовые схемы; применять к изображениям фильтры, такие как размывание, рельеф и т. д. (см. модуль ImageFilter и метод filter() модуля Image); вы можете смешивать различные изображения (методы blend() и composite()), поворачивать их на различные углы, смещать, вырезать из них фрагменты, накладывать простейшие графические элементы (такие как линии и дуги, см. модуль ImageDraw), и т. д.

Вот так, например, можно в два раза усилить «красную» составляющую исходного изображения:

ЛИСТИНГ MAXIRED.PY

#!/usr/bin/Python
# -*- coding: utf-8 -*-
 
import os, sys, Image
 
infile = sys.argv[1]
im = Image.open(infile)
if im.mode == “RGB”:
 imR, imG, imB = im.split()
 imR = imR.point(lambda pixel: pixel * 2)
 outfile = Image.merge(“RGB”, (imR, imG, imB))
 outfile.save(%s-red%s” % os.path.splitext(infile))
else:
 print >> sys.stderr, “ОШИБКА: файл не RGB”

Здесь методом split() мы раскладываем исходный объект на «цветовые» составляющие (если атрибут объекта, mode, подтверждает, что загруженный файл является RGB-изображением), методом point() применяем указанную функцию (в нашем случае — умножение на два) к каждому пикселу «красного» объекта, и с помощью merge() объединяем все снова в одно изображение.

Нужно заметить, что библиотека PIL предназначена для обработки изображений, а не для их вывода. Если вы хотите вставить фотографию в ваше графическое приложение, для этого следует использовать средства соответствующего модуля. Например, в Tkinter для этого предназначены классы BitmapImage и PhotoImage. Есть, конечно, и в PIL метод show(), но он просто сохраняет объект во временный файл и отдает его внешней программе просмотра изображений (xv под Unix/Linux, Paint в Windows), поэтому пригоден он разве что для отладочных целей, чтобы по ходу разработки скрипта можно было «на лету» контролировать промежуточные результаты.

Нам песня строить и жить помогает…

Листинг «playit.py»
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. import pymedia.audio.acodec as acodec
  4. import pymedia.audio.sound as sound
  5. import pymedia.muxer as muxer
  6. import time
  7. class playit:
  8. def __init__(self, file2play, rateCf=1):
  9. self.PLAY_FLG = 1
  10. self.file2play = file2play
  11. self.rateCf = rateCf
  12. def playit(self):
  13. dm = muxer.Demuxer(self.file2play.split(‘.’)[-1].lower())
  14. f = open(self.file2play, ‘rb’)
  15. s = f.read(32000)
  16. dm.parse(s)
  17. dec = acodec.Decoder({id’:dm.streams[0][id]})
  18. r = dec.decode(s)
  19. print ‘Play “%s”:’ % self.file2play
  20. print ‘ Bitrate: %d’ %r.bitrate
  21. print ‘ Sample Rate: %d’ % r.sample_rate
  22. print ‘ RateCf: %.2f’ % self.rateCf
  23. print ‘ Channels: %d’ % r.channels
  24. snd = sound.Output(int(r.sample_rate * self.rateCf),
  25. r.channels,
  26. sound.AFMT_S16_LE)
  27. while len(s) > 0:
  28. if r: snd.play(r.data)
  29. s = f.read(512)
  30. r = dec.decode(s)
  31. while snd.isPlaying(): time.sleep(1)
  32. self.PLAY_FLG = 0

Следующая библиотека, которую мы затронем в этом уроке — PyMedia. Скачать исходный код можно с сайта http://www.pymedia.org (там же вы найдете и документацию, и архив с примерами). Установка выполняется, в принципе, достаточно просто:

admin@toshiba:~$ tar xzvf PyMedia-1.3.7.3.tar.gz
admin@toshiba:~$ cd PyMedia-1.3.7.3
admin@toshiba:~/PyMedia-1.3.7.3$ sudo Python setup.py install

Однако, среди зависимостей — alsa, ogg, vorbis, faad, mp3lame (причем для инсталляции нужны и заголовочные файлы, то есть может потребоваться доустановить соответствующие dev-пакеты). Также потребуется пакет Python-dev. Так что, если вам посчастливится найти для своего дистрибутива двоичный пакет библиотеки (в частности, deb-пакет, правда, не самой новой версии, можно найти здесь: http://prdownloads.sourceforge.net/PyMedia/PyMedia_1.3.5_i686-py2.4.deb?download), то рекомендую воспользоваться им.

На листинге «playit.py» представлен код класса, задача которого - воспроизвести звуковой файл. Первыми строками (3-6) подключаем нужные нам модули (иерархия здесь довольно сложная, так что без документации не обойтись; ну и про примеры не забывайте).

Сначала мы должны определить идентификатор кодека, который подойдет для воспроизведения того или иного файла. Для этого мы создаем объект Demuxer, которому передается в качестве параметра расширение файла (строка 13). Этот объект мы используем для того, чтобы декодировать заголовок звукового файла (в строках 14-16 мы считываем первые 32000 байт файла и передаем их методу parse()). Теперь мы можем определить идентификатор нужного кодека по ключу ‘id’ в словаре dm.streams[0] (строка 17; можно в качестве параметра передать и весь словарь), и используем этот кодек для декодирования уже считанных данных. Поскольку здесь, помимо всего прочего, присутствует и служебная информация, то после декодирования мы получаем возможность вывести кое-что из этого на экран (строки 19-23; но это только в тестовых целях — в классах, которые могут использоваться в различных программах, оператору print, конечно, не место).

В строке 24 создается объект Output, метод play() которого будет использоваться для воспроизведения (строка 28). Изменив параметр rateCf, можно ускорить или замедлить проигрывание файла (по умолчанию используется значение 1). Наконец, в цикле (строки 27-30) мы воспроизводим уже считанный фрагмент (переменная s) и готовим следующий. Как только мы считаем и передим методу play() весь файл, в строке 31 начнется ожидание конца воспроизведения (что-бы не сильно нагружать процессор, проверку выполняем один раз в секунду). Ожидание нужно, поскольку play() является асинхронным методом, который возвращает управление сценарию до того, как воспроизведение фрагмента на самом деле завершится. Благодаря этому мы можем спокойно обрабатывать следующий фрагмент, не беспокоясь о паузах при воспроизведении.

Для использования класса playit нужно создать соответствующий объект, передав ему имя файла и, при желании, коэффициент, корректирующий скорость воспроизведения. Само воспроизведение запускается методом playit(). Причем библиотека PyMedia вполне позволяет поигрывать одновременно несколько файлов — в качестве примера рассмотрим, как можно получить простейший эффект объемного звука:

Листинг echo.py

#!/usr/bin/Python
from playit import playit
import threading, time
mp3 = ‘Aria.shtil.mp3’
ECHO = 0.1
f1 = playit(mp3)
f1.rateCf = 1
ht1 = threading._start_new_thread(f1.playit, ())
if ECHO:
time.sleep(ECHO)
f2 = playit(mp3)
f2.rateCf = 1
ht2 = threading._start_new_thread(f2.playit, ())
while(f1.PLAY_FLG or ECHO and f2.PLAY_FLG):
time.sleep(1)

В этом сценарии мы запускаем два потока (если значение переменной ECHO отлично от нуля). В каждом из потоков проигрывается один и тот же файл, но перед запуском второго делается небольшая пауза (в примере — 0,1 секунды). В результате получается довольно интересное звучание. Кстати, обратите внимание на то, как мы запустили потоки — вместо того, чтобы создавать соответствующий класс и переопределять в нем метод run(), был напрямую вызван «внутренний» метод _start_new_thread(), которому передается функция или метод, которые должны быть выполнены в потоке.

Помимо собственно воспроизведения звука, PyMedia предоставляет средства для работы с видео-файлами, ее можно использовать для конвертирования исходного файла в другие форматы, для редактирования мета-данных и самого звука, и т. д.

Малышка wave

Листинг wav2.py
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. import wave
  4. def sum(a, b):
  5. return%c%c” % (a, b)
  6. rf = wave.open(new.wav”, “rb”)
  7. print “Количество фреймов: %d “ % rf.getnframes()
  8. print “Частота дискретизации: %d” % rf.getframerate()
  9. print “Точность дискретизации: %d” % rf.getsampwidth()
  10. print “Количество каналов: %d” % rf.getnchannels()
  11. print “Тип сжатия: %s” % rf.getcomptype()
  12. total = rf.getnframes() * rf.getnchannels() * rf.getsampwidth()
  13. normal = rf.readframes(total)
  14. reverse = ‘’
  15. if rf.getsampwidth() == 2:
  16. n2 = normal[-2::-2]
  17. n1 = normal[-1::-2]
  18. for i in map(sum, n0, n1):
  19. reverse += i
  20. else:
  21. reverse = normal[-1::-1]
  22. wf = wave.open(“new2.wav”, “wb”)
  23. wf.setnchannels(rf.getnchannels())
  24. wf.setsampwidth(rf.getsampwidth())
  25. wf.setframerate(rf.getframerate())
  26. wf.writeframes(reverse)

Кстати, раз уж зашла речь об обработке звука, то и в стандартной поставке Python есть несколько модулей для выполнения простейших операций. Например, модуль wave позволяет редактировать (и даже создавать с нуля, если вы сильны в математике) звуковые файлы в формате WAV. На листинге wav2.py представлен сценарий, с помощью которого можно «развернуть» wav-файл в обратном направлении:

Открыв wav-файл (строка 6), мы можем получить некоторую информацию о нем (строки 7-11). Считываем данные в переменную normal (нормальная последовательность). «Сырой» wav-файл (то есть без компрессии; подавляющее большинство таковыми и является) представляет собой набор «фреймов», или «отсчетов», то есть значений амплитуды сигнала в данный момент времени. Частота дискретизации определяет, сколько фреймов будут формировать одну секунду звучания. Точность дискретизации показывает, сколько байт используется для хранения одного фрейма (значение 1 соответствует 8-битному звуку, 2 — 16-битному).

Для того чтобы развернуть наш файл, нужно «реверсировать» считанную нормальную последовательность. Это можно легко выполнить с помощью списковых включений (как показано в строке 21). Но если мы работаем с 16-битным файлом, то нужно позаботиться о сохранении порядка байтов во фрейме, иначе получим лишь шум. Для этого в строках 16 и 17 мы формируем две «подпоследовательности», в одну из которых попадают младшие байты фреймов, во вторую — старшие. С помощью функции map(), которая применяет функцию, указанную первым параметром (в нашем примере это sum(), описанная в строках 4-5), к каждому элементу последовательностей, переданных вторым и третьим параметрами, мы соединяем полученные полуфреймы воедино. Поскольку на выходе map() получается список, то нам нужно его вручную «собрать» снова в строку (стр. 18-19).

Нужно заметить, что map() отличается очень высокой скоростью обработки последовательностей. Если собирать результирующую строку «вручную» (например, в цикле while), то эта процедура может затянуться на десятки секунд даже для не очень большого файла, в то время как map() выполняет эту операцию почти мгновенно.

Наконец, в строках 22-26 мы открываем новый файл на запись, устанавливаем его параметры (в соответствии с исходными) и записываем содержимое. Теперь можете и сами поиграть в «АПОП»!

Только вперед!

Cсылки по теме
  • www.python.org – официальный сайт проекта
  • www.python.ru – российский сайт почитателей этого языка
  • zope.net.ru – сайт российской группы пользователей среды Zope (разработанный на Python сервер web-приложений); здесь есть информация и о Python
  • www.pythonware.com – сайт разработчиков PIL; помимо самой библиотеки, можно найти и другие интересные вещи
  • www.awaretek.com/plf.html – страничка, посвящённая изучению языка Python
  • py.vaults.ca/apyllo.py – множество примеров программ на Python, от работы с базами данных и графикой до игр

На этом мы завершаем нашу серию уроков. Благодаря тому, что разработчикам Python удалось сделать этот язык простым и в то же время удивительно мощным и расширяемым, с его помощью можно эффективно решать самые различные задачи — дополнительная сложность будет проявляться лишь там, где это действительно необходимо, в то время как простые задачи сохранят предельную простоту. И если вдруг для какого-то проекта стандартных средств окажется недостаточно, то наверняка вам поможет какая-нибудь из сторонних библиотек. Главное — не сдаваться!

Удачи!

Персональные инструменты
купить
подписаться
Яндекс.Метрика