Compare commits

...

6 Commits

Author SHA1 Message Date
103a947b23 添加 requirements.txt 2025-05-04 18:16:18 +08:00
85abaa51de 添加 README.md 2025-05-04 18:07:39 +08:00
084cfb6614 更新 sanjuuni_utils.py 2025-05-04 17:32:22 +08:00
HKXluo
6a6c6cd1ab 移除lib中的客户端文件 2025-05-04 17:24:41 +08:00
HKXluo
391acc479c 删除调试输出 2025-05-04 17:22:43 +08:00
HKXluo
54c2c9404b 添加sanjuuni功能 2025-05-04 17:22:21 +08:00
46 changed files with 271 additions and 15 deletions

84
README.md Normal file
View File

@@ -0,0 +1,84 @@
---
# GMapiServer
**GMapiServer** 是一个基于 Python 和 Flask 构建的高性能 API 服务器,支持通过 URL 输入文件并输出下载链接,提供异步处理功能,确保主进程不被阻塞。内置缓存自动清理机制(默认保留 2 小时),支持多种工具接口调用。
---
## 🚀 项目简介
**GMapiServer** 是一个轻量级、模块化的 API 服务框架,旨在为开发者提供以下核心功能:
- **URL 输入与输出**:支持通过 URL 提交文件,返回生成文件的下载链接。
- **异步处理**:任务在后台异步执行,不阻塞主进程,提升服务器并发性能。
- **自动缓存清理**:内置定时清理机制,自动删除过期的缓存文件(包括输出文件),默认保留时间 2 小时。
- **多工具接口集成**:集成 FFmpeg 和 Sanjuuni 工具接口,支持在线调用。
---
## 🛠️ 技术栈
- **编程语言**: Python
- **Web 框架**: Flask
- **异步处理**: 基于 `threading`
- **缓存清理**: 定时任务
- **依赖管理**: pip
---
## 🧩 支持的接口
### 1. FFmpeg 工具接口
- **功能**: 在线调用 FFmpeg 工具进行视频/音频处理(如转码、裁剪、合并等)。
- **接口文档**: [FFmpegApi 文档](https://www.liulikeji.cn/archives/FFmpegApi)
### 2. Sanjuuni 工具接口
- **功能**: 在线调用 [Sanjuuni 工具](https://github.com/MCJack123/sanjuuni/tree/master)(具体功能需参考其官方文档)。
- **接口文档**: [SanjuuniApi 文档](https://www.liulikeji.cn/archives/SanjuuniApi)
---
## 📦 部署与使用
### 1. 安装依赖
```bash
pip install -r requirements.txt
```
### 2. 启动服务器
```bash
python main.py
```
---
## ⏰ 自动缓存清理配置
- **默认保留时间**: 2 小时
- **自定义配置**:
```python
# 在logging_config.py文件中修改缓存清理时间
CLEANUP_INTERVAL = 3600 # 清理临时文件的间隔(秒)
FILE_EXPIRY = 7200 # 文件过期时间(秒)
```
## 端口配置
- **默认端口**: 5000
- **自定义配置**:
```python
# 在main.py文件底部中修改
app.run(debug=True,port=5000)
```
---
## ⚠️ 注意事项
1. **文件合法性**: 请确保上传文件符合法律法规,不得用于非法用途。
2. **缓存文件**: 系统会自动清理过期文件,请及时下载生成的输出文件。
---
## 📝 贡献与反馈
欢迎提交 Issue 或 Pull Request
如有问题,请联系:[xingluo01@liulikeji.cn] 或 [qq:180877430]
---

BIN
lib/sanjuuni/OpenCL.dll Normal file

Binary file not shown.

BIN
lib/sanjuuni/PocoCrypto.dll Normal file

Binary file not shown.

Binary file not shown.

BIN
lib/sanjuuni/PocoJSON.dll Normal file

Binary file not shown.

BIN
lib/sanjuuni/PocoNet.dll Normal file

Binary file not shown.

BIN
lib/sanjuuni/PocoNetSSL.dll Normal file

Binary file not shown.

BIN
lib/sanjuuni/PocoUtil.dll Normal file

Binary file not shown.

BIN
lib/sanjuuni/PocoXML.dll Normal file

Binary file not shown.

BIN
lib/sanjuuni/avcodec-60.dll Normal file

Binary file not shown.

Binary file not shown.

BIN
lib/sanjuuni/avfilter-9.dll Normal file

Binary file not shown.

Binary file not shown.

BIN
lib/sanjuuni/avutil-58.dll Normal file

Binary file not shown.

BIN
lib/sanjuuni/bz2.dll Normal file

Binary file not shown.

BIN
lib/sanjuuni/dav1d.dll Normal file

Binary file not shown.

Binary file not shown.

BIN
lib/sanjuuni/libexpat.dll Normal file

Binary file not shown.

BIN
lib/sanjuuni/liblzma.dll Normal file

Binary file not shown.

BIN
lib/sanjuuni/libmp3lame.DLL Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
lib/sanjuuni/libwebp.dll Normal file

Binary file not shown.

BIN
lib/sanjuuni/libwebpmux.dll Normal file

Binary file not shown.

BIN
lib/sanjuuni/modplug.dll Normal file

Binary file not shown.

BIN
lib/sanjuuni/ogg.dll Normal file

Binary file not shown.

BIN
lib/sanjuuni/openh264-6.dll Normal file

Binary file not shown.

BIN
lib/sanjuuni/openjp2.dll Normal file

Binary file not shown.

BIN
lib/sanjuuni/opus.dll Normal file

Binary file not shown.

BIN
lib/sanjuuni/pcre2-8.dll Normal file

Binary file not shown.

BIN
lib/sanjuuni/sanjuuni Normal file

Binary file not shown.

BIN
lib/sanjuuni/sanjuuni.exe Normal file

Binary file not shown.

BIN
lib/sanjuuni/sanjuuni.pdb Normal file

Binary file not shown.

BIN
lib/sanjuuni/snappy.dll Normal file

Binary file not shown.

BIN
lib/sanjuuni/soxr.dll Normal file

Binary file not shown.

BIN
lib/sanjuuni/speex-1.dll Normal file

Binary file not shown.

Binary file not shown.

BIN
lib/sanjuuni/swscale-7.dll Normal file

Binary file not shown.

BIN
lib/sanjuuni/theoradec.dll Normal file

Binary file not shown.

BIN
lib/sanjuuni/theoraenc.dll Normal file

Binary file not shown.

BIN
lib/sanjuuni/vorbis.dll Normal file

Binary file not shown.

BIN
lib/sanjuuni/vorbisenc.dll Normal file

Binary file not shown.

BIN
lib/sanjuuni/zlib1.dll Normal file

Binary file not shown.

54
main.py
View File

@@ -5,6 +5,7 @@ import threading
import queue import queue
import time import time
from ffmpeg_utils import process_ffmpeg from ffmpeg_utils import process_ffmpeg
from sanjuuni_utils import process_sanjuuni
from file_cleanup import start_cleanup_thread from file_cleanup import start_cleanup_thread
app = Flask(__name__) app = Flask(__name__)
@@ -13,36 +14,38 @@ app = Flask(__name__)
file_registry = {} file_registry = {}
file_lock = threading.Lock() file_lock = threading.Lock()
#输入参数列表 # 输入参数列表
enter_parameter_table = { enter_parameter_table = {
"ffmpeg": { "ffmpeg": {
"input_url": str, "input_url": str,
"output_format": str, "output_format": str,
"args": list "args": list
},
"sanjuuni": {
"input_url": str,
"output_format": str,
"args": list
} }
} }
# 检测输入参数是否符合要求 # 检测输入参数是否符合要求
def validate_request(data,api_name): def validate_request(data, api_name):
for key in enter_parameter_table[api_name]: for key in enter_parameter_table[api_name]:
if key not in data: if key not in data and key != "subtitle":
logging.warning(f"请求中没有提供{key}参数") logging.warning(f"请求中没有提供{key}参数")
return jsonify({'error': f"未提供{key}参数"}), 400 return jsonify({'error': f"未提供{key}参数"}), 400
if isinstance(data[key], enter_parameter_table[api_name][key]) == False: if key in data and isinstance(data[key], enter_parameter_table[api_name][key]) == False:
logging.warning(f"请求中{key}参数类型错误,应为{enter_parameter_table[api_name][key]}") 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 jsonify({'error': f"{key}参数类型错误,您输入为{type(data[key])},应为{enter_parameter_table[api_name][key]}" }), 400
return None, None return None, None
@app.route('/api/ffmpeg', methods=['POST']) @app.route('/api/ffmpeg', methods=['POST'])
def ffmpeg_api(): def ffmpeg_api():
logging.info("收到FFmpeg API请求") logging.info("收到FFmpeg API请求")
data = request.get_json() data = request.get_json()
# 检测参数类型 # 检测参数类型
error_response, status_code = validate_request(data,"ffmpeg") error_response, status_code = validate_request(data, "ffmpeg")
if error_response: return error_response, status_code if error_response: return error_response, status_code
# 创建处理进程 # 创建处理进程
@@ -59,7 +62,7 @@ def ffmpeg_api():
result = result_queue.get() result = result_queue.get()
if 'error' in result: if 'error' in result:
logging.error(f"处理过程中出错: {result['error']}") logging.error(f"处理过程中出错: {result['error']}")
return jsonify({'status': 'error','error': result['error']}), 500 return jsonify({'status': 'error', 'error': result['error']}), 500
else: else:
logging.info(f"处理成功返回下载URL: {result['download_url']}") logging.info(f"处理成功返回下载URL: {result['download_url']}")
return jsonify({ return jsonify({
@@ -68,15 +71,37 @@ def ffmpeg_api():
'file_id': result['file_id'] 'file_id': result['file_id']
}), 200 }), 200
@app.route('/api/sanjuuni', methods=['POST']) @app.route('/api/sanjuuni', methods=['POST'])
def sanjuuni_api(): def sanjuuni_api():
logging.info("收到Sanjuuni API请求")
data = request.get_json()
return jsonify({'status': 'error','error': '暂未开放'}), 403 # 检测参数类型
error_response, status_code = validate_request(data, "sanjuuni")
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_sanjuuni(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('/download/<file_id>/<filename>', methods=['GET']) @app.route('/download/<file_id>/<filename>', methods=['GET'])
def download_file_endpoint(file_id, filename): def download_file_endpoint(file_id, filename):
@@ -95,10 +120,9 @@ def download_file_endpoint(file_id, filename):
return file_data, 200, {'Content-Disposition': f'attachment; filename={filename}'} return file_data, 200, {'Content-Disposition': f'attachment; filename={filename}'}
except Exception as e: except Exception as e:
logging.error(f"下载文件时出错: {e}") logging.error(f"下载文件时出错: {e}")
return jsonify({'status': 'error','error': str(e)}), 500 return jsonify({'status': 'error', 'error': str(e)}), 500
if __name__ == '__main__': if __name__ == '__main__':
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
start_cleanup_thread() start_cleanup_thread()
app.run(debug=True) app.run(debug=True)

2
requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
Flask==3.1.0
Requests==2.32.3

146
sanjuuni_utils.py Normal file
View File

@@ -0,0 +1,146 @@
import os
import uuid
import subprocess
import requests
import tempfile
import shutil
import logging
from contextlib import contextmanager
import time # 导入 time 模块
@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_sanjuuni(input_path, output_path, sanjuuni_args):
try:
cmd = ['lib/sanjuuni/sanjuuni', '-i', input_path] + sanjuuni_args + ['-o', output_path]
logging.info(f"执行Sanjuuni命令: {' '.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"Sanjuuni处理失败返回码: {result.returncode}, 错误: {result.stderr}"
logging.error(error_msg)
raise Exception(error_msg)
logging.info("Sanjuuni处理成功完成")
return True
except Exception as e:
logging.error(f"执行Sanjuuni时出错: {e}")
raise
def process_sanjuuni(data, file_registry, file_lock):
try:
temp_dir = tempfile.mkdtemp(dir='temp_files')
logging.info(f"创建临时目录: {temp_dir}")
input_url = data.get('input_url')
sanjuuni_args = data.get('args', []) # 从请求数据中获取 args 参数
# 定义不允许的参数
disallowed_params = [
'-s', '--http=', '-w', '--websocket=', '-u', '--websocket-client=',
'-T', '--streamed', '--disable-opencl', '-i', '--input=', '-o', '--output=',
'-S', '--subtitles='
]
# 检查是否有不允许的参数
for arg in sanjuuni_args:
for param in disallowed_params:
if arg.startswith(param):
error_msg = f"不允许使用参数: {arg}"
logging.error(error_msg)
raise ValueError(error_msg)
subtitle = data.get('subtitle', '')
output_format = data.get('output_format', 'lua')
# 构建 sanjuuni 参数
if 'format' in data:
sanjuuni_args.extend(['-f', data['format']])
if output_format == 'lua':
sanjuuni_args.append('-l')
elif output_format == 'nfp':
sanjuuni_args.append('-n')
elif output_format == 'raw':
sanjuuni_args.append('-r')
elif output_format == 'bimg':
sanjuuni_args.append('-b')
elif output_format == '32vid':
sanjuuni_args.append('-3')
else:
raise ValueError(f"Unsupported output format: {output_format}")
input_path = download_file(input_url, temp_dir)
output_id = str(uuid.uuid4())[:8]
output_filename = f"{output_id}.{output_format}"
output_path = os.path.join(temp_dir, output_filename)
execute_sanjuuni(input_path, output_path, sanjuuni_args)
with file_lock:
file_registry[output_id] = {
'path': os.path.abspath(output_path),
'filename': output_filename,
'last_access': time.time(), # 使用 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)}