1# OpenShot Video Editor is a program that creates, modifies, and edits video files. 2# Copyright (C) 2009 Jonathan Thomas 3# 4# This file is part of OpenShot Video Editor (http://launchpad.net/openshot/). 5# 6# OpenShot Video Editor is free software: you can redistribute it and/or modify 7# it under the terms of the GNU General Public License as published by 8# the Free Software Foundation, either version 3 of the License, or 9# (at your option) any later version. 10# 11# OpenShot Video Editor is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU General Public License for more details. 15# 16# You should have received a copy of the GNU General Public License 17# along with OpenShot Video Editor. If not, see <http://www.gnu.org/licenses/>. 18 19 20# Import Blender's python API. This only works when the script is being 21# run from the context of Blender. Blender contains it's own version of Python 22# with this library pre-installed. 23import math 24import bpy 25import json 26 27 28def load_font(font_path): 29 """ Load a new TTF font into Blender, and return the font object """ 30 # get the original list of fonts (before we add a new one) 31 original_fonts = bpy.data.fonts.keys() 32 33 # load new font 34 bpy.ops.font.open(filepath=font_path) 35 36 # get the new list of fonts (after we added a new one) 37 for font_name in bpy.data.fonts.keys(): 38 if font_name not in original_fonts: 39 return bpy.data.fonts[font_name] 40 41 # no new font was added 42 return None 43 44# Debug Info: 45# ./blender -b test.blend -P demo.py 46# -b = background mode 47# -P = run a Python script within the context of the project file 48 49 50# Init all of the variables needed by this script. Because Blender executes 51# this script, OpenShot will inject a dictionary of the required parameters 52# before this script is executed. 53params = { 54 'extrude': 0.1, 55 'bevel_depth': 0.02, 56 'spacemode': 'CENTER', 57 'text_size': 1.5, 58 'width': 1.0, 59 'fontname': 'Bfont', 60 61 'color': [0.8, 0.8, 0.8], 62 'alpha': 1.0, 63 64 'output_path': '/tmp/', 65 'fps': 24, 66 'quality': 90, 67 'file_format': 'PNG', 68 'color_mode': 'RGBA', 69 'horizon_color': [0.57, 0.57, 0.57], 70 'resolution_x': 1920, 71 'resolution_y': 1080, 72 'resolution_percentage': 100, 73 'start_frame': 1, 74 'end_frame': 250, 75 'length_multiplier': 1, 76 77 'depart_title': 'Paris', 78 'depart_lat_deg': 48, 79 'depart_lat_min': 51, 80 'depart_lat_sec': 24, 81 'depart_lat_dir': "N", 82 'depart_lon_deg': 2, 83 'depart_lon_min': 21, 84 'depart_lon_sec': 7, 85 'depart_lon_dir': "E", 86 87 'arrive_title': 'New York', 88 'arrive_lat_deg': 40, 89 'arrive_lat_min': 42, 90 'arrive_lat_sec': 51, 91 'arrive_lat_dir': "N", 92 'arrive_lon_deg': 74, 93 'arrive_lon_min': 0, 94 'arrive_lon_sec': 23, 95 'arrive_lon_dir': "E", 96} 97 98 99# INJECT_PARAMS_HERE 100 101# The remainder of this script will modify the current Blender .blend project 102# file, and adjust the settings. The .blend file is specified in the XML file 103# that defines this template in OpenShot. 104# ---------------------------------------------------------------------------- 105 106# Process parameters supplied as JSON serialization 107try: 108 injected_params = json.loads(params_json) 109 params.update(injected_params) 110except NameError: 111 pass 112 113 114depart = { 115 "lat_deg": params["depart_lat_deg"], 116 "lat_min": params["depart_lat_min"], 117 "lat_sec": params["depart_lat_sec"], 118 "lat_dir": params["depart_lat_dir"], 119 120 "lon_deg": params["depart_lon_deg"], 121 "lon_min": params["depart_lon_min"], 122 "lon_sec": params["depart_lon_sec"], 123 "lon_dir": params["depart_lon_dir"], 124} 125 126arrive = { 127 "lat_deg": params["arrive_lat_deg"], 128 "lat_min": params["arrive_lat_min"], 129 "lat_sec": params["arrive_lat_sec"], 130 "lat_dir": params["arrive_lat_dir"], 131 132 "lon_deg": params["arrive_lon_deg"], 133 "lon_min": params["arrive_lon_min"], 134 "lon_sec": params["arrive_lon_sec"], 135 "lon_dir": params["arrive_lon_dir"], 136} 137 138 139def get_latitude(direction, degrees, minutes, seconds): 140 lat = degrees + minutes / 60.0 + seconds / 3600.0 141 if direction == "N": 142 # North of the equator 143 return -lat 144 else: 145 # South of the equator 146 return lat 147 148 149def get_longitude(direction, degrees, minutes, seconds): 150 lon = degrees + minutes / 60.0 + seconds / 3600.0 151 if direction == "E": 152 # East of prime meridian 153 return lon 154 else: 155 # West of prime meridian 156 return -lon 157 158 159def correct_longitude(depart_longitude, arrive_longitude): 160 if -180 < (arrive_longitude - depart_longitude) < 180: 161 return depart_longitude 162 else: 163 if depart_longitude < 0: 164 return depart_longitude + 360 165 else: 166 return depart_longitude - 360 167 168 169class Point: 170 def __init__(self, x, y, z): 171 self.x = x 172 self.y = y 173 self.z = z 174 175 176class Coord(Point): 177 def __init__(self, latitude, longitude, radius): 178 self.lat = latitude 179 self.lon = longitude 180 self.radius = radius 181 p_x = ( 182 radius 183 * math.cos(math.radians(latitude)) 184 * math.sin(math.radians(longitude)) 185 ) 186 p_y = ( 187 radius 188 * math.cos(math.radians(latitude)) 189 * math.cos(math.radians(longitude)) 190 ) 191 p_z = (radius * math.sin(math.radians(latitude))) 192 super().__init__(p_x, p_y, p_z) 193 194 195# Calculate latitude / longitude for depart and arrive points 196sphere_radius = 10.0 197point_a = Coord( 198 get_latitude( 199 depart["lat_dir"], depart["lat_deg"], 200 depart["lat_min"], depart["lat_sec"]), 201 get_longitude( 202 depart["lon_dir"], depart["lon_deg"], 203 depart["lon_min"], depart["lon_sec"]), 204 sphere_radius 205) 206point_b = Coord( 207 get_latitude( 208 arrive["lat_dir"], arrive["lat_deg"], 209 arrive["lat_min"], arrive["lat_sec"]), 210 get_longitude( 211 arrive["lon_dir"], arrive["lon_deg"], 212 arrive["lon_min"], arrive["lon_sec"]), 213 sphere_radius 214) 215 216# Correct longitude if necessary 217orig_point_a_lon = point_a.lon 218point_a.lon = correct_longitude(orig_point_a_lon, point_b.lon) 219 220# Get angle between A & B points 221ab_angle_radians = math.acos( 222 (point_a.x * point_b.x + point_a.y * point_b.y + point_a.z * point_b.z) 223 / (sphere_radius * sphere_radius) 224) 225ab_angle_degrees = ab_angle_radians * 180 / math.pi 226 227# calculate points C & D 228point_c = Coord( 229 point_a.lat + 0.25 * (point_b.lat - point_a.lat), 230 point_a.lon + 0.25 * (point_b.lon - point_a.lon), 231 sphere_radius 232) 233point_d = Coord( 234 point_a.lat + 0.75 * (point_b.lat - point_a.lat), 235 point_a.lon + 0.75 * (point_b.lon - point_a.lon), 236 sphere_radius 237) 238 239# radius of CD line segment 240location_CD = (sphere_radius + 1.0) / math.cos(ab_angle_radians / 4.0) 241 242print("EmptyPointA Transform Rotation: Y= %f Z= %f" % (point_a.lat, point_a.lon)) 243print("EmptyPointB Transform Rotation: Y= %f Z= %f" % (point_b.lat, point_b.lon)) 244print("EmptyPointC Transform Rotation: Y= %f Z= %f" % (point_c.lat, point_c.lon)) 245print("EmptyPointD Transform Rotation: Y= %f Z= %f" % (point_d.lat, point_d.lon)) 246print("EmptyPointC.001 Transform Location: X= %f" % location_CD) 247print("EmptyPointD.001 Transform Location: X= %f" % location_CD) 248print("EmptyCam Frame 20 ->Transform Rotation: Y= %f Z= %f" % (point_a.lat, point_a.lon)) 249print("EmptyCam Frame 80 ->Transform Rotation: Y= %f Z= %f" % (point_b.lat, point_b.lon)) 250 251# Set Blender properties 252for pname, point in [ 253 ("EmptyPointA", point_a), 254 ("EmptyPointB", point_b), 255 ("EmptyPointC", point_c), 256 ("EmptyPointD", point_d), 257 ]: 258 bpy.data.objects[pname].rotation_euler = ( 259 0.0, math.radians(point.lat), math.radians(point.lon) 260 ) 261bpy.data.objects["EmptyPointC.001"].location.x = location_CD 262bpy.data.objects["EmptyPointD.001"].location.x = location_CD 263 264 265def update_curve(curve, start, end): 266 coords = [ 267 (20.0, start), 268 (80.0, end), 269 ] 270 for i, coord in enumerate(coords): 271 p = curve.keyframe_points[i] 272 p.co = coord 273 p.handle_left.y = coord[1] 274 p.handle_right.y = coord[1] 275 276 277# set Y rotation on the camera 278action = bpy.data.actions["EmptyCamAction"] 279update_curve( 280 action.fcurves[1], math.radians(point_a.lat), math.radians(point_b.lat)) 281update_curve( 282 action.fcurves[2], math.radians(point_a.lon), math.radians(point_b.lon)) 283 284# set world texture (i.e. the globe texture) 285if params["map_texture"]: 286 bpy.data.textures["Texture.002"].image.filepath = params["map_texture"] 287 288# Get font object 289font = None 290if params["fontname"] != "Bfont": 291 # Add font so it's available to Blender 292 font = load_font(params["fontname"]) 293else: 294 # Get default font 295 font = bpy.data.fonts["Bfont"] 296 297# Modify Text for Departure 298text_object = bpy.data.curves["Text"] 299text_object.body = params["depart_title"] 300 301# Modify Text for Arrival 302text_object = bpy.data.curves["Text.001"] 303text_object.body = params["arrive_title"] 304 305# Set common text parameters 306for ob in [bpy.data.curves["Text"], bpy.data.curves["Text.001"]]: 307 ob.extrude = params["extrude"] 308 ob.bevel_depth = params["bevel_depth"] 309 ob.size = params["text_size"] 310 ob.space_character = params["width"] 311 ob.font = font 312 313# Modify the Materials for Text, Lines, and Pins 314for material in [ 315 "Material.001", 316 "Material.002", 317 "Material.003", 318 "Material.004", 319 "Material.005", 320 ]: 321 ob = bpy.data.materials[material] 322 ob.diffuse_color = params["diffuse_color"] 323 ob.specular_color = params["specular_color"] 324 ob.specular_intensity = params["specular_intensity"] 325 326# Set the render options. It is important that these are set 327# to the same values as the current OpenShot project. These 328# params are automatically set by OpenShot 329render = bpy.context.scene.render 330render.filepath = params["output_path"] 331render.fps = params["fps"] 332if "fps_base" in params: 333 render.fps_base = params["fps_base"] 334render.image_settings.file_format = params["file_format"] 335render.image_settings.color_mode = params["color_mode"] 336render.resolution_x = params["resolution_x"] 337render.resolution_y = params["resolution_y"] 338render.resolution_percentage = params["resolution_percentage"] 339 340# Animation Speed (use Blender's time remapping to slow or speed up animation) 341length_multiplier = int(params["length_multiplier"]) # time remapping multiplier 342new_length = int(params["end_frame"]) * length_multiplier # new length (in frames) 343render.frame_map_old = 1 344render.frame_map_new = length_multiplier 345 346# Set render length/position 347bpy.context.scene.frame_start = params["start_frame"] 348bpy.context.scene.frame_end = new_length 349