Искаженный вывод при печати строк из FFProbe STDERR

Я пытаюсь создать простую функцию для обхода FFProbe, и большую часть данных можно получить правильно.

Проблема заключается в том, что при фактической печати строк в командной строке с использованием командной строки Windows и Git Bash для Windows вывод выглядит искаженным и неупорядоченным.

В некоторых песнях (в частности, в файле Imagine Dragons - Hit Parade_ Best of the Dance Music Charts\80 - Beazz - Lime (Extended Mix).flac) отсутствуют метаданные. Я не знаю почему, но словарь, который возвращает функция ниже, пуст.

FFProbe выводит свои результаты в stderr, которые могут быть переданы в subprocess.PIPE, декодированы и проанализированы. Я выбрал регулярное выражение для бита синтаксического анализа.

Это уменьшенная версия моего кода ниже, для вывода взгляните на Суть Github.

#! /usr/bin/env python3
# -*- coding: utf-8 -*-

import os

from glob import glob
from re import findall, MULTILINE
from subprocess import Popen, PIPE


def glob_from(path, ext):
    """Return glob from a directory."""
    working_dir = os.getcwd()
    os.chdir(path)

    file_paths = glob("**/*." + ext)

    os.chdir(working_dir)

    return file_paths


def media_metadata(file_path):
    """Use FFPROBE to get information about a media file."""
    stderr = Popen(("ffprobe", file_path), shell=True, stderr=PIPE).communicate()[1].decode()

    metadata = {}

    for match in findall(r"(\w+)\s+:\s(.+)$", stderr, MULTILINE):
        metadata[match[0].lower()] = match[1]

    return metadata


if __name__ == "__main__":
    base = "C:/Users/spike/Music/Deezloader"

    for file in glob_from(base, "flac"):
        meta = media_metadata(os.path.join(base, file))
        title_length = meta.get("title", file) + " - " + meta.get("length", "000")

        print(title_length)

Вывод Gist Вывод Raw

Я не понимаю, почему вывод (строки могут быть эффективно извлечены из шаблона регулярного выражения, однако вывод странно отформатирован при печати) выглядит неупорядоченным только при выводе на консоль с помощью функции print python. Неважно, как я строю строку для печати, конкатенации, аргументов с разделителями-запятыми и так далее.

Я заканчиваю тем, что сначала указываю длину песни, а затем название песни, но без пробела между ними. Черта почему-то свисает с конца. Основываясь на операторе печати в предыдущем коде, формат должен быть Title - 000 ({title} - {length}), но вывод больше похож на 000Title -. Почему?


person spikespaz    schedule 07.02.2018    source источник


Ответы (1)


Я решил это с помощью принятого ответа в моем связанном вопросе.

Я забыл о возвратной каретке в конце каждой строки. Приведены следующие решения:

  1. Use universal_newlines=True in the subprocess call.
    • stderr = Popen(("ffprobe", file_path), shell=True, stderr=PIPE, universal_newlines=True).communicate()[1]
  2. Удаление пробелов вокруг строки из stderr.

    • *.communicate()[1].decode().rstrip() to strip all whitespace at the end.
    • *.communicate()[1].decode().strip() чтобы зачистить все пространство вокруг.
    • *.communicate()[1].decode()[:-2], чтобы удалить последние два символа.
  3. Проглатывание \r в шаблоне регулярного выражения.

    • findall(r"(\w+)\s+:\s(.+)\r$", stderr, MULTILINE)

Все это очень полезно, однако я не использовал ни одно из этих предложений.

Я не знал, что FFPROBE предлагает вывод JSON в STDOUT, но это так. Код для этого приведен ниже.

#! /usr/bin/env python3
# -*- coding: utf-8 -*-
from json import loads
from subprocess import check_output, DEVNULL, PIPE


def arg_builder(args, kwargs, defaults={}):
    """Build arguments from `args` and `kwargs` in a shell-lexical manner."""
    for key, val in defaults.items():
        kwargs[key] = kwargs.get(key, val)

    args = list(args)

    for arg, val in kwargs.items():
        if isinstance(val, bool):
            if val:
                args.append("-" + arg)
        else:
            args.extend(("-" + arg, val))

    return args


def run_ffprobe(file_path, *args, **kwargs):
    """Use FFPROBE to get information about a media file."""
    return loads(check_output(("ffprobe", arg_builder(args, kwargs, defaults={"show_format": True}),
                               "-of", "json", file_path), shell=True, stderr=DEVNULL))

Вы также можете использовать arg_builder(). Он не идеален, но достаточно хорош для простых команд оболочки. Он не сделан для защиты от идиотов, он был написан с несколькими дырками, предполагая, что программист ничего не сломает.

person spikespaz    schedule 09.02.2018