private async Task StartDownloadAsync(DownloadItem downloadInfo) {
            downloadInfo.Status = DownloadStatus.Initializing;

            // Query the download URL for the right file.
            var VTask = GetDownloadUrlsAsync(downloadInfo.Request.DownloadUrl);
            var VideoList = await VTask.ConfigureAwait(false);
            if (VideoList == null) {
                downloadInfo.Status = DownloadStatus.Failed;
                RaiseCallback(downloadInfo);
                return;
            }

            if (!downloadInfo.UpgradeAudio || downloadInfo.Request.FileName == null || !File.Exists(Settings.NaturalGroundingFolder + downloadInfo.Request.FileName)) {
                downloadInfo.UpgradeAudio = false;
                // Get the highest resolution format.

                BestFormatInfo BestFile = SelectBestFormat(VideoList);

                if (BestFile.BestVideo.AdaptiveType == AdaptiveType.None) {
                    // Download single file.
                    downloadInfo.Files.Add(new DownloadItem.FileProgress() {
                        Source = BestFile.BestVideo,
                        Destination = downloadInfo.Destination + BestFile.BestVideo.VideoExtension
                    });
                } else {
                    // Download audio and video separately.
                    downloadInfo.Files.Add(new DownloadItem.FileProgress() {
                        Source = BestFile.BestVideo,
                        Destination = downloadInfo.Destination + GetCodecExtension(BestFile.BestVideo.VideoType)
                    });
                    if (BestFile.BestAudio != null) {
                        downloadInfo.Files.Add(new DownloadItem.FileProgress() {
                            Source = BestFile.BestAudio,
                            Destination = downloadInfo.Destination + GetAudioExtension(BestFile.BestAudio.AudioType)
                        });
                    }
                }
            } else if (File.Exists(Settings.NaturalGroundingFolder + downloadInfo.Request.FileName)) {
                // Keep local video and upgrade audio.
                VideoInfo AudioFile = SelectBestAudio(from v in VideoList
                                                      where (v.CanExtractAudio || v.AdaptiveType == AdaptiveType.Audio)
                                                      orderby v.AudioBitrate descending
                                                      select v);
                downloadInfo.Files.Add(new DownloadItem.FileProgress() {
                    Source = AudioFile,
                    Destination = downloadInfo.Destination + GetAudioExtension(AudioFile.AudioType)
                });
            }

            await DownloadFilesAsync(downloadInfo, downloadInfo.Callback).ConfigureAwait(false);
        }
        /// <summary>
        /// Downloads specified video from YouTube.
        /// </summary>
        /// <param name="video">The video to download.</param>
        /// <param name="queuePos">The position in the queue to auto-play, or -1.</param>
        /// <param name="upgradeAudio">If true, only the audio will be downloaded and it will be merged with the local video file.</param>
        /// <param name="callback">The method to call once download is completed.</param>
        public async Task DownloadVideoAsync(Media video, int queuePos, bool upgradeAudio, EventHandler<DownloadCompletedEventArgs> callback) {
            if (video == null || string.IsNullOrEmpty(video.DownloadUrl))
                throw new ArgumentException("Video object is null or doesn't contain a valid YouTube URL.");

            if (IsDownloadDuplicate(video))
                return;

            // Store in the Temp folder.
            DefaultMediaPath PathCalc = new DefaultMediaPath(Settings.TempFilesPath.Substring(Settings.NaturalGroundingFolder.Length));
            string Destination = Settings.NaturalGroundingFolder + PathCalc.GetDefaultFileName(video.Artist, video.Title, null, (MediaType)video.MediaTypeId);
            string DownloadDesc = Path.GetFileName(Destination);
            Directory.CreateDirectory(Settings.TempFilesPath);

            // Add DownloadItem right away before doing any async work.
            DownloadItem DownloadInfo = new DownloadItem(video, Destination, DownloadDesc, queuePos, upgradeAudio, callback);
            Application.Current.Dispatcher.Invoke(() => downloadsList.Insert(0, DownloadInfo));

            // Notify UI of new download to show window.
            if (DownloadAdded != null)
                DownloadAdded(this, new EventArgs());

            if (downloadsList.Where(d => d.Status == DownloadStatus.Downloading || d.Status == DownloadStatus.Initializing).Count() < Settings.SimultaneousDownloads)
                await StartDownloadAsync(DownloadInfo).ConfigureAwait(false);
        }
 public DownloadCompletedEventArgs(DownloadItem downloadInfo) {
     this.DownloadInfo = downloadInfo;
 }
 private void RaiseCallback(DownloadItem downloadInfo) {
     if (downloadInfo.Callback != null)
         Application.Current.Dispatcher.Invoke(() => downloadInfo.Callback(this, new DownloadCompletedEventArgs(downloadInfo)));
 }
        private void DownloadCanceled(DownloadItem downloadInfo) {
            if (downloadInfo.Status == DownloadStatus.Canceled)
                downloadInfo.Status = DownloadStatus.Canceled;
            else if (downloadInfo.Status == DownloadStatus.Failed)
                downloadInfo.Status = DownloadStatus.Failed;
            Thread.Sleep(100);

            // Delete partially-downloaded files.
            foreach (var item in downloadInfo.Files) {
                File.Delete(item.Destination);
            }
        }
        private async Task DownloadCompletedAsync(DownloadItem downloadInfo) {
            // Separate file extension.
            string Destination = downloadInfo.Files[0].Destination;
            string DestinationExt = Path.GetExtension(Destination);
            Destination = Destination.Substring(0, Destination.Length - Path.GetExtension(Destination).Length);
            Media video = downloadInfo.Request;

            if (downloadInfo.Files.Count > 1) {
                VideoType File1Type = downloadInfo.Files[0].Source.VideoType;
                AudioType File2Type = downloadInfo.Files[1].Source.AudioType;
                string File1Ext = GetCodecExtension(File1Type);
                string File2Ext = GetAudioExtension(File2Type);
                DestinationExt = GetFinalExtension(File1Type, File2Type);

                // Make sure output file doesn't already exist.
                File.Delete(Destination + DestinationExt);

                // Merge audio and video files.
                await Task.Run(() => FfmpegBusiness.JoinAudioVideo(Destination + File1Ext, Destination + File2Ext, Destination + DestinationExt, true));

                // Delete source files
                File.Delete(Destination + File1Ext);
                File.Delete(Destination + File2Ext);
            } else if (downloadInfo.UpgradeAudio) {
                // Get original video format.
                MediaInfoReader MediaReader = new MediaInfoReader();
                //await MediaReader.LoadInfoAsync(Settings.NaturalGroundingFolder + downloadInfo.Request.FileName);
                string VideoDestExt = ".mkv";
                //if (MediaReader.VideoFormat == "VP8" || MediaReader.VideoFormat == "VP9")
                //    VideoDestExt = ".webm";
                string VideoDest = downloadInfo.Destination + " (extract)" + VideoDestExt;

                // Keep existing video and upgrade audio.
                string AudioExt = GetAudioExtension(downloadInfo.Files[0].Source.AudioType);
                DestinationExt = GetFinalExtension(Path.GetExtension(downloadInfo.Request.FileName), AudioExt);

                // Merge audio and video files.
                await Task.Run(() => {
                    FfmpegBusiness.ExtractVideo(Settings.NaturalGroundingFolder + downloadInfo.Request.FileName, VideoDest, true);
                    if (FileHasContent(VideoDest))
                        FfmpegBusiness.JoinAudioVideo(VideoDest, Destination + AudioExt, Destination + DestinationExt, true);
                });

                // Delete source files
                File.Delete(VideoDest);
                File.Delete(Destination + AudioExt);

                if (FileHasContent(Destination + DestinationExt) && File.Exists(Settings.NaturalGroundingFolder + downloadInfo.Request.FileName))
                    FileOperationAPIWrapper.MoveToRecycleBin(Settings.NaturalGroundingFolder + downloadInfo.Request.FileName);
            }

            // Ensure download and merge succeeded.
            if (FileHasContent(Destination + DestinationExt)) {
                // Get final file name.
                DefaultMediaPath PathCalc = new DefaultMediaPath();
                string NewFileName = PathCalc.GetDefaultFileName(video.Artist, video.Title, video.MediaCategoryId, (MediaType)video.MediaTypeId);
                Directory.CreateDirectory(Path.GetDirectoryName(Settings.NaturalGroundingFolder + NewFileName));
                video.FileName = NewFileName + DestinationExt;

                // Move file and overwrite.
                if (downloadInfo.Request.FileName != null && File.Exists(Settings.NaturalGroundingFolder + downloadInfo.Request.FileName))
                    FileOperationAPIWrapper.MoveToRecycleBin(Settings.NaturalGroundingFolder + downloadInfo.Request.FileName);
                File.Move(Destination + DestinationExt, Settings.NaturalGroundingFolder + video.FileName);
            } else
                throw new FileNotFoundException("Audio and video streams could not be merged.");

            // Add to database
            EditVideoBusiness Business = new EditVideoBusiness();
            Media ExistingData = Business.GetVideoById(video.MediaId);
            if (ExistingData != null) {
                // Edit video info.
                ExistingData.FileName = video.FileName;
                ExistingData.Length = null;
                ExistingData.Height = null;
                Business.Save();
            } else {
                // Add new video info.
                Business.AddVideo(video);
                Business.Save();
            }
            downloadInfo.Request.FileName = video.FileName;

            downloadInfo.Status = DownloadStatus.Done;
        }
        private async Task DownloadVideoAsync(DownloadItem downloadInfo, DownloadItem.FileProgress fileInfo, EventHandler<DownloadCompletedEventArgs> callback) {
            downloadInfo.Status = DownloadStatus.Downloading;
            VideoDownloader YTD = new VideoDownloader(fileInfo.Source, fileInfo.Destination);
            YTD.DownloadProgressChanged += (sender, e) => {
                if (downloadInfo.IsCanceled)
                    e.Cancel = true;
                else {
                    fileInfo.BytesTotal = YTD.DownloadSize;
                    fileInfo.BytesDownloaded = e.ProgressBytes;
                    downloadInfo.UpdateProgress();
                }
            };

            // Run downloader task.
            await Task.Run(() => {
                try {
                    YTD.Execute();
                } catch {
                    downloadInfo.Status = DownloadStatus.Failed;
                }
            }).ConfigureAwait(false);

            // Detect whether this is the last file.
            fileInfo.Done = true;
            if (downloadInfo.Files.Any(d => !d.Done) == false) {
                var NextDownload = StartNextDownloadAsync().ConfigureAwait(false);

                // Raise events for the last file part only.
                if (downloadInfo.IsCompleted) {
                    try {
                        await DownloadCompletedAsync(downloadInfo).ConfigureAwait(false);
                    } catch {
                        downloadInfo.Status = DownloadStatus.Failed;
                    }
                } else if (downloadInfo.IsCanceled)
                    DownloadCanceled(downloadInfo);
                RaiseCallback(downloadInfo);

                await NextDownload;
            }
        }
        /// <summary>
        /// Downloads the specified list of files.
        /// </summary>
        /// <param name="downloadInfo">The information about the files to download.</param>
        /// <param name="callback">The function to call when download is completed.</param>
        private async Task DownloadFilesAsync(DownloadItem downloadInfo, EventHandler<DownloadCompletedEventArgs> callback) {
            // Decrypt all URLs at the same time without waiting.
            List<Task> DecryptTasks = new List<Task>();
            foreach (var item in downloadInfo.Files) {
                DecryptTasks.Add(Task.Run(() => DownloadUrlResolver.DecryptDownloadUrl(item.Source)));
            }
            await Task.WhenAll(DecryptTasks.ToArray()).ConfigureAwait(false);

            if (!downloadInfo.IsCanceled) {
                // Download all files.
                List<Task> DownloadTasks = new List<Task>();
                foreach (var item in downloadInfo.Files) {
                    DownloadTasks.Add(DownloadVideoAsync(downloadInfo, item, callback));
                }
                await Task.WhenAll(DownloadTasks.ToArray()).ConfigureAwait(false);
            } else
                RaiseCallback(downloadInfo);
        }