ogg subtitle stream format (was: Re: [MPlayer-dev-eng] [Patch] ogg stream selection via -aid and -vid)

Alexander Werth (gmx) alexander.werth at gmx.de
Mon May 20 14:00:33 CEST 2002


Hello,
Am Mon, 2002-05-20 um 01.28 schrieb Arpi:
> 
> > btw, what is still missing is a subtitle demuxer.
> 
> do you knwo something about the format they are using?
> spu-like stuff, ascii text or something new?

I attached a delphi file someone sent me. I added a few comments to make
it easier to read.
It does extract subtitles from ogg megia streams.
syntax is:
oggdemux infile.ogm streamid outfile.srt

The good news are that the format is very simple and straight forward.
It's compareable to the ogg vorbis stream format just that it uses
subtitles as payload.
The subtitles itself are uncompressed.

> > -- 
> > OpenOffice 1.0.0  Evolution 1.0.4  Nautilus 1.1.14  Galeon 1.2.1
> > Gimp 1.2.3  Gnome 1.4  mplayer 0.90pre4     We are almost there.
> 
> :)))
> 
> btw, what's missing?
> i'm already "there" since 6 years, and since 1 years i didn't miss a
> single app.
> 
I don't miss one either but I still can't recommend a linux system for a
typical office environment. But things are comming together and once all
the above programms are sufficently integrated it's worth a try.
(Even so this isn't the topic of the mail.)

-- 
OpenOffice 1.0.0  Evolution 1.0.4  Nautilus 1.1.14  Galeon 1.2.1
Gimp 1.2.3  Gnome 1.4  mplayer 0.90pre4     We are almost there.
-------------- next part --------------
program Project1;

{$APPTYPE CONSOLE}

{* This is a quick and dirty hack to demonstrate subtitles*}
{* It uses information from http://www.xiph.org/ogg/vorbis/doc/framing.html *}
{* http://tobias.everwicked.com/prog.htm *}
{* And a lot of guessing *}

uses
SysUtils,classes,strutils;

var memo1,memo2:TStringList;
fs:TFileStream;
buffer:array[0..65535] of byte;
   counter,c2,c3,c4,lnum,tnum,tnum1,dnum:integer;
   tt,p1,p2:string;
label blah;
begin
   memo1:=TStringList.Create;
   memo2:=TStringList.Create;

{* open file and read in overlapping 60000 byte chunks *}
   fs:=TFileStream.Create(ParamStr(1),fmopenread);
   c2:=fs.Size div 60000;
   for counter:=0 to c2 do
   begin
      fs.Read(buffer,65536);
{* search a buffer *}
      for c3:=0 to 60000 do
      begin
         {* Search for OggS with the right Stream ID (paramstr(2) and stream_structure_version *}
{* stream serial number

Ogg allows for seperate logical bitstreams to be mixed at page granularity in a physical bitstream. The most common case would be
sequential arrangement, but it is possible to interleave pages for two seperate bitstreams to be decoded concurrently. The serial
number is the means by which pages physical pages are associated with a particular logical stream. Each logical stream must have a
unique serial number within a physical stream: 


 byte value

 14  0xXX LSB
 15  0xXX
 16  0xXX
 17  0xXX MSB
 *}
       
         {* byte 5:
byte value

  5  bitflags: 0x01: unset = fresh packet
                       set = continued packet
               0x02: unset = not first page of logical bitstream
                       set = first page of logical bitstream (bos)
               0x04: unset = not last page of logical bitstream
                       set = last page of logical bitstream (eos)
          *}
          
         if (buffer[c3] = $4f) and (buffer[c3+1] = $67) and (buffer[c3+2] = $67) and (buffer[c3+3] = $53) and (buffer[c3+4] = $00) and (buffer[c3+14] = strtoint(paramstr(2))) and ((buffer[c3+5] = $00) or (buffer[c3+5] = $04)) then
         begin
            tt:='';

{* Time Position:
  PCM absolute position

(This is packed in the same way the rest of Ogg data is packed; LSb of LSB first. Note that the 'position' data specifies a 'sample'
number (eg, in a CD quality sample is four octets, 16 bits for left and 16 bits for right; in video it would be the frame number). The
position specified is the total samples encoded after including all packets finished on this page (packets begun on this page but
continuing on to thenext page do not count). The rationale here is that the position specified in the frame header of the last page
tells how long the PCM data coded by the bitstream is. A truncated stream will still return the proper number of samples that can
be decoded fully. 

A special value of '-1' (in two's complement) indicates that no packets finish on this page. 


 byte value

  6  0xXX LSB
  7  0xXX
  8  0xXX
  9  0xXX
 10  0xXX
 11  0xXX
 12  0xXX
 13  0xXX MSB *}

            memo2.add(inttostr(buffer[c3+9]*256*256*256+buffer[c3+8]*256*256+buffer[c3+7]*256+buffer[c3+6]));
{* The number of page segments is in byte 0x26 and is ignored *}
{* segment_table (containing packet lacing values)

The lacing values for each packet segment physically appearing in this page are listed in contiguous order. 


 byte value

 27 0x00-0xff (0-255)
 [...]
 n  0x00-0xff (0-255, n=page_segments+26)

Total page size is calculated directly from the known header size and lacing values in the segment table. Packet data segments
follow immediately after the header.
*}
{* So in buffer[c3+27] we find the page size. *}
{* The content of the first three bytes is unknown and probably a subtitle headder. The first was always 88 for me. The ther two vary.*}
            for c4:=3 to buffer[c3+27] do
            begin
               tt:=tt+char(buffer[c3+28+c4]);
            end;
            memo2.text:=memo2.Text+tt;
{* EOS ? *}
            if (buffer[c3+5] = $04) then
            begin
               goto blah;
            end;
         end;
      end;
      fs.Position:=fs.Position-5536;
   end;

blah:
{* done reading sub info *}
     fs.Free;

{* Clean up a little *}
   counter:=0;
   while counter < memo2.Count do
   begin
      if (length(memo2[counter]) < 3) then
      begin
         memo2.Delete(counter);
      end;
      inc(counter);
   end;


{* Unfortunatly the pattern matching at the start does also apply to the stream setup packet. *}
   for counter:=0 to 9 do
      Writeln(memo2[counter]);
   Writeln('How many bad lines are there?');
   Readln(tt);

   c4:=strtoint(tt);

   for counter:=1 to c4 do
      memo2.Delete(0);


   memo2.Add('0');

{* Convert to .srt format *}
   lnum:=0;

   while memo2.Count > 1 do
   begin
      dnum:=1;
      inc(lnum);
      memo1.Add(inttostr(lnum));

      try
      inc(dnum);
      tnum:=strtoint(memo2[2]);
      except on EConvertError do
   begin
      try
      inc(dnum);
      tnum:=strtoint(memo2[3]);
      except on EConvertError do
   begin
      inc(dnum);
      tnum:=strtoint(memo2[4]);
   end;
   end;
   end;
end;

tnum1:=StrToInt(memo2[0]);

{* convert time *}
p1:=IfThen(length(inttostr(tnum1 div 3600000))=1,'0','')+inttostr(tnum1 div 3600000)+':'+IfThen(length(inttostr((tnum1 mod 3600000) div 60000))=1,'0','')+inttostr((tnum1 mod 3600000) div 60000)+':'+ifthen(length(inttostr(((tnum1 mod 3600000) mod 60000) div 1000))=1,'0','')+inttostr(((tnum1 mod 3600000) mod 60000) div 1000)+','+ifthen(length(inttostr(((tnum1 mod 3600000) mod 60000) mod 1000))=1,'00','')+ifthen(length(inttostr(((tnum1 mod 3600000) mod 60000) mod 1000))=2,'0','')+inttostr(((tnum1 mod 3600000) mod 60000) mod 1000);
   p2:=IfThen(length(inttostr(tnum div 3600000))=1,'0','')+inttostr(tnum div 3600000)+':'+IfThen(length(inttostr((tnum mod 3600000) div 60000))=1,'0','')+inttostr((tnum mod 3600000) div 60000)+':'+ifthen(length(inttostr(((tnum mod 3600000) mod 60000) div 1000))=1,'0','')+inttostr(((tnum mod 3600000) mod 60000) div 1000)+','+ifthen(length(inttostr(((tnum mod 3600000) mod 60000) mod 1000))=1,'00','')+ifthen(length(inttostr(((tnum mod 3600000) mod 60000) mod 1000))=2,'0','')+inttostr(((tnum mod 3600000) mod 60000) mod 1000);
   memo1.Add(p1+' --> '+p2);
   memo1.Add(memo2[1]);
   if dnum > 2 then memo1.Add(memo2[2]);
   if dnum > 3 then memo1.Add(memo2[3]);

memo1.Add('');

while dnum+1 > 0 do
begin
   dec(dnum);
   memo2.Delete(0);
end;


end;

memo1.SaveToFile(ParamStr(3));

memo1.Free;
   memo2.Free;

end.


More information about the MPlayer-dev-eng mailing list