updated/corrected documentation
[swftools.git] / src / swfextract.c
index 87797f4..a5f549a 100644 (file)
@@ -4,15 +4,27 @@
    Part of the swftools package.
    
    Copyright (c) 2001 Matthias Kramm <kramm@quiss.org>
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
 
-   This file is distributed under the GPL, see file COPYING for details */
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
 
 #include <unistd.h>
 #include <stdio.h>
 #include <fcntl.h>
 #include "../lib/rfxswf.h"
 #include "../lib/args.h"
-#include "reloc.h"
+#include "../lib/log.h"
 #ifdef HAVE_ZLIB_H
 #ifdef HAVE_LIBZ
 #include "zlib.h"
@@ -27,12 +39,15 @@ int verbose = 3;
 char* extractids = 0;
 char* extractframes = 0;
 char* extractjpegids = 0;
+char* extractfontids = 0;
 char* extractpngids = 0;
+char* extractsoundids = 0;
 char extractmp3 = 0;
 
 char* extractname = 0;
 
 char hollow = 0;
+char originalplaceobjects = 0;
 
 int numextracts = 0;
 
@@ -44,9 +59,12 @@ struct options_t options[] =
  {"i","id"},
  {"j","jpegs"},
  {"p","pngs"},
+ {"P","placeobject"},
  {"m","mp3"},
+ {"s","sound"},
  {"n","name"},
  {"f","frame"},
+ {"F","font"},
  {"V","version"},
  {0,0}
 };
@@ -98,10 +116,28 @@ int args_callback_option(char*name,char*val)
        extractjpegids = val;
        return 1;
     } 
+    else if(!strcmp(name, "F")) {
+       if(extractfontids) {
+           fprintf(stderr, "Only one --font argument is allowed. (Try to use a range, e.g. -s 1,2,3)\n");
+           exit(1);
+       }
+       numextracts++;
+       extractfontids = val;
+       return 1;
+    } 
+    else if(!strcmp(name, "s")) {
+       if(extractsoundids) {
+           fprintf(stderr, "Only one --sound argument is allowed. (Try to use a range, e.g. -s 1,2,3)\n");
+           exit(1);
+       }
+       numextracts++;
+       extractsoundids = val;
+       return 1;
+    } 
 #ifdef _ZLIB_INCLUDED_
     else if(!strcmp(name, "p")) {
        if(extractpngids) {
-           fprintf(stderr, "Only one --pngs argument is allowed. (Try to use a range, e.g. -p 1,2,3)\n");
+           fprintf(stderr, "Only one --png argument is allowed. (Try to use a range, e.g. -p 1,2,3)\n");
            exit(1);
        }
        numextracts++;
@@ -114,13 +150,17 @@ int args_callback_option(char*name,char*val)
        extractframes = val;
        return 1;
     }
+    else if(!strcmp(name, "P")) {
+       originalplaceobjects = 1;
+       return 0;
+    }
     else if(!strcmp(name, "w")) {
        hollow = 1;
        return 0;
     }
     else {
         printf("Unknown option: -%s\n", name);
-       return 0;
+       exit(1);
     }
 
     return 0;
@@ -134,16 +174,26 @@ void args_callback_usage(char*name)
     printf("Usage: %s [-v] [-n name] [-ijf ids] file.swf\n", name);
     printf("\t-v , --verbose\t\t\t Be more verbose\n");
     printf("\t-o , --output filename\t\t set output filename\n");
-    printf("\t-n , --name name\t\t instance name of the object to extract\n");
-    printf("\t-i , --id IDs\t\t\t ID of the object to extract\n");
-    printf("\t-j , --jpeg IDs\t\t\t IDs of the JPEG pictures to extract\n");
+    printf("\t-V , --version\t\t\t Print program version and exit\n\n");
+    printf("SWF Subelement extraction:\n");
+    printf("\t-n , --name name\t\t instance name of the object (SWF Define) to extract\n");
+    printf("\t-i , --id ID\t\t\t ID of the object, shape or movieclip to extract\n");
+    printf("\t-f , --frame frames\t\t frame numbers to extract\n");
+    printf("\t-w , --hollow\t\t\t hollow mode: don't remove empty frames\n"); 
+    printf("\t             \t\t\t (use with -f)\n");
+    printf("\t-P , --placeobject\t\t\t Insert original placeobject into output file\n"); 
+    printf("\t             \t\t\t (use with -i)\n");
+    printf("SWF Font/Text extraction:\n");
+    printf("\t-F , --font ID\t\t\t Extract font(s)\n");
+    printf("Picture extraction:\n");
+    printf("\t-j , --jpeg ID\t\t\t Extract JPEG picture(s)\n");
 #ifdef _ZLIB_INCLUDED_
-    printf("\t-p , --pngs IDs\t\t\t IDs of the PNG pictures to extract\n");
+    printf("\t-p , --pngs ID\t\t\t Extract PNG picture(s)\n");
 #endif
+    printf("\n");
+    printf("Sound extraction:\n");
     printf("\t-m , --mp3\t\t\t Extract main mp3 stream\n");
-    printf("\t-f , --frame frames\t\t frame numbers to extract\n");
-    printf("\t-w , --hollow\t\t\t hollow mode: don't remove empty frames (use with -f)\n");
-    printf("\t-V , --version\t\t\t Print program version and exit\n");
+    printf("\t-s , --sound ID\t\t\t Extract Sound(s)\n");
 }
 int args_callback_command(char*name,char*val)
 {
@@ -165,6 +215,7 @@ char used[65536];
 TAG*tags[65536];
 int changed;
 char * tagused;
+int extractname_id = -1;
 
 void idcallback(void*data)
 {
@@ -176,7 +227,7 @@ void idcallback(void*data)
 
 void enumerateIDs(TAG*tag, void(*callback)(void*))
 {
-    U8*data;
+/*    U8*data;
     int len = tag->len;
     if(tag->len>=64) {
        len += 6;
@@ -194,6 +245,13 @@ void enumerateIDs(TAG*tag, void(*callback)(void*))
        memcpy(&data[2], tag->data, tag->len);
     }
     map_ids_mem(data, len, callback);
+ */
+    int num = swf_GetNumUsedIDs(tag);
+    int *ptr = malloc(sizeof(int)*num);
+    int t;
+    swf_GetUsedIDs(tag, ptr);
+    for(t=0;t<num;t++)
+       callback(&tag->data[ptr[t]]);
 }
 
 void extractTag(SWF*swf, char*filename)
@@ -202,6 +260,7 @@ void extractTag(SWF*swf, char*filename)
     TAG*desttag;
     TAG*srctag;
     RGBA rgb;
+    SRECT objectbbox;
     char sprite;
     int f;
     int t;
@@ -220,11 +279,15 @@ void extractTag(SWF*swf, char*filename)
     rgb.b = mainb;
     swf_SetRGB(desttag,&rgb);
 
+    swf_GetRect(0, &objectbbox);
+
     do {
        changed = 0;
        for(t=0;t<65536;t++) {
           if(used[t] && !(used[t]&2)) {
-            if(tags[t]->id==ST_DEFINESPRITE) {
+            if(tags[t]==0) {
+                msg("<warning> ID %d is referenced, but never defined.", t);
+            } else if(tags[t]->id==ST_DEFINESPRITE) {
                 TAG*tag = tags[t];
                 while(tag->id != ST_END)
                 {
@@ -255,12 +318,15 @@ void extractTag(SWF*swf, char*filename)
            sprite = 1;
        if(swf_isDefiningTag(srctag)) {
            int id = swf_GetDefineID(srctag);
-           if(used[id]) 
+           if(used[id])  {
+               SRECT b;
                copy = 1;
+               b = swf_GetDefineBBox(srctag);
+               swf_ExpandRect2(&objectbbox, &b);
+           }
        } else 
-       if (((srctag->id == ST_PLACEOBJECT ||
-             srctag->id == ST_PLACEOBJECT2 ||
-             srctag->id == ST_STARTSOUND) && (used[swf_GetPlaceID(srctag)]&4) ) ||
+       if (((((srctag->id == ST_PLACEOBJECT || srctag->id == ST_PLACEOBJECT2) && originalplaceobjects)
+             || srctag->id == ST_STARTSOUND) && (used[swf_GetPlaceID(srctag)]&4) ) ||
              (swf_isPseudoDefiningTag(srctag) && used[swf_GetDefineID(srctag)]) ||
              (tagused[tagnum])) 
        {
@@ -286,12 +352,32 @@ void extractTag(SWF*swf, char*filename)
        srctag = srctag->next;
        tagnum ++;
     }
-    if(!extractframes && !hollow)
+    if(!extractframes && !hollow) {
+       if(!originalplaceobjects && (extractids||extractname_id>=0)) {
+           int t;
+           int s=0;
+           if((objectbbox.xmin|objectbbox.ymin|objectbbox.xmax|objectbbox.ymax)!=0)
+               newswf.movieSize = objectbbox;
+           if(extractname_id>=0) {
+               desttag = swf_InsertTag(desttag, ST_PLACEOBJECT2);
+               swf_ObjectPlace(desttag, extractname_id, extractname_id, 0,0,extractname);
+           } else {
+               for(t=0;t<65536;t++) {
+                   if(is_in_range(t, extractids)) {
+                       desttag = swf_InsertTag(desttag, ST_PLACEOBJECT2);
+                       swf_ObjectPlace(desttag, t, t, 0,0,0);
+                       s++;
+                       if(s==2)
+                           printf("warning! You should use the -P when extracting multiple objects\n");
+                   }
+               }
+           }
+       }
        desttag = swf_InsertTag(desttag,ST_SHOWFRAME);
-
+    }
     desttag = swf_InsertTag(desttag,ST_END);
     
-    f = open(filename, O_TRUNC|O_WRONLY|O_CREAT, 0644);
+    f = open(filename, O_TRUNC|O_WRONLY|O_CREAT|O_BINARY, 0644);
     if FAILED(swf_WriteSWF(f,&newswf)) fprintf(stderr,"WriteSWF() failed.\n");
     close(f);
 
@@ -304,9 +390,9 @@ void listObjects(SWF*swf)
     char first;
     int t;
     int frame = 0;
-    char*names[] = {"Shapes","MovieClips","JPEGs","PNGs","Sounds","Frames"};
+    char*names[] = {"Shapes","MovieClips","JPEGs","PNGs","Sounds","Fonts"};
     printf("Objects in file %s:\n",filename);
-    for(t=0;t<6;t++) {
+    for(t=0;t<sizeof(names)/sizeof(names[0]);t++) {
        tag = swf->firstTag;
        first = 1;
        while(tag) {
@@ -349,12 +435,11 @@ void listObjects(SWF*swf)
                sprintf(text,"%d", swf_GetDefineID(tag));
            }
            
-           if(t == 5 && (tag->id == ST_SHOWFRAME)) {
+           if(t == 5 && (tag->id == ST_DEFINEFONT || tag->id == ST_DEFINEFONT2)) {
                show = 1;
-               sprintf(text,"%d", frame);
-               frame ++;
+               sprintf(text,"%d", swf_GetDefineID(tag));
            }
-
+           
            if(show) {
                if(!first)
                    printf(", ");
@@ -368,6 +453,37 @@ void listObjects(SWF*swf)
        if(!first)
            printf("\n");
     }
+
+    if(frame)
+       printf("Frames: 0-%d\n", frame);
+    else
+       printf("Frames: 0\n");
+}
+
+void handlefont(SWF*swf, TAG*tag)
+{
+    SWFFONT* f=0;
+    U16 id;
+    char name[80];
+    char*filename = name;
+    int t;
+
+    id = swf_GetDefineID(tag);
+    sprintf(name, "font%d.swf", id);
+    if(numextracts==1) {
+       filename = destfilename;
+    }
+
+    swf_FontExtract(swf, id, &f);
+    if(!f) {
+       printf("Couldn't extract font %d\n", id);
+       return;
+    }
+    if(!f->layout)
+       swf_FontCreateLayout(f);
+
+    swf_WriteFont(f, filename);
+    swf_FontFree(f);
 }
 
 U8*jpegtables = 0;
@@ -410,39 +526,51 @@ int findjpegboundary(U8*data, int len)
 void handlejpeg(TAG*tag)
 {
     char name[80];
+    char*filename = name;
     FILE*fi;
-    sprintf(name, "pic%d.jpeg", GET16(tag->data));
+    
+    sprintf(name, "pic%d.jpg", GET16(tag->data));
+    if(numextracts==1) {
+       filename = destfilename;
+       if(!strcmp(filename,"output.swf"))
+           filename = "output.jpg";
+    }
     /* swf jpeg images have two streams, which both start with ff d8 and
        end with ff d9. The following code handles sorting the middle
        <ff d9 ff d8> bytes out, so that one stream remains */
     if(tag->id == ST_DEFINEBITS && tag->len>2 && jpegtables) {
-       fi = save_fopen(name, "wb");
+       fi = save_fopen(filename, "wb");
        fwrite(jpegtables, 1, jpegtablessize-2, fi); //don't write end tag (ff,d8)
        fwrite(&tag->data[2+2], tag->len-2-2, 1, fi); //don't write start tag (ff,d9)
        fclose(fi);
     }
-    if(tag->id == ST_DEFINEBITSJPEG2 && tag->len>2) {
+    else if(tag->id == ST_DEFINEBITSJPEG2 && tag->len>2) {
        int end = tag->len;
        int pos = findjpegboundary(&tag->data[2], tag->len-2);
        if(pos<0)
            return;
        pos+=2;
-       fi = save_fopen(name, "wb");
+       fi = save_fopen(filename, "wb");
        fwrite(&tag->data[2], pos-2, 1, fi);
        fwrite(&tag->data[pos+4], end-(pos+4), 1, fi);
        fclose(fi);
     }
-    if(tag->id == ST_DEFINEBITSJPEG3 && tag->len>6) {
+    else if(tag->id == ST_DEFINEBITSJPEG3 && tag->len>6) {
        U32 end = GET32(&tag->data[2])+6;
        int pos = findjpegboundary(&tag->data[6], tag->len-6);
        if(pos<0)
            return;
        pos+=6;
-       fi = save_fopen(name, "wb");
+       fi = save_fopen(filename, "wb");
        fwrite(&tag->data[6], pos-6, 1, fi);
        fwrite(&tag->data[pos+4], end-(pos+4), 1, fi);
        fclose(fi);
     }
+    else {
+       int id = GET16(tag->data);
+       fprintf(stderr, "Object %d is not a JPEG picture!\n",id);
+       exit(1);
+    }
 }
 
 #ifdef _ZLIB_INCLUDED_
@@ -509,6 +637,7 @@ static void png_end_chunk(FILE*fi)
 void handlelossless(TAG*tag)
 {
     char name[80];
+    char*filename = name;
     FILE*fi;
     int width, height;
     int crc;
@@ -534,8 +663,11 @@ void handlelossless(TAG*tag)
     make_crc32_table();
 
     if(tag->id != ST_DEFINEBITSLOSSLESS &&
-       tag->id != ST_DEFINEBITSLOSSLESS2)
-       return;
+       tag->id != ST_DEFINEBITSLOSSLESS2) {
+       int id = GET16(tag->data);
+       fprintf(stderr, "Object %d is not a PNG picture!\n",id);
+       exit(1);
+    }
 
     id =swf_GetU16(tag);
     format = swf_GetU8(tag);
@@ -558,11 +690,11 @@ void handlelossless(TAG*tag)
 //    if(format == 5) cols = swf_GetU32(tag) + 1;
     else cols = 0;
 
-    logf("<verbose> Width %d", width);
-    logf("<verbose> Height %d", height);
-    logf("<verbose> Format %d", format);
-    logf("<verbose> Cols %d", cols);
-    logf("<verbose> Bpp %d", bpp);
+    msg("<verbose> Width %d", width);
+    msg("<verbose> Height %d", height);
+    msg("<verbose> Format %d", format);
+    msg("<verbose> Cols %d", cols);
+    msg("<verbose> Bpp %d", bpp);
 
     datalen = (width*height*bpp/8+cols*8);
     do {
@@ -576,7 +708,7 @@ void handlelossless(TAG*tag)
        fprintf(stderr, "Zlib error %d (image %d)\n", error, id);
        return;
     }
-    logf("<verbose> Uncompressed image is %d bytes (%d colormap)", datalen, (3+alpha)*cols);
+    msg("<verbose> Uncompressed image is %d bytes (%d colormap)", datalen, (3+alpha)*cols);
     pos = 0;
     datalen2 = datalen;
     data2 = malloc(datalen2);
@@ -592,7 +724,12 @@ void handlelossless(TAG*tag)
     }
 
     sprintf(name, "pic%d.png", id);
-    fi = save_fopen(name, "wb");
+    if(numextracts==1) {
+       filename = destfilename;
+       if(!strcmp(filename,"output.swf"))
+           filename = "output.png";
+    }
+    fi = save_fopen(filename, "wb");
     fwrite(head,sizeof(head),1,fi);     
 
     png_start_chunk(fi, "IHDR", 13);
@@ -651,7 +788,7 @@ void handlelossless(TAG*tag)
        fprintf(stderr, "zlib error in pic %d\n", id);
        return;
     }
-    logf("<verbose> Compressed data is %d bytes", datalen2);
+    msg("<verbose> Compressed data is %d bytes", datalen2);
     png_start_chunk(fi, "IDAT", datalen2);
     png_write_bytes(fi,data2,datalen2);
     png_end_chunk(fi);
@@ -677,18 +814,18 @@ void handlesoundstream(TAG*tag)
        case ST_SOUNDSTREAMHEAD:
            if((tag->data[1]&0x30) == 0x20) { //mp3 compression
                mp3file = fopen(filename, "wb");
-               logf("<notice> Writing mp3 data to %s",filename);
+               msg("<notice> Writing mp3 data to %s",filename);
            }
            else
-               logf("<error> Soundstream is not mp3");
+               msg("<error> Soundstream is not mp3");
        break;
        case ST_SOUNDSTREAMHEAD2:
            if((tag->data[1]&0x30) == 0x20) {//mp3 compression
-               mp3file = fopen("mainstream.mp3", "wb");
-               logf("<notice> Writing mp3 data to %s",filename);
+               mp3file = fopen(filename, "wb");
+               msg("<notice> Writing mp3 data to %s",filename);
            }
            else
-               logf("<error> Soundstream is not mp3 (2)");
+               msg("<error> Soundstream is not mp3 (2)");
        break;
        case ST_SOUNDSTREAMBLOCK:
            if(mp3file)
@@ -697,6 +834,37 @@ void handlesoundstream(TAG*tag)
     }
 }
 
+void handledefinesound(TAG*tag)
+{
+    U8 flags;
+    U32 samples;
+    char buf[128];
+    char*filename = buf;
+    FILE*fi;
+    U16 id;
+    id = swf_GetU16(tag); //id
+    sprintf(buf, "sound%d.mp3", id);
+
+    if(numextracts==1) {
+       filename = destfilename;
+       if(!strcmp(filename,"output.swf"))
+           filename = "output.mp3";
+    }
+    flags = swf_GetU8(tag);
+    if((flags>>4)!=2) {
+       printf("Sorry, can only extract MP3 sounds. Sound %d is ADPCM or RAW.\n", id);
+       /* not mp3 */
+       return;
+    }
+    samples = swf_GetU32(tag);
+    
+    swf_GetU16(tag); //(only for mp3) numsamples_seek
+
+    fi = save_fopen(filename, "wb");
+    fwrite(&tag->data[tag->pos], tag->len - tag->pos, 1, fi);
+    fclose(fi);
+}
+
 int main (int argc,char ** argv)
 { 
     TAG*tag;
@@ -710,7 +878,7 @@ int main (int argc,char ** argv)
     processargs(argc, argv);
 
     if(!extractframes && !extractids && ! extractname && !extractjpegids && !extractpngids
-       && !extractmp3)
+       && !extractmp3 && !extractsoundids && !extractfontids)
        listavailable = 1;
 
     if(!filename)
@@ -720,7 +888,7 @@ int main (int argc,char ** argv)
     }
     initLog(0,-1,0,0,-1, verbose);
 
-    f = open(filename,O_RDONLY);
+    f = open(filename,O_RDONLY|O_BINARY);
 
     if (f<0)
     { 
@@ -786,7 +954,8 @@ int main (int argc,char ** argv)
        if(tag->id == ST_SOUNDSTREAMHEAD ||
           tag->id == ST_SOUNDSTREAMHEAD2 ||
           tag->id == ST_SOUNDSTREAMBLOCK) {
-           handlesoundstream(tag);
+           if(extractmp3)
+               handlesoundstream(tag);
        }
 
        if(tag->id == ST_JPEGTABLES)
@@ -799,9 +968,15 @@ int main (int argc,char ** argv)
                used[id] = 5;
                found = 1;
            }
+           if(extractfontids && is_in_range(id, extractfontids)) {
+               handlefont(&swf, tag);
+           }
            if(extractjpegids && is_in_range(id, extractjpegids)) {
                handlejpeg(tag);
            }
+           if(extractsoundids && is_in_range(id, extractsoundids)) {
+               handledefinesound(tag);
+           }
 #ifdef _ZLIB_INCLUDED_
            if(extractpngids && is_in_range(id, extractpngids)) {
                handlelossless(tag);
@@ -821,6 +996,7 @@ int main (int argc,char ** argv)
                found = 1;
                tagused[tagnum] = 1;
                depths[swf_GetDepth(tag)] = 1;
+               extractname_id = id;
            }
        }
        else if(tag->id == ST_SHOWFRAME) {