< Summary - Jellyfin

Information
Class: MediaBrowser.XbmcMetadata.Savers.BaseNfoSaver
Assembly: MediaBrowser.XbmcMetadata
File(s): /srv/git/jellyfin/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
Line coverage
0%
Covered lines: 1
Uncovered lines: 447
Coverable lines: 448
Total lines: 1028
Line coverage: 0.2%
Branch coverage
0%
Covered branches: 0
Total branches: 216
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%210%
.ctor(...)100%11100%
get_MinimumUpdateType()0%620%
get_Name()100%210%
get_SaverName()100%210%
GetSavePath(...)100%210%
SetHidden(...)100%210%
Save(...)0%2040%
AddMediaInfo(...)0%1980440%
AddCommonNodes(...)0%126561120%
AddCollectionItems(...)0%4260%
GetOutputTrailerUrl(...)100%210%
AddImages(...)0%2040%
AddUserData(...)0%156120%
AddActors(...)0%420200%
GetImagePathToSave(...)0%620%
AddCustomTags(...)0%110100%
GetTagForProviderKey(...)100%210%

File(s)

/srv/git/jellyfin/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs

#LineLine coverage
 1#pragma warning disable CS1591
 2
 3using System;
 4using System.Collections.Generic;
 5using System.Globalization;
 6using System.IO;
 7using System.Linq;
 8using System.Text;
 9using System.Text.RegularExpressions;
 10using System.Threading;
 11using System.Threading.Tasks;
 12using System.Xml;
 13using Jellyfin.Data.Enums;
 14using Jellyfin.Extensions;
 15using MediaBrowser.Common.Extensions;
 16using MediaBrowser.Controller.Configuration;
 17using MediaBrowser.Controller.Entities;
 18using MediaBrowser.Controller.Entities.Audio;
 19using MediaBrowser.Controller.Entities.Movies;
 20using MediaBrowser.Controller.Entities.TV;
 21using MediaBrowser.Controller.Library;
 22using MediaBrowser.Model.Configuration;
 23using MediaBrowser.Model.Entities;
 24using MediaBrowser.Model.IO;
 25using MediaBrowser.XbmcMetadata.Configuration;
 26using Microsoft.Extensions.Logging;
 27
 28namespace MediaBrowser.XbmcMetadata.Savers
 29{
 30    public abstract partial class BaseNfoSaver : IMetadataFileSaver
 31    {
 32        public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss";
 33
 34        public const string YouTubeWatchUrl = "https://www.youtube.com/watch?v=";
 35
 036        private static readonly HashSet<string> _commonTags = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
 037        {
 038            "plot",
 039            "customrating",
 040            "lockdata",
 041            "dateadded",
 042            "title",
 043            "rating",
 044            "year",
 045            "sorttitle",
 046            "mpaa",
 047            "aspectratio",
 048            "collectionnumber",
 049            "tmdbid",
 050            "rottentomatoesid",
 051            "language",
 052            "tvcomid",
 053            "tagline",
 054            "studio",
 055            "genre",
 056            "tag",
 057            "runtime",
 058            "actor",
 059            "criticrating",
 060            "fileinfo",
 061            "director",
 062            "writer",
 063            "trailer",
 064            "premiered",
 065            "releasedate",
 066            "outline",
 067            "id",
 068            "credits",
 069            "originaltitle",
 070            "watched",
 071            "playcount",
 072            "lastplayed",
 073            "art",
 074            "resume",
 075            "biography",
 076            "formed",
 077            "review",
 078            "style",
 079            "imdbid",
 080            "imdb_id",
 081            "country",
 082            "audiodbalbumid",
 083            "audiodbartistid",
 084            "enddate",
 085            "lockedfields",
 086            "zap2itid",
 087            "tvrageid",
 088
 089            "musicbrainzartistid",
 090            "musicbrainzalbumartistid",
 091            "musicbrainzalbumid",
 092            "musicbrainzreleasegroupid",
 093            "tvdbid",
 094            "collectionitem",
 095
 096            "isuserfavorite",
 097            "userrating",
 098
 099            "countrycode"
 0100        };
 101
 102        protected BaseNfoSaver(
 103            IFileSystem fileSystem,
 104            IServerConfigurationManager configurationManager,
 105            ILibraryManager libraryManager,
 106            IUserManager userManager,
 107            IUserDataManager userDataManager,
 108            ILogger<BaseNfoSaver> logger)
 109        {
 110            Logger = logger;
 111            UserDataManager = userDataManager;
 112            UserManager = userManager;
 113            LibraryManager = libraryManager;
 114            ConfigurationManager = configurationManager;
 115            FileSystem = fileSystem;
 132116        }
 117
 118        protected IFileSystem FileSystem { get; }
 119
 120        protected IServerConfigurationManager ConfigurationManager { get; }
 121
 122        protected ILibraryManager LibraryManager { get; }
 123
 124        protected IUserManager UserManager { get; }
 125
 126        protected IUserDataManager UserDataManager { get; }
 127
 128        protected ILogger<BaseNfoSaver> Logger { get; }
 129
 130        protected ItemUpdateType MinimumUpdateType
 131        {
 132            get
 133            {
 0134                if (ConfigurationManager.GetNfoConfiguration().SaveImagePathsInNfo)
 135                {
 0136                    return ItemUpdateType.ImageUpdate;
 137                }
 138
 0139                return ItemUpdateType.MetadataDownload;
 140            }
 141        }
 142
 143        /// <inheritdoc />
 0144        public string Name => SaverName;
 145
 0146        public static string SaverName => "Nfo";
 147
 148        // filters control characters but allows only properly-formed surrogate sequences
 149        // http://web.archive.org/web/20181230211547/https://emby.media/community/index.php?/topic/49071-nfo-not-generat
 150        // Web Archive version of link since it's not really explained in the thread.
 151        [GeneratedRegex(@"(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|[\x00-\x08\x0B\x0C\x0E-
 152        private static partial Regex InvalidXMLCharsRegexRegex();
 153
 154        /// <inheritdoc />
 155        public string GetSavePath(BaseItem item)
 0156            => GetLocalSavePath(item);
 157
 158        /// <summary>
 159        /// Gets the save path.
 160        /// </summary>
 161        /// <param name="item">The item.</param>
 162        /// <returns><see cref="string" />.</returns>
 163        protected abstract string GetLocalSavePath(BaseItem item);
 164
 165        /// <summary>
 166        /// Gets the name of the root element.
 167        /// </summary>
 168        /// <param name="item">The item.</param>
 169        /// <returns><see cref="string" />.</returns>
 170        protected abstract string GetRootElementName(BaseItem item);
 171
 172        /// <inheritdoc />
 173        public abstract bool IsEnabledFor(BaseItem item, ItemUpdateType updateType);
 174
 175        protected virtual IEnumerable<string> GetTagsUsed(BaseItem item)
 176        {
 177            foreach (var providerKey in item.ProviderIds.Keys)
 178            {
 179                var providerIdTagName = GetTagForProviderKey(providerKey);
 180                if (!_commonTags.Contains(providerIdTagName))
 181                {
 182                    yield return providerIdTagName;
 183                }
 184            }
 185        }
 186
 187        /// <inheritdoc />
 188        public async Task SaveAsync(BaseItem item, CancellationToken cancellationToken)
 189        {
 190            var path = GetSavePath(item);
 191
 192            using (var memoryStream = new MemoryStream())
 193            {
 194                Save(item, memoryStream, path);
 195
 196                memoryStream.Position = 0;
 197
 198                cancellationToken.ThrowIfCancellationRequested();
 199
 200                await SaveToFileAsync(memoryStream, path).ConfigureAwait(false);
 201            }
 202        }
 203
 204        private async Task SaveToFileAsync(Stream stream, string path)
 205        {
 206            var directory = Path.GetDirectoryName(path) ?? throw new ArgumentException($"Provided path ({path}) is not v
 207            Directory.CreateDirectory(directory);
 208
 209            // On Windows, saving the file will fail if the file is hidden or readonly
 210            FileSystem.SetAttributes(path, false, false);
 211
 212            var fileStreamOptions = new FileStreamOptions()
 213            {
 214                Mode = FileMode.Create,
 215                Access = FileAccess.Write,
 216                Share = FileShare.None,
 217                PreallocationSize = stream.Length,
 218                Options = FileOptions.Asynchronous
 219            };
 220
 221            var filestream = new FileStream(path, fileStreamOptions);
 222            await using (filestream.ConfigureAwait(false))
 223            {
 224                await stream.CopyToAsync(filestream).ConfigureAwait(false);
 225            }
 226
 227            if (ConfigurationManager.Configuration.SaveMetadataHidden)
 228            {
 229                SetHidden(path, true);
 230            }
 231        }
 232
 233        private void SetHidden(string path, bool hidden)
 234        {
 235            try
 236            {
 0237                FileSystem.SetHidden(path, hidden);
 0238            }
 0239            catch (IOException ex)
 240            {
 0241                Logger.LogError(ex, "Error setting hidden attribute on {Path}", path);
 0242            }
 0243        }
 244
 245        private void Save(BaseItem item, Stream stream, string xmlPath)
 246        {
 0247            var settings = new XmlWriterSettings
 0248            {
 0249                Indent = true,
 0250                Encoding = Encoding.UTF8,
 0251                CloseOutput = false
 0252            };
 253
 0254            using (var writer = XmlWriter.Create(stream, settings))
 255            {
 0256                var root = GetRootElementName(item);
 257
 0258                writer.WriteStartDocument(true);
 259
 0260                writer.WriteStartElement(root);
 261
 0262                var baseItem = item;
 263
 0264                if (baseItem is not null)
 265                {
 0266                    AddCommonNodes(baseItem, writer, LibraryManager, UserManager, UserDataManager, ConfigurationManager)
 267                }
 268
 0269                WriteCustomElements(item, writer);
 270
 0271                if (baseItem is IHasMediaSources hasMediaSources)
 272                {
 0273                    AddMediaInfo(hasMediaSources, writer);
 274                }
 275
 0276                var tagsUsed = GetTagsUsed(item).ToList();
 277
 278                try
 279                {
 0280                    AddCustomTags(xmlPath, tagsUsed, writer, Logger);
 0281                }
 0282                catch (FileNotFoundException)
 283                {
 0284                }
 0285                catch (IOException)
 286                {
 0287                }
 0288                catch (XmlException ex)
 289                {
 0290                    Logger.LogError(ex, "Error reading existing nfo");
 0291                }
 292
 0293                writer.WriteEndElement();
 294
 0295                writer.WriteEndDocument();
 0296            }
 0297        }
 298
 299        protected abstract void WriteCustomElements(BaseItem item, XmlWriter writer);
 300
 301        public static void AddMediaInfo<T>(T item, XmlWriter writer)
 302            where T : IHasMediaSources
 303        {
 0304            writer.WriteStartElement("fileinfo");
 0305            writer.WriteStartElement("streamdetails");
 306
 0307            var mediaStreams = item.GetMediaStreams();
 308
 0309            foreach (var stream in mediaStreams)
 310            {
 0311                writer.WriteStartElement(stream.Type.ToString().ToLowerInvariant());
 312
 0313                if (!string.IsNullOrEmpty(stream.Codec))
 314                {
 0315                    var codec = stream.Codec;
 316
 0317                    if ((stream.CodecTag ?? string.Empty).Contains("xvid", StringComparison.OrdinalIgnoreCase))
 318                    {
 0319                        codec = "xvid";
 320                    }
 0321                    else if ((stream.CodecTag ?? string.Empty).Contains("divx", StringComparison.OrdinalIgnoreCase))
 322                    {
 0323                        codec = "divx";
 324                    }
 325
 0326                    writer.WriteElementString("codec", codec);
 0327                    writer.WriteElementString("micodec", codec);
 328                }
 329
 0330                if (stream.BitRate.HasValue)
 331                {
 0332                    writer.WriteElementString("bitrate", stream.BitRate.Value.ToString(CultureInfo.InvariantCulture));
 333                }
 334
 0335                if (stream.Width.HasValue)
 336                {
 0337                    writer.WriteElementString("width", stream.Width.Value.ToString(CultureInfo.InvariantCulture));
 338                }
 339
 0340                if (stream.Height.HasValue)
 341                {
 0342                    writer.WriteElementString("height", stream.Height.Value.ToString(CultureInfo.InvariantCulture));
 343                }
 344
 0345                if (!string.IsNullOrEmpty(stream.AspectRatio))
 346                {
 0347                    writer.WriteElementString("aspect", stream.AspectRatio);
 0348                    writer.WriteElementString("aspectratio", stream.AspectRatio);
 349                }
 350
 0351                var framerate = stream.ReferenceFrameRate;
 352
 0353                if (framerate.HasValue)
 354                {
 0355                    writer.WriteElementString("framerate", framerate.Value.ToString(CultureInfo.InvariantCulture));
 356                }
 357
 0358                if (!string.IsNullOrEmpty(stream.Language))
 359                {
 0360                    writer.WriteElementString("language", InvalidXMLCharsRegexRegex().Replace(stream.Language, string.Em
 361                }
 362
 0363                var scanType = stream.IsInterlaced ? "interlaced" : "progressive";
 0364                writer.WriteElementString("scantype", scanType);
 365
 0366                if (stream.Channels.HasValue)
 367                {
 0368                    writer.WriteElementString("channels", stream.Channels.Value.ToString(CultureInfo.InvariantCulture));
 369                }
 370
 0371                if (stream.SampleRate.HasValue)
 372                {
 0373                    writer.WriteElementString("samplingrate", stream.SampleRate.Value.ToString(CultureInfo.InvariantCult
 374                }
 375
 0376                writer.WriteElementString("default", stream.IsDefault.ToString(CultureInfo.InvariantCulture));
 0377                writer.WriteElementString("forced", stream.IsForced.ToString(CultureInfo.InvariantCulture));
 378
 0379                if (stream.Type == MediaStreamType.Video)
 380                {
 0381                    var runtimeTicks = item.RunTimeTicks;
 0382                    if (runtimeTicks.HasValue)
 383                    {
 0384                        var timespan = TimeSpan.FromTicks(runtimeTicks.Value);
 385
 0386                        writer.WriteElementString(
 0387                            "duration",
 0388                            Math.Floor(timespan.TotalMinutes).ToString(CultureInfo.InvariantCulture));
 0389                        writer.WriteElementString(
 0390                            "durationinseconds",
 0391                            Math.Floor(timespan.TotalSeconds).ToString(CultureInfo.InvariantCulture));
 392                    }
 393
 0394                    if (item is Video video)
 395                    {
 396                        // AddChapters(video, builder, itemRepository);
 397
 0398                        if (video.Video3DFormat.HasValue)
 399                        {
 0400                            switch (video.Video3DFormat.Value)
 401                            {
 402                                case Video3DFormat.FullSideBySide:
 0403                                    writer.WriteElementString("format3d", "FSBS");
 0404                                    break;
 405                                case Video3DFormat.FullTopAndBottom:
 0406                                    writer.WriteElementString("format3d", "FTAB");
 0407                                    break;
 408                                case Video3DFormat.HalfSideBySide:
 0409                                    writer.WriteElementString("format3d", "HSBS");
 0410                                    break;
 411                                case Video3DFormat.HalfTopAndBottom:
 0412                                    writer.WriteElementString("format3d", "HTAB");
 0413                                    break;
 414                                case Video3DFormat.MVC:
 0415                                    writer.WriteElementString("format3d", "MVC");
 416                                    break;
 417                            }
 418                        }
 419                    }
 420                }
 421
 0422                writer.WriteEndElement();
 423            }
 424
 0425            writer.WriteEndElement();
 0426            writer.WriteEndElement();
 0427        }
 428
 429        /// <summary>
 430        /// Adds the common nodes.
 431        /// </summary>
 432        private void AddCommonNodes(
 433            BaseItem item,
 434            XmlWriter writer,
 435            ILibraryManager libraryManager,
 436            IUserManager userManager,
 437            IUserDataManager userDataRepo,
 438            IServerConfigurationManager config)
 439        {
 0440            var writtenProviderIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
 441
 0442            var overview = (item.Overview ?? string.Empty)
 0443                .StripHtml()
 0444                .Replace("&quot;", "'", StringComparison.Ordinal);
 445
 0446            var options = config.GetNfoConfiguration();
 447
 0448            if (item is MusicArtist)
 449            {
 0450                writer.WriteElementString("biography", overview);
 451            }
 0452            else if (item is MusicAlbum)
 453            {
 0454                writer.WriteElementString("review", overview);
 455            }
 456            else
 457            {
 0458                writer.WriteElementString("plot", overview);
 459            }
 460
 0461            if (item is not Video)
 462            {
 0463                writer.WriteElementString("outline", overview);
 464            }
 465
 0466            if (!string.IsNullOrWhiteSpace(item.CustomRating))
 467            {
 0468                writer.WriteElementString("customrating", item.CustomRating);
 469            }
 470
 0471            writer.WriteElementString("lockdata", item.IsLocked.ToString(CultureInfo.InvariantCulture).ToLowerInvariant(
 472
 0473            if (item.LockedFields.Length > 0)
 474            {
 0475                writer.WriteElementString("lockedfields", string.Join('|', item.LockedFields));
 476            }
 477
 0478            writer.WriteElementString("dateadded", item.DateCreated.ToString(DateAddedFormat, CultureInfo.InvariantCultu
 479
 0480            writer.WriteElementString("title", item.Name ?? string.Empty);
 481
 0482            if (!string.IsNullOrWhiteSpace(item.OriginalTitle))
 483            {
 0484                writer.WriteElementString("originaltitle", item.OriginalTitle);
 485            }
 486
 0487            var people = libraryManager.GetPeople(item);
 488
 0489            var directors = people
 0490                .Where(i => i.IsType(PersonKind.Director))
 0491                .Select(i => i.Name)
 0492                .ToList();
 493
 0494            foreach (var person in directors)
 495            {
 0496                writer.WriteElementString("director", person);
 497            }
 498
 0499            var writers = people
 0500                .Where(i => i.IsType(PersonKind.Writer))
 0501                .Select(i => i.Name)
 0502                .Distinct(StringComparer.OrdinalIgnoreCase)
 0503                .ToList();
 504
 0505            foreach (var person in writers)
 506            {
 0507                writer.WriteElementString("writer", person);
 508            }
 509
 0510            foreach (var person in writers)
 511            {
 0512                writer.WriteElementString("credits", person);
 513            }
 514
 0515            foreach (var trailer in item.RemoteTrailers)
 516            {
 0517                writer.WriteElementString("trailer", GetOutputTrailerUrl(trailer.Url));
 518            }
 519
 0520            if (item.CommunityRating.HasValue)
 521            {
 0522                writer.WriteElementString("rating", item.CommunityRating.Value.ToString(CultureInfo.InvariantCulture));
 523            }
 524
 0525            if (item.ProductionYear.HasValue)
 526            {
 0527                writer.WriteElementString("year", item.ProductionYear.Value.ToString(CultureInfo.InvariantCulture));
 528            }
 529
 0530            var forcedSortName = item.ForcedSortName;
 0531            if (!string.IsNullOrEmpty(forcedSortName))
 532            {
 0533                writer.WriteElementString("sorttitle", forcedSortName);
 534            }
 535
 0536            if (!string.IsNullOrEmpty(item.OfficialRating))
 537            {
 0538                writer.WriteElementString("mpaa", item.OfficialRating);
 539            }
 540
 0541            if (item is IHasAspectRatio hasAspectRatio
 0542                && !string.IsNullOrEmpty(hasAspectRatio.AspectRatio))
 543            {
 0544                writer.WriteElementString("aspectratio", hasAspectRatio.AspectRatio);
 545            }
 546
 0547            var tmdbCollection = item.GetProviderId(MetadataProvider.TmdbCollection);
 548
 0549            if (!string.IsNullOrEmpty(tmdbCollection))
 550            {
 0551                writer.WriteElementString("collectionnumber", tmdbCollection);
 0552                writtenProviderIds.Add(MetadataProvider.TmdbCollection.ToString());
 553            }
 554
 0555            var imdb = item.GetProviderId(MetadataProvider.Imdb);
 0556            if (!string.IsNullOrEmpty(imdb))
 557            {
 0558                if (item is Series)
 559                {
 0560                    writer.WriteElementString("imdb_id", imdb);
 561                }
 562                else
 563                {
 0564                    writer.WriteElementString("imdbid", imdb);
 565                }
 566
 0567                writtenProviderIds.Add(MetadataProvider.Imdb.ToString());
 568            }
 569
 570            // Series xml saver already saves this
 0571            if (item is not Series)
 572            {
 0573                var tvdb = item.GetProviderId(MetadataProvider.Tvdb);
 0574                if (!string.IsNullOrEmpty(tvdb))
 575                {
 0576                    writer.WriteElementString("tvdbid", tvdb);
 0577                    writtenProviderIds.Add(MetadataProvider.Tvdb.ToString());
 578                }
 579            }
 580
 0581            var tmdb = item.GetProviderId(MetadataProvider.Tmdb);
 0582            if (!string.IsNullOrEmpty(tmdb))
 583            {
 0584                writer.WriteElementString("tmdbid", tmdb);
 0585                writtenProviderIds.Add(MetadataProvider.Tmdb.ToString());
 586            }
 587
 0588            if (!string.IsNullOrEmpty(item.PreferredMetadataLanguage))
 589            {
 0590                writer.WriteElementString("language", item.PreferredMetadataLanguage);
 591            }
 592
 0593            if (!string.IsNullOrEmpty(item.PreferredMetadataCountryCode))
 594            {
 0595                writer.WriteElementString("countrycode", item.PreferredMetadataCountryCode);
 596            }
 597
 0598            if (item.PremiereDate.HasValue && item is not Episode)
 599            {
 0600                var formatString = options.ReleaseDateFormat;
 601
 0602                if (item is MusicArtist)
 603                {
 0604                    writer.WriteElementString(
 0605                        "formed",
 0606                        item.PremiereDate.Value.ToString(formatString, CultureInfo.InvariantCulture));
 607                }
 608                else
 609                {
 0610                    writer.WriteElementString(
 0611                        "premiered",
 0612                        item.PremiereDate.Value.ToString(formatString, CultureInfo.InvariantCulture));
 0613                    writer.WriteElementString(
 0614                        "releasedate",
 0615                        item.PremiereDate.Value.ToString(formatString, CultureInfo.InvariantCulture));
 616                }
 617            }
 618
 0619            if (item.EndDate.HasValue)
 620            {
 0621                if (item is not Episode)
 622                {
 0623                    var formatString = options.ReleaseDateFormat;
 624
 0625                    writer.WriteElementString(
 0626                        "enddate",
 0627                        item.EndDate.Value.ToString(formatString, CultureInfo.InvariantCulture));
 628                }
 629            }
 630
 0631            if (item.CriticRating.HasValue)
 632            {
 0633                writer.WriteElementString(
 0634                    "criticrating",
 0635                    item.CriticRating.Value.ToString(CultureInfo.InvariantCulture));
 636            }
 637
 0638            if (item is IHasDisplayOrder hasDisplayOrder)
 639            {
 0640                if (!string.IsNullOrEmpty(hasDisplayOrder.DisplayOrder))
 641                {
 0642                    writer.WriteElementString("displayorder", hasDisplayOrder.DisplayOrder);
 643                }
 644            }
 645
 646            // Use original runtime here, actual file runtime later in MediaInfo
 0647            var runTimeTicks = item.RunTimeTicks;
 648
 0649            if (runTimeTicks.HasValue)
 650            {
 0651                var timespan = TimeSpan.FromTicks(runTimeTicks.Value);
 652
 0653                writer.WriteElementString(
 0654                    "runtime",
 0655                    Convert.ToInt64(timespan.TotalMinutes).ToString(CultureInfo.InvariantCulture));
 656            }
 657
 0658            if (!string.IsNullOrWhiteSpace(item.Tagline))
 659            {
 0660                writer.WriteElementString("tagline", item.Tagline);
 661            }
 662
 0663            foreach (var country in item.ProductionLocations)
 664            {
 0665                writer.WriteElementString("country", country);
 666            }
 667
 0668            foreach (var genre in item.Genres)
 669            {
 0670                writer.WriteElementString("genre", genre);
 671            }
 672
 0673            foreach (var studio in item.Studios)
 674            {
 0675                writer.WriteElementString("studio", studio);
 676            }
 677
 0678            foreach (var tag in item.Tags)
 679            {
 0680                if (item is MusicAlbum || item is MusicArtist)
 681                {
 0682                    writer.WriteElementString("style", tag);
 683                }
 684                else
 685                {
 0686                    writer.WriteElementString("tag", tag);
 687                }
 688            }
 689
 0690            var externalId = item.GetProviderId(MetadataProvider.AudioDbArtist);
 691
 0692            if (!string.IsNullOrEmpty(externalId))
 693            {
 0694                writer.WriteElementString("audiodbartistid", externalId);
 0695                writtenProviderIds.Add(MetadataProvider.AudioDbArtist.ToString());
 696            }
 697
 0698            externalId = item.GetProviderId(MetadataProvider.AudioDbAlbum);
 699
 0700            if (!string.IsNullOrEmpty(externalId))
 701            {
 0702                writer.WriteElementString("audiodbalbumid", externalId);
 0703                writtenProviderIds.Add(MetadataProvider.AudioDbAlbum.ToString());
 704            }
 705
 0706            externalId = item.GetProviderId(MetadataProvider.Zap2It);
 707
 0708            if (!string.IsNullOrEmpty(externalId))
 709            {
 0710                writer.WriteElementString("zap2itid", externalId);
 0711                writtenProviderIds.Add(MetadataProvider.Zap2It.ToString());
 712            }
 713
 0714            externalId = item.GetProviderId(MetadataProvider.MusicBrainzAlbum);
 715
 0716            if (!string.IsNullOrEmpty(externalId))
 717            {
 0718                writer.WriteElementString("musicbrainzalbumid", externalId);
 0719                writtenProviderIds.Add(MetadataProvider.MusicBrainzAlbum.ToString());
 720            }
 721
 0722            externalId = item.GetProviderId(MetadataProvider.MusicBrainzAlbumArtist);
 723
 0724            if (!string.IsNullOrEmpty(externalId))
 725            {
 0726                writer.WriteElementString("musicbrainzalbumartistid", externalId);
 0727                writtenProviderIds.Add(MetadataProvider.MusicBrainzAlbumArtist.ToString());
 728            }
 729
 0730            externalId = item.GetProviderId(MetadataProvider.MusicBrainzArtist);
 731
 0732            if (!string.IsNullOrEmpty(externalId))
 733            {
 0734                writer.WriteElementString("musicbrainzartistid", externalId);
 0735                writtenProviderIds.Add(MetadataProvider.MusicBrainzArtist.ToString());
 736            }
 737
 0738            externalId = item.GetProviderId(MetadataProvider.MusicBrainzReleaseGroup);
 739
 0740            if (!string.IsNullOrEmpty(externalId))
 741            {
 0742                writer.WriteElementString("musicbrainzreleasegroupid", externalId);
 0743                writtenProviderIds.Add(MetadataProvider.MusicBrainzReleaseGroup.ToString());
 744            }
 745
 0746            externalId = item.GetProviderId(MetadataProvider.TvRage);
 0747            if (!string.IsNullOrEmpty(externalId))
 748            {
 0749                writer.WriteElementString("tvrageid", externalId);
 0750                writtenProviderIds.Add(MetadataProvider.TvRage.ToString());
 751            }
 752
 0753            if (item.ProviderIds is not null)
 754            {
 0755                foreach (var providerKey in item.ProviderIds.Keys)
 756                {
 0757                    var providerId = item.ProviderIds[providerKey];
 0758                    if (!string.IsNullOrEmpty(providerId) && !writtenProviderIds.Contains(providerKey))
 759                    {
 760                        try
 761                        {
 0762                            var tagName = GetTagForProviderKey(providerKey);
 0763                            Logger.LogDebug("Verifying custom provider tagname {0}", tagName);
 0764                            XmlConvert.VerifyName(tagName);
 0765                            Logger.LogDebug("Saving custom provider tagname {0}", tagName);
 766
 0767                            writer.WriteElementString(GetTagForProviderKey(providerKey), providerId);
 0768                        }
 0769                        catch (ArgumentException)
 770                        {
 771                            // catch invalid names without failing the entire operation
 0772                        }
 0773                        catch (XmlException)
 774                        {
 775                            // catch invalid names without failing the entire operation
 0776                        }
 777                    }
 778                }
 779            }
 780
 0781            if (options.SaveImagePathsInNfo)
 782            {
 0783                AddImages(item, writer, libraryManager);
 784            }
 785
 0786            AddUserData(item, writer, userManager, userDataRepo, options);
 787
 0788            AddActors(people, writer, libraryManager, options.SaveImagePathsInNfo);
 789
 0790            if (item is BoxSet folder)
 791            {
 0792                AddCollectionItems(folder, writer);
 793            }
 0794        }
 795
 796        private void AddCollectionItems(Folder item, XmlWriter writer)
 797        {
 0798            var items = item.LinkedChildren
 0799                .Where(i => i.Type == LinkedChildType.Manual)
 0800                .ToList();
 801
 0802            foreach (var link in items)
 803            {
 0804                writer.WriteStartElement("collectionitem");
 805
 0806                if (!string.IsNullOrWhiteSpace(link.Path))
 807                {
 0808                    writer.WriteElementString("path", link.Path);
 809                }
 810
 0811                if (!string.IsNullOrWhiteSpace(link.LibraryItemId))
 812                {
 0813                    writer.WriteElementString("ItemId", link.LibraryItemId);
 814                }
 815
 0816                writer.WriteEndElement();
 817            }
 0818        }
 819
 820        /// <summary>
 821        /// Gets the output trailer URL.
 822        /// </summary>
 823        /// <param name="url">The URL.</param>
 824        /// <returns>System.String.</returns>
 825        private string GetOutputTrailerUrl(string url)
 826        {
 827            // This is what xbmc expects
 0828            return url.Replace(YouTubeWatchUrl, "plugin://plugin.video.youtube/play/?video_id=", StringComparison.Ordina
 829        }
 830
 831        private void AddImages(BaseItem item, XmlWriter writer, ILibraryManager libraryManager)
 832        {
 0833            writer.WriteStartElement("art");
 834
 0835            var image = item.GetImageInfo(ImageType.Primary, 0);
 836
 0837            if (image is not null)
 838            {
 0839                writer.WriteElementString("poster", GetImagePathToSave(image, libraryManager));
 840            }
 841
 0842            foreach (var backdrop in item.GetImages(ImageType.Backdrop))
 843            {
 0844                writer.WriteElementString("fanart", GetImagePathToSave(backdrop, libraryManager));
 845            }
 846
 0847            writer.WriteEndElement();
 0848        }
 849
 850        private void AddUserData(BaseItem item, XmlWriter writer, IUserManager userManager, IUserDataManager userDataRep
 851        {
 0852            var userId = options.UserId;
 0853            if (string.IsNullOrWhiteSpace(userId))
 854            {
 0855                return;
 856            }
 857
 0858            var user = userManager.GetUserById(Guid.Parse(userId));
 859
 0860            if (user is null)
 861            {
 0862                return;
 863            }
 864
 0865            if (item.IsFolder)
 866            {
 0867                return;
 868            }
 869
 0870            var userdata = userDataRepo.GetUserData(user, item);
 871
 0872            writer.WriteElementString(
 0873                "isuserfavorite",
 0874                userdata.IsFavorite.ToString(CultureInfo.InvariantCulture).ToLowerInvariant());
 875
 0876            if (userdata.Rating.HasValue)
 877            {
 0878                writer.WriteElementString(
 0879                    "userrating",
 0880                    userdata.Rating.Value.ToString(CultureInfo.InvariantCulture).ToLowerInvariant());
 881            }
 882
 0883            if (!item.IsFolder)
 884            {
 0885                writer.WriteElementString(
 0886                    "playcount",
 0887                    userdata.PlayCount.ToString(CultureInfo.InvariantCulture));
 0888                writer.WriteElementString(
 0889                    "watched",
 0890                    userdata.Played.ToString(CultureInfo.InvariantCulture).ToLowerInvariant());
 891
 0892                if (userdata.LastPlayedDate.HasValue)
 893                {
 0894                    writer.WriteElementString(
 0895                        "lastplayed",
 0896                        userdata.LastPlayedDate.Value.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture).ToLo
 897                }
 898
 0899                writer.WriteStartElement("resume");
 900
 0901                var runTimeTicks = item.RunTimeTicks ?? 0;
 902
 0903                writer.WriteElementString(
 0904                    "position",
 0905                    TimeSpan.FromTicks(userdata.PlaybackPositionTicks).TotalSeconds.ToString(CultureInfo.InvariantCultur
 0906                writer.WriteElementString(
 0907                    "total",
 0908                    TimeSpan.FromTicks(runTimeTicks).TotalSeconds.ToString(CultureInfo.InvariantCulture));
 909            }
 910
 0911            writer.WriteEndElement();
 0912        }
 913
 914        private void AddActors(List<PersonInfo> people, XmlWriter writer, ILibraryManager libraryManager, bool saveImage
 915        {
 0916            foreach (var person in people)
 917            {
 0918                if (person.IsType(PersonKind.Director) || person.IsType(PersonKind.Writer))
 919                {
 920                    continue;
 921                }
 922
 0923                writer.WriteStartElement("actor");
 924
 0925                if (!string.IsNullOrWhiteSpace(person.Name))
 926                {
 0927                    writer.WriteElementString("name", person.Name);
 928                }
 929
 0930                if (!string.IsNullOrWhiteSpace(person.Role))
 931                {
 0932                    writer.WriteElementString("role", person.Role);
 933                }
 934
 0935                if (person.Type != PersonKind.Unknown)
 936                {
 0937                    writer.WriteElementString("type", person.Type.ToString());
 938                }
 939
 0940                if (person.SortOrder.HasValue)
 941                {
 0942                    writer.WriteElementString(
 0943                        "sortorder",
 0944                        person.SortOrder.Value.ToString(CultureInfo.InvariantCulture));
 945                }
 946
 0947                if (saveImagePath)
 948                {
 0949                    var personEntity = libraryManager.GetPerson(person.Name);
 0950                    var image = personEntity?.GetImageInfo(ImageType.Primary, 0);
 951
 0952                    if (image is not null)
 953                    {
 0954                        writer.WriteElementString(
 0955                            "thumb",
 0956                            GetImagePathToSave(image, libraryManager));
 957                    }
 958                }
 959
 0960                writer.WriteEndElement();
 961            }
 0962        }
 963
 964        private string GetImagePathToSave(ItemImageInfo image, ILibraryManager libraryManager)
 965        {
 0966            if (!image.IsLocalFile)
 967            {
 0968                return image.Path;
 969            }
 970
 0971            return libraryManager.GetPathAfterNetworkSubstitution(image.Path);
 972        }
 973
 974        private void AddCustomTags(string path, IReadOnlyCollection<string> xmlTagsUsed, XmlWriter writer, ILogger<BaseN
 975        {
 0976            var settings = new XmlReaderSettings()
 0977            {
 0978                ValidationType = ValidationType.None,
 0979                CheckCharacters = false,
 0980                IgnoreProcessingInstructions = true,
 0981                IgnoreComments = true
 0982            };
 983
 0984            using (var fileStream = File.OpenRead(path))
 0985            using (var streamReader = new StreamReader(fileStream, Encoding.UTF8))
 0986            using (var reader = XmlReader.Create(streamReader, settings))
 987            {
 988                try
 989                {
 0990                    reader.MoveToContent();
 0991                }
 0992                catch (Exception ex)
 993                {
 0994                    logger.LogError(ex, "Error reading existing xml tags from {Path}.", path);
 0995                    return;
 996                }
 997
 0998                reader.Read();
 999
 1000                // Loop through each element
 01001                while (!reader.EOF && reader.ReadState == ReadState.Interactive)
 1002                {
 01003                    if (reader.NodeType == XmlNodeType.Element)
 1004                    {
 01005                        var name = reader.Name;
 1006
 01007                        if (!_commonTags.Contains(name)
 01008                            && !xmlTagsUsed.Contains(name, StringComparison.OrdinalIgnoreCase))
 1009                        {
 01010                            writer.WriteNode(reader, false);
 1011                        }
 1012                        else
 1013                        {
 01014                            reader.Skip();
 1015                        }
 1016                    }
 1017                    else
 1018                    {
 01019                        reader.Read();
 1020                    }
 1021                }
 01022            }
 01023        }
 1024
 1025        private string GetTagForProviderKey(string providerKey)
 01026            => providerKey.ToLowerInvariant() + "id";
 1027    }
 1028}

Methods/Properties

.cctor()
.ctor(MediaBrowser.Model.IO.IFileSystem,MediaBrowser.Controller.Configuration.IServerConfigurationManager,MediaBrowser.Controller.Library.ILibraryManager,MediaBrowser.Controller.Library.IUserManager,MediaBrowser.Controller.Library.IUserDataManager,Microsoft.Extensions.Logging.ILogger`1<MediaBrowser.XbmcMetadata.Savers.BaseNfoSaver>)
get_MinimumUpdateType()
get_Name()
get_SaverName()
GetSavePath(MediaBrowser.Controller.Entities.BaseItem)
SetHidden(System.String,System.Boolean)
Save(MediaBrowser.Controller.Entities.BaseItem,System.IO.Stream,System.String)
AddMediaInfo(T,System.Xml.XmlWriter)
AddCommonNodes(MediaBrowser.Controller.Entities.BaseItem,System.Xml.XmlWriter,MediaBrowser.Controller.Library.ILibraryManager,MediaBrowser.Controller.Library.IUserManager,MediaBrowser.Controller.Library.IUserDataManager,MediaBrowser.Controller.Configuration.IServerConfigurationManager)
AddCollectionItems(MediaBrowser.Controller.Entities.Folder,System.Xml.XmlWriter)
GetOutputTrailerUrl(System.String)
AddImages(MediaBrowser.Controller.Entities.BaseItem,System.Xml.XmlWriter,MediaBrowser.Controller.Library.ILibraryManager)
AddUserData(MediaBrowser.Controller.Entities.BaseItem,System.Xml.XmlWriter,MediaBrowser.Controller.Library.IUserManager,MediaBrowser.Controller.Library.IUserDataManager,MediaBrowser.Model.Configuration.XbmcMetadataOptions)
AddActors(System.Collections.Generic.List`1<MediaBrowser.Controller.Entities.PersonInfo>,System.Xml.XmlWriter,MediaBrowser.Controller.Library.ILibraryManager,System.Boolean)
GetImagePathToSave(MediaBrowser.Controller.Entities.ItemImageInfo,MediaBrowser.Controller.Library.ILibraryManager)
AddCustomTags(System.String,System.Collections.Generic.IReadOnlyCollection`1<System.String>,System.Xml.XmlWriter,Microsoft.Extensions.Logging.ILogger`1<MediaBrowser.XbmcMetadata.Savers.BaseNfoSaver>)
GetTagForProviderKey(System.String)