implement -q (--quiet) option.
[swftools.git] / avi2swf / avi2swf.cc
1 /* avi2swf.cc
2    Convert avi movie files into swf.
3
4    Part of the swftools package.
5    
6    Copyright (c) 2001 Matthias Kramm <kramm@quiss.org>
7
8    This file is distributed under the GPL, see file COPYING for details */
9
10 #include <stdio.h>
11 #include <fcntl.h>
12 extern "C" {
13 #include "../lib/rfxswf.h"
14 #include "../lib/args.h"
15 }
16 #include <avifile/version.h>
17 #if (AVIFILE_MAJOR_VERSION == 0) && (AVIFILE_MINOR_VERSION==6) 
18    #include <avifile.h>
19    #include <aviplay.h>
20    #include <fourcc.h>
21    #include <creators.h>
22    #include <StreamInfo.h>
23    #define VERSION6
24 #else
25    #include <avifile.h>
26    #include <aviplay.h>
27    #include <aviutil.h>
28    #define Width width
29    #define Height height
30    #define Data data
31    #define Bpp bpp
32 #endif
33
34 /*
35 statistics: (for now)
36    37   bytes per shape (rectangle)
37    8-12 bytes per placeobject
38    4    bytes per removeobject2
39    696+ bytes per definejpeg2 (576 jpegtables)
40    576  bytes per jpegtables
41    122+ bytes per definejpeg
42
43    blocks*140 = minimal bytes per frames
44    5000/140   = maximal blocks with framerate 5000
45
46    2    bytes per showframe
47 */
48
49 static int cache_size=38; //in frames
50
51 static char * filename = 0;
52 static char * outputfilename = "output.swf";
53 static unsigned int firstframe = 0;
54 static unsigned int lastframe = 0x7fffffff;
55
56 static int jpeg_quality = 20;
57
58 static char zlib = 0;
59 static double scale = 1.0;
60
61 #ifndef ST_DEFINEBITSJPEG
62 #define ST_DEFINEBITSJPEG       6 
63 #endif
64   
65 int filesize = 0;
66
67 struct options_t options[] =
68 {
69  {"v","verbose"},
70  {"o","output"},
71  {"n","num"},
72  {"s","start"},
73  {"z","zlib"},
74  {"V","version"},
75  {0,0}
76 };
77
78 int args_callback_option(char*name,char*val)
79 {
80     if(!strcmp(name, "V")) {
81         printf("avi2swf - part of %s %s\n", PACKAGE, VERSION);
82         exit(0);
83     } 
84     else if(!strcmp(name, "o")) {
85         outputfilename = val;
86         return 1;
87     }
88     else if(!strcmp(name, "n")) {
89         lastframe = atoi(val);
90         return 1;
91     }
92     else if(!strcmp(name, "s")) {
93         firstframe = atoi(val);
94         return 1;
95     }
96     else if(!strcmp(name, "d")) {
97         scale = atoi(val)/100.0;
98         if(scale>1.0 || scale<=0) {
99             fprintf(stderr, "Scale must be in the range 1-100!\n");
100             exit(1);
101         }
102         return 1;
103     }
104     else if(!strcmp(name, "z")) {
105         zlib = 1;
106         return 0;
107     }
108     fprintf(stderr, "Unknown option: -%s\n", name);
109     exit(1);
110 }
111 int args_callback_longoption(char*name,char*val)
112 {
113     return args_long2shortoption(options, name, val);
114 }
115 void args_callback_usage(char*name)
116 {    
117     printf("\nUsage: %s file.avi\n", name);
118     printf("\t-h , --help\t\t Print help and exit\n");
119     printf("\t-o , --output filename\t Specify output filename\n"); 
120     printf("\t-n , --num frames\t Number of frames to encode\n");
121     printf("\t-s , --start frame\t First frame to encode\n");
122     printf("\t-d , --scale factor\t Scale to factor percent\n");
123     printf("\t-V , --version\t\t Print program version and exit\n");
124     exit(0);
125 }
126 int args_callback_command(char*name,char*val)
127 {
128     if(filename) {
129         fprintf(stderr, "Only one file allowed. You supplied at least two. (%s and %s)\n",
130                  filename, name);
131     }
132     filename = name;
133     return 0;
134 }
135
136 /* id allocation/deallocation */
137 char idtab[65536];
138 unsigned int idtab_pos=1;
139 int get_free_id()
140 {
141     while(idtab[idtab_pos] || !idtab_pos)
142         idtab_pos++;
143     idtab[idtab_pos]=1;
144     return idtab_pos;
145 }
146 void free_id(int id)
147 {
148     idtab[id] = 0;
149 }
150
151 void makeshape(int file, int id, int gfxid, int width, int height)
152 {
153     TAG*tag;
154     RGBA rgb;
155     MATRIX m;
156     SHAPE*s;
157     SRECT r;
158     int lines = 0;
159     int ls,fs;
160     tag = swf_InsertTag(NULL, ST_DEFINESHAPE);
161     swf_ShapeNew(&s);
162     rgb.b = rgb.g = rgb.r = 0xff;
163     if(lines)
164         ls = swf_ShapeAddLineStyle(s,20,&rgb);  
165     swf_GetMatrix(NULL,&m);
166     m.sx = 20*65536;
167     m.sy = 20*65536;
168
169     fs = swf_ShapeAddBitmapFillStyle(s,&m,gfxid,0);
170     swf_SetU16(tag,id);   // ID   
171     r.xmin = 0;
172     r.ymin = 0;
173     r.xmax = width*20;
174     r.ymax = height*20;
175     swf_SetRect(tag,&r);
176
177     swf_SetShapeStyles(tag,s);
178     swf_ShapeCountBits(s,NULL,NULL);
179     swf_SetShapeBits(tag,s);
180
181     swf_ShapeSetAll(tag,s,0,0,lines?ls:0,fs,0);
182
183     swf_ShapeSetLine(tag,s,width*20,0);
184     swf_ShapeSetLine(tag,s,0,height*20);
185     swf_ShapeSetLine(tag,s,-width*20,0);
186     swf_ShapeSetLine(tag,s,0,-height*20);
187     swf_ShapeSetEnd(tag);
188     filesize += swf_WriteTag(file, tag);
189     swf_DeleteTag(tag);
190     swf_ShapeFree(s);
191 }
192
193 void setshape(int file,int id,int depth,int x,int y,CXFORM*cx)
194 {
195     TAG*tag;
196     MATRIX m;
197     m.sx = 0x10000; m.sy = 0x10000;
198     m.r0 = 0; m.r1 = 0;
199     m.tx = x*20; 
200     m.ty = y*20;
201     if(cx && !((cx->a0!=256)||(cx->r0!=256)||(cx->g0!=256)||(cx->b0!=256)
202                 ||(cx->a1|cx->r1|cx->g1|cx->b1))) cx = 0;
203     tag = swf_InsertTag(NULL,ST_PLACEOBJECT2);
204       swf_ObjectPlace(tag,id,depth,&m,cx,0);
205     filesize += swf_WriteTag(file, tag);
206     swf_DeleteTag(tag);
207 }
208
209
210 int xblocksize;
211 int yblocksize;
212 struct GfxBlock {
213 //    static int xblocksize;
214 //    static int yblocksize;
215     U8*data;
216     int len;
217 };
218
219 int width=0;
220 int height=0;
221
222 int xblocks;
223 int yblocks;
224
225 U8* blockbuffer = 0;
226    
227 class GfxBlockCache {
228
229     GfxBlock*list;
230     char*expire; //0=block's free
231     int*ids;
232     int size;
233     int pos;
234     int hits;
235     int misses;
236
237     public:
238
239     GfxBlockCache(int file) 
240     {
241         list=0;
242         size = xblocks*yblocks*cache_size;
243         printf("initializing cache (%d entries)\n", size);
244         list = new GfxBlock[size];
245         expire = new char[size];
246         ids = new int[size];
247         memset(expire,0,size);
248         memset(list,0,sizeof(GfxBlock)*size);
249         memset(ids,0,sizeof(int)*size);
250         pos = 0;
251         hits =0;
252         misses =0;
253     }
254     void insert(GfxBlock*block, int gfxid)
255     {
256         int oldpos = pos;
257         while(++pos!=oldpos)
258         {
259             if(pos==size) pos=0;
260             if(!expire[pos])
261                 break;
262         }
263         if(pos==oldpos) {
264             // cache full- don't insert item
265             return;
266         }
267         if(list[pos].data) {
268             free(list[pos].data);
269             list[pos].data = 0;
270             //TODO: free this in the SWF, also
271         }
272         list[pos].data=(U8*)malloc(block->len);
273         memcpy(list[pos].data,block->data,block->len);
274         list[pos].len = block->len;
275         expire[pos] = cache_size;
276         ids[pos] = gfxid;
277     }
278     int find(GfxBlock*block, CXFORM*cxform)
279     {
280         //TODO: do least square regression here to derive cxform
281         int s;
282         int bestsum=-1;
283         int bestid;
284         float best;
285         for(s=0;s<size;s++)
286         if(expire[s])
287         {
288             int t = (block->len);
289             U8*ptr1 = block->data;
290             U8*ptr2 = list[s].data;
291             int sum2 = 0;
292             // notice: we treat r,g,b as equal here.
293             do {
294                 int a = (*ptr1++)-(*ptr2++);
295                 sum2 += a*a;
296             } while(--t);
297             if(bestsum < 0 || bestsum > sum2) {
298                 bestid = s;
299                 bestsum = sum2;
300             }
301         }
302         if(bestsum<0) {
303             misses++;
304             return -1;
305         }
306         best = bestsum/block->len;
307
308         if(best > 96.0) {
309             misses++;
310             return -1;
311         } 
312         expire[bestid]= cache_size;
313         hits++;
314         cxform->a0 = 256;
315         cxform->r0 = 256;
316         cxform->g0 = 256;
317         cxform->b0 = 256;
318         cxform->a1 = 0;
319         cxform->r1 = 0;
320         cxform->g1 = 0;
321         cxform->b1 = 0;
322         return ids[bestid];
323     }
324     void newframe()
325     {
326         int t;
327         for(t=0;t<size;t++)
328             if(expire[t])
329                 expire[t]--;
330
331     }
332     ~GfxBlockCache()
333     {
334         int t;
335         printf("destroying cache...\n");
336         if(hits+misses) {
337             printf("hits:%d (%02d%%)\n", hits, hits*100/(hits+misses));
338             printf("misses:%d (%02d%%)\n", misses, misses*100/(hits+misses));
339         }
340         for(t=0;t<size;t++)
341             if(expire[t] && list[t].data)
342                 free(list[t].data);
343         free(list);
344         free(expire);
345         free(ids);
346     }
347 } * cache = 0;
348
349 class GfxBlockEncoder {
350     int sizex;
351     int sizey;
352     int posx;
353     int posy;
354     int basedepth;
355     int depth[3];
356     public:
357     void init(int depth, int posx,int posy, int sizex, int sizey) 
358     {
359         this->basedepth = depth;
360         this->posx = posx;
361         this->posy = posy;
362         this->sizex = sizex;
363         this->sizey = sizey;
364         this->depth[0] = this->depth[1] = this->depth[2] = -1;
365     }
366     void clear(int file)
367     {
368         /* clear everything in the block */
369         int t;
370         for(t=0;t<3;t++)
371         if(depth[t]>=0)
372         {
373             TAG*tag;
374             tag = swf_InsertTag(NULL, ST_REMOVEOBJECT2);
375             swf_SetU16(tag, basedepth+t); //depth
376             filesize += swf_WriteTag(file, tag);
377             swf_DeleteTag(tag);
378             depth[t] = -1;
379         }
380     }
381     void writeiframe(int file, GfxBlock*block)
382     {
383         clear(file);
384
385         int gfxid = get_free_id();
386         int shapeid = get_free_id();
387
388         //memset(data,0,sizex*sizey*3);
389         TAG*tag = swf_InsertTag(NULL, ST_DEFINEBITS);
390         JPEGBITS * jb = swf_SetJPEGBitsStart(tag,sizex,sizey,jpeg_quality);
391         tag->len = 0; //bad hack
392         swf_SetU16(tag, gfxid);
393         int y;
394         for(y=0;y<sizey;y++)
395             swf_SetJPEGBitsLine(jb,&block->data[y*sizex*3]);
396         swf_SetJPEGBitsFinish(jb);
397         filesize += swf_WriteTag(file, tag);
398         swf_DeleteTag(tag);
399
400         cache->insert(block, shapeid);
401
402         makeshape(file, shapeid, gfxid, sizex, sizey);
403         setshape(file, shapeid, basedepth+1, posx, posy, 0);
404         depth[1] = shapeid;
405     }
406     void writereference(int file, int shapeid, CXFORM*form)
407     {
408         if(depth[1]!=shapeid)
409         {
410             clear(file);
411             setshape(file, shapeid, basedepth+1, posx, posy, form);
412             depth[1] = shapeid;
413         }
414     }
415     void compress(int file, GfxBlock*block)
416     {   
417         CXFORM form;
418         int id = cache->find(block, &form);
419         if(id<0)
420             writeiframe(file, block);
421         else {
422             writereference(file, id, &form);
423         }
424     }
425 } *blocks = 0;
426
427 void initdisplay(int file)
428 {
429     if(blockbuffer)
430         free(blockbuffer);
431     if(blocks) {
432         int t;
433         for(t=0;t<xblocks;t++)
434             blocks[t].clear(file);
435         free(blocks);
436     }
437     if(cache)
438         delete cache;
439     xblocksize = (width/3)&~7;
440     yblocksize = (height/2)&~7;
441     xblocks = width/xblocksize;
442     yblocks = height/yblocksize;
443     printf("%dx%d blocks of size %dx%d\n", xblocks,yblocks, xblocksize, yblocksize);
444     printf("cutting lower %d lines, right %d columns\n", 
445             height-yblocks*yblocksize, width-xblocks*xblocksize);
446     blocks = new GfxBlockEncoder[xblocks*yblocks];
447     blockbuffer = new U8[xblocksize*yblocksize*4]; //should be 3
448     cache = new GfxBlockCache(file);
449     int t;
450     for(t=0;t<xblocks*yblocks;t++) {
451         blocks[t].init(t*64,
452                        (t%xblocks)*xblocksize,
453                        (t/xblocks)*yblocksize,
454                        xblocksize, yblocksize);
455     }
456
457     TAG*tag = swf_InsertTag(NULL, ST_JPEGTABLES);
458     JPEGBITS * jpeg = swf_SetJPEGBitsStart(tag, xblocksize, yblocksize, jpeg_quality);
459     filesize += swf_WriteTag(file, tag);
460     swf_DeleteTag(tag);
461     free(jpeg);
462 }
463
464 void destroydisplay(int file)
465 {
466     delete cache;
467     free(blocks);
468     free(blockbuffer);
469 }
470
471 SWF swf;
472 TAG*tag;
473
474 class SoundReader
475 {
476
477     short int* sound_buffer;
478     int mp3_block_size;
479     int write_pos;
480     int read_pos;
481     IAviReadStream* astream;
482     void readBlock()
483     {
484         unsigned samples_read, bytes_read;
485         int ret;
486         short int tmpbuf[4096];
487         ret = astream->ReadFrames(tmpbuf, 4096*sizeof(short int),
488                 4096, samples_read, bytes_read);
489         if(ret<0) {
490             printf("couldn't read %d samples\n", mp3_block_size);
491             exit(1);
492         }
493         int t;
494         samples_read = bytes_read/sizeof(short int);
495         for(t=0;t<samples_read/2;t++) {
496             sound_buffer[write_pos+t] = tmpbuf[t*2];
497         }
498         write_pos += samples_read/2;
499
500         if(write_pos >= mp3_block_size*8)
501         {
502             if(write_pos > mp3_block_size*8)
503                 memcpy(&sound_buffer[0],&sound_buffer[mp3_block_size*8],write_pos - mp3_block_size*8);
504             write_pos %= (mp3_block_size*8);
505         }
506     }
507     public:
508
509     SoundReader(IAviReadStream*astream)
510     {
511         this->astream = astream;
512         this->write_pos = 0;
513         this->read_pos = 0;
514         this->mp3_block_size = 2304;
515         this->sound_buffer = new short int[mp3_block_size*16];
516     }
517     ~SoundReader()
518     {
519         delete sound_buffer;
520     }
521     int available()
522     {
523         if(read_pos<=write_pos)
524             return write_pos-read_pos;
525         else
526             return (write_pos+mp3_block_size*8)-read_pos;
527     }
528     short int* readFrame()
529     {
530         int tmp;
531         while(available()<mp3_block_size) {
532             readBlock();
533         }
534         tmp = read_pos;
535         read_pos += mp3_block_size;
536         read_pos %= mp3_block_size*8;
537         return &sound_buffer[tmp];
538     }
539 };
540
541
542 static int mp3_block_size = 2304;
543
544 static int do_video = 1;
545 static int do_audio = 1;
546
547 int main (int argc,char ** argv)
548
549   int file;
550   IAviReadFile* player;
551   IAviReadStream* astream;
552   IAviReadStream* vstream;
553   SRECT r;
554   double samplesperframe;
555   int samplerate;
556   int samplefix;
557   double fps;
558   int oldwidth;
559   int oldheight;
560   double reziscale;
561
562   processargs(argc, argv);
563   lastframe += firstframe;
564   if(!filename)
565       exit(0);
566
567   memset(idtab, 0, sizeof(idtab));
568
569   player = CreateIAviReadFile(filename);    
570   astream = player->GetStream(0, AviStream::Audio);
571   vstream = player->GetStream(0, AviStream::Video);
572   if(!vstream)
573       do_video = 0;
574   if(!astream)
575       do_audio = 0;
576 #ifndef VERSION6
577   MainAVIHeader head;
578   int dwMicroSecPerFrame = 0;
579   player->GetFileHeader(&head);
580   printf("fps: %d\n", 1000000/head.dwMicroSecPerFrame);
581   printf("frames: %d\n", head.dwTotalFrames);
582   printf("streams: %d\n", head.dwStreams);
583   printf("width: %d\n", head.dwWidth);
584   printf("height: %d\n", head.dwHeight);
585   printf("sound: %u samples (%f seconds)\n", astream->GetEndPos(),
586           astream->GetEndTime());
587   oldwidth = head.dwWidth;
588   oldheight = head.dwHeight;
589   dwMicroSecPerFrame = head.dwMicroSecPerFrame;
590   samplesperframe = astream->GetEndPos()/astream->GetEndTime()*head.dwMicroSecPerFrame/1000000;
591   samplerate = (int)(astream->GetEndPos()/astream->GetEndTime());
592   fps = 1000000.0/dwMicroSecPerFrame;
593 #else
594   StreamInfo*audioinfo;
595   StreamInfo*videoinfo;
596   if(do_video)
597   {
598     videoinfo = vstream->GetStreamInfo();
599     oldwidth = videoinfo->GetVideoWidth();
600     oldheight = videoinfo->GetVideoHeight();
601     fps = (double)(videoinfo->GetFps());
602     delete(videoinfo);
603   }
604   if(do_audio)
605   {
606     audioinfo = astream->GetStreamInfo();
607     samplerate = audioinfo->GetAudioSamplesPerSec();
608     samplesperframe = audioinfo->GetAudioSamplesPerSec()/videoinfo->GetFps();
609     delete(audioinfo);
610   }
611 #endif
612   width = (int)(oldwidth*scale);
613   height = (int)(oldheight*scale);
614   reziscale = 1/scale;
615
616   vstream -> StartStreaming();
617   if(do_audio)
618   {
619     astream -> StartStreaming();
620     printf("%f framerate\n", fps);
621     printf("%f samples/frame\n", samplesperframe);
622     printf("%d samplerate\n", samplerate);
623   }
624
625   if(zlib)
626     file = open("__tmp__.swf", O_WRONLY|O_CREAT|O_TRUNC, 0644);
627   else
628     file = open(outputfilename, O_WRONLY|O_CREAT|O_TRUNC, 0644);
629   
630   memset(&swf, 0, sizeof(swf));
631   swf.frameRate = (int)(fps*256);
632   swf.fileVersion = 4;
633   swf.fileSize = 0x0fffffff;
634   swf.frameCount = lastframe - firstframe;
635   r.xmin = 0;
636   r.ymin = 0;
637   r.xmax = width*20;
638   r.ymax = height*20;
639   swf.movieSize = r;
640
641   filesize += swf_WriteHeader(file, &swf);
642
643   tag = swf_InsertTag(NULL, ST_SETBACKGROUNDCOLOR);
644   swf_SetU8(tag,0); //black
645   swf_SetU8(tag,0);
646   swf_SetU8(tag,0);
647   filesize += swf_WriteTag(file, tag);
648   swf_DeleteTag(tag);
649
650   tag = swf_InsertTag(NULL, ST_SOUNDSTREAMHEAD2);
651   swf_SetSoundStreamHead(tag, (int)samplesperframe/4);
652   filesize += swf_WriteTag(file, tag);
653   swf_DeleteTag(tag);
654
655   int frame = 0;
656   initdisplay(file);
657
658   double movie_sound_pos = 0;
659   int mp3_sound_pos = 0;
660
661   if(do_audio)
662   {
663       WAVEFORMATEX wave;
664       astream->GetAudioFormatInfo(&wave,0);
665
666       printf("nChannels:%d\n", wave.nChannels);
667       printf("nSamplesPerSec:%d\n", wave.nSamplesPerSec);
668       printf("nAvgBytesPerSec:%d\n", wave.nAvgBytesPerSec);
669       printf("nBlockAlign:%d\n", wave.nBlockAlign);
670       printf("wBitsPerSample:%d\n", wave.wBitsPerSample);
671       printf("cbSize:%d\n", wave.cbSize);
672   }
673
674   SoundReader* sound = new SoundReader(astream);
675
676   while(1) {
677     if(vstream->ReadFrame()<0) {
678         printf("\n");
679         break;
680     }
681
682     if(frame < firstframe)
683     {
684         movie_sound_pos += samplesperframe;
685         if(do_audio)
686         while(mp3_sound_pos<movie_sound_pos) {
687             short int* samples = sound->readFrame();
688             mp3_sound_pos += mp3_block_size;
689         }
690         printf("\rskipping frame %d",frame);
691         fflush(stdout);
692         frame++;
693         if(frame == firstframe)
694             printf("\n");
695         continue;
696     }
697     
698     printf("\rconvert frame %d",frame);
699     fflush(stdout);
700
701     // audio
702     movie_sound_pos += samplesperframe;
703
704     int first=1;
705     if(do_audio)
706     while(mp3_sound_pos<movie_sound_pos) {
707         // rawplay -s 44100 -f s16_le -c 2 samples.test 
708         short int* samples = sound->readFrame();
709         int s;
710         int c=0;
711         if(first) { //first run
712               tag = swf_InsertTag(NULL, ST_SOUNDSTREAMBLOCK);
713               swf_SetSoundStreamBlock(tag, samples, 1);
714         } else {
715               swf_SetSoundStreamBlock(tag, samples, 0);
716         }
717         
718         mp3_sound_pos += mp3_block_size;
719
720         if(mp3_sound_pos>=movie_sound_pos) { // last run
721             filesize += swf_WriteTag(file, tag);
722             swf_DeleteTag(tag);
723         }
724         first = 0;
725     }
726  
727     // video
728
729     CImage*img = vstream->GetFrame();
730     img->ToRGB();
731     U8*data = img->Data();
732     int bpp = img->Bpp();
733     int x,y;
734     int xx,yy;
735     int fs,ls;
736     SHAPE*s;
737     MATRIX m;
738     SRECT r;
739     RGBA rgb;
740
741     /* some movies have changing dimensions */
742     if(img->Width() != oldwidth ||
743        img->Height() != oldheight) {
744         printf("\n");
745         oldwidth = img->Width();
746         oldheight = img->Height();
747         width = (int)(oldwidth*scale);
748         height = (int)(oldheight*scale);
749         initdisplay(file);
750     }
751
752     for(yy=0;yy<yblocks;yy++)
753     for(xx=0;xx<xblocks;xx++) 
754     {
755         int x,y;
756         for(y=0;y<yblocksize;y++) {
757 #ifdef VERSION6
758             U8*mydata = img->At(oldheight-(int)((yy*yblocksize+y)*reziscale));
759 #else
760             U8*mydata = img->At((int)((yy*yblocksize+y)*reziscale));
761 #endif
762             for(x=0;x<xblocksize;x++) {
763                 blockbuffer[(y*xblocksize+x)*3+2] = mydata[((int)(((xx*xblocksize+x)*reziscale)))*3+0];
764                 blockbuffer[(y*xblocksize+x)*3+1] = mydata[((int)(((xx*xblocksize+x)*reziscale)))*3+1];
765                 blockbuffer[(y*xblocksize+x)*3+0] = mydata[((int)(((xx*xblocksize+x)*reziscale)))*3+2];
766             }
767         }
768         GfxBlock b;
769         b.data = blockbuffer;
770         b.len = xblocksize*yblocksize*3;
771         blocks[yy*xblocks+xx].compress(file, &b);
772     }
773
774     tag = swf_InsertTag(NULL, ST_SHOWFRAME);
775     filesize += swf_WriteTag(file, tag);
776     swf_DeleteTag(tag);
777
778     cache->newframe();
779
780     frame++;
781     if(frame == lastframe)
782         break;
783   }
784   delete sound;
785   printf("\n");
786   destroydisplay(file);
787
788   tag = swf_InsertTag(NULL, ST_END);
789   filesize += swf_WriteTag(file, tag);
790   swf_DeleteTag(tag);
791
792   close(file);
793
794   FILE*fi;
795   if(zlib)
796     fi=fopen("tmp.swf", "r+");
797   else
798     fi=fopen(outputfilename, "r+");
799
800   if(fi)
801   {
802        fseek(fi,4,SEEK_SET);
803        unsigned char f;
804        f = filesize      ;fwrite(&f,1,1,fi);
805        f = filesize >> 8 ;fwrite(&f,1,1,fi);
806        f = filesize >> 16;fwrite(&f,1,1,fi);
807        f = filesize >> 24;fwrite(&f,1,1,fi);
808        fclose(fi);
809   }
810
811   if(zlib) {
812       char buffer[1024];
813       snprintf(buffer, 1024, "swfcombine -dz __tmp__.swf -o %s", outputfilename);
814       printf("%s\n", buffer);
815       system(buffer);
816       sprintf(buffer, "rm __tmp__.swf");
817       printf("%s\n", buffer);
818       system(buffer);
819   }
820   
821   return 0;
822 }
823