FLV - Abmessungen und Aspect Ratio
Felix Riesterer
- multimedia (audio & video)
Liebe Mitleser,
ich möchte serverseitig die Abmesungen eines Videos im FLV-Format bestimmen. Ingo Turski hatte mir dazu schon einen sehr guten Tipp gegeben (Archivpost), mit dem ich in den meisten Fällen auch sehr gut zurecht komme. Meine Lösung (falls es wer mal wissen wollte):
function flv_metadaten ($dateipfad) {
$f = fopen($dateipfad, 'r');
$dat = '';
while (substr_count($dat, 'width') < 1 && substr_count($dat, 'height') < 1) {
$dat .= fread($f, 100);
}
$dat .= fread($f, 64);
fclose($f);
$meta_daten = array(
'width' => ord(substr($dat, strpos($dat, 'width') + 7, 1)),
'height' => ord(substr($dat, strpos($dat, 'height') + 8, 1))
);
// Dimensionen dekodieren
foreach ($meta_daten as $key => $val) {
// Wertigkeit der Angaben ermitteln (Danke an Ingo Turski http://forum.de.selfhtml.org/archiv/2008/8/t176089/#m1158126)
$wert = pow(2, ($val & 0xf0) /16 - 5) * 64
+ pow(2, ($val & 0xf0) /16 - 5) * 4 * ($val & 0x0f);
$meta_daten[$key] = $wert;
}
$metadaten['filename'] = preg_replace('~^(.*?/)?~', '', $dateipfad);
return $meta_daten;
}
Obige Lösung hat leider einen kleinen Schönheitsfehler. Sie liefert nur dann korrekte Werte, wenn das Seitenverhältnis (aspect ratio) 4:3 beträgt, und dann auch nicht immer. Bei Filmen im Verhältnis von 16:9 werden die ermittelten Werte immer zu klein. Dazu ein Beispiel:
Video 1:
echte Abmessungen 600x348 (~16:9)
ermittelte Abmessungen 576x336
Video 2:
korrekt ermittelte Abmessungen 512x384 (4:3)
Die echten Abmessungen bestätigt mir mein Mediaplayer. Da Ingos Rechenbeispiel anscheinend nicht immer gilt, habe ich versucht, Hinweise zu finden, die mir ermöglichen, diese "Abweichungen" zu kompensieren.
Mein erster Schritt war, nachzusehen, ob denn nicht ein weiteres Byte Aufschluss darüber geben könnte, um welches Seitenverhältnis es sich hier handelt. Und da fand ich bei Video 1 als "Folgebyte" jeweils nach dem besagten Byte für den x- oder y-Wert _keine_ Null (0x00) vor, sondern 192 (0xC0). Also habe ich mich zu Adobe aufgemacht, um irgendwelche Spezifikationen zu finden, in denen sich mein Verdacht bestätigen ließe, dass dieses weitere Byte etwas mit dem Seitenverhältnis zu tun haben könnte.
Die Specs habe ich zwar gefunden, aber mein Anliegen wird darin entweder nicht besprochen, was mich sehr verwundern würde, oder ich habe es schlicht und ergreifend nicht geschnallt und deswegen übersehen (was mich weniger wundern würde).
Mein nächste Schritt war, bei Youtube nach 16:9-Videos zu suchen, um meinen Verdacht mit ein paar Beispielen zu überprüfen. Hier das Ergebnis:
ein 16:9-Youtube-Video:
echte Abmessungen 456x240 (~16:9)
ermittelte Abmessungen 448x240
Zum Vergleich mein 16:9-Video:
echte Abmessungen 600x348 (~16:9)
ermittelte Abmessungen 576x336
Um die Dinge nun noch weiter zu verkomplizieren, habe ich ein viertes (eigenes) Video mit folgenden Werten:
echte Abmessungen 400x300 (4:3!!)
ermittelte Abmessungen 400x288 (???)
Ich komme nicht darauf, wie das alles miteinander zusammenhängt. Wer kann mir weiterhelfen und sagen, wie ich verlässlich die tatsächlichen Abmessungen (aspect ratio) eines FLV serverseitig ermittle?
Liebe Grüße,
Felix Riesterer.
Hi Felix.
Ich komme nicht darauf, wie das alles miteinander zusammenhängt. Wer kann mir weiterhelfen und sagen, wie ich verlässlich die tatsächlichen Abmessungen (aspect ratio) eines FLV serverseitig ermittle?
FLV ist ein Containerformat und lässt zunächst nicht auf die Kodierung schließen. Ob und wann in welchem FLV gute Metadaten gesetzt sind, weiß man auch nicht immer.
Ich würde mich an Deiner Stelle von der "Eigenentwicklung" zur Extraktion der Daten lösen.
Z.B. böte sich hier der mplayer an:
mplayer -noconsolecontrols -vo null -nosound -identify video.flv
und Du erhältst einen leicht zu parsenden Dump:
ID_LENGTH=189.25
ID_VIDEO_WIDTH=640
ID_VIDEO_HEIGHT=480
Wenn Du den mplayer aktuell hältst, kannst Du mit verlässlichen Daten rechnen.
Grüße
Lieber Dirk,
FLV ist ein Containerformat und lässt zunächst nicht auf die Kodierung schließen. Ob und wann in welchem FLV gute Metadaten gesetzt sind, weiß man auch nicht immer.
ACK.
Ich würde mich an Deiner Stelle von der "Eigenentwicklung" zur Extraktion der Daten lösen.
Dann muss ich mich aber auf z.T monströse Bibliotheken stützen, wie das im Archivthread angebotene Flv4PHP
Z.B. böte sich hier der mplayer an:
Bitte? Auf meinem Webserver (shared hosting)? Ich, als Windows-erzogener, der unter Linux nur rudimentäre Kenntnisse hat (und kaum Zeit, das zu ändern)? Also, ich weiß nicht...
mplayer -noconsolecontrols -vo null -nosound -identify video.flv
und Du erhältst einen leicht zu parsenden Dump:
Und wie ist das, wenn ich alle - sagen wir einmal so - 120 Video-Dateien vermessen will, um dann eine Auswahl in meinem CMS Editor anbieten zu können?
Wenn Du den mplayer aktuell hältst, kannst Du mit verlässlichen Daten rechnen.
Ja, das käme dann auch noch dazu. Aber vielen Dank für den Vorschlag.
Liebe Grüße,
Felix Riesterer.
Ahoi,
Bitte? Auf meinem Webserver (shared hosting)? Ich, als Windows-erzogener, der unter Linux nur rudimentäre Kenntnisse hat (und kaum Zeit, das zu ändern)?
Das zu ändern (also die Erziehung nicht, aber die Linux-Kenntnisse) bringt wirklich eine Menge, kann ich aus eigener Erfahrung sagen. Ich habe allerdings auch jemanden in der AG, der mir das alles beigebracht hat, bzw. da immer einen Schritt vorweg ist. Wer bei Windows noch die Konsole (also es war nicht Windows, es war DOS) mitbekommen hat, dem ist das alles auch garnicht so fern. Und es ist die Musik der Zukunft. Gerade für Schulen ist OpenSourc/GNU/freieSoftware ja eigentlich ein "muss". Und das Betriebssystem heißt übrigens korrekterweise GNU-Slash-Linux (;-).
Dank und Gruß,
Hi there,
ich hab mir das vor längerer Zeit in Quick&Dirtymodus geschrieben, bitte keine Kommentare bzgl. wie man das Coding hätte anders oder besser machen können, anyway, es funktioniert einfach...
function getFlvRatio($f)
{
if (! file_exists($f))
{
return false;
}
$fh=fopen($f,'r');
$header=fread($fh,2048);
$w=strpos($header,'width');
if ($w !== false)
{
$wString=substr($header,$w+7,4);
$width=chr_num(dechex(ord(substr($wString,0,1))),2).chr_num(dechex(ord(substr($wString,1,1))),2);
$h=strpos($header,'height');
$hString=substr($header,$h+8,4);
$height=chr_num(dechex(ord(substr($hString,0,1))),2).chr_num(dechex(ord(substr($hString,1,1))),2);
$wExp=hexdec(substr(chr_num(dec2hex(ord(substr($wString,0,1))),2),0,1))+1;
$wFaktor=pow(2,$wExp - 8);
$wA= pow(2,$wExp);
$wB= hexdec(substr($width,1,2)) * $wFaktor;
$hExp=hexdec(substr(chr_num(dec2hex(ord(substr($hString,0,1))),2),0,1))+1;
$hFaktor=pow(2,$hExp - 8);
$hA= pow(2,$hExp);
$hB= hexdec(substr($height,1,2)) * $hFaktor;
$flvD[0]=$wA+$wB;
$flvD[1]=$hA+$hB;
$flvD[2]=(($hA+$hB) != 0)?($wA+$wB)/($hA+$hB):0;
}
else
{
$flvD[0]=-1;
$flvD[1]=-1;
$flvD[2]=-1;
}
return $flvD;
// dazu noch 3 kleine Funktionen, die ich aus Faulheit gern verwende:
function dec2hex($_dec)
{
$_hex=dechex($_dec);
if (strlen($_hex) < 2)
{
$_hex="0".$_hex;
}
return $_hex;
}
function chr_num($v,$hm){
$lv=len($v);
return repl("0",$hm-$lv).$v;
}
function repl($_char,$_howmany){
$_tmpstr="";
for ($_i = 1;$_i <=$_howmany;$_i++){
$_tmpstr=$_tmpstr.$_char;
}
return $_tmpstr;
}
Rückgabe ist ein Array, [0]=width, [1]=height, [2]= ratio
kann man sicher optimieren, war mir aber wurscht, ich verwend' das seit einigen Jahren, hat bis jetzt immer funktionert...
Lieber Klawischnigg,
schonmal ganz herzlichen Dank für Deinen Code! Ich werde mir das mor^H^H^Hheute im Verlauf des Tages anschauen und ausprobieren. Zu meinen Erkenntnissen und Ergebnissen werde ich dann hier wieder Bericht erstatten.
Liebe Grüße,
Felix Riesterer.
Lieber Klawischnigg,
was soll ich sagen? Es funzt! Ich erhalte bei allen FLV-Dateien die korrekten Abmessungen! Vielen herzlichen Dank!
Wenn's mich interessiert, dann kann ich ja einmal herausfinden, wo in Deinem Script die "Abweichungen", die bei meinem Script auftreten, kompensiert werden - denn das werden sie.
Spitze!!!
Liebe Grüße,
Felix Riesterer.
Lieber Klawischnigg,
ich habe mir das in eine handliche kleine Klasse verpackt. Falls es jemand außer mir einmal benutzen möchte, hier ist sie:
class FLVDimensionsChecker {
var $filename;
var $width;
var $height;
var $aspectRatio;
var $error = 'No video dimensions detected!';
function FLVDimensionsChecker($f) {
$this->filename = $f;
if (! file_exists($f)) {
$this->error = 'File not found!';
return;
}
$fh=fopen($f,'r');
$header=fread($fh,2048);
$w=strpos($header,'width');
if ($w !== false) {
$wString=substr($header,$w+7,4);
$width=$this->chr_num(dechex(ord(substr($wString,0,1))),2).$this->chr_num(dechex(ord(substr($wString,1,1))),2);
$h=strpos($header,'height');
$hString=substr($header,$h+8,4);
$height=$this->chr_num(dechex(ord(substr($hString,0,1))),2).$this->chr_num(dechex(ord(substr($hString,1,1))),2);
$wExp=hexdec(substr($this->chr_num($this->dec2hex(ord(substr($wString,0,1))),2),0,1))+1;
$wFaktor=pow(2,$wExp - 8);
$wA= pow(2,$wExp);
$wB= hexdec(substr($width,1,2)) * $wFaktor;
$hExp=hexdec(substr($this->chr_num($this->dec2hex(ord(substr($hString,0,1))),2),0,1))+1;
$hFaktor=pow(2,$hExp - 8);
$hA= pow(2,$hExp);
$hB= hexdec(substr($height,1,2)) * $hFaktor;
$this->width =$wA+$wB;
$this->height =$hA+$hB;
$this->aspectRatio =(($hA+$hB) != 0)?($wA+$wB)/($hA+$hB):0;
$this->error = '';
}
}
function dec2hex($_dec) {
$_hex=dechex($_dec);
if (strlen($_hex) < 2) {
$_hex='0'.$_hex;
}
return $_hex;
}
function chr_num($v,$hm){
$lv=strlen($v);
return $this->repl('0',$hm-$lv).$v;
}
function repl($_char,$_howmany){
$_tmpstr="";
for ($_i = 1;$_i <=$_howmany;$_i++) {
$_tmpstr=$_tmpstr.$_char;
}
return $_tmpstr;
}
}
Liebe Grüße,
Felix Riesterer.
Ahoi Felix,
falls es dich interessieren sollte http://framework.zend.com/manual/de/coding-standard.html, ich finde den Coding Standard garnich so verkehrt. Für die Übersicht (die jetzt bei Dir sicher nicht im Vordergrund stand) zB. auch die Unterscheidung zwischen public und private Funktionen/Vars mit dem vorgestellten Underline.
Hat es einen Grund, warum du __construct() nicht benutzt?
Dank und Gruß,
Hi there,
Wenn's mich interessiert, dann kann ich ja einmal herausfinden, wo in Deinem Script die "Abweichungen", die bei meinem Script auftreten, kompensiert werden - denn das werden sie.
Schwer zu sagen, weil ich Deinen Zugang zum Problem nicht kenne. Ich hab mir damals die Specs durchgelesen, festgestellt, daß ich sie nicht verstehe, und hab mir dann einfach ein paar FLV-Dateien mit ungewöhnlichen Dimensionen produziert. Dann bin ich mit einem Hexeditor auf die Suche nach diesen Dimensionen gegangen und bin nach einigen Überlegungen und Herumprobieren auf meinen Lösungsansatz gekommen.
Spitze!!!
Passt...;)
Moin.
Die Kodierung der Videogröße ist Codec-abhängig, die von flv4php verwendeten Algorithmen kann man sich hier anschauen.
Klawischniggs Code dürfte den letzen Fall (CODEC_ON2_VP6/CODEC_ON2_VP6ALPHA) mehr oder weniger reproduzieren.
Christoph
Wieder ich.
Ignoriert bitte meine ursprüngliche Antwort - flv4php scheint die width- und height-Angaben gar nicht auszuwerten, sondern liest sie an anderer Stelle aus.
Ich habe mir mal Klawischniggs Antwort genauer angeschaut und dabei festgestellt, dass die Größenangabe anscheinend als 12bit-Fließkommawert vorliegt.
Als handliche PHP-Funktion liest sich das wie folgt:
function flvdim($name) {
$file = @fopen($name, 'rb');
if($file === false)
return false;
$header = fread($file, 2048);
fclose($file);
if($header === false)
return false;
return array(
'width' => flvdim_get($header, 'width'),
'height' => flvdim_get($header, 'height')
);
}
function flvdim_get($header, $field) {
$pos = strpos($header, $field);
if($pos === false)
return false;
else $pos += strlen($field) + 2;
return flvdim_decode(ord($header[$pos]), ord($header[$pos + 1]));
}
function flvdim_decode($byte1, $byte2) {
$high1 = $byte1 >> 4;
$high2 = $byte2 >> 4;
$low1 = $byte1 & 0x0f;
$mantissa = ($low1 << 4) | $high2;
return (2 << $high1) + (($mantissa << $high1) >> 7);
}
Christoph
Lieber Christoph,
Dein Code ist wesentlich schlanker, als der von Klawischnigg. Ich habe daher die Klasse, die ich mir aus Klawischniggs Code gebastelt habe, durch eine aus Deinem Code ersetzt. Falls es einmal wer gebrauchen kann, hier der Code:
class FLVDimensionsChecker {
var $filename;
var $width;
var $height;
var $aspectRatio;
var $error = 'No video dimensions detected!';
function FLVDimensionsChecker($f) {
$this->filename = $f;
$file = @fopen($f, 'rb');
if ($file === false)
$this->error = 'File not found!';
$header = fread($file, 2048);
fclose($file);
if ($header !== false) {
$this->width = $this->flvdim_get($header, 'width');
$this->height = $this->flvdim_get($header, 'height');
$this->aspectRatio = ($this->width / $this->height);
$this->error = '';
}
}
function flvdim_get($header, $field) {
$pos = strpos($header, $field);
if ($pos === false)
return 0;
else $pos += strlen($field) + 2;
return $this->flvdim_decode(ord($header[$pos]), ord($header[$pos + 1]));
}
function flvdim_decode($byte1, $byte2) {
$high1 = $byte1 >> 4;
$high2 = $byte2 >> 4;
$low1 = $byte1 & 0x0f;
$mantissa = ($low1 << 4) | $high2;
return (2 << $high1) + (($mantissa << $high1) >> 7);
}
}
Liebe Grüße,
Felix Riesterer.
Liebe(r) Felix,
$file = @fopen($f, 'rb');
if ($file === false)
$this->error = 'File not found!';$header = fread($file, 2048);
fclose($file);
das ist natürlich Käse. Es muss dort so heißen:
~~~php
$file = @fopen($f, 'rb');
if ($file === false)
$this->error = 'File not found!';
else
$header = fread($file, 2048);
fclose($file);
Liebe Grüße,
Felix Riesterer.