function mov = aviread(filename,varargin)
%AVIREAD Read AVI file. 
%   MOV = AVIREAD(FILENAME) reads the AVI movie FILENAME into the
%   MATLAB movie structure MOV. If FILENAME does not include an
%   extension, then '.avi' will be used. MOV has two fields, "cdata"
%   and "colormap". If the frames are truecolor images, MOV.cdata will
%   be Height-by-Width-by-3 and MOV.colormap will be empty. If the
%   frames are indexed images, then MOV.cdata field will be
%   Height-by-Width and MOV.colormap will be M-by-3. On UNIX, FILENAME
%   must be an uncompressed AVI file.
%  
%   MOV = AVIREAD(...,INDEX) reads only the frame(s) specified by
%   INDEX.  INDEX can be a single index or an array of indices into the
%   video stream, where the first frame is number one.
%   
%   See also AVIINFO, AVIFILE.

%   Copyright 1984-2000 The MathWorks, Inc.
%   $Revision: 1.2 $  $Date: 2000/03/10 22:05:04 $

% Initialization
map = [];
index = -10000;

% Validate input/output. 
error(nargoutchk(1,1,nargout));
error(nargchk(1,2,nargin));

if nargin == 2
  if isnumeric(varargin{1})
    index = varargin{1};
  else
    error('INDEX must be numeric.');
  end
end

if ~isstr(filename)
  error('The filename must be a string.');
end

[path,name,ext] = fileparts(filename);
if isempty(ext)
  filename = strcat(filename,'.avi');
end

info = aviinfo(filename,'Robust');

if (index(1) ~= -10000)
  if ( isfield(info.MainHeader,'HasIndex') == 0 )
    error(sprintf('%s does not support the ''Index'' parameter.',filename));
  elseif any(index > info.MainHeader.TotalFrames)
    error(sprintf('Index value exceeds, %d, the total number of movie frames in this AVI movie.',info.MainHeader.TotalFrames));
  elseif any(index <= 0)
    error('Index value must be greater than zero.');
  end
end

if ispc
  i=1;
  if index == -10000
    X = readavi(filename,-1);
  else   
    X = readavi(filename,index-1);
  end 

  % The width of the  frames may be padded to be on 4 byte boundries because
  % this is how bitmaps are typically written.  However, AVI files do not
  % restrict the frames to lie on 4 byte boundries.  Because of
  % inconsistencies with the header information in AVI files, the READAVI
  % MEX-function determines if the frame is padded and reads the correct
  % number of bytes for each frame.  If the returned data is more than
  % expected (different for indexed and RGB) then the data is padded.  The
  % padding is removed before frames are returned by AVIREAD.
  
  width = info.VideoFrameHeader.Width;
  if ((info.VideoFrameHeader.Width*info.VideoFrameHeader.Height ~= length(X(1).cdata)) & (info.VideoFrameHeader.BitDepth == 8)) | ((info.VideoFrameHeader.Width*info.VideoFrameHeader.Height*3 ~= length(X(1).cdata)) & (info.VideoFrameHeader.BitDepth == 24) )
    paddedWidth = 4*ceil(width/4);
  else
    paddedWidth = width;
  end
  height = info.VideoFrameHeader.Height;
  
  if info.VideoFrameHeader.BitDepth == 8
    % Indexed frames
    for i=1:length(X)
      mov(i).cdata = rot90(reshape(X(i).cdata, paddedWidth, height));
      if paddedWidth ~= width
	mov(i).cdata = mov(i).cdata(:,1:width);
      end
      map = reshape(X(i).colormap, 4, ...
		    info.VideoFrameHeader.NumColormapEntries);
      map = double(flipud(map(1:3,:))')/255;
      mov(i).colormap = map;
    end
  elseif info.VideoFrameHeader.BitDepth == 24
    % Truecolor frames
    for i=1:length(X)
      f = X(i).cdata;
      f = rot90(reshape(f, paddedWidth*3,height));
      if paddedWidth ~= width
	f = f(:,1:width*3);
      end
      RGB(1:height, 1:width,3) = f(:,1:3:end);
      RGB(:, :, 2) = f(:,2:3:end);
      RGB(:, :, 1) = f(:,3:3:end);
      mov(i).cdata = RGB;
      mov(i).colormap = [];
    end
  else
    error('The AVI file must be 8-bit Indexed images or 24-bit TrueColor images');
  end 
end %End of PC specific code

if isunix

  if isempty(strmatch(lower(info.VideoFrameHeader.CompressionType),...
		      strvcat('dib ', 'raw ','none','raw ',char([0 0 0 0]))))
    error('Only uncompressed AVI movies can be read on UNIX.');
  end
  
  if strcmp(lower(info.VideoFrameHeader.CompressionType),char([0 0 0 0]))
    info.VideoFrameHeader.CompressionType = 'none';
  end
  
  fid = fopen(filename,'r','l');
  if fid == -1
    error(sprintf('Unable to open %s.', filename));
  end

  % Find RIFF chunk
  [chunk, msg] = findchunk(fid,'RIFF');
  errorWithFileClose(msg,fid);

  % Read AVI chunk
  [rifftype,msg] = readfourcc(fid);
  errorWithFileClose(msg,fid);
  if ( strcmp(rifftype,'AVI ') == 0 )
    error('Not a valid AVI file. Missing ''AVI '' chunk.');
  end

  % Find hdrl LIST chunk
  [hdrlsize, msg] = findlist(fid,'hdrl');
  errorWithFileClose(msg,fid);

  % Find and skip avih chunk
  [chunk,msg] = findchunk(fid,'avih');
  errorWithFileClose(msg,fid);
  msg = skipchunk(fid,chunk);
  errorWithFileClose(msg,fid);

  % Find the video stream
  for  i = 1:info.MainHeader.NumStreams
    % Find strl LIST chunk
    [strlsize,msg] = findlist(fid,'strl');
    errorWithFileClose(msg,fid);
    % Read strh chunk
    [strhchunk, msg] = findchunk(fid,'strh');
    errorWithFileClose(msg,fid);
    % Determine stream type
    streamType = readfourcc(fid);
    % Break if it is a video stream
    if(strcmp(streamType,'vids'))
      found = 1;
      break;
    else
      found  = 0;
      % Seek to end of strl list minus the amount read
      if ( fseek(fid,listsize - 16,0) == -1 ) 
	error('Incorrect chunk size information in AVI file.');
      end                              
    end
  end
  
  if (found == 0)
    error('Unable to locate video stream.');
  end

  % Skip the strh chunk minus the fourcc (4 bytes) already read.
  strhchunk.cksize = strhchunk.cksize - 4;
  msg = skipchunk(fid,strhchunk);
  errorWithFileClose(msg,fid);

  % Read strf chunk
  [strfchunk, msg] = findchunk(fid,'strf');
  errorWithFileClose(msg,fid);

  if info.VideoFrameHeader.BitDepth == 24
    % For TrueColor images, skip the Stream Format chunk
    msg = skipchunk(fid,strfchunk);
    errorWithFileClose(msg,fid);
  elseif  info.VideoFrameHeader.BitDepth == 8 
    % If bitmap has a palette Seek past the BITMAPINFOHEADER to put the
    % file pointer at the begining of the colormap
    if  fseek(fid,info.VideoFrameHeader.BitmapHeaderSize,0) == -1       
      error('Incorrect BITMAPINFOHEADER size information in AVI file.');
    end 
    map = readColormap(filename,fid,info.VideoFrameHeader.NumColorsUsed); 
    % readColormap does not move the file pointer so FSEEK past the colormap.
    if fseek(fid,info.VideoFrameHeader.NumColorsUsed*4,0) == -1
      error('Incorrect BITMAPINFOHEADER size information in AVI file.');
    end
  else
    error('Bitmap data must be 8-bit Index images or 24-bit TrueColor images');
  end

  % Search for the movi LIST
  [movisize,msg] = findlist(fid,'movi');
  errorWithFileClose(msg,fid);
  % movioffset will be used when using idx1. The offsets stored in idx1 are
  % with respect to just after the 'movi' LIST (not including 'movi')
  movioffset = ftell(fid) -4;

  %Determine method of reading movie
  if ( index ~= -10000 )
    method = 'UseIndex';
  elseif ( isfield(info,'IsInterleaved') == 1 )
    method = 'Interleaved';
  else
    method = 'Normal';
  end

  totalReadFrames = 1;			    
  switch method
   case 'Interleaved'
    % Read movies that are interleaved (contain rec lists)
    while(totalReadFrames < info.MainHeader.TotalFrames )
      [recsize, msg] = findlist(fid,'rec ');
      currentPos = ftell(fid);
      % The recListPos is the current position plus the rec list size minus
      % 4 bytes because we read the four character LIST name in findlist
      recListEndPos = currentPos + recsize - 4;
      chunk = readchunk(fid);
      while(~strcmp(chunk.ckid,'00db') & ~strcmp(chunk.ckid,'00dc'))
	msg = skipchunk(fid,chunk);
	errorWithFileClose(msg,fid);
	if (ftell(fid)==recListEndPos)
	  [recsize, msg] = findlist(fid,'rec ');
	  errorWithFileClose(msg,fid);
	  currentPos = ftell(fid);
	  recListEndPos = currentPos + recsize - 4; % minus 4 because we read
	end
	chunk = readchunk(fid);
      end
      
      % Prepare input for readbmpdata
      tempinfo = info;
      tempinfo.ImageDataOffset = ftell(fid);
      tempinfo.CompressionType = info.VideoFrameHeader.CompressionType;
      tempinfo.BitDepth = info.VideoFrameHeader.BitDepth;

      RGB = readbmpdata(tempinfo);
      % readbmpdata does not move the file pointer
      msg = skipchunk(fid,chunk);
      errorWithFileClose(msg,fid);
      % Assign to MATLAB movie structure
      mov(totalReadFrames).cdata = RGB;
      totalReadFrames = totalReadFrames+1;
    end
   case 'Normal'
    % Find each of the 00db or 00dc chunks and read the frames
    for ( i = 1:info.MainHeader.TotalFrames )
      chunk = readchunk(fid);
      while (~strcmp(chunk.ckid,'00db') & strcmp(chunk.ckid,'00dc') );
	msg = skipchunk(fid,chunk);
	errorWithFileClose(msg,fid);
	chunk = readchunk(fid);
      end
      
      % Prepare data to be sent to readbmpdata
      tempinfo.Filename = info.Filename;
      tempinfo.Width = info.VideoFrameHeader.Width;
      tempinfo.Height = info.VideoFrameHeader.Height;
      tempinfo.ImageDataOffset = ftell(fid);
      tempinfo.CompressionType = 'none';
      tempinfo.BitDepth = info.VideoFrameHeader.BitDepth;
      % Read RGB frame
      RGB = readbmpdata(tempinfo);
      msg = skipchunk(fid,chunk);
      errorWithFileClose(msg,fid);
      % Assign to MATLAB movie structure
      mov(i).cdata = RGB;
    end
   case 'UseIndex' 
    % Skip the movi LIST (minus 4 because 'movi' was read) and use idx1.  
    if ( fseek(fid,movisize-4,0) == -1 )
      error('Incorrect chunk size information in WAV file.');
    end
    % Find idx1 chunk 
    [idx1chunk, msg] = findchunk(fid,'idx1');
    errorWithFileClose(msg,fid);
    idx1ChunkPos = ftell(fid);
    for j = 1:length(index)
      fseek(fid,idx1ChunkPos,'bof');
      for i = 1:index(j)
	found = 0;
	while(found == 0)
	  id = readfourcc(fid);
	  if (id == '00db' | id == '00dc')
	    found = 1;
	  end
	  [idx1data, msg] = readIDX1(fid);
	  errorWithFileClose(msg,fid);
	end
      end
      % Prepare data to be sent to readbmpdata
      tempinfo.Filename = info.Filename;
      tempinfo.Width = info.VideoFrameHeader.Width;
      tempinfo.Height = info.VideoFrameHeader.Height;
      tempinfo.BitDepth = info.VideoFrameHeader.BitDepth;
      tempinfo.ImageDataOffset = movioffset + idx1data.offset + 8;
      tempinfo.CompressionType = info.VideoFrameHeader.CompressionType;
      frame = readbmpdata(tempinfo);
      mov(j).cdata = frame;
    end
  end
  fclose(fid);
  % Formulate outputs
  [mov.colormap] = deal(map);
  varargout{1} = mov;
end
return

function map = readColormap(filename,fid,numColors)
% FREADU8 does not search MATLAB path
filename = which(filename);
% Read colormap for 8-bit indexed images
map = freadu8(filename,ftell(fid),numColors*4);
map = reshape(map,4,numColors);
map = double(flipud(map(1:3,:))')/255;
return;


function [idx1data,msg] = readIDX1(fid)
% Read the data in the idx1 chunk.
msg = '';
[idx1data.flags, count] = fread(fid,1,'uint32');
if ( count ~= 1 )
  msg = 'Incorrect IDX1 chunk size information in AVI file.';
end
[idx1data.offset, count] = fread(fid,1,'uint32');
if ( count ~= 1 )
  msg = 'Incorrect IDX1 chunk size information in AVI file';
end
[idx1data.length, count] = fread(fid,1,'uint32');
if ( count ~= 1 )
  msg = 'Incorrect IDX1 chunk size information in AVI file';
end
return;

function errorWithFileClose(msg,fid)
%Close open file the error
if ~isempty(msg)
  fclose(fid);
  error(msg);
end
return;

