From 3a5de85cd457d49569680e8264cbc4a21b6ec4d9 Mon Sep 17 00:00:00 2001 From: Michal Vrba Date: Thu, 11 Dec 2025 16:26:43 +0100 Subject: [PATCH] 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. --- TuneRipper.Api/Services/VideoService.cs | 81 ++--- .../Models/YouTubeVideoDetails.cs | 6 - TuneRipper/Pages/Home.razor | 331 +++++++----------- 3 files changed, 166 insertions(+), 252 deletions(-) diff --git a/TuneRipper.Api/Services/VideoService.cs b/TuneRipper.Api/Services/VideoService.cs index c2cab3e..80c5998 100644 --- a/TuneRipper.Api/Services/VideoService.cs +++ b/TuneRipper.Api/Services/VideoService.cs @@ -1,5 +1,8 @@ using TuneRipper.Shared.Models; using System.Text.RegularExpressions; +using System.Net.Http; +using System.Threading.Tasks; +using System.Text.RegularExpressions; namespace TuneRipper.Api.Services; @@ -13,10 +16,12 @@ public interface IVideoService public class VideoService : IVideoService { private readonly ILogger _logger; + private readonly HttpClient _httpClient; public VideoService(ILogger logger) { _logger = logger; + _httpClient = new HttpClient(); } public async Task GetVideoDetailsAsync(string url) @@ -29,26 +34,48 @@ public class VideoService : IVideoService return null; } - // Simulate API call delay - await Task.Delay(Random.Shared.Next(500, 2000)); - var videoId = ExtractVideoId(url); - - // In production, use YouTube API or yt-dlp to get actual video details + 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, " + var titleMatch = Regex.Match(html, "(.*?)", 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 = GenerateSampleTitle(videoId), - Description = $"Video description for {videoId}. In production, this would be fetched from YouTube API.", - ThumbnailUrl = $"https://img.youtube.com/vi/{videoId}/maxresdefault.jpg", - Duration = TimeSpan.FromMinutes(Random.Shared.Next(2, 10)).Add(TimeSpan.FromSeconds(Random.Shared.Next(0, 59))), - Author = $"Channel_{Random.Shared.Next(1, 1000)}", - ViewCount = Random.Shared.Next(100, 5000000), - UploadDate = DateTime.UtcNow.AddDays(-Random.Shared.Next(1, 1000)), - Tags = GenerateSampleTags() + Title = title, + ThumbnailUrl = thumbnailUrl }; - _logger.LogInformation("Successfully fetched details for video {VideoId}", videoId); + _logger.LogInformation("Fetched image and title for video {VideoId}", videoId); return videoDetails; } catch (Exception ex) @@ -81,7 +108,6 @@ public class VideoService : IVideoService try { - // Different YouTube URL formats var patterns = new Dictionary { { @"youtu\.be/([^?&]+)", "$1" }, @@ -106,29 +132,4 @@ public class VideoService : IVideoService return string.Empty; } - - private static string GenerateSampleTitle(string videoId) - { - var sampleTitles = new[] - { - "Amazing Music Video", - "Best Song Ever", - "Epic Guitar Solo", - "Relaxing Piano Music", - "Dance Hit 2024", - "Acoustic Session", - "Live Performance", - "Official Music Video" - }; - - var randomTitle = sampleTitles[Random.Shared.Next(sampleTitles.Length)]; - return $"{randomTitle} - {videoId}"; - } - - private static List GenerateSampleTags() - { - var allTags = new[] { "music", "video", "song", "artist", "album", "live", "official", "acoustic", "cover", "remix" }; - var tagCount = Random.Shared.Next(3, 7); - return allTags.OrderBy(x => Random.Shared.Next()).Take(tagCount).ToList(); - } } \ No newline at end of file diff --git a/TuneRipper.Shared/Models/YouTubeVideoDetails.cs b/TuneRipper.Shared/Models/YouTubeVideoDetails.cs index 35969b2..94bf1cb 100644 --- a/TuneRipper.Shared/Models/YouTubeVideoDetails.cs +++ b/TuneRipper.Shared/Models/YouTubeVideoDetails.cs @@ -4,11 +4,5 @@ public class YouTubeVideoDetails { public string? Id { get; set; } public string? Title { get; set; } - public string? Description { get; set; } public string? ThumbnailUrl { get; set; } - public TimeSpan Duration { get; set; } - public string? Author { get; set; } - public int ViewCount { get; set; } - public DateTime UploadDate { get; set; } - public List Tags { get; set; } = new(); } \ No newline at end of file diff --git a/TuneRipper/Pages/Home.razor b/TuneRipper/Pages/Home.razor index fa2763c..c5d354e 100644 --- a/TuneRipper/Pages/Home.razor +++ b/TuneRipper/Pages/Home.razor @@ -6,166 +6,147 @@ TuneRipper - YouTube to MP3 Downloader -
-
- -

TuneRipper

-

Convert YouTube videos to MP3 instantly

+ +
+
+
♪
+
♫
+
♪
+
♬
+
♫
+
♪
+
♬
+
♫
+
♪
- -
-
- - -
- @* Video Information Section - Show as soon as we have details *@ - @if (currentVideoDetails != null) - { -
-

📹 Video Information

-
-
- @if (!string.IsNullOrEmpty(currentVideoDetails.ThumbnailUrl)) - { - Video thumbnail - @if (isProcessing) - { -
-
🎵
-
- } - } -
-
-

@currentVideoDetails.Title

-
- - 👤 - @currentVideoDetails.Author - - - ⏱️ - @FormatDuration(currentVideoDetails.Duration) - - - 👁️ - @FormatViewCount(currentVideoDetails.ViewCount) views - - - 📅 - @currentVideoDetails.UploadDate.ToString("MMM dd, yyyy") - -
- @if (currentVideoDetails.Tags?.Any() == true) - { -
- @foreach (var tag in currentVideoDetails.Tags.Take(5)) - { - @tag - } -
- } - @if (isProcessing) - { -
- ⚡ - Converting to MP3... -
- } -
-
-
- } +
+
+ +

TuneRipper

+

Convert YouTube videos to MP3 instantly

+
- @if (isProcessing) - { -
-
-
-
-
- @if (currentVideoDetails != null && progressPercentage > 15) +
+
+ + +
+ + @* Video Information Section - Show as soon as we have details *@ + @if (currentVideoDetails != null) + { +
+

📹 Video Information

+
+
+ @if (!string.IsNullOrEmpty(currentVideoDetails.ThumbnailUrl)) + { + Video thumbnail + @if (isProcessing) + { +
+
🎵
+
+ } + } +
+
+

@currentVideoDetails.Title

+
+
+
+ } + + @if (isProcessing) + { +
+
+
+
+
+ @if (currentVideoDetails != null && progressPercentage > 15) + { + @progressMessage + (@progressPercentage%) + } + else + { + @progressMessage (@progressPercentage%) + } +
+
+ } + + @if (!string.IsNullOrWhiteSpace(outputMessage) && !isProcessing) + { +
+

@outputMessage

+
+ } + + @* API Error Message *@ + @if (!string.IsNullOrWhiteSpace(apiErrorMessage) && !isProcessing) + { +
+

❌ @apiErrorMessage

+
+ } +
+ + @if (downloadLinks.Any()) + { + } - @if (!string.IsNullOrWhiteSpace(outputMessage) && !isProcessing) - { -
-

@outputMessage

+
+
+ ⚡ + Fast Conversion
- } - - @* API Error Message *@ - @if (!string.IsNullOrWhiteSpace(apiErrorMessage) && !isProcessing) - { -
-

❌ @apiErrorMessage

+
+ 🎧 + High Quality
- } -
- - @if (downloadLinks.Any()) - { - @@ -384,40 +365,6 @@ word-wrap: break-word; } - .video-meta { - display: flex; - flex-wrap: wrap; - gap: 16px; - margin-bottom: 12px; - } - - .meta-item { - display: flex; - align-items: center; - gap: 6px; - font-size: 13px; - color: #666; - } - - .meta-icon { - font-size: 14px; - } - - .video-tags { - display: flex; - flex-wrap: wrap; - gap: 6px; - } - - .tag { - background: #f0f0f0; - color: #666; - font-size: 11px; - padding: 4px 8px; - border-radius: 12px; - font-weight: 500; - } - .error-message { margin-top: 15px; padding: 12px; @@ -696,34 +643,6 @@ } } - private string FormatDuration(TimeSpan duration) - { - if (duration.TotalHours >= 1) - { - return duration.ToString(@"h\:mm\:ss"); - } - else - { - return duration.ToString(@"m\:ss"); - } - } - - private string FormatViewCount(int viewCount) - { - if (viewCount >= 1_000_000) - { - return $"{viewCount / 1_000_000.0:F1}M"; - } - else if (viewCount >= 1_000) - { - return $"{viewCount / 1_000.0:F1}K"; - } - else - { - return viewCount.ToString("N0"); - } - } - private string ExtractTitleFromUrl(string url) { if (url.Contains("youtube.com/watch?v=") || url.Contains("youtu.be/"))