< 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: 120
Coverable lines: 120
Total lines: 235
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: 2126/16/2026 - 12:14:30 AM Line coverage: 0% (0/120) Branch coverage: 0% (0/22) Total lines: 235 6/11/2026 - 12:16:04 AM Line coverage: 0% (0/96) Branch coverage: 0% (0/22) Total lines: 2126/16/2026 - 12:14:30 AM Line coverage: 0% (0/120) Branch coverage: 0% (0/22) Total lines: 235

Coverage delta

Coverage delta 1 -1

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;
 10
 11namespace MediaBrowser.Providers.Books.ComicInfo;
 12
 13/// <summary>
 14/// ComicInfo reader.
 15/// </summary>
 16public static class ComicInfoReader
 17{
 18    /// <summary>
 19    /// Filename to check for comic metadata either next to the comic file or inside the archive.
 20    /// </summary>
 21    public const string ComicRackMetaFile = "ComicInfo.xml";
 22
 23    /// <summary>
 24    /// Read comic book metadata.
 25    /// </summary>
 26    /// <param name="xml">The XDocument to read for comic metadata.</param>
 27    /// <returns>The resulting book.</returns>
 28    public static Book? ReadComicBookMetadata(XDocument xml)
 29    {
 030        var book = new Book();
 031        var hasFoundMetadata = false;
 32
 33        // this value is only used internally since Jellyfin has no manga flag
 034        var isManga = false;
 35
 036        hasFoundMetadata |= ReadStringInto(xml, "ComicInfo/Title", title => book.Name = title);
 037        hasFoundMetadata |= ReadStringInto(xml, "ComicInfo/Manga", manga => isManga = manga.Equals("Yes", StringComparis
 038        hasFoundMetadata |= ReadStringInto(xml, "ComicInfo/Series", series => book.SeriesName = series);
 039        hasFoundMetadata |= ReadIntInto(xml, "ComicInfo/Number", issue => book.IndexNumber = issue);
 040        hasFoundMetadata |= ReadStringInto(xml, "ComicInfo/Summary", summary => book.Overview = summary);
 041        hasFoundMetadata |= ReadIntInto(xml, "ComicInfo/Year", year => book.ProductionYear = year);
 042        hasFoundMetadata |= ReadThreePartDateInto(xml, "ComicInfo/Year", "ComicInfo/Month", "ComicInfo/Day", dateTime =>
 043        hasFoundMetadata |= ReadCommaSeparatedStringsInto(xml, "ComicInfo/Genre", genres =>
 044        {
 045            foreach (var genre in genres)
 046            {
 047                book.AddGenre(genre);
 048            }
 049        });
 050        hasFoundMetadata |= ReadStringInto(xml, "ComicInfo/Publisher", publisher => book.SetStudios([publisher]));
 51
 052        hasFoundMetadata |= ReadStringInto(xml, "ComicInfo/AlternateSeries", title =>
 053        {
 054            if (isManga)
 055            {
 056                // Software like ComicTagger (https://github.com/comictagger/comictagger) will use
 057                // this field for the series name in the original language when tagging manga.
 058                book.OriginalTitle = title;
 059            }
 060            else
 061            {
 062                // Some US comics can be part of cross-over story arcs. This field is then used to
 063                // specify an alternate series.
 064            }
 065        });
 66
 067        return hasFoundMetadata ? book : null;
 68    }
 69
 70    /// <summary>
 71    /// Read people metadata.
 72    /// </summary>
 73    /// <param name="xml">The XDocument to read for people metadata.</param>
 74    /// <param name="metadataResult">The metadata result to update.</param>
 75    public static void ReadPeopleMetadata(XDocument xml, MetadataResult<Book> metadataResult)
 76    {
 077        ReadCommaSeparatedStringsInto(xml, "ComicInfo/Writer", authors =>
 078        {
 079            foreach (var p in authors)
 080            {
 081                metadataResult.AddPerson(new PersonInfo { Name = p, Type = PersonKind.Author });
 082            }
 083        });
 84
 085        ReadCommaSeparatedStringsInto(xml, "ComicInfo/Penciller", pencillers =>
 086        {
 087            foreach (var p in pencillers)
 088            {
 089                metadataResult.AddPerson(new PersonInfo { Name = p, Type = PersonKind.Penciller });
 090            }
 091        });
 92
 093        ReadCommaSeparatedStringsInto(xml, "ComicInfo/Inker", inkers =>
 094        {
 095            foreach (var p in inkers)
 096            {
 097                metadataResult.AddPerson(new PersonInfo { Name = p, Type = PersonKind.Inker });
 098            }
 099        });
 100
 0101        ReadCommaSeparatedStringsInto(xml, "ComicInfo/Letterer", letterers =>
 0102        {
 0103            foreach (var p in letterers)
 0104            {
 0105                metadataResult.AddPerson(new PersonInfo { Name = p, Type = PersonKind.Letterer });
 0106            }
 0107        });
 108
 0109        ReadCommaSeparatedStringsInto(xml, "ComicInfo/CoverArtist", artists =>
 0110        {
 0111            foreach (var p in artists)
 0112            {
 0113                metadataResult.AddPerson(new PersonInfo { Name = p, Type = PersonKind.CoverArtist });
 0114            }
 0115        });
 116
 0117        ReadCommaSeparatedStringsInto(xml, "ComicInfo/Colourist", colorists =>
 0118        {
 0119            foreach (var p in colorists)
 0120            {
 0121                metadataResult.AddPerson(new PersonInfo { Name = p, Type = PersonKind.Colorist });
 0122            }
 0123        });
 0124    }
 125
 126    /// <summary>
 127    /// Read culture information.
 128    /// </summary>
 129    /// <param name="xml">the XDocument to read for metadata.</param>
 130    /// <param name="xPath">The path to search.</param>
 131    /// <param name="commitResult">The action to take after parsing all metadata.</param>
 132    public static void ReadCultureInfoInto(XDocument xml, string xPath, Action<CultureInfo> commitResult)
 133    {
 0134        string? culture = null;
 135
 0136        if (!ReadStringInto(xml, xPath, value => culture = value))
 137        {
 0138            return;
 139        }
 140
 141        // culture cannot be null here as the method would have returned earlier
 0142        commitResult(new CultureInfo(culture!));
 0143    }
 144
 145    private static bool ReadStringInto(XDocument xml, string xPath, Action<string> commitResult)
 146    {
 0147        var resultElement = xml.XPathSelectElement(xPath);
 148
 0149        if (resultElement is not null && !string.IsNullOrWhiteSpace(resultElement.Value))
 150        {
 0151            commitResult(resultElement.Value);
 0152            return true;
 153        }
 154
 0155        return false;
 156    }
 157
 158    private static bool ReadCommaSeparatedStringsInto(XDocument xml, string xPath, Action<IEnumerable<string>> commitRes
 159    {
 0160        var resultElement = xml.XPathSelectElement(xPath);
 161
 0162        if (resultElement is null || string.IsNullOrWhiteSpace(resultElement.Value))
 163        {
 0164            return false;
 165        }
 166
 167        try
 168        {
 0169            var splits = resultElement.Value.Split(",").Select(p => p.Trim()).ToArray();
 0170            if (splits.Length < 1)
 171            {
 0172                return false;
 173            }
 174
 0175            commitResult(splits);
 0176            return true;
 177        }
 0178        catch (ArgumentNullException)
 179        {
 0180            return false;
 181        }
 0182    }
 183
 184    private static bool ReadIntInto(XDocument xml, string xPath, Action<int> commitResult)
 185    {
 0186        var resultElement = xml.XPathSelectElement(xPath);
 187
 0188        if (resultElement is not null && !string.IsNullOrWhiteSpace(resultElement.Value))
 189        {
 0190            return ParseInt(resultElement.Value, commitResult);
 191        }
 192
 0193        return false;
 194    }
 195
 196    private static bool ReadThreePartDateInto(XDocument xml, string yearXPath, string monthXPath, string dayXPath, Actio
 197    {
 0198        int year = 0;
 0199        int month = 0;
 0200        int day = 0;
 0201        var parsed = false;
 202
 0203        parsed |= ReadIntInto(xml, yearXPath, num => year = num);
 0204        parsed |= ReadIntInto(xml, monthXPath, num => month = num);
 0205        parsed |= ReadIntInto(xml, dayXPath, num => day = num);
 206
 0207        if (!parsed)
 208        {
 0209            return false;
 210        }
 211
 212        try
 213        {
 0214            var dateTime = new DateTime(year, month, day, 0, 0, 0, DateTimeKind.Unspecified);
 215
 0216            commitResult(dateTime);
 0217            return true;
 218        }
 0219        catch (ArgumentOutOfRangeException)
 220        {
 0221            return false;
 222        }
 0223    }
 224
 225    private static bool ParseInt(string input, Action<int> commitResult)
 226    {
 0227        if (int.TryParse(input, out var parsed))
 228        {
 0229            commitResult(parsed);
 0230            return true;
 231        }
 232
 0233        return false;
 234    }
 235}