Compare commits
6 Commits
967a3ad705
...
v1.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 103a947b23 | |||
| 85abaa51de | |||
| 084cfb6614 | |||
|
|
6a6c6cd1ab | ||
|
|
391acc479c | ||
|
|
54c2c9404b |
84
README.md
Normal file
84
README.md
Normal 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
BIN
lib/sanjuuni/OpenCL.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/PocoCrypto.dll
Normal file
BIN
lib/sanjuuni/PocoCrypto.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/PocoFoundation.dll
Normal file
BIN
lib/sanjuuni/PocoFoundation.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/PocoJSON.dll
Normal file
BIN
lib/sanjuuni/PocoJSON.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/PocoNet.dll
Normal file
BIN
lib/sanjuuni/PocoNet.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/PocoNetSSL.dll
Normal file
BIN
lib/sanjuuni/PocoNetSSL.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/PocoUtil.dll
Normal file
BIN
lib/sanjuuni/PocoUtil.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/PocoXML.dll
Normal file
BIN
lib/sanjuuni/PocoXML.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/avcodec-60.dll
Normal file
BIN
lib/sanjuuni/avcodec-60.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/avdevice-60.dll
Normal file
BIN
lib/sanjuuni/avdevice-60.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/avfilter-9.dll
Normal file
BIN
lib/sanjuuni/avfilter-9.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/avformat-60.dll
Normal file
BIN
lib/sanjuuni/avformat-60.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/avutil-58.dll
Normal file
BIN
lib/sanjuuni/avutil-58.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/bz2.dll
Normal file
BIN
lib/sanjuuni/bz2.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/dav1d.dll
Normal file
BIN
lib/sanjuuni/dav1d.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/libcrypto-3-x64.dll
Normal file
BIN
lib/sanjuuni/libcrypto-3-x64.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/libexpat.dll
Normal file
BIN
lib/sanjuuni/libexpat.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/liblzma.dll
Normal file
BIN
lib/sanjuuni/liblzma.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/libmp3lame.DLL
Normal file
BIN
lib/sanjuuni/libmp3lame.DLL
Normal file
Binary file not shown.
BIN
lib/sanjuuni/libsharpyuv.dll
Normal file
BIN
lib/sanjuuni/libsharpyuv.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/libssl-3-x64.dll
Normal file
BIN
lib/sanjuuni/libssl-3-x64.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/libwebp.dll
Normal file
BIN
lib/sanjuuni/libwebp.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/libwebpmux.dll
Normal file
BIN
lib/sanjuuni/libwebpmux.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/modplug.dll
Normal file
BIN
lib/sanjuuni/modplug.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/ogg.dll
Normal file
BIN
lib/sanjuuni/ogg.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/openh264-6.dll
Normal file
BIN
lib/sanjuuni/openh264-6.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/openjp2.dll
Normal file
BIN
lib/sanjuuni/openjp2.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/opus.dll
Normal file
BIN
lib/sanjuuni/opus.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/pcre2-8.dll
Normal file
BIN
lib/sanjuuni/pcre2-8.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/sanjuuni
Normal file
BIN
lib/sanjuuni/sanjuuni
Normal file
Binary file not shown.
BIN
lib/sanjuuni/sanjuuni.exe
Normal file
BIN
lib/sanjuuni/sanjuuni.exe
Normal file
Binary file not shown.
BIN
lib/sanjuuni/sanjuuni.pdb
Normal file
BIN
lib/sanjuuni/sanjuuni.pdb
Normal file
Binary file not shown.
BIN
lib/sanjuuni/snappy.dll
Normal file
BIN
lib/sanjuuni/snappy.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/soxr.dll
Normal file
BIN
lib/sanjuuni/soxr.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/speex-1.dll
Normal file
BIN
lib/sanjuuni/speex-1.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/swresample-4.dll
Normal file
BIN
lib/sanjuuni/swresample-4.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/swscale-7.dll
Normal file
BIN
lib/sanjuuni/swscale-7.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/theoradec.dll
Normal file
BIN
lib/sanjuuni/theoradec.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/theoraenc.dll
Normal file
BIN
lib/sanjuuni/theoraenc.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/vorbis.dll
Normal file
BIN
lib/sanjuuni/vorbis.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/vorbisenc.dll
Normal file
BIN
lib/sanjuuni/vorbisenc.dll
Normal file
Binary file not shown.
BIN
lib/sanjuuni/zlib1.dll
Normal file
BIN
lib/sanjuuni/zlib1.dll
Normal file
Binary file not shown.
42
main.py
42
main.py
@@ -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__)
|
||||||
@@ -19,23 +20,25 @@ enter_parameter_table = {
|
|||||||
"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请求")
|
||||||
@@ -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):
|
||||||
@@ -97,7 +122,6 @@ def download_file_endpoint(file_id, filename):
|
|||||||
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()
|
||||||
|
|||||||
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Flask==3.1.0
|
||||||
|
Requests==2.32.3
|
||||||
146
sanjuuni_utils.py
Normal file
146
sanjuuni_utils.py
Normal 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)}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user