Youtube preview now use yt-dlp
This commit is contained in:
@@ -7,11 +7,11 @@ import re
|
|||||||
import logging
|
import logging
|
||||||
import asyncio
|
import asyncio
|
||||||
import aiohttp
|
import aiohttp
|
||||||
from pytubefix import YouTube
|
import yt_dlp
|
||||||
import simplematrixbotlib as botlib
|
import simplematrixbotlib as botlib
|
||||||
from youtube_title_parse import get_artist_title
|
from youtube_title_parse import get_artist_title
|
||||||
|
|
||||||
LYRICIST_API_URL = "https://lyrist.vercel.app/api/{}"
|
LYRICIST_API_URL = "https://lyrist.vercel.app/api/{}/{}"
|
||||||
|
|
||||||
|
|
||||||
def seconds_to_minutes_seconds(seconds):
|
def seconds_to_minutes_seconds(seconds):
|
||||||
@@ -43,18 +43,26 @@ async def fetch_lyrics(song, artist):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.get(LYRICIST_API_URL.format(song, artist)) as response:
|
url = LYRICIST_API_URL.format(artist, song)
|
||||||
|
logging.info(f"Fetching lyrics from: {url}")
|
||||||
|
async with session.get(url, timeout=aiohttp.ClientTimeout(total=10)) as response:
|
||||||
|
if response.status == 200:
|
||||||
data = await response.json()
|
data = await response.json()
|
||||||
return data.get("lyrics")
|
return data.get("lyrics")
|
||||||
|
else:
|
||||||
|
logging.warning(f"Lyrics API returned status {response.status}")
|
||||||
|
return None
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
logging.error("Timeout fetching lyrics")
|
||||||
|
return None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error fetching lyrics: {str(e)}")
|
logging.error(f"Error fetching lyrics: {str(e)}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def fetch_youtube_info(youtube_url):
|
async def fetch_youtube_info(youtube_url):
|
||||||
"""
|
"""
|
||||||
Asynchronously fetches information about a YouTube video.
|
Asynchronously fetches information about a YouTube video using yt-dlp.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
youtube_url (str): The URL of the YouTube video.
|
youtube_url (str): The URL of the YouTube video.
|
||||||
@@ -64,26 +72,71 @@ async def fetch_youtube_info(youtube_url):
|
|||||||
None if an error occurs during fetching.
|
None if an error occurs during fetching.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
video = YouTube(youtube_url)
|
logging.info(f"Fetching YouTube info for: {youtube_url}")
|
||||||
title = video.title
|
|
||||||
artist, song = get_artist_title(title)
|
# Configure yt-dlp options
|
||||||
|
ydl_opts = {
|
||||||
|
'quiet': True,
|
||||||
|
'no_warnings': True,
|
||||||
|
'extract_flat': False,
|
||||||
|
'skip_download': True,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run yt-dlp in thread pool to avoid blocking
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
|
||||||
|
def extract_info():
|
||||||
|
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||||
|
return ydl.extract_info(youtube_url, download=False)
|
||||||
|
|
||||||
|
info = await loop.run_in_executor(None, extract_info)
|
||||||
|
|
||||||
|
if not info:
|
||||||
|
logging.error("No info returned from yt-dlp")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Extract video information
|
||||||
|
title = info.get('title', 'Unknown Title')
|
||||||
|
description = info.get('description', 'No description available')
|
||||||
|
duration = info.get('duration', 0)
|
||||||
|
view_count = info.get('view_count', 0)
|
||||||
|
uploader = info.get('uploader', 'Unknown')
|
||||||
|
|
||||||
|
logging.info(f"Video title: {title}")
|
||||||
|
|
||||||
|
length = seconds_to_minutes_seconds(duration)
|
||||||
|
|
||||||
|
# Parse artist and song from title
|
||||||
|
artist, song = get_artist_title(title)
|
||||||
|
logging.info(f"Parsed artist: {artist}, song: {song}")
|
||||||
|
|
||||||
|
# Limit description length to avoid huge messages
|
||||||
|
if len(description) > 500:
|
||||||
|
description = description[:500] + "..."
|
||||||
|
|
||||||
description = video.description
|
|
||||||
length = seconds_to_minutes_seconds(video.length)
|
|
||||||
views = video.views
|
|
||||||
author = video.author
|
|
||||||
description_with_breaks = description.replace('\n', '<br>')
|
description_with_breaks = description.replace('\n', '<br>')
|
||||||
|
|
||||||
# Fetching lyrics
|
# Build basic info message
|
||||||
lyrics = await fetch_lyrics(song, artist)
|
info_message = f"""<strong>🎬🎝 Title:</strong> {title}<br><strong>Length:</strong> {length} | <strong>Views:</strong> {view_count:,} | <strong>Uploader:</strong> {uploader}<br><details><summary><strong>⤵︎Description⤵︎</strong></summary>{description_with_breaks}</details>"""
|
||||||
lyrics = lyrics.replace('\n', "<br>")
|
|
||||||
|
|
||||||
info_message = f"""<strong>🎬🎝 Title:</strong> {title} | <strong>Length</strong>: {length} minutes | <strong>Views</strong>: {views}\n<details><summary><strong>⤵︎Description⤵︎</strong></summary>{description_with_breaks}</details>"""
|
# Try to fetch lyrics if artist and song were parsed
|
||||||
|
if artist and song:
|
||||||
|
logging.info("Attempting to fetch lyrics...")
|
||||||
|
lyrics = await fetch_lyrics(song, artist)
|
||||||
if lyrics:
|
if lyrics:
|
||||||
|
lyrics = lyrics.replace('\n', "<br>")
|
||||||
|
# Limit lyrics length
|
||||||
|
if len(lyrics) > 3000:
|
||||||
|
lyrics = lyrics[:3000] + "<br>...(truncated)"
|
||||||
info_message += f"<br><details><summary><strong>🎵 Lyrics:</strong></summary><br>{lyrics}</details>"
|
info_message += f"<br><details><summary><strong>🎵 Lyrics:</strong></summary><br>{lyrics}</details>"
|
||||||
|
else:
|
||||||
|
logging.info("No lyrics found")
|
||||||
|
else:
|
||||||
|
logging.info("Could not parse artist/song from title, skipping lyrics")
|
||||||
|
|
||||||
return info_message
|
return info_message
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error fetching YouTube video information: {str(e)}")
|
logging.error(f"Error fetching YouTube video information: {str(e)}", exc_info=True)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -102,14 +155,20 @@ async def handle_command(room, message, bot, prefix, config):
|
|||||||
None
|
None
|
||||||
"""
|
"""
|
||||||
match = botlib.MessageMatch(room, message, bot, prefix)
|
match = botlib.MessageMatch(room, message, bot, prefix)
|
||||||
if match.is_not_from_this_bot() and re.search(r'youtube\.com/watch\?v=', message.body):
|
|
||||||
logging.info("YouTube link detected")
|
# Check if message contains a YouTube link
|
||||||
video_id_match = re.search(r'youtube\.com/watch\?v=([^\s]+)', message.body)
|
if match.is_not_from_this_bot() and re.search(r'(youtube\.com/watch\?v=|youtu\.be/)', message.body):
|
||||||
|
logging.info(f"YouTube link detected in message: {message.body}")
|
||||||
|
|
||||||
|
# Match both youtube.com and youtu.be formats
|
||||||
|
video_id_match = re.search(r'(?:youtube\.com/watch\?v=|youtu\.be/)([a-zA-Z0-9_-]{11})', message.body)
|
||||||
|
|
||||||
if video_id_match:
|
if video_id_match:
|
||||||
video_id = video_id_match.group(1)
|
video_id = video_id_match.group(1)
|
||||||
youtube_url = f"https://www.youtube.com/watch?v={video_id}"
|
youtube_url = f"https://www.youtube.com/watch?v={video_id}"
|
||||||
logging.info(f"Fetching information for YouTube video: {youtube_url}")
|
logging.info(f"Fetching information for YouTube video ID: {video_id}")
|
||||||
retry_count = 3
|
|
||||||
|
retry_count = 2 # Reduced retries since yt-dlp is more reliable
|
||||||
while retry_count > 0:
|
while retry_count > 0:
|
||||||
info_message = await fetch_youtube_info(youtube_url)
|
info_message = await fetch_youtube_info(youtube_url)
|
||||||
if info_message:
|
if info_message:
|
||||||
@@ -117,8 +176,12 @@ async def handle_command(room, message, bot, prefix, config):
|
|||||||
logging.info("Sent YouTube video information to the room")
|
logging.info("Sent YouTube video information to the room")
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
logging.info("Retrying...")
|
logging.warning(f"Failed to fetch info, retrying... ({retry_count-1} attempts left)")
|
||||||
retry_count -= 1
|
retry_count -= 1
|
||||||
await asyncio.sleep(1) # wait for 1 second before retrying
|
if retry_count > 0:
|
||||||
|
await asyncio.sleep(2) # wait for 2 seconds before retrying
|
||||||
else:
|
else:
|
||||||
logging.error("Failed to fetch YouTube video information after retries")
|
logging.error("Failed to fetch YouTube video information after all retries")
|
||||||
|
await bot.api.send_text_message(room.room_id, "Failed to fetch YouTube video information. The video may be unavailable or age-restricted.")
|
||||||
|
else:
|
||||||
|
logging.warning("Could not extract video ID from YouTube URL")
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
python-dotenv
|
python-dotenv
|
||||||
requests
|
requests
|
||||||
pytubefix
|
|
||||||
duckduckgo_search
|
duckduckgo_search
|
||||||
nio
|
nio
|
||||||
markdown2
|
markdown2
|
||||||
@@ -11,4 +10,4 @@ youtube_title_parse
|
|||||||
dnspython
|
dnspython
|
||||||
croniter
|
croniter
|
||||||
schedule
|
schedule
|
||||||
|
yt-dlp
|
||||||
|
Reference in New Issue
Block a user