Pull to refresh

iPod и Python: сортируем альбомы в хронологическом порядке

Reading time9 min
Views5.6K

Проблема


Иногда мне кажется, что Apple изо всех сил старается упростить свою продукцию. С одной стороны это замечательно, а с другой — весьма прискорбно. Ведь дополнительные настройки хоть и усложняют нашу жизнь в первые пару недель использования продукта (пока привыкнешь, разберешься), но зато в последствии позволяют подогнать его под себя и получить гаджет (или программу) своей мечты.

Album by YearЕсли Вы являетесь счастливым обладателем 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:
Sorting tab

Назначение этих полей я в документации не уточнял, но весь мой жизненный опыт подсказывает, что если поле 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
  1. #!usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # Created by KL-7
  4.  
  5. import logging
  6. import sys
  7. from os import path
  8.  
  9. from mutagen.easyid3 import EasyID3
  10. from mutagen.id3 import ID3NoHeaderError
  11.  
  12. # Количество успешно обработанных файлов
  13. totalfiles =  0
  14.  
  15.  
  16. # Вывод информации об ошибках
  17. def error(err):
  18.     # Настраиваем лог-файл для ошибок
  19.     logging.basicConfig(filename="error.log", level=logging.ERROR)
  20.     logging.error(err)
  21.  
  22.  
  23. # Получаем значение тега tagname из файла audio
  24. def gettag(audio, tagname):
  25.     taglist = audio.get(tagname, None)
  26.     if taglist:
  27.         return taglist[ 0]
  28.     else:
  29.         return ""
  30.  
  31.  
  32. # Заполняем тег TSOA
  33. def fill_tsoa(fpath):
  34.     audio = EasyID3(fpath)
  35.  
  36.     artist = gettag(audio, "artist")
  37.     if not artist:
  38.         error("Missing artist in %s" % fpath)
  39.  
  40.     album = gettag(audio, "album")
  41.     if not album:
  42.         error("Missing album in %s" % fpath)
  43.  
  44.     year = gettag(audio, "date")
  45.     if not year:
  46.         error("Missing year in %s" % fpath)
  47.  
  48.     # Формируем значение для тега TSOA
  49.     tsoa = "%s -- %s - %s" % (artist, year, album)
  50.  
  51.     # EasyID3 репамит названия тегов для большей наглядности.
  52.     # Тег TSOA в этих обозначениях называется albumsort.
  53.     audio["albumsort"] = tsoa
  54.     audio.save()
  55.  
  56.  
  57. def fill_rec(root):
  58.     # Эта функция будет вызваться в директории root и 
  59.     # в каждой ее поддиректории dirname.
  60.     # fnames - содержимое текущей директории dirname.
  61.     # Аргумент arg обязателен, но его мы не используем.
  62.     def process(arg, dirname, fnames):
  63.         for fname in fnames:
  64.             # Конвертируем русские названия в Unicode
  65.             fname = fname.decode("cp1251")
  66.             name, ext = path.splitext(fname)
  67.             fname = path.join(dirname, fname)
  68.             if path.isfile(fname) and ext.lower() == ".mp3":
  69.                 try:
  70.                     fill_tsoa(fname)
  71.                     global totalfiles
  72.                     totalfiles = totalfiles + 1
  73.                     print fname
  74.                 except ID3NoHeaderError:
  75.                     error("No tags in %s" % fname)
  76.  
  77.     # Запускаем рекурсивный обход из директории root
  78.     # с функциией process; None будет передан
  79.     # функции process первым параметром.
  80.     path.walk(root, process, None)
  81.  
  82.  
  83. if __name__ == "__main__":
  84.     # Если есть параметры, интерпретируем их как директории,
  85.     # которые надо обработать.
  86.     if sys.argv[1:]:
  87.         for dirname in sys.argv[1:]:
  88.             fill_rec(dirname)
  89.     # Если параметров нет, то работаем с текущей директорией.
  90.     else:
  91.         fill_rec(".")
  92.  
  93.     print "\nTotal files: %d" % totalfiles
  94.     raw_input();
  95.  


Можно скопировать скрипт в директорию с музыкой и запустить без параметров. В таком случае будут обработаны все mp3-файлы в текущей директории и во всех поддиректориях. А можно самостоятельно передать скрипту в виде параметров список нужных директорий (поддиректории будут обработаны и в этом случае).

Когда скрипт завершит работу (длится это не слишком долго), останется обновиться данные в библиотеке iTunes. Если эти файлы Вы еще не добавляли туда, то сейчас самое время. Если же вы редактировали теги в файлах, уже присутствующих в iTunes, то необходимо просто выделить их в окне iTunes, открыть Get Info через контекстное меню и, ничего там не редактируя, нажать Ok. iTunes просто обновит все теги из выделенных файлов.

AS IS


Несколько замечаний по поводу скрипта:
  1. Если Вы дорожите свой библиотекой, то возможно стоит сначала потестировать скрипт на нескольких файлах — мало ли что может произойти. У меня был back-up всей коллекции, поэтому я не волновался.
  2. Вполне возможно, что могут возникнуть проблемы при добавлении в тег TSOA слишком длинной строки (я не стал вчитываться в документацию по внутренней структуре тег-блока в mp3-файлах). Самая длинная строка в теге TSOA (с моим форматом %artist% — %year% — %album%), которую мне довелось встретить в своей библиотеке, содержала 93 символа. Тег успешно сохранился в файле и все у него было хорошо.
  3. Скрипт без проблем обработал 5000 файлов с англоязычными названиям. Русской музыки у меня в библиотеке не оказалось, но тест показал, что теги в файлы с кириллицей в названии успешно добавляются. Чтобы в консоль (cmd) названия русскоязычных файлов выводились в читабельном виде пришлось добавить декодирование названия (строка 58). Дело было под Windows, как под другими осями — не знаю. Поэтому если Вы не имеете дела с русскими названиями файлов, лучше пожалуй эту строку закомментировать.
  4. Немецкие и прочие названия файлов с диакритическими знаками лично у меня обрабатываться не захотели. Как решить проблему я пока не выяснил. Проблема в том, что файл называется, например, «Verführer.mp3», а скрипт получает название «Verfuhrer.mp3». Файла с таким именем в папке, ясное дело, нет. Опять-таки это все Windows. Лично у меня диакритические знаки встречаются в тегах (там юникод и все отлично), но в названиях файлов я их избегаю. Поэтому все прошло успешно.

Послесловие


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

На этом пока все. Буду рад, если кому-то этот скрипт окажется полезным или по крайней мере интересным.

PS
Кто-нибудь может подсказать, что побудило s-c.me добавить пробел перед нулем в 22ой строке? В исходнике его не было. Если же в html-е убрать в этом месте nbsp, то вместе с пробелом пропадает и 0. А это не хорошо.
Tags:
Hubs:
Total votes 37: ↑28 and ↓9+19
Comments28

Articles