public string SubstituteWildcards(string filename, TwitchVideo video) { if (video == null) { throw new ArgumentNullException(nameof(video)); } if (string.IsNullOrWhiteSpace(filename)) { return(filename); } string result = filename; DateTime recorded = video.RecordedDate; TwitchVideoQuality resolution = video.Resolutions.First(); result = result.Replace(FilenameWildcards.CHANNEL, video.Channel); result = result.Replace(FilenameWildcards.GAME, video.Game); result = result.Replace(FilenameWildcards.DATE, recorded.ToString("yyyyMMdd")); result = result.Replace(FilenameWildcards.TIME, recorded.ToString("hhmmsstt", CultureInfo.InvariantCulture)); result = result.Replace(FilenameWildcards.TIME24, recorded.ToString("HHmmss", CultureInfo.InvariantCulture)); result = result.Replace(FilenameWildcards.ID, video.IdTrimmed); result = result.Replace(FilenameWildcards.TITLE, video.Title); result = result.Replace(FilenameWildcards.RES, resolution.Resolution); result = result.Replace(FilenameWildcards.FPS, resolution.Fps); result = this.SubstituteInvalidChars(result, "_"); return(result); }
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); } }
private void DownloadVideo(string id) { try { lock (_commandLockObject) { if (!string.IsNullOrWhiteSpace(id)) { TwitchVideo video = Videos.Where(v => v.Id == id).FirstOrDefault(); if (video != null) { TwitchVideoAuthInfo vodAuthInfo = _apiService.GetVodAuthInfo(video.Id); if (!vodAuthInfo.Privileged && vodAuthInfo.SubOnly) { if (_isAuthenticatedSubOnly) { _dialogService.ShowMessageBox("This video is sub-only but you are not subscribed to the channel!", "Download", MessageBoxButton.OK, MessageBoxImage.Exclamation); } else { _dialogService.ShowMessageBox("This video is sub-only! You need to enable sub-only video download support first!", "Download", MessageBoxButton.OK, MessageBoxImage.Exclamation); } return; } Dictionary <TwitchVideoQuality, string> playlistInfo = _apiService.GetPlaylistInfo(id, vodAuthInfo); List <TwitchVideoQuality> qualities = playlistInfo.Keys.OrderBy(q => q).ToList(); Preferences currentPrefs = _preferencesService.CurrentPreferences.Clone(); TwitchVideoQuality selectedQuality = GetSelectedQuality(qualities, currentPrefs.DownloadDefaultQuality); string folder = currentPrefs.DownloadSubfoldersForFav && _preferencesService.IsChannelInFavourites(video.Channel) ? Path.Combine(currentPrefs.DownloadFolder, video.Channel) : currentPrefs.DownloadFolder; string filename = _filenameService.SubstituteWildcards(currentPrefs.DownloadFileName, video, selectedQuality); filename = _filenameService.EnsureExtension(filename, currentPrefs.DownloadDisableConversion); DownloadParameters downloadParams = new DownloadParameters(video, qualities, selectedQuality, folder, filename, currentPrefs.DownloadDisableConversion); if (video.StartTime.HasValue) { downloadParams.CropStartTime = video.StartTime.Value; } _navigationService.ShowDownload(downloadParams); } } } } catch (Exception ex) { _dialogService.ShowAndLogException(ex); } }
private TwitchVideoQuality GetSelectedQuality(List <TwitchVideoQuality> qualities, DefaultQuality defaultQuality) { TwitchVideoQuality sourceQuality = qualities.Find(q => q.IsSource); if (defaultQuality.IsSource) { return(sourceQuality); } if (defaultQuality.IsAudioOnly) { TwitchVideoQuality audioOnlyQuality = qualities.FirstOrDefault(q => q.IsAudioOnly); if (audioOnlyQuality != null) { return(audioOnlyQuality); } else { return(sourceQuality); } } IEnumerable <TwitchVideoQuality> visualQualities = qualities.Where(q => !q.IsAudioOnly); int defaultRes = defaultQuality.VerticalResolution; TwitchVideoQuality selectedQuality = null; foreach (TwitchVideoQuality quality in visualQualities) { if (quality.VerticalResolution <= defaultRes && (selectedQuality == null || selectedQuality.VerticalResolution < quality.VerticalResolution)) { selectedQuality = quality; } } if (selectedQuality != null) { return(selectedQuality); } foreach (TwitchVideoQuality quality in visualQualities) { if (quality.VerticalResolution >= defaultRes && (selectedQuality == null || selectedQuality.VerticalResolution > quality.VerticalResolution)) { selectedQuality = quality; } } if (selectedQuality != null) { return(selectedQuality); } return(sourceQuality); }
private void DownloadVideo(string id) { try { lock (this.commandLockObject) { if (!string.IsNullOrWhiteSpace(id)) { TwitchVideo video = this.Videos.Where(v => v.Id == id).FirstOrDefault(); if (video != null) { VodAuthInfo vodAuthInfo = this.twitchService.RetrieveVodAuthInfo(video.IdTrimmed); if (!vodAuthInfo.Privileged && vodAuthInfo.SubOnly) { if (!this.twitchService.IsAuthorized) { this.dialogService.ShowMessageBox("This video is sub-only! Please authorize your Twitch account by clicking the Twitch button in the menu.", "SUB HYPE!", MessageBoxButton.OK, MessageBoxImage.Exclamation); } else { this.dialogService.ShowMessageBox("This video is sub-only but you are not subscribed to '" + video.Channel + "'!", "SUB HYPE!", MessageBoxButton.OK, MessageBoxImage.Exclamation); } return; } Preferences currentPrefs = this.preferencesService.CurrentPreferences.Clone(); TwitchVideoQuality resolution = video.Resolutions.Where(r => r.QualityFormatted == currentPrefs.DownloadVideoQuality).FirstOrDefault(); if (resolution == null) { resolution = video.Resolutions.First(); } string filename = this.filenameService.SubstituteWildcards(currentPrefs.DownloadFileName, video); DownloadParameters downloadParams = new DownloadParameters(video, resolution, vodAuthInfo, currentPrefs.DownloadFolder, filename); this.navigationService.ShowDownload(downloadParams); } } } } catch (Exception ex) { this.dialogService.ShowAndLogException(ex); } }
public Dictionary <TwitchVideoQuality, string> GetPlaylistInfo(string vodId, TwitchVideoAuthInfo vodAuthInfo) { using (WebClient webClient = CreateWebClientWithEncoding()) { webClient.Headers.Add("Accept", "*/*"); webClient.QueryString.Add("allow_source", "true"); webClient.QueryString.Add("allow_audio_only", "true"); webClient.QueryString.Add("token", vodAuthInfo.Token); webClient.QueryString.Add("sig", vodAuthInfo.Signature); string playlist = webClient.DownloadString(string.Format(PLAYLISTS_URL, vodId)); List <string> lines = playlist.Split(new string[] { "\n" }, StringSplitOptions.RemoveEmptyEntries).ToList(); Dictionary <TwitchVideoQuality, string> playlistInfo = new Dictionary <TwitchVideoQuality, string>(); for (int i = 0; i < lines.Count; i++) { string line = lines[i]; if (!line.StartsWith("#")) { string mediaInfo = lines[i - 2]; string streamInfo = lines[i - 1]; Match groupMatch = _rxGroup.Match(mediaInfo); string id = groupMatch.Groups["group"].Value; Match nameMatch = _rxName.Match(mediaInfo); string name = nameMatch.Groups["name"].Value; string resolution = null; Match resolutionMatch = _rxResolution.Match(streamInfo); if (resolutionMatch.Success) { resolution = resolutionMatch.Groups["resolution"].Value; } TwitchVideoQuality quality = new TwitchVideoQuality(id, name, resolution); playlistInfo.Add(quality, line); } } return(playlistInfo); } }
public string SubstituteWildcards(string filename, string folder, FilenameWildcards.IsFileNameUsedsDelegate IsFileNameUsed, TwitchVideo video, TwitchVideoQuality quality = null, TimeSpan?cropStart = null, TimeSpan?cropEnd = null) { if (video == null) { throw new ArgumentNullException(nameof(video)); } if (string.IsNullOrWhiteSpace(filename)) { return(filename); } string result = filename; DateTime recorded = video.RecordedDate; TwitchVideoQuality selectedQuality = quality ?? video.Qualities.First(); TimeSpan selectedCropStart = cropStart ?? TimeSpan.Zero; TimeSpan selectedCropEnd = cropEnd ?? video.Length; result = result.Replace(FilenameWildcards.CHANNEL, video.Channel); result = result.Replace(FilenameWildcards.GAME, video.Game); result = result.Replace(FilenameWildcards.DATE, recorded.ToString("yyyyMMdd")); result = result.Replace(FilenameWildcards.TIME, recorded.ToString("hhmmsstt", CultureInfo.InvariantCulture)); result = result.Replace(FilenameWildcards.TIME24, recorded.ToString("HHmmss", CultureInfo.InvariantCulture)); result = result.Replace(FilenameWildcards.DATE_, recorded.ToString("yyyy-MM-dd")); result = result.Replace(FilenameWildcards.TIME_, recorded.ToString("hh-mm-ss_tt", CultureInfo.InvariantCulture)); result = result.Replace(FilenameWildcards.TIME24_, recorded.ToString("HH-mm-ss", CultureInfo.InvariantCulture)); result = result.Replace(FilenameWildcards.ID, video.Id); result = result.Replace(FilenameWildcards.TITLE, video.Title); result = result.Replace(FilenameWildcards.RES, !string.IsNullOrWhiteSpace(selectedQuality.Resolution) ? selectedQuality.Resolution : TwitchVideoQuality.UNKNOWN); result = result.Replace(FilenameWildcards.FPS, selectedQuality.Fps.HasValue ? selectedQuality.Fps.ToString() : TwitchVideoQuality.UNKNOWN); result = result.Replace(FilenameWildcards.START, selectedCropStart.ToShortDaylessString()); result = result.Replace(FilenameWildcards.END, selectedCropEnd.ToShortDaylessString()); result = SubstituteInvalidChars(result, "_"); if (result.Contains(FilenameWildcards.UNIQNUMBER)) { int index = 1; while (File.Exists(Path.Combine(folder, result.Replace(FilenameWildcards.UNIQNUMBER, index.ToString()))) || IsFileNameUsed(Path.Combine(folder, result.Replace(FilenameWildcards.UNIQNUMBER, index.ToString())))) { index++; } result = result.Replace(FilenameWildcards.UNIQNUMBER, index.ToString()); } return(result); }
private TwitchVideoQuality TryFindQuality(List <TwitchVideoQuality> qualities, VideoQuality shouldQuality) { if (shouldQuality == VideoQuality.Source) { return(qualities.First()); } if (shouldQuality == Core.Enums.VideoQuality.AudioOnly) { return(qualities.Find(x => x.QualityString == TwitchVideoQuality.GetQualityString(TwitchVideoQuality.QUALITY_AUDIO))); } int fpsShould = shouldQuality.GetFps(); int resolutionYShould = shouldQuality.GetResolutionY(); //sometimes fps is near value, like 61 or 59 instead 60 return(qualities.Find(x => x.Fps.HasValue && Math.Abs(x.Fps.Value - fpsShould) < 3 && x.ResolutionY.HasValue && x.ResolutionY.Value == resolutionYShould)); }
public string SubstituteWildcards(string filename, TwitchVideo video, TwitchVideoQuality quality = null, TimeSpan?cropStart = null, TimeSpan?cropEnd = null, int?splitPartNumber = null) { if (video == null) { throw new ArgumentNullException(nameof(video)); } if (string.IsNullOrWhiteSpace(filename)) { return(filename); } string result = filename; DateTime recorded = video.RecordedDate; TwitchVideoQuality selectedQuality = quality ?? video.Qualities.First(); TimeSpan selectedCropStart = cropStart ?? TimeSpan.Zero; TimeSpan selectedCropEnd = cropEnd ?? video.Length; if (splitPartNumber.HasValue && !(result.Contains(FilenameWildcards.START) && result.Contains(FilenameWildcards.END))) { result = ((int)splitPartNumber).ToString("000") + "_" + result; } result = result.Replace(FilenameWildcards.CHANNEL, video.Channel); result = result.Replace(FilenameWildcards.GAME, video.Game); result = result.Replace(FilenameWildcards.DATE, recorded.ToString("yyyyMMdd")); result = result.Replace(FilenameWildcards.TIME, recorded.ToString("hhmmsstt", CultureInfo.InvariantCulture)); result = result.Replace(FilenameWildcards.TIME24, recorded.ToString("HHmmss", CultureInfo.InvariantCulture)); result = result.Replace(FilenameWildcards.ID, video.Id); result = result.Replace(FilenameWildcards.TITLE, video.Title); result = result.Replace(FilenameWildcards.RES, !string.IsNullOrWhiteSpace(selectedQuality.Resolution) ? selectedQuality.Resolution : TwitchVideoQuality.UNKNOWN); result = result.Replace(FilenameWildcards.FPS, selectedQuality.Fps.HasValue ? selectedQuality.Fps.ToString() : TwitchVideoQuality.UNKNOWN); result = result.Replace(FilenameWildcards.START, selectedCropStart.ToShortDaylessString()); result = result.Replace(FilenameWildcards.END, selectedCropEnd.ToShortDaylessString()); result = SubstituteInvalidChars(result, "_"); return(result); }
public string SubstituteWildcards(string filename, TwitchVideo video, TwitchVideoQuality quality, TimeSpan?cropStart = null, TimeSpan?cropEnd = null) { if (video == null) { throw new ArgumentNullException(nameof(video)); } if (string.IsNullOrWhiteSpace(filename)) { return(filename); } string result = filename; DateTime recorded = video.RecordedDate; TimeSpan selectedCropStart = cropStart ?? TimeSpan.Zero; TimeSpan selectedCropEnd = cropEnd ?? video.Length; result = result.Replace(FilenameWildcards.ID, video.Id); result = result.Replace(FilenameWildcards.CHANNEL, video.Channel); result = result.Replace(FilenameWildcards.TITLE, video.Title); result = result.Replace(FilenameWildcards.RES, quality.Resolution); result = result.Replace(FilenameWildcards.YEAR, recorded.ToString("yyyy", CultureInfo.InvariantCulture)); result = result.Replace(FilenameWildcards.MONTH, recorded.ToString("MM", CultureInfo.InvariantCulture)); result = result.Replace(FilenameWildcards.DAY, recorded.ToString("dd", CultureInfo.InvariantCulture)); result = result.Replace(FilenameWildcards.HOUR24, recorded.ToString("HH", CultureInfo.InvariantCulture)); result = result.Replace(FilenameWildcards.HOUR, recorded.ToString("hh", CultureInfo.InvariantCulture)); result = result.Replace(FilenameWildcards.MINUTES, recorded.ToString("mm", CultureInfo.InvariantCulture)); result = result.Replace(FilenameWildcards.SECONDS, recorded.ToString("ss", CultureInfo.InvariantCulture)); result = result.Replace(FilenameWildcards.AMPM, recorded.ToString("tt", CultureInfo.InvariantCulture)); result = result.Replace(FilenameWildcards.START, selectedCropStart.ToShortDaylessString()); result = result.Replace(FilenameWildcards.END, selectedCropEnd.ToShortDaylessString()); result = SubstituteInvalidChars(result, "_"); return(result); }
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); } } }
private void DownloadVideo(string id) { try { lock (_commandLockObject) { if (!string.IsNullOrWhiteSpace(id)) { TwitchVideo video = Videos.Where(v => v.Id == id).FirstOrDefault(); if (video != null) { VodAuthInfo vodAuthInfo = _twitchService.RetrieveVodAuthInfo(video.Id); if (!vodAuthInfo.Privileged && vodAuthInfo.SubOnly) { if (!_twitchService.IsAuthorized) { _dialogService.ShowMessageBox("This video is sub-only! Please authorize your Twitch account by clicking the Twitch button in the menu.", "SUB HYPE!", MessageBoxButton.OK, MessageBoxImage.Exclamation); } else { _dialogService.ShowMessageBox("This video is sub-only but you are not subscribed to '" + video.Channel + "'!", "SUB HYPE!", MessageBoxButton.OK, MessageBoxImage.Exclamation); } return; } Preferences currentPrefs = _preferencesService.CurrentPreferences.Clone(); string folder = currentPrefs.DownloadSubfoldersForFav && _preferencesService.IsChannelInFavourites(video.Channel) ? Path.Combine(currentPrefs.DownloadFolder, video.Channel) : currentPrefs.DownloadFolder; string filename = currentPrefs.DownloadFileName; filename = _filenameService.EnsureExtension(filename, currentPrefs.DownloadDisableConversion); if (currentPrefs.DownloadDisableConversion || video.Length.TotalSeconds < currentPrefs.DownloadSplitTime.TotalSeconds + Preferences.MinSplitLength) { currentPrefs.DownloadSplitUse = false; } if (currentPrefs.DownloadSplitUse) {//Keep UNIQNUMBER wildcard in name to split file (only in conversation mode) string tempUniqWildcard = FilenameWildcards.UNIQNUMBER.Insert(FilenameWildcards.UNIQNUMBER.Length - 1, "_TEMP"); filename = filename.Replace(FilenameWildcards.UNIQNUMBER, tempUniqWildcard); filename = _filenameService.SubstituteWildcards(filename, folder, _twitchService.IsFileNameUsed, video); filename = filename.Replace(tempUniqWildcard, FilenameWildcards.UNIQNUMBER); } else { filename = _filenameService.SubstituteWildcards(filename, folder, _twitchService.IsFileNameUsed, video); } TwitchVideoQuality shouldQualityOrNull = TryFindQuality(video.Qualities, currentPrefs.DownloadQuality); DownloadParameters downloadParams = new DownloadParameters(video, vodAuthInfo, shouldQualityOrNull, folder, filename, currentPrefs.DownloadDisableConversion, currentPrefs.DownloadSplitUse, currentPrefs.DownloadSplitTime, currentPrefs.SplitOverlapSeconds); _navigationService.ShowDownload(downloadParams); } } } } catch (Exception ex) { _dialogService.ShowAndLogException(ex); } }