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/"))