initial commit

- re-starting version control fresh
- old repo had video files committed + node modules at one point that made it much larger than needed
pull/1/head
commit 972b16b54c

3
.gitignore vendored

@ -0,0 +1,3 @@
node_modules/
static/
__pycache__

@ -0,0 +1,51 @@
# webcam_manager
en avant les commits!
## Installation
We are looking to package this as a bundled application, in the meantime to install and run the Python code:
### Python dependencies
See `pyproject.toml` for requirements to install.
### Linux
On linux [v4l2loopback](https://github.com/umlaeute/v4l2loopback) is required to create a virtual webcam that can be used in call software.
```
sudo apt install v4l2loopback-dkms
```
The program starts the service automatically (this is why it asks for the sudo password at startup) to start manually:
```
sudo modprobe v4l2loopback devices=1
```
### Windows
On Windows OBS is required
add details here
### MacOS
Coming soon...
## Usage
### Connect to call software
### Recording and looping
- buttons
- keybaord shortcuts

@ -0,0 +1,56 @@
# TODO
- [ ] V4L2 warnings on Linux
- 9 warnings so I guess one for each camera that is not found (we test for 10 devices in `camera_indexes()`)
- [ ] Stealth button names/colors
- icons only? see [this](https://pysimplegui.readthedocs.io/en/latest/cookbook/#use-with-buttons)
- [ ] Start modprobe service automatically from Python?
- probably can call a `subprocess` or something. Probably check first if the `v4l2loopback` package exist, and if not display the command to execute?
- [ ] Package Font?
- Title using [Steps Mono](https://www.velvetyne.fr/fonts/steps-mono/download/) libre font from Velvetyne.
- Can we embed it/package it?
- let's see how it goes when we use `pyinstaller`. As far as I understand `pysimplegui` uses `tkinter` to manage fonts, so checking how tkinter does it might give us a clue.
- https://stackoverflow.com/questions/63585632/how-to-add-a-truetype-font-file-to-a-pyinstaller-executable-for-use-with-pygame
- [ ] How do we package the rest anyway?
- Cookiecutter: https://cookiecutter.readthedocs.io/en/1.7.2/index.html
- package FFMPEG builds and include in pyinstaller
https://stackoverflow.com/a/57081121
https://stackoverflow.com/a/60822647
## DONE
- [x] Keyboard shortcuts
- ~~to easily start/stop recording: space bar~~
- ~~start/stop loop: enter?~~
- Done and tested on Windows!!
- [x] Added device selector for the camera, to be tested elsewhere
- [x] Added a flush function
- Empties the video recorded in static at the start of the application
- Added a `Flush` button next the `Exit`
- [x] Pick normcore theme
- [x] create `static/` if it doesn't exist.
- [x] Output of virtualcam (e.g. previewing in Jitsi) has weird smurf colors.
- ~~preview in window is fine~~
- ~~recorded output file was fine~~
- preview in window fine, recorded out fine, preview in Zoom fine.
- [x] Are we downsizing the video resolution? why not use best res available? (line 57 see # XXX)
- we was, now we're webcam native size
- [x] Click on LOOP after launch before recording crashes
- ~~`variable referenced before assignment`~~
- fixed. If you click LOOP first and there's no static videos, the button gets disabled. Also added colours and disabled buttons
- [x] Stop saving video files locally?
- or at least add to `.gitignore`?

169
poetry.lock generated

@ -0,0 +1,169 @@
[[package]]
name = "imutils"
version = "0.5.4"
description = "A series of convenience functions to make basic image processing functions such as translation, rotation, resizing, skeletonization, displaying Matplotlib images, sorting contours, detecting edges, and much more easier with OpenCV and both Python 2.7 and Python 3."
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "numpy"
version = "1.20.1"
description = "NumPy is the fundamental package for array computing with Python."
category = "main"
optional = false
python-versions = ">=3.7"
[[package]]
name = "opencv-contrib-python"
version = "4.5.1.48"
description = "Wrapper package for OpenCV python bindings."
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
numpy = ">=1.17.3"
[[package]]
name = "pillow"
version = "8.1.2"
description = "Python Imaging Library (Fork)"
category = "main"
optional = false
python-versions = ">=3.6"
[[package]]
name = "pysimplegui"
version = "4.37.0"
description = "Python GUIs for Humans. Launched in 2018. It's 2021 & PySimpleGUI is an ACTIVE project. Super-simple to create custom GUI's. 300 Demo programs & Cookbook for rapid start. Extensive documentation. Main docs at www.PySimpleGUI.org. Your success is the focus. Examples using Machine Learning (GUI, OpenCV Integration, Chatterbot), Rainmeter Style Desktop Widgets, Matplotlib + Pyplot, PIL support, add GUI to command line scripts, PDF & Image Viewers. Great for beginners & advanced GUI programmers"
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "pyvirtualcam"
version = "0.5.0"
description = "Send frames to a virtual camera"
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
numpy = "*"
[metadata]
lock-version = "1.1"
python-versions = "^3.8"
content-hash = "00f019a9f36d6b628f19b4128196043431e52383c1a40a8af2bd3b22e6a95073"
[metadata.files]
imutils = [
{file = "imutils-0.5.4.tar.gz", hash = "sha256:03827a9fca8b5c540305c0844a62591cf35a0caec199cb0f2f0a4a0fb15d8f24"},
]
numpy = [
{file = "numpy-1.20.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ae61f02b84a0211abb56462a3b6cd1e7ec39d466d3160eb4e1da8bf6717cdbeb"},
{file = "numpy-1.20.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:65410c7f4398a0047eea5cca9b74009ea61178efd78d1be9847fac1d6716ec1e"},
{file = "numpy-1.20.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2d7e27442599104ee08f4faed56bb87c55f8b10a5494ac2ead5c98a4b289e61f"},
{file = "numpy-1.20.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:4ed8e96dc146e12c1c5cdd6fb9fd0757f2ba66048bf94c5126b7efebd12d0090"},
{file = "numpy-1.20.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:ecb5b74c702358cdc21268ff4c37f7466357871f53a30e6f84c686952bef16a9"},
{file = "numpy-1.20.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b9410c0b6fed4a22554f072a86c361e417f0258838957b78bd063bde2c7f841f"},
{file = "numpy-1.20.1-cp37-cp37m-win32.whl", hash = "sha256:3d3087e24e354c18fb35c454026af3ed8997cfd4997765266897c68d724e4845"},
{file = "numpy-1.20.1-cp37-cp37m-win_amd64.whl", hash = "sha256:89f937b13b8dd17b0099c7c2e22066883c86ca1575a975f754babc8fbf8d69a9"},
{file = "numpy-1.20.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a1d7995d1023335e67fb070b2fae6f5968f5be3802b15ad6d79d81ecaa014fe0"},
{file = "numpy-1.20.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:60759ab15c94dd0e1ed88241fd4fa3312db4e91d2c8f5a2d4cf3863fad83d65b"},
{file = "numpy-1.20.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:125a0e10ddd99a874fd357bfa1b636cd58deb78ba4a30b5ddb09f645c3512e04"},
{file = "numpy-1.20.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:c26287dfc888cf1e65181f39ea75e11f42ffc4f4529e5bd19add57ad458996e2"},
{file = "numpy-1.20.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:7199109fa46277be503393be9250b983f325880766f847885607d9b13848f257"},
{file = "numpy-1.20.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:72251e43ac426ff98ea802a931922c79b8d7596480300eb9f1b1e45e0543571e"},
{file = "numpy-1.20.1-cp38-cp38-win32.whl", hash = "sha256:c91ec9569facd4757ade0888371eced2ecf49e7982ce5634cc2cf4e7331a4b14"},
{file = "numpy-1.20.1-cp38-cp38-win_amd64.whl", hash = "sha256:13adf545732bb23a796914fe5f891a12bd74cf3d2986eed7b7eba2941eea1590"},
{file = "numpy-1.20.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:104f5e90b143dbf298361a99ac1af4cf59131218a045ebf4ee5990b83cff5fab"},
{file = "numpy-1.20.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:89e5336f2bec0c726ac7e7cdae181b325a9c0ee24e604704ed830d241c5e47ff"},
{file = "numpy-1.20.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:032be656d89bbf786d743fee11d01ef318b0781281241997558fa7950028dd29"},
{file = "numpy-1.20.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:66b467adfcf628f66ea4ac6430ded0614f5cc06ba530d09571ea404789064adc"},
{file = "numpy-1.20.1-cp39-cp39-win32.whl", hash = "sha256:12e4ba5c6420917571f1a5becc9338abbde71dd811ce40b37ba62dec7b39af6d"},
{file = "numpy-1.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:9c94cab5054bad82a70b2e77741271790304651d584e2cdfe2041488e753863b"},
{file = "numpy-1.20.1-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:9eb551d122fadca7774b97db8a112b77231dcccda8e91a5bc99e79890797175e"},
{file = "numpy-1.20.1.zip", hash = "sha256:3bc63486a870294683980d76ec1e3efc786295ae00128f9ea38e2c6e74d5a60a"},
]
opencv-contrib-python = [
{file = "opencv-contrib-python-4.5.1.48.tar.gz", hash = "sha256:74ebf353d7e1666066265922153a0f60fff9e1dd603f5929b13a99415363f078"},
{file = "opencv_contrib_python-4.5.1.48-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:74a866acca3fddb4666afa38ecf2cadfa6df319c62e5dc511760acb13caade5b"},
{file = "opencv_contrib_python-4.5.1.48-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:6afc58493e085956e548e3cb748df2b420b5b4d8a37d57f7945d7e54262d7526"},
{file = "opencv_contrib_python-4.5.1.48-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:ff62b4f0f4d3431fd13fc1eda0ec6be493ffa5e208f054d5933851987f3fabb6"},
{file = "opencv_contrib_python-4.5.1.48-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:2871c8c243c1a5217b8727c21fd347ec2ae755e39d52dd268bf8f01515b6e8d1"},
{file = "opencv_contrib_python-4.5.1.48-cp36-cp36m-win32.whl", hash = "sha256:11167768a6c643cd6a03158152ee73b62c8d4906e3bde2ed1383099b93ce14df"},
{file = "opencv_contrib_python-4.5.1.48-cp36-cp36m-win_amd64.whl", hash = "sha256:529a770bad6d01f59eab9065e73af3416492ebeac2d86f69ce603a6e922f021e"},
{file = "opencv_contrib_python-4.5.1.48-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:24e4a770908daf1718830c6059bbfe7fb4d5ff0dcb1253416cbe17d365889afe"},
{file = "opencv_contrib_python-4.5.1.48-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:8c9f350ebc8261c7b4a7d61d2cb22b94fb5cad5f1a0a7bf69bcea55886d98c90"},
{file = "opencv_contrib_python-4.5.1.48-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:9453fbad079d65b1987a963b3986bd283ff31d2ee04d349a538ba4e383ba0399"},
{file = "opencv_contrib_python-4.5.1.48-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:4f38147203216182622844ce9af539af0e3d28ba285904319fb479b0f71943a5"},
{file = "opencv_contrib_python-4.5.1.48-cp37-cp37m-win32.whl", hash = "sha256:703dc1a1f65aed065d4b5ef31756718f709a5d8fde69c110c0d637121ac3aee9"},
{file = "opencv_contrib_python-4.5.1.48-cp37-cp37m-win_amd64.whl", hash = "sha256:83681ee941e4b8b13c7023589b3354f07343b051df9cd668aae78d97bac9fa2e"},
{file = "opencv_contrib_python-4.5.1.48-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:1034b252b3d7ec5d07bf85f3a5f91fe6d3172dedaf7c4c3474848c6204d0f0f1"},
{file = "opencv_contrib_python-4.5.1.48-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:28e3562d8421a47d08e388cb19c05df9f055a604009d515ffd118cf1b459560a"},
{file = "opencv_contrib_python-4.5.1.48-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:b16aa1a5fc2fae8faf360667016592b5862489d106052a4aa01458bea775e6d2"},
{file = "opencv_contrib_python-4.5.1.48-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:aef0e18485250500059d9a08db1f3b8f1a0dda53069a011a5e9c24c01dcb5627"},
{file = "opencv_contrib_python-4.5.1.48-cp38-cp38-win32.whl", hash = "sha256:9ae70b05d9fdb61647d0fca7f23c0132eb9e245c5671637bcfbcc85a3efa131b"},
{file = "opencv_contrib_python-4.5.1.48-cp38-cp38-win_amd64.whl", hash = "sha256:6f577039e42e6255cd2f2a495e0d55b6cd283d677905c974824d575e9dc57b42"},
{file = "opencv_contrib_python-4.5.1.48-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:8b02e54f591ee0eabfae1f3fe14852821020e04a0c39af8e05a78a426be1a353"},
{file = "opencv_contrib_python-4.5.1.48-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:e2f058b19a0cdaaee5c09aefa32068d3609f55ea86a37ade48525f8b8d63fad9"},
{file = "opencv_contrib_python-4.5.1.48-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:12aa410897d47cf9bb63fbfd0513a645487982e77b3542ee84133d00d7851691"},
{file = "opencv_contrib_python-4.5.1.48-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:9ba6bdb54af0963adb92d7d20ec10b5ec9d319d10ecd3796787b058fccad789b"},
{file = "opencv_contrib_python-4.5.1.48-cp39-cp39-win32.whl", hash = "sha256:11ebe4d5ca0af69744c73d6e28560c48d6cbc65b256574d5d47f24b99923878f"},
{file = "opencv_contrib_python-4.5.1.48-cp39-cp39-win_amd64.whl", hash = "sha256:c5a523ce046f4405da8c177c11ee614e815efd8751a8ace545098b5768246628"},
]
pillow = [
{file = "Pillow-8.1.2-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:5cf03b9534aca63b192856aa601c68d0764810857786ea5da652581f3a44c2b0"},
{file = "Pillow-8.1.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:f91b50ad88048d795c0ad004abbe1390aa1882073b1dca10bfd55d0b8cf18ec5"},
{file = "Pillow-8.1.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5762ebb4436f46b566fc6351d67a9b5386b5e5de4e58fdaa18a1c83e0e20f1a8"},
{file = "Pillow-8.1.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:e2cd8ac157c1e5ae88b6dd790648ee5d2777e76f1e5c7d184eaddb2938594f34"},
{file = "Pillow-8.1.2-cp36-cp36m-win32.whl", hash = "sha256:72027ebf682abc9bafd93b43edc44279f641e8996fb2945104471419113cfc71"},
{file = "Pillow-8.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:d1d6bca39bb6dd94fba23cdb3eeaea5e30c7717c5343004d900e2a63b132c341"},
{file = "Pillow-8.1.2-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:90882c6f084ef68b71bba190209a734bf90abb82ab5e8f64444c71d5974008c6"},
{file = "Pillow-8.1.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:89e4c757a91b8c55d97c91fa09c69b3677c227b942fa749e9a66eef602f59c28"},
{file = "Pillow-8.1.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:8c4e32218c764bc27fe49b7328195579581aa419920edcc321c4cb877c65258d"},
{file = "Pillow-8.1.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:a01da2c266d9868c4f91a9c6faf47a251f23b9a862dce81d2ff583135206f5be"},
{file = "Pillow-8.1.2-cp37-cp37m-win32.whl", hash = "sha256:30d33a1a6400132e6f521640dd3f64578ac9bfb79a619416d7e8802b4ce1dd55"},
{file = "Pillow-8.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:71b01ee69e7df527439d7752a2ce8fb89e19a32df484a308eca3e81f673d3a03"},
{file = "Pillow-8.1.2-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:5a2d957eb4aba9d48170b8fe6538ec1fbc2119ffe6373782c03d8acad3323f2e"},
{file = "Pillow-8.1.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:87f42c976f91ca2fc21a3293e25bd3cd895918597db1b95b93cbd949f7d019ce"},
{file = "Pillow-8.1.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:15306d71a1e96d7e271fd2a0737038b5a92ca2978d2e38b6ced7966583e3d5af"},
{file = "Pillow-8.1.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:71f31ee4df3d5e0b366dd362007740106d3210fb6a56ec4b581a5324ba254f06"},
{file = "Pillow-8.1.2-cp38-cp38-win32.whl", hash = "sha256:98afcac3205d31ab6a10c5006b0cf040d0026a68ec051edd3517b776c1d78b09"},
{file = "Pillow-8.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:328240f7dddf77783e72d5ed79899a6b48bc6681f8d1f6001f55933cb4905060"},
{file = "Pillow-8.1.2-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:bead24c0ae3f1f6afcb915a057943ccf65fc755d11a1410a909c1fefb6c06ad1"},
{file = "Pillow-8.1.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:81b3716cc9744ffdf76b39afb6247eae754186838cedad0b0ac63b2571253fe6"},
{file = "Pillow-8.1.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:63cd413ac52ee3f67057223d363f4f82ce966e64906aea046daf46695e3c8238"},
{file = "Pillow-8.1.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:8565355a29655b28fdc2c666fd9a3890fe5edc6639d128814fafecfae2d70910"},
{file = "Pillow-8.1.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:1940fc4d361f9cc7e558d6f56ff38d7351b53052fd7911f4b60cd7bc091ea3b1"},
{file = "Pillow-8.1.2-cp39-cp39-win32.whl", hash = "sha256:46c2bcf8e1e75d154e78417b3e3c64e96def738c2a25435e74909e127a8cba5e"},
{file = "Pillow-8.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:aeab4cd016e11e7aa5cfc49dcff8e51561fa64818a0be86efa82c7038e9369d0"},
{file = "Pillow-8.1.2-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:74cd9aa648ed6dd25e572453eb09b08817a1e3d9f8d1bd4d8403d99e42ea790b"},
{file = "Pillow-8.1.2-pp36-pypy36_pp73-manylinux2010_i686.whl", hash = "sha256:e5739ae63636a52b706a0facec77b2b58e485637e1638202556156e424a02dc2"},
{file = "Pillow-8.1.2-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:903293320efe2466c1ab3509a33d6b866dc850cfd0c5d9cc92632014cec185fb"},
{file = "Pillow-8.1.2-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:5daba2b40782c1c5157a788ec4454067c6616f5a0c1b70e26ac326a880c2d328"},
{file = "Pillow-8.1.2-pp37-pypy37_pp73-manylinux2010_i686.whl", hash = "sha256:1f93f2fe211f1ef75e6f589327f4d4f8545d5c8e826231b042b483d8383e8a7c"},
{file = "Pillow-8.1.2-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:6efac40344d8f668b6c4533ae02a48d52fd852ef0654cc6f19f6ac146399c733"},
{file = "Pillow-8.1.2-pp37-pypy37_pp73-win32.whl", hash = "sha256:f36c3ff63d6fc509ce599a2f5b0d0732189eed653420e7294c039d342c6e204a"},
{file = "Pillow-8.1.2.tar.gz", hash = "sha256:b07c660e014852d98a00a91adfbe25033898a9d90a8f39beb2437d22a203fc44"},
]
pysimplegui = [
{file = "PySimpleGUI-4.37.0-py3-none-any.whl", hash = "sha256:ff1ffa7fb73a5665b299426facc2522197977732fb69d27becf9fca3c4d62ed3"},
{file = "PySimpleGUI-4.37.0.tar.gz", hash = "sha256:c5764fa58f4c9a9b25b36fb0133fc8893a2e2c8a9da1f96a268378eb54e42beb"},
]
pyvirtualcam = [
{file = "pyvirtualcam-0.5.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:bfc5d65657c710f34f1bdc76618532500da39bead0573c686449469024f6a352"},
{file = "pyvirtualcam-0.5.0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:5a3a5fee93ae4f289513a5de2ab45b799d46d1d10397d6f0cb24424368129694"},
{file = "pyvirtualcam-0.5.0-cp36-cp36m-win_amd64.whl", hash = "sha256:b243a3290df84dc4f92280660f89413fd4078356f6b11c284e0c3612ac81b93f"},
{file = "pyvirtualcam-0.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b36b1548ad1c540c27dc781315d9b702f7e88800c38a1997e062ece25eb814d0"},
{file = "pyvirtualcam-0.5.0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:a51a4caf48803c8a3684e665796c8b4ced73046457da3183c008076c65c52967"},
{file = "pyvirtualcam-0.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e049fa89407a928827b4fbe9e15ad2c83c8b718adc68c66ed2a1fa408e2c8599"},
{file = "pyvirtualcam-0.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1e2bc1ad0a60cf32a361c5e87f46abae2236678254da49e7e6b0da04c12ad419"},
{file = "pyvirtualcam-0.5.0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:e40d54dd38a28f12796a11c1f2c1770859b908f0bbeabb81b93c79a4e83eb33c"},
{file = "pyvirtualcam-0.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:de577dfb7d84f31c9526adf911fb09ebc2efebbf4e47c7781c7fa85dcef6b8b7"},
{file = "pyvirtualcam-0.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:21484ccc29ec433f6c8c8e4336287d41d0cd41791901dcbada482d874efac769"},
{file = "pyvirtualcam-0.5.0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:f66236c2adf6b17c35b11f1290869477fe85d5b55526eceb8d54d4b95f3c888c"},
{file = "pyvirtualcam-0.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:e665f108b8d4ebb899f8b24c263c33b225c120df863e63c4bfc911c68d5705dd"},
]

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.8 (py)" project-jdk-type="Python SDK" />
<component name="PyCharmProfessionalAdvertiser">
<option name="shown" value="true" />
</component>
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/py.iml" filepath="$PROJECT_DIR$/.idea/py.iml" />
</modules>
</component>
</project>

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/venv" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PackageRequirementsSettings">
<option name="keepMatchingSpecifier" value="false" />
</component>
</module>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

@ -0,0 +1,86 @@
import PIL.Image
import io
import cv2
import shutil
import os
import glob
def camera_indexes():
'''
grab the available cam indexes
checks the first 10 indexes.
'''
index = 0
arr = []
i = 10
while i > 0:
cap = cv2.VideoCapture(index)
if cap.read()[0]:
arr.append(index)
cap.release()
index += 1
i -= 1
return arr
def flushvideofiles():
''' Delete all video files accumulated from previous seshs'''
dir_path = 'static'
try:
shutil.rmtree(dir_path)
os.mkdir(dir_path)
except OSError as e:
print("Error: %s : %s" % (dir_path, e.strerror))
def isstaticfolderempty():
'''check if the static/ folder already contains files'''
if len(os.listdir('static/')) == 0:
print("Directory is empty")
filename = True
else:
print("Directory is not empty")
list_of_files = glob.glob('static/*') # * means all if need specific format then *.csv
latest_file = max(list_of_files, key=os.path.getctime)
filename = latest_file
return filename
def resize(image,window_width = 500):
'''
Resize an image to the supplied width while preserving aspect ratio
Adjusted (flipped) from here: https://stackoverflow.com/a/53631990
'''
aspect_ratio = float(image.shape[0])/float(image.shape[1])
window_height = window_width*aspect_ratio
image = cv2.resize(image, (int(window_width),int(window_height)))
return image
def convert_to_bytes(file_or_bytes, resize=None):
'''
Copied from https://pysimplegui.readthedocs.io/en/latest/cookbook/
Will convert into bytes and optionally resize an image that is a file or a base64 bytes object.
Turns into PNG format in the process so that can be displayed by tkinter
:param file_or_bytes: either a string filename or a bytes base64 image object
:type file_or_bytes: (Union[str, bytes])
:param resize: optional new size
:type resize: (Tuple[int, int] or None)
:return: (bytes) a byte-string object
:rtype: (bytes)
'''
if isinstance(file_or_bytes, str):
img = PIL.Image.open(file_or_bytes)
else:
try:
img = PIL.Image.open(io.BytesIO(base64.b64decode(file_or_bytes)))
except Exception as e:
dataBytesIO = io.BytesIO(file_or_bytes)
img = PIL.Image.open(dataBytesIO)
cur_width, cur_height = img.size
if resize:
new_width, new_height = resize
scale = min(new_height/cur_height, new_width/cur_width)
img = img.resize((int(cur_width*scale), int(cur_height*scale)), PIL.Image.ANTIALIAS)
bio = io.BytesIO()
img.save(bio, format="PNG")
del img
return bio.getvalue()

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

123
py/dist/WM.nsi vendored

@ -0,0 +1,123 @@
!include "FontRegAdv.nsh"
!include "FontName.nsh"
;NSIS Macros
!define PRODUCT_NAME "Webcam Manager"
!define EXE_NAME "webcam_manager.exe"
!define PRODUCT_VERSION "1.0"
!define PRODUCT_PUBLISHER "Webcam Managers"
!define PRODUCT_LEGAL "Webcam Managers 2021-3021"
!define TEMP_DIR ""
;--------------------------------
;Include Modern UI
!include "MUI2.nsh"
;--------------------------------
;General
;Name and file
Name "Webcam Manager"
OutFile "WebcamManager_installer.exe"
;Unicode True
;Default installation folder
InstallDir "$PROGRAMFILES\Webcam_Manager"
;Get installation folder from registry if available
;InstallDirRegKey HKCU "Software\Modern UI Test" ""
;Request application privileges for Windows Vista
;RequestExecutionLevel user
!define MUI_ICON "inc\logo.ico"
!define MUI_FINISHPAGE_RUN "$INSTDIR\webcam_manager.exe"
;--------------------------------
;Interface Settings
!define MUI_ABORTWARNING
;--------------------------------
;Pages
!insertmacro MUI_PAGE_LICENSE "inc\License.txt"
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
;--------------------------------
;Languages
!insertmacro MUI_LANGUAGE "English"
;--------------------------------
;Installer Sections
Section "Fonts" fonts
StrCpy $FONT_DIR $FONTS
!insertmacro InstallTTF "inc\Steps-Mono.ttf"
SendMessage ${HWND_BROADCAST} ${WM_FONTCHANGE} 0 0 /TIMEOUT=5000
SectionEnd
Section "OBS Studio" Prerequisites
SetOutPath "$INSTDIR"
MessageBox MB_YESNO "Install OBS?" /SD IDYES IDNO endOBS
File "inc\OBS-Studio-26.1.1-Full-Installer-x64.exe"
ExecWait "$INSTDIR\OBS-Studio-26.1.1-Full-Installer-x64.exe"
Goto endOBS
endOBS:
SectionEnd
Section "Webcam Manager" WebMan
;ADD YOUR OWN FILES HERE...
File "webcam_manager.exe"
;Create uninstaller
WriteUninstaller "$INSTDIR\Uninstall.exe"
SectionEnd
;--------------------------------
;Descriptions
;Language strings
LangString DESC_fonts ${LANG_ENGLISH} "Necessary GUI Fonts"
LangString DESC_Prerequisites ${LANG_ENGLISH} "Relies on OBS virtual camera."
LangString DESC_WebMan ${LANG_ENGLISH} "Manage your webcam"
;Assign language strings to sections
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
!insertmacro MUI_DESCRIPTION_TEXT ${fonts} $(DESC_fonts)
!insertmacro MUI_DESCRIPTION_TEXT ${Prerequisites} $(DESC_Prerequisites)
!insertmacro MUI_DESCRIPTION_TEXT ${WebMan} $(DESC_WebMan)
!insertmacro MUI_FUNCTION_DESCRIPTION_END
;--------------------------------
;Uninstaller Section
Section "Uninstall"
;ADD YOUR OWN FILES HERE...
Delete "$INSTDIR\webcam_manager.exe"
RMDir "$INSTDIR"
DeleteRegKey /ifempty HKCU "Software\Modern UI Test"
SectionEnd

@ -0,0 +1,10 @@
Copyright 2021-3021 Webcam Managers Inc.
This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
2. Altered versions must be plainly marked as such, and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any distribution.

Binary file not shown.

BIN
py/dist/inc/logo.ico vendored

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

@ -0,0 +1,37 @@
#!/usr/bin/env python
import PySimpleGUI as sg
'''
App that shows "how fonts work in PySimpleGUI".
'''
layout = [[sg.Text('This is my sample text', size=(20, 1), key='-text-')],
[sg.CB('Bold', key='-bold-', change_submits=True),
sg.CB('Italics', key='-italics-', change_submits=True),
sg.CB('Underline', key='-underline-', change_submits=True)],
[sg.Slider((6, 50), default_value=12, size=(14, 20),
orientation='h', key='-slider-', change_submits=True),
sg.Text('Font size')],
[sg.Text('Font string = '), sg.Text('', size=(25, 1), key='-fontstring-')],
[sg.Button('Exit')]]
window = sg.Window('Font string builder', layout)
text_elem = window['-text-']
while True: # Event Loop
event, values = window.read()
if event in (sg.WIN_CLOSED, 'Exit'):
break
font_string = 'Helvitica '
font_string += str(int(values['-slider-']))
if values['-bold-']:
font_string += ' bold'
if values['-italics-']:
font_string += ' italic'
if values['-underline-']:
font_string += ' underline'
text_elem.update(font=font_string)
window['-fontstring-'].update('"'+font_string+'"')
print(event, values)
window.close()

@ -0,0 +1,7 @@
from tkinter import font
import tkinter
root = tkinter.Tk()
fonts = list(font.families())
fonts.sort()
print(fonts)
root.destroy()

@ -0,0 +1,10 @@
import pyvirtualcam
import numpy as np
with pyvirtualcam.Camera(width=1280, height=720, fps=20) as cam:
print(f'Using virtual camera: {cam.device}')
frame = np.zeros((cam.height, cam.width, 3), np.uint8) # RGB
while True:
frame[:] = cam.frames_sent % 255 # grayscale animation
cam.send(frame)
cam.sleep_until_next_frame()

@ -0,0 +1,177 @@
import pyvirtualcam
import cv2
import PySimpleGUI as sg
from imutils.video import count_frames
import os
import pathlib
from WM_utils import *
# pyinstaller bundle maker, comment if not building binary
# command to execute: pyinstaller --onefile --windowed --add-data "assets;assets" webcam_manager.py
# if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
# os.chdir(sys._MEIPASS)
devices = camera_indexes()
# detect if we're on windows for default cam
if os.name == 'nt':
cv_src = 0
else:
# we are on linux
cv_src = 0
os.system("sudo modprobe v4l2loopback")
# TODO: handle macOS case
cv = cv2.VideoCapture(cv_src)
ret, frame = cv.read()
cam = pyvirtualcam.Camera(width=frame.shape[1], height=frame.shape[0], fps=30, delay=0)
nativewidth = frame.shape[1]
nativeheight = frame.shape[0]
# recordings
filename = ""
fourcc = cv2.VideoWriter_fourcc(*'XVID')
# fourcc = cv2.VideoWriter_fourcc(*'mp4v')
recordfeed = False
def main():
flushvideofiles()
global cv
recVidNum= 0
recordfeed = False
setLoop = False
framecounter = 0
frameoffset = 3
devicenum = 0
# create static/ folder if it doesn't exist
static_path = pathlib.Path("static")
static_path.mkdir(parents=True, exist_ok=True)
# check available cameras
# devices = returnCameraIndexes()
sg.theme('SystemDefault')
# window layout
layout = [
[sg.Text("Webcam Manager", size=(100,1), font=("Steps-Mono", 20), justification="left")],
[sg.Text("V α 0.1", size=(100,1), justification="left")],
[sg.Text("Device # ",size=(10,1), justification="left"), sg.Combo(devices, size=(10, 1), default_value=cv_src, key="DEVICE")],
[sg.Button(image_data=convert_to_bytes('assets/icon_reload.png', (40, 40)), button_color=(sg.theme_background_color(), sg.theme_background_color()), border_width=0, enable_events=True, key="REC"), sg.Button("STOP", size=(10, 1)), sg.Button("LOOP", size=(10,1), enable_events=True, bind_return_key=True, key="LOOP")],
[sg.Image(filename="", key="-IMAGE-")],
[sg.Button("Flush", size=(10, 1)), sg.Button("Exit", size=(10, 1))]
]
# create window
window = sg.Window("Webcam Manager", layout, size=(430,500))
window.set_icon("assets/logo.png")
# Original colors
REC_default_col = window["REC"].ButtonColor
LOOP_default_col = window["LOOP"].ButtonColor
Flush_default_col = window["Flush"].ButtonColor
while True:
event, values = window.read(timeout=20)
device = values["DEVICE"]
if device != devicenum:
cv = cv2.VideoCapture(device)
# print("--")
if event == "Exit" or event == sg.WIN_CLOSED:
break
elif event == "Flush":
flushvideofiles()
elif event == "REC":
if setLoop == True:
print("looping cannot start recording")
elif recordfeed == True:
print("STOPPED RECORDING")
window["REC"].Update(button_color=REC_default_col, disabled=False)
window["LOOP"].Update(button_color=LOOP_default_col, disabled=False)
window["Flush"].Update(button_color=Flush_default_col, disabled=False)
recordfeed = False
out.release()
else:
print("RECORDING")
filename="static/output_"
filename += str(recVidNum)
filename += ".avi"
recVidNum += 1
out = cv2.VideoWriter(filename, fourcc, 20.0, (nativewidth, nativeheight))
recordfeed = True
window["REC"].Update(button_color=('white', 'red'))
window["LOOP"].Update(button_color=('black', 'grey'), disabled=True)
window["Flush"].Update(button_color=('black', 'grey'), disabled=True)
elif event == "STOP":
print("STOPPED RECORDING/LOOPING")
window["REC"].Update(button_color=REC_default_col, disabled=False)
window["LOOP"].Update(button_color=LOOP_default_col, disabled=False)
window["Flush"].Update(button_color=Flush_default_col, disabled=False)
if recordfeed == True:
recordfeed = False
out.release()
elif setLoop == True:
setLoop = False
cv = cv2.VideoCapture(device)
elif event == "LOOP":
print("LOOPING")
if isstaticfolderempty() == True:
print("no videos to loop")
window["LOOP"].Update(button_color=('black', 'grey'), disabled=True)
else:
if recordfeed == True:
print("currently recording cannot loop")
elif setLoop == True:
print("STOPPED LOOPING")
window["REC"].Update(button_color=REC_default_col, disabled=False)
window["LOOP"].Update(button_color=LOOP_default_col, disabled=False)
window["Flush"].Update(button_color=Flush_default_col, disabled=False)
setLoop = False
cv = cv2.VideoCapture(device)
else:
filename = isstaticfolderempty()
setLoop = True
cv = cv2.VideoCapture(filename)
framecount = count_frames(filename)
window["LOOP"].Update(button_color=('black', 'yellow'))
window["REC"].Update(button_color=('black', 'grey'), disabled=True)
window["Flush"].Update(button_color=('black', 'grey'), disabled=True)
success, frame = cv.read()
if recordfeed == True:
out.write(frame)
if setLoop == True:
framecounter += 1
if framecounter >= framecount - frameoffset:
framecounter = 0
cv.set(cv2.CAP_PROP_POS_FRAMES, 0)
print("starting loop again")
scaled_preview = resize(frame, window_width=400)
imgbytes = cv2.imencode(".png", scaled_preview)[1].tobytes()
window["-IMAGE-"].update(data=imgbytes)
#window["-IMAGE-"].set_size(size=(vid_preview_w, vid_preview_h))
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
try:
cam.send(frame)
except:
pass
cam.sleep_until_next_frame()
devicenum = device
window.close()
main()

@ -0,0 +1,19 @@
[tool.poetry]
name = "webcam_manager"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]
[tool.poetry.dependencies]
python = "^3.8"
imutils = "~=0.5.4"
opencv-contrib-python = "^4.5.1"
PySimpleGUI = "^4.37.0"
pyvirtualcam = "^0.5.0"
Pillow = "^8.1.2"
[tool.poetry.dev-dependencies]
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
Loading…
Cancel
Save