Автор — Грэг Валтерс (Greg Walters)

Ваша любимая музыка тоже хранится на вашем компьютере в формате MP3? Тогда мы с вами в чём-то похожи. Согласи-тесь, когда у вас в пределах тысячи музыкальных файлов, то довольно просто разобраться что и где лежит. Но у меня, нап-ример, музыки значительно боль-ше, потому что раньше я был диджеем и уже давно перевёл большую её часть в MP3. Снача-ла главной головной болью было пространство на жёстком диске, а теперь — вспомнить, что у меня есть и где это искать. В этой и следующей частях мы рассмотрим, как организо-вать собственный каталог MP3-файлов. Заодно пройдёмся по новым свойствам языка Python и вспомним, как работать с базами данных.

Начнём с того, что MP3-файл может содержать информацию о самом себе. Эта информация может включать в себя назва-ние песни, название альбома, имя исполнителя, что угодно. Она хранится в специальных ID3-метках и носит название «метаданные». Раньше в MP3-файлах можно было хранить только ограниченное количес-тво информации. Она записыва-лась в конец файла блоком в 128 байт и из-за этого макси-мальный размер названия пес-ни, имени исполнителя или какой либо другой информации не мог превышать 30 байт. Этого вполне хватало для большинства файлов, но для одной из моих любимых песен под названием «Clowns (The Demise of the European Circus with No Thanks to Fellini)» этого явно было недостаточно. Мно-гих такая ситуация не устраива-ла, поэтому стандарт меток ID3 был переименован ID3v1, и был создан новый формат, названный, как ни странно, ID3v2. Такая метка позволяет хранить данные произвольной длины и размещается она в самом начале файла, в то время как старые метаданные ID3v1 всё так же добавляются в конец файла для поддержки старых проигрывателей. Общий размер контейнера метаданных может составлять до 256Мб, и такой размер просто идеально подходит для радиостанций и сумасшедших парней, вроде меня. В метке ID3v2 каждый блок информации хранится во фрейме, у которого есть свой идентификатор. В ранних версиях ID3v2 длина иденти-фикатора фрейма равнялась трём символам, а последняя версия (ID3v2.4) использует уже четыре символа.

Раньше, когда ещё не сущес-твовало удобных библиотек для работы с ID3-метками, нам пришлось бы открывать файл в бинарном режиме и ковыряться в нём в поисках нужной инфор-мации, что заняло бы уйму вре-мени. К счастью, сейчас таких библиотек создано достаточ-ное количество, и мы восполь-зуемся одной из них, которая называется Mutagen. Вам следует открыть Synaptic и установить пакет python-mutagen. Если при желании задать «ID3» в поиске Synaptic, то найдётся более 90 пакетов (в версии Karmic). Но если добавить к поиску «Python», то отобразят-ся восемь. У каждого пакета есть свои плюсы и минусы. Для нашего проекта мы будем ис-пользовать Mutagen, а осталь-ные я предлагаю вам изучить самостоятельно.

Как только Mutagen установ-лен, приступаем к программи-рованию.

Создайте новый проект и назовите его «mCat». Начнём с подключения модулей.

from mutagen.mp3 import MP3

import os

from os.path import join,getsize,exists

import sys

import apsw

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

def MakeDataBase():pass
def S2HMS(t):pass
def WalkThePath(musicpath):pass
def error(message):pass
def main():pass
def usage():pass

Ага, что-то новенькое. Теперь у нас есть две функции: main и usage. Давайте добавим ещё кое-что и после этого обсудим, для чего они нужны.

codeif __name__ == '__main__':main()

Это ещё что? А это такой специальный приём, который позволяет использовать наш код одновременно и как самос-тоятельное приложение, и как подключаемый модуль. Эту часть можно озвучить так: «Если файл вызван как самосто-ятельное приложение, то вызы-ваем функцию main. В против-ном случае позволим внешнему приложению импортировать наши функции и вызывать их напрямую.»

Далее мы реализуем функцию usage. Её полный код приведён ниже.

Здесь мы организуем вывод сообщения на экран пользова-теля в случае, если программа была запущена без необходи-мого для работы параметра. Обратите внимание, что мы используем «\n» для перехода на новую строку, «\t» для вставки табуляции и «{0}» для подстановки имени приложения из переменной sys.argv[0]. После этого мы выводим сообще-ние на экран с помощью функ-ции error и выходим из приложе-ния (sys.exit(1)).

Теперь давайте создадим функцию error.

def error(message):
print >> sys.stderr, str(message)

Тут мы используем операцию перенаправления («»»). С по-мощью функции print мы сообщаем интерпретатору Python о нашем намерении вывести данные на устройство вывода (например, терминал, в котором работает наша программа). Для обычного вывода негласно используется поток stdout, а для вывода информации об ошибках — stderr, который тоже является терминалом. В нашей програм-ме мы перенаправляем вывод в поток stderr.

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

Так же, как и в прошлый раз, для работы с базой мы создаём две глобальные переменные: connection и cursor. Затем с помощью sys.argv проверяем параметры командной строки. Нам должны быть переданы два параметра: имя нашего приложения (передаётся систе-мой автоматически) и путь к каталогу с MP3-файлами. Если эти два параметра не переда-ны, то вызываем функцию usage, которая выводит справку о программе на экран и завер-шает работу. А если переданы, то мы оказываемся в другой ветке оператора IF, где мы помещаем значение второго параметра в переменную StartFolder. Обратите внимание, что если в пути до каталога встречаются знаки пробела (например /mnt/musicmain/Adult Contemporary), то часть строки после пробела будет считаться уже следующим параметром, поэтому всегда заключайте такие параметры командной строки в кавычки. Далее мы устанавливаем соединение и курсор, создаём базу данных, выполняем необходимую работу с помощью функции WalkThePath, закрываем курсор и соединение и в конце сообщаем пользователю об успешном окончании работы. Полный код функции WalkThePath доступен по адресу http://pastebin.com/CegsAXjW.

Сперва мы сбрасываем все три счётчика, с помощью кото-рых мы будем отслеживать ход процесса. Затем открываем файл, в который будем записы-вать сообщения об ошибках, если они произойдут. После этого выполняем рекурсивный обход переданного в качестве параметра каталога, начиная с самого каталога и «заглядывая» во все внутренние каталоги в поисках файлов с расширением «.mp3». Чтобы вести подсчёт количества данных, которые мы уже обработали, мы на каждом шаге увеличиваем счётчики каталогов и файлов. Далее мы обрабатываем каждый найден-ный файл. Сначала сбрасываем локальные переменные, в кото-рых хранится информация о каждой песне. Затем, с помощью функции join (из модуля os.path) мы формируем полное имя файла, чтобы Mutagen смог его найти, пере-даём это имя в класс MP3 и получаем объект «audio». Далее мы проходим по всем ID3-меткам в этом файле и сохраняем значения интересующих нас меток во временные перемен-ные. Это позволяет нам свести ошибки к минимуму. Обратите внимание на часть кода, кото-рая обрабатывает номер песни. Mutagen может вернуть номер песни как в виде числа, так и в виде строки «4/18», и даже в виде массива _trk или же вовсе не вернуть ничего. Поэтому для обработки возможных ошибок мы используем блок try/except. Теперь посмотрите, каким образом мы заносим значения в базу данных. Мы это делаем несколько иначе по сравнению с прошлым разом. В принципе, мы создаём точно такой же SQL-запрос, но вместо значений ставим «?», а сами значения подставляем уже в вызов cursor.statement. Веб-сайт ASPW утверждает, что это наилучший способ передачи данных, и я с ними вполне согласен. В конце мы обрабатываем все возмож-ные ошибки. В основном это ошибки типов TypeError или ValueError, которые могут возникнуть из-за символов Unicode, которые не получилось обработать. И ещё, обратите внимание на наш хитрый способ форматирования строк. Вместо символа подстановки «%» мы используем «{0}». Этот способ является частью спецификации Python версии 3.0 и выше, и общий синтаксис такой подста-новки выглядит следующим образом:

print('String that will be printed with {0} 
number of statements'.format(replacement values))

В вызовах функций efile.writelines, помимо нового способа, мы так же используем и старый способ.

В конце стоит взглянуть на функцию S2HMS. Эта функция преобразовывает длину песни, которую Mutagen возвращает как количество секунд, в один из двух форматов: либо в строку формата «Часы:Минуты: Секунды», либо в строку форма-та «Минуты:Секунды». Посмот-рите на выражения возврата значения (return). Мы снова используем синтаксис форма-тирования Python 3.x, но привно-сим кое-что новое. У нас исполь-зованы три стандартных выра-жения подстановки. Но что это за «:02n» в конце первого и второго выражений? Это нужно для того, чтобы указать, что мы хотим дополнить указанные строки ведущими нулями таким образом, чтобы в случае если, например, длина песни состав-ляет 2 минуты и 4 секунды, то возвращаемая строка имела бы вид «2:04», а не «2:4».

Полный исходный код нашей программы доступен по ссылке http://pastebin.com/rFf4Gm7E.

Советую вам поискать в интернете подробную инфор-мацию о библиотеке Mutagen. Список её возможностей далеко не ограничен одними только MP3-файлами.

Коды

def usage():
    message = (
       '==============================================\n'       
	'mCat — находит все *.mp3 файлы в указанной и вложенных папках,\n'
	'\tчитает id3-метки и записывает информацию в базу данных SQLite.\n\n'
	'Синтаксис:\n'
	'\t{0} <имя_папки>\n'
	'\t где <имя_папки> — путь к вашим MP3-файлам.\n\n'
	'Автор: Грег Уолтерс\n'
	'Для Full Circle Magazine\n'
       '==============================================\n'
       ).format(sys.argv[0])
    error(message)
    sys.exit(1)
def main():
    global connection
    global cursor
    #----------------------------------------------
    if len(sys.argv) != 2:
        usage()
    else:
        StartFolder = sys.argv[1]
        if not exists(StartFolder): # Из os.path
            print('Путь {0} не найден… Завершение работы').format(StartFolder)
            sys.exit(1)
        else:
            print('Работаем в каталоге {0}:').format(StartFolder)            
	   # Создаём соединение и курсор.
        connection=apsw.Connection("mCat.db3")
        cursor=connection.cursor()
	  # Создаём базу данных, если она ёще не 	          существует…
        MakeDataBase()
	  # Выполняем основную работу…
        WalkThePath(StartFolder)
	  # Закрываем курсор и соединение…
        cursor.close()
        connection.close()
	  # Сообщим, что работа завершена…
	  print("ГОТОВО!")