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.
- streamlit/elements/lib/subtitle_utils.py +177 -0
- streamlit/elements/media.py +76 -2
- streamlit/proto/Video_pb2.py +7 -5
- streamlit/proto/Video_pb2.pyi +26 -1
- streamlit/runtime/memory_media_file_storage.py +1 -0
- streamlit/runtime/scriptrunner/script_runner.py +5 -6
- streamlit/static/asset-manifest.json +3 -3
- streamlit/static/index.html +1 -1
- streamlit/static/static/js/178.2203b5c2.chunk.js +1 -0
- streamlit/static/static/js/main.7f946e0f.js +2 -0
- streamlit/testing/v1/app_test.py +38 -11
- streamlit/testing/v1/local_script_runner.py +11 -0
- {streamlit_nightly-1.31.2.dev20240220.dist-info → streamlit_nightly-1.31.2.dev20240222.dist-info}/METADATA +1 -1
- {streamlit_nightly-1.31.2.dev20240220.dist-info → streamlit_nightly-1.31.2.dev20240222.dist-info}/RECORD +19 -18
- streamlit/static/static/js/178.9aed59c0.chunk.js +0 -1
- streamlit/static/static/js/main.c1c3de9f.js +0 -2
- /streamlit/static/static/js/{main.c1c3de9f.js.LICENSE.txt → main.7f946e0f.js.LICENSE.txt} +0 -0
- {streamlit_nightly-1.31.2.dev20240220.data → streamlit_nightly-1.31.2.dev20240222.data}/scripts/streamlit.cmd +0 -0
- {streamlit_nightly-1.31.2.dev20240220.dist-info → streamlit_nightly-1.31.2.dev20240222.dist-info}/WHEEL +0 -0
- {streamlit_nightly-1.31.2.dev20240220.dist-info → streamlit_nightly-1.31.2.dev20240222.dist-info}/entry_points.txt +0 -0
- {streamlit_nightly-1.31.2.dev20240220.dist-info → streamlit_nightly-1.31.2.dev20240222.dist-info}/top_level.txt +0 -0
@@ -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 ""
|
streamlit/elements/media.py
CHANGED
@@ -16,12 +16,14 @@ from __future__ import annotations
|
|
16
16
|
|
17
17
|
import io
|
18
18
|
import re
|
19
|
-
from
|
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.
|
streamlit/proto/Video_pb2.py
CHANGED
@@ -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\"\
|
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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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)
|
streamlit/proto/Video_pb2.pyi
CHANGED
@@ -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
|
@@ -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.
|
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.
|
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.
|
153
|
+
"static/js/main.7f946e0f.js"
|
154
154
|
]
|
155
155
|
}
|
streamlit/static/index.html
CHANGED
@@ -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.
|
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)))})}}}]);
|