1# ##### BEGIN GPL LICENSE BLOCK ##### 2# 3# This program is free software; you can redistribute it and/or 4# modify it under the terms of the GNU General Public License 5# as published by the Free Software Foundation; either version 2 6# of the License, or (at your option) any later version. 7# 8# This program is distributed in the hope that it will be useful, 9# but WITHOUT ANY WARRANTY; without even the implied warranty of 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11# GNU General Public License for more details. 12# 13# You should have received a copy of the GNU General Public License 14# along with this program; if not, write to the Free Software Foundation, 15# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 16# 17# ##### END GPL LICENSE BLOCK ##### 18 19# <pep8-80 compliant> 20 21# Originally written by Matt Ebb 22 23import bpy 24from bpy.types import Operator 25import os 26 27from bpy.app.translations import pgettext_tip as tip_ 28 29 30def guess_player_path(preset): 31 import sys 32 33 if preset == 'INTERNAL': 34 return bpy.app.binary_path 35 36 elif preset == 'DJV': 37 player_path = "djv" 38 if sys.platform == "darwin": 39 test_path = "/Applications/DJV2.app/Contents/Resources/bin/djv" 40 if os.path.exists(test_path): 41 player_path = test_path 42 43 elif preset == 'FRAMECYCLER': 44 player_path = "framecycler" 45 46 elif preset == 'RV': 47 player_path = "rv" 48 49 elif preset == 'MPLAYER': 50 player_path = "mplayer" 51 52 else: 53 player_path = "" 54 55 return player_path 56 57 58class PlayRenderedAnim(Operator): 59 """Play back rendered frames/movies using an external player""" 60 bl_idname = "render.play_rendered_anim" 61 bl_label = "Play Rendered Animation" 62 bl_options = {'REGISTER'} 63 64 def execute(self, context): 65 import subprocess 66 from shlex import quote 67 68 scene = context.scene 69 rd = scene.render 70 prefs = context.preferences 71 fps_final = rd.fps / rd.fps_base 72 73 preset = prefs.filepaths.animation_player_preset 74 # file_path = bpy.path.abspath(rd.filepath) # UNUSED 75 is_movie = rd.is_movie_format 76 77 # try and guess a command line if it doesn't exist 78 if preset == 'CUSTOM': 79 player_path = prefs.filepaths.animation_player 80 else: 81 player_path = guess_player_path(preset) 82 83 if is_movie is False and preset in {'FRAMECYCLER', 'RV', 'MPLAYER'}: 84 # replace the number with '#' 85 file_a = rd.frame_path(frame=0) 86 87 # TODO, make an api call for this 88 frame_tmp = 9 89 file_b = rd.frame_path(frame=frame_tmp) 90 91 while len(file_a) == len(file_b): 92 frame_tmp = (frame_tmp * 10) + 9 93 file_b = rd.frame_path(frame=frame_tmp) 94 file_b = rd.frame_path(frame=int(frame_tmp / 10)) 95 96 file = ("".join((c if file_b[i] == c else "#") 97 for i, c in enumerate(file_a))) 98 del file_a, file_b, frame_tmp 99 file = bpy.path.abspath(file) # expand '//' 100 else: 101 path_valid = True 102 # works for movies and images 103 file = rd.frame_path(frame=scene.frame_start, preview=scene.use_preview_range) 104 file = bpy.path.abspath(file) # expand '//' 105 if not os.path.exists(file): 106 err_msg = tip_("File %r not found") % file 107 self.report({'WARNING'}, err_msg) 108 path_valid = False 109 110 # one last try for full range if we used preview range 111 if scene.use_preview_range and not path_valid: 112 file = rd.frame_path(frame=scene.frame_start, preview=False) 113 file = bpy.path.abspath(file) # expand '//' 114 err_msg = tip_("File %r not found") % file 115 if not os.path.exists(file): 116 self.report({'WARNING'}, err_msg) 117 118 cmd = [player_path] 119 # extra options, fps controls etc. 120 if scene.use_preview_range: 121 frame_start = scene.frame_preview_start 122 frame_end = scene.frame_preview_end 123 else: 124 frame_start = scene.frame_start 125 frame_end = scene.frame_end 126 if preset == 'INTERNAL': 127 opts = [ 128 "-a", 129 "-f", str(rd.fps), str(rd.fps_base), 130 "-s", str(frame_start), 131 "-e", str(frame_end), 132 "-j", str(scene.frame_step), 133 file, 134 ] 135 cmd.extend(opts) 136 elif preset == 'DJV': 137 opts = [ 138 file, 139 "-speed", str(fps_final), 140 "-in_out", str(frame_start), str(frame_end), 141 "-frame", str(scene.frame_current), 142 "-time_units", "Frames" 143 ] 144 cmd.extend(opts) 145 elif preset == 'FRAMECYCLER': 146 opts = [file, "%d-%d" % (scene.frame_start, scene.frame_end)] 147 cmd.extend(opts) 148 elif preset == 'RV': 149 opts = ["-fps", str(rd.fps), "-play", "[ %s ]" % file] 150 cmd.extend(opts) 151 elif preset == 'MPLAYER': 152 opts = [] 153 if is_movie: 154 opts.append(file) 155 else: 156 opts += [ 157 ("mf://" + file.replace("#", "?")), 158 "-mf", 159 "fps=%.4f" % fps_final, 160 ] 161 162 opts += ["-loop", "0", "-really-quiet", "-fs"] 163 cmd.extend(opts) 164 else: # 'CUSTOM' 165 cmd.append(file) 166 167 # launch it 168 print("Executing command:\n ", " ".join(quote(c) for c in cmd)) 169 170 try: 171 subprocess.Popen(cmd) 172 except Exception as e: 173 err_msg = tip_("Couldn't run external animation player with command %r\n%s") % (cmd, e) 174 self.report( 175 {'ERROR'}, 176 err_msg, 177 ) 178 return {'CANCELLED'} 179 180 return {'FINISHED'} 181 182 183classes = ( 184 PlayRenderedAnim, 185) 186