Commit a66e6c58401047a1fbe43f2014b0af72a8e8ad95

Authored by André Araújo
1 parent 104cb330
Exists in master

Adiciona módulo de verificação do arquivo .blend e módulo de geração do vídeo

.gitignore
... ... @@ -3,6 +3,8 @@
3 3 !.gitignore
4 4 !.gitempty
5 5 *.log
  6 +settings_local.py
6 7 video/*
7 8 tmp/
  9 +uploads/
8 10 *.pyc
... ...
bpy_checkout.py 0 → 100644
... ... @@ -0,0 +1,32 @@
  1 +# -*- coding: UTF-8 -*-
  2 +
  3 +import bpy
  4 +import sys
  5 +import json
  6 +
  7 +def main():
  8 + if (len(sys.argv) == 8):
  9 + json_obj = json.loads(sys.argv[7])
  10 + try:
  11 + action_name = json_obj["action_name"]
  12 + action_fake_is_valid = json_obj["action_fake_is_valid"]
  13 + min_frame_count = json_obj["min_frame_count"]
  14 + except:
  15 + sys.exit(4)
  16 + action = None
  17 + for i in bpy.data.actions:
  18 + if (i.name.upper() == action_name):
  19 + action = i
  20 + break
  21 + if (action == None):
  22 + sys.exit(5)
  23 + if not (action_fake_is_valid):
  24 + if (action.use_fake_user):
  25 + sys.exit(6)
  26 + if ((bpy.context.scene.frame_end - bpy.context.scene.frame_start) < min_frame_count):
  27 + sys.exit(7)
  28 + else:
  29 + sys.exit(8)
  30 +
  31 +if __name__ == "__main__":
  32 + main()
... ...
bpy_render.py 0 → 100644
... ... @@ -0,0 +1,76 @@
  1 +# -*- coding: UTF-8 -*-
  2 +
  3 +import bpy
  4 +import json
  5 +import os
  6 +import sys
  7 +import shutil
  8 +
  9 +def configure_output():
  10 + bpy.context.scene.frame_start = 0
  11 + bpy.context.scene.frame_current = bpy.context.scene.frame_start
  12 + bpy.context.scene.frame_end = bpy.context.scene.frame_start
  13 + bpy.context.scene.render.resolution_x = 640
  14 + bpy.context.scene.render.resolution_y = 480
  15 + bpy.context.scene.render.resolution_percentage = 100
  16 + bpy.context.scene.render.image_settings.file_format = 'H264'
  17 + bpy.context.scene.render.ffmpeg.format = 'MPEG4'
  18 + bpy.context.scene.render.ffmpeg.codec = 'H264'
  19 + bpy.context.scene.render.use_shadows = False
  20 + bpy.context.scene.render.use_raytrace = False
  21 + bpy.context.scene.render.use_envmaps = False
  22 + bpy.context.scene.render.use_motion_blur = False
  23 + bpy.context.scene.render.use_shadows = False
  24 + return
  25 +
  26 +def file_rename(file_full_path):
  27 + file_path = os.path.dirname(os.path.abspath(file_full_path))
  28 + filename = os.path.basename(os.path.splitext(file_full_path)[0])
  29 + extension = os.path.splitext(file_full_path)[1]
  30 + filename_reversed = ""
  31 + valid_char = False
  32 + for char in reversed(filename):
  33 + if (valid_char == True):
  34 + filename_reversed += char
  35 + if (char == "_"):
  36 + valid_char = True
  37 + try:
  38 + filename_reversed = filename_reversed[::-1]
  39 + new_filename = os.path.join(file_path, "%s%s" % (filename_reversed, extension))
  40 + shutil.move(file_full_path, new_filename)
  41 + return new_filename
  42 + except Exception:
  43 + return ""
  44 +
  45 +def render_video(video_output = ""):
  46 + getcwd = os.path.dirname(os.path.abspath(__file__))
  47 + base_path = os.path.join(getcwd, video_output)
  48 + bpy.context.scene.render.filepath = base_path + "_"
  49 + try:
  50 + bpy.ops.render.render(animation = True, write_still = False, layer = "", scene = "")
  51 + return str("%s%0.4i-%0.4i.mp4" % (bpy.context.scene.render.filepath, bpy.context.scene.frame_start, bpy.context.scene.frame_end))
  52 + except:
  53 + return ""
  54 +
  55 +def main():
  56 + if (len(sys.argv) == 8):
  57 + json_obj = json.loads(sys.argv[7])
  58 + try:
  59 + video_output = json_obj["video_output"]
  60 + convert_to_webm = json_obj["convert_to_webm"]
  61 + except:
  62 + sys.exit(3)
  63 + try:
  64 + configure_output()
  65 + renamed_video = file_rename(render_video(video_output))
  66 + base_path = os.path.splitext(renamed_video)[0]
  67 + if (convert_to_webm):
  68 + subprocess.call(["avconv", "-loglevel", "0", "-y", "-i", renamed_video, "-r", "24", "-vcodec", "libvpx", base_path + ".webm"])
  69 + subprocess.call(["rm", renamed_video])
  70 + except:
  71 + sys.exit(4)
  72 + else:
  73 + sys.exit(5)
  74 +
  75 +if __name__ == "__main__":
  76 + main()
... ...
checkout.py 0 → 100644
... ... @@ -0,0 +1,91 @@
  1 +# -*- coding: UTF-8 -*-
  2 +
  3 +import copy
  4 +import json
  5 +import math
  6 +import os
  7 +import sys
  8 +import subprocess
  9 +
  10 +getcwd = os.path.dirname(os.path.abspath(__file__))
  11 +bpy_script_action = os.path.join(getcwd, "bpy_checkout.py")
  12 +bpy_script_render = os.path.join(getcwd, "bpy_render.py")
  13 +
  14 +def file_exists(file_path):
  15 + if ((os.path.isfile(file_path) == 1) and (os.path.exists(file_path) == 1)):
  16 + return True
  17 + else:
  18 + return False
  19 +
  20 +def check_action(blend_file = "", action_name = "", action_fake_is_valid = True, min_frame_count = 10, hide_output = True):
  21 + if (file_exists(blend_file)):
  22 + if not (isinstance(blend_file, str) and isinstance(action_name, str)):
  23 + return 1
  24 + if not (isinstance(action_fake_is_valid, bool) and isinstance(min_frame_count, int)):
  25 + return 1
  26 + if (action_name == ""):
  27 + action_name = os.path.splitext(blend_file)[0]
  28 + try:
  29 + json_object = json.JSONEncoder().encode(
  30 + {
  31 + "action_name": action_name.upper(),
  32 + "action_fake_is_valid": action_fake_is_valid,
  33 + "min_frame_count": min_frame_count
  34 + }
  35 + )
  36 + if (hide_output):
  37 + dev_null = open(os.devnull, 'w')
  38 + return subprocess.call(['blender', '-b', blend_file, '-noaudio', '-P', bpy_script_action, "--", json_object], stdout = dev_null, stderr = dev_null)
  39 + else:
  40 + return subprocess.call(['blender', '-b', blend_file, '-noaudio', '-P', bpy_script_action, "--", json_object])
  41 + except:
  42 + return 2
  43 + else:
  44 + return 3
  45 +
  46 +def render_video(blend_file = "", video_output = "", convert_to_webm = True, hide_output = False):
  47 + if (file_exists(blend_file)):
  48 + if not (isinstance(blend_file, str) and isinstance(video_output, str)):
  49 + return 1
  50 + try:
  51 + json_object = json.JSONEncoder().encode(
  52 + {
  53 + "video_output": video_output.upper(),
  54 + "convert_to_webm": convert_to_webm
  55 + }
  56 + )
  57 + if (hide_output):
  58 + dev_null = open(os.devnull, 'w')
  59 + return subprocess.call(['blender', '-b', blend_file, '-noaudio', '-P', bpy_script_render, "--", json_object], stdout = dev_null, stderr = dev_null)
  60 + else:
  61 + return subprocess.call(['blender', '-b', blend_file, '-noaudio', '-P', bpy_script_render, "--", json_object])
  62 + except:
  63 + return 2
  64 + else:
  65 + return 3
  66 +
  67 +def main():
  68 + print("check_action return:", check_action("casa.blend"))
  69 + # return codes
  70 + # 0: [OK] Blend file and Action Name exists
  71 + # 1: [ERROR] Args to check_action no match types
  72 + # 2: [ERROR] Subprocess interrupt
  73 + # 3: [ERROR] Blend file not exists
  74 + # 4: [ERROR] JSON Key no match
  75 + # 5: [ERROR] Action in blend file no exists
  76 + # 6: [ERROR] Action is fake user
  77 + # 7: [ERROR] Timeline Frame Count is less than 10
  78 + # 8: [ERROR] Args count no match
  79 +
  80 + print("render_video return:", render_video("casa.blend", "casa"))
  81 + # return codes
  82 + # 0: [OK] Video.mp4 generated
  83 + # 1: [ERROR] Args to check_action no match types
  84 + # 2: [ERROR] Subprocess interrupt
  85 + # 3: [ERROR] JSON Key no match
  86 + # 4: [ERROR] Except in process
  87 + # 5: [ERROR] Args count no match
  88 + return
  89 +
  90 +if __name__ == "__main__":
  91 + main()
... ...
corretor.py
... ... @@ -3,46 +3,47 @@ from werkzeug import secure_filename
3 3 import pbclient
4 4 import os
5 5 import pyutil
  6 +import checkout
6 7  
7 8 class Corretor:
8   -
  9 +
9 10 def __init__(self, configuration, template_env):
10 11 self.config = configuration
11 12 self.env = template_env
12 13 self.__setup_pb_client()
13 14 self.__setup_upload_folder()
14   -
  15 +
15 16 def __setup_pb_client(self):
16   - pbclient.set('endpoint', self.config['PYBOSSA_ENDPOINT'])
  17 + pbclient.set('endpoint', self.config['PYBOSSA_ENDPOINT'])
17 18 pbclient.set('api_key', self.config['PYBOSSA_API_KEY'])
18   -
  19 +
19 20 def __setup_upload_folder(self):
20 21 upload_folder = self.config['UPLOAD_FOLDER']
21 22 if not os.path.exists(upload_folder):
22 23 os.makedirs(upload_folder)
23   -
  24 +
24 25 def __find_project(self, app_short_name):
25 26 projects = pbclient.find_project(short_name=app_short_name)
26 27 return projects[0] if len(projects) > 0 else None
27   -
  28 +
28 29 def __setup_project(self, project):
29 30 self.__create_tasks(project)
30 31 self.__update_project_info(project)
31   -
  32 +
32 33 def __create_tasks(self, project):
33 34 test_signs = ["ENSINADO", "ENTANTO", "ENTENDIDO"]
34 35 for sign in test_signs:
35 36 task = dict(sign_name=sign, submission_date=pyutil.get_date_now())
36 37 pbclient.create_task(project.id, task)
37   -
38   - def __update_project_info(self, project):
  38 +
  39 + def __update_project_info(self, project):
39 40 template = self.env.get_template('template.html')
40 41 project.info['task_presenter'] = template.render(server=self.config['HOST_ENDPOINT'])
41 42 project.info['thumbnail'] = self.config['HOST_ENDPOINT'] + "/img/thumbnail.png"
42 43 project.info['sched'] = "incremental"
43 44 project.allow_anonymous_contributors = False
44 45 pbclient.update_project(project)
45   -
  46 +
46 47 def create_project(self):
47 48 app_short_name = self.config['PYBOSSA_APP_SHORT_NAME']
48 49 project = self.__find_project(app_short_name)
... ... @@ -54,23 +55,23 @@ class Corretor:
54 55 if (project):
55 56 self.__setup_project(project)
56 57 result_msg = "The project " + app_short_name + " was created."
57   - else:
  58 + else:
58 59 result_msg = "The project " + app_short_name + " couldn't be created. Check the server log for details."
59 60 pyutil.log(result_msg)
60   - return result_msg
  61 + return result_msg
61 62  
62   - def update_project(self):
  63 + def update_project(self):
63 64 app_short_name = self.config['PYBOSSA_APP_SHORT_NAME']
64 65 project = self.__find_project(app_short_name)
65 66 self.__update_project_info(project)
66 67 result_msg = "The project " + app_short_name + " was updated."
67 68 pyutil.log(result_msg)
68 69 return result_msg
69   -
  70 +
70 71 def __allowed_file(self, filename):
71 72 allowed_extensions = set(['blend'])
72 73 return '.' in filename and filename.rsplit('.', 1)[1] in allowed_extensions
73   -
  74 +
74 75 def upload_file(self):
75 76 upload_session_id = request.form['upload_session_id']
76 77 sign_name = request.form['sign_name']
... ... @@ -88,10 +89,13 @@ class Corretor:
88 89 os.makedirs(upload_dir)
89 90 uploaded_file = os.path.join(upload_dir, filename)
90 91 file.save(uploaded_file)
91   - renamed_file = os.path.join(upload_dir, secure_filename(sign_name + "_CORRIGIDO.blend"))
  92 + renamed_file = os.path.join(upload_dir, secure_filename(sign_name + ".blend"))
92 93 os.rename(uploaded_file, renamed_file)
93   - # validar o .blend
94   - code = 200
95   - result_msg = "File " + filename + " was uploaded."
  94 + if (checkout.check_action(renamed_file, sign_name) == 0):
  95 + result_msg = "File " + filename + " was uploaded."
  96 + code = 200
  97 + else:
  98 + result_msg = "File " + filename + " has not expected structure of blend file."
  99 + code = 400
96 100 pyutil.log(result_msg)
97 101 return make_response(result_msg, code)
... ...
view/template.html
... ... @@ -147,13 +147,20 @@
147 147 $("#finish-button").addClass("enabled-button");
148 148 $("#finish-button").off("click").on("click", function() {
149 149 // endpoint - /finish_task
150   - // enviar mensagem via POST para renderizar video - upload/<session_hash>/<task.info.sign_name>_CORRIGIDO.blend
  150 + // enviar mensagem via POST para renderizar video - upload/<session_hash>/<task.info.sign_name>.blend
151 151 enableLoading();
152   - setTimeout(function() {
153   - disableLoading();
154   - status = status_dict[$("#finish-button").text().trim()];
155   - saveAnswer(task, deferred, status);
156   - }, 3000);
  152 + $.post("{{ server }}/finish_task",
  153 + {
  154 + "upload_session_id": upload_session_id,
  155 + "sign_name": task.info.sign_name
  156 + },
  157 + function(response) {
  158 + console.log(response);
  159 + disableLoading();
  160 + status = status_dict[$("#finish-button").text().trim()];
  161 + saveAnswer(task, deferred, status);
  162 + }
  163 + );
157 164 });
158 165 }
159 166  
... ...