You've already forked FrameTour-RenderWorker
Init
This commit is contained in:
4
.env
Normal file
4
.env
Normal file
@ -0,0 +1,4 @@
|
||||
TEMPLATE_DIR=template/
|
||||
API_ENDPOINT=/task
|
||||
API_TOKEN=123456
|
||||
TEMP_DIR=tmp/
|
28
.gitignore
vendored
Normal file
28
.gitignore
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
.idea/
|
||||
.idea_modules/
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
build/
|
||||
*.egg-info/
|
||||
*.egg
|
||||
*.manifest
|
||||
*.spec
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
*.mo
|
||||
*.pot
|
||||
*.log
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
docs/_build/
|
||||
.pybuilder/
|
||||
target/
|
||||
.ipynb_checkpoints
|
||||
.venv
|
||||
venv/
|
||||
cython_debug/
|
56
biz/ffmpeg.py
Normal file
56
biz/ffmpeg.py
Normal file
@ -0,0 +1,56 @@
|
||||
from entity.ffmpeg import FfmpegTask
|
||||
import logging
|
||||
|
||||
from util import ffmpeg
|
||||
|
||||
logger = logging.getLogger('biz/ffmpeg')
|
||||
|
||||
|
||||
def parse_ffmpeg_task(task_info, template_info):
|
||||
tasks = []
|
||||
# 中间片段
|
||||
for part in template_info.get("video_parts"):
|
||||
sub_ffmpeg_task = parse_video_part(part, task_info)
|
||||
if not sub_ffmpeg_task:
|
||||
continue
|
||||
tasks.append(sub_ffmpeg_task)
|
||||
task = FfmpegTask(tasks, output_file="test.mp4")
|
||||
task.correct_task_type()
|
||||
overall = template_info.get("overall_template")
|
||||
task.add_lut(*overall.get('luts', []))
|
||||
task.add_audios(*overall.get('audios', []))
|
||||
task.add_overlay(*overall.get('overlays', []))
|
||||
return task
|
||||
|
||||
|
||||
def parse_video_part(video_part, task_info):
|
||||
source = select_video_if_needed(video_part.get('source'), task_info)
|
||||
if not source:
|
||||
logger.warning("no video found for part: " + str(video_part))
|
||||
return None
|
||||
task = FfmpegTask(source)
|
||||
task.add_lut(*video_part.get('luts', []))
|
||||
task.add_audios(*video_part.get('audios', []))
|
||||
task.add_overlay(*video_part.get('overlays', []))
|
||||
return task
|
||||
|
||||
|
||||
def select_video_if_needed(source, task_info):
|
||||
if source.startswith('PLACEHOLDER_'):
|
||||
placeholder_id = source.replace('PLACEHOLDER_', '')
|
||||
new_sources = task_info.get('user_videos', {}).get(placeholder_id, [])
|
||||
if type(new_sources) is list:
|
||||
if len(new_sources) == 0:
|
||||
logger.debug("no video found for placeholder: " + placeholder_id)
|
||||
return None
|
||||
else:
|
||||
# TODO: Random Pick / Policy Pick
|
||||
new_sources = new_sources[0]
|
||||
return new_sources
|
||||
return source
|
||||
|
||||
|
||||
def start_ffmpeg_task(ffmpeg_task):
|
||||
for task in ffmpeg_task.analyze_input_render_tasks():
|
||||
start_ffmpeg_task(task)
|
||||
ffmpeg.start_render(ffmpeg_task)
|
0
biz/render.py
Normal file
0
biz/render.py
Normal file
15
biz/task.py
Normal file
15
biz/task.py
Normal file
@ -0,0 +1,15 @@
|
||||
from template import get_template_def
|
||||
|
||||
|
||||
def normalize_task(task_info):
|
||||
...
|
||||
return task_info
|
||||
|
||||
|
||||
def start_task(task_info):
|
||||
from biz.ffmpeg import parse_ffmpeg_task, start_ffmpeg_task
|
||||
task_info = normalize_task(task_info)
|
||||
task_template = "test_template"
|
||||
template_info = get_template_def(task_template)
|
||||
ffmpeg_task = parse_ffmpeg_task(task_info, template_info)
|
||||
result = start_ffmpeg_task(ffmpeg_task)
|
16
config/__init__.py
Normal file
16
config/__init__.py
Normal file
@ -0,0 +1,16 @@
|
||||
import datetime
|
||||
import logging
|
||||
from logging.handlers import TimedRotatingFileHandler
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
root_logger = logging.getLogger()
|
||||
rf_handler = TimedRotatingFileHandler('all_log.log', when='midnight')
|
||||
rf_handler.setFormatter(logging.Formatter("[%(asctime)s][%(name)s]%(levelname)s - %(message)s"))
|
||||
rf_handler.setLevel(logging.DEBUG)
|
||||
f_handler = TimedRotatingFileHandler('error.log', when='midnight')
|
||||
f_handler.setLevel(logging.ERROR)
|
||||
f_handler.setFormatter(logging.Formatter("[%(asctime)s][%(name)s][:%(lineno)d]%(levelname)s - - %(message)s"))
|
||||
root_logger.addHandler(rf_handler)
|
||||
root_logger.addHandler(f_handler)
|
6
constant/__init__.py
Normal file
6
constant/__init__.py
Normal file
@ -0,0 +1,6 @@
|
||||
SUPPORT_FEATURE = (
|
||||
'simple_render_algo',
|
||||
'gpu_accelerate',
|
||||
'intel_gpu_accelerate',
|
||||
)
|
||||
SOFTWARE_VERSION = '0.0.1'
|
111
entity/ffmpeg.py
Normal file
111
entity/ffmpeg.py
Normal file
@ -0,0 +1,111 @@
|
||||
|
||||
class FfmpegTask(object):
|
||||
|
||||
def __init__(self, input_file, task_type='copy', output_file=''):
|
||||
if type(input_file) is str:
|
||||
self.input_file = [input_file]
|
||||
elif type(input_file) is list:
|
||||
self.input_file = input_file
|
||||
else:
|
||||
self.input_file = []
|
||||
self.task_type = task_type
|
||||
self.output_file = output_file
|
||||
self.mute = True
|
||||
self.speed = 1
|
||||
self.subtitles = []
|
||||
self.luts = []
|
||||
self.audios = []
|
||||
self.overlays = []
|
||||
self.annexb = False
|
||||
|
||||
def __repr__(self):
|
||||
_str = f'FfmpegTask(input_file={self.input_file}, task_type={self.task_type}'
|
||||
if len(self.luts) > 0:
|
||||
_str += f', luts={self.luts}'
|
||||
if len(self.audios) > 0:
|
||||
_str += f', audios={self.audios}'
|
||||
if len(self.overlays) > 0:
|
||||
_str += f', overlays={self.overlays}'
|
||||
if self.annexb:
|
||||
_str += f', annexb={self.annexb}'
|
||||
if self.mute:
|
||||
_str += f', mute={self.mute}'
|
||||
return _str + ')'
|
||||
|
||||
def analyze_input_render_tasks(self):
|
||||
for i in self.input_file:
|
||||
if type(i) is str:
|
||||
continue
|
||||
elif type(i) is FfmpegTask:
|
||||
if i.need_run():
|
||||
yield i
|
||||
|
||||
def need_run(self):
|
||||
"""
|
||||
判断是否需要运行
|
||||
:rtype: bool
|
||||
:return:
|
||||
"""
|
||||
if self.annexb:
|
||||
return True
|
||||
# TODO: copy from url
|
||||
return not self.check_can_copy()
|
||||
|
||||
def add_inputs(self, *inputs):
|
||||
self.input_file.extend(inputs)
|
||||
|
||||
def add_overlay(self, *overlays):
|
||||
self.overlays.extend(overlays)
|
||||
self.correct_task_type()
|
||||
|
||||
def add_audios(self, *audios):
|
||||
self.audios.extend(audios)
|
||||
self.correct_task_type()
|
||||
self.check_audio_track()
|
||||
|
||||
def add_lut(self, *luts):
|
||||
self.luts.extend(luts)
|
||||
self.correct_task_type()
|
||||
|
||||
def get_output_file(self):
|
||||
if self.task_type == 'copy':
|
||||
return self.input_file
|
||||
return self.output_file
|
||||
|
||||
def correct_task_type(self):
|
||||
if self.check_can_copy():
|
||||
self.task_type = 'copy'
|
||||
elif self.check_can_concat():
|
||||
self.task_type = 'concat'
|
||||
else:
|
||||
self.task_type = 'encode'
|
||||
|
||||
def check_can_concat(self):
|
||||
if len(self.luts) > 0:
|
||||
return False
|
||||
if len(self.overlays) > 0:
|
||||
return False
|
||||
if len(self.subtitles) > 0:
|
||||
return False
|
||||
if self.speed != 1:
|
||||
return False
|
||||
return True
|
||||
|
||||
def check_can_copy(self):
|
||||
if len(self.luts) > 0:
|
||||
return False
|
||||
if len(self.overlays) > 0:
|
||||
return False
|
||||
if len(self.subtitles) > 0:
|
||||
return False
|
||||
if self.speed != 1:
|
||||
return False
|
||||
if len(self.audios) > 1:
|
||||
return False
|
||||
if len(self.input_file) > 1:
|
||||
return False
|
||||
return True
|
||||
|
||||
def check_audio_track(self):
|
||||
if len(self.audios) > 0:
|
||||
self.mute = False
|
19
index.py
Normal file
19
index.py
Normal file
@ -0,0 +1,19 @@
|
||||
from time import sleep
|
||||
|
||||
import biz.task
|
||||
import config
|
||||
from template import load_local_template
|
||||
from util import api
|
||||
from util.system import get_sys_info
|
||||
|
||||
load_local_template()
|
||||
|
||||
|
||||
while True:
|
||||
# print(get_sys_info())
|
||||
print("waiting for task...")
|
||||
task_list = api.get_render_task()
|
||||
for task in task_list:
|
||||
print("start task:", task)
|
||||
biz.task.start_task(task)
|
||||
break
|
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@ -0,0 +1,3 @@
|
||||
requests~=2.32.3
|
||||
psutil~=6.1.0
|
||||
python-dotenv~=1.0.1
|
1
template/.gitignore
vendored
Normal file
1
template/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
**/*
|
79
template/__init__.py
Normal file
79
template/__init__.py
Normal file
@ -0,0 +1,79 @@
|
||||
import json
|
||||
import os
|
||||
import logging
|
||||
|
||||
TEMPLATES = {}
|
||||
logger = logging.getLogger("template")
|
||||
|
||||
def check_local_template(local_name):
|
||||
template_def = TEMPLATES[local_name]
|
||||
base_dir = template_def.get("local_path")
|
||||
for video_part in template_def.get("video_parts", []):
|
||||
source_file = video_part.get("source", "")
|
||||
if str(source_file).startswith("http"):
|
||||
# download file
|
||||
...
|
||||
elif str(source_file).startswith("PLACEHOLDER_"):
|
||||
continue
|
||||
else:
|
||||
if not os.path.isabs(source_file):
|
||||
source_file = os.path.join(base_dir, source_file)
|
||||
if not os.path.exists(source_file):
|
||||
logger.error(f"{source_file} not found, please check the template definition")
|
||||
raise Exception(f"{source_file} not found, please check the template definition")
|
||||
for audio in video_part.get("audios", []):
|
||||
if not os.path.isabs(audio):
|
||||
audio = os.path.join(base_dir, audio)
|
||||
if not os.path.exists(audio):
|
||||
logger.error(f"{audio} not found, please check the template definition")
|
||||
raise Exception(f"{audio} not found, please check the template definition")
|
||||
for lut in video_part.get("luts", []):
|
||||
if not os.path.isabs(lut):
|
||||
lut = os.path.join(base_dir, lut)
|
||||
if not os.path.exists(lut):
|
||||
logger.error(f"{lut} not found, please check the template definition")
|
||||
raise Exception(f"{lut} not found, please check the template definition")
|
||||
for mask in video_part.get("overlays", []):
|
||||
if not os.path.isabs(mask):
|
||||
mask = os.path.join(base_dir, mask)
|
||||
if not os.path.exists(mask):
|
||||
logger.error(f"{mask} not found, please check the template definition")
|
||||
raise Exception(f"{mask} not found, please check the template definition")
|
||||
|
||||
|
||||
def load_template(template_name, local_path):
|
||||
global TEMPLATES
|
||||
logger.info(f"加载视频模板定义:【{template_name}({local_path})】")
|
||||
template_def_file = os.path.join(local_path, "template.json")
|
||||
if os.path.exists(template_def_file):
|
||||
TEMPLATES[template_name] = json.load(open(template_def_file, 'rb'))
|
||||
TEMPLATES[template_name]["local_path"] = local_path
|
||||
try:
|
||||
check_local_template(template_name)
|
||||
logger.info(f"完成加载【{template_name}】模板")
|
||||
except Exception as e:
|
||||
logger.error(f"模板定义文件【{template_def_file}】有误,正在尝试重新下载模板", exc_info=e)
|
||||
download_template(template_name)
|
||||
|
||||
|
||||
def load_local_template():
|
||||
for template_name in os.listdir(os.getenv("TEMPLATE_DIR")):
|
||||
if template_name.startswith("_"):
|
||||
continue
|
||||
if template_name.startswith("."):
|
||||
continue
|
||||
target_path = os.path.join(os.getenv("TEMPLATE_DIR"), template_name)
|
||||
if os.path.isdir(target_path):
|
||||
load_template(template_name, target_path)
|
||||
|
||||
|
||||
def get_template_def(template_id):
|
||||
return TEMPLATES.get(template_id)
|
||||
|
||||
def download_template(template_id):
|
||||
logger.info(f"下载模板:{template_id}")
|
||||
...
|
||||
|
||||
|
||||
def analyze_template(template_id):
|
||||
...
|
63
util/api.py
Normal file
63
util/api.py
Normal file
@ -0,0 +1,63 @@
|
||||
import requests
|
||||
|
||||
session = requests.Session()
|
||||
|
||||
|
||||
def get_render_task():
|
||||
"""
|
||||
通过接口获取任务
|
||||
:return: 任务列表
|
||||
"""
|
||||
tasks = []
|
||||
tasks.append({
|
||||
'user_videos': {
|
||||
'CAM_ID': 'paper-planes.mp4'
|
||||
}
|
||||
})
|
||||
return tasks
|
||||
|
||||
|
||||
def get_template_info(template_id):
|
||||
"""
|
||||
通过接口获取模板信息
|
||||
:rtype: Template
|
||||
:param template_id: 模板id
|
||||
:type template_id: str
|
||||
:return: 模板信息
|
||||
"""
|
||||
template = {
|
||||
'id': template_id,
|
||||
'name': '模板名称',
|
||||
'description': '模板描述',
|
||||
'video_size': '1920x1080',
|
||||
'frame_rate': 30,
|
||||
'overall_duration': 30,
|
||||
'video_parts': [
|
||||
{
|
||||
'source': './template/test_template/1.mp4',
|
||||
'mute': True,
|
||||
},
|
||||
{
|
||||
'source': 'PLACEHOLDER_CAM_ID',
|
||||
'mute': True,
|
||||
'overlays': [
|
||||
'./template/test_template/2.mov'
|
||||
],
|
||||
'luts': [
|
||||
'./template/test_template/cube.cube'
|
||||
]
|
||||
},
|
||||
{
|
||||
'source': './template/test_template/3.mp4',
|
||||
'mute': True,
|
||||
}
|
||||
],
|
||||
'overall_template': {
|
||||
'source': None,
|
||||
'mute': False,
|
||||
'audios': [
|
||||
'./template/test_template/bgm.acc'
|
||||
]
|
||||
},
|
||||
}
|
||||
return template
|
27
util/ffmpeg.py
Normal file
27
util/ffmpeg.py
Normal file
@ -0,0 +1,27 @@
|
||||
from typing import Optional, IO
|
||||
|
||||
from entity.ffmpeg import FfmpegTask
|
||||
|
||||
|
||||
def start_render(ffmpeg_task: FfmpegTask):
|
||||
print(ffmpeg_task)
|
||||
|
||||
def handle_ffmpeg_output(stdout: Optional[IO[bytes]]) -> str:
|
||||
out_time = "0:0:0.0"
|
||||
if stdout is None:
|
||||
print("[!]STDOUT is null")
|
||||
return out_time
|
||||
speed = "0"
|
||||
while True:
|
||||
line = stdout.readline()
|
||||
if line == b"":
|
||||
break
|
||||
if line.strip() == b"progress=end":
|
||||
# 处理完毕
|
||||
break
|
||||
if line.startswith(b"out_time="):
|
||||
out_time = line.replace(b"out_time=", b"").decode().strip()
|
||||
if line.startswith(b"speed="):
|
||||
speed = line.replace(b"speed=", b"").decode().strip()
|
||||
print("[ ]Speed:", out_time, "@", speed)
|
||||
return out_time
|
17
util/oss.py
Normal file
17
util/oss.py
Normal file
@ -0,0 +1,17 @@
|
||||
import requests
|
||||
|
||||
|
||||
def upload_to_oss_use_signed_url(url, file_path):
|
||||
"""
|
||||
使用签名URL上传文件到OSS
|
||||
:param str url: 签名URL
|
||||
:param str file_path: 文件路径
|
||||
:return bool: 是否成功
|
||||
"""
|
||||
with open(file_path, 'rb') as f:
|
||||
try:
|
||||
response = requests.put(url, data=f)
|
||||
return response.status_code == 200
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return False
|
21
util/system.py
Normal file
21
util/system.py
Normal file
@ -0,0 +1,21 @@
|
||||
import os
|
||||
import platform
|
||||
import psutil
|
||||
from constant import SUPPORT_FEATURE, SOFTWARE_VERSION
|
||||
|
||||
|
||||
def get_sys_info():
|
||||
"""
|
||||
Returns a dictionary with system information.
|
||||
"""
|
||||
info = {
|
||||
'version': SOFTWARE_VERSION,
|
||||
'platform': platform.system(),
|
||||
'runtime_version': 'Python ' + platform.python_version(),
|
||||
'cpu_count': os.cpu_count(),
|
||||
'cpu_usage': psutil.cpu_percent(),
|
||||
'memory_total': psutil.virtual_memory().total,
|
||||
'memory_available': psutil.virtual_memory().available,
|
||||
'support_feature': SUPPORT_FEATURE
|
||||
}
|
||||
return info
|
Reference in New Issue
Block a user