├── .gitignore ├── README.md ├── doc └── cn.md ├── input ├── type1 │ └── robot_t1@act1.fla └── type2 │ ├── robot_t2@act1.fla │ ├── robot_t2@act2.fla │ └── robot_t2_01.fla ├── run.py └── scripts ├── convert ├── convert.exe ├── dumper.py ├── exportFiles.jsfl ├── handleCombine.py ├── identify ├── identify.exe ├── main.jsfl └── stack.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Description 2 | ===== 3 | 4 | Convert Flash files to [EJOY 2D](https://github.com/cloudwu/ejoy2d/) texture format or XML format. 5 | 6 | 7 | Simple Start 8 | ====== 9 | * Install Flash CC. 10 | * Install TexturePacker and install its command line tool. 11 | * ```python run.py``` 12 | 13 | 14 | Arguments 15 | ==== 16 | 17 | python run.py [-i ] [-o ] [-x] [-t] 18 | 19 | 20 | -i 21 | Input folder 22 | 23 | -o 24 | Output folder 25 | 26 | -x 27 | Output files contain XML file 28 | 29 | -t 30 | Dump tree structure of folders 31 | 32 | -s 33 | Scale 34 | 35 | --dump-pic-usage 36 | Dump picture usage info for every flash file 37 | 38 | 39 | 40 | Platform 41 | ==== 42 | 43 | MacOS X 10.9 & Windows 7 x64/x32 44 | 45 | More information 46 | === 47 | 48 | [Readme CN](https://github.com/robinxb/flash-parser/blob/master/doc/cn.md) 49 | 50 | If you have any questions, please submit [issues](https://github.com/robinxb/flash-parser/issues) 51 | 52 | Licence 53 | ==== 54 | 55 | [WTFPL](http://en.wikipedia.org/wiki/WTFPL) 56 | -------------------------------------------------------------------------------- /doc/cn.md: -------------------------------------------------------------------------------- 1 | Flash-Parser 中文使用手册 2 | ==== 3 | 4 | 5 | 6 | 7 | 这个工具能做什么? 8 | ---- 9 | 10 | * 生成[EJOY 2D 使用的texture格式](https://github.com/cloudwu/ejoy2d/blob/master/doc/apicn.md#texture) [Example](https://github.com/robinxb/flash-parser/tree/master/output) 11 | 12 | * 生成XML文件 [Example1](https://github.com/robinxb/flash-parser/blob/master/output/type1/type1.xml) [Example2](https://github.com/robinxb/flash-parser/blob/master/output/type2/type2.xml) 13 | 14 | 15 | 准备工作 16 | ---- 17 | 18 | 1. 安装Flash CC Mac 或 Windows 版本. 19 | 2. 安装TexturePacker后安装其命令行工具。 确保在 cmd/shell 中输入 ```TexturePacker``` 不会出现Command not found. 20 | 21 | 运行参数解释 22 | ---- 23 | 24 | 25 | python run.py [-i ] [-o ] [-x] [-t] [-s ] [--extend-name=] [--with-png] 26 | 27 | 28 | #### [-i \] 29 | 30 | **input** 31 | 32 | 存放Flash文件的文件夹。 支持Linux/Win格式, 支持相对路径。 33 | 34 | ***默认参数为run.py脚本所在目录的input文件夹***,等同于`python run.py -i input` 35 | 36 | 例: 37 | 38 | python run.py -i ./ 39 | python run.py -i ../abc/ 40 | python run.py -i /User/Robin/FlashFiles 41 | python run.py -i E:\Files 42 | python run.py -i E:/Files\Flash/ 43 | 44 | #### [-o \] 45 | 46 | **output** 47 | 48 | 存放输出文件目录。此目录若不存在会自动生成。 49 | ***默认参数为run.py脚本所在目录的input文件夹***,等同于`python run.py -o output` 50 | 51 | #### [-x] 52 | 53 | ***xml*** 54 | 55 | 控制是否输出xml文件, 默认关闭即只输出 ppg/ppm/lua 文件。 56 | 57 | #### [-t] 58 | 59 | ***tree*** 60 | 61 | 保留input文件夹下的目录树结构.***默认不开启*** 62 | 63 | 64 | 例: 65 | 66 | flash文件存放的文件夹树结构为 67 | 68 | input 69 | ├── type1 70 | │   └── robot@act1.fla 71 | └── type2 72 | ├── robot@act1.fla 73 | ├── robot@act2.fla 74 | └── robot_01.fla 75 | 76 | 77 | 运行命令 ```python run.py -i input -o output``` 78 | 79 | 则输出至run.py 同级目录output (如没有则自动创建) 80 | 81 | 其文件夹树结构为: 82 | 83 | output 84 | ├── type1.1.pgm 85 | ├── type1.1.ppm 86 | ├── type1.lua 87 | ├── type1.xml 88 | ├── type2.1.pgm 89 | ├── type2.1.ppm 90 | ├── type2.lua 91 | └── type2.xml 92 | 93 | 若加上```-t```参数,则输出文件夹目录为 94 | 95 | output 96 | ├── type1 97 | │   ├── type1.1.pgm 98 | │   ├── type1.1.ppm 99 | │   └── type1.lua 100 | └── type2 101 | ├── type2.1.pgm 102 | ├── type2.1.ppm 103 | └── type2.lua 104 | 105 | 106 | 107 | #### [-s \] 108 | 109 | ***scale*** 110 | 111 | 输出图素与原图大小比。 112 | 113 | 例如想输出原图一半大小的资源:```-s 0.5``` 114 | 115 | #### [--extend-name=\] 116 | 117 | 自定义生成文件后缀名。 118 | 119 | 例 ```--extend-name=_hd``` 则生成```filename_hd.lua```, ```filename_hd.1.ppm```等。 120 | 121 | #### [--with-png] 122 | 123 | 生成png图片至output目录。带有与ppm/pgn文件相同的文件名。 124 | 125 | #### Flash文件名称及元件名的特殊含义 126 | 127 | ##### 文件名 128 | 129 | 在```input/type2```文件夹下,可以看到```robot@act1.fla```和```robot@act2.fla```。 130 | 131 | ***文件名中的```@```表明 : 从```@```到后缀的```.```中间的内容为robot动作的action名。*** 132 | 133 | 如果你很熟悉ejoy2d的资源格式,想必不难理解action的用途。 134 | 135 | 你可以在lua文件中首先创建robot并指定其播放的动作: 136 | 137 | local sprite = require "ejoy2d.sprite" 138 | local obj = sprite.sprite('type1', 'robot') 139 | obj.action = "act1" 140 | --obj.action = 'act2' 141 | 142 | ##### Flash元件名 143 | 144 | 打开示例的```robot@act1.fla```文件,在库中你会发现一个名称为```@HeadMc```的元件。 145 | 146 | ***以```@```开头的元件会以其作为另一个树结构的根而保留,而不是完全摊平Flash的树结构*** 147 | 148 | 或许这样说很难理解,不妨看一下output/type2/type2.lua。 149 | 150 | ``` 151 | id = 0, 152 | name = "body.png", 153 | { tex = 1, src = {3, 3, 75, 3, 75, 113, 3, 113, }, screen = {16, 32, 1168, 32, 1168, 1792, 16, 1792, } }, 154 | type = "picture", 155 | 156 | id = 4, 157 | name = "wheel.png", 158 | { tex = 1, src = {161, 3, 235, 3, 235, 77, 161, 77, }, screen = {80, 80, 1264, 80, 1264, 1264, 80, 1264, } }, 159 | type = "picture", 160 | 161 | id = 9, 162 | type = "animation", 163 | export = "robot@act2@HeadMc", 164 | component = { 165 | {id = 3}, 166 | }, 167 | { 168 | {{index = 0, mat = {1024, 0, 0, 1024, -256, -1001}},}, 169 | }, 170 | 171 | id = 11, 172 | type = "animation", 173 | export = "robot", 174 | component = { 175 | {id = 4}, 176 | {id = 1}, 177 | {id = 5}, 178 | {id = 7}, 179 | {id = 2}, 180 | {id = 0}, 181 | {id = 5}, 182 | {id = 1}, 183 | {id = 5}, 184 | {id = 7}, 185 | {id = 2}, 186 | {id = 7}, 187 | {id = 6}, 188 | {name = "robot@act2@HeadMc", id = 9}, 189 | {name = "robot@act1@HeadMc", id = 8}, 190 | }, 191 | { action = "act2", 192 | .....省略动画 193 | }, 194 | { action = "act1", 195 | .....省略动画 196 | }, 197 | }, 198 | 199 | ``` 200 | 201 | 可以看到 robot 动画包含了HeadMc这个组件, 引用了另一个animation(id = 9 和 8)。而其他组件都只是引用了picture类型。 202 | 203 | 由于我们有两个动画(act1和act2),所以就有两个部件(robot@act2@HeadMc 和 robot@act1@HeadMc) 204 | 205 | 锚点 206 | ---- 207 | 208 | 锚点目前只支持位图(bitmap)元件来定义。 209 | 210 | 关键字为```__anchor```(双下划线) 211 | 212 | 组件名为下划线前的所有字符。若无,则默认名为```anchor```。 213 | 214 | 例: 将```boss.png```更名为```boss__anchor.png```,最终的输出资源则不包含boss__anchor.png。Animation的component中,原```{id = xx}```将被替换为```{name = "boss"}```。 215 | 216 | 镜像图片裁剪 217 | ---- 218 | 219 | Flash中,若图片去掉后缀名后以```_UD```,```_LR```,```_C```结尾,则表明,该图片支持镜像。 220 | 221 | + ```_UD```: 水平对称轴,即上下对称。 222 | + ```_LR```: 垂直对称轴,即左右对称。 223 | + ```_C``` : 水平、垂直对称轴,即中心对称。 224 | 225 | 例如有个中心对称的圆, 名叫```circle.png```,将其改名为```circle_C.png```,则在最终输出的资源中,会只有1/4个圆。 226 | 227 | 关于色彩效果 228 | ---- 229 | 230 | Flash中,变色是在组件的色彩效果中进行的。如要使用变色,样式请使用"高级"。 231 | 232 | 面板上会显示如下: 233 | 234 | Alpha: 100% x A + 0 235 | 236 | 红 : 100% x R + 0 237 | 238 | 绿 : 100% x G + 0 239 | 240 | 蓝 : 100% x B + 0 241 | 242 | EJOY2D中,颜色变换可分为color 和 add,分别对应乘法和加法。 由于flash中的变色算法和引擎有偏差,所以可以采取以下两种形式来让一个图变为任何想要的颜色: 243 | 244 | + 图片为纯白,只用color(对应flash中的百分比)。若采用此种方法,请确保flash中```+```号后的值全为0。 245 | + 图片为纯黑,只用add,对应flash中百分比后附加值。若采用此种方法,请确保flash中```%```号前的值全为100。 246 | 247 | 注意事项: 248 | 249 | * 请确保color和add两种方式不同时使用。(Alpha可共用,比如用add做变色,但是修改Alpha的百分比) 250 | * Alpha只能在color中使用,也就是Flash中```Alpha: 100% x A + 0```里的百分比。 251 | 252 | 已知问题 253 | ---- 254 | 255 | Flash中的元件类型若为影片剪辑(Movie clip),在输出的时候会将其与循环选项设定为循环(Loop)的图形(Graphic)元件相同对待。影片剪辑应该是有独立时间轴的,效果上应等于循环选项设定为播放一次(Play Once)的图形(Graphic)元件。 256 | 257 | 其他 258 | ---- 259 | 260 | 如果需要帮助请在[Issues](https://github.com/robinxb/flash-parser/issues)提出。 261 | 262 | 生成的xml格式可以方便进行二次开发。生成xml文件的脚本经轻度修改即可剥离出来。 263 | -------------------------------------------------------------------------------- /input/type1/robot_t1@act1.fla: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinxb/flash-parser/42cf34678d263174d3c30b938df866189c2b6b20/input/type1/robot_t1@act1.fla -------------------------------------------------------------------------------- /input/type2/robot_t2@act1.fla: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinxb/flash-parser/42cf34678d263174d3c30b938df866189c2b6b20/input/type2/robot_t2@act1.fla -------------------------------------------------------------------------------- /input/type2/robot_t2@act2.fla: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinxb/flash-parser/42cf34678d263174d3c30b938df866189c2b6b20/input/type2/robot_t2@act2.fla -------------------------------------------------------------------------------- /input/type2/robot_t2_01.fla: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinxb/flash-parser/42cf34678d263174d3c30b938df866189c2b6b20/input/type2/robot_t2_01.fla -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import os 3 | import platform 4 | import shutil 5 | import time 6 | import sys 7 | import codecs 8 | import inspect 9 | import getopt 10 | import math 11 | import subprocess 12 | import argparse 13 | 14 | temp_dirs = [] 15 | 16 | argParser = argparse.ArgumentParser() 17 | argParser.add_argument("-i", "--input", help="input folder", type=str, default=None) 18 | argParser.add_argument("-o", "--output", help="output folder", type=str, default=None) 19 | argParser.add_argument("-x", "--xml", help="export xml", action="store_true", default=False) 20 | argParser.add_argument("-s", "--scale", help="scale the source images", type=float, default=1) 21 | argParser.add_argument("--with-png", help="output with the combined png", action="store_true", default=False) 22 | argParser.add_argument("--quiet", action="store_true", default=False) 23 | argParser.add_argument("--extend-name", help="extra string after name", type=str, default="") 24 | argParser.add_argument("--dump-pic-usage", help="dump png usage info to text", action="store_true", default=False) 25 | group = argParser.add_mutually_exclusive_group() 26 | group.add_argument("--tree", help="dump tree structure", action="store_true",default=False) 27 | group.add_argument("--single", help="export flash one by one", action="store_true", default=False) 28 | args = argParser.parse_args() 29 | 30 | def remove_temp_dirs_when_except(): 31 | print("===remove temp dirs===") 32 | for filepath in temp_dirs: 33 | shutil.rmtree(filepath) 34 | 35 | def show_exception_and_exit(exc_type, exc_value, tb): 36 | import traceback 37 | traceback.print_exception(exc_type, exc_value, tb) 38 | raw_input("run failed.") 39 | remove_temp_dirs_when_except(); 40 | sys.exit(-1) 41 | 42 | import struct 43 | import imghdr 44 | 45 | def get_image_size(fname): 46 | '''Determine the image type of fhandle and return its size. 47 | from draco''' 48 | with open(fname, 'rb') as fhandle: 49 | head = fhandle.read(24) 50 | if len(head) != 24: 51 | return 52 | if imghdr.what(fname) == 'png': 53 | check = struct.unpack('>i', head[4:8])[0] 54 | if check != 0x0d0a1a0a: 55 | return 56 | width, height = struct.unpack('>ii', head[16:24]) 57 | elif imghdr.what(fname) == 'gif': 58 | width, height = struct.unpack('H', fhandle.read(2))[0] - 2 71 | # We are at a SOFn block 72 | fhandle.seek(1, 1) # Skip `precision' byte. 73 | height, width = struct.unpack('>HH', fhandle.read(4)) 74 | except Exception: #IGNORE:W0703 75 | return 76 | else: 77 | return 78 | return width, height 79 | 80 | sys.excepthook = show_exception_and_exit 81 | 82 | this_file = inspect.getfile(inspect.currentframe()) 83 | DIR_PATH = os.path.abspath(os.path.dirname(this_file)) 84 | SEP = os.path.sep 85 | 86 | 87 | sys.path.append(DIR_PATH + SEP + 'scripts') 88 | import handleCombine as HC 89 | 90 | FLASH_ROOT = args.input 91 | OUTPUT_PATH = args.output 92 | 93 | OUTPUT_NAME = "flash" 94 | LEAVE_FILE = ['%s.1.ppm'%OUTPUT_NAME, '%s.1.pgm'%OUTPUT_NAME, '%s.lua'%OUTPUT_NAME, "%s.txt"%OUTPUT_NAME] 95 | if args.with_png: 96 | LEAVE_FILE.append('%s.1.png'%OUTPUT_NAME) 97 | 98 | if args.xml: 99 | LEAVE_FILE.append('combine.xml') 100 | 101 | global sysOpen 102 | TMP_FOLDER_NAME = "__tmp" 103 | SCRIPT_PATH = DIR_PATH + SEP + 'scripts' 104 | OUTPUT_PATH = OUTPUT_PATH or DIR_PATH + SEP + 'output' 105 | FLASH_ROOT = FLASH_ROOT or DIR_PATH + SEP + 'input' 106 | FLASH_ROOT = os.path.realpath(FLASH_ROOT) 107 | OUTPUT_PATH = os.path.realpath(OUTPUT_PATH) 108 | CONVERT_PATH = SCRIPT_PATH + SEP + 'convert ' 109 | 110 | if not os.path.exists(OUTPUT_PATH): 111 | os.makedirs(OUTPUT_PATH) 112 | 113 | class CmdError(Exception): 114 | def __init__(self, string): 115 | self.string = string 116 | 117 | def __str__(self): 118 | return repr(self.string) 119 | 120 | class MainTree(): 121 | def __init__(self, path): 122 | self.mainpath = path 123 | self.folders = {} 124 | self.files = [] 125 | self.tmpPath = self.mainpath + '/' + TMP_FOLDER_NAME 126 | temp_dirs.append(self.tmpPath) 127 | for root, dirs, files in os.walk(path): 128 | self.SaveFiles(files) 129 | self.ParseDirs(dirs) 130 | dirs[:] = [] 131 | 132 | def SaveFiles(self, fileTuple): 133 | for k in fileTuple: 134 | if k[-4:] == '.fla': 135 | self.files.append(self.mainpath + '/' + k) 136 | 137 | def ParseDirs(self, dirs): 138 | for k in dirs: 139 | self.folders[k] = MainTree(self.mainpath + '/' + k) 140 | 141 | def SetSingleExport(self): 142 | self.single_export = True 143 | 144 | def Export(self, forceBatch=False): 145 | self.Clean() 146 | 147 | if len(self.files) > 0: 148 | if args.single and not forceBatch: 149 | self.SingleExport() 150 | else: 151 | self.BatchExport() 152 | 153 | for (k,v) in self.folders.items(): 154 | v.Export() 155 | 156 | def BatchExport(self): 157 | if (not args.quiet): 158 | print('[info]Walking into %s'%self.mainpath) 159 | os.makedirs(self.tmpPath) 160 | self.CopyScript(self.tmpPath) 161 | 162 | # pngs 163 | if (not args.quiet): 164 | print ('[info]Export png') 165 | os.system(sysOpen + ' ' + self.tmpPath + SEP + 'exportFiles.jsfl') 166 | self.WaitJSDone(self.tmpPath + SEP + 'done', True) 167 | 168 | #TP 169 | self.PreHandleMirror() 170 | self.RemoveAnchorPng() 171 | self.TexturePacker() 172 | if os.path.isfile(self.tmpPath + SEP + "%s.png"%OUTPUT_NAME): 173 | self.ImageMagicka() 174 | self.WriteOriginImgSizeInfo() 175 | else: 176 | self.CopyScript(self.tmpPath, True) 177 | 178 | #xml 179 | os.system(sysOpen + ' ' + self.tmpPath + SEP + '/main.jsfl') 180 | self.WaitJSDone(self.tmpPath + SEP + 'done', True) 181 | 182 | self.Combine() 183 | self.CopyUsefulFiles() 184 | self.Clean() 185 | 186 | def SingleExport(self): 187 | groupFiles = [] 188 | os.makedirs(self.tmpPath) 189 | for fpath in self.files: 190 | fname = os.path.basename(fpath) 191 | file_dir_path = self.tmpPath + SEP + fname[:-4] 192 | if "@" in fname: 193 | groupName = fname.split('@')[0] 194 | if groupName not in groupFiles: 195 | groupFiles.append(groupName) 196 | groupDir = self.tmpPath + SEP + groupName 197 | if not os.path.isdir(groupDir): 198 | os.makedirs(groupDir) 199 | shutil.copy(fpath, self.tmpPath + SEP + groupName + SEP + fname) 200 | else: 201 | os.makedirs(file_dir_path) 202 | shutil.copy(fpath, file_dir_path + SEP + fname) 203 | tree = MainTree(file_dir_path) 204 | tree.SetSingleExport() 205 | tree.Export(True) 206 | for groupName in groupFiles: 207 | tree = MainTree(self.tmpPath + SEP + groupName) 208 | tree.SetSingleExport() 209 | tree.Export(True) 210 | self.Clean() 211 | 212 | def WaitJSDone(self, filepath, bRemove = False): 213 | while(not os.path.exists(filepath)): 214 | time.sleep(0.5) 215 | if bRemove: 216 | while os.path.exists(filepath): 217 | try: 218 | os.remove(filepath) 219 | except OSError: 220 | time.sleep(0.5) 221 | finally: 222 | return 223 | 224 | 225 | def CopyUsefulFiles(self): 226 | if args.with_png: 227 | os.rename(self.tmpPath + SEP + "%s.png"%OUTPUT_NAME, self.tmpPath + SEP + "%s.1.png"%OUTPUT_NAME) 228 | for filename in LEAVE_FILE: 229 | if os.path.exists(self.tmpPath + '/%s'%filename): 230 | name, ext = os.path.splitext(filename) 231 | else: 232 | continue 233 | if ext == ".ppm" or ext == ".pgm" or ext == ".png": 234 | ext = ".1" + ext 235 | ext = args.extend_name + ext 236 | dirname = os.path.dirname(self.tmpPath.replace('\\', '/')) 237 | names = dirname.split('/') 238 | output_filename = names[len(names) - 1] + ext 239 | if args.tree: 240 | path_tree = self.mainpath .replace(FLASH_ROOT, '') 241 | if not os.path.exists(OUTPUT_PATH + path_tree): 242 | os.makedirs(OUTPUT_PATH + path_tree) 243 | shutil.copy(self.tmpPath + '/%s'%filename, OUTPUT_PATH + path_tree + '/%s'%output_filename) 244 | else: 245 | shutil.copy(self.tmpPath + '/%s'%filename, OUTPUT_PATH + '/%s'%output_filename) 246 | 247 | 248 | def Clean(self): 249 | while os.path.exists(self.tmpPath): 250 | try: 251 | shutil.rmtree(self.tmpPath) 252 | except OSError: 253 | time.sleep(0.5) 254 | finally: 255 | return 256 | 257 | def Combine(self): 258 | bExist = False 259 | filepath = self.tmpPath + '/combine.xml' 260 | for root, dirs, files in os.walk(self.tmpPath): 261 | for k in files: 262 | if not k[-4:] == '.xml': 263 | continue 264 | if not bExist: 265 | bExist = True 266 | handle = codecs.open(filepath, 'a') 267 | handle.write('\n') 268 | handle.close() 269 | src = codecs.open(root + '/' +k, 'r') 270 | content = src.read() 271 | handle = codecs.open(filepath, 'a') 272 | handle.write(content) 273 | handle.write('\n') 274 | handle.close() 275 | break 276 | if bExist: 277 | handle = codecs.open(filepath, 'a') 278 | handle.write('\n') 279 | handle.close() 280 | self.hc = HC.Handler(filepath.replace('\\','/'), quiet = args.quiet) 281 | self.hc.Export(self.tmpPath.replace('\\','/') + '/%s.lua'%OUTPUT_NAME) 282 | if args.dump_pic_usage: 283 | self.hc.ExportDumpinfo(self.tmpPath.replace('\\','/') + '/%s.txt'%OUTPUT_NAME) 284 | 285 | def PreHandleMirror(self): 286 | self.originImgSize = {} 287 | tpath = self.tmpPath 288 | imgpath = self.tmpPath + '/singleimg' 289 | sysType = platform.system() 290 | if sysType == "Windows": 291 | tpath = tpath.replace('/','\\') 292 | imgpath = imgpath.replace('/','\\') 293 | files = os.listdir(imgpath) 294 | identify_path = SCRIPT_PATH + SEP + 'identify ' 295 | if sysType == "Windows": 296 | identify_path = SCRIPT_PATH + SEP + 'identify.exe ' 297 | for k in files: 298 | w, h = get_image_size(imgpath + '%s%s'%(os.path.sep ,k)) 299 | w, h = int(w), int(h) 300 | self.originImgSize[k] = '{"w": %s, "h":%s}'%(w, h) 301 | main_name, ext = os.path.splitext(k) 302 | bCrop = False 303 | if main_name[-3:] == '_UD': 304 | bCrop = True 305 | s_w, s_h = w, h / 2 306 | elif main_name[-3:] == '_LR': 307 | bCrop = True 308 | s_w, s_h = w / 2, h 309 | elif main_name[-2:] == '_C': 310 | bCrop = True 311 | s_w, s_h = w / 2, h / 2 312 | if bCrop: 313 | if not w % 2 == 0: 314 | s_w = s_w + 1 315 | if not h % 2 == 0: 316 | s_h = s_h + 1 317 | self.CropImage(imgpath, k, s_w, s_h) 318 | 319 | def CropImage(self, imgpath, filename, w, h): 320 | cmd = "%s %s -crop %sx%s+0+0 %s" 321 | cmd = cmd%( 322 | CONVERT_PATH, 323 | imgpath + os.path.sep + filename, 324 | w, 325 | h, 326 | imgpath + os.path.sep + filename, 327 | ) 328 | self.ExecuteCmd(cmd) 329 | 330 | def RemoveAnchorPng(self): 331 | imgpath = self.tmpPath + os.path.sep + 'singleimg' 332 | files = os.listdir(imgpath) 333 | for k in files: 334 | if k.find('__anchor') >= 0: 335 | os.remove(imgpath + os.path.sep + k) 336 | 337 | def ExecuteCmd(self, cmd): 338 | pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True, universal_newlines=True) 339 | output = "".join(pipe.stdout.readlines()) 340 | sts = pipe.returncode 341 | if sts is None: sts = 0 342 | if not sts == 0: 343 | self.Clean() 344 | raise CmdError, output 345 | sys.exit(-1) 346 | return sts, output 347 | 348 | def ImageMagicka(self): 349 | cmd = CONVERT_PATH + self.tmpPath + SEP + "%s.png "%OUTPUT_NAME + self.tmpPath + SEP + "%s.1.ppm"%OUTPUT_NAME 350 | cmd2 = CONVERT_PATH + self.tmpPath + SEP + "%s.png "%OUTPUT_NAME + ' -channel A -separate %s.1.pgm'%(self.tmpPath + SEP + OUTPUT_NAME) 351 | self.ExecuteCmd(cmd) 352 | self.ExecuteCmd(cmd2) 353 | 354 | def TexturePacker(self): 355 | tpath = self.tmpPath 356 | sysType = platform.system() 357 | if sysType == "Windows": 358 | tpath = tpath.replace('/','\\') 359 | 360 | cmd = ' '.join([ 361 | 'TexturePacker', 362 | '--algorithm MaxRects', 363 | '--maxrects-heuristics Best', 364 | '--pack-mode Best', 365 | '--scale %s'%args.scale, 366 | '--premultiply-alpha', 367 | '--sheet %s' %(tpath + os.path.sep + '%s.png'%OUTPUT_NAME), 368 | '--texture-format png', 369 | '--extrude 2', 370 | '--data %s' % (tpath + os.path.sep + '%s.json'%OUTPUT_NAME), 371 | '--format json', 372 | '--trim-mode Trim', 373 | '--disable-rotation', 374 | '--size-constraints AnySize', 375 | '--max-width 2048', 376 | '--max-height 2048', 377 | '--common-division-x 2', 378 | '--common-division-y 2', 379 | #'--shape-debug', 380 | '%s' % (tpath + os.path.sep + 'singleimg') 381 | ]) 382 | 383 | sts, out = self.ExecuteCmd(cmd) 384 | if sts == 0: 385 | if (not args.quiet): 386 | print('[info]TP success') 387 | 388 | def WriteOriginImgSizeInfo(self): 389 | content = "{" 390 | for k,v in self.originImgSize.items(): 391 | content += '"%s" : %s,\n'%(k, v) 392 | content += "}" 393 | handle = open(self.mainpath + '/' + TMP_FOLDER_NAME + '/originsize.json', 'w') 394 | handle.write(content) 395 | handle.close() 396 | 397 | def CopyScript(self, path, no_png_out=False): 398 | handle = open(SCRIPT_PATH + '/exportFiles.jsfl') 399 | content = handle.read() 400 | handle.close() 401 | 402 | header = "var publishFolder = '%s'; \n"%(self.mainpath.replace('\\','/')) 403 | footer = """batToDo(publishFolder); 404 | FLfile.write("file:///%s",FLfile.uriToPlatformPath(publishFolder) + "\\n");"""%(self.mainpath.replace('\\','/') + '/' + TMP_FOLDER_NAME + '/done') 405 | content = header + content + footer 406 | handle = open(self.mainpath + '/' + TMP_FOLDER_NAME + '/exportFiles.jsfl', 'w') 407 | handle.write(content) 408 | handle.close() 409 | 410 | handle = open(SCRIPT_PATH + '/main.jsfl') 411 | content = handle.read() 412 | handle.close() 413 | 414 | header = "" 415 | if not no_png_out: 416 | header = 'eval("var JSONFILE = " + FLfile.read("file:///%s/%s.json"));\n'%(self.tmpPath.replace('\\','/'), OUTPUT_NAME) 417 | header += 'eval("var ORIGIN_SIZE = " + FLfile.read("file:///%s/%s.json"));\n'%(self.tmpPath.replace('\\','/'), "originsize") 418 | else: 419 | header = 'eval("var JSONFILE = {};") \n' 420 | header += 'eval("var ORIGIN_SIZE = {};") \n' 421 | footer = "var publishFolder = '%s'; \n"%(self.mainpath.replace('\\','/')) 422 | footer += "var tmpPath = '%s';\n"%(self.tmpPath.replace('\\','/')) 423 | footer += """batToDo(publishFolder, tmpPath); 424 | FLfile.write("file:///%s",FLfile.uriToPlatformPath(publishFolder) + "\\n");"""%(self.mainpath.replace('\\','/') + '/' + TMP_FOLDER_NAME + '/done') 425 | content = header + content + footer 426 | handle = open(self.mainpath + '/' + TMP_FOLDER_NAME + '/main.jsfl', 'w') 427 | handle.write(content) 428 | handle.close() 429 | 430 | def Run(self): 431 | self.Export() 432 | 433 | 434 | def loadFiles(): 435 | root = MainTree(FLASH_ROOT) 436 | return root 437 | 438 | if __name__ == '__main__': 439 | sysType = platform.system() 440 | if sysType == "Darwin": 441 | sysOpen = "open" 442 | elif sysType == "Windows": 443 | sysOpen = "start" 444 | CONVERT_PATH = SCRIPT_PATH + SEP + 'convert.exe ' 445 | else: 446 | raise CmdError, 'System not support: %s'%sysType 447 | 448 | root = loadFiles() 449 | root.Run() 450 | 451 | 452 | -------------------------------------------------------------------------------- /scripts/convert: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinxb/flash-parser/42cf34678d263174d3c30b938df866189c2b6b20/scripts/convert -------------------------------------------------------------------------------- /scripts/convert.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinxb/flash-parser/42cf34678d263174d3c30b938df866189c2b6b20/scripts/convert.exe -------------------------------------------------------------------------------- /scripts/dumper.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import codecs 3 | 4 | SPACE = ' ' 5 | 6 | class Dumper(): 7 | def __init__(self, indent = 0): 8 | self.buffer = [] 9 | self.indent = indent 10 | self.__CreateIndentSpace() 11 | self.BeginFile() 12 | 13 | def __CreateIndentSpace(self): 14 | self.indent_space = SPACE * self.indent 15 | 16 | def BeginFile(self): 17 | self.buffer = ['return{\n'] 18 | 19 | def EndFile(self): 20 | self.buffer.append('}\n') 21 | 22 | def Dump(self, path): 23 | self.EndFile() 24 | handle = codecs.open(path, 'w', encoding='UTF-8') 25 | handle.write("".join(self.buffer)) 26 | 27 | def GetSpace(self): 28 | return self.indent_space 29 | 30 | def Oneline(self, tag, arg): 31 | if type(arg) == type(1): 32 | self.buffer.append('%s%s = %s,\n'%(self.indent_space, tag, arg)) 33 | else: 34 | self.buffer.append('%s%s = "%s",\n'%(self.indent_space, tag, arg)) 35 | 36 | def Append(self, str): 37 | self.buffer.append("%s%s\n"%(self.indent_space, str)) 38 | 39 | def ChildBegin(self, tag = None): 40 | if tag: 41 | self.buffer.append("%s%s = {\n"%(self.indent_space, tag)) 42 | else: 43 | self.buffer.append('%s{\n'%(self.indent_space)) 44 | self.indent += 1 45 | self.__CreateIndentSpace() 46 | 47 | 48 | def ChildEnd(self): 49 | self.indent -= 1 50 | self.__CreateIndentSpace() 51 | self.buffer += self.indent_space 52 | self.buffer += '},\n' 53 | -------------------------------------------------------------------------------- /scripts/exportFiles.jsfl: -------------------------------------------------------------------------------- 1 | function batToDo(folder) { 2 | fl.showIdleMessage(false) 3 | fl.closeAll(false) 4 | var files = FLfile.listFolder('file:///' + folder + "/*.fla", "files"); 5 | for (var i = 0; i < files.length; i++) { 6 | pub(folder, files[i]); 7 | } 8 | fl.showIdleMessage(true) 9 | } 10 | 11 | function pub(dir, file) { 12 | var t = 'file:///' + dir.replace(":", "|") + "/" + file 13 | var doc = fl.openDocument(t); 14 | var lib = fl.getDocumentDOM().library.items; 15 | for (var i = 0, len = lib.length; i < len; i++) { 16 | var item = lib[i]; 17 | if (item.itemType == 'bitmap' && !isUnused(item)) { 18 | var name = item.name, 19 | fixed_name = item.name.replace(/^\s+|\s+$/g, ""), 20 | ext_name = fixed_name.split('.').pop() 21 | if (ext_name != "png"){ 22 | fixed_name = fixed_name + ".png"; 23 | } 24 | if (fixed_name != name){ 25 | fl.trace("changing " + file + ':'+ item.name + '->' + fixed_name); 26 | item.name = fixed_name 27 | } 28 | item.exportToFile("file:///" + dir + '/__tmp/singleimg/' + item.name, 100) 29 | } 30 | } 31 | doc.save(true); 32 | doc.close(false); 33 | } 34 | 35 | function isUnused(item){ 36 | var arr = fl.getDocumentDOM().library.unusedItems; 37 | for (var i in arr){ 38 | if (item == arr[i]){ 39 | return true 40 | } 41 | } 42 | return false 43 | } -------------------------------------------------------------------------------- /scripts/handleCombine.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import codecs 3 | import dumper 4 | import sys 5 | import stack as ST 6 | 7 | try: 8 | import xml.etree.cElementTree as ET 9 | except ImportError: 10 | import xml.etree.ElementTree as ET 11 | 12 | EXPORT_NAME_CN = unicode("场景 1", "utf-8") 13 | EXPORT_NAME_EN = "Scene 1" 14 | 15 | RUN_IN_QUIET = False 16 | 17 | class Handler(): 18 | def __init__(self, file, quiet = False): 19 | global RUN_IN_QUIET 20 | RUN_IN_QUIET = quiet 21 | 22 | self.file = file 23 | self.dumper = dumper.Dumper() 24 | self.aniLib = {} 25 | self.picLib = {} 26 | self.labelLib = {} 27 | self.actionGroup = {} 28 | self.dumpinfo = {} 29 | handle = codecs.open(file, 'r') 30 | content = handle.read() 31 | handle.close() 32 | self.root = ET.fromstring(content) 33 | self.PreFind() 34 | 35 | def PreFind(self): #find the doc's root 36 | for doc in self.root.iterfind("document[@filename]"): 37 | for tl in doc: 38 | if tl.get('name') == EXPORT_NAME_CN or\ 39 | tl.get('name') == EXPORT_NAME_EN: 40 | if not RUN_IN_QUIET: 41 | print ('[info]Parsing %s.fla\t\twait ...'%doc.get('filename')) 42 | self.ParseTL(doc, tl, doc.get('filename')) 43 | self.CombineAction() 44 | self.MarkID() 45 | self.ExportDoc() 46 | 47 | def CombineAction(self): 48 | aniToRemove = [] 49 | for name, frames in self.aniLib.items(): 50 | if name.find('|') > -1: 51 | name = name[name.find('|')+1 :] 52 | idx = name.find('@') 53 | if idx < 1 or idx == len(name) - 1: 54 | continue 55 | ani = name[:idx] 56 | act = name[idx + 1 :] 57 | if act.find('@') > -1: #a component when filename contains @ 58 | continue 59 | if not RUN_IN_QUIET: 60 | print ('[info]action found\t\t%s\t\t%s'%(ani, act)) 61 | aniToRemove.append(name) 62 | if not self.actionGroup.get(ani): 63 | self.actionGroup[ani] = [] 64 | self.actionGroup[ani].append((act, frames)) 65 | for ani in aniToRemove: 66 | self.aniLib.pop(ani) 67 | 68 | def ParseTL(self, doc, tl, tlname = None): 69 | if not tlname: 70 | tlname = tl.get('name') 71 | frames = [] 72 | for i in xrange(int(tl.get('framecount'))): 73 | matStack = ST.Stack() 74 | colorStack = ST.Stack() 75 | frames.append([]) 76 | self.ParseFrame(doc, tl, frames[i], i, matStack, colorStack, True) 77 | self.aniLib[tlname] = frames 78 | 79 | def ParseFrame(self, doc, tl, db, iFrame, ms, cs, bNoLoop): 80 | docname = doc.get("filename") 81 | bIsEmpty = True 82 | for layer in tl: 83 | tmpFrame = iFrame 84 | fcount = int(layer.get('frameCount')) 85 | if not bNoLoop : 86 | tmpFrame = tmpFrame % fcount 87 | for frame in layer: 88 | startFrame = int(frame.get('startFrame')) 89 | duration = int(frame.get('duration')) 90 | if startFrame + duration <= tmpFrame: 91 | continue 92 | if frame.find('element') == None: 93 | break 94 | for element in frame: 95 | bIsEmpty = False 96 | if element.get('desc') or element.get('idStr'): #pic or label 97 | thisMS = ms.Clone() 98 | thisMS.Push(element.get('mat')) 99 | thisCS = cs 100 | color_mat = element.get('color') 101 | if color_mat: 102 | thisCS = cs.Clone() 103 | thisCS.Push(color_mat) 104 | db.append((element, thisMS.CalAllMat(), thisCS.CalAllColor())) 105 | if element.get('desc'): 106 | if element.get('name').find('__anchor') < 0: 107 | if not self.dumpinfo.get(docname, None): 108 | self.dumpinfo[docname] = [] 109 | ename = element.get("name") 110 | if not ename in self.dumpinfo[docname]: 111 | self.dumpinfo[docname].append(ename) 112 | self.AddPic(element) 113 | else: 114 | element.set('name', doc.get('filename') + '|' + element.get('idStr') + '[%s]'%element.get('string')) 115 | self.AddLabel(element) 116 | elif element.get('name')[:1] == '@': 117 | thisMS = ms.Clone() 118 | thisMS.Push(element.get('mat')) 119 | thisCS = cs 120 | color_mat = element.get('color') 121 | if color_mat: 122 | thisCS = cs.Clone() 123 | thisCS.Push(color_mat) 124 | db.append((element, thisMS.CalAllMat(), thisCS.CalAllColor())) 125 | ename = element.get('name') 126 | timeline = doc.find("Timeline[@name='%s']"%ename) 127 | if not timeline: 128 | timeline = doc.find("Timeline[@name='%s']"%(doc.get('filename') + "|" + ename)) 129 | assert(timeline) 130 | tlName = doc.get('filename') + "|" + ename 131 | timeline.set('name', tlName) 132 | element.set('nickname', tlName) 133 | self.ParseTL(doc, timeline) 134 | else: 135 | ename = element.get('name') 136 | timeline = doc.find("Timeline[@name='%s']"%ename) 137 | assert(timeline) 138 | thisMS = ms.Clone() 139 | thisMS.Push(element.get('mat')) 140 | thisCS = cs 141 | color_mat = element.get('color') 142 | if color_mat: 143 | thisCS = cs.Clone() 144 | thisCS.Push(color_mat) 145 | iFrameNext = tmpFrame 146 | loopType = element.get('loop') 147 | nextNoLoop = False 148 | if loopType == "single frame": 149 | iFrameNext = int(element.get('firstFrame')) 150 | else: 151 | if (iFrame - startFrame) == 0: 152 | iFrameNext = int(element.get('firstFrame')) 153 | else: 154 | iFrameNext = (iFrame - startFrame) + int(element.get('firstFrame')) 155 | if loopType == "play once": 156 | nextNoLoop = True 157 | self.ParseFrame(doc, timeline, db, iFrameNext, thisMS, thisCS, nextNoLoop) 158 | break 159 | if bIsEmpty: 160 | db = [] 161 | 162 | def ExportDumpinfo(self, filepath): 163 | import json 164 | with open(filepath, 'w') as f: 165 | json.dump(self.dumpinfo, f, indent=4) 166 | 167 | def AddPic(self, e): 168 | eName = e.get('name') 169 | assert(eName) 170 | if self.picLib.get(eName): 171 | return 172 | self.picLib[eName] = e.get('desc') 173 | 174 | def AddLabel(self, e): 175 | eName = e.get('name') 176 | assert(eName) 177 | if self.labelLib.get(eName): 178 | return 179 | alignType = e.get('align') 180 | align = None 181 | if alignType == "left": 182 | align = 0 183 | elif alignType == "right": 184 | align = 1 185 | else: 186 | align = 2 187 | color = e.get('textcolor').replace('#', '') 188 | color = '0xff' + color 189 | desc = 'align = %s, size = %s, width = %s, height = %s, color = %s'%(align, e.get('size'), e.get('width'), e.get('height'), color) 190 | self.labelLib[eName] = desc 191 | 192 | def MarkID(self): 193 | id = 0 194 | idtable = {} 195 | for name in self.picLib.keys(): 196 | if idtable.get(name): 197 | continue 198 | idtable[name] = id 199 | id += 1 200 | for name in self.labelLib.keys(): 201 | if idtable.get(name): 202 | continue 203 | idtable[name] = id 204 | id += 1 205 | for name in self.aniLib.keys(): 206 | if idtable.get(name): 207 | continue 208 | idtable[name] = id 209 | id += 1 210 | for name in self.actionGroup.keys(): 211 | if idtable.get(name): 212 | continue 213 | idtable[name] = id 214 | id += 1 215 | self.idTable = idtable 216 | 217 | def ExportDoc(self): 218 | self.ExportPng(self.dumper) 219 | self.ExportLabel(self.dumper) 220 | self.ExportNormalAni(self.dumper) 221 | self.ExportActionGroup(self.dumper) 222 | 223 | def ExportActionGroup(self, dp): 224 | for name, actions in self.actionGroup.items(): 225 | dp.ChildBegin() 226 | dp.Oneline('id', self.idTable[name]) 227 | dp.Oneline('type', 'animation') 228 | dp.Oneline('export', name) 229 | component = Component(self.idTable) 230 | for (action, frames) in actions: 231 | component.AddFrames(frames) 232 | dp.ChildBegin('component') 233 | for k in component.GetCArr(): 234 | cStr = "{" 235 | if k.find('[') >= 0 and k.find(']') >= 0 : 236 | fileName = k.split('|')[0] 237 | cName = k[ k.find('[') + 1: k.find(']') ] 238 | cStr += 'name = "%s", '%(fileName.split('@')[-1] + '@' + cName) 239 | elif k.find('@') >= 0: 240 | (fileName, subName) = k.split('|') 241 | cStr += 'name = "%s", '%(fileName.split('@')[-1] + subName) 242 | if k.find('__anchor') >= 0: 243 | anchorName = k[:k.find('__anchor')] 244 | if anchorName == '': 245 | anchorName = 'anchor' 246 | cStr += 'name = "%s"'%anchorName 247 | else: 248 | cStr += 'id = %d'%(self.idTable[k]) 249 | cStr += "}," 250 | dp.Append(cStr) 251 | dp.ChildEnd() 252 | for (action, frames) in actions: 253 | dp.Append('{ action = "%s",'%action) 254 | dp.indent += 1 255 | for f in frames: 256 | str = "{" 257 | for v in f: 258 | e = v[0] 259 | if e != None: 260 | mat = v[1] 261 | color, add = v[2][0],v[2][1] 262 | matStr = "mat = {%d, %d, %d, %d, %d, %d}"%(mat[0],mat[1],mat[2],mat[3],mat[4],mat[5]) 263 | colorStr = "color = %s"%(hex(color[0]<<24 | color[1]<<16 | color[2]<<8 | color[3]).rstrip("L")) 264 | additiveStr = "add = %s"%(hex(add[0]<<24 | add[1]<<16 | add[2]<<8 | add[3]).rstrip("L")) 265 | idx = component.GetIndex(e) 266 | str += "{index = %d, %s"%(idx, matStr) 267 | if colorStr != "color = 0xffffffff": 268 | str += ", %s"%colorStr 269 | if additiveStr != "add = 0xff000000": 270 | str += ", %s"%additiveStr 271 | str += "}," 272 | else: 273 | continue 274 | str += "}," 275 | dp.Append(str) 276 | component.NextFrame() 277 | dp.ChildEnd() 278 | dp.ChildEnd() 279 | 280 | def ExportPng(self, dp): 281 | for name, desc in self.picLib.items(): 282 | dp.ChildBegin() 283 | dp.Oneline('id', self.idTable[name]) 284 | dp.Oneline('name', name) 285 | dp.Append(desc + ',') 286 | dp.Oneline('type', 'picture') 287 | dp.ChildEnd() 288 | 289 | def ExportLabel(self, dp): 290 | for name, desc in self.labelLib.items(): 291 | dp.ChildBegin() 292 | string = "type = 'label', id = %d, "%self.idTable[name] 293 | string += desc 294 | dp.Append(string) 295 | # dp.Oneline('name', name) 296 | # dp.Append(desc + ',') 297 | dp.ChildEnd() 298 | 299 | def ExportNormalAni(self, dp): 300 | for name, frames in self.aniLib.items(): 301 | dp.ChildBegin() 302 | dp.Oneline('id', self.idTable[name]) 303 | dp.Oneline('type', 'animation') 304 | dp.Oneline('export', name) 305 | component = Component(self.idTable, frames) 306 | dp.ChildBegin('component') 307 | for k in component.GetCArr(): 308 | cStr = "{" 309 | if k.find('[') >= 0 and k.find(']') >= 0 : 310 | cName = k[ k.find('[') + 1 : k.find(']') ] 311 | cStr += 'name = "%s", '%cName 312 | elif k[:1] == "@": 313 | cStr += 'name = "%s", '%(k.split('@')[-1]) 314 | elif k.find('|') and k[k.find('|') + 1] == '@': 315 | cStr += 'name = "%s", '%(k.split('@')[-1]) 316 | if k.find('__anchor') >= 0: 317 | anchorName = k[:k.find('__anchor')] 318 | if anchorName == '': 319 | anchorName = 'anchor' 320 | cStr += 'name = "%s"'%anchorName 321 | else: 322 | cStr += 'id = %d'%(self.idTable[k]) 323 | cStr += "}," 324 | dp.Append(cStr) 325 | dp.ChildEnd() 326 | dp.ChildBegin() 327 | for f in frames: 328 | str = "{" 329 | for v in f: 330 | e = v[0] 331 | if e != None: 332 | mat = v[1] 333 | color, add = v[2][0],v[2][1] 334 | matStr = "mat = {%d, %d, %d, %d, %d, %d}"%(mat[0],mat[1],mat[2],mat[3],mat[4],mat[5]) 335 | colorStr = "color = %s"%(hex(color[0]<<24 | color[1]<<16 | color[2]<<8 | color[3]).rstrip("L")) 336 | additiveStr = "add = %s"%(hex(add[0]<<24 | add[1]<<16 | add[2]<<8 | add[3]).rstrip("L")) 337 | str += "{index = %d, %s"%(component.GetIndex(e), matStr) 338 | if colorStr != "color = 0xffffffff": 339 | str += ", %s"%colorStr 340 | if additiveStr != "add = 0xff000000": 341 | str += ", %s"%additiveStr 342 | str += "}," 343 | else: 344 | continue 345 | str += "}," 346 | dp.Append(str) 347 | component.NextFrame() 348 | dp.ChildEnd() 349 | dp.ChildEnd() 350 | 351 | def GetMat(self, e): 352 | mat = e.get('mat') 353 | assert(mat) 354 | mat = mat.split(',') 355 | matF = {} 356 | matF['a'] = mat[0] 357 | matF['b'] = mat[1] 358 | matF['c'] = mat[2] 359 | matF['d'] = mat[3] 360 | matF['tx'] = mat[4] 361 | matF['ty'] = mat[5] 362 | return matF 363 | 364 | 365 | def Export(self, path): 366 | self.dumper.Dump(path) 367 | 368 | class Component(): 369 | def __init__(self, idTable, frames = None): 370 | self.c = [] 371 | self.used = {} 372 | self.idTable = idTable 373 | if frames != None: 374 | self.AddFrames(frames) 375 | 376 | def AddFrames(self, frames): 377 | for frame in frames: 378 | for v in frame: 379 | if v[0] == None: 380 | continue 381 | self.GetIndex(v[0]) 382 | self.NextFrame() 383 | 384 | def GetCArr(self): 385 | return self.c 386 | 387 | def GetIndex(self, e): 388 | eName = e.get('nickname') or e.get('name') 389 | i = 0 390 | for k in self.c: 391 | if k != eName: 392 | i += 1 393 | continue 394 | if self.used.get(i): 395 | i += 1 396 | continue 397 | self.used[i] = True 398 | return i 399 | self.c.append(eName) 400 | self.used[i] = True 401 | return i 402 | 403 | def NextFrame(self): 404 | self.used = {} 405 | 406 | -------------------------------------------------------------------------------- /scripts/identify: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinxb/flash-parser/42cf34678d263174d3c30b938df866189c2b6b20/scripts/identify -------------------------------------------------------------------------------- /scripts/identify.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinxb/flash-parser/42cf34678d263174d3c30b938df866189c2b6b20/scripts/identify.exe -------------------------------------------------------------------------------- /scripts/main.jsfl: -------------------------------------------------------------------------------- 1 | 2 | String.prototype.repeat = function(count) { 3 | if (count < 1) return ''; 4 | var result = '', pattern = this.valueOf(); 5 | while (count > 1) { 6 | if (count & 1) result += pattern; 7 | count >>= 1, pattern += pattern; 8 | } 9 | return result + pattern; 10 | }; 11 | 12 | 13 | function Point(x, y, scale){ 14 | this.x = x; 15 | this.y = y; 16 | if (scale) { 17 | this.scale = scale 18 | this.Scale(scale) 19 | } 20 | } 21 | 22 | Point.prototype.Scale = function (s) { 23 | this.x = this.x * s; 24 | this.y = this.y * s; 25 | return this 26 | } 27 | 28 | Point.prototype.ToString = function (mul){ 29 | if (!mul){ 30 | mul = 16; 31 | } 32 | return this.x * mul + ', ' + this.y * mul + ', ' 33 | } 34 | 35 | Point.prototype.ChangePoint = function (x, y){ 36 | this.x = x; 37 | this.y = y; 38 | if (this.scale) { 39 | this.Scale(this.scale) 40 | } 41 | } 42 | 43 | Point.prototype.DeltaChangeX = function (dx){ 44 | this.ChangePoint(this.x + dx, this.y); 45 | } 46 | 47 | Point.prototype.DeltaChangeY = function (dy){ 48 | this.ChangePoint(this.x, this.y + dy); 49 | } 50 | 51 | function Square(x1, y1, x2, y2, x3, y3, x4, y4, scale){ 52 | this.p1 = new Point(x1, y1, scale); 53 | this.p2 = new Point(x2, y2, scale); 54 | this.p3 = new Point(x3, y3, scale); 55 | this.p4 = new Point(x4, y4, scale); 56 | } 57 | 58 | Square.prototype.ToString = function (mul){ 59 | return this.p1.ToString(mul) + this.p2.ToString(mul) + this.p3.ToString(mul) + this.p4.ToString(mul) 60 | } 61 | 62 | Square.prototype.Clone = function (){ 63 | return new Square( 64 | this.p1.x, 65 | this.p1.y, 66 | this.p2.x, 67 | this.p2.y, 68 | this.p3.x, 69 | this.p3.y, 70 | this.p4.x, 71 | this.p4.y, 72 | this.p1.scale 73 | ) 74 | } 75 | 76 | function endWith(s1,s2){ 77 | if(s1.length 1){ 90 | arr.length = len - 1; 91 | } 92 | return arr.join(''); 93 | } 94 | 95 | function isUnused(item){ 96 | var arr = fl.getDocumentDOM().library.unusedItems; 97 | for (var i in arr){ 98 | if (item == arr[i]){ 99 | return true 100 | } 101 | } 102 | return false 103 | } 104 | 105 | ORIGIN_SIZE.getSize = function (filename){ 106 | if (!ORIGIN_SIZE[filename]) { 107 | fl.trace("cant find orgin size ," + filename); 108 | return 109 | } 110 | return ORIGIN_SIZE[filename] 111 | } 112 | 113 | JSONFILE.findSrc = function (filename) { 114 | if (!JSONFILE.frames[filename]) { 115 | fl.trace("cant find " + filename); 116 | return 117 | } 118 | return JSONFILE.frames[filename]; 119 | } 120 | 121 | JSONFILE.getDesc = function (filename) { 122 | if (filename.indexOf('__anchor') >= 0){ 123 | return "anchor" 124 | } 125 | var file = JSONFILE.findSrc(filename); 126 | var originSize = ORIGIN_SIZE.getSize(filename) 127 | var s = file.frame; 128 | var bIsRotated = file.rotated; 129 | var trimSize = file.spriteSourceSize; 130 | var sourceSize = file.sourceSize; 131 | var x = s.x, 132 | y = s.y, 133 | w = s.w, 134 | h = s.h; 135 | var tx = trimSize.x, 136 | ty = trimSize.y; 137 | var scale = Number(JSONFILE.meta.scale) 138 | 139 | var sh = originSize.h, 140 | sw = originSize.w; 141 | 142 | var offset_w = sourceSize.w - trimSize.w, 143 | offset_h = sourceSize.h - trimSize.h; 144 | 145 | main_name = getFileName(filename); 146 | 147 | var odd_h = false, 148 | odd_w = false; 149 | if (sh % 2 != 0) { 150 | odd_h = true; 151 | } 152 | if (sw % 2 != 0) { 153 | odd_w = true; 154 | } 155 | 156 | if (endWith(main_name, '_C')){ 157 | sh = sh / 2; 158 | sw = sw / 2; 159 | }else if (endWith(main_name, '_LR')){ 160 | sw = sw / 2; 161 | }else if (endWith(main_name, '_UD')){ 162 | sh = sh / 2; 163 | } 164 | sh = Math.floor(sh); 165 | sw = Math.floor(sw); 166 | 167 | if (bIsRotated){ 168 | t = w; 169 | w = h; 170 | h = t; 171 | } 172 | 173 | var sq_src, sq_screen; 174 | if (!bIsRotated) { 175 | sq_src = new Square(x, y, x + w, y, x + w, y + h, x, y + h); 176 | } else { 177 | sq_src = new Square(x + w, y, x + w, y + h, x, y + h, x, y); 178 | } 179 | sw = sw - offset_w; 180 | sh = sh - offset_h; 181 | sq_screen = new Square( tx, ty, tx + sw, ty, tx + sw, ty + sh, tx, ty + sh); 182 | 183 | var str = '{ tex = 1, src = {' + sq_src.ToString(1) + '}, screen = {' + sq_screen.ToString()+ '} }'; 184 | if (endWith(main_name, '_LR')){ 185 | var sq_screen = new Square(tx + 2 * sw - offset_w, ty, tx + sw, ty, tx + sw, ty + sh, tx + 2 * sw - offset_w, ty + sh); 186 | if (odd_w) { 187 | sq_src.p2.DeltaChangeX(-1); 188 | sq_src.p3.DeltaChangeX(-1); 189 | } 190 | str += ',{ tex = 1, src = {' + sq_src.ToString(1) + '}, screen = {' + sq_screen.ToString() + '} }'; 191 | }else if (endWith(main_name, '_UD')){ 192 | var sq_screen = new Square(tx, 2 * sh + ty - offset_h, sw + tx, 2 * sh + ty - offset_h, sw + tx, sh + ty, tx, sh + ty); 193 | if (odd_h) { 194 | sq_src.p3.DeltaChangeY(-1); 195 | sq_src.p4.DeltaChangeY(-1); 196 | } 197 | str += ',{ tex = 1, src = {' + sq_src.ToString(1) + '}, screen = {' + sq_screen.ToString() + '} }'; 198 | }else if (endWith(main_name, '_C')){ 199 | var sq_screen = new Square(2 * sw + tx - offset_w, ty, sw + tx, ty, sw + tx, sh + ty, 2 * sw + tx - offset_w, sh + ty); 200 | if (odd_w) { 201 | var new_sq_src = sq_src.Clone(), 202 | new_sq_screen = sq_screen.Clone(); 203 | new_sq_src.p2.DeltaChangeX(-1); 204 | new_sq_src.p3.DeltaChangeX(-1); 205 | str += ',{ tex = 1, src = {' +new_sq_src.ToString(1) + '}, screen = {' + new_sq_screen.ToString() + '} }'; 206 | }else { 207 | str += ',{ tex = 1, src = {' + sq_src.ToString(1) + '}, screen = {' + sq_screen.ToString() + '} }'; 208 | } 209 | 210 | sq_screen = new Square(tx, 2 * sh + ty - offset_h, sw + tx, 2 * sh + ty - offset_h, sw + tx, sh + ty, tx, sh + ty); 211 | if (odd_h) { 212 | var new_sq_src = sq_src.Clone(), 213 | new_sq_screen = sq_screen.Clone(); 214 | new_sq_src.p3.DeltaChangeY(-1); 215 | new_sq_src.p4.DeltaChangeY(-1); 216 | str += ',{ tex = 1, src = {' +new_sq_src.ToString(1) + '}, screen = {' + new_sq_screen.ToString() + '} }'; 217 | }else { 218 | str += ',{ tex = 1, src = {' + sq_src.ToString(1) + '}, screen = {' + sq_screen.ToString() + '} }'; 219 | } 220 | sq_screen = new Square(2 * sw + tx - offset_w, 2 * sh + ty - offset_h, sw + tx, 2 * sh + ty - offset_h, sw + tx, sh + ty, 2 * sw + tx - offset_w, sh + ty); 221 | if (odd_w || odd_h) { 222 | var new_sq_src = sq_src.Clone(), 223 | new_sq_screen = sq_screen.Clone(); 224 | if (odd_w) { 225 | new_sq_src.p2.DeltaChangeX(-1); 226 | new_sq_src.p3.DeltaChangeX(-1); 227 | } 228 | if (odd_h) { 229 | new_sq_src.p3.DeltaChangeY(-1); 230 | new_sq_src.p4.DeltaChangeY(-1); 231 | } 232 | str += ',{ tex = 1, src = {' +new_sq_src.ToString(1) + '}, screen = {' + new_sq_screen.ToString() + '} }'; 233 | }else { 234 | str += ',{ tex = 1, src = {' + sq_src.ToString(1) + '}, screen = {' + sq_screen.ToString() + '} }'; 235 | } 236 | } 237 | return str; 238 | } 239 | 240 | function each(array, fn, bind) { 241 | if (!bind) { 242 | bind = this; 243 | } 244 | for (var i = 0, len = array.length; i < len; i++) { 245 | fn.call(bind, array[i], i); 246 | } 247 | } 248 | 249 | function reverseEach(array, fn, bind) { 250 | if (!bind) { 251 | bind = this; 252 | } 253 | for (var i = array.length - 1; i >= 0; i--) { 254 | fn.call(bind, array[i], i); 255 | } 256 | } 257 | 258 | function XML(indent) { 259 | this.buffer = []; 260 | this.space = '\t'; 261 | this.space_buffer = "\t" 262 | this.indent = indent ? indent : 0; 263 | this.tags = []; 264 | } 265 | 266 | XML.prototype.getargs = function (args) { 267 | var buf = ''; 268 | var stack = [] 269 | for (var p in args) { 270 | if (args[p] == undefined) { 271 | continue; 272 | } 273 | stack.push(p + '="' + args[p] + '"'); 274 | } 275 | buf += stack.join(' '); 276 | return buf; 277 | } 278 | 279 | XML.prototype.begin = function (tag, args) { 280 | this.buffer.push(this.space_buffer + '<' + tag + ' ' + this.getargs(args) + '>\n') 281 | this.indent += 1; 282 | this.space_buffer = this.space.repeat(this.indent) 283 | this.tags.push(tag); 284 | } 285 | 286 | XML.prototype.end = function () { 287 | this.indent -= 1; 288 | this.space_buffer = this.space.repeat(this.indent) 289 | this.buffer.push(this.space_buffer + '\n') 290 | } 291 | 292 | XML.prototype.content = function (buf) { 293 | this.buffer.push(this.space_buffer + buf) 294 | } 295 | 296 | XML.prototype.oneline = function (tag, args) { 297 | this.buffer.push(this.space_buffer + '<' + tag + " " + this.getargs(args) + '>' + '\n'); 298 | } 299 | 300 | XML.prototype.print = function () { 301 | fl.trace(this.buffer.join("")); 302 | } 303 | 304 | XML.prototype.export = function (path) { 305 | FLfile.write('file:///' + path, this.buffer.join("")); 306 | } 307 | 308 | 309 | function Timeline(timeline, xml) { 310 | this.timeline = timeline; 311 | this.xml = xml; 312 | this.layers = []; 313 | this.textItems = [] 314 | } 315 | 316 | Timeline.prototype.parse = function () { 317 | reverseEach(this.timeline.layers, function (l, index) { 318 | var layer = new Layer(this, l, index); 319 | layer.parse(); 320 | this.layers.push(layer); 321 | }, this); 322 | for (var tl in this.itemTimelines) { 323 | this.itemTimelines[tl].parse() 324 | } 325 | } 326 | 327 | Timeline.prototype.buildLib = function (library) { 328 | this.libItems = {}; 329 | this.itemTimelines = {}; 330 | each(library.items, function (item) { 331 | if (isUnused(item)) { 332 | return 333 | } 334 | if (item.itemType == 'bitmap') { 335 | this.libItems[item.name] = item; 336 | } else if (item.itemType == 'graphic') { 337 | this.libItems[item.name] = item; 338 | this.itemTimelines[item.name] = new Timeline(item.timeline, this.xml); 339 | } else if (item.itemType == 'movie clip') { // now same as graphic, more future to add. 340 | this.libItems[item.name] = item; 341 | this.itemTimelines[item.name] = new Timeline(item.timeline, this.xml); 342 | } 343 | }, this); 344 | } 345 | 346 | Timeline.prototype.export = function (tmpPath, filename) { 347 | filename = filename.replace(".fla", "") 348 | this.xml.begin('document', { 349 | 'filename': filename 350 | }); 351 | this.parseXML(); 352 | for (var tl in this.itemTimelines) { 353 | this.itemTimelines[tl].parseXML() 354 | } 355 | this.xml.end(); 356 | this.xml.export(tmpPath + '/' + filename + '.xml'); 357 | } 358 | 359 | Timeline.prototype.parseXML = function () { 360 | this.xml.begin('Timeline', { 361 | "name": this.timeline.name, 362 | 'framecount': this.timeline.frameCount 363 | }); 364 | each(this.layers, function (l) { 365 | l.parseXML(); 366 | }, this); 367 | this.xml.end(); 368 | } 369 | 370 | Timeline.prototype.addText = function (item, layerName){ 371 | for (var i in this.textItems) { 372 | if (item == this.textItems[i].obj) { 373 | fl.trace("return " + i) 374 | return i 375 | } 376 | } 377 | var idStr = this.timeline.name + '|' + layerName 378 | this.textItems.push(new TextObj(item, idStr)) 379 | return idStr 380 | } 381 | 382 | function TextObj(item, idStr){ 383 | this.obj = item 384 | this.idStr = idStr 385 | } 386 | 387 | function Layer(timeline, layer, index) { 388 | this.timeline = timeline; 389 | this.xml = timeline.xml; 390 | this.layer = layer; 391 | this.index = index 392 | this.frames = []; 393 | } 394 | 395 | var times = 0 396 | 397 | Layer.prototype.parse = function () { 398 | if (this.layer.frameCount > 1){ 399 | this.timeline.timeline.setSelectedLayers(Number(this.index)); 400 | this.timeline.timeline.convertToKeyframes(0, this.layer.frameCount - 1); 401 | } 402 | 403 | for (var index in this.layer.frames) { 404 | if (this.layer.frames[index].startFrame == index) { 405 | var frame = new Frame(this, this.layer.frames[index]); 406 | this.frames.push(frame); 407 | } 408 | } 409 | } 410 | 411 | Layer.prototype.parseXML = function () { 412 | this.xml.begin('layer', { 413 | 'name': this.layer.name, 414 | 'frameCount': this.layer.frameCount 415 | }); 416 | each(this.frames, function (f) { 417 | f.parseXML() 418 | }, this); 419 | this.xml.end(); 420 | } 421 | 422 | function Frame(layer, frame) { 423 | this.layer = layer; 424 | this.frame = frame; 425 | this.xml = layer.xml; 426 | } 427 | 428 | Frame.prototype.parseXML = function () { 429 | this.xml.begin('frame', { 430 | 'startFrame': this.frame.startFrame, 431 | 'duration': this.frame.duration 432 | }); 433 | each(this.frame.elements, function (e) { 434 | var mat = [e.matrix.a * 1024, e.matrix.b * 1024, e.matrix.c * 1024, e.matrix.d * 1024, e.matrix.tx * 16, e.matrix.ty * 16].join(','); 435 | // both graphic and movie clip are symbol 436 | // but movie clip does not have the firstFrame 437 | if (e.instanceType == 'symbol') { 438 | var aa = e.colorAlphaPercent, 439 | ab = e.colorAlphaAmount, 440 | ra = e.colorRedPercent, 441 | rb = e.colorRedAmount, 442 | ga = e.colorGreenPercent, 443 | gb = e.colorGreenAmount, 444 | ba = e.colorBluePercent, 445 | bb = e.colorBlueAmount; 446 | var bIsNormalColor = true 447 | if (("" + aa + ab + ra + rb + ga + gb + ba + bb) == "1000100010001000") { 448 | bIsNormalColor = false 449 | } 450 | this.xml.oneline('element', { 451 | 'name': e.libraryItem.name, 452 | 'mat': mat, 453 | 'firstFrame': e.firstFrame ? e.firstFrame : 0, 454 | 'color': bIsNormalColor ? [aa, ab, ra, rb, ga, gb, ba, bb].join(',') : undefined, 455 | 'loop': e.loop 456 | }); 457 | } else if (e.instanceType == 'bitmap') { 458 | var desc = JSONFILE.getDesc(e.libraryItem.name); 459 | this.xml.oneline('element', { 460 | 'name': e.libraryItem.name, 461 | 'mat': mat, 462 | 'desc': desc 463 | }); 464 | } else if (e.elementType == "text"){ 465 | var idStr = this.layer.timeline.addText(e, this.layer.layer.name) 466 | var mat = [e.matrix.a * 1024, e.matrix.b * 1024, e.matrix.c * 1024, e.matrix.d * 1024, (e.matrix.tx - (e.matrix.tx - e.left)) * 16, e.matrix.ty * 16].join(','); 467 | this.xml.oneline('element', { 468 | 'idStr' : idStr, 469 | 'mat': mat, 470 | 'string': e.getTextString(), 471 | 'length' : e.length, 472 | 'height' : e.height, 473 | 'width' : e.width, 474 | 'align' : e.getTextAttr('alignment'), 475 | 'size' : e.getTextAttr('size'), 476 | 'textcolor' : e.getTextAttr('fillColor') 477 | }); 478 | } 479 | }, this); 480 | this.xml.end(); 481 | } 482 | 483 | function batToDo(folder, tmpPath) { 484 | fl.showIdleMessage(false) 485 | fl.closeAll(false) 486 | var files = FLfile.listFolder('file:///' + folder + "/*.fla", "files"); 487 | for (var i = 0; i < files.length; i++) { 488 | pub(folder, files[i], tmpPath); 489 | } 490 | fl.showIdleMessage(true) 491 | } 492 | 493 | function pub(dir, file, tmpPath) { 494 | var doc = fl.openDocument('file:///' + dir.replace(":", "|") + "/" + file); 495 | var xml = new XML(1); 496 | var tl = new Timeline(fl.getDocumentDOM().getTimeline(), xml); 497 | tl.buildLib(fl.getDocumentDOM().library) 498 | tl.parse(); 499 | tl.export(tmpPath, file); 500 | doc.close(false); 501 | } 502 | 503 | -------------------------------------------------------------------------------- /scripts/stack.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import copy 3 | class Stack(): 4 | def __init__(self): 5 | self.stack=[]; 6 | self.top=-1; 7 | 8 | def Push(self,ele): 9 | self.stack.append(ele); 10 | self.top = self.top+1; 11 | 12 | def Pop(self): 13 | self.top = self.top-1; 14 | return self.stack.pop(); 15 | 16 | def IsEmpty(self): 17 | return self.top == -1; 18 | 19 | def Drop(self): 20 | self.stack = [] 21 | self.top = -1 22 | 23 | def Clone(self): 24 | r = Stack() 25 | r.stack = copy.deepcopy(self.stack) 26 | r.top = len(r.stack) - 1 27 | return r 28 | 29 | def CalAllMat(self): 30 | mat = [1024.0, 0.0, 0.0, 1024.0, 0.0, 0.0] 31 | for i in xrange(len(self.stack)): 32 | tmat = self.ParseMat(self.stack[i]) 33 | mat = self.Mul(tmat, mat) 34 | return mat 35 | 36 | def CalAllColor(self): 37 | add = self.CalAdditiveColor() 38 | color = self.CalColor() 39 | return (color, add) 40 | 41 | def CalColor(self): 42 | color = [255, 255, 255, 255] 43 | for i in xrange(len(self.stack)): 44 | t = self.ParseColor(self.stack[i]) 45 | color[0] *= int(t[0]) / 100.0 46 | color[1] *= int(t[2]) / 100.0 47 | color[2] *= int(t[4]) / 100.0 48 | color[3] *= int(t[6]) / 100.0 49 | for i in xrange(4): 50 | if color[i] > 255: 51 | color[i] = 255 52 | color[i] = int(color[i]) 53 | return color 54 | 55 | def CalAlpha(self): 56 | a = 255 57 | for i in xrange(len(self.stack)): 58 | t = self.ParseColor(self.stack[i]) 59 | a *=(int(t[0]) / 100.0) 60 | return int(a) 61 | 62 | def CalAdditiveColor(self): 63 | add = [255, 0, 0, 0] 64 | for i in xrange(len(self.stack)): 65 | t = self.ParseColor(self.stack[i]) 66 | add[1] += int(t[3]) 67 | add[2] += int(t[5]) 68 | add[3] += int(t[7]) 69 | for i in xrange(4): 70 | if add[i] > 255: 71 | add[i] = 255 72 | return add 73 | 74 | #"100,0,100,164,100,164,100,0" 75 | def ParseColor(self, str): 76 | t = str.split(',') 77 | assert(len(t) == 8) 78 | return t 79 | 80 | def Mul(self, a, b): 81 | m = [] 82 | m.append((float(a[0]) * float(b[0]) + float(a[1]) * float(b[2])) / 1024.0) 83 | m.append((float(a[0]) * float(b[1]) + float(a[1]) * float(b[3])) / 1024.0) 84 | m.append((float(a[2]) * float(b[0]) + float(a[3]) * float(b[2])) / 1024.0) 85 | m.append((float(a[2]) * float(b[1]) + float(a[3]) * float(b[3])) / 1024.0) 86 | m.append((float(a[4]) * float(b[0]) + float(a[5]) * float(b[2])) / 1024.0 + float(b[4])) 87 | m.append((float(a[4]) * float(b[1]) + float(a[5]) * float(b[3])) / 1024.0 + float(b[5])) 88 | return m 89 | 90 | def ParseMat(self, mat): 91 | mat = mat.split(',') 92 | matF = [] 93 | for i in xrange(6): 94 | matF.append(float(mat[i])) 95 | return matF 96 | 97 | --------------------------------------------------------------------------------