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:
commit
26423e11c8
|
|
@ -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"""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}`, {
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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 && (
|
||||
|
|
|
|||
|
|
@ -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 && (
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue