Пишем на Питоне конвертер mp3 в opus для Linux, сохраняем картинку

newbie

Опубликован:  2020-03-14T09:22:31.928232Z
Отредактирован:  2020-03-14T09:18:44.388636Z
3400
Продолжаем пошаговую разработку конвертера mp3pus. В этом обзоре рассмотрим технологию переноса изображения (front cover) из метаданных файла mp3 в метаданные файла opus в процессе кодирования. Кроме этого, продемонстрируем экспорт картинки из файла изображения средствами opusenc без дополнительного программирования рутины.

1. В предыдущих сериях

Конвертер mp3pus - консольная утилита для получения группы файлов формата opus в заданном каталоге из группы файлов формата mp3 заданного каталога файловой системы. В блоге демонстрируется пошаговая разработка этого конвертера на языке Python3 в рабочем окружении Debian buster. Все посвященные mp3pus статьи можно отфильтровать по одноимённой метке mp3pus.

На текущий момент mp3pus имеет git-репозиторий и сценарий установки, таким образом программа доступна для скачивания и установки, и она будет работать на любой системе с ядром linux. Разрабатывается этот конвертер под свободной лицензией GNU GPLv.3.

В этом обзоре рассмотрим извлечение картинки (front cover) из файла mp3, если она имеется в наличии, и сохранение этой картинки в метаданных файла opus. Кроме этого, рассмотрим, как в рамках исполнения mp3pus можно сохранить изображение из файла (jpeg) в метаданных файла opus.

Медленно но верно mp3pus движется к своему первому релизу...

2. Экспорт изображения в метаданные opus средствами opusenc

Поскольку mp3pus задумывался и создаётся как универсальное средство получения файлов opus из файлов mp3, будет логично и правильно, если программа даст возможность сохранять не только текстовые метаданные исходных файлов, но и изображение - front cover, которое практически всегда имеется в файлах mp3, полученных из внешних источников. Я не очень люблю, когда в метаданных медиа-файлов хранится слишком много изображений, но сохранить front cover альбома имеет смысл.

Достаточно часто вместе с файлами mp3 в составе альбома можно увидеть и отдельные файлы изображений, в том числе и front cover. Посмотрим на оглавление каталога одного из mp3-альбомов, файлы которого я использую для отладки и тестирования своей программы.

giKUOC6ZBm.png

Файл Обложка.jpg - картинка (front cover). На текущий момент mp3pus способен без особого труда экспортировать эту картинку в полученные в результате перекодирования файлы opus, и делается это очень просто средствами opusenc.

mp3pus -i . -d ../opus/ -o "--picture 3||'front cover'||Обложка.jpg"

Да, в mp3pus предусмотрена возможность передавать opusenc любые поддерживаемые им опции, поэтому достаточно правильно указать файл изображения и соответствующим образом задать энкодеру ключ --picture.

XOYJRVaDg4.png

Давайте посмотрим на метаданные первого файла альбома.

4jKFYF1fRo.png

В медиа проигрывателе файл будет иметь соответствующий вид.

UnKgYQYLTZ.png

Шикардос..! Одной командой и без особых усилий, в итоге весь альбом имеет одну картинку в качестве front cover. Но!

  1. Что делать, если файл изображения в каталоге с mp3-файлами отсутствует?
  2. Что делать, если каждый mp3-файл имеет собственную вшитую картинку и нужно сохранить именно её?

Собственно, ничего сложного в задаче нет, просто нужно запрограммировать дополнительную рутину - извлекающие картинку действия. Этим и займёмся далее...

3. Дополнительная логика mp3pus

Итак, мне нужно сохранить вшитую в mp3-файл картинку, при этом я должен предусмотреть вариант исполнения mp3pus, при котором пользователь не хочет сохранять вшитую картинку. Это значит, что программе нужен дополнительный аргумент командной строки. Открываю файл mp3pus/main.py и в функцию parse_args дописываю новый атрибут.

def parse_args(version):
    ...
    args.add_argument(
        '-p',
        action='store_true',
        dest='picture',
        default=False,
        help='get the picture if there is one')
    return args.parse_args()

aMLmrWWZtp.png

Таким образом пользователь mp3pus получает возможность указать программе, желает ли он сохранить вшитую в mp3 картинку в файле opus.

iEpvcvKF7T.png

Теперь давайте взглянем на два mp3-файла из разных источников и посмотрим, как в них хранится front cover.

iODt9K92eJ.png

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

Второй файл хранит картинку в теге с несколько иным именем.

kTc0nxFGzd.png

Разница заключается в том, что соответствующий тег первого файла имеет дескриптор - front cover, а вот соответствующий тег второго файла такого дескриптора не имеет, и это отражается на имени тега с картинкой. Это следует учитывать. Извлекать картинку я буду в каталог с временными файлами - /tmp - этот каталог теряет своё содержимое каждый раз при перезагрузке компьютера, то есть файл изображения, с которым я буду работать, будет в свою очередь временным файлом. Открываю файл mp3pus/convert/convert.py и дописываю в дандеринит класса Target два дополнительных свойства.

class Target:
    def __init__(self, filename, out_dir):
        ...
        self.TMP = '/tmp/mp3pus.tmp.picture'
        self.picture = None

ZJ8HezBWgh.png

Извлекать и сохранять картинку во временный файл я буду с помощью дополнительного метода класса Target - _get_picture.

class Target:
    ...

    def _get_picture(self, item):
        for key in item.keys():
            if 'APIC:' in key:
                pic = item.get(key)
                if pic and pic.type.real == 3:
                    f = open(self.TMP, 'wb')
                    f.write(pic.data)
                    f.close()
                    return " --picture 3||'front cover'||{}".format(
                        self.TMP)

n8JF9Bt4G4.png

Вызов этого метода я встрою в общую рутину в методе get_metadata. Теперь у этого метода появится новый аргумент, сигнализирующий, нужно ли выполнять действие.

class Target:
    ...

    def get_metadata(self, picture=False):
        try:
            ...
            if picture:
                self.picture = self._get_picture(item)
        except MutagenError:
            ...

bf6ZOlO75r.png

Поскольку у класса появился новый атрибут, его нужно аккуратно встроить в команду opusenc. Редактирую соответствующим образом метод _get_opus.

class Target:
    ...

    def _get_opus(self, options):
        cmd = 'opusenc {0}{1}{2}{3}{4}{5}{6}{7}{8}{9} - "{10}"'.format(
            options or '',
            self.album or '',
            self.genre or '',
            self.title or '',
            self.artist or '',
            self.tracknumber or '',
            self.tracktotal or '',
            self.date or '',
            self.comment or '',
            self.picture or '',
            self.opus)
        return shlex.split(cmd)

JMMOPGyyX4.png

Чтобы дополнительная логика заработала при запуске программы, возвращаюсь в файл mp3pus/main.py и корректирую функцию start_the_process, новый вариант будет выглядеть так:

def start_the_process(arguments):
    if not os.path.exists(arguments.input_dir):
        raise OSError('{} does not exist'.format(arguments.input_dir))
    if not os.path.exists(arguments.output_dir):
        raise OSError('{} does not exist'.format(arguments.output_dir))
    template = os.path.join(os.path.realpath(arguments.input_dir), '*.mp3')
    from .convert.convert import Target
    target = None                     #### new
    for each in sorted(glob.glob(template)):
        if not os.path.exists(each):
            print('{0} - not found, passed'.format(os.path.basename(each)))
        else:
            target = Target(each, arguments.output_dir)
            target.get_metadata(arguments.picture)         #### new
            if target.is_mp3:
                print(os.path.basename(target.target), end=' ')
                print('->', end=' ')
                print(os.path.basename(target.opus))
                target.convert(arguments.enc_options)
    if target and os.path.exists(target.TMP):
        os.remove(target.TMP)                          #### new

ndfM4pGcYJ.png

Всё... момент истины! Лезу в консоль и запускаю mp3pus с новым ключом.

i7Srk9i1uv.png

Процесс пошёл, дожидаюсь его завершения и смотрю на метаданные любого из полученных файлов opus.

KGTDpb648f.png

В плеере весь альбом будет выглядеть так.

LyHnE7Vkp1.png

Шикардос..! Попробовал бы этот ларчик не открыться. Заявленная цель обзора полностью достигнута...

4. Заключение

Исходный текст программы в текущем состоянии можно увидеть в моём профиле на github.com. В сущности mp3pus набрал необходимый и достаточный функционал, от релиза его отделяет решение одной нерешенной пока проблемы, а именно валидация переданных энкодеру опции ключом -o. В ближайшее время я сосредоточу своё внимание над решением этой проблемы, но описывать это решение дополнительно пока не планирую.

Комментарии: