It is currently Tue Oct 22, 2019 4:48 am

All times are UTC + 1 hour




Post new topic Reply to topic  [ 60 posts ]  Go to page 1, 2, 3, 4  Next
Author Message
 Post subject: ogg vorbis comment (tag)
PostPosted: Sun Jul 21, 2019 7:52 am 
Offline
Addict
Addict

Joined: Fri Aug 28, 2015 6:10 pm
Posts: 1026
Location: Portugal
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/p7exmds88ymoo46/OGG%20vorbis%20Comment%20Edit.zip?dl=0

New editor written with batch etc.

_________________
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.


Last edited by collectordave on Wed Aug 28, 2019 3:57 am, edited 3 times in total.

Top
 Profile  
Reply with quote  
 Post subject: Re: ogg vorbis comment (tag)
PostPosted: Tue Jul 23, 2019 12:14 am 
Offline
Addict
Addict

Joined: Fri Aug 28, 2015 6:10 pm
Posts: 1026
Location: Portugal
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:
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

_________________
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.


Top
 Profile  
Reply with quote  
 Post subject: Re: ogg vorbis comment (tag)
PostPosted: Fri Jul 26, 2019 5:29 pm 
Offline
Addict
Addict

Joined: Fri Aug 28, 2015 6:10 pm
Posts: 1026
Location: Portugal
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.

_________________
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.


Top
 Profile  
Reply with quote  
 Post subject: Re: ogg vorbis comment (tag)
PostPosted: Sat Jul 27, 2019 5:53 am 
Offline
PureBasic Expert
PureBasic Expert

Joined: Sun Aug 08, 2004 5:21 am
Posts: 3490
Location: Netherlands
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 ?

_________________
macOS 10.15 Catalina, PB 5.71 x64


Top
 Profile  
Reply with quote  
 Post subject: Re: ogg vorbis comment (tag)
PostPosted: Sun Jul 28, 2019 7:37 am 
Offline
Addict
Addict

Joined: Fri Aug 28, 2015 6:10 pm
Posts: 1026
Location: Portugal
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

_________________
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.


Top
 Profile  
Reply with quote  
 Post subject: Re: ogg vorbis comment (tag)
PostPosted: Sun Jul 28, 2019 9:55 am 
Offline
Addict
Addict

Joined: Fri Aug 28, 2015 6:10 pm
Posts: 1026
Location: Portugal
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

_________________
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.


Top
 Profile  
Reply with quote  
 Post subject: Re: ogg vorbis comment (tag)
PostPosted: Sun Jul 28, 2019 10:55 am 
Offline
PureBasic Expert
PureBasic Expert

Joined: Sun Aug 08, 2004 5:21 am
Posts: 3490
Location: Netherlands
I more or less understand the structure now. :wink:

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.

_________________
macOS 10.15 Catalina, PB 5.71 x64


Top
 Profile  
Reply with quote  
 Post subject: Re: ogg vorbis comment (tag)
PostPosted: Sun Jul 28, 2019 11:48 am 
Offline
Addict
Addict

Joined: Fri Aug 28, 2015 6:10 pm
Posts: 1026
Location: Portugal
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 https://www.purebasic.fr/english/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.


Top
 Profile  
Reply with quote  
 Post subject: Re: ogg vorbis comment (tag)
PostPosted: Sun Jul 28, 2019 12:26 pm 
Offline
Addict
Addict

Joined: Fri Aug 28, 2015 6:10 pm
Posts: 1026
Location: Portugal
To help see streams in an ogg file cobbled this together:-

Code:
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


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

_________________
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.


Top
 Profile  
Reply with quote  
 Post subject: Re: ogg vorbis comment (tag)
PostPosted: Mon Jul 29, 2019 9:32 am 
Offline
PureBasic Expert
PureBasic Expert

Joined: Sun Aug 08, 2004 5:21 am
Posts: 3490
Location: Netherlands
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.

_________________
macOS 10.15 Catalina, PB 5.71 x64


Top
 Profile  
Reply with quote  
 Post subject: Re: ogg vorbis comment (tag)
PostPosted: Mon Jul 29, 2019 10:14 am 
Offline
Addict
Addict

Joined: Fri Aug 28, 2015 6:10 pm
Posts: 1026
Location: Portugal
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/ke4ousu8b4qjmh6/Queen%20-%20Action%20This%20Day.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.


Top
 Profile  
Reply with quote  
 Post subject: Re: ogg vorbis comment (tag)
PostPosted: Mon Jul 29, 2019 10:21 am 
Offline
PureBasic Expert
PureBasic Expert

Joined: Sun Aug 08, 2004 5:21 am
Posts: 3490
Location: Netherlands
collectordave wrote:
According to vorbis it should be either png or jpg.

Interested if you extract the image as I haven't tried yet.

The code below is what I came up with so far.
It does show a Base64 (RFC 4648) encoded METADATA_BLOCK_PICTURE but I haven't tried to decode it yet.

Code:
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

_________________
macOS 10.15 Catalina, PB 5.71 x64


Last edited by wilbert on Tue Jul 30, 2019 4:06 pm, edited 2 times in total.

Top
 Profile  
Reply with quote  
 Post subject: Re: ogg vorbis comment (tag)
PostPosted: Tue Jul 30, 2019 9:29 am 
Offline
Addict
Addict

Joined: Fri Aug 28, 2015 6:10 pm
Posts: 1026
Location: Portugal
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

_________________
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.


Top
 Profile  
Reply with quote  
 Post subject: Re: ogg vorbis comment (tag)
PostPosted: Tue Jul 30, 2019 1:16 pm 
Offline
Addict
Addict

Joined: Fri Aug 28, 2015 6:10 pm
Posts: 1026
Location: Portugal
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.

Code:
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
 


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.


Top
 Profile  
Reply with quote  
 Post subject: Re: ogg vorbis comment (tag)
PostPosted: Tue Jul 30, 2019 2:21 pm 
Offline
PureBasic Expert
PureBasic Expert

Joined: Sun Aug 08, 2004 5:21 am
Posts: 3490
Location: Netherlands
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

Can you explain a bit more what you exactly want ?
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. :?

_________________
macOS 10.15 Catalina, PB 5.71 x64


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 60 posts ]  Go to page 1, 2, 3, 4  Next

All times are UTC + 1 hour


Who is online

Users browsing this forum: No registered users and 13 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
Jump to:  

 


Powered by phpBB © 2008 phpBB Group
subSilver+ theme by Canver Software, sponsor Sanal Modifiye