public void Should_SetDesiredVideoCodec_When_ContentIsPadded_ForTransportStream() { var ffmpegProfile = new FFmpegProfile { NormalizeResolution = true, Resolution = new Resolution { Width = 1920, Height = 1080 }, NormalizeVideoCodec = false, VideoCodec = "testCodec" }; // not anamorphic var version = new MediaVersion { Width = 1918, Height = 1080, SampleAspectRatio = "1:1" }; FFmpegPlaybackSettings actual = _calculator.CalculateSettings( StreamingMode.TransportStream, ffmpegProfile, version, DateTimeOffset.Now, DateTimeOffset.Now); actual.ScaledSize.IsNone.Should().BeTrue(); actual.PadToDesiredResolution.Should().BeTrue(); actual.VideoCodec.Should().Be("testCodec"); }
Should_SetVideoBufferSize_When_ContentIsCorrectSize_And_NormalizingWrongCodec_ForTransportStream() { var ffmpegProfile = new FFmpegProfile { NormalizeResolution = true, Resolution = new Resolution { Width = 1920, Height = 1080 }, NormalizeVideoCodec = true, VideoBufferSize = 2525 }; // not anamorphic var version = new MediaVersion { Width = 1920, Height = 1080, SampleAspectRatio = "1:1", VideoCodec = "mpeg2video" }; FFmpegPlaybackSettings actual = _calculator.CalculateSettings( StreamingMode.TransportStream, ffmpegProfile, version, DateTimeOffset.Now, DateTimeOffset.Now); actual.ScaledSize.IsNone.Should().BeTrue(); actual.PadToDesiredResolution.Should().BeFalse(); actual.VideoBufferSize.IfNone(0).Should().Be(2525); }
public async Task <Either <BaseError, bool> > RefreshStatistics(string ffprobePath, MediaItem mediaItem) { try { string filePath = mediaItem switch { Movie m => m.MediaVersions.Head().MediaFiles.Head().Path, Episode e => e.MediaVersions.Head().MediaFiles.Head().Path, _ => throw new ArgumentOutOfRangeException(nameof(mediaItem)) }; Either <BaseError, FFprobe> maybeProbe = await GetProbeOutput(ffprobePath, filePath); return(await maybeProbe.Match( async ffprobe => { MediaVersion version = ProjectToMediaVersion(ffprobe); bool result = await ApplyVersionUpdate(mediaItem, version, filePath); return Right <BaseError, bool>(result); }, error => Task.FromResult(Left <BaseError, bool>(error)))); } catch (Exception ex) { _logger.LogWarning(ex, "Failed to refresh statistics for media item {Id}", mediaItem.Id); return(BaseError.New(ex.Message)); } }
public void Should_SetAudioDuration_With_CorrectFormat_ForTransportStream() { FFmpegProfile ffmpegProfile = TestProfile() with { AudioSampleRate = 48, AudioFormat = FFmpegProfileAudioFormat.Ac3 }; var version = new MediaVersion { SampleAspectRatio = "1:1", Width = 1920, Height = 1080, Duration = TimeSpan.FromMinutes(5) }; // not pulled from here FFmpegPlaybackSettings actual = _calculator.CalculateSettings( StreamingMode.TransportStream, ffmpegProfile, version, new MediaStream(), new MediaStream { Codec = "ac3" }, DateTimeOffset.Now, DateTimeOffset.Now, TimeSpan.Zero, TimeSpan.FromMinutes(2), false, None); actual.AudioDuration.IfNone(TimeSpan.MinValue).Should().Be(TimeSpan.FromMinutes(2)); }
public void Should_SetCopyVideoFormat_When_ContentIsCorrectSize_ForHttpLiveStreamingDirect() { var ffmpegProfile = new FFmpegProfile { Resolution = new Resolution { Width = 1920, Height = 1080 }, VideoFormat = FFmpegProfileVideoFormat.H264 }; // not anamorphic var version = new MediaVersion { Width = 1920, Height = 1080, SampleAspectRatio = "1:1" }; FFmpegPlaybackSettings actual = _calculator.CalculateSettings( StreamingMode.HttpLiveStreamingDirect, ffmpegProfile, version, new MediaStream { Codec = "mpeg2video" }, new MediaStream(), DateTimeOffset.Now, DateTimeOffset.Now, TimeSpan.Zero, TimeSpan.Zero, false, None); actual.ScaledSize.IsNone.Should().BeTrue(); actual.PadToDesiredResolution.Should().BeFalse(); actual.VideoFormat.Should().Be(FFmpegProfileVideoFormat.Copy); }
public void Should_SetVideoBufferSize_When_ContentIsCorrectSize_ForTransportStream() { var ffmpegProfile = new FFmpegProfile { Resolution = new Resolution { Width = 1920, Height = 1080 }, VideoBufferSize = 2525 }; // not anamorphic var version = new MediaVersion { Width = 1920, Height = 1080, SampleAspectRatio = "1:1" }; FFmpegPlaybackSettings actual = _calculator.CalculateSettings( StreamingMode.TransportStream, ffmpegProfile, version, new MediaStream { Codec = "mpeg2video" }, new MediaStream(), DateTimeOffset.Now, DateTimeOffset.Now, TimeSpan.Zero, TimeSpan.Zero, false, None); actual.ScaledSize.IsNone.Should().BeTrue(); actual.PadToDesiredResolution.Should().BeFalse(); actual.VideoBufferSize.IfNone(0).Should().Be(2525); }
public void Should_NotPadToDesiredResolution_When_UnscaledContentIsUnderSized_ForHttpLiveStreaming() { FFmpegProfile ffmpegProfile = TestProfile() with { Resolution = new Resolution { Width = 1920, Height = 1080 } }; // not anamorphic var version = new MediaVersion { Width = 1918, Height = 1080, SampleAspectRatio = "1:1" }; FFmpegPlaybackSettings actual = _calculator.CalculateSettings( StreamingMode.HttpLiveStreamingDirect, ffmpegProfile, version, new MediaStream(), new MediaStream(), DateTimeOffset.Now, DateTimeOffset.Now, TimeSpan.Zero, TimeSpan.Zero, false, None); actual.ScaledSize.IsNone.Should().BeTrue(); actual.PadToDesiredResolution.Should().BeFalse(); }
public void Should_SetDesiredVideoFormat_When_ContentIsPadded_ForTransportStream() { var ffmpegProfile = new FFmpegProfile { Resolution = new Resolution { Width = 1920, Height = 1080 }, VideoFormat = FFmpegProfileVideoFormat.H264 }; // not anamorphic var version = new MediaVersion { Width = 1918, Height = 1080, SampleAspectRatio = "1:1" }; FFmpegPlaybackSettings actual = _calculator.CalculateSettings( StreamingMode.TransportStream, ffmpegProfile, version, new MediaStream(), new MediaStream(), DateTimeOffset.Now, DateTimeOffset.Now, TimeSpan.Zero, TimeSpan.Zero, false, None); actual.ScaledSize.IsNone.Should().BeTrue(); actual.PadToDesiredResolution.Should().BeTrue(); actual.VideoFormat.Should().Be(FFmpegProfileVideoFormat.H264); }
public void ShouldNot_SetScaledSize_When_ScaledSizeWouldEqualContentSize_ForTransportStream() { FFmpegProfile ffmpegProfile = TestProfile() with { Resolution = new Resolution { Width = 1920, Height = 1080 } }; // not anamorphic var version = new MediaVersion { Width = 1918, Height = 1080, SampleAspectRatio = "1:1" }; FFmpegPlaybackSettings actual = _calculator.CalculateSettings( StreamingMode.TransportStream, ffmpegProfile, version, new MediaStream(), new MediaStream(), DateTimeOffset.Now, DateTimeOffset.Now, TimeSpan.Zero, TimeSpan.Zero, false, None); actual.ScaledSize.IsNone.Should().BeTrue(); }
public void Should_ScaleToEvenDimensions_ForTransportStream() { FFmpegProfile ffmpegProfile = TestProfile() with { Resolution = new Resolution { Width = 1280, Height = 720 } }; var version = new MediaVersion { Width = 706, Height = 362, SampleAspectRatio = "32:27" }; FFmpegPlaybackSettings actual = _calculator.CalculateSettings( StreamingMode.TransportStream, ffmpegProfile, version, new MediaStream(), new MediaStream(), DateTimeOffset.Now, DateTimeOffset.Now, TimeSpan.Zero, TimeSpan.Zero, false, None); IDisplaySize scaledSize = actual.ScaledSize.IfNone(new MediaVersion { Width = 0, Height = 0 }); scaledSize.Width.Should().Be(1280); scaledSize.Height.Should().Be(554); actual.PadToDesiredResolution.Should().BeTrue(); }
private async Task <string> GetPlayoutItemPath(PlayoutItem playoutItem) { MediaVersion version = playoutItem.MediaItem.GetHeadVersion(); MediaFile file = version.MediaFiles.Head(); string path = file.Path; return(playoutItem.MediaItem switch { PlexMovie plexMovie => await _plexPathReplacementService.GetReplacementPlexPath( plexMovie.LibraryPathId, path), PlexEpisode plexEpisode => await _plexPathReplacementService.GetReplacementPlexPath( plexEpisode.LibraryPathId, path), JellyfinMovie jellyfinMovie => await _jellyfinPathReplacementService.GetReplacementJellyfinPath( jellyfinMovie.LibraryPathId, path), JellyfinEpisode jellyfinEpisode => await _jellyfinPathReplacementService.GetReplacementJellyfinPath( jellyfinEpisode.LibraryPathId, path), EmbyMovie embyMovie => await _embyPathReplacementService.GetReplacementEmbyPath( embyMovie.LibraryPathId, path), EmbyEpisode embyEpisode => await _embyPathReplacementService.GetReplacementEmbyPath( embyEpisode.LibraryPathId, path), _ => path });
public async Task <Either <BaseError, bool> > RefreshStatistics( string ffmpegPath, string ffprobePath, MediaItem mediaItem, string mediaItemPath) { try { Either <BaseError, FFprobe> maybeProbe = await GetProbeOutput(ffprobePath, mediaItemPath); return(await maybeProbe.Match( async ffprobe => { MediaVersion version = ProjectToMediaVersion(mediaItemPath, ffprobe); if (version.Duration.TotalSeconds < 1) { await AnalyzeDuration(ffmpegPath, mediaItemPath, version); } bool result = await ApplyVersionUpdate(mediaItem, version, mediaItemPath); return Right <BaseError, bool>(result); }, error => Task.FromResult(Left <BaseError, bool>(error)))); } catch (Exception ex) { _logger.LogWarning(ex, "Failed to refresh statistics for media item {Id}", mediaItem.Id); _client.Notify(ex); return(BaseError.New(ex.Message)); } }
private async Task <Either <BaseError, PlexEpisode> > UpdateStatistics( PlexEpisode existing, PlexEpisode incoming, PlexConnection connection, PlexServerAuthToken token) { MediaVersion existingVersion = existing.MediaVersions.Head(); MediaVersion incomingVersion = incoming.MediaVersions.Head(); if (incomingVersion.DateUpdated > existingVersion.DateUpdated || string.IsNullOrWhiteSpace(existingVersion.SampleAspectRatio)) { Either <BaseError, MediaVersion> maybeStatistics = await _plexServerApiClient.GetStatistics(incoming.Key.Split("/").Last(), connection, token); await maybeStatistics.Match( async mediaVersion => { existingVersion.SampleAspectRatio = mediaVersion.SampleAspectRatio ?? "1:1"; existingVersion.VideoScanKind = mediaVersion.VideoScanKind; existingVersion.DateUpdated = incomingVersion.DateUpdated; await _metadataRepository.UpdatePlexStatistics(existingVersion); }, _ => Task.CompletedTask); } return(Right <BaseError, PlexEpisode>(existing)); }
public void Should_NotPadToDesiredResolution_When_NotNormalizingResolution() { FFmpegProfile ffmpegProfile = TestProfile() with { NormalizeResolution = false, Resolution = new Resolution { Width = 1920, Height = 1080 } }; // not anamorphic var version = new MediaVersion { Width = 1918, Height = 1080, SampleAspectRatio = "1:1" }; FFmpegPlaybackSettings actual = _calculator.CalculateSettings( StreamingMode.TransportStream, ffmpegProfile, version, DateTimeOffset.Now, DateTimeOffset.Now); actual.ScaledSize.IsNone.Should().BeTrue(); actual.PadToDesiredResolution.Should().BeFalse(); }
/// <summary> /// Adds or updates the given model in the database /// depending on its state. /// </summary> /// <param name="content">The content to save</param> public async Task Save(Models.Media model) { var media = await _db.Media .Include(m => m.Versions) .FirstOrDefaultAsync(m => m.Id == model.Id) .ConfigureAwait(false); if (media == null) { media = new Media() { Id = model.Id, Created = DateTime.Now }; await _db.Media.AddAsync(media).ConfigureAwait(false); } media.Filename = model.Filename; media.FolderId = model.FolderId; media.Type = model.Type; media.Size = model.Size; media.Width = model.Width; media.Height = model.Height; media.ContentType = model.ContentType; media.LastModified = DateTime.Now; // Delete removed versions var current = model.Versions.Select(v => v.Id).ToArray(); var removed = media.Versions.Where(v => !current.Contains(v.Id)).ToArray(); if (removed.Length > 0) { _db.MediaVersions.RemoveRange(removed); } // Add new versions foreach (var version in model.Versions) { if (media.Versions.All(v => v.Id != version.Id)) { var mediaVersion = new MediaVersion { Id = version.Id, MediaId = media.Id, Size = version.Size, Width = version.Width, Height = version.Height, FileExtension = version.FileExtension }; _db.MediaVersions.Add(mediaVersion); media.Versions.Add(mediaVersion); } } // Save all changes await _db.SaveChangesAsync().ConfigureAwait(false); }
private async Task <bool> ApplyVersionUpdate(MediaItem mediaItem, MediaVersion version, string filePath) { MediaVersion mediaItemVersion = mediaItem.GetHeadVersion(); bool durationChange = mediaItemVersion.Duration != version.Duration; version.DateUpdated = _localFileSystem.GetLastWriteTime(filePath); return(await _metadataRepository.UpdateLocalStatistics(mediaItem, version) && durationChange); }
// ************************************** // GetContentList // ************************************** public static IList<RemoteContent> GetContentList(MediaVersion version, string filter = null) { var mediaFolder = GetContentPrefix(version); var s3objects = GetBucketList(mediaFolder, RemoteMediaConfiguration.BucketName, filter); return s3objects.Select(x => new RemoteContent() { Key = x.Key, LastUpdatedOn = DateTime.Parse(x.LastModified), Size = x.Size }).ToList(); }
public Task <bool> UpdatePlexStatistics(MediaVersion mediaVersion) => _dbConnection.ExecuteAsync( @"UPDATE MediaVersion SET SampleAspectRatio = @SampleAspectRatio, VideoScanKind = @VideoScanKind, DateUpdated = @DateUpdated WHERE Id = @MediaVersionId", new { mediaVersion.SampleAspectRatio, mediaVersion.VideoScanKind, mediaVersion.DateUpdated, MediaVersionId = mediaVersion.Id }).Map(result => result > 0);
private async Task AddLanguages(ISearchRepository searchRepository, Document doc, List <MediaVersion> mediaVersions) { Option <MediaVersion> maybeVersion = mediaVersions.HeadOrNone(); if (maybeVersion.IsSome) { MediaVersion version = maybeVersion.ValueUnsafe(); var mediaCodes = version.Streams .Filter(ms => ms.MediaStreamKind == MediaStreamKind.Audio) .Map(ms => ms.Language).Distinct() .ToList(); await AddLanguages(searchRepository, doc, mediaCodes); } }
public void Test() { var provider = new LocalStatisticsProvider( new Mock <IMetadataRepository>().Object, new Mock <ILocalFileSystem>().Object, new Mock <IClient>().Object, new Mock <ILogger <LocalStatisticsProvider> >().Object); var input = new LocalStatisticsProvider.FFprobe( new LocalStatisticsProvider.FFprobeFormat("123.45", null), new List <LocalStatisticsProvider.FFprobeStream>(), new List <LocalStatisticsProvider.FFprobeChapter>()); MediaVersion result = provider.ProjectToMediaVersion("test", input); result.Duration.Should().Be(TimeSpan.FromSeconds(123.45)); }
private async Task <string> GetPlayoutItemPath(PlayoutItem playoutItem) { MediaVersion version = playoutItem.MediaItem switch { Movie m => m.MediaVersions.Head(), Episode e => e.MediaVersions.Head(), _ => throw new ArgumentOutOfRangeException(nameof(playoutItem)) }; MediaFile file = version.MediaFiles.Head(); string path = file.Path; return(playoutItem.MediaItem switch { PlexMovie plexMovie => await GetReplacementPlexPath(plexMovie.LibraryPathId, path), PlexEpisode plexEpisode => await GetReplacementPlexPath(plexEpisode.LibraryPathId, path), _ => path });
public void Should_SetAudioBufferSize_When_NormalizingWrongCodec_ForTransportStream() { FFmpegProfile ffmpegProfile = TestProfile() with { NormalizeAudioCodec = true, AudioBufferSize = 2424 }; var version = new MediaVersion { AudioCodec = "ac3" }; FFmpegPlaybackSettings actual = _calculator.CalculateSettings( StreamingMode.TransportStream, ffmpegProfile, version, DateTimeOffset.Now, DateTimeOffset.Now); actual.AudioBufferSize.IfNone(0).Should().Be(2424); }
public void Should_SetCopyAudioCodec_When_NormalizingWrongCodec_ForHttpLiveStreaming() { FFmpegProfile ffmpegProfile = TestProfile() with { NormalizeAudioCodec = true, AudioCodec = "aac" }; var version = new MediaVersion { AudioCodec = "ac3" }; FFmpegPlaybackSettings actual = _calculator.CalculateSettings( StreamingMode.HttpLiveStreaming, ffmpegProfile, version, DateTimeOffset.Now, DateTimeOffset.Now); actual.AudioCodec.Should().Be("copy"); }
public void ShouldNot_SetAudioSampleRate_When_CorrectCodec_ForTransportStream() { FFmpegProfile ffmpegProfile = TestProfile() with { NormalizeAudioCodec = true, NormalizeAudio = true, AudioCodec = "ac3", AudioSampleRate = 48 }; var version = new MediaVersion { AudioCodec = "ac3" }; FFmpegPlaybackSettings actual = _calculator.CalculateSettings( StreamingMode.TransportStream, ffmpegProfile, version, DateTimeOffset.Now, DateTimeOffset.Now); actual.AudioSampleRate.IsNone.Should().BeTrue(); }
private MediaVersion ProjectToMediaVersion(FFprobe probeOutput) => Optional(probeOutput) .Filter(json => json?.format != null && json.streams != null) .ToValidation <BaseError>("Unable to parse ffprobe output") .ToEither <FFprobe>() .Match( json => { var duration = TimeSpan.FromSeconds(double.Parse(json.format.duration)); var version = new MediaVersion { Name = "Main", Duration = duration }; FFprobeStream audioStream = json.streams.FirstOrDefault(s => s.codec_type == "audio"); if (audioStream != null) { version.AudioCodec = audioStream.codec_name; } FFprobeStream videoStream = json.streams.FirstOrDefault(s => s.codec_type == "video"); if (videoStream != null) { version.SampleAspectRatio = videoStream.sample_aspect_ratio; version.DisplayAspectRatio = videoStream.display_aspect_ratio; version.Width = videoStream.width; version.Height = videoStream.height; version.VideoCodec = videoStream.codec_name; version.VideoProfile = (videoStream.profile ?? string.Empty).ToLowerInvariant(); version.VideoScanKind = ScanKindFromFieldOrder(videoStream.field_order); } return(version); }, _ => new MediaVersion { Name = "Main" });
public void ShouldNot_SetScaledSize_When_ContentIsCorrectSize_ForTransportStream() { FFmpegProfile ffmpegProfile = TestProfile() with { NormalizeResolution = true, Resolution = new Resolution { Width = 1920, Height = 1080 } }; // not anamorphic var version = new MediaVersion { Width = 1920, Height = 1080, SampleAspectRatio = "1:1" }; FFmpegPlaybackSettings actual = _calculator.CalculateSettings( StreamingMode.TransportStream, ffmpegProfile, version, DateTimeOffset.Now, DateTimeOffset.Now); actual.ScaledSize.IsNone.Should().BeTrue(); }
private async Task <bool> ApplyVersionUpdate(MediaItem mediaItem, MediaVersion version, string filePath) { MediaVersion mediaItemVersion = mediaItem switch { Movie m => m.MediaVersions.Head(), Episode e => e.MediaVersions.Head(), _ => throw new ArgumentOutOfRangeException(nameof(mediaItem)) }; bool durationChange = mediaItemVersion.Duration != version.Duration; mediaItemVersion.DateUpdated = _localFileSystem.GetLastWriteTime(filePath); mediaItemVersion.Duration = version.Duration; mediaItemVersion.AudioCodec = version.AudioCodec; mediaItemVersion.SampleAspectRatio = version.SampleAspectRatio; mediaItemVersion.DisplayAspectRatio = version.DisplayAspectRatio; mediaItemVersion.Width = version.Width; mediaItemVersion.Height = version.Height; mediaItemVersion.VideoCodec = version.VideoCodec; mediaItemVersion.VideoProfile = version.VideoProfile; mediaItemVersion.VideoScanKind = version.VideoScanKind; return(await _metadataRepository.UpdateLocalStatistics(mediaItemVersion) && durationChange); }
public virtual ActionResult StreamUrl(int id, MediaVersion version = MediaVersion.Preview) { try { var contentType = "application/mp3"; Response.ContentType = contentType; var mediaUrl = GetUrl(id, version); if (mediaUrl != null){ return Redirect(mediaUrl); } else { var msg = "You do not have access to this file"; this.FeedbackError(msg); var ex = new AccessViolationException(msg); Log.Error(ex); return RedirectToAction(MVC.Error.Problem()); } } catch (Exception ex) { Log.Error(ex); this.FeedbackError("There was an error loading this page. Please try again in a bit."); return RedirectToAction(MVC.Search.Index()); } }
// ************************************** // Stream // ************************************** //[OutputCache(Duration = 60, VaryByParam = "id;version")] private string GetUrl(int id, MediaVersion version = MediaVersion.Preview) { var content = SearchService.GetContent(id, Account.User()); var contentMedia = content.ContentMedia.SingleOrDefault(x => x.MediaVersion == (int)version); if (content != null) { return MediaService.GetContentMediaPath(contentMedia); } else { return null; } }
public static ContentMedia Media(this Content content, MediaVersion version) { return content.ContentMedia != null ? content.ContentMedia.SingleOrDefault(x => x.MediaVersion == (int)version) : null; }
//// ************************************** //// Download //// ************************************** //public virtual ActionResult DownloadUrl(int id) { // try { // var contentType = "application/unknown"; // Response.ContentType = contentType; // var mediaUrl = GetUrl(id, MediaVersion.Full); // if (mediaUrl != null) { // return Redirect(mediaUrl); // } else { // var msg = "You do not have access to this file"; // this.FeedbackError(msg); // return RedirectToAction(MVC.Error.Index(new AccessViolationException(msg), msg, "Media", "Stream")); // } // } // catch { // this.FeedbackError("There was an error loading this page. Please try again in a bit."); // return RedirectToAction(MVC.Search.Index()); // } //} //// ************************************** //// Download //// ************************************** ////[OutputCache(Duration = 60, VaryByParam = "id;version")] //public virtual ActionResult Get(int id, MediaVersion version = MediaVersion.Preview) { // var user = Account.User(); // var content = SearchService.GetContent(id, user); // if (content != null) { // var downloadName = String.Concat(content.UserDownloadableName, SystemConfig.MediaDefaultExtension); // var contentType = "application/unknown"; // Response.ContentType = contentType; // if (SystemConfig.UseRemoteMedia) { // var url = GetUrl(id, version); // return base.File(url, contentType, downloadName); // } else { // var media = _mediaService.GetContentMedia(id, (MediaVersion)version, user); // return base.File(media, contentType, downloadName); // } // } else { // var msg = "You do not have access to this file"; // this.FeedbackError(msg); // return RedirectToAction(MVC.Error.Index(new AccessViolationException(msg), msg, "Media", "Download" )); // } //} // ************************************** // Stream // ************************************** //[OutputCache(Duration = 60, VaryByParam = "id;version")] public virtual ActionResult Stream(int id, MediaVersion version = MediaVersion.Preview) { try { var content = SearchService.GetContent(id, Account.User()); var contentMedia = content.ContentMedia.SingleOrDefault(x => x.MediaVersion == (int)version); if (content != null) { if (SystemConfig.UseRemoteMedia && content.IsMediaOnRemoteServer) { return RedirectToAction(Actions.StreamUrl(id, version)); } else { var mediaPath = MediaService.GetContentMediaPath(contentMedia); var contentType = "application/mp3"; Response.ContentType = contentType; return new FileStreamResult(new FileStream(mediaPath, System.IO.FileMode.Open), contentType); } } else { var msg = "You do not have access to this file"; this.FeedbackError(msg); var ex = new AccessViolationException(msg); Log.Error(ex); return RedirectToAction(MVC.Error.Problem()); } } catch (Exception ex) { Log.Error(ex); this.FeedbackError("There was an error loading this page. Please try again in a bit."); return RedirectToAction(MVC.Search.Index()); } }
// ************************************** // GetContentPrefix // ************************************** public static string GetContentPrefix(MediaVersion version) { return String.Format(SystemConfig.MediaFolderUrlFormat, version.ToString()); }
protected override async Task <Either <BaseError, PlayoutItemProcessModel> > GetProcess( TvContext dbContext, GetPlayoutItemProcessByChannelNumber request, Channel channel, string ffmpegPath, string ffprobePath, CancellationToken cancellationToken) { DateTimeOffset now = request.Now; Either <BaseError, PlayoutItemWithPath> maybePlayoutItem = await dbContext.PlayoutItems .Include(i => i.MediaItem) .ThenInclude(mi => (mi as Episode).EpisodeMetadata) .ThenInclude(em => em.Subtitles) .Include(i => i.MediaItem) .ThenInclude(mi => (mi as Episode).MediaVersions) .ThenInclude(mv => mv.MediaFiles) .Include(i => i.MediaItem) .ThenInclude(mi => (mi as Episode).MediaVersions) .ThenInclude(mv => mv.Streams) .Include(i => i.MediaItem) .ThenInclude(mi => (mi as Movie).MovieMetadata) .ThenInclude(mm => mm.Subtitles) .Include(i => i.MediaItem) .ThenInclude(mi => (mi as Movie).MediaVersions) .ThenInclude(mv => mv.MediaFiles) .Include(i => i.MediaItem) .ThenInclude(mi => (mi as Movie).MediaVersions) .ThenInclude(mv => mv.Streams) .Include(i => i.MediaItem) .ThenInclude(mi => (mi as MusicVideo).MusicVideoMetadata) .ThenInclude(mvm => mvm.Subtitles) .Include(i => i.MediaItem) .ThenInclude(mi => (mi as MusicVideo).MediaVersions) .ThenInclude(mv => mv.MediaFiles) .Include(i => i.MediaItem) .ThenInclude(mi => (mi as MusicVideo).MediaVersions) .ThenInclude(mv => mv.Streams) .Include(i => i.MediaItem) .ThenInclude(mi => (mi as OtherVideo).OtherVideoMetadata) .ThenInclude(ovm => ovm.Subtitles) .Include(i => i.MediaItem) .ThenInclude(mi => (mi as OtherVideo).MediaVersions) .ThenInclude(ov => ov.MediaFiles) .Include(i => i.MediaItem) .ThenInclude(mi => (mi as OtherVideo).MediaVersions) .ThenInclude(ov => ov.Streams) .Include(i => i.MediaItem) .ThenInclude(mi => (mi as Song).MediaVersions) .ThenInclude(mv => mv.MediaFiles) .Include(i => i.MediaItem) .ThenInclude(mi => (mi as Song).MediaVersions) .ThenInclude(mv => mv.Streams) .Include(i => i.MediaItem) .ThenInclude(mi => (mi as Song).SongMetadata) .ThenInclude(sm => sm.Artwork) .Include(i => i.Watermark) .ForChannelAndTime(channel.Id, now) .Map(o => o.ToEither <BaseError>(new UnableToLocatePlayoutItem())) .BindT(ValidatePlayoutItemPath); if (maybePlayoutItem.LeftAsEnumerable().Any(e => e is UnableToLocatePlayoutItem)) { maybePlayoutItem = await CheckForFallbackFiller(dbContext, channel, now); } foreach (PlayoutItemWithPath playoutItemWithPath in maybePlayoutItem.RightToSeq()) { MediaVersion version = playoutItemWithPath.PlayoutItem.MediaItem.GetHeadVersion(); string videoPath = playoutItemWithPath.Path; MediaVersion videoVersion = version; string audioPath = playoutItemWithPath.Path; MediaVersion audioVersion = version; Option <ChannelWatermark> maybeGlobalWatermark = await dbContext.ConfigElements .GetValue <int>(ConfigElementKey.FFmpegGlobalWatermarkId) .BindT( watermarkId => dbContext.ChannelWatermarks .SelectOneAsync(w => w.Id, w => w.Id == watermarkId)); if (playoutItemWithPath.PlayoutItem.MediaItem is Song song) { (videoPath, videoVersion) = await _songVideoGenerator.GenerateSongVideo( song, channel, Optional(playoutItemWithPath.PlayoutItem.Watermark), maybeGlobalWatermark, ffmpegPath, ffprobePath, cancellationToken); } bool saveReports = await dbContext.ConfigElements .GetValue <bool>(ConfigElementKey.FFmpegSaveReports) .Map(result => result.IfNone(false)); List <Subtitle> subtitles = GetSubtitles(playoutItemWithPath); Command process = await _ffmpegProcessService.ForPlayoutItem( ffmpegPath, ffprobePath, saveReports, channel, videoVersion, audioVersion, videoPath, audioPath, subtitles, playoutItemWithPath.PlayoutItem.PreferredAudioLanguageCode ?? channel.PreferredAudioLanguageCode, playoutItemWithPath.PlayoutItem.PreferredSubtitleLanguageCode ?? channel.PreferredSubtitleLanguageCode, playoutItemWithPath.PlayoutItem.SubtitleMode ?? channel.SubtitleMode, playoutItemWithPath.PlayoutItem.StartOffset, playoutItemWithPath.PlayoutItem.FinishOffset, request.StartAtZero?playoutItemWithPath.PlayoutItem.StartOffset : now, Optional(playoutItemWithPath.PlayoutItem.Watermark), maybeGlobalWatermark, channel.FFmpegProfile.VaapiDriver, channel.FFmpegProfile.VaapiDevice, request.HlsRealtime, playoutItemWithPath.PlayoutItem.FillerKind, playoutItemWithPath.PlayoutItem.InPoint, playoutItemWithPath.PlayoutItem.OutPoint, request.PtsOffset, request.TargetFramerate); var result = new PlayoutItemProcessModel( process, playoutItemWithPath.PlayoutItem.FinishOffset - (request.StartAtZero ? playoutItemWithPath.PlayoutItem.StartOffset : now), playoutItemWithPath.PlayoutItem.FinishOffset); return(Right <BaseError, PlayoutItemProcessModel>(result)); } foreach (BaseError error in maybePlayoutItem.LeftToSeq()) { Option <TimeSpan> maybeDuration = await dbContext.PlayoutItems .Filter(pi => pi.Playout.ChannelId == channel.Id) .Filter(pi => pi.Start > now.UtcDateTime) .OrderBy(pi => pi.Start) .FirstOrDefaultAsync(cancellationToken) .Map(Optional) .MapT(pi => pi.StartOffset - now); DateTimeOffset finish = maybeDuration.Match(d => now.Add(d), () => now); switch (error) { case UnableToLocatePlayoutItem: Command offlineProcess = await _ffmpegProcessService.ForError( ffmpegPath, channel, maybeDuration, "Channel is Offline", request.HlsRealtime, request.PtsOffset); return(new PlayoutItemProcessModel(offlineProcess, maybeDuration, finish)); case PlayoutItemDoesNotExistOnDisk: Command doesNotExistProcess = await _ffmpegProcessService.ForError( ffmpegPath, channel, maybeDuration, error.Value, request.HlsRealtime, request.PtsOffset); return(new PlayoutItemProcessModel(doesNotExistProcess, maybeDuration, finish)); default: Command errorProcess = await _ffmpegProcessService.ForError( ffmpegPath, channel, maybeDuration, "Channel is Offline", request.HlsRealtime, request.PtsOffset); return(new PlayoutItemProcessModel(errorProcess, maybeDuration, finish)); } } return(BaseError.New($"Unexpected error locating playout item for channel {channel.Number}")); }
private async Task <Either <BaseError, PlayoutItemWithPath> > CheckForFallbackFiller( TvContext dbContext, Channel channel, DateTimeOffset now) { // check for channel fallback Option <FillerPreset> maybeFallback = await dbContext.FillerPresets .SelectOneAsync(w => w.Id, w => w.Id == channel.FallbackFillerId); // then check for global fallback if (maybeFallback.IsNone) { maybeFallback = await dbContext.ConfigElements .GetValue <int>(ConfigElementKey.FFmpegGlobalFallbackFillerId) .BindT(fillerId => dbContext.FillerPresets.SelectOneAsync(w => w.Id, w => w.Id == fillerId)); } foreach (FillerPreset fallbackPreset in maybeFallback) { // turn this into a playout item var collectionKey = CollectionKey.ForFillerPreset(fallbackPreset); List <MediaItem> items = await MediaItemsForCollection.Collect( _mediaCollectionRepository, _televisionRepository, _artistRepository, collectionKey); // TODO: shuffle? does it really matter since we loop anyway MediaItem item = items[new Random().Next(items.Count)]; Option <TimeSpan> maybeDuration = await dbContext.PlayoutItems .Filter(pi => pi.Playout.ChannelId == channel.Id) .Filter(pi => pi.Start > now.UtcDateTime) .OrderBy(pi => pi.Start) .FirstOrDefaultAsync() .Map(Optional) .MapT(pi => pi.StartOffset - now); MediaVersion version = item.GetHeadVersion(); version.MediaFiles = await dbContext.MediaFiles .AsNoTracking() .Filter(mf => mf.MediaVersionId == version.Id) .ToListAsync(); version.Streams = await dbContext.MediaStreams .AsNoTracking() .Filter(ms => ms.MediaVersionId == version.Id) .ToListAsync(); DateTimeOffset finish = maybeDuration.Match( // next playout item exists // loop until it starts now.Add, // no next playout item exists // loop for 5 minutes if less than 30s, otherwise play full item () => version.Duration < TimeSpan.FromSeconds(30) ? now.AddMinutes(5) : now.Add(version.Duration)); var playoutItem = new PlayoutItem { MediaItem = item, MediaItemId = item.Id, Start = now.UtcDateTime, Finish = finish.UtcDateTime, FillerKind = FillerKind.Fallback, InPoint = TimeSpan.Zero, OutPoint = version.Duration }; return(await ValidatePlayoutItemPath(playoutItem)); } return(new UnableToLocatePlayoutItem()); }
public static IList<UploadFile> GetUploadFiles(this IList<string> tempFiles, MediaVersion mediaVersion) { var uploadFiles = new List<UploadFile>(); tempFiles.ForEach(f => uploadFiles.Add(new UploadFile() { FileMediaVersion = mediaVersion, FilePath = f, FileName = Path.GetFileName(f) }) ); return uploadFiles; }
public static void Upload(MediaVersion version) { using (_amazon = new AmazonCloudService()) { using (var session = new SongSearchDataSession()) { var contents = session.All<Content>().ToList(); var remoteContents = _amazon.GetContentList(version); var remoteFolder = _amazon.GetContentPrefix(version); foreach (var content in contents) { var dbContent = session.Single<Content>(c => c.ContentId == content.ContentId); var key = _amazon.GetContentKey(content.Media(version)); var filePath = Path.Combine(version == MediaVersion.Full ? SystemConfig.MediaPathFull : SystemConfig.MediaPathPreview, String.Concat(content.ContentId,SystemConfig.MediaDefaultExtension) ); var file = new FileInfo(filePath); if (file.Exists) { var remoteFile = remoteContents.SingleOrDefault(x => x.Key == key && x.Size == file.Length); if (remoteFile == null) { try { Console.WriteLine("Uploading " + file.Name + " to " + remoteFolder); _amazon.PutContentMedia(file.FullName, content.Media(version)); dbContent.IsMediaOnRemoteServer = true; } catch (Exception ex){ Console.WriteLine("FAILED: " + file.Name + "-------------------"); Console.WriteLine("ERROR: " + ex.Message); dbContent.IsMediaOnRemoteServer = false; } } else { dbContent.IsMediaOnRemoteServer = true; } } else { dbContent.IsMediaOnRemoteServer = false; } session.CommitChanges(); } Console.WriteLine("Done uploading"); } } }
// ************************************** // MoveToMediaVersionFolder // ************************************** private IList<UploadFile> MoveToMediaVersionFolder(List<string> files, MediaVersion mediaVersion) { var newFiles = new List<string>(); var user = Account.User(); foreach (var file in files) { var filePath = user.UploadFile(fileName: file); if (File.Exists(filePath)) { var newFilePath = user.UploadFile(file, mediaVersion.ToString()); if (File.Exists(newFilePath)) { File.Delete(newFilePath); } File.Move(filePath, newFilePath); newFiles.Add(newFilePath); } } return newFiles.GetUploadFiles(mediaVersion); }
protected override async Task <Either <BaseError, Process> > GetProcess( GetPlayoutItemProcessByChannelNumber _, Channel channel, string ffmpegPath) { DateTimeOffset now = DateTimeOffset.Now; Either <BaseError, PlayoutItemWithPath> maybePlayoutItem = await _playoutRepository .GetPlayoutItem(channel.Id, now) .Map(o => o.ToEither <BaseError>(new UnableToLocatePlayoutItem())) .BindT(ValidatePlayoutItemPath); return(await maybePlayoutItem.Match( async playoutItemWithPath => { MediaVersion version = playoutItemWithPath.PlayoutItem.MediaItem switch { Movie m => m.MediaVersions.Head(), Episode e => e.MediaVersions.Head(), _ => throw new ArgumentOutOfRangeException(nameof(playoutItemWithPath)) }; bool saveReports = await _configElementRepository.GetValue <bool>(ConfigElementKey.FFmpegSaveReports) .Map(result => result.IfNone(false)); return Right <BaseError, Process>( _ffmpegProcessService.ForPlayoutItem( ffmpegPath, saveReports, channel, version, playoutItemWithPath.Path, playoutItemWithPath.PlayoutItem.StartOffset, now)); }, async error => { var offlineTranscodeMessage = $"offline image is unavailable because transcoding is disabled in ffmpeg profile '{channel.FFmpegProfile.Name}'"; Option <TimeSpan> maybeDuration = await Optional(channel.FFmpegProfile.Transcode) .Filter(transcode => transcode) .Match( _ => _playoutRepository.GetNextItemStart(channel.Id, now) .MapT(nextStart => nextStart - now), () => Option <TimeSpan> .None.AsTask()); switch (error) { case UnableToLocatePlayoutItem: if (channel.FFmpegProfile.Transcode) { return _ffmpegProcessService.ForError( ffmpegPath, channel, maybeDuration, "Channel is Offline"); } else { var message = $"Unable to locate playout item for channel {channel.Number}; {offlineTranscodeMessage}"; return BaseError.New(message); } case PlayoutItemDoesNotExistOnDisk: if (channel.FFmpegProfile.Transcode) { return _ffmpegProcessService.ForError(ffmpegPath, channel, maybeDuration, error.Value); } else { var message = $"Playout item does not exist on disk for channel {channel.Number}; {offlineTranscodeMessage}"; return BaseError.New(message); } default: if (channel.FFmpegProfile.Transcode) { return _ffmpegProcessService.ForError( ffmpegPath, channel, maybeDuration, "Channel is Offline"); } else { var message = $"Unexpected error locating playout item for channel {channel.Number}; {offlineTranscodeMessage}"; return BaseError.New(message); } } })); }
// ************************************** // UploadToRemote // ************************************** public static void UploadToRemote(bool checkSize = false, bool onlyNewContent = false, MediaVersion[] mediaVersions = null) { Log.Debug(String.Format("Starting Amazon upload at {0}", DateTime.Now)); using (var ctx = new SongSearchContext()) { var query = ctx.Contents.AsQueryable(); if (onlyNewContent) { query = query.Where(c => c.ContentMedia.Any(m => m.IsRemote == false)); } var contents = query.ToList(); if (contents.Count > 0) { Log.Debug("Getting remote file list"); var remoteList = GetRemoteFileList(mediaVersions ?? new MediaVersion[] { MediaVersion.Preview, MediaVersion.Full }); //var remoteFolder = AmazonCloudService.GetContentPrefix(version); Log.Debug("Comparing " + contents.Count + " content items"); foreach (var content in contents) { Log.Debug("Checking ContentId " + content.ContentId); var dbContent = ctx.Contents.SingleOrDefault(c => c.ContentId == content.ContentId); var contentMedia = dbContent.ContentMedia.ToList(); if (mediaVersions != null) { contentMedia = contentMedia.Where(c => mediaVersions.Contains((MediaVersion)c.MediaVersion)).ToList(); } foreach (var media in contentMedia) { var key = AmazonCloudService.GetContentKey(media); var remoteMedia = remoteList[(MediaVersion)media.MediaVersion]; var filePath = Path.Combine((MediaVersion)media.MediaVersion == MediaVersion.Full ? SystemConfig.MediaPathFull : SystemConfig.MediaPathPreview, String.Concat(content.ContentId, SystemConfig.MediaDefaultExtension) ); var file = new FileInfo(filePath); if (file.Exists) { var remoteFile = checkSize ? remoteMedia.SingleOrDefault(x => x.Key == key && x.Size == file.Length) : remoteMedia.SingleOrDefault(x => x.Key == key); if (remoteFile == null) { try { AmazonCloudService.PutContentMedia(file.FullName, media); Log.Debug(String.Format("Uploaded local file {0}", file.FullName)); media.IsRemote = true; } catch (Exception ex) { Log.Error(ex); media.IsRemote = false; continue; } } else { Log.Debug(String.Format("File for {0} matches", content.ContentId)); media.IsRemote = true; } } else { // file is not local, let's see if it's remote var remoteFile = remoteMedia.SingleOrDefault(x => x.Key == key); if (remoteFile != null) { //if (checkSize) { //RepushMedia(content, media, filePath, file); media.IsRemote = true; //} } else { media.IsRemote = false; } } } ctx.SaveChanges(); } // DataSession.CommitChanges(); Log.Debug(String.Format("Completed Amazon upload at {0}", DateTime.Now)); } } }
public async Task <string> EnsureVersionAsync(Media media, int width, int?height = null) { // Get the media type var type = App.MediaTypes.GetItem(media.Filename); // If this type doesn't allow processing, return the original url if (!type.AllowProcessing) { return(GetPublicUrl(media)); } // If the requested size is equal to the original size, return true if (media.Width == width && (!height.HasValue || media.Height == height.Value)) { return(GetPublicUrl(media)); } var query = media.Versions .Where(v => v.Width == width); query = height.HasValue ? query.Where(v => v.Height == height) : query.Where(v => !v.Height.HasValue); var version = query.FirstOrDefault(); if (version != null) { return(media.Width == width && (!height.HasValue || media.Height == height.Value) ? GetPublicUrl(media) : GetPublicUrl(media, width, height, version.FileExtension)); } // Get the image file using (var stream = new MemoryStream()) { using (var session = await _storage.OpenAsync().ConfigureAwait(false)) { if (!await session.GetAsync(media.Id + "-" + media.Filename, stream).ConfigureAwait(false)) { return(null); } // Reset strem position stream.Position = 0; using (var output = new MemoryStream()) { if (height.HasValue) { _processor.CropScale(stream, output, width, height.Value); } else { _processor.Scale(stream, output, width); } output.Position = 0; bool upload = false; lock (ScaleMutex) { // We have to make sure we don't scale multiple files // at the same time as it can create index violations. version = query.FirstOrDefault(); if (version == null) { var info = new FileInfo(media.Filename); version = new MediaVersion { Id = Guid.NewGuid(), Size = output.Length, Width = width, Height = height, FileExtension = info.Extension }; media.Versions.Add(version); _repo.Save(media).Wait(); RemoveFromCache(media); upload = true; } } if (upload) { return(await session.PutAsync(GetResourceName(media, width, height), media.ContentType, output) .ConfigureAwait(false)); } //When moving this out of its parent method, realized that if the mutex failed, it would just fall back to the null instead of trying to return the issue. //Added this to ensure that queries didn't just give up if they weren't the first to the party. return(GetPublicUrl(media, width, height, version.FileExtension)); } } } // If the requested size is equal to the original size, return true }
private static Dictionary<MediaVersion, IList<RemoteContent>> GetRemoteFileList(MediaVersion[] versions) { var remoteList = new Dictionary<MediaVersion, IList<RemoteContent>>(); versions.ForEach(v => remoteList.Add(v, AmazonCloudService.GetContentList(v)) ); return remoteList; }