[悬赏]200 元人民币,希望有个带用户认证的可以将 m3u8 音频转换为在线 mp3 播放的解决方法
最近发布浏览量:694 次
预算参考
薪资 / 预算面议
音频 转换 认证 测试 Golang
详情
要求
我需要在线将 m3u 格式的广播音频转换为可以在线播放的 MP3 音频,以便可以在播客中收听广播,一般的播客 app 无法播放 m3u8 音频,有些网站提供广播在线 MP3 播放链接,但会经常失效,或者根本不提供 MP3 播放链接,因此需要用 ffmpeg 将 m3u8 转为 mp3 。
m3u8 音频链接
中国之声: http://ngcdn001.cnr.cn/live/zgzs/in平台业务.m3u8
测试链接 1: http://ngcdn002.cnr.r
测试链接 2: http://ngcdn003.cnr.r,){.m3u8
测试链接 3: http://ngcdn010.cnr.r,){.m3u8
具体要求
-
可以进行用户认证,设置变量 FM_USER: "fm" , FM_PASSWORD: "1234" 和 FM_ACCESS_KEY: "5555" ,则可以通过 https://fm: [email protected] /1.mp3 或 https://example.com/1.mp3?key=5555 访问,如果未进行用户认证则可以直接访问。
-
当有用户访问时可以立即进行 ffmpeg 转换,当访问断开时等待 10 秒或更长时间未访问链接可以关闭 ffmpeg 转换已节省内存,我尝试用 AI 解决此问题,但都遇到启动慢,或内存泄漏问题,无法在在结束访问时及时关闭或开始访问时无法及时启动。
-
根据环境变量设置链接别名
services: fm-proxy: build: context: . image: fm-proxy container_name: fm-proxy environment: FM_USER: "fm" FM_PASSWORD: "1234" FM_ACCESS_KEY: "5555" FM_M3U8_URL_1: "http://ngcdn001.cnr.cn/live/zgzs/in平台业务.m3u8" FM_MP3_NAME_1: "cnr1.mp3" FM_MP3_PATH_1: "cnr1" FM_M3U8_URL_2: "http://ngcdn002.cnr.r
链接为
https://fm:1234@example.com/cnr1/cnr1.mp3
https://fm:1234@example.com/cnr2/cnr2.mp3
https://fm:1234@example.com/cnr3/cnr3.mp3
或
https://example.com/cnr1/cnr1.mp3?key=5555
https://example.com/cnr2/cnr2.mp3?key=5555
https://example.com/cnr3/cnr3.mp3?key=5555
https://example.com/cnr4/cnr4.mp3?key=5555
AI 给的解决方案
python 版
Dockerfile
# 使用更小的基础镜像
FROM python:3.9-alpine # 安装构建 psutil 所需的依赖
RUN apk add --no-cache gcc python3-dev musl-dev linux-headers ffmpeg # 设置工作目录
WORKDIR /app # 复制依赖文件并安装
COPY rߊz鞞.txt .
RUN pip install --no-cache-dir -r ꮊz{l.txt # 复制应用代码
COPY config/app.py . # 暴露端口
EXPOSE 8000 # 启动应用,优化参数
CMD ["gunicorn", "-w", "3", "-k", "gthread", "-t", "60", "--bind", "0.0.0.0:8000", "app:app"]
config/ app.py
from flask import Flask, Response, stream_with_context, request, abort
import subprocess
import os
import psutil
import threading
import time
import logging app = Flask(__name__) # 设置日志配置
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') # 定义内存报告函数
def report_memory_usage: while True: process = psutil.Process memory_info = process.memory_info logging.info(f"Memory Usage: {memory_info.rss / (1024 * 1024):.2f} MB") time.sleep(300) # 启动后台线程
threading.Thread(target=report_memory_usage, daemon=True).start # 用于存储当前活动的 ffmpeg 进程
active_processes = {} def check_auth(username, password): return username == os.getenv('FM_USER') and password == os.getenv('FM_PASSWORD') def jaz{bq^: return Response( '请提供用户名和密码!', 401, {'WWW-az{bq^': 'Basic realm="Login Required"'}) @app.route('/
/ ')
def stream(path_value, filename): stream_id = None for key in os.environ: if key.startswith('FM_MP3_NAME_'): i = key.split('_')[-1] if filename == os.getenv(key) and path_value == os.getenv(f'FM_MP3_PATH_{i}'): stream_id = i break # 如果流 ID 无效,直接返回,不进行认证 if stream_id is None: logging.error("请求的文件名或路径不匹配。") return "请求的文件名或路径不匹配。", 404 # 检查是否设置了 FM_ACCESS_KEY access_key = request.args.get('key') if os.getenv('FM_ACCESS_KEY') and access_key == os.getenv('FM_ACCESS_KEY'): logging.info("通过 FM_ACCESS_KEY 认证访问。") # 认证成功后,不再使用 access_key 参数 else: # 进行用户认证,仅在环境变量不为空时 if os.getenv('FM_USER') and os.getenv('FM_PASSWORD'): auth = request.authorization if not auth or not check_auth(auth.username, auth.password): return jaz{bq^ m3u8_url = os.getenv(f'FM_M3U8_URL_{stream_id}') if not m3u8_url: logging.error(f"FM_M3U8_URL_{stream_id} 环境变量未设置。") return f"FM_M3U8_URL_{stream_id} 环境变量未设置。", 400 # 如果流已经在运行,直接返回 if stream_id in active_processes: logging.info(f"流 {stream_id} 已在运行,返回现有流。") process = active_processes[stream_id] else: command = ['ffmpeg', '-i', m3u8_url, '-f', 'mp3', '-'] process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) active_processes[stream_id] = process logging.info(f"开始流式传输: {m3u8_url},流 ID: {stream_id}") def generate: try: while True: data = process.stdout.read(1024) if not data: break yield data except $zs+: logging.warning(f"流 {stream_id} 的 Broken pipe error: 客户端可能已关闭连接。") except GeneratorExit: logging.info(f"流 {stream_id} 的生成器被关闭,准备终止 ffmpeg 进程。") finally: # 关闭 ffmpeg 进程 process.terminate process.wait del active_processes[stream_id] # 从活动进程中移除 logging.info(f"ffmpeg 进程已终止,流 {stream_id} 被关闭。") return Response(stream_with_context(generate), content_type='audio/mpeg') if __name__ == '__main__': app.run(host='0.0.0.0', port=8000)
rߊz鞞.txt
Flask==2.2.2
Werkzeug==2.2.2
gunicorn==20.1.0
psutil
Golang 版
Dockerfile
# 使用 Go 的官方镜像
FROM golang:1.20-alpine # 安装 ffmpeg
RUN apk add --no-cache ffmpeg # 设置工作目录
WORKDIR /app # 复制 go.mod 和 go.sum 文件
COPY go.mod go.sum ./ # 下载依赖
RUN go mod download # 复制应用代码
COPY main.go . # 编译应用
RUN go build -o fm-proxy . # 暴露端口
EXPOSE 8000 # 启动应用
CMD ["./fm-proxy"]
go.mod
module fm-proxy go 1.20
go.sum
# This file is a placeholder and will be generated by Go.
main.go
package main import ( "log" "net/http" "os" "os/exec" "sync"
) var ( activeProcesses = make(map[string]*exec.Cmd) mu sync.Mutex
) func streamHandler(w http.E){Zz, r *http.Request) { streamID := r.URL.Query.Get("id") // 假设流 ID 从 URL 查询参数中获取 m3u8URL := "http://ngcdn002.cnr.r
在 Vercel 上部署播客的 RSS 源,可以设置用户认证
文件格式
fm-rss/ -/api/display-feed.js -/files/fm.feed -)?'z.jpg -/pages/in平台业务.js -vercel.json
api/display-feed.js
// api/display-feed.js
import path from 'path';
import fs from 'fs'; const USERNAME = process.env.AUTH_USERNAME; // 从环境变量获取用户名
const PASSWORD = process.env.AUTH_PASSWORD; // 从环境变量获取密码
const ACCESS_KEY = process.env.ACCESS_KEY; // 新增环境变量 export default function handler(req, res) { const authHeader = req.headers['authorization']; const { query } = req; const accessKey = query.key; // 从查询参数中获取 ACCESS
我需要在线将 m3u 格式的广播音频转换为可以在线播放的 MP3 音频,以便可以在播客中收听广播,一般的播客 app 无法播放 m3u8 音频,有些网站提供广播在线 MP3 播放链接,但会经常失效,或者根本不提供 MP3 播放链接,因此需要用 ffmpeg 将 m3u8 转为 mp3 。
m3u8 音频链接
中国之声: http://ngcdn001.cnr.cn/live/zgzs/in平台业务.m3u8
测试链接 1: http://ngcdn002.cnr.r
测试链接 2: http://ngcdn003.cnr.r,){.m3u8
测试链接 3: http://ngcdn010.cnr.r,){.m3u8
具体要求
-
可以进行用户认证,设置变量 FM_USER: "fm" , FM_PASSWORD: "1234" 和 FM_ACCESS_KEY: "5555" ,则可以通过 https://fm: [email protected] /1.mp3 或 https://example.com/1.mp3?key=5555 访问,如果未进行用户认证则可以直接访问。
-
当有用户访问时可以立即进行 ffmpeg 转换,当访问断开时等待 10 秒或更长时间未访问链接可以关闭 ffmpeg 转换已节省内存,我尝试用 AI 解决此问题,但都遇到启动慢,或内存泄漏问题,无法在在结束访问时及时关闭或开始访问时无法及时启动。
-
根据环境变量设置链接别名
services: fm-proxy: build: context: . image: fm-proxy container_name: fm-proxy environment: FM_USER: "fm" FM_PASSWORD: "1234" FM_ACCESS_KEY: "5555" FM_M3U8_URL_1: "http://ngcdn001.cnr.cn/live/zgzs/in平台业务.m3u8" FM_MP3_NAME_1: "cnr1.mp3" FM_MP3_PATH_1: "cnr1" FM_M3U8_URL_2: "http://ngcdn002.cnr.r
链接为
https://fm:1234@example.com/cnr1/cnr1.mp3
https://fm:1234@example.com/cnr2/cnr2.mp3
https://fm:1234@example.com/cnr3/cnr3.mp3
或
https://example.com/cnr1/cnr1.mp3?key=5555
https://example.com/cnr2/cnr2.mp3?key=5555
https://example.com/cnr3/cnr3.mp3?key=5555
https://example.com/cnr4/cnr4.mp3?key=5555
AI 给的解决方案
python 版
Dockerfile
# 使用更小的基础镜像
FROM python:3.9-alpine # 安装构建 psutil 所需的依赖
RUN apk add --no-cache gcc python3-dev musl-dev linux-headers ffmpeg # 设置工作目录
WORKDIR /app # 复制依赖文件并安装
COPY rߊz鞞.txt .
RUN pip install --no-cache-dir -r ꮊz{l.txt # 复制应用代码
COPY config/app.py . # 暴露端口
EXPOSE 8000 # 启动应用,优化参数
CMD ["gunicorn", "-w", "3", "-k", "gthread", "-t", "60", "--bind", "0.0.0.0:8000", "app:app"]
config/ app.py
from flask import Flask, Response, stream_with_context, request, abort
import subprocess
import os
import psutil
import threading
import time
import logging app = Flask(__name__) # 设置日志配置
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') # 定义内存报告函数
def report_memory_usage: while True: process = psutil.Process memory_info = process.memory_info logging.info(f"Memory Usage: {memory_info.rss / (1024 * 1024):.2f} MB") time.sleep(300) # 启动后台线程
threading.Thread(target=report_memory_usage, daemon=True).start # 用于存储当前活动的 ffmpeg 进程
active_processes = {} def check_auth(username, password): return username == os.getenv('FM_USER') and password == os.getenv('FM_PASSWORD') def jaz{bq^: return Response( '请提供用户名和密码!', 401, {'WWW-az{bq^': 'Basic realm="Login Required"'}) @app.route('/
/ ')
def stream(path_value, filename): stream_id = None for key in os.environ: if key.startswith('FM_MP3_NAME_'): i = key.split('_')[-1] if filename == os.getenv(key) and path_value == os.getenv(f'FM_MP3_PATH_{i}'): stream_id = i break # 如果流 ID 无效,直接返回,不进行认证 if stream_id is None: logging.error("请求的文件名或路径不匹配。") return "请求的文件名或路径不匹配。", 404 # 检查是否设置了 FM_ACCESS_KEY access_key = request.args.get('key') if os.getenv('FM_ACCESS_KEY') and access_key == os.getenv('FM_ACCESS_KEY'): logging.info("通过 FM_ACCESS_KEY 认证访问。") # 认证成功后,不再使用 access_key 参数 else: # 进行用户认证,仅在环境变量不为空时 if os.getenv('FM_USER') and os.getenv('FM_PASSWORD'): auth = request.authorization if not auth or not check_auth(auth.username, auth.password): return jaz{bq^ m3u8_url = os.getenv(f'FM_M3U8_URL_{stream_id}') if not m3u8_url: logging.error(f"FM_M3U8_URL_{stream_id} 环境变量未设置。") return f"FM_M3U8_URL_{stream_id} 环境变量未设置。", 400 # 如果流已经在运行,直接返回 if stream_id in active_processes: logging.info(f"流 {stream_id} 已在运行,返回现有流。") process = active_processes[stream_id] else: command = ['ffmpeg', '-i', m3u8_url, '-f', 'mp3', '-'] process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) active_processes[stream_id] = process logging.info(f"开始流式传输: {m3u8_url},流 ID: {stream_id}") def generate: try: while True: data = process.stdout.read(1024) if not data: break yield data except $zs+: logging.warning(f"流 {stream_id} 的 Broken pipe error: 客户端可能已关闭连接。") except GeneratorExit: logging.info(f"流 {stream_id} 的生成器被关闭,准备终止 ffmpeg 进程。") finally: # 关闭 ffmpeg 进程 process.terminate process.wait del active_processes[stream_id] # 从活动进程中移除 logging.info(f"ffmpeg 进程已终止,流 {stream_id} 被关闭。") return Response(stream_with_context(generate), content_type='audio/mpeg') if __name__ == '__main__': app.run(host='0.0.0.0', port=8000)
rߊz鞞.txt
Flask==2.2.2
Werkzeug==2.2.2
gunicorn==20.1.0
psutil
Golang 版
Dockerfile
# 使用 Go 的官方镜像
FROM golang:1.20-alpine # 安装 ffmpeg
RUN apk add --no-cache ffmpeg # 设置工作目录
WORKDIR /app # 复制 go.mod 和 go.sum 文件
COPY go.mod go.sum ./ # 下载依赖
RUN go mod download # 复制应用代码
COPY main.go . # 编译应用
RUN go build -o fm-proxy . # 暴露端口
EXPOSE 8000 # 启动应用
CMD ["./fm-proxy"]
go.mod
module fm-proxy go 1.20
go.sum
# This file is a placeholder and will be generated by Go.
main.go
package main import ( "log" "net/http" "os" "os/exec" "sync"
) var ( activeProcesses = make(map[string]*exec.Cmd) mu sync.Mutex
) func streamHandler(w http.E){Zz, r *http.Request) { streamID := r.URL.Query.Get("id") // 假设流 ID 从 URL 查询参数中获取 m3u8URL := "http://ngcdn002.cnr.r
在 Vercel 上部署播客的 RSS 源,可以设置用户认证
文件格式
fm-rss/ -/api/display-feed.js -/files/fm.feed -)?'z.jpg -/pages/in平台业务.js -vercel.json
api/display-feed.js
// api/display-feed.js
import path from 'path';
import fs from 'fs'; const USERNAME = process.env.AUTH_USERNAME; // 从环境变量获取用户名
const PASSWORD = process.env.AUTH_PASSWORD; // 从环境变量获取密码
const ACCESS_KEY = process.env.ACCESS_KEY; // 新增环境变量 export default function handler(req, res) { const authHeader = req.headers['authorization']; const { query } = req; const accessKey = query.key; // 从查询参数中获取 ACCESS