streamlit-nightly 1.31.2.dev20240220__py2.py3-none-any.whl → 1.31.2.dev20240222__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,177 @@
1
+ # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2024)
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ import hashlib
18
+ import io
19
+ import os
20
+ import re
21
+ from pathlib import Path
22
+
23
+ from streamlit import runtime
24
+ from streamlit.runtime import caching
25
+
26
+ # Regular expression to match the SRT timestamp format
27
+ # It matches the
28
+ # "hours:minutes:seconds,milliseconds --> hours:minutes:seconds,milliseconds" format
29
+ SRT_VALIDATION_REGEX = r"\d{2}:\d{2}:\d{2},\d{3} --> \d{2}:\d{2}:\d{2},\d{3}"
30
+
31
+ SRT_CONVERSION_REGEX = r"(\d{2}:\d{2}:\d{2}),(\d{3})"
32
+
33
+ SUBTITLE_ALLOWED_FORMATS = (".srt", ".vtt")
34
+
35
+
36
+ def _is_srt(stream: str | io.BytesIO | bytes) -> bool:
37
+ # Handle raw bytes
38
+ if isinstance(stream, bytes):
39
+ stream = io.BytesIO(stream)
40
+
41
+ # Convert str to io.BytesIO if 'stream' is a string
42
+ if isinstance(stream, str):
43
+ stream = io.BytesIO(stream.encode("utf-8"))
44
+
45
+ # Set the stream position to the beginning in case it's been moved
46
+ stream.seek(0)
47
+
48
+ # Read enough bytes to reliably check for SRT patterns
49
+ # This might be adjusted, but 33 bytes should be enough to read the first numeric
50
+ # line, the full timestamp line, and a bit of the next line
51
+ header = stream.read(33)
52
+
53
+ try:
54
+ header_str = header.decode("utf-8").strip() # Decode and strip whitespace
55
+ except UnicodeDecodeError:
56
+ # If it's not valid utf-8, it's probably not a valid SRT file
57
+ return False
58
+
59
+ # Split the header into lines and process them
60
+ lines = header_str.split("\n")
61
+
62
+ # Check for the pattern of an SRT file: digit(s), newline, timestamp
63
+ if len(lines) >= 2 and lines[0].isdigit():
64
+ match = re.search(SRT_VALIDATION_REGEX, lines[1])
65
+ if match:
66
+ return True
67
+
68
+ return False
69
+
70
+
71
+ def _srt_to_vtt(srt_data: str | bytes) -> bytes:
72
+ """
73
+ Convert subtitles from SubRip (.srt) format to WebVTT (.vtt) format.
74
+ This function accepts the content of the .srt file either as a string
75
+ or as a BytesIO stream.
76
+ Parameters
77
+ ----------
78
+ srt_data : str or bytes
79
+ The content of the .srt file as a string or a bytes stream.
80
+ Returns
81
+ -------
82
+ bytes
83
+ The content converted into .vtt format.
84
+ """
85
+
86
+ # If the input is a bytes stream, convert it to a string
87
+ if isinstance(srt_data, bytes):
88
+ # Decode the bytes to a UTF-8 string
89
+ try:
90
+ srt_data = srt_data.decode("utf-8")
91
+ except UnicodeDecodeError as e:
92
+ raise ValueError("Could not decode the input stream as UTF-8.") from e
93
+ if not isinstance(srt_data, str):
94
+ # If it's not a string by this point, something is wrong.
95
+ raise TypeError(
96
+ f"Input must be a string or a bytes stream, not {type(srt_data)}."
97
+ )
98
+
99
+ # Replace SubRip timing with WebVTT timing
100
+ vtt_data = re.sub(SRT_CONVERSION_REGEX, r"\1.\2", srt_data)
101
+
102
+ # Add WebVTT file header
103
+ vtt_content = "WEBVTT\n\n" + vtt_data
104
+ # Convert the vtt content to bytes
105
+ vtt_content = vtt_content.strip().encode("utf-8")
106
+
107
+ return vtt_content
108
+
109
+
110
+ def _handle_string_or_path_data(data_or_path: str | Path) -> bytes:
111
+ """Handles string data, either as a file path or raw content."""
112
+ if os.path.isfile(data_or_path):
113
+ path = Path(data_or_path)
114
+ file_extension = path.suffix.lower()
115
+
116
+ if file_extension not in SUBTITLE_ALLOWED_FORMATS:
117
+ raise ValueError(
118
+ f"Incorrect subtitle format {file_extension}. Subtitles must be in "
119
+ f"one of the following formats: {', '.join(SUBTITLE_ALLOWED_FORMATS)}"
120
+ )
121
+ with open(data_or_path, "rb") as file:
122
+ content = file.read()
123
+ return _srt_to_vtt(content) if file_extension == ".srt" else content
124
+ elif isinstance(data_or_path, Path):
125
+ raise ValueError(f"File {data_or_path} does not exist.")
126
+
127
+ content_string = data_or_path.strip()
128
+
129
+ if content_string.startswith("WEBVTT") or content_string == "":
130
+ return content_string.encode("utf-8")
131
+ elif _is_srt(content_string):
132
+ return _srt_to_vtt(content_string)
133
+ raise ValueError("The provided string neither matches valid VTT nor SRT format.")
134
+
135
+
136
+ def _handle_stream_data(stream: io.BytesIO) -> bytes:
137
+ """Handles io.BytesIO data, converting SRT to VTT content if needed."""
138
+ stream.seek(0)
139
+ stream_data = stream.getvalue()
140
+ return _srt_to_vtt(stream_data) if _is_srt(stream) else stream_data
141
+
142
+
143
+ def _handle_bytes_data(data: bytes) -> bytes:
144
+ """Handles io.BytesIO data, converting SRT to VTT content if needed."""
145
+ return _srt_to_vtt(data) if _is_srt(data) else data
146
+
147
+
148
+ def process_subtitle_data(
149
+ coordinates: str,
150
+ data: str | bytes | Path | io.BytesIO,
151
+ label: str,
152
+ ) -> str:
153
+
154
+ # Determine the type of data and process accordingly
155
+ if isinstance(data, (str, Path)):
156
+ subtitle_data = _handle_string_or_path_data(data)
157
+ elif isinstance(data, io.BytesIO):
158
+ subtitle_data = _handle_stream_data(data)
159
+ elif isinstance(data, bytes):
160
+ subtitle_data = _handle_bytes_data(data)
161
+ else:
162
+ raise TypeError(f"Invalid binary data format for subtitle: {type(data)}.")
163
+
164
+ if runtime.exists():
165
+ filename = hashlib.md5(label.encode()).hexdigest()
166
+ # Save the processed data and return the file URL
167
+ file_url = runtime.get_instance().media_file_mgr.add(
168
+ path_or_data=subtitle_data,
169
+ mimetype="text/vtt",
170
+ coordinates=coordinates,
171
+ file_name=f"{filename}.vtt",
172
+ )
173
+ caching.save_media_data(subtitle_data, "text/vtt", coordinates)
174
+ return file_url
175
+ else:
176
+ # When running in "raw mode", we can't access the MediaFileManager.
177
+ return ""
@@ -16,12 +16,14 @@ from __future__ import annotations
16
16
 
17
17
  import io
18
18
  import re
19
- from typing import TYPE_CHECKING, Final, Union, cast
19
+ from pathlib import Path
20
+ from typing import TYPE_CHECKING, Dict, Final, Union, cast
20
21
 
21
22
  from typing_extensions import TypeAlias
22
23
 
23
24
  import streamlit as st
24
25
  from streamlit import runtime, type_util, url_util
26
+ from streamlit.elements.lib.subtitle_utils import process_subtitle_data
25
27
  from streamlit.errors import StreamlitAPIException
26
28
  from streamlit.proto.Audio_pb2 import Audio as AudioProto
27
29
  from streamlit.proto.Video_pb2 import Video as VideoProto
@@ -39,6 +41,10 @@ MediaData: TypeAlias = Union[
39
41
  str, bytes, io.BytesIO, io.RawIOBase, io.BufferedReader, "npt.NDArray[Any]", None
40
42
  ]
41
43
 
44
+ SubtitleData: TypeAlias = Union[
45
+ str, Path, bytes, io.BytesIO, Dict[str, Union[str, Path, bytes, io.BytesIO]], None
46
+ ]
47
+
42
48
 
43
49
  class MediaMixin:
44
50
  @gather_metrics("audio")
@@ -120,6 +126,8 @@ class MediaMixin:
120
126
  data: MediaData,
121
127
  format: str = "video/mp4",
122
128
  start_time: int = 0,
129
+ *, # keyword-only arguments:
130
+ subtitles: SubtitleData = None,
123
131
  ) -> DeltaGenerator:
124
132
  """Display a video player.
125
133
 
@@ -135,6 +143,22 @@ class MediaMixin:
135
143
  See https://tools.ietf.org/html/rfc4281 for more info.
136
144
  start_time: int
137
145
  The time from which this element should start playing.
146
+ subtitles: str, dict, or io.BytesIO
147
+ Optional subtitle data for the video, supporting several input types:
148
+ * None (default): No subtitles.
149
+ * A string: File path to a subtitle file in '.vtt' or '.srt' formats, or
150
+ the raw content of subtitles conforming to these formats.
151
+ If providing raw content, the string must adhere to the WebVTT or SRT
152
+ format specifications.
153
+ * A dictionary: Pairs of labels and file paths or raw subtitle content in
154
+ '.vtt' or '.srt' formats.
155
+ Enables multiple subtitle tracks. The label will be shown in the video
156
+ player. Example:
157
+ {'English': 'path/to/english.vtt', 'French': 'path/to/french.srt'}
158
+ * io.BytesIO: A BytesIO stream that contains valid '.vtt' or '.srt'
159
+ formatted subtitle data. When provided, subtitles are displayed
160
+ by default. For multiple tracks, the first one is displayed by default.
161
+ Not supported for YouTube videos.
138
162
 
139
163
  Example
140
164
  -------
@@ -159,7 +183,7 @@ class MediaMixin:
159
183
  """
160
184
  video_proto = VideoProto()
161
185
  coordinates = self.dg._get_delta_path_str()
162
- marshall_video(coordinates, video_proto, data, format, start_time)
186
+ marshall_video(coordinates, video_proto, data, format, start_time, subtitles)
163
187
  return self.dg._enqueue("video", video_proto)
164
188
 
165
189
  @property
@@ -263,6 +287,7 @@ def marshall_video(
263
287
  data: MediaData,
264
288
  mimetype: str = "video/mp4",
265
289
  start_time: int = 0,
290
+ subtitles: SubtitleData = None,
266
291
  ) -> None:
267
292
  """Marshalls a video proto, using url processors as needed.
268
293
 
@@ -281,6 +306,17 @@ def marshall_video(
281
306
  See https://tools.ietf.org/html/rfc4281 for more info.
282
307
  start_time : int
283
308
  The time from which this element should start playing. (default: 0)
309
+ subtitles: str, dict, or io.BytesIO
310
+ Optional subtitle data for the video, supporting several input types:
311
+ * None (default): No subtitles.
312
+ * A string: File path to a subtitle file in '.vtt' or '.srt' formats, or the raw content of subtitles conforming to these formats.
313
+ If providing raw content, the string must adhere to the WebVTT or SRT format specifications.
314
+ * A dictionary: Pairs of labels and file paths or raw subtitle content in '.vtt' or '.srt' formats.
315
+ Enables multiple subtitle tracks. The label will be shown in the video player.
316
+ Example: {'English': 'path/to/english.vtt', 'French': 'path/to/french.srt'}
317
+ * io.BytesIO: A BytesIO stream that contains valid '.vtt' or '.srt' formatted subtitle data.
318
+ When provided, subtitles are displayed by default. For multiple tracks, the first one is displayed by default.
319
+ Not supported for YouTube videos.
284
320
  """
285
321
 
286
322
  proto.start_time = start_time
@@ -294,12 +330,50 @@ def marshall_video(
294
330
  if youtube_url := _reshape_youtube_url(data):
295
331
  proto.url = youtube_url
296
332
  proto.type = VideoProto.Type.YOUTUBE_IFRAME
333
+ if subtitles:
334
+ raise StreamlitAPIException(
335
+ "Subtitles are not supported for YouTube videos."
336
+ )
297
337
  else:
298
338
  proto.url = data
299
339
 
300
340
  else:
301
341
  _marshall_av_media(coordinates, proto, data, mimetype)
302
342
 
343
+ if subtitles:
344
+ subtitle_items: list[tuple[str, str | Path | bytes | io.BytesIO]] = []
345
+
346
+ # Single subtitle
347
+ if isinstance(subtitles, (str, bytes, io.BytesIO, Path)):
348
+ subtitle_items.append(("default", subtitles))
349
+ # Multiple subtitles
350
+ elif isinstance(subtitles, dict):
351
+ subtitle_items.extend(subtitles.items())
352
+ else:
353
+ raise StreamlitAPIException(
354
+ f"Unsupported data type for subtitles: {type(subtitles)}. "
355
+ f"Only str (file paths) and dict are supported."
356
+ )
357
+
358
+ for label, subtitle_data in subtitle_items:
359
+ sub = proto.subtitles.add()
360
+ sub.label = label or ""
361
+
362
+ # Coordinates used in media_file_manager to identify the place of
363
+ # element, in case of subtitle, we use same video coordinates
364
+ # with suffix.
365
+ # It is not aligned with common coordinates format, but in
366
+ # media_file_manager we use it just as unique identifier, so it is fine.
367
+ subtitle_coordinates = f"{coordinates}[subtitle{label}]"
368
+ try:
369
+ sub.url = process_subtitle_data(
370
+ subtitle_coordinates, subtitle_data, label
371
+ )
372
+ except (TypeError, ValueError) as original_err:
373
+ raise StreamlitAPIException(
374
+ f"Failed to process the provided subtitle: {label}"
375
+ ) from original_err
376
+
303
377
 
304
378
  def _validate_and_normalize(data: npt.NDArray[Any]) -> tuple[bytes, int]:
305
379
  """Validates and normalizes numpy array data.
@@ -13,7 +13,7 @@ _sym_db = _symbol_database.Default()
13
13
 
14
14
 
15
15
 
16
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1bstreamlit/proto/Video.proto\"\x97\x01\n\x05Video\x12\x0b\n\x03url\x18\x06 \x01(\t\x12\x12\n\nstart_time\x18\x03 \x01(\x05\x12\x19\n\x04type\x18\x05 \x01(\x0e\x32\x0b.Video.Type\"2\n\x04Type\x12\n\n\x06UNUSED\x10\x00\x12\n\n\x06NATIVE\x10\x01\x12\x12\n\x0eYOUTUBE_IFRAME\x10\x02J\x04\x08\x01\x10\x02J\x04\x08\x02\x10\x03J\x04\x08\x04\x10\x05R\x06\x66ormatR\x04\x64\x61taB*\n\x1c\x63om.snowflake.apps.streamlitB\nVideoProtob\x06proto3')
16
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1bstreamlit/proto/Video.proto\"+\n\rSubtitleTrack\x12\r\n\x05label\x18\x01 \x01(\t\x12\x0b\n\x03url\x18\x02 \x01(\t\"\xba\x01\n\x05Video\x12\x0b\n\x03url\x18\x06 \x01(\t\x12\x12\n\nstart_time\x18\x03 \x01(\x05\x12\x19\n\x04type\x18\x05 \x01(\x0e\x32\x0b.Video.Type\x12!\n\tsubtitles\x18\x07 \x03(\x0b\x32\x0e.SubtitleTrack\"2\n\x04Type\x12\n\n\x06UNUSED\x10\x00\x12\n\n\x06NATIVE\x10\x01\x12\x12\n\x0eYOUTUBE_IFRAME\x10\x02J\x04\x08\x01\x10\x02J\x04\x08\x02\x10\x03J\x04\x08\x04\x10\x05R\x06\x66ormatR\x04\x64\x61taB*\n\x1c\x63om.snowflake.apps.streamlitB\nVideoProtob\x06proto3')
17
17
 
18
18
  _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
19
19
  _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'streamlit.proto.Video_pb2', globals())
@@ -21,8 +21,10 @@ if _descriptor._USE_C_DESCRIPTORS == False:
21
21
 
22
22
  DESCRIPTOR._options = None
23
23
  DESCRIPTOR._serialized_options = b'\n\034com.snowflake.apps.streamlitB\nVideoProto'
24
- _VIDEO._serialized_start=32
25
- _VIDEO._serialized_end=183
26
- _VIDEO_TYPE._serialized_start=101
27
- _VIDEO_TYPE._serialized_end=151
24
+ _SUBTITLETRACK._serialized_start=31
25
+ _SUBTITLETRACK._serialized_end=74
26
+ _VIDEO._serialized_start=77
27
+ _VIDEO._serialized_end=263
28
+ _VIDEO_TYPE._serialized_start=181
29
+ _VIDEO_TYPE._serialized_end=231
28
30
  # @@protoc_insertion_point(module_scope)
@@ -17,7 +17,9 @@ See the License for the specific language governing permissions and
17
17
  limitations under the License.
18
18
  """
19
19
  import builtins
20
+ import collections.abc
20
21
  import google.protobuf.descriptor
22
+ import google.protobuf.internal.containers
21
23
  import google.protobuf.internal.enum_type_wrapper
22
24
  import google.protobuf.message
23
25
  import sys
@@ -30,6 +32,24 @@ else:
30
32
 
31
33
  DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
32
34
 
35
+ class SubtitleTrack(google.protobuf.message.Message):
36
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
37
+
38
+ LABEL_FIELD_NUMBER: builtins.int
39
+ URL_FIELD_NUMBER: builtins.int
40
+ label: builtins.str
41
+ """Label for the subtitle e.g. "English" or "Spanish"."""
42
+ url: builtins.str
43
+ def __init__(
44
+ self,
45
+ *,
46
+ label: builtins.str = ...,
47
+ url: builtins.str = ...,
48
+ ) -> None: ...
49
+ def ClearField(self, field_name: typing_extensions.Literal["label", b"label", "url", b"url"]) -> None: ...
50
+
51
+ global___SubtitleTrack = SubtitleTrack
52
+
33
53
  class Video(google.protobuf.message.Message):
34
54
  DESCRIPTOR: google.protobuf.descriptor.Descriptor
35
55
 
@@ -53,19 +73,24 @@ class Video(google.protobuf.message.Message):
53
73
  URL_FIELD_NUMBER: builtins.int
54
74
  START_TIME_FIELD_NUMBER: builtins.int
55
75
  TYPE_FIELD_NUMBER: builtins.int
76
+ SUBTITLES_FIELD_NUMBER: builtins.int
56
77
  url: builtins.str
57
78
  """A url pointing to a video file"""
58
79
  start_time: builtins.int
59
80
  """The currentTime attribute of the HTML <video> tag's <source> subtag."""
60
81
  type: global___Video.Type.ValueType
61
82
  """Type affects how browser wraps the video in tags: plain HTML5, YouTube..."""
83
+ @property
84
+ def subtitles(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___SubtitleTrack]:
85
+ """Repeated field for subtitle tracks"""
62
86
  def __init__(
63
87
  self,
64
88
  *,
65
89
  url: builtins.str = ...,
66
90
  start_time: builtins.int = ...,
67
91
  type: global___Video.Type.ValueType = ...,
92
+ subtitles: collections.abc.Iterable[global___SubtitleTrack] | None = ...,
68
93
  ) -> None: ...
69
- def ClearField(self, field_name: typing_extensions.Literal["start_time", b"start_time", "type", b"type", "url", b"url"]) -> None: ...
94
+ def ClearField(self, field_name: typing_extensions.Literal["start_time", b"start_time", "subtitles", b"subtitles", "type", b"type", "url", b"url"]) -> None: ...
70
95
 
71
96
  global___Video = Video
@@ -39,6 +39,7 @@ _LOGGER: Final = get_logger(__name__)
39
39
  # so we handle it ourselves.
40
40
  PREFERRED_MIMETYPE_EXTENSION_MAP: Final = {
41
41
  "audio/wav": ".wav",
42
+ "text/vtt": ".vtt",
42
43
  }
43
44
 
44
45
 
@@ -512,7 +512,7 @@ class ScriptRunner:
512
512
  # ...
513
513
  # ```
514
514
  # in their scripts.
515
- module = _new_module("__main__")
515
+ module = self._new_module("__main__")
516
516
 
517
517
  # Install the fake module as the __main__ module. This allows
518
518
  # the pickle module to work inside the user's code, since it now
@@ -626,6 +626,10 @@ class ScriptRunner:
626
626
  if config.get_option("runner.postScriptGC"):
627
627
  gc.collect(2)
628
628
 
629
+ def _new_module(self, name: str) -> types.ModuleType:
630
+ """Create a new module with the given name."""
631
+ return types.ModuleType(name)
632
+
629
633
 
630
634
  class ScriptControlException(BaseException):
631
635
  """Base exception for ScriptRunner."""
@@ -676,11 +680,6 @@ def _clean_problem_modules() -> None:
676
680
  pass
677
681
 
678
682
 
679
- def _new_module(name: str) -> types.ModuleType:
680
- """Create a new module with the given name."""
681
- return types.ModuleType(name)
682
-
683
-
684
683
  # The reason this is not a decorator is because we want to make it clear at the
685
684
  # calling location that this function is being used.
686
685
  def _log_if_error(fn: Callable[[], None]) -> None:
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "files": {
3
3
  "main.css": "./static/css/main.77d1c464.css",
4
- "main.js": "./static/js/main.c1c3de9f.js",
4
+ "main.js": "./static/js/main.7f946e0f.js",
5
5
  "static/js/9336.2d95d840.chunk.js": "./static/js/9336.2d95d840.chunk.js",
6
6
  "static/js/9330.c0dd1723.chunk.js": "./static/js/9330.c0dd1723.chunk.js",
7
7
  "static/js/7217.d970c074.chunk.js": "./static/js/7217.d970c074.chunk.js",
@@ -19,7 +19,7 @@
19
19
  "static/js/2469.3e9c3ce9.chunk.js": "./static/js/2469.3e9c3ce9.chunk.js",
20
20
  "static/js/4113.1e7eff4d.chunk.js": "./static/js/4113.1e7eff4d.chunk.js",
21
21
  "static/js/1168.d5c307a0.chunk.js": "./static/js/1168.d5c307a0.chunk.js",
22
- "static/js/178.9aed59c0.chunk.js": "./static/js/178.9aed59c0.chunk.js",
22
+ "static/js/178.2203b5c2.chunk.js": "./static/js/178.2203b5c2.chunk.js",
23
23
  "static/js/1792.16c16498.chunk.js": "./static/js/1792.16c16498.chunk.js",
24
24
  "static/js/3513.57cff89c.chunk.js": "./static/js/3513.57cff89c.chunk.js",
25
25
  "static/js/7602.7e796651.chunk.js": "./static/js/7602.7e796651.chunk.js",
@@ -150,6 +150,6 @@
150
150
  },
151
151
  "entrypoints": [
152
152
  "static/css/main.77d1c464.css",
153
- "static/js/main.c1c3de9f.js"
153
+ "static/js/main.7f946e0f.js"
154
154
  ]
155
155
  }
@@ -1 +1 @@
1
- <!doctype html><html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"/><link rel="shortcut icon" href="./favicon.png"/><link rel="preload" href="./static/media/SourceSansPro-Regular.0d69e5ff5e92ac64a0c9.woff2" as="font" type="font/woff2" crossorigin><link rel="preload" href="./static/media/SourceSansPro-SemiBold.abed79cd0df1827e18cf.woff2" as="font" type="font/woff2" crossorigin><link rel="preload" href="./static/media/SourceSansPro-Bold.118dea98980e20a81ced.woff2" as="font" type="font/woff2" crossorigin><title>Streamlit</title><script>window.prerenderReady=!1</script><script defer="defer" src="./static/js/main.c1c3de9f.js"></script><link href="./static/css/main.77d1c464.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
1
+ <!doctype html><html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"/><link rel="shortcut icon" href="./favicon.png"/><link rel="preload" href="./static/media/SourceSansPro-Regular.0d69e5ff5e92ac64a0c9.woff2" as="font" type="font/woff2" crossorigin><link rel="preload" href="./static/media/SourceSansPro-SemiBold.abed79cd0df1827e18cf.woff2" as="font" type="font/woff2" crossorigin><link rel="preload" href="./static/media/SourceSansPro-Bold.118dea98980e20a81ced.woff2" as="font" type="font/woff2" crossorigin><title>Streamlit</title><script>window.prerenderReady=!1</script><script defer="defer" src="./static/js/main.7f946e0f.js"></script><link href="./static/css/main.77d1c464.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
@@ -0,0 +1 @@
1
+ "use strict";(self.webpackChunk_streamlit_app=self.webpackChunk_streamlit_app||[]).push([[178],{178:(e,t,r)=>{r.r(t),r.d(t,{default:()=>d});var a=r(66845),s=r(16295),i=r(68785),n=r(40864);const l=528;function d(e){let{element:t,width:r,endpoints:d}=e;const c=(0,a.useRef)(null),{type:o,url:u,startTime:m,subtitles:f}=t;(0,a.useEffect)((()=>{c.current&&(c.current.currentTime=m)}),[m]),(0,a.useEffect)((()=>{const e=c.current,r=()=>{e&&(e.currentTime=t.startTime)};return e&&e.addEventListener("loadedmetadata",r),()=>{e&&e.removeEventListener("loadedmetadata",r)}}),[t]);const h=e=>{const{startTime:r}=t;return r?"".concat(e,"?start=").concat(r):e};if(o===s.nk.Type.YOUTUBE_IFRAME){const e=0!==r?.75*r:l;return(0,n.jsx)("iframe",{"data-testid":"stVideo",title:u,src:h(u),width:r,height:e,style:{colorScheme:"light dark"},frameBorder:"0",allow:"autoplay; encrypted-media",allowFullScreen:!0})}return(0,n.jsx)("video",{"data-testid":"stVideo",ref:c,controls:!0,src:d.buildMediaURL(u),className:"stVideo",style:{width:r,height:0===r?l:void 0},crossOrigin:i.td&&f.length>0?"anonymous":void 0,children:f&&f.map(((e,t)=>(0,n.jsx)("track",{kind:"captions",src:d.buildMediaURL(e.url),label:e.label,default:0===t},t)))})}}}]);