前言
这几天需要把本网站的视频放到服务器提供在线播放,直接用mp4格式的话,加载和快进都会比较慢,主要是流量用的比较多,把MP4视频切片成ts后就能节约流量也能提高播放速度,以后大家就不用去网盘享受那龟速的下载体验,也不用去开网盘会员了;当然我们还是会同步提供下载课程的入口。
说干就干
我们让AI基于 FFmpeg 和 Python 写的视频转换m3u8实用小工具,用于高效地将各种格式的视频转换为 TS 格式,同时生成对应的 M3U8 播放列表文件。该工具旨在简化视频处理过程,特别适用于需要快速部署 HLS(HTTP Live Streaming)视频流媒体的场景。

准备工作
安装python:
python官方下载地址:https://www.python.org/downloads/
Video load failed
安装ffpmeg:
ffpmeg官方下载地址:https://ffmpeg.org/download.html
Video load failed
视频切片ts软件功能特点
- 多格式支持
- 支持常见的视频格式(如 MP4、MKV、AVI、MOV 等),能够轻松进行格式转换而无需复杂配置。
- 高效处理
- 借助 FFmpeg 的强大功能,该工具通过无编码方式直接切片视频为 TS 文件,避免重复编码导致的画质损失和处理速度下降。
- 自动生成 M3U8 播放列表
- 在将视频切片成 TS 文件的同时,自动生成对应的 M3U8 文件,方便用于流媒体播放。
- 简单易用
- 提供用户友好的界面(CLI 或 GUI),无需深入了解 FFmpeg 命令行,任何用户都能轻松上手。
- 可配置性强
- 支持自定义切片长度(例如每 10 秒生成一个 TS 文件),满足不同应用场景的需求。
用AI写的python代码
import tkinter as tkfrom tkinter import ttk, filedialog, scrolledtextimport osimport subprocessimport threadingfrom pathlib import Pathimport loggingimport reclass VideoSlicer:def __init__(self, root):self.root = rootself.root.title("视频切片工具-www.baoxiache.com")self.root.geometry("800x600")# 配置日志self.setup_logging()# 创建GUI元素self.create_widgets()# 支持的视频格式self.video_extensions = ('.mp4', '.mkv', '.avi', '.mov', '.flv', '.wmv')# 处理状态self.is_processing = Falsedef setup_logging(self):logging.basicConfig(level=logging.INFO)self.logger = logging.getLogger(__name__)def create_widgets(self):# 输入目录选择input_frame = ttk.LabelFrame(self.root, text="输入设置", padding="5")input_frame.pack(fill="x", padx=5, pady=5)ttk.Label(input_frame, text="输入目录:").pack(side="left")self.input_path = tk.StringVar()ttk.Entry(input_frame, textvariable=self.input_path, width=50).pack(side="left", padx=5)ttk.Button(input_frame, text="浏览", command=self.select_input_dir).pack(side="left")# 输出目录选择output_frame = ttk.LabelFrame(self.root, text="输出设置", padding="5")output_frame.pack(fill="x", padx=5, pady=5)ttk.Label(output_frame, text="输出目录:").pack(side="left")self.output_path = tk.StringVar()ttk.Entry(output_frame, textvariable=self.output_path, width=50).pack(side="left", padx=5)ttk.Button(output_frame, text="浏览", command=self.select_output_dir).pack(side="left")# 切片时长设置duration_frame = ttk.LabelFrame(self.root, text="切片设置", padding="5")duration_frame.pack(fill="x", padx=5, pady=5)ttk.Label(duration_frame, text="切片时长(秒):").pack(side="left")self.slice_duration = tk.StringVar(value="10")ttk.Entry(duration_frame, textvariable=self.slice_duration, width=10).pack(side="left", padx=5)# 进度条self.progress_var = tk.DoubleVar()self.progress = ttk.Progressbar(self.root, variable=self.progress_var, maximum=100)self.progress.pack(fill="x", padx=5, pady=5)# 开始按钮self.start_button = ttk.Button(self.root, text="开始处理", command=self.start_processing)self.start_button.pack(pady=5)# 日志窗口log_frame = ttk.LabelFrame(self.root, text="处理日志", padding="5")log_frame.pack(fill="both", expand=True, padx=5, pady=5)self.log_text = scrolledtext.ScrolledText(log_frame, height=10)self.log_text.pack(fill="both", expand=True)def select_input_dir(self):directory = filedialog.askdirectory()if directory:self.input_path.set(directory)def select_output_dir(self):directory = filedialog.askdirectory()if directory:self.output_path.set(directory)def log_message(self, message):self.log_text.insert(tk.END, f"{message}\n")self.log_text.see(tk.END)def sanitize_filename(self, filename):# 移除非法字符,保留中文和基本字符return re.sub(r'[<>:"/\\|?*]', '', filename)def process_video(self, video_path, output_base_dir):try:# 获取相对路径以保持目录结构rel_path = os.path.relpath(video_path, self.input_path.get())video_dir = os.path.dirname(rel_path)video_name = os.path.splitext(os.path.basename(video_path))[0]# 创建输出目录output_dir = os.path.join(output_base_dir, video_dir, self.sanitize_filename(video_name))os.makedirs(output_dir, exist_ok=True)# 生成切片slice_duration = self.slice_duration.get()m3u8_path = os.path.join(output_dir, 'playlist.m3u8')cmd = ['ffmpeg', '-i', video_path,'-c', 'copy','-f', 'segment','-segment_time', slice_duration,'-segment_list', m3u8_path,'-segment_format', 'mpegts',os.path.join(output_dir, 'segment_%03d.ts')]process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)_, stderr = process.communicate()if process.returncode == 0:self.log_message(f"成功处理: {video_path}")else:self.log_message(f"处理失败: {video_path}\n错误信息: {stderr.decode()}")except Exception as e:self.log_message(f"处理出错: {video_path}\n错误信息: {str(e)}")def scan_videos(self, directory):videos = []for root, _, files in os.walk(directory):for file in files:if file.lower().endswith(self.video_extensions):videos.append(os.path.join(root, file))return videosdef start_processing(self):if self.is_processing:returninput_dir = self.input_path.get()output_dir = self.output_path.get()if not input_dir or not output_dir:self.log_message("请选择输入和输出目录")returnif not self.slice_duration.get().isdigit():self.log_message("请输入有效的切片时长")returnself.is_processing = Trueself.start_button.state(['disabled'])def process_thread():try:videos = self.scan_videos(input_dir)total_videos = len(videos)if total_videos == 0:self.log_message("未找到支持的视频文件")returnself.log_message(f"找到 {total_videos} 个视频文件")for i, video in enumerate(videos, 1):self.process_video(video, output_dir)progress = (i / total_videos) * 100self.progress_var.set(progress)self.log_message("所有文件处理完成")except Exception as e:self.log_message(f"处理过程出错: {str(e)}")finally:self.is_processing = Falseself.start_button.state(['!disabled'])self.progress_var.set(0)threading.Thread(target=process_thread, daemon=True).start()if __name__ == "__main__":root = tk.Tk()app = VideoSlicer(root)root.mainloop()import tkinter as tk from tkinter import ttk, filedialog, scrolledtext import os import subprocess import threading from pathlib import Path import logging import re class VideoSlicer: def __init__(self, root): self.root = root self.root.title("视频切片工具-www.baoxiache.com") self.root.geometry("800x600") # 配置日志 self.setup_logging() # 创建GUI元素 self.create_widgets() # 支持的视频格式 self.video_extensions = ('.mp4', '.mkv', '.avi', '.mov', '.flv', '.wmv') # 处理状态 self.is_processing = False def setup_logging(self): logging.basicConfig(level=logging.INFO) self.logger = logging.getLogger(__name__) def create_widgets(self): # 输入目录选择 input_frame = ttk.LabelFrame(self.root, text="输入设置", padding="5") input_frame.pack(fill="x", padx=5, pady=5) ttk.Label(input_frame, text="输入目录:").pack(side="left") self.input_path = tk.StringVar() ttk.Entry(input_frame, textvariable=self.input_path, width=50).pack(side="left", padx=5) ttk.Button(input_frame, text="浏览", command=self.select_input_dir).pack(side="left") # 输出目录选择 output_frame = ttk.LabelFrame(self.root, text="输出设置", padding="5") output_frame.pack(fill="x", padx=5, pady=5) ttk.Label(output_frame, text="输出目录:").pack(side="left") self.output_path = tk.StringVar() ttk.Entry(output_frame, textvariable=self.output_path, width=50).pack(side="left", padx=5) ttk.Button(output_frame, text="浏览", command=self.select_output_dir).pack(side="left") # 切片时长设置 duration_frame = ttk.LabelFrame(self.root, text="切片设置", padding="5") duration_frame.pack(fill="x", padx=5, pady=5) ttk.Label(duration_frame, text="切片时长(秒):").pack(side="left") self.slice_duration = tk.StringVar(value="10") ttk.Entry(duration_frame, textvariable=self.slice_duration, width=10).pack(side="left", padx=5) # 进度条 self.progress_var = tk.DoubleVar() self.progress = ttk.Progressbar(self.root, variable=self.progress_var, maximum=100) self.progress.pack(fill="x", padx=5, pady=5) # 开始按钮 self.start_button = ttk.Button(self.root, text="开始处理", command=self.start_processing) self.start_button.pack(pady=5) # 日志窗口 log_frame = ttk.LabelFrame(self.root, text="处理日志", padding="5") log_frame.pack(fill="both", expand=True, padx=5, pady=5) self.log_text = scrolledtext.ScrolledText(log_frame, height=10) self.log_text.pack(fill="both", expand=True) def select_input_dir(self): directory = filedialog.askdirectory() if directory: self.input_path.set(directory) def select_output_dir(self): directory = filedialog.askdirectory() if directory: self.output_path.set(directory) def log_message(self, message): self.log_text.insert(tk.END, f"{message}\n") self.log_text.see(tk.END) def sanitize_filename(self, filename): # 移除非法字符,保留中文和基本字符 return re.sub(r'[<>:"/\\|?*]', '', filename) def process_video(self, video_path, output_base_dir): try: # 获取相对路径以保持目录结构 rel_path = os.path.relpath(video_path, self.input_path.get()) video_dir = os.path.dirname(rel_path) video_name = os.path.splitext(os.path.basename(video_path))[0] # 创建输出目录 output_dir = os.path.join(output_base_dir, video_dir, self.sanitize_filename(video_name)) os.makedirs(output_dir, exist_ok=True) # 生成切片 slice_duration = self.slice_duration.get() m3u8_path = os.path.join(output_dir, 'playlist.m3u8') cmd = [ 'ffmpeg', '-i', video_path, '-c', 'copy', '-f', 'segment', '-segment_time', slice_duration, '-segment_list', m3u8_path, '-segment_format', 'mpegts', os.path.join(output_dir, 'segment_%03d.ts') ] process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) _, stderr = process.communicate() if process.returncode == 0: self.log_message(f"成功处理: {video_path}") else: self.log_message(f"处理失败: {video_path}\n错误信息: {stderr.decode()}") except Exception as e: self.log_message(f"处理出错: {video_path}\n错误信息: {str(e)}") def scan_videos(self, directory): videos = [] for root, _, files in os.walk(directory): for file in files: if file.lower().endswith(self.video_extensions): videos.append(os.path.join(root, file)) return videos def start_processing(self): if self.is_processing: return input_dir = self.input_path.get() output_dir = self.output_path.get() if not input_dir or not output_dir: self.log_message("请选择输入和输出目录") return if not self.slice_duration.get().isdigit(): self.log_message("请输入有效的切片时长") return self.is_processing = True self.start_button.state(['disabled']) def process_thread(): try: videos = self.scan_videos(input_dir) total_videos = len(videos) if total_videos == 0: self.log_message("未找到支持的视频文件") return self.log_message(f"找到 {total_videos} 个视频文件") for i, video in enumerate(videos, 1): self.process_video(video, output_dir) progress = (i / total_videos) * 100 self.progress_var.set(progress) self.log_message("所有文件处理完成") except Exception as e: self.log_message(f"处理过程出错: {str(e)}") finally: self.is_processing = False self.start_button.state(['!disabled']) self.progress_var.set(0) threading.Thread(target=process_thread, daemon=True).start() if __name__ == "__main__": root = tk.Tk() app = VideoSlicer(root) root.mainloop()import tkinter as tk from tkinter import ttk, filedialog, scrolledtext import os import subprocess import threading from pathlib import Path import logging import re class VideoSlicer: def __init__(self, root): self.root = root self.root.title("视频切片工具-www.baoxiache.com") self.root.geometry("800x600") # 配置日志 self.setup_logging() # 创建GUI元素 self.create_widgets() # 支持的视频格式 self.video_extensions = ('.mp4', '.mkv', '.avi', '.mov', '.flv', '.wmv') # 处理状态 self.is_processing = False def setup_logging(self): logging.basicConfig(level=logging.INFO) self.logger = logging.getLogger(__name__) def create_widgets(self): # 输入目录选择 input_frame = ttk.LabelFrame(self.root, text="输入设置", padding="5") input_frame.pack(fill="x", padx=5, pady=5) ttk.Label(input_frame, text="输入目录:").pack(side="left") self.input_path = tk.StringVar() ttk.Entry(input_frame, textvariable=self.input_path, width=50).pack(side="left", padx=5) ttk.Button(input_frame, text="浏览", command=self.select_input_dir).pack(side="left") # 输出目录选择 output_frame = ttk.LabelFrame(self.root, text="输出设置", padding="5") output_frame.pack(fill="x", padx=5, pady=5) ttk.Label(output_frame, text="输出目录:").pack(side="left") self.output_path = tk.StringVar() ttk.Entry(output_frame, textvariable=self.output_path, width=50).pack(side="left", padx=5) ttk.Button(output_frame, text="浏览", command=self.select_output_dir).pack(side="left") # 切片时长设置 duration_frame = ttk.LabelFrame(self.root, text="切片设置", padding="5") duration_frame.pack(fill="x", padx=5, pady=5) ttk.Label(duration_frame, text="切片时长(秒):").pack(side="left") self.slice_duration = tk.StringVar(value="10") ttk.Entry(duration_frame, textvariable=self.slice_duration, width=10).pack(side="left", padx=5) # 进度条 self.progress_var = tk.DoubleVar() self.progress = ttk.Progressbar(self.root, variable=self.progress_var, maximum=100) self.progress.pack(fill="x", padx=5, pady=5) # 开始按钮 self.start_button = ttk.Button(self.root, text="开始处理", command=self.start_processing) self.start_button.pack(pady=5) # 日志窗口 log_frame = ttk.LabelFrame(self.root, text="处理日志", padding="5") log_frame.pack(fill="both", expand=True, padx=5, pady=5) self.log_text = scrolledtext.ScrolledText(log_frame, height=10) self.log_text.pack(fill="both", expand=True) def select_input_dir(self): directory = filedialog.askdirectory() if directory: self.input_path.set(directory) def select_output_dir(self): directory = filedialog.askdirectory() if directory: self.output_path.set(directory) def log_message(self, message): self.log_text.insert(tk.END, f"{message}\n") self.log_text.see(tk.END) def sanitize_filename(self, filename): # 移除非法字符,保留中文和基本字符 return re.sub(r'[<>:"/\\|?*]', '', filename) def process_video(self, video_path, output_base_dir): try: # 获取相对路径以保持目录结构 rel_path = os.path.relpath(video_path, self.input_path.get()) video_dir = os.path.dirname(rel_path) video_name = os.path.splitext(os.path.basename(video_path))[0] # 创建输出目录 output_dir = os.path.join(output_base_dir, video_dir, self.sanitize_filename(video_name)) os.makedirs(output_dir, exist_ok=True) # 生成切片 slice_duration = self.slice_duration.get() m3u8_path = os.path.join(output_dir, 'playlist.m3u8') cmd = [ 'ffmpeg', '-i', video_path, '-c', 'copy', '-f', 'segment', '-segment_time', slice_duration, '-segment_list', m3u8_path, '-segment_format', 'mpegts', os.path.join(output_dir, 'segment_%03d.ts') ] process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) _, stderr = process.communicate() if process.returncode == 0: self.log_message(f"成功处理: {video_path}") else: self.log_message(f"处理失败: {video_path}\n错误信息: {stderr.decode()}") except Exception as e: self.log_message(f"处理出错: {video_path}\n错误信息: {str(e)}") def scan_videos(self, directory): videos = [] for root, _, files in os.walk(directory): for file in files: if file.lower().endswith(self.video_extensions): videos.append(os.path.join(root, file)) return videos def start_processing(self): if self.is_processing: return input_dir = self.input_path.get() output_dir = self.output_path.get() if not input_dir or not output_dir: self.log_message("请选择输入和输出目录") return if not self.slice_duration.get().isdigit(): self.log_message("请输入有效的切片时长") return self.is_processing = True self.start_button.state(['disabled']) def process_thread(): try: videos = self.scan_videos(input_dir) total_videos = len(videos) if total_videos == 0: self.log_message("未找到支持的视频文件") return self.log_message(f"找到 {total_videos} 个视频文件") for i, video in enumerate(videos, 1): self.process_video(video, output_dir) progress = (i / total_videos) * 100 self.progress_var.set(progress) self.log_message("所有文件处理完成") except Exception as e: self.log_message(f"处理过程出错: {str(e)}") finally: self.is_processing = False self.start_button.state(['!disabled']) self.progress_var.set(0) threading.Thread(target=process_thread, daemon=True).start() if __name__ == "__main__": root = tk.Tk() app = VideoSlicer(root) root.mainloop()
下载完整python代码直接运行
视频切片工具.zip
zip文件
2.2K
© 版权声明
THE END
暂无评论内容