private void WriteNewPlaylist(Action <string> log, WebChunkList webChunkList, string playlistFile) { log(Environment.NewLine + Environment.NewLine + "Creating local m3u8 playlist for FFMPEG..."); StringBuilder sb = new StringBuilder(); webChunkList.Header.ForEach(line => { sb.AppendLine(line); }); webChunkList.Content.ForEach(webChunk => { sb.AppendLine(webChunk.ExtInf); sb.AppendLine(webChunk.LocalFile); }); webChunkList.Footer.ForEach(line => { sb.AppendLine(line); }); log(" done!"); log(Environment.NewLine + "Writing playlist to '" + playlistFile + "'..."); FileSystem.DeleteFile(playlistFile); File.WriteAllText(playlistFile, sb.ToString()); log(" done!"); }
private void DownloadChunks(Action <string> log, Action <string> setStatus, Action <int> setProgress, WebChunkList webChunkList, CancellationToken cancellationToken) { int webChunkCount = webChunkList.Content.Count; int maxConnectionCount = ServicePointManager.DefaultConnectionLimit; log(Environment.NewLine + Environment.NewLine + "Starting parallel video chunk download"); log(Environment.NewLine + "Number of video chunks to download: " + webChunkCount); log(Environment.NewLine + "Maximum connection count: " + maxConnectionCount); setStatus("Downloading"); log(Environment.NewLine + Environment.NewLine + "Parallel video chunk download is running..."); long completedChunkDownloads = 0; Parallel.ForEach(webChunkList.Content, new ParallelOptions() { MaxDegreeOfParallelism = maxConnectionCount - 1 }, (webChunk, loopState) => { using (WebClient downloadClient = new WebClient()) { byte[] bytes = downloadClient.DownloadData(webChunk.DownloadUrl); Interlocked.Increment(ref completedChunkDownloads); FileSystem.DeleteFile(webChunk.LocalFile); File.WriteAllBytes(webChunk.LocalFile, bytes); long completed = Interlocked.Read(ref completedChunkDownloads); setProgress((int)(completedChunkDownloads * 100 / webChunkCount)); } if (cancellationToken.IsCancellationRequested) { loopState.Stop(); } }); setProgress(100); log(" done!"); log(Environment.NewLine + Environment.NewLine + "Download of all video chunks complete!"); }
private WebChunkList RetrieveWebChunkList(Action <string> log, WebClient webClient, string tempDir, string playlistUrl) { log(Environment.NewLine + Environment.NewLine + "Retrieving playlist..."); string playlistStr = webClient.DownloadString(playlistUrl); log(" done!"); string playlistUrlPrefix = playlistUrl.Substring(0, playlistUrl.LastIndexOf("/") + 1); log(Environment.NewLine + "Parsing playlist..."); WebChunkList webChunkList = WebChunkList.Parse(tempDir, playlistStr, playlistUrlPrefix); log(" done!"); log(Environment.NewLine + "Number of video chunks: " + webChunkList.Content.Count); return(webChunkList); }
private CropInfo CropWebChunkList(WebChunkList webChunkList, bool cropStart, bool cropEnd, TimeSpan cropStartTime, TimeSpan cropEndTime) { double start = cropStartTime.TotalMilliseconds; double lengthOrg = cropEndTime.TotalMilliseconds; double length = cropEndTime.TotalMilliseconds; if (cropStart) { length -= start; } start = Math.Round(start / 1000, 3); lengthOrg = Math.Round(lengthOrg / 1000, 3); length = Math.Round(length / 1000, 3); List <WebChunk> content = webChunkList.Content; List <WebChunk> deleteStart = new List <WebChunk>(); List <WebChunk> deleteEnd = new List <WebChunk>(); if (cropStart) { double chunkSum = 0; foreach (WebChunk webChunk in content) { chunkSum += webChunk.Length; if (chunkSum < start) { deleteStart.Add(webChunk); } else { start = Math.Round(chunkSum - start, 3); break; } } } if (cropEnd) { double chunkSum = 0; foreach (WebChunk webChunk in content) { chunkSum += webChunk.Length; if (chunkSum > lengthOrg) { deleteEnd.Add(webChunk); } } } deleteStart.ForEach(webChunk => { content.Remove(webChunk); }); deleteEnd.ForEach(webChunk => { content.Remove(webChunk); }); return(new CropInfo(cropStart, cropEnd, cropStart ? start : 0, length)); }
private void StartQueuedDownloadIfExists() { if (this.paused) { return; } if (Monitor.TryEnter(this.changeDownloadLockObject)) { try { if (!this.downloads.Where(d => d.DownloadStatus == DownloadStatus.Active).Any()) { TwitchVideoDownload download = this.downloads.Where(d => d.DownloadStatus == DownloadStatus.Queued).FirstOrDefault(); if (download == null) { return; } DownloadParameters downloadParams = download.DownloadParams; CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); CancellationToken cancellationToken = cancellationTokenSource.Token; string downloadId = download.Id; string urlIdTrimmed = downloadParams.Video.IdTrimmed; string tempDir = Path.Combine(downloadParams.Folder, TEMP_PREFIX + downloadId); string playlistFile = Path.Combine(tempDir, PLAYLIST_NAME); string ffmpegFile = Path.Combine(appDir, Environment.Is64BitOperatingSystem ? FFMPEG_EXE_X64 : FFMPEG_EXE_X86); string outputFile = downloadParams.FullPath; bool cropStart = downloadParams.CropStart; bool cropEnd = downloadParams.CropEnd; TimeSpan cropStartTime = downloadParams.CropStartTime; TimeSpan cropEndTime = downloadParams.CropEndTime; TwitchVideoResolution resolution = downloadParams.Resolution; Action <DownloadStatus> setDownloadStatus = download.SetDownloadStatus; Action <string> log = download.AppendLog; Action <string> setStatus = download.SetStatus; Action <int> setProgress = download.SetProgress; Action <bool> setIsEncoding = download.SetIsEncoding; Task downloadVideoTask = new Task(() => { setStatus("Initializing"); log("Download task has been started!"); this.WriteDownloadInfo(log, downloadParams, ffmpegFile, tempDir); this.CheckTempDirectory(log, tempDir); using (WebClient webClient = new WebClient()) { AuthInfo authInfo = this.RetrieveAuthInfo(log, webClient, urlIdTrimmed); cancellationToken.ThrowIfCancellationRequested(); string playlistUrl = this.RetrievePlaylistUrlForQuality(log, webClient, resolution, urlIdTrimmed, authInfo); cancellationToken.ThrowIfCancellationRequested(); WebChunkList webChunkList = this.RetrieveWebChunkList(log, webClient, tempDir, playlistUrl); cancellationToken.ThrowIfCancellationRequested(); CropInfo cropInfo = this.CropWebChunkList(webChunkList, cropStart, cropEnd, cropStartTime, cropEndTime); cancellationToken.ThrowIfCancellationRequested(); this.DownloadChunks(log, setStatus, setProgress, webChunkList, cancellationToken); cancellationToken.ThrowIfCancellationRequested(); this.WriteNewPlaylist(log, webChunkList, playlistFile); cancellationToken.ThrowIfCancellationRequested(); this.EncodeVideo(log, setStatus, setProgress, setIsEncoding, ffmpegFile, playlistFile, outputFile, cropInfo); } }, cancellationToken); Task continueTask = downloadVideoTask.ContinueWith(task => { log(Environment.NewLine + Environment.NewLine + "Starting temporary download folder cleanup!"); this.CleanUp(tempDir, log); setProgress(100); setIsEncoding(false); bool success = false; if (task.IsFaulted) { setDownloadStatus(DownloadStatus.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) { setDownloadStatus(DownloadStatus.Canceled); log(Environment.NewLine + Environment.NewLine + "Download task was canceled!"); } else { success = true; setDownloadStatus(DownloadStatus.Finished); log(Environment.NewLine + Environment.NewLine + "Download task ended successfully!"); } DownloadTask downloadTask; if (!this.downloadTasks.TryRemove(downloadId, out downloadTask)) { throw new ApplicationException("Could not remove download task with ID '" + downloadId + "' from download task collection!"); } if (success && this.preferencesService.CurrentPreferences.DownloadRemoveCompleted) { this.eventAggregator.GetEvent <DownloadCompletedEvent>().Publish(downloadId); } }); if (this.downloadTasks.TryAdd(downloadId, new DownloadTask(downloadVideoTask, continueTask, cancellationTokenSource))) { downloadVideoTask.Start(); setDownloadStatus(DownloadStatus.Active); } } } finally { Monitor.Exit(this.changeDownloadLockObject); } } }