浏览代码

allow passing local file to image,video,audio src

Rodja Trappe 1 年之前
父节点
当前提交
6dc8e6c0e0

+ 7 - 2
nicegui/elements/audio.py

@@ -1,5 +1,8 @@
 import warnings
 import warnings
+from pathlib import Path
+from typing import Union
 
 
+from .. import globals
 from ..dependencies import register_component
 from ..dependencies import register_component
 from ..element import Element
 from ..element import Element
 
 
@@ -8,7 +11,7 @@ register_component('audio', __file__, 'audio.js')
 
 
 class Audio(Element):
 class Audio(Element):
 
 
-    def __init__(self, src: str, *,
+    def __init__(self, src: Union[str, Path], *,
                  controls: bool = True,
                  controls: bool = True,
                  autoplay: bool = False,
                  autoplay: bool = False,
                  muted: bool = False,
                  muted: bool = False,
@@ -17,7 +20,7 @@ class Audio(Element):
                  ) -> None:
                  ) -> None:
         """Audio
         """Audio
 
 
-        :param src: URL of the audio source
+        :param src: URL or local file path of the audio source
         :param controls: whether to show the audio controls, like play, pause, and volume (default: `True`)
         :param controls: whether to show the audio controls, like play, pause, and volume (default: `True`)
         :param autoplay: whether to start playing the audio automatically (default: `False`)
         :param autoplay: whether to start playing the audio automatically (default: `False`)
         :param muted: whether the audio should be initially muted (default: `False`)
         :param muted: whether the audio should be initially muted (default: `False`)
@@ -27,6 +30,8 @@ class Audio(Element):
         for a list of events you can subscribe to using the generic event subscription `on()`.
         for a list of events you can subscribe to using the generic event subscription `on()`.
         """
         """
         super().__init__('audio')
         super().__init__('audio')
+        if Path(src).is_file():
+            src = globals.app.add_media_file(src)
         self._props['src'] = src
         self._props['src'] = src
         self._props['controls'] = controls
         self._props['controls'] = controls
         self._props['autoplay'] = autoplay
         self._props['autoplay'] = autoplay

+ 5 - 2
nicegui/elements/image.py

@@ -1,13 +1,16 @@
+from pathlib import Path
+from typing import Union
+
 from .mixins.source_element import SourceElement
 from .mixins.source_element import SourceElement
 
 
 
 
 class Image(SourceElement):
 class Image(SourceElement):
 
 
-    def __init__(self, source: str = '') -> None:
+    def __init__(self, source: Union[str, Path] = '') -> None:
         """Image
         """Image
 
 
         Displays an image.
         Displays an image.
 
 
-        :param source: the source of the image; can be a URL or a base64 string
+        :param source: the source of the image; can be a URL, local file path or a base64 string
         """
         """
         super().__init__(tag='q-img', source=source)
         super().__init__(tag='q-img', source=source)

+ 3 - 2
nicegui/elements/interactive_image.py

@@ -1,5 +1,6 @@
 from __future__ import annotations
 from __future__ import annotations
 
 
+from pathlib import Path
 from typing import Any, Callable, Dict, List, Optional
 from typing import Any, Callable, Dict, List, Optional
 
 
 from ..dependencies import register_component
 from ..dependencies import register_component
@@ -14,7 +15,7 @@ class InteractiveImage(SourceElement, ContentElement):
     CONTENT_PROP = 'content'
     CONTENT_PROP = 'content'
 
 
     def __init__(self,
     def __init__(self,
-                 source: str = '', *,
+                 source: Union[str, Path] = '', *,
                  content: str = '',
                  content: str = '',
                  on_mouse: Optional[Callable[..., Any]] = None,
                  on_mouse: Optional[Callable[..., Any]] = None,
                  events: List[str] = ['click'],
                  events: List[str] = ['click'],
@@ -28,7 +29,7 @@ class InteractiveImage(SourceElement, ContentElement):
         Thereby repeatedly updating the image source will automatically adapt to the available bandwidth.
         Thereby repeatedly updating the image source will automatically adapt to the available bandwidth.
         See `OpenCV Webcam <https://github.com/zauberzeug/nicegui/tree/main/examples/opencv_webcam/main.py>`_ for an example.
         See `OpenCV Webcam <https://github.com/zauberzeug/nicegui/tree/main/examples/opencv_webcam/main.py>`_ for an example.
 
 
-        :param source: the source of the image; can be an URL or a base64 string
+        :param source: the source of the image; can be an URL, local file path or a base64 string
         :param content: SVG content which should be overlayed; viewport has the same dimensions as the image
         :param content: SVG content which should be overlayed; viewport has the same dimensions as the image
         :param on_mouse: callback for mouse events (yields `type`, `image_x` and `image_y`)
         :param on_mouse: callback for mouse events (yields `type`, `image_x` and `image_y`)
         :param events: list of JavaScript events to subscribe to (default: `['click']`)
         :param events: list of JavaScript events to subscribe to (default: `['click']`)

+ 4 - 0
nicegui/elements/mixins/source_element.py

@@ -1,7 +1,9 @@
+from pathlib import Path
 from typing import Any, Callable
 from typing import Any, Callable
 
 
 from typing_extensions import Self
 from typing_extensions import Self
 
 
+from ... import globals
 from ...binding import BindableProperty, bind, bind_from, bind_to
 from ...binding import BindableProperty, bind, bind_from, bind_to
 from ...element import Element
 from ...element import Element
 
 
@@ -11,6 +13,8 @@ class SourceElement(Element):
 
 
     def __init__(self, *, source: str, **kwargs: Any) -> None:
     def __init__(self, *, source: str, **kwargs: Any) -> None:
         super().__init__(**kwargs)
         super().__init__(**kwargs)
+        if Path(source).is_file():
+            source = globals.app.add_static_file(source)
         self.source = source
         self.source = source
         self._props['src'] = source
         self._props['src'] = source
 
 

+ 7 - 2
nicegui/elements/video.py

@@ -1,5 +1,8 @@
 import warnings
 import warnings
+from pathlib import Path
+from typing import Union
 
 
+from .. import globals
 from ..dependencies import register_component
 from ..dependencies import register_component
 from ..element import Element
 from ..element import Element
 
 
@@ -8,7 +11,7 @@ register_component('video', __file__, 'video.js')
 
 
 class Video(Element):
 class Video(Element):
 
 
-    def __init__(self, src: str, *,
+    def __init__(self, src: Union[str, Path], *,
                  controls: bool = True,
                  controls: bool = True,
                  autoplay: bool = False,
                  autoplay: bool = False,
                  muted: bool = False,
                  muted: bool = False,
@@ -17,7 +20,7 @@ class Video(Element):
                  ) -> None:
                  ) -> None:
         """Video
         """Video
 
 
-        :param src: URL of the video source
+        :param src: URL or local file path of the video source
         :param controls: whether to show the video controls, like play, pause, and volume (default: `True`)
         :param controls: whether to show the video controls, like play, pause, and volume (default: `True`)
         :param autoplay: whether to start playing the video automatically (default: `False`)
         :param autoplay: whether to start playing the video automatically (default: `False`)
         :param muted: whether the video should be initially muted (default: `False`)
         :param muted: whether the video should be initially muted (default: `False`)
@@ -27,6 +30,8 @@ class Video(Element):
         for a list of events you can subscribe to using the generic event subscription `on()`.
         for a list of events you can subscribe to using the generic event subscription `on()`.
         """
         """
         super().__init__('video')
         super().__init__('video')
+        if Path(src).is_file():
+            src = globals.app.add_media_file(src)
         self._props['src'] = src
         self._props['src'] = src
         self._props['controls'] = controls
         self._props['controls'] = controls
         self._props['autoplay'] = autoplay
         self._props['autoplay'] = autoplay

+ 34 - 7
tests/test_serving_files.py

@@ -10,22 +10,27 @@ from nicegui import app, ui
 from .screen import PORT, Screen
 from .screen import PORT, Screen
 from .test_helpers import TEST_DIR
 from .test_helpers import TEST_DIR
 
 
+IMAGE_FILE = Path(TEST_DIR).parent / 'examples' / 'slideshow' / 'slides' / 'slide1.jpg'
+VIDEO_FILE = Path(TEST_DIR) / 'media' / 'test.mp4'
+
 
 
 @pytest.fixture(autouse=True)
 @pytest.fixture(autouse=True)
 def provide_media_files():
 def provide_media_files():
-    mp4 = Path(TEST_DIR / 'media' / 'test.mp4')
-    if not mp4.exists():
-        mp4.parent.mkdir(exist_ok=True)
+    if not VIDEO_FILE.exists():
+        VIDEO_FILE.parent.mkdir(exist_ok=True)
         url = 'https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/360/Big_Buck_Bunny_360_10s_1MB.mp4'
         url = 'https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/360/Big_Buck_Bunny_360_10s_1MB.mp4'
         with httpx.stream('GET', url) as response:
         with httpx.stream('GET', url) as response:
-            with open(mp4, 'wb') as file:
+            with open(VIDEO_FILE, 'wb') as file:
                 for chunk in response.iter_raw():
                 for chunk in response.iter_raw():
                     file.write(chunk)
                     file.write(chunk)
 
 
 
 
 def assert_video_file_streaming(path: str) -> None:
 def assert_video_file_streaming(path: str) -> None:
     with httpx.Client() as http_client:
     with httpx.Client() as http_client:
-        r = http_client.get(f'http://localhost:{PORT}{path}', headers={'Range': 'bytes=0-1000'})
+        r = http_client.get(
+            path if 'http' in path else f'http://localhost:{PORT}{path}',
+            headers={'Range': 'bytes=0-1000'}
+        )
         assert r.status_code == 206
         assert r.status_code == 206
         assert r.headers['Accept-Ranges'] == 'bytes'
         assert r.headers['Accept-Ranges'] == 'bytes'
         assert r.headers['Content-Range'].startswith('bytes 0-1000/')
         assert r.headers['Content-Range'].startswith('bytes 0-1000/')
@@ -40,17 +45,39 @@ def test_media_files_can_be_streamed(screen: Screen):
 
 
 
 
 def test_adding_single_media_file(screen: Screen):
 def test_adding_single_media_file(screen: Screen):
-    url_path = app.add_media_file(Path(TEST_DIR) / 'media' / 'test.mp4')
+    url_path = app.add_media_file(VIDEO_FILE)
 
 
     screen.open('/')
     screen.open('/')
     assert_video_file_streaming(url_path)
     assert_video_file_streaming(url_path)
 
 
 
 
 def test_adding_single_static_file(screen: Screen):
 def test_adding_single_static_file(screen: Screen):
-    url_path = app.add_static_file(Path(TEST_DIR).parent / 'examples' / 'slideshow' / 'slides' / 'slide1.jpg')
+    url_path = app.add_static_file(IMAGE_FILE)
 
 
     screen.open('/')
     screen.open('/')
     with httpx.Client() as http_client:
     with httpx.Client() as http_client:
         r = http_client.get(f'http://localhost:{PORT}{url_path}')
         r = http_client.get(f'http://localhost:{PORT}{url_path}')
         assert r.status_code == 200
         assert r.status_code == 200
         assert 'max-age=' in r.headers['Cache-Control']
         assert 'max-age=' in r.headers['Cache-Control']
+
+
+def test_auto_serving_file_from_image_source(screen: Screen):
+    ui.image(IMAGE_FILE)
+
+    screen.open('/')
+    img = screen.find_by_tag('img')
+    assert '/_nicegui/auto/static/' in img.get_attribute('src')
+    assert screen.selenium.execute_script("""
+    return arguments[0].complete && 
+        typeof arguments[0].naturalWidth != "undefined" && 
+        arguments[0].naturalWidth > 0""", img), 'image should load successfully'
+
+
+def test_auto_serving_file_from_video_source(screen: Screen):
+    ui.video(VIDEO_FILE)
+
+    screen.open('/')
+    video = screen.find_by_tag('video')
+    assert '/_nicegui/auto/media/' in video.get_attribute('src')
+    ic(video.get_attribute('src'))
+    assert_video_file_streaming(video.get_attribute('src'))