< Summary - Jellyfin

Information
Class: MediaBrowser.Providers.Books.ComicInfo.ComicInfoReader
Assembly: MediaBrowser.Providers
File(s): /srv/git/jellyfin/MediaBrowser.Providers/Books/ComicInfo/ComicInfoReader.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 96
Coverable lines: 96
Total lines: 212
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 22
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 6/11/2026 - 12:16:04 AM Line coverage: 0% (0/96) Branch coverage: 0% (0/22) Total lines: 212 6/11/2026 - 12:16:04 AM Line coverage: 0% (0/96) Branch coverage: 0% (0/22) Total lines: 212

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
ReadComicBookMetadata(...)0%620%
ReadPeopleMetadata(...)100%210%
ReadCultureInfoInto(...)0%620%
ReadStringInto(...)0%2040%
ReadCommaSeparatedStringsInto(...)0%4260%
ReadIntInto(...)0%2040%
ReadThreePartDateInto(...)0%620%
ParseInt(...)0%620%

File(s)

/srv/git/jellyfin/MediaBrowser.Providers/Books/ComicInfo/ComicInfoReader.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Globalization;
 4using System.Linq;
 5using System.Xml.Linq;
 6using System.Xml.XPath;
 7using Jellyfin.Data.Enums;
 8using MediaBrowser.Controller.Entities;
 9using MediaBrowser.Controller.Providers;
 10using SharpCompress;
 11
 12namespace MediaBrowser.Providers.Books.ComicInfo;
 13
 14/// <summary>
 15/// ComicInfo reader.
 16/// </summary>
 17public static class ComicInfoReader
 18{
 19    /// <summary>
 20    /// Filename to check for comic metadata either next to the comic file or inside the archive.
 21    /// </summary>
 22    public const string ComicRackMetaFile = "ComicInfo.xml";
 23
 24    /// <summary>
 25    /// Read comic book metadata.
 26    /// </summary>
 27    /// <param name="xml">The XDocument to read for comic metadata.</param>
 28    /// <returns>The resulting book.</returns>
 29    public static Book? ReadComicBookMetadata(XDocument xml)
 30    {
 031        var book = new Book();
 032        var hasFoundMetadata = false;
 33
 34        // this value is only used internally since Jellyfin has no manga flag
 035        var isManga = false;
 36
 037        hasFoundMetadata |= ReadStringInto(xml, "ComicInfo/Title", title => book.Name = title);
 038        hasFoundMetadata |= ReadStringInto(xml, "ComicInfo/Manga", manga => isManga = manga.Equals("Yes", StringComparis
 039        hasFoundMetadata |= ReadStringInto(xml, "ComicInfo/Series", series => book.SeriesName = series);
 040        hasFoundMetadata |= ReadIntInto(xml, "ComicInfo/Number", issue => book.IndexNumber = issue);
 041        hasFoundMetadata |= ReadStringInto(xml, "ComicInfo/Summary", summary => book.Overview = summary);
 042        hasFoundMetadata |= ReadIntInto(xml, "ComicInfo/Year", year => book.ProductionYear = year);
 043        hasFoundMetadata |= ReadThreePartDateInto(xml, "ComicInfo/Year", "ComicInfo/Month", "ComicInfo/Day", dateTime =>
 044        hasFoundMetadata |= ReadCommaSeparatedStringsInto(xml, "ComicInfo/Genre", genres => genres.ForEach(genre => book
 045        hasFoundMetadata |= ReadStringInto(xml, "ComicInfo/Publisher", publisher => book.SetStudios([publisher]));
 46
 047        hasFoundMetadata |= ReadStringInto(xml, "ComicInfo/AlternateSeries", title =>
 048        {
 049            if (isManga)
 050            {
 051                // Software like ComicTagger (https://github.com/comictagger/comictagger) will use
 052                // this field for the series name in the original language when tagging manga.
 053                book.OriginalTitle = title;
 054            }
 055            else
 056            {
 057                // Some US comics can be part of cross-over story arcs. This field is then used to
 058                // specify an alternate series.
 059            }
 060        });
 61
 062        return hasFoundMetadata ? book : null;
 63    }
 64
 65    /// <summary>
 66    /// Read people metadata.
 67    /// </summary>
 68    /// <param name="xml">The XDocument to read for people metadata.</param>
 69    /// <param name="metadataResult">The metadata result to update.</param>
 70    public static void ReadPeopleMetadata(XDocument xml, MetadataResult<Book> metadataResult)
 71    {
 072        ReadCommaSeparatedStringsInto(xml, "ComicInfo/Writer", authors =>
 073        {
 074            authors.ForEach(p => metadataResult.AddPerson(new PersonInfo { Name = p, Type = PersonKind.Author }));
 075        });
 76
 077        ReadCommaSeparatedStringsInto(xml, "ComicInfo/Penciller", pencillers =>
 078        {
 079            pencillers.ForEach(p => metadataResult.AddPerson(new PersonInfo { Name = p, Type = PersonKind.Penciller }));
 080        });
 81
 082        ReadCommaSeparatedStringsInto(xml, "ComicInfo/Inker", inkers =>
 083        {
 084            inkers.ForEach(p => metadataResult.AddPerson(new PersonInfo { Name = p, Type = PersonKind.Inker }));
 085        });
 86
 087        ReadCommaSeparatedStringsInto(xml, "ComicInfo/Letterer", letterers =>
 088        {
 089            letterers.ForEach(p => metadataResult.AddPerson(new PersonInfo { Name = p, Type = PersonKind.Letterer }));
 090        });
 91
 092        ReadCommaSeparatedStringsInto(xml, "ComicInfo/CoverArtist", artists =>
 093        {
 094            artists.ForEach(p => metadataResult.AddPerson(new PersonInfo { Name = p, Type = PersonKind.CoverArtist }));
 095        });
 96
 097        ReadCommaSeparatedStringsInto(xml, "ComicInfo/Colourist", colorists =>
 098        {
 099            colorists.ForEach(p => metadataResult.AddPerson(new PersonInfo { Name = p, Type = PersonKind.Colorist }));
 0100        });
 0101    }
 102
 103    /// <summary>
 104    /// Read culture information.
 105    /// </summary>
 106    /// <param name="xml">the XDocument to read for metadata.</param>
 107    /// <param name="xPath">The path to search.</param>
 108    /// <param name="commitResult">The action to take after parsing all metadata.</param>
 109    public static void ReadCultureInfoInto(XDocument xml, string xPath, Action<CultureInfo> commitResult)
 110    {
 0111        string? culture = null;
 112
 0113        if (!ReadStringInto(xml, xPath, value => culture = value))
 114        {
 0115            return;
 116        }
 117
 118        // culture cannot be null here as the method would have returned earlier
 0119        commitResult(new CultureInfo(culture!));
 0120    }
 121
 122    private static bool ReadStringInto(XDocument xml, string xPath, Action<string> commitResult)
 123    {
 0124        var resultElement = xml.XPathSelectElement(xPath);
 125
 0126        if (resultElement is not null && !string.IsNullOrWhiteSpace(resultElement.Value))
 127        {
 0128            commitResult(resultElement.Value);
 0129            return true;
 130        }
 131
 0132        return false;
 133    }
 134
 135    private static bool ReadCommaSeparatedStringsInto(XDocument xml, string xPath, Action<IEnumerable<string>> commitRes
 136    {
 0137        var resultElement = xml.XPathSelectElement(xPath);
 138
 0139        if (resultElement is null || string.IsNullOrWhiteSpace(resultElement.Value))
 140        {
 0141            return false;
 142        }
 143
 144        try
 145        {
 0146            var splits = resultElement.Value.Split(",").Select(p => p.Trim()).ToArray();
 0147            if (splits.Length < 1)
 148            {
 0149                return false;
 150            }
 151
 0152            commitResult(splits);
 0153            return true;
 154        }
 0155        catch (ArgumentNullException)
 156        {
 0157            return false;
 158        }
 0159    }
 160
 161    private static bool ReadIntInto(XDocument xml, string xPath, Action<int> commitResult)
 162    {
 0163        var resultElement = xml.XPathSelectElement(xPath);
 164
 0165        if (resultElement is not null && !string.IsNullOrWhiteSpace(resultElement.Value))
 166        {
 0167            return ParseInt(resultElement.Value, commitResult);
 168        }
 169
 0170        return false;
 171    }
 172
 173    private static bool ReadThreePartDateInto(XDocument xml, string yearXPath, string monthXPath, string dayXPath, Actio
 174    {
 0175        int year = 0;
 0176        int month = 0;
 0177        int day = 0;
 0178        var parsed = false;
 179
 0180        parsed |= ReadIntInto(xml, yearXPath, num => year = num);
 0181        parsed |= ReadIntInto(xml, monthXPath, num => month = num);
 0182        parsed |= ReadIntInto(xml, dayXPath, num => day = num);
 183
 0184        if (!parsed)
 185        {
 0186            return false;
 187        }
 188
 189        try
 190        {
 0191            var dateTime = new DateTime(year, month, day, 0, 0, 0, DateTimeKind.Unspecified);
 192
 0193            commitResult(dateTime);
 0194            return true;
 195        }
 0196        catch (ArgumentOutOfRangeException)
 197        {
 0198            return false;
 199        }
 0200    }
 201
 202    private static bool ParseInt(string input, Action<int> commitResult)
 203    {
 0204        if (int.TryParse(input, out var parsed))
 205        {
 0206            commitResult(parsed);
 0207            return true;
 208        }
 209
 0210        return false;
 211    }
 212}