Athena Note
  • 动态日历
    统计近10个月的博主文章和评论数目

    Loading...

    分类雷达图

    Loading...

    发布统计图

    Loading...

    分类统计图

    Loading...

    标签统计图

    Loading...

  • RSS
  • MAP
  • 时光机 新
    时光机
    🎨生活 雀魂单抽出了蛇贵人,换了五抽就用了两抽,也是欧了一把
    August 21st, 2025 at 09:24 pm
    🎨生活 干什么嘞。我这小破站还有人惦记呐
    August 18th, 2025 at 06:17 pm
    🎨生活 某模块又更新了,加密方式修改了一下又要重新分析分析,但人老了真不想动脑子
    May 27th, 2025 at 08:34 am
  • 文章
  • 时光机
Athena 松花酿酒,春水煎茶
  • 下午好,是时候打个盹了

  • 首页
  • 相册
  • 朋友
  • 归档
  • 图床
  • 分类
    • 9安卓
    • 6网站
    • 1相册
  • 页面
    • 归档
    • 朋友
    • 时光
    • 关于
  • 友链
    • 一世繁华
    • ShuiYue Blog

部署M3U8视频离线下载遇到的问题及解决

  • 博主: Athena
  • 发布时间:2025 年 02 月 15 日
  • 201 次浏览
  • 暂无评论
  • 12390字数
  • 分类: 网站
  1. 首页
  2. 正文  
  3. 分享到:

下载日志:
``` [/tab] [tab name="play.html"] ``` m3u8在线视频播放器
Video Player
  • 视频下载
  • 视频播放(current)
  • 关于项目

m3u8在线播放器

``` [/tab] [/tabs] [album] ![原视频下载界面][2] ![原视频播放界面][3] ![修改后视频下载][4] ![修改后视频播放][5] [/album] ## 上传问题及aria2下载问题 [tabs] [tab name="Alist上传" active="true"] 使用原本上传代码,测试一直无法成功上传到alist上,不过脚本还可以完善,比如增加上传进度检测等,但实在是懒得整了,主打一个功能正常,能用就行。 ```python import requests from urllib.parse import quote import os import json # 读取配置文件 import configparser config = configparser.ConfigParser() config.read("./config/config.ini", encoding="utf-8") base_url = config.get("upload_settings", "base_url", fallback="") remote_path = config.get("upload_settings", "remote_path", fallback="") log_path_file = config.get("download_settings", "log_path_file", fallback="./log.txt") def login(): """ 登录 Alist 并获取 token """ url = base_url + '/api/auth/login' payload = {'Username': config.get("upload_settings", "UserName", fallback=""), 'Password': config.get("upload_settings", "PassWord", fallback="")} try: response = requests.post(url, data=payload) response_data = response.json() if response_data.get('code') in ['200', 200]: return response_data.get('data').get('token') else: print(f"登录失败!错误信息:{response_data}") return None except Exception as e: print(f"登录过程中发生错误!错误信息:{e}") return None def upload_alist(local_file_path, title): """ 上传文件到 Alist """ token = login() if not token: print("获取 Alist token 失败,无法上传文件!") return False try: # 构造请求 url = base_url + "/api/fs/put" file_size = os.path.getsize(local_file_path) ori_url = remote_path + title + ".mp4" encode_url = quote(ori_url, 'utf-8') headers = { "Authorization": token, "Content-Length": str(file_size), "Content-Type": "video/mp4", "File-Path": encode_url, 'As-Task': 'true', } # 上传文件 with open(local_file_path, "rb") as f: response = requests.put(url, data=f, headers=headers) # 读取响应内容(只读取一次) response_data = response.json() # 检查响应状态 if response_data.get("code") in ["200", 200]: with open(log_path_file, "a", encoding="utf-8") as log_file: log_file.write(f"文件 {title}.mp4 上传成功!\n") return True else: with open(log_path_file, "a", encoding="utf-8") as log_file: log_file.write(f"上传失败!错误信息:{response_data}\n") return False except Exception as e: with open(log_path_file, "a", encoding="utf-8") as log_file: log_file.write(f"上传过程中发生错误!错误信息:{e}\n") return False ``` [/tab] [tab name="Aria2下载"] 然后本人aria2-pro同样是docker部署,alist可以调用,但是这个脚本一直调用不了,所有稍微修改了一下,直接调用Alist添加 离线下载任务。 ```python import http.client import json import configparser import datetime import time import upload # 引用 upload.py 中的 login 函数 from typing import Optional, List # 读取配置文件 config = configparser.ConfigParser() config.read("./config/config.ini", encoding="utf-8") # 配置项 try: base_url = config.get("upload_settings", "base_url") except: base_url = "http://127.0.0.1:5244" try: download_path = config.get("aria2_settings", "download_path") except: download_path = "./" try: log_path_file = config.get("download_settings", "log_path_file") except: log_path_file = "./log.txt" def update_last_line(filename, new_line): """ 更新指定文件的最后一行 """ try: with open(filename, 'r', encoding='utf-8') as file: lines = file.readlines() if len(lines) > 0: # 如果读到内容,更新最后一行 lines[-1] = new_line + '\n' with open(filename, 'w', encoding='utf-8') as file: file.writelines(lines) else: # 如果没有读到内容,直接写入 with open(filename, 'w', encoding='utf-8') as file: file.writelines(new_line) except FileNotFoundError: with open(filename, 'w', encoding='utf-8') as file: file.writelines(f"{filename} not found.") def adduri(uri): """ 提交离线下载任务到 Alist """ # 获取文件名 title = uri.split('/')[-1] # 获取 Alist 的 token token = upload.login() if not token: with open(log_path_file, "a", encoding="utf-8") as log_file: log_file.write("获取 Alist token 失败,无法提交离线下载任务!\n") return # 构造请求 conn = http.client.HTTPConnection(base_url.split('//')[1]) # 去掉协议部分 payload = json.dumps({ "path": download_path, "urls": [uri], "tool": "aria2", "delete_policy": "delete_on_upload_succeed" }) headers = { 'Authorization': token, 'Content-Type': 'application/json' } try: # 提交任务 conn.request("POST", "/api/fs/add_offline_download", payload, headers) res = conn.getresponse() data = res.read().decode("utf-8") res_data = json.loads(data) if res_data.get("code") in [200, "200"]: with open(log_path_file, "a", encoding="utf-8") as log_file: log_file.write(f"任务 {title} 已成功提交到 Alist 离线下载队列\n") print(f"任务 {title} 已成功提交到 Alist 离线下载队列") else: with open(log_path_file, "a", encoding="utf-8") as log_file: log_file.write(f"提交任务失败!错误信息:{res_data.get('message')}\n") print(f"提交任务失败!错误信息:{res_data.get('message')}") except Exception as e: with open(log_path_file, "a", encoding="utf-8") as log_file: log_file.write(f"提交任务时发生错误!错误信息:{e}\n") print(f"提交任务时发生错误!错误信息:{e}") finally: conn.close() ``` [/tab] [/tabs] ## 后记 这样修改完基本属于可以使用了,本人基本也不太会代码修改,这里面的代码基本依靠deepseek完成相应调整及修改 ::aru:shy:: [1]: https://github.com/1314ysys/m3u8dudu [2]: https://122.517128.xyz/1739586891376 [3]: https://122.517128.xyz/1739586952978 [4]: https://122.517128.xyz/1739587384494 [5]: https://122.517128.xyz/1739587410527
AI摘要:本文讨论了在部署M3U8视频离线下载项目时遇到的问题及其解决方案。主要问题包括容器无法启动和布局错乱。针对启动问题,提供了三种解决方法,其中手动复制配置文件为最佳选择。布局问题则通过修改HTML文件解决。此外,文章还提到上传到Alist和使用aria2下载的相关问题,并提供了相应的代码示例。最终,经过调整,项目基本可用。

前言

最近在某知名网站上看到自己喜欢的视频就想着下载下来,每次都用客户端、软件下载太麻烦,就想着能不能整个网页,直接下载好一键传到网盘,然后就找到了这个项目M3U8dudu,但实测用docker部署会有一些问题。

无法启动

碰到的第一个问题就是使用作者给的命令后容器无法启动,根本原因是挂载的/root/config目录下未生成config.ini配置文件。

  • 方法一
  • 方法二(不推荐)
  • 方法三

手动复制一份config.ini文件到/root/config目录下,并将启动命令修改为python app.py --config /root/config/config.ini

修改启动命令是因为,两边文件无法自动同步更改,不修改的话容器仍然以容器内部config.ini为准,后续管理起来麻烦。


直接删掉/root/config:/app/config,取消挂载配置文件也可以启动。不过后续配置麻烦,且容器更新的话,配置文件会丢失。

修改镜像文件,删除项目内config文件夹及里面config.ini,新建一个generate_config.py来创建配置文件,实测是可以的。不过本人测试环境误删了,导致代码保存下来

布局问题

项目启动后,使用电脑端访问布局错乱,手机端和平板显示正常,这里提供我修改好的index.html和play.html。

  • index.html
  • play.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>离线视频下载</title>
    <link rel="shortcut icon" href="https://cdn-icons-png.flaticon.com/512/3121/3121602.png">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.2/dist/css/bootstrap.min.css">
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-lg navbar-light bg-light">
            <a class="navbar-brand" href=" ">Video Download</a >
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarNav">
                <ul class="navbar-nav">
                    <li class="nav-item active">
                        <a class="nav-link" href="/">视频下载 <span class="sr-only">(current)</span></a >
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="/play">视频播放</a >
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="https://github.com/1314ysys/m3u8dudu">关于项目</a >
                    </li>
                </ul>
            </div>
        </nav>
    </header>

    <main class="container">
        <h2 class="text-center my-4">m3u8视频离线下载</h2>
        <form method="POST" action="/download" onsubmit="return onSubmit();">
            <div class="form-group">
                <label for="file_content">请输入要下载的m3u8地址链接:</label>
                <textarea class="form-control" id="file_content" name="file_content" rows="6" placeholder="每行一个链接,链接后空一格表示自定义标题"></textarea>
            </div>
            <div class="row justify-content-end">
                <div class="col-12 col-sm-4 col-md-3">
                    <button type="submit" class="btn btn-primary mb-2 btn-block">开始下载</button>
                </div>
            </div>
        </form>
        <hr>
        <h5>下载日志:</h5>
        <textarea id="log" class="form-control" style="height: 200px; overflow-y: scroll;" readonly></textarea>
        <div class="row justify-content-end mt-3">
            <div class="col-auto">
                <button class="btn btn-secondary btn-block" onclick="clearLog()">清空日志</button>
            </div>
        </div>
    </main>

    <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.1/dist/umd/popper.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.2/dist/js/bootstrap.min.js"></script>

    <script>
        function onSubmit() {
            const fileContent = document.getElementById("file_content").value.trim();
            if (!fileContent) {
                alert('没有输入链接');
                return false;
            }
            document.getElementById("file_content").value = "";

            alert('开始下载');
            fetch('/download', {
                method: 'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                body: new URLSearchParams({ file_content: fileContent })
            })
            .then(response => response.text())
            .then(data => {
                const log = document.getElementById("log");
                log.value += data;
                localStorage.setItem('log', log.value);
            })
            .catch(error => console.error('下载失败:', error));

            return false;
        }

        let lastLineCount = 0;
        function pollLog() {
            fetch(`/log?last_line_count=${lastLineCount}`)
            .then(response => response.text())
            .then(data => {
                const logArea = document.getElementById('log');
                logArea.value = '';
                localStorage.removeItem('log');
                logArea.value += data;
                lastLineCount += data.trim().split('\n').length;
            })
            .catch(error => console.error('获取日志失败:', error));

            setTimeout(pollLog, 1000);
        }

        window.onload = pollLog;

        function clearLog() {
            if (confirm('确定要清空日志吗?')) {
                document.getElementById('log').value = '';
                localStorage.removeItem('log');

                fetch('/clear_log')
                .then(response => response.text())
                .then(data => console.log('日志已清空:', data))
                .catch(error => console.error('清空日志失败:', error));
            }
        }
    </script>    
</body>
</html>

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>m3u8在线视频播放器</title>
    <link rel="shortcut icon" type="image/x-icon" href="https://cdn-icons-png.flaticon.com/512/2696/2696537.png">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.2/dist/css/bootstrap.min.css">
    <style>
        .video-box {
            width: 100%;
            max-width: 90vw;
            max-height: 80vh;
            margin-top: 2vh;
        }
        .container {
            width: 100%;
            max-width: 90vw;
        }
    </style>
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-lg navbar-light bg-light">
            <a class="navbar-brand" href="#">Video Player</a>
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarNav">
                <ul class="navbar-nav">
                    <li class="nav-item">
                        <a class="nav-link" href="/">视频下载</a>
                    </li>
                    <li class="nav-item active">
                        <a class="nav-link" href="/play">视频播放<span class="sr-only">(current)</span></a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="https://github.com/1314ysys/m3u8dudu">关于项目</a>
                    </li>
                </ul>
            </div>
        </nav>
    </header>

    <main class="container">
        <h2 class="text-center my-4">m3u8在线播放器</h2>
        <div class="mt-5">
            <form id="video-form">
                <div class="d-flex flex-row">
                    <div class="flex-grow-1">
                        <input type="text" class="form-control rounded-0" id="url" placeholder="输入m3u8视频链接" required>
                    </div>
                    <div class="align-self-start ml-0">
                        <button type="submit" class="btn btn-primary rounded-0">播放</button>
                    </div>
                </div>          
            </form>
        </div>
    
        <div class="video-box">
            <video id="my-video" class="video-js vjs-default-skin vjs-big-play-centered" controls preload="auto" width="100%" height="100%"></video>
        </div>
    </main>

    <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.2/dist/js/bootstrap.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
    
    <script>
        const video = document.getElementById('my-video');
        const videoSrc = document.getElementById('url');
        const form = document.getElementById('video-form');
        const hls = new Hls();

        form.addEventListener('submit', function(event) {
            event.preventDefault();
            const url = videoSrc.value.trim();

            if (Hls.isSupported()) {
                hls.loadSource(url);
                hls.attachMedia(video);
                hls.on(Hls.Events.MANIFEST_PARSED, function() {
                    video.play();
                });
                hls.on(Hls.Events.ERROR, function(event, data) {
                    if (data.fatal) {
                        switch (data.fatal) {
                            case Hls.ErrorTypes.NETWORK_ERROR:
                                alert('网络错误,无法加载视频.');
                                break;
                            case Hls.ErrorTypes.MEDIA_ERROR:
                                alert('媒体错误,无法播放视频.');
                                break;
                            case Hls.ErrorTypes.OTHER_ERROR:
                                alert('其他错误.');
                                break;
                        }
                    }
                });
            } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
                video.src = url;
                video.addEventListener('loadedmetadata', function() {
                    video.play();
                });
            } else {
                alert('此浏览器不支持播放该视频格式。');
            }
        });
    </script>
</body>
</html>


原视频下载界面
原视频下载界面
原视频播放界面
原视频播放界面
修改后视频下载
修改后视频下载
修改后视频播放
修改后视频播放

上传问题及aria2下载问题

  • Alist上传
  • Aria2下载

使用原本上传代码,测试一直无法成功上传到alist上,不过脚本还可以完善,比如增加上传进度检测等,但实在是懒得整了,主打一个功能正常,能用就行。

import requests
from urllib.parse import quote
import os
import json

# 读取配置文件
import configparser
config = configparser.ConfigParser()
config.read("./config/config.ini", encoding="utf-8")

base_url = config.get("upload_settings", "base_url", fallback="")
remote_path = config.get("upload_settings", "remote_path", fallback="")
log_path_file = config.get("download_settings", "log_path_file", fallback="./log.txt")

def login():
    """
    登录 Alist 并获取 token
    """
   url = base_url + '/api/auth/login'
    payload = {'Username': config.get("upload_settings", "UserName", fallback=""),
               'Password': config.get("upload_settings", "PassWord", fallback="")}
    try:
        response = requests.post(url, data=payload)
        response_data = response.json()
        if response_data.get('code') in ['200', 200]:
            return response_data.get('data').get('token')
        else:
            print(f"登录失败!错误信息:{response_data}")
            return None
    except Exception as e:
        print(f"登录过程中发生错误!错误信息:{e}")
        return None

def upload_alist(local_file_path, title):
    """
    上传文件到 Alist
    """
    token = login()
    if not token:
        print("获取 Alist token 失败,无法上传文件!")
        return False

    try:
        # 构造请求
        url = base_url + "/api/fs/put"
        file_size = os.path.getsize(local_file_path)
        ori_url = remote_path + title + ".mp4"
        encode_url = quote(ori_url, 'utf-8')

        headers = {
            "Authorization": token,
            "Content-Length": str(file_size),
            "Content-Type": "video/mp4",
            "File-Path": encode_url,
            'As-Task': 'true',
        }
    # 上传文件
        with open(local_file_path, "rb") as f:
            response = requests.put(url, data=f, headers=headers)

        # 读取响应内容(只读取一次)
        response_data = response.json()

        # 检查响应状态
        if response_data.get("code") in ["200", 200]:
            with open(log_path_file, "a", encoding="utf-8") as log_file:
                log_file.write(f"文件 {title}.mp4 上传成功!\n")
            return True
        else:
            with open(log_path_file, "a", encoding="utf-8") as log_file:
                log_file.write(f"上传失败!错误信息:{response_data}\n")
            return False

    except Exception as e:
        with open(log_path_file, "a", encoding="utf-8") as log_file:
            log_file.write(f"上传过程中发生错误!错误信息:{e}\n")
        return False


然后本人aria2-pro同样是docker部署,alist可以调用,但是这个脚本一直调用不了,所有稍微修改了一下,直接调用Alist添加 离线下载任务。

import http.client
import json
import configparser
import datetime
import time
import upload  # 引用 upload.py 中的 login 函数
from typing import Optional, List

# 读取配置文件
config = configparser.ConfigParser()
config.read("./config/config.ini", encoding="utf-8")

# 配置项
try:
    base_url = config.get("upload_settings", "base_url")
except:
    base_url = "http://127.0.0.1:5244"
try:
    download_path = config.get("aria2_settings", "download_path")
except:
    download_path = "./"
try:
    log_path_file = config.get("download_settings", "log_path_file")
except:
    log_path_file = "./log.txt"


def update_last_line(filename, new_line):
    """
    更新指定文件的最后一行
    """
    try:
        with open(filename, 'r', encoding='utf-8') as file:
            lines = file.readlines()
        if len(lines) > 0:  # 如果读到内容,更新最后一行
            lines[-1] = new_line + '\n'
            with open(filename, 'w', encoding='utf-8') as file:
                file.writelines(lines)
        else:  # 如果没有读到内容,直接写入
            with open(filename, 'w', encoding='utf-8') as file:
               file.writelines(new_line)
    except FileNotFoundError:
        with open(filename, 'w', encoding='utf-8') as file:
            file.writelines(f"{filename} not found.")

def adduri(uri):
    """
    提交离线下载任务到 Alist
    """
    # 获取文件名
    title = uri.split('/')[-1]

    # 获取 Alist 的 token
    token = upload.login()
    if not token:
        with open(log_path_file, "a", encoding="utf-8") as log_file:
            log_file.write("获取 Alist token 失败,无法提交离线下载任务!\n")
        return

    # 构造请求
    conn = http.client.HTTPConnection(base_url.split('//')[1])  # 去掉协议部分
    payload = json.dumps({
        "path": download_path,
        "urls": [uri],
        "tool": "aria2",
        "delete_policy": "delete_on_upload_succeed"
    })
    headers = {
        'Authorization': token,
        'Content-Type': 'application/json'
    }

    try:
        # 提交任务
        conn.request("POST", "/api/fs/add_offline_download", payload, headers)
        res = conn.getresponse()
        data = res.read().decode("utf-8")
        res_data = json.loads(data)

        if res_data.get("code") in [200, "200"]:
            with open(log_path_file, "a", encoding="utf-8") as log_file:
                log_file.write(f"任务 {title} 已成功提交到 Alist 离线下载队列\n")
                print(f"任务 {title} 已成功提交到 Alist 离线下载队列")
        else:
            with open(log_path_file, "a", encoding="utf-8") as log_file:
                log_file.write(f"提交任务失败!错误信息:{res_data.get('message')}\n")
                print(f"提交任务失败!错误信息:{res_data.get('message')}")

    except Exception as e:
        with open(log_path_file, "a", encoding="utf-8") as log_file:
            log_file.write(f"提交任务时发生错误!错误信息:{e}\n")
        print(f"提交任务时发生错误!错误信息:{e}")

    finally:
        conn.close()

后记

这样修改完基本属于可以使用了,本人基本也不太会代码修改,这里面的代码基本依靠deepseek完成相应调整及修改

END
本文作者: Athena
文章标题:部署M3U8视频离线下载遇到的问题及解决
本文地址:https://233.517128.xyz/archives/36.html
版权说明:若无注明,本文皆学习笔记原创,转载请保留文章出处。
最后修改:2025 年 02 月 22 日
© 允许规范转载
如果觉得我的文章对你有用,请随意赞赏
  • 文章引用
  • 反向引用

Loading...

暂未引用其他文章
暂未被其它文章引用
  • 下一篇
  • 上一篇

发表评论 取消回复
使用cookie技术保留您的个人信息以便您下次快速评论,继续评论表示您已同意该条款

🎲
  • 热门文章
  • 最新评论
  • 随机文章
  • Xchat模块修复

    评论数: 5
  • 甜萝模块第二弹

    评论数: 1
  • 甜萝模块修改教程

    评论数: 1
  • 壁纸

    评论数: 1
  • 在小米6上部署lxc与docker

    评论数: 1
  • 大名鼎鼎的贫僧
    感谢分享
  • 11
    感谢分享
  • 不知名的匿名人士
    感谢!
  • f
    感谢
  • 很好
    看看隐藏
  • 小丑模块逆向分析

    评论数: 0
  • 壁纸

    评论数: 1
  • 51friend模块逆向分析第二弹

    评论数: 0
  • 为oppor9s编译lxc-docker内核

    评论数: 0
  • 在小米6上部署lxc与docker

    评论数: 1
博客信息
  • 16文章数目
  • 40评论数目
  • 240天运行天数
  • 4 个月前最后活动
文章标签
m3u8dudu m3u8 离线下载
文章目录

部署M3U8视频离线下载遇到的问题及解决

Athena • 2025 年 02 月 15 日
文章目录
Powered by Typecho
|
Theme by Handsome
Copyright ©2025 Athena Note
|
萌ICP备 20250525号