3

我编写了一个 Nautilus 扩展来读取图片的元数据(正在执行exiftool),但是当我打开包含许多文件的文件夹时,它确实会减慢文件管理器的速度并挂起,直到它完成读取文件的数据。

有没有办法让 Nautilus 在运行我的扩展程序时保持其工作?当我继续工作时,也许 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
4

3 回答 3

2

That happens because you are invoking update_file_info, which is part of the asynchronous IO system of Nautilus. Therefore, it blocks nautilus if the operations are not fast enough.

In your case it is exacerbated because you are calling an external program, and that is an expensive operation. Notice that update_file_info is called once per file. If you have 100 files, then you will call 100 times the external program, and Nautilus will have to wait for each one before processing the next one.

Since nautilus-python 0.7 are available update_file_info_full and cancel_update, which allows you to program async calls. You can check the documentation of Nautilus 0.7 for more details.

It worth to mention this was a limitation of nautilus-python only, which previously did not expose those methods available in C.

EDIT: Added a couple of examples.

The trick is make the process as fast as possible or make it asynchronous.

Example 1: Invoking an external program

Using a simplified version of your code, we make asynchronous using 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

The code above will not block Nautilus, and if the column 'Date Original' is available in the column view, the JPEG and PNG images will show the 'unknown' value, and slowly they will being updated (the subprocess is called after 1 second).

Examples 2: Using a library

Rather than invoking an external program, it could be better to use a library. As the example below:

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

Eventually, if the routine is slow you would need to make it asynchronous (maybe using something better than GObject.timeout_add_seconds.

At last but not least, in my examples I used GObject Introspection (typically for Nautilus 3), but it easy to change it to use the module nautilus directly.

于 2012-05-05T22:36:14.413 回答
1

上述解决方案仅部分正确。

在file_info元数据的状态更改之间,用户应该调用file_info.invalidate_extension_info()来通知 nautilus 更改。如果不这样做,最终可能会在您的列中出现“未知”。

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 文档

于 2016-12-11T09:11:06.143 回答
0

感谢戴夫!

多年来,我一直在寻找解决列中“未知”文本的方法

file_info.invalidate_extension_info() 

马上为我解决了这个问题:)

根据 api API 文档

https://projects-old.gnome.org/nautilus-python/documentation/html/class-nautilus-python-file-info.html#method-nautilus-python-file-info--invalidate-extension-info

Nautilus.FileInfo.invalidate_extension_info

def invalidate_extension_info()

使 Nautilus 拥有的有关此文件的信息无效,这会导致它从其 Nautilus.InfoProvider 提供程序请求新信息。

于 2016-12-14T21:49:41.633 回答