whitphx / streamlit-webrtc

Real-time video and audio processing on Streamlit
https://discuss.streamlit.io/t/new-component-streamlit-webrtc-a-new-way-to-deal-with-real-time-media-streams/8669
MIT License
1.38k stars 185 forks source link

Record video in high quality? #483

Open talhaanwarch opened 2 years ago

talhaanwarch commented 2 years ago

I am following this code in app_record but it save video file in low quality though i am capturing it with my mobile. Is there a way to save video in high quality and smoothly as we capture via mobile camera without streamlit-webrtc

whitphx commented 2 years ago

It is probably impossible now due to https://github.com/aiortc/aiortc/issues/183 Although the input stream resolution can be changed in this way, the recorder does not support size config now as written in the thread above.

I will investigate the way to solve it in this library's layer though.

whitphx commented 2 years ago

Memo: https://github.com/whitphx/streamlit-webrtc/blob/bdc6287117babe47eed42d90102c3ff8f7dd4bc3/streamlit_webrtc/webrtc.py#L188-L191

whitphx commented 2 years ago

aiortc 1.3.2 may solve this.

https://aiortc.readthedocs.io/en/latest/changelog.html#id1

Determine video size from first frame received by [aiortc.contrib.media.MediaRecorder](https://aiortc.readthedocs.io/en/latest/helpers.html#aiortc.contrib.media.MediaRecorder).

GaetanDeflandre commented 1 year ago

The actual MediaRecorder class set the video size using the size of the first frame. But, WebRTC will adaptively change the video track size relative to the network conditions and device capabilities, so the video track first frame size is likely to be low.

Below is a workaround the record media when the size of the video to record is known:

class SizedMediaRecorder(MediaRecorder):
    """Class that handles recording sized media.  

    A media sink that writes audio and/or video to a file with specified width
    and height.

    Examples:

    .. code-block:: python
        # Write to a video file with specified width and height.
        player = SizedMediaRecorder('/path/to/file.mp4', width=1280, height=720)

    Attributes:
        width (int): The width of the video frame.
        height (int): The height of the video frame.
    """

    def __init__(self, file, width: int, height: int, format=None, options={}):
        """## Constructor
        Initializes a new CustomMediaRecorder instance with specified width and
        height.

        Args:
            file (str): The path to a file, or a file-like object.
            width (int): The width of the video frame.
            height (int): The height of the video frame.
            format (str, optional): Specific format to use. Defaults to
            autodect.
            options (dict, optional): Options to pass to the container and all
            streams.
        """
        super().__init__(file, format=format, options=options)
        self.width = width
        self.height = height

    def addTrack(self, track: MediaStreamTrack):
        """
        Add a track to be recorded.

        Args:
            track (MediaStreamTrack): A `MediaStreamTrack` instance to be recorded.

        Note:
            This method overrides the `addTrack` method from the `MediaRecorder`
            superclass. It sets video track context width and height to the
            specified dimensions by directly accessing the `__tracks` private
            attribute of the superclass using name mangling.
        """
        super().addTrack(track)
        if track.kind == "video":
            self._MediaRecorder__tracks[track].stream.width = self.width
            self._MediaRecorder__tracks[track].stream.height = self.height

    async def _MediaRecorder__run_track(self, track: MediaStreamTrack, context: MediaRecorderContext):
        """
        Run the specified track and encode frames with the specified width and
        height.

        Args:
            track (MediaStreamTrack): The media stream track to run.
            context (MediaRecorderContext): The context for the media recorder.

        Note:
            This method overrides the private method `__run_track` from the
            `MediaRecorder` superclass and accesses the superclass `__container`
            private attribute by using name mangling.
        """
        while True:
            try:
                frame = await track.recv()
            except MediaStreamError:
                return

            for packet in context.stream.encode(frame):
                self._MediaRecorder__container.mux(packet)