
Windows/Linux 批量 TS 转 MP4FFmpeg 结合 Shell/Python 的 2 种自动化方案视频文件格式转换是数字媒体处理中的常见需求特别是当我们需要将 TSTransport Stream格式的视频批量转换为更通用的 MP4 格式时。TS 文件虽然在某些专业场景下有其优势但在日常使用中MP4 格式因其广泛的兼容性和高效的压缩算法而成为更受欢迎的选择。本文将深入探讨两种跨平台的自动化解决方案基于 Shell 脚本的快速转换方案和基于 Python 的工程化方案帮助开发者和高级用户高效处理大批量 TS 文件。1. 环境准备与 FFmpeg 基础在开始批量转换之前我们需要确保系统已经安装了 FFmpeg 工具。FFmpeg 是一个强大的多媒体框架能够解码、编码、转码、复用、解复用、流式传输、过滤和播放几乎所有类型的媒体文件。安装 FFmpeg 的步骤Windows 系统访问 FFmpeg 官方网站 下载 Windows 版本解压下载的压缩包到指定目录如C:\ffmpeg将 FFmpeg 的 bin 目录如C:\ffmpeg\bin添加到系统 PATH 环境变量Linux/macOS 系统# Ubuntu/Debian sudo apt update sudo apt install ffmpeg # CentOS/RHEL sudo yum install epel-release sudo yum install ffmpeg # macOS (使用 Homebrew) brew install ffmpeg验证安装是否成功ffmpeg -version基础转换命令解析 单个 TS 文件转换为 MP4 的基本命令如下ffmpeg -i input.ts -c:v copy -c:a copy output.mp4参数说明-i input.ts指定输入文件-c:v copy视频流直接复制不重新编码-c:a copy音频流直接复制不重新编码output.mp4输出文件名提示使用-c copy参数可以避免重新编码实现无损转换处理速度极快。但前提是 TS 文件中的编码格式与 MP4 容器兼容通常是 H.264 视频和 AAC 音频。2. Windows 平台批量处理方案对于 Windows 用户我们可以创建健壮的批处理脚本解决路径中包含空格或特殊字符的问题并添加错误处理机制。完整 PowerShell 脚本保存为convert-ts-to-mp4.ps1# .SYNOPSIS Batch convert TS files to MP4 format using FFmpeg .DESCRIPTION This script recursively processes all .ts files in the specified directory, converting them to MP4 format while preserving original quality. # param( [string]$directory (Get-Location) ) # 检查 FFmpeg 是否可用 if (-not (Get-Command ffmpeg -ErrorAction SilentlyContinue)) { Write-Error FFmpeg is not installed or not in PATH. Please install FFmpeg first. exit 1 } # 获取所有 TS 文件包括子目录 $tsFiles Get-ChildItem -Path $directory -Filter *.ts -Recurse if ($tsFiles.Count -eq 0) { Write-Host No TS files found in the specified directory. exit 0 } # 创建日志目录 $logDir Join-Path $directory ConversionLogs if (-not (Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir | Out-Null } $successCount 0 $failedCount 0 foreach ($tsFile in $tsFiles) { $outputFile Join-Path $tsFile.DirectoryName ($tsFile.BaseName .mp4) $logFile Join-Path $logDir ($tsFile.BaseName .log) Write-Host Processing $($tsFile.FullName)... # 使用 try-catch 捕获错误 try { # 使用 -safe 0 参数处理可能的安全问题 ffmpeg -i $($tsFile.FullName) -c:v copy -c:a copy -bsf:a aac_adtstoasc $outputFile 2 $logFile if ($LASTEXITCODE -eq 0) { Write-Host Successfully converted to $outputFile -ForegroundColor Green $successCount } else { throw FFmpeg exited with code $LASTEXITCODE } } catch { Write-Host Failed to convert $($tsFile.FullName): $_ -ForegroundColor Red $failedCount # 删除可能生成的不完整输出文件 if (Test-Path $outputFile) { Remove-Item $outputFile -Force } } } # 输出统计信息 Write-Host nConversion completed: Write-Host Success: $successCount Write-Host Failed: $failedCount Write-Host Log files are saved in: $logDir脚本特点支持递归处理子目录中的 TS 文件正确处理包含空格或特殊字符的文件路径详细的错误处理和日志记录进度显示和最终统计信息使用-bsf:a aac_adtstoasc比特流过滤器解决可能的 AAC 音频问题使用方式将脚本保存为convert-ts-to-mp4.ps1在 PowerShell 中运行.\convert-ts-to-mp4.ps1 -directory D:\MyVideos如果不指定目录参数默认处理当前目录3. Linux/macOS 平台批量处理方案对于 Linux 和 macOS 用户我们可以编写一个更灵活的 Bash 脚本支持并发处理以加快转换速度。完整 Bash 脚本保存为convert-ts-to-mp4.sh#!/bin/bash # 批量 TS 转 MP4 脚本 # 用法: ./convert-ts-to-mp4.sh [输入目录] [输出目录] [并发数] set -euo pipefail # 默认参数 INPUT_DIR${1:-.} OUTPUT_DIR${2:-${INPUT_DIR}} MAX_JOBS${3:-4} LOG_DIR${OUTPUT_DIR}/conversion_logs # 创建输出目录和日志目录 mkdir -p ${OUTPUT_DIR} mkdir -p ${LOG_DIR} # 检查 FFmpeg if ! command -v ffmpeg /dev/null; then echo 错误: FFmpeg 未安装。请先安装 FFmpeg。 exit 1 fi # 查找所有 TS 文件包括子目录 readarray -d ts_files (find ${INPUT_DIR} -type f -name *.ts -print0) if [ ${#ts_files[]} -eq 0 ]; then echo 在 ${INPUT_DIR} 中未找到 TS 文件。 exit 0 fi echo 找到 ${#ts_files[]} 个 TS 文件待处理... # 初始化计数器 success_count0 failed_count0 # 定义转换函数 convert_file() { local ts_file$1 local relative_path${ts_file#${INPUT_DIR}/} local output_file${OUTPUT_DIR}/${relative_path%.ts}.mp4 local log_file${LOG_DIR}/$(basename ${ts_file%.ts}).log mkdir -p $(dirname ${output_file}) echo 正在处理: ${ts_file} if ffmpeg -i ${ts_file} -c:v copy -c:a copy -bsf:a aac_adtstoasc ${output_file} 2 ${log_file}; then echo 转换成功: ${output_file} ((success_count)) else echo 转换失败: ${ts_file} # 删除可能生成的不完整文件 rm -f ${output_file} ((failed_count)) fi } # 导出函数以便在并行中使用 export -f convert_file export INPUT_DIR OUTPUT_DIR LOG_DIR # 使用 parallel 进行并行处理 if command -v parallel /dev/null; then printf %s\0 ${ts_files[]} | \ parallel -0 -j ${MAX_JOBS} --bar convert_file else echo 警告: GNU parallel 未安装将使用串行处理速度较慢。 for ts_file in ${ts_files[]}; do convert_file ${ts_file} done fi # 输出统计信息 echo -e \n转换完成: echo 成功: ${success_count} echo 失败: ${failed_count} echo 日志文件保存在: ${LOG_DIR}脚本特点支持递归处理子目录可指定输入/输出目录使用 GNU parallel 实现并行处理大幅提高转换速度完善的错误处理和日志记录保留原始目录结构使用方式给脚本添加执行权限chmod x convert-ts-to-mp4.sh运行脚本# 基本用法使用当前目录4个并行任务 ./convert-ts-to-mp4.sh # 指定输入目录和并发数 ./convert-ts-to-mp4.sh /path/to/ts/files 8 # 指定输入和输出目录 ./convert-ts-to-mp4.sh /input/path /output/path注意如果系统未安装 GNU parallel脚本会自动回退到串行处理。建议安装 parallel 以获得最佳性能# Ubuntu/Debian sudo apt install parallel # CentOS/RHEL sudo yum install parallel # macOS brew install parallel4. Python 工程化解决方案对于需要更复杂逻辑或跨平台兼容性的场景我们可以使用 Python 编写更强大的转换工具。以下是一个完整的 Python 脚本支持递归处理、进度显示、错误重试和配置文件。完整 Python 脚本保存为ts_to_mp4_converter.py#!/usr/bin/env python3 TS 批量转 MP4 工具 功能 - 递归扫描目录中的 TS 文件 - 多线程并行处理 - 进度显示和错误日志 - 配置文件支持 - 自动重试失败的任务 import os import sys import subprocess import argparse import threading import queue import time from pathlib import Path from datetime import datetime import configparser import platform # 配置默认值 DEFAULT_CONFIG { general: { max_workers: 4, retry_times: 3, output_suffix: _converted, preserve_structure: true, log_level: info, }, ffmpeg: { video_codec: copy, audio_codec: copy, extra_options: -bsf:a aac_adtstoasc, } } class TSConverter: def __init__(self, config_pathNone): self.config configparser.ConfigParser() self.config.read_dict(DEFAULT_CONFIG) if config_path and os.path.exists(config_path): self.config.read(config_path) self.running True self.task_queue queue.Queue() self.lock threading.Lock() self.success_count 0 self.failed_count 0 self.skipped_count 0 self.processed_files set() # 初始化日志目录 self.log_dir os.path.join(os.getcwd(), conversion_logs) os.makedirs(self.log_dir, exist_okTrue) # 检查 FFmpeg self.ffmpeg_path self._find_ffmpeg() if not self.ffmpeg_path: raise RuntimeError(FFmpeg 未找到。请确保 FFmpeg 已安装并在 PATH 中。) def _find_ffmpeg(self): 查找系统上的 FFmpeg 可执行文件 try: subprocess.run([ffmpeg, -version], checkTrue, stdoutsubprocess.PIPE, stderrsubprocess.PIPE) return ffmpeg except: # 检查常见安装位置 possible_paths [ /usr/local/bin/ffmpeg, /usr/bin/ffmpeg, C:\\ffmpeg\\bin\\ffmpeg.exe ] for path in possible_paths: if os.path.exists(path): return path return None def _build_output_path(self, input_path, output_rootNone): 构建输出文件路径 input_path Path(input_path) if self.config.getboolean(general, preserve_structure): if output_root: relative input_path.relative_to(Path.cwd()) output_path Path(output_root) / relative else: output_path input_path.parent else: output_path Path(output_root) if output_root else input_path.parent # 创建输出目录 output_path.mkdir(parentsTrue, exist_okTrue) # 添加后缀如果配置 suffix self.config.get(general, output_suffix, fallback) output_filename f{input_path.stem}{suffix}.mp4 return output_path / output_filename def _convert_file(self, ts_file, output_path, retry_count0): 执行单个文件转换 log_file os.path.join(self.log_dir, f{Path(ts_file).stem}.log) cmd [ self.ffmpeg_path, -i, ts_file, -c:v, self.config[ffmpeg][video_codec], -c:a, self.config[ffmpeg][audio_codec], ] # 添加额外选项 extra_options self.config[ffmpeg][extra_options] if extra_options: cmd.extend(extra_options.split()) cmd.append(str(output_path)) try: with open(log_file, w) as log: result subprocess.run( cmd, stdoutlog, stderrsubprocess.STDOUT, checkTrue, textTrue ) return True except subprocess.CalledProcessError as e: if retry_count self.config.getint(general, retry_times): time.sleep(1) # 延迟后重试 return self._convert_file(ts_file, output_path, retry_count 1) return False except Exception as e: return False def worker(self): 工作线程函数 while self.running: try: task self.task_queue.get(timeout1) if task is None: break ts_file, output_path task # 检查输出文件是否已存在 if output_path.exists(): with self.lock: self.skipped_count 1 continue # 执行转换 success self._convert_file(ts_file, output_path) with self.lock: if success: self.success_count 1 else: self.failed_count 1 # 删除可能生成的不完整文件 if output_path.exists(): output_path.unlink() # 打印进度 total self.success_count self.failed_count self.skipped_count print(f\r处理中: {total} 完成 | 成功: {self.success_count} 失败: {self.failed_count} 跳过: {self.skipped_count}, end) except queue.Empty: continue except Exception as e: with self.lock: self.failed_count 1 continue finally: self.task_queue.task_done() def process_directory(self, input_dir, output_dirNone): 处理整个目录 start_time datetime.now() print(f开始处理: {start_time.strftime(%Y-%m-%d %H:%M:%S)}) print(f输入目录: {input_dir}) if output_dir: print(f输出目录: {output_dir}) # 查找所有 TS 文件 ts_files [] for root, _, files in os.walk(input_dir): for file in files: if file.lower().endswith(.ts): ts_files.append(os.path.join(root, file)) if not ts_files: print(未找到 TS 文件。) return print(f找到 {len(ts_files)} 个 TS 文件待处理...) # 创建工作线程 max_workers self.config.getint(general, max_workers) threads [] for _ in range(max_workers): t threading.Thread(targetself.worker) t.start() threads.append(t) # 添加任务到队列 for ts_file in ts_files: output_path self._build_output_path(ts_file, output_dir) self.task_queue.put((ts_file, output_path)) # 等待所有任务完成 self.task_queue.join() # 停止工作线程 self.running False for _ in range(max_workers): self.task_queue.put(None) for t in threads: t.join() # 输出统计信息 end_time datetime.now() duration end_time - start_time print(\n\n转换完成:) print(f 总文件数: {len(ts_files)}) print(f 成功: {self.success_count}) print(f 失败: {self.failed_count}) print(f 跳过: {self.skipped_count}) print(f 耗时: {duration}) print(f日志文件保存在: {self.log_dir}) def main(): parser argparse.ArgumentParser( description批量 TS 转 MP4 工具, formatter_classargparse.ArgumentDefaultsHelpFormatter ) parser.add_argument(input_dir, help包含 TS 文件的输入目录) parser.add_argument(-o, --output-dir, helpMP4 文件的输出目录) parser.add_argument(-c, --config, help配置文件路径) parser.add_argument(-j, --jobs, typeint, help并行任务数, defaultDEFAULT_CONFIG[general][max_workers]) args parser.parse_args() try: converter TSConverter(args.config) if args.jobs: converter.config[general][max_workers] str(args.jobs) converter.process_directory(args.input_dir, args.output_dir) except Exception as e: print(f错误: {e}, filesys.stderr) sys.exit(1) if __name__ __main__: main()脚本特点跨平台支持Windows/Linux/macOS配置文件支持可自定义 FFmpeg 参数多线程并行处理自动重试失败的任务详细的进度显示和统计信息保留原始目录结构或自定义输出结构跳过已处理的文件使用方式确保系统已安装 Python 3.6安装依赖无额外依赖运行脚本# 基本用法 python ts_to_mp4_converter.py /path/to/ts/files # 指定输出目录和并行任务数 python ts_to_mp4_converter.py /input/path -o /output/path -j 8 # 使用配置文件 python ts_to_mp4_converter.py /input/path -c config.ini示例配置文件config.ini[general] max_workers 8 retry_times 3 output_suffix _converted preserve_structure true log_level info [ffmpeg] video_codec copy audio_codec copy extra_options -bsf:a aac_adtstoasc5. 高级技巧与问题排查在实际使用中可能会遇到各种问题。以下是一些常见问题的解决方案和高级技巧常见问题及解决方案问题现象可能原因解决方案转换后只有音频没有视频TS 文件中的视频编码不被 MP4 容器支持尝试使用-c:v libx264重新编码视频转换过程非常慢系统正在重新编码视频确保使用-c:v copy参数避免重新编码音频不同步TS 文件本身有问题尝试添加-async 1参数转换失败提示 Invalid data found文件损坏或不完整尝试使用-err_detect ignore_err忽略错误输出文件异常大可能没有正确复制流检查是否使用了-c copy参数性能优化技巧对于多核系统增加并行任务数可以显著提高处理速度使用 SSD 存储可以加快文件读写速度对于网络存储考虑先在本地处理再传输结果批量处理大量文件时可以按目录分批处理以减少内存占用高级 FFmpeg 参数# 硬件加速需要硬件支持 ffmpeg -hwaccel cuda -i input.ts -c:v h264_nvenc -c:a copy output.mp4 # 限制 CPU 使用防止系统卡顿 ffmpeg -i input.ts -c:v copy -c:a copy -threads 2 output.mp4 # 保留元数据 ffmpeg -i input.ts -c:v copy -c:a copy -map_metadata 0 output.mp4 # 处理时间戳问题 ffmpeg -i input.ts -c:v copy -c:a copy -fflags genpts output.mp4日志分析技巧 当转换失败时检查日志文件中的以下关键信息[error]开头的行Could not open file表示文件访问问题Invalid data found表示文件损坏Codec not supported表示编码格式问题Conversion failed!表示转换过程出错