{FLV file format}
unit fmt_FLV;
{$include compilers.inc}

interface
uses wat_api;

function ReadFLV(var Info:tSongInfo):boolean; cdecl;

implementation
uses windows,common,io,srv_format;

type
  tFLVHeader = packed record
    Signature:array [0..2] of AnsiChar; // FLV
    Version  :byte;
    flags    :byte;
    Offset   :dword; // reversed byte order
  end;
type
  tFLVStream = packed record
    PreviousTagSize  :dword;
    TagType          :byte;
    BodyLength       :array [0..2] of byte;
    Timestamp        :array [0..2] of byte;
    TimestampExtended:byte;
    StreamID         :array [0..2] of byte;
//    Body
  end;
{
  twork = record
    endptr:PAnsiChar;
    Info  :pSongInfo;
    key   :PAnsiChar;
    len   :cardinal;
  end;
}
//  FLVTagTypes
const
  FLV_AUDIO = 8;
  FLV_VIDEO = 9;
  FLV_META  = 18;

const
  BufSize = 128*1024;

type
  pArr = ^tArr;
  tArr = array [0..7] of byte;

  transform=packed record
    case byte of
      0: (txt:array [0..3] of AnsiChar);
      1: (num:dword);
  end;
  trecode=packed record
    case byte of
      0: (i:int64);
      1: (d:double);
  end;

function Reverse(buf:int64;len:integer):int64;
var
  i:integer;
begin
  result:=0;
  for i:=0 to len-1 do
    result:=(result shl 8)+tArr(buf)[i];
end;

function ProcessValue(var ptr:PAnsiChar;key:pAnsiChar;var Info:tSongInfo):integer;
var
  tmp:int64;
  i,len:integer;
  recode:trecode;
  code:integer;
  codec:transform;
  value:array [0..63] of AnsiChar;
begin
  result:=1;
  code:=ord(ptr^); // type of value
  ptr^:=#0;        // finalization for key name
  inc(ptr);        // value data pointer

  case code of // v10.1 specification

    // Numeric, Double (8 bytes)
    0: begin
      move(ptr^,tmp,8);
      recode.i:=Reverse(tmp,8);
      i:=trunc(recode.d);
      if      StrCmp(key,'duration'     )=0 then Info.total :=i
      else if StrCmp(key,'totalduration')=0 then Info.total :=i
      else if StrCmp(key,'width'        )=0 then Info.width :=i
      else if StrCmp(key,'height'       )=0 then Info.height:=i
      else if StrCmp(key,'audiodatarate')=0 then Info.kbps  :=i
      else if StrCmp(key,'framerate'    )=0 then Info.fps   :=trunc(recode.d*100)
      else if StrCmp(key,'audiosize'    )=0 then
      begin
        if Info.kbps=0 then
          Info.kbps:=trunc((recode.d*8)/(Info.total*1000))
      end
      else if StrCmp(key,'videocodecid')=0 then
      begin
        case i of
          2:   codec.txt:='H263';
          3:   codec.txt:='Scrn';
          4,5: codec.txt:='VP6 ';
          6:   codec.txt:='Src2';
          7:   codec.txt:='AVC ';
        end;
        Info.codec:=codec.num;
      end;

      inc(ptr,8);
    end;
  
    // Boolean, UI8
    1: begin
      if StrCmp(key,'stereo')=0 then Info.channels:=ORD(ptr^)+1;

      inc(ptr);
    end;
    
    // String
    2: begin
      i:=Reverse(pWord(ptr)^,2); inc(ptr,2);

      if StrCmp(key,'creationdate')=0 then
      begin
        move(ptr^,value[0],i);
        value[i]:=#0;
        AnsiToWide(value,Info.year);
      end;

      inc(ptr,i);
    end;

    // Object
    3: begin
      repeat
        len:=Reverse(pWord(ptr)^,2); inc(ptr,2); // key name length
        key:=ptr; inc(ptr,len);                  // key name

        result:=ProcessValue(ptr,key,Info);
      until result<=0;

      if result<0 then
        result:=1;
    end;

    // Movie clip, reserved
    4: begin
    end;
  
    // NULL
    5: begin
    end;

    // Undefined
    6: begin
    end;
  
    // reference, UI16
    7: begin
      inc(ptr,2);
    end;
  
    // ECMA array
    8: begin
      i:=pdword(ptr)^; inc(ptr,4);
      i:=Reverse(i,4);
      while i>0 do
      begin

        len:=Reverse(pWord(ptr)^,2); inc(ptr,2); // key name length
        key:=ptr; inc(ptr,len);                  // key name

        result:=ProcessValue(ptr,key,Info);

        if result=0 then break
        else if result<0 then
        begin
          result:=1;
          break;
        end;
        dec(i);
      end;
    end;

    // Object end marker, UI8[3]=0,0,9
    9: begin
      result:=-1;
      inc(ptr,3);
    end;
  
    10: // array, 4 bytes - num of elements, elements
    begin
      i:=pdword(ptr)^; inc(ptr,4);
      i:=Reverse(i,4);
      while i>0 do
      begin
        result:=ProcessValue(ptr,nil,Info);
        if result=0 then exit
        else if result<0 then
        begin
          result:=1;
          break;
        end;
        dec(i);
      end;
    end;

    // Date, Double + UI16 (UTC)
    11: begin
      inc(ptr,8);
      inc(ptr,2);
    end;
  
    // LongString, 4 bytes = len, len - string
    12: begin
      i:=pdword(ptr)^; inc(ptr,4);
      i:=Reverse(i,4);

      inc(ptr,i);
    end;

  end;
end;

function ReadFLV(var Info:tSongInfo):boolean; cdecl;
var
  f:THANDLE;
  codec:transform;
  FLVHdr:tFLVHeader;
  StrmHdr:tFLVStream;
  i,len:integer;
  buf,pp,p,endbuf:PAnsiChar;
begin
  result:=false;
  f:=Reset(Info.mfile);
  if f=THANDLE(INVALID_HANDLE_VALUE) then
    exit;

  mGetMem(buf,BufSize);
  endbuf:=buf+BlockRead(f,buf^,BufSize);
  p:=buf;
  CloseHandle(f);
  move(p^,FLVHdr,SizeOf(tFLVHeader));
  if (FLVHdr.Signature[0]='F') and (FLVHdr.Signature[1]='L') and
     (FLVHdr.Signature[2]='V') and (FLVHdr.Version=1) then
  begin
    inc(p,SizeOf(tFLVHeader));
    result:=true;
    while (p<endbuf) and ((FLVHdr.flags and 5)<>0) do
    begin
      move(p^,StrmHdr,SizeOf(tFLVStream));
      inc(p,SizeOf(tFLVStream));
      len:=(StrmHdr.BodyLength[0] shl 16)+(StrmHdr.BodyLength[1] shl 8)+
            StrmHdr.BodyLength[2];
      pp:=p;
      case StrmHdr.TagType of

        FLV_AUDIO: begin
          Info.channels:=(ord(p^) and 1)+1;
          // samplesize is (S_Byte and 2) shr 1 = 8 or 16
          case (ord(p^) and $C) shr 2 of
            0: Info.khz:=5;
            1: Info.khz:=11;
            2: Info.khz:=22;
            3: Info.khz:=44;
          end;
          FLVHdr.flags:=FLVHdr.flags and not 4;
        end;

        FLV_VIDEO: begin
          case ord(p^) and $0F of
            2:   codec.txt:='H263';
            3:   codec.txt:='Scrn';
            4,5: codec.txt:='VP6 ';
            6:   codec.txt:='Src2';
            7:   codec.txt:='AVC ';
          end;
          Info.codec:=codec.num;
          FLVHdr.flags:=FLVHdr.flags and not 1;
        end;

        FLV_META: begin
          if (StrmHdr.TagType and $40)=0 then // not encripted
          begin
            if pByte(p)^=2 then // string
            begin
              Inc(p);
              i:=Reverse(pWord(p)^,2); inc(p,2);
              if StrCmp(p,'onMetaData',i)=0 then // Metadata processing start
              begin
                inc(p,i);
                ProcessValue(p,nil,Info); // metadata, no need key name, our info
                // checking for video
                if Info.codec<>0 then
                  FLVHdr.flags:=FLVHdr.flags and not 1;
                // checking for audio
                if (Info.khz<>0) and (Info.channels<>0) then
                  FLVHdr.flags:=FLVHdr.flags and not 4;
                // break; // if metainfo is enough
              end;
            end;
          end;
        end;

      end;
      p:=pp+len;
    end;
  end;
  mFreeMem(buf);
end;

var
  LocalFormatLink:twFormat;

procedure InitLink;
begin
  LocalFormatLink.Next:=FormatLink;

  LocalFormatLink.This.proc :=@ReadFLV;
  LocalFormatLink.This.ext  :='FLV';
  LocalFormatLink.This.flags:=WAT_OPT_VIDEO;

  FormatLink:=@LocalFormatLink;
end;

initialization
  InitLink;
end.