将多个 *.ts 文件(字节或类字节格式)连接到一个 mp4 文件
Concatenate multiple *.ts Files (byte or byte-like-Format) to one mp4 File
我用scrapy写了一个下载器,可以异步下载多个ts文件。现在我正在尝试连接并转换为 mp4。我写了下面的代码,但视频顺序似乎不正确,mp4 文件完全乱七八糟,无法观看。
import ffmpeg
if all_files_downloaded:
BASE_URL=f"/test{i}"
process = ffmpeg.input('pipe:').output(f'{BASE_URL}/video.mp4', vcodec='h264_nvenc').overwrite_output().run_async(pipe_stdin=True)
for snippets in sorted(self.parallelviddeos.get(id)):
self.parallelviddeos.get(id)[snippets].seek(0)
process.stdin.write(self.parallelviddeos.get(id)[snippets].read())
process.stdin.close()
process.wait()
字典 'parallelviddeos' 以 id 作为键和 ByteIO 对象列表(*.ts 文件)
作为相应视频的输入看起来像这样
parallelviddeos={id1:{0:BytesIO,2:BytesIO,3:BytesIO,4:BytesIO},id2:{0:BytesIO,...}}
谁能帮我把 BytesIO-Part 转换成完整的 mp4 文件。
我正在寻找基于管道的方法。
我正在获取 ts 文件作为字节格式请求的响应。
我们可能不认为以这种方式将 TS 文件写入管道。
如果没有 PIPE,我们可以使用 concat
options.
之一
我认为最安全的选择是使用另一个 FFmpeg sub-process 将 TS 解码为原始视频帧,然后逐帧写入管道。
你可以从我下面的post开始,循环执行
这是一个“自包含”代码示例:
import ffmpeg
import numpy as np
import cv2 # Use OpenCV for testing
import io
import subprocess as sp
import threading
from functools import partial
out_filename = 'video.mp4'
# Build synthetic input, and read into BytesIO
###############################################
# Assume we know the width and height from advance
# (In case you don't know the resolution, I posted solution for getting it using FFprobe).
width = 192
height = 108
fps = 10
n_frames = 100
in_filename1 = 'in1.ts'
in_filename2 = 'in2.ts'
# Build synthetic video (in1.ts) for testing:
(
ffmpeg
.input(f'testsrc=size={width}x{height}:rate=1:duration={n_frames}', f='lavfi', r=fps)
.filter('setpts', f'N/{fps}/TB')
.output(in_filename1, vcodec='libx264', crf=17, pix_fmt='yuv420p', loglevel='error')
.global_args('-hide_banner')
.overwrite_output()
.run()
)
# Build synthetic video (in1.ts) for testing:
(
ffmpeg
.input(f'mandelbrot=size={width}x{height}:rate=1', f='lavfi', r=fps)
.filter('setpts', f'N/{fps}/TB')
.output(in_filename2, vcodec='libx264', crf=17, pix_fmt='yuv420p', loglevel='error', t=n_frames)
.global_args('-hide_banner')
.overwrite_output()
.run()
)
# Read the file into in-memory binary streams
with open(in_filename1, 'rb') as f:
in_bytes = f.read()
stream1 = io.BytesIO(in_bytes)
# Read the file into in-memory binary streams
with open(in_filename2, 'rb') as f:
in_bytes = f.read()
stream2 = io.BytesIO(in_bytes)
# Use list instead of dictionary (just for the example).
in_memory_viddeos = [stream1, stream2]
###############################################
# Writer thread: Write to stdin in chunks of 1024 bytes
def writer(decoder_process, stream):
for chunk in iter(partial(stream.read, 1024), b''):
decoder_process.stdin.write(chunk)
decoder_process.stdin.close()
def decode_in_memory_and_re_encode(vid_bytesio):
""" Decode video in BytesIO, and write the decoded frame into the encoder sub-process """
vid_bytesio.seek(0)
# Execute video decoder sub-process
decoder_process = (
ffmpeg
.input('pipe:') #, f='mpegts', vcodec='h264')
.video
.output('pipe:', format='rawvideo', pix_fmt='bgr24')
.run_async(pipe_stdin=True, pipe_stdout=True)
)
thread = threading.Thread(target=writer, args=(decoder_process, vid_bytesio))
thread.start()
# Read decoded video (frame by frame), and display each frame (using cv2.imshow for testing)
while True:
# Read raw video frame from stdout as bytes array.
in_bytes = decoder_process.stdout.read(width * height * 3)
if not in_bytes:
break
# Write the decoded frame to the encoder.
encoder_process.stdin.write(in_bytes)
# transform the byte read into a numpy array (for testing)
in_frame = np.frombuffer(in_bytes, np.uint8).reshape([height, width, 3])
# Display the frame (for testing)
cv2.imshow('in_frame', in_frame)
cv2.waitKey(10)
thread.join()
decoder_process.wait()
# Execute video encoder sub-process
encoder_process = (
ffmpeg
.input('pipe:', r=fps, f='rawvideo', s=f'{width}x{height}', pixel_format='bgr24')
.video
.output(out_filename, vcodec='libx264', crf=17, pix_fmt='yuv420p')
.overwrite_output()
.run_async(pipe_stdin=True)
)
# Re-encode the "in memory" videos in a loop
for memvid in in_memory_viddeos:
decode_in_memory_and_re_encode(memvid)
encoder_process.stdin.close()
encoder_process.wait()
cv2.destroyAllWindows()
抱歉忽略了你的字典结构。
我假设问题与视频编码有关,而不是与您迭代 BytesIO 的方式有关 objects.
更新:
正在从 in-memory 视频流中读取 width
和 height
:
选项很少。
我决定从 BMP 图像的 header 中读取 width
和 height
。
使用以下参数(image2pipe
格式和 bmp
视频编解码器)启动 FFmpeg sub-process:
decoder_process = ffmpeg.input('pipe:'), f='mpegts', vcodec='h264').video.output('pipe:',f='image2pipe', vcodec='bmp').run_async(pipe_stdin=True, pipe_stdout=True)
解析header得到with
和height
:
in_bytes = decoder_process.stdout.read(54) # 54 bytes BMP Header + 4 bytes
(width, height) = struct.unpack("<ll", in_bytes[18:26])
完整代码示例:
import ffmpeg
import numpy as np
import cv2 # Use OpenCV for testing
import io
import subprocess as sp
import threading
from functools import partial
import struct
out_filename = 'video.mp4'
# Build synthetic input, and read into BytesIO
###############################################
# Assume we know the width and height from advance
width = 192
height = 108
fps = 10
n_frames = 100
in_filename1 = 'in1.ts'
in_filename2 = 'in2.ts'
## Build synthetic video (in1.ts) for testing:
#(
# ffmpeg
# .input(f'testsrc=size={width}x{height}:rate=1:duration={n_frames}', f='lavfi', r=fps)
# .filter('setpts', f'N/{fps}/TB')
# .output(in_filename1, vcodec='libx264', crf=17, pix_fmt='yuv420p', loglevel='error')
# .global_args('-hide_banner')
# .overwrite_output()
# .run()
#)
## Build synthetic video (in1.ts) for testing:
#(
# ffmpeg
# .input(f'mandelbrot=size={width}x{height}:rate=1', f='lavfi', r=fps)
# .filter('setpts', f'N/{fps}/TB')
# .output(in_filename2, vcodec='libx264', crf=17, pix_fmt='yuv420p', loglevel='error', t=n_frames)
# .global_args('-hide_banner')
# .overwrite_output()
# .run()
#)
# Read the file into in-memory binary streams
with open(in_filename1, 'rb') as f:
in_bytes = f.read()
stream1 = io.BytesIO(in_bytes)
# Read the file into in-memory binary streams
with open(in_filename2, 'rb') as f:
in_bytes = f.read()
stream2 = io.BytesIO(in_bytes)
# Use list instead of dictionary (just for the example).
in_memory_viddeos = [stream1, stream2]
###############################################
# Writer thread: Write to stdin in chunks of 1024 bytes
def writer(decoder_process, stream):
for chunk in iter(partial(stream.read, 1024), b''):
try:
decoder_process.stdin.write(chunk)
except (BrokenPipeError, OSError):
# get_in_memory_video_frame_size causes BrokenPipeError exception and OSError exception.
# This in not a clean solution, but it's the simplest I could find.
return
decoder_process.stdin.close()
def get_in_memory_video_frame_size(vid_bytesio):
""" Get the resolution of a video in BytesIO """
vid_bytesio.seek(0)
# Execute video decoder sub-process, the output format is BMP
decoder_process = (
ffmpeg
.input('pipe:') #, f='mpegts', vcodec='h264')
.video
.output('pipe:', f='image2pipe', vcodec='bmp')
.run_async(pipe_stdin=True, pipe_stdout=True)
)
thread = threading.Thread(target=writer, args=(decoder_process, vid_bytesio))
thread.start()
# Read raw video frame from stdout as bytes array.
# https://en.wikipedia.org/wiki/BMP_file_format
in_bytes = decoder_process.stdout.read(54) # 54 bytes BMP Header + 4 bytes
decoder_process.stdout.close()
thread.join()
decoder_process.wait()
vid_bytesio.seek(0)
# The width and height are located in bytes 18 to 25 (4 bytes each)
(width, height) = struct.unpack("<ll", in_bytes[18:26])
return width, abs(height)
def decode_in_memory_and_re_encode(vid_bytesio):
""" Decode video in BytesIO, and write the decoded frame into the encoder sub-process """
vid_bytesio.seek(0)
# Execute video decoder sub-process
decoder_process = (
ffmpeg
.input('pipe:') #, f='mpegts', vcodec='h264')
.video
.output('pipe:', format='rawvideo', pix_fmt='bgr24')
.run_async(pipe_stdin=True, pipe_stdout=True)
)
thread = threading.Thread(target=writer, args=(decoder_process, vid_bytesio))
thread.start()
# Read decoded video (frame by frame), and display each frame (using cv2.imshow for testing)
while True:
# Read raw video frame from stdout as bytes array.
in_bytes = decoder_process.stdout.read(width * height * 3)
if not in_bytes:
break
# Write the decoded frame to the encoder.
encoder_process.stdin.write(in_bytes)
# transform the byte read into a numpy array (for testing)
in_frame = np.frombuffer(in_bytes, np.uint8).reshape([height, width, 3])
# Display the frame (for testing)
cv2.imshow('in_frame', in_frame)
cv2.waitKey(10)
thread.join()
decoder_process.wait()
width, height = get_in_memory_video_frame_size(in_memory_viddeos[0])
# Execute video encoder sub-process
encoder_process = (
ffmpeg
.input('pipe:', r=fps, f='rawvideo', s=f'{width}x{height}', pixel_format='bgr24')
.video
.output(out_filename, vcodec='libx264', crf=17, pix_fmt='yuv420p')
.overwrite_output()
.run_async(pipe_stdin=True)
)
# Re-encode the "in memory" videos in a loop
for memvid in in_memory_viddeos:
decode_in_memory_and_re_encode(memvid)
encoder_process.stdin.close()
encoder_process.wait()
cv2.destroyAllWindows()
我用scrapy写了一个下载器,可以异步下载多个ts文件。现在我正在尝试连接并转换为 mp4。我写了下面的代码,但视频顺序似乎不正确,mp4 文件完全乱七八糟,无法观看。
import ffmpeg
if all_files_downloaded:
BASE_URL=f"/test{i}"
process = ffmpeg.input('pipe:').output(f'{BASE_URL}/video.mp4', vcodec='h264_nvenc').overwrite_output().run_async(pipe_stdin=True)
for snippets in sorted(self.parallelviddeos.get(id)):
self.parallelviddeos.get(id)[snippets].seek(0)
process.stdin.write(self.parallelviddeos.get(id)[snippets].read())
process.stdin.close()
process.wait()
字典 'parallelviddeos' 以 id 作为键和 ByteIO 对象列表(*.ts 文件) 作为相应视频的输入看起来像这样
parallelviddeos={id1:{0:BytesIO,2:BytesIO,3:BytesIO,4:BytesIO},id2:{0:BytesIO,...}}
谁能帮我把 BytesIO-Part 转换成完整的 mp4 文件。 我正在寻找基于管道的方法。
我正在获取 ts 文件作为字节格式请求的响应。
我们可能不认为以这种方式将 TS 文件写入管道。
如果没有 PIPE,我们可以使用 concat
options.
我认为最安全的选择是使用另一个 FFmpeg sub-process 将 TS 解码为原始视频帧,然后逐帧写入管道。
你可以从我下面的post开始,循环执行
这是一个“自包含”代码示例:
import ffmpeg
import numpy as np
import cv2 # Use OpenCV for testing
import io
import subprocess as sp
import threading
from functools import partial
out_filename = 'video.mp4'
# Build synthetic input, and read into BytesIO
###############################################
# Assume we know the width and height from advance
# (In case you don't know the resolution, I posted solution for getting it using FFprobe).
width = 192
height = 108
fps = 10
n_frames = 100
in_filename1 = 'in1.ts'
in_filename2 = 'in2.ts'
# Build synthetic video (in1.ts) for testing:
(
ffmpeg
.input(f'testsrc=size={width}x{height}:rate=1:duration={n_frames}', f='lavfi', r=fps)
.filter('setpts', f'N/{fps}/TB')
.output(in_filename1, vcodec='libx264', crf=17, pix_fmt='yuv420p', loglevel='error')
.global_args('-hide_banner')
.overwrite_output()
.run()
)
# Build synthetic video (in1.ts) for testing:
(
ffmpeg
.input(f'mandelbrot=size={width}x{height}:rate=1', f='lavfi', r=fps)
.filter('setpts', f'N/{fps}/TB')
.output(in_filename2, vcodec='libx264', crf=17, pix_fmt='yuv420p', loglevel='error', t=n_frames)
.global_args('-hide_banner')
.overwrite_output()
.run()
)
# Read the file into in-memory binary streams
with open(in_filename1, 'rb') as f:
in_bytes = f.read()
stream1 = io.BytesIO(in_bytes)
# Read the file into in-memory binary streams
with open(in_filename2, 'rb') as f:
in_bytes = f.read()
stream2 = io.BytesIO(in_bytes)
# Use list instead of dictionary (just for the example).
in_memory_viddeos = [stream1, stream2]
###############################################
# Writer thread: Write to stdin in chunks of 1024 bytes
def writer(decoder_process, stream):
for chunk in iter(partial(stream.read, 1024), b''):
decoder_process.stdin.write(chunk)
decoder_process.stdin.close()
def decode_in_memory_and_re_encode(vid_bytesio):
""" Decode video in BytesIO, and write the decoded frame into the encoder sub-process """
vid_bytesio.seek(0)
# Execute video decoder sub-process
decoder_process = (
ffmpeg
.input('pipe:') #, f='mpegts', vcodec='h264')
.video
.output('pipe:', format='rawvideo', pix_fmt='bgr24')
.run_async(pipe_stdin=True, pipe_stdout=True)
)
thread = threading.Thread(target=writer, args=(decoder_process, vid_bytesio))
thread.start()
# Read decoded video (frame by frame), and display each frame (using cv2.imshow for testing)
while True:
# Read raw video frame from stdout as bytes array.
in_bytes = decoder_process.stdout.read(width * height * 3)
if not in_bytes:
break
# Write the decoded frame to the encoder.
encoder_process.stdin.write(in_bytes)
# transform the byte read into a numpy array (for testing)
in_frame = np.frombuffer(in_bytes, np.uint8).reshape([height, width, 3])
# Display the frame (for testing)
cv2.imshow('in_frame', in_frame)
cv2.waitKey(10)
thread.join()
decoder_process.wait()
# Execute video encoder sub-process
encoder_process = (
ffmpeg
.input('pipe:', r=fps, f='rawvideo', s=f'{width}x{height}', pixel_format='bgr24')
.video
.output(out_filename, vcodec='libx264', crf=17, pix_fmt='yuv420p')
.overwrite_output()
.run_async(pipe_stdin=True)
)
# Re-encode the "in memory" videos in a loop
for memvid in in_memory_viddeos:
decode_in_memory_and_re_encode(memvid)
encoder_process.stdin.close()
encoder_process.wait()
cv2.destroyAllWindows()
抱歉忽略了你的字典结构。
我假设问题与视频编码有关,而不是与您迭代 BytesIO 的方式有关 objects.
更新:
正在从 in-memory 视频流中读取 width
和 height
:
选项很少。
我决定从 BMP 图像的 header 中读取 width
和 height
。
使用以下参数(image2pipe
格式和 bmp
视频编解码器)启动 FFmpeg sub-process:
decoder_process = ffmpeg.input('pipe:'), f='mpegts', vcodec='h264').video.output('pipe:',f='image2pipe', vcodec='bmp').run_async(pipe_stdin=True, pipe_stdout=True)
解析header得到with
和height
:
in_bytes = decoder_process.stdout.read(54) # 54 bytes BMP Header + 4 bytes
(width, height) = struct.unpack("<ll", in_bytes[18:26])
完整代码示例:
import ffmpeg
import numpy as np
import cv2 # Use OpenCV for testing
import io
import subprocess as sp
import threading
from functools import partial
import struct
out_filename = 'video.mp4'
# Build synthetic input, and read into BytesIO
###############################################
# Assume we know the width and height from advance
width = 192
height = 108
fps = 10
n_frames = 100
in_filename1 = 'in1.ts'
in_filename2 = 'in2.ts'
## Build synthetic video (in1.ts) for testing:
#(
# ffmpeg
# .input(f'testsrc=size={width}x{height}:rate=1:duration={n_frames}', f='lavfi', r=fps)
# .filter('setpts', f'N/{fps}/TB')
# .output(in_filename1, vcodec='libx264', crf=17, pix_fmt='yuv420p', loglevel='error')
# .global_args('-hide_banner')
# .overwrite_output()
# .run()
#)
## Build synthetic video (in1.ts) for testing:
#(
# ffmpeg
# .input(f'mandelbrot=size={width}x{height}:rate=1', f='lavfi', r=fps)
# .filter('setpts', f'N/{fps}/TB')
# .output(in_filename2, vcodec='libx264', crf=17, pix_fmt='yuv420p', loglevel='error', t=n_frames)
# .global_args('-hide_banner')
# .overwrite_output()
# .run()
#)
# Read the file into in-memory binary streams
with open(in_filename1, 'rb') as f:
in_bytes = f.read()
stream1 = io.BytesIO(in_bytes)
# Read the file into in-memory binary streams
with open(in_filename2, 'rb') as f:
in_bytes = f.read()
stream2 = io.BytesIO(in_bytes)
# Use list instead of dictionary (just for the example).
in_memory_viddeos = [stream1, stream2]
###############################################
# Writer thread: Write to stdin in chunks of 1024 bytes
def writer(decoder_process, stream):
for chunk in iter(partial(stream.read, 1024), b''):
try:
decoder_process.stdin.write(chunk)
except (BrokenPipeError, OSError):
# get_in_memory_video_frame_size causes BrokenPipeError exception and OSError exception.
# This in not a clean solution, but it's the simplest I could find.
return
decoder_process.stdin.close()
def get_in_memory_video_frame_size(vid_bytesio):
""" Get the resolution of a video in BytesIO """
vid_bytesio.seek(0)
# Execute video decoder sub-process, the output format is BMP
decoder_process = (
ffmpeg
.input('pipe:') #, f='mpegts', vcodec='h264')
.video
.output('pipe:', f='image2pipe', vcodec='bmp')
.run_async(pipe_stdin=True, pipe_stdout=True)
)
thread = threading.Thread(target=writer, args=(decoder_process, vid_bytesio))
thread.start()
# Read raw video frame from stdout as bytes array.
# https://en.wikipedia.org/wiki/BMP_file_format
in_bytes = decoder_process.stdout.read(54) # 54 bytes BMP Header + 4 bytes
decoder_process.stdout.close()
thread.join()
decoder_process.wait()
vid_bytesio.seek(0)
# The width and height are located in bytes 18 to 25 (4 bytes each)
(width, height) = struct.unpack("<ll", in_bytes[18:26])
return width, abs(height)
def decode_in_memory_and_re_encode(vid_bytesio):
""" Decode video in BytesIO, and write the decoded frame into the encoder sub-process """
vid_bytesio.seek(0)
# Execute video decoder sub-process
decoder_process = (
ffmpeg
.input('pipe:') #, f='mpegts', vcodec='h264')
.video
.output('pipe:', format='rawvideo', pix_fmt='bgr24')
.run_async(pipe_stdin=True, pipe_stdout=True)
)
thread = threading.Thread(target=writer, args=(decoder_process, vid_bytesio))
thread.start()
# Read decoded video (frame by frame), and display each frame (using cv2.imshow for testing)
while True:
# Read raw video frame from stdout as bytes array.
in_bytes = decoder_process.stdout.read(width * height * 3)
if not in_bytes:
break
# Write the decoded frame to the encoder.
encoder_process.stdin.write(in_bytes)
# transform the byte read into a numpy array (for testing)
in_frame = np.frombuffer(in_bytes, np.uint8).reshape([height, width, 3])
# Display the frame (for testing)
cv2.imshow('in_frame', in_frame)
cv2.waitKey(10)
thread.join()
decoder_process.wait()
width, height = get_in_memory_video_frame_size(in_memory_viddeos[0])
# Execute video encoder sub-process
encoder_process = (
ffmpeg
.input('pipe:', r=fps, f='rawvideo', s=f'{width}x{height}', pixel_format='bgr24')
.video
.output(out_filename, vcodec='libx264', crf=17, pix_fmt='yuv420p')
.overwrite_output()
.run_async(pipe_stdin=True)
)
# Re-encode the "in memory" videos in a loop
for memvid in in_memory_viddeos:
decode_in_memory_and_re_encode(memvid)
encoder_process.stdin.close()
encoder_process.wait()
cv2.destroyAllWindows()