将多个 *.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 视频流中读取 widthheight

选项很少。
我决定从 BMP 图像的 header 中读取 widthheight

使用以下参数(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得到withheight:

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()