• Jetzt anmelden. Es dauert nur 2 Minuten und ist kostenlos!

Seltsame Ausgabe bei Prüfung auf Umlaute

  • Ersteller Ersteller ohrflieger
  • Erstellt am Erstellt am
O

ohrflieger

Guest
Abend,
mein Script sollte eigentlich eine XML-Datei mit einer Liste von MP3-Dateien + Titeln generieren. Leider gibt es seltsame Probleme mit Umlauten. Die Titel werden aus den ID3-Tags der MP3-Datei ausgelesen, und wenn sich dort ein Umlaut befindet, wird ein Alternativer Titel angezeigt.
Verständlicher wird es am Script:

PHP:
function checkUml($zeile) {
	$zeile = str_replace("ü","ü",$zeile);
	$zeile = str_replace("Ü","Ü",$zeile);
	$zeile = str_replace("ä","ä",$zeile);
	$zeile = str_replace("Ä","Ä",$zeile);
	$zeile = str_replace("ö","ö",$zeile);
	$zeile = str_replace("Ö","Ö",$zeile);
	$zeile = str_replace("ß","ß",$zeile);
	return $zeile;
}

function checkUml2($zeile) {
	$error = true;
	if(strpos($zeile,"ü")) $error = false;
	if(strpos($zeile,"Ü")) $error = false;
	if(strpos($zeile,"ä")) $error = false;
	if(strpos($zeile,"Ä")) $error = false;
	if(strpos($zeile,"ö")) $error = false;
	if(strpos($zeile,"Ö")) $error = false;
	if(strpos($zeile,"ß")) $error = false;
	return $error;
}

			$streamverz = 'files/downloads/'.$actexp[2].'/stream/';
			if(is_dir($streamverz)) {
				$verz = opendir($streamverz);
				while($file = readdir($verz)) {
					if(substr(strrev($file),0,4) == '3pm.') {
						$tracks[] = $file;
					}
				}
				sort($tracks);
				$nr = 1;
				foreach($tracks as $play) {
					$tags = getID3v2Tags($streamverz.$play);
					if($tags['TIT2'] != '') {
						if(checkUml2(trim($tags['TIT2']))) $titel = checkUml(trim($tags['TIT2']));
						else $titel = $actexp[2].' Track '.$nr;
					} else $titel = $actexp[2].' Track '.$nr;
					echo '<item name="'.$titel.'" filename="'.$actexp[2].'/stream/'.$play.'" />';
					$nr++;
					unset($tags);
				}
			} else {
				echo '<item name="Fehler2" filename="" />';
			}

Teilweise werden dort willkürlich Alternativtitel angezeigt, wo gar keine Umlaute vorhanden sind (die Namen der MP3s sind gleichzeitig die ID3-Titel).
Wo liegt das Problem?
Wenn jemand einen besseren Lösungsvorschlag hat, gerne raus damit :)

EDIT: getID3v2Tags() ermittelt nur die MP3-Tags und hat sonst nichts mit dem Script zu tun.
 
Zuletzt bearbeitet von einem Moderator:
Du musst nur in checkUml2 true und false vertauschen, weil du ja die Umlaute austauschen lässt, falls checkUml2 true zurückliefert:
PHP:
function checkUml2($zeile) {
    $error = false;
    if(strpos($zeile,"ü")) $error = true;
    if(strpos($zeile,"Ü")) $error = true;
    if(strpos($zeile,"ä")) $error = true;
    if(strpos($zeile,"Ä")) $error = true;
    if(strpos($zeile,"ö")) $error = true;
    if(strpos($zeile,"Ö")) $error = true;
    if(strpos($zeile,"ß")) $error = true;
    return $error;
}
mfg Bleistift
 
Das stimmt so nicht. Die Funktion hätte ich in dem Fall gar nicht gebraucht (ist noch ein Überbleibsel von meinen Experimenten).
Noch einmal erklärt:

PHP:
if($tags['TIT2'] != '') {//wenn ID3-Tag nicht leer
                        if(checkUml2(trim($tags['TIT2']))) $titel = trim($tags['TIT2']); //wenn im ID3-Tag KEINE Umlaute vorhanden sind, soll der Titel im ID3-Tag in $titel gespeichert werden
                        else $titel = $actexp[2].' Track '.$nr; //wenn Umlaute vorhanden SIND, Ersatztitel anzeigen
} else $titel = $actexp[2].' Track '.$nr; //wenn ID3-Tag leer, Ersatztitel in $titel speichern

Das erklärt aber immer noch nicht, warum das Script auch Ersatztitel anzeigt, wo gar keine Umlaute auftauchen?
 
Ich finde es schwierig, das Problem nachzuvollziehen. Was sollte denn angezeigt werden und was wird angezeigt?

Gruß Marc
 
Okay, ist vielleicht etwas kompliziert, aber ich versuche es zu erklären :)

Ziel: Es soll eine Playlist für einen Stream-Flashplayer erstellt werden. Dazu werden alle MP3-Dateien in dem Verzeichnis /stream/ ausgelesen und in eine XML-Datei als Playlist geschrieben (siehe obiger Link).
Als Titelname wird der Songtitel aus den ID3-Tags der MP3-Dateien ausgelesen.
Da liegt das Problem: Sind nämlich Umlaute im Songtitel vorhanden, motzt XML und gibt Zeichensalat aus, da die Umlaute nicht UTF-8 codiert sind.
Um das Problem zu lösen, habe ich bereits versucht, die ID3-Titel von Umlauten zu befreien, aber das hat nicht funktioniert (warum auch immer). Also wollte ich da, wo Umlaute im Songtitel auftauchen, einen Alternativtitel anzeigen lassen (z.B. "Track 14"). Hierbei werden leider nicht nur die Songtitel ersetzt, die Umlaute enthalten, sondern (scheinbar willkürlich) verschiedene andere Titel (siehe Link oben).

Die ID3-Tag-Titel sind in diesem Fall die gleichen wie die Dateinamen, aber das trifft nicht immer zu (das Script erzeugt verschiedene Playlists, je nach Parameter).
Vielleicht habe ich irgendwo einen Logikfehler im Script, aber weil ich nichts gefunden habe, frage ich hier ;)
 
Da liegt das Problem: Sind nämlich Umlaute im Songtitel vorhanden, motzt XML und gibt Zeichensalat aus, da die Umlaute nicht UTF-8 codiert sind.

Was heißt das genau? Gibt es eine Fehlermeldung? Wie sieht der ausgegebene Zeichensalat aus?

Ich würde tippen, dass es ausreicht, die Inhalte der Tags nach UTF-8 zu konvertieren. Probier einfach mal utf8_encode.

Gruß Marc
 
Code:
<?xml version="1.0"?>
<media>
<item name="þÿ�G�e�s�e�t�z� �g�e�g�e�n� �S�c�h�u�l�e�s�c�h�w�ä�n�z�e�n" filename="stream/Gesetz gegen Schuleschwaenzen.mp3" />
<item name="þÿ�W�a�s� �f�ü�r� �E�u�l�e�n" filename="stream/Was fuer Eulen.mp3" />
</media>

Code:
XML-Verarbeitungsfehler: nicht wohlgeformt
Adresse: http://***
Zeile Nr. 1, Spalte 856:

Das kommt dabei heraus (mit utf8_encode). Keine Ahnung wie das zustande kommt, aber deshalb frage ich ja. Aber scheinbar weiß das keiner.
 
Zuletzt bearbeitet von einem Moderator:
PHP:
<?php // Datei ist in UTF-8 kodiert

header('Content-Type: text/html; charset=ISO-8859-1');

echo iconv('UTF-8', 'UTF-16', 'Schulschwänzer');

// Ausgabe: ÿþS�c�h�u�l�s�c�h�w�ä�n�z�e�r�

Das ist UTF-16. Mit mb_detect_encoding solltest du feststellen können, in welchem Zeichensatz ein Feld vorliegt, mit iconv lässt sich der Inhalt umformen. "þÿ" ist eine Byte order mark, aber das ist hier wahrscheinlich nicht weiter wichtig.

Gruß Marc
 
Zuletzt bearbeitet:
Ich hab die Funktionen mal ausprobiert.
Bei mb_detect_encoding() gibt er vor, dass die nach UTF-16 aussehenden Titel mit Umlauten als Zeichensatz UTF-8 haben. Bei den anderen sagt er ASCII:

Code:
<?xml version="1.0"?><media>
<item name="ASCII - Schlangen" filename="stream/Schlangen.mp3" />
<item name="UTF-8 - ���G�e�s�e�t�z� �g�e�g�e�n� �S�c�h�u�l�e�s�c�h�w���n�z�e�n" filename="stream/Gesetz gegen Schuleschwaenzen.mp3" />
<item name="UTF-8 - ���W�a�s� �f���r� �E�u�l�e�n" filename="stream/Was fuer Eulen.mp3" />
</media>

Also hab ich versucht, den Titel von UTF-16 zu UTF-8 zu konvertieren, wenn mb_detect_encoding angibt, dass UTF-8 vorliegt. Aber das funktioniert überhaupt nicht. Die betroffene Stelle des PHP-Codes:

PHP:
if($tags['TIT2']) {
	if(mb_detect_encoding($tags['TIT2']) == 'UTF-8') $titel = iconv('UTF-16', 'UTF-8', $tags['TIT2']);
	else $titel = mb_detect_encoding($tags['TIT2']).' - '.$tags['TIT2'];
} else $titel = $actexp[2].' Track '.$nr.' (Fehler '.$tags[_error].')';
 
Zuletzt bearbeitet von einem Moderator:
Natürlich. Setz bei den Pfaden in der XML-Datei einfach noch [aus Sicherheitsgründen entfernt] davor.
 
Zuletzt bearbeitet von einem Moderator:
So sollte es klappen.

Gruß Marc

PHP:
<?php

error_reporting(E_ALL | E_STRICT);

class Id3
{
    /**
     * Get ID3v2 tags from file
     *
     * Slightly modified version. Original found at:
     *   http://www.tutorials.de/forum/php/13267-mp3-und-php.html#post102911
     *
     * @param  string $file
     * @param  int    $blnAllFrames
     * @return array
     */
    public static function getTagsV2($file, $blnAllFrames = 0)
    {
        $arrTag['_file'] = $file;
        $fp = fopen($file, 'rb');
        if ($fp) {
            $id3v2 = fread($fp, 3);
            if ($id3v2 == 'ID3') {
                // a ID3v2 tag always starts with 'ID3'
                $arrTag['_ID3v2'] = 1;
                // = version.revision
                $arrTag['_version'] = ord(fread($fp, 1)) . '.' . ord(fread($fp, 1));
                // skip 1 'flag' byte, because i don't need it :)
                fseek($fp, 6);
                $tagSize = 0;
                for($i=0;$i<4;$i++){
                    $tagSize=$tagSize.base_convert(ord(fread($fp,1)),10,16);
                }
                $tagSize=hexdec($tagSize);
                if($tagSize>filesize($file)){
                    $arrTag['_error']=4;// = tag is bigger than file
                }
                fseek($fp,10);
                while(ereg("^[A-Z][A-Z0-9]{3}$",$frameName=fread($fp,4))){
                    $frameSize = 0;
                    for($i=0;$i<4;$i++){
                        $frameSize=$frameSize.base_convert(ord(fread($fp,1)),10,16);
                    }
                    $frameSize=hexdec($frameSize);
                    if($frameSize>$tagSize){
                        $arrTag['_error']=5;// = frame is bigger than tag
                        break;
                    }
                    fseek($fp,ftell($fp)+2);// skip 2 'flag' bytes, because i don't need them :)
                    if($frameSize<1){
                        $arrTag['_error']=6;// = frame size is smaller then 1
                        break;
                    }
                    if($blnAllFrames==0){
                        if(!ereg("^T",$frameName)){// = not a text frame, they always starts with 'T'
                            unset($arrTag[$frameName]);
                            fseek($fp,ftell($fp)+$frameSize);// go to next frame
                            continue;// read next frame
                        }
                    }
                    $frameContent=fread($fp,$frameSize);
                    if(!(isset($arrTag[$frameName]))) {
                        $arrTag[$frameName]=trim($frameContent);// the frame content (always?) starts with 0, so it's better to remove it
                    }
                    else{// if there is more than one frame with the same name
                        $arrTag[$frameName]=$arrTag[$frameName]."~".trim($frameContent);
                    }
                }// while(ereg("^[A-Z0-9]{4}$",fread($fp,4)))
            }// if($id3v2=="ID3")
            else{
                $arrTag['_ID3v2']=0;// = no ID3v2 tag found
                $arrTag['_error']=3;// = no ID3v2 tag found
            }
        }// if($fp)
        else{
            $arrTag['_error']=2;// can't open file
        }
        fclose($fp);

        return $arrTag;
    }
}

header('Content-Type: text/html; charset=UTF-8');

foreach (glob('./files/*.mp3') as $file) {
    $tags = Id3::getTagsV2($file);

    // Check for UTF-16 BOM (not sure why there's a \x01 byte in front)
    // (This snippet should be added to the function itself)
    if ((substr($tags['TIT2'], 0, 3) == "\x01\xFF\xFE")
        || (substr($tags['TIT2'], 0, 3) == "\x01\xFE\xFF")
    ) {
        $tags['TIT2'] = iconv('UTF-16', 'UTF-8', substr($tags['TIT2'], 1));
    }

    echo '<p>' . $tags['TIT2'] . '</p>';
}
 
Vielen, vielen Dank! :D Hat mich (dich vielleicht auch) ganz schön nerven gekostet...

Vermute ich richtig, dass die Konvertierung in UTF-8 an dem einen Byte gescheitert ist?
Und würden irgendwelche Probleme auftreten, wenn ich folgenden Teil der ID3-Funktion auskommentiere? Sonst werden längere Songtitel nicht eingelesen (das war das zweite Problem).

PHP:
       if($frameSize>$tagSize){
             $arrTag['_error']=5;// = frame is bigger than tag
             break;
       }
 
Ja, mehr oder weniger. Die eigentliche Ursache ist, dass der Autor der Funktion den Standard teilweise falsch implementiert hat. Daher kam auch das Problem mit den zu langen Frame-Inhalten.

Inzwischen ist's halbwegs annehmbar.

Gruß Marc

PHP:
<?php

error_reporting(E_ALL | E_STRICT);

/**
 * Reads frames from ID3 tags
 *
 * Please note: This class is far from perfect.
 *
 * Modelled after:
 *   http://www.id3.org/id3v2.3.0
 *
 * Heavily modified version of a function found at:
 *   http://www.tutorials.de/forum/php/13267-mp3-und-php.html#post102911
 * which refers to a 404 page at:
 *   http://projekt182.de/prg/mp3/ID3v2Info.htm
 *
 * @author  Marc Ermshaus <http://ermshaus.org/>
 * @version 2009-08-16
 */
class Id3
{
    /**
     * Decodes a 4-byte-encoded number into an integer
     *
     * Layout:
     *   [xAAA AAAA] [xBBB BBBB] [xCCC CCCC] [xDDD DDDD]
     *
     * Size:
     *   [0000 AAAA] [AAAB BBBB] [BBCC CCCC] [CDDD DDDD]
     *
     * TODO [me:2009-08-16] This function might be unnecessarily complicated
     *
     * @param  string $bytes
     * @return int
     */
    private static function decodeNumber($bytes)
    {
        $size = 0;
        $exp = 27;
        $byteArray = array();

        for ($i = 0; $i < strlen($bytes); $i++) {
            $byteArray[] = substr($bytes, $i, 1);
        }

        foreach ($byteArray as $b) {
            $b = ord($b);
            for ($i = 0; $i <= 6; $i++) {
                if ($b & pow(2, $i)) {
                    $size += pow(2, $exp - (6 - $i));
                }
            }
            $exp -= 7;
        }

        return $size;
    }

    /**
     * Converts the content of a frame to UTF-8
     *
     * First byte -> encoding:
     * 0x00 -> ISO-8859-1
     * 0x01 -> UTF-16
     *
     * @param  string $s
     * @return string
     */
    private static function convertToUtf8($s)
    {
        if (substr($s, 0, 1) == "\x00") {
            // ISO-8859-1
            $s = utf8_encode(substr($s, 1));
        } else if (substr($s, 0, 1) == "\x01"){
            // UTF-16
            $s = iconv('UTF-16', 'UTF-8', substr($s, 1));
        }

        return $s;
    }

    /**
     * Reads frames from the tag
     *
     * @param  resource $fp
     * @param  int      $tagSize
     * @param  bool     $onlyTextFrames
     * @return array
     */
    private static function getFrames($fp, $tagSize, $onlyTextFrames)
    {
        $frames = array();

        fseek($fp, 10);
        while (preg_match('/^[A-Z][A-Z0-9]{3}$/', $frameName = fread($fp, 4))) {
            $frameSize = self::decodeNumber(fread($fp, 4));

            if ($frameSize > $tagSize) {
                // = frame is bigger than tag
                break;
            } else if ($frameSize < 1) {
                // = frame size is smaller then 1
                break;
            }

            // skip 2 'flag' bytes, because i don't need them :)
            fseek($fp, ftell($fp) + 2);

            if ($onlyTextFrames) {
                if (substr($frameName, 0, 1) != 'T') {
                    // = not a text frame, they always starts with 'T'

                    // go to next frame
                    fseek($fp, ftell($fp) + $frameSize);
                    // read next frame
                    continue;
                }
            }

            $frameContent = self::convertToUtf8(fread($fp, $frameSize));

            // Append content if frame name did already exist for some reason
            if (!(isset($frames[$frameName]))) {
                $frames[$frameName] = $frameContent;
            } else {
                $frames[$frameName] .= '~' . $frameContent;
            }
        }

        return $frames;
    }

    /**
     * Gets ID3v2 tags from a file
     *
     * @param  string $file
     * @param  bool   $onlyTextFrames
     * @return array
     */
    public static function getTagV2($file, $onlyTextFrames = true)
    {
        $fp = fopen($file, 'rb');

        if (!$fp) {
            throw new Exception('Can\'t open file');
        }

        if (fread($fp, 3) != 'ID3') {
            throw new Exception('No ID3-Tag found');
        }

        $version = ord(fread($fp, 1)) . '.' . ord(fread($fp, 1));

        fseek($fp, 6);
        $tagSize = self::decodeNumber(fread($fp, 4));

        if ($tagSize > filesize($file)) {
            throw new Exception('Tag is bigger than file');
        }

        $frames             = self::getFrames($fp, $tagSize, $onlyTextFrames);
        $frames['_version'] = $version;

        fclose($fp);

        return $frames;
    }
}



header('Content-Type: text/plain; charset=UTF-8');

try {
    foreach (glob('./files/*.mp3') as $file) {
        $frames = Id3::getTagV2($file);

        foreach ($frames as $k => $v) {
            echo $k . ' = {' . $v . '}' . "\n";
        }

        echo "\n";
    }
} catch (Exception $e) {
    echo $e;
 
Zuletzt bearbeitet:
Zurück
Oben