diff --git a/.gitignore b/.gitignore index 744d9f68..7b477c26 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,7 @@ *.pyc *.swp /venv/ -.vscode/settings.j/son +.vscode/settings.json kvmd_config/ __pycache__/ kvmd_data/run/kvmd/* @@ -30,3 +30,7 @@ kvmd-launcher.dist kvmd-launcher.onefile-build ustreamer/ node_modules/ +build/ +*/dist/* +*/build/* +*.spec \ No newline at end of file diff --git a/Makefile b/Makefile deleted file mode 100644 index 4941bf4c..00000000 --- a/Makefile +++ /dev/null @@ -1,366 +0,0 @@ --include config.mk - -TESTENV_IMAGE ?= kvmd-testenv -TESTENV_HID ?= /dev/ttyS10 -TESTENV_VIDEO ?= /dev/video0 -TESTENV_GPIO ?= /dev/gpiochip0 -TESTENV_RELAY ?= $(if $(shell ls /dev/hidraw0 2>/dev/null || true),/dev/hidraw0,) - -LIBGPIOD_VERSION ?= 1.6.3 - -USTREAMER_MIN_VERSION ?= $(shell grep -o 'ustreamer>=[^"]\+' PKGBUILD | sed 's/ustreamer>=//g') - -DEFAULT_PLATFORM ?= v2-hdmiusb-rpi4 - -DOCKER ?= docker - - -# ===== -define optbool -$(filter $(shell echo $(1) | tr A-Z a-z),yes on 1) -endef - - -# ===== -all: - @ echo "Useful commands:" - @ echo " make # Print this help" - @ echo " make testenv # Build test environment" - @ echo " make tox # Run tests and linters" - @ echo " make tox E=pytest # Run selected test environment" - @ echo " make gpio # Create gpio mockup" - @ echo " make run # Run kvmd" - @ echo " make run CMD=... # Run specified command inside kvmd environment" - @ echo " make run-cfg # Run kvmd -m" - @ echo " make run-ipmi # Run kvmd-ipmi" - @ echo " make run-ipmi CMD=... # Run specified command inside kvmd-ipmi environment" - @ echo " make run-vnc # Run kvmd-vnc" - @ echo " make run-vnc CMD=... # Run specified command inside kvmd-vnc environment" - @ echo " make regen # Regen some sources like keymap" - @ echo " make bump # Bump minor version" - @ echo " make bump V=major # Bump major version" - @ echo " make release # Publish the new release (include bump minor)" - @ echo " make clean # Remove garbage" - @ echo " make clean-all # Remove garbage and test results" - @ echo - @ echo "Also you can add option NC=1 to rebuild docker test environment" - - -testenv: - $(DOCKER) build \ - $(if $(call optbool,$(NC)),--no-cache,) \ - --rm \ - --tag $(TESTENV_IMAGE) \ - --build-arg LIBGPIOD_VERSION=$(LIBGPIOD_VERSION) \ - --build-arg USTREAMER_MIN_VERSION=$(USTREAMER_MIN_VERSION) \ - -f testenv/Dockerfile . - test -d testenv/.ssl || $(DOCKER) run --rm \ - --volume `pwd`:/src:ro \ - --volume `pwd`/testenv:/src/testenv:rw \ - -t $(TESTENV_IMAGE) bash -c " \ - groupadd kvmd-nginx \ - && groupadd kvmd-vnc \ - && /src/scripts/kvmd-gencert --do-the-thing \ - && /src/scripts/kvmd-gencert --do-the-thing --vnc \ - && chown -R root:root /etc/kvmd/{nginx,vnc}/ssl \ - && chmod 664 /etc/kvmd/{nginx,vnc}/ssl/* \ - && chmod 775 /etc/kvmd/{nginx,vnc}/ssl \ - && mkdir /src/testenv/.ssl \ - && mv /etc/kvmd/nginx/ssl /src/testenv/.ssl/nginx \ - && mv /etc/kvmd/vnc/ssl /src/testenv/.ssl/vnc \ - " - - -tox: testenv - time $(DOCKER) run --rm \ - --volume `pwd`:/src:ro \ - --volume `pwd`/testenv:/src/testenv:rw \ - --volume `pwd`/testenv/tests:/src/testenv/tests:ro \ - --volume `pwd`/extras:/usr/share/kvmd/extras:ro \ - --volume `pwd`/configs:/usr/share/kvmd/configs.default:ro \ - --volume `pwd`/contrib/keymaps:/usr/share/kvmd/keymaps:ro \ - -t $(TESTENV_IMAGE) bash -c " \ - cp -a /src/testenv/.ssl/nginx /etc/kvmd/nginx/ssl \ - && cp -a /src/testenv/.ssl/vnc /etc/kvmd/vnc/ssl \ - && cp /src/testenv/platform /usr/share/kvmd \ - && cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \ - && cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \ - && cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \ - && cp /usr/share/kvmd/configs.default/kvmd/main.yaml /etc/kvmd/main.yaml \ - && mkdir -p /etc/kvmd/override.d \ - && cp /src/testenv/$(if $(P),$(P),$(DEFAULT_PLATFORM)).override.yaml /etc/kvmd/override.yaml \ - && cd /src \ - && $(if $(CMD),$(CMD),tox -q -c testenv/tox.ini $(if $(E),-e $(E),-p auto)) \ - " - - -$(TESTENV_GPIO): - test ! -e $(TESTENV_GPIO) - sudo modprobe gpio-mockup gpio_mockup_ranges=0,40 - test -c $(TESTENV_GPIO) - - -run: testenv $(TESTENV_GPIO) - - $(DOCKER) run --rm --name kvmd \ - --privileged \ - --volume `pwd`/testenv/run:/run/kvmd:rw \ - --volume `pwd`/testenv:/testenv:ro \ - --volume `pwd`/kvmd:/kvmd:ro \ - --volume `pwd`/testenv/env.py:/kvmd/env.py:ro \ - --volume `pwd`/web:/usr/share/kvmd/web:ro \ - --volume `pwd`/extras:/usr/share/kvmd/extras:ro \ - --volume `pwd`/configs:/usr/share/kvmd/configs.default:ro \ - --volume `pwd`/contrib/keymaps:/usr/share/kvmd/keymaps:ro \ - --device $(TESTENV_VIDEO):$(TESTENV_VIDEO) \ - --device $(TESTENV_GPIO):$(TESTENV_GPIO) \ - $(if $(TESTENV_RELAY),--device $(TESTENV_RELAY):$(TESTENV_RELAY),) \ - --publish 8080:8080/tcp \ - --publish 4430:4430/tcp \ - -it $(TESTENV_IMAGE) /bin/bash -c " \ - mkdir -p /tmp/kvmd-nginx \ - && mount -t debugfs none /sys/kernel/debug \ - && test -d /sys/kernel/debug/gpio-mockup/`basename $(TESTENV_GPIO)`/ || (echo \"Missing GPIO mockup\" && exit 1) \ - && (socat PTY,link=$(TESTENV_HID) PTY,link=/dev/ttyS11 &) \ - && cp -r /usr/share/kvmd/configs.default/nginx/* /etc/kvmd/nginx \ - && cp -a /testenv/.ssl/nginx /etc/kvmd/nginx/ssl \ - && cp -a /testenv/.ssl/vnc /etc/kvmd/vnc/ssl \ - && cp /testenv/platform /usr/share/kvmd \ - && cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \ - && cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \ - && cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \ - && cp /usr/share/kvmd/configs.default/kvmd/main/$(if $(P),$(P),$(DEFAULT_PLATFORM)).yaml /etc/kvmd/main.yaml \ - && ln -s /testenv/web.css /etc/kvmd/web.css \ - && mkdir -p /etc/kvmd/override.d \ - && cp /testenv/$(if $(P),$(P),$(DEFAULT_PLATFORM)).override.yaml /etc/kvmd/override.yaml \ - && python -m kvmd.apps.ngxmkconf /etc/kvmd/nginx/nginx.conf.mako /etc/kvmd/nginx/nginx.conf \ - && nginx -c /etc/kvmd/nginx/nginx.conf -g 'user http; error_log stderr;' \ - && ln -s $(TESTENV_VIDEO) /dev/kvmd-video \ - && ln -s $(TESTENV_GPIO) /dev/kvmd-gpio \ - && $(if $(CMD),$(CMD),python -m kvmd.apps.kvmd --run) \ - " - - -run-cfg: testenv - - $(DOCKER) run --rm --name kvmd-cfg \ - --volume `pwd`/testenv/run:/run/kvmd:rw \ - --volume `pwd`/testenv:/testenv:ro \ - --volume `pwd`/kvmd:/kvmd:ro \ - --volume `pwd`/extras:/usr/share/kvmd/extras:ro \ - --volume `pwd`/configs:/usr/share/kvmd/configs.default:ro \ - --volume `pwd`/contrib/keymaps:/usr/share/kvmd/keymaps:ro \ - -it $(TESTENV_IMAGE) /bin/bash -c " \ - cp -a /testenv/.ssl/nginx /etc/kvmd/nginx/ssl \ - && cp -a /testenv/.ssl/vnc /etc/kvmd/vnc/ssl \ - && cp /testenv/platform /usr/share/kvmd \ - && cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \ - && cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \ - && cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \ - && cp /usr/share/kvmd/configs.default/kvmd/main.yaml /etc/kvmd/main.yaml \ - && mkdir -p /etc/kvmd/override.d \ - && cp /testenv/$(if $(P),$(P),$(DEFAULT_PLATFORM)).override.yaml /etc/kvmd/override.yaml \ - && $(if $(CMD),$(CMD),python -m kvmd.apps.kvmd -m) \ - " - - -run-ipmi: testenv - - $(DOCKER) run --rm --name kvmd-ipmi \ - --volume `pwd`/testenv/run:/run/kvmd:rw \ - --volume `pwd`/testenv:/testenv:ro \ - --volume `pwd`/kvmd:/kvmd:ro \ - --volume `pwd`/extras:/usr/share/kvmd/extras:ro \ - --volume `pwd`/configs:/usr/share/kvmd/configs.default:ro \ - --volume `pwd`/contrib/keymaps:/usr/share/kvmd/keymaps:ro \ - --publish 6230:623/udp \ - -it $(TESTENV_IMAGE) /bin/bash -c " \ - cp -a /testenv/.ssl/nginx /etc/kvmd/nginx/ssl \ - && cp -a /testenv/.ssl/vnc /etc/kvmd/vnc/ssl \ - && cp /testenv/platform /usr/share/kvmd \ - && cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \ - && cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \ - && cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \ - && cp /usr/share/kvmd/configs.default/kvmd/main.yaml /etc/kvmd/main.yaml \ - && mkdir -p /etc/kvmd/override.d \ - && cp /testenv/$(if $(P),$(P),$(DEFAULT_PLATFORM)).override.yaml /etc/kvmd/override.yaml \ - && $(if $(CMD),$(CMD),python -m kvmd.apps.ipmi --run) \ - " - - -run-vnc: testenv - - $(DOCKER) run --rm --name kvmd-vnc \ - --volume `pwd`/testenv/run:/run/kvmd:rw \ - --volume `pwd`/testenv:/testenv:ro \ - --volume `pwd`/kvmd:/kvmd:ro \ - --volume `pwd`/extras:/usr/share/kvmd/extras:ro \ - --volume `pwd`/configs:/usr/share/kvmd/configs.default:ro \ - --volume `pwd`/contrib/keymaps:/usr/share/kvmd/keymaps:ro \ - --publish 5900:5900/tcp \ - -it $(TESTENV_IMAGE) /bin/bash -c " \ - cp -a /testenv/.ssl/nginx /etc/kvmd/nginx/ssl \ - && cp -a /testenv/.ssl/vnc /etc/kvmd/vnc/ssl \ - && cp /testenv/platform /usr/share/kvmd \ - && cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \ - && cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \ - && cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \ - && cp /usr/share/kvmd/configs.default/kvmd/main.yaml /etc/kvmd/main.yaml \ - && mkdir -p /etc/kvmd/override.d \ - && cp /testenv/$(if $(P),$(P),$(DEFAULT_PLATFORM)).override.yaml /etc/kvmd/override.yaml \ - && $(if $(CMD),$(CMD),python -m kvmd.apps.vnc --run) \ - " - - -regen: keymap pug - - -keymap: testenv - $(DOCKER) run --user `id -u`:`id -g` --rm \ - --volume `pwd`:/src \ - -it $(TESTENV_IMAGE) bash -c "cd src \ - && ./genmap.py keymap.csv kvmd/keyboard/mappings.py.mako kvmd/keyboard/mappings.py \ - && ./genmap.py keymap.csv hid/arduino/lib/drivers/usb-keymap.h.mako hid/arduino/lib/drivers/usb-keymap.h \ - && ./genmap.py keymap.csv hid/arduino/lib/drivers-avr/ps2/keymap.h.mako hid/arduino/lib/drivers-avr/ps2/keymap.h \ - && ./genmap.py keymap.csv hid/pico/src/ph_usb_keymap.h.mako hid/pico/src/ph_usb_keymap.h \ - " - - -pug: testenv - $(DOCKER) run --user `id -u`:`id -g` --rm \ - --volume `pwd`:/src \ - -it $(TESTENV_IMAGE) bash -c "cd src \ - && pug --pretty web/index.pug -o web \ - && pug --pretty web/login/index.pug -o web/login \ - && pug --pretty web/kvm/index.pug -o web/kvm \ - && pug --pretty web/ipmi/index.pug -o web/ipmi \ - && pug --pretty web/vnc/index.pug -o web/vnc \ - " - - -release: - make clean - make tox - make clean - make push - make bump V=$(V) - make push - make clean - - -bump: - bumpversion $(if $(V),$(V),minor) - - -push: - git push - git push --tags - - -clean: - rm -rf testenv/run/*.{pid,sock} build site dist pkg src v*.tar.gz *.pkg.tar.{xz,zst} *.egg-info kvmd-*.tar.gz - find kvmd testenv/tests -name __pycache__ | xargs rm -rf - make -C hid/arduino clean - make -C hid/pico clean - - -clean-all: testenv clean - make -C hid/arduino clean-all - make -C hid/pico clean-all - - $(DOCKER) run --rm \ - --volume `pwd`:/src \ - -it $(TESTENV_IMAGE) bash -c "cd src && rm -rf testenv/{.ssl,.tox,.mypy_cache,.coverage}" - - -.PHONY: testenv - -run-stage-0: - $(DOCKER) buildx build -t registry.cn-hangzhou.aliyuncs.com/silentwind/kvmd-stage-0 \ - --allow security.insecure --progress plain \ - --platform linux/amd64,linux/arm64,linux/arm/v7 \ - -f build/Dockerfile-stage-0 . \ - --push - $(DOCKER) buildx build -t silentwind0/kvmd-stage-0 \ - --allow security.insecure --progress plain \ - --platform linux/amd64,linux/arm64,linux/arm/v7 \ - -f build/Dockerfile-stage-0 . \ - --push - -run-build-dev: - $(DOCKER) buildx build -t registry.cn-hangzhou.aliyuncs.com/silentwind/kvmd:dev \ - --platform linux/amd64,linux/arm64,linux/arm/v7 \ - --build-arg CACHEBUST=$(date +%s) --allow security.insecure \ - -f build/Dockerfile . \ - --push - $(DOCKER) buildx build -t silentwind0/kvmd:dev \ - --platform linux/amd64,linux/arm64,linux/arm/v7 \ - --build-arg CACHEBUST=$(date +%s) --allow security.insecure \ - -f build/Dockerfile . \ - --push - -run-build-new: - $(DOCKER) buildx build -t registry.cn-hangzhou.aliyuncs.com/silentwind/kvmd:dev \ - --platform linux/amd64 \ - --build-arg CACHEBUST=$(date +%s) \ - -f build/Dockerfile . \ - --load - -run-build-release: - $(DOCKER) buildx build -t registry.cn-hangzhou.aliyuncs.com/silentwind/kvmd \ - --progress plain \ - --platform linux/amd64,linux/arm64,linux/arm/v7 \ - --build-arg CACHEBUST=$(date +%s) \ - -f build/Dockerfile . \ - --push - $(DOCKER) buildx build -t silentwind0/kvmd \ - --progress plain \ - --platform linux/amd64,linux/arm64,linux/arm/v7 \ - --build-arg CACHEBUST=$(date +%s) \ - -f build/Dockerfile . \ - --push - -run-nogpio: testenv - - $(DOCKER) run --rm --name kvmd \ - --privileged \ - --volume `pwd`/testenv/run:/run/kvmd:rw \ - --volume `pwd`/testenv:/testenv:ro \ - --volume `pwd`/kvmd:/kvmd:ro \ - --volume `pwd`/testenv/env.py:/kvmd/env.py:ro \ - --volume `pwd`/web:/usr/share/kvmd/web:ro \ - --volume `pwd`/extras:/usr/share/kvmd/extras:ro \ - --volume `pwd`/configs:/usr/share/kvmd/configs.default:ro \ - --volume `pwd`/contrib/keymaps:/usr/share/kvmd/keymaps:ro \ - --device $(TESTENV_VIDEO):$(TESTENV_VIDEO) \ - $(if $(TESTENV_RELAY),--device $(TESTENV_RELAY):$(TESTENV_RELAY),) \ - --publish 8080:8080/tcp \ - --publish 4430:4430/tcp \ - -it $(TESTENV_IMAGE) /bin/bash -c " \ - mkdir -p /tmp/kvmd-nginx \ - && mount -t debugfs none /sys/kernel/debug \ - && (socat PTY,link=$(TESTENV_HID) PTY,link=/dev/ttyS11 &) \ - && cp -r /usr/share/kvmd/configs.default/nginx/* /etc/kvmd/nginx \ - && cp -a /testenv/.ssl/nginx /etc/kvmd/nginx/ssl \ - && cp -a /testenv/.ssl/vnc /etc/kvmd/vnc/ssl \ - && touch /etc/kvmd/.docker_flag \ - && cp /testenv/platform /usr/share/kvmd \ - && cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \ - && cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \ - && cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \ - && cp /usr/share/kvmd/configs.default/kvmd/main.yaml /etc/kvmd/main.yaml \ - && ln -s /testenv/web.css /etc/kvmd/web.css \ - && mkdir -p /etc/kvmd/override.d \ - && cp /testenv/$(if $(P),$(P),$(DEFAULT_PLATFORM)).override.yaml /etc/kvmd/override.yaml \ - && python -m kvmd.apps.ngxmkconf /etc/kvmd/nginx/nginx.conf.mako /etc/kvmd/nginx/nginx.conf \ - && nginx -c /etc/kvmd/nginx/nginx.conf -g 'user http; error_log stderr;' \ - && $(if $(CMD),$(CMD),python -m kvmd.apps.kvmd --run) \ - " -nuitka: - python3.11 -m nuitka kvmd-launcher.py --standalone --onefile --no-deployment-flag=self-execution --include-module=\ -kvmd.plugins.auth.htpasswd,kvmd.plugins.auth.http,kvmd.plugins.auth.ldap,\ -kvmd.plugins.auth.pam,kvmd.plugins.auth.radius,\ -kvmd.plugins.hid.ch9329,kvmd.plugins.hid.bt,kvmd.plugins.hid.otg,\ -kvmd.plugins.atx.disabled,kvmd.plugins.atx.gpio,\ -kvmd.plugins.msd.disabled,kvmd.plugins.msd.otg,\ -kvmd.plugins.ugpio.gpio,kvmd.plugins.ugpio.wol,kvmd.plugins.ugpio.cmd,\ -kvmd.plugins.ugpio.ipmi,kvmd.plugins.ugpio.anelpwr,kvmd.plugins.ugpio.cmdret,\ -kvmd.plugins.ugpio.extron,kvmd.plugins.ugpio.ezcoo,kvmd.plugins.ugpio.hidrelay,\ -kvmd.plugins.ugpio.hue,kvmd.plugins.ugpio.locator,kvmd.plugins.ugpio.noyito,\ -kvmd.plugins.ugpio.otgconf,kvmd.plugins.ugpio.pway,kvmd.plugins.ugpio.pwm,\ -kvmd.plugins.ugpio.servo,kvmd.plugins.ugpio.tesmart,kvmd.plugins.ugpio.xh_hk4401,\ -passlib.handlers.sha1_crypt,pygments.formatters.terminal \ No newline at end of file diff --git a/kvmd-launcher.py b/kvmd-launcher.py index 5ca4ae3f..ef44b340 100644 --- a/kvmd-launcher.py +++ b/kvmd-launcher.py @@ -1,11 +1,68 @@ +import multiprocessing +import os +import sys from kvmd.apps.kvmd import main as kvmd_main + +import fileinput + +# 文件路径 +file_path = '_internal/kvmd_data/etc/kvmd/kvmd_data/etc/kvmd/override.yaml' + +# 使用fileinput.input进行原地编辑 + + +def resource_path(relative_path): + if hasattr(sys, '_MEIPASS'): + base_path = sys._MEIPASS + else: + base_path = os.path.abspath(".") + return os.path.join(base_path, relative_path) + +def replace_streamer_command(override_config_path): + lines_to_replace = [ + " - \"C:/Users/mofen/miniconda3/python.exe\"\n", + " - \"ustreamer-win/ustreamer-win.py\"\n" + ] + new_line = " - \"ustreamer-win.exe\"\n" + + with open(override_config_path, 'r', encoding='utf-8') as file: + lines = file.readlines() + + with open(override_config_path, 'w', encoding='utf-8') as file: + i = 0 + while i < len(lines): + if lines[i] in lines_to_replace: + if i + 1 < len(lines) and lines[i + 1] == lines_to_replace[1]: + file.write(new_line) + i += 2 + continue + file.write(lines[i]) + i += 1 + def start(): + main_config_path = resource_path('kvmd_data/etc/kvmd/main.yaml') + override_config_path = resource_path('kvmd_data/etc/kvmd/override.yaml') + flag_path = resource_path('kvmd_data/run_flag') + + + + if not os.path.exists(flag_path): + with fileinput.input(override_config_path, inplace=True) as file: + for line in file: + updated_line = line.replace('kvmd_data/', '_internal/kvmd_data/') + print(updated_line, end='') + with open(flag_path, 'w') as flag_file: + flag_file.write("1") + + replace_streamer_command(override_config_path) + custom_argv = [ 'kvmd', - '-c', 'kvmd_data/etc/kvmd/main.yaml', + '-c',main_config_path, '--run' ] kvmd_main(argv=custom_argv) if __name__ == '__main__': + multiprocessing.freeze_support() start() \ No newline at end of file diff --git a/kvmd/apps/kvmd/info/extras.py b/kvmd/apps/kvmd/info/extras.py index 6cae6574..80dbfc44 100644 --- a/kvmd/apps/kvmd/info/extras.py +++ b/kvmd/apps/kvmd/info/extras.py @@ -53,7 +53,7 @@ class ExtrasInfoSubmanager(BaseInfoSubmanager): await sui.open() except Exception as ex: if not os.path.exists("/etc/kvmd/.docker_flag") or not sys.platform.startswith('linux'): - get_logger(0).error("Can't open systemd bus to get extras state: %s", tools.efmt(ex)) + get_logger(0).error("Can't open systemd bus to get extras state.") sui = None try: extras: dict[str, dict] = {} diff --git a/kvmd/apps/kvmd/info/hw.py b/kvmd/apps/kvmd/info/hw.py index db91e586..bc4af604 100644 --- a/kvmd/apps/kvmd/info/hw.py +++ b/kvmd/apps/kvmd/info/hw.py @@ -83,9 +83,9 @@ class HwInfoSubmanager(BaseInfoSubmanager): ) return { "platform": { - "type": "rpi", + "type": "windows", "base": base, - "serial": serial, + "serial": "windows1000000000", **platform, # type: ignore }, "health": { @@ -124,7 +124,7 @@ class HwInfoSubmanager(BaseInfoSubmanager): self.__dt_cache[name] = (await aiotools.read_file(path)).strip(" \t\r\n\0") except Exception as err: #get_logger(0).warn("Can't read DT %s from %s: %s", name, path, err) - return None + return "windows" return self.__dt_cache[name] async def __read_platform_file(self) -> dict: @@ -142,8 +142,8 @@ class HwInfoSubmanager(BaseInfoSubmanager): "board": parsed["PIKVM_BOARD"], } except Exception: - get_logger(0).exception("Can't read device model") - return {"model": None, "video": None, "board": None} + #get_logger(0).exception("Can't read device model") + return {"model": "V2", "video": "USB_VIDEO", "board": "Windows"} async def __get_cpu_temp(self) -> (float | None): temp_path = f"{env.SYSFS_PREFIX}/sys/class/thermal/thermal_zone0/temp" @@ -155,21 +155,9 @@ class HwInfoSubmanager(BaseInfoSubmanager): async def __get_cpu_percent(self) -> (float | None): try: - st = psutil.cpu_times_percent() - user = st.user - st.guest - nice = st.nice - st.guest_nice - idle_all = st.idle + st.iowait - system_all = st.system + st.irq + st.softirq - virtual = st.guest + st.guest_nice - total = max(1, user + nice + system_all + idle_all + st.steal + virtual) - return int( - st.nice / total * 100 - + st.user / total * 100 - + system_all / total * 100 - + (st.steal + st.guest) / total * 100 - ) + return int(psutil.cpu_percent(interval=1)) except Exception as ex: - #get_logger(0).error("Can't get CPU percent: %s", ex) + get_logger(0).error("Can't get CPU percent: %s", ex) return None async def __get_mem(self) -> dict: diff --git a/kvmd/apps/kvmd/streamer.py b/kvmd/apps/kvmd/streamer.py index 0f117e4a..82a7f524 100644 --- a/kvmd/apps/kvmd/streamer.py +++ b/kvmd/apps/kvmd/streamer.py @@ -418,7 +418,7 @@ class Streamer: # pylint: disable=too-many-instance-attributes await self.__start_streamer_proc() assert self.__streamer_proc is not None await aioproc.log_stdout_infinite(self.__streamer_proc, logger) - raise RuntimeError("Streamer unexpectedly died") + logger.exception("Streamer unexpectedly died") except asyncio.CancelledError: break except Exception: diff --git a/kvmd_data/etc/kvmd/main.yaml b/kvmd_data/etc/kvmd/main.yaml index 6d60dcf2..d54f79ed 100644 --- a/kvmd_data/etc/kvmd/main.yaml +++ b/kvmd_data/etc/kvmd/main.yaml @@ -2,7 +2,7 @@ # Use override.yaml to modify required settings. # You can find a working configuration in /usr/share/kvmd/configs.default/kvmd. -override: !include [override.d, override.yaml] +override: !include [override.yaml] logging: !include logging.yaml diff --git a/kvmd_data/etc/kvmd/meta.yaml b/kvmd_data/etc/kvmd/meta.yaml index f024a83f..43bf3591 100644 --- a/kvmd_data/etc/kvmd/meta.yaml +++ b/kvmd_data/etc/kvmd/meta.yaml @@ -4,7 +4,7 @@ # will be displayed in the web interface. server: - host: docker + host: windows kvm: { base_on: PiKVM, diff --git a/kvmd_data/etc/kvmd/override.yaml b/kvmd_data/etc/kvmd/override.yaml index 40f04548..70f81f55 100644 --- a/kvmd_data/etc/kvmd/override.yaml +++ b/kvmd_data/etc/kvmd/override.yaml @@ -40,12 +40,11 @@ kvmd: keymap: kvmd_data/usr/share/kvmd/keymaps/en-us msd: - #type: otg - remount_cmd: /bin/true - msd_path: /var/lib/kvmd/msd - normalfiles_path: NormalFiles - normalfiles_size: 256 + type: disabled + log_reader: + enabled: false + ocr: langs: - eng @@ -53,7 +52,7 @@ kvmd: streamer: resolution: - default: 1280x720 + default: 1920x1080 forever: true diff --git a/kvmd_data/usr/bin/ustreamer b/kvmd_data/usr/bin/ustreamer deleted file mode 100755 index 8c0a1571..00000000 Binary files a/kvmd_data/usr/bin/ustreamer and /dev/null differ diff --git a/kvmd_data/usr/share/kvmd/platform b/kvmd_data/usr/share/kvmd/platform index 0f00370c..0c9d0ac4 100644 --- a/kvmd_data/usr/share/kvmd/platform +++ b/kvmd_data/usr/share/kvmd/platform @@ -1,3 +1,3 @@ -PIKVM_MODEL=docker_model -PIKVM_VIDEO=docker_video -PIKVM_BOARD=docker_board +PIKVM_MODEL=windows_model +PIKVM_VIDEO=windows_video +PIKVM_BOARD=windows_board diff --git a/kvmd_data/usr/share/kvmd/web/kvm/index.html b/kvmd_data/usr/share/kvmd/web/kvm/index.html index 774a64bb..f5799301 100644 --- a/kvmd_data/usr/share/kvmd/web/kvm/index.html +++ b/kvmd_data/usr/share/kvmd/web/kvm/index.html @@ -899,7 +899,7 @@
-
+
diff --git a/kvmd_data/usr/share/kvmd/web/kvm/window-stream.pug b/kvmd_data/usr/share/kvmd/web/kvm/window-stream.pug index ee75646c..918260ab 100644 --- a/kvmd_data/usr/share/kvmd/web/kvm/window-stream.pug +++ b/kvmd_data/usr/share/kvmd/web/kvm/window-stream.pug @@ -13,7 +13,7 @@ div(id="stream-window" class="window window-resizable") div(id="stream-info") button(class="window-button-exit-full-tab") ▼ - div(id="stream-box" class="stream-box-online") + div(id="stream-box" class="stream-box-offline") img(id="stream-image" src=`${png_dir}/blank-stream.png`) video(id="stream-video" class="hidden" disablePictureInPicture="true" autoplay playsinline muted) div(id="stream-fullscreen-active") diff --git a/kvmd_data/usr/share/kvmd/web/share/js/kvm/stream_mjpeg.js b/kvmd_data/usr/share/kvmd/web/share/js/kvm/stream_mjpeg.js index 5de3e1e7..6b8b775d 100644 --- a/kvmd_data/usr/share/kvmd/web/share/js/kvm/stream_mjpeg.js +++ b/kvmd_data/usr/share/kvmd/web/share/js/kvm/stream_mjpeg.js @@ -127,8 +127,9 @@ export function MjpegStreamer(__setActive, __setInactive, __setInfo) { var __checkStream = function() { __findId(); - - if (__id.legnth > 0 && __id in __state.stream.clients_stat) { + console.log("__state.stream.clients_stat",__state.stream.clients_stat) + console.log("__id",__id) + if (__id.length > 0 && __id in __state.stream.clients_stat) { __setStreamActive(); __stopChecking(); diff --git a/quick_start.sh b/quick_start.sh deleted file mode 100755 index a304ab26..00000000 --- a/quick_start.sh +++ /dev/null @@ -1,317 +0,0 @@ -#!/bin/bash -#Install Latest Stable One-KVM Dcoker Release - -DOCKER_IMAGE_PATH="registry.cn-hangzhou.aliyuncs.com/silentwind/kvmd" -DOCKER_PORT="-p 8080:8080 -p 4430:4430 -p 5900:5900 -p 623:623" -DOCKER_NAME="kvmd" -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[0;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -function check_os_architecture(){ - osCheck=$(uname -a) - if [[ $osCheck =~ 'x86_64' ]];then - architecture="amd64" - elif [[ $osCheck =~ 'arm64' ]] || [[ $osCheck =~ 'aarch64' ]];then - architecture="arm64" - elif [[ $osCheck =~ 'armv7l' ]];then - architecture="armv7l" - else - echo "暂不支持的系统架构,请参阅官方文档,选择受支持的系统。\n退出程序" - exit 1 - fi -} - -function check_docker_exists() { - if command -v docker &> /dev/null; then - echo "$(docker -v)" - else - echo "Docker 未安装,退出程序" - exit 1 - fi -} - -function check_sudo_exists() { - if command -v sudo > /dev/null 2>&1; then - sudo_command="sudo" - else - sudo_command="" - fi -} - -function delete_kvmd_container(){ - if docker ps -a --format '{{.Names}}' | grep -q '^kvmd$'; then - $sudo_command docker stop $DOCKER_NAME - $sudo_command docker rm $DOCKER_NAME - fi -} - -function check_otg_device(){ - $sudo_command modprobe libcomposite > /dev/null|| echo -e "${YELLOW}libcomposite 内核模块加载失败${NC}" - if [[ "$architecture" != "amd64" ]] && [[ -d "/sys/class/udc" ]]; then - if [[ "$(ls -A /sys/class/udc)" ]] || [[ "$(ls -A /sys/class/usb_role)" ]]; then - otg_devices=$(ls -A /sys/class/udc) - otg_status=$(cat /sys/class/usb_role/*/role 2>/dev/null | head -n 1) - echo -e "${GREEN}当前系统支持 OTG:$otg_devices OTG 状态:$otg_status${NC}" - fi - else - echo -e "${RED}当前系统不支持 OTG,退出程序${NC}" - exit 1 - fi - if [[ ! -d "/sys/kernel/config" ]];then - echo -e "${RED}当前系统不支持 configfs 文件系统,退出程序${NC}" - exit 1 - fi -} - -function check_video_device(){ - if ls /dev/video* 1> /dev/null 2>&1; then - video_devices=($(ls /dev/video* 2>/dev/null)) - video_num_devices=${#video_devices[@]} - echo -e ""${GREEN}找到视频设备:$(ls -A /dev/video*)${NC}"" - else - echo -e "${RED}未找到任何视频采集设备,退出程序${NC}" - exit 1 - fi -} - -function check_repeat_install(){ - if docker ps -a --format '{{.Names}}' | grep -q '^kvmd$'; then - echo -e "${YELLOW}检查到 kvmd 容器已存在,是否删除容器重新部署?${NC}" - read -p "y/n: " delete_choice - case $delete_choice in - y|Y) - delete_kvmd_container - ;; - n|N) - echo -e "${RED}退出程序${NC}" - exit 1 - ;; - *) - echo -e "${RED}无效的选择,请输入 y 或者 n,退出程序${NC}" - exit 1 - ;; - esac - fi - if [[ -d "kvmd_config" ]]; then - echo -e "${YELLOW}检查到此前配置文件夹已存在,是否删除此前配置文件夹?${NC}" - read -p "y/n: " delete_choice - case $delete_choice in - y|Y) - $sudo_command rm -r kvmd_config - ;; - n|N) - echo -e "" - ;; - *) - echo -e "${RED}无效的选择,请输入 y 或者 n,退出程序${NC}" - exit 1 - ;; - esac - fi -} - -function show_main_menu() { - echo -e "${BLUE}==============================${NC}" - echo -e "${BLUE} One-KVM Docker 版管理 ${NC}" - echo -e "${BLUE}==============================${NC}" - - echo " 1. 安装 One-KVM Docker 版" - echo "" - echo " 2. 卸载 One-KVM Docker 版" - echo "" - echo " 3. 拉取 One-KVM 最新镜像" - echo "" - echo " 4. 更多信息" - - echo -e "${BLUE}==============================${NC}" - read -p "请输入数字(1-4): " choice - while [[ "$choice" != "1" && "$choice" != "2" && "$choice" != "3" && "$choice" != "4" ]]; do - echo -e "${RED}无效的选择,请输入1-4${NC}" - read -p "请输入数字(1-4): " choice - done - case $choice in - 1) - check_repeat_install - get_hid_info - get_video_info - get_audio_info - get_userinfo - get_userenv - show_install_info - get_install_command - execute_command - ;; - 2) - delete_kvmd_container - ;; - 3) - $sudo_command docker pull $DOCKER_IMAGE_PATH - ;; - 4) - echo -e "${BLUE}作者:${NC}\t\t默风SilentWind" - echo -e "${BLUE}文档:${NC}\t\thttps://one-kvm.mofeng.run/" - echo -e "${BLUE}Github:${NC}\thttps://github.com/mofeng-git/One-KVM" - ;; - *) - echo -e "${RED}无效的选择,请输入1-4之间的数字,退出程序${NC}" - exit 1 - ;; - esac -} - -function get_hid_info() { - if [[ "$architecture" == "amd64" ]]; then - echo -e "${GREEN}使用的 HID 硬件类型:CH9329${NC}" - use_hid="CH9329" - else - echo -e "${GREEN}请选择使用的 HID 硬件类型:${NC}" - echo " 1. OTG" - echo " 2. CH9329" - read -p "请输入数字(1 或 2): " hardware_type - while [[ "$hardware_type" != "1" && "$hardware_type" != "2" ]]; do - echo -e "${RED}无效的选择,请输入1或2。${NC}" - read -p "请输入数字(1 或 2): " hardware_type - done - if [[ "$hardware_type" == "1" ]]; then - use_hid="OTG" - else - use_hid="CH9329" - fi - fi - - if [[ "$use_hid" == "CH9329" ]]; then - if ls /dev/ttyUSB* 1> /dev/null 2>&1; then - echo -e ""${GREEN}找到串口设备:$(ls -A /dev/ttyUSB*)${NC}"" - else - echo -e "${RED}未找到任何 USB 串口设备,退出程序${NC}" - exit 1 - fi - read -p "请输入 CH9329 硬件的地址(回车使用默认值 /dev/ttyUSB0): " ch9329_address - read -p "请输入 CH9329 硬件的波特率(回车使用默认值 9600): " ch9329_serial_rate - ch9329_address=${ch9329_address:-/dev/ttyUSB0} - ch9329_serial_rate=${ch9329_serial_rate:-9600} - fi - - if [[ "$use_hid" == "OTG" ]]; then - check_otg_device - fi -} - -function get_video_info() { - check_video_device - if [[ "$video_num_devices" == "3" ]]; then - video_default_device="/dev/video1" - echo -e "${YELLOW}经检测 /dev/video0 可能不可用,建议使用 /dev/video1${NC}" - else - video_default_device="/dev/video0" - fi - read -p "请输入视频设备路径(回车使用默认值 $video_default_device): " video_device - if [[ -z "$video_device" ]]; then - video_device=$video_default_device - fi -} - -function get_audio_info() { - if [[ -d "/dev/snd" ]]; then - echo -e ""${GREEN}找到音频设备:$(ls -A /dev/snd)${NC}"" - read -p "请输入音频设备路径(回车使用默认值 hw:0): " audio_device - if [[ -z "$audio_device" ]]; then - audio_device="hw:0" - fi - else - echo -e "${YELLOW}未找到任何音频采集设备${NC}" - audio_device="none" - fi -} - -function get_userinfo() { - read -p "请输入用户名(回车使用默认值 admin): " username - read -s -p "请输入密码(回车使用默认值 admin): " password - if [[ -z "$username" ]]; then - username="admin" - fi - if [[ -z "$password" ]]; then - password="admin" - fi -} - -function get_userenv() { - echo -e "\n" - read -p "额外用户环境变量(回车则留空): " userenv -} - -function show_install_info() { - echo -e "\n\n${BLUE}==============================${NC}" - echo -e "${BLUE}安装信息总览:${NC}" - if [[ "$use_hid" == "CH9329" ]]; then - echo -e "CH9329 设备: \t${GREEN}$ch9329_address${NC} \tCH9329 波特率: \t${GREEN}$ch9329_serial_rate${NC}" - fi - if [[ "$use_hid" == "OTG" ]]; then - echo -e "OTG端口:\t${GREEN}$otg_devices${NC} \tOTG 状态:\t${GREEN}$otg_status${NC}" - fi - echo -e "视频设备: \t${GREEN}$video_device${NC} \t音频设备: \t${GREEN}$audio_device${NC}" - echo -e "用户名: \t${GREEN}$username${NC} \t\t密码: \t${GREEN}$password${NC}" -} - -function get_install_command(){ - local docker_init_command="docker run -itd --name $DOCKER_NAME" - local append_command="" - local append_env="" - - if [[ "$use_hid" == "CH9329" ]]; then - append_command="--device $video_device:/dev/video0 --device $ch9329_address:/dev/ttyUSB0 -v ./kvmd_config:/etc/kvmd" - - if [[ -d "/dev/snd" ]]; then - append_command="$append_command --device /dev/snd:/dev/snd -e AUDIONUM=${audio_device:3}" - fi - append_env="-e USERNAME=$username -e PASSWORD=$password -e CH9329SPEED=$ch9329_serial_rate" - docker_command="$sudo_command $docker_init_command $append_command $DOCKER_PORT $append_env $userenv $DOCKER_IMAGE_PATH" - else - append_command="--privileged=true -v /lib/modules:/lib/modules:ro -v /dev:/dev -v /sys/kernel/config:/sys/kernel/config -v ./kvmd_config:/etc/kvmd" - if [[ -d "/dev/snd" ]]; then - append_command="$append_command -e AUDIONUM=${audio_device:3}" - fi - append_env="-e OTG=1 -e USERNAME=$username -e PASSWORD=$password -e VIDEONUM=${video_device:10} -e AUDIONUM=${audio_device:3}" - docker_command="$sudo_command $docker_init_command $append_command $DOCKER_PORT $append_env $userenv $DOCKER_IMAGE_PATH" - fi - echo -e "\n${BLUE}Docker 部署命令:${NC}\n$docker_command" - echo -e "${BLUE}==============================${NC}\n" -} - -function execute_command(){ - echo -e "${BLUE}One-KVM 部署中......${NC}" - eval "$docker_command" - local exit_status=$? - if [[ $exit_status -eq 0 ]]; then - echo -e "${BLUE}One-KVM 部署成功${NC}" - $sudo_command docker update --restart=always $DOCKER_NAME - if [[ "$use_hid" == "OTG" ]]; then - execute_otg_command - fi - else - echo -e "${RED}One-KVM 部署失败,退出状态码为 $exit_status${NC}" - fi -} - -function execute_otg_command(){ - $sudo_command echo "device" > /sys/class/usb_role/**/role || echo -e "${YELLOW}OTG 端口切换 device 模式失败${NC}" - if grep -q "usb_role" /etc/rc.local; then - echo -e "" - else - $sudo_command sed -i '/^exit 0/i echo device > \/sys\/class\/usb_role\/\*\*\/role' /etc/rc.local - $sudo_command chmod +x /etc/rc.local - fi - if grep -q "libcomposite" /etc/modules.conf; then - echo -e "" - else - $sudo_command echo "libcomposite" >> /etc/modules.conf - fi -} - -check_os_architecture -check_docker_exists -check_sudo_exists -show_main_menu \ No newline at end of file diff --git a/tools/test_video.py b/tools/test_video.py new file mode 100644 index 00000000..0e0237f3 --- /dev/null +++ b/tools/test_video.py @@ -0,0 +1,29 @@ +# 模块导入 +import numpy as np +import cv2 as cv +# 相机捕获 +cap = cv.VideoCapture(0,cv.CAP_DSHOW) +#更改默认参数 +cap.set(6,cv.VideoWriter.fourcc('M','J','P','G'))# 视频流格式 +cap.set(5, 30);# 帧率 +cap.set(3, 1280)# 帧宽 +cap.set(4, 720)# 帧高 +# 获取相机宽高以及帧率 +width = cap.get(3) +height = cap.get(4) +frame = cap.get(5) #帧率只对视频有效,因此返回值为0 +#打印信息 +print(width ,height) +# 循环 +while(True): + # 获取一帧图片 + ret, img = cap.read() + # 显示图片 + cv.imshow('img', img) + # 等待键盘事件 + k = cv.waitKey(1) & 0xFF + if k == 27: + break +#资源释放 +cap.release() +cv.destroyAllWindows() \ No newline at end of file diff --git a/ustreamer-win/mjpeg_stream.py b/ustreamer-win/mjpeg_stream.py index b9b13529..87bfbd27 100644 --- a/ustreamer-win/mjpeg_stream.py +++ b/ustreamer-win/mjpeg_stream.py @@ -3,9 +3,9 @@ import threading import time import json from collections import deque -from typing import List, Optional, Tuple, Union, Dict, Any +from typing import List, Optional, Tuple, Dict +import uuid -import aiohttp import cv2 import logging import numpy as np @@ -13,8 +13,7 @@ from aiohttp import MultipartWriter, web from aiohttp.web_runner import GracefulExit class MjpegStream: - """MJPEG video stream class for handling video frames and providing HTTP streaming service""" - + def __init__( self, name: str, @@ -26,19 +25,7 @@ class MjpegStream: device_name: str = "Unknown Camera", log_requests: bool = True ) -> None: - """ - Initialize MJPEG stream - - Args: - name: Stream name - size: Video size (width, height) - quality: JPEG compression quality (1-100) - fps: Target frame rate - host: Server host address - port: Server port - device_name: Camera device name - log_requests: Whether to log stream requests - """ + self.name = name.lower().replace(" ", "_") self.size = size self.quality = max(1, min(quality, 100)) @@ -48,53 +35,58 @@ class MjpegStream: self._device_name = device_name self.log_requests = log_requests - # Video frame and synchronization self._frame = np.zeros((320, 240, 1), dtype=np.uint8) self._lock = asyncio.Lock() - self._byte_frame_window = deque(maxlen=30) - self._bandwidth_last_modified_time = time.time() self._is_online = True - self._last_frame_time = time.time() + self._last_repeat_frame_time = time.time() + self._last_fps_update_time = time.time() + self._last_frame_data = None + self.per_second_fps = 0 + self.frame_counter = 0 - - # 设置日志级别为ERROR,以隐藏HTTP请求日志 if not self.log_requests: logging.getLogger('aiohttp.access').setLevel(logging.ERROR) - # Server setup self._app = web.Application() self._app.router.add_route("GET", f"/{self.name}", self._stream_handler) self._app.router.add_route("GET", "/state", self._state_handler) self._app.router.add_route("GET", "/", self._index_handler) + self._app.router.add_route("GET", "/snapshot", self._snapshot_handler) self._app.is_running = False + self._clients: Dict[str, Dict] = {} + self._clients_lock = asyncio.Lock() + def set_frame(self, frame: np.ndarray) -> None: - """Set the current video frame""" self._frame = frame - self._last_frame_time = time.time() self._is_online = True - def get_bandwidth(self) -> float: - """Get current bandwidth usage (bytes/second)""" - if time.time() - self._bandwidth_last_modified_time >= 1: - self._byte_frame_window.clear() - return sum(self._byte_frame_window) - async def _process_frame(self) -> Tuple[np.ndarray, Dict[str, str]]: - """Process video frame (resize and JPEG encode)""" frame = cv2.resize( self._frame, self.size or (self._frame.shape[1], self._frame.shape[0]) ) success, encoded = cv2.imencode( ".jpg", frame, [cv2.IMWRITE_JPEG_QUALITY, self.quality] ) + if not success: raise ValueError("Error encoding frame") - - self._byte_frame_window.append(len(encoded.tobytes())) - self._bandwidth_last_modified_time = time.time() + + current_frame_data = encoded.tobytes() + current_time = time.time() + + if current_frame_data == self._last_frame_data and current_time - self._last_repeat_frame_time < 1: + return None, {} + else: + self._last_frame_data = current_frame_data + self._last_repeat_frame_time = current_time - # Add KVMD-compatible header information + if current_time - self._last_fps_update_time >= 1: + self.per_second_fps = self.frame_counter + self.frame_counter = 0 + self._last_fps_update_time = current_time + + self.frame_counter += 1 headers = { "X-UStreamer-Online": str(self._is_online).lower(), "X-UStreamer-Width": str(frame.shape[1]), @@ -109,61 +101,69 @@ class MjpegStream: return encoded, headers async def _stream_handler(self, request: web.Request) -> web.StreamResponse: - """Handle MJPEG stream requests""" + client_id = request.query.get("client_id", uuid.uuid4().hex[:8]) + client_key = request.query.get("key", "0") + advance_headers = request.query.get("advance_headers", "0") == "1" + response = web.StreamResponse( status=200, reason="OK", - headers={"Content-Type": "multipart/x-mixed-replace;boundary=frame"} + headers={ + "Content-Type": "multipart/x-mixed-replace;boundary=frame", + "Set-Cookie": f"stream_client={client_key}/{client_id}; Path=/; Max-Age=30" + } ) await response.prepare(request) - if self.log_requests: - print(f"Stream request received: {request.path}") + async with self._clients_lock: + if client_id not in self._clients: + self._clients[client_id] = { + "key": client_key, + "advance_headers": advance_headers, + "extra_headers": False, + "zero_data": False, + "fps": 0, + } + try: + while True: + async with self._lock: + frame, headers = await self._process_frame() + if frame is None: + continue + + #Enable workaround for the Chromium/Blink bug https://issues.chromium.org/issues/41199053 + if advance_headers: + headers.pop('Content-Length', None) + for k in list(headers.keys()): + if k.startswith('X-UStreamer-'): + del headers[k] + + with MultipartWriter("image/jpeg", boundary="frame") as mpwriter: + part = mpwriter.append(frame.tobytes(), {"Content-Type": "image/jpeg"}) + for key, value in headers.items(): + part.headers[key] = value + try: + await mpwriter.write(response, close_boundary=False) + except (ConnectionResetError, ConnectionAbortedError): + return web.Response(status=499) + await response.write(b"\r\n") + self._clients[client_id]["fps"]=self.per_second_fps + finally: + async with self._clients_lock: + if client_id in self._clients: + del self._clients[client_id] - while True: - await asyncio.sleep(1 / self.fps) - - # Check if the device is online - if time.time() - self._last_frame_time > 5: - self._is_online = False - - async with self._lock: - frame, headers = await self._process_frame() - - with MultipartWriter("image/jpeg", boundary="frame") as mpwriter: - part = mpwriter.append(frame.tobytes(), {"Content-Type": "image/jpeg"}) - for key, value in headers.items(): - part.headers[key] = value - try: - await mpwriter.write(response, close_boundary=False) - except (ConnectionResetError, ConnectionAbortedError): - return web.Response(status=499) - await response.write(b"\r\n") async def _state_handler(self, request: web.Request) -> web.Response: - """Handle /state requests and return device status information""" state = { + "ok": "true", "result": { "instance_id": "", "encoder": { "type": "CPU", "quality": self.quality }, - "h264": { - "bitrate": 4875, - "gop": 60, - "online": self._is_online, - "fps": self.fps - }, - "sinks": { - "jpeg": { - "has_clients": False - }, - "h264": { - "has_clients": False - } - }, "source": { "resolution": { "width": self.size[0] if self.size else self._frame.shape[1], @@ -171,21 +171,12 @@ class MjpegStream: }, "online": self._is_online, "desired_fps": self.fps, - "captured_fps": 0 # You can update this with actual captured fps if needed + "captured_fps": self.fps }, "stream": { - "queued_fps": 2, # Placeholder value, update as needed - "clients": 1, # Placeholder value, update as needed - "clients_stat": { - "70bf63a507f71e47": { - "fps": 2, # Placeholder value, update as needed - "extra_headers": False, - "advance_headers": True, - "dual_final_frames": False, - "zero_data": False, - "key": "tIR9TtuedKIzDYZa" # Placeholder key, update as needed - } - } + "queued_fps": self.fps, + "clients": len(self._clients), + "clients_stat": self._clients } } } @@ -195,18 +186,30 @@ class MjpegStream: ) async def _index_handler(self, _: web.Request) -> web.Response: - """Handle root path requests and display available streams""" html = f""" -

Available Video Streams:

-