mirror of
https://github.com/tubearchivist/tubearchivist.git
synced 2025-07-02 23:31:10 +00:00
build improvements, #build
Changed: - bump dependencies - faster builds and better caching - auto update yt-dlp
This commit is contained in:
commit
4a64bdea33
2
.gitignore
vendored
2
.gitignore
vendored
@ -12,3 +12,5 @@ backend/.env
|
||||
|
||||
# JavaScript stuff
|
||||
node_modules
|
||||
|
||||
.editorconfig
|
||||
|
14
Dockerfile
14
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
|
||||
|
||||
|
@ -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 |
|
||||
|
||||
|
@ -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 = {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -47,7 +47,11 @@ const DownloadListItem = ({ download, setRefresh }: DownloadListItemProps) => {
|
||||
|
||||
{!download.channel_indexed && <span>{download.channel_name}</span>}
|
||||
|
||||
<a href={`https://www.youtube.com/watch?v=${download.youtube_id}`} target="_blank">
|
||||
<a
|
||||
href={`https://www.youtube.com/watch?v=${download.youtube_id}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<h3>{download.title}</h3>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -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 && <button onClick={() => handleUpdate(name, null)}>reset</button>}
|
||||
{loading && (
|
||||
<>
|
||||
<div className="lds-ring" style={{ color: 'var(--accent-font-dark)' }}>
|
||||
<div />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{loading && <LoadingIndicator />}
|
||||
{success && <span>✅</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
9
frontend/src/components/LoadingIndicator.tsx
Normal file
9
frontend/src/components/LoadingIndicator.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
const LoadingIndicator = () => {
|
||||
return (
|
||||
<div className="lds-ring" style={{ color: 'var(--accent-font-dark)' }}>
|
||||
<div />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoadingIndicator;
|
@ -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 (
|
||||
<>
|
||||
<div id="player" className={embed ? '' : 'player-wrapper'}>
|
||||
<div className={embed ? '' : 'video-main'}>
|
||||
<div
|
||||
id="player"
|
||||
className={embed ? '' : `player-wrapper ${isTheaterMode ? 'theater-mode' : ''}`}
|
||||
>
|
||||
<div className={embed ? '' : `video-main ${isTheaterMode ? 'theater-mode' : ''}`}>
|
||||
<video
|
||||
ref={videoRef}
|
||||
key={`${getApiUrl()}${videoUrl}`}
|
||||
@ -423,6 +459,18 @@ const VideoPlayer = ({
|
||||
<td>Toggle fullscreen</td>
|
||||
<td>f</td>
|
||||
</tr>
|
||||
{!embed && (
|
||||
<>
|
||||
<tr>
|
||||
<td>Toggle theater mode</td>
|
||||
<td>t</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Exit theater mode</td>
|
||||
<td>Esc</td>
|
||||
</tr>
|
||||
</>
|
||||
)}
|
||||
<tr>
|
||||
<td>Toggle subtitles (if available)</td>
|
||||
<td>c</td>
|
||||
@ -467,11 +515,19 @@ const VideoPlayer = ({
|
||||
<h4>
|
||||
This video doesn't have any sponsor segments added. To add a segment go to{' '}
|
||||
<u>
|
||||
<a href={`https://www.youtube.com/watch?v=${videoId}`}>this video on YouTube</a>
|
||||
<a
|
||||
href={`https://www.youtube.com/watch?v=${videoId}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
this video on YouTube
|
||||
</a>
|
||||
</u>{' '}
|
||||
and add a segment using the{' '}
|
||||
<u>
|
||||
<a href="https://sponsor.ajay.app/">SponsorBlock</a>
|
||||
<a href="https://sponsor.ajay.app/" target="_blank" rel="noopener noreferrer">
|
||||
SponsorBlock
|
||||
</a>
|
||||
</u>{' '}
|
||||
extension.
|
||||
</h4>
|
||||
@ -480,11 +536,19 @@ const VideoPlayer = ({
|
||||
<h4>
|
||||
This video has unlocked sponsor segments. Go to{' '}
|
||||
<u>
|
||||
<a href={`https://www.youtube.com/watch?v=${videoId}`}>this video on YouTube</a>
|
||||
<a
|
||||
href={`https://www.youtube.com/watch?v=${videoId}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
this video on YouTube
|
||||
</a>
|
||||
</u>{' '}
|
||||
and vote on the segments using the{' '}
|
||||
<u>
|
||||
<a href="https://sponsor.ajay.app/">SponsorBlock</a>
|
||||
<a href="https://sponsor.ajay.app/" target="_blank" rel="noopener noreferrer">
|
||||
SponsorBlock
|
||||
</a>
|
||||
</u>{' '}
|
||||
extension.
|
||||
</h4>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import iconUnseen from '/img/icon-unseen.svg';
|
||||
import iconSeen from '/img/icon-seen.svg';
|
||||
import { useEffect, useState } from 'react';
|
||||
import LoadingIndicator from './LoadingIndicator';
|
||||
|
||||
type WatchedCheckBoxProps = {
|
||||
watched: boolean;
|
||||
@ -32,9 +33,7 @@ const WatchedCheckBox = ({ watched, onClick, onDone }: WatchedCheckBoxProps) =>
|
||||
<>
|
||||
{loading && (
|
||||
<>
|
||||
<div className="lds-ring" style={{ color: 'var(--accent-font-dark)' }}>
|
||||
<div />
|
||||
</div>
|
||||
<LoadingIndicator />
|
||||
</>
|
||||
)}
|
||||
{!loading && watched && (
|
||||
|
@ -130,7 +130,11 @@ const ChannelAbout = () => {
|
||||
{channel.channel_active && (
|
||||
<p>
|
||||
Youtube:{' '}
|
||||
<a href={`https://www.youtube.com/channel/${channel.channel_id}`} target="_blank">
|
||||
<a
|
||||
href={`https://www.youtube.com/channel/${channel.channel_id}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Active
|
||||
</a>
|
||||
</p>
|
||||
@ -316,7 +320,7 @@ const ChannelAbout = () => {
|
||||
<div>
|
||||
<p>
|
||||
Overwrite{' '}
|
||||
<a href="https://sponsor.ajay.app/" target="_blank">
|
||||
<a href="https://sponsor.ajay.app/" target="_blank" rel="noopener noreferrer">
|
||||
SponsorBlock
|
||||
</a>
|
||||
</p>
|
||||
|
@ -97,16 +97,8 @@ const ChannelVideo = ({ videoType }: ChannelVideoProps) => {
|
||||
videoId,
|
||||
]);
|
||||
|
||||
if (!channel) {
|
||||
return (
|
||||
<div className="boxed-content">
|
||||
<br />
|
||||
<h2>Channel {channelId} not found!</h2>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
channel && (
|
||||
<>
|
||||
<title>{`TA | Channel: ${channel.channel_name}`}</title>
|
||||
<ScrollToTopOnNavigate />
|
||||
@ -180,8 +172,8 @@ const ChannelVideo = ({ videoType }: ChannelVideoProps) => {
|
||||
<>
|
||||
<h2>No videos found...</h2>
|
||||
<p>
|
||||
Try going to the <Link to={Routes.Downloads}>downloads page</Link> to start the scan
|
||||
and download tasks.
|
||||
Try going to the <Link to={Routes.Downloads}>downloads page</Link> to start the
|
||||
scan and download tasks.
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
@ -195,6 +187,7 @@ const ChannelVideo = ({ videoType }: ChannelVideoProps) => {
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -5,6 +5,7 @@ import Colours from '../configuration/colours/Colours';
|
||||
import Button from '../components/Button';
|
||||
import signIn from '../api/actions/signIn';
|
||||
import loadAuth from '../api/loader/loadAuth';
|
||||
import LoadingIndicator from '../components/LoadingIndicator';
|
||||
|
||||
const Login = () => {
|
||||
const navigate = useNavigate();
|
||||
@ -134,10 +135,7 @@ const Login = () => {
|
||||
{waitingForBackend && (
|
||||
<>
|
||||
<p>
|
||||
Waiting for backend{' '}
|
||||
<div className="lds-ring" style={{ color: 'var(--accent-font-dark)' }}>
|
||||
<div />
|
||||
</div>
|
||||
Waiting for backend <LoadingIndicator />
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
|
@ -180,6 +180,7 @@ const Playlist = () => {
|
||||
<a
|
||||
href={`https://www.youtube.com/playlist?list=${playlist.playlist_id}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Active
|
||||
</a>
|
||||
|
@ -779,7 +779,11 @@ const SettingsApplication = () => {
|
||||
<li>Make sure to contribute to this excellent project.</li>
|
||||
<li>
|
||||
More details{' '}
|
||||
<a target="_blank" href="https://returnyoutubedislike.com/">
|
||||
<a
|
||||
href="https://returnyoutubedislike.com/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
here
|
||||
</a>
|
||||
.
|
||||
@ -794,7 +798,11 @@ const SettingsApplication = () => {
|
||||
<li>Make sure to contribute to this excellent project.</li>
|
||||
<li>
|
||||
More details{' '}
|
||||
<a target="_blank" href="https://sponsor.ajay.app/">
|
||||
<a
|
||||
href="https://sponsor.ajay.app/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
here
|
||||
</a>
|
||||
.
|
||||
@ -831,7 +839,11 @@ const SettingsApplication = () => {
|
||||
<div>
|
||||
<p>
|
||||
Enable{' '}
|
||||
<a target="_blank" href="https://returnyoutubedislike.com/">
|
||||
<a
|
||||
href="https://returnyoutubedislike.com/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
returnyoutubedislike
|
||||
</a>
|
||||
</p>
|
||||
@ -846,7 +858,7 @@ const SettingsApplication = () => {
|
||||
<div>
|
||||
<p>
|
||||
Enable{' '}
|
||||
<a href="https://sponsor.ajay.app/" target="_blank">
|
||||
<a href="https://sponsor.ajay.app/" target="_blank" rel="noopener noreferrer">
|
||||
Sponsorblock
|
||||
</a>
|
||||
</p>
|
||||
|
@ -279,7 +279,11 @@ const Video = () => {
|
||||
{video.active && (
|
||||
<p>
|
||||
Youtube:{' '}
|
||||
<a href={`https://www.youtube.com/watch?v=${video.youtube_id}`} target="_blank">
|
||||
<a
|
||||
href={`https://www.youtube.com/watch?v=${video.youtube_id}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Active
|
||||
</a>
|
||||
</p>
|
||||
|
@ -458,6 +458,36 @@ button:hover {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.player-wrapper.theater-mode {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 1000;
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.video-main.theater-mode {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.video-main.theater-mode video {
|
||||
max-height: 95vh;
|
||||
max-width: 95vw;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.video-player {
|
||||
display: grid;
|
||||
align-content: space-evenly;
|
||||
|
Loading…
x
Reference in New Issue
Block a user