Seite 1 von 1

Klasse in einem Paket erweitern

Verfasst: Sonntag 2. Juni 2013, 21:16
von FlicFlac
Hallo,

ich verwende das Paket 'mutagen' um die ID3-Tags von mp3-Dateien zu bearbeiten.
In meinem Skript verwende ich nur die Module 'mutagen.id3' und 'mutagen.mp3'.

In dem Modul mutagen.id3 muss ich eine Klasse (EncodedTextSpec) ändern, da ich sonst Strings mit einem bestimmten Encoding nicht in den ID3-Tag schreiben kann.
Allerdings möchte ich das nicht direkt in dem Paket ändern, sondern aus meinem Skript heraus. Denn das Ganze soll auch auf anderen PCs laufen, ohne dass dort zuvor eine bleibende Änderung an dem Paket gemacht werden muss.

Dank diesem Forum bin ich auf diesen Beitrag gestoßen, indem ein ähnliches Problem mit einem Monkey Patch gelöst worden ist. Den Lösungsvorschlag habe ich dann auch analog bei mir angewendet:

Code: Alles auswählen

import mutagen.id3

class EncodedTextSpec2(mutagen.id3.Spec):
    ...

mutagen.id3.EncodedTextSpec = EncodedTextSpec2
Ich habe dabei den Code aus der Klasse EncodedTextSpec - die geändert werden soll - kopiert, die Änderungen darin vorgenommen und in die Klasse EncodedTextSpec2 eingefügt.
Die Vererbung stimmt, da dies auch so in der Originalklasse ist.

Leider funktioniert das bei mir aber nicht :(
Nach der Ausführung taucht noch immer der gleiche Traceback auf, der schon zuvor auf die Zeile verwiesen hat, die den Fehler auslöst. Genau diese Zeile habe ich aber in meiner Klasse geändert.
Es wird also nicht meine neu definierte Klasse verwendet.

Ich weiß jetzt nicht mehr weiter und bin um jede Hilfe von eurer Seite dankbar :)

Viele Grüße

Re: Klasse in einem Paket erweitern

Verfasst: Sonntag 2. Juni 2013, 22:20
von Sirius3
Hallo FlicFlac,
in Zeile 23 mußt Du natürlich das j durch ein i ersetzen. ;-)
Wie sollen wir Dir helfen können, wenn wir weder die Fehlermeldung, noch den Aufruf, noch Deine bisherigen Versuche kennen?

Re: Klasse in einem Paket erweitern

Verfasst: Montag 3. Juni 2013, 00:20
von FlicFlac
i und j sollte auch nicht so dicht auf der Tastatur beieinander liegen ;)

Mein erster Post war nicht seeehr ausführlich; das gebe ich zu :oops:
Mir ging es in erster Linie um diesen Monkey Patch. Ich wollte euch nicht gleich mit seitenlangen Text erschlagen... Das kommt dafür jetzt :lol:

Der Aufruf von dieser Klasse geschieht Paketintern im Modul id3.

Der prinzipielle Vorgang sieht so in meinem Skript aus (das ist nur für Anschauungszwecke zusammengebastelt):

Code: Alles auswählen

import mutagen.id3

### ID3 object
audio = mutagen.id3.ID3(r"C:\audio.mp3")

### Text für den ID3-Titel vorbereiten
# Text für den ID3-Titel als Unicode 
id3_title_unicode = u'0001 \u9677\u843d \u91cc\u6839 \u7535\u89e3\u94dd'
# Encoding des Textes für den ID3-Title mit gb18030
id3_title_gb18030 = id3_title_unicode.encode('gb18030')

### Titel-Frame object
# Text übergebe ich bei der Initialisierung zuerst nicht, da er sonst gleich in Unicode umgewandelt wird
# Für TextFrames muss das Encoding angegeben werden. encoding=0 ist latin-1
# Die ID3v2.3-Spec erlaubt offiziell nur latin-1 und utf16
# Als Trick wird für chinesische Encodings trotzdem latin-1 für das encoding angegeben
# Blöderweise encodiert aber mutagen im späteren Ablauf dann auf latin-1, was in meinem Fall auch der Fehler ist
title_frame = mutagen.id3.TIT2(encoding=0, text="")
# Dem Text-Attribut den Titel als Liste (wird so gefordert) übergeben. Encoding bleibt bestehen und wird nicht in Unicode gecastet.
title_frame.text = [id3_title_gb18030]

### TitleFrame an das ID3-object übergeben
audio['TIT2'] = title_frame

### ID3-Tag speichern
# hier tritt auch der Fehler auf
audio.save()
Hier der Traceback, der geschmissen wird:

Code: Alles auswählen

Traceback (most recent call last):
  File "<pyshell#14>", line 1, in <module>
    audio.save()
  File "C:\Python27\lib\site-packages\mutagen\id3.py", line 370, in save
    framedata = [self.__save_frame(frame) for (key, frame) in frames]
  File "C:\Python27\lib\site-packages\mutagen\id3.py", line 461, in __save_frame
    framedata = frame._writeData()
  File "C:\Python27\lib\site-packages\mutagen\id3.py", line 1060, in _writeData
    data.append(writer.write(self, getattr(self, writer.name)))
  File "C:\Python27\lib\site-packages\mutagen\id3.py", line 773, in write
    data.append(self.specs[0].write(frame, v))
  File "C:\Python27\lib\site-packages\mutagen\id3.py", line 748, in write
    return value.encode(enc) + term
UnicodeEncodeError: 'latin-1' codec can't encode character u'\u017e' in position 12: ordinal not in range(256)
Der ganze Ablauf findet also nur in dem id3-Modul statt.
Und hier die für den Fehler verantwortliche Klasse im Modul mutagen.id3:

Code: Alles auswählen

class EncodedTextSpec(Spec):
    # Okay, seriously. This is private and defined explicitly and
    # completely by the ID3 specification. You can't just add
    # encodings here however you want.
    _encodings = ( ('latin1', '\x00'), ('utf16', '\x00\x00'),
                   ('utf_16_be', '\x00\x00'), ('utf8', '\x00') )

    def read(self, frame, data):
        enc, term = self._encodings[frame.encoding]
        ret = ''
        if len(term) == 1:
            if term in data:
                data, ret = data.split(term, 1)
        else:
            offset = -1
            try:
                while True:
                    offset = data.index(term, offset+1)
                    if offset & 1: continue
                    data, ret = data[0:offset], data[offset+2:]; break
            except ValueError: pass

        if len(data) < len(term): return u'', ret
        return data.decode(enc), ret

    def write(self, frame, value):
        enc, term = self._encodings[frame.encoding]
        return value.encode(enc) + term   ################ line 748!
		
    def validate(self, frame, value): return unicode(value)
In der write-Methode wird der Text für ein Frame auf das im Frame angegebene Encoding kodiert und zurückgegeben.
Das ist aber in meinem Fall nicht erwünscht.

Wenn ich den Rückgabewert wie folgt ändere...

Code: Alles auswählen

def write(self, frame, value):
    enc, term = self._encodings[frame.encoding]
    return value + term
...dann kann der ID3-Tag in der mp3-Datei gespeichert werden, ohne Fehlermeldung.

Diese Änderung möchte ich jetzt aber nicht im Paket selbst vornehmen.
Daher habe ich auch die Schritte durchgeführt, die ich im ersten Posting erklärt habe. Also erst die Klassendefinition, dann den Monkey Patch und danach den gleichen Code zum Bearbeiten des ID3-Tags.
Das hat bei mir aber leider nicht funktioniert. Es erscheint derselbe Traceback wie oben. Das Modul mutagen.id3 verwendet also nicht die neu definierte Klasse.

Komischerweise ist es so, wenn ich meiner Klasse eine weitere Methode hinzufüge...

Code: Alles auswählen

import mutagen.id3

class EncodedTextSpec2(mutagen.id3.Spec):
    ...
    def test(self): print 'irgendetwas'

mutagen.id3.EncodedTextSpec = EncodedTextSpec2
...dann kann diese Methode auch aufgerufen werden:

Code: Alles auswählen

mutagen.id3.EncodedTextSpec('').test()
irgendetwas
Also funktioniert der Monkey Patch ja doch.
Mich verwirrt das nur :?

Wie gesagt, ich bin um jeden Ratschlag dankbar.

Re: Klasse in einem Paket erweitern

Verfasst: Montag 3. Juni 2013, 00:49
von BlackJack
@FlicFlac: Kannst Du statt den Code zu ändern nicht einfach bei den Daten das übergeben was erwartet wird? Also eine Unicode-Zeichenkette in der der ursprüngliche Titel als GB... kodierter Latin-1-Text steht? Also das Ergebnis von ``id3_title_unicode.encode('gb18030').decode('latin1')``. Darauf würde `mutagen` dann ja wieder `encode('latin1')` ausführen und zu den Bytes führen, die Du in der Datei haben willst.

Re: Klasse in einem Paket erweitern

Verfasst: Montag 3. Juni 2013, 05:37
von Sirius3
@FlicFlac: der Fehler kann so in Deinem Code nicht auftreten. Nachdem Du Deine Zeichen nach gb18030 codiert hast, kann im String kein Wert mehr auftreten, der >255 ist. Also scheinst Du von irgendwo anders her ein 'ž' zu importieren, vielleicht aus 'audio.mp3'? BlackJacks Vorschlag, den String einfach als Latin-1 zu decodieren ist in diesem Fall aber „sauberer“, da an anderen Stellen im Code Unicode verlangt wird und es da dann zu Problemen kommen könnte, wenn Strings mit Zeichen >127 auftauchen.

Re: Klasse in einem Paket erweitern

Verfasst: Montag 3. Juni 2013, 08:41
von FlicFlac
Danke @BlackJack und @Sirius3! Das funktioniert :D
Da habe ich selber gar nicht daran gedacht :roll:

@Sirius3: Der Fehler tritt trotzdem auf, da irgendwo im Paket der String wohl doch noch mal in unicode gecastet wird:

Code: Alles auswählen

unicode('0001 \xcf\xdd\xc2\xe4 \xc0\xef\xb8\xf9 \xb5\xe7\xbd\xe2\xc2\xc1')
u'0001 \xcf\xdd\xc2\xe4 \xc0\xef\u017e\xf9 \xb5\xe7\u0153\xe2\xc2\xc1'
Dazu hätte ich auch noch eine Frage:
Welches Encoding wird verwendet, wenn ein String der unicode-Funktion übergeben wird?
Das was in der Magic Line angegeben wird, also das Encoding in dem das Skript gespeichert worden ist?

Re: Klasse in einem Paket erweitern

Verfasst: Montag 3. Juni 2013, 09:51
von BlackJack
@FlicFlac: Es wird 'ASCII' angenommen. Dass heisst was Du da zeigst kann so gar nicht funktionieren denn die Bytekette enthält Werte ausserhalb des ASCII-Wertebereichs:

Code: Alles auswählen

In [8]: unicode('0001 \xcf\xdd\xc2\xe4 \xc0\xef\xb8\xf9 \xb5\xe7\xbd\xe2\xc2\xc1')
---------------------------------------------------------------------------
UnicodeDecodeError                        Traceback (most recent call last)
/home/bj/<ipython-input-8-16c82a04dc0e> in <module>()
----> 1 unicode('0001 \xcf\xdd\xc2\xe4 \xc0\xef\xb8\xf9 \xb5\xe7\xbd\xe2\xc2\xc1')

UnicodeDecodeError: 'ascii' codec can't decode byte 0xcf in position 5: ordinal not in range(128)
Der Kodierungskommentar betrifft nur den Compiler, damit der weiss wie literale Unicode-Zeichenketten im Quelltext beim übersetzen dekodiert werden müssen. Zur Laufzeit des Python-Codes in dem Modul ist das vollkommen irrelevant.

Re: Klasse in einem Paket erweitern

Verfasst: Montag 3. Juni 2013, 10:01
von FlicFlac
Ich benutze Python 2.7.1.4
Wenn ich das in IDLE eingebe, dann kommt bei mir kein UnicodeDecodeError.

Kann es sein, dass das Encoding der Standardausgabe benutzt wird (sys.stdout.encoding)?

Re: Klasse in einem Paket erweitern

Verfasst: Montag 3. Juni 2013, 10:32
von BlackJack
@FlicFlac: Ein Grund warum ich nicht so viel von Python-IDEs halte. Die meisten weichen irgendwo vom normalen Verhalten ab, was einem dann auf die Füsse fällt wenn man ein Programm ohne die IDE laufen lässt.

Die geratene Kodierung der Standardausgabe hat damit nichts zu tun. Ergänzend zu meinem letzten interaktiven Beispiel:

Code: Alles auswählen

In [9]: import sys

In [10]: sys.stdout.encoding
Out[10]: 'UTF-8'
Es wird bei `unicode()` ohne zweites Argument trotzdem 'ascii' verwendet.