Skip to content

Instantly share code, notes, and snippets.

@OrangeChannel
Last active September 22, 2019 21:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save OrangeChannel/be03f30e16a554ecb3d651e5c2a1e6b1 to your computer and use it in GitHub Desktop.
Save OrangeChannel/be03f30e16a554ecb3d651e5c2a1e6b1 to your computer and use it in GitHub Desktop.
Export Blender motion tracking markers to Adobe After Effects 6.0 compatible files. Works with Aegisub-Motion.
# 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.
#
# Updated to 2.80.x by @OrangeChannel on GitHub
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, 2, 0),
"blender": (2, 80, 0),
"location": "File > Export > Adobe After Effects 6.0 Keyframe Data",
"warning": "",
"category": "Import-Export",
}
import math
import bpy
import mathutils
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 = []
f.write("Adobe After Effects 6.0 Keyframe Data\r\n\r\n")
f.write("\tUnits Per Second\t{0:.3f}\r\n".format(fps))
f.write("\tSource Width\t{0}\r\n".format(clip.size[0]))
f.write("\tSource Height\t{0}\r\n".format(clip.size[1]))
f.write("\tSource Pixel Aspect Ratio\t1\r\n")
f.write("\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 is None:
startarea = area
if startwidth is None:
startwidth = width
if startheight is 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 is 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")
f.write("\tFrame\tX pixels\tY pixels\tZ pixels\r\n")
f.write(positions)
f.write("Position\r\n")
f.write("\tFrame\tX pixels\tY pixels\tZ pixels\r\n")
f.write(positions)
f.write("Scale\r\n")
f.write("\tFrame\tX percent\tY percent\tZ percent\r\n")
f.write(scales)
f.write("Rotation\r\n")
f.write("\tFrame Degrees\r\n")
f.write(rotations)
f.write("End of Keyframe Data\r\n")
trackno += 1
clipno += 1
return {'FINISHED'}
from bpy.props import StringProperty
from bpy_extras.io_utils import ExportHelper
class ExportAFXKey(bpy.types.Operator, ExportHelper):
"""Export motion tracking markers to Adobe After Effects 6.0 compatible files"""
bl_idname = "export_animation.afxkey"
bl_label = "Export to Adobe After Effects 6.0 Keyframe Data"
filename_ext = ""
filter_glob: StringProperty(default="*", options={'HIDDEN'})
def execute(self, context):
write_files(self.filepath, context)
return {'FINISHED'}
def menu_export(self, context):
import os
default_path = os.path.splitext(bpy.data.filepath)[0] + ""
self.layout.operator(ExportAFXKey.bl_idname, text="Adobe After Effects 6.0 Keyframe Data").filepath = default_path
def register():
bpy.utils.register_class(ExportAFXKey)
bpy.types.TOPBAR_MT_file_export.append(menu_export)
def unregister():
bpy.utils.unregister_class(ExportAFXKey)
bpy.types.TOPBAR_MT_file_export.remove(menu_export)
if __name__ == "__main__":
register()
@OrangeChannel
Copy link
Author

Warning: using this with multiple tracking markers active will output a plaintext file for each one!

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