Files
TuneRipper/TuneRipper.Api/Services/VideoService.cs
T
Michal Vrba 3a5de85cd4 Update video details to fetch real title & thumbnail only
Refactored backend to extract actual YouTube title and thumbnail from HTML, removing simulated metadata fields (description, author, duration, views, upload date, tags). Simplified UI to display only title and thumbnail, and enhanced visuals with animated musical notes background. Removed related formatting and display logic for unused metadata.
2025-12-11 16:26:43 +01:00

135 lines
4.2 KiB
C#

using TuneRipper.Shared.Models;
using System.Text.RegularExpressions;
using System.Net.Http;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
namespace TuneRipper.Api.Services;
public interface IVideoService
{
Task<YouTubeVideoDetails?> GetVideoDetailsAsync(string url);
bool IsValidYouTubeUrl(string url);
string ExtractVideoId(string url);
}
public class VideoService : IVideoService
{
private readonly ILogger<VideoService> _logger;
private readonly HttpClient _httpClient;
public VideoService(ILogger<VideoService> logger)
{
_logger = logger;
_httpClient = new HttpClient();
}
public async Task<YouTubeVideoDetails?> GetVideoDetailsAsync(string url)
{
try
{
if (!IsValidYouTubeUrl(url))
{
_logger.LogWarning("Invalid YouTube URL: {Url}", url);
return null;
}
var videoId = ExtractVideoId(url);
var thumbnailUrl = $"https://img.youtube.com/vi/{videoId}/maxresdefault.jpg";
string? title = null;
// Fetch HTML and parse title
try
{
var html = await _httpClient.GetStringAsync(url);
// Try OpenGraph title first
var ogTitleMatch = Regex.Match(html, "<meta property='og:title' content='(.*?)'", RegexOptions.IgnoreCase);
if (!ogTitleMatch.Success)
{
ogTitleMatch = Regex.Match(html, "<meta property=\"og:title\" content=\"(.*?)\"", RegexOptions.IgnoreCase);
}
if (ogTitleMatch.Success)
{
title = ogTitleMatch.Groups[1].Value;
}
else
{
// Fallback to <title>
var titleMatch = Regex.Match(html, "<title>(.*?)</title>", RegexOptions.IgnoreCase);
if (titleMatch.Success)
{
title = titleMatch.Groups[1].Value.Replace(" - YouTube", "").Trim();
}
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to fetch or parse video title for {Url}", url);
title = $"YouTube Video {videoId}";
}
var videoDetails = new YouTubeVideoDetails
{
Id = videoId,
Title = title,
ThumbnailUrl = thumbnailUrl
};
_logger.LogInformation("Fetched image and title for video {VideoId}", videoId);
return videoDetails;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error fetching video details for URL: {Url}", url);
return null;
}
}
public bool IsValidYouTubeUrl(string url)
{
if (string.IsNullOrWhiteSpace(url))
return false;
var patterns = new[]
{
@"^https?://(www\.)?youtube\.com/watch\?v=[\w-]+",
@"^https?://(www\.)?youtu\.be/[\w-]+",
@"^https?://(www\.)?youtube\.com/embed/[\w-]+",
@"^https?://(www\.)?youtube\.com/v/[\w-]+"
};
return patterns.Any(pattern => Regex.IsMatch(url, pattern, RegexOptions.IgnoreCase));
}
public string ExtractVideoId(string url)
{
if (string.IsNullOrWhiteSpace(url))
return string.Empty;
try
{
var patterns = new Dictionary<string, string>
{
{ @"youtu\.be/([^?&]+)", "$1" },
{ @"youtube\.com/watch\?v=([^&]+)", "$1" },
{ @"youtube\.com/embed/([^?&]+)", "$1" },
{ @"youtube\.com/v/([^?&]+)", "$1" }
};
foreach (var pattern in patterns)
{
var match = Regex.Match(url, pattern.Key, RegexOptions.IgnoreCase);
if (match.Success)
{
return match.Groups[1].Value;
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error extracting video ID from URL: {Url}", url);
}
return string.Empty;
}
}