Skip to content

Instantly share code, notes, and snippets.

@torque
Last active August 17, 2020 15:24
Show Gist options
  • Save torque/6453947 to your computer and use it in GitHub Desktop.
Save torque/6453947 to your computer and use it in GitHub Desktop.
Python script for exporting AAE formatted motion tracking data from Blender. Written by lachs0r.
# Copyright (c) 2013, Martin Herkt
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
bl_info = {
"name": "Export: Adobe After Effects 6.0 Keyframe Data",
"description": "Export motion tracking markers to Adobe After Effects 6.0 compatible files",
"author": "Martin Herkt",
"version": (0, 1, 2),
"blender": (2, 62, 0),
"location": "File > Export > Adobe After Effects 6.0 Keyframe Data",
"warning": "",
"category": "Import-Export",
}
import bpy, mathutils, math, pyperclip
def write_files(prefix, context):
scene = context.scene
fps = scene.render.fps / scene.render.fps_base
clipno = 0
for clip in bpy.data.movieclips:
trackno = 0
for track in clip.tracking.tracks:
with open("{0}_c{1:02d}_t{2:02d}.txt".format(prefix, clipno, trackno), "w") as f:
frameno = clip.frame_start
startarea = None
startwidth = None
startheight = None
startrot = None
data = []
clipboard = ""
f.write("Adobe After Effects 6.0 Keyframe Data\r\n\r\n")
clipboard += "Adobe After Effects 6.0 Keyframe Data\r\n\r\n"
f.write("\tUnits Per Second\t{0:.3f}\r\n".format(fps))
clipboard += "\tUnits Per Second\t{0:.3f}\r\n".format(fps)
f.write("\tSource Width\t{0}\r\n".format(clip.size[0]))
clipboard += "\tSource Width\t{0}\r\n".format(clip.size[0])
f.write("\tSource Height\t{0}\r\n".format(clip.size[1]))
clipboard += "\tSource Height\t{0}\r\n".format(clip.size[1])
f.write("\tSource Pixel Aspect Ratio\t1\r\n")
clipboard += "\tSource Pixel Aspect Ratio\t1\r\n"
f.write("\tComp Pixel Aspect Ratio\t1\r\n\r\n")
clipboard += "\tComp Pixel Aspect Ratio\t1\r\n\r\n"
while frameno <= clip.frame_duration:
marker = track.markers.find_frame(frameno)
frameno += 1
if not marker or marker.mute:
continue
coords = marker.co
corners = marker.pattern_corners
area = 0
width = math.sqrt((corners[1][0] - corners[0][0]) * (corners[1][0] - corners[0][0]) + (corners[1][1] - corners[0][1]) * (corners[1][1] - corners[0][1]))
height = math.sqrt((corners[3][0] - corners[0][0]) * (corners[3][0] - corners[0][0]) + (corners[3][1] - corners[0][1]) * (corners[3][1] - corners[0][1]))
for i in range(1,3):
x1 = corners[i][0] - corners[0][0]
y1 = corners[i][1] - corners[0][1]
x2 = corners[i+1][0] - corners[0][0]
y2 = corners[i+1][1] - corners[0][1]
area += x1 * y2 - x2 * y1
area = abs(area / 2)
if startarea == None:
startarea = area
if startwidth == None:
startwidth = width
if startheight == None:
startheight = height
zoom = math.sqrt(area / startarea) * 100
xscale = width / startwidth * 100
yscale = height / startheight * 100
p1 = mathutils.Vector(corners[0])
p2 = mathutils.Vector(corners[1])
mid = (p1 + p2) / 2
diff = mid - mathutils.Vector((0,0))
rotation = math.atan2(diff[0], diff[1]) * 180 / math.pi
if startrot == None:
startrot = rotation
rotation = 0
else:
rotation -= startrot - 360
x = coords[0] * clip.size[0]
y = (1 - coords[1]) * clip.size[1]
data.append([marker.frame, x, y, xscale, yscale, rotation])
posline = "\t{0}\t{1:.3f}\t{2:.3f}\t0"
scaleline = "\t{0}\t{1:.3f}\t{2:.3f}\t100"
rotline = "\t{0}\t{1:.3f}"
positions = "\r\n".join([posline.format(d[0], d[1], d[2]) for d in data]) + "\r\n\r\n"
scales = "\r\n".join([scaleline.format(d[0], d[3], d[4]) for d in data]) + "\r\n\r\n"
rotations = "\r\n".join([rotline.format(d[0], d[5]) for d in data]) + "\r\n\r\n"
f.write("Anchor Point\r\n")
clipboard += "Anchor Point\r\n"
f.write("\tFrame\tX pixels\tY pixels\tZ pixels\r\n")
clipboard += "\tFrame\tX pixels\tY pixels\tZ pixels\r\n"
f.write(positions)
clipboard += positions
f.write("Position\r\n")
clipboard += "Position\r\n"
f.write("\tFrame\tX pixels\tY pixels\tZ pixels\r\n")
clipboard += "\tFrame\tX pixels\tY pixels\tZ pixels\r\n"
f.write(positions)
clipboard += positions
f.write("Scale\r\n")
clipboard += "Scale\r\n"
f.write("\tFrame\tX percent\tY percent\tZ percent\r\n")
clipboard += "\tFrame\tX percent\tY percent\tZ percent\r\n"
f.write(scales)
clipboard += scales
f.write("Rotation\r\n")
clipboard += "Rotation\r\n"
f.write("\tFrame Degrees\r\n")
clipboard += "\tFrame Degrees\r\n"
f.write(rotations)
clipboard += rotations
f.write("End of Keyframe Data\r\n")
clipboard += "End of Keyframe Data\r\n"
trackno += 1
clipno += 1
pyperclip.copy(clipboard)
return {'FINISHED'}
from bpy_extras.io_utils import ExportHelper
from bpy.props import StringProperty
class ExportAFXKey(bpy.types.Operator, ExportHelper):
"""Export motion tracking markers to Adobe After Effects 6.0 compatible files"""
bl_idname = "export.afxkey"
bl_label = "Export to Adobe After Effects 6.0 Keyframe Data"
filename_ext = ""
filter_glob = StringProperty(default="*", options={'HIDDEN'})
def execute(self, context):
return write_files(self.filepath, context)
def menu_func(self, context):
self.layout.operator(ExportAFXKey.bl_idname, text="Adobe After Effects 6.0 Keyframe Data")
def register():
bpy.utils.register_class(ExportAFXKey)
bpy.types.INFO_MT_file_export.append(menu_func)
def unregister():
bpy.utils.unregister_class(ExportAFXKey)
bpy.types.INFO_MT_file_export.remove(menu_func)
if __name__ == "__main__":
register()
@chlowden
Copy link

hello
I am very interested in the above script. What version does it work it? On 2.69 mac I get the following errors we I try to install activate it.
Any ideas what I am doing wrong please?
Thanks
Christopher

par57p00446:~ lowdenc$ /Applications/Blender/blender.app/Contents/MacOS/blender ; exit;
ndof: 3Dx driver not found
Read new prefs: /Users/lowdenc/Library/Application Support/Blender/2.69/config/userpref.blend
found bundled python: /Applications/Blender/blender.app/Contents/MacOS/2.69/python
reloading addon: aae-export 1392996961.0 1392997153.0 /Users/lowdenc/Library/Application Support/Blender/2.69/scripts/addons/aae-export.py
Modules Installed from '/Users/lowdenc/Desktop/aae-export.py' into '/Users/lowdenc/Library/Application Support/Blender/2.69/scripts/addons' ()
reloading addon: aae-export 1392997153.0 1392997159.0 /Users/lowdenc/Library/Application Support/Blender/2.69/scripts/addons/aae-export.py
Modules Installed from '/Users/lowdenc/Desktop/aae-export.py' into '/Users/lowdenc/Library/Application Support/Blender/2.69/scripts/addons' ()
Traceback (most recent call last):
File "/Applications/Blender/blender.app/Contents/MacOS/2.69/scripts/modules/addon_utils.py", line 298, in enable
mod = import(module_name)
File "/Users/lowdenc/Library/Application Support/Blender/2.69/scripts/addons/aae-export.py", line 29
scene = context.scene
^
IndentationError: expected an indented block

@Moodkiller
Copy link

Hi torque,

I seem to have a problem with 2nd round of exporting motion tracking data. The first exports fine, but the second comes up with the following error:
http://i.imgur.com/59z7lt0.png

I dont know if this is an issue with pyperclip, or aae-export, but thought I would take a chance and post it here. It seems to me that pyperclip takes over the default clipboard that Windows uses, and is only available to me again when I close and exit Blender!

Im using Windows 7 Professional x64, pyperclip 1.5.6, and Blender 2.7.3

Edit:
Fixed this by removing all pyperclip related functions from the aae-export v1.2 script.

On Line 26 remove ', pyperclip' and
on line 158 remove 'pyperclip.copy(clipboard)'

@asweigart
Copy link

Pyperclip is somewhat brittle at this point. I think it can handle most unicode, but doesn't handle non-text data (though that isn't an issue in this case). Though from the error message, it looks like it might not necessarily be something in Pyperclip.

Sorry I can't be more helpful.

@Pin14
Copy link

Pin14 commented May 26, 2015

I use your very nice script with Aegisub, there is only one thing I need to change:
=> positions = "\r\n".join([posline.format(d[0], d[1], (clip.size[1]-d[2])) for d in data]) + "\r\n\r\n"
(Line 100)
If someone need this for subbing with Aegisub.
(in Aegisub is the coordinate of y is reverse)

@Subarashii-no-Fansub
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment