< Summary - Jellyfin

Information
Class: MediaBrowser.XbmcMetadata.Parsers.EpisodeNfoParser
Assembly: MediaBrowser.XbmcMetadata
File(s): /srv/git/jellyfin/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs
Line coverage
96%
Covered lines: 80
Uncovered lines: 3
Coverable lines: 83
Total lines: 223
Line coverage: 96.3%
Branch coverage
88%
Covered branches: 71
Total branches: 80
Branch coverage: 88.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/16/2026 - 12:15:55 AM Line coverage: 97.2% (70/72) Branch coverage: 97.3% (74/76) Total lines: 2025/20/2026 - 12:15:44 AM Line coverage: 97.2% (70/72) Branch coverage: 88.1% (67/76) Total lines: 20210/29/2026 - 12:17:11 AM Line coverage: 96.3% (80/83) Branch coverage: 88.7% (71/80) Total lines: 223 5/16/2026 - 12:15:55 AM Line coverage: 97.2% (70/72) Branch coverage: 97.3% (74/76) Total lines: 2025/20/2026 - 12:15:44 AM Line coverage: 97.2% (70/72) Branch coverage: 88.1% (67/76) Total lines: 20210/29/2026 - 12:17:11 AM Line coverage: 96.3% (80/83) Branch coverage: 88.7% (71/80) Total lines: 223

Coverage delta

Coverage delta 10 -10

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
Fetch(...)94.44%181893.47%
FetchDataFromXmlNode(...)85.71%5656100%
ReadEpisodeDetailsFromXml(...)100%66100%

File(s)

/srv/git/jellyfin/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.IO;
 4using System.Linq;
 5using System.Text;
 6using System.Threading;
 7using System.Xml;
 8using MediaBrowser.Common.Configuration;
 9using MediaBrowser.Controller.Entities.TV;
 10using MediaBrowser.Controller.Extensions;
 11using MediaBrowser.Controller.Library;
 12using MediaBrowser.Controller.Providers;
 13using Microsoft.Extensions.Logging;
 14
 15namespace MediaBrowser.XbmcMetadata.Parsers
 16{
 17    /// <summary>
 18    /// Nfo parser for episodes.
 19    /// </summary>
 20    public class EpisodeNfoParser : BaseNfoParser<Episode>
 21    {
 22        /// <summary>
 23        /// Initializes a new instance of the <see cref="EpisodeNfoParser"/> class.
 24        /// </summary>
 25        /// <param name="logger">Instance of the <see cref="ILogger{BaseNfoParser}"/> interface.</param>
 26        /// <param name="config">Instance of the <see cref="IConfigurationManager"/> interface.</param>
 27        /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
 28        /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
 29        /// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
 30        /// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
 31        public EpisodeNfoParser(
 32            ILogger logger,
 33            IConfigurationManager config,
 34            IProviderManager providerManager,
 35            IUserManager userManager,
 36            IUserDataManager userDataManager,
 37            IDirectoryService directoryService)
 738            : base(logger, config, providerManager, userManager, userDataManager, directoryService)
 39        {
 740        }
 41
 42        /// <inheritdoc />
 43        protected override void Fetch(MetadataResult<Episode> item, string metadataFile, XmlReaderSettings settings, Can
 44        {
 545            item.ResetPeople();
 46
 547            var xmlFile = File.ReadAllText(metadataFile);
 48
 49            // Split the nfo into its episodedetails blocks.
 50            // This is needed because XBMC metadata uses multiple episodedetails blocks instead of an episodenumberend t
 51            const string Srch = "</episodedetails>";
 552            var blocks = new List<string>();
 53            int index;
 1554            while ((index = xmlFile.IndexOf(Srch, StringComparison.OrdinalIgnoreCase)) != -1)
 55            {
 1056                blocks.Add(xmlFile.Substring(0, index + Srch.Length));
 1057                xmlFile = xmlFile.Substring(index + Srch.Length);
 58            }
 59
 560            if (blocks.Count == 0)
 61            {
 62                // No closing tag, let the xml reader deal with whatever is in the file
 063                blocks.Add(xmlFile);
 64            }
 65
 66            // These are not going to be valid xml so no sense in causing the provider to fail and spamming the log with
 67            try
 68            {
 569                if (blocks.Count == 1)
 70                {
 271                    ReadEpisodeDetailsFromXml(item, blocks[0], settings, cancellationToken);
 272                    return;
 73                }
 74
 75                // The blocks are not guaranteed to be written in ascending episode order, so parse them all
 76                // and sort them before merging.
 377                var episodes = blocks
 378                    .Select(block =>
 379                    {
 380                        var episode = new MetadataResult<Episode>()
 381                        {
 382                            Item = new Episode()
 383                        };
 384
 385                        ReadEpisodeDetailsFromXml(episode, block, settings, cancellationToken);
 386
 387                        return (Xml: block, Result: episode);
 388                    })
 389                    .OrderBy(episode => episode.Result.Item.IndexNumber ?? int.MaxValue)
 390                    .ToList();
 91
 92                // Extract the details of the lowest numbered episode into the item that is returned to the caller
 393                ReadEpisodeDetailsFromXml(item, episodes[0].Xml, settings, cancellationToken);
 94
 95                // Concatenate the name, originalTitle and overview tags of the remaining episodes with the first one
 96                // and take the highest episode number as the last episode of the file
 397                var name = new StringBuilder(item.Item.Name);
 398                var originalTitle = new StringBuilder(item.Item.OriginalTitle);
 399                var overview = new StringBuilder(item.Item.Overview);
 16100                for (var i = 1; i < episodes.Count; i++)
 101                {
 5102                    var additionalEpisode = episodes[i].Result;
 103
 5104                    if (!string.IsNullOrEmpty(additionalEpisode.Item.Name))
 105                    {
 4106                        name.Append(" / ").Append(additionalEpisode.Item.Name);
 107                    }
 108
 5109                    if (!string.IsNullOrEmpty(additionalEpisode.Item.Overview))
 110                    {
 2111                        overview.Append(" / ").Append(additionalEpisode.Item.Overview);
 112                    }
 113
 5114                    if (!string.IsNullOrEmpty(additionalEpisode.Item.OriginalTitle))
 115                    {
 3116                        originalTitle.Append(" / ").Append(additionalEpisode.Item.OriginalTitle);
 117                    }
 118
 5119                    if (additionalEpisode.Item.IndexNumber is not null)
 120                    {
 5121                        item.Item.IndexNumberEnd = Math.Max((int)additionalEpisode.Item.IndexNumber, item.Item.IndexNumb
 122                    }
 123                }
 124
 3125                item.Item.Name = name.ToString();
 3126                item.Item.OriginalTitle = originalTitle.ToString();
 3127                item.Item.Overview = overview.ToString();
 3128            }
 0129            catch (XmlException)
 130            {
 0131            }
 5132        }
 133
 134        /// <inheritdoc />
 135        protected override void FetchDataFromXmlNode(XmlReader reader, MetadataResult<Episode> itemResult)
 136        {
 170137            var item = itemResult.Item;
 138
 170139            switch (reader.Name)
 140            {
 141                case "season":
 13142                    if (reader.TryReadInt(out var seasonNumber))
 143                    {
 13144                        item.ParentIndexNumber = seasonNumber;
 145                    }
 146
 13147                    break;
 148                case "episode":
 13149                    if (reader.TryReadInt(out var episodeNumber))
 150                    {
 13151                        item.IndexNumber = episodeNumber;
 152                    }
 153
 13154                    break;
 155                case "episodenumberend":
 1156                    if (reader.TryReadInt(out var episodeNumberEnd))
 157                    {
 1158                        item.IndexNumberEnd = episodeNumberEnd;
 159                    }
 160
 1161                    break;
 162                case "airsbefore_episode":
 163                case "displayepisode":
 2164                    if (reader.TryReadInt(out var airsBeforeEpisode))
 165                    {
 2166                        item.AirsBeforeEpisodeNumber = airsBeforeEpisode;
 167                    }
 168
 2169                    break;
 170                case "airsafter_season":
 171                case "displayafterseason":
 1172                    if (reader.TryReadInt(out var airsAfterSeason))
 173                    {
 1174                        item.AirsAfterSeasonNumber = airsAfterSeason;
 175                    }
 176
 1177                    break;
 178                case "airsbefore_season":
 179                case "displayseason":
 2180                    if (reader.TryReadInt(out var airsBeforeSeason))
 181                    {
 2182                        item.AirsBeforeSeasonNumber = airsBeforeSeason;
 183                    }
 184
 2185                    break;
 186                case "showtitle":
 1187                    item.SeriesName = reader.ReadNormalizedString();
 1188                    break;
 189                default:
 137190                    base.FetchDataFromXmlNode(reader, itemResult);
 191                    break;
 192            }
 137193        }
 194
 195        /// <summary>
 196        /// Reads the episode details from the given xml and saves the result in the provided result item.
 197        /// </summary>
 198        private void ReadEpisodeDetailsFromXml(MetadataResult<Episode> item, string xml, XmlReaderSettings settings, Can
 199        {
 13200            using (var stringReader = new StringReader(xml))
 13201            using (var reader = XmlReader.Create(stringReader, settings))
 202            {
 13203                reader.MoveToContent();
 13204                reader.Read();
 205
 206                // Loop through each element
 414207                while (!reader.EOF && reader.ReadState == ReadState.Interactive)
 208                {
 401209                    cancellationToken.ThrowIfCancellationRequested();
 210
 401211                    if (reader.NodeType == XmlNodeType.Element)
 212                    {
 170213                        FetchDataFromXmlNode(reader, item);
 214                    }
 215                    else
 216                    {
 231217                        reader.Read();
 218                    }
 219                }
 13220            }
 13221        }
 222    }
 223}