Anime Studio 11 uses a new document file format with a .anime extension. While the file may look binary if you opened it in a text editor like Notepad or TextEdit, the document is just a Zip archive with a .anime extension. Other popular document formats like Microsoft Office (e.g. .docx, .xlsx) and Comic Book files (e.g. .cbr, .cbz) make use of this same mechanism. The main document file and supporting data are compressed within an archive. If you were to expand the .anime file using something like StuffIt Expander, WinZip, or WinRar you would find either 1 or 2 files:
- Project.animeproj
- preview.jpg
If you opened Project.animeproj in a text editor, you'd see all the descriptive information of the document including layers, asset paths, styles, layer comp definitions, and project settings (e.g. FPS, resolution). While the file itself is in a condensed form with no whitespace, there are various JSON utilities out there to "beautify" this JSON automatically.
The second file in the archive is a preview image of the document. If you choose not to save document thumbnails with your document (See the document preferences), this file should not exist.
Now that the document format uses open standards like Zip and JSON, you can use scripting languages like Python and Ruby to browse and manipulate the document without ever having to open the .animeproj file in a text editor. Unfortunately LUA doesn't seem to support either without adding extra support from third parties.
To show you how easy it is to browse and manipulate a document, in the code section below, there is a python script that has a number of different functions. It can list the paths to image and audio layers:
python anime_util.py list /path/to/MyScene.anime
and it can do a simple search and replace within that file:
python anime_util.py update "/Users/Bob/Content/" "" /path/to/MyScene.anime
The previous would remove path references to the /Users/Bob/Content, essentially making the path relative. To extract the .animeproj file and name it MyScene.animeproj:
python anime_util.py extract /path/to/MyScene.anime MyScene.animeproj
To use the script, just save the following code to anime_util.py. In 11.1, this script will be shipped with Anime Studio in a "Utility" scripts folder.
You can use this script as a starting point to make batch changes to a bunch of files. For example if you have 40 document files that all reference the same asset, you could potentially rename all of them without having to open each of the documents in Anime Studio. Or you could use this script as a starting point to search all .anime files that reference a particular asset. The only disclaimer I have is that any document scripting that you do can irrevocably break the files so they are no longer readable. Backups of files before running a script is mandatory.
Code: Select all
#!/usr/bin/env python
"""anime_util - Manipulate a .anime file
Commands are list, extract, and update.
list prints the paths of image and audio layers.
extract saves the Project.animeproj to a desired location.
update replaces existing paths with the one supplied.
anime_util.py -h |command| for help.
"""
import zipfile
import json
import argparse
import os
import sys
def extractProject(animeFile):
"""Return the Project.animeproj extracted from a zip file as a string."""
zf = zipfile.ZipFile(animeFile)
for filename in [ 'Project.animeproj' ]:
data = zf.read(filename)
return data
def writeProject(animeFile, projectData):
"""Write data back into Project.animeproj in a zip file"""
zf = zipfile.ZipFile(animeFile, mode="w")
zf.writestr('Project.animeproj', projectData)
def printLayerInfo(layer):
"""Print the path of an image or audio layer."""
if layer.has_key('image_path'):
print "image:", layer['image_path']
if layer.has_key('audio_path'):
print "audio:", layer['audio_path']
def updateLayerInfo(layer, update):
"""Replace the path of an image or audio layer."""
if layer.has_key('image_path'):
path = layer['image_path']
layer['image_path'] = path.replace(update[0], update[1])
if layer.has_key('audio_path'):
path = layer['audio_path']
layer['audio_path'] = path.replace(update[0], update[1])
def isContainerLayer(layer):
"""Return True if the layer is a group or switch layer."""
type = layer['type']
return type == "GroupLayer" or type == "SwitchLayer"
def iterateContainerLayer(groupLayer, update=None):
"""Iterate a group or switch layer descending into sub groups and updating or printing layers otherwise."""
for layer in groupLayer:
if isContainerLayer(layer):
iterateContainerLayer(layer['layers'], update)
else:
if update:
updateLayerInfo(layer, update)
else:
printLayerInfo(layer)
def iterateLayers(path, update=None):
"""Iterate the root project layers."""
projectPath = path
projectData = extractProject(projectPath)
if len(projectData) > 0:
jsonData = json.loads(projectData)
layers = jsonData['layers']
iterateContainerLayer(layers, update)
if update:
writeProject(projectPath, json.dumps(jsonData))
iterateContainerLayer(layers)
def extractProjectFile(animeFile, projectFilepath = None):
"""Iterate Project.animeproj to projectFilePath."""
jsonData = extractProject(animeFile)
if len(jsonData) > 0:
jsonData = json.loads(jsonData)
if projectFilepath:
output = file(projectFilepath, 'wb')
else:
output = file('Project.animeproj', 'wb')
json.dump(jsonData, output, sort_keys=False, indent=4)
else:
parser.error("Project.animeproj is empty")
def perform():
parser = argparse.ArgumentParser(description="Open a .anime file and print and/or update the paths of image and audio layers")
subParsers = parser.add_subparsers(help="commands", dest="command")
parser_list = subParsers.add_parser("list", help="list image and audio paths in Anime Studio file (.anime).")
parser_list.add_argument('path', help="path to Anime Studio file")
parser_extract = subParsers.add_parser("extract", help="extract Project.animeproj from Anime Studio file (.anime).")
parser_extract.add_argument('path', help="path to Anime Studio file")
parser_extract.add_argument('projectFile', metavar='project file', help="path to save Anime Studio project file")
parser_extract.add_argument('-b', '--beautify', action="store_true", help="beautify the project file")
parser_update = subParsers.add_parser("update", help="update image and audio paths in Anime Studio file (.anime).")
parser_update.add_argument('existing', help="existing path(s) in file")
parser_update.add_argument('new', help="path to replace existing path(s) with")
parser_update.add_argument('path', help="path to Anime Studio file")
args = parser.parse_args()
path = os.path.abspath(args.path)
if not os.path.exists(path):
parser.error("%s does not exist." % path)
if not path.endswith(".anime"):
parser.error("%s is not an Anime Studio (.anime) file." % path)
updateInfo = None
if args.command == "update":
updateInfo = (args.existing, args.new)
if args.command == "extract":
extractProjectFile(path, os.path.abspath(args.projectFile))
else:
iterateLayers(path, update=updateInfo)
if __name__ == '__main__':
perform()
Erik