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

附言:是的,我知道还有其他 Select ,比如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和Feed 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'

我错过了什么吗?

推荐答案

The fist part uses GStreamer syntax, and OpenCV for Python is most likely not built with GStreamer.
The answer is going to be focused on the second part (also because I don't know GStreamer so well).

有几个问题:

  • 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
    
  • One option to end the recording is when user press Esc key.
    Break the loop if cv2.waitKey(1) == 27.
    cv2.waitKey(1) is going to work only after executing cv2.imshow.
    A simple solution is executing cv2.imshow every 30 frames (for example).

     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:

In cases where cv2.VideoCapture is not working, we may use FFmpeg CLI as sub-process.
ffmpeg-python module is Python binding for FFmpeg CLI.
Using ffmpeg-python is almost like using subprocess module, it used here mainly for simplifying the usage of FFprobe.


使用FFbe获取视频帧分辨率和帧速率(不使用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])

Closing FFmpeg sub-process:
Closing stdout pipe ends FFmpeg (with "broken pipe" error).

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

Note:
In case you care about the quality of the recorded video, using cv2.VideoWriter is not the best choice...

Python相关问答推荐

如何在msgraph.GraphServiceClient上进行身份验证?

如何根据参数推断对象的返回类型?

什么相当于pytorch中的numpy累积ufunc

log 1 p numpy的意外行为

使用setuptools pyproject.toml和自定义目录树构建PyPi包

当独立的网络调用不应该互相阻塞时,'

OR—Tools中CP—SAT求解器的IntVar设置值

递归访问嵌套字典中的元素值

在含噪声的3D点网格中识别4连通点模式

无法在Docker内部运行Python的Matlab SDK模块,但本地没有问题

Polars asof在下一个可用日期加入

如何在Python 3.9.6和MacOS Sonoma 14.3.1下安装Pyregion

如何根据rame中的列值分别分组值

Python类型提示:对于一个可以迭代的变量,我应该使用什么?

如何在Gekko中处理跨矢量优化

如何在python tkinter中绑定键盘上的另一个回车?

如何从一个维基页面中抓取和存储多个表格?

在MongoDB文档中仅返回数组字段

如何通过函数的强式路径动态导入函数?

Django REST框架+Django Channel->;[Errno 111]连接调用失败(';127.0.0.1';,6379)