# Copyright 2018-2022 Streamlit Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import io import re from typing import cast from validators import url import streamlit from streamlit import type_util from streamlit.in_memory_file_manager import in_memory_file_manager from streamlit.proto.Audio_pb2 import Audio as AudioProto from streamlit.proto.Video_pb2 import Video as VideoProto class MediaMixin: def audio(self, data, format="audio/wav", start_time=0): """Display an audio player. Parameters ---------- data : str, bytes, BytesIO, numpy.ndarray, or file opened with io.open(). Raw audio data, filename, or a URL pointing to the file to load. Numpy arrays and raw data formats must include all necessary file headers to match specified file format. start_time: int The time from which this element should start playing. format : str The mime type for the audio file. Defaults to 'audio/wav'. See https://tools.ietf.org/html/rfc4281 for more info. Example ------- >>> audio_file = open('myaudio.ogg', 'rb') >>> audio_bytes = audio_file.read() >>> >>> st.audio(audio_bytes, format='audio/ogg') .. output:: https://share.streamlit.io/streamlit/docs/main/python/api-examples-source/charts.audio.py height: 465px """ audio_proto = AudioProto() coordinates = self.dg._get_delta_path_str() marshall_audio(coordinates, audio_proto, data, format, start_time) return self.dg._enqueue("audio", audio_proto) def video(self, data, format="video/mp4", start_time=0): """Display a video player. Parameters ---------- data : str, bytes, BytesIO, numpy.ndarray, or file opened with io.open(). Raw video data, filename, or URL pointing to a video to load. Includes support for YouTube URLs. Numpy arrays and raw data formats must include all necessary file headers to match specified file format. format : str The mime type for the video file. Defaults to 'video/mp4'. See https://tools.ietf.org/html/rfc4281 for more info. start_time: int The time from which this element should start playing. Example ------- >>> video_file = open('myvideo.mp4', 'rb') >>> video_bytes = video_file.read() >>> >>> st.video(video_bytes) .. output:: https://share.streamlit.io/streamlit/docs/main/python/api-examples-source/charts.video.py height: 700px .. note:: Some videos may not display if they are encoded using MP4V (which is an export option in OpenCV), as this codec is not widely supported by browsers. Converting your video to H.264 will allow the video to be displayed in Streamlit. See this `StackOverflow post `_ or this `Streamlit forum post `_ for more information. """ video_proto = VideoProto() coordinates = self.dg._get_delta_path_str() marshall_video(coordinates, video_proto, data, format, start_time) return self.dg._enqueue("video", video_proto) @property def dg(self) -> "streamlit.delta_generator.DeltaGenerator": """Get our DeltaGenerator.""" return cast("streamlit.delta_generator.DeltaGenerator", self) # Regular expression explained at https://regexr.com/4n2l2 Covers any youtube # URL (incl. shortlinks and embed links) and extracts its code. YOUTUBE_RE = re.compile( # Protocol r"http(?:s?):\/\/" # Domain r"(?:www\.)?youtu(?:be\.com|\.be)\/" # Path and query string r"(?P(watch\?v=)|embed\/)?(?P[\w\-\_]*)(&(amp;)?[\w\?=]*)?" ) def _reshape_youtube_url(url): """Return whether URL is any kind of YouTube embed or watch link. If so, reshape URL into an embed link suitable for use in an iframe. If not a YouTube URL, return None. Parameters ---------- url : str or bytes Example ------- >>> print(_reshape_youtube_url('https://youtu.be/_T8LGqJtuGc')) .. output:: https://www.youtube.com/embed/_T8LGqJtuGc """ match = YOUTUBE_RE.match(url) if match: return "https://www.youtube.com/embed/{code}".format(**match.groupdict()) return None def _marshall_av_media(coordinates, proto, data, mimetype): """Fill audio or video proto based on contents of data. Given a string, check if it's a url; if so, send it out without modification. Otherwise assume strings are filenames and let any OS errors raise. Load data either from file or through bytes-processing methods into a InMemoryFile object. Pack proto with generated Tornado-based URL. """ # Audio and Video methods have already checked if this is a URL by this point. if isinstance(data, str): # Assume it's a filename or blank. Allow OS-based file errors. with open(data, "rb") as fh: this_file = in_memory_file_manager.add(fh.read(), mimetype, coordinates) proto.url = this_file.url return if data is None: # Allow empty values so media players can be shown without media. return # Assume bytes; try methods until we run out. if isinstance(data, bytes): pass elif isinstance(data, io.BytesIO): data.seek(0) data = data.getvalue() elif isinstance(data, io.RawIOBase) or isinstance(data, io.BufferedReader): data.seek(0) data = data.read() elif type_util.is_type(data, "numpy.ndarray"): data = data.tobytes() else: raise RuntimeError("Invalid binary data format: %s" % type(data)) this_file = in_memory_file_manager.add(data, mimetype, coordinates) proto.url = this_file.url def marshall_video(coordinates, proto, data, mimetype="video/mp4", start_time=0): """Marshalls a video proto, using url processors as needed. Parameters ---------- coordinates : str proto : the proto to fill. Must have a string field called "data". data : str, bytes, BytesIO, numpy.ndarray, or file opened with io.open(). Raw video data or a string with a URL pointing to the video to load. Includes support for YouTube URLs. If passing the raw data, this must include headers and any other bytes required in the actual file. mimetype : str The mime type for the video file. Defaults to 'video/mp4'. See https://tools.ietf.org/html/rfc4281 for more info. start_time : int The time from which this element should start playing. (default: 0) """ proto.start_time = start_time # "type" distinguishes between YouTube and non-YouTube links proto.type = VideoProto.Type.NATIVE if isinstance(data, str) and url(data): youtube_url = _reshape_youtube_url(data) if youtube_url: proto.url = youtube_url proto.type = VideoProto.Type.YOUTUBE_IFRAME else: proto.url = data else: _marshall_av_media(coordinates, proto, data, mimetype) def marshall_audio(coordinates, proto, data, mimetype="audio/wav", start_time=0): """Marshalls an audio proto, using data and url processors as needed. Parameters ---------- coordinates : str proto : The proto to fill. Must have a string field called "url". data : str, bytes, BytesIO, numpy.ndarray, or file opened with io.open() Raw audio data or a string with a URL pointing to the file to load. If passing the raw data, this must include headers and any other bytes required in the actual file. mimetype : str The mime type for the audio file. Defaults to "audio/wav". See https://tools.ietf.org/html/rfc4281 for more info. start_time : int The time from which this element should start playing. (default: 0) """ proto.start_time = start_time if isinstance(data, str) and url(data): proto.url = data else: _marshall_av_media(coordinates, proto, data, mimetype)