把這個ps1 放在相同主檔名的 mp4 jpg旁邊
按右鍵 → 用PowerShell 執行
只有moov在檔案尾端的mp4,才會處理
只有寫入幾百KB的資料,很快
程式碼流程
1.理解MP4的box樹狀結構
請去 google mp4 結構
封面是放在 moov\udta\meta\ilst\covr 裡面
2.確認這個MP4是否正確
讀取MP4、尋找 ftyp moov mdat
3.試著去尋找 moov\udta\meta\ilst\covr
如果有哪一層box不存在,等一下會去建立它
根據尋找結果,得知「插入點」、「備份點」
通常,插入點在前、備份點在後
插入點:從這個位置開始,將會寫入「新建立的box」和「圖片」
備份點:從這個位置開始,將會把內容備份到buffer
因為,寫入「新建立的box」和「圖片」內容太多,可能會覆蓋到這裡的資料
4.把「新建立的box」和「圖片」,寫到buffer
把「備份點」之後的資料,寫到buffer
在「插入點」,把buffer裡的資料寫入MP4
5.如果「加入的圖片」比「原本的圖片」小
MP4的檔案大小,會比設定封面之前還要小
要用 $fs.SetLength 設定MP4正確的檔案大小
這樣可以:清除最後面垃圾資料、避免MP4在讀取時解讀失敗
6.修改「原本已存在的box」的box size
程式碼
function FindBox_1st($fs, $start, $end, $boxPath){
$buffer = $script:buffer
$fs.position = $start #從 $start 開始找
$arrBoxName = $boxPath -split '\\'
$index = 0
$targetBox = $arrBoxName[0]
$result = @()
while($fs.position -lt $end){
if ($fs.position + 8 -gt $end){throw 'no enough data'}
$boxBegin = $boxSizePos = $fs.position
# 讀取 8 byte (內含boxSize 4byte 、 boxName 4byte )
[void]$fs.read($buffer, 0, 8)
#MP4是以 big endian 存放 boxSize 資料
#然而,.net frameWork 在處理資料時,認為資料是以 little endian 存放
$boxSize = [System.BitConverter]::ToUInt32($buffer[3..0], 0)
$boxEnd = $boxBegin + $boxSize
$boxName = [char[]]$buffer[4..7] -join ''
#$boxSize 只有 4byte,可以表示的大小為 0 到 (4GB -1)
#如果 $boxSize 等於 1,表示接下來有 8 byte 存放真正的boxSize
#即 boxSize 4byte(等於 1)、boxName 4byte、boxSize 8byte
if ($boxSize -eq 1){#largeSize
if ($fs.position + 8 -gt $end){throw 'no enough data'}
$boxSizePos = $fs.position
[void]$fs.read($buffer, 0, 8)
$boxSize = [System.BitConverter]::ToUInt64($buffer[7..0], 0)
$boxEnd = $boxBegin + $boxSize
}elseif ($boxSize -lt 8 ){#boxSize等於 0 有別的意思,這裡沒有處理
throw 'mp4 error'
}#end of if
#===================================
if ($boxEnd -gt $end){throw "$boxName Box incomplete"}
if ($targetBox -eq '*'){#第一層全部列出
$h = @{boxName = $boxName; boxSizePos = $boxSizePos; boxSize = $boxSize; boxBegin = $boxBegin; boxEnd = $boxEnd}
$result += $h
$fs.position = $boxEnd
}elseif ($targetBox -eq $boxName){#找到了
$h = @{boxName = $boxName; boxSizePos = $boxSizePos; boxSize = $boxSize; boxBegin = $boxBegin; boxEnd = $boxEnd}
$result += $h
$index++
if ($index -lt $arrBoxName.length){#還有其他要找的
$targetBox = $arrBoxName[$index]
# meta box 是 fullBox,多了 4byte(flag version)
if ($boxName -eq 'meta') {$fs.position += 4}
}else{#找完了
return $result
}
}else{#沒找到
$fs.position = $boxEnd
}
}#end of while
return ,$result
}
function CheckMp4($fs){#確認這個MP4是否正確
#尋找MP4 第一層的所有box
$result = FindBox_1st $fs 0 $fs.length '*'
$boxBegin = @{} # 建立一個空的 hashtable 來存放資料
foreach ($h in $result){# $result裡面有許多 hashtable
$boxBegin[$h.boxName] = $h.boxBegin
}
#確認 ftyp moov mdat 這些box 是否存在
if ($boxBegin.ftyp -ne 0 -or $boxBegin.moov -eq $null -or $boxBegin.mdat -eq $null){
throw 'This is not a MP4 file'
}
#不處理「moov在檔案開始」的mp4
if ($boxBegin.moov -lt $boxBegin.mdat){
throw "can't process mp4 that moov in the beginning of file"
}
}
function MakeAddBox($fs, [string]$boxPath, $result, $srcFile){
$arrBoxName = $boxPath -split '\\'
#缺少多少box?(有多少需要被建立?)
$n = $arrBoxName.count - $result.count
if ($n -eq 0) {$n = 1} #沒有缺少box。但是,最後一個box總是需要被重新建立
#讀取圖片資料
[byte[]]$srcData = [IO.File]::ReadAllBytes($srcFile)
if (!$srcData.Count){return}
#=============================================
#covr box裡面,其實有一個 data box
$arrBoxName += 'data'
$n += 1
[byte[]]$newBox = @()
[UInt32]$TotalLength = $srcData.Length #初始值是圖片大小
#moov\udta\meta\ilst\covr\data
#如果有缺少box,從最後一個box開始建立
-1 .. -$n | %{
$boxName = $arrBoxName[$_]
#$after8 表示 (boxSize boxName)後面的資料
[byte[]]$after8 = @()
if ($boxName -eq 'data'){
$after8 = [byte[]](0,0,0,13,0,0,0,0)
}
if ($boxName -eq 'meta'){
$after8 = [byte[]](0,0,0,0) #fullBox(flag version)
# meta box 裡面有 hdlr box。這是hdlr box的完整內容
$hdlr = [byte[]](0,0,0,33 + 'hdlr'[0..3] + ,0*8 + 'mdirappl'[0..7] + ,0*9)
$after8 += $hdlr
}
$TotalLength += 8 + $after8.Length
$arrBytes = [System.BitConverter]::GetBytes($TotalLength)
#MP4是以big endian存放資料。而.net framework是以 little endian
$newBox = $arrBytes[3..0] + $boxName[0..3] + $after8 + $newBox
}#end of foreach
#傳回資料。不使用 return $newBox + $srcData的形式
$newBox
$srcData
}
function insertBox($fs, $BoxPath, $srcFile){
$buffer = $script:buffer
#試著去尋找 moov\udta\meta\ilst\covr
$result = FindBox_1st $fs 0 $fs.length $BoxPath
[byte[]]$AddBox = @()
$arrBoxName = $boxPath -split '\\'
if ($srcFile.length -gt 0){#make addbox
#根據 $result 建立「缺少的box」
#最後一個box covr 不管有沒有找到,都會建立
#AddBox 包含 「新建立的box」和「圖片」
$AddBox = MakeAddBox $fs $BoxPath $result $srcFile
}else{#clear box
#no need to clear box
#找到的box太少,不用去清除box
if ($result.Count -lt $arrBoxName.Count){return}
}
#============$insertPos $backupPos===========
if ($result.count -lt $arrBoxName.count) { # 有些box沒找到
$insertPos = $backupPos = $result[-1].boxEnd
}else{ # 所有box 都找到了。重建 or 清除 covr box
$insertPos = $result[-1].boxBegin
$backupPos = $result[-1].boxEnd #不備份 covr 的資料
}
#======= 把「新建立的box」和「圖片」,寫到buffer =========
$bufferPos = 0
if ($AddBox.count){#如果有東西要寫入
if ($AddBox.count -gt $buffer.length){
throw "bufferSize $($buffer.length/1MB)MB is too small"
}
[array]::copy($AddBox, 0, $buffer, $bufferPos, $AddBox.Count)
$bufferPos += $AddBox.Count
#下次要寫入buffer時,會從 $bufferPos開始
}
#=========== 把「備份點」之後的資料,寫到buffer ===============
$n = 0
#如果「加入的圖片」和「原本的圖片」檔案大小不一樣
#以致於 $insertPos + $AddBox.Count 不等於 $backupPos
if ($insertPos + $AddBox.Count -ne $backupPos){
$n = $fs.length - $backupPos
}
#如果「加入的圖片」和「原本的圖片」檔案大小是一樣的
#$n 會是 0,就不用把「備份點」之後的資料,寫到buffer
if ($n -gt 0){
if ($n -gt $buffer.length - $bufferPos){
throw "bufferSize $($buffer.length/1MB)MB is too small"
}
$fs.position = $backupPos
$bufferPos += $fs.read($buffer, $bufferPos, $n)
}
#======= 在「插入點」,把buffer裡的資料寫入MP4 ===========
$fs.position = $insertPos
$fs.write($buffer, 0, $bufferPos)
#============ 如果「加入的圖片」比「原本的圖片」小=============
$editSize = $AddBox.Count - ($backupPos - $insertPos)
if ($editSize -lt 0){# 設定MP4正確的檔案大小
$fs.SetLength($fs.length + $editSize)
}
#==========修改「原本已存在的box」的box size============
if ($result.count -eq $arrBoxName.count){# 所有box都有找到
#最後一個box不用修改。因為它總是被重新建立
$n = $result.count - 1
}else{ # 有些box沒找到
$n = $result.count
}
for ($i = 0 ; $i -lt $n ; $i++) {
if ($result[$i].boxSize -is 'UInt32'){
[UInt32]$boxSize = $result[$i].boxSize + $editSize
$arrBytes = [System.BitConverter]::GetBytes($boxSize)
$arrBytes = $arrBytes[3..0]
#MP4 是以 big endian 存放資料
#但是,.net framework是以 little endian 存放資料
}else{
[UInt64]$boxSize = $result[$i].boxSize + $editSize
$arrBytes = [System.BitConverter]::GetBytes($boxSize)
$arrBytes = $arrBytes[7..0]
}
$fs.position = $result[$i].boxSizePos
$fs.write($arrBytes, 0, $arrBytes.count)
}
}
function addMetaData($mp4, $srcFile){
trap{$_; $fs.close(); read-host; return}
$fs = $mp4.Open('open', 'readWrite', 'read')
#確認這個MP4是否正確
CheckMp4 $fs
if ($srcFile.length -gt 3MB){throw 'jpg size is too big'}
$boxPath = 'moov\udta\meta\ilst\covr'
insertBox $fs $boxPath $srcFile
$fs.close()
}
#=====================================================
$bufferSize = 20MB
$buffer = new-object byte[]($bufferSize)
#切換工作目錄到 ps1 所在地
#cd -literal $PSScriptRoot
cd -literal (Split-Path $MyInvocation.MyCommand.Path)
#列出 mp4 檔案
#dir *.mp4 -file |%{
dir *.mp4 |?{! $_.PSIsContainer}|%{
$mp4 = $_
$jpgName = $mp4.BaseName + '.jpg'
#有沒有相同主檔名的jpg
if (Test-Path -literal $jpgName -PathType Leaf){
$jpg = gi -literal $jpgName
addMetaData $mp4 $jpg
}
}#end of foreach
$buffer = $null
沒有留言:
張貼留言