dotnettools / SharpGrabber

Download from YouTube, Vimeo, HLS (M3U8 files) and more with .NET and JavaScript - Library and desktop app for downloading high quality media
GNU Lesser General Public License v3.0
315 stars 46 forks source link

Need an example on how to save one m3u8 link into a local video file. #37

Closed zydjohnHotmail closed 3 years ago

zydjohnHotmail commented 3 years ago

Hello: I can use VLC Media Player to play the video with the following M3U8 link: https://bitdash-a.akamaihd.net/content/MI201109210084_1/m3u8s/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8 And I can save the stream as a video in my local C: drive. I want to know if I can do this with SharpGrabber. I didn’t see any example on how to save live stream into a video file in local hard drive. Please give me an example on how to do this, you can use the above M3U8 link. By the way, I am using Windows 10 (Version 21H1), and my IDE is Visual Studio 2019 (Version 16.10.4) Thanks,

javidsho commented 3 years ago

Hello there, You can do that for sure. I tried it myself just now, just to be sure. For demonstration:

  1. Run the SharpGrabber.Desktop project from Visual Studio or download the binaries from the releases section.
  2. Provide it with the URL of the m3u8 file.
  3. Click grab
  4. Select quality - or just download
  5. It will download and join all of the segments

To do that in your code, you not only need the SharpGrabber library, but also you'll need SharpGrabber.Converter which is not available as a NuGet package but you can easily make one. Hope that helps

zydjohnHotmail commented 3 years ago

Hello: Thank you very much for your help. But I don't quite understand your instructions. I will spend some time to understand it, the biggest issue for me is I don't know where I can provide the m3u8 url. The program.cs in SharpGrabber.Desktop project looks rather complicated, as it uses Avalonia. By the way, I want to know if I can make a C# console project or a windowsform project instead of one WPF project, as there are many restrictions on WPF project. I mean if I want to create Desktop project using either C# console project or winform project, and target .net 5.0, since I don't like WPF project, which I believe the Desktop project is using WPF project. Please advise!

javidsho commented 3 years ago

You're welcome. No problem and don't worry I'm sure you will understand it, because it's not complicated at all.

I wrote a small sample app that downloads all segment files of an HLS stream. The conversion is up to you. Either use SharpGrabber.Converter or just run ffmpeg.exe to join the segments.

class Program
{
    private static readonly HttpClient _http = new HttpClient();

    public static async Task Main(string[] args)
    {
        Console.WriteLine("Enter M3U8 URL: ");
        var uri = new Uri(Console.ReadLine());
        await Grab(uri);
    }

    private static async Task Grab(Uri uri)
    {
        var grabber = MultiGrabber.CreateDefault();
        var result = await grabber.Grab(uri);
        var resources = result.Resources.OfType<GrabbedStreamMetadata>().ToList();
        if (resources.Count == 0)
            throw new Exception("The specified URI is not an HLS playlist.");
        if (resources.Count > 1)
            await ChooseStream(resources);
        else
            await Download(resources.Single());
    }

    private static async Task ChooseStream(IList<GrabbedStreamMetadata> streams)
    {
        Console.WriteLine();
        for (var i = 1; i <= streams.Count; i++)
        {
            var stream = streams[i - 1];
            Console.WriteLine("{0}- {1}", i, stream.Name ?? stream.Resolution?.ToString() ?? stream.ResourceUri.AbsoluteUri);
        }
        Console.WriteLine();
        Console.Write("Choose a stream: ");
        var index = int.Parse(Console.ReadLine()) - 1;
        if (index < 0 || index >= streams.Count)
            throw new InvalidOperationException("Number out of range.");
        var targetStream = streams[index];
        await Grab(targetStream.ResourceUri);
    }

    private static async Task Download(GrabbedStreamMetadata metadata)
    {
        var stream = await metadata.Stream.ResolveAsync();
        Console.WriteLine("Downloading {0} segment(s)", stream.Segments.Count);
        Console.WriteLine("Total duration: {0}", stream.Length);
        var i = 0;
        foreach (var segment in stream.Segments)
        {
            Console.WriteLine("Downloading segment {0} from {1}...", i + 1, segment.Uri);
            using var response = await _http.GetAsync(segment.Uri);
            response.EnsureSuccessStatusCode();
            using var responseStream = await response.Content.ReadAsStreamAsync();
            await SaveSegment(i, responseStream);
            i++;
        }
    }

    private static async Task SaveSegment(int index, Stream responseStream)
    {
        using var fileStream = new FileStream($"segment-{index}.ts", FileMode.Create);
        await responseStream.CopyToAsync(fileStream);
    }
}