戰(zhàn))
簡介這是一款面向普通用戶與音視頻愛好者的TS視頻合并工具專門解決瀏覽器下載視頻后碎片化、無法直接播放的問題。當(dāng)視頻以0.ts、1.ts、2.ts等連續(xù)序號(hào)形式散落在文件夾中時(shí)只需打開工具頁面輸入末尾序號(hào)并生成合并腳本即可將碎片快速拼接為完整MP4文件無需掌握命令行或?qū)I(yè)剪輯軟件屬于零門檻的實(shí)用小工具。資源包共5個(gè)文件包含html工具頁面、bat批處理腳本、txt使用手冊以及兩段wmv操作演示視頻壓縮包約1.54MB體積輕巧、解壓即用。目前已有2633人學(xué)習(xí)下載說明該方案在同類需求中具備一定認(rèn)可度。需要留意的是工具僅支持非加密且序號(hào)連續(xù)的ts文件若文件名帶有隨機(jī)前綴則需另行聯(lián)系作者處理。整體而言配套的圖文手冊與視頻教程能幫助讀者快速理解合并原理與排錯(cuò)思路適合需要批量整理本地緩存視頻的用戶參考使用。1. 從一堆 0.ts、1.ts 說起這類分片視頻到底怎么無損合并手里拿到一個(gè)文件夾里面躺著0.ts、1.ts、2.ts……一直到幾百號(hào)每個(gè)文件幾兆到幾十兆不等播放器單獨(dú)打開能放但拖進(jìn)度條就斷想發(fā)給別人又沒法一個(gè)個(gè)傳。這就是典型的 HLS 分片落地形態(tài)視頻被切成等長的 MPEG-TS 片段靠一個(gè)播放列表串起來順序播放。你要做的不是轉(zhuǎn)碼而是把這些碎片按正確順序拼回一個(gè)完整文件最好還不重新編碼、不掉畫質(zhì)、幾分鐘搞定。這件事的門檻比想象中低但坑比想象中多。順序錯(cuò)一個(gè)整段音畫就亂時(shí)間戳沒對齊合并后進(jìn)度條會(huì)跳封裝格式選錯(cuò)某些播放器直接黑屏。下面按「先搞懂 TS 是什么 → 選工具 → 寫命令 → 批量腳本 → 排錯(cuò) → 進(jìn)階校驗(yàn)」的路徑走一遍新手能照著復(fù)現(xiàn)熟手能直接拿去改。2. 先弄明白 TS 分片和合并的底層邏輯為什么不能直接 cat2.1 MPEG-TS 的結(jié)構(gòu)決定了「拼接」這件事的可行性MPEG-TS 全稱 MPEG Transport Stream設(shè)計(jì)初衷就是為傳輸而生。它把音視頻數(shù)據(jù)打成固定 188 字節(jié)的包每個(gè)包帶 PID 標(biāo)識(shí)屬于哪路流再靠 PTS/DTS 時(shí)間戳決定播放順序。關(guān)鍵點(diǎn)在于TS 本身就是為「流式拼接」設(shè)計(jì)的每個(gè)分片都是自包含的理論上按順序首尾相接就能還原成一條連續(xù)流。這也是為什么很多老工程師第一反應(yīng)是cat 0.ts 1.ts 2.ts out.ts。這個(gè)做法在純 TS 流上確實(shí)經(jīng)常能work因?yàn)?TS 沒有全局文件頭不需要像 MP4 那樣在開頭寫 moov box。但它有兩個(gè)致命前提分片必須嚴(yán)格按序、且時(shí)間戳連續(xù)。一旦中間缺片或者時(shí)間戳有跳變cat 出來的文件播放器就會(huì)卡死或音畫不同步。2.2 直接 cat 會(huì)在什么情況下翻車我踩過的典型場景有三個(gè)。第一分片不是從 0 開始編號(hào)比如從 100.ts 開始說明前面還有內(nèi)容直接 cat 會(huì)丟頭。第二分片之間存在重疊或間隙某些下載工具會(huì)重復(fù)寫入邊界幀cat 后出現(xiàn)花屏。第三音視頻 PID 在不同分片里不一致這種情況少見但一旦出現(xiàn)cat 出來的文件只有畫面沒聲音。所以結(jié)論是cat 可以作為快速驗(yàn)證手段但不能作為生產(chǎn)方案。真正可靠的合并要么用 ffmpeg 的 concat 協(xié)議要么先轉(zhuǎn)成 MPEG-TS 再走 concat demuxer讓工具去處理時(shí)間戳和邊界。2.3 合并前必須先做的三件事動(dòng)手之前花兩分鐘做檢查能省掉后面半小時(shí)的排錯(cuò)。第一確認(rèn)分片完整性??淳幪?hào)是否連續(xù)有沒有0.ts到N.ts中間缺號(hào)。缺號(hào)意味著數(shù)據(jù)不完整合并出來必然有斷點(diǎn)。第二確認(rèn)分片格式。用ffprobe抽查一兩個(gè)文件看容器是不是 mpegts、編碼是不是 h264/aac。有些站點(diǎn)會(huì)把分片偽裝成 .ts 但實(shí)際是 fMP4 片段這種就不能按 TS 處理。第三確認(rèn)播放列表。如果目錄里有.m3u8文件直接讀它里面#EXTINF的順序就是權(quán)威順序比按文件名排序靠譜得多。文件名排序在遇到10.ts和2.ts時(shí)會(huì)按字典序排成10在2前面這是新手最常翻的車。# 抽查分片格式確認(rèn)是 mpegts 容器 ffprobe -v error -show_entries formatformat_name -of defaultnoprint_wrappers1 0.ts # 查看分片時(shí)長和編碼確認(rèn)音視頻流都存在 ffprobe -v error -show_entries streamindex,codec_name,codec_type -of csv 0.ts # 如果有 m3u8直接看它的分片順序 grep -E \.ts playlist.m3u8 | head -20ffprobe的-v error只輸出錯(cuò)誤避免刷屏format_name返回mpegts就說明是標(biāo)準(zhǔn) TS。stream那行會(huì)列出每個(gè)流的 codec正常應(yīng)該看到一個(gè) h264 視頻流和一個(gè) aac 音頻流。如果只有視頻沒有音頻說明分片本身有問題合并也救不回來。3. 用 ffmpeg 合并 TSconcat 協(xié)議和 concat demuxer 怎么選3.1 兩種合并方式的本質(zhì)區(qū)別ffmpeg 提供兩條路。第一條是concat 協(xié)議寫法是concat:0.ts|1.ts|2.ts它把多個(gè)文件當(dāng)成一個(gè)虛擬輸入流底層直接做字節(jié)級(jí)拼接。優(yōu)點(diǎn)是快幾乎不消耗 CPU因?yàn)椴唤獯a。缺點(diǎn)是要求所有分片的編碼參數(shù)完全一致且對時(shí)間戳跳變?nèi)萑潭鹊?。第二條是concat demuxer需要先寫一個(gè)列表文件然后ffmpeg -f concat -i list.txt -c copy out.mp4。它會(huì)解析每個(gè)分片的時(shí)間戳并重新對齊容錯(cuò)性更好還能順便換封裝格式。缺點(diǎn)是比協(xié)議方式稍慢但依然是 copy 模式不重新編碼。我的選擇習(xí)慣是分片來自同一個(gè)源、編號(hào)連續(xù)、格式統(tǒng)一用 concat 協(xié)議一行命令搞定分片來源混雜、時(shí)間戳有跳變、或者要輸出 MP4用 concat demuxer。3.2 concat 協(xié)議的最小可用命令# 最簡形式按順序列出所有分片-c copy 表示不重新編碼 ffmpeg -i concat:0.ts|1.ts|2.ts|3.ts -c copy -bsf:a aac_adtstoasc output.mp4這里有幾個(gè)參數(shù)必須說清楚。-c copy是核心它告訴 ffmpeg 直接復(fù)制音視頻流不做解碼再編碼所以速度快、畫質(zhì)無損。-bsf:a aac_adtstoasc是音頻比特流過濾器作用是把 AAC 的 ADTS 頭轉(zhuǎn)成 ASC 格式因?yàn)?TS 里的 AAC 帶 ADTS 頭而 MP4 容器要求 ASC 格式不加這個(gè)參數(shù)輸出 MP4 時(shí)音頻會(huì)報(bào)錯(cuò)或直接丟失。如果輸出還是 .ts 格式這個(gè)過濾器可以不加。分片數(shù)量少的時(shí)候手寫管道符沒問題但幾百個(gè)分片就不現(xiàn)實(shí)了。這時(shí)候用 shell 拼字符串# 用 seq 生成 0 到 299 的分片列表拼成 concat 協(xié)議字符串 files$(seq -f %g.ts 0 299 | paste -sd |) ffmpeg -i concat:$files -c copy -bsf:a aac_adtstoasc output.mp4seq -f %g.ts 0 299生成0.ts到299.tspaste -sd |用豎線把它們連成一行。這個(gè)寫法比手敲可靠但前提是編號(hào)連續(xù)且從 0 開始。如果編號(hào)有跳號(hào)得先手動(dòng)核對。3.3 concat demuxer 的列表文件寫法# 生成 concat 列表file 后面跟路徑單引號(hào)防止特殊字符 for f in $(ls -v *.ts); do echo file $PWD/$f done list.txt # 用 concat demuxer 合并輸出 MP4 ffmpeg -f concat -safe 0 -i list.txt -c copy -bsf:a aac_adtstoasc output.mp4ls -v是關(guān)鍵它做的是自然排序version sort會(huì)把2.ts排在10.ts前面避免字典序的坑。-f concat指定使用 concat 解復(fù)用器-safe 0允許列表里出現(xiàn)絕對路徑不加這個(gè)ffmpeg 會(huì)拒絕非相對路徑。列表文件里每行file 路徑路徑用單引號(hào)包住防止空格和特殊字符出問題。提示如果分片文件名里有單引號(hào)需要轉(zhuǎn)義成\這種情況少見但遇到會(huì)報(bào)語法錯(cuò)誤。3.4 輸出格式怎么選TS 還是 MP4如果只是自己存檔、用支持 TS 的播放器看直接輸出.ts最省事不需要aac_adtstoasc也不會(huì)有封裝兼容問題。但如果要發(fā)給別人、上傳到常見平臺(tái)、或者用手機(jī)默認(rèn)播放器打開輸出 MP4 兼容性最好。輸出 MP4 時(shí)注意一點(diǎn)-c copy模式下如果原視頻是 h264、音頻是 aacMP4 完全支持直接封裝沒問題。但如果視頻是 h265老設(shè)備可能放不了這不是合并的問題是編碼兼容性問題需要重新編碼才能解決而重新編碼就違背了「無損快速合并」的初衷。4. 批量合并腳本從單目錄到多目錄的自動(dòng)化處理4.1 單目錄批量合并的 shell 腳本實(shí)際場景往往不是合并一個(gè)視頻而是一批。比如下載了十幾個(gè)課程每個(gè)課程一個(gè)文件夾里面都是 TS 分片。手敲命令不現(xiàn)實(shí)寫個(gè)腳本一次處理。#!/bin/bash # 遍歷當(dāng)前目錄下所有子目錄每個(gè)子目錄當(dāng)作一個(gè)視頻的分片集合 for dir in */; do # 去掉目錄名末尾的斜杠作為輸出文件名 name${dir%/} # 進(jìn)入目錄按自然序生成列表 cd $dir || continue if ls *.ts /dev/null 21; then for f in $(ls -v *.ts); do echo file $PWD/$f done list.txt # 輸出到上級(jí)目錄文件名用目錄名 ffmpeg -f concat -safe 0 -i list.txt -c copy -bsf:a aac_adtstoasc ../${name}.mp4 -y echo 完成: ${name}.mp4 else echo 跳過: ${dir} 沒有 ts 文件 fi cd .. done這個(gè)腳本的邏輯是外層循環(huán)遍歷所有子目錄內(nèi)層用ls -v生成自然序列表然后調(diào)用 ffmpeg 合并輸出到上級(jí)目錄并以目錄名命名。-y表示覆蓋已存在文件避免交互確認(rèn)卡住腳本。ls *.ts /dev/null 21用來判斷目錄里有沒有 TS 文件沒有就跳過防止空目錄報(bào)錯(cuò)。4.2 處理編號(hào)不連續(xù)的分片編號(hào)不連續(xù)是常見情況比如0.ts、1.ts、3.ts缺了2.ts。這時(shí)候直接合并會(huì)丟一段內(nèi)容但有時(shí)候缺的那片本來就沒下載成功你只能接受。腳本層面要做的是檢測缺號(hào)并提示而不是默默合并。# 檢測編號(hào)連續(xù)性輸出缺失的編號(hào) prev-1 for f in $(ls -v *.ts); do num${f%.ts} # 只處理純數(shù)字文件名 if [[ $num ~ ^[0-9]$ ]]; then if [[ $prev -ge 0 $((num - prev)) -ne 1 ]]; then echo 警告: $((prev1)) 到 $((num-1)) 之間缺片 fi prev$num fi done這段邏輯遍歷所有 TS 文件提取文件名里的數(shù)字檢查相鄰編號(hào)差值是否為 1。不是 1 就說明中間有缺號(hào)打印警告。[[ $num ~ ^[0-9]$ ]]用來過濾掉非純數(shù)字命名的文件避免誤判。4.3 用 Python 做更可控的批量處理shell 腳本適合簡單場景但如果要處理幾百個(gè)目錄、需要記錄日志、需要跳過已完成的Python 更合適。import os import subprocess from pathlib import Path def merge_ts_dir(src_dir, out_file): 合并單個(gè)目錄下的 TS 分片返回是否成功 ts_files sorted( [f for f in os.listdir(src_dir) if f.endswith(.ts)], keylambda x: int(x[:-3]) if x[:-3].isdigit() else 0 ) if not ts_files: return False list_path os.path.join(src_dir, list.txt) with open(list_path, w, encodingutf-8) as fp: for f in ts_files: fp.write(ffile {os.path.join(src_dir, f)}\n) cmd [ ffmpeg, -f, concat, -safe, 0, -i, list_path, -c, copy, -bsf:a, aac_adtstoasc, out_file, -y ] result subprocess.run(cmd, capture_outputTrue) return result.returncode 0 # 遍歷當(dāng)前目錄下所有子目錄 for d in sorted(os.listdir(.)): if os.path.isdir(d): out fdrlblxrh1.mp4 if os.path.exists(out): print(f已存在跳過: {out}) continue ok merge_ts_dir(d, out) print(f{成功 if ok else 失敗}: {out})關(guān)鍵點(diǎn)在排序函數(shù)keylambda x: int(x[:-3])把文件名去掉.ts后轉(zhuǎn)成整數(shù)排序這樣10.ts會(huì)正確排在2.ts后面。subprocess.run用capture_outputTrue捕獲輸出避免 ffmpeg 的日志刷屏同時(shí)通過returncode判斷成功與否。跳過已存在文件的邏輯讓腳本可以中斷后重跑不用從頭再來。5. 合并 TS 分片最容易踩的坑排查清單5.1 合并后音畫不同步進(jìn)度條亂跳現(xiàn)象合并出來的文件能播放但聲音比畫面快或慢幾秒拖進(jìn)度條時(shí)時(shí)間顯示異常。原因分片之間的 PTS 時(shí)間戳不連續(xù)concat 協(xié)議直接拼接時(shí)沒有重新對齊時(shí)間戳播放器按錯(cuò)誤的時(shí)間軸解碼。解決改用 concat demuxer它會(huì)重新計(jì)算時(shí)間戳。如果還不行加-fflags genpts讓 ffmpeg 重新生成 PTSffmpeg -f concat -safe 0 -i list.txt -c copy -fflags genpts -bsf:a aac_adtstoasc output.mp45.2 輸出 MP4 后沒有聲音現(xiàn)象合并過程沒報(bào)錯(cuò)但輸出文件只有畫面音頻軌缺失。原因TS 里的 AAC 音頻帶 ADTS 頭MP4 容器不認(rèn)ffmpeg 在 copy 模式下不會(huì)自動(dòng)轉(zhuǎn)換導(dǎo)致音頻流被丟棄。解決加上-bsf:a aac_adtstoasc。如果加了還是沒聲音用ffprobe檢查原分片有沒有音頻流可能源頭就沒有。5.3 分片順序錯(cuò)亂畫面跳來跳去現(xiàn)象合并后內(nèi)容順序不對比如第 10 分鐘的內(nèi)容跑到了第 2 分鐘。原因用了字典序排序10.ts排在了2.ts前面。解決用ls -v或 Python 的整數(shù)排序。如果目錄里有 m3u8直接按 m3u8 里的順序生成列表那是最權(quán)威的。5.4 ffmpeg 報(bào) Non-monotonous DTS 警告現(xiàn)象合并時(shí)刷大量Non-monotonous DTS in output stream警告輸出文件可能仍有問題。原因分片的時(shí)間戳有回退或重復(fù)常見于從不同來源下載的分片混在一起。解決這種警告在 copy 模式下無法完全消除因?yàn)?ffmpeg 不能改時(shí)間戳??尚械霓k法是先轉(zhuǎn)成 MPEG-TS 中間格式再合并或者接受警告但檢查輸出是否可正常播放。如果嚴(yán)重影響播放只能重新編碼用-c:v libx264 -c:a aac犧牲速度換正確性。5.5 合并后文件巨大或異常小現(xiàn)象輸出文件比所有分片加起來還大或者小得離譜。原因文件巨大通常是誤用了重新編碼且碼率設(shè)太高文件異常小通常是 concat 列表只包含了一部分分片或者路徑寫錯(cuò)導(dǎo)致 ffmpeg 跳過了文件。解決檢查 list.txt 的行數(shù)是否等于分片數(shù)檢查每條路徑是否真實(shí)存在。用-c copy確認(rèn)沒有觸發(fā)編碼。正常情況輸出大小應(yīng)該約等于所有分片大小之和。6. 合并之后的校驗(yàn)與進(jìn)階技巧別讓幾小時(shí)下載毀在最后一步合并完成不等于萬事大吉。我一般會(huì)做三件事校驗(yàn)時(shí)長、抽查關(guān)鍵幀、確認(rèn)音視頻軌完整。時(shí)長校驗(yàn)最直接用ffprobe讀輸出文件的 duration和所有分片時(shí)長之和對比誤差在幾秒內(nèi)算正常。如果差太多說明有分片沒被合并進(jìn)去。# 查看合并后文件的總時(shí)長和流信息 ffprobe -v error -show_entries formatduration -show_entries streamcodec_type,codec_name -of defaultnoprint_wrappers1 output.mp4抽查關(guān)鍵幀用ffmpeg截幾張圖看畫面是否正常、有沒有花屏。尤其是分片邊界處最容易出問題。# 在 10%、50%、90% 位置各截一幀檢查畫面 ffmpeg -ss 00:10:00 -i output.mp4 -frames:v 1 check1.png -y ffmpeg -ss 00:30:00 -i output.mp4 -frames:v 1 check2.png -y進(jìn)階一點(diǎn)的做法是如果分片數(shù)量特別多上千個(gè)concat 列表會(huì)很長ffmpeg 解析列表本身會(huì)變慢。這時(shí)候可以分批合并先每 100 個(gè)分片合并成一個(gè)中間 TS再把中間 TS 合并成最終文件。這樣每步的列表都短整體更快而且中間產(chǎn)物還能作為斷點(diǎn)續(xù)做的依據(jù)。另一個(gè)技巧是保留原始分片直到確認(rèn)輸出無誤。我吃過虧合并完覺得沒問題就把分片刪了結(jié)果發(fā)現(xiàn)輸出文件在某個(gè)播放器上不兼容想重來已經(jīng)沒有原料了。分片占的空間雖然不小但在確認(rèn)最終文件能正常播放、時(shí)長正確、音畫同步之前別急著清理。最后一個(gè)習(xí)慣合并命令和 list.txt 一起存到輸出目錄旁邊。過幾個(gè)月回頭看能立刻知道這個(gè)文件是怎么來的、用的什么參數(shù)。這比事后回憶靠譜得多。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取