diff --git a/anime/__main__.py b/anime/__main__.py index a27812ed4d01aa54a5d1540ea767275faedf7f17..586cdb0d33a105e79137bb801cf61ca821640fb8 100644 --- a/anime/__main__.py +++ b/anime/__main__.py @@ -1,12 +1,13 @@ from pathlib import Path from typing import Optional -from fastapi import FastAPI, HTTPException, Request, Response + +import uvicorn +from fastapi import FastAPI from fastapi.responses import JSONResponse from fastapi.staticfiles import StaticFiles -from typer import Typer, Argument -import uvicorn +from typer import Argument, Typer + from anime.routes import router -from starlette.middleware.base import BaseHTTPMiddleware CURRENT_DIR = Path(__file__).parent STATIC_DIR = CURRENT_DIR / "static" diff --git a/anime/routes.py b/anime/routes.py index 54920470d565d840d6b260cbfcabc2d587140902..fc34140b849fd51c68035fdd29ac3db57dbb998b 100644 --- a/anime/routes.py +++ b/anime/routes.py @@ -1,7 +1,8 @@ import os -from pathlib import Path import shutil import subprocess +from pathlib import Path + from fastapi import APIRouter, HTTPException, Request from anime.dtos import KillRequest, PlayerOffsetRequest @@ -10,6 +11,16 @@ CWD = Path.cwd() router = APIRouter() +def is_pid_alive(pid: int) -> bool: + if pid: + try: + os.kill(pid, 0) + except OSError: + return False + else: + return True + + @router.get("/can-start-watching") def can_start_watching(request: Request) -> None: if not request.app.state.anime_dir: @@ -27,16 +38,11 @@ def start_watching(request: Request) -> None: anime_dir = request.app.state.anime_dir if not anime_dir: raise HTTPException(status_code=400, detail="Anime directory is not set") - if request.app.state.pid: - try: - os.kill(request.app.state.pid, 0) - except OSError: - pass - else: - raise HTTPException( - status_code=400, - detail="Awatch is already running", - ) + if request.app.state.pid and is_pid_alive(request.app.state.pid): + raise HTTPException( + status_code=400, + detail="Awatch is already running", + ) awatch = shutil.which("awatch") if awatch is None: raise Exception("awatch command is not available") @@ -75,3 +81,24 @@ async def play_pause() -> None: ) subprocess.run([playerctl, "play-pause"], check=False) + + +@router.get("/player/playing") +def player_state(request: Request): + playerctl = shutil.which("playerctl") + if playerctl is None: + raise HTTPException( + status_code=500, + detail="playerctl command is not available", + ) + + status_cmd = subprocess.Popen([playerctl, "status"], stdout=subprocess.PIPE) + status_cmd.wait() + current_state = status_cmd.stdout.read().decode("utf-8").strip().lower() + if ( + request.app.state.pid + and is_pid_alive(request.app.state.pid) + and current_state == "playing" + ): + return {"playing": True} + return {"playing": False} diff --git a/frontend/src/components/PlayerComponent.vue b/frontend/src/components/PlayerComponent.vue index 88707a537a36e2a9e68fa8e2658675d2df7ae709..3e75a5a308c5363a33c579daebef891d1fb8ca84 100644 --- a/frontend/src/components/PlayerComponent.vue +++ b/frontend/src/components/PlayerComponent.vue @@ -1,7 +1,9 @@ <script setup> import { ActionBar, ActionBarButton } from 'vant'; import { postRequest } from '@/utils' +import { ref } from 'vue' +const is_playing = ref(false); async function offsetRequest(offset, forward) { await postRequest(`/api/player/offset`, { @@ -12,16 +14,28 @@ async function offsetRequest(offset, forward) { async function playPauseRequest() { await postRequest(`/api/player/play-pause`) + update_state() } +function update_state() { + fetch('/api/player/playing').then((response) => { + if (response.ok) { + response.json().then((resp_json) => { + is_playing.value = resp_json.playing + }) + } + }) +} + +update_state() </script> <template> <ActionBar> - <ActionBarButton icon="arrow-double-left" @click="offsetRequest(90, false)"></ActionBarButton> + <ActionBarButton icon="arrow-double-left" @click="offsetRequest(85, false)"></ActionBarButton> <ActionBarButton icon="arrow-left" @click="offsetRequest(10, false)"></ActionBarButton> - <ActionBarButton icon="play" @click="playPauseRequest()"></ActionBarButton> + <ActionBarButton :icon="is_playing ? 'pause' : 'play'" @click="playPauseRequest()"></ActionBarButton> <ActionBarButton icon="arrow" @click="offsetRequest(10, true)"></ActionBarButton> - <ActionBarButton icon="arrow-double-right" @click="offsetRequest(90, true)"></ActionBarButton> + <ActionBarButton icon="arrow-double-right" @click="offsetRequest(85, true)"></ActionBarButton> </ActionBar> </template> \ No newline at end of file diff --git a/frontend/src/views/HomeView.vue b/frontend/src/views/HomeView.vue index 29008c619c07bf9eb9e6c452cbf3288e077c282e..f19dba1a286e5b47efa79e29ac9de20262cffbc4 100644 --- a/frontend/src/views/HomeView.vue +++ b/frontend/src/views/HomeView.vue @@ -17,14 +17,14 @@ async function kill(...names) { async function startWatching() { await postRequest("/api/start-watching") } - </script> <template> <Space fill direction="vertical" :size="20"> <Button type="warning" round size="large" @click="kill('mpv')">Убить MPV</Button> <Button type="danger" round size="large" @click="kill('awatch', 'mpv')">Убить awatch</Button> - <Button type="primary" round size="large" :disabled="!can_watch" @click="startWatching">Ðачать потребление анимы</Button> + <Button type="primary" round size="large" :disabled="!can_watch" @click="startWatching">Ðачать потребление + анимы</Button> </Space> <PlayerComponent /> </template> diff --git a/poetry.lock b/poetry.lock index 02e567f65045871ade2222977128c14cf31ab346..b56d2294d773cb268caf657db7a4055fe34039f3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -31,6 +31,50 @@ doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphin test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] trio = ["trio (>=0.23)"] +[[package]] +name = "black" +version = "24.4.2" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"}, + {file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"}, + {file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"}, + {file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"}, + {file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"}, + {file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"}, + {file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"}, + {file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"}, + {file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"}, + {file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"}, + {file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"}, + {file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"}, + {file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"}, + {file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"}, + {file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"}, + {file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"}, + {file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"}, + {file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"}, + {file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"}, + {file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"}, + {file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"}, + {file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + [[package]] name = "click" version = "8.1.7" @@ -180,6 +224,55 @@ files = [ {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "packaging" +version = "24.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "platformdirs" +version = "4.2.1" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"}, + {file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] + [[package]] name = "pydantic" version = "2.7.1" @@ -397,6 +490,32 @@ pygments = ">=2.13.0,<3.0.0" [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] +[[package]] +name = "ruff" +version = "0.4.2" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.4.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:8d14dc8953f8af7e003a485ef560bbefa5f8cc1ad994eebb5b12136049bbccc5"}, + {file = "ruff-0.4.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:24016ed18db3dc9786af103ff49c03bdf408ea253f3cb9e3638f39ac9cf2d483"}, + {file = "ruff-0.4.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2e06459042ac841ed510196c350ba35a9b24a643e23db60d79b2db92af0c2b"}, + {file = "ruff-0.4.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3afabaf7ba8e9c485a14ad8f4122feff6b2b93cc53cd4dad2fd24ae35112d5c5"}, + {file = "ruff-0.4.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:799eb468ea6bc54b95527143a4ceaf970d5aa3613050c6cff54c85fda3fde480"}, + {file = "ruff-0.4.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ec4ba9436a51527fb6931a8839af4c36a5481f8c19e8f5e42c2f7ad3a49f5069"}, + {file = "ruff-0.4.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6a2243f8f434e487c2a010c7252150b1fdf019035130f41b77626f5655c9ca22"}, + {file = "ruff-0.4.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8772130a063f3eebdf7095da00c0b9898bd1774c43b336272c3e98667d4fb8fa"}, + {file = "ruff-0.4.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ab165ef5d72392b4ebb85a8b0fbd321f69832a632e07a74794c0e598e7a8376"}, + {file = "ruff-0.4.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1f32cadf44c2020e75e0c56c3408ed1d32c024766bd41aedef92aa3ca28eef68"}, + {file = "ruff-0.4.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:22e306bf15e09af45ca812bc42fa59b628646fa7c26072555f278994890bc7ac"}, + {file = "ruff-0.4.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:82986bb77ad83a1719c90b9528a9dd663c9206f7c0ab69282af8223566a0c34e"}, + {file = "ruff-0.4.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:652e4ba553e421a6dc2a6d4868bc3b3881311702633eb3672f9f244ded8908cd"}, + {file = "ruff-0.4.2-py3-none-win32.whl", hash = "sha256:7891ee376770ac094da3ad40c116258a381b86c7352552788377c6eb16d784fe"}, + {file = "ruff-0.4.2-py3-none-win_amd64.whl", hash = "sha256:5ec481661fb2fd88a5d6cf1f83403d388ec90f9daaa36e40e2c003de66751798"}, + {file = "ruff-0.4.2-py3-none-win_arm64.whl", hash = "sha256:cbd1e87c71bca14792948c4ccb51ee61c3296e164019d2d484f3eaa2d360dfaf"}, + {file = "ruff-0.4.2.tar.gz", hash = "sha256:33bcc160aee2520664bc0859cfeaebc84bb7323becff3f303b8f1f2d81cb4edc"}, +] + [[package]] name = "shellingham" version = "1.5.4" @@ -704,4 +823,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "393201687c83f5df988de3af661b5887843efb8e03128761f5ba5174b7331d69" +content-hash = "f9e9fd7090b241c75ccfdafcf03212d386197cfc54664a7055ec40446a970232" diff --git a/pyproject.toml b/pyproject.toml index 9406048eed49b9019297a8b8178c824c1d0af0ff..9de668d65e20c034bbe545a7be434dcb1ed06634 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,10 @@ anime = "anime.__main__:main" generate-setup-file = false script = "build.py" +[tool.poetry.group.dev.dependencies] +black = "^24.4.2" +ruff = "^0.4.2" + [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api"