Update dependencies, #build

Changed:
- Update yt-dlp to latest release for upstream fixes
- Fix videos delete from playlist
- Index all sponsorblock categories
- Better missing items and loading indicator
This commit is contained in:
Simon 2025-08-11 15:12:33 +07:00
commit 26423e11c8
No known key found for this signature in database
GPG Key ID: 2C15AA5E89985DD4
16 changed files with 596 additions and 504 deletions

View File

@ -21,6 +21,7 @@ from common.src.helper import (
rand_sleep,
)
from common.src.ta_redis import RedisQueue
from common.src.urlparser import ParsedURLType
from download.src.queue import PendingList
from download.src.yt_dlp_base import YtWrap
from playlist.src.index import YoutubePlaylist
@ -347,7 +348,7 @@ class DownloadPostProcess(DownloaderBase):
self._auto_delete_watched(data)
@staticmethod
def _auto_delete_watched(data):
def _auto_delete_watched(data) -> None:
"""delete watched videos after x days"""
to_delete = IndexPaginate("ta_video", data).get_results()
if not to_delete:
@ -359,8 +360,20 @@ class DownloadPostProcess(DownloaderBase):
YoutubeVideo(youtube_id).delete_media_file()
print("add deleted to ignore list")
vids = [{"type": "video", "url": i["youtube_id"]} for i in to_delete]
PendingList(youtube_ids=vids).parse_url_list(status="ignore")
parsed_ids: list[ParsedURLType] = []
for video_item in to_delete:
vid_type = getattr(VideoTypeEnum, video_item["vid_type"].upper())
parsed_ids.append(
{
"type": "video",
"url": video_item["youtube_id"],
"vid_type": vid_type,
}
)
PendingList(youtube_ids=parsed_ids).parse_url_list(status="ignore")
def refresh_playlist(self) -> None:
"""match videos with playlists"""

View File

@ -1,10 +1,10 @@
-r requirements.txt
ipython==9.4.0
pre-commit==4.2.0
pre-commit==4.3.0
pylint-django==2.6.1
pylint==3.3.7
pylint==3.3.8
pytest-django==4.11.1
pytest==8.4.1
python-dotenv==1.1.1
requirementscheck==0.1.0
types-requests==2.32.4.20250611
types-requests==2.32.4.20250809

View File

@ -1,15 +1,15 @@
apprise==1.9.3
apprise==1.9.4
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.4
djangorestframework==3.16.0
Django==5.2.5
djangorestframework==3.16.1
drf-spectacular==0.28.0
Pillow==11.3.0
redis==6.2.0
redis==6.4.0
requests==2.32.4
ryd-client==0.0.6
uvicorn==0.35.0
whitenoise==6.9.0
yt-dlp[default]==2025.7.21
yt-dlp[default]==2025.8.11

View File

@ -48,11 +48,28 @@ class SponsorBlock:
def get_timestamps(self, youtube_id):
"""get timestamps from the API"""
url = f"{self.API}/skipSegments?videoID={youtube_id}"
url = f"{self.API}/skipSegments"
headers = {"User-Agent": self.user_agent}
categories = [
"sponsor",
"selfpromo",
"interaction",
"intro",
"outro",
"preview",
"music_offtopic",
"poi_highlight",
"filler",
]
params = {
"videoID": youtube_id,
"category": categories,
}
print(f"{youtube_id}: get sponsorblock timestamps")
try:
response = requests.get(url, headers=headers, timeout=10)
response = requests.get(
url, headers=headers, params=params, timeout=10
)
except (requests.ReadTimeout, requests.ConnectionError) as err:
print(f"{youtube_id}: sponsorblock API error: {str(err)}")
return False
@ -75,9 +92,17 @@ class SponsorBlock:
def _get_sponsor_dict(self, all_segments):
"""format and process response"""
_ = [i.pop("description", None) for i in all_segments]
has_unlocked = not any(i.get("locked") for i in all_segments)
# Set only sponsor to skip (retain legacy behaviour)
categories_skip = ["sponsor"]
for segment in all_segments:
segment_category = segment["category"]
if segment_category in categories_skip:
segment["actionType"] = "skip"
else:
segment["actionType"] = "none"
sponsor_dict = {
"last_refresh": self.last_refresh,
"has_unlocked": has_unlocked,

File diff suppressed because it is too large Load Diff

View File

@ -12,26 +12,26 @@
},
"dependencies": {
"dompurify": "^3.2.6",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-router-dom": "^7.6.3",
"zustand": "^5.0.6"
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-router-dom": "^7.8.0",
"zustand": "^5.0.7"
},
"devDependencies": {
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@typescript-eslint/eslint-plugin": "^8.32.1",
"@typescript-eslint/parser": "^8.32.1",
"@vitejs/plugin-react-swc": "^3.10.2",
"eslint": "^9.30.1",
"eslint-config-prettier": "^10.1.5",
"@types/react": "^19.1.9",
"@types/react-dom": "^19.1.7",
"@typescript-eslint/eslint-plugin": "^8.39.0",
"@typescript-eslint/parser": "^8.39.0",
"@vitejs/plugin-react-swc": "^4.0.0",
"eslint": "^9.33.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.20",
"globals": "^16.3.0",
"prettier": "3.6.2",
"typescript": "^5.8.3",
"typescript-eslint": "^8.32.1",
"vite": ">=7.0.0",
"vite-plugin-checker": "^0.9.3"
"typescript": "^5.9.2",
"typescript-eslint": "^8.39.0",
"vite": ">=7.1.1",
"vite-plugin-checker": "^0.10.2"
}
}

View File

@ -3,7 +3,7 @@ import APIClient from '../../functions/APIClient';
const deletePlaylist = async (playlistId: string, allVideos = false) => {
let params = '';
if (allVideos) {
params = '?delete-videos=true';
params = '?delete_videos=true';
}
return APIClient(`/api/playlist/${playlistId}/${params}`, {

View File

@ -7,6 +7,7 @@ import FormattedNumber from './FormattedNumber';
import Button from './Button';
import ChannelIcon from './ChannelIcon';
import ChannelBanner from './ChannelBanner';
import LoadingIndicator from './LoadingIndicator';
import { useUserConfigStore } from '../stores/UserConfigStore';
type ChannelListProps = {
@ -18,8 +19,11 @@ const ChannelList = ({ channelList, refreshChannelList }: ChannelListProps) => {
const { userConfig } = useUserConfigStore();
const viewStyle = userConfig.view_style_channel;
if (!channelList || channelList.length === 0) {
return <p>No channels found.</p>;
if (!channelList) {
return <LoadingIndicator />;
}
if (channelList.length === 0) {
return <h2>No channels found...</h2>;
}
return (

View File

@ -4,6 +4,7 @@ import updatePlaylistSubscription from '../api/actions/updatePlaylistSubscriptio
import formatDate from '../functions/formatDates';
import Button from './Button';
import PlaylistThumbnail from './PlaylistThumbnail';
import LoadingIndicator from './LoadingIndicator';
import { useUserConfigStore } from '../stores/UserConfigStore';
import { PlaylistType } from '../api/loader/loadPlaylistById';
@ -16,8 +17,11 @@ const PlaylistList = ({ playlistList, setRefresh }: PlaylistListProps) => {
const { userConfig } = useUserConfigStore();
const viewStyle = userConfig.view_style_playlist;
if (!playlistList || playlistList.length === 0) {
return <p>No playlists found.</p>;
if (!playlistList) {
return <LoadingIndicator />;
}
if (playlistList.length === 0) {
return <h2>No playlists found...</h2>;
}
return (

View File

@ -2,6 +2,7 @@ import { ViewStylesEnum, ViewStylesType } from '../configuration/constants/ViewS
import { VideoType } from '../pages/Home';
import VideoListItem from './VideoListItem';
import VideoListItemTable from './VideoListItemTable';
import LoadingIndicator from './LoadingIndicator';
type VideoListProps = {
videoList: VideoType[] | undefined;
@ -18,8 +19,11 @@ const VideoList = ({
showReorderButton = false,
refreshVideoList,
}: VideoListProps) => {
if (!videoList || videoList.length === 0) {
return <p>No videos found.</p>;
if (!videoList) {
return <LoadingIndicator />;
}
if (videoList.length === 0) {
return <h2>No videos found...</h2>;
}
if (viewStyle === ViewStylesEnum.Table) {

View File

@ -70,9 +70,11 @@ const handleTimeUpdate =
if (sponsorBlock && sponsorBlock.segments) {
sponsorBlock.segments.forEach((segment: SponsorBlockSegmentType) => {
const actionType = segment.actionType;
const doSkip = actionType == 'skip';
const [from, to] = segment.segment;
if (currentTime >= from && currentTime <= from + 0.3) {
if (doSkip && currentTime >= from && currentTime <= from + 0.3) {
videoTag.currentTarget.currentTime = to;
setSponsorSegmentSkipped?.((segments: SponsorSegmentsSkippedType) => {

View File

@ -168,17 +168,13 @@ const ChannelVideo = ({ videoType }: ChannelVideoProps) => {
<div className={`boxed-content ${gridView}`}>
<div className={`video-list ${viewStyle} ${gridViewGrid}`}>
{!hasVideos && (
<>
<h2>No videos found...</h2>
<p>
Try going to the <Link to={Routes.Downloads}>downloads page</Link> to start the
scan and download tasks.
</p>
</>
)}
<VideoList videoList={videoList} viewStyle={viewStyle} refreshVideoList={setRefresh} />
{!hasVideos && (
<p>
Try going to the <Link to={Routes.Downloads}>downloads page</Link> to start the scan
and download tasks.
</p>
)}
</div>
</div>
{pagination && (

View File

@ -59,7 +59,6 @@ const Channels = () => {
const channels = channelListResponseData?.data;
const pagination = channelListResponseData?.paginate;
const hasChannels = channels?.length !== 0;
const handleUserConfigUpdate = async (config: Partial<UserConfigType>) => {
const updatedUserConfig = await updateUserConfig(config);
@ -195,9 +194,7 @@ const Channels = () => {
</div>
<div className={`channel-list ${userConfig.view_style_channel}`}>
{!hasChannels && <h2>No channels found...</h2>}
{hasChannels && <ChannelList channelList={channels} refreshChannelList={setRefresh} />}
<ChannelList channelList={channels} refreshChannelList={setRefresh} />
</div>
{pagination && (

View File

@ -197,23 +197,17 @@ const Home = () => {
<div className={`boxed-content ${gridView}`}>
<div className={`video-list ${userConfig.view_style_home} ${gridViewGrid}`}>
<VideoList
videoList={videoList}
viewStyle={userConfig.view_style_home}
refreshVideoList={setRefreshVideoList}
/>
{!hasVideos && (
<>
<h2>No videos found...</h2>
<p>
If you've already added a channel or playlist, try going to the{' '}
<Link to={Routes.Downloads}>downloads page</Link> to start the scan and download
tasks.
</p>
</>
)}
{hasVideos && (
<VideoList
videoList={videoList}
viewStyle={userConfig.view_style_home}
refreshVideoList={setRefreshVideoList}
/>
<p>
If you've already added a channel or playlist, try going to the{' '}
<Link to={Routes.Downloads}>downloads page</Link> to start the scan and download
tasks.
</p>
)}
</div>
</div>

View File

@ -349,16 +349,21 @@ const Playlist = () => {
<div className={`boxed-content ${gridView}`}>
<div className={`video-list ${viewStyle} ${gridViewGrid}`}>
<VideoList
videoList={videos}
viewStyle={viewStyle}
playlistId={playlistId}
showReorderButton={isCustomPlaylist}
refreshVideoList={setRefresh}
/>
{videoInPlaylistCount === 0 && (
<>
<h2>No videos found...</h2>
{isCustomPlaylist && (
<p>
Try going to the <a href="{% url 'home' %}">home page</a> to add videos to this
playlist.
</p>
)}
{!isCustomPlaylist && (
<p>
Try going to the <Link to={Routes.Downloads}>downloads page</Link> to start the
@ -367,15 +372,6 @@ const Playlist = () => {
)}
</>
)}
{videoInPlaylistCount !== 0 && (
<VideoList
videoList={videos}
viewStyle={viewStyle}
playlistId={playlistId}
showReorderButton={isCustomPlaylist}
refreshVideoList={setRefresh}
/>
)}
</div>
</div>

View File

@ -38,8 +38,6 @@ const Playlists = () => {
const playlistList = playlistResponseData?.data;
const pagination = playlistResponseData?.paginate;
const hasPlaylists = playlistResponseData?.data?.length !== 0;
const viewStyle = userConfig.view_style_playlist;
const showSubedOnly = userConfig.show_subed_only;
@ -197,9 +195,7 @@ const Playlists = () => {
</div>
<div className={`playlist-list ${viewStyle}`}>
{!hasPlaylists && <h2>No playlists found...</h2>}
{hasPlaylists && <PlaylistList playlistList={playlistList} setRefresh={setRefresh} />}
<PlaylistList playlistList={playlistList} setRefresh={setRefresh} />
</div>
</div>