using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Security; using System.Text; using System.Xml; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.XbmcMetadata.Configuration; namespace MediaBrowser.XbmcMetadata.Savers { public static class XmlSaverHelpers { private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); private static readonly Dictionary CommonTags = new[] { "plot", "customrating", "lockdata", "type", "dateadded", "title", "rating", "year", "sorttitle", "mpaa", "mpaadescription", "aspectratio", "website", "collectionnumber", "tmdbid", "rottentomatoesid", "language", "tvcomid", "budget", "revenue", "tagline", "studio", "genre", "tag", "runtime", "actor", "criticratingsummary", "criticrating", "fileinfo", "director", "writer", "trailer", "premiered", "releasedate", "outline", "id", "votes", "credits", "originaltitle", "watched", "playcount", "lastplayed", "art", "resume", "biography", "formed", "review", "style", "imdbid", "imdb_id", "plotkeyword", "country", "audiodbalbumid", "audiodbartistid", "awardsummary", "enddate", "lockedfields", "metascore", "zap2itid", "tvrageid", "gamesdbid", "musicbrainzartistid", "musicbrainzalbumartistid", "musicbrainzalbumid", "musicbrainzreleasegroupid", "tvdbid", "collectionitem" }.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase); /// /// Saves the specified XML. /// /// The XML. /// The path. /// The XML tags used. public static void Save(StringBuilder xml, string path, List xmlTagsUsed) { if (File.Exists(path)) { var tags = xmlTagsUsed.ToList(); var position = xml.ToString().LastIndexOf(" /// Gets the custom tags. /// /// The path. /// The XML tags used. /// System.String. private static string GetCustomTags(string path, List xmlTagsUsed) { var settings = new XmlReaderSettings { CheckCharacters = false, IgnoreProcessingInstructions = true, IgnoreComments = true, ValidationType = ValidationType.None }; var builder = new StringBuilder(); using (var streamReader = new StreamReader(path, Encoding.UTF8)) { // Use XmlReader for best performance using (var reader = XmlReader.Create(streamReader, settings)) { reader.MoveToContent(); // Loop through each element while (reader.Read()) { if (reader.NodeType == XmlNodeType.Element) { var name = reader.Name; if (!CommonTags.ContainsKey(name) && !xmlTagsUsed.Contains(name, StringComparer.OrdinalIgnoreCase)) { builder.AppendLine(reader.ReadOuterXml()); } else { reader.Skip(); } } } } } return builder.ToString(); } public static void AddMediaInfo(T item, StringBuilder builder) where T : BaseItem, IHasMediaSources { builder.Append(""); builder.Append(""); foreach (var stream in item.GetMediaSources(false).First().MediaStreams) { builder.Append("<" + stream.Type.ToString().ToLower() + ">"); if (!string.IsNullOrEmpty(stream.Codec)) { builder.Append("" + SecurityElement.Escape(stream.Codec) + ""); builder.Append("" + SecurityElement.Escape(stream.Codec) + ""); } if (stream.BitRate.HasValue) { builder.Append("" + stream.BitRate.Value.ToString(UsCulture) + ""); } if (stream.Width.HasValue) { builder.Append("" + stream.Width.Value.ToString(UsCulture) + ""); } if (stream.Height.HasValue) { builder.Append("" + stream.Height.Value.ToString(UsCulture) + ""); } if (!string.IsNullOrEmpty(stream.AspectRatio)) { builder.Append("" + SecurityElement.Escape(stream.AspectRatio) + ""); builder.Append("" + SecurityElement.Escape(stream.AspectRatio) + ""); } var framerate = stream.AverageFrameRate ?? stream.RealFrameRate; if (framerate.HasValue) { builder.Append("" + framerate.Value.ToString(UsCulture) + ""); } if (!string.IsNullOrEmpty(stream.Language)) { builder.Append("" + SecurityElement.Escape(stream.Language) + ""); } var scanType = stream.IsInterlaced ? "interlaced" : "progressive"; if (!string.IsNullOrEmpty(scanType)) { builder.Append("" + SecurityElement.Escape(scanType) + ""); } if (stream.Channels.HasValue) { builder.Append("" + stream.Channels.Value.ToString(UsCulture) + ""); } if (stream.SampleRate.HasValue) { builder.Append("" + stream.SampleRate.Value.ToString(UsCulture) + ""); } builder.Append("" + SecurityElement.Escape(stream.IsDefault.ToString()) + ""); builder.Append("" + SecurityElement.Escape(stream.IsForced.ToString()) + ""); if (stream.Type == MediaStreamType.Video) { if (item.RunTimeTicks.HasValue) { var timespan = TimeSpan.FromTicks(item.RunTimeTicks.Value); builder.Append("" + Convert.ToInt32(timespan.TotalMinutes).ToString(UsCulture) + ""); builder.Append("" + Convert.ToInt32(timespan.TotalSeconds).ToString(UsCulture) + ""); } var video = item as Video; if (video != null) { //AddChapters(video, builder, itemRepository); if (video.Video3DFormat.HasValue) { switch (video.Video3DFormat.Value) { case Video3DFormat.FullSideBySide: builder.Append("FSBS"); break; case Video3DFormat.FullTopAndBottom: builder.Append("FTAB"); break; case Video3DFormat.HalfSideBySide: builder.Append("HSBS"); break; case Video3DFormat.HalfTopAndBottom: builder.Append("HTAB"); break; } } } } builder.Append(""); } builder.Append(""); builder.Append(""); } /// /// Adds the common nodes. /// /// Task. public static void AddCommonNodes(BaseItem item, StringBuilder builder, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataRepo, IFileSystem fileSystem, IServerConfigurationManager config) { var overview = (item.Overview ?? string.Empty) .StripHtml() .Replace(""", "'"); var options = config.GetNfoConfiguration(); if (item is MusicArtist) { builder.Append(""); } else if (item is MusicAlbum) { builder.Append(""); } else { builder.Append(""); } var hasShortOverview = item as IHasShortOverview; if (hasShortOverview != null) { var outline = (hasShortOverview.ShortOverview ?? string.Empty) .StripHtml() .Replace(""", "'"); builder.Append(""); } else { builder.Append(""); } builder.Append("" + SecurityElement.Escape(item.CustomRating ?? string.Empty) + ""); builder.Append("" + item.IsLocked.ToString().ToLower() + ""); if (item.LockedFields.Count > 0) { builder.Append("" + string.Join("|", item.LockedFields.Select(i => i.ToString()).ToArray()) + ""); } if (!string.IsNullOrEmpty(item.DisplayMediaType)) { builder.Append("" + SecurityElement.Escape(item.DisplayMediaType) + ""); } builder.Append("" + SecurityElement.Escape(item.DateCreated.ToString("yyyy-MM-dd HH:mm:ss")) + ""); builder.Append("" + SecurityElement.Escape(item.Name ?? string.Empty) + ""); builder.Append("" + SecurityElement.Escape(item.Name ?? string.Empty) + ""); var directors = item.People .Where(i => IsPersonType(i, PersonType.Director)) .Select(i => i.Name) .ToList(); foreach (var person in directors) { builder.Append("" + SecurityElement.Escape(person) + ""); } var writers = item.People .Where(i => IsPersonType(i, PersonType.Writer)) .Select(i => i.Name) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); foreach (var person in writers) { builder.Append("" + SecurityElement.Escape(person) + ""); } if (writers.Count > 0) { builder.Append("" + SecurityElement.Escape(string.Join(" / ", writers.ToArray())) + ""); } var hasTrailer = item as IHasTrailers; if (hasTrailer != null) { foreach (var trailer in hasTrailer.RemoteTrailers) { builder.Append("" + SecurityElement.Escape(GetOutputTrailerUrl(trailer.Url)) + ""); } } if (item.CommunityRating.HasValue) { builder.Append("" + SecurityElement.Escape(item.CommunityRating.Value.ToString(UsCulture)) + ""); } if (item.ProductionYear.HasValue) { builder.Append("" + SecurityElement.Escape(item.ProductionYear.Value.ToString(UsCulture)) + ""); } if (!string.IsNullOrEmpty(item.ForcedSortName)) { builder.Append("" + SecurityElement.Escape(item.ForcedSortName) + ""); } if (!string.IsNullOrEmpty(item.OfficialRating)) { builder.Append("" + SecurityElement.Escape(item.OfficialRating) + ""); } if (!string.IsNullOrEmpty(item.OfficialRatingDescription)) { builder.Append("" + SecurityElement.Escape(item.OfficialRatingDescription) + ""); } var hasAspectRatio = item as IHasAspectRatio; if (hasAspectRatio != null) { if (!string.IsNullOrEmpty(hasAspectRatio.AspectRatio)) { builder.Append("" + SecurityElement.Escape(hasAspectRatio.AspectRatio) + ""); } } if (!string.IsNullOrEmpty(item.HomePageUrl)) { builder.Append("" + SecurityElement.Escape(item.HomePageUrl) + ""); } var rt = item.GetProviderId(MetadataProviders.RottenTomatoes); if (!string.IsNullOrEmpty(rt)) { builder.Append("" + SecurityElement.Escape(rt) + ""); } var tmdbCollection = item.GetProviderId(MetadataProviders.TmdbCollection); if (!string.IsNullOrEmpty(tmdbCollection)) { builder.Append("" + SecurityElement.Escape(tmdbCollection) + ""); } var imdb = item.GetProviderId(MetadataProviders.Imdb); if (!string.IsNullOrEmpty(imdb)) { if (item is Series) { builder.Append("" + SecurityElement.Escape(imdb) + ""); } else { builder.Append("" + SecurityElement.Escape(imdb) + ""); } } // Series xml saver already saves this if (!(item is Series)) { var tvdb = item.GetProviderId(MetadataProviders.Tvdb); if (!string.IsNullOrEmpty(tvdb)) { builder.Append("" + SecurityElement.Escape(tvdb) + ""); } } var tmdb = item.GetProviderId(MetadataProviders.Tmdb); if (!string.IsNullOrEmpty(tmdb)) { builder.Append("" + SecurityElement.Escape(tmdb) + ""); } var tvcom = item.GetProviderId(MetadataProviders.Tvcom); if (!string.IsNullOrEmpty(tvcom)) { builder.Append("" + SecurityElement.Escape(tvcom) + ""); } var hasLanguage = item as IHasPreferredMetadataLanguage; if (hasLanguage != null) { if (!string.IsNullOrEmpty(hasLanguage.PreferredMetadataLanguage)) { builder.Append("" + SecurityElement.Escape(hasLanguage.PreferredMetadataLanguage) + ""); } } if (item.PremiereDate.HasValue && !(item is Episode)) { var formatString = options.ReleaseDateFormat; if (item is MusicArtist) { builder.Append("" + SecurityElement.Escape(item.PremiereDate.Value.ToString(formatString)) + ""); } else { builder.Append("" + SecurityElement.Escape(item.PremiereDate.Value.ToString(formatString)) + ""); builder.Append("" + SecurityElement.Escape(item.PremiereDate.Value.ToString(formatString)) + ""); } } if (item.EndDate.HasValue) { if (!(item is Episode)) { var formatString = options.ReleaseDateFormat; builder.Append("" + SecurityElement.Escape(item.EndDate.Value.ToString(formatString)) + ""); } } var hasCriticRating = item as IHasCriticRating; if (hasCriticRating != null) { if (hasCriticRating.CriticRating.HasValue) { builder.Append("" + SecurityElement.Escape(hasCriticRating.CriticRating.Value.ToString(UsCulture)) + ""); } if (!string.IsNullOrEmpty(hasCriticRating.CriticRatingSummary)) { builder.Append(""); } } var hasDisplayOrder = item as IHasDisplayOrder; if (hasDisplayOrder != null) { if (!string.IsNullOrEmpty(hasDisplayOrder.DisplayOrder)) { builder.Append("" + SecurityElement.Escape(hasDisplayOrder.DisplayOrder) + ""); } } if (item.VoteCount.HasValue) { builder.Append("" + SecurityElement.Escape(item.VoteCount.Value.ToString(UsCulture)) + ""); } var hasBudget = item as IHasBudget; if (hasBudget != null) { if (hasBudget.Budget.HasValue) { builder.Append("" + SecurityElement.Escape(hasBudget.Budget.Value.ToString(UsCulture)) + ""); } if (hasBudget.Revenue.HasValue) { builder.Append("" + SecurityElement.Escape(hasBudget.Revenue.Value.ToString(UsCulture)) + ""); } } var hasMetascore = item as IHasMetascore; if (hasMetascore != null && hasMetascore.Metascore.HasValue) { builder.Append("" + SecurityElement.Escape(hasMetascore.Metascore.Value.ToString(UsCulture)) + ""); } // Use original runtime here, actual file runtime later in MediaInfo var runTimeTicks = item.RunTimeTicks; if (runTimeTicks.HasValue) { var timespan = TimeSpan.FromTicks(runTimeTicks.Value); builder.Append("" + Convert.ToInt32(timespan.TotalMinutes).ToString(UsCulture) + ""); } var hasTaglines = item as IHasTaglines; if (hasTaglines != null) { foreach (var tagline in hasTaglines.Taglines) { builder.Append("" + SecurityElement.Escape(tagline) + ""); } } var hasProductionLocations = item as IHasProductionLocations; if (hasProductionLocations != null) { foreach (var country in hasProductionLocations.ProductionLocations) { builder.Append("" + SecurityElement.Escape(country) + ""); } } foreach (var genre in item.Genres) { builder.Append("" + SecurityElement.Escape(genre) + ""); } foreach (var studio in item.Studios) { builder.Append("" + SecurityElement.Escape(studio) + ""); } var hasTags = item as IHasTags; if (hasTags != null) { foreach (var tag in hasTags.Tags) { if (item is MusicAlbum || item is MusicArtist) { builder.Append(""); } else { builder.Append("" + SecurityElement.Escape(tag) + ""); } } } var hasKeywords = item as IHasKeywords; if (hasKeywords != null) { foreach (var tag in hasKeywords.Keywords) { builder.Append("" + SecurityElement.Escape(tag) + ""); } } var hasAwards = item as IHasAwards; if (hasAwards != null && !string.IsNullOrEmpty(hasAwards.AwardSummary)) { builder.Append("" + SecurityElement.Escape(hasAwards.AwardSummary) + ""); } var externalId = item.GetProviderId(MetadataProviders.AudioDbArtist); if (!string.IsNullOrEmpty(externalId)) { builder.Append("" + SecurityElement.Escape(externalId) + ""); } externalId = item.GetProviderId(MetadataProviders.AudioDbAlbum); if (!string.IsNullOrEmpty(externalId)) { builder.Append("" + SecurityElement.Escape(externalId) + ""); } externalId = item.GetProviderId(MetadataProviders.Zap2It); if (!string.IsNullOrEmpty(externalId)) { builder.Append("" + SecurityElement.Escape(externalId) + ""); } externalId = item.GetProviderId(MetadataProviders.MusicBrainzAlbum); if (!string.IsNullOrEmpty(externalId)) { builder.Append("" + SecurityElement.Escape(externalId) + ""); } externalId = item.GetProviderId(MetadataProviders.MusicBrainzAlbumArtist); if (!string.IsNullOrEmpty(externalId)) { builder.Append("" + SecurityElement.Escape(externalId) + ""); } externalId = item.GetProviderId(MetadataProviders.MusicBrainzArtist); if (!string.IsNullOrEmpty(externalId)) { builder.Append("" + SecurityElement.Escape(externalId) + ""); } externalId = item.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup); if (!string.IsNullOrEmpty(externalId)) { builder.Append("" + SecurityElement.Escape(externalId) + ""); } externalId = item.GetProviderId(MetadataProviders.Gamesdb); if (!string.IsNullOrEmpty(externalId)) { builder.Append("" + SecurityElement.Escape(externalId) + ""); } externalId = item.GetProviderId(MetadataProviders.TvRage); if (!string.IsNullOrEmpty(externalId)) { builder.Append("" + SecurityElement.Escape(externalId) + ""); } if (options.SaveImagePathsInNfo) { AddImages(item, builder, fileSystem, config); } AddUserData(item, builder, userManager, userDataRepo, options); AddActors(item, builder, libraryManager, fileSystem, config); var folder = item as BoxSet; if (folder != null) { AddCollectionItems(folder, builder); } } public static void AddChapters(Video item, StringBuilder builder, IItemRepository repository) { var chapters = repository.GetChapters(item.Id); foreach (var chapter in chapters) { builder.Append(""); builder.Append("" + SecurityElement.Escape(chapter.Name) + ""); var time = TimeSpan.FromTicks(chapter.StartPositionTicks); var ms = Convert.ToInt64(time.TotalMilliseconds); builder.Append("" + SecurityElement.Escape(ms.ToString(UsCulture)) + ""); builder.Append(""); } } public static void AddCollectionItems(Folder item, StringBuilder builder) { var items = item.LinkedChildren .Where(i => i.Type == LinkedChildType.Manual && !string.IsNullOrWhiteSpace(i.ItemName)) .ToList(); foreach (var link in items) { builder.Append(""); builder.Append("" + SecurityElement.Escape(link.ItemName) + ""); builder.Append("" + SecurityElement.Escape(link.ItemType) + ""); if (link.ItemYear.HasValue) { builder.Append("" + SecurityElement.Escape(link.ItemYear.Value.ToString(UsCulture)) + ""); } builder.Append(""); } } /// /// Gets the output trailer URL. /// /// The URL. /// System.String. private static string GetOutputTrailerUrl(string url) { // This is what xbmc expects return url.Replace("http://www.youtube.com/watch?v=", "plugin://plugin.video.youtube/?action=play_video&videoid=", StringComparison.OrdinalIgnoreCase); } private static void AddImages(BaseItem item, StringBuilder builder, IFileSystem fileSystem, IServerConfigurationManager config) { builder.Append(""); var poster = item.PrimaryImagePath; if (!string.IsNullOrEmpty(poster)) { builder.Append("" + SecurityElement.Escape(GetPathToSave(item.PrimaryImagePath, fileSystem, config)) + ""); } foreach (var backdrop in item.GetImages(ImageType.Backdrop)) { builder.Append("" + SecurityElement.Escape(GetPathToSave(backdrop.Path, fileSystem, config)) + ""); } builder.Append(""); } private static void AddUserData(BaseItem item, StringBuilder builder, IUserManager userManager, IUserDataManager userDataRepo, XbmcMetadataOptions options) { var userId = options.UserId; if (string.IsNullOrWhiteSpace(userId)) { return; } var user = userManager.GetUserById(new Guid(userId)); if (user == null) { return; } if (item.IsFolder) { return; } var userdata = userDataRepo.GetUserData(user.Id, item.GetUserDataKey()); builder.Append("" + userdata.PlayCount.ToString(UsCulture) + ""); builder.Append("" + userdata.Played.ToString().ToLower() + ""); if (userdata.LastPlayedDate.HasValue) { builder.Append("" + SecurityElement.Escape(userdata.LastPlayedDate.Value.ToString("yyyy-MM-dd HH:mm:ss")) + ""); } builder.Append(""); var runTimeTicks = item.RunTimeTicks ?? 0; builder.Append("" + TimeSpan.FromTicks(userdata.PlaybackPositionTicks).TotalSeconds.ToString(UsCulture) + ""); builder.Append("" + TimeSpan.FromTicks(runTimeTicks).TotalSeconds.ToString(UsCulture) + ""); builder.Append(""); } public static void AddActors(BaseItem item, StringBuilder builder, ILibraryManager libraryManager, IFileSystem fileSystem, IServerConfigurationManager config) { var actors = item.People .Where(i => !IsPersonType(i, PersonType.Director) && !IsPersonType(i, PersonType.Writer)) .ToList(); foreach (var person in actors) { builder.Append(""); builder.Append("" + SecurityElement.Escape(person.Name ?? string.Empty) + ""); builder.Append("" + SecurityElement.Escape(person.Role ?? string.Empty) + ""); builder.Append("" + SecurityElement.Escape(person.Type ?? string.Empty) + ""); if (person.SortOrder.HasValue) { builder.Append("" + SecurityElement.Escape(person.SortOrder.Value.ToString(UsCulture)) + ""); } try { var personEntity = libraryManager.GetPerson(person.Name); if (!string.IsNullOrEmpty(personEntity.PrimaryImagePath)) { builder.Append("" + SecurityElement.Escape(GetPathToSave(personEntity.PrimaryImagePath, fileSystem, config)) + ""); } } catch (Exception) { // Already logged in core } builder.Append(""); } } private static bool IsPersonType(PersonInfo person, string type) { return string.Equals(person.Type, type, StringComparison.OrdinalIgnoreCase) || string.Equals(person.Role, type, StringComparison.OrdinalIgnoreCase); } private static string GetPathToSave(string path, IFileSystem fileSystem, IServerConfigurationManager config) { foreach (var map in config.Configuration.PathSubstitutions) { path = fileSystem.SubstitutePath(path, map.From, map.To); } return path; } public static string ReplaceString(string str, string oldValue, string newValue, StringComparison comparison) { var sb = new StringBuilder(); int previousIndex = 0; int index = str.IndexOf(oldValue, comparison); while (index != -1) { sb.Append(str.Substring(previousIndex, index - previousIndex)); sb.Append(newValue); index += oldValue.Length; previousIndex = index; index = str.IndexOf(oldValue, index, comparison); } sb.Append(str.Substring(previousIndex)); return sb.ToString(); } } }