Moin,
Anbei ist ein Skript, das aus einem Film einen "Movie Barcode" vorgegebener Größe erzeugt.
Der Code ist mehr oder weniger aus dem DE und EN Forum zusammengeklaut und neu zusammengesetzt. Da ich aber noch niemanden gesehen habe der so einen Generator gebastelt hat, dachte ich es wäre praktisch das mal zu machen. Damit das Skript funktioniert muss irgendwo eine FFMPEG.exe herumliegen. Da man sich heutzutage erstmal durch 20 Websites die 50 Versionen des Sourcecodes für alle möglichen Linux-Versionen anbieten klicken muss, bevor man eine .exe findet ist hier ein Direktlink: https://github.com/BtbN/FFmpeg-Builds/releases
Hier ist das Skript
#include <GDIPlus.au3>
#include <Memory.au3>
#include <Array.au3>
#include <Date.au3>
Global Const $ffmpeg_path = 'ffmpeg.exe'
; Movie Barcode test
_FFMPEG_Barcode('irgendein_film.avi', 'irgendein_film.png', 1024, 256)
Func _FFMPEG_Barcode($sFile_Input, $sFile_Output, $iWidth, $iHeight, $nOversampling = 0.1)
Local $iOffset = 0
Local $iFrames = 1000 ; Irgendeine Zahl größer als jeder Frame des Videos
Local $iFPS = 29.97
Local Const $tagBITMAPFILEHEADER = "struct;align 2;word bfType;dword bfSize;word bfReserved1;word bfReserved2;dword bfOffBits;endstruct;"
Local $tBMPHEADER = DllStructCreate($tagBITMAPFILEHEADER & $tagBITMAPINFO)
Local $tBMPHEADER_B = DllStructCreate("byte[" & DllStructGetSize($tBMPHEADER) & "];", DllStructGetPtr($tBMPHEADER))
Local $bData, $bTmp, $sErr, $iLen
Local $sCMD = __FFMPEG_CMDGetFrames($sFile_Input, 0, 0, 0, 0, 1)
ConsoleWrite("> CMD: " & $sCMD & @CRLF)
Local $hPID = Run($sCMD, @ScriptDir, @SW_HIDE, BitOR(0x2, 0x4))
Local $sStderr = ''
While True
Local $sTmp = StderrRead($hPID)
$sStderr &= $sTmp
If StringInStr($sStderr, 'Duration') Then ; Das steht nach Duration im Output
$sStderr = $sTmp
While True
$sStderr &= StderrRead($hPID)
If StringInStr($sStderr, 'Stream') Then ExitLoop 2
WEnd
EndIf
WEnd
Local $aLines = StringSplit($sStderr, @CRLF, 3), $iDuration, $nFPS_Source
For $i = 0 To UBound($aLines) - 1 Step 1
Local $sLine = StringStripWS($aLines[$i], 8)
If StringStartsWith($sLine, 'Duration') Then
Local $aSplit = StringSplit(StringSplit($sLine, ',', 2)[0], ':', 2)
$iDuration = Number($aSplit[1]) * 3600 + Number($aSplit[2]) * 60 + Number($aSplit[3])
EndIf
If StringStartsWith($sLine, 'Stream') Then
Local $aSplit = StringSplit($sLine, ',', 2)
For $ii = 0 To UBound($aSplit) - 1 Step 1
If StringInStr($aSplit[$ii], 'fps') Then
$nFPS_Source = Number($aSplit[$ii]) ; Schneidet den String "fps" ab und macht ein double daraus.
ExitLoop 2
EndIf
Next
EndIf
Next
ConsoleWrite('get info ...')
ProcessWaitClose($hPID, 1000)
ConsoleWrite('done.' & @CRLF)
$iFPS = $nFPS_Source * $nOversampling
$iFrames = Round($iDuration * $iFPS * 1.1) ; Etwas überschätzen damit platz ist.
Local $iFrameWidth = 1 ; Jeder Frame = 1 pixel breit
Local $iFrameHeight = $iHeight ; mal Oversampling
ConsoleWrite('Time[s] ' & $iDuration & @CRLF)
ConsoleWrite('FPS ' & $nFPS_Source & @CRLF)
ConsoleWrite('Frames ~ ' & StringLeft(Round($iDuration * $nFPS_Source) & ' ', 10) & ' ( ~ ' & Round($iDuration * $iFPS) & ' with oversampling factor of ' & $nOversampling & ')' & @CRLF)
$sCMD = __FFMPEG_CMDGetFrames($sFile_Input, $iOffset, $iFrames, $iFrameWidth, $iFrameHeight, $iFPS)
ConsoleWrite("> CMD: " & $sCMD & @CRLF)
$hPID = Run($sCMD, @ScriptDir, @SW_HIDE, BitOR(0x2, 0x4))
_GDIPlus_Startup()
Local $hBMP = _GDIPlus_BitmapCreateFromScan0($iFrames, $iFrameHeight)
Local $hGFX = _GDIPlus_ImageGetGraphicsContext($hBMP)
Local $vLINE = DllStructCreate('byte col[' & $iFrameHeight * 4 & ']') ; Reverse order (stride = -4)
Local $hLINE = _GDIPlus_BitmapCreateFromScan0(1, $iFrameHeight, $GDIP_PXF32ARGB, -4, DllStructGetPtr($vLINE) + ($iFrameHeight - 1) * 4)
Local $iLineOffset = 0, $bFirst = True
While True
$bTmp = StdoutRead($hPID, False, True)
If @error Then ExitLoop
If BinaryLen($bTmp) Then
$bData = Binary($bData & $bTmp)
While 1
DllStructSetData($tBMPHEADER_B, 1, $bData)
$iLen = BinaryLen($bData)
If $iLen < $tBMPHEADER.bfSize Then ExitLoop
If Not $bFirst Then ; Der erste Frame ist immer doppelt ?! Keine Ahnung warum...
DllStructSetData($vLINE, 1, BinaryMid($bData, 55, $tBMPHEADER.bfSize - 54))
_GDIPlus_GraphicsDrawImageRect($hGFX, $hLINE, $iLineOffset, 0, 1, $iFrameHeight)
$iLineOffset += 1
EndIf
$bFirst = False
$bData = BinaryMid($bData, $tBMPHEADER.bfSize + 1, $iLen)
WEnd
EndIf
$sErr = StderrRead($hPID)
;~ If $sErr Then ConsoleWrite($sErr)
Sleep(10)
WEnd
_GDIPlus_GraphicsDispose($hGFX)
_GDIPlus_BitmapDispose($hLINE)
Local $hArea = _GDIPlus_BitmapCloneArea($hBMP, 0, 0, $iLineOffset, $iFrameHeight, $GDIP_PXF32ARGB)
Local $hNew = _GDIPlus_ImageResize($hArea, $iWidth, $iHeight)
_GDIPlus_ImageSaveToFile($hNew, $sFile_Output)
_GDIPlus_BitmapDispose($hBMP)
_GDIPlus_BitmapDispose($hNew)
_GDIPlus_Shutdown()
EndFunc ;==>_FFMPEG_Test
Func StringStartsWith($sString, $sSubstring)
Return StringLeft($sString, StringLen($sSubstring)) = $sSubstring
EndFunc
Func __FFMPEG_CMDGetFrames($sFile, $iOffset, $iFrames, $iW, $iH, $iFPS)
Local $sCMD = $ffmpeg_path & ' -y'
If $iOffset Then
Local $_iH, $_iM, $_iS
_TicksToTime($iOffset, $_iH, $_iM, $_iS)
Local $_iMS = $iOffset - _TimeToTicks($_iH, $_iM, $_iS)
$sCMD &= ' -ss ' & StringFormat("%02s:%02s:%02s.%03s", $_iH, $_iM, $_iS, $_iMS)
EndIf
If $iFPS <> -1 Then
$sCMD &= ' -i "' & $sFile & '"' & ' -r ' & $iFPS
Else
$sCMD &= ' -i "' & $sFile & '"'
EndIf
If $iW > 1 Or $iH > 1 Then $sCMD &= ' -vf "scale=' & $iW & ':' & $iH & '"'
$sCMD &= ' -vframes ' & $iFrames & ' -update true -vcodec bmp -pix_fmt bgra -f image2 -'
Return $sCMD
EndFunc
; Jaaaa
Func __FFMPEG_GetAverageColor($aBMPi0)
Local $iData, $hData, $pData, $tData, $pStream, $hImage
$iData = BinaryLen($aBMPi0)
$hData = _MemGlobalAlloc($iData, $GMEM_MOVEABLE)
$pData = _MemGlobalLock($hData)
$tData = DllStructCreate("byte[" & $iData & "]", $pData)
DllStructSetData($tData, 1, $aBMPi0)
Local $bgra[4]
For $i = 55 To $iData Step 4
$bgra[0] += DllStructGetData($tData, 1, $i + 0)
$bgra[1] += DllStructGetData($tData, 1, $i + 1)
$bgra[2] += DllStructGetData($tData, 1, $i + 2)
$bgra[3] += DllStructGetData($tData, 1, $i + 3)
Next
For $i = 0 To 3 Step 1
$bgra[$i] /= ($iData - 54) / 4
Next
Return $bgra
EndFunc
; Kann man so machen. Für "große" Bilder ist das auch richtig so. Wir haben hier aber nur 1px breite Bilder. Man schießt nicht mit Kanonen auf Spatzen.
Func __FFMPEG_GetImage(ByRef $aBMP, $i)
Local $iData, $hData, $pData, $tData, $pStream, $hImage
$iData = BinaryLen($aBMP[$i][0])
$hData = _MemGlobalAlloc($iData, $GMEM_MOVEABLE)
$pData = _MemGlobalLock($hData)
$tData = DllStructCreate("byte[" & $iData & "]", $pData)
DllStructSetData($tData, 1, $aBMP[$i][0])
_MemGlobalUnlock($hData)
$pStream = _WinAPI_CreateStreamOnHGlobal($hData)
$hImage = _GDIPlus_ImageLoadFromStream($pStream)
_WinAPI_ReleaseStream($pStream)
Return $hImage
EndFunc
Alles anzeigen
Damit lässt sich z.B. sowas hier generieren:
(das fünfte element)
(watchmen)
Die Einstellungen fürs Oversampling sind mit Vorsicht zu genießen. Der Wert von 1.0 bedeutet: JEDER Frame wird decodiert und als eine 1px hohe Linie gespeichert bevor die Bitmap anschließend in Form gebracht wird. Bei einem Film mit 200.000 Frames wird das Probleme geben (daher habe ich als Default-Wert eine 0.1 eingetragen, dann wird jeder 10te Frame verwendet und mit 20.000 Frames geht noch alles gut).
(edit: Da ffmpeg die meiste Arbeit verrichtet werden sehr viele Videoformate unterstützt und die Geschwindigkeit ist auch ganz gut, da AutoIt nur das "Management" übernimmt und selbst gar nicht rechnen muss)
(edit2: Für sehr große Mengen an Frames ist AutoIt das Bottleneck, da im Code angenommen wird, dass die Zeit die AutoIt braucht um die dekodierten Frames abzuarbeiten vergleichbar ist mit der Zeit die zum dekodieren gebraucht wird. Geht das Dekodieren signifikant schneller als das Managen der Daten gibt es eine quadratische Laufzeit, da im inner Loop $bData = BinaryMid($bData, $tBMPHEADER.bfSize + 1, $iLen) verwendet wird, was extrem langsam ist, wenn bData z.B. 10.000 Frames enthält. Vielleicht überarbeite ich diese Methode nochmal für Lineare Laufzeit)
lg
M