一键下载 M3U8 并转换为 MP4升级版

 之前的下载 M3U8程序,有很多问题, 为此做了一些升级,分享给大家。

  • 增加了存在播放列表的情况处理
  • 播放列表路径和ts路径错误问题
  • 多线程问题
  • 对于电视剧多文件下载的处理

这里从网上找了一部的链接,可以参考这个网站https://www.zuida001.com/


import os
import urllib3
import requests
import subprocess
import m3u8
from urllib import parse
from tenacity import retry, wait_random, stop_after_attempt
import gevent
from gevent.threadpool import ThreadPoolurllib3.disable_warnings()class M3u8Downloader:def __init__(self, pool_size=10):self.pool = ThreadPool(pool_size)@retry(stop=stop_after_attempt(3), wait=wait_random(2, 5))def request(self, url):"""发送请求"""try:res = requests.get(url, verify=False, timeout=5)return resexcept Exception as e:print(url, e)raise edef download_segment(self, url, file):"""下载ts文件"""if os.path.exists(file):return res = self.request(url)with open(file, "wb")as f:f.write(res.content)def convert_mp4(self, path, output, key):if not os.path.exists(output):# 使用FFmpeg将所有.ts文件合并为一个MP4文件    ffmpeg -allowed_extensions ALL -i index.m3u8 -c copy xxx.mp4if key:subprocess.call(['ffmpeg', '-allowed_extensions', 'ALL', '-i', 'local.m3u8', '-c', 'copy', output], cwd=path)else:subprocess.call(['ffmpeg', '-i', 'local.m3u8', '-c', 'copy', output], cwd=path)def download_m3u8(self, url, path):"""下载M3U8文件,有些存在播放列表,默认选择第一个"""m3u8_file_name = os.path.join(path, "index.m3u8")res = self.request(url)with open(m3u8_file_name, "w", encoding="utf-8")as f:f.write(res.text)# 解析M3U8文件m3u8_obj = m3u8.loads(res.text)# 如果存在清晰度列表,请求解析清晰度列表m3u8_playlist = []for playlist in m3u8_obj.playlists:uri = parse.urljoin(url, playlist.uri)bandwidth = playlist.stream_info.bandwidthresolution= playlist.stream_info.resolutionm3u8_file_name = os.path.join(path, f"{'x'.join([str(i) for i in resolution])}_{bandwidth}.m3u8")res = self.request(uri)with open(m3u8_file_name, "w", encoding="utf-8")as f:f.write(res.text)cur_m3u8 = m3u8.loads(res.text)cur_m3u8.uri = urim3u8_playlist.append(cur_m3u8)if m3u8_playlist:# 播放列表默认选择第一个return m3u8_playlist[0]else:return m3u8_objdef download(self, url, path, output):"""下载单个m3u8主程序url: m3u8链接urlpath: 单个m3u8目录output: 转换输出文件路径名"""# 创建目录segment_dir = os.path.join(path, "index")if not os.path.exists(segment_dir):os.makedirs(segment_dir)output_dir = os.path.dirname(output)if not os.path.exists(output_dir):os.makedirs(output_dir)# 解析M3U8文件m3u8_obj = self.download_m3u8(url, path)# 下载key文件 for item in m3u8_obj.keys:if item:key_url = parse.urljoin(url, item.uri)key_file_name = os.path.join(path, key_url.split("/")[-1])res = self.request(key_url)with open(key_file_name, "w", encoding="utf-8")as f:f.write(res.text)segments = []# 生成新的本地M3U8文件内容for index, segment in enumerate(m3u8_obj.segments):uri = parse.urljoin(m3u8_obj.uri, segment.uri)segments.append(uri)# 有些ts文件名过长,对其以序号重新命名segment.uri = f"index/{index}.{uri.split('.')[-1]}"# 保存M3U8文件local_file_name = os.path.join(path, "local.m3u8")with open(local_file_name, 'w') as f:f.write(m3u8_obj.dumps())# 下载ts文件for index, url in enumerate(segments):file = os.path.join(segment_dir, f"{index}.{url.split('.')[-1]}")self.pool.spawn(self.download_segment, url, file)gevent.wait()# ts文件下载完成, 转换成mp4文件if len(segments) == len(os.listdir(segment_dir)):self.convert_mp4(path, output, key=[item.uri for item in m3u8_obj.keys if item])if __name__ == "__main__":cur_path = os.path.abspath(os.path.dirname(__file__))data = [{"name": "凶劫601航班第01集", "url": "https://v4.mstopq.com/202404/11/4cSTt8dMgB7/video/index.m3u8"},{"name": "凶劫601航班第02集", "url": "https://v4.mstopq.com/202404/11/JtNttP8HfS7/video/index.m3u8"},{"name": "凶劫601航班第03集", "url": "https://v4.mstopq.com/202404/11/b10TUD8C4T7/video/index.m3u8"},{"name": "凶劫601航班第04集", "url": "https://v4.mstopq.com/202404/11/eWLuKWqFBL7/video/index.m3u8"},{"name": "凶劫601航班第05集", "url": "https://v4.mstopq.com/202404/11/Sj3pwtX0hN7/video/index.m3u8"},{"name": "凶劫601航班第06集", "url": "https://v4.mstopq.com/202404/11/i62BN8wMfc7/video/index.m3u8"},]for item in data:url = item["url"]name = item["name"]path = os.path.join(cur_path, "凶劫601航班_tmp", f"{name}") output = os.path.join(cur_path, "凶劫601航班", f"{name}.mp4")# 因为是多线程下载,可能存在某个线程下载失败的情况, 如果发现下载不完整,没有输出文件,可以尝试重新运行,已经下载过的不会再次下载。download = M3u8Downloader(pool_size=20)download.download(url, path, output)

查看原文:一键下载 M3U8 并转换为 MP4升级版

 关注公众号 "字节航海家" 及时获取最新内容

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/304608.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

3 突破编程_前端_SVG(rect 矩形)

1 rect 元素的基本属性和用法 在SVG中&#xff0c;<rect> 元素用于创建矩形。 <rect> 元素有一些基本的属性&#xff0c;可以用来定义矩形的形状、位置、颜色等。以下是这些属性的详细解释&#xff1a; x 和 y &#xff1a;这两个属性定义矩形左上角的位置。 x …

Llama 3下月正式发布,继续开源!

4月10日&#xff0c;Techcrunch消息&#xff0c;Meta在本周伦敦举办的一场活动中确定&#xff0c;下个月将正式发布Llama 3并且继续开源。 Meta全球事务总裁Nick Clegg表示&#xff0c;我们希望在下个月&#xff0c;甚至更短的时间内&#xff0c;正式推出新一代基础模型Llama …

全光谱台灯哪个牌子好,2024全光谱护眼台灯推荐

近年来&#xff0c;全光谱台灯悄然跻身于家庭必备品之列&#xff0c;赢得了众多消费者的好评。它们以减轻眼睛疲劳的功效而受到推崇&#xff0c;尽管也有声音质疑其实际效用&#xff0c;认为所谓的益处不过是一种心理安慰。面对这些相互矛盾的观点&#xff0c;许多消费者感到困…

Java数据结构二叉树

概念 一棵二叉树是结点的一个有限集合&#xff0c;该集合&#xff1a; 1. 或者为空 2. 或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。 从上图可以看出&#xff1a; 1. 二叉树不存在度大于2的结点 2. 二叉树的子树有左右之分&#xff0c;次序不能颠倒&#x…

【QingHub】EMQX单节点一键部署

EMQX 简介 EMQX是全球最具扩展性的开源MQTT 代理&#xff0c;具有高性能&#xff0c;可在 1 个集群中连接 1 亿多个 IoT 设备&#xff0c;同时保持每秒 100 万条消息的吞吐量和亚毫秒级的延迟。 EMQX 支持MQTT、HTTP、QUIC、WebSocket等多种开放标准协议。它 100% 符合MQTT 5.…

【氧化镓】β-Ga2O3肖特基势垒二极管的缺陷识别

本文是一篇关于β-Ga2O3肖特基势垒二极管在电子辐射和退火调节下缺陷识别的研究。文章首先介绍了β-Ga2O3作为一种高性能器件材料的重要性&#xff0c;然后详细描述了实验方法&#xff0c;包括样品制备、电子辐照、热退火处理以及电学特性和深能级瞬态谱&#xff08;DLTS&#…

Java快速入门系列-7(测试与调试)

第七章:测试与调试 第7章:测试与调试7.1 单元测试(JUnit)7.1.1 为什么要进行单元测试7.1.2 JUnit基础7.1.3 断言7.1.4 测试套件7.2 集成测试与系统测试7.2.1 集成测试7.2.2 系统测试7.3 调试技巧与工具7.3.1 断点7.3.2 单步执行7.3.3 变量检查7.3.4 条件断点7.3.5 日志记录…

集群服务器使用

查看剩余资源&#xff1a;sinfo -O Nodehost,Gres:.30,GresUsed:.45 第二列是总资源 第三列是占用量 申请资源&#xff1a;salloc -N 1 -n 1 -p normal --gresgpu:NVIDIAGeForceGTX1080Ti1 gres的名字来源于sinfo 查看任务情况 squeue JOBID NODES 连接资源 ssh NODES …

云原生__K8S

createrepo --update /var/localrepo/# 禁用 firewall 和 swap [rootmaster ~]# sed /swap/d -i /etc/fstab [rootmaster ~]# swapoff -a [rootmaster ~]# dnf remove -y firewalld-*[rootmaster ~]# vim /etc/hosts 192.168.1.30 harbor 192.168.1.50 master 192.168.1.…

注解(Annotation) --java学习笔记

注解 就是Java代码里的特殊标记&#xff0c;比如:Override、Test等&#xff0c;作用是:让其他程序根据注解信息来决定怎么执行该程序注意:注解可以用在类上、构造器上、方法上、成员变量上、参数上、等位置处 自定义注解 就是自己定义注解 自定义注解到底该怎么写&#xff1a…

linux离线安装redis

一、下载linux版本压缩包 地址&#xff1a;Download | Redis 为了安全稳定性&#xff0c;下载 6.2 版本&#xff0c;不下载最新版 二、上传到linux服务器 笔者上传到 /opt/redis下 &#xff0c;使用Xftp和Xshell工具&#xff0c;使用root权限 cd /opt sudo mkdir redis cd r…

Dude, where’s that IP? Circumventing measurement-based IP geolocation(2010年)

下载地址:https://www.usenix.org/legacy/event/sec10/tech/full_papers/Gill.pdf 被引次数:102 Gill P, Ganjali Y, Wong B. Dude, Wheres That {IP}? Circumventing Measurement-based {IP} Geolocation[C]//19th USENIX Security Symposium (USENIX Security 10). 2010.…

SQLite 4.9的虚拟表机制(十四)

返回&#xff1a;SQLite—系列文章目录 上一篇:SQLite 4.9的 OS 接口或“VFS”&#xff08;十三&#xff09; 下一篇:SQLite数据库文件格式&#xff08;十五&#xff09; 1. 引言 虚拟表是向打开的 SQLite 数据库连接注册的对象。从SQL语句的角度来看&#xff0c; 虚拟表…

MySQL 主从 AUTO_INCREMENT 不一致问题分析

作者&#xff1a;vivo 互联网数据库团队 - Wei Haodong 本文介绍了 MySQL5.7 中常见的replace into 操作造成的主从auto_increment不一致现象&#xff0c;一旦触发了主从切换&#xff0c;业务的正常插入操作会触发主键冲突的报错提示。 一、问题描述 1.1 问题现象 在 MySQL …

网络——初识网络

在现如今&#xff0c;网络已经成了一种基础设施&#xff0c;大到国家&#xff0c;小到个人&#xff0c;网络已经充斥在我们每个人的身 边&#xff0c;如果一个人突然失去了网络&#xff0c;那么它的生活或多或少会出现一些不方便的地方&#xff0c;网络现在已 经伴随着我们的吃…

python镜像安装(ios、windows)

如果你在使用Python时发现官方网站下载速度过慢&#xff0c;可以考虑使用国内的Python镜像源下载Python。国内的Python镜像源可以提供更快的下载速度和更好的下载体验。 以下是使用国内Python镜像源下载Python的步骤&#xff1a; 步骤 1&#xff1a;选择Python版本 首先&…

C++11 设计模式1. 模板方法(Template Method)模式学习。UML图

一 什么是 "模板方法&#xff08;Template Method&#xff09;模式" 在固定步骤确定的情况下&#xff0c;通过多态机制在多个子类中对每个步骤的细节进行差异化实现&#xff0c;这就是模板方法模式能够达到的效果。 模板方法模式属于&#xff1a;行为型模式。 二 &…

PostgreSQL入门到实战-第十四弹

PostgreSQL入门到实战 PostgreSQL数据过滤(七)官网地址PostgreSQL概述PostgreSQL中BETWEEN 命令理论PostgreSQL中BETWEEN 命令实战更新计划 PostgreSQL数据过滤(七) BETWEEN运算符允许您检查值是否在值的范围内。 官网地址 声明: 由于操作系统, 版本更新等原因, 文章所列内容…

坚持十天做完Python入门编程100题第三天加班

坚持十天做完Python入门编程100题第三天加班 第24题 扫描文件列表第25题 如何将字典转换成JSON并写入json文件&#xff1f;第26题 JSON转换成字典 第24题 扫描文件列表 如何扫描当前目录下的文件列表&#xff1f;解析&#xff1a;可以使用python内置的glob模块&#xff0c;用法…

Python | Leetcode Python题解之第22题括号生成

题目&#xff1a; 题解&#xff1a; class Solution:def generateParenthesis(self, n: int) -> List[str]:if n 0:return []total_l []total_l.append([None]) # 0组括号时记为Nonetotal_l.append(["()"]) # 1组括号只有一种情况for i in range(2,n1): …