ref: 82b046f36f8084a22bbb5d71edd0edd9179561eb
dir: /appl/wm/avi.b/
implement WmAVI;
include "sys.m";
sys: Sys;
include "draw.m";
draw: Draw;
Rect, Display, Image: import draw;
include "tk.m";
tk: Tk;
Toplevel: import tk;
include "tkclient.m";
tkclient: Tkclient;
ctxt: ref Draw->Context;
include "selectfile.m";
selectfile: Selectfile;
include "dialog.m";
dialog: Dialog;
include "riff.m";
avi: Riff;
AVIhdr, AVIstream, RD: import avi;
video: ref AVIstream;
WmAVI: module
{
init: fn(ctxt: ref Draw->Context, argv: list of string);
};
Stopped, Playing, Paused: con iota;
state := Stopped;
cmap: array of byte;
codedbuf: array of byte;
pixelbuf: array of byte;
pixelrec: Draw->Rect;
task_cfg := array[] of {
"canvas .c",
"frame .b",
"button .b.File -text File -command {send cmd file}",
"button .b.Stop -text Stop -command {send cmd stop}",
"button .b.Pause -text Pause -command {send cmd pause}",
"button .b.Play -text Play -command {send cmd play}",
"frame .f",
"label .f.file -text {File:}",
"label .f.name",
"pack .f.file .f.name -side left",
"pack .b.File .b.Stop .b.Pause .b.Play -side left",
"pack .f -fill x",
"pack .b -anchor w",
"pack .c -side bottom -fill both -expand 1",
"pack propagate . 0",
};
init(xctxt: ref Draw->Context, nil: list of string)
{
sys = load Sys Sys->PATH;
draw = load Draw Draw->PATH;
tk = load Tk Tk->PATH;
tkclient= load Tkclient Tkclient->PATH;
dialog = load Dialog Dialog->PATH;
selectfile = load Selectfile Selectfile->PATH;
ctxt = xctxt;
sys->pctl(Sys->NEWPGRP, nil);
tkclient->init();
dialog->init();
selectfile->init();
(t, wmctl) := tkclient->toplevel(ctxt, "", "AVI Player", 0);
cmd := chan of string;
tk->namechan(t, cmd, "cmd");
for (c:=0; c<len task_cfg; c++)
tk->cmd(t, task_cfg[c]);
tk->cmd(t, "bind . <Configure> {send cmd resize}");
tk->cmd(t, "update");
tkclient->onscreen(t, nil);
tkclient->startinput(t, "kbd"::"ptr"::nil);
avi = load Riff Riff->PATH;
if(avi == nil) {
dialog->prompt(ctxt, t.image, "error -fg red", "Loading Interfaces",
"Failed to load the RIFF/AVI\ninterface:"+sys->sprint("%r"),
0, "Exit"::nil);
return;
}
avi->init();
fname := "";
state = Stopped;
for(;;) alt {
s := <-t.ctxt.kbd =>
tk->keyboard(t, s);
s := <-t.ctxt.ptr =>
tk->pointer(t, *s);
s := <-t.ctxt.ctl or
s = <-t.wreq or
s = <-wmctl =>
if(s == "exit") {
state = Stopped;
return;
}
tkclient->wmctl(t, s);
press := <-cmd =>
case press {
"file" =>
state = Stopped;
patterns := list of {
"*.avi (Microsoft movie files)",
"* (All Files)"
};
fname = selectfile->filename(ctxt, t.image, "Locate AVI files",
patterns, nil);
if(fname != nil) {
tk->cmd(t, ".f.name configure -text {"+fname+"}");
tk->cmd(t, "update");
}
"play" =>
if (state != Stopped) {
state = Playing;
continue;
}
if(fname != nil) {
state = Playing;
spawn play(t, fname);
}
"pause" =>
if(state == Playing)
state = Paused;
"stop" =>
state = Stopped;
}
}
}
play(t: ref Toplevel, file: string)
{
sp := list of { "Stop Play" };
(r, err) := avi->open(file);
if(err != nil) {
dialog->prompt(ctxt, t.image, "error -fg red", "Open AVI file", err, 0, sp);
return;
}
err = avi->r.check4("AVI ");
if(err != nil) {
dialog->prompt(ctxt, t.image, "error -fg red", "Read AVI format", err, 0, sp);
return;
}
(code, l) := avi->r.gethdr();
if(code != "LIST") {
dialog->prompt(ctxt, t.image, "error -fg red", "Parse AVI headers",
"no list under AVI section header", 0, sp);
return;
}
err = avi->r.check4("hdrl");
if(err != nil) {
dialog->prompt(ctxt, t.image, "error -fg red", "Read AVI header", err, 0, sp);
return;
}
avihdr: ref AVIhdr;
(avihdr, err) = avi->r.avihdr();
if(err != nil) {
dialog->prompt(ctxt, t.image, "error -fg red", "Read AVI header", err, 0, sp);
return;
}
#
# read the stream info & format structures
#
stream := array[avihdr.streams] of ref AVIstream;
for(i := 0; i < avihdr.streams; i++) {
(stream[i], err) = avi->r.streaminfo();
if(err != nil) {
dialog->prompt(ctxt, t.image, "error -fg red", "Parse AVI headers",
"Failed to parse stream headers\n"+err, 0, sp);
return;
}
if(stream[i].stype == "vids") {
video = stream[i];
err = video.fmt2binfo();
if(err != nil) {
dialog->prompt(ctxt, t.image, "error -fg red",
"Parse AVI Video format",
"Invalid stream headers\n"+err, 0, sp);
return;
}
}
}
img: ref Draw->Image;
if(video != nil) {
case video.binfo.compression {
* =>
dialog->prompt(ctxt, t.image, "error -fg red",
"Parse AVI Compression method",
"unknown compression/encoding method", 0, sp);
return;
avi->BI_RLE8 =>
cmap = array[len video.binfo.cmap] of byte;
for(i = 0; i < len video.binfo.cmap; i++) {
e := video.binfo.cmap[i];
cmap[i] = byte ctxt.display.rgb2cmap(e.r, e.g, e.b);
}
break;
}
chans: draw->Chans;
case video.binfo.bitcount {
* =>
dialog->prompt(ctxt, t.image, "error -fg red",
"Check AVI Video format",
string video.binfo.bitcount+
" bits per pixel not supported", 0, sp);
return;
8 =>
chans = Draw->CMAP8;
mem := video.binfo.width*video.binfo.height;
pixelbuf = array[mem] of byte;
};
pixelrec.min = (0, 0);
pixelrec.max = (video.binfo.width, video.binfo.height);
img = ctxt.display.newimage(pixelrec, chans, 0, Draw->White);
if (img == nil) {
sys->fprint(sys->fildes(2), "coffee: failed to allocate image\n");
exit;
}
}
#
# Parse out the junk headers we don't understand
#
parse: for(;;) {
(code, l) = avi->r.gethdr();
if(l < 0)
break;
case code {
* =>
# sys->print("%s %d\n", code, l);
avi->r.skip(l);
"LIST" =>
err = avi->r.check4("movi");
if(err != nil) {
dialog->prompt(ctxt, t.image, "error -fg red",
"Strip AVI headers",
"no movi chunk", 0, sp);
return;
}
break parse;
}
}
canvr := canvsize(t);
p := (Draw->Point)(0, 0);
dx := canvr.dx();
if(dx > video.binfo.width)
p.x = (dx - video.binfo.width)/2;
dy := canvr.dy();
if(dy > video.binfo.height)
p.y = (dy - video.binfo.height)/2;
canvr = canvr.addpt(p);
chunk: for(;;) {
while(state == Paused)
sys->sleep(0);
if(state == Stopped)
break chunk;
(code, l) = avi->r.gethdr();
if(l <= 0)
break;
if(l & 1)
l++;
case code {
* =>
avi->r.skip(l);
"00db" => # Stream 0 Video DIB
dib(r, img, l);
"00dc" => # Stream 0 Video DIB compressed
dibc(r, img, l);
t.image.draw(canvr, img, nil, img.r.min);
"idx1" =>
break chunk;
}
}
state = Stopped;
}
dib(r: ref RD, i: ref Draw->Image, l: int): int
{
if(len codedbuf < l)
codedbuf = array[l] of byte;
if(r.readn(codedbuf, l) != l)
return -1;
case video.binfo.bitcount {
8 =>
for(k := 0; k < l; k++)
codedbuf[k] = cmap[int codedbuf[k]];
i.writepixels(pixelrec, codedbuf);
}
return 0;
}
dibc(r: ref RD, i: ref Draw->Image, l: int): int
{
if(len codedbuf < l)
codedbuf = array[l] of byte;
if(r.readn(codedbuf, l) != l)
return -1;
case video.binfo.compression {
avi->BI_RLE8 =>
p := 0;
posn := 0;
x := 0;
y := video.binfo.height-1;
w := video.binfo.width;
decomp: while(p < l) {
n := int codedbuf[p++];
if(n == 0) {
esc := int codedbuf[p++];
case esc {
0 => # end of line
x = 0;
y--;
1 => # end of image
break decomp;
2 => # Delta dx,dy
x += int codedbuf[p++];
y -= int codedbuf[p++];
* =>
posn = x+y*w;
for(k := 0; k < esc; k++)
pixelbuf[posn++] = cmap[int codedbuf[p++]];
x += esc;
if(p & 1)
p++;
};
}
else {
posn = x+y*w;
v := cmap[int codedbuf[p++]];
for(k := 0; k < n; k++)
pixelbuf[posn++] = v;
x += n;
}
}
i.writepixels(pixelrec, pixelbuf);
}
return 0;
}
canvsize(t: ref Toplevel): Rect
{
r: Rect;
r.min.x = int tk->cmd(t, ".c cget -actx");
r.min.y = int tk->cmd(t, ".c cget -acty");
r.max.x = r.min.x + int tk->cmd(t, ".c cget -width");
r.max.y = r.min.y + int tk->cmd(t, ".c cget -height");
return r;
}