public bool PrepareDownload(StreamExtended stream) { string streamUrl; streamUrl = "https://www.twitch.tv/videos/" + stream.streamId; YoutubeDlVideoJson.YoutubeDlVideoInfo youtubeDlVideoInfo = StreamHelpers.GetDownloadQualityUrl(streamUrl, stream.streamerId); string streamDirectory = $"{GlobalConfig.GetGlobalConfig("contentRootPath")}streamers/{stream.streamerId}/vods/{stream.streamId}"; Directory.CreateDirectory(streamDirectory); if (!string.IsNullOrEmpty(stream.thumbnailLocation)) { //todo handle missing thumbnail, maybe use youtubedl generated thumbnail instead DownloadHelpers.DownloadFile( stream.thumbnailLocation.Replace("%{width}", "320").Replace("%{height}", "180"), $"{streamDirectory}/thumbnail.jpg"); } string title = String.IsNullOrEmpty(stream.title) ? "vod" : stream.title; string outputPath = $"{streamDirectory}/{title}.{stream.streamId}"; string dbOutputPath = $"streamers/{stream.streamerId}/vods/{stream.streamId}/{title}.{stream.streamId}.mp4"; //TODO more should be queued, not done immediately IJobDetail job; string triggerIdentity; job = JobBuilder.Create <DownloadStreamJob>() .WithIdentity("StreamDownload" + stream.streamId) .UsingJobData("title", title) .UsingJobData("streamDirectory", streamDirectory) .UsingJobData("formatId", youtubeDlVideoInfo.formatId) .UsingJobData("url", streamUrl) .UsingJobData("isLive", false) .UsingJobData("youtubeDlVideoInfoDuration", youtubeDlVideoInfo.duration) .UsingJobData("retry", true) .RequestRecovery() .Build(); job.JobDataMap.Put("stream", stream); triggerIdentity = $"StreamDownload{stream.streamId}"; /*string jobId = BackgroundJob.Enqueue(() => * DownloadStream(stream, title, streamDirectory, youtubeDlVideoInfo.url, CancellationToken.None, * isLive, youtubeDlVideoInfo.duration));*/ Stream? dbStream; bool downloadChat = false; IJobDetail chatDownloadJob = new JobDetailImpl(); using (var context = new MainDataContext()) { dbStream = context.Streams.FirstOrDefault(item => item.streamId == stream.streamId); if (dbStream != null) { dbStream.streamId = stream.streamId; dbStream.streamerId = stream.streamerId; dbStream.quality = youtubeDlVideoInfo.quality; dbStream.url = youtubeDlVideoInfo.url; dbStream.title = stream.title; dbStream.createdAt = stream.createdAt; dbStream.location = $"streamers/{stream.streamerId}/vods/{stream.streamId}/"; dbStream.fileName = $"{title}.{stream.streamId}.mp4"; dbStream.duration = youtubeDlVideoInfo.duration; dbStream.downloading = true; dbStream.downloadJobId = job.Key.ToString(); } else { downloadChat = true; chatDownloadJob = JobBuilder.Create <ChatDownloadJob>() .WithIdentity("DownloadChat" + stream.streamId) .UsingJobData("streamId", stream.streamId) .UsingJobData("retry", true) .RequestRecovery() .Build(); dbStream = new Stream { streamId = stream.streamId, streamerId = stream.streamerId, quality = youtubeDlVideoInfo.quality, title = stream.title, url = youtubeDlVideoInfo.url, createdAt = stream.createdAt, location = $"streamers/{stream.streamerId}/vods/{stream.streamId}/", fileName = $"{title}.{stream.streamId}.mp4", duration = youtubeDlVideoInfo.duration, downloading = true, chatDownloading = true, downloadJobId = job.Key.ToString(), chatDownloadJobId = chatDownloadJob.Key.ToString() }; // only download chat if this is a new vod context.Add(dbStream); } context.SaveChanges(); } //var chatSchedulerFactory = new StdSchedulerFactory(QuartzSchedulers.SingleThreadScheduler()); var vodSchedulerFactory = new StdSchedulerFactory(QuartzSchedulers.PrimaryScheduler()); //IScheduler chatScheduler = chatSchedulerFactory.GetScheduler().Result; IScheduler vodScheduler = vodSchedulerFactory.GetScheduler().Result; //chatScheduler.Start(); vodScheduler.Start(); ISimpleTrigger trigger = (ISimpleTrigger)TriggerBuilder.Create() .WithIdentity(triggerIdentity) .StartNow() .Build(); vodScheduler.ScheduleJob(job, trigger); if (downloadChat) { ISimpleTrigger chatDownloadTrigger = (ISimpleTrigger)TriggerBuilder.Create() .WithIdentity("DownloadChat" + stream.streamId) .StartNow() .Build(); vodScheduler.ScheduleJob(chatDownloadJob, chatDownloadTrigger); } //_hubContext.Clients.All.SendAsync("ReceiveMessage", CheckForDownloadingStreams()); return(true); }
public static YoutubeDlVideoJson.YoutubeDlVideoInfo ParseBestPossibleQuality( YoutubeDlVideoJson.YoutubeDlVideo deserializedJson, int streamerId) { var returnValue = new YoutubeDlVideoJson.YoutubeDlVideoInfo(); List <SetupQualityExtendedJsonClass> availableQualities = new List <SetupQualityExtendedJsonClass>(); for (var x = 0; x < deserializedJson.formats.Count; x++) { availableQualities.Add(new SetupQualityExtendedJsonClass { Resolution = deserializedJson.formats[x].height, Fps = RoundToNearest10(Convert.ToInt32(deserializedJson.formats[x].fps)), tbr = deserializedJson.formats[x].tbr.Value }); } // sort by highest quality first availableQualities = availableQualities.OrderByDescending(item => item.tbr).ToList(); // saving for later, just in case /*for (var x = 0; x < deserializedJson.formats.Count; x++) { * var splitRes = deserializedJson.formats[x].format_id.Split("p"); * availableQualities.Add(new SetupQualityExtendedJsonClass() { * Resolution = int.Parse(splitRes[0]), * Fps = int.Parse(splitRes[1]), * Counter = x * }); * }*/ Streamer streamerQuality; string defaultQuality = GlobalConfig.GetGlobalConfig("streamQuality"); using (var context = new MainDataContext()) { streamerQuality = context.Streamers.FirstOrDefault(item => item.streamerId == streamerId); } int resolution = 0; double fps = 0; if (streamerQuality != null && streamerQuality.quality == null) { if (defaultQuality != null) { var parsedQuality = JsonConvert.DeserializeObject <SetupQualityJsonClass>(defaultQuality); resolution = parsedQuality.Resolution; fps = parsedQuality.Fps; } } else { var parsedQuality = JsonConvert.DeserializeObject <SetupQualityJsonClass>(streamerQuality.quality); resolution = parsedQuality.Resolution; fps = parsedQuality.Fps; } if (resolution != 0 && fps != 0) { // check if the chosen resolution and fps is available var existingQuality = availableQualities.FirstOrDefault(item => item.Resolution == resolution && item.Fps == fps); if (existingQuality != null) { var selectedQuality = deserializedJson.formats.FirstOrDefault(item => item.tbr == existingQuality.tbr); if (selectedQuality != null) { returnValue.url = selectedQuality.url; returnValue.quality = selectedQuality.height; } } else { // get same resolution, but different fps (720p 60fps not available, maybe 720p 30fps?) existingQuality = availableQualities.FirstOrDefault(item => item.Resolution == resolution); if (existingQuality != null) { var selectedQuality = deserializedJson.formats.FirstOrDefault(item => item.tbr == existingQuality.tbr); if (selectedQuality != null) { returnValue.url = selectedQuality.url; returnValue.quality = selectedQuality.height; } } else { // same resolution and fps not available; choose the next best value (after sorting the list) existingQuality = availableQualities.FirstOrDefault(item => item.Resolution < resolution); if (existingQuality != null) { var selectedQuality = deserializedJson.formats.FirstOrDefault(item => item.tbr == existingQuality.tbr); if (selectedQuality != null) { returnValue.url = selectedQuality.url; returnValue.quality = selectedQuality.height; } } } } } else { returnValue.url = deserializedJson.url; returnValue.quality = deserializedJson.height; } return(returnValue); }
public Task PrepareLiveStreamDownload(StreamExtended stream, string streamerName) { streamUrl = "https://twitch.tv/" + streamerName; YoutubeDlVideoJson.YoutubeDlVideoInfo youtubeDlVideoInfo = StreamHelpers.GetDownloadQualityUrl(streamUrl, stream.streamerId); streamDirectory = $"{GlobalConfig.GetGlobalConfig("contentRootPath")}streamers/{stream.streamerId}/vods/{stream.streamId}"; try { Directory.CreateDirectory(streamDirectory); } catch (UnauthorizedAccessException e) { _logger.Error(e); // todo handle this throw; } outputPath = $"{streamDirectory}/{stream.title}.{stream.streamId}"; dbOutputPath = $"streamers/{stream.streamerId}/vods/{stream.streamId}/{stream.title}.{stream.streamId}.mp4"; var job = JobBuilder.Create <LiveStreamDownloadJob>() .WithIdentity("LiveStreamDownloadJob" + stream.streamId) .UsingJobData("url", streamUrl) .UsingJobData("streamDirectory", streamDirectory) .UsingJobData("streamId", stream.streamId) .UsingJobData("title", stream.title) .UsingJobData("streamerId", stream.streamerId) .Build(); var triggerIdentity = $"LiveStreamDownload{stream.streamId}"; var chatDownloadJob = JobBuilder.Create <LiveStreamChatDownloadJob>() .WithIdentity("LiveStreamChatDownloadJob" + stream.streamId) .UsingJobData("channel", streamerName) .UsingJobData("streamId", stream.streamId) .Build(); using (var context = new MainDataContext()) { var dbStream = new Stream { streamId = stream.streamId, vodId = stream.vodId, streamerId = stream.streamerId, quality = youtubeDlVideoInfo.quality, title = stream.title, url = youtubeDlVideoInfo.url, createdAt = stream.createdAt, location = $"streamers/{stream.streamerId}/vods/{stream.streamId}/", fileName = $"{stream.title}.{stream.streamId}.mp4", downloading = true, chatDownloading = true, downloadJobId = job.Key.ToString(), chatDownloadJobId = chatDownloadJob.Key.ToString() }; context.Add(dbStream); context.SaveChanges(); } var schedulerFactory = new StdSchedulerFactory(QuartzSchedulers.RamScheduler()); IScheduler scheduler = schedulerFactory.GetScheduler().Result; scheduler.Start(); ISimpleTrigger trigger = (ISimpleTrigger)TriggerBuilder.Create() .WithIdentity(triggerIdentity) .StartNow() .Build(); scheduler.ScheduleJob(job, trigger); PrepareLiveChat(chatDownloadJob, stream.streamId); return(Task.CompletedTask); }