1

I am working on a project that extracts YouTube videos' audio and saves them to your computer. To do this, I used a library from GitHub called YouTubeExtractor.

I am using a backgroundworker in order to make the UI usable while the file is being downloaded. This is the code I have so far.

public partial class MainWindow : Window
{
    private readonly BackgroundWorker worker = new BackgroundWorker();
    public MainWindow()
    {
        InitializeComponent();
        worker.DoWork += worker_DoWork;
        worker.WorkerReportsProgress = true;
        worker.WorkerSupportsCancellation = true;
    }

    private void downloadButton_Click(object sender, RoutedEventArgs e)
    {
        worker.RunWorkerAsync();
    }
    string link;
    double percentage;
    private void worker_DoWork(object sender, DoWorkEventArgs e)
    {
        this.Dispatcher.Invoke((Action)(() =>
        {
            link = videoURL.Text;
        }));


        /*
         * Get the available video formats.
         * We'll work with them in the video and audio download examples.
         */
        IEnumerable<VideoInfo> videoInfos = DownloadUrlResolver.GetDownloadUrls(link);

        /*
         * We want the first extractable video with the highest audio quality.
         */
        VideoInfo video = videoInfos
            .Where(info => info.CanExtractAudio)
            .OrderByDescending(info => info.AudioBitrate)
            .First();

        /*
         * If the video has a decrypted signature, decipher it
         */
        if (video.RequiresDecryption)
        {
            DownloadUrlResolver.DecryptDownloadUrl(video);
        }

        /*
         * Create the audio downloader.
         * The first argument is the video where the audio should be extracted from.
         * The second argument is the path to save the audio file.
         */
        var audioDownloader = new AudioDownloader(video, System.IO.Path.Combine("C:/Downloads", video.Title + video.AudioExtension));

        // Register the progress events. We treat the download progress as 85% of the progress and the extraction progress only as 15% of the progress,
        // because the download will take much longer than the audio extraction.
        audioDownloader.DownloadProgressChanged += (send, args) => Console.WriteLine(args.ProgressPercentage * 0.85);
        audioDownloader.AudioExtractionProgressChanged += (send, args) => Console.WriteLine(85 + args.ProgressPercentage * 0.15);
        /*
         * Execute the audio downloader.
         * For GUI applications note, that this method runs synchronously.
         */
        audioDownloader.Execute();
    }
}

}

The problem I have is that I want to display this

      audioDownloader.DownloadProgressChanged += (send, args) => Console.WriteLine(args.ProgressPercentage * 0.85);
      audioDownloader.AudioExtractionProgressChanged += (send, args) => Console.WriteLine(85 + args.ProgressPercentage * 0.15);

In a UI element like a label or a progressbar instead of Console.WriteLine

Whenever I do label1.Text = (85 + args.ProgressPercentage * 0.15); It throws me a an error like

" The calling thread cannot access this object because a different thread owns it."

I know you can do solve this with a delegate, I need a clear instruction on how so.

Thank you.

aybe
  • 15,516
  • 9
  • 57
  • 105
user2572329
  • 35
  • 1
  • 8
  • You already know how to solve this. `this.Dispatcher.Invoke`. – SLaks Oct 19 '14 at 02:51
  • @SLaks Yes, but when I try to solve it with that, it is giving me more errors, I don't know if I am implementing this wrong or... However, the string called "link" is working flawlessly because I am getting text FROM the UI, to display ON the UI doesn't work. – user2572329 Oct 19 '14 at 02:59
  • **Read** the errors, and show us what you tried. Also, you aren't getting any good out of using BackgroundWorker. – SLaks Oct 19 '14 at 03:05
  • You should use Tasks (http://msdn.microsoft.com/en-us/library/system.threading.tasks.task(v=vs.110).aspx) and why not using the existing infrastructure to report progress (http://msdn.microsoft.com/en-us/library/system.windows.shell.taskbariteminfo(v=vs.110).aspx) ? – aybe Oct 19 '14 at 03:09
  • @SLaks Why do you think I shouldn't use a BackgroundWorker? – user2572329 Oct 19 '14 at 03:10
  • Isn't BackgroundWorker superseded by Tasks now ? – aybe Oct 19 '14 at 03:11

1 Answers1

1

Here's a modern approach for doing this using Tasks and async / await keywords

Plus the usage of Dispatcher.BeginInvoke for updating your UI.

enter image description here

Code:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using YoutubeExtractor;

namespace WpfApplication1
{
    public partial class MainWindow
    {
        public MainWindow() {
            InitializeComponent();
        }

        private async void Button_Click(object sender, RoutedEventArgs e) {
            string videoUrl = @"https://www.youtube.com/watch?v=5aXsrYI3S6g";
            await DownloadVideoAsync(videoUrl);
        }

        private Task DownloadVideoAsync(string url) {
            return Task.Run(() => {
                IEnumerable<VideoInfo> videoInfos = DownloadUrlResolver.GetDownloadUrls(url);
                VideoInfo videoInfo = videoInfos.FirstOrDefault();
                if (videoInfo != null) {
                    if (videoInfo.RequiresDecryption) {
                        DownloadUrlResolver.DecryptDownloadUrl(videoInfo);
                    }

                    string savePath =
                        Path.Combine(
                            Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
                            Path.ChangeExtension("myVideo", videoInfo.VideoExtension));
                    var downloader = new VideoDownloader(videoInfo, savePath);
                    downloader.DownloadProgressChanged += downloader_DownloadProgressChanged;
                    downloader.Execute();
                }
            });
        }

        private void downloader_DownloadProgressChanged(object sender, ProgressEventArgs e) {
            Dispatcher.BeginInvoke((Action) (() => {
                double progressPercentage = e.ProgressPercentage;
                ProgressBar1.Value = progressPercentage;
                TextBox1.Text = string.Format("{0:F} %", progressPercentage);
            }));
        }
    }
}

XAML:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"
        Width="525"
        Height="350">
    <Grid>

        <StackPanel>
            <Button Click="Button_Click" Content="Download" />
            <ProgressBar x:Name="ProgressBar1"
                         Height="20"
                         Maximum="100" />
            <TextBox x:Name="TextBox1" />
        </StackPanel>
    </Grid>
</Window>
aybe
  • 15,516
  • 9
  • 57
  • 105
  • Works perfectly, I adjusted the code so that only the audio is downloading now. Do you have any ideas on how I can increase the quality of the audio? Thanks. – user2572329 Oct 19 '14 at 04:23
  • From what I understand in your API not all videos' audio can be extracted, especially the HD one (on the video I've put in the code). What you can do, grab the HD one then use FFMPEG to separate stream with something like `ffmpeg -i video.mp4 -vn acodec copy audio.aac`, you will get the best quality then (see http://askubuntu.com/questions/221026/how-can-i-batch-extract-audio-from-mp4-files-with-ffmpeg-without-decompression for details). – aybe Oct 19 '14 at 07:27
  • Other than that, enhancing an audio file could be done with something like that : http://sound.stackexchange.com/questions/28300/how-to-increase-recorded-audio-quality-using-audacity and http://www.fxsound.com/dfx/. Personally I'd opt for the FFMPEG solution since it's free and fast to implement with Process.Start (http://msdn.microsoft.com/en-us/library/system.diagnostics.process.start(v=vs.110).aspx); also it is ideal because you get the best audio from Youtube directly instead of 'enhancers'. Nothing prevents you to do both things, though :D – aybe Oct 19 '14 at 07:31
  • Do you have Skype? I really need some help with understanding these procedures more clearly. I am really interested in the FFmpeg solution but need more clarification. Thanks. – user2572329 Oct 19 '14 at 11:35
  • No sorry I don't. But you can ask another question about it, notify me here and I'll answer it. – aybe Oct 19 '14 at 11:48
  • I just need a head start on how I can implement FFmpeg on to my current project. Where do I start? What libraries? etc etc. – user2572329 Oct 19 '14 at 12:12
  • Grab it here http://ffmpeg.zeranoe.com/builds/, copy all files to your project then set their Build Action = Content, Copy to output = Copy, then use Process.Start to start it (there is CreateNoWindow property to hide it). – aybe Oct 19 '14 at 12:29