Python調(diào)用ffmpeg截取視頻片段并進(jìn)行批量處理的方法
背景
我本地下載了一些番劇,但是片頭片尾無用還占空間,因此決定使用ffmpeg對視頻切割,只保留中間的正片內(nèi)容。
用到的ffmpeg命令
ffmpeg
中文文檔:https://ffmpeg.github.net.cn/ffmpeg.html
?????先說用到的ffmpeg
命令,你可以自行在cmd窗口中執(zhí)行以處理單個(gè)視頻。
- 獲取視頻時(shí)長:
ffprobe -show_entries format=duration -v error -select_streams v:0 <視頻路徑>
示例輸出:
[FORMAT] duration=1200.981333 [/FORMAT]
ffmpeg -threads 4 -i <視頻路徑> -ss <開始時(shí)間點(diǎn)> -t <持續(xù)時(shí)間(s)> -c copy -y <輸出路徑>
-threads 4
:多線程,感覺作用不是很大<開始時(shí)間點(diǎn)>
:格式可以為hh:mm:ss
,也可以為具體的視頻第幾秒<持續(xù)時(shí)間(s)>
:從指定的開始時(shí)間點(diǎn)往后截取多少秒,不是結(jié)束時(shí)間點(diǎn)-c copy
:音視頻都直接復(fù)制,不重新編碼,速度快-y
:如果指定的輸出文件已經(jīng)存在,則覆蓋- 注意:如果輸出路徑在其他文件夾內(nèi),則必須提前創(chuàng)建好文件夾,比如輸出文件為
./trimed_video/video.mp4
,則需要提前創(chuàng)建好trimed_video
文件夾,否則報(bào)錯(cuò)
python調(diào)用bash指令的方法
使用subprocess
包,詳細(xì)信息可以自己查,簡單用法如下:
import subprocess command = "ffprobe -show_entries format=duration -v error -select_streams v:0 video.mp4" result = subprocess.run(command, check=True,capture_output=True,text=True) print(result.stdout) # 獲取命令執(zhí)行后的輸出數(shù)據(jù)
參數(shù)說明:
command
:要執(zhí)行的命令字符串,可以通過字符串操作來拼接需要的命令check=True
:如果進(jìn)程退出碼不為0,則拋出異常subprocess.CalledProcessError
,可以通過try-catch進(jìn)行錯(cuò)誤處理capture_output=True
:捕獲標(biāo)準(zhǔn)輸出或標(biāo)準(zhǔn)錯(cuò)誤,獲取命令執(zhí)行的輸出信息text=True
:默認(rèn)標(biāo)準(zhǔn)輸出為字節(jié)類型,這個(gè)可以改為字符串類型,方便字符串解析
python處理代碼
注意,我所有代碼都沒考慮時(shí)長超過1小時(shí)的視頻,如果需要操作1小時(shí)以上的視頻,請自行修改相關(guān)代碼
準(zhǔn)備函數(shù)
由于這兩個(gè)函數(shù)可能在多個(gè)文件中使用,因此單獨(dú)創(chuàng)建了一個(gè)文件,其他文件需要調(diào)用時(shí)通過import myfunc
即可
"""myfunc.py""" import os,re from pathlib import Path def match_files(dir,extension,content_range=[], not_content_range=[]): """在指定目錄下找符合擴(kuò)展名的文件,不遞歸 用法示例:match_files("./",[".mp4", ".mkv"],range(74,80+1)) extension:需要的擴(kuò)展名,字符串列表 content_range:包含的范圍,為空則不限制,整數(shù)列表 not_content_range:不包含的范圍,整數(shù)列表 """ matchs = [] files = os.listdir(dir) for f in files: if Path(f).suffix in extension: # 檢查文件擴(kuò)展名是否在指定的擴(kuò)展名列表中 # 提取文件名中的第一個(gè)數(shù)字序列作為編號 number = int(re.findall(r'\d+',f)[0]) if content_range:# 判斷是否指定了包含范圍,如果指定則判斷是否在范圍內(nèi) if number in content_range and number not in not_content_range : matchs.append(f) else: # 如果不指定范圍,則匹配所有 if number not in not_content_range : matchs.append(f) return matchs def time_to_seconds(time_str): """將時(shí)間字符串轉(zhuǎn)換為秒,格式mm:ss""" minutes, seconds = map(int, time_str.split(':')) return minutes * 60 + seconds
python批量處理
import myfunc import subprocess import re """ 注意寫好路徑,擴(kuò)展名,以及需要處理的序號范圍,排除的序號范圍 """ videos = myfunc.match_files("./",[".mp4", ".mkv"],[140]) start_time_point = "02:35" end_time_point = "17:42" for video in videos: # 如果文件名有空格,需要加引號 command1 = "ffprobe -show_entries format=duration -v error -select_streams v:0 \""+video+"\"" try: # 先獲取視頻時(shí)長 result = subprocess.run(command1, check=True,capture_output=True,text=True) duration = round(float(re.search(r"duration=([\d.]+)",result.stdout).group(1))) """注意修改command2的參數(shù), 00默認(rèn)小時(shí)為00,即不考慮時(shí)長超過1小時(shí)的情況,按需修改 "\"./trimed_video/"+video+"\""是輸出視頻路徑 需要根據(jù)自己的視頻情況修改 """ command2 = "ffmpeg -threads 4 -i "+"\""+ video +"\""+ " -ss 00:" + start_time_point + " -t "+str(myfunc.time_to_seconds(end_time_point)-myfunc.time_to_seconds(start_time_point)) +" -c copy -y "+"\"./trimed_video/"+video+"\"" try: # 運(yùn)行FFmpeg命令 subprocess.run(command2, check=True,capture_output=True) print(f"視頻已成功裁剪到 {video}") except subprocess.CalledProcessError as e: print(f"FFmpeg命令執(zhí)行失敗: {e}", video) except subprocess.CalledProcessError as e: print(f"FFmpeg命令執(zhí)行失敗: {e}", video)
特殊情況處理
可能視頻的片頭和片尾時(shí)長并不總是固定的,導(dǎo)致不能方便地按照 python批量處理的代碼,直接按相同的片頭長度和片尾長度操作,因此我使用了csv表格來記錄視頻的片頭片尾長度,并使用python按照csv表格內(nèi)的數(shù)據(jù)裁剪視頻。
csv表格示例內(nèi)容如下片頭片尾信息.csv
???序號
,片頭時(shí)間點(diǎn)
,片尾時(shí)間點(diǎn)
,總時(shí)長
是必填項(xiàng),剩余兩項(xiàng)可以空著,但是必須填寫英文分號。
其實(shí)總時(shí)長
可以通過ffmpeg命令獲取,但是既然是特殊情況,手動打開視頻了,填一下總時(shí)長也不麻煩
可選操作:填補(bǔ)csv數(shù)據(jù)
有時(shí)候需要填寫幾個(gè)視頻信息,來判斷這兩個(gè)序號之間的視頻是不是片頭片尾時(shí)長一樣,如果一樣就可以通過python批量處理的代碼來操作,因此寫了下面的代碼,可以自動計(jì)算csv表格中的最后兩列數(shù)據(jù),觀察片頭時(shí)間點(diǎn)
和片尾長度
是否一直可以粗略判斷
import csv # 文件路徑 file_path = "./片頭片尾信息.csv" # 將時(shí)間字符串轉(zhuǎn)換為秒 def time_to_seconds(time_str): minutes, seconds = map(int, time_str.split(':')) return minutes * 60 + seconds # 讀取CSV文件 with open(file_path, mode='r', encoding='utf-8') as file: reader = csv.reader(file) rows = list(reader) rows = [row for row in rows if row] for i in range(1, len(rows)): end_time_str = rows[i][2] if rows[i][4] != '': continue # 如果已經(jīng)有值了,則不再計(jì)算 total_duration_str = rows[i][3] end_time_seconds = time_to_seconds(end_time_str) total_duration_seconds = time_to_seconds(total_duration_str) tail_length_seconds = total_duration_seconds - end_time_seconds rows[i][4] = str(tail_length_seconds) start_time_seconds = time_to_seconds(rows[i][1]) rows[i][5] = str(end_time_seconds - start_time_seconds) # 將更新后的內(nèi)容寫回CSV文件 with open(file_path, mode='w', newline='', encoding='utf-8') as file: writer = csv.writer(file) writer.writerows(rows)
python根據(jù)csv數(shù)據(jù)裁剪視頻
import csv # 文件路徑 file_path = "./片頭片尾信息.csv" # 將時(shí)間字符串轉(zhuǎn)換為秒 def time_to_seconds(time_str): minutes, seconds = map(int, time_str.split(':')) return minutes * 60 + seconds # 讀取CSV文件 with open(file_path, mode='r', encoding='utf-8') as file: reader = csv.reader(file) rows = list(reader) rows = [row for row in rows if row] for i in range(1, len(rows)): end_time_str = rows[i][2] if rows[i][4] != '': continue # 如果已經(jīng)有值了,則不再計(jì)算 total_duration_str = rows[i][3] end_time_seconds = time_to_seconds(end_time_str) total_duration_seconds = time_to_seconds(total_duration_str) tail_length_seconds = total_duration_seconds - end_time_seconds rows[i][4] = str(tail_length_seconds) start_time_seconds = time_to_seconds(rows[i][1]) rows[i][5] = str(end_time_seconds - start_time_seconds) # 將更新后的內(nèi)容寫回CSV文件 with open(file_path, mode='w', newline='', encoding='utf-8') as file: writer = csv.writer(file) writer.writerows(rows)
python根據(jù)csv數(shù)據(jù)裁剪視頻
import myfunc import csv, re,subprocess """注意修改你想要匹配的文件擴(kuò)展名""" videos = myfunc.match_files("./",[".mp4", ".mkv"]) """注意改成你的csv文件路徑""" with open("./片頭片尾信息.csv", mode='r', encoding='utf-8') as file: reader = csv.reader(file) rows = list(reader) # 提取第一列數(shù)據(jù) del rows[0]# 刪除表頭 first_column = [int(row[0]) for row in rows if row] # 使用列表推導(dǎo)式,跳過空行 videos = [video for video in videos if int(re.findall(r'\d+',video)[0]) in first_column] count = 0 for video in videos: command1 = "ffprobe -show_entries format=duration -v error -select_streams v:0 \""+video+"\"" try: # 先獲取視頻時(shí)長 result = subprocess.run(command1, check=True,capture_output=True,text=True) duration = round(float(re.search(r"duration=([\d.]+)",result.stdout).group(1))) start_time_pint = myfunc.time_to_seconds(rows[count][1]) end_time_pount = myfunc.time_to_seconds(rows[count][2]) """注意替換你想要的輸出路徑""" command2 = "ffmpeg -threads 4 -i "+video + " -ss " + str(start_time_pint) + " -t "+str(end_time_pount-start_time_pint) +" -c copy -y "+"\"./trimed_video/"+video+"\"" # print(command2) try: # 運(yùn)行FFmpeg命令 subprocess.run(command2, check=True,capture_output=True) print(f"視頻已成功裁剪到 {video}") except subprocess.CalledProcessError as e: print(f"FFmpeg命令執(zhí)行失敗: {e}", video) except subprocess.CalledProcessError as e: print(f"FFmpeg命令執(zhí)行失敗: {e}", video) count += 1
視頻具有多個(gè)片段的處理 [TODO]
TODO有大佬知道的話歡迎討論,我覺得先切片再合并太麻煩。
這種特殊情況一般出現(xiàn)在,視頻有彩蛋之類的,在片頭之前或片尾之后仍有正片內(nèi)容。
網(wǎng)上搜了但沒找到特別好的,找到一個(gè)文章但測試后不好用,所以選擇了手動切片再合并,
多個(gè)視頻合并的ffmpeg命令:
- 創(chuàng)建文本文件
filelist.txt
,并寫入要合并的多個(gè)視頻片段
file 'input1.mp4' file 'input2.mp4'
- 執(zhí)行合并命令:
ffmpeg -f concat -safe 0 -i filelist.txt -c copy output.mp4
由于這種情況比較少,懶得寫python代碼,自己手動在cmd執(zhí)行吧
以上就是Python調(diào)用ffmpeg截取視頻片段并進(jìn)行批量處理方法的詳細(xì)內(nèi)容,更多關(guān)于Python ffmpeg截取視頻批量處理的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Numpy實(shí)現(xiàn)按指定維度拼接兩個(gè)數(shù)組的實(shí)現(xiàn)示例
Numpy提供了多個(gè)函數(shù)來拼接數(shù)組,其中最常用的是np.concatenate、np.vstack、np.hstack等,本文就來介紹一下Numpy實(shí)現(xiàn)按指定維度拼接兩個(gè)數(shù)組的實(shí)現(xiàn),感興趣的可以了解一下2024-03-03python 實(shí)現(xiàn)一個(gè)圖形界面的匯率計(jì)算器
這篇文章主要介紹了python 實(shí)現(xiàn)一個(gè)圖形界面的匯率計(jì)算器,幫助大家更好的理解和學(xué)習(xí)如何制作gui程序,感興趣的朋友可以了解下2020-11-11