Page MenuHome

VoiceOverAssistant.py

Authored By
iND Dev (ind)
Nov 13 2013, 4:40 PM
Size
9 KB
Subscribers
None

VoiceOverAssistant.py

bl_info = {
"name": "Voice Over Assistant",
"description": "Shows text over a video clip, used for "
"assisting audio voice-over recording.",
"author": "Neal Delfeld",
"version": (0, 8),
"blender": (2, 65, 0),
"location": "Sequencer",
"warning": "For the time being, the filename has to be manually"
"entered in the function 'get_script_filename()'",
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"
"Scripts/My_Script",
"tracker_url": "http://projects.blender.org/tracker/index.php?"
"func=detail&aid=<number>",
"category": "Animation"}
'''
Uses the Header, and should probably be a Menu, but it's a Panel right now.
'''
import bpy
import bgl
import blf
import re
from bpy.types import Header, Panel, Operator
# return current filename
def get_script_filename(self):
return Put your filename here.
# font drawing function
def draw_callback_px(self, context):
if not context.window_manager.voice_over_is_on:
return
scene = context.scene
# check for resized window/region
# reset visible chars to the max chars in order to force it to shorten later
if self.regionWidth != int(context.region.width):
self.regionWidth = int(context.region.width)
self.visibleChars = self.maxVisibleChars
self.scriptText = self.getScriptListFromFile(self.filename)
# get the correct text line from the list
if scene.use_preview_range:
minFrm = scene.frame_preview_start
maxFrm = scene.frame_preview_end
else:
minFrm = scene.frame_start
maxFrm = scene.frame_end
# no divide by zero
if maxFrm == minFrm:
return
curPercPos = max(0,min(1,(scene.frame_current - minFrm) / (maxFrm - minFrm)))
showStr = self.scriptText[int((len(self.scriptText)-1) * curPercPos)]
sqc = context.scene
font_id = 0
# changing the font size has a huge memory hit, since all the cached text has to be re-written
# blf.size(font_id, sqc.voice_over_font_size, 72)
blf.size(font_id, 21, 72)
# get text line's onscreen width and height
lineWid, lineHt = blf.dimensions(font_id, showStr)
# shorten the max text length to fit onscreen
if self.visibleChars > self.minVisibleChars and (sqc.voice_over_pos_x + lineWid) > (context.region.width - (sqc.voice_over_pos_x * 2)):
self.visibleChars = max(self.visibleChars - 10, self.minVisibleChars)
self.scriptText = self.getScriptListFromFile(self.filename)
# --- RECURSION ---
draw_callback_px(self, context)
else:
# bgl.glColor3f(1.0, 1.0, 1.0)
bgl.glColor3f(sqc.voice_over_text_color[0], sqc.voice_over_text_color[1], sqc.voice_over_text_color[2])
blf.position(font_id, sqc.voice_over_pos_x, sqc.voice_over_pos_y, 0)
blf.draw(font_id, showStr)
class VoiceOverReader(Operator):
"""Voice Over Reader"""
bl_idname = "sequencer.voice_over_reader"
bl_label = "Voice Over Reader"
_fileText = None
_timer = None
_handle = None
regionWidth = 0
scriptText = None
maxVisibleChars = 300
minVisibleChars = 30
visibleChars = maxVisibleChars
filename = None
# returns a list of lines of text, split into words, but no longer than visibleChars length
# it will be longer than visibleChars on certain occasions.
def getScriptListFromFile(self, curfilename):
if not self._fileText:
if not curfilename:
filename = get_script_filename(self)
curfilename = filename
self._fileText = open(curfilename, "rt").read()
self._fileText = re.sub('[=]','',self._fileText)
self._fileText = self._fileText.replace('\n\n','\n').replace('\n','\n(pause)\n')
self._fileText = re.split('\s+',self._fileText)
tf3 = [""]
for wrd in self._fileText:
if (len(tf3[len(tf3)-1]) + len(wrd)) > self.visibleChars:
tf3.append("")
tf3[len(tf3)-1] += wrd + " "
return tf3
def modal(self, context, event):
# sometimes the region is empty, so this error check catches that
try:
context.area.tag_redraw()
except:
return {'PASS_THROUGH'}
# main thingy
if event.type == 'TIMER':
draw_callback_px(self, context)
return {'PASS_THROUGH'}
# to quit
if event.type in {'RIGHTMOUSE', 'ESC'}:
return self.cancel(context)
# this is last because this function has to clear the screen first (I think)
if not context.window_manager.voice_over_is_on:
return self.cancel(context)
return {'PASS_THROUGH'}
def invoke(self, context, event):
self.scriptText = self.getScriptListFromFile(self.filename)
# if context.screen.is_animation_playing:
if context.window_manager.voice_over_is_on is False:
# operator is called for the first time, start everything
context.window_manager.voice_over_is_on = True
self.regionWidth = 0
self.visibleChars = self.maxVisibleChars
self._timer = context.window_manager.event_timer_add(0.05, context.window)
self._handle = bpy.types.SpaceSequenceEditor.draw_handler_add(draw_callback_px, (self, context), 'WINDOW', 'POST_PIXEL')
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
else:
# operator is called again, stop displaying
return self.cancel(context)
# remove event listeners, etc.
def cancel(self, context):
try:
context.window_manager.event_timer_remove(self._timer)
except:
pass
try:
bpy.types.SpaceSequenceEditor.draw_handler_remove(self._handle, 'WINDOW')
except:
pass
context.window_manager.voice_over_is_on = False
return {'CANCELLED'}
# properties used by the script
def init_properties():
sequencer = bpy.types.Scene
wm = bpy.types.WindowManager
sequencer.voice_over_script_filename = bpy.props.StringProperty(
name="Script Filename",
description="Script Filename",
# get=get_script_filename,
subtype='FILE_PATH')
sequencer.voice_over_pos_x = bpy.props.IntProperty(
name="X",
description="Voice over text Y position",
default=15,
min=5,
max=100)
sequencer.voice_over_pos_y = bpy.props.IntProperty(
name="Y",
description="Voice over text Y position",
default=30,
min=5,
max=100)
sequencer.voice_over_text_color = bpy.props.FloatVectorProperty(
name="Color",
description="Voice over text color",
default=(1.0, 1.0, 1.0, 1.0),
min=0.1,
max=1,
subtype='COLOR',
size=4)
# sequencer.voice_over_font_size = bpy.props.IntProperty(
# name="Size",
# description="Voice over text size",
# default=21, min=12, max=24)
# Runstate initially always set to False
# note: it is not stored in the sequencer, but in window manager:
wm.voice_over_is_on = bpy.props.BoolProperty(default=False)
# removal of properties when script is disabled
def clear_properties():
props = (
"voice_over_is_on",
"voice_over_script_filename",
"voice_over_pos_x",
"voice_over_pos_y",
"voice_over_text_color",
# "voice_over_font_size",
)
wm = bpy.context.window_manager
for p in props:
if p in wm:
del wm[p]
# defining the header items
class SEQ_Voice_Over_Reader_display(Header):
bl_label = "Voice Over Reader Display"
bl_space_type = 'SEQUENCE_EDITOR'
bl_idname = "SEQ_Voice_Over_Reader_display"
@classmethod
def poll(cls, context):
return (context.object is not None)
def draw(self, context):
wm = context.window_manager
sc = context.scene
layout = self.layout
if not wm.voice_over_is_on:
layout.operator("sequencer.voice_over_reader", text="Voice Over", icon = "PLAY")
else:
layout.operator("sequencer.voice_over_reader", text="Hide V.O.", icon = "PAUSE")
row = layout.row()
row.prop(sc, "voice_over_script_filename", text="")
row = layout.row(align = True)
# row.label(text="Position:")
row.prop(sc, "voice_over_pos_x", text="X")
row.prop(sc, "voice_over_pos_y", text="Y")
row = layout.row()
row.prop(sc, "voice_over_text_color", text="")
# row = layout.row(align = True)
# row.prop(sc, "voice_over_font_size")
def register():
init_properties()
bpy.utils.register_class(SEQ_Voice_Over_Reader_display)
bpy.utils.register_class(VoiceOverReader)
def unregister():
bpy.utils.unregister_class(VoiceOverReader)
bpy.utils.unregister_class(SEQ_Voice_Over_Reader_display)
clear_properties()
if __name__ == "__main__":
register()
# if __name__ == "__main__": # only for live edit.
# bpy.utils.register_module(__name__)

File Metadata

Mime Type
text/x-python
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
a7/20/4401af99f38c8a52edfedd89fd14

Event Timeline