struct array und co...

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Ich hab die Siemens M740AV DVB-T Box. Diese speichert TV-Aufnahmen auf ein Netzlaufwerk. Dabei werden Informationsdateien *.crid (Formatbeschreibung) abgespeichert. Diese liegen in einem Binärformat vor.

Ich hab eigentlich keine Ahnung von Binärdatenverarbeitung... Aber ich hab es halbwegs geschaft, mir die Daten Anzeigen zu lassen.

Allerdings etwas unschön (Source-Auszug):

Code: Alles auswählen

def unpack(txt,fmt=">i"):
    try:
        return struct.unpack(fmt,txt)[0]
    except struct.error:
        return False


def dumpCRIDfile(filename):
    print filename
    f = open(filename,"rb")
    txt = f.read()
    f.close()
    
    print "Versionsbyte:",unpack(txt[0:4])
    print "Status:",unpack(txt[12:16])," 1=noch nicht aufgenommen, 2=während der Aufnahme, 3=fertig aufgenommen"
    
    beginTime = unpack(txt[16:20])
    print "Beginn Timestamp: %s" % beginTime
Nun hab ich mal versucht das ganze mit array auszuwerten.
Aber das will so nicht:

Code: Alles auswählen

def CRIDfile( filename ):
    print filename
    fileobj = file( filename,"rb" )

    binvalues = array.array("b")
    binvalues.fromfile(fileobj, 4)
    print "Versionsbyte:",binvalues

    binvalues = array.array("b")
    binvalues.fromfile(fileobj, 4)
    print "Status:",binvalues

    binvalues = array.array("f")
    binvalues.fromfile(fileobj, 8)
    print "Beginn Timestamp:", binvalues

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Irgendwie hab ich das mit dem array nicht so ganz gecheckt... Hab mir aber jetzt eine bessere Version gebastelt:

Code: Alles auswählen

class StructUnpacker:
    def __init__( self, fileObj, byteorder ):
        self.fileObj = fileObj
        self.byteorder = byteorder
        self.data = {}

    def read( self, Ctype, item ):
        self.data[item] = self.getitem( Ctype )

    def getitem( self, Ctype ):
        size = struct.calcsize( Ctype )
        fmt = self.byteorder + Ctype
        return struct.unpack( fmt, self.fileObj.read( size ) )[0]

    def read_variable_String( self, item ):
        string_length = self.getitem( "i" )
        self.data[item] = self.fileObj.read( string_length ).strip()


    def dumpdata( self ):
        for k,v in self.data.iteritems():
            print "%s: %s" % (k,v)

def CRID( filename ):
    print filename
    fileObj = open(filename,"rb")

    su = StructUnpacker( fileObj, byteorder=">" )

    su.read( "i", "CRID-Version" )
    su.read( "q", "CRID-ID" )
    su.read( "i", "Recording-State" )
    su.read( "i", "epg start time" )
    su.read( "i", "epg end time" )
    su.read( "i", "user access data" )
    su.read( "i", "rec pre-offset" )
    su.read( "i", "rec post-offset" )
    su.read( "i", "rec type" )
    su.read( "i", "series ID" )
    su.read( "h", "protected flag" )
    su.read_variable_String( "crid title" )
    su.read( "i", "pieces" )
    su.read_variable_String( "rec control file" )
    su.read( "i", "absolute rec start time" )
    su.read( "q", "start-timestamp" )
    su.read( "q", "end-timestamp" )
    su.read_variable_String( "epg short text" )
    su.read_variable_String( "epg long text" )
    su.read( "i", "series ID" )

    su.dumpdata()

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
BlackJack

Erstmal ist `array` dafür nicht das richtge Modul. Das ist für viele gleichartige Werte. Für die CRID-Datei ist `struct` besser geeignet.

Die Methode 'fromfile(f, n)' liest `n` Werte *vom Typ des arrays* ein! Das heisst in deinem letzten Aufruf werden 8 *Floats* mit jeweils 4 Byte Länge eingelesen.

Ausserdem bzw. auch deshalb stimmen die Lesepositionen in der Datei nicht. Du liest erst 4 Bytes (Versionsnummer) ein und dann gleich nochmal 4 Bytes, die Du als Status bezeichnest -- zwischen Versionsnummer und Status kommen in der Formatbeschreibung von CRID Dateien aber noch 8 andere Bytes.

Floats scheinen in der Dateibeschreibung übrigens gar nicht vorzukommen.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Nach der Uhrzeit der Beiträge zu urteilen, hast du wahrscheinlich mein letzten Beitrag nicht gesehen...

Ich habe es mit dem array schon wieder Aufgegeben, irgendwie hab ich keine richtige Werte erhalten... Wo kann man dabei im übrigen die Byte order angeben?

Gibt es bei struct irgend einen Automatismuss für mein read_variable_String()? Ich hab was gelesen von einem "Pascal string", aber ich hab's nicht geschafft das richtig einzusetzten...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
BlackJack

jens hat geschrieben:Nach der Uhrzeit der Beiträge zu urteilen, hast du wahrscheinlich mein letzten Beitrag nicht gesehen...
Richtig, habe ich nicht gesehen.
Ich habe es mit dem array schon wieder Aufgegeben, irgendwie hab ich keine richtige Werte erhalten... Wo kann man dabei im übrigen die Byte order angeben?
Nirgends. In der Doku steht unter der Tabelle mit den Format-Codes, dass die Repräsentation der Daten immer von der Architektur bzw. des C Compilers abhängt, mit dem Python übersetzt wurde.
Gibt es bei struct irgend einen Automatismuss für mein read_variable_String()? Ich hab was gelesen von einem "Pascal string", aber ich hab's nicht geschafft das richtig einzusetzten...
Zeichenketten in Pascal (also im "Original") konnten maximal 255 Zeichen lang sein, weil die so im Speicher abgelegt wurden, dass das erste Byte die Länge angegeben hat. In der CRID-Beschreibung ist die Längenangabe aber 4 Bytes lang.

In aktuellen Pascals also Delphi oder FreePascal gibt's den Typ `AnsiString` der immerhin 2GiB gross werden kann.

Zurück zu Deinem Programm: Der Sinn, das `unpack()` ein Tupel liefert ist eigentlich, dass man eine längere Format-Zeichenkette angibt und mehr als einen Wert auf einmal dekodiert.

Wenn das ganze portabel sein soll, dann solltest Du "big endian" explizit vorgeben.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

BlackJack hat geschrieben:
Gibt es bei struct irgend einen Automatismuss für mein read_variable_String()? Ich hab was gelesen von einem "Pascal string", aber ich hab's nicht geschafft das richtig einzusetzten...
Zeichenketten in Pascal (also im "Original") konnten maximal 255 Zeichen lang sein, weil die so im Speicher abgelegt wurden, dass das erste Byte die Länge angegeben hat. In der CRID-Beschreibung ist die Längenangabe aber 4 Bytes lang.
Also dann gehe ich davon aus, das es keine automatische Auswertung für Strings mit Längenangabe von 4 Bytes gibt?
Ich habe die Methode read_variable_String() nochmal umgeschrieben, sodas nicht direkt aus dem File-Objekt der String mit .read( X ) ausgelesen wird, sondern auch mit struct.unpack() wobei das Format dann die länge des Strings beinhaltet, also string_length + "s"
BlackJack hat geschrieben:Zurück zu Deinem Programm: Der Sinn, das `unpack()` ein Tupel liefert ist eigentlich, dass man eine längere Format-Zeichenkette angibt und mehr als einen Wert auf einmal dekodiert.
Dafür konnte ich mir aber keine einfache Beschreibung der Binär-Daten ausdenken, also wie die Daten Aufgebaut sind...
Hier mal mein bisheriger Stand:

StructUnpacker.py

Code: Alles auswählen

#!/usr/bin/python
# -*- coding: UTF-8 -*-

import struct


class StructUnpacker:
    """
    Allgemeine Klasse zum einlesen von Binärdaten
    Bsp.:
    su = StructUnpacker.StructUnpacker( fileObj, byteorder=">" )
    su.read( "i", "CRID-Version" )
    su.read( "q", "CRID-ID" )
    su.read( "h", "protected flag" )
    su.read_variable_String( "crid title" )
    print su.dumpdata()
    """
    def __init__( self, fileObj, byteorder ):
        self.fileObj = fileObj
        self.byteorder = byteorder
        self.data = {}

    ##################################################################

    def read( self, Ctype, item ):
        """
        Einlesen eines Zeichen
        """
        self.data[item] = self._readitem( Ctype )

    def read_variable_String( self, item ):
        """
        Einlesen eines Textes von variabler Länge.
        Dabei wird erst die Anzahl der Text-Länge eingelesen, danach der
        eigentliche Text.
        """
        string_length = self._readitem( "i" )
        self.data[item] = self._readitem( "%ss" % string_length )
        #~ self.data[item] = self.fileObj.read( string_length ).strip()

    #-----------------------------------------------------------------

    def _readitem( self, Ctype ):
        """
        lesen eines Zeichens aus dem File-Objekt
        wird von read() und read_variable_String() gleichzeitig genutzt
        """
        size = struct.calcsize( Ctype )
        fmt = self.byteorder + Ctype
        return struct.unpack( fmt, self.fileObj.read( size ) )[0]

    ##################################################################

    def __getitem__( self, key ):
        """
        Liefert einen gespeicherten Eintrag anhand des Key's zurück
        """
        return self.data[key]

    ##################################################################

    def dump( self ):
        print "="*80
        print ">>> dump [%s]" % self.fileObj.name
        for k,v in self.data.iteritems():
            print "%s: %s" % (k,v)
        print "="*80
Der Aufbau der Binärdatei ist in einer einfache Funktion festgehalten, diese Ließt die Datei aus und liefert die StructUnpacker-Instanz zurück, mit der man dann die Daten abfragen kann:

Code: Alles auswählen

import StructUnpacker

def parse( fileObj ):
    """
    Verarbeitet eine CRID-Datei der M740AV-DVB-Box
    """
    su = StructUnpacker.StructUnpacker( fileObj, byteorder=">" )

    su.read( "i", "CRID-Version" )
    su.read( "q", "CRID-ID" )
    su.read( "i", "Recording-State" )
    su.read( "i", "epg start time" )
    su.read( "i", "epg end time" )
    su.read( "i", "user access data" )
    su.read( "i", "rec pre-offset" )
    su.read( "i", "rec post-offset" )
    su.read( "i", "rec type" )
    su.read( "i", "series ID" )
    su.read( "h", "protected flag" )
    su.read_variable_String( "crid title" )
    su.read( "i", "pieces" )
    su.read_variable_String( "rec control file" )
    su.read( "i", "absolute rec start time" )
    su.read( "q", "start-timestamp" )
    su.read( "q", "end-timestamp" )
    su.read_variable_String( "epg short text" )
    su.read_variable_String( "epg long text" )
    su.read( "i", "series ID" )

    return su
BlackJack hat geschrieben:Wenn das ganze portabel sein soll, dann solltest Du "big endian" explizit vorgeben.
Das verstehe ich nicht ganz... Ich dachte ehr, ich wäre Variabler, wenn man die byteorder frei wählen kann. Also ich erzeuge die Binär Dateien nicht selber, diese produziert die Box ja selber. Was ich mir allerdings vorstellen könnte, das ich irgendwann vielleicht die Dateien editieren möchte... (Wobei ich dabei nciht weiß wie ich anfangen würde)

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
BlackJack

jens hat geschrieben:Also dann gehe ich davon aus, das es keine automatische Auswertung für Strings mit Längenangabe von 4 Bytes gibt?
Stimmt, gibt's leider nicht.
BlackJack hat geschrieben:Zurück zu Deinem Programm: Der Sinn, das `unpack()` ein Tupel liefert ist eigentlich, dass man eine längere Format-Zeichenkette angibt und mehr als einen Wert auf einmal dekodiert.
Dafür konnte ich mir aber keine einfache Beschreibung der Binär-Daten ausdenken, also wie die Daten Aufgebaut sind...
Kannst Du nicht einfach eine read()-Methode für mehrere Werte zur Verfügung stellen? Als Parameter eine Liste von Tupeln mit Formatcode und Name. Dann kannst Du aus den Formatcodes eine längere Format-Zeichenkette basteln.

Code: Alles auswählen

import StructUnpacker

def parse( fileObj ):
    """
    Verarbeitet eine CRID-Datei der M740AV-DVB-Box
    """
    su = StructUnpacker.StructUnpacker( fileObj, byteorder=">" )

    su.read( "i", "CRID-Version" )
    su.read( "q", "CRID-ID" )
    su.read( "i", "Recording-State" )
    su.read( "i", "epg start time" )
    su.read( "i", "epg end time" )
    su.read( "i", "user access data" )
    su.read( "i", "rec pre-offset" )
    su.read( "i", "rec post-offset" )
    su.read( "i", "rec type" )
    su.read( "i", "series ID" )
    su.read( "h", "protected flag" )
    su.read_variable_String( "crid title" )
    su.read( "i", "pieces" )
    su.read_variable_String( "rec control file" )
    su.read( "i", "absolute rec start time" )
    su.read( "q", "start-timestamp" )
    su.read( "q", "end-timestamp" )
    su.read_variable_String( "epg short text" )
    su.read_variable_String( "epg long text" )
    su.read( "i", "series ID" )

    return su
Das sieht auf jeden Fall "unpythonic" aus. Da wäre *ein* Methodenaufruf besser, der alle Daten erhält. Sowas wie `su.read_many()`, das zumindest mal die ganzen einzelnen `read()`-Aufrufe in einer Schleife ausführt. Bei der Gelegenheit kannst Du Dir für die Zeichenketten mit variabler Länge vielleicht einen eigenen Formatcode überlegen, den Deine Klasse versteht und dann wirklich mit einer Methode auskommen.
BlackJack hat geschrieben:Wenn das ganze portabel sein soll, dann solltest Du "big endian" explizit vorgeben.
Das verstehe ich nicht ganz... Ich dachte ehr, ich wäre Variabler, wenn man die byteorder frei wählen kann.
Grundsätzlich ja, aber im Fall der CRID Daten funktioniert es auf "little endian" Systemen nicht wenn Du nicht explizit sagst, das die Daten "big endian" sind. Das steht nämlich in der Formatbeschreibung, das sie *immer* "big endian" sind.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

BlackJack hat geschrieben:Das sieht auf jeden Fall "unpythonic" aus. Da wäre *ein* Methodenaufruf besser, der alle Daten erhält.
Hm! Ich weiß nicht recht, es würde wahrscheinlich doch dann sowas ähnliches raus kommen:

Code: Alles auswählen

su.read_many(
    ["i", "CRID-Version"],
    ["q", "CRID-ID"],
    ["i", "Recording-State"]
    )
Aber ich weiß nicht, ist das wirklich viel besser?

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
BlackJack

Ob es viel besser ist weiss ich nicht, aber man ist etwas flexibler weil die Daten aus allen möglichen Quellen kommen können.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Hm! Ist ein Argument :lol:
Weiß nur nicht was ich mit su.read_variable_String() machen könnte... Die einfachste Variante wäre noch:

Code: Alles auswählen

su.read_many(
    ["i", "CRID-Version"],
    ["q", "CRID-ID"],
    ["i", "Recording-State"]
    ["variable_String", "crid title"]
    )

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Antworten