# flask-karaoke **Repository Path**: brt2/flask-karaoke ## Basic Information - **Project Name**: flask-karaoke - **Description**: 基于本地mv / iframe 提供点歌服务器 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-07-25 - **Last Updated**: 2026-07-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 简易卡拉OK 服务器 一个用 Python3 + Flask 写的极简卡拉OK服务器。**两个页面**: | 路径 | 用途 | |---|---| | `/` | 点歌台:搜索本地视频、添加 Web 视频、维护队列、切换"当前播放" | | `/display` | 全屏播放页面:跟随点歌台设置的"当前播放"自动播放,一曲结束自动播队列里下一首 | 适合两台设备的典型用法:手机/笔记本开 `/` 点歌;电视/投影开 `/display` 全屏播。两端靠后端共享"当前播放"指针同步。 ## 功能 - 📁 扫描配置的目录里的视频文件(mp4 / mkv / avi / mov / webm / flv / wmv / m4v / ts / rmvb) - 🌐 接受 B站 / YouTube / Vimeo 等 Web 视频 URL(自动解析成嵌入 URL)或任意 .mp4/.m3u8/.webm 直链 - 🏷️ Web 视频加入队列后自动拉真实标题(B站用 `api.bilibili.com`、YouTube/Vimeo 用 oEmbed,其它站点抓 `og:title`/``),后台异步刷新,3 秒内队列列表显示真名字(失败时留占位标题) - 🔍 模糊搜索(优先用 `find -iname`,没结果再用 Python 子串匹配兜底) - ➕ 加入播放队列(自动去重,文件按路径、web 视频按 URL 去重) - ⇅ 队列里可以:置顶 / 上移 / 下移 / 置底 / 删除 / 清空 - 💾 队列导入 / 导出:M3U 列表(VLC / mpv / 其它播放器通用)和 JSON 备份(无损,包含所有元数据) - 🎯 管理页点队列项切换"当前播放" - ⏯ 管理页"▶/⏸"按钮:暂停 / 继续;队列为空时按一下会自动开播首项 - ⏭ 播放页:本地文件 / 直链走 `<video>` 自动播放、自动连播下一首;iframe-kind(B站/YT/Vimeo)走 `<iframe>` 嵌入,用户用各家自带的启停控件 - 🖥️ 播放页 HUD 上的"⏭ 下一首"按钮:iframe 没法自动连播,看完后手动点 - 🖥️ 播放页支持浏览器全屏 (F 按钮),HUD 长时间无操作自动淡出 - 📋 播放页右栏队列面板(点 HUD 的 📋 按钮或右边缘手柄展开):显示完整队列、当前播放带 ▶ 标记 队列存在内存里,进程重启就清空——本就是极简版,没上数据库。 ## 安装 需要 Python 3.8+。 ```bash pip3 install -r requirements.txt ``` ## 运行 ### 方式一:命令行参数 ```bash python3 app.py --dir /path/to/your/movies --port 5000 ``` ### 方式二:环境变量 ```bash export KARAOKE_MOVIE_DIR=/path/to/your/movies export PORT=5000 python3 app.py ``` 启动后浏览器访问: - 管理页 <http://localhost:5000/> - 播放页 <http://localhost:5000/display> 参数: | 参数 | 默认 | 说明 | |---|---|---| | `--dir` | `$KARAOKE_MOVIE_DIR` 或 `./movies` | 视频目录 | | `--host` | `0.0.0.0` | 监听地址 | | `--port` | `5000` | 端口 | | `--debug` | off | 开启 Flask debug 模式 | ## API(前端在用,你也可以直接调) | 方法 | 路径 | 说明 | |---|---|---| | GET | `/api/info` | 配置信息(目录、队列长度) | | GET | `/api/search?q=xxx` | 搜索,`q` 为空时列出前 500 个 | | GET | `/api/queue` | 取队列 | | POST | `/api/queue` | 加入本地文件,body: `{"id": "/abs/path"}` | | POST | `/api/queue/web` | 加入 Web 视频,body: `{"url": "https://...", "title"?: "自定义标题"}` | | GET | `/api/web/resolve?url=...` | 预览 URL 解析结果(不入队) | | POST | `/api/queue/move` | 调整顺序,body: `{"id":..., "action":"top|up|down|bottom|to", "position"?:int}` | | POST | `/api/queue/remove` | 移除单项,body: `{"id":...}` | | POST | `/api/queue/clear` | 清空队列 | | GET | `/api/queue/export?format=m3u\|json` | 导出队列;M3U 用相对路径 (在 MOVIE_DIR 内) 或绝对路径 | | POST | `/api/queue/import?mode=append\|replace` | 从请求体导入 M3U / JSON;自动判格式;按 id 去重;失败条目跳过不报错 | | GET | `/api/state` | 当前"应播放"状态:`{"current": "<id或null>", "paused": bool}` | | POST | `/api/state` | 改写状态,body 里 `current` 和 `paused` 都是可选:`{"current": "<id或null>?"}` / `{"paused": bool?}`,可同时传 | | GET | `/api/play?id=<绝对路径>` | 流式播放本地文件(支持 Range) | 队列项结构: ```json // 本地文件 { "id": "/abs/path.mp4", "type": "file", "title": "...", "path": "...", "rel_path": "..." } // Web 视频 (iframe 嵌入) { "id": "https://www.bilibili.com/video/BV1xxx", "type": "web", "kind": "iframe", "title": "...", "play_url": "https://player.bilibili.com/player.html?..." } // Web 直链 { "id": "https://x/y.mp4", "type": "web", "kind": "video", "title": "y.mp4", "play_url": "https://x/y.mp4" } ``` ## 支持的 Web 视频 URL - **B站** 普通视频 (`/video/BVxxx` 或 `/video/avxxx`)、番剧 (`/bangumi/play/ssxxx` 或 `epxxx`)、短链 `b23.tv` - **YouTube** `youtube.com/watch?v=...` 或 `youtu.be/...` - **Vimeo** `vimeo.com/<id>` - 任意 `.mp4` / `.m3u8` / `.webm` / `.mov` / `.ogg` / `.mpd` 直链 → 直接交给 `<video>` - 其它 URL → 回落到直接 iframe 原始页面(很多网站允许嵌入) ## 安全说明 - 路径校验:所有写入队列/播放的请求都校验 `id` 必须在配置的 `MOVIE_DIR` 之内,防止爬任意文件。 - 当前没有任何鉴权,**不要在公网直接暴露**。本机 / 内网用就好。 ## 目录结构 ``` . ├── app.py # Flask 后端 (扫描、搜索、队列、播放、state) ├── templates/ │ ├── index.html # 管理页 (/) │ └── display.html # 全屏播放页 (/display) ├── static/ │ ├── style.css # 管理页样式 │ ├── display.css # 播放页样式 │ ├── app.js # 管理页逻辑 │ └── display.js # 播放页逻辑 ├── requirements.txt └── README.md ```