Compare commits

...

10 Commits

Author SHA1 Message Date
92160b05ea 改改bug 2025-01-13 10:33:36 +08:00
ce469dacf2 直接拼接逻辑 2025-01-11 17:25:58 +08:00
da6579a9ed 2 2024-12-26 16:42:18 +08:00
7bcb561a65 修改 2024-12-24 14:59:52 +08:00
5ed7198a8a 添加开始接口 2024-12-13 12:15:15 +08:00
515bf156ab 对接 2024-12-09 09:25:58 +08:00
8d6159d302 qsv、对接 2024-12-07 18:00:57 +08:00
fb51d144c0 对接 2024-12-07 15:00:10 +08:00
ea5e994a3b 支持特效字幕 2024-12-05 09:32:29 +08:00
679f61c18f 对接任务 2024-12-04 16:19:18 +08:00
11 changed files with 457 additions and 78 deletions

4
.env
View File

@ -1,4 +1,4 @@
TEMPLATE_DIR=template/ TEMPLATE_DIR=template/
API_ENDPOINT=/task API_ENDPOINT=http://127.0.0.1:8030/task/v1
API_TOKEN=123456 ACCESS_KEY=TEST_ACCESS_KEY
TEMP_DIR=tmp/ TEMP_DIR=tmp/

View File

@ -1,9 +1,11 @@
import json
import os.path import os.path
import time
from entity.ffmpeg import FfmpegTask from entity.ffmpeg import FfmpegTask
import logging import logging
from util import ffmpeg from util import ffmpeg, oss
logger = logging.getLogger('biz/ffmpeg') logger = logging.getLogger('biz/ffmpeg')
@ -11,12 +13,15 @@ logger = logging.getLogger('biz/ffmpeg')
def parse_ffmpeg_task(task_info, template_info): def parse_ffmpeg_task(task_info, template_info):
tasks = [] tasks = []
# 中间片段 # 中间片段
task_params_str = task_info.get("taskParams", "{}")
task_params = json.loads(task_params_str)
for part in template_info.get("video_parts"): 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: if not source:
logger.warning("no video found for part: " + str(part)) logger.warning("no video found for part: " + str(part))
continue continue
sub_ffmpeg_task = FfmpegTask(source) sub_ffmpeg_task = FfmpegTask(source)
sub_ffmpeg_task.annexb = True
sub_ffmpeg_task.frame_rate = template_info.get("frame_rate", 25) sub_ffmpeg_task.frame_rate = template_info.get("frame_rate", 25)
for lut in part.get('filters', []): for lut in part.get('filters', []):
sub_ffmpeg_task.add_lut(os.path.join(template_info.get("local_path"), lut)) 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', []): for overlay in part.get('overlays', []):
sub_ffmpeg_task.add_overlay(os.path.join(template_info.get("local_path"), overlay)) sub_ffmpeg_task.add_overlay(os.path.join(template_info.get("local_path"), overlay))
tasks.append(sub_ffmpeg_task) 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") overall = template_info.get("overall_template")
task.frame_rate = template_info.get("frame_rate", 25) 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', []): for lut in overall.get('filters', []):
task.add_lut(os.path.join(template_info.get("local_path"), lut)) task.add_lut(os.path.join(template_info.get("local_path"), lut))
for audio in overall.get('audios', []): for audio in overall.get('audios', []):
@ -37,17 +46,22 @@ def parse_ffmpeg_task(task_info, template_info):
return task 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_'): if source.startswith('PLACEHOLDER_'):
placeholder_id = source.replace('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 type(new_sources) is list:
if len(new_sources) == 0: if len(new_sources) == 0:
logger.debug("no video found for placeholder: " + placeholder_id) logger.debug("no video found for placeholder: " + placeholder_id)
return None return None
else: else:
# TODO: Random Pick / Policy Pick # 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 new_sources
return os.path.join(template_info.get("local_path"), source) 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): def start_ffmpeg_task(ffmpeg_task):
for task in ffmpeg_task.analyze_input_render_tasks(): for task in ffmpeg_task.analyze_input_render_tasks():
start_ffmpeg_task(task) 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())

View File

@ -1,15 +1,24 @@
from template import get_template_def from template import get_template_def
from util import api
def normalize_task(task_info):
...
return task_info
def start_task(task_info): def start_task(task_info):
from biz.ffmpeg import parse_ffmpeg_task, start_ffmpeg_task from biz.ffmpeg import parse_ffmpeg_task, start_ffmpeg_task, clear_task_tmp_file, probe_video_info
task_info = normalize_task(task_info) task_info = api.normalize_task(task_info)
task_template = "test_template" template_info = get_template_def(task_info.get("templateId"))
template_info = get_template_def(task_template) api.report_task_start(task_info)
ffmpeg_task = parse_ffmpeg_task(task_info, template_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
})

View File

@ -4,7 +4,7 @@ from logging.handlers import TimedRotatingFileHandler
from dotenv import load_dotenv from dotenv import load_dotenv
load_dotenv() load_dotenv()
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.INFO)
root_logger = logging.getLogger() root_logger = logging.getLogger()
rf_handler = TimedRotatingFileHandler('all_log.log', when='midnight') rf_handler = TimedRotatingFileHandler('all_log.log', when='midnight')
rf_handler.setFormatter(logging.Formatter("[%(asctime)s][%(name)s]%(levelname)s - %(message)s")) rf_handler.setFormatter(logging.Formatter("[%(asctime)s][%(name)s]%(levelname)s - %(message)s"))

View File

@ -1,8 +1,14 @@
import time
import uuid
class FfmpegTask(object): class FfmpegTask(object):
def __init__(self, input_file, task_type='copy', output_file=''): def __init__(self, input_file, task_type='copy', output_file=''):
self.annexb = False
if type(input_file) is str: if type(input_file) is str:
if input_file.endswith(".ts"):
self.annexb = True
self.input_file = [input_file] self.input_file = [input_file]
elif type(input_file) is list: elif type(input_file) is list:
self.input_file = input_file self.input_file = input_file
@ -17,7 +23,6 @@ class FfmpegTask(object):
self.luts = [] self.luts = []
self.audios = [] self.audios = []
self.overlays = [] self.overlays = []
self.annexb = False
def __repr__(self): def __repr__(self):
_str = f'FfmpegTask(input_file={self.input_file}, task_type={self.task_type}' _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) self.input_file.extend(inputs)
def add_overlay(self, *overlays): 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() self.correct_task_type()
def add_audios(self, *audios): def add_audios(self, *audios):
@ -115,11 +124,14 @@ class FfmpegTask(object):
def get_ffmpeg_args(self): def get_ffmpeg_args(self):
args = ['-y', '-hide_banner'] args = ['-y', '-hide_banner']
video_output_str = "[0:v]"
if self.task_type == 'encode': if self.task_type == 'encode':
# args += ('-hwaccel', 'qsv', '-hwaccel_output_format', 'qsv')
input_args = [] input_args = []
filter_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]" video_output_str = "[0:v]"
audio_output_str = "[0:v]" audio_output_str = "[0:v]"
video_input_count = 0 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"{video_output_str}[{input_index}:v]scale=rw:rh[v]")
filter_args.append(f"[v][{input_index}:v]overlay=1:eof_action=endall[v]") filter_args.append(f"[v][{input_index}:v]overlay=1:eof_action=endall[v]")
video_output_str = "[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("-map")
output_args.append(video_output_str) output_args.append(video_output_str)
output_args.append("-r") output_args.append("-r")
@ -163,11 +178,36 @@ class FfmpegTask(object):
output_args.append(audio_output_str) output_args.append(audio_output_str)
return args + input_args + ["-filter_complex", ";".join(filter_args)] + output_args + [self.get_output_file()] return args + input_args + ["-filter_complex", ";".join(filter_args)] + output_args + [self.get_output_file()]
elif self.task_type == 'concat': elif self.task_type == 'concat':
# 无法通过 annexb 合并的
input_args = [] 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 = [] filter_args = []
video_output_str = "[0:v]" video_output_str = "[0:v]"
audio_output_str = "[0:v]" audio_output_str = "[0:a]"
video_input_count = 0 video_input_count = 0
audio_input_count = 0 audio_input_count = 0
for input_file in self.input_file: for input_file in self.input_file:
@ -204,11 +244,40 @@ class FfmpegTask(object):
output_args.append(f"-map") output_args.append(f"-map")
output_args.append(audio_output_str) output_args.append(audio_output_str)
return args + input_args + ["-filter_complex", ";".join(filter_args)] + output_args + [self.get_output_file()] 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): def set_output_file(self, file=None):
if file is None: if file is None:
if self.output_file == '': if self.output_file == '':
# TODO: Random Filename if self.annexb:
self.output_file = "rand.mp4" self.output_file = "rand_" + str(uuid.uuid4()) + ".ts"
else:
self.output_file = "rand_" + str(uuid.uuid4()) + ".mp4"
else: 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

View File

@ -4,7 +4,6 @@ import biz.task
import config import config
from template import load_local_template from template import load_local_template
from util import api from util import api
from util.system import get_sys_info
load_local_template() load_local_template()
@ -12,8 +11,9 @@ load_local_template()
while True: while True:
# print(get_sys_info()) # print(get_sys_info())
print("waiting for task...") 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: for task in task_list:
print("start task:", task) print("start task:", task)
biz.task.start_task(task) biz.task.start_task(task)
break

View File

@ -2,6 +2,8 @@ import json
import os import os
import logging import logging
from util import api, oss
TEMPLATES = {} TEMPLATES = {}
logger = logging.getLogger("template") logger = logging.getLogger("template")
@ -68,11 +70,53 @@ def load_local_template():
def get_template_def(template_id): def get_template_def(template_id):
if template_id not in TEMPLATES:
download_template(template_id)
return TEMPLATES.get(template_id) return TEMPLATES.get(template_id)
def download_template(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): def analyze_template(template_id):

View File

@ -1,19 +1,40 @@
import logging
import os
import requests import requests
import util.system
session = requests.Session() session = requests.Session()
logger = logging.getLogger(__name__)
def get_render_task(): def normalize_task(task_info):
...
return task_info
def sync_center():
""" """
通过接口获取任务 通过接口获取任务
:return: 任务列表 :return: 任务列表
""" """
tasks = [] try:
tasks.append({ response = session.post(os.getenv('API_ENDPOINT') + "/sync", json={
'user_videos': { 'accessKey': os.getenv('ACCESS_KEY'),
'CAM_ID': 'paper-planes.mp4' '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 return tasks
@ -25,39 +46,117 @@ def get_template_info(template_id):
:type template_id: str :type template_id: str
:return: 模板信息 :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 = { template = {
'id': template_id, 'id': template_id,
'name': '模板名称', 'scenic_name': remote_template_info.get('scenicName', '景区'),
'description': '模板描述', 'name': remote_template_info.get('name', '模版'),
'video_size': '1920x1080', 'video_size': '1920x1080',
'frame_rate': 30, 'frame_rate': 30,
'overall_duration': 30, 'overall_duration': 30,
'video_parts': [ '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

View File

@ -1,23 +1,48 @@
import logging
import os import os
import subprocess
from datetime import datetime from datetime import datetime
from typing import Optional, IO from typing import Optional, IO
from entity.ffmpeg import FfmpegTask 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): def start_render(ffmpeg_task: FfmpegTask):
print(ffmpeg_task) logger.info(ffmpeg_task)
print(ffmpeg_task.get_ffmpeg_args()) if not ffmpeg_task.need_run():
os.system("ffmpeg.exe "+" ".join(ffmpeg_task.get_ffmpeg_args())) 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" out_time = "0:0:0.0"
if stdout is None: if stdout is None:
print("[!]STDOUT is null") print("[!]STDOUT is null")
return out_time return out_time
speed = "0" speed = "0"
while True: for line in stdout.split(b"\n"):
line = stdout.readline()
if line == b"": if line == b"":
break break
if line.strip() == b"progress=end": if line.strip() == b"progress=end":
@ -28,8 +53,75 @@ def handle_ffmpeg_output(stdout: Optional[IO[bytes]]) -> str:
if line.startswith(b"speed="): if line.startswith(b"speed="):
speed = line.replace(b"speed=", b"").decode().strip() speed = line.replace(b"speed=", b"").decode().strip()
print("[ ]Speed:", out_time, "@", speed) print("[ ]Speed:", out_time, "@", speed)
return out_time return out_time+"@"+speed
def duration_str_to_float(duration_str: str) -> float: def duration_str_to_float(duration_str: str) -> float:
_duration = datetime.strptime(duration_str, "%H:%M:%S.%f") - datetime(1900, 1, 1) _duration = datetime.strptime(duration_str, "%H:%M:%S.%f") - datetime(1900, 1, 1)
return _duration.total_seconds() 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

View File

@ -1,7 +1,12 @@
import logging
import os
import requests 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 使用签名URL上传文件到OSS
:param str url: 签名URL :param str url: 签名URL
@ -15,3 +20,24 @@ def upload_to_oss_use_signed_url(url, file_path):
except Exception as e: except Exception as e:
print(e) print(e)
return False 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

View File

@ -1,5 +1,7 @@
import os import os
import platform import platform
from datetime import datetime
import psutil import psutil
from constant import SUPPORT_FEATURE, SOFTWARE_VERSION from constant import SUPPORT_FEATURE, SOFTWARE_VERSION
@ -10,6 +12,7 @@ def get_sys_info():
""" """
info = { info = {
'version': SOFTWARE_VERSION, 'version': SOFTWARE_VERSION,
'client_datetime': datetime.now().isoformat(),
'platform': platform.system(), 'platform': platform.system(),
'runtime_version': 'Python ' + platform.python_version(), 'runtime_version': 'Python ' + platform.python_version(),
'cpu_count': os.cpu_count(), 'cpu_count': os.cpu_count(),