web-dev-qa-db-fra.com

Python - mécanisme pour identifier le type de fichier compressé et décompresser

Un fichier compressé peut être classé dans les groupes logiques ci-dessous.
une. Le système d'exploitation sur lequel vous travaillez (* ix, Win) etc.
b. Différents types d’algorithmes de compression (par exemple .Zip, .Z, .bz2, .rar, .gzip). Au moins parmi une liste standard des fichiers compressés les plus utilisés.
c. Ensuite, nous avons le mécanisme de tarball - où je suppose qu'il n'y a pas de compression. Mais cela ressemble plus à une concaténation. 

Maintenant, si nous commençons à aborder l'ensemble de fichiers compressés ci-dessus,
une. L'option (a) serait prise en charge par python puisqu'il s'agit d'un langage indépendant de la plate-forme.
b. Les options b) et c) semblent poser problème. 

De quoi ai-je besoin
Comment identifier le type de fichier (type de compression) puis le compresser? 


Comme: 

fileType = getFileType(fileName)  
switch(fileType):  
case .rar:  unrar....
case .Zip:  unzip....

etc  

La question fondamentale est donc de savoir comment identifier l'algorithme de compression en fonction du fichier (en supposant que l'extension n'est pas fournie ou incorrecte). Existe-t-il un moyen spécifique de le faire en python?

24
kumar_m_kiran

Cette page contient une liste de signatures de fichiers "magiques". Prenez ceux dont vous avez besoin et mettez-les dans un dict comme ci-dessous. Nous avons ensuite besoin d’une fonction qui fait correspondre les clés dict au début du fichier. J'ai écrit une suggestion, bien qu'elle puisse être optimisée en prétraitant le magic_dict, par exemple. un regexp géant compilé.

magic_dict = {
    "\x1f\x8b\x08": "gz",
    "\x42\x5a\x68": "bz2",
    "\x50\x4b\x03\x04": "Zip"
    }

max_len = max(len(x) for x in magic_dict)

def file_type(filename):
    with open(filename) as f:
        file_start = f.read(max_len)
    for magic, filetype in magic_dict.items():
        if file_start.startswith(magic):
            return filetype
    return "no match"

Cette solution doit être multi-plate-forme et ne dépend évidemment pas de l’extension de nom de fichier, mais elle peut donner des faux positifs pour les fichiers au contenu aléatoire qui commencent à démarrer avec des octets magiques spécifiques.

28
Lauritz V. Thaulow

Basé sur la réponse de lazyr et mon commentaire, voici ce que je veux dire:

class CompressedFile (object):
    magic = None
    file_type = None
    mime_type = None
    proper_extension = None

    def __init__(self, f):
        # f is an open file or file like object
        self.f = f
        self.accessor = self.open()

    @classmethod
    def is_magic(self, data):
        return data.startswith(self.magic)

    def open(self):
        return None

import zipfile

class ZIPFile (CompressedFile):
    magic = '\x50\x4b\x03\x04'
    file_type = 'Zip'
    mime_type = 'compressed/Zip'

    def open(self):
        return zipfile.ZipFile(self.f)

import bz2

class BZ2File (CompressedFile):
    magic = '\x42\x5a\x68'
    file_type = 'bz2'
    mime_type = 'compressed/bz2'

    def open(self):
        return bz2.BZ2File(self.f)

import gzip

class GZFile (CompressedFile):
    magic = '\x1f\x8b\x08'
    file_type = 'gz'
    mime_type = 'compressed/gz'

    def open(self):
        return gzip.GzipFile(self.f)


# factory function to create a suitable instance for accessing files
def get_compressed_file(filename):
    with file(filename, 'rb') as f:
        start_of_file = f.read(1024)
        f.seek(0)
        for cls in (ZIPFile, BZ2File, GZFile):
            if cls.is_magic(start_of_file):
                return cls(f)

        return None

filename='test.Zip'
cf = get_compressed_file(filename)
if cf is not None:
    print filename, 'is a', cf.mime_type, 'file'
    print cf.accessor

Peut maintenant accéder aux données compressées en utilisant cf.accessor. Tous les modules fournissent des méthodes similaires, telles que 'read ()', 'write ()', etc.

14
Ber

Il s’agit d’une question complexe qui dépend de nombreux facteurs: le plus important est de savoir dans quelle mesure votre solution doit être portable.

L'essentiel pour trouver le type de fichier à partir d'un fichier consiste à trouver un en-tête d'identification dans le fichier, généralement appelé "séquence magique" ou en-tête de signature , qui identifie le type d'un fichier. Son nom ou son extension n'est généralement pas utilisé s'il peut être évité. Python est intégré à certains fichiers. Par exemple, pour traiter les fichiers .tar, vous pouvez utiliser le module tarfile, qui possède une méthode pratique is_tarfile. Il existe un module similaire nommé zipfile. Ces modules vous permettront également d'extraire des fichiers en pur Python.

Par exemple:

f = file('myfile','r')
if zipfile.is_zipfile(f):
    Zip = zipfile.ZipFile(f)
    Zip.extractall('/dest/dir')
Elif tarfile.is_tarfile(f):
    ...

Si votre solution est uniquement Linux ou OSX, il existe également la commande file qui effectuera une grande partie du travail à votre place. Vous pouvez également utiliser les outils intégrés pour décompresser les fichiers. Si vous ne faites qu'un script simple, cette méthode est plus simple et vous donnera de meilleures performances.

4
Krumelur

"a" est complètement faux.

"b" peut facilement être mal interprété, car ".Zip" ne signifie pas que le fichier est en fait un fichier Zip. Ce pourrait être un fichier JPEG avec extension Zip (pour des raisons de confusion, si vous le souhaitez).

En réalité, vous devez vérifier si les données contenues dans le fichier correspondent aux données attendues par son extension. Regardez aussi octet magique .

0
alexandernst

Si l'exercice consiste à l'identifier uniquement pour étiqueter des fichiers, vous avez beaucoup de réponses. Si vous voulez décompresser l'archive, pourquoi ne tentez-vous pas simplement d'attraper les exécutions/erreurs? Par exemple:

>>> tarfile.is_tarfile('lala.txt')
False
>>> zipfile.is_zipfile('lala.txt')
False
>>> with bz2.BZ2File('startup.bat','r') as f:
...    f.read()
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
IOError: invalid data stream
0
Burhan Khalid