#------------------------------------------------------------------------
#
# Copyright (c) 1997-1998 by Cornell University.
# 
# See the file "license.txt" for information on usage and redistribution
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
#
#------------------------------------------------------------------------
package require DvmBasic
package require DvmMpeg
package require DvmPnm
package require DvmColor
source  makeindex.tcl

#---------------------------------------------------------------
# Get inputs from user or command line arguments
#---------------------------------------------------------------
if {$argc < 3} {
    puts "enter the MPEG file to extract from: "
    set infile [gets stdin]
    puts "enter start frame: "
    set start [gets stdin]
    puts "enter end frame: "
    set end [gets stdin]
} else {
    set infile [lindex $argv 0]
    set start [lindex $argv 1]
    set end [lindex $argv 2]
}
if {$end < $start} {
    error "end cannot be less than start!"
}


#---------------------------------------------------------------
# proc swap
# 
# swap the value of two pointers.
#---------------------------------------------------------------
proc swap {a b} {
    upvar $a aa
    upvar $b bc
    set temp $aa
    set aa $bc
    set bc $temp
}

#---------------------------------------------------------------
# proc write_pgm
#
# This procedure encode a byte image into a bitstream bs,
# using bitparser bp, and output it to a tcl channel called
# name.  Assumes that the header is already encoded in the 
# bitstream.  (This is an improvement over the routines in 
# pnmlib.tcl since it reuse the same header and bitstream)
#---------------------------------------------------------------
proc write_pgm {y bsmpeg bpmpeg name} {
    set chan [open $name w]
    fconfigure $chan -translation binary -buffersize 65536
    set curr [bitparser_tell $bpmpeg]
    pgm_encode $y $bpmpeg
    bitstream_channel_write $bsmpeg $chan 0
    bitparser_seek $bpmpeg $curr
    close $chan
}

#----------------------------------------------------------------
# open file, create new bitparser, new bitstream, mmap entire file
# to bitstream and attach the bitparser to the bitstream 
#----------------------------------------------------------------
set bpmpeg [bitparser_new]
set bsmpeg [bitstream_mmap_read_new $infile]
set inname [lindex $infile 0]
bitparser_wrap $bpmpeg $bsmpeg

#----------------------------------------------------------------
# Call make_mpeg_video_index (defined in makeindex.tcl) 
# to creates an index to the video.  
#----------------------------------------------------------------

set in   [make_mpeg_video_index $bpmpeg]

#----------------------------------------------------------------
# parse the mpeg sequence header and find out the dimension and
# size of the frames.  Care must be taken if frame dimenion is 
# not multiple of 16
#----------------------------------------------------------------
set sh [mpeg_seq_hdr_new]
mpeg_seq_hdr_find $bpmpeg
mpeg_seq_hdr_parse $bpmpeg $sh
set seqw   [mpeg_seq_hdr_get_width $sh]
set seqh   [mpeg_seq_hdr_get_height $sh]
set picSize [mpeg_seq_hdr_get_buffer_size $sh]
set remw [expr $seqw % 16]
set remh [expr $seqh % 16]
if {$remw != 0} { 
    set w [expr $seqw + 16 - $remw]
} else {
    set w $seqw
}
if {$remh != 0} { 
    set h [expr $seqh + 16 - $remh]
} else {
    set h $seqh
}
set halfw [expr $w/2]
set halfh [expr $h/2]

#----------------------------------------------------------------
# allocate a bunch of byte buffer, sc buffer, mv buffer,
# mpeg_pic_hdr
#----------------------------------------------------------------
set y       [byte_new $w $h]
set prevy   [byte_new $w $h]
set futurey [byte_new $w $h]
set fwdmv   [vector_new [expr $w/16] [expr $h/16]]
set bwdmv   [vector_new [expr $w/16] [expr $h/16]]
set scy     [sc_new [expr $w/8] [expr $h/8]]
set scu     [sc_new [expr $w/16] [expr $h/16]]
set scv     [sc_new [expr $w/16] [expr $h/16]]
set fh      [mpeg_pic_hdr_new]

#----------------------------------------------------------------
# initialize stuff for pgm file output
# we only need to write the header once.
#----------------------------------------------------------------
set pnmhdr  [pnm_hdr_new]
pnm_hdr_set_type   $pnmhdr "pgm-bin"
pnm_hdr_set_width  $pnmhdr $seqw 
pnm_hdr_set_height $pnmhdr $seqh
pnm_hdr_set_maxval $pnmhdr 255
set outbs   [bitstream_new [expr 3*$seqw*$seqh + 20]]
set outbp   [bitparser_new]
bitparser_wrap $outbp $outbs
pnm_hdr_encode $pnmhdr $outbp
pnm_hdr_free $pnmhdr

#----------------------------------------------------------------
# Find out how many frames must we decode in order to decode 
# frame $start.  We then call mpeg_video_index_findrefs
# to retrives index entries of frames that are needed to be
# decoded in order to decode frame $start.  These index 
# entries will be stored in a second mpeg_video_index called $out.
#
# Now start decoding the video frames from out index.  These are
# not written to disk, but decoded in memory until size = 0. 
#----------------------------------------------------------------
set size [mpeg_video_index_numrefs $in $start]
set out  [mpeg_video_index_new $size]
mpeg_video_index_findrefs $in $out $start
for {set i [expr $size-1]} {$i >= 0} {incr i -1} {
    set offset [mpeg_video_index_get_offset $out $i]
    bitparser_seek $bpmpeg $offset
    mpeg_pic_hdr_parse $bpmpeg $fh
    set type [mpeg_pic_hdr_get_type $fh]
    swap futurey prevy
    if {$type == "i"} {
	mpeg_pic_i_parse $bpmpeg $sh $fh $scy $scu $scv
	sc_i_to_byte $scy $y
    }  elseif { $type == "p"} {
        mpeg_pic_p_parse $bpmpeg $sh $fh $scy $scu $scv $fwdmv
        sc_p_to_y  $scy $fwdmv $prevy $y
    }
    swap y futurey
}

swap prevy futurey

#----------------------------------------------------------------
# Now start decoding the video frames to write to disk. This
# process is very similar to mpgtopgm.tcl. 
#
# These decoding process is in display order.  
# - I frame : if this frame hasn't been decoded before, decode it.
# - P frame : if this frame hasn't been decoded before, decode it.
# - B frame : find the future frame, decode the future frame, and
#             decode this frame.  Mark the future frame as decoded.
#             so we don't need to decode it again.
#----------------------------------------------------------------

set futureframe -1
mpeg_pic_hdr_find $bpmpeg
set currFrame $start
while {$currFrame <= $end} {
    set offset [mpeg_video_index_get_offset $in $currFrame]
    bitparser_seek $bpmpeg $offset
    mpeg_pic_hdr_parse $bpmpeg $fh
    set type [mpeg_pic_hdr_get_type $fh]
    if {$type == "i"} {
	if {$currFrame == $futureframe} {
	    write_pgm $futurey $outbs $outbp [format "%03di.pgm" $currFrame] 
	    swap futurey prevy
	} else {
	    mpeg_pic_i_parse $bpmpeg $sh $fh $scy $scu $scv
	    sc_i_to_byte $scy $y
	    write_pgm $y $outbs $outbp [format "%03di.pgm" $currFrame] 
	    swap y prevy
	}
    } elseif {$type == "p"} {
	if {$currFrame == $futureframe} {
	    write_pgm $futurey $outbs $outbp [format "%03dp.pgm" $currFrame] 
	    swap futurey prevy
	} else {
	    mpeg_pic_p_parse $bpmpeg $sh $fh $scy $scu $scv $fwdmv
	    sc_p_to_y  $scy $fwdmv $prevy $y
	    write_pgm $y $outbs $outbp [format "%03dp.pgm" $currFrame] 
	    swap y prevy
	}
    } else {
	set frameref [mpeg_video_index_get_next $in $currFrame]
	set futureframe [expr $currFrame + $frameref]
	set off [mpeg_video_index_get_offset $in $futureframe]
	bitparser_seek $bpmpeg $off
	mpeg_pic_hdr_parse $bpmpeg $fh
	set type [mpeg_pic_hdr_get_type $fh]
        # Decode the future frame
	if {$type == "i"} {
	    mpeg_pic_i_parse $bpmpeg $sh $fh $scy $scu $scv
	    sc_i_to_byte $scy $futurey

	} else {
	    mpeg_pic_p_parse $bpmpeg $sh $fh $scy $scu $scv $fwdmv
	    sc_p_to_y  $scy $fwdmv $prevy $futurey
	}
	set off [mpeg_video_index_get_offset $in $currFrame]
	bitparser_seek $bpmpeg $off
	mpeg_pic_hdr_parse $bpmpeg $fh
	mpeg_pic_b_parse $bpmpeg $sh $fh $scy $scu $scv $fwdmv $bwdmv 
	sc_b_to_y  $scy $fwdmv $bwdmv $prevy $futurey $y
	write_pgm $y $outbs $outbp [format "%03db.pgm" $currFrame]
    }
    incr currFrame
}


#----------------------------------------------------------------
# clean upmes from out index.  These are
# not written to disk, but decoded
#----------------------------------------------------------------
mpeg_pic_hdr_free $fh
mpeg_seq_hdr_free $sh
mpeg_video_index_free $in
mpeg_video_index_free $out
byte_free $y
byte_free $prevy
byte_free $futurey
sc_free $scy
sc_free $scu
sc_free $scv
vector_free $fwdmv
vector_free $bwdmv