fixed framerate calculation.
[swftools.git] / src / jpeg2swf.c
1 /* jpeg2swf.c
2
3    JPEG to SWF converter tool
4
5    Part of the swftools package.
6
7    Copyright (c) 2001 Rainer Böhme <rfxswf@reflex-studio.de>
8    Copyright (c) 2002,2003 Matthias Kramm <kramm@quiss.org>
9  
10    This program is free software; you can redistribute it and/or modify
11    it under the terms of the GNU General Public License as published by
12    the Free Software Foundation; either version 2 of the License, or
13    (at your option) any later version.
14
15    This program is distributed in the hope that it will be useful,
16    but WITHOUT ANY WARRANTY; without even the implied warranty of
17    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18    GNU General Public License for more details.
19
20    You should have received a copy of the GNU General Public License
21    along with this program; if not, write to the Free Software
22    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
23  
24 #include <stdio.h>
25 #include <math.h>
26 #include <fcntl.h>
27 #include <jpeglib.h>
28 #include "../lib/rfxswf.h"
29 #include "../lib/args.h"        // not really a header ;-)
30
31 #define MAX_INPUT_FILES 1024
32 #define VERBOSE(x) (global.verbose>=x)
33
34 struct {
35     int quality;
36     float framerate;
37     int max_image_width;
38     int max_image_height;
39     int force_width;
40     int force_height;
41     int nfiles;
42     int verbose;
43     char *outfile;
44     int mx;
45 } global;
46
47 typedef struct _image {
48     char *filename;
49     int quality;
50     int width;
51     int height;
52 } image_t;
53 image_t image[MAX_INPUT_FILES];
54
55 VIDEOSTREAM stream;
56
57 TAG *MovieStart(SWF * swf, float framerate, int dx, int dy)
58 {
59     TAG *t;
60     RGBA rgb;
61
62     memset(swf, 0x00, sizeof(SWF));
63
64     swf->fileVersion = 4;
65     swf->frameRate = (int)(256.0 * framerate);
66     swf->movieSize.xmax = dx * 20;
67     swf->movieSize.ymax = dy * 20;
68
69     t = swf->firstTag = swf_InsertTag(NULL, ST_SETBACKGROUNDCOLOR);
70
71     rgb.r = rgb.g = rgb.b = rgb.a = 0x00;
72     swf_SetRGB(t, &rgb);
73
74     if (global.mx) {
75         t = swf_InsertTag(t, ST_DEFINEVIDEOSTREAM);
76         swf_SetU16(t, 0xf00d);
77         swf_SetVideoStreamDefine(t, &stream, 65535, dx, dy);
78     }
79
80     return t;
81 }
82
83 int MovieFinish(SWF * swf, TAG * t, char *sname)
84 {
85     int handle, so = fileno(stdout);
86     t = swf_InsertTag(t, ST_END);
87
88     if ((!isatty(so)) && (!sname))
89         handle = so;
90     else {
91         if (!sname)
92             sname = "output.swf";
93         handle = open(sname, O_BINARY | O_RDWR | O_CREAT | O_TRUNC, 0666);
94     }
95     if (swf_WriteSWF(handle, swf)<0) 
96             fprintf(stderr, "Unable to write output file: %s\n", sname);
97     if (handle != so)
98         close(handle);
99
100     swf_FreeTags(swf);
101     return 0;
102 }
103
104 int getJPEG(char*filename, int* width, int* height, RGBA**pic2)
105 {
106     struct jpeg_decompress_struct cinfo;
107     struct jpeg_error_mgr jerr;
108     struct jpeg_source_mgr mgr;
109     int x,y;
110     FILE*f;
111     RGBA*pic,*js;
112     U8*buf;
113
114     if ((f=fopen(filename,"rb"))==NULL) {
115         fprintf(stderr, "rfxswf: file open error\n");
116         return 0;
117     }
118
119     cinfo.err = jpeg_std_error(&jerr);
120     jpeg_create_decompress(&cinfo); 
121     jpeg_stdio_src(&cinfo, f);
122     jpeg_read_header(&cinfo, TRUE);
123     jpeg_start_decompress(&cinfo);
124
125     pic = malloc(cinfo.output_width*cinfo.output_height*sizeof(RGBA));
126     buf = malloc(cinfo.output_width*4);
127     memset(pic, 255, cinfo.output_width*cinfo.output_height*sizeof(RGBA));
128     js = pic;
129
130     *width = cinfo.output_width;
131     *height = cinfo.output_height;
132     
133     for (y=0;y<cinfo.output_height;y++) {
134         int x;
135         jpeg_read_scanlines(&cinfo,&buf,1);
136
137         if(cinfo.out_color_space == JCS_GRAYSCALE) {
138             for(x=0;x<cinfo.output_width;x++) {
139                 js[x].r = js[x].g = js[x].b = buf[x];
140             }
141         } else if(cinfo.out_color_space == JCS_RGB) {
142             for (x=0;x<cinfo.output_width;x++)
143             { 
144                 js[x].r = buf[x*3+0];
145                 js[x].g = buf[x*3+1];
146                 js[x].b = buf[x*3+2];
147             }
148         } else if(cinfo.out_color_space == JCS_YCCK) {
149             //FIXME
150             fprintf(stderr, "Error: Can't convert YCCK to RGB.\n");
151             return -1;
152         } else if(cinfo.out_color_space == JCS_YCbCr) {
153             for(x=0;x<cinfo.output_width;x++) {
154                 int y = buf[x*3+0];
155                 int u = buf[x*3+1];
156                 int v = buf[x*3+1];
157                 js[x].r = y + ((360*(v-128))>>8);
158                 js[x].g = y - ((88*(u-128)+183*(v-128))>>8);
159                 js[x].b = y + ((455 * (u-128))>>8);
160             }
161         }
162         else if(cinfo.out_color_space == JCS_CMYK) 
163         { 
164             for(x=0;x<cinfo.output_width;x++) {
165                   int white = 255 - buf[x*4+3];
166                   js[x].r = white - ((buf[x*4]*white)>>8);
167                   js[x].g = white - ((buf[x*4+1]*white)>>8);
168                   js[x].b = white - ((buf[x*4+2]*white)>>8);
169             }
170         }
171         js += cinfo.output_width;
172     }
173
174     jpeg_finish_decompress(&cinfo);
175     jpeg_destroy_decompress(&cinfo);
176     
177     free(buf);
178     *pic2 = pic;
179     return 1;
180 }
181
182
183 int frame = 0;
184 TAG *MovieAddFrame(SWF * swf, TAG * t, char *sname, int quality, 
185                    int id, int width, int height)
186 {
187     SHAPE *s;
188     SRECT r;
189     MATRIX m;
190     int fs;
191
192
193     if(global.mx) {
194         int sizex, sizey;
195         RGBA * pic2;
196         SWFPLACEOBJECT obj;
197         int quant=0;
198         getJPEG(sname, &sizex, &sizey, &pic2);
199         if(sizex != stream.owidth || sizey != stream.oheight) {
200             fprintf(stderr, "All images must have the same dimensions if using -m!");
201             exit(1);
202         }
203
204         t = swf_InsertTag(t, ST_VIDEOFRAME);
205         swf_SetU16(t, 0xf00d);
206         quant = 1+(30-(30*quality)/100);
207         if(!(frame&127)) {
208             swf_SetVideoStreamIFrame(t, &stream, pic2, quant);
209         } else {
210             swf_SetVideoStreamPFrame(t, &stream, pic2, quant);
211         }
212
213         t = swf_InsertTag(t, ST_PLACEOBJECT2);
214         swf_GetPlaceObject(0, &obj);
215         if(frame==0) {
216             obj.depth = 1;
217             obj.id = 0xf00d;
218         } else {
219             obj.depth = 1;
220             obj.move = 1;
221             obj.ratio = frame;
222         }
223         swf_SetPlaceObject(t,&obj);
224
225         t = swf_InsertTag(t, ST_SHOWFRAME);
226     } else {
227         t = swf_InsertTag(t, ST_DEFINEBITSJPEG2);
228         swf_SetU16(t, id);              // id
229         swf_SetJPEGBits(t,sname,quality);
230
231         t = swf_InsertTag(t, ST_DEFINESHAPE);
232         swf_ShapeNew(&s);
233         swf_GetMatrix(NULL, &m);
234         m.sx = 20 * 0x10000;
235         m.sy = 20 * 0x10000;
236         fs = swf_ShapeAddBitmapFillStyle(s, &m, id, 0);
237         swf_SetU16(t, id + 1);  // id
238         r.xmin = r.ymin = 0;
239         r.xmax = width * 20;
240         r.ymax = height * 20;
241         swf_SetRect(t, &r);
242         swf_SetShapeHeader(t, s);
243         swf_ShapeSetAll(t, s, 0, 0, 0, fs, 0);
244         swf_ShapeSetLine(t, s, r.xmax, 0);
245         swf_ShapeSetLine(t, s, 0, r.ymax);
246         swf_ShapeSetLine(t, s, -r.xmax, 0);
247         swf_ShapeSetLine(t, s, 0, -r.ymax);
248         swf_ShapeSetEnd(t);
249
250         t = swf_InsertTag(t, ST_REMOVEOBJECT2);
251         swf_SetU16(t, 1);               // depth
252
253         t = swf_InsertTag(t, ST_PLACEOBJECT2);
254         swf_GetMatrix(NULL, &m);
255         m.tx = (swf->movieSize.xmax - (int) width * 20) / 2;
256         m.ty = (swf->movieSize.ymax - (int) height * 20) / 2;
257         swf_ObjectPlace(t, id + 1, 1, &m, NULL, NULL);
258
259         t = swf_InsertTag(t, ST_SHOWFRAME);
260     }
261     frame++;
262
263     return t;
264 }
265
266 int CheckInputFile(image_t* i, char *fname, char **realname)
267 {
268     struct jpeg_decompress_struct cinfo;
269     struct jpeg_error_mgr jerr;
270     FILE *f;
271     char *s = malloc(strlen(fname) + 5);
272     int width, height;
273
274     if (!s)
275         exit(2);
276     (*realname) = s;
277     strcpy(s, fname);
278
279     // Check whether file exists (with typical extensions)
280
281     if ((f = fopen(s, "rb")) == NULL) {
282         sprintf(s, "%s.jpg", fname);
283         if ((f = fopen(s, "rb")) == NULL) {
284             sprintf(s, "%s.jpeg", fname);
285             if ((f = fopen(s, "rb")) == NULL) {
286                 sprintf(s, "%s.JPG", fname);
287                 if ((f = fopen(s, "rb")) == NULL) {
288                     sprintf(s, "%s.JPEG", fname);
289                     if ((f = fopen(s, "rb")) == NULL)
290                         return -1;
291                 }
292             }
293         }
294     }
295
296     cinfo.err = jpeg_std_error(&jerr);
297     jpeg_create_decompress(&cinfo);
298     jpeg_stdio_src(&cinfo, f);
299     jpeg_read_header(&cinfo, TRUE);
300
301     width = cinfo.image_width;
302     height = cinfo.image_height;
303
304     i->width = width;
305     i->height = height;
306
307     // Get image dimensions
308
309     if (global.max_image_width < width)
310         global.max_image_width = width;
311     if (global.max_image_height < height)
312         global.max_image_height = height;
313
314     jpeg_destroy_decompress(&cinfo);
315     fclose(f);
316
317     return 0;
318 }
319
320 int args_callback_option(char *arg, char *val)
321 {
322     int res = 0;
323     if (arg[1])
324         res = -1;
325     else
326         switch (arg[0]) {
327         case 'q':
328             if (val)
329                 global.quality = atoi(val);
330             if ((global.quality < 1) ||(global.quality > 100)) {
331                 if (VERBOSE(1))
332                     fprintf(stderr,
333                             "Error: You must specify a valid quality between 1 and 100.\n");
334                 exit(1);
335             }
336             res = 1;
337             break;
338
339         case 'r':
340             if (val)
341                 global.framerate = atof(val);
342             if ((global.framerate < 1.0/256) || (global.framerate >= 256.0)) {
343                 if (VERBOSE(1))
344                     fprintf(stderr,
345                             "Error: You must specify a valid framerate between 1 and 10000.\n");
346                 exit(1);
347             }
348             res = 1;
349             break;
350
351         case 'o':
352             if (val)
353                 global.outfile = val;
354             res = 1;
355             break;
356
357         case 'v':
358             if (val)
359                 global.verbose = atoi(val);
360             res = 1;
361             break;
362
363         case 'X':
364             if (val)
365                 global.force_width = atoi(val);
366             res = 1;
367             break;
368
369         case 'm':
370             global.mx = 1;
371             return 0;
372
373         case 'Y':
374             if (val)
375                 global.force_height = atoi(val);
376             res = 1;
377             break;
378
379         case 'V':
380             printf("jpeg2swf - part of %s %s\n", PACKAGE, VERSION);
381             exit(0);
382
383         default:
384             res = -1;
385             break;
386         }
387
388     if (res < 0) {
389         if (VERBOSE(1))
390             fprintf(stderr, "Unknown option: -%s\n", arg);
391         exit(1);
392         return 0;
393     }
394     return res;
395 }
396
397 struct options_t options[] = { {"q", "quality"},
398 {"o", "output"},
399 {"m", "mx"},
400 {"r", "rate"},
401 {"v", "verbose"},
402 {"X", "width"},
403 {"Y", "height"},
404 {"V", "version"},
405 {0, 0},
406 };
407
408 int args_callback_longoption(char *name, char *val)
409 {
410     return args_long2shortoption(options, name, val);
411 }
412
413 int args_callback_command(char *arg, char *next)        // actually used as filename
414 {
415     char *s;
416     image_t* i = &image[global.nfiles];
417     if (CheckInputFile(i, arg, &s) < 0) {
418         if (VERBOSE(1))
419             fprintf(stderr, "Unable to open input file: %s\n", arg);
420         free(s);
421     } else {
422         i->filename = s;
423         i->quality = global.quality;
424         global.nfiles++;
425         if (global.nfiles >= MAX_INPUT_FILES) {
426             if (VERBOSE(1))
427                 fprintf(stderr, "Error: Too many input files.\n");
428             exit(1);
429         }
430     }
431     return 0;
432 }
433
434 void args_callback_usage(char *name)
435 {
436     printf
437         ("Usage: %s  [-options [value]] imagefiles[.jpg]|[.jpeg] [...]\n",
438          name);
439     printf
440         ("-o outputfile       --output   explicitly specify output file. (otherwise, output.swf will be used)\n");
441     printf
442         ("-m                  --mx       Use Flash MX H.263 compression (use for correlated images)\n");
443     printf
444         ("-q quality          --quality  Set compression quality (1-100, 1=worst, 100=best)\n");
445     printf
446         ("-r framerate        --rate     Set movie framerate (frames per second)\n");
447     printf
448         ("-o outputfile       --output   Set name for SWF output file\n");
449     printf
450         ("-X pixel            --width    Force movie width to pixel (default: autodetect)\n");
451     printf
452         ("-Y pixel            --height   Force movie height to pixel (default: autodetect)\n");
453     printf
454         ("-v level            --verbose  Set verbose level (0=quiet, 1=default, 2=debug)\n");
455     printf
456         ("-V                  --version  Print version information and exit\n");
457     printf
458         ("The following options can be set independently for each image: -q\n");
459 }
460
461
462 int main(int argc, char **argv)
463 {
464     SWF swf;
465     TAG *t;
466
467     memset(&global, 0x00, sizeof(global));
468
469     global.quality = 60;
470     global.framerate = 1.0;
471     global.verbose = 1;
472
473     processargs(argc, argv);
474
475     if (VERBOSE(2))
476         fprintf(stderr, "Processing %i file(s)...\n", global.nfiles);
477
478     t = MovieStart(&swf, global.framerate,
479                    global.force_width ? global.force_width : global.
480                    max_image_width,
481                    global.force_height ? global.force_height : global.
482                    max_image_height);
483
484     {
485         int i;
486         for (i = 0; i < global.nfiles; i++) {
487             if (VERBOSE(3))
488                 fprintf(stderr, "[%03i] %s (%i%%, 1/%i)\n", i,
489                         image[i].filename, image[i].quality);
490             t = MovieAddFrame(&swf, t, image[i].filename, image[i].quality,
491                               (i * 2) + 1, 
492                               image[i].width, image[i].height);
493             free(image[i].filename);
494         }
495     }
496
497     MovieFinish(&swf, t, global.outfile);
498
499     return 0;
500 }