private TwitchPlaylist GetVodPlaylist(Action <string> log, string tempDir, Dictionary <TwitchVideoQuality, string> playlistInfo, TwitchVideoQuality selectedQuality) { TwitchVideoQuality quality = playlistInfo.Keys.First(q => q.Equals(selectedQuality)); string playlistUrl = playlistInfo[quality]; log(Environment.NewLine + Environment.NewLine + "Playlist url for selected quality '" + quality.DisplayString + "' is '" + playlistUrl + "'"); using (WebClient webClient = new WebClient()) { log(Environment.NewLine + Environment.NewLine + "Retrieving playlist..."); string playlistStr = webClient.DownloadString(playlistUrl); log(" done!"); if (string.IsNullOrWhiteSpace(playlistStr)) { throw new ApplicationException("The playlist is empty!"); } string urlPrefix = playlistUrl.Substring(0, playlistUrl.LastIndexOf("/") + 1); log(Environment.NewLine + "Parsing playlist..."); TwitchPlaylist vodPlaylist = TwitchPlaylist.Parse(tempDir, playlistStr, urlPrefix); log(" done!"); log(Environment.NewLine + "Number of video chunks: " + vodPlaylist.Count()); return(vodPlaylist); } }
public void ConcatParts(Action <string> log, Action <string> setStatus, Action <double> setProgress, TwitchPlaylist vodPlaylist, string concatFile) { setStatus("Merging files"); setProgress(0); log(Environment.NewLine + Environment.NewLine + "Merging all VOD parts into '" + concatFile + "'..."); using (FileStream outputStream = new FileStream(concatFile, FileMode.OpenOrCreate, FileAccess.Write)) { int partsCount = vodPlaylist.Count; for (int i = 0; i < partsCount; i++) { TwitchPlaylistPart part = vodPlaylist[i]; using (FileStream partStream = new FileStream(part.LocalFile, FileMode.Open, FileAccess.Read)) { int maxBytes; byte[] buffer = new byte[4096]; while ((maxBytes = partStream.Read(buffer, 0, buffer.Length)) > 0) { outputStream.Write(buffer, 0, maxBytes); } } FileSystem.DeleteFile(part.LocalFile); setProgress(i * 100 / partsCount); } } setProgress(100); }
private void DownloadParts(Action <string> log, Action <string> setStatus, Action <double> setProgress, TwitchPlaylist vodPlaylist, CancellationToken cancellationToken) { int partsCount = vodPlaylist.Count; int maxConnectionCount = ServicePointManager.DefaultConnectionLimit; log(Environment.NewLine + Environment.NewLine + "Starting parallel video chunk download"); log(Environment.NewLine + "Number of video chunks to download: " + partsCount); log(Environment.NewLine + "Maximum connection count: " + maxConnectionCount); setStatus("Downloading"); log(Environment.NewLine + Environment.NewLine + "Parallel video chunk download is running..."); long completedPartDownloads = 0; Parallel.ForEach(vodPlaylist, new ParallelOptions() { MaxDegreeOfParallelism = maxConnectionCount - 1 }, (part, loopState) => { int retryCounter = 0; bool success = false; do { try { using (WebClient downloadClient = new WebClient()) { byte[] bytes = downloadClient.DownloadData(part.RemoteFile); Interlocked.Increment(ref completedPartDownloads); FileSystem.DeleteFile(part.LocalFile); File.WriteAllBytes(part.LocalFile, bytes); long completed = Interlocked.Read(ref completedPartDownloads); setProgress((double)completed / partsCount * 100); success = true; } } catch (WebException ex) { if (retryCounter < DOWNLOAD_RETRIES) { retryCounter++; log(Environment.NewLine + Environment.NewLine + "Downloading file '" + part.RemoteFile + "' failed! Trying again in " + DOWNLOAD_RETRY_TIME + "s"); log(Environment.NewLine + ex.ToString()); Thread.Sleep(DOWNLOAD_RETRY_TIME * 1000); } else { throw new ApplicationException("Could not download file '" + part.RemoteFile + "' after " + DOWNLOAD_RETRIES + " retries!"); } } }while (!success); if (cancellationToken.IsCancellationRequested) { loopState.Stop(); } }); setProgress(100); log(Environment.NewLine + Environment.NewLine + "Download of all video chunks complete!"); }
private CropInfo CropVodPlaylist(TwitchPlaylist vodPlaylist, bool cropStart, bool cropEnd, TimeSpan cropStartTime, TimeSpan cropEndTime) { double start = cropStartTime.TotalMilliseconds; double end = cropEndTime.TotalMilliseconds; double length = cropEndTime.TotalMilliseconds; if (cropStart) { length -= start; } start = Math.Round(start / 1000, 3); end = Math.Round(end / 1000, 3); length = Math.Round(length / 1000, 3); List <TwitchPlaylistPart> deleteStart = new List <TwitchPlaylistPart>(); List <TwitchPlaylistPart> deleteEnd = new List <TwitchPlaylistPart>(); if (cropStart) { double lengthSum = 0; foreach (TwitchPlaylistPart part in vodPlaylist) { double partLength = part.Length; if (lengthSum + partLength < start) { lengthSum += partLength; deleteStart.Add(part); } else { start = Math.Round(start - lengthSum, 3); break; } } } if (cropEnd) { double lengthSum = 0; foreach (TwitchPlaylistPart part in vodPlaylist) { if (lengthSum >= end) { deleteEnd.Add(part); } lengthSum += part.Length; } } deleteStart.ForEach(part => { vodPlaylist.Remove(part); }); deleteEnd.ForEach(part => { vodPlaylist.Remove(part); }); return(new CropInfo(cropStart, cropEnd, cropStart ? start : 0, length)); }
private void StartQueuedDownloadIfExists() { if (_paused) { return; } if (Monitor.TryEnter(_changeDownloadLockObject)) { try { if (!_downloads.Where(d => d.DownloadState == DownloadState.Downloading).Any()) { TwitchVideoDownload download = _downloads.Where(d => d.DownloadState == DownloadState.Queued).FirstOrDefault(); if (download == null) { return; } DownloadParameters downloadParams = download.DownloadParams; CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); CancellationToken cancellationToken = cancellationTokenSource.Token; string downloadId = download.Id; string vodId = downloadParams.Video.Id; string tempDir = Path.Combine(_preferencesService.CurrentPreferences.DownloadTempFolder, downloadId); string ffmpegFile = _processingService.FFMPEGExe; string concatFile = Path.Combine(tempDir, Path.GetFileNameWithoutExtension(downloadParams.FullPath) + ".ts"); string outputFile = downloadParams.FullPath; bool disableConversion = downloadParams.DisableConversion; bool cropStart = downloadParams.CropStart; bool cropEnd = downloadParams.CropEnd; TimeSpan cropStartTime = downloadParams.CropStartTime; TimeSpan cropEndTime = downloadParams.CropEndTime; TwitchVideoQuality quality = downloadParams.SelectedQuality; Action <DownloadState> setDownloadState = download.SetDownloadState; Action <string> log = download.AppendLog; Action <string> setStatus = download.SetStatus; Action <double> setProgress = download.SetProgress; Action <bool> setIsIndeterminate = download.SetIsIndeterminate; Task downloadVideoTask = new Task(() => { setStatus("Initializing"); log("Download task has been started!"); WriteDownloadInfo(log, downloadParams, ffmpegFile, tempDir); cancellationToken.ThrowIfCancellationRequested(); log(Environment.NewLine + Environment.NewLine + "Retrieving VOD access information..."); TwitchVideoAuthInfo vodAuthInfo = _apiService.GetVodAuthInfo(vodId); log(" done!"); cancellationToken.ThrowIfCancellationRequested(); WriteVodAuthInfo(log, vodAuthInfo); cancellationToken.ThrowIfCancellationRequested(); CheckTempDirectory(log, tempDir); cancellationToken.ThrowIfCancellationRequested(); log(Environment.NewLine + Environment.NewLine + "Retrieving playlist information for all VOD qualities..."); Dictionary <TwitchVideoQuality, string> playlistInfo = _apiService.GetPlaylistInfo(vodId, vodAuthInfo); log(" done!"); cancellationToken.ThrowIfCancellationRequested(); WritePlaylistInfo(log, playlistInfo); cancellationToken.ThrowIfCancellationRequested(); TwitchPlaylist vodPlaylist = GetVodPlaylist(log, tempDir, playlistInfo, quality); cancellationToken.ThrowIfCancellationRequested(); CropInfo cropInfo = CropVodPlaylist(vodPlaylist, cropStart, cropEnd, cropStartTime, cropEndTime); cancellationToken.ThrowIfCancellationRequested(); DownloadParts(log, setStatus, setProgress, vodPlaylist, cancellationToken); cancellationToken.ThrowIfCancellationRequested(); _processingService.ConcatParts(log, setStatus, setProgress, vodPlaylist, disableConversion ? outputFile : concatFile); if (!disableConversion) { cancellationToken.ThrowIfCancellationRequested(); _processingService.ConvertVideo(log, setStatus, setProgress, setIsIndeterminate, concatFile, outputFile, cropInfo); } }, cancellationToken); Task continueTask = downloadVideoTask.ContinueWith(task => { log(Environment.NewLine + Environment.NewLine + "Starting temporary download folder cleanup!"); CleanUp(tempDir, log); setProgress(100); setIsIndeterminate(false); bool success = false; if (task.IsFaulted) { setDownloadState(DownloadState.Error); log(Environment.NewLine + Environment.NewLine + "Download task ended with an error!"); if (task.Exception != null) { log(Environment.NewLine + Environment.NewLine + task.Exception.ToString()); } } else if (task.IsCanceled) { setDownloadState(DownloadState.Canceled); log(Environment.NewLine + Environment.NewLine + "Download task was canceled!"); } else { success = true; setDownloadState(DownloadState.Done); log(Environment.NewLine + Environment.NewLine + "Download task ended successfully!"); } if (!_downloadTasks.TryRemove(downloadId, out DownloadTask downloadTask)) { throw new ApplicationException("Could not remove download task with ID '" + downloadId + "' from download task collection!"); } if (success && _preferencesService.CurrentPreferences.DownloadRemoveCompleted) { _eventAggregator.GetEvent <RemoveDownloadEvent>().Publish(downloadId); } }); if (_downloadTasks.TryAdd(downloadId, new DownloadTask(downloadVideoTask, continueTask, cancellationTokenSource))) { downloadVideoTask.Start(); setDownloadState(DownloadState.Downloading); } } } finally { Monitor.Exit(_changeDownloadLockObject); } } }