In this post I read an Mpeg (.MP4) file and write the results to an Xml file using VBA (and a little help from Python).
So this is a rewrite, previously I wrote this program and used user defined types to record the state but I was unhappy with this because I would have needed to write a load of gui code. In this rewrite I record the results to an Xml document and then let some xml pretty printing code place each element of a separate line nicely tabbed, I delivered this code in the previous blog post.
I don't have much to add, either you have a requirement to inspect mp4 files or you don't. There is little point me explaining mp4 files to those uninterested and uninitiated.
Please don't take this code and place in github and has happened with previous code. If in github then I don't get any traffic; if github changes policy on this then I will then use it. I also intend to use Github for upcoming work where I deliberately want to collaborate and enlist the help of others to achieve a task I have in mind. Please respect the intellectual property rights of this blog, thanks.
Source Modules
Firstly, the Python module which I needed for long numbers, this is to be called PythonBigNumbersComServer.py and needs to be run from the command line so that the COM class is registered (this needs administrator rights, so expect an elevation request if not running as adminstrator).
PythonBigNumbersComServer.py
import pythoncom
class PythonBigNumbersComServer:
_reg_progid_ = "PythonInVBA.PythonBigNumbersComServer"
_reg_clsid_ = "{5AD66FCC-EFFC-4A78-B5B6-9A42C523272E}"
_public_methods_ = ['LongByteArrayToDecimalString']
def LongByteArrayToDecimalString(self, bytes):
ret = 0
for byte in bytes:
print(byte)
ret = ret * 256 + byte
return str(ret)
if __name__ == '__main__':
import win32com.server.register
win32com.server.register.UseCommandLine(PythonBigNumbersComServer)
try:
import win32com.client
bigNumbers = win32com.client.Dispatch("PythonInVBA.PythonBigNumbersComServer")
#bigNumbers = PythonBigNumbersComServer()
x = bytes('01', 'utf8')
print(x)
asDecimal = bigNumbers.LongByteArrayToDecimalString(x)
print('asDecimal:' + asDecimal)
except Exception as e:
print("Error : " + str(e) + "\n")
Next is the modXmlReports module given in the previous blog post.
Okay from here on all the code is VBA. So next is the DefinedSymbols class
DefinedSymbols Class
Option Explicit
'**********************************************************************************************************
'* Class Name: DefinedSymbols
'* Copyright exceldevelopmentplatform.blogspot.com 2nd November 2019
'*
'* Requires Tools Reference
'* Microsoft Scripting Runtime
'*
'* Description:
'* This class collaborates with modVLCSourceProcessor to read VLC Media Player C++ source file on github and
'* scrapes four character codes identifiers found in mp4 files, such as moov, trak, trhd etc.
'* The class then stores the results and services requests for atom identification whilst parsing an mp4 file
'*
'* Notes:
'* we have to categorise the identifiers into Brand, Atom, Handler and SampleGroup because otherwise we will
'* get duplicates, this is why we have a nested dictionary to store the data. The main code is only interested
'* in atoms, however.
'*
'* Also, for each category, we maintain a map each way, i.e. two maps, because the four character codes can
'* contain special characters which are not valid in C++ source code identifiers.
'* This is a pain, but it is handled.
'*
'* To hide this complex state housing I invented this class
'*
'**********************************************************************************************************
Private mdicMaps As New Scripting.Dictionary
'*********************************************************************************************************
'* Name: AddMapEntry
'* Description: Abstracts the storage of categorise defined symbols maps. This for loading
'* the store of information.
'*********************************************************************************************************
Public Function AddMapEntry(ByVal sMap As String, ByVal sKey As String, ByVal sValue As String, ByVal sSourceLine As String) As Variant
Dim vMaps As Variant
vMaps = GetCategoryMap(sMap)
Debug.Assert IsArray(vMaps)
Dim dicMap As Scripting.Dictionary
Set dicMap = vMaps(0)
Dim dicMapReverse As Scripting.Dictionary
Set dicMapReverse = vMaps(1)
If Not dicMap.Exists(sKey) Then
dicMap.Add sKey, sValue
If Not dicMapReverse.Exists(sValue) Then
dicMapReverse.Add sValue, sKey
Else
Debug.Print "duplicate item in line:" & sSourceLine
End If
Else
If dicMap.Item(sKey) <> sValue Then
Debug.Print "Symbol " & sKey & " defined twice to two different values (" & dicMap.Item(sKey) & "," & sValue & "):" & sSourceLine
End If
End If
End Function
'*********************************************************************************************************
'* Name: LookupMapEntry
'* Description: Abstracts the storage of parent-child relationships in nested dictionaries. This for loading
'* the store of information.
'*********************************************************************************************************
Public Function LookupMapEntry(ByVal sMap As String, ByVal sKey As String, ByVal bReverse As Boolean) As Variant
Dim vMaps As Variant
vMaps = GetCategoryMap(sMap)
Debug.Assert IsArray(vMaps)
If bReverse Then
Dim dicMap As Scripting.Dictionary
Set dicMap = vMaps(1)
Else
Set dicMap = vMaps(0)
End If
If dicMap.Exists(sKey) Then
LookupMapEntry = dicMap.Item(sKey)
End If
End Function
Private Function GetCategoryMap(ByVal sMap As String)
Dim dicMap As Scripting.Dictionary
Dim dicMapReverse As Scripting.Dictionary
Dim vMaps As Variant
If Not mdicMaps.Exists(sMap) Then
Set dicMap = New Scripting.Dictionary
dicMap.CompareMode = BinaryCompare
Set dicMapReverse = New Scripting.Dictionary
dicMapReverse.CompareMode = BinaryCompare
vMaps = Array(dicMap, dicMapReverse)
mdicMaps.Add sMap, vMaps
Else
vMaps = mdicMaps.Item(sMap)
End If
GetCategoryMap = vMaps
End Function
Private Sub Class_Initialize()
mdicMaps.CompareMode = BinaryCompare
End Sub
Next is the BoxFunctionTable class
BoxFunctionTable class
Option Explicit
'**********************************************************************************************************
'* Class Name: BoxFunctionTable
'* Copyright exceldevelopmentplatform.blogspot.com 2nd November 2019
'*
'* Requires Tools Reference
'* Microsoft Scripting Runtime
'*
'* Description:
'* This class collaborates with modVLCSourceProcessor to read VLC Media Player C++ source file on github and
'* scrapes parent-child relationships between atoms defined in the box function block of code
'* The class then stores the results and services requests for possible children atoms for a given parent
'*
'* To hide this complex state housing I invented this class
'*
'**********************************************************************************************************
Private mdicChildrenOfParents As New Scripting.Dictionary
'*********************************************************************************************************
'* Name: RecordAParentsPotentialChild
'* Description: Abstracts the storage of parent-child relationships in nested dictionaries. This for loading
'* the store of information.
'*********************************************************************************************************
Friend Function RecordAParentsPotentialChild(ByVal sParentAtom As String, ByVal sChildAtom As String)
Dim dicChildrenOfAParent As Scripting.Dictionary
If Not mdicChildrenOfParents.Exists(sParentAtom) Then
Set dicChildrenOfAParent = New Scripting.Dictionary
mdicChildrenOfParents.Add sParentAtom, dicChildrenOfAParent
Else
Set dicChildrenOfAParent = mdicChildrenOfParents.Item(sParentAtom)
End If
Debug.Assert Not dicChildrenOfAParent.Exists(sChildAtom)
dicChildrenOfAParent.Add sChildAtom, Empty
End Function
'*********************************************************************************************************
'* Name: RecordAParentsPotentialChild
'* Description: For a given parent atom will return a list (ass Dictionary) of potential child atoms.
'* This for reading the store of information.
'*********************************************************************************************************
Public Function ChildrenOfAParentAsADictionary(ByVal sParentAtom As String) As Scripting.Dictionary
'Debug.Assert mdicChildrenOfParents.Exists(sParentAtom)
If mdicChildrenOfParents.Exists(sParentAtom) Then
Set ChildrenOfAParentAsADictionary = mdicChildrenOfParents.Item(sParentAtom)
Else
Set ChildrenOfAParentAsADictionary = New Scripting.Dictionary
End If
End Function
'*********************************************************************************************************
'* Name: AddBoxFunction
'* Description: A wrapper to RecordAParentsPotentialChild; this is called whilst reading
'* the VLC C++ source code
'*********************************************************************************************************
Public Function AddBoxFunction(ByVal sAtom As String, ByVal sFunctionName As String, _
ByVal sParentAtom As String, ByVal sSrcLine As String) As Variant
Dim sAtom2 As String, sParentAtom2 As String
sAtom2 = AtomFourCC(sAtom)
sParentAtom2 = AtomFourCC(sParentAtom)
Call RecordAParentsPotentialChild(sParentAtom2, sAtom2)
End Function
Private Function AtomFourCC(ByVal sAtom As String) As String
'* this strips the identifier of leading prefixes to give
'* the core four character identifier
If sAtom = "0" Then
AtomFourCC = ""
Else
Debug.Assert Left$(sAtom, 5) = "ATOM_"
AtomFourCC = Mid$(sAtom, 6)
End If
End Function
That is the classes done. So now three more modules. Next is the modVLCSourceProcessor module
modVLCSourceProcessor module
Option Explicit
Option Private Module
'***********************************************************************************
'* Module Name: modVLCSourceProcessor
'* Copyright exceldevelopmentplatform.blogspot.com 2nd November 2019
'*
'* Other class and module dependencies:
'* BoxFunctionTable, DefinedSymbols
'*
'* Description:
'* This modules reads the VLC Media Player C++ sources file on github and scrapes
'* (1) four character codes identifiers found in mp4 files, such as moov, trak, trhd etc.
'* (2) parent-child relationships between atoms defined in the box function block of code
'*
'* Other comments:
'* Ordinarily requires internet access to call out to github, to get the files
'* (a) modules\demux\libmp4.h and
'* (b) modules\demux\libmp4.c
'* however it is possible to work with a local copies
'* copy in libmp4.h and libmp4.c into a module in this workbook called srcLibMp_h and srcLibMp_c
'* please comment it out the code (it is C++ and so will not compile obvs)
'* then add a conditional compilation constant VLCLOCAL=1
'***********************************************************************************
'*********************************************************************************************************
'* Name: ReadVLCSource
'* Description: This module's sole entry point. Call here to read the VLC media player source files from github.
'* Plus we added some extra parent-child realtionships not found in VLC media player source.
'*
'* Returns : an instance of BoxFunctionTable class which can service requests regarding parent-child
'* relationships and an instance of DefinedSymbols class which can service requests regarding symbols
'*********************************************************************************************************
Public Function ReadVLCSource(ByRef poDefinedSymbols As DefinedSymbols, _
ByRef poBoxFunctionTable As BoxFunctionTable)
Dim vaLibMpSrcs
vaLibMpSrcs = GetVLCSourceCode()
If poDefinedSymbols Is Nothing Then
Set poDefinedSymbols = ReadDefinedSymbols(vaLibMpSrcs(0))
End If
If poBoxFunctionTable Is Nothing Then
Set poBoxFunctionTable = ReadBoxFunctionTable(vaLibMpSrcs(1))
'* manually add some not found in vlc source
Call poBoxFunctionTable.RecordAParentsPotentialChild("dinf", "dref")
Call poBoxFunctionTable.RecordAParentsPotentialChild("dinf", "urn ")
Call poBoxFunctionTable.RecordAParentsPotentialChild("dinf", "url ")
'Stop
End If
End Function
'************************************************************
'* test routine
'************************************************************
Private Sub TestReadBoxFunctionTable()
Dim vaLibMpSrcs
vaLibMpSrcs = GetVLCSourceCode()
Dim oBoxFunctionTable As BoxFunctionTable
Set oBoxFunctionTable = ReadBoxFunctionTable(vaLibMpSrcs(1))
Debug.Print "Potential children of moov are:" & Join(oBoxFunctionTable.ChildrenOfAParentAsADictionary("moov").Keys)
Debug.Print "Potential children of trak are:" & Join(oBoxFunctionTable.ChildrenOfAParentAsADictionary("trak").Keys)
Debug.Print "Potential children of mdia are:" & Join(oBoxFunctionTable.ChildrenOfAParentAsADictionary("mdia").Keys)
Debug.Print "Potential children of minf are:" & Join(oBoxFunctionTable.ChildrenOfAParentAsADictionary("minf").Keys)
Debug.Print "Potential children of stbl are:" & Join(oBoxFunctionTable.ChildrenOfAParentAsADictionary("stbl").Keys)
Stop
End Sub
'*********************************************************************************************************
'* Name: ReadBoxFunctionTable
'* Description: Given the source code lines from lipmp4.c this code will look for lines of code such as
'*
'* (a) { ATOM_minf, MP4_ReadBoxContainer, ATOM_mdia },
'* (b) { ATOM_stbl, MP4_ReadBoxContainer, ATOM_minf },
'*
'* where it will parse out (a) 'minf' and 'mdia' and (b) 'stbl' and 'minf'
'* the atom on the left is a potential child of the atom on the right
'*
'* Returns : an instance of BoxFunctionTable class which can service requests regarding parent-child
'* relationships
'*********************************************************************************************************
Private Function ReadBoxFunctionTable(vCFileLines As Variant) As BoxFunctionTable
Debug.Assert IsArray(vCFileLines)
Dim oBoxFunctionTable As BoxFunctionTable
Set oBoxFunctionTable = New BoxFunctionTable
Dim bInBoxFunctionTable As Boolean: bInBoxFunctionTable = False
'\{ (ATOM_\w*), *(MP4_ReadBox\w*), * (0|ATOM_\w*) *\}, * *.*
Dim regexBoxFunction As Object 'VBScript_RegExp_55.RegExp
Set regexBoxFunction = VBA.CreateObject("VBScript.RegExp")
regexBoxFunction.IgnoreCase = True
regexBoxFunction.Global = True
regexBoxFunction.Pattern = "\{ (0|ATOM_\w*) *, *(MP4_ReadBox\w*), * (0|ATOM_\w*) *\}, * *.*"
Dim lLineLoop As Long
For lLineLoop = LBound(vCFileLines) To UBound(vCFileLines)
Dim vLineLoop As Variant
vLineLoop = vCFileLines(lLineLoop)
If Trim(vCFileLines(lLineLoop)) = "/* Containers */" Then
If Trim(vCFileLines(lLineLoop - 1)) = "{" _
And Trim(vCFileLines(lLineLoop - 2)) = "} MP4_Box_Function [] =" Then bInBoxFunctionTable = True
End If
If InStr(1, vLineLoop, "{ 0, MP4_ReadBox_default, 0 }", vbBinaryCompare) > 0 Then bInBoxFunctionTable = False
If bInBoxFunctionTable Then
Dim sTrimmed As String
sTrimmed = Trim(vLineLoop)
If Not (Left$(sTrimmed, 2) = "/*" And Right$(sTrimmed, 2) = "*/") And Len(sTrimmed) > 0 And sTrimmed <> "{" And sTrimmed <> "/*" Then
Debug.Assert InStr(1, sTrimmed, "MP4_ReadBox", vbTextCompare) > 0
If regexBoxFunction.Test(sTrimmed) Then
Dim matchCol As Object 'VBScript_RegExp_55.MatchCollection
Set matchCol = regexBoxFunction.Execute(sTrimmed)
Dim match As Object 'VBScript_RegExp_55.match
Set match = matchCol.Item(0)
Debug.Assert match.SubMatches.count = 3
oBoxFunctionTable.AddBoxFunction match.SubMatches.Item(0), match.SubMatches.Item(1), _
match.SubMatches.Item(2), sTrimmed
Else
Debug.Print "failed to parse:" & sTrimmed
End If
End If
End If
Next
Set ReadBoxFunctionTable = oBoxFunctionTable
End Function
'************************************************************
'* test routine
'************************************************************
Private Sub TestReadDefinedSymbols()
Dim vaLibMpSrcs
vaLibMpSrcs = GetVLCSourceCode()
Dim oDefinedSymbols As DefinedSymbols
Set oDefinedSymbols = ReadDefinedSymbols(vaLibMpSrcs(0))
Debug.Assert oDefinedSymbols.LookupMapEntry("BRAND", "smoo", False) = "smoo"
Debug.Assert oDefinedSymbols.LookupMapEntry("BRAND", "smoo", True) = "smoo"
Debug.Assert oDefinedSymbols.LookupMapEntry("ATOM", "url", False) = "url "
Debug.Assert oDefinedSymbols.LookupMapEntry("ATOM", "url ", True) = "url"
Debug.Assert oDefinedSymbols.LookupMapEntry("ATOM", "ms55", False) = "ms" & Chr$(0) & "U"
Debug.Assert oDefinedSymbols.LookupMapEntry("ATOM", "ms" & Chr$(0) & "U", True) = "ms55"
Stop
End Sub
'*********************************************************************************************************
'* Name: ReadDefinedSymbols
'* Description: Given the source code lines from lipmp4.h this code will look for lines of code such as
'*
'* (a) #define ATOM_ipma VLC_FOURCC('i','p','m','a')
'* (b) #define ATOM_0x40PRM VLC_FOURCC( '@', 'P', 'R', 'M' )
'*
'* where it will parse out (a) 'ipma' and 'ipma' and (b) '0x40PRM' and '@PRM'
'* most of the time but not always the source identifier is identical to the four character code
'*
'* Returns : an instance of DefinedSymbols class which can service requests regarding symbols
'*********************************************************************************************************
Private Function ReadDefinedSymbols(vHeaderLines As Variant) As DefinedSymbols
Debug.Assert IsArray(vHeaderLines)
Dim oDefinedSymbols As DefinedSymbols
Set oDefinedSymbols = New DefinedSymbols
Dim regexFourCC As Object 'VBScript_RegExp_55.RegExp
Set regexFourCC = VBA.CreateObject("VBScript.RegExp")
regexFourCC.IgnoreCase = True
regexFourCC.Global = True
regexFourCC.Pattern = "#define *(.*)_(.*) *VLC_FOURCC\( *('.'|0x\w{1,2}) *, *('.'|0x\w{1,2}) *, *('.'|0x\w{1,2}) *, *('.'|0x\w{1,2}) *\) *.*"
'Stop
Dim vLineLoop As Variant
For Each vLineLoop In vHeaderLines
If InStr(1, vLineLoop, "VLC_FOURCC", vbBinaryCompare) > 0 Then
If regexFourCC.Test(vLineLoop) Then
Dim matchCol As Object 'VBScript_RegExp_55.MatchCollection
Set matchCol = regexFourCC.Execute(vLineLoop)
Dim match As Object 'VBScript_RegExp_55.match
Set match = matchCol.Item(0)
Debug.Assert match.SubMatches.count = 6
Dim sSymbolCat As String
sSymbolCat = Trim(match.SubMatches.Item(0))
Dim sSymbolKey As String
sSymbolKey = Trim(match.SubMatches.Item(1))
Dim sSymbolValue As String: sSymbolValue = ""
Dim lCharLoop As Long
For lCharLoop = 2 To 5
Dim sSubMatch As String
sSubMatch = Trim(match.SubMatches.Item(lCharLoop))
Dim sChar As String
If InStr(1, sSubMatch, "'", vbBinaryCompare) > 0 Then
sChar = VBA.Replace(sSubMatch, "'", "")
Else
sSubMatch = "&h" & Mid$(sSubMatch, 3)
Dim lChar As Long
lChar = Val(sSubMatch)
sChar = Chr$(lChar)
End If
sSymbolValue = sSymbolValue & sChar
Next
Else
Debug.Print "Regular expression failed to parse line:" & vLineLoop
End If
oDefinedSymbols.AddMapEntry sSymbolCat, sSymbolKey, sSymbolValue, vLineLoop
End If
Next
Set ReadDefinedSymbols = oDefinedSymbols
End Function
'************************************************************
'* test routine
'************************************************************
Private Sub TestGetVLCSourceCode()
Dim vaLibMpSrcs
vaLibMpSrcs = GetVLCSourceCode()
Stop
End Sub
'*********************************************************************************************************
'* Name: GetVLCSourceCode
'* Description: Will acquire code from either GitHub or a local copy in a module in this workbook
'* Returns : A variant array of strings, each element is a line
'*********************************************************************************************************
Private Function GetVLCSourceCode()
#If VLCLOCAL = 1 Then
GetVLCSourceCode = GetVLCSourceCodeInner(True)
#Else
GetVLCSourceCode = GetVLCSourceCodeInner(False)
#End If
End Function
'*********************************************************************************************************
'* Name: GetVLCSourceCodeInner
'* Description: Will acquire code from either GitHub or a local copy in a module in this workbook
'* Returns : A variant array of strings, each element is a line
'*********************************************************************************************************
Private Function GetVLCSourceCodeInner(ByVal bLocalCopy As Boolean)
Dim vSources(0 To 1) As Variant
Dim vSrcSuffixes As Variant
vSrcSuffixes = Array("h", "c")
Dim lFileLoop As Long
If bLocalCopy Then
'* this code is switched on with a conditional constant
'* I have copied into my workbook a copy of the source files
For lFileLoop = 0 To 1
Dim objLibMpSrcVBComp As Object
Set objLibMpSrcVBComp = GetVBComponentOERN("srcLibMp_" & vSrcSuffixes(lFileLoop))
Dim dicSrc As Object
Set dicSrc = VBA.CreateObject("Scripting.Dictionary")
Dim lLineLoop As Long
For lLineLoop = 1 To objLibMpSrcVBComp.CountOfLines
Dim sLine As String
sLine = objLibMpSrcVBComp.Lines(lLineLoop, 1)
dicSrc.Add dicSrc.count, Mid(sLine, 2)
Next lLineLoop
vSources(lFileLoop) = dicSrc.Items
Next lFileLoop
Else
Dim oXHR As MSXML2.XMLHTTP60
For lFileLoop = 0 To 1
Set oXHR = New MSXML2.XMLHTTP60
oXHR.Open "GET", "https://raw.githubusercontent.com/videolan/vlc/master/modules/demux/mp4/libmp4." & vSrcSuffixes(lFileLoop), False
oXHR.send
vSources(lFileLoop) = VBA.Split(oXHR.responseText, Chr$(10))
Next lFileLoop
End If
GetVLCSourceCodeInner = vSources
End Function
Private Function GetVBComponentOERN(ByVal sName As String) As Object
'* kills errors
On Error Resume Next
Set GetVBComponentOERN = ThisWorkbook.VBProject.VBComponents.Item(sName).CodeModule
End Function
So now all the helper classes and modules are in place. Next the main module, the modMpegToXml module.
modMpegToXml module
Option Explicit
'**********************************************************************************************************
'* Module Name: modMpegToXml
'* Copyright exceldevelopmentplatform.blogspot.com 2nd November 2019
'*
'* Requires Tools Reference
'* Microsoft Scripting Runtime
'* Microsoft XML, v6.0
'*
'* Other class and module dependencies:
'* modMyFiles, modLVCSourceProcessor, modXmlReports, BoxFunctionTable, DefinedSymbols
'*
'* Python dependency:
'* sadly VBA cannot handle numbers bigger than 32-bit arithmetically so I had to enlist a Python COM class
'* to help convert large numbers to a string, that Python code is in file PythonBigNumbersComServer.py
'* and will need running from command line to register the COM class
'*
'* Description:
'* This is main module that orchestrates logic from the other classes and modules. It reads an mp4 file
'* recursively parsing the atom structure replicating the nested structure in a more readable xml file.
'*
'* I wrote this because I was unhappy with existing mp4 file examiners which did not handle
'* fragmented mp4 files or dash files. But I did not want to write a GUI so I wrote to an xml file instead.
'*
'**********************************************************************************************************
Private fso As New Scripting.FileSystemObject
Private mbytes() As Byte
Private mlByteCount As Long
Private moDefinedSymbols As DefinedSymbols
Private moBoxFunctionTable As BoxFunctionTable
Private mxmlMpeg As MSXML2.DOMDocument60
Private mxmlCurrentNode As MSXML2.IXMLDOMElement
Private mlAtomCount As Long
Private mlOmissionThreshold As Long '* 0 means show none, positive integers n means show the first n
Private Enum MP4_TRUN_FLAGS
MP4_TRUN_DATA_OFFSET = 1 '(1<<0)
MP4_TRUN_FIRST_FLAGS = 4 '(1<<2)
MP4_TRUN_SAMPLE_DURATION = 256 '(1<<8)
MP4_TRUN_SAMPLE_SIZE = 512 '(1<<9)
MP4_TRUN_SAMPLE_FLAGS = 1024 '(1<<10)
MP4_TRUN_SAMPLE_TIME_OFFSET = 2048 '(1<<11)
End Enum
'*********************************************************************************************************
'* Name: BatchXmlReportOnMpegFile
'* Description: Top level entry point which allows batch of mp4 files (mpeg file) and reports on each.
'* Pass in a dictionary of files. One can also specify a working folder (it will use temp if blank)
'*********************************************************************************************************
Public Sub BatchXmlReportOnMpegFile(ByVal dicMepgFiles As Scripting.Dictionary, ByVal bCancelColoring As Boolean, ByVal sWorkFolder As String)
RemoveReports
Dim sMepgFile
For Each sMepgFile In dicMepgFiles.Keys
DoEvents
XmlReportOnMpegFile sMepgFile, bCancelColoring, sWorkFolder
Next
End Sub
'*********************************************************************************************************
'* test routine
'*********************************************************************************************************
Private Sub TestBatchXmlReportOnMpegFile()
Dim dicMepgFiles As Scripting.Dictionary
Set dicMepgFiles = modMyFiles.MyMepgFiles() '* I have defined mine in a separate module, modMyFiles
BatchXmlReportOnMpegFile dicMepgFiles, False, ""
'End
End Sub
'*********************************************************************************************************
'* Name: XmlReportOnMpegFile
'* Description: Top level entry point which examines an mp4 file (mpeg file) and reports the nested atom
'* structure into a nested xml report. Then it writes the xml report to a worksheet by calling
'* modXmlReports.PrettyReportXml.
'* One can also specify a working folder (it will use temp if blank)
'*********************************************************************************************************
Public Sub XmlReportOnMpegFile(ByVal sMepgFile As String, ByVal bCancelColoring As Boolean, ByVal sWorkFolder As String)
Application.StatusBar = False
mlOmissionThreshold = 5
'* need symbols and parent-child atom relationships information
Call modVLCSourceProcessor.ReadVLCSource(moDefinedSymbols, moBoxFunctionTable)
Debug.Assert fso.FileExists(sMepgFile)
Set mxmlMpeg = New MSXML2.DOMDocument60
Debug.Assert mxmlMpeg.LoadXML(Chr$(60) & "mpeg/>")
Set mxmlCurrentNode = mxmlMpeg.DocumentElement
mlAtomCount = 0
mbytes() = ReadByteFile(sMepgFile)
mlByteCount = UBound(mbytes) - LBound(mbytes) + 1
mxmlCurrentNode.setAttribute "size", mlByteCount
ReadAtom "", 0, mlByteCount - 1, 0
Dim sXmlReportFile As String
sXmlReportFile = XmlReportFileName(sMepgFile)
mxmlMpeg.Save sXmlReportFile
modXmlReports.PrettyReportXml ThisWorkbook, sXmlReportFile, bCancelColoring, sWorkFolder
End Sub
'*********************************************************************************************************
'* Name: XmlReportFileName
'* Description: returns name of a wotrking file to which will write the xml
'*********************************************************************************************************
Private Function XmlReportFileName(ByVal sMepgFile As String) As String
'* we want to work with our file which we will place in same directory as ThisWorkbook
Dim filMpeg As Scripting.File
Set filMpeg = fso.GetFile(sMepgFile)
XmlReportFileName = fso.BuildPath(TempFolder, filMpeg.name & ".xml")
End Function
'*********************************************************************************************************
'* Name: TempFolder
'* Description: create and locate a folder within the system's temporary folder
'*********************************************************************************************************
Private Function TempFolder() As String
Dim fso As Scripting.FileSystemObject
Set fso = New Scripting.FileSystemObject
Const TemporaryFolder As Long = 2
TempFolder = fso.GetSpecialFolder(TemporaryFolder)
If Not fso.FolderExists(fso.BuildPath(TempFolder, "modMpegToXml")) Then
Dim fldTemp As Scripting.Folder
Set fldTemp = fso.GetFolder(TempFolder)
fldTemp.SubFolders.Add "modMpegToXml"
Debug.Assert fso.FolderExists(fso.BuildPath(TempFolder, "modMpegToXml"))
End If
TempFolder = fso.BuildPath(TempFolder, "modMpegToXml")
Set fso = Nothing
End Function
'Private Function ThisWorkbookHomeFolder() As String
' 'Debug.Assert ThisWorkbook.Saved = True
' ThisWorkbookHomeFolder = fso.GetFile(ThisWorkbook.FullName).ParentFolder.ShortPath
'
' '* alternative
' 'Dim vSplit
' 'vSplit = VBA.Split(ThisWorkbook.FullName, "\")
' 'ReDim Preserve vSplit(0 To UBound(vSplit) - 1)
' 'ThisWorkbookHomeFolder = VBA.Join(vSplit, "\")
'End Function
'*********************************************************************************************************
'* Name: ReadAtom
'* Description: Recursive routine at the heart of the parsing logic.
'*
'* (1) Establishes list of potential child atoms for a given parent atom.
'* (2) Calls FindAtomAtPosition2() to find a child atom
'* (3) For each atom it finds it will (a) create an Xml element in the report, (b) establish its size,
'* (c) for full atoms read version and flags (d) adorn with any atom specific attributes ,
'* (e) adorn description and (f) if it can have children then recursively call into ReadAtom()
'* to look for those child atoms
'* (4) Then look for another atom at this level, i.e. a sibling atom
'* (5) if no more atoms found then quit routine return to caller
'*
'*
'*********************************************************************************************************
Private Sub ReadAtom(ByVal sParent As String, ByVal lPosition As Long, ByVal lEndOfParent As Long, ByVal lRecursionLevel As Long)
Dim dicChildrenOfAParent As Scripting.Dictionary
Set dicChildrenOfAParent = moBoxFunctionTable.ChildrenOfAParentAsADictionary(sParent)
If dicChildrenOfAParent.count = 0 Then Exit Sub
Dim lSize As Long
Do
DoEvents
Dim sAtom As String: sAtom = ""
Dim lAtomFoundAt As Long
lAtomFoundAt = FindAtomAtPosition2(lPosition, lEndOfParent, dicChildrenOfAParent, sAtom)
If lAtomFoundAt < 0 Then
'* something went wrong, we have to abandon this branch
GoTo SingleExit
End If
lSize = FourBytesToLong(lAtomFoundAt - 4)
If lSize = 0 Then
'* something went wrong, we have to abandon this branch
GoTo SingleExit
End If
Dim lNextAtomAt As Long
lNextAtomAt = lAtomFoundAt - 4 + lSize
Debug.Print "Found atom '" & sAtom & "' at position " & lAtomFoundAt - 4 & " of size " & lSize & " which thus spans to " & lPosition + lSize & " (&h" & VBA.Hex$(lPosition + lSize) & ")"
Dim xmlAtom As MSXML2.IXMLDOMElement
Set xmlAtom = mxmlMpeg.createElement(sAtom)
xmlAtom.setAttribute "size", lSize
Dim lVersion As Long: lVersion = 0
Dim lFlags As Long: lFlags = 0
If FullBoxAtoms.Exists(sAtom) Then
Dim idx As Long
idx = lPosition + 8
lVersion = BytesToLong(mbytes, 1, idx)
lFlags = BytesToLong(mbytes, 3, idx)
Call xmlAtom.setAttribute("version", lVersion)
Call xmlAtom.setAttribute("flags", FlagsToString(sAtom, lFlags))
End If
AtomAttributes sAtom, xmlAtom, lPosition, lSize, lVersion, lFlags
If AtomDescriptions().Exists(sAtom) Then xmlAtom.setAttribute "desc", AtomDescriptions().Item(sAtom)
mxmlCurrentNode.appendChild xmlAtom
mlAtomCount = mlAtomCount + 1
If moBoxFunctionTable.ChildrenOfAParentAsADictionary(sAtom).count > 0 Then
Set mxmlCurrentNode = xmlAtom
ReadAtom sAtom, lAtomFoundAt + 4, lNextAtomAt - 1, lRecursionLevel + 1
End If
lPosition = lNextAtomAt
Loop Until lPosition >= lEndOfParent
SingleExit:
If mxmlCurrentNode.nodeName <> "mpeg" Then
Set mxmlCurrentNode = mxmlCurrentNode.ParentNode
End If
End Sub
'*********************************************************************************************************
'* Name: AtomDescriptions
'* Description: associates four letter character codes with descriptions
'*********************************************************************************************************
Private Function AtomDescriptions() As Scripting.Dictionary
Static dic As Scripting.Dictionary
Static dicAmbiguous As Scripting.Dictionary
If dicAmbiguous Is Nothing Then
Set dicAmbiguous = New Scripting.Dictionary
dicAmbiguous.Add "hnti", "trackhintinformation"
'dicAmbiguous.Add "hnti", "moviehintinformation"
End If
If dic Is Nothing Then
Set dic = New Scripting.Dictionary
dic.Add "ftyp", "FileTypeBox"
dic.Add "moov", "MovieBox"
dic.Add "mdat", "MediaDataBox"
dic.Add "mvhd", "MovieHeaderBox"
dic.Add "trak", "TrackBox"
dic.Add "tkhd", "TrackHeaderBox"
dic.Add "tref", "TrackReferenceBox"
dic.Add "mdia", "MediaBox"
dic.Add "mdhd", "MediaHeaderBox"
dic.Add "hdlr", "HandlerBox"
dic.Add "minf", "MediaInformationBox"
dic.Add "vmhd", "VideoMediaHeaderBox"
dic.Add "smhd", "SoundMediaHeaderBox"
dic.Add "hmhd", "HintMediaHeaderBox"
dic.Add "nmhd", "NullMediaHeaderBox"
dic.Add "dinf", "DataInformationBox"
dic.Add "url ", "DataEntryUrlBox"
dic.Add "urn ", "DataEntryUrnBox"
dic.Add "dref", "DataReferenceBox"
dic.Add "stbl", "SampleTableBox"
dic.Add "stts", "TimeToSampleBox"
dic.Add "ctts", "CompositionOffsetBox"
dic.Add "stsd", "SampleDescriptionBox"
dic.Add "stsz", "SampleSizeBox"
dic.Add "stz2", "CompactSampleSizeBox"
dic.Add "stsc", "SampleToChunkBox"
dic.Add "stco", "ChunkOffsetBox"
dic.Add "co64", "ChunkLargeOffsetBox"
dic.Add "stss", "SyncSampleBox"
dic.Add "stsh", "ShadowSyncSampleBox"
dic.Add "stdp", "DegradationPriorityBox"
dic.Add "padb", "PaddingBitsBox"
dic.Add "free", "FreeSpaceBox"
dic.Add "skip", "FreeSpaceBox"
dic.Add "edts", "EditBox"
dic.Add "elst", "EditListBox"
dic.Add "udta", "UserDataBox"
dic.Add "cprt", "CopyrightBox"
dic.Add "mvex", "MovieExtendsBox"
dic.Add "mehd", "MovieExtendsHeaderBox"
dic.Add "trex", "TrackExtendsBox"
dic.Add "moof", "MovieFragmentBox"
dic.Add "mfhd", "MovieFragmentHeaderBox"
dic.Add "traf", "TrackFragmentBox"
dic.Add "tfhd", "TrackFragmentHeaderBox"
dic.Add "trun", "TrackRunBox"
dic.Add "mfra", "MovieFragmentRandomAccessBox"
dic.Add "tfra", "TrackFragmentRandomAccessBox"
dic.Add "mfro", "MovieFragmentRandomAccessOffsetBox"
dic.Add "sdtp", "SampleDependencyTypeBox"
dic.Add "sbgp", "SampleToGroupBox"
dic.Add "sgpd", "SampleGroupDescriptionBox"
dic.Add "stsl", "SampleScaleBox"
dic.Add "subs", "SubSampleInformationBox"
dic.Add "pdin", "ProgressiveDownloadInfoBox"
dic.Add "meta", "MetaBox"
dic.Add "xml ", "XMLBox"
dic.Add "bxml", "BinaryXMLBox"
dic.Add "iloc", "ItemLocationBox"
dic.Add "pitm", "PrimaryItemBox"
dic.Add "ipro", "ItemProtectionBox"
dic.Add "infe", "ItemInfoEntry"
dic.Add "iinf", "ItemInfoBox"
dic.Add "sinf", "ProtectionSchemeInfoBox"
dic.Add "frma", "OriginalFormatBox"
dic.Add "ipmc", "IPMPControlBox"
dic.Add "schm", "SchemeTypeBox"
dic.Add "schi", "SchemeInformationBox"
dic.Add "srpp", "SRTPProcessBox"
'dic.Add "hnti", "moviehintinformation"
dic.Add "rtp ", "rtpmoviehintinformation"
'dic.Add "hnti", "trackhintinformation"
dic.Add "sdp ", "rtptracksdphintinformation"
dic.Add "hinf", "hintstatisticsbox"
dic.Add "trpy", "hintBytesSent"
dic.Add "nump", "hintPacketsSent"
dic.Add "tpyl", "hintBytesSent"
dic.Add "totl", "hintBytesSent"
dic.Add "npck", "hintPacketsSent"
dic.Add "tpay", "hintBytesSent"
dic.Add "maxr", "hintmaxrate"
dic.Add "dmed", "hintmediaBytesSent"
dic.Add "dimm", "hintimmediateBytesSent"
dic.Add "drep", "hintrepeatedBytesSent"
dic.Add "tmin", "hintminrelativetime"
dic.Add "tmax", "hintmaxrelativetime"
dic.Add "pmax", "hintlargestpacket"
dic.Add "dmax", "hintlongestpacket"
dic.Add "payt", "hintpayloadID"
'* not in the spec
dic.Add "tfdt", "TrackFragmentDecodeTimeBox"
End If
Set AtomDescriptions = dic
End Function
'*********************************************************************************************************
'*********************************************************************************************************
' ______ ___ __ __ _ _ _ _ _ _ _ _
' / \|_ _/ _ \| \/ | / \ | |_| |_ _ __(_) |__ _ _| |_ ___ ___ _ __ ___ _ _| |_(_)_ __ ___ ___
' / _ \ | || | | | |\/| | / _ \| __| __| '__| | '_ \| | | | __/ _ \/ __| | '__/ _ \| | | | __| | '_ \ / _ \/ __|
' / ___ \| || |_| | | | | / ___ \ |_| |_| | | | |_) | |_| | || __/\__ \ | | | (_) | |_| | |_| | | | | __/\__ \
' /_/ \_\_| \___/|_| |_| /_/ \_\__|\__|_| |_|_.__/ \__,_|\__\___||___/ |_| \___/ \__,_|\__|_|_| |_|\___||___/
'*
'*
'* Description: a set of routins which adorns various atom with extra atom specific attributes
'*********************************************************************************************************
'*********************************************************************************************************
'*********************************************************************************************************
'* Name: AtomAttributes
'* Description: convert a stream of bytes to a string (implies ASCII / UTF-8)
'*********************************************************************************************************
Private Function AtomAttributes(ByVal sAtom As String, ByVal xmlAtom As MSXML2.IXMLDOMElement, _
ByVal lPosition As Long, ByVal lSize As Long, ByVal lVersion As Long, ByVal lFlags As Long)
Select Case sAtom
Case "ftyp":
FtypAttributes xmlAtom, lPosition, lSize
Case "mvhd"
MvhdAttributes xmlAtom, lPosition, lSize, lVersion
Case "tkhd":
TkhdAttributes xmlAtom, lPosition, lSize, lVersion
Case "mfhd":
MfhdAttributes xmlAtom, lPosition, lSize
Case "tfhd":
TfhdAttributes xmlAtom, lPosition, lSize
Case "trun":
TrunAttributes xmlAtom, lPosition, lSize, lVersion, lFlags
Case "tfdt":
TfdtAttributes xmlAtom, lPosition, lSize, lVersion
Case "mehd":
MehdAttributes xmlAtom, lPosition, lSize, lVersion
Case "trex":
TrexAttributes xmlAtom, lPosition, lSize, lVersion
Case "elst":
ElstAttributes xmlAtom, lPosition, lSize, lVersion
Case "mdhd":
MdhdAttributes xmlAtom, lPosition, lSize, lVersion
Case "hdlr":
HdlrAttributes xmlAtom, lPosition, lSize, lVersion
Case "vmhd":
VmhdAttributes xmlAtom, lPosition, lSize, lVersion, lFlags
Case "smhd":
SmhdAttributes xmlAtom, lPosition, lSize, lVersion, lFlags
Case "stsd":
StsdAttributes xmlAtom, lPosition, lSize, lVersion, lFlags
Case "stts":
SttsAttributes xmlAtom, lPosition, lSize, lVersion, lFlags
Case "stss":
StssAttributes xmlAtom, lPosition, lSize, lVersion, lFlags
Case "stsc":
StscAttributes xmlAtom, lPosition, lSize, lVersion, lFlags
Case "stco":
StcoAttributes xmlAtom, lPosition, lSize, lVersion, lFlags
Case "stsz":
StszAttributes xmlAtom, lPosition, lSize, lVersion, lFlags
Case "stts":
StszAttributes xmlAtom, lPosition, lSize, lVersion, lFlags
Case "sgpd":
SgpdAttributes xmlAtom, lPosition, lSize, lVersion, lFlags
Case "sbgp":
SbgpAttributes xmlAtom, lPosition, lSize, lVersion, lFlags
Case "ctts":
CttsAttributes xmlAtom, lPosition, lSize, lVersion, lFlags
Case "dref":
DrefAttributes xmlAtom, lPosition, lSize, lVersion, lFlags
End Select
End Function
Private Function DrefAttributes(ByVal xmlAtom As MSXML2.IXMLDOMElement, ByVal lPosition As Long, ByVal lSize As Long, _
ByVal lVersion As Long, ByVal lFlags As Long)
'* DataReferenceBox
Dim idx As Long
idx = lPosition + 12
Dim lEntryCount As Long
lEntryCount = BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "entry_count", lEntryCount
Dim lEntryLoop As Long
For lEntryLoop = 1 To lEntryCount
DoEvents
If lEntryLoop > mlOmissionThreshold Then Exit For
Dim xmlEntry As MSXML2.IXMLDOMElement
Set xmlEntry = xmlAtom.OwnerDocument.createElement("entry")
xmlAtom.appendChild xmlEntry
Dim sDataEntry As String
sDataEntry = BytesToLong(mbytes, lSize - idx, idx)
Dim lNullTerm As Long
lNullTerm = InStr(1, sDataEntry, Chr$(0), vbBinaryCompare)
If lNullTerm > 0 Then
sDataEntry = Left$(sDataEntry, lNullTerm - 1)
End If
xmlEntry.setAttribute "data_entry", sDataEntry
Next
If lEntryCount > mlOmissionThreshold Then
xmlAtom.appendChild xmlAtom.OwnerDocument.createElement("omission")
End If
End Function
Private Function CttsAttributes(ByVal xmlAtom As MSXML2.IXMLDOMElement, ByVal lPosition As Long, ByVal lSize As Long, _
ByVal lVersion As Long, ByVal lFlags As Long)
'* CompositionOffsetBox
Dim idx As Long
idx = lPosition + 12
Dim lEntryCount As Long
lEntryCount = BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "entry_count", lEntryCount
Dim lEntryLoop As Long
For lEntryLoop = 1 To lEntryCount
DoEvents
If lEntryLoop > mlOmissionThreshold Then Exit For
Dim xmlEntry As MSXML2.IXMLDOMElement
Set xmlEntry = xmlAtom.OwnerDocument.createElement("entry")
xmlAtom.appendChild xmlEntry
xmlEntry.setAttribute "sample_count", BytesToLong(mbytes, 4, idx)
xmlEntry.setAttribute "sample_offset", BytesToLong(mbytes, 4, idx)
Next
If lEntryCount > mlOmissionThreshold Then
xmlAtom.appendChild xmlAtom.OwnerDocument.createElement("omission")
End If
End Function
Private Function SgpdAttributes(ByVal xmlAtom As MSXML2.IXMLDOMElement, ByVal lPosition As Long, ByVal lSize As Long, _
ByVal lVersion As Long, ByVal lFlags As Long)
'* SampleGroupDescriptionBox
Dim idx As Long
idx = lPosition + 12
Dim sHandlerType As String
sHandlerType = BytesToString(mbytes, 4, idx)
xmlAtom.setAttribute "handler_type", sHandlerType
xmlAtom.setAttribute "grouping_type", BytesToLong(mbytes, 4, idx)
Dim lEntryCount As Long
lEntryCount = BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "entry_count", lEntryCount
Dim lLoop As Long
For lLoop = 1 To lEntryCount
DoEvents
If lLoop > mlOmissionThreshold Then Exit For
Dim xmlEntry As MSXML2.IXMLDOMElement
Set xmlEntry = xmlAtom.OwnerDocument.createElement("entry")
xmlAtom.appendChild xmlEntry
Select Case sHandlerType
Case "vide":
'TODO
Case "soun":
'TODO
Case "hint":
'TODO
Case "roll":
xmlEntry.setAttribute "roll_distance", BytesToLong(mbytes, 2, idx)
End Select
Next
If lEntryCount > mlOmissionThreshold Then
xmlAtom.appendChild xmlAtom.OwnerDocument.createElement("omission")
End If
End Function
Private Function SbgpAttributes(ByVal xmlAtom As MSXML2.IXMLDOMElement, ByVal lPosition As Long, ByVal lSize As Long, _
ByVal lVersion As Long, ByVal lFlags As Long)
'* SampleToGroupBox
Dim idx As Long
idx = lPosition + 12
xmlAtom.setAttribute "grouping_type", BytesToLong(mbytes, 4, idx)
Dim lEntries As Long
lEntries = BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "entry_count", lEntries
Dim lLoop As Long
For lLoop = 1 To lEntries
DoEvents
If lLoop > mlOmissionThreshold Then Exit For
Dim xmlEntry As MSXML2.IXMLDOMElement
Set xmlEntry = xmlAtom.OwnerDocument.createElement("entry")
xmlAtom.appendChild xmlEntry
xmlEntry.setAttribute "sample_count", BytesToLong(mbytes, 4, idx)
xmlEntry.setAttribute "group_description_index", BytesToLong(mbytes, 4, idx)
Next
If lEntries > mlOmissionThreshold Then
xmlAtom.appendChild xmlAtom.OwnerDocument.createElement("omission")
End If
End Function
Private Function StssAttributes(ByVal xmlAtom As MSXML2.IXMLDOMElement, ByVal lPosition As Long, ByVal lSize As Long, ByVal lVersion As Long, ByVal lFlags As Long)
'* SyncSampleBox
Dim idx As Long
idx = lPosition + 12
Dim lEntries As Long
lEntries = BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "entry_count", lEntries
Dim lLoop As Long
For lLoop = 1 To lEntries
DoEvents
If lLoop > mlOmissionThreshold Then Exit For
Dim xmlEntry As MSXML2.IXMLDOMElement
Set xmlEntry = xmlAtom.OwnerDocument.createElement("entry")
xmlAtom.appendChild xmlEntry
xmlEntry.setAttribute "sample_number", BytesToLong(mbytes, 4, idx)
Next
If lEntries > mlOmissionThreshold Then
xmlAtom.appendChild xmlAtom.OwnerDocument.createElement("omission")
End If
End Function
Private Function StcoAttributes(ByVal xmlAtom As MSXML2.IXMLDOMElement, ByVal lPosition As Long, ByVal lSize As Long, ByVal lVersion As Long, ByVal lFlags As Long)
'* ChunkOffsetBox
Dim idx As Long
idx = lPosition + 12
Dim lEntries As Long
lEntries = BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "entry_count", lEntries
Dim lLoop As Long
For lLoop = 1 To lEntries
DoEvents
If lLoop > mlOmissionThreshold Then Exit For
Dim xmlEntry As MSXML2.IXMLDOMElement
Set xmlEntry = xmlAtom.OwnerDocument.createElement("entry")
xmlAtom.appendChild xmlEntry
xmlEntry.setAttribute "chunk_offset", BytesToLong(mbytes, 4, idx)
Next
If lEntries > mlOmissionThreshold Then
xmlAtom.appendChild xmlAtom.OwnerDocument.createElement("omission")
End If
End Function
Private Function StszAttributes(ByVal xmlAtom As MSXML2.IXMLDOMElement, ByVal lPosition As Long, ByVal lSize As Long, ByVal lVersion As Long, ByVal lFlags As Long)
'* SampleSizeBox
Dim idx As Long
idx = lPosition + 12
Dim lGlobalSampleSize As Long
lGlobalSampleSize = BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "sample_size", lGlobalSampleSize
Dim lCount As Long
lCount = BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "sample_count", lCount
If lGlobalSampleSize = 0 Then
Dim lLoop As Long
For lLoop = 1 To lCount
DoEvents
If lLoop > mlOmissionThreshold Then Exit For
Dim xmlEntry As MSXML2.IXMLDOMElement
Set xmlEntry = xmlAtom.OwnerDocument.createElement("entry")
xmlAtom.appendChild xmlEntry
xmlEntry.setAttribute "entry_size", BytesToLong(mbytes, 4, idx)
Next
If lCount > mlOmissionThreshold Then
xmlAtom.appendChild xmlAtom.OwnerDocument.createElement("omission")
End If
End If
End Function
Private Function StscAttributes(ByVal xmlAtom As MSXML2.IXMLDOMElement, ByVal lPosition As Long, ByVal lSize As Long, ByVal lVersion As Long, ByVal lFlags As Long)
'* SampleToChunkBox
Dim idx As Long
idx = lPosition + 12
Dim lEntries As Long
lEntries = BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "entry_count", lEntries
Dim lLoop As Long
For lLoop = 1 To lEntries
DoEvents
If lLoop > mlOmissionThreshold Then Exit For
Dim xmlEntry As MSXML2.IXMLDOMElement
Set xmlEntry = xmlAtom.OwnerDocument.createElement("entry")
xmlAtom.appendChild xmlEntry
xmlEntry.setAttribute "first_chunk", BytesToLong(mbytes, 4, idx)
xmlEntry.setAttribute "samples_per_chunk", BytesToLong(mbytes, 4, idx)
xmlEntry.setAttribute "sample_description_index", BytesToLong(mbytes, 4, idx)
Next
If lEntries > mlOmissionThreshold Then
xmlAtom.appendChild xmlAtom.OwnerDocument.createElement("omission")
End If
End Function
Private Function SttsAttributes(ByVal xmlAtom As MSXML2.IXMLDOMElement, ByVal lPosition As Long, ByVal lSize As Long, ByVal lVersion As Long, ByVal lFlags As Long)
'* TimeToSampleBox
Dim idx As Long
idx = lPosition + 12
Dim entry_count As Long
entry_count = BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "entry_count", entry_count
Dim lLoop As Long
For lLoop = 1 To entry_count
DoEvents
If lLoop > mlOmissionThreshold Then Exit For
Dim xmlSttsSample As MSXML2.IXMLDOMElement
Set xmlSttsSample = xmlAtom.OwnerDocument.createElement("entry")
xmlAtom.appendChild xmlSttsSample
xmlSttsSample.setAttribute "sample_count", BytesToLong(mbytes, 4, idx)
xmlSttsSample.setAttribute "sample_delta", BytesToLong(mbytes, 4, idx)
Next
If entry_count > mlOmissionThreshold Then
xmlAtom.appendChild xmlAtom.OwnerDocument.createElement("omission")
End If
End Function
Private Function StsdAttributes(ByVal xmlAtom As MSXML2.IXMLDOMElement, ByVal lPosition As Long, ByVal lSize As Long, ByVal lVersion As Long, ByVal lFlags As Long)
'* SampleDescriptionBox
Dim idx As Long
idx = lPosition + 12
Dim entry_count As Long
entry_count = BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "entry_count", entry_count
Dim lLoop As Long
For lLoop = 1 To entry_count
Dim xmlEntry As MSXML2.IXMLDOMElement
Set xmlEntry = xmlAtom.OwnerDocument.createElement("entry")
xmlAtom.appendChild xmlEntry
'xmlEntry.setAttribute "sample_count", BytesToLong(mbytes, 4, idx)
'xmlEntry.setAttribute "sample_delta", BytesToLong(mbytes, 4, idx)
Next
End Function
Private Function SmhdAttributes(ByVal xmlAtom As MSXML2.IXMLDOMElement, ByVal lPosition As Long, ByVal lSize As Long, ByVal lVersion As Long, ByVal lFlags As Long)
'* SoundMediaHeaderBox
Dim idx As Long
idx = lPosition + 12
xmlAtom.setAttribute "balance", BytesToLong(mbytes, 2, idx)
xmlAtom.setAttribute "reserved", BytesToLong(mbytes, 2, idx)
End Function
Private Function VmhdAttributes(ByVal xmlAtom As MSXML2.IXMLDOMElement, ByVal lPosition As Long, ByVal lSize As Long, ByVal lVersion As Long, ByVal lFlags As Long)
'* VideoMediaHeaderBox
Dim idx As Long
idx = lPosition + 12
xmlAtom.setAttribute "graphicsmode", BytesToLong(mbytes, 2, idx)
xmlAtom.setAttribute "opcolor_0", BytesToLong(mbytes, 2, idx)
xmlAtom.setAttribute "opcolor_1", BytesToLong(mbytes, 2, idx)
xmlAtom.setAttribute "opcolor_2", BytesToLong(mbytes, 2, idx)
End Function
Private Function HdlrAttributes(ByVal xmlAtom As MSXML2.IXMLDOMElement, ByVal lPosition As Long, ByVal lSize As Long, ByVal lVersion As Long)
'* HandlerBox
Dim idx As Long
idx = lPosition + 12
xmlAtom.setAttribute "pre_defined", BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "handler_type", BytesToString(mbytes, 4, idx)
Dim lReservedIdx As Long
For lReservedIdx = 0 To 2
xmlAtom.setAttribute "reserved_" & lReservedIdx, BytesToLong(mbytes, 4, idx)
Next
xmlAtom.setAttribute "name", BytesToString(mbytes, lPosition + lSize - idx, (idx))
End Function
Private Function MdhdAttributes(ByVal xmlAtom As MSXML2.IXMLDOMElement, ByVal lPosition As Long, ByVal lSize As Long, ByVal lVersion As Long)
'* MediaHeaderBox
Dim idx As Long
idx = lPosition + 12
If lVersion = 1 Then
xmlAtom.setAttribute "creation_time", BytesToVeryLongDecimalString(mbytes, 8, idx)
xmlAtom.setAttribute "modification_time", BytesToVeryLongDecimalString(mbytes, 8, idx)
xmlAtom.setAttribute "timescale", BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "duration", BytesToVeryLongDecimalString(mbytes, 8, idx)
Else
xmlAtom.setAttribute "creation_time", BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "modification_time", BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "timescale", BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "duration", BytesToLong(mbytes, 4, idx)
End If
Dim languagesPack As Long
languagesPack = BytesToLong(mbytes, 2, idx)
xmlAtom.setAttribute "language", ISO639_2T(languagesPack)
xmlAtom.setAttribute "pre_defined", BytesToLong(mbytes, 2, idx)
End Function
Private Function ISO639_2T(ByVal lThreeFiveBitCharacters As Long) As String
'* "Each character is packed as the difference between its ASCII value and 0x60. Since the code
'* is confined to being three lower-case letters, these values are strictly positive"
'* typical results are 'eng' for English and 'und' for undefined/undetermined
Dim lLoop As Long
For lLoop = 1 To 3
ISO639_2T = Chr$(96 + (lThreeFiveBitCharacters And 31)) & ISO639_2T
lThreeFiveBitCharacters = lThreeFiveBitCharacters / 32
Next
End Function
Private Function ElstAttributes(ByVal xmlAtom As MSXML2.IXMLDOMElement, ByVal lPosition As Long, ByVal lSize As Long, ByVal lVersion As Long)
'* EditListBox
Dim idx As Long
idx = lPosition + 12
Dim lEntryCount As Long
lEntryCount = BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "entry_count", lEntryCount
Dim lEntryLoop As Long
For lEntryLoop = 1 To lEntryCount
If lVersion = 1 Then
xmlAtom.setAttribute "segment_duration", BytesToVeryLongDecimalString(mbytes(), 8, idx)
xmlAtom.setAttribute "media_time", BytesToVeryLongDecimalString(mbytes(), 8, idx)
Else
xmlAtom.setAttribute "segment_duration", BytesToLong(mbytes(), 4, idx)
xmlAtom.setAttribute "media_time", BytesToLong(mbytes(), 4, idx)
End If
xmlAtom.setAttribute "media_rate_integer", BytesToLong(mbytes(), 2, idx)
xmlAtom.setAttribute "media_rate_fraction", BytesToLong(mbytes(), 2, idx)
Next
End Function
Private Function TrexAttributes(ByVal xmlAtom As MSXML2.IXMLDOMElement, ByVal lPosition As Long, ByVal lSize As Long, ByVal lVersion As Long)
'* TrackExtendsBox
Dim idx As Long
idx = lPosition + 12
xmlAtom.setAttribute "track_ID", BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "default_sample_description_index", BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "default_sample_duration", BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "default_sample_size", BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "default_sample_flags", BytesToLong(mbytes, 4, idx)
End Function
Private Function MehdAttributes(ByVal xmlAtom As MSXML2.IXMLDOMElement, ByVal lPosition As Long, ByVal lSize As Long, ByVal lVersion As Long)
'* MovieExtendsHeaderBox
xmlAtom.setAttribute "fragment_duration", BytesToVeryLongDecimalString(mbytes, VBA.IIf(lVersion = 1, 8, 4), (lPosition + 12))
End Function
Private Function MfhdAttributes(ByVal xmlAtom As MSXML2.IXMLDOMElement, ByVal lPosition As Long, ByVal lSize As Long)
'* MovieFragmentHeaderBox
xmlAtom.setAttribute "sequence_number", BytesToLongPtr(mbytes, 8, (lPosition + 8))
End Function
Private Function MvhdAttributes(ByVal xmlAtom As MSXML2.IXMLDOMElement, ByVal lPosition As Long, ByVal lSize As Long, ByVal lVersion As Long)
'* MovieHeaderBox
Dim idx As Long
idx = lPosition + 12
If lVersion = 1 Then
xmlAtom.setAttribute "creationtime", BytesToLongPtr(mbytes, 8, idx)
xmlAtom.setAttribute "modificationtime", BytesToLongPtr(mbytes, 8, idx)
xmlAtom.setAttribute "timescale", BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "duration", BytesToLongPtr(mbytes, 8, idx)
Else
xmlAtom.setAttribute "creationtime", BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "modificationtime", BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "timescale", BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "duration", BytesToLong(mbytes, 4, idx)
End If
xmlAtom.setAttribute "preferredrate", BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "preferredvolume", BytesToLong(mbytes, 2, idx)
xmlAtom.setAttribute "reserved1", BytesToLong(mbytes, 2, idx)
xmlAtom.setAttribute "reserved2_0", BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "reserved2_1", BytesToLong(mbytes, 4, idx)
Dim lMatrixIdx As Long
For lMatrixIdx = 0 To 8
xmlAtom.setAttribute "matrix_" & lMatrixIdx, BytesToLong(mbytes, 4, idx)
Next
Dim lPreDefinedIdx As Long
For lPreDefinedIdx = 0 To 5
xmlAtom.setAttribute "pre_defined_" & lPreDefinedIdx, BytesToLong(mbytes, 4, idx)
Next
xmlAtom.setAttribute "nexttrackid", BytesToVeryLongDecimalString(mbytes, 4, idx)
End Function
Private Function TkhdAttributes(ByVal xmlAtom As MSXML2.IXMLDOMElement, ByVal lPosition As Long, _
ByVal lSize As Long, ByVal lVersion As Long)
'* TrackHeaderBox
Dim idx As Long
idx = lPosition + 12
If lVersion = 1 Then
xmlAtom.setAttribute "creation_time", BytesToLongPtr(mbytes, 8, idx)
xmlAtom.setAttribute "modification_time", BytesToLongPtr(mbytes, 8, idx)
xmlAtom.setAttribute "track_ID", BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "reserved", BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "duration", BytesToLongPtr(mbytes, 8, idx)
Else
xmlAtom.setAttribute "creation_time", BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "modification_time", BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "track_ID", BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "reserved", BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "duration", BytesToLong(mbytes, 4, idx)
End If
xmlAtom.setAttribute "reserved1_0", BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "reserved1_1", BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "layer", BytesToLong(mbytes, 2, idx)
xmlAtom.setAttribute "alternationgroup", BytesToLong(mbytes, 2, idx)
xmlAtom.setAttribute "volume", BytesToLong(mbytes, 2, idx)
xmlAtom.setAttribute "reserved2", BytesToLong(mbytes, 2, idx)
Dim lMatrixIdx As Long
For lMatrixIdx = 0 To 8
xmlAtom.setAttribute "matrixstructure_" & lMatrixIdx, BytesToLong(mbytes, 4, idx)
Next
xmlAtom.setAttribute "trackwidth", BytesToLong(mbytes, 4, idx)
xmlAtom.setAttribute "trackheight", BytesToLong(mbytes, 4, idx)
End Function
Private Function TrunAttributes(ByVal xmlAtom As MSXML2.IXMLDOMElement, ByVal lPosition As Long, ByVal lSize As Long, ByVal lVersion As Long, ByVal lFlags As Long)
'* TrackRunBox
Dim idx As Long
idx = lPosition + 12
Dim lSampleCount As Long
lSampleCount = BytesToLong(mbytes(), 4, idx)
xmlAtom.setAttribute "sample_count", lSampleCount
If lFlags And MP4_TRUN_DATA_OFFSET = 1 Then
xmlAtom.setAttribute "data_offset", BytesToLong(mbytes(), 4, idx)
End If
If lFlags And MP4_TRUN_FIRST_FLAGS = 1 Then
xmlAtom.setAttribute "first_sample_flags", BytesToLong(mbytes(), 4, idx)
End If
Dim bSampleHasDuration As Boolean
bSampleHasDuration = lFlags And MP4_TRUN_SAMPLE_DURATION
Dim bSampleHasSize As Boolean
bSampleHasSize = lFlags And MP4_TRUN_SAMPLE_SIZE
Dim bSampleHasFlags As Boolean
bSampleHasFlags = lFlags And MP4_TRUN_SAMPLE_FLAGS
Dim bSampleHasTimeOffset As Boolean
bSampleHasTimeOffset = lFlags And MP4_TRUN_SAMPLE_TIME_OFFSET
Dim entry_size As Long
entry_size = VBA.IIf(bSampleHasDuration, 1, 0) + _
VBA.IIf(bSampleHasSize, 1, 0) + _
VBA.IIf(bSampleHasFlags, 1, 0) + _
VBA.IIf(bSampleHasTimeOffset, 1, 0)
Debug.Assert entry_size = 1 '* we assuming the trun table is sample lengths
Dim lSampleSizesTotalled As Long
lSampleSizesTotalled = 0
Dim lSampleLoop As Long
For lSampleLoop = 0 To lSampleCount - 1
Dim lSampleDuration As Long: lSampleDuration = 0
Dim lSampleSize As Long: lSampleSize = 0
Dim lSampleFlags As Long: lSampleFlags = 0
Dim lSampleTimeOffset As Long: lSampleTimeOffset = 0
If bSampleHasDuration Then lSampleDuration = BytesToLong(mbytes(), 4, idx)
If bSampleHasSize Then
lSampleSize = BytesToLong(mbytes(), 4, idx)
lSampleSizesTotalled = lSampleSizesTotalled + lSampleSize
End If
If bSampleHasFlags Then lSampleFlags = BytesToLong(mbytes(), 4, idx)
If bSampleHasTimeOffset Then lSampleTimeOffset = BytesToLong(mbytes(), 4, idx)
If lSampleLoop <= mlOmissionThreshold Then
Dim xmlSample As MSXML2.IXMLDOMElement
Set xmlSample = xmlAtom.OwnerDocument.createElement("entry")
xmlAtom.appendChild xmlSample
If bSampleHasDuration Then xmlSample.setAttribute "sample_duration", lSampleDuration
If bSampleHasSize Then xmlSample.setAttribute "sample_size", lSampleSize
If bSampleHasFlags Then xmlSample.setAttribute "sample_flags", lSampleFlags
If bSampleHasTimeOffset Then xmlSample.setAttribute "sample_composition_time_offset", lSampleTimeOffset
End If
Next lSampleLoop
If lSampleCount > mlOmissionThreshold Then
Dim xmlOmission As MSXML2.IXMLDOMElement
Set xmlOmission = xmlAtom.OwnerDocument.createElement("omission")
xmlAtom.appendChild xmlOmission
End If
xmlAtom.setAttribute "SampleSizesTotalled", lSampleSizesTotalled
End Function
Private Function TfdtAttributes(ByVal xmlAtom As MSXML2.IXMLDOMElement, ByVal lPosition As Long, ByVal lSize As Long, ByVal lVersion As Long)
'* TrackFragmentDecodeTimeBox
lPosition = lPosition + 12
xmlAtom.setAttribute "base_media_decode_time", BytesToVeryLongDecimalString(mbytes(), VBA.IIf(lVersion = 0, 4, 8), lPosition)
End Function
Private Function TfhdAttributes(ByVal xmlAtom As MSXML2.IXMLDOMElement, ByVal lPosition As Long, ByVal lSize As Long)
'* TrackFragmentHeaderBox
lPosition = lPosition + 12
xmlAtom.setAttribute "track_ID", BytesToLong(mbytes(), 4, lPosition)
'Stop
If lSize = 36 Then
'* we have optional fields
xmlAtom.setAttribute "base_data_offset", BytesToLongPtr(mbytes(), 8, lPosition)
xmlAtom.setAttribute "sample_description_index", BytesToLong(mbytes(), 4, lPosition)
xmlAtom.setAttribute "default_sample_duration", BytesToLong(mbytes(), 4, lPosition)
xmlAtom.setAttribute "default_sample_size", BytesToLong(mbytes(), 4, lPosition)
xmlAtom.setAttribute "default_sample_flags", BytesToLong(mbytes(), 4, lPosition)
End If
End Function
Private Function FtypAttributes(ByVal xmlAtom As MSXML2.IXMLDOMElement, ByVal lPosition As Long, ByVal lSize As Long)
'* FileTypeBox
lPosition = lPosition + 8
xmlAtom.setAttribute "major_brand", BytesToString(mbytes(), 4, lPosition)
xmlAtom.setAttribute "minor_version", BytesToLong(mbytes(), 4, lPosition)
Dim lCompatibleBrandsLen As Long
lCompatibleBrandsLen = lSize - lPosition
Dim lCompatibleBrandsCount As Long
lCompatibleBrandsCount = lCompatibleBrandsLen / 4
Dim sCompatibleBrands As String
If lCompatibleBrandsCount > 0 Then
sCompatibleBrands = BytesToString(mbytes(), lCompatibleBrandsLen, lPosition) '* eat what's left
'* add comma separators
Dim sCompatibleBrandsCSV As String
sCompatibleBrandsCSV = ""
Dim lBrandLoop As Long
For lBrandLoop = 1 To lCompatibleBrandsCount
sCompatibleBrandsCSV = sCompatibleBrandsCSV & VBA.IIf(Len(sCompatibleBrandsCSV) > 0, ",", "") & Mid$(sCompatibleBrands, 4 * (lBrandLoop - 1) + 1, 4)
Next
End If
xmlAtom.setAttribute "compatible_brands", sCompatibleBrandsCSV
End Function
'*********************************************************************************************************
'* Name: BytesToString
'* Description: convert a stream of bytes to a string (implies ASCII / UTF-8)
'*********************************************************************************************************
Private Function BytesToString(ByRef bytes() As Byte, ByVal cBytes As Long, ByRef plPosition As Long) As String
Dim lLoop As Long
For lLoop = 0 To cBytes - 1
BytesToString = BytesToString & Chr$(bytes(plPosition + lLoop))
Next
plPosition = plPosition + cBytes
End Function
'*********************************************************************************************************
'* Name: FullBoxAtoms
'* Description: serves two purposes
'* (1) which atom are 'FullBox' and so have minimum size of 12 bits and
'* (2) where flags are given it details the enumeration so a numer can be decomposed
'*********************************************************************************************************
Private Function FullBoxAtoms() As Scripting.Dictionary
Static dicFullBoxAtoms As Scripting.Dictionary
If dicFullBoxAtoms Is Nothing Then
Set dicFullBoxAtoms = New Scripting.Dictionary
dicFullBoxAtoms.CompareMode = TextCompare
Dim vFullBoxAtoms As Variant
vFullBoxAtoms = Array( _
Array("stco", ""), _
Array("stsz", ""), _
Array("stsc", ""), _
Array("stsd", ""), _
Array("stts", ""), _
Array("stss", ""), _
Array("ctts", ""), _
Array("dref", ""), _
Array("smhd", ""), _
Array("hdlr", ""), _
Array("mfhd", ""), _
Array("mdhd", ""), _
Array("mvhd", ""), _
Array("tfdt", ""), _
Array("trex", ""), _
Array("elst", ""), _
Array("mehd", ""), _
Array("vmhd", ""), _
Array("sgpd", ""), _
Array("sbgp", ""), _
Array("tfhd", "base-data-offset-present|sample-description-index-present||default-sample-duration-present|default-sample-size-present|default-sample-flags-present|||||||||||duration-is-empty|default-base-is-moof"), _
Array("tkhd", "Track_enabled|Track_in_movie|Track_In_preview"), _
Array("trun", "data-offset-present||first-sample-flags-present||||||sample-duration-present|sample-size-present|sample-flags-present|sample-composition-time-offsets-present"))
Dim vLoop As Variant
For Each vLoop In vFullBoxAtoms
dicFullBoxAtoms.Add vLoop(0), vLoop(1)
Next vLoop
End If
Set FullBoxAtoms = dicFullBoxAtoms
End Function
'************************************************************
'* test routine
'************************************************************
Private Sub TestFlagsToString()
Debug.Assert FlagsToString("tkhd", 5) = "(5) (h1) Track_enabled,(h4) Track_In_preview"
Debug.Assert FlagsToString("tkhd", 0) = "(0)"
Debug.Assert FlagsToString("tkhd", 4) = "(4) (h4) Track_In_preview"
End Sub
'*********************************************************************************************************
'* Name: FlagsToString
'* Description: decompose a binary flags enum to a list of comma separated strings
'*********************************************************************************************************
Private Function FlagsToString(ByVal sAtom As String, ByVal lFlags As Long, Optional lStartBit As Long) As String
Dim sFlagsEnum As String
If FullBoxAtoms().Exists(sAtom) Then
sFlagsEnum = FullBoxAtoms().Item(sAtom)
End If
If Len(sFlagsEnum) > 0 Then
FlagsToString = "(" & CStr(lFlags) & ") " & FlagsToStringInner(sFlagsEnum, lFlags)
Else
FlagsToString = CStr(lFlags)
End If
FlagsToString = Trim(FlagsToString)
End Function
'*********************************************************************************************************
'* Name: FlagsToStringInner
'* Description: decompose a binary flags enum to a list of comma separated strings
'*********************************************************************************************************
Private Function FlagsToStringInner(ByVal sFlagsEnum As String, ByVal lFlags As Long, Optional lStartBit As Long) As String
Dim vFlags
vFlags = VBA.Split(sFlagsEnum, "|")
FlagsToStringInner = ""
Dim idx As Long
idx = 0
Dim bDebug As Boolean
bDebug = lFlags > 1512
'Debug.Assert lFlags < 1512
Dim lBit As LongPtr
lBit = 1
Dim lFlagsCopy As Long
lFlagsCopy = lFlags
Do
DoEvents
If lFlagsCopy Mod 2 = 1 Then
FlagsToStringInner = FlagsToStringInner & VBA.IIf(Len(FlagsToStringInner) > 0, ",", "") & "(h" & Hex$(lBit) & ") " & vFlags(idx)
lFlagsCopy = lFlagsCopy - 1 '* eat the bit
If bDebug Then
Debug.Print FlagsToStringInner
End If
End If
lFlagsCopy = lFlagsCopy / 2
lBit = lBit * 2
idx = idx + 1
Loop While lFlagsCopy > 0
End Function
'*********************************************************************************************************
'* Name: FindAtomAtPosition2
'* Description: given a list of potential children and an end of parent limit we go look for child atoms
'*********************************************************************************************************
Private Function FindAtomAtPosition2(ByVal lPosition As Long, ByVal lEndOfParent As Long, dicChildrenOfAParent As Scripting.Dictionary, _
ByRef psAtom As String)
FindAtomAtPosition2 = -1
Dim lOffset As Long
lOffset = 0
Do
DoEvents
Dim sFourCC As String
sFourCC = FourBytesToFourCC(lPosition + lOffset)
If dicChildrenOfAParent.Exists(sFourCC) Then
FindAtomAtPosition2 = lPosition + lOffset
psAtom = sFourCC
GoTo SingleExit
End If
lOffset = lOffset + 1
Loop Until lOffset + lPosition >= lEndOfParent
SingleExit:
End Function
'*********************************************************************************************************
'* Name: FourBytesToFourCC
'* Description: Read four bytes and convert to a four characer string
'*********************************************************************************************************
Private Function FourBytesToFourCC(ByVal lPosition As Long)
On Error Resume Next
If lPosition + 3 > UBound(mbytes) Then
FourBytesToFourCC = ""
Else
FourBytesToFourCC = Chr$(mbytes(lPosition + 0)) + Chr$(mbytes(lPosition + 1)) + Chr$(mbytes(lPosition + 2)) + Chr$(mbytes(lPosition + 3))
End If
End Function
'*********************************************************************************************************
'* Name: FourBytesToLong
'* Description: Read four bytes and convert to a 32-bit Long
'*********************************************************************************************************
Private Function FourBytesToLong(ByVal lPosition As Long) As Long
Dim lWork As Long
Debug.Assert lPosition >= 0
lWork = mbytes(lPosition)
lWork = lWork * 256 + mbytes(lPosition + 1)
lWork = lWork * 256 + mbytes(lPosition + 2)
lWork = lWork * 256 + mbytes(lPosition + 3)
FourBytesToLong = lWork
End Function
'*********************************************************************************************************
'* Name: ReadByteFile
'* Description: Read a file's contents into a byte array
'*********************************************************************************************************
Private Function ReadByteFile(ByVal sFileName As String) As Byte()
Debug.Assert fso.FileExists(sFileName)
Dim fileNum As Integer
Dim bytes() As Byte
fileNum = FreeFile
Open sFileName For Binary As fileNum
ReDim bytes(LOF(fileNum) - 1)
Get fileNum, , bytes
Close fileNum
ReadByteFile = bytes
End Function
'*********************************************************************************************************
'* Name: BytesToLong
'* Description: Convert a stream of bytes to a 32-bit Long
'*********************************************************************************************************
Private Function BytesToLong(ByRef bytes() As Byte, ByVal cBytes As Long, ByRef plPosition As Long, Optional bLittleEndian As Boolean = False) As Long
BytesToLong = 0
Dim lLoop As Long
If bLittleEndian Then
For lLoop = cBytes - 1 To 0 Step -1
BytesToLong = BytesToLong * 256 + bytes(plPosition + lLoop)
Next
Else
For lLoop = 0 To cBytes - 1
BytesToLong = BytesToLong * 256 + bytes(plPosition + lLoop)
Next
End If
plPosition = plPosition + cBytes
End Function
'*********************************************************************************************************
'* Name: BytesToLongPtr
'* Description: Convert a long stream of bytes to a long point (LongPtr) to try to to break out of 32-bit Long boundary
'*********************************************************************************************************
Private Function BytesToLongPtr(ByRef bytes() As Byte, ByVal cBytes As Long, ByRef plPosition As Long, Optional bLittleEndian As Boolean = False) As LongPtr
BytesToLongPtr = 0
Dim lLoop As Long
If bLittleEndian Then
For lLoop = cBytes - 1 To 0 Step -1
BytesToLongPtr = BytesToLongPtr * 256# + bytes(plPosition + lLoop)
Next
Else
For lLoop = 0 To cBytes - 1
BytesToLongPtr = BytesToLongPtr * 256# + bytes(plPosition + lLoop)
Next
End If
plPosition = plPosition + cBytes
End Function
'*********************************************************************************************************
'* Name: BytesToLongArray
'* Description: Convert a long stream of bytes to an array of long to break out of 32-bit Long boundary
'*********************************************************************************************************
Private Function BytesToLongArray(ByRef bytes() As Byte, ByVal cBytes As Long, ByRef plPosition As Long, Optional bLittleEndian As Boolean = False) As Long()
Dim cLongs As Long
cLongs = cBytes \ 4 + 1
ReDim alReturn(0 To cLongs - 1) As Long
Dim lLoop As Long
For lLoop = 0 To cBytes - 1
alReturn(lLoop) = BytesToLong(bytes, cBytes, plPosition, bLittleEndian)
Next lLoop
BytesToLongArray = alReturn
End Function
'*********************************************************************************************************
'* Name: RemoveReports
'* Description: Clear down our workbook of previous reports
'*********************************************************************************************************
Private Sub RemoveReports()
On Error GoTo SingleExit
Dim ws As Excel.Worksheet
Dim lLoop As Long
For lLoop = ThisWorkbook.Worksheets.count To 1 Step -1
Set ws = ThisWorkbook.Worksheets.Item(lLoop)
If Left$(ws.Cells(1, 1), 5) = Chr$(60) & "mpeg" Then
Application.DisplayAlerts = False
ws.Delete
Application.DisplayAlerts = True
End If
Next
SingleExit:
Application.DisplayAlerts = True
End Sub
'*********************************************************************************************************
'* Name: BytesToVeryLongDecimalString
'* Description: Converts a long string of bytes into a decimal string
'*
'* Dependency: Requires Python COM Class found in file PythonBigNumbersComServer.py to be registered
'*
'*********************************************************************************************************
Private Function BytesToVeryLongDecimalString(ByRef bytes() As Byte, ByVal cBytes As Long, ByRef plPosition As Long, Optional bLittleEndian As Boolean = False) As String
BytesToVeryLongDecimalString = ""
If bLittleEndian Then
'not yet implemented
Else
Static server As Object
If server Is Nothing Then
Set server = VBA.CreateObject("PythonInVBA.PythonBigNumbersComServer")
End If
Dim lLoop As Long
ReDim bytessubarray(0 To cBytes - 1) As Byte
For lLoop = 0 To cBytes - 1
bytessubarray(lLoop) = bytes(plPosition + lLoop)
Next lLoop
'Stop
BytesToVeryLongDecimalString = server.LongByteArrayToDecimalString(bytessubarray)
'Stop
End If
plPosition = plPosition + cBytes
End Function
Finally, I hived off data specific to me in a module called modMyFiles. This is to emphasise that your files will be located elsewhere.
modMyFiles module
Option Explicit
'***********************************************************************************
'* Module Name: modMyFiles
'* Copyright exceldevelopmentplatform.blogspot.com 2nd November 2019
'*
'* Description:
'* This module simply contains file locations of mp4 files to work with
'* It allows me to isolate the details of my files, as opposed to your files dear reader
'*
'***********************************************************************************
Public Function MyMepgFiles() As Scripting.Dictionary
Dim dic As Scripting.Dictionary
Set dic = New Scripting.Dictionary
'* these will differ for you, dear reader!
dic.Add "C:\Users\Simon\Documents\Expression\Expression Encoder\Output\ENVY 01-11-2019 22.03.33\newsnight.mp4", ""
Set MyMepgFiles = dic
End Function