hdmi
This commit is contained in:
@@ -1,10 +1,3 @@
|
|||||||
[youtube]
|
[youtube]
|
||||||
# 只写 stream key(推荐方式)
|
# 只写 stream key(推荐方式)
|
||||||
key = qxvb-r47b-r5ju-6ud3-6k7z
|
key = qxvb-r47b-r5ju-6ud3-6k7z
|
||||||
|
|
||||||
# 如果想推多个频道,直接加更多 key(自动多路推流)
|
|
||||||
# main = qxvb-r47b-r5ju-6ud3-6k7z
|
|
||||||
# backup = xxxx-xxxx-xxxx-xxxx-xxxx
|
|
||||||
|
|
||||||
# 如果你想直接写完整 RTMP 地址(兼容旧方式)
|
|
||||||
# full_url = rtmp://a.rtmp.youtube.com/live2/qxvb-r47b-r5ju-6ud3-6k7z
|
|
||||||
3
dist/douyin_youtube_ffplay.py
vendored
Normal file
3
dist/douyin_youtube_ffplay.py
vendored
Normal file
File diff suppressed because one or more lines are too long
2
dist/pyarmor_runtime_000000/__init__.py
vendored
2
dist/pyarmor_runtime_000000/__init__.py
vendored
@@ -1,2 +1,2 @@
|
|||||||
# Pyarmor 9.2.3 (trial), 000000, 2026-01-24T06:52:53.934396
|
# Pyarmor 9.2.3 (trial), 000000, 2026-01-27T08:55:17.418182
|
||||||
from .pyarmor_runtime import __pyarmor__
|
from .pyarmor_runtime import __pyarmor__
|
||||||
|
|||||||
149
index.html
149
index.html
@@ -4,7 +4,6 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
<title>多进程录制控制台</title>
|
<title>多进程录制控制台</title>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
|
|
||||||
<style>
|
<style>
|
||||||
:root{--bg:#1a237e;--accent:#283593;--text:#fff;--card:#fff;--card-text:#000}
|
:root{--bg:#1a237e;--accent:#283593;--text:#fff;--card:#fff;--card-text:#000}
|
||||||
@media(prefers-color-scheme:dark){:root{--card:#1e1e1e;--card-text:#fff}}
|
@media(prefers-color-scheme:dark){:root{--card:#1e1e1e;--card-text:#fff}}
|
||||||
@@ -14,17 +13,17 @@ h1{margin:0 0 24px;font-size:1.6em;display:flex;align-items:center;gap:12px}
|
|||||||
#processSelect{padding:8px;border-radius:8px;border:1px solid #ddd;font-size:1em}
|
#processSelect{padding:8px;border-radius:8px;border:1px solid #ddd;font-size:1em}
|
||||||
#statusIndicator{font-size:1.2em;font-weight:600}
|
#statusIndicator{font-size:1.2em;font-weight:600}
|
||||||
h2{margin:24px 0 12px;font-size:1.4em}
|
h2{margin:24px 0 12px;font-size:1.4em}
|
||||||
input,button{width:100%;padding:12px;margin:8px 0;border-radius:8px;border:none;box-sizing:border-box;font-size:1em}
|
input,button,textarea{width:100%;padding:12px;margin:8px 0;border-radius:8px;border:none;box-sizing:border-box;font-size:1em}
|
||||||
input{background:#f5f5f5;color:#000;border:1px solid #ddd}
|
input,textarea{background:#f5f5f5;color:#000;border:1px solid #ddd;font-family:monospace}
|
||||||
button{background:var(--accent);color:#fff;cursor:pointer;font-weight:600;transition:.2s}
|
button{background:var(--accent);color:#fff;cursor:pointer;font-weight:600;transition:.2s}
|
||||||
button:hover{background:#1a237e}
|
button:hover{background:#1a237e}
|
||||||
button.loading{opacity:.7;pointer-events:none}
|
button.loading{opacity:.7;pointer-events:none}
|
||||||
button.loading::after{content:" ⏳";animation:spin 1s linear infinite}
|
button.loading::after{content:" ⏳";animation:spin 1s linear infinite}
|
||||||
#pm2Buttons{display:grid;grid-template-columns:repeat(auto-fit,minmax(100px,1fr));gap:8px;margin:12px 0}
|
#pm2Buttons{display:grid;grid-template-columns:repeat(auto-fit,minmax(100px,1fr));gap:8px;margin:12px 0}
|
||||||
#videoPlayer{position:relative;width:100%;padding-bottom:56.25%;background:#000;border-radius:8px;overflow:hidden;margin:16px 0;display:none}
|
|
||||||
video{position:absolute;inset:0;width:100%;height:100%}
|
|
||||||
#loading{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:#fff;font-size:1.8em;pointer-events:none;background:rgba(0,0,0,.5);opacity:0;transition:opacity .3s;border-radius:8px}
|
|
||||||
#pm2Log{background:#000;color:#0f0;padding:12px;height:320px;overflow-y:auto;font-family:monospace;font-size:.9em;border-radius:8px;white-space:pre-wrap;word-break:break-all}
|
#pm2Log{background:#000;color:#0f0;padding:12px;height:320px;overflow-y:auto;font-family:monospace;font-size:.9em;border-radius:8px;white-space:pre-wrap;word-break:break-all}
|
||||||
|
#configButtons{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:8px;margin:12px 0}
|
||||||
|
#configStatus{color:#0f0;margin-top:8px;font-family:monospace}
|
||||||
|
#obsAddress{background:#f0f0f0;padding:12px;border-radius:8px;font-family:monospace;font-size:1.1em;margin:8px 0;word-break:break-all}
|
||||||
@keyframes spin{to{transform:rotate(360deg)}}
|
@keyframes spin{to{transform:rotate(360deg)}}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
@@ -39,13 +38,26 @@ video{position:absolute;inset:0;width:100%;height:100%}
|
|||||||
</select>
|
</select>
|
||||||
<span id="statusIndicator">⚪ 加载中...</span>
|
<span id="statusIndicator">⚪ 加载中...</span>
|
||||||
</h1>
|
</h1>
|
||||||
<h2>视频播放器(M3U8 / FLV)</h2>
|
|
||||||
<input type="text" id="videoUrlInput" placeholder="请输入视频链接">
|
<!-- YouTube 配置编辑区(仅 youtube 时显示) -->
|
||||||
<button id="playButton">播放</button>
|
<div id="configSection" style="display:none;">
|
||||||
<div id="videoPlayer">
|
<h2>YouTube 配置编辑 (config/youtube.ini)</h2>
|
||||||
<video controls autoplay playsinline></video>
|
<textarea id="youtubeConfig" rows="20"></textarea>
|
||||||
<div id="loading">加载中...</div>
|
<div id="configButtons">
|
||||||
|
<button id="loadConfigBtn">加载配置</button>
|
||||||
|
<button id="saveConfigBtn">保存配置</button>
|
||||||
|
</div>
|
||||||
|
<pre id="configStatus">就绪</pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- OBS 推流地址区(仅 obs 时显示) -->
|
||||||
|
<div id="obsSection" style="display:none;">
|
||||||
|
<h2>OBS 直播推流地址</h2>
|
||||||
|
<p>请在 OBS → 设置 → 推流 → 服务选择“自定义”,服务器填写以下地址:</p>
|
||||||
|
<div id="obsAddress">srt://live.local:10080/live/obs</div>
|
||||||
|
<p>密钥(Stream Key)留空即可。</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h2>PM2 进程控制</h2>
|
<h2>PM2 进程控制</h2>
|
||||||
<div id="pm2Buttons">
|
<div id="pm2Buttons">
|
||||||
<button data-action="start">启动</button>
|
<button data-action="start">启动</button>
|
||||||
@@ -57,70 +69,48 @@ video{position:absolute;inset:0;width:100%;height:100%}
|
|||||||
<pre id="pm2Log">正在加载状态与日志...</pre>
|
<pre id="pm2Log">正在加载状态与日志...</pre>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
const video = document.querySelector('video');
|
|
||||||
const player = document.getElementById('videoPlayer');
|
|
||||||
const loading = document.getElementById('loading');
|
|
||||||
const logEl = document.getElementById('pm2Log');
|
const logEl = document.getElementById('pm2Log');
|
||||||
const statusIndicator = document.getElementById('statusIndicator');
|
const statusIndicator = document.getElementById('statusIndicator');
|
||||||
const processSelect = document.getElementById('processSelect');
|
const processSelect = document.getElementById('processSelect');
|
||||||
|
let currentProcess = 'tiktok';
|
||||||
|
|
||||||
let currentProcess = 'tiktok'; // 默认进程
|
// 各专属区域
|
||||||
|
const configSection = document.getElementById('configSection');
|
||||||
|
const obsSection = document.getElementById('obsSection');
|
||||||
|
const youtubeConfig = document.getElementById('youtubeConfig');
|
||||||
|
const loadConfigBtn = document.getElementById('loadConfigBtn');
|
||||||
|
const saveConfigBtn = document.getElementById('saveConfigBtn');
|
||||||
|
const configStatus = document.getElementById('configStatus');
|
||||||
|
|
||||||
// 当选择进程改变时,更新当前进程并刷新状态
|
|
||||||
processSelect.addEventListener('change', () => {
|
processSelect.addEventListener('change', () => {
|
||||||
currentProcess = processSelect.value;
|
currentProcess = processSelect.value;
|
||||||
fetchStatus();
|
fetchStatus();
|
||||||
|
updateSectionsVisibility();
|
||||||
});
|
});
|
||||||
|
|
||||||
function httpToHttps(url){return url.replace(/^http:/,"https:")}
|
// 根据当前进程显示/隐藏对应区域
|
||||||
|
function updateSectionsVisibility() {
|
||||||
|
configSection.style.display = currentProcess === 'youtube' ? 'block' : 'none';
|
||||||
|
obsSection.style.display = currentProcess === 'obs' ? 'block' : 'none';
|
||||||
|
|
||||||
|
if (currentProcess === 'youtube') {
|
||||||
|
loadConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function scrollLog(){logEl.scrollTop = logEl.scrollHeight}
|
function scrollLog(){logEl.scrollTop = logEl.scrollHeight}
|
||||||
|
|
||||||
function updateStatus(process_status){
|
function updateStatus(process_status){
|
||||||
let emoji = '⚪', text = '未知';
|
let emoji = '⚪', text = '未知';
|
||||||
if(process_status === 'online'){ emoji = ' '; text = '运行中'; }
|
if(process_status === 'online'){ emoji = '🟢'; text = '运行中'; }
|
||||||
else if(process_status === 'stopped'){ emoji = ' '; text = '已停止'; }
|
else if(process_status === 'stopped'){ emoji = '🔴'; text = '已停止'; }
|
||||||
else if(process_status === 'errored'){ emoji = ' '; text = '错误'; }
|
else if(process_status === 'errored'){ emoji = '🔴'; text = '错误'; }
|
||||||
else if(process_status === 'launching' || process_status === 'starting'){ emoji = ' '; text = '启动中'; }
|
else if(process_status === 'launching' || process_status === 'starting'){ emoji = '🟡'; text = '启动中'; }
|
||||||
else if(process_status === 'waiting restart'){ emoji = ' '; text = '等待重启'; }
|
else if(process_status === 'waiting restart'){ emoji = '🟡'; text = '等待重启'; }
|
||||||
else if(process_status === 'not_found'){ emoji = '❌'; text = '未注册到 PM2'; }
|
else if(process_status === 'not_found'){ emoji = '❌'; text = '未注册到 PM2'; }
|
||||||
statusIndicator.innerHTML = `${emoji} ${text}`;
|
statusIndicator.innerHTML = `${emoji} ${text}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function playVideo(){
|
|
||||||
let url = document.getElementById('videoUrlInput').value.trim();
|
|
||||||
if(!url) return alert('请输入视频链接');
|
|
||||||
url = httpToHttps(url);
|
|
||||||
player.style.display = 'block';
|
|
||||||
loading.style.opacity = 1;
|
|
||||||
video.pause(); video.src = '';
|
|
||||||
if(url.endsWith('.m3u8')){
|
|
||||||
if(Hls.isSupported()){
|
|
||||||
const hls = new Hls({enableWorker:true});
|
|
||||||
hls.loadSource(url);
|
|
||||||
hls.attachMedia(video);
|
|
||||||
hls.on(Hls.Events.MANIFEST_PARSED, ()=>loading.style.opacity=0);
|
|
||||||
hls.on(Hls.Events.ERROR, (e,data)=>{alert('HLS 错误: '+data.type); loading.style.opacity=0});
|
|
||||||
}else if(video.canPlayType('application/vnd.apple.mpegurl')){
|
|
||||||
video.src = url; video.play(); loading.style.opacity=0;
|
|
||||||
}else{alert('浏览器不支持 M3U8'); return}
|
|
||||||
}else if(url.endsWith('.flv')){
|
|
||||||
if(typeof flvjs !== 'undefined' && flvjs.isSupported()){
|
|
||||||
const flvPlayer = flvjs.createPlayer({type:'flv',url,isLive:true});
|
|
||||||
flvPlayer.attachMediaElement(video);
|
|
||||||
flvPlayer.load(); flvPlayer.play();
|
|
||||||
flvPlayer.on(flvjs.Events.LOADING_COMPLETE, ()=>loading.style.opacity=0);
|
|
||||||
}else{
|
|
||||||
const script = document.createElement('script');
|
|
||||||
script.src = 'https://cdn.jsdelivr.net/npm/flv.js@1.6.2/dist/flv.min.js';
|
|
||||||
script.onload = playVideo;
|
|
||||||
document.head.appendChild(script);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}else{alert('仅支持 .m3u8 或 .flv 格式'); return}
|
|
||||||
}
|
|
||||||
document.getElementById('playButton').onclick = playVideo;
|
|
||||||
|
|
||||||
// PM2 控制 + 实时状态/日志(所有请求都带 ?process=xxx)
|
|
||||||
async function pm2Action(action){
|
async function pm2Action(action){
|
||||||
const btn = document.querySelector(`[data-action="${action}"]`);
|
const btn = document.querySelector(`[data-action="${action}"]`);
|
||||||
if(btn) btn.classList.add('loading');
|
if(btn) btn.classList.add('loading');
|
||||||
@@ -166,6 +156,44 @@ async function pm2Action(action){
|
|||||||
|
|
||||||
async function fetchStatus(){ pm2Action('status'); }
|
async function fetchStatus(){ pm2Action('status'); }
|
||||||
|
|
||||||
|
// 配置加载
|
||||||
|
async function loadConfig(){
|
||||||
|
configStatus.textContent = '加载中...';
|
||||||
|
try{
|
||||||
|
const res = await fetch(`/get_config?process=${currentProcess}`);
|
||||||
|
if(!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||||
|
const data = await res.json();
|
||||||
|
youtubeConfig.value = data.content || '';
|
||||||
|
configStatus.textContent = '加载成功';
|
||||||
|
}catch(err){
|
||||||
|
configStatus.textContent = `加载失败: ${err.message}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 配置保存
|
||||||
|
async function saveConfig(){
|
||||||
|
configStatus.textContent = '保存中...';
|
||||||
|
const content = youtubeConfig.value;
|
||||||
|
try{
|
||||||
|
const res = await fetch(`/save_config?process=${currentProcess}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({content})
|
||||||
|
});
|
||||||
|
if(!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||||
|
const data = await res.json();
|
||||||
|
configStatus.textContent = data.message || '保存成功';
|
||||||
|
}catch(err){
|
||||||
|
configStatus.textContent = `保存失败: ${err.message}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadConfigBtn.onclick = loadConfig;
|
||||||
|
saveConfigBtn.onclick = () => {
|
||||||
|
saveConfigBtn.classList.add('loading');
|
||||||
|
saveConfig().finally(() => saveConfigBtn.classList.remove('loading'));
|
||||||
|
};
|
||||||
|
|
||||||
// 按钮事件委托
|
// 按钮事件委托
|
||||||
document.getElementById('pm2Buttons').addEventListener('click', e=>{
|
document.getElementById('pm2Buttons').addEventListener('click', e=>{
|
||||||
const btn = e.target.closest('button');
|
const btn = e.target.closest('button');
|
||||||
@@ -174,9 +202,12 @@ document.getElementById('pm2Buttons').addEventListener('click', e=>{
|
|||||||
if(action && action !== 'status') pm2Action(action);
|
if(action && action !== 'status') pm2Action(action);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 自动刷新(每 1 秒)
|
// 自动刷新状态与日志
|
||||||
setInterval(fetchStatus, 1000);
|
setInterval(fetchStatus, 1000);
|
||||||
|
|
||||||
|
// 初始加载
|
||||||
fetchStatus();
|
fetchStatus();
|
||||||
|
updateSectionsVisibility();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
17
mpv.sh
Normal file
17
mpv.sh
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
mpv \
|
||||||
|
--fs \
|
||||||
|
--no-border \
|
||||||
|
--vo=drm \
|
||||||
|
--drm-device=/dev/dri/card0 \
|
||||||
|
--hwdec=auto \
|
||||||
|
--video-sync=display-resample \
|
||||||
|
--untimed \
|
||||||
|
--cache=no \
|
||||||
|
--demuxer-lavf-o=ffflags=nobuffer \
|
||||||
|
--demuxer-lavf-probesize=32 \
|
||||||
|
--demuxer-lavf-analyzeduration=0 \
|
||||||
|
--vf=setpts=PTS-STARTPTS \
|
||||||
|
--no-audio \
|
||||||
|
--loop=inf \
|
||||||
|
'srt://127.0.0.1:10080?streamid=#!::r=live/livestream,m=request&latency=10'
|
||||||
3
obs.sh
3
obs.sh
@@ -1,4 +1,5 @@
|
|||||||
|
export DISPLAY=:0
|
||||||
ffplay -fflags nobuffer -flags low_delay -framedrop \
|
ffplay -fflags nobuffer -flags low_delay -framedrop \
|
||||||
-probesize 32 -analyzeduration 0 -sync ext -infbuf \
|
-probesize 32 -analyzeduration 0 -sync ext -infbuf \
|
||||||
-vf "setpts=PTS-STARTPTS" \
|
-vf "setpts=PTS-STARTPTS" \
|
||||||
'srt://127.0.0.1:10080?streamid=#!::r=live/livestream,m=request&latency=10'
|
'srt://127.0.0.1:10080?streamid=#!::r=live/livestream,m=request&latency=10'
|
||||||
|
|||||||
23
obs1.sh
Normal file
23
obs1.sh
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 先确保有推流!(pm2 start tiktok/youtube 等,让 ffmpeg 推到 SRS 的 live/livestream)
|
||||||
|
|
||||||
|
export SDL_VIDEODRIVER=kmsdrm # 尝试 SDL 的 DRM 驱动(直接 HDMI 输出,无需 DISPLAY)
|
||||||
|
|
||||||
|
mpv \
|
||||||
|
--fs \
|
||||||
|
--no-border \
|
||||||
|
--vo=drm \
|
||||||
|
--drm-device=/dev/dri/card0 \ # 如果 card0 不对,改 card1(ls /dev/dri/ 查看)
|
||||||
|
--hwdec=auto \
|
||||||
|
--video-sync=display-resample \
|
||||||
|
--untimed \
|
||||||
|
--cache=no \
|
||||||
|
--demuxer-lavf-o=ffflags=nobuffer \
|
||||||
|
--demuxer-lavf-probesize=32 \
|
||||||
|
--demuxer-lavf-analyzeduration=0 \
|
||||||
|
--vf=setpts=PTS-STARTPTS \
|
||||||
|
--no-audio \
|
||||||
|
--loop=inf \
|
||||||
|
http://127.0.0.1:8081/live/livestream.flv # 先用 HTTP-FLV 测试(稳定,不依赖 SRT)
|
||||||
|
# 如果 HTTP-FLV 正常,再切回 SRT:
|
||||||
|
# 'srt://127.0.0.1:10080?streamid=#!::r=live/livestream,m=request&latency=10'
|
||||||
52
web.py
52
web.py
@@ -1,4 +1,4 @@
|
|||||||
from fastapi import FastAPI, Query
|
from fastapi import FastAPI, Query, Request
|
||||||
from fastapi.responses import FileResponse, JSONResponse
|
from fastapi.responses import FileResponse, JSONResponse
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import asyncio
|
import asyncio
|
||||||
@@ -7,19 +7,21 @@ from fastapi.middleware.cors import CORSMiddleware
|
|||||||
|
|
||||||
app = FastAPI(title="多进程录制控制台")
|
app = FastAPI(title="多进程录制控制台")
|
||||||
|
|
||||||
# ---------------- 配置 ----------------
|
# ---------------- 配置(修正为根目录结构) ----------------
|
||||||
BASE_DIR = Path(__file__).parent
|
BASE_DIR = Path(__file__).parent
|
||||||
DEFAULT_LOG_DIR = BASE_DIR.parent / ".pm2" / "logs"
|
CONFIG_DIR = BASE_DIR / "config" # 直接在根目录下
|
||||||
|
DEFAULT_LOG_DIR = BASE_DIR / ".pm2" / "logs" # 直接在根目录下
|
||||||
|
|
||||||
MAX_LOG_LINES = 300
|
MAX_LOG_LINES = 300
|
||||||
|
|
||||||
# 支持的进程列表(可扩展)
|
# 支持的进程列表
|
||||||
VALID_PROCESSES = {"tiktok", "youtube", "obs"}
|
VALID_PROCESSES = {"tiktok", "youtube", "obs"}
|
||||||
|
|
||||||
# CORS
|
# CORS(支持 POST)
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
allow_origins=["*"],
|
allow_origins=["*"],
|
||||||
allow_methods=["GET"],
|
allow_methods=["GET", "POST"],
|
||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -84,7 +86,7 @@ async def get_process_info(process: str) -> dict:
|
|||||||
out_path = value
|
out_path = value
|
||||||
elif key == "err_log_path":
|
elif key == "err_log_path":
|
||||||
err_path = value
|
err_path = value
|
||||||
# 备用默认路径(仅当 describe 未提供时)
|
# 备用默认路径(修正为根目录)
|
||||||
if not out_path:
|
if not out_path:
|
||||||
out_path = str(DEFAULT_LOG_DIR / f"{process}-out.log")
|
out_path = str(DEFAULT_LOG_DIR / f"{process}-out.log")
|
||||||
if not err_path:
|
if not err_path:
|
||||||
@@ -95,7 +97,41 @@ async def get_process_info(process: str) -> dict:
|
|||||||
"err_path": err_path,
|
"err_path": err_path,
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---------------- 路由 ----------------
|
# ---------------- 配置路由 ----------------
|
||||||
|
@app.get("/get_config")
|
||||||
|
async def get_config(process: str = Query(..., description="进程名")):
|
||||||
|
if process != "youtube":
|
||||||
|
return JSONResponse({"error": "仅 youtube 支持配置编辑"}, status_code=400)
|
||||||
|
|
||||||
|
config_file = CONFIG_DIR / "youtube.ini"
|
||||||
|
if not config_file.exists():
|
||||||
|
return {"content": "# youtube.ini 文件不存在,将创建空文件\n"}
|
||||||
|
try:
|
||||||
|
content = config_file.read_text(encoding="utf-8")
|
||||||
|
return {"content": content}
|
||||||
|
except Exception as e:
|
||||||
|
return JSONResponse({"error": f"读取失败: {str(e)}"}, status_code=500)
|
||||||
|
|
||||||
|
@app.post("/save_config")
|
||||||
|
async def save_config(request: Request, process: str = Query(..., description="进程名")):
|
||||||
|
if process != "youtube":
|
||||||
|
return JSONResponse({"error": "仅 youtube 支持配置编辑"}, status_code=400)
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = await request.json()
|
||||||
|
content = data.get("content", "")
|
||||||
|
except:
|
||||||
|
return JSONResponse({"error": "无效的 JSON 数据"}, status_code=400)
|
||||||
|
|
||||||
|
config_file = CONFIG_DIR / "youtube.ini"
|
||||||
|
try:
|
||||||
|
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
config_file.write_text(content, encoding="utf-8")
|
||||||
|
return {"message": "配置保存成功"}
|
||||||
|
except Exception as e:
|
||||||
|
return JSONResponse({"error": f"保存失败: {str(e)}"}, status_code=500)
|
||||||
|
|
||||||
|
# ---------------- 原有路由 ----------------
|
||||||
@app.get("/start")
|
@app.get("/start")
|
||||||
async def start(process: str = Query(..., description="进程名")):
|
async def start(process: str = Query(..., description="进程名")):
|
||||||
if process not in VALID_PROCESSES:
|
if process not in VALID_PROCESSES:
|
||||||
|
|||||||
Reference in New Issue
Block a user