diff --git a/.gitignore b/.gitignore index 812a03ba..474bdc25 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ backend/.env # JavaScript stuff node_modules + +.editorconfig diff --git a/Dockerfile b/Dockerfile index b8260679..c4319f71 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,20 +1,24 @@ # multi stage to build tube archivist # build python wheel, download and extract ffmpeg, copy into final image +FROM node:lts-alpine AS npm-builder +COPY frontend/package.json frontend/package-lock.json / +RUN npm i + FROM node:lts-alpine AS node-builder # RUN npm config set registry https://registry.npmjs.org/ +COPY --from=npm-builder ./node_modules /frontend/node_modules COPY ./frontend /frontend - WORKDIR /frontend -RUN npm i + RUN npm run build:deploy WORKDIR / # First stage to build python wheel -FROM python:3.11.8-slim-bookworm AS builder +FROM python:3.11.13-slim-bookworm AS builder RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential gcc libldap2-dev libsasl2-dev libssl-dev git @@ -24,7 +28,7 @@ COPY ./backend/requirements.txt /requirements.txt RUN pip install --user -r requirements.txt # build ffmpeg -FROM python:3.11.8-slim-bookworm AS ffmpeg-builder +FROM python:3.11.13-slim-bookworm AS ffmpeg-builder ARG TARGETPLATFORM @@ -32,7 +36,7 @@ COPY docker_assets/ffmpeg_download.py ffmpeg_download.py RUN python ffmpeg_download.py $TARGETPLATFORM # build final image -FROM python:3.11.8-slim-bookworm AS tubearchivist +FROM python:3.11.13-slim-bookworm AS tubearchivist ARG INSTALL_DEBUG @@ -54,9 +58,9 @@ RUN apt-get clean && apt-get -y update && apt-get -y install --no-install-recomm # install debug tools for testing environment RUN if [ "$INSTALL_DEBUG" ] ; then \ - apt-get -y update && apt-get -y install --no-install-recommends \ - vim htop bmon net-tools iputils-ping procps lsof \ - && pip install --user ipython pytest pytest-django \ + apt-get -y update && apt-get -y install --no-install-recommends \ + vim htop bmon net-tools iputils-ping procps lsof \ + && pip install --user ipython pytest pytest-django \ ; fi # make folders diff --git a/README.md b/README.md index 7d3b2a96..9c50cd8c 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ All environment variables are explained in detail in the docs [here](https://doc | ELASTIC_USER | Change the default ElasticSearch user | Optional | | TA_LDAP | Configure TA to use LDAP Authentication | [Read more](https://docs.tubearchivist.com/configuration/ldap/) | | DISABLE_STATIC_AUTH | Remove authentication from media files, (Google Cast...) | [Read more](https://docs.tubearchivist.com/installation/env-vars/#disable_static_auth) | +| TA_AUTO_UPDATE_YTDLP | Configure TA to automatically install the latest yt-dlp on container start | Optional | | DJANGO_DEBUG | Return additional error messages, for debug only | Optional | | TA_LOGIN_AUTH_MODE | Configure the order of login authentication backends (Default: single) | Optional | diff --git a/backend/config/settings.py b/backend/config/settings.py index 39d2186e..abcc4aa8 100644 --- a/backend/config/settings.py +++ b/backend/config/settings.py @@ -316,7 +316,7 @@ CORS_ALLOW_HEADERS = list(default_headers) + [ # TA application settings TA_UPSTREAM = "https://github.com/tubearchivist/tubearchivist" -TA_VERSION = "v0.5.4" +TA_VERSION = "v0.5.5-unstable" # API REST_FRAMEWORK = { diff --git a/backend/requirements-dev.txt b/backend/requirements-dev.txt index a5ee0352..30696ad3 100644 --- a/backend/requirements-dev.txt +++ b/backend/requirements-dev.txt @@ -4,7 +4,7 @@ pre-commit==4.2.0 pylint-django==2.6.1 pylint==3.3.7 pytest-django==4.11.1 -pytest==8.4.0 -python-dotenv==1.1.0 +pytest==8.4.1 +python-dotenv==1.1.1 requirementscheck==0.0.6 -types-requests==2.32.0.20250602 +types-requests==2.32.4.20250611 diff --git a/backend/requirements.txt b/backend/requirements.txt index 9751c2cb..b939b37f 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -3,13 +3,13 @@ celery==5.5.3 django-auth-ldap==5.2.0 django-celery-beat==2.8.1 django-cors-headers==4.7.0 -Django==5.2.2 +Django==5.2.3 djangorestframework==3.16.0 drf-spectacular==0.28.0 Pillow==11.2.1 redis==6.2.0 requests==2.32.4 ryd-client==0.0.6 -uvicorn==0.34.3 +uvicorn==0.35.0 whitenoise==6.9.0 -yt-dlp[default]==2025.6.9 +yt-dlp[default]==2025.6.30 diff --git a/docker_assets/run.sh b/docker_assets/run.sh index 9f065d7b..b09bee22 100644 --- a/docker_assets/run.sh +++ b/docker_assets/run.sh @@ -9,6 +9,15 @@ else LOGLEVEL="INFO" fi +# update yt-dlp if needed +if [[ "${TA_AUTO_UPDATE_YTDLP,,}" =~ ^(release|nightly)$ ]]; then + echo "Updating yt-dlp..." + preflag=$([[ "${TA_AUTO_UPDATE_YTDLP,,}" == "nightly" ]] && echo "--pre" || echo "") + python -m pip install --target=/root/.local/bin --upgrade $preflag "yt-dlp[default]" || { + echo "yt-dlp update failed" + } +fi + # stop on pending manual migration python manage.py ta_stop_on_error diff --git a/frontend/src/components/DownloadListItem.tsx b/frontend/src/components/DownloadListItem.tsx index bb0a82e3..ff0273dd 100644 --- a/frontend/src/components/DownloadListItem.tsx +++ b/frontend/src/components/DownloadListItem.tsx @@ -47,7 +47,11 @@ const DownloadListItem = ({ download, setRefresh }: DownloadListItemProps) => { {!download.channel_indexed && {download.channel_name}} - +

{download.title}

diff --git a/frontend/src/components/InputConfig.tsx b/frontend/src/components/InputConfig.tsx index a378a5fb..cf1bfa13 100644 --- a/frontend/src/components/InputConfig.tsx +++ b/frontend/src/components/InputConfig.tsx @@ -1,4 +1,5 @@ import { useState } from 'react'; +import LoadingIndicator from './LoadingIndicator'; type InputTextProps = { type: 'text' | 'number'; @@ -51,13 +52,7 @@ const InputConfig = ({ type, name, value, setValue, oldValue, updateCallback }: )} {oldValue !== null && } - {loading && ( - <> -
-
-
- - )} + {loading && } {success && }
diff --git a/frontend/src/components/LoadingIndicator.tsx b/frontend/src/components/LoadingIndicator.tsx new file mode 100644 index 00000000..0eaeaeb4 --- /dev/null +++ b/frontend/src/components/LoadingIndicator.tsx @@ -0,0 +1,9 @@ +const LoadingIndicator = () => { + return ( +
+
+
+ ); +}; + +export default LoadingIndicator; diff --git a/frontend/src/components/VideoPlayer.tsx b/frontend/src/components/VideoPlayer.tsx index 7684e24a..de8fc4bb 100644 --- a/frontend/src/components/VideoPlayer.tsx +++ b/frontend/src/components/VideoPlayer.tsx @@ -140,6 +140,8 @@ const VideoPlayer = ({ const [showHelpDialog, setShowHelpDialog] = useState(false); const [showInfoDialog, setShowInfoDialog] = useState(false); const [infoDialogContent, setInfoDialogContent] = useState(''); + const [isTheaterMode, setIsTheaterMode] = useState(false); + const [theaterModeKeyPressed, setTheaterModeKeyPressed] = useState(false); const questionmarkPressed = useKeyPress('?'); const mutePressed = useKeyPress('m'); @@ -151,6 +153,8 @@ const VideoPlayer = ({ const arrowRightPressed = useKeyPress('ArrowRight'); const arrowLeftPressed = useKeyPress('ArrowLeft'); const pPausedPressed = useKeyPress('p'); + const theaterModePressed = useKeyPress('t'); + const escapePressed = useKeyPress('Escape'); const videoId = video.youtube_id; const videoUrl = video.media_url; @@ -345,10 +349,42 @@ const VideoPlayer = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [questionmarkPressed]); + useEffect(() => { + if (embed) { + return; + } + + if (theaterModePressed && !theaterModeKeyPressed) { + setTheaterModeKeyPressed(true); + + const newTheaterMode = !isTheaterMode; + setIsTheaterMode(newTheaterMode); + + infoDialog(newTheaterMode ? 'Theater mode' : 'Normal mode'); + } else if (!theaterModePressed) { + setTheaterModeKeyPressed(false); + } + }, [theaterModePressed, isTheaterMode, theaterModeKeyPressed]); + + useEffect(() => { + if (embed) { + return; + } + + if (escapePressed && isTheaterMode) { + setIsTheaterMode(false); + + infoDialog('Normal mode'); + } + }, [escapePressed, isTheaterMode]); + return ( <> -
-
+
+