implement --merge, --frame and --stack1 options.
[swftools.git] / src / swfcombine.c
1 /* swfcombine.c
2    main routine for swfcombine(1), which is a tool for merging .swf-files.
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 <stdlib.h>
11 #include <stdio.h>
12 #include <string.h>
13 #include "../lib/args.h"
14 #include "combine.h"
15 #include "settings.h"
16 #include "types.h"
17 #include "flash.h"
18 #include "../config.h"
19
20 char * master_filename = 0;
21 char * master_name = 0;
22 char * slave_filename[128];
23 char * slave_name[128];
24 int slave_movex[128];
25 int slave_movey[128];
26 float slave_scalex[128];
27 float slave_scaley[128];
28 char slave_isframe[128];
29 int numslaves = 0;
30
31 char * outputname = "output.swf";
32
33 int args_callback_option(char*name,char*val) {
34     if(!strcmp(name,"c"))
35     {
36         config.clip = 1;
37         return 0;
38     }
39     else if(!strcmp(name,"l"))
40     {
41         config.overlay = 1;
42         return 0;
43     }
44     else if (!strcmp(name, "o"))
45     {
46         outputname = val;
47         return 1;
48     }
49     else if (!strcmp(name, "v"))
50     {
51         config.loglevel ++;
52         return 0;
53     }
54     else if (!strcmp(name, "a"))
55     {
56         config.cat = 1;
57         return 0;
58     }
59     else if (!strcmp(name, "A"))
60     {
61         config.alloctest = 1;
62         return 0;
63     }
64     else if (!strcmp(name, "x"))
65     {
66         config.movex = atoi(val);
67         return 1;
68     }
69     else if (!strcmp(name, "y"))
70     {
71         config.movey = atoi(val);
72         return 1;
73     }
74     else if (!strcmp(name, "m"))
75     {
76         config.merge = 1;
77         return 0;
78     }
79     else if (!strcmp(name, "f"))
80     {
81         config.isframe = 1;
82         return 0;
83     }
84     else if (!strcmp(name, "d"))
85     {
86         config.dummy = 1;
87         return 0;
88     }
89     else if (!strcmp(name, "r"))
90     {
91         config.framerate = atoi(val)*256/100;
92         return 1;
93     }
94     else if (!strcmp(name, "X"))
95     {
96         config.sizex = atoi(val)*20;
97         config.hassizex = 1;
98         return 1;
99     }
100     else if (!strcmp(name, "Y"))
101     {
102         config.sizey = atoi(val)*20;
103         config.hassizey = 1;
104         return 1;
105     }
106     else if (!strcmp(name, "s"))
107     {
108         config.scalex = config.scaley = atoi(val)/100.0;
109         return 1;
110     }
111     else if (!strcmp(name, "t") || !strcmp(name, "T"))
112     {
113         if(master_filename) {
114             fprintf(stderr, "error with arguments. Try --help.\n");
115             exit(1);
116         }
117         config.stack = 1;
118         if(!strcmp(name,"T"))
119             config.stack1 = 1;
120         master_filename = "__none__";
121         return 0;
122     }
123     else if (!strcmp(name, "V"))
124     {   
125         printf("swfcombine - part of %s %s\n", PACKAGE, VERSION);
126         exit(0);
127     }
128     else 
129     {
130         fprintf(stderr, "Unknown option: -%s\n", name);
131         exit(1);
132     }
133 }
134
135 struct options_t options[] =
136 {{"o","output"},
137  {"s","scale"},
138  {"d","dummy"},
139  {"x","xpos"},
140  {"y","ypos"},
141  {"X","width"},
142  {"Y","height"},
143  {"r","rate"},
144  {"f","frame"},
145  {"l","overlay"},
146  {"m","merge"},
147  {"t","stack"},
148  {"T","stack1"},
149  {"v","verbose"},
150  {"V","version"},
151  {"c","clip"},
152  {"a","cat"},
153  {0,0}
154 };
155
156 int args_callback_longoption(char*name,char*val) {
157     return args_long2shortoption(options, name, val);
158 }
159
160 int args_callback_command(char*name, char*val) {
161     char*myname = strdup(name);
162     char*filename;
163     filename = strchr(myname, '=');
164     if(filename) {
165         *filename = 0;
166         filename++;
167     } else {
168         // argument has no explicit name field. guess one from the file name
169         char*path = strrchr(myname, '/');
170         char*ext = strrchr(myname, '.');
171         if(!path) path = myname;
172         else path ++;
173         if(ext) *ext = 0;
174         myname = path;
175         filename = name;
176     }
177
178     if(!master_filename) {
179         master_filename = filename;
180         master_name = myname;
181     } else {             
182         logf("<verbose> slave entity %s (named \"%s\")\n", filename, myname);
183
184         slave_filename[numslaves] = filename;
185         slave_name[numslaves] = myname;
186         slave_movex[numslaves] = config.movex;
187         slave_movey[numslaves] = config.movey;
188         slave_scalex[numslaves] = config.scalex;
189         slave_scaley[numslaves] = config.scaley;
190         slave_isframe[numslaves] = config.isframe; 
191         config.isframe = 0;
192         config.movex = config.movey = 0;
193         config.scalex = config.scaley = 1.0;
194         numslaves ++;
195     }
196     return 0;
197 }
198
199 void args_callback_usage(char*name)
200 {
201     printf("Usage: %s [-rXYomlcv] [-f] masterfile] [-xysf] [(name1|#id1)=]slavefile1 .. [-xysf] [(nameN|#idN)=]slavefileN\n", name);
202     printf("OR:    %s [-rXYomv] --stack[1] [-xysf] [(name1|#id1)=]slavefile1 .. [-xysf] [(nameN|#idN)=]slavefileN\n", name);
203     printf("OR:    %s [-rXYov] --cat [-xysf] [(name1|#id1)=]slavefile1 .. [-xysf] [(nameN|#idN)=]slavefileN\n", name);
204     printf("OR:    %s [-rXYomlcv] --dummy [-xys] [file]\n", name);
205     printf("\n");
206     printf("-o outputfile       --output    explicitly specify output file. (otherwise, output.swf will be used\n");
207     printf("-t                  --stack     place each slave in a seperate frame (no master movie\n");
208     printf("-T                  --stack1    place each slave in the first frame (no master movie\n");
209     printf("-m                  --merge     Don't store the slaves in Sprites/MovieClips\n");
210     printf("-a                  --cat       concatenate all slave files (no master movie\n");
211     printf("-l                  --overlay   Don't remove any master objects, only overlay new objects\n");
212     printf("-c                  --clip      Clip the slave objects by the corresponding master objects\n");
213     printf("-v                  --verbose   Use more than one -v for greater effect \n");
214     printf("-d                  --dummy     Don't require slave objects \n");
215     printf("-f                  --frame     The following identifier is a frame or framelabel, not an id or objectname\n");
216     printf("-x xpos             --movex     x Adjust position of slave by xpos twips (1/20 pixel\n");
217     printf("-y ypos             --movey     y Adjust position of slave by ypos twips (1/20 pixel\n");
218     printf("-s scale            --scale     Adjust size of slave by scale%\n");
219     printf("-r framerate        --rate      Set movie framerate (100 frames/sec\n");
220     printf("-X width            --width     Force movie width to scale (default: use master width (not with -t\n");
221     printf("-Y height           --height    Force movie height to scale (default: use master height (not with -t\n");
222 }
223
224 /* read a whole file in memory */
225 char* fi_slurp(FILE*fi, unsigned int * setlength)
226 {
227     char * mem;
228     long long int length; //;)  
229     long long int pos = 0;
230     fseek(fi,0,SEEK_END);
231     length = ftell(fi);
232     fseek(fi,0,SEEK_SET);
233     if(!length)
234         return 0;
235     mem = malloc(length);
236     if(!mem)
237         return 0;
238     while(!feof(fi))
239     {
240         pos += fread(&mem[pos], 1, 65536, fi);
241     }
242     if (setlength) 
243         *setlength = length;
244     return mem;
245 }
246
247 void fi_dump(FILE*fi, void*_mem, int length)
248 {
249     char*mem = (char*)_mem;
250     int pos = 0;
251     while(pos < length)
252     {
253         int size = 65536;
254         if (size > (length - pos))
255                 size = (length - pos);
256         pos += fwrite(&mem[pos], 1, size, fi);
257     }
258 }
259
260 void makestackmaster(u8**masterdata, int*masterlength)
261 {
262     u8 head[] = {'F','W','S'};
263     u8 *pos;
264     u32 * fixpos;
265     int t;
266     struct RECT box;
267     int strlength = 0;
268     int fileversion = 1;
269
270     /* scan all slaves for bounding box */
271     for(t=0;t<numslaves;t++)
272     {
273         FILE*fi=fopen(slave_filename[t],"rb");
274         u8 data[256];
275         int ret;
276         struct flash_header head;
277         strlength += strlen(slave_name[t]) + 9;
278         if(!fi) {
279             logf("<fatal> Couldn't open %s.", slave_filename[t]);
280             exit(1);
281         }
282         ret = fread(data,1,256,fi);
283         if(ret < 13) {
284             logf("<fatal> File %s is to small (%d bytes)", slave_filename[t], ret);
285             exit(1);
286         }
287         swf_init(data,256);
288         head = swf_read_header();
289         logf("<verbose> File %s has bounding box %d:%d:%d:%d\n",
290                 slave_filename[t], 
291                 head.boundingBox.x1, head.boundingBox.y1,
292                 head.boundingBox.x2, head.boundingBox.y2);
293         if(head.version > fileversion)
294             fileversion = head.version;
295         if(!t)
296             box = head.boundingBox;
297         else {
298             if(head.boundingBox.x1 < box.x1)
299                 box.x1 = head.boundingBox.x1;
300             if(head.boundingBox.y1 < box.y1)
301                 box.y1 = head.boundingBox.y1;
302             if(head.boundingBox.x2 > box.x2)
303                 box.x2 = head.boundingBox.x2;
304             if(head.boundingBox.y2 > box.y2)
305                 box.y2 = head.boundingBox.y2;
306         }
307         logf("<verbose> New master bounding box is %d:%d:%d:%d\n",
308                 box.x1, box.y1,
309                 box.x2, box.y2);
310         fclose(fi);
311     }
312
313     /* we don't have a master, so we create one ourselves. */
314     /* (please notice the philosophical content) */
315     *masterlength = (numslaves + 1) * 32 + strlength;
316     *masterdata = (u8*)malloc(*masterlength);
317     pos = *masterdata;
318     memcpy(pos, head, sizeof(head));
319     pos += sizeof(head);
320     *pos++ = fileversion;
321     fixpos = (u32*)pos;
322     *(u32*)pos = 0x12345678; // to be overwritten
323     pos += 4;
324     writeRECT(&pos, &box);
325     *(u16*)pos = 0x2000; // framerate
326     pos += 2;
327     *(u16*)pos = numslaves;
328     pos += 2;
329     for(t=0;t<numslaves;t++)
330     {
331         char buf[128];
332         int namelen;
333
334         if(1) {
335             sprintf(buf, "Frame%02d", t);
336             slave_name[t] = strdup(buf);
337         } 
338         namelen = strlen(slave_name[t]);
339
340         *(u16*)&pos[0] = (u16)(TAGID_DEFINESPRITE<<6) + 6;
341         *(u16*)&pos[2] = t+1; //ID
342         *(u16*)&pos[4] = 0; // Frames
343         *(u16*)&pos[6] = 0; // TAG1
344         *(u16*)&pos[8] = (u16)(TAGID_PLACEOBJECT2<<6) + 6 + namelen;
345         *(u16*)&pos[10]= 34; //flags: id+name
346         *(u16*)&pos[11]= 1+t; // depth
347         *(u16*)&pos[13]= t+1; // id
348         sprintf(&pos[15],slave_name[t]);
349         pos += 15 + namelen + 1;
350         if(!config.stack1 || t == numslaves-1) {
351             *(u16*)&pos[0]= (u16)(TAGID_SHOWFRAME<<6) + 0;
352             pos += 2;
353         }
354         if(!config.stack)
355         if(t!=numslaves-1)
356         {
357             *(u16*)&pos[0]= (u16)(TAGID_REMOVEOBJECT2<<6) + 2;
358             *(u16*)&pos[2]= 1+t; // depth;
359             pos += 4;
360         }
361     }
362     *(u16*)pos = TAGID_END<<6 + 0;
363     *masterlength = pos - *masterdata;
364     *fixpos = *masterlength;
365 }
366
367 struct config_t config;
368 int main(int argn, char *argv[])
369 {
370     FILE*fi;
371     u8*masterdata;
372     unsigned int masterlength;
373     u8*slavedata;
374     unsigned int slavelength;
375     u8*newdata;
376     unsigned int newlength;
377     int t;
378
379     config.overlay = 0; 
380     config.antistream = 0; 
381     config.alloctest = 0;
382     config.cat = 0;
383     config.merge = 0;
384     config.clip = 0;
385     config.loglevel = 2; 
386     config.movex = 0;
387     config.movey = 0;
388     config.scalex = 1.0;
389     config.scaley = 1.0;
390     config.sizex = 0;
391     config.sizey = 0;
392     config.hassizex = 0;
393     config.hassizey = 0;
394     config.framerate = 0;
395     config.stack = 0;
396     config.stack1 = 0;
397     config.dummy = 0;
398
399     processargs(argn, argv);
400     initLog(0,-1,0,0,-1,config.loglevel);
401
402     if(config.merge && config.cat) {
403         logf("<error> Can't combine --cat and --merge");
404         exit(1);
405     }
406
407     if(config.stack) {
408
409         if(config.overlay) {
410             logf("<error> Can't combine -l and -t");
411             exit(1);
412         }
413         if(config.clip) {
414             logf("<error> Can't combine -c and -t");
415             exit(1);
416         }
417         logf("<verbose> (stacking) %d files found\n", numslaves);
418
419         makestackmaster(&masterdata,&masterlength);
420
421         logf("<verbose> Generated %d bytes of master data", masterlength);
422     }
423     else {
424         logf("<verbose> master entity %s (named \"%s\")\n", master_filename, master_name);
425         fi = fopen(master_filename, "rb");
426         if(!fi) {
427             fprintf(stderr, "Failed to open %s\n", master_filename);
428             return 1;
429         }
430         masterdata = fi_slurp(fi, &masterlength);
431         if(!masterdata) {
432             fprintf(stderr, "Failed to read from %s\n", master_filename);
433             return 1;
434         }
435         logf("<debug> Read %d bytes from masterfile\n", masterlength);
436         fclose(fi);
437     }
438
439     for(t=0;t<numslaves;t++) {
440             logf("<verbose> slave entity(%d) %s (%s \"%s\")\n", t+1, slave_filename[t], 
441                     slave_isframe[t]?"frame":"object", slave_name[t]);
442     }
443
444     if(config.dummy)
445     {
446         if(numslaves)
447         {
448             logf("<error> --dummy (-d) implies there are zero slave objects. You supplied %d.", numslaves);
449             exit(1);
450         }
451         numslaves = 1;
452         slave_filename[0] = "!!dummy!!";
453         slave_name[0] = "!!dummy!!";
454         slave_isframe[0] = 0;
455     }
456
457     if (config.alloctest)
458     {
459         int*bitmap = malloc(sizeof(int)*65536);
460         memset(bitmap, -1, 65536*sizeof(int));
461         memset(bitmap, 1, 101*sizeof(int));
462         swf_relocate(masterdata, masterlength, bitmap);
463         newdata = masterdata;
464         newlength = masterlength;
465         free(bitmap);
466     }
467     else
468     {
469         if (!numslaves)
470         {
471             logf("<error> You must have at least one slave entity.");
472             return 0;
473         }
474         for(t = 0; t < numslaves; t++)
475         {
476             config.movex = slave_movex[t];
477             config.movey = slave_movey[t];
478             config.scalex = slave_scalex[t];
479             config.scaley = slave_scaley[t];
480             config.isframe = slave_isframe[t];
481
482             logf("<notice> Combine [%s]%s and [%s]%s", master_name, master_filename,
483                     slave_name[t], slave_filename[t]);
484             if(!config.dummy)
485             {
486                 fi = fopen(slave_filename[t], "rb");
487                 if(!fi) {
488                     fprintf(stderr, "Failed to open %s\n", slave_filename[t]);
489                     return 1;
490                 }
491                 slavedata = fi_slurp(fi, &slavelength);
492                 if(!slavedata) {
493                     fprintf(stderr, "Failed to read from %s\n", slave_filename[t]);
494                     return 1;
495                 }
496                 logf("<debug> Read %d bytes from slavefile\n", slavelength);
497                 fclose(fi);
498             }
499             else
500             {
501                 slavedata = (u8*)malloc(16);
502                 slavedata[0] = 'F';
503                 slavedata[1] = 'W';
504                 slavedata[2] = 'S';
505                 slavedata[3] = 4; //version
506                 *(u32*)&slavedata[4] = 14; // length
507                 slavedata[8] = 0; // boundingbox
508                 *(u16*)&slavedata[9] = 0; // rate
509                 *(u16*)&slavedata[11] = 0; // count
510                 *(u16*)&slavedata[13] = 0; // end tag
511                 slavelength = 17;
512             }
513
514             newdata = combine(masterdata, masterlength, slave_name[t], slavedata, slavelength, &newlength);
515             if(!newdata) { 
516                 logf("<fatal> Aborting.");
517                 return 1;
518             }
519
520             free(masterdata);
521             masterdata = newdata;
522             masterlength = newlength;
523         }
524     }
525
526     logf("<debug> New File is %d bytes \n", newlength);
527     if(newdata && newlength) {
528         FILE*fi = fopen(outputname, "wb");
529         fi_dump(fi, newdata, newlength);
530         fclose(fi);
531     }
532     return 0;
533 }
534