ogg vorbis comment (tag)
-
- Addict
- Posts: 1309
- Joined: Fri Aug 28, 2015 6:10 pm
- Location: Portugal
ogg vorbis comment (tag)
Has anyone written anything to read and write vorbis comment tags info to ogg files?
Need cross platform so native PB if poss.
Regards
CD
Written the comment editor now available here. https://www.dropbox.com/s/p7exmds88ymoo ... t.zip?dl=0
New editor written with batch etc.
Need cross platform so native PB if poss.
Regards
CD
Written the comment editor now available here. https://www.dropbox.com/s/p7exmds88ymoo ... t.zip?dl=0
New editor written with batch etc.
Last edited by collectordave on Wed Aug 28, 2019 3:57 am, edited 3 times in total.
Any intelligent fool can make things bigger and more complex. It takes a touch of genius — and a lot of courage to move in the opposite direction.
-
- Addict
- Posts: 1309
- Joined: Fri Aug 28, 2015 6:10 pm
- Location: Portugal
Re: ogg vorbis comment (tag)
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.
regards
CD
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
CD
Any intelligent fool can make things bigger and more complex. It takes a touch of genius — and a lot of courage to move in the opposite direction.
-
- Addict
- Posts: 1309
- Joined: Fri Aug 28, 2015 6:10 pm
- Location: Portugal
Re: ogg vorbis comment (tag)
Answer from another thread.
Quicker than mine!
Getting the idea of what to do writing ogg comments now.
Just need to work out a quick way to:-
1. Copy file up to a point
2. Miss comment section
3. Add my comment page
4. Copy rest of file
Basically copy ogg file to Number of segments, add my comment segment then copy rest of file.
Quicker than mine!
Getting the idea of what to do writing ogg comments now.
Just need to work out a quick way to:-
1. Copy file up to a point
2. Miss comment section
3. Add my comment page
4. Copy rest of file
Basically copy ogg file to Number of segments, add my comment segment then copy rest of file.
Any intelligent fool can make things bigger and more complex. It takes a touch of genius — and a lot of courage to move in the opposite direction.
Re: ogg vorbis comment (tag)
After seeing your posts I was curious about the ogg format.
What's not entirely clear to me is how to handle ogg files with multiple streams inside them.
Do you know if an ogg file can contain multiple vorbis streams ?
I also read a comment header can span multiple pages.
Do you know if the multiple pages from a comment header always follow each other directly when there are multiple streams or can a page from another stream be multiplexed in between them ?
What's not entirely clear to me is how to handle ogg files with multiple streams inside them.
Do you know if an ogg file can contain multiple vorbis streams ?
I also read a comment header can span multiple pages.
Do you know if the multiple pages from a comment header always follow each other directly when there are multiple streams or can a page from another stream be multiplexed in between them ?
Windows (x64)
Raspberry Pi OS (Arm64)
Raspberry Pi OS (Arm64)
-
- Addict
- Posts: 1309
- Joined: Fri Aug 28, 2015 6:10 pm
- Location: Portugal
Re: ogg vorbis comment (tag)
Hi Wilbert
I am no expert but ogg can handle multiple streams.
The biggest problem I have had is the terminology used it differs from one explanation to another so this is my explanation.
An ogg file is split into pages every page begins with "OggS" and a version number byte after (allways 0). Each page ends when another page starts.
The little routine I wrote to look for a pattern in a file returning the position in the file of each occurrence of the pattern returns a list of offsets.
So for example the third offset returned is the start of the third page and is also the end of the second page.
Now each page actually contains two parts, a header and segments.
First the segments. A segment is a block of 0 to 255 bytes. Each page can contain up to 255 segments.
Now the header. The header is of variable length, you will read that the header is fixed at 27 bytes but it also has a segment table after the 27 bytes whose length is determined
by the number of segments used.
It is the 27 bytes you are interested in for the streams. Here is a header structure to explain.
Byte order: Little-endian
Offset Length Contents
[ 0 4 bytes "OggS"
4 1 byte Stream structure version (0x00)
5 1 byte Packet flag:
bit 0: true if page continued
bit 1: true if first page
bit 2: true if last page
bit 3..7: reserved
6 8 bytes The end pcm sample position (64bit integer)
14 4 bytes Stream serial number
18 4 bytes Page number
22 4 bytes Check sum
26 1 byte Number of segments(s)
27 (s)bytes Segment table
27+(s) (b)bytes Body (b := header[27] + header[27+1] + ... + header[27+s-1])
]*
The last line is the varable part depending on the value of byte 26
The interesting parts are the Stream serial number and the page number (I find it easier to call this sequence it counts from 1 to number of pages in stream in increments of 1).
To assemble a stream completely you would have to go through the whole file finding all the pages with the same Stream serial number and then assemble them
in the order dictated by the page number (sequence).
If there is a second stream there will be pages with a different stream serial number.
Now vorbis. As I understand it a vorbis stream is comments followed by stream data comments are not mixed up in the vorbis stream thay are only at the beginning.
So for any vorbis stream find the length of the comments then you have the start of the stream data.
Of course we are talking vorbis in an ogg file here. I can find nothing which guarantees the position of an ogg page so with stream serial and sequence they can be anywhere in any order.
Except page 0 of course. Pages of different streams can then be mixed sorted when read.
Comments can logically span multiple pages this is worked out using the segment table in a page header.
vorbis comments are allways first in a vorbis stream so find the page of the stream you are interested in then calculate the length of the comments section.
Here is a typical segment table
10 D3 FF FF FF FF FF FF FF FF FF FF FF FF FF FF
The first byte is 10 (16) showing 16 segments used in this page. (Confused here seems 16 - 1 are used)
From the 10 you start with zero and read each byte in turn if it is FF add to previous byte(0 at first not 10) if less than FF add to previous and this is the end of this section i.e. comments in this case.
In the example the next byte is D3 which is < FF so comments uses D3 bytes.
The next byte in a vorbis stream is the start of the stream data.
If the whole segment table was FF then you have to move to page 2 in the stream and keep reading to get the comments length.
Hope this helps
CD
I am no expert but ogg can handle multiple streams.
The biggest problem I have had is the terminology used it differs from one explanation to another so this is my explanation.
An ogg file is split into pages every page begins with "OggS" and a version number byte after (allways 0). Each page ends when another page starts.
The little routine I wrote to look for a pattern in a file returning the position in the file of each occurrence of the pattern returns a list of offsets.
So for example the third offset returned is the start of the third page and is also the end of the second page.
Now each page actually contains two parts, a header and segments.
First the segments. A segment is a block of 0 to 255 bytes. Each page can contain up to 255 segments.
Now the header. The header is of variable length, you will read that the header is fixed at 27 bytes but it also has a segment table after the 27 bytes whose length is determined
by the number of segments used.
It is the 27 bytes you are interested in for the streams. Here is a header structure to explain.
Byte order: Little-endian
Offset Length Contents
[ 0 4 bytes "OggS"
4 1 byte Stream structure version (0x00)
5 1 byte Packet flag:
bit 0: true if page continued
bit 1: true if first page
bit 2: true if last page
bit 3..7: reserved
6 8 bytes The end pcm sample position (64bit integer)
14 4 bytes Stream serial number
18 4 bytes Page number
22 4 bytes Check sum
26 1 byte Number of segments(s)
27 (s)bytes Segment table
27+(s) (b)bytes Body (b := header[27] + header[27+1] + ... + header[27+s-1])
]*
The last line is the varable part depending on the value of byte 26
The interesting parts are the Stream serial number and the page number (I find it easier to call this sequence it counts from 1 to number of pages in stream in increments of 1).
To assemble a stream completely you would have to go through the whole file finding all the pages with the same Stream serial number and then assemble them
in the order dictated by the page number (sequence).
If there is a second stream there will be pages with a different stream serial number.
Now vorbis. As I understand it a vorbis stream is comments followed by stream data comments are not mixed up in the vorbis stream thay are only at the beginning.
So for any vorbis stream find the length of the comments then you have the start of the stream data.
Of course we are talking vorbis in an ogg file here. I can find nothing which guarantees the position of an ogg page so with stream serial and sequence they can be anywhere in any order.
Except page 0 of course. Pages of different streams can then be mixed sorted when read.
Comments can logically span multiple pages this is worked out using the segment table in a page header.
vorbis comments are allways first in a vorbis stream so find the page of the stream you are interested in then calculate the length of the comments section.
Here is a typical segment table
10 D3 FF FF FF FF FF FF FF FF FF FF FF FF FF FF
The first byte is 10 (16) showing 16 segments used in this page. (Confused here seems 16 - 1 are used)
From the 10 you start with zero and read each byte in turn if it is FF add to previous byte(0 at first not 10) if less than FF add to previous and this is the end of this section i.e. comments in this case.
In the example the next byte is D3 which is < FF so comments uses D3 bytes.
The next byte in a vorbis stream is the start of the stream data.
If the whole segment table was FF then you have to move to page 2 in the stream and keep reading to get the comments length.
Hope this helps
CD
Any intelligent fool can make things bigger and more complex. It takes a touch of genius — and a lot of courage to move in the opposite direction.
-
- Addict
- Posts: 1309
- Joined: Fri Aug 28, 2015 6:10 pm
- Location: Portugal
Re: ogg vorbis comment (tag)
Trying to make it easier to understand.
When talking about ogg vorbis we are talking about two separate things.
ogg is a container which can hold many types of file. I imagine it as having a piece of paper with writing on it. this can be seen as a standard text file extension .txt I can put this sheet into an envelope, now to read the text I have to open the envelope to get at the piece of paper. The envelope in this case is called .ogg so to read the text file I have to take it out of the ogg.
Confusion starts as vorbis have hogged the ogg envelope (forgive the pun).
You can pack any file into an ogg container.
vorbis is a data compression format especially for audio files I think. You could have a .vorbis file the inside of which would look like
vorbis comments|vorbis bitstream
ogg vorbis is a vorbis bit stream packed into an ogg container.
Ah more mud.
CD
When talking about ogg vorbis we are talking about two separate things.
ogg is a container which can hold many types of file. I imagine it as having a piece of paper with writing on it. this can be seen as a standard text file extension .txt I can put this sheet into an envelope, now to read the text I have to open the envelope to get at the piece of paper. The envelope in this case is called .ogg so to read the text file I have to take it out of the ogg.
Confusion starts as vorbis have hogged the ogg envelope (forgive the pun).
You can pack any file into an ogg container.
vorbis is a data compression format especially for audio files I think. You could have a .vorbis file the inside of which would look like
vorbis comments|vorbis bitstream
ogg vorbis is a vorbis bit stream packed into an ogg container.
Ah more mud.
CD
Any intelligent fool can make things bigger and more complex. It takes a touch of genius — and a lot of courage to move in the opposite direction.
Re: ogg vorbis comment (tag)
I more or less understand the structure now.
I think the biggest problem when editing comments would be handling comment packets that span multiple pages.
Especially if the updated comment packet consists of a different number of pages compared to the original one.
Since the crc also seems to include the page number, you would have to recalculate the crc for all pages were the page number changes.
I think the biggest problem when editing comments would be handling comment packets that span multiple pages.
Especially if the updated comment packet consists of a different number of pages compared to the original one.
Since the crc also seems to include the page number, you would have to recalculate the crc for all pages were the page number changes.
Windows (x64)
Raspberry Pi OS (Arm64)
Raspberry Pi OS (Arm64)
-
- Addict
- Posts: 1309
- Joined: Fri Aug 28, 2015 6:10 pm
- Location: Portugal
Re: ogg vorbis comment (tag)
Oh yes looked at doing that had a small brain overload.
Luckily comments on an audio file rarely exceed 2Kb but I am looking at taking it easy and padding the comments page to get 8Kb of comment space then add the original or edited comments back. Only one page to deal with then. Have to remember that a cover art image can be encoded as a comment so limited size available (resized images not whole hidef images).
The first page of a stream allways seems to be loaded with 15 segments of preamble for the bitstream the comments normally taking up just one or two segments so with 255 segments available in the page padding the comments section to use 32 segments should not be a problem. I cannot see comments taking up more than this in every ogg vorbis file.
I am doing this to extend the ogg player I am writing here viewtopic.php?f=12&t=73239 and have thought about when people want more info than comments can deliver and maybe add a link to a relevant CDDB page as a comment and add a link to a personal music database on the clients machine where they can store as much as they like. Needs to be a balance between what is easily achievable and client expectations.
Most other ogg players will simply ignore these comments some may display them but they will only have meaning in my little player.
CD
Luckily comments on an audio file rarely exceed 2Kb but I am looking at taking it easy and padding the comments page to get 8Kb of comment space then add the original or edited comments back. Only one page to deal with then. Have to remember that a cover art image can be encoded as a comment so limited size available (resized images not whole hidef images).
The first page of a stream allways seems to be loaded with 15 segments of preamble for the bitstream the comments normally taking up just one or two segments so with 255 segments available in the page padding the comments section to use 32 segments should not be a problem. I cannot see comments taking up more than this in every ogg vorbis file.
I am doing this to extend the ogg player I am writing here viewtopic.php?f=12&t=73239 and have thought about when people want more info than comments can deliver and maybe add a link to a relevant CDDB page as a comment and add a link to a personal music database on the clients machine where they can store as much as they like. Needs to be a balance between what is easily achievable and client expectations.
Most other ogg players will simply ignore these comments some may display them but they will only have meaning in my little player.
CD
Any intelligent fool can make things bigger and more complex. It takes a touch of genius — and a lot of courage to move in the opposite direction.
-
- Addict
- Posts: 1309
- Joined: Fri Aug 28, 2015 6:10 pm
- Location: Portugal
Re: ogg vorbis comment (tag)
To help see streams in an ogg file cobbled this together:-
Select an ogg file
debug shows each page found with Stream serial and segments used.
I do not have a multistream ogg file so my serial is allways the same but if you have one then two different serials will show on the page where they are. Not counting number of streams or saving offsets to load each stream.
Cd
Code: Select all
Structure OGG_STRUCT
capture_pattern.l
stream_structure_version.a
header_type_flag.a
granule_position.q
bitstream_serial_number.l
page_sequence_number.l
crc_checksum.l
number_page_segments.a
EndStructure
Global NewList OffSets.l() ;List to hold all positions where serched for string is found
Global StringToFind.s
Procedure FindPatternInFile(FileName.s,*SearchBuffer,SLength.i,List FoundAt.l())
Define *FileBuffer
Define CommentStart.l,CommentEnd.i,CurrentPos.i
Define BufferSize.i,SearchPosition.i
Define DataAmount.i
;BufferSize determines the maximum amount of data to read each time
BufferSize = 10000
;DataAmount is the actual amount of data read from the file at any point
DataAmount = 0
;SearchPosition is the position in the file reached by the search or file pointer
SearchPosition = 0
;create the file buffer
*FileBuffer = AllocateMemory(BufferSize)
;Ensure list of offsets is cleared
ClearList(FoundAt())
;Open The File
If ReadFile(0, FileName)
While Not Eof(0)
;Read the first BufferSize chunk of data. DataRead is less than BufferSize when EOF reached
DataRead = ReadData(0,*FileBuffer,BufferSize)
;Go Through Buffer Looking For String. SLength - 1 is last position to search as looking for whole string in less bytes cannot succeed
For iLoop = 0 To Dataread - (SLength - 1)
;Add iLoop to *FileBuffer address and check for pattern
If CompareMemory(*FileBuffer + iLoop,*SearchBuffer,SLength) = 1 ;If Pattern found
;Add an element to the list
AddElement(FoundAt())
;The offset is iLoop i.e. where in this loop we are at plus the amount of data allready read
FoundAt() = iLoop + DataAmount
EndIf
Next iLoop
If DataRead = BufferSize ;Not End Of File
If SearchPosition = 0 ;If this is the first block to stop negative FileSeek()
SearchPosition = BufferSize - (SLength- 1)
;SLength- 1 just in case string found in last few bytes so next block read will not include whole string
;but will include the whole string if the string crosses the read boundary
Else
;Same as above but totaling all data read
SearchPosition = SearchPosition + (BufferSize - (SLength- 1))
EndIf
;Set DataAmount to be actual amount of dataread for this loop
DataAmount = SearchPosition
;Move the file pointer to the new position
FileSeek(0,SearchPosition)
EndIf
Wend
EndIf
CloseFile(0)
EndProcedure
Procedure ShowStreamSerial(FileName.s)
Define ThisFile.i,PageNumber.i
Protected ogg.OGG_STRUCT
ResetList(OffSets())
;Open The File
If ReadFile(ThisFile, FileName)
ForEach OffSets()
FileSeek(ThisFile,OffSets())
ClearStructure(@ogg,OGG_STRUCT)
If ReadData(handle,@ogg,SizeOf(OGG_STRUCT)) = SizeOf(OGG_STRUCT)
Debug "Page Number = " + Str(PageNumber)
PageNumber = PageNumber + 1
Debug "Stream Serial Number = " + Hex(ogg\bitstream_serial_number)
Debug "Page sequence Number = " + Hex(ogg\page_sequence_number)
Debug "Number of Segments = " + Hex(ogg\number_page_segments)
EndIf
Next
EndIf
EndProcedure
;Searching For Pages
FileToSearch.s = OpenFileRequester("Please choose file to load", "", "Ogg Files (*.ogg)|*.ogg", 0)
If FileToSearch
StringToFind.s = "OggS"
;SLength is the length of the buffer required for the string in a particular format
SLength = StringByteLength(StringToFind,#PB_UTF8) ;Zero string terminator not counted
*SearchBuffer = AllocateMemory(SLength)
PokeS(*SearchBuffer,StringToFind,SLength,#PB_UTF8|#PB_String_NoZero)
;Used to search in a .txt file
FindPatternInFile(FileToSearch,*SearchBuffer,SLength,OffSets())
ShowStreamSerial(FileToSearch)
EndIf
debug shows each page found with Stream serial and segments used.
I do not have a multistream ogg file so my serial is allways the same but if you have one then two different serials will show on the page where they are. Not counting number of streams or saving offsets to load each stream.
Cd
Any intelligent fool can make things bigger and more complex. It takes a touch of genius — and a lot of courage to move in the opposite direction.
Re: ogg vorbis comment (tag)
After some more reading online ...
If I understand correctly, an ogg file can have multiple streams but only 1 vorbis stream at a time.
Chaining however is allowed. So you can have multiple vorbis streams sequentially in one ogg file that should be played one after the other.
Do you have an ogg file with embedded artwork ?
I tried to find one online to test with but can't find one.
If I understand correctly, an ogg file can have multiple streams but only 1 vorbis stream at a time.
Chaining however is allowed. So you can have multiple vorbis streams sequentially in one ogg file that should be played one after the other.
Do you have an ogg file with embedded artwork ?
I tried to find one online to test with but can't find one.
Windows (x64)
Raspberry Pi OS (Arm64)
Raspberry Pi OS (Arm64)
-
- Addict
- Posts: 1309
- Joined: Fri Aug 28, 2015 6:10 pm
- Location: Portugal
Re: ogg vorbis comment (tag)
Read that as well I think it is a vorbis restriction not an ogg restriction.
I Have one with cover art here https://www.dropbox.com/s/ke4ousu8b4qjm ... y.ogg?dl=0
According to vorbis it should be either png or jpg.
Interested if you extract the image as I haven't tried yet.
I Have one with cover art here https://www.dropbox.com/s/ke4ousu8b4qjm ... y.ogg?dl=0
According to vorbis it should be either png or jpg.
Interested if you extract the image as I haven't tried yet.
Any intelligent fool can make things bigger and more complex. It takes a touch of genius — and a lot of courage to move in the opposite direction.
Re: ogg vorbis comment (tag)
The code below is what I came up with so far.collectordave wrote:According to vorbis it should be either png or jpg.
Interested if you extract the image as I haven't tried yet.
It does show a Base64 (RFC 4648) encoded METADATA_BLOCK_PICTURE but I haven't tried to decode it yet.
Code: Select all
EnableExplicit
; >> Structures <<
Structure OGGPage
CapturePattern.l
Version.a
HeaderType.a
GranulePosition.q
BitstreamSerialNumber.l
PageSequenceNumber.l
Checksum.l
PageSegments.a
SegmentTable.a[255]
EndStructure
Structure VorbisIdentification
PacketType.a
Identifier.a[6]
VorbisVersion.l
AudioChannnels.a
AudioSampleRate.l
BitrateMaximum.l
BitrateNominal.l
BitrateMinimum.l
BlockSize01.a
FramingFlag.a
EndStructure
Structure VorbisStream
BitstreamSerialNumber.l
AudioChannels.l
AudioSampleRate.l
Duration.l
Vendor.s
List Comment.s()
EndStructure
; >> ParseOGG procedure <<
Procedure ParseOGG(Filename.s, List VorbisStreams.VorbisStream())
Protected OGGFile.i, NextPageLoc.q, Page.OGGPage, Id.VorbisIdentification
Protected *Mem.Long, *CommentPacket, BitstreamSerialNumber.l
Protected.i i, NextPage, LastSegment, BytesToRead, PacketSize, Length
OGGFile = ReadFile(#PB_Any, Filename)
If OGGFile
ClearList(VorbisStreams())
; >> Process pages <<
While ReadData(OGGFile, @Page, 27) = 27 And
Page\CapturePattern = $5367674F And
ReadData(OGGFile, @Page + 27, Page\PageSegments) = Page\PageSegments
; >> Calculate file position of next page <<
LastSegment = Page\PageSegments - 1
NextPageLoc = Loc(OGGFile)
For i = 0 To LastSegment
NextPageLoc + Page\SegmentTable[i]
Next
; >> Check for new vorbis stream <<
If Page\HeaderType & 2 And Page\SegmentTable[0] = 30
If ReadData(OGGFile, @Id, 30) = 30 And PeekS(@Id\Identifier, 6, #PB_Ascii) = "vorbis"
; Beginning of vorbis stream
BitstreamSerialNumber = Page\BitstreamSerialNumber
PacketSize = 0
NextPage = 1
; Add new vorbis stream to list
AddElement(VorbisStreams())
VorbisStreams()\BitstreamSerialNumber = BitstreamSerialNumber
VorbisStreams()\AudioChannels = Id\AudioChannnels
VorbisStreams()\AudioSampleRate = Id\AudioSampleRate
EndIf
EndIf
If BitstreamSerialNumber = Page\BitstreamSerialNumber
; Set duration when end of stream is reached
If Page\HeaderType & 4
VorbisStreams()\Duration = Page\GranulePosition / Id\AudioSampleRate
EndIf
; Process a comment page
If Page\PageSequenceNumber = NextPage
; Calculate how many bytes to read
BytesToRead = 0
For i = 0 To LastSegment
If Page\SegmentTable[i] = 255
BytesToRead + 255
Else
BytesToRead + Page\SegmentTable[i]
NextPage = 0; No more comment pages
Break
EndIf
Next
; Reallocate memory for comment packet
*Mem = ReAllocateMemory(*CommentPacket, PacketSize + BytesToRead, #PB_Memory_NoClear)
If *Mem
*CommentPacket = *Mem
; Read bytes
If ReadData(OGGFile, *CommentPacket + PacketSize, BytesToRead) = BytesToRead
PacketSize + BytesToRead
If NextPage
NextPage + 1
Else
; Done reading comment packet
*Mem + 7
Length = *Mem\l : *Mem + 4
; Set vendor
VorbisStreams()\Vendor = PeekS(*Mem, Length, #PB_UTF8) : *Mem + Length
; Clear the comments list
ClearList(VorbisStreams()\Comment())
i = *Mem\l : *Mem + 4
; Loop through comments and add to list
While i
AddElement(VorbisStreams()\Comment())
Length = *Mem\l : *Mem + 4
VorbisStreams()\Comment() = PeekS(*Mem, Length, #PB_UTF8) : *Mem + Length
i - 1
Wend
EndIf
EndIf
EndIf
EndIf
EndIf
; Set the file pointer to the next page
FileSeek(OGGFile, NextPageLoc)
Wend
If *CommentPacket
; Free allocated memory
FreeMemory(*CommentPacket)
EndIf
CloseFile(OGGFile)
EndIf
EndProcedure
; Test the code
NewList VorbisStreams.VorbisStream()
ParseOGG("Queen - Action This Day.ogg", VorbisStreams())
ForEach VorbisStreams()
Debug "Vorbis stream: " + Str(VorbisStreams()\BitstreamSerialNumber)
Debug "Channels: " + Str(VorbisStreams()\AudioChannels)
Debug "Sample rate: " + Str(VorbisStreams()\AudioSampleRate)
Debug "Duration: " + Str(VorbisStreams()\Duration)
Debug "Vendor: " + VorbisStreams()\Vendor
Debug "Comments: "
ForEach VorbisStreams()\Comment()
Debug VorbisStreams()\Comment()
Next
Next
Last edited by wilbert on Tue Jul 30, 2019 4:06 pm, edited 2 times in total.
Windows (x64)
Raspberry Pi OS (Arm64)
Raspberry Pi OS (Arm64)
-
- Addict
- Posts: 1309
- Joined: Fri Aug 28, 2015 6:10 pm
- Location: Portugal
Re: ogg vorbis comment (tag)
Genius!
Adapting bits to get what I think I want.
If I am reading the vorbis spec correctly there are three required vorbis (not ogg) headers each beginning with 'vorbis' the last of these finishes the ogg page on which it resides with the audio data begining on the next ogg page.
The file I sent you does indeed have comments spanning two pages so with a variable number of pages depending on the length of the comments I will have redo all pages in the ogg file if the number of pages changes Argh!
Thanks wilbert great
Adapting bits to get what I think I want.
If I am reading the vorbis spec correctly there are three required vorbis (not ogg) headers each beginning with 'vorbis' the last of these finishes the ogg page on which it resides with the audio data begining on the next ogg page.
The file I sent you does indeed have comments spanning two pages so with a variable number of pages depending on the length of the comments I will have redo all pages in the ogg file if the number of pages changes Argh!
Thanks wilbert great
Any intelligent fool can make things bigger and more complex. It takes a touch of genius — and a lot of courage to move in the opposite direction.
-
- Addict
- Posts: 1309
- Joined: Fri Aug 28, 2015 6:10 pm
- Location: Portugal
Re: ogg vorbis comment (tag)
Going out my head!
With the help of wilberts code above and numerous other hints and tips managed to cobble the following together.
The idea is that a vorbis stream in an ogg file can be extracted. It has four sections in total.
1. An Identity header
2. A comments header
3. A header for the codec
4. The audio stream
Each of the first three start with a single byte identity then 'vorbis', the identity bytes are 01 03 and 05
The fourth just gets on with it however in section four each section is significant. It starts on the next ogg page after section 3 ends.
I cannot get the idea of saving each section to a buffer for checking. wilbert did it with the comments header in his code.
My programme outputs
Page Number = 0
Bytes To read = 1E
New Section
For each section read. Where a section spans a page no new page number will be shown. So the bytes to read maybe a collection of bytes from a previous page or pages.
Any help appreciated.
CD
With the help of wilberts code above and numerous other hints and tips managed to cobble the following together.
The idea is that a vorbis stream in an ogg file can be extracted. It has four sections in total.
1. An Identity header
2. A comments header
3. A header for the codec
4. The audio stream
Each of the first three start with a single byte identity then 'vorbis', the identity bytes are 01 03 and 05
The fourth just gets on with it however in section four each section is significant. It starts on the next ogg page after section 3 ends.
I cannot get the idea of saving each section to a buffer for checking. wilbert did it with the comments header in his code.
My programme outputs
Page Number = 0
Bytes To read = 1E
New Section
For each section read. Where a section spans a page no new page number will be shown. So the bytes to read maybe a collection of bytes from a previous page or pages.
Any help appreciated.
Code: Select all
EnableExplicit
; >> Structures <<
Structure OGGPage
CapturePattern.l
Version.a
HeaderType.a
GranulePosition.q
BitstreamSerialNumber.l
PageSequenceNumber.l
Checksum.l
PageSegments.a
SegmentTable.a[255]
EndStructure
Structure VorbisIdentification
PacketType.a
Identifier.a[6]
VorbisVersion.l
AudioChannnels.a
AudioSampleRate.l
BitrateMaximum.l
BitrateNominal.l
BitrateMinimum.l
BlockSize01.a
FramingFlag.a
EndStructure
Structure VorbisStream
BitstreamSerialNumber.l
AudioChannels.l
AudioSampleRate.l
Vendor.s
List Comment.s()
EndStructure
Structure Section
Page.l
Boundary.s
NumBytes.i
EndStructure
Global NewList BytesPerSec.Section()
Global PageNo.i
Global OGGFile.i, NextPageLoc.q, Page.OGGPage, Id.VorbisIdentification
Global *Mem.Long, *CommentPacket, BitstreamSerialNumber.l
Global.i i, NextPage, LastSegment, BytesToRead, PacketSize, Length
OGGFile = ReadFile(#PB_Any, "C:\CD Media\Queen\Queen - Action This Day.ogg")
If OGGFile
; ClearList(VorbisStreams())
PageNo = 0
; >> Process pages <<
While ReadData(OGGFile, @Page, 27) = 27 And
Page\CapturePattern = $5367674F And
ReadData(OGGFile, @Page + 27, Page\PageSegments) = Page\PageSegments
PageNo + 1
; >> Calculate file position of next page <<
LastSegment = Page\PageSegments - 1
NextPageLoc = Loc(OGGFile)
For i = 0 To LastSegment
NextPageLoc + Page\SegmentTable[i]
Next
;Debug NextPageLoc
; Calculate how many bytes to read
BytesToRead = 0
For i = 0 To LastSegment
If Page\SegmentTable[i] = 255
BytesToRead + 255
Else
BytesToRead + Page\SegmentTable[i]
AddElement(BytesPerSec())
BytesPerSec()\Page = Page\PageSequenceNumber
BytesPerSec()\NumBytes = BytesToRead
BytesPerSec()\Boundary = "New Section "
;No more For This Section
;Ready For next section
BytesToRead = 0
EndIf
Next
FileSeek(OGGFile,NextPageLoc)
Wend
EndIf
ForEach BytesPerSec()
Debug "Page Number = " + Str(BytesPerSec()\Page)
Debug "Bytes To read = " + Hex(BytesPerSec()\NumBytes)
Debug BytesPerSec()\Boundary
Debug ""
Next
Any intelligent fool can make things bigger and more complex. It takes a touch of genius — and a lot of courage to move in the opposite direction.
Re: ogg vorbis comment (tag)
Can you explain a bit more what you exactly want ?collectordave wrote:The idea is that a vorbis stream in an ogg file can be extracted. It has four sections in total.
1. An Identity header
2. A comments header
3. A header for the codec
4. The audio stream
Extracting a stream if the ogg file has multiple streams should be as simple as extracting all pages with the same bitstream serial number.
For your initial idea of editing a comment tag, this shouldn't be required.
Editing the comments header and updating the page number of all following pages should be sufficient.
Windows (x64)
Raspberry Pi OS (Arm64)
Raspberry Pi OS (Arm64)