加速Nautilus Python扩展读取图像Exif的速度

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
3个回答

2
这是因为您正在调用Nautilus的异步IO系统之一update_file_info,如果操作不够快,则会阻塞Nautilus。在您的情况下,由于您调用了外部程序,这使得问题更加严重。请注意,update_file_info每个文件调用一次。如果您有100个文件,则会调用100次外部程序,Nautilus必须等待每个文件完成才能处理下一个文件。
自nautilus-python 0.7以来,提供了update_file_info_fullcancel_update,允许您编程异步调用。您可以查看Nautilus 0.7文档获取更多详细信息。
值得一提的是,这是nautilus-python的限制,先前它没有公开C中可用的这些方法。 编辑:添加了几个示例。
关键是尽可能快地进行处理或将其变成异步处理。

示例1:调用外部程序

使用您的代码的简化版本,在update_file_info_full中使用GObject.timeout_add_seconds实现异步操作。
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图像将显示“未知”值,它们将逐渐更新(子进程一秒钟后被调用)。
例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

我已经尝试将 update_file_info 更改为 update_file_info_full [Pastebin code](http://pastebin.com/MJ2CAM7x),但它仍然像以前一样慢,可能是我没有理解它的工作方式。有没有其他例子可以参考? - Stefano d'Antonio
你必须将你的代码变成异步的。Nautilus 不会为你完成这个任务。update_file_info_full 允许你返回 Nautilus.OperationResult.IN_PROGRESS(这告诉 Nautilus 操作还没有完成)。每次你返回“...IN_PROGRESS”(可能是每个文件),你需要调用一个方法来调用 Nautilus.info_provider_update_complete_invoke(这告诉 Nautilus 操作已经完成)。请阅读我提供的文档。 - gpoo
顺便提一下,建议在这里粘贴代码片段,而不是使用pastebin,因为后者很快就会过期,人们会失去上下文。 - gpoo
即使我按照你的建议操作,Nautilus仍然被阻塞。我无法使用pyexiv2,因为它不支持原始文件(如果我没记错的话,因为我曾经考虑过这个问题,但由于某些缺失的功能而放弃了这个想法)。 - Stefano d'Antonio
你在谈论将计算异步化...您能否给出一个简短的例子来说明一下?谢谢! - Nicolas Raoul
显示剩余6条评论

1
上述解决方案只是部分正确。
在文件信息元数据的状态更改之间,用户应调用 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文档


给那位点踩的人:这个有什么问题吗?如果有,能解释一下吗? - Stefano d'Antonio

0

感谢 Dave!

我花了很长时间寻找解决列中“未知”文本的方法。

file_info.invalidate_extension_info() 

立即为我解决了问题 :)

根据 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提供程序请求新信息。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接