[Edit Linked Library] Fixes based on code review from Campbell
[blender-addons-contrib.git] / io_import_lipSync_Importer.py
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 bl_info = {
20     "name": "LipSync Importer & Blinker",
21     "author": "Yousef Harfoush - bat3a ;)",
22     "version": (0, 5, 1),
23     "blender": (2, 65, 0),
24     "location": "3D window > Tool Shelf",
25     "description": "Plot Moho (Papagayo, Jlipsync, Yolo) file to frames and adds automatic blinking",
26     "warning": "",
27     "wiki_url": "http://wiki.blender.org/index.php?title=Extensions:2.6/Py/Scripts/Import-Export/Lipsync Importer",
28     "tracker_url": "http://projects.blender.org/tracker/index.php?func=detail&aid=24080&group_id=153&atid=468",
29     "category": "Import-Export"}
30
31
32 import bpy, re
33 from random import random
34 from bpy.props import *
35 from bpy.props import IntProperty, FloatProperty, StringProperty
36
37 global lastPhoneme
38 lastPhoneme="nothing"
39
40 # add blinking
41 def blinker():
42     
43     scn = bpy.context.scene
44     obj = bpy.context.object
45
46     if scn.regMenuTypes.enumBlinkTypes == '0':
47         modifier = 0
48     elif scn.regMenuTypes.enumBlinkTypes == '1':
49         modifier = scn.blinkMod
50     
51     #creating keys with blinkNm count
52     for y in range(scn.blinkNm):
53         frame = y * scn.blinkSp + int(random()*modifier)
54         createShapekey('blink', frame)
55
56 # -----------code contributed by dalai felinto adds armature support modified by me-------------------
57
58 bone_keys = {
59 "AI":   ('location', 0),
60 "E":    ('location', 1),
61 "FV":   ('location', 2),
62 "L":    ('rotation_euler', 0),
63 "MBP":  ('rotation_euler', 1),
64 "O":    ('rotation_euler', 2),
65 "U":    ('scale', 0),
66 "WQ":   ('scale', 1),
67 "etc":  ('scale', 2),
68 "rest": ('ik_stretch', -1)
69 }
70
71 def lipsyncerBone():
72     # reading imported file & creating keys
73     object = bpy.context.object
74     scene = bpy.context.scene
75     bone = bpy.context.active_pose_bone
76     
77     resetBoneScale(bone)
78     
79     f=open(scene.fpath) # importing file
80     f.readline() # reading the 1st line that we don"t need
81     
82     for line in f:
83         # removing new lines
84         lsta = re.split("\n+", line)
85
86         # building a list of frames & shapes indexes
87         lst = re.split(":? ", lsta[0])# making a list of a frame & number 
88         frame = int(lst[0])
89         
90         for key,attribute in bone_keys.items():
91             if lst[1] == key:
92                 createBoneKeys(key, bone, attribute, frame)
93
94 def resetBoneScale(bone):
95     # set the attributes used by papagayo to 0.0
96     for attribute,index in bone_keys.values():
97         if index != -1:
98             #bone.location[0] = 0.0
99             exec("bone.%s[%d] = %f" % (attribute, index, 0.0))
100         else:
101             exec("bone.%s = %f" % (attribute, 0.0))
102
103 def addBoneKey(bone, data_path, index=-1, value=None, frame=0, group=""):
104     # set a value and keyframe for the bone
105     # it assumes the 'bone' variable was defined before
106     # and it's the current selected bone
107     frame=bpy.context.scene.frame_current
108     if value != None:
109         if index != -1:
110             # bone.location[0] = 0.0
111             exec("bone.%s[%d] = %f" % (data_path, index, value))
112         else:
113             exec("bone.%s = %f" % (data_path, value))
114
115     # bone.keyframe_insert("location", 0, 10.0, "Lipsync")
116     exec('bone.keyframe_insert("%s", %d, %f, "%s")' % (data_path, index, frame, group))
117
118 # creating keys with offset and eases for a phonem @ the Skframe
119 def createBoneKeys(phoneme, bone, attribute, frame):
120     global lastPhoneme
121     
122     scene = bpy.context.scene
123     object = bpy.context.object
124     
125     offst = scene.offset     # offset value
126     skVlu = scene.skscale    # shape key value
127     
128     #in case of Papagayo format
129     if scene.regMenuTypes.enumFileTypes == '0' :
130         frmIn = scene.easeIn     # ease in value
131         frmOut = scene.easeOut   # ease out value
132         hldIn = scene.holdGap    # holding time value
133         
134     #in case of Jlipsync format or Yolo
135     elif scene.regMenuTypes.enumFileTypes == '1' :
136         frmIn = 1
137         frmOut = 1
138         hldIn = 0
139
140     # inserting the In key only when phonem change or when blinking
141     if lastPhoneme!=phoneme or eval(scene.regMenuTypes.enumModeTypes) == 1:
142         addBoneKey(bone, attribute[0], attribute[1], 0.0, offst+frame-frmIn, "Lipsync")
143
144     addBoneKey(bone, attribute[0], attribute[1], skVlu, offst+frame, "Lipsync")
145     addBoneKey(bone, attribute[0], attribute[1], skVlu, offst+frame+hldIn, "Lipsync")
146     addBoneKey(bone, attribute[0], attribute[1], 0.0, offst+frame+hldIn+frmOut, "Lipsync")
147     
148     lastPhoneme=phoneme
149
150 # -------------------------------------------------------------------------------
151
152 # reading imported file & creating keys
153 def lipsyncer():
154     
155     obj = bpy.context.object
156     scn = bpy.context.scene
157     
158     f=open(scn.fpath) # importing file
159     f.readline() # reading the 1st line that we don"t need
160     
161     for line in f:
162
163         # removing new lines
164         lsta = re.split("\n+", line)
165
166         # building a list of frames & shapes indexes
167         lst = re.split(":? ", lsta[0])# making a list of a frame & number 
168         frame = int(lst[0])
169         
170         for key in obj.data.shape_keys.key_blocks:
171             if lst[1] == key.name:
172                 createShapekey(key.name, frame)
173
174 # creating keys with offset and eases for a phonem @ the frame
175 def createShapekey(phoneme, frame):
176     
177     global lastPhoneme
178     
179     scn = bpy.context.scene
180     obj = bpy.context.object
181     objSK = obj.data.shape_keys
182     
183     offst = scn.offset     # offset value
184     skVlu = scn.skscale    # shape key value
185     
186     #in case of Papagayo format
187     if scn.regMenuTypes.enumFileTypes == '0' :
188         frmIn = scn.easeIn     # ease in value
189         frmOut = scn.easeOut   # ease out value
190         hldIn = scn.holdGap    # holding time value
191         
192     #in case of Jlipsync format or Yolo
193     elif scn.regMenuTypes.enumFileTypes == '1' :
194         frmIn = 1
195         frmOut = 1
196         hldIn = 0
197
198     # inserting the In key only when phonem change or when blinking
199     if lastPhoneme!=phoneme or eval(scn.regMenuTypes.enumModeTypes) == 1:
200         objSK.key_blocks[phoneme].value=0.0
201         objSK.key_blocks[phoneme].keyframe_insert("value",
202             -1, offst+frame-frmIn, "Lipsync")
203             
204     objSK.key_blocks[phoneme].value=skVlu
205     objSK.key_blocks[phoneme].keyframe_insert("value", 
206         -1, offst+frame, "Lipsync")
207     
208     objSK.key_blocks[phoneme].value=skVlu
209     objSK.key_blocks[phoneme].keyframe_insert("value", 
210         -1, offst+frame+hldIn, "Lipsync")
211             
212     objSK.key_blocks[phoneme].value=0.0
213     objSK.key_blocks[phoneme].keyframe_insert("value", 
214     -1, offst+frame+hldIn+frmOut, "Lipsync")
215     
216     lastPhoneme = phoneme
217
218 # lipsyncer operation start
219 class btn_lipsyncer(bpy.types.Operator):
220     bl_idname = 'lipsync.go'
221     bl_label = 'Start Processing'
222     bl_description = 'Plots the voice file keys to timeline'
223
224     def execute(self, context):
225
226         scn = context.scene
227         obj = context.active_object
228
229         # testing if object is valid
230         if obj!=None:
231             if obj.type=="MESH":
232                 if obj.data.shape_keys!=None:
233                     if scn.fpath!='': lipsyncer()
234                     else: print ("select a Moho file")
235                 else: print("No shape keys")
236     
237             elif obj.type=="ARMATURE":
238                 if 1:#XXX add prop test
239                     if scn.fpath!='': lipsyncerBone()
240                     else: print ("select a Moho file")
241                 else: print("Create Pose properties")
242                 
243             else: print ("Object is not a mesh ot bone")
244         else: print ("Select object")
245         return {'FINISHED'}
246
247 # blinker operation start
248 class btn_blinker(bpy.types.Operator):
249     bl_idname = 'blink.go'
250     bl_label = 'Start Processing'
251     bl_description = 'Add blinks at random or specifice frames'
252
253     def execute(self, context):
254         
255         scn = context.scene
256         obj = context.object
257
258          # testing if object is valid
259         if obj!=None:
260             if obj.type=="MESH":
261                 if obj.data.shape_keys!=None:
262                     for key in obj.data.shape_keys.key_blocks:
263                         if key.name=='blink':
264                             blinker()
265                             #return
266                 else: print("No shape keys")
267             else: print ("Object is not a mesh ot bone")
268         else: print ("Select object")
269         return {'FINISHED'}
270
271
272 #defining custom enumeratos
273 class menuTypes(bpy.types.PropertyGroup):
274
275     enumFileTypes = EnumProperty(items =(('0', 'Papagayo', ''), 
276                                          ('1', 'Jlipsync Or Yolo', '')
277                                        #,('2', 'Retarget', '')
278                                          ),
279                                  name = 'Choose FileType',
280                                  default = '0')
281
282     enumBlinkTypes = EnumProperty(items =(('0', 'Specific', ''),
283                                           ('1', 'Random','')),
284                                   name = 'Choose BlinkType',
285                                   default = '0')
286
287     enumModeTypes = EnumProperty(items =(('0', 'Lipsyncer',''),
288                                          ('1', 'Blinker','')),
289                                  name = 'Choose Mode',
290                                  default = '0')
291                                  
292 # drawing the user interface
293 class LipSyncBoneUI(bpy.types.Panel):
294     bl_space_type = "VIEW_3D"
295     bl_region_type = "UI"
296     bl_label = "Phonemes"
297     
298     def draw(self, context):
299         layout = self.layout
300         col = layout.column()
301
302         bone = bpy.context.active_pose_bone
303         
304         #showing the current object type
305         if bone: #and if scn.regMenuTypes.enumModeTypes == '0':
306             col.prop(bone, "location", index=0, text="AI")
307             col.prop(bone, "location", index=1, text="E")
308             col.prop(bone, "location", index=2, text="FV")
309             if bpy.context.scene.unit_settings.system_rotation == 'RADIANS':
310                 col.prop(bone, "rotation_euler", index=0, text="L")
311                 col.prop(bone, "rotation_euler", index=1, text="MBP")
312                 col.prop(bone, "rotation_euler", index=2, text="O")
313             else:
314                 row=col.row()
315                 row.prop(bone, "rotation_euler", index=0, text="L")
316                 row.label(text=str("%4.2f" % (bone.rotation_euler.x)))
317                 row=col.row()
318                 row.prop(bone, "rotation_euler", index=1, text="MBP")
319                 row.label(text=str("%4.2f" % (bone.rotation_euler.y)))
320                 row=col.row()
321                 row.prop(bone, "rotation_euler", index=2, text="O")
322                 row.label(text=str("%4.2f" % (bone.rotation_euler.z)))
323             col.prop(bone, "scale", index=0, text="U")
324             col.prop(bone, "scale", index=1, text="WQ")
325             col.prop(bone, "scale", index=2, text="etc")
326         else:
327             layout.label(text="No good bone is selected")
328             
329 # drawing the user interface
330 class LipSyncUI(bpy.types.Panel):
331     bl_space_type = "VIEW_3D"
332     bl_region_type = "TOOL_PROPS"
333     bl_label = "LipSync Importer & Blinker"
334     
335     newType= bpy.types.Scene
336     
337     newType.fpath = StringProperty(name="Import File ", description="Select your voice file", subtype="FILE_PATH")
338     newType.skscale = FloatProperty(description="Smoothing shape key values", min=0.1, max=1.0, default=0.8)
339     newType.offset = IntProperty(description="Offset your frames", default=0)
340
341     newType.easeIn = IntProperty(description="Smoothing In curve", min=1, default=3)
342     newType.easeOut = IntProperty(description="Smoothing Out curve", min=1, default=3)
343     newType.holdGap = IntProperty(description="Holding for slow keys", min=0, default=0)
344
345     newType.blinkSp = IntProperty(description="Space between blinks", min=1, default=100)
346     newType.blinkNm = IntProperty(description="Number of blinks", min=1, default=10)
347     
348     newType.blinkMod = IntProperty(description="Randomzing keyframe placment", min=1, default=10)
349     
350     def draw(self, context):
351         
352         obj = bpy.context.active_object
353         scn = bpy.context.scene
354         
355         layout = self.layout
356         col = layout.column()
357
358         # showing the current object type
359         if obj != None:
360             if obj.type == "MESH":
361                 split = col.split(align=True)
362                 split.label(text="The active object is: ", icon="OBJECT_DATA")
363                 split.label(obj.name, icon="EDITMODE_HLT")
364             elif obj.type == "ARMATURE": # bone needs to be selected
365                 if obj.mode == "POSE": # mode needs to be pose
366                     split = col.split(align=True)
367                     split.label(text="The active object is: ", icon="ARMATURE_DATA")
368                     split.label(obj.name, icon="EDITMODE_HLT")
369                 else:
370                     col.label(text="You need to select Pose mode!", icon="OBJECT_DATA")
371             else:
372                 col.label(text="The active object is not a Mesh or Armature!", icon="OBJECT_DATA")
373         else:
374             layout.label(text="No object is selected", icon="OBJECT_DATA")
375             
376         col.row().prop(scn.regMenuTypes, 'enumModeTypes')
377         col.separator()
378         
379         # the lipsyncer panel 
380         if scn.regMenuTypes.enumModeTypes == '0':
381             # choose the file format
382             col.row().prop(scn.regMenuTypes, 'enumFileTypes', text = ' ', expand = True)
383                 
384             # Papagayo panel
385             if scn.regMenuTypes.enumFileTypes == '0':
386                 col.prop(context.scene, "fpath")
387                 split = col.split(align=True)
388                 split.label("Key Value :")
389                 split.prop(context.scene, "skscale")
390                 split = col.split(align=True)
391                 split.label("Frame Offset :")
392                 split.prop(context.scene, "offset")
393                 split = col.split(align=True)
394                 split.prop(context.scene, "easeIn", "Ease In")
395                 split.prop(context.scene, "holdGap", "Hold Gap")
396                 split.prop(context.scene, "easeOut", "Ease Out")
397                 
398                 col.operator('lipsync.go', text='Plot Keys to the Timeline')
399
400             # Jlipsync & Yolo panel
401             elif scn.regMenuTypes.enumFileTypes == '1':
402                 col.prop(context.scene, "fpath")
403                 split = col.split(align=True)
404                 split.label("Key Value :")
405                 split.prop(context.scene, "skscale")
406                 split = col.split(align=True)
407                 split.label("Frame Offset :")
408                 split.prop(context.scene, "offset")
409                 
410                 col.operator('lipsync.go', text='Plot Keys to the Timeline')
411         
412         # the blinker panel
413         elif scn.regMenuTypes.enumModeTypes == '1':
414             # choose blink type
415             col.row().prop(scn.regMenuTypes, 'enumBlinkTypes', text = ' ', expand = True)
416             
417             # specific panel
418             if scn.regMenuTypes.enumBlinkTypes == '0':
419                 split = col.split(align=True)
420                 split.label("Key Value :")
421                 split.prop(context.scene, "skscale")
422                 split = col.split(align=True)
423                 split.label("Frame Offset :")
424                 split.prop(context.scene, "offset")
425                 split = col.split(align=True)
426                 split.prop(context.scene, "easeIn", "Ease In")
427                 split.prop(context.scene, "holdGap", "Hold Gap")
428                 split.prop(context.scene, "easeOut", "Ease Out")
429                 col.prop(context.scene, "blinkSp", "Spacing")
430                 col.prop(context.scene, "blinkNm", "Times")
431                 col.operator('blink.go', text='Add Keys to the Timeline')
432             
433             # Random panel
434             elif scn.regMenuTypes.enumBlinkTypes == '1':
435                 split = col.split(align=True)
436                 split.label("Key Value :")
437                 split.prop(context.scene, "skscale")
438                 split = col.split(align=True)
439                 split.label("Frame Start :")
440                 split.prop(context.scene, "offset")
441                 split = col.split(align=True)
442                 split.prop(context.scene, "easeIn", "Ease In")
443                 split.prop(context.scene, "holdGap", "Hold Gap")
444                 split.prop(context.scene, "easeOut", "Ease Out")
445                 split = col.split(align=True)
446                 split.prop(context.scene, "blinkSp", "Spacing")
447                 split.prop(context.scene, "blinkMod", "Random Modifier")
448                 col.prop(context.scene, "blinkNm", "Times")
449                 col.operator('blink.go', text='Add Keys to the Timeline')
450         
451         
452 # clearing vars
453 def clear_properties():
454
455     # can happen on reload
456     if bpy.context.scene is None:
457         return
458      
459     props = ["fpath", "skscale", "offset", "easeIn", "easeOut", "holdGap", "blinkSp", "blinkNm", "blinkMod"]
460     for p in props:
461         if p in bpy.types.Scene.bl_rna.properties:
462             exec("del bpy.types.Scene."+p)
463         if p in bpy.context.scene:
464             del bpy.context.scene[p]
465
466 # registering the script
467 def register():
468     bpy.utils.register_module(__name__)
469     bpy.types.Scene.regMenuTypes = PointerProperty(type = menuTypes)
470
471 def unregister():
472     bpy.utils.unregister_module(__name__)
473     del bpy.context.scene.regMenuTypes
474
475     clear_properties()
476
477 if __name__ == "__main__":
478     register()