Вызвать exiftool из скрипта Python?

Я хочу использовать exiftool для сканирования тегов EXIF ​​из моих фотографий и видео. Это исполняемый файл Perl. Каков наилучший способ взаимодействия с этим? Существуют ли какие-либо библиотеки Python для этого? Или я должен напрямую вызывать исполняемый файл и анализировать вывод? (Последнее кажется грязным.) Спасибо.

Причина, по которой я спрашиваю, заключается в том, что в настоящее время я использую pyexiv2, который не поддерживает видео. exiftool Perl имеет очень широкую поддержку изображений и видео, и я хотел бы использовать его.


person ensnare    schedule 09.04.2012    source источник


Ответы (1)


Чтобы не запускать новый процесс для каждого изображения, следует запускать exiftool с помощью -stay_open флаг. Затем вы можете отправлять команды процессу через стандартный ввод и читать вывод на стандартный вывод. ExifTool поддерживает вывод JSON, что, вероятно, является лучшим вариантом для чтения метаданных.

Вот простой класс, который запускает процесс exiftool и содержит метод execute() для отправки команд этому процессу. Я также включил get_metadata() для чтения метаданных в формате JSON:

import subprocess
import os
import json

class ExifTool(object):

    sentinel = "{ready}\n"

    def __init__(self, executable="/usr/bin/exiftool"):
        self.executable = executable

    def __enter__(self):
        self.process = subprocess.Popen(
            [self.executable, "-stay_open", "True",  "-@", "-"],
            stdin=subprocess.PIPE, stdout=subprocess.PIPE)
        return self

    def  __exit__(self, exc_type, exc_value, traceback):
        self.process.stdin.write("-stay_open\nFalse\n")
        self.process.stdin.flush()

    def execute(self, *args):
        args = args + ("-execute\n",)
        self.process.stdin.write(str.join("\n", args))
        self.process.stdin.flush()
        output = ""
        fd = self.process.stdout.fileno()
        while not output.endswith(self.sentinel):
            output += os.read(fd, 4096)
        return output[:-len(self.sentinel)]

    def get_metadata(self, *filenames):
        return json.loads(self.execute("-G", "-j", "-n", *filenames))

Этот класс написан как диспетчер контекста, чтобы обеспечить выход из процесса, если вы закончите. Вы можете использовать его как

with ExifTool() as e:
    metadata = e.get_metadata(*filenames)

РЕДАКТИРОВАТЬ для python 3: чтобы это работало в python 3, необходимы два небольших изменения. Первый является дополнительным аргументом для subprocess.Popen:

self.process = subprocess.Popen(
         [self.executable, "-stay_open", "True",  "-@", "-"],
         universal_newlines=True,
         stdin=subprocess.PIPE, stdout=subprocess.PIPE)

Во-вторых, вам нужно декодировать серию байтов, возвращаемую os.read():

output += os.read(fd, 4096).decode('utf-8')

РЕДАКТИРОВАТЬ для Windows: чтобы это работало в Windows, sentinel необходимо изменить на "{ready}\r\n", т.е.

sentinel = "{ready}\r\n"

В противном случае программа зависнет, потому что цикл while внутри execute() не остановится.

person Sven Marnach    schedule 09.04.2012
comment
Спасибо, это было очень полезно. Как мне передать команды exiftool, чтобы исполняемый файл оставался открытым? Если я помещу исполняемый файл в функцию с именем parseImage(), не будет ли он открываться каждый раз при вызове функции? - person ensnare; 09.04.2012
comment
@ensnare: я включил немного кода. Я улучшу код через несколько минут. - person Sven Marnach; 09.04.2012
comment
Да, это здорово. Большое спасибо. - person ensnare; 09.04.2012
comment
Почему низкоуровневый os вызывает? - person Taymon; 09.04.2012
comment
@Taymon: Это был единственный способ, которым я заработал, не останавливаясь на первом EOF. У вас есть лучшее предложение? - person Sven Marnach; 09.04.2012
comment
Тестирую это сейчас, но я продолжаю получать ошибку AttributeError: объект ExifTool не имеет атрибута «процесс» - есть идеи, что это может означать? Спасибо. - person ensnare; 09.04.2012
comment
@ensnare: это означает, что вы не использовали ExifTool, как показано в примере. Атрибут process создается только при входе в контекст. - person Sven Marnach; 09.04.2012
comment
Понял - спасибо, вы правы. Как я могу использовать это из класса, в котором функция scanImage() вызывается для каждого отдельного изображения? - person ensnare; 09.04.2012
comment
@ensnare: это зависит от дизайна вашей программы. Используйте класс, замыкание или даже глобальную переменную для хранения экземпляра ExifTool. - person Sven Marnach; 09.04.2012
comment
Как я могу отправить список имен файлов в функцию get_metadata()? - person ensnare; 09.04.2012
comment
@ensnare: Не совсем с текущим кодом. Чем это должно быть полезно? - person Sven Marnach; 09.04.2012
comment
Я пытаюсь понять переменную *filenames. Как бы вы предоставили изображения для этого? Может быть, *filenames вводит меня в заблуждение, и переменная должна называться *file? - person ensnare; 09.04.2012
comment
давайте продолжим это обсуждение в чате - person ensnare; 09.04.2012
comment
Большое спасибо за это. Я многому научился, и другие оболочки, похоже, не заботятся о том, чтобы процесс оставался открытым. Я предложил редактирование для совместимости с Python 3, я надеюсь, что этот вид могильных копаний в порядке в stackoverflow. - person imsodin; 15.06.2016
comment
Так стыдно. После долгого чтения я заставил его работать сам, и теперь я вижу, что вы уже создали версию, совместимую с python3, и разместили ее на github... - извините. - person imsodin; 15.06.2016
comment
@Gringo Да, смотрите мой комментарий к вопросу. - person Sven Marnach; 15.02.2019
comment
Круто, возможно, захочется сделать его более заметным и подчеркнуть, что пакет PyPi должен быть установлен с помощью pip для простоты использования. Ваше здоровье, - person Gringo Suave; 15.02.2019