diff --git a/backend/models.py b/backend/models.py index e4edd2b..2389a5b 100644 --- a/backend/models.py +++ b/backend/models.py @@ -60,6 +60,7 @@ class Video(Base): tags = Column(Text) # JSON array string category = Column(String) chapters = Column(Text) # JSON array of {start_time, end_time, title} + view_count = Column(Integer) class UserVideo(Base): diff --git a/backend/routers/videos.py b/backend/routers/videos.py index 3e3054b..85e7aac 100644 --- a/backend/routers/videos.py +++ b/backend/routers/videos.py @@ -65,6 +65,7 @@ class VideoDetail(BaseModel): download_resolution: Optional[str] = None local_file_url: Optional[str] = None is_recommended: bool = False + view_count: Optional[int] = None model_config = {"from_attributes": True} @@ -421,7 +422,7 @@ def surprise_me( _VIDEO_SELECT = """ SELECT v.id, v.youtube_video_id, v.title, v.description, v.thumbnail_url, - v.duration_seconds, v.published_at, v.tags, v.category, + v.duration_seconds, v.published_at, v.tags, v.category, v.view_count, c.id AS channel_id, c.name AS channel_name, c.youtube_channel_id AS channel_youtube_id, COALESCE(uv.watched, 0) AS watched, COALESCE(uv.watch_progress_seconds, 0) AS watch_progress_seconds, diff --git a/backend/services/ytdlp.py b/backend/services/ytdlp.py index 7f6db03..b0a53bd 100644 --- a/backend/services/ytdlp.py +++ b/backend/services/ytdlp.py @@ -74,6 +74,7 @@ def _normalize_video(info: dict) -> dict: "tags": json.dumps(info.get("tags") or []), "category": info.get("category") or (info.get("categories") or [None])[0], "chapters": json.dumps(chapters) if chapters else None, + "view_count": info.get("view_count"), "channel": { "youtube_channel_id": info.get("channel_id"), "name": info.get("channel") or info.get("uploader", ""), diff --git a/frontend/src/pages/Watch.jsx b/frontend/src/pages/Watch.jsx index e12d703..116de0f 100644 --- a/frontend/src/pages/Watch.jsx +++ b/frontend/src/pages/Watch.jsx @@ -27,6 +27,14 @@ function formatDate(s) { return new Date(s).toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" }); } +function formatViews(n) { + if (!n) return null; + if (n >= 1_000_000_000) return `${(n / 1_000_000_000).toFixed(1)}B views`; + if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(n >= 100_000_000 ? 0 : 1)}M views`; + if (n >= 1_000) return `${Math.round(n / 1_000)}K views`; + return `${n} views`; +} + function formatSubs(n) { if (!n) return null; if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(n >= 10_000_000 ? 0 : 1)}M`; @@ -885,6 +893,7 @@ export default function Watch() {