From 24246ba85fef0e7667b9ab624874a91c28df8419 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 22 Mar 2014 14:25:03 -0400 Subject: [PATCH] support conditions with direct play profiles --- .../Dlna/DirectPlayProfile.cs | 6 +- MediaBrowser.Dlna/MediaBrowser.Dlna.csproj | 1 + MediaBrowser.Dlna/PlayTo/DlnaController.cs | 35 ++- MediaBrowser.Dlna/PlayTo/PlaylistItem.cs | 78 +---- .../PlayTo/PlaylistItemFactory.cs | 278 ++++++++++++++++++ MediaBrowser.Dlna/PlayTo/StreamHelper.cs | 2 +- .../FileOrganization/EpisodeFileOrganizer.cs | 26 +- .../FileOrganization/NameUtils.cs | 1 + 8 files changed, 332 insertions(+), 95 deletions(-) create mode 100644 MediaBrowser.Dlna/PlayTo/PlaylistItemFactory.cs diff --git a/MediaBrowser.Controller/Dlna/DirectPlayProfile.cs b/MediaBrowser.Controller/Dlna/DirectPlayProfile.cs index 68e25e2f7b..4b9a43867f 100644 --- a/MediaBrowser.Controller/Dlna/DirectPlayProfile.cs +++ b/MediaBrowser.Controller/Dlna/DirectPlayProfile.cs @@ -68,13 +68,15 @@ namespace MediaBrowser.Controller.Dlna public class ProfileCondition { public ProfileConditionType Condition { get; set; } - public ProfileConditionValue Value { get; set; } + public ProfileConditionValue Property { get; set; } + public string Value { get; set; } } public enum DlnaProfileType { Audio = 0, - Video = 1 + Video = 1, + Photo = 2 } public enum ProfileConditionType diff --git a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj index a7ee05cf32..800fb1b236 100644 --- a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj +++ b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj @@ -66,6 +66,7 @@ Code + diff --git a/MediaBrowser.Dlna/PlayTo/DlnaController.cs b/MediaBrowser.Dlna/PlayTo/DlnaController.cs index bda737ad2d..e94663802f 100644 --- a/MediaBrowser.Dlna/PlayTo/DlnaController.cs +++ b/MediaBrowser.Dlna/PlayTo/DlnaController.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Session; @@ -263,7 +264,7 @@ namespace MediaBrowser.Dlna.PlayTo case PlaystateCommand.Seek: var playlistItem = Playlist.FirstOrDefault(p => p.PlayState == 1); - if (playlistItem != null && playlistItem.Transcode && playlistItem.IsVideo && _currentItem != null) + if (playlistItem != null && playlistItem.Transcode && playlistItem.MediaType == DlnaProfileType.Video && _currentItem != null) { var newItem = CreatePlaylistItem(_currentItem, command.SeekPositionTicks ?? 0, GetServerAddress()); playlistItem.StartPositionTicks = newItem.StartPositionTicks; @@ -394,11 +395,13 @@ namespace MediaBrowser.Dlna.PlayTo var deviceInfo = _device.Properties; - var playlistItem = PlaylistItem.Create(item, _dlnaManager.GetProfile(deviceInfo.ToDeviceIdentification())); + var playlistItem = GetPlaylistItem(item, _dlnaManager.GetProfile(deviceInfo.ToDeviceIdentification())); playlistItem.StartPositionTicks = startPostionTicks; - if (playlistItem.IsAudio) + if (playlistItem.MediaType == DlnaProfileType.Audio) + { playlistItem.StreamUrl = StreamHelper.GetAudioUrl(playlistItem, serverAddress); + } else { playlistItem.StreamUrl = StreamHelper.GetVideoUrl(_device.Properties, playlistItem, streams, serverAddress); @@ -412,6 +415,32 @@ namespace MediaBrowser.Dlna.PlayTo return playlistItem; } + private PlaylistItem GetPlaylistItem(BaseItem item, DeviceProfile profile) + { + var video = item as Video; + + if (video != null) + { + return new PlaylistItemFactory(_itemRepository).Create(video, profile); + } + + var audio = item as Audio; + + if (audio != null) + { + return new PlaylistItemFactory(_itemRepository).Create(audio, profile); + } + + var photo = item as Photo; + + if (photo != null) + { + return new PlaylistItemFactory(_itemRepository).Create(photo, profile); + } + + throw new ArgumentException("Unrecognized item type."); + } + /// /// Plays the items. /// diff --git a/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs b/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs index 4f776807e2..3524575631 100644 --- a/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs +++ b/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs @@ -1,9 +1,4 @@ using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Entities; -using System; -using System.IO; -using System.Linq; namespace MediaBrowser.Dlna.PlayTo { @@ -11,11 +6,11 @@ namespace MediaBrowser.Dlna.PlayTo { public string ItemId { get; set; } + public string MediaSourceId { get; set; } + public bool Transcode { get; set; } - public bool IsVideo { get; set; } - - public bool IsAudio { get; set; } + public DlnaProfileType MediaType { get; set; } public string FileFormat { get; set; } @@ -30,72 +25,5 @@ namespace MediaBrowser.Dlna.PlayTo public string Didl { get; set; } public long StartPositionTicks { get; set; } - - public static PlaylistItem Create(BaseItem item, DeviceProfile profile) - { - var playlistItem = new PlaylistItem - { - ItemId = item.Id.ToString() - }; - - DlnaProfileType profileType; - if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) - { - playlistItem.IsVideo = true; - profileType = DlnaProfileType.Video; - } - else - { - playlistItem.IsAudio = true; - profileType = DlnaProfileType.Audio; - } - - var path = item.Path; - - var directPlay = profile.DirectPlayProfiles.FirstOrDefault(i => i.Type == profileType && IsSupported(i, path)); - - if (directPlay != null) - { - playlistItem.Transcode = false; - playlistItem.FileFormat = Path.GetExtension(path); - playlistItem.MimeType = directPlay.MimeType; - return playlistItem; - } - - var transcodingProfile = profile.TranscodingProfiles.FirstOrDefault(i => i.Type == profileType && IsSupported(profile, i, path)); - - if (transcodingProfile != null) - { - playlistItem.Transcode = true; - //Just to make sure we have a "." for the url, remove it in case a user adds it or not - playlistItem.FileFormat = "." + transcodingProfile.Container.TrimStart('.'); - - playlistItem.MimeType = transcodingProfile.MimeType; - } - - return playlistItem; - } - - private static bool IsSupported(DirectPlayProfile profile, string path) - { - var mediaContainer = Path.GetExtension(path); - - if (!profile.Containers.Any(i => string.Equals("." + i.TrimStart('.'), mediaContainer, StringComparison.OrdinalIgnoreCase))) - { - return false; - } - - // Placeholder for future conditions - - // TODO: Support codec list as additional restriction - - return true; - } - - private static bool IsSupported(DeviceProfile profile, TranscodingProfile transcodingProfile, string path) - { - // Placeholder for future conditions - return true; - } } } \ No newline at end of file diff --git a/MediaBrowser.Dlna/PlayTo/PlaylistItemFactory.cs b/MediaBrowser.Dlna/PlayTo/PlaylistItemFactory.cs new file mode 100644 index 0000000000..2eb51e2149 --- /dev/null +++ b/MediaBrowser.Dlna/PlayTo/PlaylistItemFactory.cs @@ -0,0 +1,278 @@ +using MediaBrowser.Controller.Dlna; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Entities; +using System; +using System.Globalization; +using System.IO; +using System.Linq; + +namespace MediaBrowser.Dlna.PlayTo +{ + public class PlaylistItemFactory + { + private readonly IItemRepository _itemRepo; + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + + public PlaylistItemFactory(IItemRepository itemRepo) + { + _itemRepo = itemRepo; + } + + public PlaylistItem Create(Audio item, DeviceProfile profile) + { + var playlistItem = new PlaylistItem + { + ItemId = item.Id.ToString("N"), + MediaType = DlnaProfileType.Audio + }; + + var mediaStreams = _itemRepo.GetMediaStreams(new MediaStreamQuery + { + ItemId = item.Id, + Type = MediaStreamType.Audio + }); + + var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); + + var directPlay = profile.DirectPlayProfiles + .FirstOrDefault(i => i.Type == playlistItem.MediaType && IsSupported(i, item, audioStream)); + + if (directPlay != null) + { + playlistItem.Transcode = false; + playlistItem.FileFormat = Path.GetExtension(item.Path); + playlistItem.MimeType = directPlay.MimeType; + + return playlistItem; + } + + var transcodingProfile = profile.TranscodingProfiles + .FirstOrDefault(i => i.Type == playlistItem.MediaType && IsSupported(profile, i, item)); + + if (transcodingProfile != null) + { + playlistItem.Transcode = true; + + playlistItem.FileFormat = "." + transcodingProfile.Container.TrimStart('.'); + playlistItem.MimeType = transcodingProfile.MimeType; + } + + return playlistItem; + } + + public PlaylistItem Create(Photo item, DeviceProfile profile) + { + var playlistItem = new PlaylistItem + { + ItemId = item.Id.ToString("N"), + MediaType = DlnaProfileType.Photo + }; + + var directPlay = profile.DirectPlayProfiles + .FirstOrDefault(i => i.Type == playlistItem.MediaType && IsSupported(i, item)); + + if (directPlay != null) + { + playlistItem.Transcode = false; + playlistItem.FileFormat = Path.GetExtension(item.Path); + playlistItem.MimeType = directPlay.MimeType; + + return playlistItem; + } + + var transcodingProfile = profile.TranscodingProfiles + .FirstOrDefault(i => i.Type == playlistItem.MediaType && IsSupported(profile, i, item)); + + if (transcodingProfile != null) + { + playlistItem.Transcode = true; + + playlistItem.FileFormat = "." + transcodingProfile.Container.TrimStart('.'); + playlistItem.MimeType = transcodingProfile.MimeType; + } + + return playlistItem; + } + + public PlaylistItem Create(Video item, DeviceProfile profile) + { + var playlistItem = new PlaylistItem + { + ItemId = item.Id.ToString("N"), + MediaType = DlnaProfileType.Video + }; + + var mediaStreams = _itemRepo.GetMediaStreams(new MediaStreamQuery + { + ItemId = item.Id + + }).ToList(); + + var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); + var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); + + var directPlay = profile.DirectPlayProfiles + .FirstOrDefault(i => i.Type == playlistItem.MediaType && IsSupported(i, item, videoStream, audioStream)); + + if (directPlay != null) + { + playlistItem.Transcode = false; + playlistItem.FileFormat = Path.GetExtension(item.Path); + playlistItem.MimeType = directPlay.MimeType; + + return playlistItem; + } + + var transcodingProfile = profile.TranscodingProfiles + .FirstOrDefault(i => i.Type == playlistItem.MediaType && IsSupported(profile, i, item)); + + if (transcodingProfile != null) + { + playlistItem.Transcode = true; + + playlistItem.FileFormat = "." + transcodingProfile.Container.TrimStart('.'); + playlistItem.MimeType = transcodingProfile.MimeType; + } + + return playlistItem; + } + + private bool IsSupported(DirectPlayProfile profile, Photo item) + { + var mediaPath = item.Path; + + var mediaContainer = Path.GetExtension(mediaPath); + + if (!profile.Containers.Any(i => string.Equals("." + i.TrimStart('.'), mediaContainer, StringComparison.OrdinalIgnoreCase))) + { + return false; + } + + if (!profile.Conditions.Any(i => IsConditionSatisfied(i, mediaPath, null, null))) + { + return false; + } + + return true; + } + + private bool IsSupported(DirectPlayProfile profile, Audio item, MediaStream audioStream) + { + var mediaPath = item.Path; + + var mediaContainer = Path.GetExtension(mediaPath); + + if (!profile.Containers.Any(i => string.Equals("." + i.TrimStart('.'), mediaContainer, StringComparison.OrdinalIgnoreCase))) + { + return false; + } + + if (!profile.Conditions.Any(i => IsConditionSatisfied(i, mediaPath, null, audioStream))) + { + return false; + } + + return true; + } + + private bool IsSupported(DirectPlayProfile profile, Video item, MediaStream videoStream, MediaStream audioStream) + { + if (item.VideoType != VideoType.VideoFile) + { + return false; + } + + var mediaPath = item.Path; + + var mediaContainer = Path.GetExtension(mediaPath); + + if (!profile.Containers.Any(i => string.Equals("." + i.TrimStart('.'), mediaContainer, StringComparison.OrdinalIgnoreCase))) + { + return false; + } + + if (!profile.Conditions.Any(i => IsConditionSatisfied(i, mediaPath, videoStream, audioStream))) + { + return false; + } + + return true; + } + + private bool IsSupported(DeviceProfile profile, TranscodingProfile transcodingProfile, Audio item) + { + // Placeholder for future conditions + return true; + } + + private bool IsSupported(DeviceProfile profile, TranscodingProfile transcodingProfile, Photo item) + { + // Placeholder for future conditions + return true; + } + + private bool IsSupported(DeviceProfile profile, TranscodingProfile transcodingProfile, Video item) + { + // Placeholder for future conditions + return true; + } + + private bool IsConditionSatisfied(ProfileCondition condition, string mediaPath, MediaStream videoStream, MediaStream audioStream) + { + var actualValue = GetConditionValue(condition, mediaPath, videoStream, audioStream); + + if (actualValue.HasValue) + { + long expected; + if (long.TryParse("", NumberStyles.Any, _usCulture, out expected)) + { + switch (condition.Condition) + { + case ProfileConditionType.Equals: + return actualValue.Value == expected; + case ProfileConditionType.GreaterThanEqual: + return actualValue.Value >= expected; + case ProfileConditionType.LessThanEqual: + return actualValue.Value <= expected; + case ProfileConditionType.NotEquals: + return actualValue.Value != expected; + default: + throw new InvalidOperationException("Unexpected ProfileConditionType"); + } + } + } + + return false; + } + + private long? GetConditionValue(ProfileCondition condition, string mediaPath, MediaStream videoStream, MediaStream audioStream) + { + switch (condition.Property) + { + case ProfileConditionValue.AudioBitrate: + return audioStream == null ? null : audioStream.BitRate; + case ProfileConditionValue.AudioChannels: + return audioStream == null ? null : audioStream.Channels; + case ProfileConditionValue.Filesize: + return new FileInfo(mediaPath).Length; + case ProfileConditionValue.VideoBitrate: + return videoStream == null ? null : videoStream.BitRate; + case ProfileConditionValue.VideoFramerate: + return videoStream == null ? null : (ConvertToLong(videoStream.AverageFrameRate ?? videoStream.RealFrameRate)); + case ProfileConditionValue.VideoHeight: + return videoStream == null ? null : videoStream.Height; + case ProfileConditionValue.VideoWidth: + return videoStream == null ? null : videoStream.Width; + default: + throw new InvalidOperationException("Unexpected Property"); + } + } + + private long? ConvertToLong(float? val) + { + return val.HasValue ? Convert.ToInt64(val.Value) : (long?)null; + } + } +} diff --git a/MediaBrowser.Dlna/PlayTo/StreamHelper.cs b/MediaBrowser.Dlna/PlayTo/StreamHelper.cs index 97d208aa37..f5025fdd61 100644 --- a/MediaBrowser.Dlna/PlayTo/StreamHelper.cs +++ b/MediaBrowser.Dlna/PlayTo/StreamHelper.cs @@ -60,7 +60,7 @@ namespace MediaBrowser.Dlna.PlayTo { contentFeatures = "DLNA.ORG_PN=MPEG_PS_PAL"; } - else if (item.IsVideo) + else if (item.MediaType == Controller.Dlna.DlnaProfileType.Video) { //Default to AVI for video contentFeatures = "DLNA.ORG_PN=AVI"; diff --git a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs index 5d326f1caa..d448118860 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs @@ -180,7 +180,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization result.StatusMessage = string.Empty; return; } - + if (fileExists || otherDuplicatePaths.Count > 0) { result.Status = FileSortingStatus.SkippedExisting; @@ -453,24 +453,22 @@ namespace MediaBrowser.Server.Implementations.FileOrganization private bool IsSameEpisode(string sourcePath, string newPath) { + var sourceFileInfo = new FileInfo(sourcePath); + var destinationFileInfo = new FileInfo(newPath); - FileInfo sourceFileInfo = new FileInfo(sourcePath); - FileInfo destinationFileInfo = new FileInfo(newPath); - - try + try + { + if (sourceFileInfo.Length == destinationFileInfo.Length) { - if (sourceFileInfo.Length == destinationFileInfo.Length) - { - return true; - } + return true; } - catch (FileNotFoundException) - { - return false; - } - + } + catch (FileNotFoundException) + { return false; + } + return false; } } } diff --git a/MediaBrowser.Server.Implementations/FileOrganization/NameUtils.cs b/MediaBrowser.Server.Implementations/FileOrganization/NameUtils.cs index 23d0363cb8..7f936791a7 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/NameUtils.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/NameUtils.cs @@ -67,6 +67,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization .Replace("!", " ") .Replace("(", " ") .Replace(")", " ") + .Replace(":", " ") .Replace(",", " ") .Replace("-", " ") .Replace(" a ", String.Empty, StringComparison.OrdinalIgnoreCase)