Проблема
Иногда мне кажется, что Apple изо всех сил старается упростить свою продукцию. С одной стороны это замечательно, а с другой — весьма прискорбно. Ведь дополнительные настройки хоть и усложняют нашу жизнь в первые пару недель использования продукта (пока привыкнешь, разберешься), но зато в последствии позволяют подогнать его под себя и получить гаджет (или программу) своей мечты.
Если Вы являетесь счастливым обладателем iPod-a, то наверно заметили, что все альбомы на нем (именно на плеере) сортируются строго в алфавитном порядке. И никак иначе. И никаких Вам настроек и expert-модов. Сказали «в алфавитном», значит в алфавитном. Что примечательно — в iTunes такая кнопочка (Album by Year) есть. Но там она мне была меньше всего нужна.
К счастью, эта проблема (как и проблема добавления отдельно скачанных эпизодов в имеющийся iTunes-подкаст) имеет решение. Не столь простое, как хотелось бы, но зато весьма увлекательное.
Решаем в лоб
Совершенно простое, но не слишком элегантное решение приходит в первые в первые пару секунд. Просто для всех альбомов превращаем содержимое тега %album% в %year% — %album%, и проблема решена. И сортируется как надо, да еще и год можно прямо на iPod-e посмотреть (без такого рода подготовки год выпуска альбома на самом плеере не выяснить).
Возможно Вас устроит такой вариант, но мне он показался неудовлетворительным. Одно дело папка на диске (их я привык называю в формате %year% — %album%), а другое дело — нарядный альбом с albumart-ом на iPod-e. Ко всему прочему меня не интересует конкретный год, в котором альбом выпустили. Я лишь хочу знать, какие альбомы относятся к раннему творчеству той или иной группы, а какие были выпущены совсем недавно, чтобы видеть, как группа менялась со временем, как менялся ее стиль. Именно это и побудило меня на более детальное изучение проблемы и поиск ее решения.
iTunes
Нет идеальных программ. iTunes не исключение. И тем не менее это удобное и приятное в работе приложение. По крайней мере так я начал думать после нескольких месяцев использования, когда уже окончательно смирился с этой Apple-овской простотой. А когда выяснилось, что в iTunes есть то, что в определенной степени поможет решить проблему, я окончательно избавился от мыслей о поиске замены iTunes-у.
Он сказал: «Поехали!»
Вступления позади и наконец-то мы начинаем действовать. Выделяем все треки какого-нибудь альбома и открываем контекстное меню. Там мы обнаруживаем замечательный пункт Get Info, за которым скрывается вся информация о выбранных треках. В настоящий момент нас интересует вкладка Sorting:
Назначение этих полей я в документации не уточнял, но весь мой жизненный опыт подсказывает, что если поле Sort Album заполнено, то при сортировке альбомов в iTunes (если в колонке альбомов выбрано не «Album by Year», а просто «Album») в качестве ключа будет выступать именно оно (аналогично с остальными sort-полями). И, что самое главное, этот же ключ будет использоваться и при сортировке альбомов непосредственно на самом плеере. Это-то нам и надо. Вводим в указанное поле «2007 — Are You Listening» и радуемся, как у нас все складно вышло. Теперь мы имеем ровно один альбом, который сортируется (сам с собой) в хронологическом порядке =)
А впереди — вечность
Проблема казалось бы уже решена. А на самом деле все еще только начинается. Ведь выпустив плеер на n-ое количество гигабайт, в Apple должно быть просто не ожидали, что столько музыки можно купить в Apple Store (а другие источники в рассчет вероятно не брали). А может они не ожидали, что кому-то в один прекрасный день захочется отсортировать все альбомы по дате. Дело в том, что повторить указанную процедуру с остальными пятьюстами альбомами в своей фонотеке я просто не в состоянии. Как было сказано в недавней C# головоломке: «Never send a human to do a machine's job».
И тут на сцене появляется Python
Именно его мы и используем, чтобы обработать нашу библиотеку. Всю и сразу.
Как выяснилось, iTunes сохраняет значение поля "Sort Album" в теге TSOA mp3-файла (про остальные теги iTunes можно почитать тут). Зная это, мы можем соответствующим образом заполнить этот тег в наших файлах, а затем добавить их в iTunes-библиотеку.
Python библиотек для работы с id3 тегами оказалось не много, но и не мало. Мой выбор пал на mutagen (с другими вариантами можно ознакомиться тут).
Mutagen позволяет работать с метаданными достаточно большого количества аудио форматов (ASF, FLAC, M4A, Monkey's Audio, MP3, Musepack, Ogg FLAC, Ogg Speex, Ogg Theora и т.д.) Нас же интересуют именно id3 теги в mp3-файлах. Для манипулирования ими библиотека предоставляет два интерфейса: ID3 и EasyID3. Думаю разницу пояснять не надо. Я сначала поигрался с первым, но потом выяснилось, что и возможностей второго вполне достаточно.
Собственно код
Сразу оговорюсь, что мой опыт программирования на Python-e стремиться к бесконечности, но пока приблизительно равен нулю. Я старался написать все так, чтобы не стыдно было показать обществу, но не уверен, что получилось достаточно хорошо. Поэтому буду признателен за любые содержательные замечания, выраженные в дружественной форме.
Что же касается алгоритма, то он вполне очевиден: пробегаем по нужным файлам, достаем год выпуска и название альбома из тегов и соответствующим образом заполняем тег TSOA. Я пришел к выводу, что меня больше всего устроит формат %artist% — %year% — %album%. С таким значением тега TSOA альбомы будут сортироваться по исполнителю, затем по году, а затем по алфавиту (если вдруг в один прекрасный год Ваша любимая группа расстаралась более чем на один альбом). Python, говорят, обладает весьма дружественным синтаксисом, поэтому, даже если Вы с ним не знакомы, внести небольшие коррективы по своему вкусу в приведенный ниже скрипт проблем не составит.
В процессе обработки пяти тысяч треков может произойти многое, поэтому я добавил немного служебного вывода, чтобы наблюдать, как продвигается дело, и лог для вывода ошибок о нехватке тегов в файле. Как-никак добавлять в iTunes файл без исполнителя или альбома в тегах — гиблое дело, поэтому будет полезно выяснить, какие файлы у нас еще не готовы к миграции на iPod.
C docstring-ами я поленился, но комментариями код разбавил. Надеюсь это поможет сориентироваться тем, кто не сильно знаком с Python-ом.
Код:
Copy Source | Copy HTML
- #!usr/bin/env python
- # -*- coding: utf-8 -*-
- # Created by KL-7
-
- import logging
- import sys
- from os import path
-
- from mutagen.easyid3 import EasyID3
- from mutagen.id3 import ID3NoHeaderError
-
- # Количество успешно обработанных файлов
- totalfiles = 0
-
-
- # Вывод информации об ошибках
- def error(err):
- # Настраиваем лог-файл для ошибок
- logging.basicConfig(filename="error.log", level=logging.ERROR)
- logging.error(err)
-
-
- # Получаем значение тега tagname из файла audio
- def gettag(audio, tagname):
- taglist = audio.get(tagname, None)
- if taglist:
- return taglist[ 0]
- else:
- return ""
-
-
- # Заполняем тег TSOA
- def fill_tsoa(fpath):
- audio = EasyID3(fpath)
-
- artist = gettag(audio, "artist")
- if not artist:
- error("Missing artist in %s" % fpath)
-
- album = gettag(audio, "album")
- if not album:
- error("Missing album in %s" % fpath)
-
- year = gettag(audio, "date")
- if not year:
- error("Missing year in %s" % fpath)
-
- # Формируем значение для тега TSOA
- tsoa = "%s -- %s - %s" % (artist, year, album)
-
- # EasyID3 репамит названия тегов для большей наглядности.
- # Тег TSOA в этих обозначениях называется albumsort.
- audio["albumsort"] = tsoa
- audio.save()
-
-
- def fill_rec(root):
- # Эта функция будет вызваться в директории root и
- # в каждой ее поддиректории dirname.
- # fnames - содержимое текущей директории dirname.
- # Аргумент arg обязателен, но его мы не используем.
- def process(arg, dirname, fnames):
- for fname in fnames:
- # Конвертируем русские названия в Unicode
- fname = fname.decode("cp1251")
- name, ext = path.splitext(fname)
- fname = path.join(dirname, fname)
- if path.isfile(fname) and ext.lower() == ".mp3":
- try:
- fill_tsoa(fname)
- global totalfiles
- totalfiles = totalfiles + 1
- print fname
- except ID3NoHeaderError:
- error("No tags in %s" % fname)
-
- # Запускаем рекурсивный обход из директории root
- # с функциией process; None будет передан
- # функции process первым параметром.
- path.walk(root, process, None)
-
-
- if __name__ == "__main__":
- # Если есть параметры, интерпретируем их как директории,
- # которые надо обработать.
- if sys.argv[1:]:
- for dirname in sys.argv[1:]:
- fill_rec(dirname)
- # Если параметров нет, то работаем с текущей директорией.
- else:
- fill_rec(".")
-
- print "\nTotal files: %d" % totalfiles
- raw_input();
-
Можно скопировать скрипт в директорию с музыкой и запустить без параметров. В таком случае будут обработаны все mp3-файлы в текущей директории и во всех поддиректориях. А можно самостоятельно передать скрипту в виде параметров список нужных директорий (поддиректории будут обработаны и в этом случае).
Когда скрипт завершит работу (длится это не слишком долго), останется обновиться данные в библиотеке iTunes. Если эти файлы Вы еще не добавляли туда, то сейчас самое время. Если же вы редактировали теги в файлах, уже присутствующих в iTunes, то необходимо просто выделить их в окне iTunes, открыть Get Info через контекстное меню и, ничего там не редактируя, нажать Ok. iTunes просто обновит все теги из выделенных файлов.
AS IS
Несколько замечаний по поводу скрипта:
- Если Вы дорожите свой библиотекой, то возможно стоит сначала потестировать скрипт на нескольких файлах — мало ли что может произойти. У меня был back-up всей коллекции, поэтому я не волновался.
- Вполне возможно, что могут возникнуть проблемы при добавлении в тег TSOA слишком длинной строки (я не стал вчитываться в документацию по внутренней структуре тег-блока в mp3-файлах). Самая длинная строка в теге TSOA (с моим форматом %artist% — %year% — %album%), которую мне довелось встретить в своей библиотеке, содержала 93 символа. Тег успешно сохранился в файле и все у него было хорошо.
- Скрипт без проблем обработал 5000 файлов с англоязычными названиям. Русской музыки у меня в библиотеке не оказалось, но тест показал, что теги в файлы с кириллицей в названии успешно добавляются. Чтобы в консоль (cmd) названия русскоязычных файлов выводились в читабельном виде пришлось добавить декодирование названия (строка 58). Дело было под Windows, как под другими осями — не знаю. Поэтому если Вы не имеете дела с русскими названиями файлов, лучше пожалуй эту строку закомментировать.
- Немецкие и прочие названия файлов с диакритическими знаками лично у меня обрабатываться не захотели. Как решить проблему я пока не выяснил. Проблема в том, что файл называется, например, «Verführer.mp3», а скрипт получает название «Verfuhrer.mp3». Файла с таким именем в папке, ясное дело, нет. Опять-таки это все Windows. Лично у меня диакритические знаки встречаются в тегах (там юникод и все отлично), но в названиях файлов я их избегаю. Поэтому все прошло успешно.
Послесловие
Если будут еще какие-то замечания или проблемы — пишите. Если будут предложения, как решить проблемы с кодировками — пишите еще быстрее. Если честно, то у меня на всех системах и во всех языках всю мою сознательную жизнь постоянно возникали проблемы с кодировками. Может мы с ними слишком разные, а может мне просто стоит сесть и хорошенько с ними со всеми разобраться. Однажды я этим обязательно займусь.
На этом пока все. Буду рад, если кому-то этот скрипт окажется полезным или по крайней мере интересным.
PS
Кто-нибудь может подсказать, что побудило s-c.me добавить пробел перед нулем в 22ой строке? В исходнике его не было. Если же в html-е убрать в этом месте nbsp, то вместе с пробелом пропадает и 0. А это не хорошо.