| | 1 | | using System; |
| | 2 | | using System.Collections.Generic; |
| | 3 | | using System.IO; |
| | 4 | | using System.Linq; |
| | 5 | | using Emby.Naming.Common; |
| | 6 | | using Emby.Naming.Video; |
| | 7 | | using MediaBrowser.Model.IO; |
| | 8 | |
|
| | 9 | | namespace Emby.Naming.AudioBook |
| | 10 | | { |
| | 11 | | /// <summary> |
| | 12 | | /// Class used to resolve Name, Year, alternative files and extras from stack of files. |
| | 13 | | /// </summary> |
| | 14 | | public class AudioBookListResolver |
| | 15 | | { |
| | 16 | | private readonly NamingOptions _options; |
| | 17 | | private readonly AudioBookResolver _audioBookResolver; |
| | 18 | |
|
| | 19 | | /// <summary> |
| | 20 | | /// Initializes a new instance of the <see cref="AudioBookListResolver"/> class. |
| | 21 | | /// </summary> |
| | 22 | | /// <param name="options">Naming options passed along to <see cref="AudioBookResolver"/> and <see cref="AudioBoo |
| | 23 | | public AudioBookListResolver(NamingOptions options) |
| | 24 | | { |
| 25 | 25 | | _options = options; |
| 25 | 26 | | _audioBookResolver = new AudioBookResolver(_options); |
| 25 | 27 | | } |
| | 28 | |
|
| | 29 | | /// <summary> |
| | 30 | | /// Resolves Name, Year and differentiate alternative files and extras from regular audiobook files. |
| | 31 | | /// </summary> |
| | 32 | | /// <param name="files">List of files related to audiobook.</param> |
| | 33 | | /// <returns>Returns IEnumerable of <see cref="AudioBookInfo"/>.</returns> |
| | 34 | | public IEnumerable<AudioBookInfo> Resolve(IEnumerable<FileSystemMetadata> files) |
| | 35 | | { |
| | 36 | | // File with empty fullname will be sorted out here. |
| | 37 | | var audiobookFileInfos = files |
| | 38 | | .Select(i => _audioBookResolver.Resolve(i.FullName)) |
| | 39 | | .OfType<AudioBookFileInfo>(); |
| | 40 | |
|
| | 41 | | var stackResult = StackResolver.ResolveAudioBooks(audiobookFileInfos); |
| | 42 | |
|
| | 43 | | foreach (var stack in stackResult) |
| | 44 | | { |
| | 45 | | var stackFiles = stack.Files |
| | 46 | | .Select(i => _audioBookResolver.Resolve(i)) |
| | 47 | | .OfType<AudioBookFileInfo>() |
| | 48 | | .ToList(); |
| | 49 | |
|
| | 50 | | stackFiles.Sort(); |
| | 51 | |
|
| | 52 | | var nameParserResult = new AudioBookNameParser(_options).Parse(stack.Name); |
| | 53 | |
|
| | 54 | | FindExtraAndAlternativeFiles(ref stackFiles, out var extras, out var alternativeVersions, nameParserResu |
| | 55 | |
|
| | 56 | | var info = new AudioBookInfo( |
| | 57 | | nameParserResult.Name, |
| | 58 | | nameParserResult.Year, |
| | 59 | | stackFiles, |
| | 60 | | extras, |
| | 61 | | alternativeVersions); |
| | 62 | |
|
| | 63 | | yield return info; |
| | 64 | | } |
| | 65 | | } |
| | 66 | |
|
| | 67 | | private void FindExtraAndAlternativeFiles(ref List<AudioBookFileInfo> stackFiles, out List<AudioBookFileInfo> ex |
| | 68 | | { |
| 35 | 69 | | extras = new List<AudioBookFileInfo>(); |
| 35 | 70 | | alternativeVersions = new List<AudioBookFileInfo>(); |
| | 71 | |
|
| 35 | 72 | | var haveChaptersOrPages = stackFiles.Any(x => x.ChapterNumber is not null || x.PartNumber is not null); |
| 35 | 73 | | var groupedBy = stackFiles.GroupBy(file => new { file.ChapterNumber, file.PartNumber }); |
| 35 | 74 | | var nameWithReplacedDots = nameParserResult.Name.Replace(' ', '.'); |
| | 75 | |
|
| 164 | 76 | | foreach (var group in groupedBy) |
| | 77 | | { |
| 47 | 78 | | if (group.Key.ChapterNumber is null && group.Key.PartNumber is null) |
| | 79 | | { |
| 20 | 80 | | if (group.Count() > 1 || haveChaptersOrPages) |
| | 81 | | { |
| 9 | 82 | | List<AudioBookFileInfo>? ex = null; |
| 9 | 83 | | List<AudioBookFileInfo>? alt = null; |
| | 84 | |
|
| 50 | 85 | | foreach (var audioFile in group) |
| | 86 | | { |
| 16 | 87 | | var name = Path.GetFileNameWithoutExtension(audioFile.Path.AsSpan()); |
| 16 | 88 | | if (name.Equals("audiobook", StringComparison.OrdinalIgnoreCase) |
| 16 | 89 | | || name.Contains(nameParserResult.Name, StringComparison.OrdinalIgnoreCase) |
| 16 | 90 | | || name.Contains(nameWithReplacedDots, StringComparison.OrdinalIgnoreCase)) |
| | 91 | | { |
| 7 | 92 | | (alt ??= new()).Add(audioFile); |
| | 93 | | } |
| | 94 | | else |
| | 95 | | { |
| 9 | 96 | | (ex ??= new()).Add(audioFile); |
| | 97 | | } |
| | 98 | | } |
| | 99 | |
|
| 9 | 100 | | if (ex is not null) |
| | 101 | | { |
| 8 | 102 | | var extra = ex |
| 8 | 103 | | .OrderBy(x => x.Container) |
| 8 | 104 | | .ThenBy(x => x.Path) |
| 8 | 105 | | .ToList(); |
| | 106 | |
|
| 8 | 107 | | stackFiles = stackFiles.Except(extra).ToList(); |
| 8 | 108 | | extras.AddRange(extra); |
| | 109 | | } |
| | 110 | |
|
| 9 | 111 | | if (alt is not null) |
| | 112 | | { |
| 5 | 113 | | var alternatives = alt |
| 5 | 114 | | .OrderBy(x => x.Container) |
| 5 | 115 | | .ThenBy(x => x.Path) |
| 5 | 116 | | .ToList(); |
| | 117 | |
|
| 5 | 118 | | var main = FindMainAudioBookFile(alternatives, nameParserResult.Name); |
| 5 | 119 | | alternatives.Remove(main); |
| 5 | 120 | | stackFiles = stackFiles.Except(alternatives).ToList(); |
| 5 | 121 | | alternativeVersions.AddRange(alternatives); |
| | 122 | | } |
| | 123 | | } |
| | 124 | | } |
| 27 | 125 | | else if (group.Count() > 1) |
| | 126 | | { |
| 3 | 127 | | var alternatives = group |
| 3 | 128 | | .OrderBy(x => x.Container) |
| 3 | 129 | | .ThenBy(x => x.Path) |
| 3 | 130 | | .Skip(1) |
| 3 | 131 | | .ToList(); |
| | 132 | |
|
| 3 | 133 | | stackFiles = stackFiles.Except(alternatives).ToList(); |
| 3 | 134 | | alternativeVersions.AddRange(alternatives); |
| | 135 | | } |
| | 136 | | } |
| 35 | 137 | | } |
| | 138 | |
|
| | 139 | | private AudioBookFileInfo FindMainAudioBookFile(List<AudioBookFileInfo> files, string name) |
| | 140 | | { |
| 5 | 141 | | var main = files.Find(x => Path.GetFileNameWithoutExtension(x.Path).Equals(name, StringComparison.OrdinalIgn |
| 5 | 142 | | main ??= files.FirstOrDefault(x => Path.GetFileNameWithoutExtension(x.Path).Equals("audiobook", StringCompar |
| 5 | 143 | | main ??= files.OrderBy(x => x.Container) |
| 5 | 144 | | .ThenBy(x => x.Path) |
| 5 | 145 | | .First(); |
| | 146 | |
|
| 5 | 147 | | return main; |
| | 148 | | } |
| | 149 | | } |
| | 150 | | } |