From 86faa9aef7e65e35ba7dc93bf2745da3d7143149 Mon Sep 17 00:00:00 2001 From: admin Date: Thu, 18 Dec 2025 13:19:16 -0600 Subject: [PATCH] feat(indexer): add multi-media type foundation Add MediaType enum and indexer support for books/audiobooks: - MediaType enum (Movie, TV, Music, Book, Audiobook, Podcast, Comic) - NewznabStandardCategory constants for all media types - Database migration 243 for SupportedMediaTypes column - Updated IndexerDefinition, IIndexer, IndexerBase - Updated README with current project status --- README.md | 46 +++++-- .../243_add_mediatype_to_indexers.cs | 14 ++ src/NzbDrone.Core/Datastore/TableMapping.cs | 2 + src/NzbDrone.Core/Indexers/IIndexer.cs | 2 + src/NzbDrone.Core/Indexers/IndexerBase.cs | 2 + .../Indexers/IndexerDefinition.cs | 4 + .../Indexers/NewznabStandardCategory.cs | 128 ++++++++++++++++++ src/NzbDrone.Core/MediaTypes/MediaType.cs | 14 ++ 8 files changed, 203 insertions(+), 9 deletions(-) create mode 100644 src/NzbDrone.Core/Datastore/Migration/243_add_mediatype_to_indexers.cs create mode 100644 src/NzbDrone.Core/Indexers/NewznabStandardCategory.cs create mode 100644 src/NzbDrone.Core/MediaTypes/MediaType.cs diff --git a/README.md b/README.md index 0504dabdd2..a73d32209d 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ All-in-one media manager for movies, books, and audiobooks. Aletheia (from Greek ἀλήθεια - "truth, disclosure") is a unified media management system forked from Radarr. It provides automated monitoring, downloading, and library management for multiple media types through a single interface. -**Current Status:** Early development. Movie functionality inherited from Radarr is working. Book and audiobook support is planned. +**Current Status:** Active development. Movie functionality inherited from Radarr is working. Multi-media foundation being implemented. ## Features @@ -15,20 +15,21 @@ Aletheia (from Greek ἀλήθεια - "truth, disclosure") is a unified media m - Metadata and artwork management - Integration with download clients and indexers -**Books (planned):** +**Books (in development):** - EPUB, MOBI, PDF quality tracking - Author and series hierarchy - Goodreads/Hardcover metadata -**Audiobooks (planned):** +**Audiobooks (in development):** - M4B, MP3, FLAC support -- Narrator tracking and duration metadata -- Audible metadata integration +- Narrator tracking (competitive differentiator) +- Duration metadata and Audible integration **General:** - Usenet and BitTorrent support - SABnzbd, NZBGet, qBittorrent, Deluge, rTorrent, Transmission integration - Plex and Kodi integration +- Built-in archive extraction (Unpackerr functionality) ## Privacy @@ -69,13 +70,40 @@ dotnet run --project src/Radarr ## Roadmap -1. **Foundation** - Generalize database schema, implement hierarchical monitoring (Author → Series → Item) -2. **Multi-Media** - Add book and audiobook quality profiles, port metadata providers -3. **Interface** - Unified dashboard with type filters, type-specific detail views +See [ROADMAP.md](../ROADMAP.md) for detailed phase planning. + +**Completed:** +- Phase 0-1: Privacy & security fixes +- Phase 2: Foundation (fork, CI/CD, branding) +- Phase 2.5: Community standards, quality gates, Unpackerr absorption + +**Current:** +- Phase 3: Multi-media foundation (database generalization, indexer management) + +**Planned:** +- Phase 4: Books & audiobooks support +- Phase 5: TV shows +- Phase 6: Music (with fingerprinting and quality analysis) +- Phase 7: Subtitles (Bazarr replacement), podcasts, comics + +## Key Differences from Radarr + +| Feature | Radarr | Aletheia | +|---------|--------|----------| +| Media types | Movies only | Movies, books, audiobooks (planned: TV, music, podcasts) | +| Telemetry | Enabled by default | Disabled by default | +| Indexer management | External (Prowlarr) | Built-in (planned) | +| Archive extraction | External (Unpackerr) | Built-in | +| Narrator tracking | N/A | Native support for audiobooks | ## Contributing -Early development phase. See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and code guidelines. +See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, code guidelines, and PR process. + +**Development standards:** +- Conventional commits (`feat:`, `fix:`, `docs:`, etc.) +- Feature branches + PRs to `develop` +- Pre-commit hooks for linting ## License diff --git a/src/NzbDrone.Core/Datastore/Migration/243_add_mediatype_to_indexers.cs b/src/NzbDrone.Core/Datastore/Migration/243_add_mediatype_to_indexers.cs new file mode 100644 index 0000000000..13935c59f1 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/243_add_mediatype_to_indexers.cs @@ -0,0 +1,14 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(243)] + public class add_mediatype_to_indexers : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Alter.Table("Indexers").AddColumn("SupportedMediaTypes").AsString().WithDefaultValue("[\"Movie\"]"); + } + } +} diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs index b0412ce210..1606f5cf56 100644 --- a/src/NzbDrone.Core/Datastore/TableMapping.cs +++ b/src/NzbDrone.Core/Datastore/TableMapping.cs @@ -26,6 +26,7 @@ using NzbDrone.Core.Jobs; using NzbDrone.Core.Languages; using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.MediaTypes; using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Movies; using NzbDrone.Core.Movies.AlternativeTitles; @@ -202,6 +203,7 @@ private static void RegisterMappers() SqlMapper.AddTypeHandler(new DapperLanguageIntConverter()); SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter>(new LanguageIntConverter())); SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter>()); + SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter>()); SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter(new QualityIntConverter(), new LanguageIntConverter())); SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter()); SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter()); diff --git a/src/NzbDrone.Core/Indexers/IIndexer.cs b/src/NzbDrone.Core/Indexers/IIndexer.cs index 3f4e3ba2db..30f56604ce 100644 --- a/src/NzbDrone.Core/Indexers/IIndexer.cs +++ b/src/NzbDrone.Core/Indexers/IIndexer.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using NzbDrone.Common.Http; using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.MediaTypes; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.ThingiProvider; @@ -12,6 +13,7 @@ public interface IIndexer : IProvider bool SupportsRss { get; } bool SupportsSearch { get; } DownloadProtocol Protocol { get; } + IEnumerable SupportedMediaTypes { get; } Task> FetchRecent(); Task> Fetch(MovieSearchCriteria searchCriteria); diff --git a/src/NzbDrone.Core/Indexers/IndexerBase.cs b/src/NzbDrone.Core/Indexers/IndexerBase.cs index 335486bc28..9c1e5318dc 100644 --- a/src/NzbDrone.Core/Indexers/IndexerBase.cs +++ b/src/NzbDrone.Core/Indexers/IndexerBase.cs @@ -9,6 +9,7 @@ using NzbDrone.Core.Configuration; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Languages; +using NzbDrone.Core.MediaTypes; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.ThingiProvider; @@ -29,6 +30,7 @@ public abstract class IndexerBase : IIndexer public abstract bool SupportsRss { get; } public abstract bool SupportsSearch { get; } + public virtual IEnumerable SupportedMediaTypes => new[] { MediaType.Movie }; public IndexerBase(IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger) { diff --git a/src/NzbDrone.Core/Indexers/IndexerDefinition.cs b/src/NzbDrone.Core/Indexers/IndexerDefinition.cs index 6bd4297030..db03429bb0 100644 --- a/src/NzbDrone.Core/Indexers/IndexerDefinition.cs +++ b/src/NzbDrone.Core/Indexers/IndexerDefinition.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using Equ; +using NzbDrone.Core.MediaTypes; using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Indexers @@ -13,6 +15,7 @@ public class IndexerDefinition : ProviderDefinition, IEquatable { MediaType.Movie }; } [MemberwiseEqualityIgnore] @@ -29,6 +32,7 @@ public IndexerDefinition() public bool EnableInteractiveSearch { get; set; } public int DownloadClientId { get; set; } public int Priority { get; set; } + public List SupportedMediaTypes { get; set; } [MemberwiseEqualityIgnore] public override bool Enable => EnableRss || EnableAutomaticSearch || EnableInteractiveSearch; diff --git a/src/NzbDrone.Core/Indexers/NewznabStandardCategory.cs b/src/NzbDrone.Core/Indexers/NewznabStandardCategory.cs new file mode 100644 index 0000000000..23b7da9295 --- /dev/null +++ b/src/NzbDrone.Core/Indexers/NewznabStandardCategory.cs @@ -0,0 +1,128 @@ +using System.Collections.Generic; +using NzbDrone.Core.MediaTypes; + +namespace NzbDrone.Core.Indexers +{ + public static class NewznabStandardCategory + { + public static readonly int Console = 1000; + public static readonly int ConsoleNDS = 1010; + public static readonly int ConsolePSP = 1020; + public static readonly int ConsoleWii = 1030; + public static readonly int ConsoleXbox = 1040; + public static readonly int ConsoleXbox360 = 1050; + public static readonly int ConsoleWiiware = 1060; + public static readonly int ConsoleXbox360DLC = 1070; + public static readonly int ConsolePS3 = 1080; + public static readonly int ConsoleOther = 1090; + public static readonly int Console3DS = 1110; + public static readonly int ConsolePSVita = 1120; + public static readonly int ConsoleWiiU = 1130; + public static readonly int ConsoleXboxOne = 1140; + public static readonly int ConsolePS4 = 1180; + + public static readonly int Movies = 2000; + public static readonly int MoviesForeign = 2010; + public static readonly int MoviesOther = 2020; + public static readonly int MoviesSD = 2030; + public static readonly int MoviesHD = 2040; + public static readonly int MoviesUHD = 2045; + public static readonly int MoviesBluRay = 2050; + public static readonly int Movies3D = 2060; + public static readonly int MoviesDVD = 2070; + public static readonly int MoviesWEBDL = 2080; + + public static readonly int Audio = 3000; + public static readonly int AudioMP3 = 3010; + public static readonly int AudioVideo = 3020; + public static readonly int AudioAudiobook = 3030; + public static readonly int AudioLossless = 3040; + public static readonly int AudioOther = 3050; + public static readonly int AudioForeign = 3060; + + public static readonly int PC = 4000; + public static readonly int PC0day = 4010; + public static readonly int PCISO = 4020; + public static readonly int PCMac = 4030; + public static readonly int PCMobileOther = 4040; + public static readonly int PCGames = 4050; + public static readonly int PCMobileiOS = 4060; + public static readonly int PCMobileAndroid = 4070; + + public static readonly int TV = 5000; + public static readonly int TVWEBDL = 5010; + public static readonly int TVForeign = 5020; + public static readonly int TVSD = 5030; + public static readonly int TVHD = 5040; + public static readonly int TVUHD = 5045; + public static readonly int TVOther = 5050; + public static readonly int TVSport = 5060; + public static readonly int TVAnime = 5070; + public static readonly int TVDocumentary = 5080; + + public static readonly int XXX = 6000; + public static readonly int XXXDVD = 6010; + public static readonly int XXXWMV = 6020; + public static readonly int XXXXviD = 6030; + public static readonly int XXXx264 = 6040; + public static readonly int XXXOther = 6050; + public static readonly int XXXImageset = 6060; + public static readonly int XXXPacks = 6070; + + public static readonly int Books = 7000; + public static readonly int BooksMags = 7010; + public static readonly int BooksEBook = 7020; + public static readonly int BooksComics = 7030; + public static readonly int BooksTechnical = 7040; + public static readonly int BooksOther = 7050; + public static readonly int BooksForeign = 7060; + + public static readonly int Other = 8000; + public static readonly int OtherMisc = 8010; + public static readonly int OtherHashed = 8020; + + public static IReadOnlyList GetCategoriesForMediaType(MediaType mediaType) + { + return mediaType switch + { + MediaType.Movie => new[] + { + Movies, MoviesForeign, MoviesOther, MoviesSD, MoviesHD, + MoviesUHD, MoviesBluRay, Movies3D, MoviesDVD, MoviesWEBDL + }, + MediaType.TV => new[] + { + TV, TVWEBDL, TVForeign, TVSD, TVHD, TVUHD, + TVOther, TVSport, TVAnime, TVDocumentary + }, + MediaType.Music => new[] + { + Audio, AudioMP3, AudioVideo, AudioLossless, AudioOther, AudioForeign + }, + MediaType.Audiobook => new[] { AudioAudiobook, Audio }, + MediaType.Book => new[] + { + Books, BooksMags, BooksEBook, BooksTechnical, BooksOther, BooksForeign + }, + MediaType.Comic => new[] { BooksComics, Books }, + MediaType.Podcast => new[] { Audio, AudioOther }, + _ => new[] { Movies } + }; + } + + public static IReadOnlyList GetIgnoredCategoriesForMediaType(MediaType mediaType) + { + return mediaType switch + { + MediaType.Movie => new[] { Console, Audio, PC, XXX, Books }, + MediaType.TV => new[] { Console, Audio, PC, Movies, XXX, Books }, + MediaType.Music => new[] { Console, PC, Movies, XXX, Books, TV }, + MediaType.Audiobook => new[] { Console, PC, Movies, XXX, TV }, + MediaType.Book => new[] { Console, Audio, PC, Movies, XXX, TV }, + MediaType.Comic => new[] { Console, Audio, PC, Movies, XXX, TV }, + MediaType.Podcast => new[] { Console, PC, Movies, XXX, Books, TV }, + _ => new[] { Console, Audio, PC, XXX, Books } + }; + } + } +} diff --git a/src/NzbDrone.Core/MediaTypes/MediaType.cs b/src/NzbDrone.Core/MediaTypes/MediaType.cs new file mode 100644 index 0000000000..5a6bbf305a --- /dev/null +++ b/src/NzbDrone.Core/MediaTypes/MediaType.cs @@ -0,0 +1,14 @@ +namespace NzbDrone.Core.MediaTypes +{ + public enum MediaType + { + Unknown = 0, + Movie = 1, + TV = 2, + Music = 3, + Book = 4, + Audiobook = 5, + Podcast = 6, + Comic = 7 + } +}