Duration aus MP4 auslesen…

Ich möchte die Länge eines Filmes ermitteln, genauer, die Duration einer MP4-Datei.

Es gibt zwar die entsprechende Eigenschaft im MoviePlayer, aber die steht nur beim Abspielen zur Verfügung. Weil ich die Daten aber schnell von sehr vielen Filmen ermitteln möchte, fällt diese Möglichkeit flach.

Nun steht die Info ja vermutlich direkt irgendwo im Header der Datei und deshalb brauche ich ja noch lange nicht den ganzen Container zu interpretieren. Also gesucht und im Netz das hier gefunden…

found page…

Ich kann kein Java-Script, aber trotzdem ist das ganz gut zu verstehen, meine Interpretation ist diese…

Es reicht die ersten 100 Bytes der Datei zu lesen um darin die Kennung „mvhd“ zu finden. Von dieser Stelle aus kann man die Daten für TimeScale und Duration finden, die Filmlänge errechnet sich daraus. Kann ja nicht so schwer sein :slight_smile:

Also…

var f as folderItem

var b as binarystream
var mm as memoryBlock

f = specialFolder.desktop.child( “test.mp4” )
if f <> nil then

b = binaryStream.Open( f, true )
mm = b.read( 100 )
b.close
label1.text = mm.StringValue( 0, 100 )

end if

’ Der wesentliche Code der Seite, nur zum Nachsehen…
'const start = buffer.indexOf(Buffer.from(“mvhd”)) + 16;
'const timeScale = buffer.readUInt32BE(start);
'const duration = buffer.readUInt32BE(start + 4);
'const movieLength = Math.floor(duration / timeScale);

Leider finde ich schon in dem angezeigten Text das Label „mvhd“ nicht, auch nicht in den ersten 500 Bytes.

Was mache ich falsch?

Hast Du mal ein MP4 in einen HexEditor geladen? Bei einem random MP4 von der Festplatte sehe ich zwar mvhd aber danach nichts mehr:

für macOS?
Dann könnt ihr die AVAssetMBS Klasse nehmen im MBS Xojo Plugins.

Also auf der GitHub-Seite steht irgendwo ganz unten, dass die Kennungen auch schonmal am Ende der Datei stehen können, das scheint beim meiner Testdatei der Fall zu sein.
Das kann ich ja dann unterscheiden.

Das dahinter dann nichts käme, ist ok, denn zur ermittelten Position wird ja noch 16 addiert, da steht dann timeScale, ein Long dahinter duration.

Bei mir steht da aber immer Unsinn, wahrscheinlich, weil ich nicht weiß was “indexOf” macht, ich kann kein JS.

Buffer.from(“mvhd”) könnte die (erste gefundene) Bytepostion dieses Strings sein, aber was macht dann indexOf?.. Der Rest erklärt sich ja gut.

aString.IndexOf(anotherString) entspricht dem Xojo-Äquivalent, gibt also die Position eines Strings innerhalb eines anderen zurück (0-basiert). Angenommen, du hast dein Video komplett in einen String (oder MemoryBlock) geladen und suchst “mvhd” darin. Dann wäre das Xojo-Ergebnis im Beispielbild &h02c = dec. 44. Auf diese Position 16 addiert gäbe
Var TimeScale as Integer = b.Uint32value(60)
und
Var Duration as Integer = b.uint32value(64)
(Position + 16 und + 20 dec.). Falls auf Windows 32 bit, lieber Uint32 oder Int64 für die Variablen verwenden, sonst könnte ein hoher Wert negativ interpretiert werden)

Ah, super, verstehe…

Buffer.from(“mvhd”) stellt, wie ich gerade gelernt habe, auch nur den Speicher für den Suchstring dar, mit dem dann mit indexOf gesucht wird.

Ich denke, das bekomme ich jetzt hin. Vielen Dank.

Wenn ich fertig bin, stelle ich das Resultat hier rein.

Zum Testen habe ich mir einen Film genommen, bei dem die Kennung auch wirklich vorne in den ersten 100 Bytes liegt, als erstes Positiv-Muster.

Zu finden hier…
https://mediathekviewweb.de

“Handelsforscher Rieb zur Zukunft der Warenhäuser, Morgenmagazin, 23.6 MB”

Folgende Methode sollte eigentlich stimmen, funktioniert aber nicht. TimeScale und Duration sind ähnlich riesig (2163133440 und 2159738880, zumindest positiv), was dividiert und mit Floor aber immer 1 ergibt.

Irgendwas mache ich noch falsch.

getMP4Duration( f as folderItem) as integer

if f = nil or not f.exists then return -1
if right( f.name, 4 ) <> ".mp4" then return -2

var b as binaryStream = binaryStream.Open( f, true )
if b = nil then return -3

var m as memoryBlock = b.read( 100 )
if m = nil then return -4

b.close

var s as string = m.StringValue( 0, 100 )
var start as integer = s.IndexOfBytes( "mvhd" ) + 16
var timeScale as integer = m.UInt32Value( start )
var duration as integer = m.UInt32Value( start + 4 )

return floor( duration / timeScale )

Nachdem ich alles Mögliche probiert habe, die Sache aber nicht funktionieren mag, habe ich die Vorgehensweise mal kontrolliert und dazu diese Seite, bzw. PDF-Datei gefunden…

Hier wird der Aufbau des MP4 beschrieben, auf Seite 4 unten die mvhd-Struktur. Nach der Kennung kommen vier ungebrauchte Bytes, vier für CreationTime, vier für ModificationTime und dann TimeScale und Duration, jeweils auch als Long. Mit anderen Worten, das ist alles völlig korrekt. Der Offset ist 16 Byte, beide Angaben sind Int mit 32 Bit.

Warum der Code als JS funktioniert, die Xojo-Umsetzung aber nicht, kann ich nicht ermitteln.

Nur kurz zur Information:

Ich hätte wirklich gerne hier eine Methode eingestellt, die jeder kostenfrei benutzen kann um die Laufzeit (und mehr) aus MP4-Datein lesen zu können. Leider bekomme ich das mit Xojo nicht hin, ich habe probiert, was mir einfiel und gefragt, wenn ich kannte, es klappt aber nicht.

Die Aufgabe löst nun jemand anderes, lustigerweise mit demselben JS-Code.

Irgendwie habe ich damals mit RealBasic mehr hinbekommen, mit Xojo klappt es irgendwie nicht mehr. Trotzdem besten Dank an Alle.

Um wie viele Dateien handelt es sich denn? Wenn man einen Movieplayer auf dem Fenster (im Beispiel “mp”) nur 1 Pixel groß macht, bei den Properties alles abschaltet (inkl. Visible) und nach dem Starten des Films eine ganz kurze Zeit wartet (“wait”), geht das schon mit “Bordmitteln” …

Sub Action() Handles Action
Dim f As folderitem = GetOpenFolderItem(“.”)
Dim mv As New Movie
If f<>Nil Then
mv = movie.open(f)
mp.Movie = mv
mp.Play
wait
MsgBox mp.Duration.ToString
mp.Stop
End If
End Sub

Public Sub wait()
Dim d As Double = Ticks + 3
While Ticks <d
app.DoEvents
wend
End Sub

Ohne wait() kommt bei mir immer Null bzw “NaN” (Mac).

Es geht letztlich um sehr viele Dateien, deshalb überhaupt der Weg der Programmierung.
Und natürlich finden sich auch “Workarounds” um doch noch zum Ziel zu gelangen, vielen Dank dafür.

Aber das ist keine Basis um zukünftig ernsthaft damit arbeiten zu können. REALbasic war eine tolle Sache die ich lange und gerne nutzte, Xojo heute ist für mich nicht mehr wirklich nutzbar, jedenfalls nicht ernsthaft. Auch die Sache mit der Siginierung, unfassbar wie viele Leute ich kennengelernt habe, die die Sache am Ende einfach hingeschmissen haben und abraten. Auch Hilfe zu finden, selbst bei simplen Sachen, ist problematisch.

Nein, danke, ich denke ich habe nun genug gelesen, gehört, bezahlt und verstanden um mich in Zukunft anderweitig zu orientieren.

Besten Dank und alles Gute.

1 Like

JEDES Programm unter macOS und Windows muß signiert werden. Das hat nichts mit Xojo zu tun. Die Signierung unter macOS ist dabei noch wesentlich einfacher als die für Windows.

1 Like

Hallo Stephan, für sowas bietet sich auch das ExifTool an, dass man leicht über eine Shell nutzen kann:

exiftool -duration FILE

Mit Blick auf das ursprüngliche Javascript übersiehst du, glaube ich, dass da “readUInt32BE” steht. BE für die Byte-Reihenfolge am Ende bedeutet BigEndian, Standard auf dem Mac ist aber längst LE (LittleEndian).

Du musst also dem Memoryblock sagen, dass er BigEndian interpretieren soll:
m.LittleEndian=false

Dann sollte es auch mit den Zahlendimensionen passen.

Hallo Stephan, hast du dich schon anderweitig orientiert? Das ist schade, denn der Code funktioniert mit der vorgeschlagenen Änderung. Vielleicht kannst du meinen Beitrag als Lösung markieren, dann haben andere Anwender noch etwas davon.

Viele Grüße, Thomas