Compare commits
10 Commits
9139727dc6
...
92160b05ea
Author | SHA1 | Date | |
---|---|---|---|
92160b05ea | |||
ce469dacf2 | |||
da6579a9ed | |||
7bcb561a65 | |||
5ed7198a8a | |||
515bf156ab | |||
8d6159d302 | |||
fb51d144c0 | |||
ea5e994a3b | |||
679f61c18f |
4
.env
4
.env
@ -1,4 +1,4 @@
|
||||
TEMPLATE_DIR=template/
|
||||
API_ENDPOINT=/task
|
||||
API_TOKEN=123456
|
||||
API_ENDPOINT=http://127.0.0.1:8030/task/v1
|
||||
ACCESS_KEY=TEST_ACCESS_KEY
|
||||
TEMP_DIR=tmp/
|
@ -1,9 +1,11 @@
|
||||
import json
|
||||
import os.path
|
||||
import time
|
||||
|
||||
from entity.ffmpeg import FfmpegTask
|
||||
import logging
|
||||
|
||||
from util import ffmpeg
|
||||
from util import ffmpeg, oss
|
||||
|
||||
logger = logging.getLogger('biz/ffmpeg')
|
||||
|
||||
@ -11,12 +13,15 @@ logger = logging.getLogger('biz/ffmpeg')
|
||||
def parse_ffmpeg_task(task_info, template_info):
|
||||
tasks = []
|
||||
# 中间片段
|
||||
task_params_str = task_info.get("taskParams", "{}")
|
||||
task_params = json.loads(task_params_str)
|
||||
for part in template_info.get("video_parts"):
|
||||
source = select_video_if_needed(part.get('source'), task_info, template_info)
|
||||
source = parse_video(part.get('source'), task_params, template_info)
|
||||
if not source:
|
||||
logger.warning("no video found for part: " + str(part))
|
||||
continue
|
||||
sub_ffmpeg_task = FfmpegTask(source)
|
||||
sub_ffmpeg_task.annexb = True
|
||||
sub_ffmpeg_task.frame_rate = template_info.get("frame_rate", 25)
|
||||
for lut in part.get('filters', []):
|
||||
sub_ffmpeg_task.add_lut(os.path.join(template_info.get("local_path"), lut))
|
||||
@ -25,9 +30,13 @@ def parse_ffmpeg_task(task_info, template_info):
|
||||
for overlay in part.get('overlays', []):
|
||||
sub_ffmpeg_task.add_overlay(os.path.join(template_info.get("local_path"), overlay))
|
||||
tasks.append(sub_ffmpeg_task)
|
||||
task = FfmpegTask(tasks, output_file="test.mp4")
|
||||
output_file = "out_" + str(time.time()) + ".mp4"
|
||||
task = FfmpegTask(tasks, output_file=output_file)
|
||||
overall = template_info.get("overall_template")
|
||||
task.frame_rate = template_info.get("frame_rate", 25)
|
||||
if overall.get('source', ''):
|
||||
source = parse_video(overall.get('source'), task_params, template_info)
|
||||
task.add_inputs(source)
|
||||
for lut in overall.get('filters', []):
|
||||
task.add_lut(os.path.join(template_info.get("local_path"), lut))
|
||||
for audio in overall.get('audios', []):
|
||||
@ -37,17 +46,22 @@ def parse_ffmpeg_task(task_info, template_info):
|
||||
return task
|
||||
|
||||
|
||||
def select_video_if_needed(source, task_info, template_info):
|
||||
def parse_video(source, task_params, template_info):
|
||||
print(source)
|
||||
if source.startswith('PLACEHOLDER_'):
|
||||
placeholder_id = source.replace('PLACEHOLDER_', '')
|
||||
new_sources = task_info.get('user_videos', {}).get(placeholder_id, [])
|
||||
new_sources = task_params.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]
|
||||
new_sources = new_sources[0].get("url")
|
||||
if new_sources.startswith("http"):
|
||||
_, source_name = os.path.split(new_sources)
|
||||
oss.download_from_oss(new_sources, source_name)
|
||||
return source_name
|
||||
return new_sources
|
||||
return os.path.join(template_info.get("local_path"), source)
|
||||
|
||||
@ -55,4 +69,27 @@ def select_video_if_needed(source, task_info, template_info):
|
||||
def start_ffmpeg_task(ffmpeg_task):
|
||||
for task in ffmpeg_task.analyze_input_render_tasks():
|
||||
start_ffmpeg_task(task)
|
||||
ffmpeg.start_render(ffmpeg_task)
|
||||
ffmpeg_task.correct_task_type()
|
||||
return ffmpeg.start_render(ffmpeg_task)
|
||||
|
||||
|
||||
def clear_task_tmp_file(ffmpeg_task):
|
||||
for task in ffmpeg_task.analyze_input_render_tasks():
|
||||
clear_task_tmp_file(task)
|
||||
try:
|
||||
if "template" not in ffmpeg_task.get_output_file():
|
||||
os.remove(ffmpeg_task.get_output_file())
|
||||
logger.info("delete tmp file: " + ffmpeg_task.get_output_file())
|
||||
else:
|
||||
logger.info("skip delete template file: " + ffmpeg_task.get_output_file())
|
||||
except OSError:
|
||||
logger.warning("delete tmp file failed: " + ffmpeg_task.get_output_file())
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def probe_video_info(ffmpeg_task):
|
||||
# 获取视频长度宽度和时长
|
||||
return ffmpeg.probe_video_info(ffmpeg_task.get_output_file())
|
||||
|
||||
|
||||
|
29
biz/task.py
29
biz/task.py
@ -1,15 +1,24 @@
|
||||
from template import get_template_def
|
||||
|
||||
|
||||
def normalize_task(task_info):
|
||||
...
|
||||
return task_info
|
||||
from util import api
|
||||
|
||||
|
||||
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)
|
||||
from biz.ffmpeg import parse_ffmpeg_task, start_ffmpeg_task, clear_task_tmp_file, probe_video_info
|
||||
task_info = api.normalize_task(task_info)
|
||||
template_info = get_template_def(task_info.get("templateId"))
|
||||
api.report_task_start(task_info)
|
||||
ffmpeg_task = parse_ffmpeg_task(task_info, template_info)
|
||||
result = start_ffmpeg_task(ffmpeg_task)
|
||||
result = start_ffmpeg_task(ffmpeg_task)
|
||||
if not result:
|
||||
return api.report_task_failed(task_info)
|
||||
oss_result = api.upload_task_file(task_info, ffmpeg_task)
|
||||
if not oss_result:
|
||||
return api.report_task_failed(task_info)
|
||||
# 获取视频长度宽度和时长
|
||||
width, height, duration = probe_video_info(ffmpeg_task)
|
||||
clear_task_tmp_file(ffmpeg_task)
|
||||
api.report_task_success(task_info, videoInfo={
|
||||
"width": width,
|
||||
"height": height,
|
||||
"duration": duration
|
||||
})
|
@ -4,7 +4,7 @@ from logging.handlers import TimedRotatingFileHandler
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
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"))
|
||||
|
@ -1,8 +1,14 @@
|
||||
import time
|
||||
import uuid
|
||||
|
||||
|
||||
class FfmpegTask(object):
|
||||
|
||||
def __init__(self, input_file, task_type='copy', output_file=''):
|
||||
self.annexb = False
|
||||
if type(input_file) is str:
|
||||
if input_file.endswith(".ts"):
|
||||
self.annexb = True
|
||||
self.input_file = [input_file]
|
||||
elif type(input_file) is list:
|
||||
self.input_file = input_file
|
||||
@ -17,7 +23,6 @@ class FfmpegTask(object):
|
||||
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}'
|
||||
@ -56,7 +61,11 @@ class FfmpegTask(object):
|
||||
self.input_file.extend(inputs)
|
||||
|
||||
def add_overlay(self, *overlays):
|
||||
self.overlays.extend(overlays)
|
||||
for overlay in overlays:
|
||||
if str(overlay).endswith('.ass'):
|
||||
self.subtitles.append(overlay)
|
||||
else:
|
||||
self.overlays.append(overlay)
|
||||
self.correct_task_type()
|
||||
|
||||
def add_audios(self, *audios):
|
||||
@ -115,11 +124,14 @@ class FfmpegTask(object):
|
||||
|
||||
def get_ffmpeg_args(self):
|
||||
args = ['-y', '-hide_banner']
|
||||
video_output_str = "[0:v]"
|
||||
if self.task_type == 'encode':
|
||||
# args += ('-hwaccel', 'qsv', '-hwaccel_output_format', 'qsv')
|
||||
input_args = []
|
||||
filter_args = []
|
||||
output_args = []
|
||||
output_args = ["-shortest", "-c:v", "h264_qsv", "-global_quality", "28", "-look_ahead", "1"]
|
||||
if self.annexb:
|
||||
output_args.append("-bsf:v")
|
||||
output_args.append("h264_mp4toannexb")
|
||||
video_output_str = "[0:v]"
|
||||
audio_output_str = "[0:v]"
|
||||
video_input_count = 0
|
||||
@ -139,6 +151,9 @@ class FfmpegTask(object):
|
||||
filter_args.append(f"{video_output_str}[{input_index}:v]scale=rw:rh[v]")
|
||||
filter_args.append(f"[v][{input_index}:v]overlay=1:eof_action=endall[v]")
|
||||
video_output_str = "[v]"
|
||||
for subtitle in self.subtitles:
|
||||
filter_args.append(f"{video_output_str}ass={subtitle}[v]")
|
||||
video_output_str = "[v]"
|
||||
output_args.append("-map")
|
||||
output_args.append(video_output_str)
|
||||
output_args.append("-r")
|
||||
@ -163,11 +178,36 @@ class FfmpegTask(object):
|
||||
output_args.append(audio_output_str)
|
||||
return args + input_args + ["-filter_complex", ";".join(filter_args)] + output_args + [self.get_output_file()]
|
||||
elif self.task_type == 'concat':
|
||||
# 无法通过 annexb 合并的
|
||||
input_args = []
|
||||
output_args = []
|
||||
output_args = ["-shortest"]
|
||||
if self.check_annexb() and len(self.audios) <= 1:
|
||||
# output_args
|
||||
if len(self.audios) > 0:
|
||||
input_args.append("-an")
|
||||
_tmp_file = "tmp_concat_"+str(time.time())+".txt"
|
||||
with open(_tmp_file, "w", encoding="utf-8") as f:
|
||||
for input_file in self.input_file:
|
||||
if type(input_file) is str:
|
||||
f.write("file '"+input_file+"'\n")
|
||||
elif isinstance(input_file, FfmpegTask):
|
||||
f.write("file '" + input_file.get_output_file() + "'\n")
|
||||
input_args += ("-f", "concat", "-safe", "0", "-i", _tmp_file)
|
||||
output_args.append("-c:v")
|
||||
output_args.append("copy")
|
||||
if len(self.audios) > 0:
|
||||
input_args.append("-i")
|
||||
input_args.append(self.audios[0])
|
||||
output_args.append("-c:a")
|
||||
output_args.append("copy")
|
||||
output_args.append("-f")
|
||||
output_args.append("mp4")
|
||||
output_args += ("-c:v", "h264_qsv", "-r", "25", "-global_quality", "28", "-look_ahead", "1")
|
||||
return args + input_args + output_args + [self.get_output_file()]
|
||||
output_args += ("-c:v", "h264_qsv", "-r", "25", "-global_quality", "28", "-look_ahead", "1")
|
||||
filter_args = []
|
||||
video_output_str = "[0:v]"
|
||||
audio_output_str = "[0:v]"
|
||||
audio_output_str = "[0:a]"
|
||||
video_input_count = 0
|
||||
audio_input_count = 0
|
||||
for input_file in self.input_file:
|
||||
@ -204,11 +244,40 @@ class FfmpegTask(object):
|
||||
output_args.append(f"-map")
|
||||
output_args.append(audio_output_str)
|
||||
return args + input_args + ["-filter_complex", ";".join(filter_args)] + output_args + [self.get_output_file()]
|
||||
elif self.task_type == 'copy':
|
||||
if len(self.input_file) == 1:
|
||||
if type(self.input_file[0]) is str:
|
||||
if self.input_file[0] == self.get_output_file():
|
||||
return []
|
||||
return args + ["-i", self.input_file[0]] + ["-c", "copy", self.get_output_file()]
|
||||
|
||||
def set_output_file(self, file=None):
|
||||
if file is None:
|
||||
if self.output_file == '':
|
||||
# TODO: Random Filename
|
||||
self.output_file = "rand.mp4"
|
||||
if self.annexb:
|
||||
self.output_file = "rand_" + str(uuid.uuid4()) + ".ts"
|
||||
else:
|
||||
self.output_file = "rand_" + str(uuid.uuid4()) + ".mp4"
|
||||
else:
|
||||
self.output_file = file
|
||||
if isinstance(file, FfmpegTask):
|
||||
if file == self:
|
||||
return
|
||||
self.output_file = file.get_output_file()
|
||||
if type(file) is str:
|
||||
self.output_file = file
|
||||
|
||||
def check_annexb(self):
|
||||
for input_file in self.input_file:
|
||||
if type(input_file) is str:
|
||||
if self.task_type == 'encode':
|
||||
return self.annexb
|
||||
elif self.task_type == 'concat':
|
||||
return False
|
||||
elif self.task_type == 'copy':
|
||||
return self.annexb
|
||||
else:
|
||||
return False
|
||||
elif isinstance(input_file, FfmpegTask):
|
||||
if not input_file.check_annexb():
|
||||
return False
|
||||
return True
|
6
index.py
6
index.py
@ -4,7 +4,6 @@ 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()
|
||||
|
||||
@ -12,8 +11,9 @@ load_local_template()
|
||||
while True:
|
||||
# print(get_sys_info())
|
||||
print("waiting for task...")
|
||||
task_list = api.get_render_task()
|
||||
task_list = api.sync_center()
|
||||
if len(task_list) == 0:
|
||||
sleep(5)
|
||||
for task in task_list:
|
||||
print("start task:", task)
|
||||
biz.task.start_task(task)
|
||||
break
|
@ -2,6 +2,8 @@ import json
|
||||
import os
|
||||
import logging
|
||||
|
||||
from util import api, oss
|
||||
|
||||
TEMPLATES = {}
|
||||
logger = logging.getLogger("template")
|
||||
|
||||
@ -68,11 +70,53 @@ def load_local_template():
|
||||
|
||||
|
||||
def get_template_def(template_id):
|
||||
if template_id not in TEMPLATES:
|
||||
download_template(template_id)
|
||||
return TEMPLATES.get(template_id)
|
||||
|
||||
def download_template(template_id):
|
||||
logger.info(f"下载模板:{template_id}")
|
||||
...
|
||||
template_info = api.get_template_info(template_id)
|
||||
if not os.path.isdir(template_info['local_path']):
|
||||
os.makedirs(template_info['local_path'])
|
||||
# download template assets
|
||||
overall_template = template_info['overall_template']
|
||||
video_parts = template_info['video_parts']
|
||||
def _download_assets(_template):
|
||||
if 'source' in _template:
|
||||
if str(_template['source']).startswith("http"):
|
||||
_, _fn = os.path.split(_template['source'])
|
||||
new_fp = os.path.join(template_info['local_path'], _fn)
|
||||
oss.download_from_oss(_template['source'], new_fp)
|
||||
if _fn.endswith(".mp4"):
|
||||
from util.ffmpeg import to_annexb
|
||||
new_fp = to_annexb(new_fp)
|
||||
_template['source'] = os.path.relpath(new_fp, template_info['local_path'])
|
||||
if 'overlays' in _template:
|
||||
for i in range(len(_template['overlays'])):
|
||||
overlay = _template['overlays'][i]
|
||||
if str(overlay).startswith("http"):
|
||||
_, _fn = os.path.split(overlay)
|
||||
oss.download_from_oss(overlay, os.path.join(template_info['local_path'], _fn))
|
||||
_template['overlays'][i] = _fn
|
||||
if 'luts' in _template:
|
||||
for i in range(len(_template['luts'])):
|
||||
lut = _template['luts'][i]
|
||||
if str(lut).startswith("http"):
|
||||
_, _fn = os.path.split(lut)
|
||||
oss.download_from_oss(lut, os.path.join(template_info['local_path'], _fn))
|
||||
_template['luts'][i] = _fn
|
||||
if 'audios' in _template:
|
||||
for i in range(len(_template['audios'])):
|
||||
if str(_template['audios'][i]).startswith("http"):
|
||||
_, _fn = os.path.split(_template['audios'][i])
|
||||
oss.download_from_oss(_template['audios'][i], os.path.join(template_info['local_path'], _fn))
|
||||
_template['audios'][i] = _fn
|
||||
_download_assets(overall_template)
|
||||
for video_part in video_parts:
|
||||
_download_assets(video_part)
|
||||
with open(os.path.join(template_info['local_path'], 'template.json'), 'w', encoding='utf-8') as f:
|
||||
json.dump(template_info, f)
|
||||
load_template(template_id, template_info['local_path'])
|
||||
|
||||
|
||||
def analyze_template(template_id):
|
||||
|
171
util/api.py
171
util/api.py
@ -1,19 +1,40 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
import requests
|
||||
|
||||
import util.system
|
||||
|
||||
session = requests.Session()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_render_task():
|
||||
def normalize_task(task_info):
|
||||
...
|
||||
return task_info
|
||||
|
||||
|
||||
def sync_center():
|
||||
"""
|
||||
通过接口获取任务
|
||||
:return: 任务列表
|
||||
"""
|
||||
tasks = []
|
||||
tasks.append({
|
||||
'user_videos': {
|
||||
'CAM_ID': 'paper-planes.mp4'
|
||||
}
|
||||
})
|
||||
try:
|
||||
response = session.post(os.getenv('API_ENDPOINT') + "/sync", json={
|
||||
'accessKey': os.getenv('ACCESS_KEY'),
|
||||
'clientStatus': util.system.get_sys_info()
|
||||
}, timeout=10)
|
||||
response.raise_for_status()
|
||||
except requests.RequestException as e:
|
||||
logger.error("请求失败!", e)
|
||||
return []
|
||||
data = response.json()
|
||||
logger.debug("获取任务结果:【%s】", data)
|
||||
if data.get('code', 0) == 200:
|
||||
tasks = data.get('data', {}).get('tasks', [])
|
||||
else:
|
||||
tasks = []
|
||||
logger.warning("获取任务失败")
|
||||
return tasks
|
||||
|
||||
|
||||
@ -25,39 +46,117 @@ def get_template_info(template_id):
|
||||
:type template_id: str
|
||||
:return: 模板信息
|
||||
"""
|
||||
try:
|
||||
response = session.post('{0}/template/{1}'.format(os.getenv('API_ENDPOINT'), template_id), json={
|
||||
'accessKey': os.getenv('ACCESS_KEY'),
|
||||
}, timeout=10)
|
||||
response.raise_for_status()
|
||||
except requests.RequestException as e:
|
||||
logger.error("请求失败!", e)
|
||||
return None
|
||||
data = response.json()
|
||||
remote_template_info = data.get('data', {})
|
||||
logger.debug("获取模板信息结果:【%s】", remote_template_info)
|
||||
template = {
|
||||
'id': template_id,
|
||||
'name': '模板名称',
|
||||
'description': '模板描述',
|
||||
'scenic_name': remote_template_info.get('scenicName', '景区'),
|
||||
'name': remote_template_info.get('name', '模版'),
|
||||
'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
|
||||
|
||||
def _template_normalizer(template_info):
|
||||
_template = {}
|
||||
_placeholder_type = template_info.get('isPlaceholder', -1)
|
||||
if _placeholder_type == 0:
|
||||
# 固定视频
|
||||
_template['source'] = template_info.get('sourceUrl', '')
|
||||
elif _placeholder_type == 1:
|
||||
# 占位符
|
||||
_template['source'] = "PLACEHOLDER_" + template_info.get('sourceUrl', '')
|
||||
_template['mute'] = template_info.get('mute', True)
|
||||
else:
|
||||
_template['source'] = None
|
||||
_overlays = template_info.get('overlays', '')
|
||||
if _overlays:
|
||||
_template['overlays'] = _overlays.split(",")
|
||||
_audios = template_info.get('audios', '')
|
||||
if _audios:
|
||||
_template['audios'] = _audios.split(",")
|
||||
_luts = template_info.get('luts', '')
|
||||
if _luts:
|
||||
_template['luts'] = _luts.split(",")
|
||||
return _template
|
||||
|
||||
# outer template definition
|
||||
overall_template = _template_normalizer(remote_template_info)
|
||||
template['overall_template'] = overall_template
|
||||
# inter template definition
|
||||
inter_template_list = remote_template_info.get('children', [])
|
||||
for children_template in inter_template_list:
|
||||
parts = _template_normalizer(children_template)
|
||||
template['video_parts'].append(parts)
|
||||
template['local_path'] = os.path.join(os.getenv('TEMPLATE_DIR'), str(template_id))
|
||||
return template
|
||||
|
||||
|
||||
def report_task_success(task_info, **kwargs):
|
||||
try:
|
||||
response = session.post('{0}/{1}/success'.format(os.getenv('API_ENDPOINT'), task_info.get("id")), json={
|
||||
'accessKey': os.getenv('ACCESS_KEY'),
|
||||
**kwargs
|
||||
}, timeout=10)
|
||||
response.raise_for_status()
|
||||
except requests.RequestException as e:
|
||||
logger.error("请求失败!", e)
|
||||
return None
|
||||
|
||||
|
||||
def report_task_start(task_info):
|
||||
try:
|
||||
response = session.post('{0}/{1}/start'.format(os.getenv('API_ENDPOINT'), task_info.get("id")), json={
|
||||
'accessKey': os.getenv('ACCESS_KEY'),
|
||||
}, timeout=10)
|
||||
response.raise_for_status()
|
||||
except requests.RequestException as e:
|
||||
logger.error("请求失败!", e)
|
||||
return None
|
||||
|
||||
|
||||
def report_task_failed(task_info):
|
||||
try:
|
||||
response = session.post('{0}/{1}/fail'.format(os.getenv('API_ENDPOINT'), task_info.get("id")), json={
|
||||
'accessKey': os.getenv('ACCESS_KEY'),
|
||||
}, timeout=10)
|
||||
response.raise_for_status()
|
||||
except requests.RequestException as e:
|
||||
logger.error("请求失败!", e)
|
||||
return None
|
||||
|
||||
|
||||
def upload_task_file(task_info, ffmpeg_task):
|
||||
logger.info("开始上传文件: %s", task_info.get("id"))
|
||||
try:
|
||||
response = session.post('{0}/{1}/uploadUrl'.format(os.getenv('API_ENDPOINT'), task_info.get("id")), json={
|
||||
'accessKey': os.getenv('ACCESS_KEY'),
|
||||
}, timeout=10)
|
||||
response.raise_for_status()
|
||||
except requests.RequestException as e:
|
||||
logger.error("请求失败!", e)
|
||||
return False
|
||||
data = response.json()
|
||||
url = data.get('data', "")
|
||||
logger.info("开始上传文件: %s 至 %s", task_info.get("id"), url)
|
||||
try:
|
||||
with open(ffmpeg_task.get_output_file(), 'rb') as f:
|
||||
requests.put(url, data=f)
|
||||
except requests.RequestException as e:
|
||||
logger.error("上传失败!", e)
|
||||
return False
|
||||
finally:
|
||||
logger.info("上传文件结束: %s", task_info.get("id"))
|
||||
return True
|
||||
|
106
util/ffmpeg.py
106
util/ffmpeg.py
@ -1,23 +1,48 @@
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
from datetime import datetime
|
||||
from typing import Optional, IO
|
||||
|
||||
from entity.ffmpeg import FfmpegTask
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def to_annexb(file):
|
||||
if not os.path.exists(file):
|
||||
return file
|
||||
logger.info("ToAnnexb: %s", file)
|
||||
ffmpeg_process = subprocess.run(["ffmpeg.exe", "-y", "-hide_banner", "-i", file, "-c", "copy", "-bsf:v", "h264_mp4toannexb",
|
||||
"-f", "mpegts", file+".ts"])
|
||||
logger.info("ToAnnexb: %s, returned: %s", file, ffmpeg_process.returncode)
|
||||
if ffmpeg_process.returncode == 0:
|
||||
os.remove(file)
|
||||
return file+".ts"
|
||||
else:
|
||||
return file
|
||||
|
||||
def start_render(ffmpeg_task: FfmpegTask):
|
||||
print(ffmpeg_task)
|
||||
print(ffmpeg_task.get_ffmpeg_args())
|
||||
os.system("ffmpeg.exe "+" ".join(ffmpeg_task.get_ffmpeg_args()))
|
||||
logger.info(ffmpeg_task)
|
||||
if not ffmpeg_task.need_run():
|
||||
ffmpeg_task.set_output_file(ffmpeg_task.input_file[0])
|
||||
return True
|
||||
ffmpeg_args = ffmpeg_task.get_ffmpeg_args()
|
||||
logger.info(ffmpeg_args)
|
||||
if len(ffmpeg_args) == 0:
|
||||
ffmpeg_task.set_output_file(ffmpeg_task.input_file[0])
|
||||
return True
|
||||
ffmpeg_process = subprocess.run(["ffmpeg.exe", "-progress", "-", "-loglevel", "error", *ffmpeg_args], **subprocess_args(True))
|
||||
logger.info("FINISH TASK, OUTPUT IS %s", handle_ffmpeg_output(ffmpeg_process.stdout))
|
||||
code = ffmpeg_process.returncode
|
||||
return code == 0
|
||||
|
||||
def handle_ffmpeg_output(stdout: Optional[IO[bytes]]) -> str:
|
||||
def handle_ffmpeg_output(stdout: Optional[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()
|
||||
for line in stdout.split(b"\n"):
|
||||
if line == b"":
|
||||
break
|
||||
if line.strip() == b"progress=end":
|
||||
@ -28,8 +53,75 @@ def handle_ffmpeg_output(stdout: Optional[IO[bytes]]) -> str:
|
||||
if line.startswith(b"speed="):
|
||||
speed = line.replace(b"speed=", b"").decode().strip()
|
||||
print("[ ]Speed:", out_time, "@", speed)
|
||||
return out_time
|
||||
return out_time+"@"+speed
|
||||
|
||||
|
||||
def duration_str_to_float(duration_str: str) -> float:
|
||||
_duration = datetime.strptime(duration_str, "%H:%M:%S.%f") - datetime(1900, 1, 1)
|
||||
return _duration.total_seconds()
|
||||
|
||||
|
||||
def probe_video_info(video_file):
|
||||
# 获取宽度和高度
|
||||
result = subprocess.run(
|
||||
["ffprobe.exe", '-v', 'error', '-select_streams', 'v:0', '-show_entries', 'stream=width,height:format=duration', '-of',
|
||||
'csv=s=x:p=0', video_file],
|
||||
stderr=subprocess.STDOUT,
|
||||
**subprocess_args(True)
|
||||
)
|
||||
all_result = result.stdout.decode('utf-8').strip()
|
||||
wh, duration = all_result.split('\n')
|
||||
width, height = wh.strip().split('x')
|
||||
|
||||
return int(width), int(height), float(duration)
|
||||
|
||||
|
||||
# Create a set of arguments which make a ``subprocess.Popen`` (and
|
||||
# variants) call work with or without Pyinstaller, ``--noconsole`` or
|
||||
# not, on Windows and Linux. Typical use::
|
||||
#
|
||||
# subprocess.call(['program_to_run', 'arg_1'], **subprocess_args())
|
||||
#
|
||||
# When calling ``check_output``::
|
||||
#
|
||||
# subprocess.check_output(['program_to_run', 'arg_1'],
|
||||
# **subprocess_args(False))
|
||||
def subprocess_args(include_stdout=True):
|
||||
# The following is true only on Windows.
|
||||
if hasattr(subprocess, 'STARTUPINFO'):
|
||||
# On Windows, subprocess calls will pop up a command window by default
|
||||
# when run from Pyinstaller with the ``--noconsole`` option. Avoid this
|
||||
# distraction.
|
||||
si = subprocess.STARTUPINFO()
|
||||
si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||
# Windows doesn't search the path by default. Pass it an environment so
|
||||
# it will.
|
||||
env = os.environ
|
||||
else:
|
||||
si = None
|
||||
env = None
|
||||
|
||||
# ``subprocess.check_output`` doesn't allow specifying ``stdout``::
|
||||
#
|
||||
# Traceback (most recent call last):
|
||||
# File "test_subprocess.py", line 58, in <module>
|
||||
# **subprocess_args(stdout=None))
|
||||
# File "C:\Python27\lib\subprocess.py", line 567, in check_output
|
||||
# raise ValueError('stdout argument not allowed, it will be overridden.')
|
||||
# ValueError: stdout argument not allowed, it will be overridden.
|
||||
#
|
||||
# So, add it only if it's needed.
|
||||
if include_stdout:
|
||||
ret = {'stdout': subprocess.PIPE}
|
||||
else:
|
||||
ret = {}
|
||||
|
||||
# On Windows, running this from the binary produced by Pyinstaller
|
||||
# with the ``--noconsole`` option requires redirecting everything
|
||||
# (stdin, stdout, stderr) to avoid an OSError exception
|
||||
# "[Error 6] the handle is invalid."
|
||||
ret.update({'stdin': subprocess.PIPE,
|
||||
'startupinfo': si,
|
||||
'env': env})
|
||||
return ret
|
||||
|
||||
|
28
util/oss.py
28
util/oss.py
@ -1,7 +1,12 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
import requests
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def upload_to_oss_use_signed_url(url, file_path):
|
||||
|
||||
def upload_to_oss(url, file_path):
|
||||
"""
|
||||
使用签名URL上传文件到OSS
|
||||
:param str url: 签名URL
|
||||
@ -15,3 +20,24 @@ def upload_to_oss_use_signed_url(url, file_path):
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return False
|
||||
|
||||
def download_from_oss(url, file_path):
|
||||
"""
|
||||
使用签名URL下载文件到OSS
|
||||
:param str url: 签名URL
|
||||
:param Union[LiteralString, str, bytes] file_path: 文件路径
|
||||
:return bool: 是否成功
|
||||
"""
|
||||
logging.info("download_from_oss: %s", url)
|
||||
file_dir, file_name = os.path.split(file_path)
|
||||
if file_dir:
|
||||
if not os.path.exists(file_dir):
|
||||
os.makedirs(file_dir)
|
||||
try:
|
||||
response = requests.get(url)
|
||||
with open(file_path, 'wb') as f:
|
||||
f.write(response.content)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return False
|
||||
|
@ -1,5 +1,7 @@
|
||||
import os
|
||||
import platform
|
||||
from datetime import datetime
|
||||
|
||||
import psutil
|
||||
from constant import SUPPORT_FEATURE, SOFTWARE_VERSION
|
||||
|
||||
@ -10,6 +12,7 @@ def get_sys_info():
|
||||
"""
|
||||
info = {
|
||||
'version': SOFTWARE_VERSION,
|
||||
'client_datetime': datetime.now().isoformat(),
|
||||
'platform': platform.system(),
|
||||
'runtime_version': 'Python ' + platform.python_version(),
|
||||
'cpu_count': os.cpu_count(),
|
||||
|
Loading…
x
Reference in New Issue
Block a user