Ускорьте nautilus Python-расширения для чтения Exif изображения - PullRequest
3 голосов
/ 12 марта 2012

Я написал расширение Nautilus, которое считывает метаданные изображения (выполнение exiftool), но когда я открываю папки с большим количеством файлов, он действительно замедляет работу файлового менеджера и зависает, пока не завершит чтение данных файла.

Есть ли способ заставить Наутилуса продолжать работать, пока он запускает мое расширение?Возможно, данные Exif могли постепенно появляться в столбцах, пока я продолжаю свою работу.

#!/usr/bin/python

# Richiede:
# nautilus-python
# exiftool
# gconf-python

# Versione 0.15

import gobject
import nautilus
from subprocess import Popen, PIPE
from urllib import unquote
import gconf

def getexiftool(filename):
    options = '-fast2 -f -m -q -q -s3 -ExifIFD:DateTimeOriginal -IFD0:Software -ExifIFD:Flash -Composite:ImageSize -IFD0:Model'
    exiftool=Popen(['/usr/bin/exiftool'] + options.split() + [filename],stdout=PIPE,stderr=PIPE)
    #'-Nikon:ShutterCount' non utilizzabile con l'argomento -fast2
    output,errors=exiftool.communicate()
    return output.split('\n')

class ColumnExtension(nautilus.ColumnProvider, nautilus.InfoProvider, gobject.GObject):
    def __init__(self):
        pass

    def get_columns(self):
        return (
            nautilus.Column("NautilusPython::ExifIFD:DateTimeOriginal","ExifIFD:DateTimeOriginal","Data (ExifIFD)","Data di scatto"),
            nautilus.Column("NautilusPython::IFD0:Software","IFD0:Software","Software (IFD0)","Software utilizzato"),
            nautilus.Column("NautilusPython::ExifIFD:Flash","ExifIFD:Flash","Flash (ExifIFD)","Modalit\u00e0 del flash"),
            nautilus.Column("NautilusPython::Composite:ImageSize","Composite:ImageSize","Risoluzione (Exif)","Risoluzione dell'immagine"),
            nautilus.Column("NautilusPython::IFD0:Model","IFD0:Model","Fotocamera (IFD0)","Modello fotocamera"),
            #nautilus.Column("NautilusPython::Nikon:ShutterCount","Nikon:ShutterCount","Contatore scatti (Nikon)","Numero di scatti effettuati dalla macchina a questo file"),
            nautilus.Column("NautilusPython::Mp","Mp","Megapixel (Exif)","Dimensione dell'immagine in megapixel"),
        )

    def update_file_info_full(self, provider, handle, closure, file):
        client = gconf.client_get_default()

        if not client.get_bool('/apps/nautilus/nautilus-metadata/enable'):
            client.set_bool('/apps/nautilus/nautilus-metadata/enable',0)
            return

        if file.get_uri_scheme() != 'file':
            return

        if file.get_mime_type() in ('image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/x-nikon-nef', 'image/x-xcf', 'image/vnd.adobe.photoshop'):
            gobject.timeout_add_seconds(1, self.update_exif, provider, handle, closure, file)
            return Nautilus.OperationResult.IN_PROGRESS

        file.add_string_attribute('ExifIFD:DateTimeOriginal','')
        file.add_string_attribute('IFD0:Software','')
        file.add_string_attribute('ExifIFD:Flash','')
        file.add_string_attribute('Composite:ImageSize','')
        file.add_string_attribute('IFD0:Model','')
        file.add_string_attribute('Nikon:ShutterCount','')
        file.add_string_attribute('Mp','')

        return Nautilus.OperationResult.COMPLETE

    def update_exif(self, provider, handle, closure, file):
        filename = unquote(file.get_uri()[7:])

        data = getexiftool(filename)

        file.add_string_attribute('ExifIFD:DateTimeOriginal',data[0].replace(':','-',2))
        file.add_string_attribute('IFD0:Software',data[1])
        file.add_string_attribute('ExifIFD:Flash',data[2])
        file.add_string_attribute('Composite:ImageSize',data[3])
        file.add_string_attribute('IFD0:Model',data[4])
        #file.add_string_attribute('Nikon:ShutterCount',data[5])
        width, height = data[3].split('x')
        mp = float(width) * float(height) / 1000000
        mp = "%.2f" % mp
        file.add_string_attribute('Mp',str(mp) + ' Mp')

        Nautilus.info_provider_update_complete_invoke(closure, provider, handle, Nautilus.OperationResult.COMPLETE)

        return false

Ответы [ 3 ]

2 голосов
/ 06 мая 2012

Это происходит потому, что вы вызываете update_file_info , которая является частью системы асинхронного ввода-вывода Nautilus.Следовательно, он блокирует nautilus, если операции выполняются недостаточно быстро.

В вашем случае это усугубляется тем, что вы вызываете внешнюю программу, и это дорогостоящая операция.Обратите внимание, что update_file_info вызывается один раз для каждого файла.Если у вас есть 100 файлов, то вы будете вызывать внешнюю программу 100 раз, и Nautilus придется ждать каждого из них, прежде чем обрабатывать следующий.

Поскольку доступны nautilus-python 0.7 * update_file_info_full и cancel_update , что позволяет программировать асинхронные вызовы.Вы можете проверить документацию для Nautilus 0.7 для получения более подробной информации .

Стоит упомянуть, что это было ограничением только для nautilus-python, который ранее не предоставлял те методы, которые доступны в C.

РЕДАКТИРОВАТЬ : добавлена ​​пара примеров.

Хитрость заключается в том, чтобы максимально ускорить процесс или сделать его асинхронным.

Пример 1. Вызоввнешняя программа

Используя упрощенную версию вашего кода, мы выполняем асинхронно, используя GObject.timeout_add_seconds in update_file_info_full .

from gi.repository import Nautilus, GObject
from urllib import unquote
from subprocess import Popen, PIPE

def getexiftool(filename):
    options = '-fast2 -f -m -q -q -s3 -ExifIFD:DateTimeOriginal'
    exiftool = Popen(['/usr/bin/exiftool'] + options.split() + [filename],
                     stdout=PIPE, stderr=PIPE)
    output, errors = exiftool.communicate()
    return output.split('\n')

class MyExtension(Nautilus.ColumnProvider, Nautilus.InfoProvider, GObject.GObject):
    def __init__(self):
        pass

    def get_columns(self):
        return (
            Nautilus.Column(name='MyExif::DateTime',
                            attribute='Exif:Image:DateTime',
                            label='Date Original',
                            description='Data time original'
            ),
        )

    def update_file_info_full(self, provider, handle, closure, file_info):
        if file_info.get_uri_scheme() != 'file':
            return

        filename = unquote(file_info.get_uri()[7:])
        attr = ''

        if file_info.get_mime_type() in ('image/jpeg', 'image/png'):
            GObject.timeout_add_seconds(1, self.update_exif, 
                                        provider, handle, closure, file_info)
            return Nautilus.OperationResult.IN_PROGRESS

        file_info.add_string_attribute('Exif:Image:DateTime', attr)

        return Nautilus.OperationResult.COMPLETE

    def update_exif(self, provider, handle, closure, file_info):
        filename = unquote(file_info.get_uri()[7:])

        try:
            data = getexiftool(filename)
            attr = data[0]
        except:
            attr = ''

        file_info.add_string_attribute('Exif:Image:DateTime', attr)

        Nautilus.info_provider_update_complete_invoke(closure, provider, 
                               handle, Nautilus.OperationResult.COMPLETE)
        return False

Кодвыше не будет блокировать Nautilus, и если в представлении столбца доступен столбец «Дата оригинала», изображения JPEG и PNG будут показывать значение « unknown », и они будут медленно обновляться (подпроцессвызывается через 1 секунду).

Примеры 2. Использование библиотеки

Вместо того, чтобы вызывать внешнюю программу, лучше использовать библиотеку.Как показано в примере ниже:

from gi.repository import Nautilus, GObject
from urllib import unquote
import pyexiv2

class MyExtension(Nautilus.ColumnProvider, Nautilus.InfoProvider, GObject.GObject):
    def __init__(self):
        pass

    def get_columns(self):
        return (
            Nautilus.Column(name='MyExif::DateTime',
                            attribute='Exif:Image:DateTime',
                            label='Date Original',
                            description='Data time original'
            ),
        )

    def update_file_info_full(self, provider, handle, closure, file_info):
        if file_info.get_uri_scheme() != 'file':
            return

        filename = unquote(file_info.get_uri()[7:])
        attr = ''

        if file_info.get_mime_type() in ('image/jpeg', 'image/png'):
            metadata = pyexiv2.ImageMetadata(filename)
            metadata.read()

            try:
                tag = metadata['Exif.Image.DateTime'].value
                attr = tag.strftime('%Y-%m-%d %H:%M')
            except:
                attr = ''

        file_info.add_string_attribute('Exif:Image:DateTime', attr)

        return Nautilus.OperationResult.COMPLETE

В конце концов, если процедура медленная, вам нужно будет сделать ее асинхронной (возможно, с использованием чего-то лучшего, чем GObject.timeout_add_seconds .

Наконец, что не менее важно, в моих примерах я использовал GObject Introspection (обычно для Nautilus 3), но его легко изменить, чтобы использовать модуль nautilus напрямую.

0 голосов
/ 15 декабря 2016

благодаря Дейву!

я искал решение для текста ' unknown ' в столбце целую вечность

file_info.invalidate_extension_info() 

Исправлена ​​проблема для меня сразу:)

В соответствии с API API Документация

https://projects -old.gnome.org / наутилус-питон / документации / html / класс-наутилус-питон-файлы info.html # метод-наутилус-питон-файл-инфо - аннулированию-расширение -info

Nautilus.FileInfo.invalidate_extension_info

def invalidate_extension_info()

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

0 голосов
/ 11 декабря 2016

Приведенное выше решение верное только частично.

Между изменениями состояния для метаданных file_info пользователь должен вызвать file_info.invalidate_extension_info () , чтобы уведомить nautilus оменять.Невыполнение этого требования может привести к появлению « unknown » в ваших столбцах.

file_info.add_string_attribute('video_width', video_width)
file_info.add_string_attribute('video_height', video_height)
file_info.add_string_attribute('name_suggestion', name_suggestion)   

file_info.invalidate_extension_info()

Nautilus.info_provider_update_complete_invoke(closure, provider, handle, Nautilus.OperationResult.COMPLETE)

Полный рабочий пример здесь:

Полностью рабочий пример

Документация API

...