editor: another undo fix
[iv.d.git] / zmbv_samples / zmplay.d
blobac0fe086a086a0e0222cb022b1125f485f8cc3fe
1 /* Invisible Vector Library
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 module zmplay /*is aliced*/;
19 import std.stdio : File;
21 import iv.alice;
22 import iv.exex;
23 import iv.stream;
24 import iv.vl2;
25 import iv.zmbv;
26 import iv.writer;
29 ////////////////////////////////////////////////////////////////////////////////
30 mixin(MyException!"PlayerError");
33 ////////////////////////////////////////////////////////////////////////////////
34 File aviFl;
35 uint fps;
36 bool hasIndex;
37 uint framesTotal;
38 uint streamCount;
39 uint videoW, videoH;
40 ulong movieOfs;
41 uint movieSize;
42 ulong moviePos;
43 Decoder zmd;
46 // in: st.tell == 0
47 void loadHeader(ST) (auto ref ST st) if (isReadableStream!ST && isSeekableStream!ST) {
48 char[4] sign;
49 st.rawReadExact(sign[]);
50 if (sign != "RIFF") throw new PlayerError("not a RIFF");
51 uint riffsize = st.readNum!uint();
52 st.rawReadExact(sign[]);
53 if (sign != "AVI ") throw new PlayerError("not an AVI");
54 st.rawReadExact(sign[]);
55 if (sign != "LIST") throw new PlayerError("not an AVI");
56 uint listsize = st.readNum!uint();
57 if (listsize > 512) throw new PlayerError("AVI header too big");
58 ulong hepos = st.tell+listsize;
59 // load headers
60 st.rawReadExact(sign[]);
61 if (sign != "hdrl") throw new PlayerError("invalid AVI");
62 st.rawReadExact(sign[]);
63 if (sign != "avih") throw new PlayerError("no AVI header");
64 uint sz = st.readNum!uint();
65 if (sz < 56) throw new PlayerError("invalid AVI header");
66 ulong npos = st.tell+sz;
67 fps = 1000000/st.readNum!uint(); // microseconds per frame --> FPS
68 if (fps < 1 || fps > 255) throw new PlayerError("invalid FPS");
69 st.readNum!uint();
70 st.readNum!uint();
71 uint flags = st.readNum!uint();
72 if ((flags&0x100) == 0) throw new PlayerError("non-interleaved AVI");
73 hasIndex = ((flags&0x10) != 0);
74 framesTotal = st.readNum!uint();
75 st.readNum!uint(); // initial frames
76 streamCount = st.readNum!uint();
77 if (streamCount < 1 || streamCount > 2) throw new PlayerError("invalid number of streams");
78 st.readNum!uint();
79 videoW = st.readNum!uint();
80 videoH = st.readNum!uint();
81 if (videoW < 32 || videoH < 32 || videoW > 4096 || videoH > 4096) throw new PlayerError("invalid video dimensions");
82 // now load stream list
83 st.seek(npos);
84 st.rawReadExact(sign[]);
85 if (sign != "LIST") throw new PlayerError("not an AVI");
86 st.readNum!uint();
87 st.rawReadExact(sign[]);
88 if (sign != "strl") throw new PlayerError("no stream list found");
89 st.rawReadExact(sign[]);
90 if (sign != "strh") throw new PlayerError("no stream list found");
91 sz = st.readNum!uint();
92 if (sz < 56) throw new PlayerError("invalid AVI header");
93 npos = st.tell+sz;
94 st.rawReadExact(sign[]);
95 if (sign != "vids") throw new PlayerError("first stream is not video");
96 st.rawReadExact(sign[]);
97 if (sign != "ZMBV") throw new PlayerError("first stream is ZMBV");
98 // now look for actual data
99 st.seek(hepos);
100 for (;;) {
101 st.rawReadExact(sign[]);
102 sz = st.readNum!uint();
103 npos = st.tell+sz;
104 if (sz >= 3*4 && sign == "LIST") {
105 st.rawReadExact(sign[]);
106 if (sign == "movi") {
107 movieOfs = st.tell;
108 movieSize = sz-4;
109 moviePos = movieOfs;
110 break;
113 st.seek(npos);
115 writeln("FPS: ", fps);
116 writeln("hasIndex: ", hasIndex);
117 writeln("framesTotal: ", framesTotal);
118 writeln(videoW, "x", videoH);
119 //TODO: index
120 zmd = new Decoder(videoW, videoH);
124 ubyte[] encframe;
125 Color[256] framepal;
126 bool paused;
127 bool videoDone = false;
128 uint curFrame;
131 bool loadNextFrame(ST) (auto ref ST st) if (isReadableStream!ST && isSeekableStream!ST) {
132 //writeln(moviePos, " ", movieOfs+movieSize);
133 while (moviePos < movieOfs+movieSize) {
134 char[4] sign;
135 st.seek(moviePos);
136 st.rawReadExact(sign[]);
137 uint sz = st.readNum!uint();
138 //writeln(sz, " ", sign);
139 moviePos += 8+sz;
140 if (moviePos&0x01) ++moviePos;
141 if (sign == "00dc") {
142 if (encframe.length < sz) encframe.length = sz;
143 st.rawReadExact(encframe[0..sz]);
144 zmd.decodeFrame(encframe[0..sz]);
145 //writeln(zmd.width, "x", zmd.height, " : ", zmd.format);
146 if (zmd.format != Codec.Format.bpp8 && zmd.format != Codec.Format.bpp32) throw new PlayerError("invalid frame format");
147 //if (zmd.width != videoW || zmd.height != videoH) throw new PlayerError("frame size changes are not supported yet");
148 if (zmd.paletteChanged) {
149 auto pal = zmd.palette;
150 foreach (immutable idx; 0..256) framepal[idx] = rgb2col(pal[idx*3+0], pal[idx*3+1], pal[idx*3+2]);
152 ++curFrame;
153 return true;
156 return false;
160 ////////////////////////////////////////////////////////////////////////////////
161 void realizeFrame () {
162 foreach (immutable y; 0..zmd.height) {
163 auto src = zmd.line(y).ptr;
164 auto dst = vlVScr+y*vlWidth;
165 uint wdt = zmd.width;
166 if (wdt > vlWidth) wdt = vlWidth;
167 if (zmd.format == Codec.Format.bpp8) {
168 foreach (immutable x; 0..wdt) *dst++ = framepal[*src++];
169 } else {
170 import core.stdc.string : memcpy;
171 memcpy(dst, src, wdt*4);
177 private void updateCB (int elapsedTicks) {
178 if (!videoDone && !paused) videoDone = !loadNextFrame(aviFl);
179 vlsOvl.fillRect(0, 0, vlsOvl.width, vlsOvl.height, 0);
180 realizeFrame();
181 if (!videoDone) {
182 if (curFrame) vlsOvl.hline(0, 0, cast(uint)(cast(ulong)vlsOvl.width*curFrame/framesTotal), rgb2col(0, 255, 0));
183 if (paused) vlsOvl.drawStrPropOut(3, 3, "paused", rgb2col(255, 127, 0), 0);
184 } else {
185 vlsOvl.drawStrPropOut(3, 3, "DONE!", rgb2col(255, 0, 0), 0);
187 vlFrameChanged();
191 ////////////////////////////////////////////////////////////////////////////////
192 private void keyDownCB (in ref SDL_KeyboardEvent ev) {
193 if (ev.keysym.sym == SDLK_RETURN && (ev.keysym.mod&KMOD_ALT)) { vlSwitchFullscreen(); return; }
194 if (ev.keysym.sym == SDLK_ESCAPE) { vlPostQuitMessage(); return; }
195 switch (ev.keysym.sym) {
196 case SDLK_SPACE: paused = !paused; break;
198 case SDLK_UP: case SDLK_KP_8:
199 case SDLK_LEFT: case SDLK_KP_4:
200 if (spmasked) {
201 if (num > 0) num -= 2;
202 } else {
203 if (findByNum(--num) == uint.max) ++num;
205 break;
206 case SDLK_DOWN: case SDLK_KP_2:
207 case SDLK_RIGHT: case SDLK_KP_6:
208 if (spmasked) {
209 if (findByNum(num+1) != uint.max && findByNum(num+2) != uint.max) {
210 num += 2;
211 } else {
212 if (findByNum(num+1) != uint.max) { import iv.writer; writeln("extra sprite!"); }
214 } else {
215 if (findByNum(++num) == uint.max) --num;
217 break;
219 default:
224 ////////////////////////////////////////////////////////////////////////////////
225 void main (string[] args) {
226 vlProcessArgs(args);
227 if (args.length != 2) {
228 import iv.writer;
229 writeln("WTF?!");
230 return;
232 aviFl = File(args[1]);
233 loadHeader(aviFl);
234 vlWidth = videoW;
235 vlHeight = videoH;
236 if (videoW > 320 || videoH > 400) vlMag2x = false;
237 try {
238 vlInit("ZMBV Player/SDL");
239 } catch (Throwable e) {
240 import iv.writer;
241 writeln("FATAL: ", e.msg);
242 return;
244 vlFPS = fps;
245 vlOnUpdate = &updateCB;
246 vlOnKeyDown = &keyDownCB;
247 vlMainLoop();