上传main
This commit is contained in:
parent
70da8b17a5
commit
967a3ad705
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.history
|
||||||
|
__pycache__/
|
||||||
|
app.log
|
117
ffmpeg_utils.py
Normal file
117
ffmpeg_utils.py
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
import subprocess
|
||||||
|
import requests
|
||||||
|
import tempfile
|
||||||
|
import shutil
|
||||||
|
import logging
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
UPLOAD_FOLDER = 'temp_files'
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def temp_directory(dir=None):
|
||||||
|
temp_dir = tempfile.mkdtemp(dir=dir)
|
||||||
|
try:
|
||||||
|
yield temp_dir
|
||||||
|
finally:
|
||||||
|
# 不再在这里自动删除临时目录
|
||||||
|
pass
|
||||||
|
|
||||||
|
def download_file(url, temp_dir):
|
||||||
|
try:
|
||||||
|
headers = {
|
||||||
|
'User-Agent': 'Mozilla/5.0',
|
||||||
|
'Accept': '*/*'
|
||||||
|
}
|
||||||
|
|
||||||
|
logging.info(f"开始从URL下载文件: {url}")
|
||||||
|
response = requests.get(url, headers=headers, stream=True, timeout=30)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
file_path = os.path.join(temp_dir, 'input_audio')
|
||||||
|
|
||||||
|
with open(file_path, 'wb') as f:
|
||||||
|
for chunk in response.iter_content(chunk_size=8192):
|
||||||
|
if chunk:
|
||||||
|
f.write(chunk)
|
||||||
|
|
||||||
|
file_size = os.path.getsize(file_path)
|
||||||
|
if file_size < 1024:
|
||||||
|
error_msg = f"下载的文件太小(仅{file_size}字节),可能无效"
|
||||||
|
logging.error(error_msg)
|
||||||
|
raise Exception(error_msg)
|
||||||
|
|
||||||
|
logging.info(f"文件下载成功,保存到: {file_path} (大小: {file_size}字节)")
|
||||||
|
return file_path
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"从 {url} 下载文件时出错: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def execute_ffmpeg(input_path, output_path, ffmpeg_args):
|
||||||
|
try:
|
||||||
|
filtered_args = [arg for arg in ffmpeg_args if not (arg.lower() in ['-i', '-input'] or
|
||||||
|
(ffmpeg_args.index(arg) > 0 and ffmpeg_args[ffmpeg_args.index(arg) - 1].lower() == '-i'))]
|
||||||
|
|
||||||
|
cmd = ['lib/ffmpeg/bin/ffmpeg', '-i', input_path] + filtered_args + [output_path]
|
||||||
|
logging.info(f"执行FFmpeg命令: {' '.join(cmd)}")
|
||||||
|
|
||||||
|
result = subprocess.run(
|
||||||
|
cmd,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
universal_newlines=True,
|
||||||
|
encoding='utf-8',
|
||||||
|
errors='replace'
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
error_msg = f"FFmpeg处理失败,返回码: {result.returncode}, 错误: {result.stderr}"
|
||||||
|
logging.error(error_msg)
|
||||||
|
raise Exception(error_msg)
|
||||||
|
|
||||||
|
logging.info("FFmpeg处理成功完成")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"执行FFmpeg时出错: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def process_ffmpeg(data, file_registry, file_lock):
|
||||||
|
try:
|
||||||
|
temp_dir = tempfile.mkdtemp(dir=UPLOAD_FOLDER)
|
||||||
|
logging.info(f"创建临时目录: {temp_dir}")
|
||||||
|
|
||||||
|
input_url = data.get('input_url')
|
||||||
|
ffmpeg_args = data.get('args', [])
|
||||||
|
input_path = download_file(input_url, temp_dir)
|
||||||
|
|
||||||
|
output_id = str(uuid.uuid4())[:8]
|
||||||
|
output_format = data.get('output_format', 'mp4')
|
||||||
|
output_filename = f"{output_id}.{output_format}"
|
||||||
|
output_path = os.path.join(temp_dir, output_filename)
|
||||||
|
|
||||||
|
execute_ffmpeg(input_path, output_path, ffmpeg_args)
|
||||||
|
|
||||||
|
with file_lock:
|
||||||
|
file_registry[output_id] = {
|
||||||
|
'path': os.path.abspath(output_path),
|
||||||
|
'filename': output_filename,
|
||||||
|
'last_access': time.time(),
|
||||||
|
'download_count': 0
|
||||||
|
}
|
||||||
|
logging.info(f"已注册新文件ID: {output_id}, 路径: {output_path}")
|
||||||
|
|
||||||
|
# 返回临时目录路径以便在主函数中删除
|
||||||
|
return {
|
||||||
|
'status': 'success',
|
||||||
|
'download_url': f"http://ffmpeg.liulikeji.cn/download/{output_id}/{output_filename}",
|
||||||
|
'file_id': output_id,
|
||||||
|
'temp_dir': temp_dir # 返回临时目录路径
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"处理过程中出错: {e}")
|
||||||
|
return {'error': str(e)}
|
52
file_cleanup.py
Normal file
52
file_cleanup.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
|
||||||
|
UPLOAD_FOLDER = 'temp_files'
|
||||||
|
CLEANUP_INTERVAL = 3600 # 清理临时文件的间隔(秒)
|
||||||
|
FILE_EXPIRY = 7200 # 文件过期时间(秒)
|
||||||
|
|
||||||
|
file_registry = {}
|
||||||
|
file_lock = threading.Lock()
|
||||||
|
|
||||||
|
def cleanup_temp_files():
|
||||||
|
current_time = time.time()
|
||||||
|
expired_folders = []
|
||||||
|
|
||||||
|
for folder_name in os.listdir(UPLOAD_FOLDER):
|
||||||
|
folder_path = os.path.join(UPLOAD_FOLDER, folder_name)
|
||||||
|
|
||||||
|
if not os.path.isdir(folder_path):
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
folder_mtime = os.path.getmtime(folder_path)
|
||||||
|
|
||||||
|
if current_time - folder_mtime > FILE_EXPIRY:
|
||||||
|
expired_folders.append(folder_path)
|
||||||
|
logging.info(f"检测到过期文件夹: {folder_path} (修改时间: {time.ctime(folder_mtime)})")
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"获取文件夹 {folder_path} 信息失败: {e}")
|
||||||
|
|
||||||
|
for folder_path in expired_folders:
|
||||||
|
try:
|
||||||
|
shutil.rmtree(folder_path)
|
||||||
|
logging.info(f"已删除过期文件夹: {folder_path}")
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"删除文件夹 {folder_path} 失败: {e}")
|
||||||
|
|
||||||
|
logging.info(f"清理完成,共删除 {len(expired_folders)} 个过期文件夹")
|
||||||
|
|
||||||
|
def start_cleanup_thread():
|
||||||
|
def cleanup_loop():
|
||||||
|
while True:
|
||||||
|
logging.info("开始执行定期清理任务...")
|
||||||
|
cleanup_temp_files()
|
||||||
|
time.sleep(CLEANUP_INTERVAL)
|
||||||
|
|
||||||
|
thread = threading.Thread(target=cleanup_loop)
|
||||||
|
thread.daemon = True
|
||||||
|
thread.start()
|
||||||
|
logging.info("已启动后台清理线程")
|
BIN
lib/ffmpeg/bin/ffmpeg
Normal file
BIN
lib/ffmpeg/bin/ffmpeg
Normal file
Binary file not shown.
BIN
lib/ffmpeg/bin/ffmpeg.exe
Normal file
BIN
lib/ffmpeg/bin/ffmpeg.exe
Normal file
Binary file not shown.
BIN
lib/ffmpeg/bin/ffplay
Normal file
BIN
lib/ffmpeg/bin/ffplay
Normal file
Binary file not shown.
BIN
lib/ffmpeg/bin/ffplay.exe
Normal file
BIN
lib/ffmpeg/bin/ffplay.exe
Normal file
Binary file not shown.
BIN
lib/ffmpeg/bin/ffprobe
Normal file
BIN
lib/ffmpeg/bin/ffprobe
Normal file
Binary file not shown.
BIN
lib/ffmpeg/bin/ffprobe.exe
Normal file
BIN
lib/ffmpeg/bin/ffprobe.exe
Normal file
Binary file not shown.
26
logging_config.py
Normal file
26
logging_config.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import logging
|
||||||
|
from logging.handlers import RotatingFileHandler
|
||||||
|
|
||||||
|
def setup_logging():
|
||||||
|
log_format = '%(asctime)s - %(levelname)s - %(message)s'
|
||||||
|
|
||||||
|
console_handler = logging.StreamHandler()
|
||||||
|
console_handler.setLevel(logging.INFO)
|
||||||
|
console_handler.setFormatter(logging.Formatter(log_format))
|
||||||
|
|
||||||
|
file_handler = RotatingFileHandler(
|
||||||
|
'app.log',
|
||||||
|
maxBytes=10 * 1024 * 1024,
|
||||||
|
backupCount=5,
|
||||||
|
encoding='utf-8'
|
||||||
|
)
|
||||||
|
file_handler.setLevel(logging.INFO)
|
||||||
|
file_handler.setFormatter(logging.Formatter(log_format))
|
||||||
|
|
||||||
|
root_logger = logging.getLogger()
|
||||||
|
root_logger.setLevel(logging.INFO)
|
||||||
|
root_logger.addHandler(console_handler)
|
||||||
|
root_logger.addHandler(file_handler)
|
||||||
|
|
||||||
|
werkzeug_logger = logging.getLogger('werkzeug')
|
||||||
|
werkzeug_logger.setLevel(logging.WARNING)
|
104
main.py
104
main.py
@ -0,0 +1,104 @@
|
|||||||
|
import logging
|
||||||
|
import json
|
||||||
|
from flask import Flask, request, jsonify
|
||||||
|
import threading
|
||||||
|
import queue
|
||||||
|
import time
|
||||||
|
from ffmpeg_utils import process_ffmpeg
|
||||||
|
from file_cleanup import start_cleanup_thread
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
# 假设这些是主文件中已定义的变量
|
||||||
|
file_registry = {}
|
||||||
|
file_lock = threading.Lock()
|
||||||
|
|
||||||
|
#输入参数列表
|
||||||
|
enter_parameter_table = {
|
||||||
|
"ffmpeg": {
|
||||||
|
"input_url": str,
|
||||||
|
"output_format": str,
|
||||||
|
"args": list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 检测输入参数是否符合要求
|
||||||
|
def validate_request(data,api_name):
|
||||||
|
for key in enter_parameter_table[api_name]:
|
||||||
|
if key not in data:
|
||||||
|
logging.warning(f"请求中没有提供{key}参数")
|
||||||
|
return jsonify({'error': f"未提供{key}参数"}), 400
|
||||||
|
if isinstance(data[key], enter_parameter_table[api_name][key]) == False:
|
||||||
|
logging.warning(f"请求中{key}参数类型错误,应为{enter_parameter_table[api_name][key]}")
|
||||||
|
return jsonify({'error': f"{key}参数类型错误,您输入为{type(data[key])},应为{enter_parameter_table[api_name][key]}" }), 400
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/ffmpeg', methods=['POST'])
|
||||||
|
def ffmpeg_api():
|
||||||
|
logging.info("收到FFmpeg API请求")
|
||||||
|
data = request.get_json()
|
||||||
|
|
||||||
|
# 检测参数类型
|
||||||
|
error_response, status_code = validate_request(data,"ffmpeg")
|
||||||
|
if error_response: return error_response, status_code
|
||||||
|
|
||||||
|
# 创建处理进程
|
||||||
|
result_queue = queue.Queue()
|
||||||
|
def run_process(data, file_registry, file_lock, result_queue):
|
||||||
|
with app.app_context(): # 设置应用上下文
|
||||||
|
result = process_ffmpeg(data, file_registry, file_lock)
|
||||||
|
result_queue.put(result)
|
||||||
|
|
||||||
|
thread = threading.Thread(target=run_process, args=(data, file_registry, file_lock, result_queue))
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
# 等待处理结果
|
||||||
|
result = result_queue.get()
|
||||||
|
if 'error' in result:
|
||||||
|
logging.error(f"处理过程中出错: {result['error']}")
|
||||||
|
return jsonify({'status': 'error','error': result['error']}), 500
|
||||||
|
else:
|
||||||
|
logging.info(f"处理成功,返回下载URL: {result['download_url']}")
|
||||||
|
return jsonify({
|
||||||
|
'status': 'success',
|
||||||
|
'download_url': result['download_url'],
|
||||||
|
'file_id': result['file_id']
|
||||||
|
}), 200
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/sanjuuni', methods=['POST'])
|
||||||
|
def sanjuuni_api():
|
||||||
|
|
||||||
|
return jsonify({'status': 'error','error': '暂未开放'}), 403
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/download/<file_id>/<filename>', methods=['GET'])
|
||||||
|
def download_file_endpoint(file_id, filename):
|
||||||
|
logging.info(f"收到文件下载请求 - 文件ID: {file_id}, 文件名: {filename}")
|
||||||
|
with file_lock:
|
||||||
|
if file_id not in file_registry:
|
||||||
|
logging.warning(f"文件ID: {file_id} 不存在")
|
||||||
|
return jsonify({'error': '文件不存在'}), 404
|
||||||
|
file_info = file_registry[file_id]
|
||||||
|
file_info['last_access'] = time.time()
|
||||||
|
file_info['download_count'] += 1
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(file_info['path'], 'rb') as f:
|
||||||
|
file_data = f.read()
|
||||||
|
return file_data, 200, {'Content-Disposition': f'attachment; filename={filename}'}
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"下载文件时出错: {e}")
|
||||||
|
return jsonify({'status': 'error','error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
start_cleanup_thread()
|
||||||
|
app.run(debug=True)
|
Loading…
x
Reference in New Issue
Block a user