widget_media.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. # Copyright (c) Jupyter Development Team.
  2. # Distributed under the terms of the Modified BSD License.
  3. import mimetypes
  4. from .widget_core import CoreWidget
  5. from .domwidget import DOMWidget
  6. from .valuewidget import ValueWidget
  7. from .widget import register
  8. from traitlets import Unicode, CUnicode, Bytes, Bool
  9. from .trait_types import bytes_serialization
  10. from .util import text_type
  11. @register
  12. class _Media(DOMWidget, ValueWidget, CoreWidget):
  13. """Base class for Image, Audio and Video widgets.
  14. The `value` of this widget accepts a byte string. The byte string is the
  15. raw data that you want the browser to display.
  16. If you pass `"url"` to the `"format"` trait, `value` will be interpreted
  17. as a URL as bytes encoded in UTF-8.
  18. """
  19. # Define the custom state properties to sync with the front-end
  20. value = Bytes(help="The media data as a byte string.").tag(sync=True, **bytes_serialization)
  21. @classmethod
  22. def _from_file(cls, tag, filename, **kwargs):
  23. """
  24. Create an :class:`Media` from a local file.
  25. Parameters
  26. ----------
  27. filename: str
  28. The location of a file to read into the value from disk.
  29. **kwargs:
  30. The keyword arguments for `Media`
  31. Returns an `Media` with the value set from the filename.
  32. """
  33. value = cls._load_file_value(filename)
  34. if 'format' not in kwargs:
  35. format = cls._guess_format(tag, filename)
  36. if format is not None:
  37. kwargs['format'] = format
  38. return cls(value=value, **kwargs)
  39. @classmethod
  40. def from_url(cls, url, **kwargs):
  41. """
  42. Create an :class:`Media` from a URL.
  43. :code:`Media.from_url(url)` is equivalent to:
  44. .. code-block: python
  45. med = Media(value=url, format='url')
  46. But both unicode and bytes arguments are allowed for ``url``.
  47. Parameters
  48. ----------
  49. url: [str, bytes]
  50. The location of a URL to load.
  51. """
  52. if isinstance(url, text_type):
  53. # If unicode (str in Python 3), it needs to be encoded to bytes
  54. url = url.encode('utf-8')
  55. return cls(value=url, format='url')
  56. def set_value_from_file(self, filename):
  57. """
  58. Convenience method for reading a file into `value`.
  59. Parameters
  60. ----------
  61. filename: str
  62. The location of a file to read into value from disk.
  63. """
  64. value = self._load_file_value(filename)
  65. self.value = value
  66. @classmethod
  67. def _load_file_value(cls, filename):
  68. if getattr(filename, 'read', None) is not None:
  69. return filename.read()
  70. else:
  71. with open(filename, 'rb') as f:
  72. return f.read()
  73. @classmethod
  74. def _guess_format(cls, tag, filename):
  75. # file objects may have a .name parameter
  76. name = getattr(filename, 'name', None)
  77. name = name or filename
  78. try:
  79. mtype, _ = mimetypes.guess_type(name)
  80. if not mtype.startswith('{}/'.format(tag)):
  81. return None
  82. return mtype[len('{}/'.format(tag)):]
  83. except Exception:
  84. return None
  85. def _get_repr(self, cls):
  86. # Truncate the value in the repr, since it will
  87. # typically be very, very large.
  88. class_name = self.__class__.__name__
  89. # Return value first like a ValueWidget
  90. signature = []
  91. sig_value = repr(self.value)
  92. prefix, rest = sig_value.split("'", 1)
  93. content = rest[:-1]
  94. if len(content) > 100:
  95. sig_value = "{}'{}...'".format(prefix, content[0:100])
  96. signature.append('%s=%s' % ('value', sig_value))
  97. for key in super(cls, self)._repr_keys():
  98. if key == 'value':
  99. continue
  100. value = str(getattr(self, key))
  101. signature.append('%s=%r' % (key, value))
  102. signature = ', '.join(signature)
  103. return '%s(%s)' % (class_name, signature)
  104. @register
  105. class Image(_Media):
  106. """Displays an image as a widget.
  107. The `value` of this widget accepts a byte string. The byte string is the
  108. raw image data that you want the browser to display. You can explicitly
  109. define the format of the byte string using the `format` trait (which
  110. defaults to "png").
  111. If you pass `"url"` to the `"format"` trait, `value` will be interpreted
  112. as a URL as bytes encoded in UTF-8.
  113. """
  114. _view_name = Unicode('ImageView').tag(sync=True)
  115. _model_name = Unicode('ImageModel').tag(sync=True)
  116. # Define the custom state properties to sync with the front-end
  117. format = Unicode('png', help="The format of the image.").tag(sync=True)
  118. width = CUnicode(help="Width of the image in pixels. Use layout.width "
  119. "for styling the widget.").tag(sync=True)
  120. height = CUnicode(help="Height of the image in pixels. Use layout.height "
  121. "for styling the widget.").tag(sync=True)
  122. def __init__(self, *args, **kwargs):
  123. super(Image, self).__init__(*args, **kwargs)
  124. @classmethod
  125. def from_file(cls, filename, **kwargs):
  126. return cls._from_file('image', filename, **kwargs)
  127. def __repr__(self):
  128. return self._get_repr(Image)
  129. @register
  130. class Video(_Media):
  131. """Displays a video as a widget.
  132. The `value` of this widget accepts a byte string. The byte string is the
  133. raw video data that you want the browser to display. You can explicitly
  134. define the format of the byte string using the `format` trait (which
  135. defaults to "mp4").
  136. If you pass `"url"` to the `"format"` trait, `value` will be interpreted
  137. as a URL as bytes encoded in UTF-8.
  138. """
  139. _view_name = Unicode('VideoView').tag(sync=True)
  140. _model_name = Unicode('VideoModel').tag(sync=True)
  141. # Define the custom state properties to sync with the front-end
  142. format = Unicode('mp4', help="The format of the video.").tag(sync=True)
  143. width = CUnicode(help="Width of the video in pixels.").tag(sync=True)
  144. height = CUnicode(help="Height of the video in pixels.").tag(sync=True)
  145. autoplay = Bool(True, help="When true, the video starts when it's displayed").tag(sync=True)
  146. loop = Bool(True, help="When true, the video will start from the beginning after finishing").tag(sync=True)
  147. controls = Bool(True, help="Specifies that video controls should be displayed (such as a play/pause button etc)").tag(sync=True)
  148. @classmethod
  149. def from_file(cls, filename, **kwargs):
  150. return cls._from_file('video', filename, **kwargs)
  151. def __repr__(self):
  152. return self._get_repr(Video)
  153. @register
  154. class Audio(_Media):
  155. """Displays a audio as a widget.
  156. The `value` of this widget accepts a byte string. The byte string is the
  157. raw audio data that you want the browser to display. You can explicitly
  158. define the format of the byte string using the `format` trait (which
  159. defaults to "mp3").
  160. If you pass `"url"` to the `"format"` trait, `value` will be interpreted
  161. as a URL as bytes encoded in UTF-8.
  162. """
  163. _view_name = Unicode('AudioView').tag(sync=True)
  164. _model_name = Unicode('AudioModel').tag(sync=True)
  165. # Define the custom state properties to sync with the front-end
  166. format = Unicode('mp3', help="The format of the audio.").tag(sync=True)
  167. autoplay = Bool(True, help="When true, the audio starts when it's displayed").tag(sync=True)
  168. loop = Bool(True, help="When true, the audio will start from the beginning after finishing").tag(sync=True)
  169. controls = Bool(True, help="Specifies that audio controls should be displayed (such as a play/pause button etc)").tag(sync=True)
  170. @classmethod
  171. def from_file(cls, filename, **kwargs):
  172. return cls._from_file('audio', filename, **kwargs)
  173. def __repr__(self):
  174. return self._get_repr(Audio)