AudioBookListResolver.cs 6.2 KB

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