Сортируем MP3

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

Поскольку при этом MP3-файлов накапливается в одной папке довольно много, то я решил, что стоит написать shell-скрипт, который бы их рассортировывал по папкам согласно классической схеме: верхний каталог - по тегу Artist, а вложенные - по тегу Album. И так как у большинства артистов набирается всего по несколько файлов от каждого, то раскладывать файлы по альбомам я решил сделать опцией.

Вот, что получилось в результате.

Сперва я написал функцию has_option(), которая проверяет, есть ли в параметрах вызова скрипта та или иная опция:

function has_option() # locate script argument

{

result=no

for op in $1 ; do

if [ "x$op" = "x$2" ] ; then # short option

result=yes

return

fi

if [ "x$op" = "x$3" ] ; then # long option

result=yes

return

fi

done

}

Функция принимает три аргумента - строку с ключами ($1), короткий ключ ($2) и длинный ключ ($3). Например, стандартный ключ запроса подсказки может быть представлен как коротким ключом -h, так и длинным --help. Вот функция has_option() может проверять как наличие короткого ключа, так и длинного. При этом она присваивает переменной $result значение либо yes, либо no.

Следующая функция, opts_albums(), проверяет, задан ли ключ -a (сортировать по альбомам), и если да, то присваивает переменной $albums значение либо yes, либо no:

function opts_albums()

{

albums=no

has_option "$*" -a

if [ $result = "yes" ] ; then

albums=yes

fi

}

Далее я просто вызвал эту функцию:

opts_albums "$*"

Чтобы особо не утруждать себя, какой программой читать теги в MP3-файлах, я решил брать эту информацию из вывода mpg123, и обрабатывать с помощью awk. Чтобы mpg123 не проигрывал файл, я запускаю его с ключом -t (тестирование), а вывод с stderr перенаправляю в файл, чтобы не декодировать MP3-шник два раза:

mpg123 -t $fn 2>${fn}'.txt'

Затем я составил два почти идентичных скрипта для awk с той лишь разницей, что один выполняется для тега Artist, а другой - для тега Album:

artist_script='/Artist:/ {

str = $0;

n = index(str, "Artist:");

an = substr(str, n+7);

sub(/^[ \t]+/, "", an);

sub(/[ \t]+$/, "", an);

gsub(/[ \t\n\+]/, "_", an);

gsub(/[^0-9A-Za-z_\-]/, "q", an);

print an

}'

album_script='/Album:/ {

str = $0;

n = index(str, "Album:");

an = substr(str, n+6);

sub(/^[ \t]+/, "", an);

sub(/[ \t]+$/, "", an);

gsub(/[ \t\n\+]/, "_", an);

gsub(/[^0-9A-Za-z_\-]/, "q", an);

print an

}'

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

После этого я написал простой цикл, в котором обрабатывается вывод команды ls *.mp3.

Для использования скрипта я поместил его в каталог $HOME/bin. Чтобы рассортировать MP3-шки с его помощью, я перехожу в папку с MP3-шками, открываю эмулятор терминала и вызываю в ней свой скрипт. Скрипт создаёт подкаталоги по именам артистов, а если требуется, то и по названиям альбомов, и копирует в эти каталоги соответствующие файлы.

Таким образом, на каждой итерации цикла получается имя каталога для артиста, проверяется, есть ли такой файл, и если есть, то каталог ли это, и если да, то задан ли ключ -a, и если да, то тоже самое - для названия альбома:

for fn in `ls *.mp3` ; do

echo "File: $fn"

dfn=$fn'.txt'

mpg123 -t $fn 2>$dfn

dn=`cat $dfn | awk "$artist_script"`

if [ "x$dn" != "x" ] ; then

echo " Artist directory: $dn"

if [ ! -e $dn ] ; then

mkdir $dn ;

fi

if [ -d $dn ] ; then

nfn=${dn}/$fn

if [ $albums = "yes" ] ; then

adn=`cat $dfn | awk "$album_script"`

if [ "x$adn" != "x" ] ; then

echo " Album directory: $adn"

if [ ! -e ${dn}/$adn ] ; then

mkdir ${dn}/$adn ;

fi

if [ -d ${dn}/$adn ] ; then

nfn=${dn}/${adn}/$fn ;

fi

fi

fi

mv $fn $nfn ;

fi

fi

rm $dfn

done

Выглядит жутковато, но это потому, что лень было дополнительные переменные объявлять в целях повышения читаемости кода. Скачать скрипт.

Автор: Андрей Шаройко <vanyamboe@gmail.com>