python - cv2.VideoWriter 问题

标签 python opencv ffmpeg video-processing http-live-streaming

我希望通过使用 streamlink.streams(url)(返回 .m3u8 url)向其提供直接直播 URL 来录制 Twitch 直播。这样,我可以毫无问题地读取流,甚至从中写入一些图像,但当将其写入视频时,我会遇到错误。

P.S.:是的,我知道还有其他选项,例如 Streamlink 和 yt-dwl,但我想仅在 python 中操作,而不是使用 CLI...我相信这两个只是处理(用于录制)。

这是我目前拥有的:

if streamlink.streams(url):
    stream = streamlink.streams(url)['best']
    stream = str(stream).split(', ')
    stream = stream[1].strip("'")
    cap = cv2.VideoCapture(stream)
    gst_out = "appsrc ! video/x-raw, format=BGR ! queue ! nvvidconv ! omxh264enc ! h264parse ! qtmux ! filesink location=stream "
    out = cv2.VideoWriter(gst_out, cv2.VideoWriter_fourcc(*'mp4v'), 30, (1920, 1080))
    while True:
        _, frame = cap.read()
        out.write(frame)

对于此代码,我收到此错误消息:

[tls @ 0x1278a74f0] Error in the pull function.

如果我删除 gst_out 并提供 stream ,并将 capout 移入 while像这样循环:

if streamlink.streams(url):
    stream = streamlink.streams(url)['best']
    stream = str(stream).split(', ')
    stream = stream[1].strip("'")
    while True:
        cap = cv2.VideoCapture(stream)
        _, frame = cap.read()
        out = cv2.VideoWriter(stream, cv2.VideoWriter_fourcc(*'mp4v'), 30, (1920, 1080))
        out.write(frame)

我得到:

OpenCV: FFMPEG: tag 0x7634706d/'mp4v' is not supported with codec id 12 and format 'hls / Apple HTTP Live Streaming'

我在这里缺少什么?

最佳答案

第一部分使用 GStreamer 语法,而 OpenCV for Python 很可能不是用 GStreamer 构建的。
答案将集中在第二部分(也是因为我不太了解 GStreamer)。

有几个问题:

  • cap = cv2.VideoCapture(stream) 应位于 while True 循环之前。
  • out = cv2.VideoWriter(stream, cv2.VideoWriter_fourcc(*'mp4v'), 30, (1920, 1080)) 应位于 while True 循环之前.
  • cv2.VideoWriter 的第一个参数应该是 MP4 文件名,而不是stream
  • 为了获取有效的输出文件,我们必须在循环后执行 out.release(),但循环可能永远不会结束。

  • 建议获取输入视频的帧大小和速率,并相应地设置 VideoWriter:

     width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
     height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
     fps = int(cap.get(cv2.CAP_PROP_FPS))
    
     video_file_name = 'output.mp4'
    
     out = cv2.VideoWriter(video_file_name, cv2.VideoWriter_fourcc(*'mp4v'), fps, (width, height))  # Open video file for writing
    
  • 如果retFalse,建议中断循环:

     ret, frame = cap.read()
    
     if not ret:
         break
    
  • 结束录制的一个选项是当用户按 Esc 键时。
    如果cv2.waitKey(1) == 27则中断循环。
    cv2.waitKey(1) 仅在执行 cv2.imshow 后才起作用。
    一个简单的解决方案是每 30 帧执行一次 cv2.imshow(例如)。

     if (frame_counter % 30 == 0):
         cv2.imshow('frame', frame)  # Show frame every 30 frames (for testing)
    
     if cv2.waitKey(1) == 27:  # Press Esc for stop recording (cv2.waitKey is going to work only when cv2.imshow is used).
         break
    

完整代码示例:

from streamlink import Streamlink
import cv2

def stream_to_url(url, quality='best'):
    session = Streamlink()
    streams = session.streams(url)

    if streams:
        return streams[quality].to_url()
    else:
        raise ValueError('Could not locate your stream.')


url = 'https://www.twitch.tv/noraexplorer'  # Need to login to twitch.tv first (using the browser)...
quality='best'

stream_url = stream_to_url(url, quality)  # Get the video URL
cap = cv2.VideoCapture(stream_url, cv2.CAP_FFMPEG)  # Open video stream for capturing

# Get frame size and rate of the input video
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = int(cap.get(cv2.CAP_PROP_FPS))


video_file_name = 'output.mp4'

out = cv2.VideoWriter(video_file_name, cv2.VideoWriter_fourcc(*'mp4v'), fps, (width, height))  # Open video file for writing


frame_counter = 0
while True:
    ret, frame = cap.read()
    
    if not ret:
        break

    if (frame_counter % 30 == 0):
        cv2.imshow('frame', frame)  # Show frame every 30 frames (for testing)

    out.write(frame)  # Write frame to output.mp4

    if cv2.waitKey(1) == 27:  # Press Esc for stop recording (cv2.waitKey is going to work only when cv2.imshow is used).
        break

    frame_counter += 1

cap.release()
out.release()
cv2.destroyAllWindows()

使用FFplaysubprocess模块测试设置:

from streamlink import Streamlink
import subprocess

def stream_to_url(url, quality='best'):
    session = Streamlink()
    streams = session.streams(url)

    if streams:
        return streams[quality].to_url()
    else:
        raise ValueError('Could not locate your stream.')


#url = 'https://www.twitch.tv/noraexplorer'  # Need to login to twitch.tv first (using the browser)...
url = 'https://www.twitch.tv/valorant'
quality='best'

stream_url = stream_to_url(url, quality)  # Get the video URL

subprocess.run(['ffplay', stream_url])

更新:

使用ffmpeg-python用于读取视频,OpenCV 用于录制视频:

如果cv2.VideoCapture不起作用,我们可以使用FFmpeg CLI作为子进程。
ffmpeg-python 模块是 FFmpeg CLI 的 Python 绑定(bind)。
使用ffmpeg-python几乎和使用subprocess模块一样,这里使用它主要是为了简化FFprobe的使用。


使用 FFprobe 获取视频帧分辨率和帧速率(不使用 OpenCV):

p = ffmpeg.probe(stream_url, select_streams='v');
width = p['streams'][0]['width']
height = p['streams'][0]['height']
r_frame_rate = p['streams'][0]['r_frame_rate']  # May return 60000/1001

if '/' in r_frame_rate:
    fps = float(r_frame_rate.split("/")[0]) / float(r_frame_rate.split("/")[1])  # Convert from 60000/1001 to 59.94
elif r_frame_rate != '0':
    fps = float(r_frame_rate)
else:
    fps = 30  # Used as default

获取帧速率可能有点困难......

注意:ffprobe CLI 应位于执行路径中。


使用 stdout 作为管道启动 FFmpeg 子进程:

ffmpeg_process = (
    ffmpeg
    .input(stream_url)
    .video
    .output('pipe:', format='rawvideo', pix_fmt='bgr24')
    .run_async(pipe_stdout=True)
)

注意:ffmpeg CLI 应位于执行路径中。


从管道读取帧,并将其从字节转换为 NumPy 数组:

in_bytes = ffmpeg_process.stdout.read(width*height*3)
frame = np.frombuffer(in_bytes, np.uint8).reshape([height, width, 3])

关闭 FFmpeg 子进程:
关闭 stdout 管道结束 FFmpeg(出现“管道损坏”错误)。

ffmpeg_process.stdout.close()
ffmpeg_process.wait()  # Wait for the sub-process to finish

完整代码示例:

from streamlink import Streamlink
import cv2
import numpy as np
import ffmpeg

def stream_to_url(url, quality='best'):
    session = Streamlink()
    streams = session.streams(url)

    if streams:
        return streams[quality].to_url()
    else:
        raise ValueError('Could not locate your stream.')


#url = 'https://www.twitch.tv/noraexplorer'  # Need to login to twitch.tv first (using the browser)...
url = 'https://www.twitch.tv/valorant'
quality='best'

stream_url = stream_to_url(url, quality)  # Get the video URL

#subprocess.run(['ffplay', stream_url])  # Use FFplay for testing

# Use FFprobe to get video frames resolution and framerate.
################################################################################
p = ffmpeg.probe(stream_url, select_streams='v');
width = p['streams'][0]['width']
height = p['streams'][0]['height']
r_frame_rate = p['streams'][0]['r_frame_rate']  # May return 60000/1001

if '/' in r_frame_rate:
    fps = float(r_frame_rate.split("/")[0]) / float(r_frame_rate.split("/")[1])  # Convert from 60000/1001 to 59.94
elif r_frame_rate != '0':
    fps = float(r_frame_rate)
else:
    fps = 30  # Used as default

#cap = cv2.VideoCapture(stream_url, cv2.CAP_FFMPEG)  # Open video stream for capturing
#width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
#height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
#fps = int(cap.get(cv2.CAP_PROP_FPS))
################################################################################


# Use FFmpeg sub-process instead of using cv2.VideoCapture
################################################################################
ffmpeg_process = (
    ffmpeg
    .input(stream_url, an=None)  # an=None applies -an argument (used for ignoring the input audio - it is not required, just more elegant).
    .video
    .output('pipe:', format='rawvideo', pix_fmt='bgr24')
    .run_async(pipe_stdout=True)
)
################################################################################


video_file_name = 'output.mp4'

out = cv2.VideoWriter(video_file_name, cv2.VideoWriter_fourcc(*'mp4v'), fps, (width, height))  # Open video file for writing


frame_counter = 0
while True:
    #ret, frame = cap.read()    
    in_bytes = ffmpeg_process.stdout.read(width*height*3)  # Read raw video frame from stdout as bytes array.
    
    if len(in_bytes) < width*height*3:  #if not ret:
        break

    frame = np.frombuffer(in_bytes, np.uint8).reshape([height, width, 3])  # Convert bytes array to NumPy array.

    if (frame_counter % 30 == 0):
        cv2.imshow('frame', frame)  # Show frame every 30 frames (for testing)

    out.write(frame)  # Write frame to output.mp4

    if cv2.waitKey(1) == 27:  # Press Esc for stop recording (cv2.waitKey is going to work only when cv2.imshow is used).
        break

    frame_counter += 1

#cap.release()
ffmpeg_process.stdout.close()  # Close stdout pipe (it also closes FFmpeg).
out.release()
cv2.destroyAllWindows()
ffmpeg_process.wait()  # Wait for the sub-process to finish

注意:
如果您关心录制视频的质量,使用 cv2.VideoWriter 并不是最佳选择...

关于python - cv2.VideoWriter 问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73324872/

相关文章:

ffmpeg - 使用 ffmpeg : playback issue in older quicktime (7. 6.6 从 PNG 创建 MOV

Python子进程模块使用

python - AWS Lambda 和 HDF5

python - 显示完整的行,突出显示数据帧 df1 、 df2 之间的差异,但仅当行单元格中存在差异时

python - 如何在不下载的情况下读取谷歌云存储中的视频?使用视频网址

javascript - ffmpeg命令行代码转换为fluent-ffmpeg

Python 日期将多年日期跨度分隔为单年跨度

c++ - 如何检索单应性计算的 findHomography 和 RANSAC 的点?

python - solvePnPRansac 为 rvecs 和 tvecs 返回零值

docker - ffmpeg 图像和 flask 应用程序在 Docker 中的两个不同容器中组合。如何在 Flask 应用程序中使用 ffmpeg?