11. oktober 2012 - 08:55Der er
52 kommentarer og 1 løsning
Få længden af et mp4 klip online
Jeg har et site der afspiller videoklip som mp4 i Flowplayer. Jeg kunne godt tænke mig, at der var en måde at aflæse klippenes længde online, så det kunne skrives i detaljerne om klippet, før man klikkede på det for at se klippet. Er det mon muligt?
Jeg kan ikke rigtig blive klog på den online player, for det er også noget video software. Alle mine sites er opbygget med Flowplayer. Hvis du mener, at måske ved ombygning med ffmpeg player, så kunne det være muligt at få hentet et klips længde ud til brug i ASP, og hvis du ved mere, sig gerne til...
Min tanke var egentlig kun, at du skulle bruge ffmpeg til at hente oplysningerne om videoklippet ud af klippet og gemme disse i din database, sammen med de øvrige oplysninger. Resten burde kunne fungere som hidtil.
Udfordringen med ffmpeg er blot at den kører som en kommandolinje applikation, så derfor skal du nok omkring en shell og noget med at tolke på en fil som ffmpeg genererer, men det burde være til at løse.
Jeg har ikke kendskab til andre løsninger (men der findes jo nok nogle), hvor man kan opnå det samme uden at skulle igennem dette.
Jeg prøver lige at lure på ffmpeg, for at se om jeg kan finde noget mere konkret til dig...
Set objShell = Server.CreateObject("WScript.Shell") Set objWshScriptExec = objShell.Exec(ffmpegCommand) Set objStdOut = objWshScriptExec.StdOut
While Not objStdOut.AtEndOfStream strLine = objStdOut.ReadLine If InStr(strLine,"Duration") Then ' fortolk linjen og find info om varigheden af videoen... End If Wend
Har jeg fotstået det sådan, at ffmpeg scripting, har nogle muligheder, som måske kan hente længden af et klip, der vel at mærke ligger online på en webserver. Altså et mp4 h.264 kodet streaming klip?
Jeg ved ikke lige hvad ffmpeg scripting er for noget. Det jeg har benyttet mig af, er ffmpeg's kommandolinjeinterface til at aflæse nogle oplysninger om en video-fil. Da ffmpeg er et kommandolinjeværktøj, er det så nødvendigt at starte en kommandoprompt fra det miljø, hvori programmet skal bruges (hvis altså miljøet ikke i forvejen er kommandolinje :-)). I ASP kan man bruge WScript.Shell, i andre miljøer gøres det måske på andre måder...
OK, det kan jeg godt se af scriptet. Men jeg er på bar bund ang. implementering. Den ffmpeg player, jeg har forsøgt at finde een som kører streaming... ffmpeg.org er en meget nørdet side.
Ja undskyld at jeg ikke lige fatter det. Jeg laver et projekt, og er snart ved afslutningen, har brugt usansynlig meget tid på research. Men at få hentet duration, ville være et stort plus. Vel at mærke duration skal kunne hentes og vises på en klipliste, hvorfra man starter klippet.
Du skal ikke skifte player. Du skal ikke ændre noget i det du har lavet. Du skal bare tilføje den viste stump kode i forbindelse med at videoen lægges på sitet. Jeg ved i sagens natur ikke hvordan det foregår og hvornår, men når det sker, køres koden altså på videofilen og de data der kommer ud af det, skal du gemme sammen med de øvrige oplysninger om videoen. Efter varigheden er aflæst med ffmpeg, kommer videoen i princippet ikke kontakt med ffmpeg igen og din video kan afspilles i samme afspiller som du hele tiden har benyttet.
ffmpegsti = fso.GetParentFolderName(Server.MapPath("/")) & "\bin\ffmpeg.exe" Det ser ud til at ffmpeg.exe er en fil, der skal ligge på webserveren. Det er vel så noget jeg skal spørge om vores host TeamNetHosting kan installere?
Egentlig skal du nok mest af alt have ffmpeg.exe lagt i en mappe, hvor du har eksekveringsrettigheder, for selve ffmpeg findes i en udgave, der blot kan køres uden forudgående installation.
Har fået lagt filer som beskrevet og prøvet med de to eksempler. Når jeg kører i roden af localhost, er der ingen shell fejl med at filen ikke bliver fundet.
While Not objStdOut.AtEndOfStream strLine = objStdOut.ReadLine If InStr(strLine,"Duration") Then ' fortolk linjen og find info om varigheden af videoen... Response.write "Duration skal findes her..." Else Response.write "Ikke fundet... Bliver ikke udskrevet heller..." End If Wend
Set objShell = Server.CreateObject("WScript.Shell") Set objWshScriptExec = objShell.Exec(ffmpegCommand) Set objStdOut = objWshScriptExec.StdOut
While Not objStdOut.AtEndOfStream strLine = objStdOut.ReadLine If InStr(strLine,"Duration") Then ' fortolk linjen og find info om varigheden af videoen... Response.write "Duration skal findes her..." Else Response.write "Ikke fundet... Bliver ikke udskrevet heller..." End If Wend
Jeg har rettet referencerne til stierne og undladt, at omdirigere output fra kaldet til ffmpeg til en fil, da det output jo nu læses direkte fra shell objektets stdout-egenskab (der er altså ikke længere behov for en midlertidig fil til at opsamle output).
Stierne skulle nu være korrekte. MEN med ny kode, sker der intet. While Not objStdOut.AtEndOfStream løkken tilgås slet ikke. Og hvis jeg kalder ffmpeg.exe direkte, får jeg fejl-skiltet: programmet kan ikke startes da avcodec-52.dll mangler på computeren. Prøv at installere programmet igen for at løse dette problem. Jeg har så prøvet at downloade og kopier eavcodec-52.dll ind i forskellige windows foldere for dll'ere, uden held. Jeg har bare fra starten kopieret ffmpeg.exe ind i bin folderen, fra en download af en sansynlig udgave af ffmpeg.exe.
Jeg ved ikke om det løser problemet, men det skulle være en exe som indeholder alle afhængigheder, så du ikke burde installere noget (det duer jo heller ikke når det skal ligge på en webserver du ikke selv administrerer).
OK, med den statiske kommer der ikke fejl, når jeg kører den manuelt, der kommer en cmd et øjeblik. Men While Not objStdOut.AtEndOfStream løkken tilgås stadig ikke. Jeg ville naturligvis gerne betale mange flere point. Kan du muligvis demonstrere, at det virker på fx en side, som du uploader. Eller måske kræver det egentlig, at man har sin egen webserver, hvor man kan installere og teste.
Det lader til at ffmpeg ikke sådan uden videre bare skriver til stdout, så man skal åbenbart springe gennem lidt ringe for at få den til det. Prøv f.eks. med dette:
videosti = Server.MapPath("/video.mp4") set fso = Server.CreateObject("Scripting.FileSystemObject") ffmpegsti = Server.MapPath("/bin/ffmpeg.exe")
Set objShell = Server.CreateObject("WScript.Shell") Set objWshScriptExec = objShell.Exec(ffmpegCommand) Set objStdOut = objWshScriptExec.StdOut
While Not objStdOut.AtEndOfStream strLine = objStdOut.ReadLine If InStr(strLine,"Duration") Then ' fortolk linjen og find info om varigheden af videoen... Response.write "Duration skal findes her..." Else Response.write "Ikke fundet... Bliver ikke udskrevet heller..." End If Wend
Der skal vist være overensstemmelse mellem filtypen og det du skriver i parameteren -f, f.eks. -f mp4 for mp4-format, -f avi for avi-format osv. Det er dog ikke noget jeg er afklaret med, men det er jo noget du kan prøve lidt af.
Jeg har forsøgt ovenstående princip i en VBScript-fil på min lokale maskine, så det er testet i nogen omfang, men om det rent faktisk fungerer på en webserver... ja, det finder du jo nok hurtigt ud af.
Det jeg umiddelbart kan komme i tanke om der kunne være galt er, hvis formatet i -f parameteren ikke stemmer ovenens med den faktisk video's format, for der oplevede jeg nemlig også at output var blankt, så prøv evt. med en video hvor du er helt sikker på codec og se om det så fungerer. Du kan evt. lave testen lokalt inden du tester det på serveren.
Set objShell = CreateObject("WScript.Shell") Set objWshScriptExec = objShell.Exec(ffmpegCommand) Set objStdOut = objWshScriptExec.StdOut
cnt = 20 msg = "" While Not objStdOut.AtEndOfStream and cnt > 0 strLine = objStdOut.ReadLine
If InStr(1, strLine, "Duration", vbTextCompare) > 0 Then ' fortolk linjen og find info om varigheden af videoen... msg = "Duration findes!" cnt = 0 Else msg = msg & strLine & vbCrLf End If cnt = cnt - 1 Wend
objStdOut.Close
msgbox msg
Processen er: - Lav en fil lokalt og kald den test.vbs - Indsæt det ovenfor viste kode - Tilret stier til ffmpeg og video og gem - Dobbeltklikker på vbs-filen i stifinder
Hvis det fungerer skrives der noget i beskedboksen, ellers er den tom. Jeg har som sagt fået det til at fungere med dette script, så under de rigtige omstændigheder burde det også fungere for dig.
Hvis det så kommer til at fungere på din egen maskine, kan du jo prøve med de samme indstillinger på webserveren.
Tak igen. Men stadig blank. jeg har prøvet med lidt forskellige mp4 filer. Jeg ved at dem jeg anvender og har testet er beregnet til at streame i Flowplayer og har codec h.264.
Du skal muligvis tage højde for at stdout og stderr bufferne bliver fyldt og dermed vil få din kode til at hænge. Du kan finde en løsning på problemet her: http://support.microsoft.com/kb/960246
Med Set objStdOut = objWshScriptExec.StdErr Ca. 30 gange vises: Ikke fundet... Bliver ikke udskrevet heller...Ikke fundet... Bliver ikke udskrevet heller...
Ved vi nu mere om, hvad der kan prøves? Eller må vi give op her?
Du er da kommet meget nærmere et resultat, idet der rent faktisk læses noget output fra ffmpeg! Nu skal du blot have læst hele resultatet af stdout OG stderr og fundet Duration.
Hvis du prøver at lure på det link jeg refererede til, kan du se hvordan du skal læse begge dele fuldt ud. Derefter burde det jo være en form sag at tolke det output...
Du kunne jo evt. prøve at udskrive det der rent faktisk læses i stedet for at skrive generiske tekster, så kunne det være det var mere åbenlyst for dig, hvor tæt du rent faktisk er målet.
Jeg ahr nu været igennem linkets forklaring og eksempler. Fx har jeg testet med følgende, som giver fejl "Et objekt er obligatorisk: 'WScript' "
Set WSHShell = CreateObject("WScript.Shell") Set oExec = WSHShell.Exec("cscript scriptworker.vbs") With oExec Do While .Status = 0 WSHShell.Sleep 10 Do While Not .StdOut.AtEndOfStream WScript.Echo .StdOut.ReadLine
'Check the .StdErr to see if it is at the end of its 'stream. If not, call ReadLine on it If Not .StdErr.AtEndOfStream Then .StdErr.ReadLine End If Loop Loop End With
Jeg kan forstå hvordan parent og child objecterne virker, men kan ikke få det til at virke.
Har også prøvet at uploadet det hele til serveren, men får Access denied. Har ikke lige adgang til at tillade kørsel af ffmpeg.exe, men det skal vel og så køre lokalt først.
Det var meningen at du skulle bruge linket som inspiration til at tilpasse det vi var kommet frem til ikke bruge det kode der var vist...
Grunden til at du får fejlen er, at der refererer til en variabel, som ikke er erklæret eller instantieret (den variabel du ønsker at bruge hedder WSHShell og ikke WScript), men det er som nævnt lidt ligegyldigt ifht. den løsning du skal arbejde hen mod :-)
Det du skal fokusere på, er måden det med læsning af stdOut og stdErr er løst på, dvs. løkke i løkke med Sleep og læsning af de to buffere for at undgå at disse fyldes op. Resten skal være som det hidtil har været (#24 + patch fra #26, #27 og #29).
ok, og tak igen, men det er lidt som russisk for mig. Er det noget i den her retning?
Do While .Status = 0 objShell.Sleep 10 While Not objStdOut.AtEndOfStream strLine = objStdOut.ReadLine If InStr(strLine,"Duration") Then ' fortolk linjen og find info om varigheden... Response.write "Duration skal findes her..." Else Response.write "Den gik ikke..." End If Wend Loop
Nederst har jeg udfærdiget et script som er testet og kører lokalt på min maskine. Prøv lige at klippe scriptet ud og læg det i en ny fil (kald den f.eks. test.vbs). Placér den sammen med din testvideo (f.eks. video.mp4) og ffmpeg-programmet. Aktivér scriptet ved, fra en commandoprompt, at skifte til mappen med de 3 filer og skriv så:
test video.mp4
Scriptet ser således ud:
option explicit
dim vsti, ffsti, objFac
vsti = WScript.Arguments(0) ffsti = "ffmpeg.exe" set objFac = GetRef("ObjectFactory")
Set objShell = ObjFactory("WScript.Shell") Set objExec = objShell.Exec(ffmpegCommand) Set objStdOut = objExec.StdOut Set objStdErr = objExec.StdErr
strTemp = "" strDuration = "" Do While objExec.Status = 0 'WScript.Sleep 10 Do While Not objStdOut.AtEndOfStream Or Not objStdErr.AtEndOfStream strLineOut = "" If Not objStdErr.AtEndOfStream Then strLineOut = objStdOut.ReadLine End If
strLineErr = "" If Not objStdErr.AtEndOfStream Then strLineErr = objStdErr.ReadLine End If
If InStr(1, strLineOut, strToFind, vbTextCompare) > 0 Then strLine = strLineOut ElseIf Instr(1, strLineErr, strToFind, vbTextCompare) > 0 Then strLine = strLineErr Else strLine = "" End If
If InStr(1, strLine, strToFind, vbTextCompare) > 0 Then strDuration = GetDuration(strLine) End If Loop Loop
objStdErr.Close objStdOut.Close
FindVideoDuration = strDuration end function
' ========================================================= ' fortolk linjen og find info om varigheden af videoen... ' ========================================================= function GetDuration(str) dim re, m, res
Set re = new RegExp re.Pattern = strToFind & "\: (\d{2}\:\d{2}\:\d{2}\.\d+)" re.IgnoreCase = true
Set m = re.Execute(str) If m.Count > 0 Then res = m(0).SubMatches(0) End If
GetDuration = res end function
' ========================================================= ' ========================================================= function ObjectFactory(PROGID) set ObjectFactory = CreateObject(PROGID) end function
Prøv at paste dette ind i en ASP-fil og kør det på webserveren (hvor video-filen, dvs. i eksemplet video.mp4, er placeret i roden af sitet og ffmpeg.exe er placeret i bin-mappen, som ligeledes ligger i roden af sitet):
option explicit
dim vsti, ffsti, objFac
vsti = Server.MapPath("/video.mp4") ffsti = Server.MapPath("/bin/ffmpeg.exe") set objFac = GetRef("ObjectFactory")
Set objShell = ObjFactory("WScript.Shell") Set objExec = objShell.Exec(ffmpegCommand) Set objStdOut = objExec.StdOut Set objStdErr = objExec.StdErr
strTemp = "" strDuration = "" Do While objExec.Status = 0 Do While Not objStdOut.AtEndOfStream Or Not objStdErr.AtEndOfStream strLineOut = "" If Not objStdErr.AtEndOfStream Then strLineOut = objStdOut.ReadLine End If
strLineErr = "" If Not objStdErr.AtEndOfStream Then strLineErr = objStdErr.ReadLine End If
If InStr(1, strLineOut, strToFind, vbTextCompare) > 0 Then strLine = strLineOut ElseIf Instr(1, strLineErr, strToFind, vbTextCompare) > 0 Then strLine = strLineErr Else strLine = "" End If
If InStr(1, strLine, strToFind, vbTextCompare) > 0 Then strDuration = GetDuration(strLine) End If Loop Loop
objStdErr.Close objStdOut.Close
FindVideoDuration = strDuration end function
' ========================================================= ' fortolk linjen og find info om varigheden af videoen... ' ========================================================= function GetDuration(str) dim re, m, res
Set re = new RegExp re.Pattern = strToFind & "\: (\d{2}\:\d{2}\:\d{2}\.\d+)" re.IgnoreCase = true
Set m = re.Execute(str) If m.Count > 0 Then res = m(0).SubMatches(0) End If
GetDuration = res end function
' ========================================================= ' ========================================================= function ObjectFactory(PROGID) set ObjectFactory = Server.CreateObject(PROGID) end function
DET VIRKER. Der skulle også en ordentlig stak kode til. Men nu kan jeg bare kalde en funktion, og så få skrevet længden ind i databasen. Så er det bare at få det til at køre med exe tilladelse på webhostingen. Men det går nok.
det kører også i andet en wwwroot. Jeg har uploaded det samme til den rigtige webserver, den ser ikke ser ud til at give access denied. Men foreløbig er der denne fejl:
WshShell.Exec error '800700c1'
C:\websider\domaene.dk\www\bin\ffmpeg.exe -i "C:\websider\domaene.dk\www\video.mp4" is not a valid Win32 application.
Det er sådan set klaret. Jeg placerede ffmpeg.exe i roden af webhostingen, og her må den åbenbart godt exekveres. Måske det er hensigsmæssigt at få den lidt af vejen.
PS: Sjovt nok, kan jeg fra localhost undersøge længden på videofiler der ligger på webserveren... Meget praktisk, så kan scriptet også kkøre, når jeg tester lokalt.
Ja, jeg ville nok også prøve at få den helt væk fra sitets filstruktur (f.eks. i en mappe sidestillet med root på webserveren). Det er sjældent godt at have eksekverbare programmer tilgængeligt i sitestrukturen (med mindre der er taget specielle forholdsregler).
Hvis du lige er med lidt endnu... Har lige et lille problem. Jeg lægger scriptet/funktionen ind i bunden af min conf fil. I den og på resten af sitet bruger jeg ikke option explicit, jeg synes det går fint uden, og koderne er mere overskuelige. Når jeg fjerner det og dim i koden, får jeg en fejl (ikke når jeg kører scriptet seperat):
Der opstod en Microsoft VBScript-kompileringsfejl fejl '800a0411'
Jeg kan kun anbefale dig at bruge option explicit og at erklære din variable inden de bruges (med dim) og helst i det scope hvor de er relevante. Det giver dig en meget bedre mulighed for at fejlfinde, hvis du har stavet variabelnavne forkert, hvilket ellers kan være en pestillens at fejlfinde. Samtidig undgår du at komme til at overskrive andre variable med samme navn i det globale scope, som kan give nogle uventede sideeffekter, der igen er noget træls at fejlfinde på. Jeg er lidt forundret (og bekymret) over, at du bevidst vælger at sænke kvaliteten af din kode... :S
I dit konkrete tilfælde har du åbenbart allerede en anden variabel som hedder strToFind, så derfor kan du ikke definere en const med dette navn.
Forstår bekymringen, men jeg har valgt option explicit fra for år tilbage. Det hjælper ikke mig, jeg kan let nok finde en fejl. Dog er de koder du har forsynet mig med, noget udover min forstand. Men mine sider kører hurtigt nok uden de formaliteter. Jeg er efterhånden gået over til PHP, og her er der ikke den slags omstændighder.
Synes godt om
Ny brugerNybegynder
Din løsning...
Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.