Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
UPLOADS_DIR.mkdir(exist_ok=True)
THUMBNAILS_DIR = BASE / "thumbnails"
THUMBNAILS_DIR.mkdir(exist_ok=True)
SHARED_DIR = BASE / "shared"
SHARED_DIR.mkdir(exist_ok=True)
META_FILE = UPLOADS_DIR / "metadata.json"
_SHARE_TTL = 7 * 24 * 3600 # 7 days

DH_API = "http://154.17.17.154:18801"
OPENAI_TTS_URL = "https://api.openai.com/v1/audio/speech"
Expand Down Expand Up @@ -682,6 +685,40 @@ async def ai_talk(
)


def _cleanup_shared():
"""Lazy cleanup: remove shared files older than 7 days."""
now = time.time()
for f in SHARED_DIR.iterdir():
if f.is_file() and (now - f.stat().st_mtime) > _SHARE_TTL:
f.unlink(missing_ok=True)


@app.post("/api/share")
async def share_video(video: UploadFile = File(...)):
_cleanup_shared()
share_id = uuid.uuid4().hex[:10]
path = SHARED_DIR / f"{share_id}.mp4"
data = await video.read()
if len(data) < 500:
raise HTTPException(400, "视频文件无效")
if len(data) > 50 * 1024 * 1024:
raise HTTPException(400, "视频文件过大(最大50MB)")
path.write_bytes(data)
return {"share_id": share_id}


@app.get("/api/shared/{share_id}")
async def get_shared_video(share_id: str):
_cleanup_shared()
# Sanitize share_id to prevent path traversal
safe_id = "".join(c for c in share_id if c.isalnum())
path = SHARED_DIR / f"{safe_id}.mp4"
if not path.exists():
raise HTTPException(404, "分享链接已过期或不存在")
return FileResponse(path, media_type="video/mp4",
headers={"Cache-Control": "public, max-age=3600"})


@app.get("/api/services")
async def services_proxy():
try:
Expand Down
30 changes: 30 additions & 0 deletions static/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ let currentMode = "direct";
let currentEngine = "sadtalker";
let currentCategory = null;
let currentVideoUrl = null;
let currentVideoBlob = null;
let videoHistory = [];
const MAX_HISTORY = 5;
let voiceLabels = {};
Expand Down Expand Up @@ -271,6 +272,7 @@ async function generate() {
llmBox.classList.add("hidden");
timeBadge.classList.add("hidden");
$("#download-btn").classList.add("hidden");
$("#share-btn").classList.add("hidden");

// Reset steps
setStep(0);
Expand Down Expand Up @@ -368,6 +370,8 @@ async function generate() {
timeBadge.textContent = elapsed + "s";
timeBadge.classList.remove("hidden");
$("#download-btn").classList.remove("hidden");
$("#share-btn").classList.remove("hidden");
currentVideoBlob = blob;

addToHistory(url, avatarData[selectedAvatar]?.name || selectedAvatar, spokenText);

Expand Down Expand Up @@ -653,6 +657,32 @@ $("#download-btn").addEventListener("click", () => {
document.body.removeChild(a);
});

/* ── Share button ── */
$("#share-btn").addEventListener("click", async () => {
if (!currentVideoBlob) return;
const btn = $("#share-btn");
btn.disabled = true;
try {
const form = new FormData();
form.append("video", currentVideoBlob, "video.mp4");
const resp = await fetchWithRetry("/api/share", { method: "POST", body: form });
if (!resp.ok) throw new Error("分享失败");
const data = await resp.json();
const shareUrl = `${location.origin}/api/shared/${data.share_id}`;
try {
await navigator.clipboard.writeText(shareUrl);
toast("链接已复制");
} catch {
// Fallback: prompt
prompt("复制此链接分享:", shareUrl);
}
} catch (e) {
toast(e.message || "分享失败", 3000);
} finally {
btn.disabled = false;
}
});

/* ── Hamburger menu (mobile) ── */
(() => {
const hamburger = $("#hamburger");
Expand Down
7 changes: 5 additions & 2 deletions static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数字人直播</title>
<link rel="icon" type="image/png" href="/favicon.png">
<link rel="stylesheet" href="style.css?v=23">
<link rel="stylesheet" href="style.css?v=24">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;600&display=swap" rel="stylesheet">
</head>
Expand Down Expand Up @@ -128,6 +128,9 @@
<p id="loading-timer" class="loading-timer"></p>
</div>
<span id="time-badge" class="badge hidden"></span>
<button id="share-btn" class="share-btn hidden" title="分享视频">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/></svg>
</button>
<button id="download-btn" class="download-btn hidden" title="下载视频">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
</button>
Expand Down Expand Up @@ -171,6 +174,6 @@

<div id="toast" class="toast hidden"></div>

<script src="app.js?v=23"></script>
<script src="app.js?v=24"></script>
</body>
</html>
20 changes: 20 additions & 0 deletions static/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,26 @@ body {
}
.badge.hidden { display: none; }

/* ── Share button ── */
.share-btn {
position: absolute;
bottom: 10px; right: 54px;
width: 36px; height: 36px;
background: var(--accent2);
color: #fff;
border: none;
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center; justify-content: center;
box-shadow: var(--shadow2);
z-index: 5;
transition: all 0.2s ease;
}
.share-btn:hover { background: #4a6db5; transform: scale(1.08); }
.share-btn.hidden { display: none; }
.share-btn:disabled { opacity: 0.5; cursor: not-allowed; }

/* ── Download button ── */
.download-btn {
position: absolute;
Expand Down
Loading