Ok I have made a start not sure it is right but getting there. Just reading comments at the moment.
Each packet starts with 'OggS' and the comments are in the second packet.
1. open the file and search for the second 'OggS'
2. From this point search for 'vorbis'
3. The next four bytes are a little endian number so read these four bytes as a long number
In a HEX editor you will see as an example
76 6F 72 62 69 73 34 00 00 00
which is 'vorbis' followed by a four byte number in real life 00 00 00 34 (52 in decimal remember it is little endian)
4. Read in the next 52 bytes as a UTF-8 encoded string. This is the vendor string.
5. Read the next four bytes as a number this gives you the number of comments in the file.
6. The four bytes after this are the length of the comment.
7. Read in this number of bytes as a UTF-8 encoded string
Repeat steps 6 and 7 for the number of comments retrieved in step 5
All comments are in the form 'TITLE=Tell Me Why'
The first part before the = is the comment title (not TITLE) the second part is the comment content.
Some files have picture metadata in them I have not sorted this out yet but it is still read the same way.
Writing new comments I have not looked into yet but I think it is copy file to comment start to another temp file add new comments including total and length for each then from comments end, in original, copy all bytes to temp file, delete original file then rename temp to original.
Here is the code for a module and example use to read ogg comments please feel free to suggest improvements.
Code: Select all
DeclareModule oggTags
Structure tag
Title.s
Value.s
EndStructure
Declare GetComments(FileName.s,List Comments.tag())
EndDeclareModule
Module oggTags
Structure ByteArray
byte.b[0]
EndStructure
Procedure.i QuickSearch (*mainMem.ByteArray, mainSize.i, *findMem.ByteArray, findSize.i, startOff.i=0)
; -- Simplification of the Boyer-Moore algorithm;
; searches for a sequence of bytes in memory
; (not for characters, so it works in ASCII mode and Unicode mode)
; in : *mainMem: pointer to memory area where to search
; mainSize: size of memory area where to search (bytes)
; *findMem: pointer to byte sequence to search for
; findSize: number of bytes to search for
; startOff: offset in <mainMem>, where the search begins (bytes)
; out: offset in <mainMem>, where <findMem> was found (bytes);
; -1 if not found
; Note: The first offset is 0 (not 1)!
;
; after <http://www-igm.univ-mlv.fr/~lecroq/string/node19.html#SECTION00190>, 31.8.2008
; (translated from C to PureBasic by Little John)
Protected i.i, diff.i
Protected Dim badByte.i(255)
; Preprocessing
For i = 0 To 255
badByte(i) = findSize + 1
Next
For i = 0 To findSize - 1
badByte(*findMem\byte[i] & #FF) = findSize - i
Next
; Searching
diff = mainSize - findSize
While startOff <= diff
If CompareMemory(*mainMem + startOff, *findMem, findSize) = 1
ProcedureReturn startOff
EndIf
startOff + badByte(*mainMem\byte[startOff + findSize] & #FF) ; shift
Wend
ProcedureReturn -1 ; not found
EndProcedure
Procedure.q FindInFile (infile.i, *find, findSize.i, startOff.q=0, bufferSize.i=4096)
;Code From Purebasic Forum By littleJohn
; -- Looks in <infile> for byte sequence at *find;
; works in ASCII mode and Unicode mode.
; in : infile : number of a file, that was opened for reading
; *find : pointer to byte sequence to search for
; findSize : number of bytes to search for
; startOff : offset in the file where the search begins (bytes)
; bufferSize: size of used memory buffer (bytes)
; out: offset in the file, where byte sequence at *find was found (bytes),
; -1 if byte sequence at *find was not found in <infile>,
; -2 on error
; Note: The first offset is 0 (not 1)!
Protected *buffer
Protected offset.q, move.i, bytes.i
move = bufferSize - findSize + 1
If move < 1
ProcedureReturn -2 ; error
EndIf
*buffer = AllocateMemory(bufferSize)
If *buffer = 0
ProcedureReturn -2 ; error
EndIf
Repeat
FileSeek(infile, startOff)
bytes = ReadData(infile, *buffer, bufferSize)
; QuickSearch returns the offset in the buffer (bytes),
; or -1 if not found:
offset = QuickSearch(*buffer, bytes, *find, findSize)
If offset <> -1 ; found
offset + startOff
Break
EndIf
startOff + move
Until bytes < bufferSize
FreeMemory(*buffer)
ProcedureReturn offset
EndProcedure
Procedure GetComments(FileName.s,List Comments.tag())
Define SearchedFile.i, SearchFor.s,format.i, numBytes.i, *searchBuffer, found.q
Define TempString.s,TagStart.i,iLoop.i,TagLength.i
Define HeaderNumber.i,StartPos.i
SearchFor = "OggS"
ClearList(Comments())
AddElement(Comments())
SearchedFile = ReadFile(#PB_Any, FileName)
If SearchedFile
format = #PB_UTF8
numBytes = StringByteLength(SearchFor, format)
*searchBuffer = AllocateMemory(numBytes+2)
If *searchBuffer
PokeS(*searchBuffer, SearchFor, -1, format)
StartPos = 0
While HeaderNumber < 2
found = FindInFile(SearchedFile, *searchBuffer, numBytes,StartPos)
Select found
Case -2
Debug "Error."
Case -1
Debug "'" + search$ + "' not found in file '" + file$ + "'."
Default
HeaderNumber = HeaderNumber + 1
StartPos = Found + 4 ;Add On Bytes for "OggS"
EndSelect
Wend
FreeMemory(*searchBuffer)
;Now Find "vorbis"
SearchFor = "vorbis"
numBytes = StringByteLength(SearchFor, format)
*searchBuffer = AllocateMemory(numBytes+2)
If *searchBuffer
PokeS(*searchBuffer, SearchFor, -1, format)
found = FindInFile(SearchedFile, *searchBuffer, numBytes,StartPos)
Select found
Case -2
Debug "Error."
Case -1
Debug "'" + SearchFor + "' not found in file '" + file$ + "'."
Default
StartPos = found + 6 ;Add On Bytes for "vorbis"
FileSeek(SearchedFile,StartPos)
;Get Vendor String
TagLength = ReadLong(SearchedFile) ;Vendor String Length
;Debug "Vendor String " + ReadString(SearchedFile,#PB_UTF8,TagLength) ;Vendor String
Comments()\Title = "Vendor String"
Comments()\Value = ReadString(SearchedFile,#PB_UTF8,TagLength)
;Get Number Of Comments
TagStart = ReadLong(SearchedFile)
;Debug "Number Of Comments " + Hex(TagStart)
;Loop To Get Each Comment(Tag)
For iLoop = 0 To TagStart - 1
TagLength = ReadLong(SearchedFile)
AddElement(Comments())
TempString = ReadString(SearchedFile,#PB_UTF8,TagLength)
Comments()\Title = StringField(TempString,1,"=")
Comments()\Value = StringField(TempString,2,"=")
Next iLoop
EndSelect
EndIf
FreeMemory(*searchBuffer)
Else
Debug "Error allocating memory for search string."
EndIf
CloseFile(SearchedFile)
Else
Debug "Error reading from file '" + file$ + "'."
EndIf
EndProcedure
EndModule
Global NewList SongTags.oggTags::tag()
Global SongFile.s
SongFile = OpenFileRequester("Please choose file to load", "", "Songs (*.ogg)|*.ogg", 0)
If SongFile
oggTags::getComments(SongFile,SongTags())
Else
MessageRequester("Information", "Operation canceled.", 0)
EndIf
ForEach SongTags()
Debug SongTags()\Title
Debug SongTags()\Value
Next
regards
CD