我的用例需要将分布在 16 个文件夹中的一堆 .mp3 文件连接到每个文件夹一个 .mp3 中。ffmpeg.org和ffmpeg-python上的文档被发现有些缺乏或令人困惑......
我希望能够创建一个 ffmpeg 对象,然后将一个列表或将单个路径对象迭代到 ffmpeg 的输入中,然后在输入对象上运行 concat,然后输出到一个目的地,但ffmpeg.input(<FILE LIST>).concat(acodec='copy').output(<OUTFILE>).run()
最终成为一个巨大的失败球.. . 直到我遇到@CCfVssZijV2X的帖子,其中包含 concat 输入格式,它为我破解了整个事情。
下面的脚本将基本路径作为输入。假设基本路径包含包含多个音频文件的文件夹,这些文件将作为基本路径中的文件夹名称连接到每个文件夹中的单个 .mp3 中。
如果基本路径仅包含一个文件夹,则将文件夹批处理直接发送到 combine_audio,否则将批处理列表发送到多处理功能,该功能将每个批处理拆分为您拥有或可用的内核数量。
虽然我确信我与 python 包装器有关的一些问题的原因与我对 ffmpeg 的无知和不熟悉有关,但我希望这可以帮助某人,或者让他们开始为他们的用例走上正确的道路。
from concurrent.futures import ProcessPoolExecutor
from pathlib import Path as p
from os import cpu_count
import ffmpeg
class FFConcat:
def __init__(self, path, check_files=False):
self.check_files = check_files
self.path = p(path)
self.cores = cpu_count()
self.batches = []
def file_check(self):
'''
Optional:
Iterates over folders in path, renames with leading zero for sorting if missing.
'''
for folder in sorted(self.path.iterdir()):
# Folder names were originally "Folder Name - [Disk 1]"
disk_number = folder.stem.split()[-1].strip(']')
if len(disk_number) == 1:
folder.rename('{}/Folder Name - Disk 0{}'.format(self.path, disk_number))
elif len(disk_number) == 2:
folder.rename('{}/Folder Name - Disk {}'.format(self.path, disk_number))
def file_batch(self):
'''
Iterates over folders in path creating a list. Converts list into string format
which is combined with resulting output filename in a dict and added to
batch list.
'''
print('Batching audio files by folder.')
for folder in sorted(self.path.iterdir()):
# Use folder names as concat output name for final file.
outfile = (self.path/'{}.mp3'.format(folder.stem)).as_posix()
tracks = []
# Create a list of sorted tracks in folder.
for track in sorted(folder.iterdir()):
tracks.append(track.as_posix())
print('Located {} audio files in \"{}\"'.format(len(tracks), folder.stem))
# Format file list in string format which ffmpeg will accept via input.
file_list = '|'.join(_ for _ in tracks)
# Generate list of dictionaries containing the file list and output filemame
self.batches.append({
'file_list': file_list,
'outfile': outfile
})
def combine_audio(self, batch):
'''
Input: single dictionary containing a string formatted file list for each folder
and output filename. Converts list into ffmpeg input concat object. Runs object
with audio codec copy concatenating files within folder into single file.
'''
print('Starting concat for: {}'.format(batch['outfile']))
tracks = ffmpeg.input('concat:{}'.format(batch['file_list']))
tracks.output(batch['outfile'], acodec='copy').run()
print('Completed concat for: {}'.format(batch['outfile']))
def mp(self, function, iterable):
'''
Input: combine_audio function and batch list.
Sets max workers depending on iterable length and core count.
'''
if len(iterable) >= self.cores:
workers = self.cores
elif len(iterable) < self.cores:
workers = len(iterable)
with ProcessPoolExecutor(max_workers=workers) as p:
p.map(function, iterable)
def run(self):
if self.check_files:
self.file_check()
self.file_batch()
if len(self.batches) == 1:
print('One batch found. Sending directly to concatenator.')
self.combine_audio(self.batches[0])
elif len(self.batches) > 1:
print('Sending {} batches to multi-processing.'.format(len(self.batches)))
self.mp(self.combine_audio, self.batches)
concat = FFConcat(path='Base path containing folders with tracks')
concat.run()
最后需要连接一些视频,并且看到其中一条评论询问了这一点,这是我想出的将两个大文件连接在一起的快速而肮脏的方法。
from pathlib import Path as p
import ffmpeg
# point basepath to input directory where videos are located. ideally without anything else.
basepath = p('/video_input_directory')
# path iterator to grab input files in basepath, and create a posix filename list.
# you can keep, modify, or drop the suffix filter.
input_files = sorted([
_.as_posix() for _ in basepath.iterdir()
if _.is_file() and _.suffix == '.mp4'
])
# set output filename
outFile = basepath / 'concated.mp4'
# create file object that will contain files for ffmpeg to concat
input_files_path = basepath / 'input_files.txt'
# iterate over sorted posix files in list, and dump to file in the required format
with input_files_path.open('w') as f:
for file in input_files:
f.write('file \'{}\'\n'.format(file))
# this seems to be the proper concat input, with the path containing the list
# of files for ffmpeg to concat, along with the format parameter, and safe, if i
# read the docs correctly, is default/optional
ffInput = ffmpeg.input(input_file_path.as_posix(), format='concat', safe=0)
# output parameters
params = {
'c': 'copy'
}
# input stream -> output stream with output filename and expanded params
ffOutput = ffInput.output(ouFile.as_posix(), **params)
# make ffmpeg quiet
ffOutput = ffOutput.global_args('-loglevel', 'error')
# something, something, run.
ffOutput.run(overwrite_output=True)
连接视频时有很多 *,所以一定要查看ffmpeg 网站上的 concat 部分,以及这个 ffmpeg concat wiki。