使用Python读取元数据

时间:2012-09-20 21:57:52

标签: python windows metadata

在过去的两天里,我一直在扫描互联网,试图找到问题的解决方案。我有一个不同文件的文件夹。他们运行文件类型的开局。我正在尝试编写一个python脚本,它将读取每个文件中的元数据(如果存在)。目的是最终将数据输出到文件以与另一个程序的元数据提取进行比较。

我找到了一些例子,它适用于目录中的极少数文件。我找到的所有方法都涉及打开一个Storage Container对象。我是Python新手,不知道什么是Storage Container对象。我只知道在尝试使用

时,我的大多数文件都出错了
pythoncom.StgOpenStorage(<File Name>, None, flags)

通过少数实际工作,我能够获得主要元数据标签,如标题,主题,作者,创建等。

有没有人知道存储容器以外的方式来获取元数据?此外,如果有一种更简单的方法可以使用其他语言,请务必建议。

由于

5 个答案:

答案 0 :(得分:7)

您可以使用Shell com对象检索任何元数据 在资源管理器中可见:

import win32com.client
sh=win32com.client.gencache.EnsureDispatch('Shell.Application',0)
ns = sh.NameSpace(r'm:\music\Aerosmith\Classics Live!')
colnum = 0
columns = []
while True:
    colname=ns.GetDetailsOf(None, colnum)
    if not colname:
        break
    columns.append(colname)
    colnum += 1

for item in ns.Items():
    print (item.Path)
    for colnum in range(len(columns)):
        colval=ns.GetDetailsOf(item, colnum)
        if colval:
            print('\t', columns[colnum], colval)

答案 1 :(得分:2)

问题是Windows存储文件元数据有两种方式。您正在使用的方法适用于COM应用程序创建的文件;这些数据包含在文件本身中。但是,随着NTFS5的引入,任何文件都可以包含元数据作为备用数据流的一部分。因此,成功的文件可能是COM-app创建的文件,而失败的文件则不是。

这是一种处理COM-app创建文件的可能更强大的方法:Get document summary information from any file

使用备用数据流,可以直接读取它们:

meta = open('myfile.ext:StreamName').read()

更新:好的,现在我发现这些都不相关,因为您使用的是文档元数据,而不是文件元数据。问题的清晰度可以有多大区别:|

试试这个:How to retrieve author of a office file in python?

答案 2 :(得分:1)

我决定编写自己的答案,以尝试合并并阐明上述答案(这在很大程度上帮助我解决了问题)。

我想说有两种方法可以解决这个问题。

情况1:您知道文件包含哪些元数据(您感兴趣的元数据)。

在这种情况下,假设您有一个字符串列表,其中包含您感兴趣的元数据。在这里,我认为这些标记是正确的(即,您对.txt的像素数不感兴趣)文件)。

metadata = ['Name', 'Size', 'Item type', 'Date modified', 'Date created']

现在,使用Greedo和Roger Upole提供的代码,我创建了一个函数,该函数分别接受文件的完整路径和名称,并返回包含感兴趣的元数据的字典:

def get_file_metadata(path, filename, metadata):
# Path shouldn't end with backslash, i.e. "E:\Images\Paris"
# filename must include extension, i.e. "PID manual.pdf"
# Returns dictionary containing all file metadata.
sh = win32com.client.gencache.EnsureDispatch('Shell.Application', 0)
ns = sh.NameSpace(path)

# Enumeration is necessary because ns.GetDetailsOf only accepts an integer as 2nd argument
file_metadata = dict()
item = ns.ParseName(str(filename))
for ind, attribute in enumerate(metadata):
    attr_value = ns.GetDetailsOf(item, ind)
    if attr_value:
        file_metadata[attribute] = attr_value

return file_metadata

注意:您必须知道文件的总路径。 用法示例:

folder = 'E:\Docs\BMW'
filename = 'BMW series 1 owners manual.pdf'
metadata = ['Name', 'Size', 'Item type', 'Date modified', 'Date created']
print(get_file_metadata_stack(folder, filename, metadata))

结果:

{'Name': 'BMW series 1 owners manual.pdf', 'Size': '11.4 MB', 'Item type': 'Foxit Reader PDF Document', 'Date modified': '8/30/2020 11:10 PM', 'Date created': '8/30/2020 11:10 PM'}

这是正确的,因为我刚刚创建了文件,并且使用Foxit PDF阅读器作为主要的PDF阅读器。 因此,此函数返回一个字典,其中的键是元数据标签,值是给定文件的那些标签的值。

情况2:您不知道文件包含哪些元数据

这是一个比较困难的情况,尤其是在最优性方面。我分析了Roger Upole提出的代码,基本上,他尝试读取None文件的元数据,这导致他获得了所有可能的元数据标签的列表。因此,我认为硬拷贝此列表然后尝试读取每个标签可能会更容易。这样,完成后,您将拥有一个包含文件实际拥有的所有标签的字典。

只需复制我认为的是所有可能的元数据标签,然后尝试从文件中获取所有标签。 基本上,只需复制此python列表声明,然后使用上面的代码(用此新列表替换元数据):

metadata = ['Name', 'Size', 'Item type', 'Date modified', 'Date created', 'Date accessed', 'Attributes', 'Offline status', 'Availability', 'Perceived type', 'Owner', 'Kind', 'Date taken', 'Contributing artists', 'Album', 'Year', 'Genre', 'Conductors', 'Tags', 'Rating', 'Authors', 'Title', 'Subject', 'Categories', 'Comments', 'Copyright', '#', 'Length', 'Bit rate', 'Protected', 'Camera model', 'Dimensions', 'Camera maker', 'Company', 'File description', 'Masters keywords', 'Masters keywords']

我认为这不是一个很好的解决方案,但另一方面,您可以将此列表保留为全局变量,然后使用它,而无需将其传递给每个函数调用。为了完整起见,以下是使用此新元数据列表的前一个函数的输出:

{'Name': 'BMW series 1 owners manual.pdf', 'Size': '11.4 MB', 'Item type': 'Foxit Reader PDF Document', 'Date modified': '8/30/2020 11:10 PM', 'Date created': '8/30/2020 11:10 PM', 'Date accessed': '8/30/2020 11:10 PM', 'Attributes': 'A', 'Perceived type': 'Unspecified', 'Owner': 'KEMALS-ASPIRE-E\\kemal', 'Kind': 'Document', 'Rating': 'Unrated'}

如您所见,现在返回的字典包含文件包含的所有元数据。 起作用的原因是由于 if语句

if attribute_value:

这意味着只要属性等于None,就不会将其添加到返回的字典中。

我要强调的是,如果要处理许多文件,最好将列表声明为全局/静态变量,而不是每次都将其传递给函数。

答案 3 :(得分:0)

Roger Upole的回答极大地帮助了。但是,我还需要阅读“ .xls”文件中的“最后保存者”详细信息。

XLS文件属性可以用win32com读取。 Workbook对象具有一个BuiltinDocumentPropertieshttps://gist.github.com/justengel/87bac3355b1a925288c59500d2ce6ef5

import os
import win32com.client  # Requires "pip install pywin32"


__all__ = ['get_xls_properties', 'get_file_details']


# https://docs.microsoft.com/en-us/dotnet/api/microsoft.office.tools.excel.workbook.builtindocumentproperties?view=vsto-2017
BUILTIN_XLS_ATTRS = ['Title', 'Subject', 'Author', 'Keywords', 'Comments', 'Template', 'Last Author', 'Revision Number',
                     'Application Name', 'Last Print Date', 'Creation Date', 'Last Save Time', 'Total Editing Time',
                     'Number of Pages', 'Number of Words', 'Number of Characters', 'Security', 'Category', 'Format',
                     'Manager', 'Company', 'Number of Btyes', 'Number of Lines', 'Number of Paragraphs',
                     'Number of Slides', 'Number of Notes', 'Number of Hidden Slides', 'Number of Multimedia Clips',
                     'Hyperlink Base', 'Number of Characters (with spaces)']


def get_xls_properties(filename, xl=None):
    """Return the known XLS file attributes for the given .xls filename."""
    quit = False
    if xl is None:
        xl = win32com.client.DispatchEx('Excel.Application')
        quit = True

    # Open the workbook
    wb = xl.Workbooks.Open(filename)

    # Save the attributes in a dictionary
    attrs = {}
    for attrname in BUILTIN_XLS_ATTRS:
        try:
            val = wb.BuiltinDocumentProperties(attrname).Value
            if val:
                attrs[attrname] = val
        except:
            pass

    # Quit the excel application
    if quit:
        try:
            xl.Quit()
            del xl
        except:
            pass

    return attrs


def get_file_details(directory, filenames=None):
    """Collect the a file or list of files attributes.
    Args:
        directory (str): Directory or filename to get attributes for
        filenames (str/list/tuple): If the given directory is a directory then a filename or list of files must be given
    Returns:
         file_attrs (dict): Dictionary of {filename: {attribute_name: value}} or dictionary of {attribute_name: value}
            if a single file is given.
    """
    if os.path.isfile(directory):
        directory, filenames = os.path.dirname(directory), [os.path.basename(directory)]
    elif filenames is None:
        filenames = os.listdir(directory)
    elif not isinstance(filenames, (list, tuple)):
        filenames = [filenames]

    if not os.path.exists(directory):
        raise ValueError('The given directory does not exist!')

    # Open the com object
    sh = win32com.client.gencache.EnsureDispatch('Shell.Application', 0)  # Generates local compiled with make.py
    ns = sh.NameSpace(os.path.abspath(directory))

    # Get the directory file attribute column names
    cols = {}
    for i in range(512):  # 308 seemed to be max for excel file
        attrname = ns.GetDetailsOf(None, i)
        if attrname:
            cols[i] = attrname

    # Get the information for the files.
    files = {}
    for file in filenames:
        item = ns.ParseName(os.path.basename(file))
        files[os.path.abspath(item.Path)] = attrs = {}  # Store attributes in dictionary

        # Save attributes
        for i, attrname in cols.items():
            attrs[attrname] = ns.GetDetailsOf(item, i)

        # For xls file save special properties
        if os.path.splitext(file)[-1] == '.xls':
            xls_attrs = get_xls_properties(item.Path)
            attrs.update(xls_attrs)

    # Clean up the com object
    try:
        del sh
    except:
        pass

    if len(files) == 1:
        return files[list(files.keys())[0]]
    return files


if __name__ == '__main__':
    import argparse

    P = argparse.ArgumentParser(description="Read and print file details.")
    P.add_argument('filename', type=str, help='Filename to read and print the details for.')
    P.add_argument('-v', '--show-empty', action='store_true', help='If given print keys with empty values.')
    ARGS = P.parse_args()

    # Argparse Variables
    FILENAME = ARGS.filename
    SHOW_EMPTY = ARGS.show_empty
    DETAILS = get_file_details(FILENAME)

    print(os.path.abspath(FILENAME))
    for k, v in DETAILS.items():
        if v or SHOW_EMPTY:
            print('\t', k, '=', v)

答案 4 :(得分:0)

Windows API代码包可以与 Python for .NET 一起使用,以读取/写入文件元数据。

  1. WindowsAPICodePack-Core下载NuGet软件包,然后 WindowsAPICodePack-Shell

  2. 使用7-Zip等压缩实用程序将 .nupkg 文件提取到脚本的路径或系统路径变量中定义的某个位置。

  3. 使用[Unit] Description=TestScript Service After=network-online.target [Service] Type=idle ExecStart=/usr/bin/python3 /home/pi/MyProject/my_script.py > /home/pi/my_script.log 2>&1 [Install] WantedBy=network-online.target 安装适用于.NET的Python。

获取和设置MP4视频标题的示例代码:

pip install pythonnet

无法检查可用属性:

import clr
clr.AddReference("Microsoft.WindowsAPICodePack")
clr.AddReference("Microsoft.WindowsAPICodePack.Shell")
from Microsoft.WindowsAPICodePack.Shell import ShellFile

# create shell file object
f = ShellFile.FromFilePath(r'movie..mp4')

# read video title
print(f.Properties.System.Title.Value)

# set video title
f.Properties.System.Title.Value = 'My video'
相关问题