My animation process toolset: the JSON configuration file
TL;DR
- I have created a Python script to write a JSON file containing the basic config details of the animation.
- I am using the timestamp of the created files as a proxy for the layer order.
In part 1 we created a tool to save mouse movement paths as JSON files. In part 2, I found a way to export a grouped and layered PSD from Photoshop into files and directories like this:
./Solar system 3000,3000
./Solar system 3000,3000/Planet and moon 954,527
./Solar system 3000,3000/Planet and moon 954,527/moon-593,413.png
./Solar system 3000,3000/Planet and moon 954,527/planet-593,413.png
./Solar system 3000,3000/Sun-954,527.png
./stars-0,0.png
3 directories, 4 files
Oh I’ve just spotted an issue. The order of the layers is important, but is not retained using this method. And there is no way to add the order using the Photoshop script as it. Let’s have a look at the code and see if we can fix this, to add a layer number at the start of the exported names. This will be an interesting test for Claude 3.7. The script is about 3.5k lines, and the change needed quite a simple on I think, although it may require multiple simple edits. Let’s see if it can do it… As I expected, that didn’t work. The script just wouldn’t execute, and I think you must need a developer version of Photoshop to enable debugging. Too much effort, so let’s just ignore that issue (our animation editor should make it easy to reorder layers if necessary). Also if we are lucky the creation timestamp of the files may be in the order we need. Let me check that:
find . -exec stat -f "%B %N" {} + | sort -n | cut -d' ' -f2-
.
./Solar system 3000,3000
./Solar system 3000,3000/Planet and moon 954,527
./Solar system 3000,3000/Planet and moon 954,527/moon-593,413.png
./Solar system 3000,3000/Planet and moon 954,527/planet-593,413.png
./Solar system 3000,3000/Sun-954,527.png
./stars-0,0.png
Yay! That’s in the correct order of the layers. Moving on…
Javascript running in the browser can’t read local files and directory structures. And we need a main JSON file with all the details exported from the PSD. So we need a python script to generate the JSON file. What might that file look like? I’ll make it by hand first, so I know what I need to ask Claude to write a Python script to do. Here’s what I think the file needs to look like:
[
{
"group": "Solar system",
"centerX": 3000,
"centerY": 3000,
"children": [
{
"group": "Planet and moon",
"centerX": 954,
"centerY": 527,
"children": [
{
"image": "moon.png",
"centerX": 593,
"centerY": 413
},
{
"image": "planet.png",
"centerX": 593,
"centerY": 413
}
]
},
{
"image": "sun.png",
"centerX": 954,
"centerY": 527
}
]
},
{
"image": "stars.png",
"centerX": 0,
"centerY": 0
}
]
So now I have my input, and expect output, so Claude should be able to write a Python script to convert the file structure to the JSON. I also want it to change the filenames too. This should be easy for it to do I think.
That worked! Here’s the script:
#!/usr/bin/env python3
import os
import json
import re
import sys
import shutil
def parse_name_coordinates(name):
"""Extract the name and coordinates from a directory or file name."""
# Match pattern: name coordinates or name-coordinates
= re.search(r'(.+?)[ -](\d+),(\d+)(?:\.png)?$', name)
match if match:
= match.groups()
name, x, y # Remove any trailing hyphens from the name for files
if name.endswith('-'):
= name[:-1]
name return name, int(x), int(y)
return name, 0, 0
def process_directory(path, output_dir, file_mappings):
"""Recursively process directories and files to build the JSON structure."""
= []
items
# Get all entries in the current directory
= os.listdir(path)
entries
# Create a list of tuples (entry, timestamp, is_dir)
= []
entry_data for entry in entries:
= os.path.join(path, entry)
entry_path = os.path.isdir(entry_path)
is_dir # Get creation time (or modification time as fallback)
try:
# For macOS
= os.stat(entry_path).st_birthtime
timestamp except AttributeError:
# Fallback for other platforms that don't have creation time
= os.path.getmtime(entry_path)
timestamp
entry_data.append((entry, timestamp, is_dir))
# Sort by timestamp (creation time)
=lambda x: x[1])
entry_data.sort(key
# Process directories and files in order of creation time
for entry, _, is_dir in entry_data:
= os.path.join(path, entry)
entry_path
if is_dir:
# This is a directory
= parse_name_coordinates(entry)
dir_name, center_x, center_y
# Create a group entry with children
= {
group_entry "group": dir_name,
"centerX": center_x,
"centerY": center_y,
"children": process_directory(entry_path, output_dir, file_mappings)
}
items.append(group_entry)elif entry.lower().endswith('.png'):
# This is a PNG file
= parse_name_coordinates(entry)
file_name, center_x, center_y
# New filename for the output directory
= f"{file_name}.png"
new_filename
# Save mapping of original file to new file
= os.path.join(output_dir, new_filename)
file_mappings[os.path.join(path, entry)]
# Create an image entry
= {
image_entry "image": new_filename,
"centerX": center_x,
"centerY": center_y
}
items.append(image_entry)
return items
def main():
if len(sys.argv) < 2:
print("Usage: python script.py <input_directory> [output_directory]")
1)
sys.exit(
= sys.argv[1]
input_dir = sys.argv[2] if len(sys.argv) > 2 else "output"
output_dir
# Ensure output_dir is treated as a directory name, not a filename
if not os.path.isdir(output_dir):
try:
=True)
os.makedirs(output_dir, exist_okexcept Exception as e:
print(f"Error creating output directory: {e}")
1)
sys.exit(
# Define output JSON file path inside the directory
= os.path.join(output_dir, "animation_data.json")
output_json_file
# Dictionary to track file mappings (original path -> new path)
= {}
file_mappings
# Create an empty result list
= []
result
# Get all entries in the input directory with timestamps
= os.listdir(input_dir)
entries = []
entry_data
for entry in entries:
= os.path.join(input_dir, entry)
entry_path = os.path.isdir(entry_path)
is_dir
# Get creation time (or modification time as fallback)
try:
# For macOS
= os.stat(entry_path).st_birthtime
timestamp except AttributeError:
# Fallback for other platforms that don't have creation time
= os.path.getmtime(entry_path)
timestamp
entry_data.append((entry, timestamp, is_dir))
# Sort by timestamp (creation time)
=lambda x: x[1])
entry_data.sort(key
# Process entries in order of creation time
for entry, _, is_dir in entry_data:
= os.path.join(input_dir, entry)
entry_path
if is_dir:
# This is a directory like "Solar system 3000,3000"
= parse_name_coordinates(entry)
dir_name, center_x, center_y
# Create a group entry with children
= {
group_entry "group": dir_name,
"centerX": center_x,
"centerY": center_y,
"children": process_directory(entry_path, output_dir, file_mappings)
}
result.append(group_entry)elif os.path.isfile(entry_path) and entry.lower().endswith('.png'):
# This is a file like "stars-0,0.png" at the root level
= parse_name_coordinates(entry)
file_name, center_x, center_y
# New filename for the output directory
= f"{file_name}.png"
new_filename
# Save mapping of original file to new file
= os.path.join(output_dir, new_filename)
file_mappings[entry_path]
# Create an image entry
= {
image_entry "image": new_filename,
"centerX": center_x,
"centerY": center_y
}
result.append(image_entry)
# Write the JSON output
with open(output_json_file, 'w') as f:
=2)
json.dump(result, f, indent
# Copy all PNG files to the output directory with their new names
for src_path, dest_path in file_mappings.items():
shutil.copy2(src_path, dest_path)print(f"Copied: {os.path.basename(src_path)} → {os.path.basename(dest_path)}")
print(f"JSON data has been written to {output_json_file}")
print(f"All files have been copied to the {output_dir} directory")
if __name__ == "__main__":
main()
To run just give it the directory of the export from Photoshop:
python create-json01.py "./export"
I think I’m finally at the point when I can start creating the tool that actually does the animating.