Merge pull request '(videobundler) implement streaming and fix unknown var issue' (#95) from nin0dev/poke:main into main

Reviewed-on: https://codeberg.org/ashley/poke/pulls/95
This commit is contained in:
Ashley //// 2024-06-26 07:59:30 +00:00
commit f875214bee
2 changed files with 233 additions and 30 deletions

170
videobundler/.gitignore vendored Normal file
View file

@ -0,0 +1,170 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
bin/
include/
lib64/
pyvenv.cfg
*.m4a
*.mp4
.env
done.*

View file

@ -5,6 +5,7 @@ import string
import os import os
import random import random
import subprocess import subprocess
from aiohttp.web import Response, FileResponse
app = web.Application() app = web.Application()
app.router._frozen = False app.router._frozen = False
@ -15,16 +16,27 @@ def get_random_string(length):
result_str = "".join(random.choice(letters) for i in range(length)) result_str = "".join(random.choice(letters) for i in range(length))
return result_str return result_str
async def merge(request): async def run_command(cmd):
# Create subprocess
process = await asyncio.create_subprocess_shell(
cmd,
stdout=asyncio.subprocess.PIPE,
)
# Wait for the subprocess to finish
stdout, stderr = await process.communicate()
# Check for errors
if process.returncode!= 0:
# Log or handle the error
print(f"Command '{args}' failed with return code {process.returncode}")
return None
# Decode stdout and return
return stdout
async def merge(request: aiohttp.web.Request):
# register params # register params
try: video_id: str = request.rel_url.query["id"]
job_id = request.rel_url.query["id"] audio_itag: str = request.rel_url.query["audio_itag"]
video_id: str = request.rel_url.query["id"] video_itag: str = request.rel_url.query["video_itag"]
audio_itag: str = request.rel_url.query["audio_itag"]
video_itag: str = request.rel_url.query["video_itag"]
except:
# no one gives a fuck
_ = 0
# validate # validate
if " " in video_id or len(video_id) > 11: if " " in video_id or len(video_id) > 11:
print(f"Video {video_id} flagged as invalid, dropping request") print(f"Video {video_id} flagged as invalid, dropping request")
@ -35,27 +47,48 @@ async def merge(request):
if not video_itag.isdigit(): if not video_itag.isdigit():
print(f"Video itag {video_itag} flagged as invalid, dropping request") print(f"Video itag {video_itag} flagged as invalid, dropping request")
return return
if os.path.isfile(f"done.{job_id}"): if "Firefox" in request.headers["User-Agent"]:
return web.FileResponse( # Sane browser that supports streaming
path=f"output.{job_id}.mp4"
cmdline = f"ffmpeg -i \"https://eu-proxy.poketube.fun/latest_version?id={video_id}&itag={audio_itag}&local=true\" -i \"https://eu-proxy.poketube.fun/latest_version?id={video_id}&itag={video_itag}&local=true\" -c copy -f mp4 -movflags frag_keyframe+empty_moov -"
process = await asyncio.create_subprocess_shell(
cmdline,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
) )
proc_audio = await asyncio.create_subprocess_shell( response = web.StreamResponse(status=206, reason='OK', headers={
f"wget -O{job_id}.m4a \"https://eu-proxy.poketube.fun/latest_version?id={video_id}&itag={audio_itag}&local=true\"", 'Content-Type': 'application/octet-stream',
) 'Transfer-Encoding': 'chunked',
proc_video = await asyncio.create_subprocess_shell( 'Content-Disposition': 'inline'
f"wget -O{job_id}.mp4 \"https://eu-proxy.poketube.fun/latest_version?id={video_id}&itag={video_itag}&local=true\"" })
) await response.prepare(request)
await asyncio.gather(proc_audio.wait(), proc_video.wait()) try:
proc_ffmpeg = await asyncio.create_subprocess_shell( while True:
f"ffmpeg -i {job_id}.m4a -i {job_id}.mp4 -c copy output.{job_id}.mp4" chunk = await process.stdout.readline()
) if not chunk:
await proc_ffmpeg.wait() break
f = open(f"done.{job_id}", "a") await response.write(chunk)
f.write(":3") except Exception as e:
f.close()
return web.FileResponse( print(f"Error streaming FFmpeg output: {e}")
path=f"output.{job_id}.mp4" #finally:
) #await response.write_eof()
else:
# Likely to be chromium browser, so to avoid browser shitting itself we download file
job_id = f"{request.rel_url.query["id"]}_{request.rel_url.query["audio_itag"]}_{request.rel_url.query["video_itag"]}"
if os.path.isfile(f"{job_id}.mp4"):
return web.FileResponse(
path=f"{job_id}.mp4"
)
cmdline = f"ffmpeg -i \"https://eu-proxy.poketube.fun/latest_version?id={video_id}&itag={audio_itag}&local=true\" -i \"https://eu-proxy.poketube.fun/latest_version?id={video_id}&itag={video_itag}&local=true\" -c:v copy -f mp4 -movflags frag_keyframe+empty_moov {job_id}.mp4"
process = await asyncio.create_subprocess_shell(
cmdline
)
await process.wait()
if process.returncode != 0: # Log or handle the error
return None
response = FileResponse(path=f"{job_id}.mp4")
return response
async def ping(request): async def ping(request):
return web.Response(body='{"success": true}', content_type="application/json") return web.Response(body='{"success": true}', content_type="application/json")
@ -68,4 +101,4 @@ async def init_app():
if __name__ == '__main__': if __name__ == '__main__':
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
app = loop.run_until_complete(init_app()) app = loop.run_until_complete(init_app())
web.run_app(app, port=3030) web.run_app(app, port=3030)