Skip to content

Commit

Permalink
added download helper for .m3u and .m3u8 files
Browse files Browse the repository at this point in the history
  • Loading branch information
bezzad committed Oct 7, 2024
1 parent 5c4daef commit 5113e7d
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 11 deletions.
12 changes: 10 additions & 2 deletions src/Samples/Downloader.Sample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -168,17 +168,25 @@ private static async Task DownloadFile(DownloadItem downloadItem)
try
{
await Console.Out.WriteLineAsync("The url `" + downloadItem.Url + "` need to convert...");
VideoDownloaderHelper videoDownloaderHelper = new (CurrentDownloadConfiguration.RequestConfiguration.Proxy);
downloadItem.Url = await videoDownloaderHelper.GetUrlAsync(downloadItem.Url);
VideoDownloaderHelper helper = new (CurrentDownloadConfiguration.RequestConfiguration.Proxy);
downloadItem.Url = await helper.GetCookedUrlAsync(downloadItem.Url);
await Console.Out.WriteLineAsync("Redirect: " + downloadItem.Url);
await SaveDownloadItems(DownloadList);
return;
}
catch (Exception e)
{
Console.WriteLine(e);
return;
}
}
if (downloadItem.Url.Contains(".m3u", StringComparison.OrdinalIgnoreCase))
{
VideoDownloaderHelper helper = new (CurrentDownloadConfiguration.RequestConfiguration.Proxy);
await helper.DownloadM3U8File(downloadItem.Url,
downloadItem.FileName ?? Path.Combine(downloadItem.FolderPath, Path.GetRandomFileName(), ".mp4"));
return;
}

CurrentDownloadService = CreateDownloadService(CurrentDownloadConfiguration, Logger);

Expand Down
77 changes: 68 additions & 9 deletions src/Samples/Downloader.Sample/VideoDownloaderHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;

Expand Down Expand Up @@ -57,12 +57,17 @@ public VideoDownloaderHelper(IWebProxy proxy = null)
}

Client.DefaultRequestHeaders.Add("Referer", "https://publer.io/");
Client.DefaultRequestHeaders.Add("User-Agent",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:131.0) Gecko/20100101 Firefox/131.0");
Client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 Gecko/20100101 Firefox/131.0");
}

// Helper method to initiate the video processing job and wait for completion
public async Task<string> GetUrlAsync(string videoUrl)
/// <summary>
/// Helper method to initiate the video processing job and wait for completion from https://publer.io/
/// </summary>
/// <param name="videoUrl"></param>
/// <returns></returns>
/// <exception cref="Exception">Link is invalid</exception>
/// <exception cref="WebException">The given link video was removed from server</exception>
public async Task<string> GetCookedUrlAsync(string videoUrl)
{
// 1. Initiate the video processing job
var jobRequestPayload = new { url = videoUrl };
Expand All @@ -73,7 +78,7 @@ public async Task<string> GetUrlAsync(string videoUrl)
createJobResponse.EnsureSuccessStatusCode();

string resp = await createJobResponse.Content.ReadAsStringAsync();
var media = JsonConvert.DeserializeObject<MediaResponse>(resp);
MediaResponse media = JsonConvert.DeserializeObject<MediaResponse>(resp);

if (string.IsNullOrEmpty(media?.JobId))
{
Expand All @@ -89,12 +94,12 @@ public async Task<string> GetUrlAsync(string videoUrl)
jobStatusResponse.EnsureSuccessStatusCode();

string jobStatusResponseBody = await jobStatusResponse.Content.ReadAsStringAsync();
var jobStatus = JsonConvert.DeserializeObject<JobStatusResponse>(jobStatusResponseBody);
JobStatusResponse jobStatus = JsonConvert.DeserializeObject<JobStatusResponse>(jobStatusResponseBody);

if (jobStatus?.Status?.Equals("complete", StringComparison.OrdinalIgnoreCase) == true)
{
// Extract the download URL from the payload
var data = jobStatus.Payloads?.FirstOrDefault();
Payload data = jobStatus.Payloads?.FirstOrDefault();
if (!string.IsNullOrWhiteSpace(data?.Error))
{
throw new WebException(data.Error);
Expand All @@ -105,11 +110,65 @@ public async Task<string> GetUrlAsync(string videoUrl)
return data.path;
}

throw new WebException("Link is invalid: " + videoUrl);
throw new Exception("Link is invalid: " + videoUrl);
}

// Wait for a few seconds before retrying
await Task.Delay(3000); // 3 seconds delay
}
}

public async Task DownloadM3U8File(string m3U8Url, string outputFilePath)
{
// Step 1: Download the .m3u8 file
string m3U8Content = await Client.GetStringAsync(m3U8Url);

// Step 2: Parse the .m3u8 file and extract the media segment URLs
string[] segmentUrls = ParseM3U8(m3U8Content, m3U8Url);

// Step 3: Download and combine the segments into a single file
await DownloadAndCombineSegments(segmentUrls, outputFilePath);
}

// Helper method to parse the .m3u8 file
static string[] ParseM3U8(string m3U8Content, string baseUrl)
{
string[] lines = m3U8Content.Split(["\r\n", "\r", "\n"], StringSplitOptions.None);
List<string> segmentUrls = new();

foreach (string line in lines)
{
if (!line.StartsWith("#") && !string.IsNullOrWhiteSpace(line))
{
// If the line is not a comment, treat it as a media segment URL
string segmentUrl = line;

// Handle relative URLs
if (!Uri.IsWellFormedUriString(segmentUrl, UriKind.Absolute))
{
Uri baseUri = new(baseUrl);
Uri segmentUri = new(baseUri, segmentUrl);
segmentUrl = segmentUri.ToString();
}

segmentUrls.Add(segmentUrl);
}
}

return segmentUrls.ToArray();
}

// Helper method to download and combine segments
static async Task DownloadAndCombineSegments(string[] segmentUrls, string outputFilePath)
{
await using FileStream output = new(outputFilePath, FileMode.Create);
for (int i = 0; i < segmentUrls.Length; i++)
{
string segmentUrl = segmentUrls[i];
Console.WriteLine($"Downloading segment {i + 1} of {segmentUrls.Length}");

byte[] segmentData = await Client.GetByteArrayAsync(segmentUrl);
await output.WriteAsync(segmentData, 0, segmentData.Length);
}
}
}

0 comments on commit 5113e7d

Please sign in to comment.