Source code for panel.pane.media

"""
Contains Media panes including renderers for Audio and Video content.
"""
from __future__ import annotations

import os

from base64 import b64encode
from io import BytesIO
from typing import (
    TYPE_CHECKING, Any, ClassVar, List, Mapping, Optional,
)

import numpy as np
import param

from ..models import Audio as _BkAudio, Video as _BkVideo
from ..util import isfile, isurl
from .base import PaneBase

if TYPE_CHECKING:
    from bokeh.document import Document
    from bokeh.model import Model
    from pyviz_comms import Comm


class _MediaBase(PaneBase):

    loop = param.Boolean(default=False, doc="""
        Whether the meida should loop""")

    time = param.Number(default=0, doc="""
        The current timestamp""")

    throttle = param.Integer(default=250, doc="""
        How frequently to sample the current playback time in milliseconds""")

    paused = param.Boolean(default=True, doc="""
        Whether the media is currently paused""")

    object = param.String(default='', allow_None=True, doc="""
        The media file either local or remote.""")

    volume = param.Number(default=None, bounds=(0, 100), doc="""
        The volume of the media player.""")

    autoplay = param.Boolean(default=False, doc="""
        When True, it specifies that the output will play automatically.
        In Chromium browsers this requires the user to click play once.""")

    muted = param.Boolean(default=False, doc="""
        When True, it specifies that the output should be muted.""")

    _default_mime: ClassVar[str]

    _formats: ClassVar[List[str]]

    _media_type: ClassVar[str]

    _rename: ClassVar[Mapping[str, str | None]] = {
        'name': None, 'sample_rate': None, 'object': 'value'}

    _rerender_params: ClassVar[List[str]] = []

    _updates: ClassVar[bool] = True

    __abstract = True

    @classmethod
    def applies(cls, obj: Any) -> float | bool | None:
        if isinstance(obj, str):
            if isfile(obj) and any(obj.endswith('.'+fmt) for fmt in cls._formats):
                return True
            if isurl(obj, cls._formats):
                return True
        if hasattr(obj, 'read'):  # Check for file like object
            return True
        return False

    def _get_model(
        self, doc: Document, root: Optional[Model] = None,
        parent: Optional[Model] = None, comm: Optional[Comm] = None
    ) -> Model:
        props = self._process_param_change(self._init_params())
        model = self._bokeh_model(**props)
        if root is None:
            root = model
        self._models[root.ref['id']] = (model, parent)
        self._link_props(model, list(model.properties()), doc, root, comm)
        return model

    def _from_numpy(self, data):
        from scipy.io import wavfile
        buffer = BytesIO()
        wavfile.write(buffer, self.sample_rate, data)
        return buffer

    def _process_property_change(self, msg):
        msg = super()._process_property_change(msg)
        if 'js_property_callbacks' in msg:
            del msg['js_property_callbacks']
        return msg

    def _process_param_change(self, msg):
        msg = super()._process_param_change(msg)
        if 'value' in msg:
            value = msg['value']
            fmt = self._default_mime
            if isinstance(value, np.ndarray):
                fmt = 'wav'
                buffer = self._from_numpy(value)
                data = b64encode(buffer.getvalue())
            elif os.path.isfile(value):
                fmt = value.split('.')[-1]
                with open(value, 'rb') as f:
                    data = f.read()
                data = b64encode(data)
            elif value.lower().startswith('http'):
                return msg
            elif not value or value == f'data:{self._media_type}/{fmt};base64,':
                data = b''
            else:
                raise ValueError(f'Object should be either path to a {self._media_type} file or numpy array.')
            msg['value'] = f"data:{self._media_type}/{fmt};base64,{data.decode('utf-8')}"
        return msg


[docs]class Audio(_MediaBase): """ The `Audio` pane displays an audio player given a local or remote audio file or numpy array. The pane also allows access and control over the player state including toggling of playing/paused and loop state, the current time, and the volume. The audio player supports ogg, mp3, and wav files as well as numpy arrays. Reference: https://panel.holoviz.org/reference/panes/Audio.html :Example: >>> Audio('http://ccrma.stanford.edu/~jos/mp3/pno-cs.mp3', name='Audio') """ object = param.ClassSelector(default='', class_=(str, np.ndarray,), allow_None=True, doc=""" The audio file either local or remote.""") sample_rate = param.Integer(default=44100, doc=""" The sample_rate of the audio when given a NumPy array.""") _bokeh_model = _BkAudio _default_mime = 'wav' _formats = ['mp3', 'wav', 'ogg'] _media_type = 'audio'
[docs] @classmethod def applies(cls, obj: Any) -> float | bool | None: return (super().applies(obj) or (isinstance(obj, np.ndarray) and obj.ndim==1 and obj.dtype in [np.int16, np.uint16]))
[docs]class Video(_MediaBase): """ The `Video` Pane displays a video player given a local or remote video file. The widget also allows access and control over the player state including toggling of playing/paused and loop state, the current time, and the volume. Depending on the browser the video player supports mp4, webm, and ogg containers and a variety of codecs. Reference: https://panel.holoviz.org/reference/panes/Video.html :Example: >>> Video( ... 'https://file-examples-com.github.io/uploads/2017/04/file_example_MP4_640_3MG.mp4', ... width=640, height=360, loop=True ... ) """ _bokeh_model = _BkVideo _default_mime = 'mp4' _formats = ['mp4', 'webm', 'ogg'] _media_type = 'video'