Add scheduled sync, disk space awareness, and subtitle downloads
- auto-sync daemon: background thread checks every hour and syncs followed channels for users with sync_interval_hours set (6/12/24h options) - disk stats: /api/stats now returns total/used/free/download bytes; Stats page shows a disk usage bar - subtitles: subtitle_langs setting (e.g. "en,sv") passed through all download paths; yt-dlp writes .srt files alongside the video - Settings page: sync interval dropdown + subtitle languages input Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -147,6 +147,7 @@ def _index_channel_task(channel_id: int, user_id: int):
|
||||
|
||||
if channel_auto:
|
||||
quality = user_settings.preferred_quality if user_settings else "best"
|
||||
subtitle_langs = (user_settings.subtitle_langs or "") if user_settings else ""
|
||||
from ..routers.downloads import _on_progress, _on_complete, _on_error
|
||||
for yt_id, vid_id in new_video_ids:
|
||||
existing_dl = db.query(Download).filter_by(
|
||||
@@ -159,7 +160,7 @@ def _index_channel_task(channel_id: int, user_id: int):
|
||||
import threading
|
||||
t = threading.Thread(
|
||||
target=ytdlp.start_download,
|
||||
args=(yt_id, dl.id, _on_progress, _on_complete, _on_error, quality),
|
||||
args=(yt_id, dl.id, _on_progress, _on_complete, _on_error, quality, subtitle_langs),
|
||||
daemon=True,
|
||||
)
|
||||
t.start()
|
||||
|
||||
@@ -127,6 +127,7 @@ def create_download(
|
||||
user_settings = db.query(UserSettings).filter_by(user_id=current_user.id).first()
|
||||
default_quality = user_settings.preferred_quality if user_settings else "best"
|
||||
quality = body.quality if body.quality in ytdlp.QUALITY_FORMATS else default_quality
|
||||
subtitle_langs = (user_settings.subtitle_langs or "") if user_settings else ""
|
||||
|
||||
_DL_SELECT = """
|
||||
SELECT d.id, d.status, d.progress_percent, d.resolution,
|
||||
@@ -155,7 +156,7 @@ def create_download(
|
||||
ytdlp.start_download,
|
||||
video.youtube_video_id, dl.id,
|
||||
_on_progress, _on_complete, _on_error,
|
||||
quality,
|
||||
quality, subtitle_langs,
|
||||
)
|
||||
|
||||
row = db.execute(text(_DL_SELECT), {"id": dl.id}).mappings().first()
|
||||
@@ -190,6 +191,11 @@ def _get_quality(db, user_id: int) -> str:
|
||||
return s.preferred_quality if s else "best"
|
||||
|
||||
|
||||
def _get_subtitle_langs(db, user_id: int) -> str:
|
||||
s = db.query(UserSettings).filter_by(user_id=user_id).first()
|
||||
return (s.subtitle_langs or "") if s else ""
|
||||
|
||||
|
||||
@router.post("/channel/{channel_id}", status_code=202)
|
||||
def download_channel_videos(
|
||||
channel_id: int,
|
||||
@@ -198,6 +204,7 @@ def download_channel_videos(
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
quality = _get_quality(db, current_user.id)
|
||||
subtitle_langs = _get_subtitle_langs(db, current_user.id)
|
||||
rows = db.execute(
|
||||
text("""
|
||||
SELECT v.id, v.youtube_video_id
|
||||
@@ -216,7 +223,7 @@ def download_channel_videos(
|
||||
db.flush()
|
||||
background_tasks.add_task(
|
||||
ytdlp.start_download, row["youtube_video_id"], dl.id,
|
||||
_on_progress, _on_complete, _on_error, quality,
|
||||
_on_progress, _on_complete, _on_error, quality, subtitle_langs,
|
||||
)
|
||||
count += 1
|
||||
db.commit()
|
||||
@@ -230,6 +237,7 @@ def download_following_videos(
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
quality = _get_quality(db, current_user.id)
|
||||
subtitle_langs = _get_subtitle_langs(db, current_user.id)
|
||||
rows = db.execute(
|
||||
text("""
|
||||
SELECT v.id, v.youtube_video_id
|
||||
|
||||
@@ -34,6 +34,8 @@ class SettingsOut(BaseModel):
|
||||
feed_weight_affinity: float = 5.0
|
||||
feed_weight_channel: float = 5.0
|
||||
use_oauth2: bool = False
|
||||
sync_interval_hours: int = 0
|
||||
subtitle_langs: str = ""
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
@@ -55,6 +57,8 @@ class SettingsPatch(BaseModel):
|
||||
feed_weight_affinity: Optional[float] = Field(None, ge=0.0, le=10.0)
|
||||
feed_weight_channel: Optional[float] = Field(None, ge=0.0, le=10.0)
|
||||
use_oauth2: Optional[bool] = None
|
||||
sync_interval_hours: Optional[int] = Field(None, ge=0, le=168)
|
||||
subtitle_langs: Optional[str] = None
|
||||
|
||||
|
||||
def _get_or_create(db: Session, user_id: int) -> UserSettings:
|
||||
@@ -123,6 +127,10 @@ def update_settings(
|
||||
if body.use_oauth2 is not None:
|
||||
s.use_oauth2 = body.use_oauth2
|
||||
ytdlp.set_oauth2(body.use_oauth2)
|
||||
if body.sync_interval_hours is not None:
|
||||
s.sync_interval_hours = body.sync_interval_hours
|
||||
if body.subtitle_langs is not None:
|
||||
s.subtitle_langs = body.subtitle_langs.strip()
|
||||
|
||||
db.commit()
|
||||
db.refresh(s)
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import text
|
||||
|
||||
from ..auth_utils import get_current_user
|
||||
from ..config import settings
|
||||
from ..database import get_db
|
||||
from ..models import User, UserTagAffinity
|
||||
|
||||
@@ -120,6 +124,15 @@ def get_stats(
|
||||
{"uid": uid},
|
||||
).mappings().first()
|
||||
|
||||
try:
|
||||
disk = shutil.disk_usage(settings.download_path)
|
||||
download_bytes = sum(
|
||||
e.stat().st_size for e in os.scandir(settings.download_path) if e.is_file()
|
||||
)
|
||||
except Exception:
|
||||
disk = None
|
||||
download_bytes = 0
|
||||
|
||||
return {
|
||||
"total_watched": totals["total_watched"] or 0,
|
||||
"total_watch_seconds": totals["total_watch_seconds"] or 0,
|
||||
@@ -141,6 +154,12 @@ def get_stats(
|
||||
"total_liked": liked_count["n"] or 0,
|
||||
"top_categories": [dict(r) for r in top_categories],
|
||||
"taste_profile": [dict(r) for r in taste_profile],
|
||||
"disk": {
|
||||
"total_bytes": disk.total if disk else None,
|
||||
"free_bytes": disk.free if disk else None,
|
||||
"used_bytes": disk.used if disk else None,
|
||||
"download_bytes": download_bytes,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user