@ -0,0 +1,157 @@ | |||||
# ManimOnline | |||||
## 简介 | |||||
**Manim**(**Mathematical Animation Engine**)是由知名Youtube频道3Blue1Brown的运营者**Grant Sanderson**开发的一个数学动画制作引擎。它可以让用户通过编程的方式精准的生成解释性的数学动 画,以帮助观众更加直观的理解晦涩难懂的数学概念。 | |||||
随着Manim功能的扩展,现如今许多科普视频作者都会使用Manim工具制作动画来帮助更直观的解释专 业的知识。然而,使用Manim工具需要通过**Python**语言编写脚本来控制动画中的一切元素与逻辑,这 就使得该工具具有一定的使用⻔槛。此外,Manim渲染和生成动画需要占用一定的系统资源,当画面中元素较为复杂时,就会对用户的硬件系统造成不小的压力。本项目旨在通过网⻚UI交互的形式,让用户能够直观的创建动画中所需的元素,并通过云端渲染快速得到最终的科普动画。 | |||||
## 部署方式 | |||||
### 使用UCloud镜像部署 | |||||
UCloud镜像: | |||||
| 名称 | ManimOnline | | |||||
| ------ | --------------- | | |||||
| ID | uimage-d5ups52d | | |||||
| 用户名 | ubuntu | | |||||
| 密码 | abcd123456 | | |||||
若要使用镜像部署本项目,请将镜像恢复到一台云主机上,随后输入如下命令启动服务:**(服务器镜像中装有screen工具,可创建一个独立的screen使得服务可以在后台持续运行)** | |||||
```shell | |||||
> sudo su | |||||
> cd /home/ubuntu/manim_backend/ | |||||
> export FLASK_APP=app.py | |||||
> export FLASK_ENV=development | |||||
> flask run --host 0.0.0.0 --port 80 | |||||
``` | |||||
### 自行部署 | |||||
若要在云主机上部署本项目,请使用Ubuntu或其他带有apt-get工具的系统。 | |||||
由于本项目使用了Manim作为动画生成工具,因此请先安装必要的系统依赖库: | |||||
```shell | |||||
> apt install sox ffmpeg libcairo2 libcairo2-dev texlive-full | |||||
``` | |||||
请确保系统中装有python环境及pip工具,并通过pip工具安装Flask环境及Manim库: | |||||
```shell | |||||
> pip3 install Flask | |||||
> pip3 install Flask-Cors | |||||
> pip3 install manimlib | |||||
``` | |||||
请使用如下命令启动服务: | |||||
```shell | |||||
> export FLASK_RUN=app.py | |||||
> export FLASK_ENV=development | |||||
> flask run --host 0.0.0.0 --port 80 | |||||
``` | |||||
## 使用说明 | |||||
若要使用**交互式方法**生成动画,您可以通过界面上半部分的组件来创建动画逻辑。为了演示起见,我们在网页中预设了一组动画生成逻辑,您只需要在对应位置**输入画布名称**并**选择画质**即可直接提交生成。随后您需要稍等片刻,网站会自动跳转到视频播放界面**(后台可以看到视频生成进度)**。 | |||||
若要直接使用**Python代码**生成动画,您可以直接在界面下半部分的代码框中输入Python代码,输入画布名称并选择画质即可提交生成**(请注意,画布名称需与场景类名一致!)**。随后稍等片刻,网站同样会自动跳转到对应的视频播放界面。 | |||||
您可以使用如下的Python代码进行较为复杂的动画生成测试:**(该场景元素较为复杂,在常规云主机上可能需要花费1~2分钟左右进行视频生成,此时前端无任何反应属正常现象,请耐心等待,同时也可以通过后台追踪生成进度)** | |||||
```python | |||||
from manimlib.imports import * | |||||
# Test即为画布名 | |||||
class Test(Scene): | |||||
def construct(self): | |||||
title = TextMobject("Hello World!") # 文字 | |||||
basel = TexMobject( # 公式 | |||||
"\\sigma (x) = \\frac{1}{1+e^{-x}}" | |||||
) | |||||
VGroup(title, basel).arrange(DOWN) # 集合到一起后排列位置 | |||||
self.play( | |||||
Write(title), # "写"出title文字 | |||||
FadeInFrom(basel, UP), # 将basel公式从上方淡入 | |||||
) | |||||
self.wait() # 停顿一秒 | |||||
transform_title = TextMobject("That was a transform") | |||||
transform_title.to_corner(UP + LEFT) # 放到最左上角 | |||||
self.play( | |||||
Transform(title, transform_title), # 将title变换为transform_title | |||||
LaggedStart(*map(FadeOutAndShiftDown, basel)), # 将basel公式中的每个字符依次从下方淡出 | |||||
) | |||||
self.wait() # 停顿一秒 | |||||
grid = NumberPlane() # 构建一个坐标平面 | |||||
grid_title = TextMobject("This is a grid") | |||||
grid_title.scale(1.5) | |||||
grid_title.move_to(transform_title) | |||||
self.add(grid, grid_title) # 确保grid_title在grid上方 | |||||
self.play( | |||||
FadeOut(title), # 淡出title | |||||
FadeInFromDown(grid_title), # 从下方淡入grid_title | |||||
ShowCreation(grid, run_time=3, lag_ratio=0.1), # 创建grid的动画,时长为3,延迟为10% | |||||
) | |||||
self.wait() | |||||
grid_transform_title = TextMobject( | |||||
"That was a non-linear function \\\\" | |||||
"applied to the grid" | |||||
) | |||||
grid_transform_title.move_to(grid_title, UL) | |||||
grid.prepare_for_nonlinear_transform() # 让grid准备进行非线性变换 | |||||
self.play( | |||||
grid.apply_function, # 对grid施加一个函数,实现非线性变换 | |||||
lambda p: p + np.array([ # 输入值为一个点,返回值也为一个点 | |||||
np.sin(p[1]), | |||||
np.sin(p[0]), | |||||
0, | |||||
]), | |||||
run_time=3, | |||||
) | |||||
self.wait() | |||||
self.play( | |||||
Transform(grid_title, grid_transform_title) | |||||
) | |||||
self.wait() | |||||
``` | |||||
## 开发环境配置 | |||||
### 后端 | |||||
本项目使用Flask作为后端框架,因此仅需使用带有Flask的python3环境打开deploy目录即可构建后端开发环境。其他系统及库依赖与部署环境一致。 | |||||
### 前端 | |||||
本项目使用了Vue Cli作为前端生成框架,若要搭建前端开发环境,请先在系统中安装Vue-Cli 3脚手架及相应的Node JS工具。随后在系统中使用如下命令安装必要的依赖库: | |||||
```shell | |||||
> npm install axios --save-dev | |||||
> npm install element-ui -S | |||||
``` | |||||
如若该vue文件还未部署到服务器上,需把**src/components**中所有涉及到的地址与**axios请求**中的url前加上后端地址,这样就可以与后端产生交互。 | |||||
请使用如下命令生成dist文件夹,并将其中对应的文件放入后端对应的**templates**及**static**目录内,即可将构建完成的前端网页部署到网站中: | |||||
```shell | |||||
> npm run build | |||||
``` | |||||
## 开发者说明 | |||||
本项目尚处于开发初级阶段,网站中可能存在些许BUG(尤其是前端交互界面),还请多多谅解! | |||||
如有任何问题或建议,请联系我们: | |||||
Github:[GONGGONGJOHN](https://github.com/gonggongjohn) | |||||
WeChat: gonggongjohn | |||||
@ -0,0 +1,119 @@ | |||||
# Byte-compiled / optimized / DLL files | |||||
__pycache__/ | |||||
*.py[cod] | |||||
*$py.class | |||||
# C extensions | |||||
*.so | |||||
/test/ | |||||
# Distribution / packaging | |||||
.Python | |||||
build/ | |||||
develop-eggs/ | |||||
dist/ | |||||
downloads/ | |||||
eggs/ | |||||
.eggs/ | |||||
lib/ | |||||
lib64/ | |||||
parts/ | |||||
sdist/ | |||||
var/ | |||||
wheels/ | |||||
pip-wheel-metadata/ | |||||
share/python-wheels/ | |||||
*.egg-info/ | |||||
.installed.cfg | |||||
*.egg | |||||
MANIFEST | |||||
# PyInstaller | |||||
# Usually these files are written by a python script from a template | |||||
# before PyInstaller builds the exe, so as to inject date/other infos into it. | |||||
*.manifest | |||||
*.spec | |||||
.idea/ | |||||
# Installer logs | |||||
pip-log.txt | |||||
pip-delete-this-directory.txt | |||||
# Unit test / coverage reports | |||||
htmlcov/ | |||||
.tox/ | |||||
.nox/ | |||||
.coverage | |||||
.coverage.* | |||||
.cache | |||||
nosetests.xml | |||||
coverage.xml | |||||
*.cover | |||||
.hypothesis/ | |||||
.pytest_cache/ | |||||
# Translations | |||||
*.mo | |||||
*.pot | |||||
# Django stuff: | |||||
*.log | |||||
local_settings.py | |||||
db.sqlite3 | |||||
# Flask stuff: | |||||
instance/ | |||||
.webassets-cache | |||||
# Scrapy stuff: | |||||
.scrapy | |||||
# Sphinx documentation | |||||
docs/_build/ | |||||
# PyBuilder | |||||
target/ | |||||
# Jupyter Notebook | |||||
.ipynb_checkpoints | |||||
# IPython | |||||
profile_default/ | |||||
ipython_config.py | |||||
# pyenv | |||||
.python-version | |||||
# celery beat schedule file | |||||
celerybeat-schedule | |||||
# SageMath parsed files | |||||
*.sage.py | |||||
# Environments | |||||
.env | |||||
.venv | |||||
env/ | |||||
venv/ | |||||
ENV/ | |||||
env.bak/ | |||||
venv.bak/ | |||||
# Spyder project settings | |||||
.spyderproject | |||||
.spyproject | |||||
# Rope project settings | |||||
.ropeproject | |||||
# mkdocs documentation | |||||
/site | |||||
# mypy | |||||
.mypy_cache/ | |||||
.dmypy.json | |||||
dmypy.json | |||||
# Pyre type checker | |||||
.pyre/ |
@ -0,0 +1,172 @@ | |||||
from flask import Flask | |||||
from flask import request | |||||
from flask import render_template | |||||
from flask_cors import cross_origin | |||||
from manim_utils import ManimHelper | |||||
from code_generator import CodeGenerator | |||||
from video_generator import generate_video | |||||
import json | |||||
import sqlite3 | |||||
import os | |||||
app = Flask(__name__) | |||||
DATABASE = 'database/om.db' | |||||
@app.before_first_request | |||||
def create_db(): | |||||
if not os.path.exists(DATABASE): | |||||
connection = sqlite3.connect(DATABASE) | |||||
cursor = connection.cursor() | |||||
with app.open_resource('schema.sql') as f: | |||||
cursor.executescript(f.read().decode('utf8')) | |||||
connection.commit() | |||||
def get_db(): | |||||
db = sqlite3.connect(DATABASE) | |||||
db.row_factory = sqlite3.Row | |||||
return db | |||||
def db_operate(command): | |||||
db = get_db() | |||||
cur = db.execute(command) | |||||
db.commit() | |||||
result = cur.fetchall() | |||||
db.close() | |||||
return result | |||||
@app.route('/') | |||||
@cross_origin() | |||||
def root(): | |||||
return render_template('index.html') | |||||
@app.route('/register') | |||||
@cross_origin() | |||||
def register(): | |||||
username = request.args.get('username') | |||||
password = request.args.get('password') | |||||
sql_user_str = 'SELECT username FROM user WHERE username=\'{0}\''.format(username) | |||||
result = db_operate(sql_user_str) | |||||
if len(result) == 0: | |||||
sql_str = 'INSERT INTO user (username, password) VALUES (\'{0}\', \'{1}\')'.format(username, password) | |||||
db_operate(sql_str) | |||||
return json.dumps({'status': 'successful'}) | |||||
else: | |||||
return json.dumps({'status': 'fail'}) | |||||
@app.route('/login') | |||||
@cross_origin() | |||||
def login(): | |||||
username = request.args.get('username') | |||||
password = request.args.get('password') | |||||
sql_str = 'SELECT password FROM user WHERE username=\'{0}\''.format(username) | |||||
result = db_operate(sql_str) | |||||
if len(result) != 0: | |||||
if password == result[0][0]: | |||||
sql_dir_str = 'SELECT name FROM directory WHERE owner=\'{0}\''.format(username) | |||||
result_dir = db_operate(sql_dir_str) | |||||
name_list = [] | |||||
for name in result_dir: | |||||
name_list.append(name[0]) | |||||
print(result_dir) | |||||
return json.dumps({'status': 'successful', 'dir': name_list}) | |||||
else: | |||||
return json.dumps({'status': 'fail'}) | |||||
else: | |||||
return json.dumps({'status': 'fail'}) | |||||
@app.route('/class') | |||||
@cross_origin() | |||||
def get_class(): | |||||
manim_helper = ManimHelper() | |||||
class_list = manim_helper.get_class_list() | |||||
return json.dumps(class_list) | |||||
@app.route('/param') | |||||
@cross_origin() | |||||
def get_param(): | |||||
function = request.args.get('function') | |||||
manim_helper = ManimHelper() | |||||
arguments = manim_helper.get_arguments(function) | |||||
return json.dumps(arguments) | |||||
@app.route('/function') | |||||
@cross_origin() | |||||
def get_function(): | |||||
class_name = request.args.get('class') | |||||
manim_helper = ManimHelper() | |||||
functions = manim_helper.get_function(str(class_name)) | |||||
return json.dumps(functions) | |||||
@app.route('/generate', methods=['POST']) | |||||
@cross_origin() | |||||
def generate(): | |||||
codes = json.loads(request.get_data()) | |||||
username = codes['username'] | |||||
scene_name = codes['scene_name'] | |||||
quality = codes['quality'] | |||||
sql_str = 'SELECT id FROM directory WHERE owner=\'{0}\' AND name=\'{1}\''.format(username, scene_name) | |||||
result = db_operate(sql_str) | |||||
if len(result) == 0: | |||||
sql_create_str = 'INSERT INTO directory (owner, name) VALUES (\'{0}\', \'{1}\')'.format(username, scene_name) | |||||
db_operate(sql_create_str) | |||||
result = db_operate(sql_str) | |||||
directory_name = result[0][0] | |||||
code_generator = CodeGenerator() | |||||
if codes['mode'] == 'interactive': | |||||
code_generator.assemble_code(codes) | |||||
# Put to user directory | |||||
directory_path = 'static/{0}/'.format(directory_name) | |||||
code_file_path = 'static/{0}/{0}.py'.format(directory_name) | |||||
video_file_path = 'static/{0}/'.format(directory_name) | |||||
asset_file_path = 'static/{0}/'.format(directory_name) | |||||
video_name = scene_name | |||||
if codes['mode'] == 'interactive': | |||||
code_generator.to_file(directory_path, code_file_path) | |||||
elif codes['mode'] == 'python': | |||||
code_generator.py_to_file(codes['codes'], directory_path, code_file_path) | |||||
w, h = generate_video(code_file_path, asset_file_path, video_file_path, video_name, quality) | |||||
return json.dumps({'status': 'successful'}) | |||||
@app.route('/video') | |||||
@cross_origin() | |||||
def show_video(): | |||||
username = request.args.get('username') | |||||
scene_name = request.args.get('scene_name') | |||||
quality = request.args.get('quality') | |||||
sql_str = 'SELECT id FROM directory WHERE owner=\'{0}\' AND name=\'{1}\''.format(username, scene_name) | |||||
result = db_operate(sql_str) | |||||
if len(result) == 0: | |||||
return json.dumps({'status': 'fail'}) | |||||
directory_name = result[0][0] | |||||
video_file_path = 'static/{0}/'.format(directory_name) | |||||
video_name = scene_name | |||||
w = 2560 | |||||
h = 1440 | |||||
if quality == 'w': | |||||
w = 2560 | |||||
h = 1440 | |||||
elif quality == 'h': | |||||
w = 1920 | |||||
h = 1080 | |||||
elif quality == 'm': | |||||
w = 1280 | |||||
h = 720 | |||||
elif quality == 'l': | |||||
w = 854 | |||||
h = 480 | |||||
return render_template('video_presentator.html', width=w, height=h, video=video_file_path + video_name + '.mp4') | |||||
if __name__ == '__main__': | |||||
app.run() |
@ -0,0 +1,62 @@ | |||||
import json | |||||
import os | |||||
class CodeGenerator: | |||||
def __init__(self): | |||||
self.import_str = 'from manimlib.imports import *' | |||||
self.class_declare_template = 'class {0}(Scene):' | |||||
self.construct_str = 'def construct(self):' | |||||
self.indent = ' ' | |||||
self.code_list = [] | |||||
self.wait_str = 'self.wait()' | |||||
def assemble_code(self, codes): | |||||
self.code_list.append(self.import_str) | |||||
class_declare = self.class_declare_template.format(codes['scene_name']) | |||||
self.code_list.append(class_declare) | |||||
self.code_list.append(self.indent + self.construct_str) | |||||
indent_cur = 2 | |||||
for item in codes['codes']: | |||||
param_list = [] | |||||
for param in item['params']: | |||||
if param == 'varargs': | |||||
vararg_list = [] | |||||
for sub_param in item['params'][param]: | |||||
if sub_param['type'] == 'string': | |||||
vararg_list.append('\"' + sub_param['content'] + '\"') | |||||
elif sub_param['type'] == 'instance': | |||||
vararg_list.append(sub_param['content']) | |||||
param_list.extend(vararg_list) | |||||
else: | |||||
if item['params'][param]['type'] == 'string': | |||||
param_display = '\'' + item['params'][param]['content'] + '\'' | |||||
elif item['params'][param]['type'] == 'constant': | |||||
param_display = item['params'][param]['content'] | |||||
else: | |||||
param_display = str(item['params'][param]['content']) | |||||
param_item = param + '=' + param_display | |||||
param_list.append(param_item) | |||||
param_str = ', '.join(param_list) | |||||
if item['type'] == 'create_instance': | |||||
construct_str = item['class'] + '(' + param_str + ')' | |||||
line = self.indent * indent_cur + item['name'] + ' = ' + construct_str | |||||
self.code_list.append(line) | |||||
elif item['type'] == 'call_function': | |||||
function_str = item['function'] + '(' + param_str + ')' | |||||
line = self.indent * indent_cur + item['instance'] + '.' + function_str | |||||
self.code_list.append(line) | |||||
self.code_list.append(self.indent * indent_cur + self.wait_str) | |||||
def to_file(self, directory_path, file_path): | |||||
if not os.path.exists(directory_path): | |||||
os.mkdir(directory_path) | |||||
with open(file_path, 'w') as file: | |||||
for code in self.code_list: | |||||
file.write(code + '\n') | |||||
def py_to_file(self, code_str, directory_path, file_path): | |||||
if not os.path.exists(directory_path): | |||||
os.mkdir(directory_path) | |||||
with open(file_path, 'w') as file: | |||||
file.write(code_str) |
@ -0,0 +1,51 @@ | |||||
import manimlib.imports | |||||
import inspect | |||||
import json | |||||
class ManimHelper: | |||||
def hierarchical_phase(self, class_dict, module_list, class_name): | |||||
if len(module_list) == 1: | |||||
if module_list[0] not in class_dict: | |||||
class_dict[module_list[0]] = [] | |||||
class_dict[module_list[0]].append(class_name) | |||||
else: | |||||
if module_list[0] not in class_dict: | |||||
class_dict[module_list[0]] = {} | |||||
tmp_key = module_list.pop(0) | |||||
class_dict[tmp_key] = self.hierarchical_phase(class_dict[tmp_key], module_list, class_name) | |||||
return class_dict | |||||
def get_class_list(self): | |||||
members = inspect.getmembers(manimlib.imports, inspect.isclass) | |||||
class_dict = {} | |||||
for (member_name, member_type) in members: | |||||
module_list = str(member_type.__module__).split('.') | |||||
if module_list[0] == 'manimlib': | |||||
class_dict = self.hierarchical_phase(class_dict, module_list, member_name) | |||||
return class_dict | |||||
def get_arguments(self, name): | |||||
arg_spec = inspect.getfullargspec(eval(name)) | |||||
print(arg_spec) | |||||
arg_name = arg_spec.args | |||||
if arg_name[0] == 'self': | |||||
arg_name.pop(0) | |||||
arg_default = arg_spec.defaults | |||||
arguments = [] | |||||
for i in range(0, len(arg_name)): | |||||
instance_dict = {'name': arg_name[i], 'default': 0} | |||||
arguments.append(instance_dict) | |||||
return {'arguments': arguments} | |||||
def get_function(self, class_name): | |||||
functions = inspect.getmembers(eval(class_name), inspect.isfunction) | |||||
function_list = [] | |||||
for (function, _) in functions: | |||||
if function[0:2] != '__': | |||||
function_list.append(function) | |||||
return {'functions': function_list} | |||||
# manim_helper = ManimHelper() | |||||
# print(manim_helper.get_function('manimlib.mobject.svg.tex_mobject.TextMobject')) |
@ -0,0 +1,14 @@ | |||||
DROP TABLE IF EXISTS user; | |||||
DROP TABLE IF EXISTS directory; | |||||
CREATE TABLE user( | |||||
id INTEGER PRIMARY KEY AUTOINCREMENT, | |||||
username TEXT UNIQUE NOT NULL, | |||||
password TEXT NOT NULL | |||||
); | |||||
CREATE TABLE directory( | |||||
id INTEGER PRIMARY KEY AUTOINCREMENT, | |||||
owner TEXT NOT NULL, | |||||
name TEXT NOT NULL | |||||
); |
@ -0,0 +1,2 @@ | |||||
!function(r){var n=window.webpackJsonp;window.webpackJsonp=function(e,u,c){for(var f,i,p,a=0,l=[];a<e.length;a++)i=e[a],o[i]&&l.push(o[i][0]),o[i]=0;for(f in u)Object.prototype.hasOwnProperty.call(u,f)&&(r[f]=u[f]);for(n&&n(e,u,c);l.length;)l.shift()();if(c)for(a=0;a<c.length;a++)p=t(t.s=c[a]);return p};var e={},o={2:0};function t(n){if(e[n])return e[n].exports;var o=e[n]={i:n,l:!1,exports:{}};return r[n].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=r,t.c=e,t.d=function(r,n,e){t.o(r,n)||Object.defineProperty(r,n,{configurable:!1,enumerable:!0,get:e})},t.n=function(r){var n=r&&r.__esModule?function(){return r.default}:function(){return r};return t.d(n,"a",n),n},t.o=function(r,n){return Object.prototype.hasOwnProperty.call(r,n)},t.p="./",t.oe=function(r){throw console.error(r),r}}([]); | |||||
//# sourceMappingURL=manifest.3ad1d5771e9b13dbdad2.js.map |
@ -0,0 +1 @@ | |||||
{"version":3,"sources":["webpack:///webpack/bootstrap 33d76d614ad607f8d328"],"names":["parentJsonpFunction","window","chunkIds","moreModules","executeModules","moduleId","chunkId","result","i","resolves","length","installedChunks","push","Object","prototype","hasOwnProperty","call","modules","shift","__webpack_require__","s","installedModules","2","exports","module","l","m","c","d","name","getter","o","defineProperty","configurable","enumerable","get","n","__esModule","object","property","p","oe","err","console","error"],"mappings":"aACA,IAAAA,EAAAC,OAAA,aACAA,OAAA,sBAAAC,EAAAC,EAAAC,GAIA,IADA,IAAAC,EAAAC,EAAAC,EAAAC,EAAA,EAAAC,KACQD,EAAAN,EAAAQ,OAAoBF,IAC5BF,EAAAJ,EAAAM,GACAG,EAAAL,IACAG,EAAAG,KAAAD,EAAAL,GAAA,IAEAK,EAAAL,GAAA,EAEA,IAAAD,KAAAF,EACAU,OAAAC,UAAAC,eAAAC,KAAAb,EAAAE,KACAY,EAAAZ,GAAAF,EAAAE,IAIA,IADAL,KAAAE,EAAAC,EAAAC,GACAK,EAAAC,QACAD,EAAAS,OAAAT,GAEA,GAAAL,EACA,IAAAI,EAAA,EAAYA,EAAAJ,EAAAM,OAA2BF,IACvCD,EAAAY,IAAAC,EAAAhB,EAAAI,IAGA,OAAAD,GAIA,IAAAc,KAGAV,GACAW,EAAA,GAIA,SAAAH,EAAAd,GAGA,GAAAgB,EAAAhB,GACA,OAAAgB,EAAAhB,GAAAkB,QAGA,IAAAC,EAAAH,EAAAhB,IACAG,EAAAH,EACAoB,GAAA,EACAF,YAUA,OANAN,EAAAZ,GAAAW,KAAAQ,EAAAD,QAAAC,IAAAD,QAAAJ,GAGAK,EAAAC,GAAA,EAGAD,EAAAD,QAKAJ,EAAAO,EAAAT,EAGAE,EAAAQ,EAAAN,EAGAF,EAAAS,EAAA,SAAAL,EAAAM,EAAAC,GACAX,EAAAY,EAAAR,EAAAM,IACAhB,OAAAmB,eAAAT,EAAAM,GACAI,cAAA,EACAC,YAAA,EACAC,IAAAL,KAMAX,EAAAiB,EAAA,SAAAZ,GACA,IAAAM,EAAAN,KAAAa,WACA,WAA2B,OAAAb,EAAA,SAC3B,WAAiC,OAAAA,GAEjC,OADAL,EAAAS,EAAAE,EAAA,IAAAA,GACAA,GAIAX,EAAAY,EAAA,SAAAO,EAAAC,GAAsD,OAAA1B,OAAAC,UAAAC,eAAAC,KAAAsB,EAAAC,IAGtDpB,EAAAqB,EAAA,KAGArB,EAAAsB,GAAA,SAAAC,GAA8D,MAApBC,QAAAC,MAAAF,GAAoBA","file":"static/js/manifest.3ad1d5771e9b13dbdad2.js","sourcesContent":[" \t// install a JSONP callback for chunk loading\n \tvar parentJsonpFunction = window[\"webpackJsonp\"];\n \twindow[\"webpackJsonp\"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {\n \t\t// add \"moreModules\" to the modules object,\n \t\t// then flag all \"chunkIds\" as loaded and fire callback\n \t\tvar moduleId, chunkId, i = 0, resolves = [], result;\n \t\tfor(;i < chunkIds.length; i++) {\n \t\t\tchunkId = chunkIds[i];\n \t\t\tif(installedChunks[chunkId]) {\n \t\t\t\tresolves.push(installedChunks[chunkId][0]);\n \t\t\t}\n \t\t\tinstalledChunks[chunkId] = 0;\n \t\t}\n \t\tfor(moduleId in moreModules) {\n \t\t\tif(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {\n \t\t\t\tmodules[moduleId] = moreModules[moduleId];\n \t\t\t}\n \t\t}\n \t\tif(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules, executeModules);\n \t\twhile(resolves.length) {\n \t\t\tresolves.shift()();\n \t\t}\n \t\tif(executeModules) {\n \t\t\tfor(i=0; i < executeModules.length; i++) {\n \t\t\t\tresult = __webpack_require__(__webpack_require__.s = executeModules[i]);\n \t\t\t}\n \t\t}\n \t\treturn result;\n \t};\n\n \t// The module cache\n \tvar installedModules = {};\n\n \t// objects to store loaded and loading chunks\n \tvar installedChunks = {\n \t\t2: 0\n \t};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"./\";\n\n \t// on error function for async loading\n \t__webpack_require__.oe = function(err) { console.error(err); throw err; };\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap 33d76d614ad607f8d328"],"sourceRoot":""} |
@ -0,0 +1 @@ | |||||
<!DOCTYPE html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>demo</title><link rel="shortcut icon" href=./M.jpg><link href=./static/css/app.e3820924ee579536348e1dd361736c70.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=./static/js/manifest.3ad1d5771e9b13dbdad2.js></script><script type=text/javascript src=./static/js/vendor.b1e79088973ebca1a446.js></script><script type=text/javascript src=./static/js/app.7ae722821be0ba88f848.js></script></body></html> |
@ -0,0 +1,12 @@ | |||||
<!DOCTYPE html> | |||||
<html lang="en"> | |||||
<head> | |||||
<meta charset="UTF-8"> | |||||
<title>视频推送</title> | |||||
</head> | |||||
<body> | |||||
<video width="{{ width }}" height="{{ height }}" controls="controls"> | |||||
<source src="{{video}}" type="video/mp4" /> | |||||
</video> | |||||
</body> | |||||
</html> |
@ -0,0 +1,25 @@ | |||||
import os | |||||
def generate_video(source_path, target_dir, video_dir, scene_name, quality): | |||||
command_template = 'manim {0} {1} --media_dir {2} --video_output_dir {3} {4}' | |||||
width = 2560 | |||||
height = 1440 | |||||
q_str = '-w' | |||||
if quality == 'w': | |||||
q_str = '-w' | |||||
elif quality == 'h': | |||||
q_str = '--high_quality' | |||||
width = 1920 | |||||
height = 1080 | |||||
elif quality == 'm': | |||||
q_str = '-m' | |||||
width = 1280 | |||||
height = 720 | |||||
elif quality == 'l': | |||||
q_str = '-l' | |||||
width = 854 | |||||
height = 480 | |||||
command = command_template.format(source_path, scene_name, target_dir, video_dir, q_str) | |||||
os.system(command) | |||||
return width, height |