Add feed deletion and single video addition features
- Implemented DELETE /api/channels/<id> to remove channels and cleanup downloaded files - Added delete button to channels page with confirmation dialog - Added functionality to add single videos via URL - Updated navigation menu
This commit is contained in:
131
feed_parser.py
131
feed_parser.py
@@ -8,6 +8,7 @@ from datetime import datetime
|
||||
import feedparser
|
||||
from typing import Dict, List, Optional
|
||||
import re
|
||||
import yt_dlp
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
@@ -198,3 +199,133 @@ class YouTubeFeedParser:
|
||||
|
||||
db_session.commit()
|
||||
return channel
|
||||
|
||||
|
||||
def fetch_single_video(video_url: str) -> Optional[Dict]:
|
||||
"""Fetch metadata for a single YouTube video using yt-dlp.
|
||||
|
||||
Args:
|
||||
video_url: YouTube video URL
|
||||
|
||||
Returns:
|
||||
Dictionary with video metadata or None if fetch fails
|
||||
"""
|
||||
# Check if URL contains "shorts" - reject shorts
|
||||
if "shorts" in video_url.lower():
|
||||
return None
|
||||
|
||||
try:
|
||||
ydl_opts = {
|
||||
'quiet': True,
|
||||
'no_warnings': True,
|
||||
'extract_flat': False,
|
||||
}
|
||||
|
||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||
info = ydl.extract_info(video_url, download=False)
|
||||
|
||||
if not info:
|
||||
return None
|
||||
|
||||
# Extract video ID from URL
|
||||
video_id = info.get('id')
|
||||
if not video_id:
|
||||
return None
|
||||
|
||||
# Get channel info
|
||||
channel_id = info.get('channel_id')
|
||||
channel_name = info.get('channel') or info.get('uploader')
|
||||
channel_url = info.get('channel_url') or f"https://www.youtube.com/channel/{channel_id}"
|
||||
|
||||
# Get thumbnail - prefer maxresdefault, fall back to other qualities
|
||||
thumbnail_url = None
|
||||
if info.get('thumbnails'):
|
||||
# Get highest quality thumbnail
|
||||
thumbnail_url = info['thumbnails'][-1].get('url')
|
||||
elif info.get('thumbnail'):
|
||||
thumbnail_url = info.get('thumbnail')
|
||||
|
||||
# Parse upload date
|
||||
upload_date_str = info.get('upload_date')
|
||||
if upload_date_str:
|
||||
# Format: YYYYMMDD
|
||||
published_at = datetime.strptime(upload_date_str, '%Y%m%d')
|
||||
else:
|
||||
published_at = datetime.utcnow()
|
||||
|
||||
return {
|
||||
'video_id': video_id,
|
||||
'title': info.get('title'),
|
||||
'video_url': f"https://www.youtube.com/watch?v={video_id}",
|
||||
'description': info.get('description'),
|
||||
'thumbnail_url': thumbnail_url,
|
||||
'published_at': published_at,
|
||||
'channel_id': channel_id,
|
||||
'channel_name': channel_name,
|
||||
'channel_url': channel_url,
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error fetching video metadata: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def save_single_video_to_db(db_session: Session, video_data: Dict, user_id: int) -> VideoEntry:
|
||||
"""Save a single video to the database.
|
||||
|
||||
Args:
|
||||
db_session: SQLAlchemy database session
|
||||
video_data: Dictionary containing video metadata (from fetch_single_video)
|
||||
user_id: ID of the user adding this video
|
||||
|
||||
Returns:
|
||||
The VideoEntry model instance
|
||||
|
||||
This method:
|
||||
- Creates channel if it doesn't exist for this user
|
||||
- Creates video entry if it doesn't exist
|
||||
- Returns existing video if already in database
|
||||
"""
|
||||
# Get or create channel for this user
|
||||
channel = db_session.query(Channel).filter_by(
|
||||
user_id=user_id,
|
||||
channel_id=video_data['channel_id']
|
||||
).first()
|
||||
|
||||
if not channel:
|
||||
# Create new channel
|
||||
channel = Channel(
|
||||
user_id=user_id,
|
||||
channel_id=video_data['channel_id'],
|
||||
title=video_data['channel_name'],
|
||||
link=video_data['channel_url'],
|
||||
rss_url=f"https://www.youtube.com/feeds/videos.xml?channel_id={video_data['channel_id']}",
|
||||
last_fetched_at=datetime.utcnow()
|
||||
)
|
||||
db_session.add(channel)
|
||||
db_session.flush() # Get the channel ID
|
||||
|
||||
# Check if video already exists for this channel
|
||||
existing_video = db_session.query(VideoEntry).filter_by(
|
||||
channel_id=channel.id,
|
||||
video_id=video_data['video_id']
|
||||
).first()
|
||||
|
||||
if existing_video:
|
||||
return existing_video
|
||||
|
||||
# Create new video entry
|
||||
video = VideoEntry(
|
||||
channel_id=channel.id,
|
||||
video_id=video_data['video_id'],
|
||||
title=video_data['title'],
|
||||
video_url=video_data['video_url'],
|
||||
thumbnail_url=video_data.get('thumbnail_url'),
|
||||
description=video_data.get('description'),
|
||||
published_at=video_data['published_at'],
|
||||
created_at=datetime.utcnow()
|
||||
)
|
||||
db_session.add(video)
|
||||
db_session.commit()
|
||||
|
||||
return video
|
||||
|
||||
Reference in New Issue
Block a user