mirror of
https://github.com/Readarr/Readarr.git
synced 2026-04-27 22:56:45 -04:00
New: Readarr 0.1
This commit is contained in:
@@ -31,29 +31,20 @@ namespace NzbDrone.Core.Parser.Model
|
||||
public List<LocalTrack> LocalTracks { get; set; }
|
||||
public int TrackCount => LocalTracks.Count;
|
||||
|
||||
public TrackMapping TrackMapping { get; set; }
|
||||
public Distance Distance { get; set; }
|
||||
public AlbumRelease AlbumRelease { get; set; }
|
||||
public Book Book { get; set; }
|
||||
public List<LocalTrack> ExistingTracks { get; set; }
|
||||
public bool NewDownload { get; set; }
|
||||
|
||||
public void PopulateMatch()
|
||||
{
|
||||
if (AlbumRelease != null)
|
||||
if (Book != null)
|
||||
{
|
||||
LocalTracks = LocalTracks.Concat(ExistingTracks).DistinctBy(x => x.Path).ToList();
|
||||
foreach (var localTrack in LocalTracks)
|
||||
{
|
||||
localTrack.Release = AlbumRelease;
|
||||
localTrack.Album = AlbumRelease.Album.Value;
|
||||
localTrack.Artist = localTrack.Album.Artist.Value;
|
||||
|
||||
if (TrackMapping.Mapping.ContainsKey(localTrack))
|
||||
{
|
||||
var track = TrackMapping.Mapping[localTrack].Item1;
|
||||
localTrack.Tracks = new List<Track> { track };
|
||||
localTrack.Distance = TrackMapping.Mapping[localTrack].Item2;
|
||||
}
|
||||
localTrack.Album = Book;
|
||||
localTrack.Artist = Book.Author.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,16 +54,4 @@ namespace NzbDrone.Core.Parser.Model
|
||||
return "[" + string.Join(", ", LocalTracks.Select(x => Path.GetDirectoryName(x.Path)).Distinct()) + "]";
|
||||
}
|
||||
}
|
||||
|
||||
public class TrackMapping
|
||||
{
|
||||
public TrackMapping()
|
||||
{
|
||||
Mapping = new Dictionary<LocalTrack, Tuple<Track, Distance>>();
|
||||
}
|
||||
|
||||
public Dictionary<LocalTrack, Tuple<Track, Distance>> Mapping { get; set; }
|
||||
public List<LocalTrack> LocalExtra { get; set; }
|
||||
public List<Track> MBExtra { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,6 @@ namespace NzbDrone.Core.Parser.Model
|
||||
{
|
||||
public class LocalTrack
|
||||
{
|
||||
public LocalTrack()
|
||||
{
|
||||
Tracks = new List<Track>();
|
||||
}
|
||||
|
||||
public string Path { get; set; }
|
||||
public long Size { get; set; }
|
||||
public DateTime Modified { get; set; }
|
||||
@@ -20,10 +15,8 @@ namespace NzbDrone.Core.Parser.Model
|
||||
public ParsedTrackInfo FolderTrackInfo { get; set; }
|
||||
public ParsedAlbumInfo DownloadClientAlbumInfo { get; set; }
|
||||
public List<string> AcoustIdResults { get; set; }
|
||||
public Artist Artist { get; set; }
|
||||
public Album Album { get; set; }
|
||||
public AlbumRelease Release { get; set; }
|
||||
public List<Track> Tracks { get; set; }
|
||||
public Author Artist { get; set; }
|
||||
public Book Album { get; set; }
|
||||
public Distance Distance { get; set; }
|
||||
public QualityModel Quality { get; set; }
|
||||
public bool ExistingFile { get; set; }
|
||||
|
||||
@@ -11,7 +11,11 @@ namespace NzbDrone.Core.Parser.Model
|
||||
public string CleanTitle { get; set; }
|
||||
public string ArtistTitle { get; set; }
|
||||
public string AlbumTitle { get; set; }
|
||||
public ArtistTitleInfo ArtistTitleInfo { get; set; }
|
||||
public string SeriesTitle { get; set; }
|
||||
public string SeriesIndex { get; set; }
|
||||
public string Isbn { get; set; }
|
||||
public string Asin { get; set; }
|
||||
public string GoodreadsId { get; set; }
|
||||
public string ArtistMBId { get; set; }
|
||||
public string AlbumMBId { get; set; }
|
||||
public string ReleaseMBId { get; set; }
|
||||
@@ -21,13 +25,16 @@ namespace NzbDrone.Core.Parser.Model
|
||||
public int DiscCount { get; set; }
|
||||
public IsoCountry Country { get; set; }
|
||||
public uint Year { get; set; }
|
||||
public string Publisher { get; set; }
|
||||
public string Label { get; set; }
|
||||
public string Source { get; set; }
|
||||
public string CatalogNumber { get; set; }
|
||||
public string Disambiguation { get; set; }
|
||||
public TimeSpan Duration { get; set; }
|
||||
public QualityModel Quality { get; set; }
|
||||
public MediaInfoModel MediaInfo { get; set; }
|
||||
public int[] TrackNumbers { get; set; }
|
||||
public string Language { get; set; }
|
||||
public string ReleaseGroup { get; set; }
|
||||
public string ReleaseHash { get; set; }
|
||||
|
||||
|
||||
@@ -10,15 +10,15 @@ namespace NzbDrone.Core.Parser.Model
|
||||
{
|
||||
public ReleaseInfo Release { get; set; }
|
||||
public ParsedAlbumInfo ParsedAlbumInfo { get; set; }
|
||||
public Artist Artist { get; set; }
|
||||
public List<Album> Albums { get; set; }
|
||||
public Author Artist { get; set; }
|
||||
public List<Book> Albums { get; set; }
|
||||
public bool DownloadAllowed { get; set; }
|
||||
public TorrentSeedConfiguration SeedConfiguration { get; set; }
|
||||
public int PreferredWordScore { get; set; }
|
||||
|
||||
public RemoteAlbum()
|
||||
{
|
||||
Albums = new List<Album>();
|
||||
Albums = new List<Book>();
|
||||
}
|
||||
|
||||
public bool IsRecentAlbum()
|
||||
|
||||
@@ -195,7 +195,7 @@ namespace NzbDrone.Core.Parser
|
||||
private static readonly Regex YearInTitleRegex = new Regex(@"^(?<title>.+?)(?:\W|_)?(?<year>\d{4})",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex WordDelimiterRegex = new Regex(@"(\s|\.|,|_|-|=|\|)+", RegexOptions.Compiled);
|
||||
private static readonly Regex WordDelimiterRegex = new Regex(@"(\s|\.|,|_|-|=|\(|\)|\[|\]|\|)+", RegexOptions.Compiled);
|
||||
private static readonly Regex PunctuationRegex = new Regex(@"[^\w\s]", RegexOptions.Compiled);
|
||||
private static readonly Regex CommonWordRegex = new Regex(@"\b(a|an|the|and|or|of)\b\s?", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
private static readonly Regex SpecialEpisodeWordRegex = new Regex(@"\b(part|special|edition|christmas)\b\s?", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
@@ -219,6 +219,8 @@ namespace NzbDrone.Core.Parser
|
||||
|
||||
private static readonly Regex AfterDashRegex = new Regex(@"[-:].*", RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex CalibreIdRegex = new Regex(@"\((?<id>\d+)\)", RegexOptions.Compiled);
|
||||
|
||||
public static ParsedTrackInfo ParseMusicPath(string path)
|
||||
{
|
||||
var fileInfo = new FileInfo(path);
|
||||
@@ -291,7 +293,7 @@ namespace NzbDrone.Core.Parser
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
result.Quality = QualityParser.ParseQuality(title, null, 0);
|
||||
result.Quality = QualityParser.ParseQuality(title);
|
||||
Logger.Debug("Quality parsed: {0}", result.Quality);
|
||||
|
||||
return result;
|
||||
@@ -317,7 +319,7 @@ namespace NzbDrone.Core.Parser
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ParsedAlbumInfo ParseAlbumTitleWithSearchCriteria(string title, Artist artist, List<Album> album)
|
||||
public static ParsedAlbumInfo ParseAlbumTitleWithSearchCriteria(string title, Author artist, List<Book> album)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -341,47 +343,39 @@ namespace NzbDrone.Core.Parser
|
||||
|
||||
simpleTitle = CleanTorrentSuffixRegex.Replace(simpleTitle);
|
||||
|
||||
var escapedArtist = Regex.Escape(artistName.RemoveAccent()).Replace(@"\ ", @"[\W_]");
|
||||
var escapedAlbums = string.Join("|", album.Select(s => Regex.Escape(s.Title.RemoveAccent())).ToList()).Replace(@"\ ", @"[\W_]");
|
||||
var bestAlbum = album.OrderByDescending(x => simpleTitle.FuzzyContains(x.Title)).First();
|
||||
|
||||
var releaseRegex = new Regex(@"^(\W*|\b)(?<artist>" + escapedArtist + @")(\W*|\b).*(\W*|\b)(?<album>" + escapedAlbums + @")(\W*|\b)", RegexOptions.IgnoreCase);
|
||||
var foundArtist = GetTitleFuzzy(simpleTitle, artistName, out var remainder);
|
||||
var foundAlbum = GetTitleFuzzy(remainder, bestAlbum.Title, out _);
|
||||
|
||||
var match = releaseRegex.Matches(simpleTitle);
|
||||
Logger.Trace($"Found {foundArtist} - {foundAlbum} with fuzzy parser");
|
||||
|
||||
if (match.Count != 0)
|
||||
if (foundArtist == null || foundAlbum == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = ParseAlbumMatchCollection(match);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
result.Quality = QualityParser.ParseQuality(title, null, 0);
|
||||
Logger.Debug("Quality parsed: {0}", result.Quality);
|
||||
var result = new ParsedAlbumInfo
|
||||
{
|
||||
ArtistName = foundArtist,
|
||||
ArtistTitleInfo = GetArtistTitleInfo(foundArtist),
|
||||
AlbumTitle = foundAlbum
|
||||
};
|
||||
|
||||
result.ReleaseGroup = ParseReleaseGroup(releaseTitle);
|
||||
try
|
||||
{
|
||||
result.Quality = QualityParser.ParseQuality(title);
|
||||
Logger.Debug("Quality parsed: {0}", result.Quality);
|
||||
|
||||
var subGroup = GetSubGroup(match);
|
||||
if (!subGroup.IsNullOrWhiteSpace())
|
||||
{
|
||||
result.ReleaseGroup = subGroup;
|
||||
}
|
||||
result.ReleaseGroup = ParseReleaseGroup(releaseTitle);
|
||||
|
||||
Logger.Debug("Release Group parsed: {0}", result.ReleaseGroup);
|
||||
Logger.Debug("Release Group parsed: {0}", result.ReleaseGroup);
|
||||
|
||||
result.ReleaseHash = GetReleaseHash(match);
|
||||
if (!result.ReleaseHash.IsNullOrWhiteSpace())
|
||||
{
|
||||
Logger.Debug("Release Hash parsed: {0}", result.ReleaseHash);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
catch (InvalidDateException ex)
|
||||
{
|
||||
Logger.Debug(ex, ex.Message);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (InvalidDateException ex)
|
||||
{
|
||||
Logger.Debug(ex, ex.Message);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -396,6 +390,86 @@ namespace NzbDrone.Core.Parser
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string GetTitleFuzzy(string report, string name, out string remainder)
|
||||
{
|
||||
remainder = report;
|
||||
|
||||
Logger.Trace($"Finding '{name}' in '{report}'");
|
||||
var loc = report.ToLowerInvariant().FuzzyFind(name.ToLowerInvariant(), 0.6);
|
||||
|
||||
if (loc == -1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Logger.Trace($"start '{loc}'");
|
||||
|
||||
var boundaries = WordDelimiterRegex.Matches(report);
|
||||
|
||||
if (boundaries.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var starts = new List<int>();
|
||||
var finishes = new List<int>();
|
||||
|
||||
if (boundaries[0].Index == 0)
|
||||
{
|
||||
starts.Add(boundaries[0].Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
starts.Add(0);
|
||||
}
|
||||
|
||||
foreach (Match match in boundaries)
|
||||
{
|
||||
var start = match.Index + match.Length;
|
||||
if (start < report.Length)
|
||||
{
|
||||
starts.Add(start);
|
||||
}
|
||||
|
||||
var finish = match.Index - 1;
|
||||
if (finish >= 0)
|
||||
{
|
||||
finishes.Add(finish);
|
||||
}
|
||||
}
|
||||
|
||||
var lastMatch = boundaries[boundaries.Count - 1];
|
||||
if (lastMatch.Index + lastMatch.Length < report.Length)
|
||||
{
|
||||
finishes.Add(report.Length - 1);
|
||||
}
|
||||
|
||||
Logger.Trace(starts.ConcatToString(x => x.ToString()));
|
||||
Logger.Trace(finishes.ConcatToString(x => x.ToString()));
|
||||
|
||||
var wordStart = starts.OrderBy(x => Math.Abs(x - loc)).First();
|
||||
var wordEnd = finishes.OrderBy(x => Math.Abs(x - (loc + name.Length))).First();
|
||||
|
||||
var found = report.Substring(wordStart, wordEnd - wordStart + 1);
|
||||
|
||||
if (found.ToLowerInvariant().FuzzyMatch(name.ToLowerInvariant()) >= 0.8)
|
||||
{
|
||||
remainder = report.Remove(wordStart, wordEnd - wordStart + 1);
|
||||
return found.Replace('.', ' ').Replace('_', ' ');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static int ParseCalibreId(this string path)
|
||||
{
|
||||
var bookFolder = path.GetParentPath();
|
||||
|
||||
var match = CalibreIdRegex.Match(bookFolder);
|
||||
|
||||
return match.Success ? int.Parse(match.Groups["id"].Value) : 0;
|
||||
}
|
||||
|
||||
public static ParsedAlbumInfo ParseAlbumTitle(string title)
|
||||
{
|
||||
try
|
||||
@@ -450,7 +524,7 @@ namespace NzbDrone.Core.Parser
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
result.Quality = QualityParser.ParseQuality(title, null, 0);
|
||||
result.Quality = QualityParser.ParseQuality(title);
|
||||
Logger.Debug("Quality parsed: {0}", result.Quality);
|
||||
|
||||
result.ReleaseGroup = ParseReleaseGroup(releaseTitle);
|
||||
@@ -562,7 +636,7 @@ namespace NzbDrone.Core.Parser
|
||||
title = FileExtensionRegex.Replace(title, m =>
|
||||
{
|
||||
var extension = m.Value.ToLower();
|
||||
if (MediaFiles.MediaFileExtensions.Extensions.Contains(extension) || new[] { ".par2", ".nzb" }.Contains(extension))
|
||||
if (MediaFiles.MediaFileExtensions.AllExtensions.Contains(extension) || new[] { ".par2", ".nzb" }.Contains(extension))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
@@ -653,7 +727,6 @@ namespace NzbDrone.Core.Parser
|
||||
ParsedTrackInfo result = new ParsedTrackInfo();
|
||||
|
||||
result.ArtistTitle = artistName;
|
||||
result.ArtistTitleInfo = GetArtistTitleInfo(result.ArtistTitle);
|
||||
|
||||
Logger.Debug("Track Parsed. {0}", result);
|
||||
return result;
|
||||
|
||||
@@ -13,38 +13,37 @@ namespace NzbDrone.Core.Parser
|
||||
{
|
||||
public interface IParsingService
|
||||
{
|
||||
Artist GetArtist(string title);
|
||||
Artist GetArtistFromTag(string file);
|
||||
Author GetArtist(string title);
|
||||
Author GetArtistFromTag(string file);
|
||||
RemoteAlbum Map(ParsedAlbumInfo parsedAlbumInfo, SearchCriteriaBase searchCriteria = null);
|
||||
RemoteAlbum Map(ParsedAlbumInfo parsedAlbumInfo, int artistId, IEnumerable<int> albumIds);
|
||||
List<Album> GetAlbums(ParsedAlbumInfo parsedAlbumInfo, Artist artist, SearchCriteriaBase searchCriteria = null);
|
||||
RemoteAlbum Map(ParsedAlbumInfo parsedAlbumInfo, int authorId, IEnumerable<int> bookIds);
|
||||
List<Book> GetAlbums(ParsedAlbumInfo parsedAlbumInfo, Author artist, SearchCriteriaBase searchCriteria = null);
|
||||
|
||||
ParsedAlbumInfo ParseAlbumTitleFuzzy(string title);
|
||||
|
||||
// Music stuff here
|
||||
Album GetLocalAlbum(string filename, Artist artist);
|
||||
Book GetLocalAlbum(string filename, Author artist);
|
||||
}
|
||||
|
||||
public class ParsingService : IParsingService
|
||||
{
|
||||
private readonly IArtistService _artistService;
|
||||
private readonly IAlbumService _albumService;
|
||||
private readonly ITrackService _trackService;
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public ParsingService(ITrackService trackService,
|
||||
IArtistService artistService,
|
||||
public ParsingService(IArtistService artistService,
|
||||
IAlbumService albumService,
|
||||
IMediaFileService mediaFileService,
|
||||
Logger logger)
|
||||
{
|
||||
_albumService = albumService;
|
||||
_artistService = artistService;
|
||||
_trackService = trackService;
|
||||
_mediaFileService = mediaFileService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Artist GetArtist(string title)
|
||||
public Author GetArtist(string title)
|
||||
{
|
||||
var parsedAlbumInfo = Parser.ParseAlbumTitle(title);
|
||||
|
||||
@@ -64,11 +63,11 @@ namespace NzbDrone.Core.Parser
|
||||
return artistInfo;
|
||||
}
|
||||
|
||||
public Artist GetArtistFromTag(string file)
|
||||
public Author GetArtistFromTag(string file)
|
||||
{
|
||||
var parsedTrackInfo = Parser.ParseMusicPath(file);
|
||||
|
||||
var artist = new Artist();
|
||||
var artist = new Author();
|
||||
|
||||
if (parsedTrackInfo.ArtistMBId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
@@ -116,17 +115,17 @@ namespace NzbDrone.Core.Parser
|
||||
return remoteAlbum;
|
||||
}
|
||||
|
||||
public List<Album> GetAlbums(ParsedAlbumInfo parsedAlbumInfo, Artist artist, SearchCriteriaBase searchCriteria = null)
|
||||
public List<Book> GetAlbums(ParsedAlbumInfo parsedAlbumInfo, Author artist, SearchCriteriaBase searchCriteria = null)
|
||||
{
|
||||
var albumTitle = parsedAlbumInfo.AlbumTitle;
|
||||
var result = new List<Album>();
|
||||
var result = new List<Book>();
|
||||
|
||||
if (parsedAlbumInfo.AlbumTitle == null)
|
||||
{
|
||||
return new List<Album>();
|
||||
return new List<Book>();
|
||||
}
|
||||
|
||||
Album albumInfo = null;
|
||||
Book albumInfo = null;
|
||||
|
||||
if (parsedAlbumInfo.Discography)
|
||||
{
|
||||
@@ -157,13 +156,13 @@ namespace NzbDrone.Core.Parser
|
||||
if (albumInfo == null)
|
||||
{
|
||||
// TODO: Search by Title and Year instead of just Title when matching
|
||||
albumInfo = _albumService.FindByTitle(artist.ArtistMetadataId, parsedAlbumInfo.AlbumTitle);
|
||||
albumInfo = _albumService.FindByTitle(artist.AuthorMetadataId, parsedAlbumInfo.AlbumTitle);
|
||||
}
|
||||
|
||||
if (albumInfo == null)
|
||||
{
|
||||
_logger.Debug("Trying inexact album match for {0}", parsedAlbumInfo.AlbumTitle);
|
||||
albumInfo = _albumService.FindByTitleInexact(artist.ArtistMetadataId, parsedAlbumInfo.AlbumTitle);
|
||||
albumInfo = _albumService.FindByTitleInexact(artist.AuthorMetadataId, parsedAlbumInfo.AlbumTitle);
|
||||
}
|
||||
|
||||
if (albumInfo != null)
|
||||
@@ -178,19 +177,19 @@ namespace NzbDrone.Core.Parser
|
||||
return result;
|
||||
}
|
||||
|
||||
public RemoteAlbum Map(ParsedAlbumInfo parsedAlbumInfo, int artistId, IEnumerable<int> albumIds)
|
||||
public RemoteAlbum Map(ParsedAlbumInfo parsedAlbumInfo, int authorId, IEnumerable<int> bookIds)
|
||||
{
|
||||
return new RemoteAlbum
|
||||
{
|
||||
ParsedAlbumInfo = parsedAlbumInfo,
|
||||
Artist = _artistService.GetArtist(artistId),
|
||||
Albums = _albumService.GetAlbums(albumIds)
|
||||
Artist = _artistService.GetArtist(authorId),
|
||||
Albums = _albumService.GetAlbums(bookIds)
|
||||
};
|
||||
}
|
||||
|
||||
private Artist GetArtist(ParsedAlbumInfo parsedAlbumInfo, SearchCriteriaBase searchCriteria)
|
||||
private Author GetArtist(ParsedAlbumInfo parsedAlbumInfo, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
Artist artist = null;
|
||||
Author artist = null;
|
||||
|
||||
if (searchCriteria != null)
|
||||
{
|
||||
@@ -217,7 +216,48 @@ namespace NzbDrone.Core.Parser
|
||||
return artist;
|
||||
}
|
||||
|
||||
public Album GetLocalAlbum(string filename, Artist artist)
|
||||
public ParsedAlbumInfo ParseAlbumTitleFuzzy(string title)
|
||||
{
|
||||
var bestScore = 0.0;
|
||||
|
||||
Author bestAuthor = null;
|
||||
Book bestBook = null;
|
||||
|
||||
var possibleAuthors = _artistService.GetReportCandidates(title);
|
||||
|
||||
foreach (var author in possibleAuthors)
|
||||
{
|
||||
_logger.Trace($"Trying possible author {author}");
|
||||
|
||||
var authorMatch = title.FuzzyMatch(author.Metadata.Value.Name, 0.5);
|
||||
var possibleBooks = _albumService.GetCandidates(author.AuthorMetadataId, title);
|
||||
|
||||
foreach (var book in possibleBooks)
|
||||
{
|
||||
var bookMatch = title.FuzzyMatch(book.Title, 0.5);
|
||||
var score = (authorMatch.Item2 + bookMatch.Item2) / 2;
|
||||
|
||||
_logger.Trace($"Book {book} has score {score}");
|
||||
|
||||
if (score > bestScore)
|
||||
{
|
||||
bestAuthor = author;
|
||||
bestBook = book;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Trace($"Best match: {bestAuthor} {bestBook}");
|
||||
|
||||
if (bestAuthor != null)
|
||||
{
|
||||
return Parser.ParseAlbumTitleWithSearchCriteria(title, bestAuthor, new List<Book> { bestBook });
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Book GetLocalAlbum(string filename, Author artist)
|
||||
{
|
||||
if (Path.HasExtension(filename))
|
||||
{
|
||||
@@ -226,10 +266,10 @@ namespace NzbDrone.Core.Parser
|
||||
|
||||
var tracksInAlbum = _mediaFileService.GetFilesByArtist(artist.Id)
|
||||
.FindAll(s => Path.GetDirectoryName(s.Path) == filename)
|
||||
.DistinctBy(s => s.AlbumId)
|
||||
.DistinctBy(s => s.BookId)
|
||||
.ToList();
|
||||
|
||||
return tracksInAlbum.Count == 1 ? _albumService.GetAlbum(tracksInAlbum.First().AlbumId) : null;
|
||||
return tracksInAlbum.Count == 1 ? _albumService.GetAlbum(tracksInAlbum.First().BookId) : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,24 +25,10 @@ namespace NzbDrone.Core.Parser
|
||||
private static readonly Regex RealRegex = new Regex(@"\b(?<real>REAL)\b",
|
||||
RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex BitRateRegex = new Regex(@"\b(?:(?<B096>96[ ]?kbps|96|[\[\(].*96.*[\]\)])|
|
||||
(?<B128>128[ ]?kbps|128|[\[\(].*128.*[\]\)])|
|
||||
(?<B160>160[ ]?kbps|160|[\[\(].*160.*[\]\)]|q5)|
|
||||
(?<B192>192[ ]?kbps|192|[\[\(].*192.*[\]\)]|q6)|
|
||||
(?<B224>224[ ]?kbps|224|[\[\(].*224.*[\]\)]|q7)|
|
||||
(?<B256>256[ ]?kbps|256|itunes\splus|[\[\(].*256.*[\]\)]|q8)|
|
||||
(?<B320>320[ ]?kbps|320|[\[\(].*320.*[\]\)]|q9)|
|
||||
(?<B500>500[ ]?kbps|500|[\[\(].*500.*[\]\)]|q10)|
|
||||
(?<VBRV0>V0[ ]?kbps|V0|[\[\(].*V0.*[\]\)])|
|
||||
(?<VBRV2>V2[ ]?kbps|V2|[\[\(].*V2.*[\]\)]))\b",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
|
||||
private static readonly Regex CodecRegex = new Regex(@"\b(?:(?<PDF>PDF)|(?<MOBI>MOBI)|(?<EPUB>EPUB)|(?<AZW3>AZW3?)|(?<MP1>MPEG Version \d(.5)? Audio, Layer 1|MP1)|(?<MP2>MPEG Version \d(.5)? Audio, Layer 2|MP2)|(?<MP3VBR>MP3.*VBR|MPEG Version \d(.5)? Audio, Layer 3 vbr)|(?<MP3CBR>MP3|MPEG Version \d(.5)? Audio, Layer 3)|(?<FLAC>flac)|(?<WAVPACK>wavpack|wv)|(?<ALAC>alac)|(?<WMA>WMA\d?)|(?<WAV>WAV|PCM)|(?<AAC>M4A|M4P|M4B|AAC|mp4a|MPEG-4 Audio(?!.*alac))|(?<OGG>OGG|OGA|Vorbis))\b|(?<APE>monkey's audio|[\[|\(].*\bape\b.*[\]|\)])|(?<OPUS>Opus Version \d(.5)? Audio|[\[|\(].*\bopus\b.*[\]|\)])",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private static readonly Regex SampleSizeRegex = new Regex(@"\b(?:(?<S24>24[ ]bit|24bit|[\[\(].*24bit.*[\]\)]))");
|
||||
|
||||
private static readonly Regex CodecRegex = new Regex(@"\b(?:(?<MP1>MPEG Version \d(.5)? Audio, Layer 1|MP1)|(?<MP2>MPEG Version \d(.5)? Audio, Layer 2|MP2)|(?<MP3VBR>MP3.*VBR|MPEG Version \d(.5)? Audio, Layer 3 vbr)|(?<MP3CBR>MP3|MPEG Version \d(.5)? Audio, Layer 3)|(?<FLAC>flac)|(?<WAVPACK>wavpack|wv)|(?<ALAC>alac)|(?<WMA>WMA\d?)|(?<WAV>WAV|PCM)|(?<AAC>M4A|M4P|M4B|AAC|mp4a|MPEG-4 Audio(?!.*alac))|(?<OGG>OGG|OGA|Vorbis))\b|(?<APE>monkey's audio|[\[|\(].*\bape\b.*[\]|\)])|(?<OPUS>Opus Version \d(.5)? Audio|[\[|\(].*\bopus\b.*[\]|\)])",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
public static QualityModel ParseQuality(string name, string desc, int fileBitrate, int fileSampleSize = 0)
|
||||
public static QualityModel ParseQuality(string name, string desc = null)
|
||||
{
|
||||
Logger.Debug("Trying to parse quality for {0}", name);
|
||||
|
||||
@@ -54,7 +40,7 @@ namespace NzbDrone.Core.Parser
|
||||
var descCodec = ParseCodec(desc, "");
|
||||
Logger.Trace($"Got codec {descCodec}");
|
||||
|
||||
result.Quality = FindQuality(descCodec, fileBitrate, fileSampleSize);
|
||||
result.Quality = FindQuality(descCodec);
|
||||
|
||||
if (result.Quality != Quality.Unknown)
|
||||
{
|
||||
@@ -64,164 +50,40 @@ namespace NzbDrone.Core.Parser
|
||||
}
|
||||
|
||||
var codec = ParseCodec(normalizedName, name);
|
||||
var bitrate = ParseBitRate(normalizedName);
|
||||
var sampleSize = ParseSampleSize(normalizedName);
|
||||
|
||||
switch (codec)
|
||||
{
|
||||
case Codec.MP1:
|
||||
case Codec.MP2:
|
||||
result.Quality = Quality.Unknown;
|
||||
case Codec.PDF:
|
||||
result.Quality = Quality.PDF;
|
||||
break;
|
||||
case Codec.MP3VBR:
|
||||
if (bitrate == BitRate.VBRV0)
|
||||
{
|
||||
result.Quality = Quality.MP3_VBR;
|
||||
}
|
||||
else if (bitrate == BitRate.VBRV2)
|
||||
{
|
||||
result.Quality = Quality.MP3_VBR_V2;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Quality = Quality.Unknown;
|
||||
}
|
||||
|
||||
case Codec.EPUB:
|
||||
result.Quality = Quality.EPUB;
|
||||
break;
|
||||
case Codec.MP3CBR:
|
||||
if (bitrate == BitRate.B096)
|
||||
{
|
||||
result.Quality = Quality.MP3_096;
|
||||
}
|
||||
else if (bitrate == BitRate.B128)
|
||||
{
|
||||
result.Quality = Quality.MP3_128;
|
||||
}
|
||||
else if (bitrate == BitRate.B160)
|
||||
{
|
||||
result.Quality = Quality.MP3_160;
|
||||
}
|
||||
else if (bitrate == BitRate.B192)
|
||||
{
|
||||
result.Quality = Quality.MP3_192;
|
||||
}
|
||||
else if (bitrate == BitRate.B256)
|
||||
{
|
||||
result.Quality = Quality.MP3_256;
|
||||
}
|
||||
else if (bitrate == BitRate.B320)
|
||||
{
|
||||
result.Quality = Quality.MP3_320;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Quality = Quality.Unknown;
|
||||
}
|
||||
|
||||
case Codec.MOBI:
|
||||
result.Quality = Quality.MOBI;
|
||||
break;
|
||||
case Codec.AZW3:
|
||||
result.Quality = Quality.AZW3;
|
||||
break;
|
||||
case Codec.FLAC:
|
||||
if (sampleSize == SampleSize.S24)
|
||||
{
|
||||
result.Quality = Quality.FLAC_24;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Quality = Quality.FLAC;
|
||||
}
|
||||
|
||||
break;
|
||||
case Codec.ALAC:
|
||||
result.Quality = Quality.ALAC;
|
||||
break;
|
||||
case Codec.WAVPACK:
|
||||
result.Quality = Quality.WAVPACK;
|
||||
result.Quality = Quality.FLAC;
|
||||
break;
|
||||
case Codec.MP1:
|
||||
case Codec.MP2:
|
||||
case Codec.MP3VBR:
|
||||
case Codec.MP3CBR:
|
||||
case Codec.APE:
|
||||
result.Quality = Quality.APE;
|
||||
break;
|
||||
case Codec.WMA:
|
||||
result.Quality = Quality.WMA;
|
||||
break;
|
||||
case Codec.WAV:
|
||||
result.Quality = Quality.WAV;
|
||||
break;
|
||||
case Codec.AAC:
|
||||
if (bitrate == BitRate.B192)
|
||||
{
|
||||
result.Quality = Quality.AAC_192;
|
||||
}
|
||||
else if (bitrate == BitRate.B256)
|
||||
{
|
||||
result.Quality = Quality.AAC_256;
|
||||
}
|
||||
else if (bitrate == BitRate.B320)
|
||||
{
|
||||
result.Quality = Quality.AAC_320;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Quality = Quality.AAC_VBR;
|
||||
}
|
||||
|
||||
break;
|
||||
case Codec.AACVBR:
|
||||
result.Quality = Quality.AAC_VBR;
|
||||
break;
|
||||
case Codec.OGG:
|
||||
case Codec.OPUS:
|
||||
if (bitrate == BitRate.B160)
|
||||
{
|
||||
result.Quality = Quality.VORBIS_Q5;
|
||||
}
|
||||
else if (bitrate == BitRate.B192)
|
||||
{
|
||||
result.Quality = Quality.VORBIS_Q6;
|
||||
}
|
||||
else if (bitrate == BitRate.B224)
|
||||
{
|
||||
result.Quality = Quality.VORBIS_Q7;
|
||||
}
|
||||
else if (bitrate == BitRate.B256)
|
||||
{
|
||||
result.Quality = Quality.VORBIS_Q8;
|
||||
}
|
||||
else if (bitrate == BitRate.B320)
|
||||
{
|
||||
result.Quality = Quality.VORBIS_Q9;
|
||||
}
|
||||
else if (bitrate == BitRate.B500)
|
||||
{
|
||||
result.Quality = Quality.VORBIS_Q10;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Quality = Quality.Unknown;
|
||||
}
|
||||
|
||||
result.Quality = Quality.MP3_320;
|
||||
break;
|
||||
case Codec.Unknown:
|
||||
if (bitrate == BitRate.B192)
|
||||
{
|
||||
result.Quality = Quality.MP3_192;
|
||||
}
|
||||
else if (bitrate == BitRate.B256)
|
||||
{
|
||||
result.Quality = Quality.MP3_256;
|
||||
}
|
||||
else if (bitrate == BitRate.B320)
|
||||
{
|
||||
result.Quality = Quality.MP3_320;
|
||||
}
|
||||
else if (bitrate == BitRate.VBR)
|
||||
{
|
||||
result.Quality = Quality.MP3_VBR_V2;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Quality = Quality.Unknown;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
result.Quality = Quality.Unknown;
|
||||
break;
|
||||
@@ -259,6 +121,26 @@ namespace NzbDrone.Core.Parser
|
||||
return Codec.Unknown;
|
||||
}
|
||||
|
||||
if (match.Groups["PDF"].Success)
|
||||
{
|
||||
return Codec.PDF;
|
||||
}
|
||||
|
||||
if (match.Groups["EPUB"].Success)
|
||||
{
|
||||
return Codec.EPUB;
|
||||
}
|
||||
|
||||
if (match.Groups["MOBI"].Success)
|
||||
{
|
||||
return Codec.MOBI;
|
||||
}
|
||||
|
||||
if (match.Groups["AZW3"].Success)
|
||||
{
|
||||
return Codec.AZW3;
|
||||
}
|
||||
|
||||
if (match.Groups["FLAC"].Success)
|
||||
{
|
||||
return Codec.FLAC;
|
||||
@@ -327,287 +209,26 @@ namespace NzbDrone.Core.Parser
|
||||
return Codec.Unknown;
|
||||
}
|
||||
|
||||
private static BitRate ParseBitRate(string name)
|
||||
{
|
||||
//var nameWithNoSpaces = Regex.Replace(name, @"\s+", "");
|
||||
var match = BitRateRegex.Match(name);
|
||||
|
||||
if (!match.Success)
|
||||
{
|
||||
return BitRate.Unknown;
|
||||
}
|
||||
|
||||
if (match.Groups["B096"].Success)
|
||||
{
|
||||
return BitRate.B096;
|
||||
}
|
||||
|
||||
if (match.Groups["B128"].Success)
|
||||
{
|
||||
return BitRate.B128;
|
||||
}
|
||||
|
||||
if (match.Groups["B160"].Success)
|
||||
{
|
||||
return BitRate.B160;
|
||||
}
|
||||
|
||||
if (match.Groups["B192"].Success)
|
||||
{
|
||||
return BitRate.B192;
|
||||
}
|
||||
|
||||
if (match.Groups["B224"].Success)
|
||||
{
|
||||
return BitRate.B224;
|
||||
}
|
||||
|
||||
if (match.Groups["B256"].Success)
|
||||
{
|
||||
return BitRate.B256;
|
||||
}
|
||||
|
||||
if (match.Groups["B320"].Success)
|
||||
{
|
||||
return BitRate.B320;
|
||||
}
|
||||
|
||||
if (match.Groups["B500"].Success)
|
||||
{
|
||||
return BitRate.B500;
|
||||
}
|
||||
|
||||
if (match.Groups["VBR"].Success)
|
||||
{
|
||||
return BitRate.VBR;
|
||||
}
|
||||
|
||||
if (match.Groups["VBRV0"].Success)
|
||||
{
|
||||
return BitRate.VBRV0;
|
||||
}
|
||||
|
||||
if (match.Groups["VBRV2"].Success)
|
||||
{
|
||||
return BitRate.VBRV2;
|
||||
}
|
||||
|
||||
return BitRate.Unknown;
|
||||
}
|
||||
|
||||
private static SampleSize ParseSampleSize(string name)
|
||||
{
|
||||
var match = SampleSizeRegex.Match(name);
|
||||
|
||||
if (!match.Success)
|
||||
{
|
||||
return SampleSize.Unknown;
|
||||
}
|
||||
|
||||
if (match.Groups["S24"].Success)
|
||||
{
|
||||
return SampleSize.S24;
|
||||
}
|
||||
|
||||
return SampleSize.Unknown;
|
||||
}
|
||||
|
||||
private static Quality FindQuality(Codec codec, int bitrate, int sampleSize = 0)
|
||||
private static Quality FindQuality(Codec codec)
|
||||
{
|
||||
switch (codec)
|
||||
{
|
||||
case Codec.ALAC:
|
||||
case Codec.FLAC:
|
||||
case Codec.WAVPACK:
|
||||
case Codec.WAV:
|
||||
return Quality.FLAC;
|
||||
case Codec.MP1:
|
||||
case Codec.MP2:
|
||||
return Quality.Unknown;
|
||||
case Codec.MP3VBR:
|
||||
return Quality.MP3_VBR;
|
||||
case Codec.MP3CBR:
|
||||
if (bitrate == 8)
|
||||
{
|
||||
return Quality.MP3_008;
|
||||
}
|
||||
|
||||
if (bitrate == 16)
|
||||
{
|
||||
return Quality.MP3_016;
|
||||
}
|
||||
|
||||
if (bitrate == 24)
|
||||
{
|
||||
return Quality.MP3_024;
|
||||
}
|
||||
|
||||
if (bitrate == 32)
|
||||
{
|
||||
return Quality.MP3_032;
|
||||
}
|
||||
|
||||
if (bitrate == 40)
|
||||
{
|
||||
return Quality.MP3_040;
|
||||
}
|
||||
|
||||
if (bitrate == 48)
|
||||
{
|
||||
return Quality.MP3_048;
|
||||
}
|
||||
|
||||
if (bitrate == 56)
|
||||
{
|
||||
return Quality.MP3_056;
|
||||
}
|
||||
|
||||
if (bitrate == 64)
|
||||
{
|
||||
return Quality.MP3_064;
|
||||
}
|
||||
|
||||
if (bitrate == 80)
|
||||
{
|
||||
return Quality.MP3_080;
|
||||
}
|
||||
|
||||
if (bitrate == 96)
|
||||
{
|
||||
return Quality.MP3_096;
|
||||
}
|
||||
|
||||
if (bitrate == 112)
|
||||
{
|
||||
return Quality.MP3_112;
|
||||
}
|
||||
|
||||
if (bitrate == 128)
|
||||
{
|
||||
return Quality.MP3_128;
|
||||
}
|
||||
|
||||
if (bitrate == 160)
|
||||
{
|
||||
return Quality.MP3_160;
|
||||
}
|
||||
|
||||
if (bitrate == 192)
|
||||
{
|
||||
return Quality.MP3_192;
|
||||
}
|
||||
|
||||
if (bitrate == 224)
|
||||
{
|
||||
return Quality.MP3_224;
|
||||
}
|
||||
|
||||
if (bitrate == 256)
|
||||
{
|
||||
return Quality.MP3_256;
|
||||
}
|
||||
|
||||
if (bitrate == 320)
|
||||
{
|
||||
return Quality.MP3_320;
|
||||
}
|
||||
|
||||
return Quality.Unknown;
|
||||
case Codec.FLAC:
|
||||
if (sampleSize == 24)
|
||||
{
|
||||
return Quality.FLAC_24;
|
||||
}
|
||||
|
||||
return Quality.FLAC;
|
||||
case Codec.ALAC:
|
||||
return Quality.ALAC;
|
||||
case Codec.WAVPACK:
|
||||
return Quality.WAVPACK;
|
||||
case Codec.APE:
|
||||
return Quality.APE;
|
||||
case Codec.WMA:
|
||||
return Quality.WMA;
|
||||
case Codec.WAV:
|
||||
return Quality.WAV;
|
||||
case Codec.AAC:
|
||||
if (bitrate == 192)
|
||||
{
|
||||
return Quality.AAC_192;
|
||||
}
|
||||
|
||||
if (bitrate == 256)
|
||||
{
|
||||
return Quality.AAC_256;
|
||||
}
|
||||
|
||||
if (bitrate == 320)
|
||||
{
|
||||
return Quality.AAC_320;
|
||||
}
|
||||
|
||||
return Quality.AAC_VBR;
|
||||
case Codec.OGG:
|
||||
if (bitrate == 160)
|
||||
{
|
||||
return Quality.VORBIS_Q5;
|
||||
}
|
||||
|
||||
if (bitrate == 192)
|
||||
{
|
||||
return Quality.VORBIS_Q6;
|
||||
}
|
||||
|
||||
if (bitrate == 224)
|
||||
{
|
||||
return Quality.VORBIS_Q7;
|
||||
}
|
||||
|
||||
if (bitrate == 256)
|
||||
{
|
||||
return Quality.VORBIS_Q8;
|
||||
}
|
||||
|
||||
if (bitrate == 320)
|
||||
{
|
||||
return Quality.VORBIS_Q9;
|
||||
}
|
||||
|
||||
if (bitrate == 500)
|
||||
{
|
||||
return Quality.VORBIS_Q10;
|
||||
}
|
||||
|
||||
return Quality.Unknown;
|
||||
case Codec.OPUS:
|
||||
if (bitrate < 130)
|
||||
{
|
||||
return Quality.Unknown;
|
||||
}
|
||||
|
||||
if (bitrate < 180)
|
||||
{
|
||||
return Quality.VORBIS_Q5;
|
||||
}
|
||||
|
||||
if (bitrate < 205)
|
||||
{
|
||||
return Quality.VORBIS_Q6;
|
||||
}
|
||||
|
||||
if (bitrate < 240)
|
||||
{
|
||||
return Quality.VORBIS_Q7;
|
||||
}
|
||||
|
||||
if (bitrate < 290)
|
||||
{
|
||||
return Quality.VORBIS_Q8;
|
||||
}
|
||||
|
||||
if (bitrate < 410)
|
||||
{
|
||||
return Quality.VORBIS_Q9;
|
||||
}
|
||||
|
||||
return Quality.VORBIS_Q10;
|
||||
default:
|
||||
return Quality.Unknown;
|
||||
return Quality.MP3_320;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -661,28 +282,10 @@ namespace NzbDrone.Core.Parser
|
||||
OGG,
|
||||
OPUS,
|
||||
WAV,
|
||||
Unknown
|
||||
}
|
||||
|
||||
public enum BitRate
|
||||
{
|
||||
B096,
|
||||
B128,
|
||||
B160,
|
||||
B192,
|
||||
B224,
|
||||
B256,
|
||||
B320,
|
||||
B500,
|
||||
VBR,
|
||||
VBRV0,
|
||||
VBRV2,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
public enum SampleSize
|
||||
{
|
||||
S24,
|
||||
PDF,
|
||||
EPUB,
|
||||
MOBI,
|
||||
AZW3,
|
||||
Unknown
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user