Frontend caching reload fix, #build
Changed: - Fix frontend caching issue - Better published date handling and parsing with mapping fixes - bump yt-dlp
This commit is contained in:
commit
0e6149cc50
|
|
@ -434,6 +434,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"published": {
|
||||
"type": "date",
|
||||
"format": "epoch_second||strict_date_optional_time"
|
||||
},
|
||||
"vid_thumb_url": {
|
||||
"type": "keyword"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
"""middleware"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
|
||||
|
||||
class StartTimeMiddleware(MiddlewareMixin):
|
||||
"""add a start time header"""
|
||||
|
||||
def __call__(self, request):
|
||||
response = self.get_response(request)
|
||||
response["X-Start-Timestamp"] = settings.TA_START
|
||||
return response
|
||||
|
|
@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/3.2/ref/settings/
|
|||
"""
|
||||
|
||||
import hashlib
|
||||
from datetime import datetime
|
||||
from os import environ, path
|
||||
from pathlib import Path
|
||||
|
||||
|
|
@ -76,6 +77,7 @@ MIDDLEWARE = [
|
|||
"django.middleware.security.SecurityMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"corsheaders.middleware.CorsMiddleware",
|
||||
"config.middleware.StartTimeMiddleware",
|
||||
"whitenoise.middleware.WhiteNoiseMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
|
|
@ -220,10 +222,13 @@ CORS_ALLOW_CREDENTIALS = True
|
|||
CORS_ALLOW_HEADERS = list(default_headers) + [
|
||||
"mode",
|
||||
]
|
||||
CORS_EXPOSE_HEADERS = ["X-Start-Timestamp"]
|
||||
|
||||
|
||||
# TA application settings
|
||||
TA_UPSTREAM = "https://github.com/tubearchivist/tubearchivist"
|
||||
TA_VERSION = "v0.5.8-unstable"
|
||||
TA_START = str(int(datetime.now().timestamp()))
|
||||
|
||||
# API
|
||||
REST_FRAMEWORK = {
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ class PendingList(PendingIndex):
|
|||
if entry["type"] == "video":
|
||||
to_add = self._add_video(entry["url"], entry["vid_type"])
|
||||
if to_add:
|
||||
self.__notify_add(
|
||||
self._notify_add(
|
||||
item_type="video",
|
||||
name=to_add["title"],
|
||||
idx=idx,
|
||||
|
|
@ -218,7 +218,7 @@ class PendingList(PendingIndex):
|
|||
|
||||
total = len(video_results)
|
||||
for idx, video_data in enumerate(video_results, start=1):
|
||||
to_add = self.__parse_channel_video(
|
||||
to_add = self._parse_channel_video(
|
||||
video_data, vid_type, channel_handler.json_data
|
||||
)
|
||||
if self.task and self.task.is_stopped():
|
||||
|
|
@ -228,14 +228,14 @@ class PendingList(PendingIndex):
|
|||
continue
|
||||
|
||||
self.missing_videos.append(to_add)
|
||||
self.__notify_add(
|
||||
self._notify_add(
|
||||
item_type="channel",
|
||||
name=channel_handler.json_data["channel_name"],
|
||||
idx=idx,
|
||||
total=total,
|
||||
)
|
||||
|
||||
def __parse_channel_video(
|
||||
def _parse_channel_video(
|
||||
self, video_data, vid_type, channel_json
|
||||
) -> dict | None:
|
||||
"""parse video of channel"""
|
||||
|
|
@ -300,7 +300,7 @@ class PendingList(PendingIndex):
|
|||
continue
|
||||
|
||||
self.missing_videos.append(to_add)
|
||||
self.__notify_add(
|
||||
self._notify_add(
|
||||
item_type="playlist",
|
||||
name=playlist.json_data["playlist_name"],
|
||||
idx=idx,
|
||||
|
|
@ -368,11 +368,11 @@ class PendingList(PendingIndex):
|
|||
to_add = {
|
||||
"youtube_id": video_data["id"],
|
||||
"title": video_data["title"],
|
||||
"vid_thumb_url": self.__extract_thumb(video_data),
|
||||
"vid_thumb_url": self._extract_thumb(video_data),
|
||||
"duration": get_duration_str(video_data.get("duration", 0)),
|
||||
"published": self.__extract_published(video_data),
|
||||
"published": self._extract_published(video_data),
|
||||
"timestamp": int(datetime.now().timestamp()),
|
||||
"vid_type": self.__extract_vid_type(video_data),
|
||||
"vid_type": self._extract_vid_type(video_data),
|
||||
"channel_name": video_data["channel"],
|
||||
"channel_id": video_data["channel_id"],
|
||||
"channel_indexed": video_data["channel_id"] in self.all_channels,
|
||||
|
|
@ -380,7 +380,7 @@ class PendingList(PendingIndex):
|
|||
|
||||
return to_add
|
||||
|
||||
def __extract_thumb(self, video_data) -> str | None:
|
||||
def _extract_thumb(self, video_data) -> str | None:
|
||||
"""extract thumb"""
|
||||
if "thumbnail" in video_data:
|
||||
return video_data["thumbnail"]
|
||||
|
|
@ -390,7 +390,7 @@ class PendingList(PendingIndex):
|
|||
|
||||
return None
|
||||
|
||||
def __extract_published(self, video_data) -> str | int | None:
|
||||
def _extract_published(self, video_data) -> str | int | None:
|
||||
"""build published date or timestamp"""
|
||||
timestamp = video_data.get("timestamp")
|
||||
if timestamp:
|
||||
|
|
@ -405,7 +405,7 @@ class PendingList(PendingIndex):
|
|||
|
||||
return published
|
||||
|
||||
def __extract_vid_type(self, video_data) -> str:
|
||||
def _extract_vid_type(self, video_data) -> str:
|
||||
"""build vid type"""
|
||||
if (
|
||||
"vid_type" in video_data
|
||||
|
|
@ -464,7 +464,7 @@ class PendingList(PendingIndex):
|
|||
|
||||
return len(self.missing_videos)
|
||||
|
||||
def __notify_add(
|
||||
def _notify_add(
|
||||
self, item_type: str, name: str, idx: int, total: int
|
||||
) -> None:
|
||||
"""notify"""
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
apprise==1.9.4
|
||||
apprise==1.9.5
|
||||
celery==5.5.3
|
||||
django-auth-ldap==5.2.0
|
||||
django-celery-beat==2.8.1
|
||||
django-cors-headers==4.9.0
|
||||
Django==5.2.6
|
||||
Django==5.2.7
|
||||
djangorestframework==3.16.1
|
||||
drf-spectacular==0.28.0
|
||||
Pillow==11.3.0
|
||||
redis==6.4.0
|
||||
Pillow==12.0.0
|
||||
redis==7.0.0
|
||||
requests==2.32.5
|
||||
ryd-client==0.0.6
|
||||
uvicorn==0.37.0
|
||||
uvicorn==0.38.0
|
||||
whitenoise==6.11.0
|
||||
yt-dlp[default]==2025.9.26
|
||||
yt-dlp[default]==2025.10.22
|
||||
|
|
|
|||
|
|
@ -222,6 +222,10 @@ class YoutubeVideo(YouTubeItem, YoutubeSubtitle):
|
|||
return timestamp
|
||||
|
||||
upload_date = self.youtube_meta["upload_date"]
|
||||
if not upload_date:
|
||||
raise ValueError(
|
||||
f"Could not extract published date for {self.youtube_id}"
|
||||
)
|
||||
upload_date_time = datetime.strptime(upload_date, "%Y%m%d")
|
||||
published = upload_date_time.strftime("%Y-%m-%d")
|
||||
|
||||
|
|
|
|||
|
|
@ -50,17 +50,21 @@ server {
|
|||
root /app/static;
|
||||
index index.html;
|
||||
|
||||
location ~* .(?:css|js)$ {
|
||||
location ~* ^/(?!static/|cache/).*\.(?:css|js|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
|
||||
try_files $uri $uri/ /index.html =404;
|
||||
}
|
||||
|
||||
location = /index.html {
|
||||
add_header Cache-Control 'no-store';
|
||||
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
||||
add_header Pragma "no-cache";
|
||||
add_header Expires 0;
|
||||
expires 0;
|
||||
}
|
||||
|
||||
location / {
|
||||
add_header Cache-Control 'no-store';
|
||||
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
||||
add_header Pragma "no-cache";
|
||||
add_header Expires 0;
|
||||
try_files $uri $uri/ /index.html =404;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -4,6 +4,7 @@ import getFetchCredentials from '../configuration/getFetchCredentials';
|
|||
import logOut from '../api/actions/logOut';
|
||||
import getCookie from './getCookie';
|
||||
import Routes from '../configuration/routes/RouteList';
|
||||
import { useBackendStore } from '../stores/BackendStore';
|
||||
|
||||
export interface ApiClientOptions extends Omit<RequestInit, 'body'> {
|
||||
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
||||
|
|
@ -44,6 +45,12 @@ const APIClient = async <T>(
|
|||
...options,
|
||||
});
|
||||
|
||||
const backendTimestamp = response.headers.get('X-Start-Timestamp');
|
||||
if (backendTimestamp) {
|
||||
const { setStartTimestamp } = useBackendStore.getState();
|
||||
setStartTimestamp(backendTimestamp);
|
||||
}
|
||||
|
||||
// Handle common errors
|
||||
if (response.status === 400) {
|
||||
const data = await response.json();
|
||||
|
|
|
|||
|
|
@ -396,19 +396,31 @@ const SettingsApplication = () => {
|
|||
<span className="settings-current">
|
||||
{'bestvideo[height<=720]+bestaudio/best[height<=720]'}
|
||||
</span>
|
||||
: best audio and max video height of 720p.
|
||||
: <q>best</q> video and <q>best</q> audio (yt-dlp's choice), max video
|
||||
height of 720p.
|
||||
</li>
|
||||
<li>
|
||||
<span className="settings-current">
|
||||
{'bestvideo[height<=1080]+bestaudio/best[height<=1080]'}
|
||||
</span>
|
||||
: best audio and max video height of 1080p.
|
||||
: <q>best</q> video and <q>best</q> audio (yt-dlp's choice), max video
|
||||
height of 1080p.
|
||||
</li>
|
||||
<li>
|
||||
<span className="settings-current">
|
||||
{'bestvideo[height<=1080][vcodec*=avc1]+bestaudio[acodec*=mp4a]/mp4'}
|
||||
</span>
|
||||
: Max 1080p video height with iOS compatible video and audio codecs.
|
||||
: <q>best</q> universally iOS-compatible video (forced avc1) and{' '}
|
||||
<q>best</q> audio (forced mp4a), mp4 container, max video height of 1080p.
|
||||
</li>
|
||||
<li>
|
||||
<span className="settings-current">
|
||||
'bv*[vcodec~=av01]+ba[acodec~=mp4a]/bv*[vcodec~=av01]+ba/
|
||||
bv*[vcodec~=avc1]+ba[acodec~=mp4a]/bv*[vcodec~=avc1]+ba/bv*+ba/b'
|
||||
</span>
|
||||
: <q>best</q> iOS-compatible video (av01, compatible with iPhone 15 Pro
|
||||
and newer) and <q>best</q> audio (mp4a), no max video height, with
|
||||
fallback to avc1 and other formats if necessary.
|
||||
</li>
|
||||
<li>This can also be configured on a per channel basis.</li>
|
||||
<li>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
import { create } from 'zustand';
|
||||
|
||||
interface BackendState {
|
||||
startTimestamp: string | null;
|
||||
setStartTimestamp: (timestamp: string) => void;
|
||||
}
|
||||
|
||||
export const useBackendStore = create<BackendState>((set, get) => ({
|
||||
startTimestamp: null,
|
||||
setStartTimestamp: timestamp => {
|
||||
const prev = get().startTimestamp;
|
||||
if (prev && prev !== timestamp) {
|
||||
console.warn('Backend restart detected — reloading frontend...');
|
||||
window.location.reload();
|
||||
}
|
||||
set({ startTimestamp: timestamp });
|
||||
},
|
||||
}));
|
||||
|
|
@ -8,14 +8,22 @@ export default defineConfig({
|
|||
base: '/',
|
||||
server: {
|
||||
host: true,
|
||||
port: 3000, // This is the port which we will use in docker
|
||||
// Thanks @sergiomoura for the window fix
|
||||
// add the next lines if you're using windows and hot reload doesn't work
|
||||
port: 3000,
|
||||
watch: {
|
||||
usePolling: true,
|
||||
},
|
||||
},
|
||||
build: {
|
||||
sourcemap: true,
|
||||
outDir: 'dist',
|
||||
assetsDir: 'assets',
|
||||
manifest: true,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
entryFileNames: 'assets/[name].[hash].js',
|
||||
chunkFileNames: 'assets/[name].[hash].js',
|
||||
assetFileNames: 'assets/[name].[hash].[ext]',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
-r backend/requirements.txt
|
||||
ipython==9.5.0
|
||||
ipython==9.6.0
|
||||
pre-commit==4.3.0
|
||||
pylint-django==2.6.1
|
||||
pylint==3.3.8
|
||||
pylint==3.3.9
|
||||
pytest-django==4.11.1
|
||||
pytest==8.4.2
|
||||
python-dotenv==1.1.1
|
||||
python-dotenv==1.2.1
|
||||
requirementscheck==0.1.0
|
||||
types-requests==2.32.4.20250913
|
||||
|
|
|
|||
Loading…
Reference in New Issue