Prathamesh1420
commited on
Upload 16 files
Browse files- .cruft.json +20 -0
- .flake8 +20 -0
- .gitignore +109 -0
- .pre-commit-config.yaml +19 -0
- LICENSE +22 -0
- MANIFEST.in +3 -0
- example_app/requirements.txt +2 -0
- example_app/streamlit_app.py +26 -0
- pyproject.toml +49 -0
- requirements.txt +1 -0
- setup.py +23 -0
- src/camera_input_live/__init__.py +57 -0
- src/camera_input_live/frontend/index.html +20 -0
- src/camera_input_live/frontend/main.js +111 -0
- src/camera_input_live/frontend/streamlit-component-lib.js +34 -0
- src/camera_input_live/frontend/style.css +43 -0
.cruft.json
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"template": "https://github.com/blackary/cookiecutter-streamlit-component/",
|
3 |
+
"commit": "c972054b7b0890a1aa1c02e792bedc71c525ea87",
|
4 |
+
"checkout": null,
|
5 |
+
"context": {
|
6 |
+
"cookiecutter": {
|
7 |
+
"author_name": "Zachary Blackwood",
|
8 |
+
"author_email": "[email protected]",
|
9 |
+
"project_name": "Streamlit Camera Input Live",
|
10 |
+
"package_name": "streamlit-camera-input-live",
|
11 |
+
"import_name": "camera_input_live",
|
12 |
+
"description": "Alternative version of st.camera_input which returns the webcam images live, without any button press needed",
|
13 |
+
"deployment_via_github_actions": "y",
|
14 |
+
"working_with_dataframes": "n",
|
15 |
+
"open_source_license": "MIT license",
|
16 |
+
"_template": "https://github.com/blackary/cookiecutter-streamlit-component/"
|
17 |
+
}
|
18 |
+
},
|
19 |
+
"directory": null
|
20 |
+
}
|
.flake8
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[flake8]
|
2 |
+
max-line-length = 88
|
3 |
+
select =
|
4 |
+
E # pep8 errors
|
5 |
+
F # pyflakes errors
|
6 |
+
W # pep8 warnings
|
7 |
+
B # flake8-bugbear warnings
|
8 |
+
ignore =
|
9 |
+
E501 # "Line lengths are recommended to be no greater than 79 characters"
|
10 |
+
E203 # "Whitespace before ':'": conflicts with black
|
11 |
+
W503 # "line break before binary operator": conflicts with black
|
12 |
+
exclude =
|
13 |
+
.git
|
14 |
+
.vscode
|
15 |
+
.pytest_cache
|
16 |
+
.mypy_cache
|
17 |
+
.venv
|
18 |
+
.env
|
19 |
+
.direnv
|
20 |
+
per-file-ignores =
|
.gitignore
ADDED
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Byte-compiled / optimized / DLL files
|
2 |
+
__pycache__/
|
3 |
+
*.py[cod]
|
4 |
+
*$py.class
|
5 |
+
|
6 |
+
# OSX useful to ignore
|
7 |
+
*.DS_Store
|
8 |
+
.AppleDouble
|
9 |
+
.LSOverride
|
10 |
+
|
11 |
+
# Thumbnails
|
12 |
+
._*
|
13 |
+
|
14 |
+
# Files that might appear in the root of a volume
|
15 |
+
.DocumentRevisions-V100
|
16 |
+
.fseventsd
|
17 |
+
.Spotlight-V100
|
18 |
+
.TemporaryItems
|
19 |
+
.Trashes
|
20 |
+
.VolumeIcon.icns
|
21 |
+
.com.apple.timemachine.donotpresent
|
22 |
+
|
23 |
+
# Directories potentially created on remote AFP share
|
24 |
+
.AppleDB
|
25 |
+
.AppleDesktop
|
26 |
+
Network Trash Folder
|
27 |
+
Temporary Items
|
28 |
+
.apdisk
|
29 |
+
|
30 |
+
# C extensions
|
31 |
+
*.so
|
32 |
+
|
33 |
+
# Distribution / packaging
|
34 |
+
.Python
|
35 |
+
env/
|
36 |
+
venv/
|
37 |
+
build/
|
38 |
+
develop-eggs/
|
39 |
+
dist/
|
40 |
+
downloads/
|
41 |
+
eggs/
|
42 |
+
.eggs/
|
43 |
+
lib/
|
44 |
+
lib64/
|
45 |
+
parts/
|
46 |
+
sdist/
|
47 |
+
var/
|
48 |
+
*.egg-info/
|
49 |
+
.installed.cfg
|
50 |
+
*.egg
|
51 |
+
|
52 |
+
# PyInstaller
|
53 |
+
# Usually these files are written by a python script from a template
|
54 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
55 |
+
*.manifest
|
56 |
+
*.spec
|
57 |
+
|
58 |
+
# Installer logs
|
59 |
+
pip-log.txt
|
60 |
+
pip-delete-this-directory.txt
|
61 |
+
|
62 |
+
# Unit test / coverage reports
|
63 |
+
htmlcov/
|
64 |
+
.tox/
|
65 |
+
.coverage
|
66 |
+
.coverage.*
|
67 |
+
.cache
|
68 |
+
nosetests.xml
|
69 |
+
coverage.xml
|
70 |
+
*,cover
|
71 |
+
.hypothesis/
|
72 |
+
.pytest_cache/
|
73 |
+
|
74 |
+
# Translations
|
75 |
+
*.mo
|
76 |
+
*.pot
|
77 |
+
|
78 |
+
# Django stuff:
|
79 |
+
*.log
|
80 |
+
|
81 |
+
# Sphinx documentation
|
82 |
+
docs/_build/
|
83 |
+
|
84 |
+
# IntelliJ Idea family of suites
|
85 |
+
.idea
|
86 |
+
*.iml
|
87 |
+
## File-based project format:
|
88 |
+
*.ipr
|
89 |
+
*.iws
|
90 |
+
## mpeltonen/sbt-idea plugin
|
91 |
+
.idea_modules/
|
92 |
+
|
93 |
+
# PyBuilder
|
94 |
+
target/
|
95 |
+
|
96 |
+
# Cookiecutter
|
97 |
+
output/
|
98 |
+
python_boilerplate/
|
99 |
+
cookiecutter-pypackage-env/
|
100 |
+
|
101 |
+
# IDE settings
|
102 |
+
.vscode/
|
103 |
+
|
104 |
+
# direnv
|
105 |
+
.envrc
|
106 |
+
.direnv/
|
107 |
+
|
108 |
+
# streamlit
|
109 |
+
.streamlit/secrets.toml
|
.pre-commit-config.yaml
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
repos:
|
2 |
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
3 |
+
rev: v0.8.3
|
4 |
+
hooks:
|
5 |
+
- id: ruff
|
6 |
+
args:
|
7 |
+
- --fix
|
8 |
+
- id: ruff-format
|
9 |
+
- repo: https://github.com/pre-commit/mirrors-mypy
|
10 |
+
rev: v1.13.0
|
11 |
+
hooks:
|
12 |
+
- id: mypy
|
13 |
+
args:
|
14 |
+
- --ignore-missing-imports
|
15 |
+
- --follow-imports=silent
|
16 |
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
17 |
+
rev: v5.0.0 # Use the ref you want to point at
|
18 |
+
hooks:
|
19 |
+
- id: trailing-whitespace
|
LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
MIT License
|
2 |
+
|
3 |
+
Copyright (c) 2022, Zachary Blackwood
|
4 |
+
|
5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6 |
+
of this software and associated documentation files (the "Software"), to deal
|
7 |
+
in the Software without restriction, including without limitation the rights
|
8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9 |
+
copies of the Software, and to permit persons to whom the Software is
|
10 |
+
furnished to do so, subject to the following conditions:
|
11 |
+
|
12 |
+
The above copyright notice and this permission notice shall be included in all
|
13 |
+
copies or substantial portions of the Software.
|
14 |
+
|
15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21 |
+
SOFTWARE.
|
22 |
+
|
MANIFEST.in
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
recursive-include src/camera_input_live/frontend *
|
2 |
+
include README.md
|
3 |
+
include LICENSE
|
example_app/requirements.txt
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
streamlit-camera-input-live
|
2 |
+
opencv-python-headless
|
example_app/streamlit_app.py
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import cv2
|
2 |
+
import numpy as np
|
3 |
+
import streamlit as st
|
4 |
+
|
5 |
+
from camera_input_live import camera_input_live
|
6 |
+
|
7 |
+
"# Streamlit camera input live Demo"
|
8 |
+
"## Try holding a qr code in front of your webcam"
|
9 |
+
|
10 |
+
image = camera_input_live()
|
11 |
+
|
12 |
+
if image is not None:
|
13 |
+
st.image(image)
|
14 |
+
bytes_data = image.getvalue()
|
15 |
+
cv2_img = cv2.imdecode(np.frombuffer(bytes_data, np.uint8), cv2.IMREAD_COLOR)
|
16 |
+
|
17 |
+
detector = cv2.QRCodeDetector()
|
18 |
+
|
19 |
+
data, bbox, straight_qrcode = detector.detectAndDecode(cv2_img)
|
20 |
+
|
21 |
+
if data:
|
22 |
+
st.write("# Found QR code")
|
23 |
+
st.write(data)
|
24 |
+
with st.expander("Show details"):
|
25 |
+
st.write("BBox:", bbox)
|
26 |
+
st.write("Straight QR code:", straight_qrcode)
|
pyproject.toml
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[tool.ruff]
|
2 |
+
exclude = [
|
3 |
+
".git",
|
4 |
+
".vscode",
|
5 |
+
".pytest_cache",
|
6 |
+
".bamboo",
|
7 |
+
".tecton",
|
8 |
+
".mypy_cache",
|
9 |
+
".env",
|
10 |
+
]
|
11 |
+
ignore = [
|
12 |
+
"B008",
|
13 |
+
"ISC001",
|
14 |
+
"E501",
|
15 |
+
"W191"
|
16 |
+
]
|
17 |
+
line-length = 88
|
18 |
+
select = [
|
19 |
+
"B", # https://pypi.org/project/flake8-bugbear/
|
20 |
+
"E", # https://pycodestyle.pycqa.org/en/latest/intro.html#error-codes
|
21 |
+
"F", # https://flake8.pycqa.org/en/latest/user/error-codes.html
|
22 |
+
"W", # https://pycodestyle.pycqa.org/en/latest/intro.html#error-codes
|
23 |
+
"I", # https://pycqa.github.io/isort/
|
24 |
+
"N", # https://github.com/PyCQA/pep8-naming
|
25 |
+
"C4", # https://github.com/adamchainz/flake8-comprehensions
|
26 |
+
"EXE", # https://pypi.org/project/flake8-executable/
|
27 |
+
"ISC", # https://github.com/flake8-implicit-str-concat/flake8-implicit-str-concat
|
28 |
+
"ICN", # https://pypi.org/project/flake8-import-conventions/
|
29 |
+
"PIE", # https://pypi.org/project/flake8-pie/
|
30 |
+
"PT", # https://github.com/m-burst/flake8-pytest-style
|
31 |
+
"RET", # https://pypi.org/project/flake8-return/
|
32 |
+
"SIM", # https://pypi.org/project/flake8-simplify/
|
33 |
+
"ERA", # https://pypi.org/project/flake8-eradicate/
|
34 |
+
"PLC", # https://beta.ruff.rs/docs/rules/#convention-plc
|
35 |
+
"RUF", # https://beta.ruff.rs/docs/rules/#ruff-specific-rules-ruf
|
36 |
+
"ARG", # https://beta.ruff.rs/docs/rules/#flake8-unused-arguments-arg
|
37 |
+
]
|
38 |
+
|
39 |
+
[tool.ruff.per-file-ignores]
|
40 |
+
"__init__.py" = ["F401"]
|
41 |
+
|
42 |
+
[tool.mypy]
|
43 |
+
files = [
|
44 |
+
"**/*.py",
|
45 |
+
]
|
46 |
+
follow_imports = "silent"
|
47 |
+
ignore_missing_imports = true
|
48 |
+
scripts_are_modules = true
|
49 |
+
python_version = 3.9
|
requirements.txt
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
streamlit
|
setup.py
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pathlib import Path
|
2 |
+
|
3 |
+
import setuptools
|
4 |
+
|
5 |
+
this_directory = Path(__file__).parent
|
6 |
+
long_description = (this_directory / "README.md").read_text()
|
7 |
+
|
8 |
+
setuptools.setup(
|
9 |
+
name="streamlit-camera-input-live",
|
10 |
+
version="0.2.0",
|
11 |
+
author="Zachary Blackwood",
|
12 |
+
author_email="[email protected]",
|
13 |
+
description="Alternative version of st.camera_input which returns the webcam images live, without any button press needed",
|
14 |
+
long_description=long_description,
|
15 |
+
long_description_content_type="text/markdown",
|
16 |
+
packages=setuptools.find_packages(where="src"),
|
17 |
+
package_dir={"": "src"},
|
18 |
+
include_package_data=True,
|
19 |
+
classifiers=[],
|
20 |
+
python_requires=">=3.7",
|
21 |
+
install_requires=["streamlit>=1.2", "jinja2"],
|
22 |
+
url="https://github.com/blackary/streamlit-camera-input-live",
|
23 |
+
)
|
src/camera_input_live/__init__.py
ADDED
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import base64
|
2 |
+
from io import BytesIO
|
3 |
+
from pathlib import Path
|
4 |
+
from typing import Optional
|
5 |
+
|
6 |
+
import streamlit as st
|
7 |
+
import streamlit.components.v1 as components
|
8 |
+
|
9 |
+
# Tell streamlit that there is a component called camera_input_live,
|
10 |
+
# and that the code to display that component is in the "frontend" folder
|
11 |
+
frontend_dir = (Path(__file__).parent / "frontend").absolute()
|
12 |
+
_component_func = components.declare_component(
|
13 |
+
"camera_input_live", path=str(frontend_dir)
|
14 |
+
)
|
15 |
+
|
16 |
+
|
17 |
+
def camera_input_live(
|
18 |
+
debounce: int = 1000,
|
19 |
+
height: int = 530,
|
20 |
+
width: int = 704,
|
21 |
+
key: Optional[str] = None,
|
22 |
+
show_controls: bool = True,
|
23 |
+
start_label: str = "Start capturing",
|
24 |
+
stop_label: str = "Pause capturing",
|
25 |
+
) -> Optional[BytesIO]:
|
26 |
+
"""
|
27 |
+
Add a descriptive docstring
|
28 |
+
"""
|
29 |
+
b64_data: Optional[str] = _component_func(
|
30 |
+
height=height,
|
31 |
+
width=width,
|
32 |
+
debounce=debounce,
|
33 |
+
showControls=show_controls,
|
34 |
+
startLabel=start_label,
|
35 |
+
stopLabel=stop_label,
|
36 |
+
key=key,
|
37 |
+
)
|
38 |
+
|
39 |
+
if b64_data is None:
|
40 |
+
return None
|
41 |
+
|
42 |
+
raw_data = b64_data.split(",")[1] # Strip the data: type prefix
|
43 |
+
|
44 |
+
return BytesIO(base64.b64decode(raw_data))
|
45 |
+
|
46 |
+
|
47 |
+
def main():
|
48 |
+
st.write("## Example")
|
49 |
+
|
50 |
+
image = camera_input_live(show_controls=True)
|
51 |
+
|
52 |
+
if image is not None:
|
53 |
+
st.image(image)
|
54 |
+
|
55 |
+
|
56 |
+
if __name__ == "__main__":
|
57 |
+
main()
|
src/camera_input_live/frontend/index.html
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8" />
|
5 |
+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
7 |
+
<title>streamlit-camera-input-live</title>
|
8 |
+
<script src="./streamlit-component-lib.js"></script>
|
9 |
+
<script src="./main.js"></script>
|
10 |
+
<link rel="stylesheet" href="./style.css" />
|
11 |
+
</head>
|
12 |
+
<body>
|
13 |
+
<button id="button">Stop</button>
|
14 |
+
<div class="padding"></div>
|
15 |
+
<div id="container">
|
16 |
+
<video id="video" autoplay="true"></video>
|
17 |
+
<canvas id="canvas"> </canvas>
|
18 |
+
</div>
|
19 |
+
</body>
|
20 |
+
</html>
|
src/camera_input_live/frontend/main.js
ADDED
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// The `Streamlit` object exists because our html file includes
|
2 |
+
// `streamlit-component-lib.js`.
|
3 |
+
// If you get an error about "Streamlit" not being defined, that
|
4 |
+
// means you're missing that file.
|
5 |
+
|
6 |
+
function sendValue(value) {
|
7 |
+
Streamlit.setComponentValue(value)
|
8 |
+
}
|
9 |
+
|
10 |
+
|
11 |
+
|
12 |
+
/**
|
13 |
+
* The component's render function. This will be called immediately after
|
14 |
+
* the component is initially loaded, and then again every time the
|
15 |
+
* component gets new data from Python.
|
16 |
+
*/
|
17 |
+
function onRender(event) {
|
18 |
+
// Only run the render code the first time the component is loaded.
|
19 |
+
if (!window.rendered) {
|
20 |
+
// You most likely want to get the data passed in like this
|
21 |
+
const {height, width, debounce, showControls, startLabel, stopLabel} = event.detail.args
|
22 |
+
|
23 |
+
if (showControls) {
|
24 |
+
Streamlit.setFrameHeight(45)
|
25 |
+
}
|
26 |
+
|
27 |
+
if (isNaN(height)) {
|
28 |
+
height = width / (4/3);
|
29 |
+
}
|
30 |
+
|
31 |
+
let video = document.getElementById('video');
|
32 |
+
let canvas = document.getElementById('canvas');
|
33 |
+
let button = document.getElementById('button');
|
34 |
+
|
35 |
+
let stopped = false;
|
36 |
+
|
37 |
+
video.setAttribute('width', width);
|
38 |
+
video.setAttribute('height', height);
|
39 |
+
canvas.setAttribute('width', width);
|
40 |
+
canvas.setAttribute('height', height);
|
41 |
+
|
42 |
+
function takepicture() {
|
43 |
+
if (stopped) {
|
44 |
+
return;
|
45 |
+
}
|
46 |
+
let context = canvas.getContext('2d');
|
47 |
+
canvas.width = width;
|
48 |
+
canvas.height = height;
|
49 |
+
context.drawImage(video, 0, 0, width, height);
|
50 |
+
|
51 |
+
var data = canvas.toDataURL('image/png');
|
52 |
+
sendValue(data);
|
53 |
+
}
|
54 |
+
|
55 |
+
|
56 |
+
function stopVideo() {
|
57 |
+
video.pause();
|
58 |
+
video.srcObject.getTracks()[0].stop();
|
59 |
+
stopped = true;
|
60 |
+
}
|
61 |
+
|
62 |
+
function startVideo() {
|
63 |
+
navigator.mediaDevices.getUserMedia({ video: true })
|
64 |
+
.then(function(stream) {
|
65 |
+
video.srcObject = stream;
|
66 |
+
video.play();
|
67 |
+
})
|
68 |
+
.catch(function(err) {
|
69 |
+
console.log("An error occurred: " + err);
|
70 |
+
});
|
71 |
+
}
|
72 |
+
|
73 |
+
function toggleVideo() {
|
74 |
+
if (stopped) {
|
75 |
+
startVideo();
|
76 |
+
stopped = false;
|
77 |
+
} else {
|
78 |
+
stopVideo();
|
79 |
+
stopped = true;
|
80 |
+
}
|
81 |
+
// Toggle the button text
|
82 |
+
button.textContent = stopped ? startLabel : stopLabel;
|
83 |
+
}
|
84 |
+
|
85 |
+
if (navigator.mediaDevices.getUserMedia) {
|
86 |
+
navigator.mediaDevices
|
87 |
+
.getUserMedia({ video: true })
|
88 |
+
.then(function (stream) {
|
89 |
+
video.srcObject = stream;
|
90 |
+
})
|
91 |
+
.catch(function (error) {
|
92 |
+
console.log("Something went wrong!");
|
93 |
+
console.error(error);
|
94 |
+
});
|
95 |
+
}
|
96 |
+
|
97 |
+
button.addEventListener('click', toggleVideo);
|
98 |
+
button.textContent = stopped ? startLabel : stopLabel;
|
99 |
+
|
100 |
+
takepicture();
|
101 |
+
setInterval(takepicture, debounce);
|
102 |
+
window.rendered = true
|
103 |
+
}
|
104 |
+
}
|
105 |
+
|
106 |
+
// Render the component whenever python send a "render event"
|
107 |
+
Streamlit.events.addEventListener(Streamlit.RENDER_EVENT, onRender)
|
108 |
+
// Tell Streamlit that the component is ready to receive events
|
109 |
+
Streamlit.setComponentReady()
|
110 |
+
// Don't actually need to display anything, so set the height to 0
|
111 |
+
Streamlit.setFrameHeight(0)
|
src/camera_input_live/frontend/streamlit-component-lib.js
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
// Borrowed minimalistic Streamlit API from Thiago
|
3 |
+
// https://discuss.streamlit.io/t/code-snippet-create-components-without-any-frontend-tooling-no-react-babel-webpack-etc/13064
|
4 |
+
function sendMessageToStreamlitClient(type, data) {
|
5 |
+
console.log(type, data)
|
6 |
+
const outData = Object.assign({
|
7 |
+
isStreamlitMessage: true,
|
8 |
+
type: type,
|
9 |
+
}, data);
|
10 |
+
window.parent.postMessage(outData, "*");
|
11 |
+
}
|
12 |
+
|
13 |
+
const Streamlit = {
|
14 |
+
setComponentReady: function() {
|
15 |
+
sendMessageToStreamlitClient("streamlit:componentReady", {apiVersion: 1});
|
16 |
+
},
|
17 |
+
setFrameHeight: function(height) {
|
18 |
+
sendMessageToStreamlitClient("streamlit:setFrameHeight", {height: height});
|
19 |
+
},
|
20 |
+
setComponentValue: function(value) {
|
21 |
+
sendMessageToStreamlitClient("streamlit:setComponentValue", {value: value});
|
22 |
+
},
|
23 |
+
RENDER_EVENT: "streamlit:render",
|
24 |
+
events: {
|
25 |
+
addEventListener: function(type, callback) {
|
26 |
+
window.addEventListener("message", function(event) {
|
27 |
+
if (event.data.type === type) {
|
28 |
+
event.detail = event.data
|
29 |
+
callback(event);
|
30 |
+
}
|
31 |
+
});
|
32 |
+
}
|
33 |
+
}
|
34 |
+
}
|
src/camera_input_live/frontend/style.css
ADDED
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
button {
|
2 |
+
text-size-adjust: 100%;
|
3 |
+
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
4 |
+
-webkit-font-smoothing: auto;
|
5 |
+
box-sizing: border-box;
|
6 |
+
font-size: inherit;
|
7 |
+
font-family: inherit;
|
8 |
+
overflow: visible;
|
9 |
+
text-transform: none;
|
10 |
+
appearance: button;
|
11 |
+
display: inline-flex;
|
12 |
+
-webkit-box-align: center;
|
13 |
+
align-items: center;
|
14 |
+
-webkit-box-pack: center;
|
15 |
+
justify-content: center;
|
16 |
+
font-weight: 400;
|
17 |
+
padding: 0.25rem 0.75rem;
|
18 |
+
border-radius: 0.25rem;
|
19 |
+
margin: 0px;
|
20 |
+
line-height: 1.6;
|
21 |
+
color: inherit;
|
22 |
+
width: auto;
|
23 |
+
user-select: none;
|
24 |
+
background-color: rgb(255, 255, 255);
|
25 |
+
border: 1px solid rgba(49, 51, 63, 0.2);
|
26 |
+
cursor: pointer;
|
27 |
+
position: absolute;
|
28 |
+
left: 0;
|
29 |
+
}
|
30 |
+
|
31 |
+
button:hover {
|
32 |
+
border-color: rgb(255, 75, 75);
|
33 |
+
color: rgb(255, 75, 75);
|
34 |
+
}
|
35 |
+
button:active {
|
36 |
+
color: rgb(255, 255, 255);
|
37 |
+
border-color: rgb(255, 75, 75);
|
38 |
+
background-color: rgb(255, 75, 75);
|
39 |
+
}
|
40 |
+
|
41 |
+
.padding {
|
42 |
+
height: 100px;
|
43 |
+
}
|