VirtualLib est un plugin Emby qui expose les bibliothèques de serveurs médias distants comme des bibliothèques natives sur le serveur hôte. Il repose sur trois mécanismes :
- Sync — interroge périodiquement les serveurs sources pour construire une arborescence de fichiers
.strm+.nfo - Index — Emby indexe cette arborescence comme une bibliothèque normale
- Proxy — un endpoint HTTP intégré au plugin proxifie le stream depuis la source, permettant le transcodage par le serveur hôte
Client Emby
│
▼
Emby Server A (hôte)
├── Bibliothèques locales (normales)
├── Bibliothèques virtuelles (générées par le plugin)
│ ├── Films_ServerB/
│ │ ├── Inception (2010).strm → http://A/virtuallib/proxy/emby-b/lib456/12345
│ │ └── movie.nfo
│ └── Séries_ServerB/
│
└── Plugin VirtualLib
├── IMediaServerConnector (interface)
├── EmbyConnector / PlexConnector
├── StrmGenerator + NfoGenerator
├── SyncService + LibrarySyncJob
└── ProxyController
│
└──► Serveur B (source) — stream + metadata
| Composant | Rôle | Documentation |
|---|---|---|
IMediaServerConnector |
Abstraction serveur source | connectors/SPEC.md |
EmbyConnector |
Connecteur Emby/Jellyfin | connectors/EMBY.md |
PlexConnector |
Connecteur Plex | connectors/PLEX.md |
StrmGenerator + NfoGenerator |
Génération fichiers locaux | SYNC.md |
SyncService + LibrarySyncJob |
Orchestration sync + états utilisateur | SYNC.md |
ProxyController |
Proxy streaming | PROXY.md |
AudioBookNfoProvider etc. |
Providers métadonnées locaux | media-types/AUDIOBOOK_BOOK.md |
| Référence API Emby REST | Endpoints utilisés | API_EMBY.md |
LibrarySyncJob
→ SyncService.SyncConnectorAsync()
→ Connector.ListItemsAsync()
← items[] (avec TechnicalInfo + états utilisateur)
→ StrmGenerator.Generate(item)
→ écrit .strm (URL proxy)
→ NfoGenerator.Generate(metadata)
→ écrit .nfo avec <fileinfo><streamdetails>
→ ArtworkDownloader.Download(item)
→ écrit poster.jpg, fanart.jpg
→ QueueLibraryScan()
→ SyncService.PushMetadataAsync() [polling loop post-scan]
→ FindByPath(strmPath) → item en DB ?
→ UpdateItem() + SaveMediaStreams() + SyncUserFlags()
Client Emby
→ browse → Emby A sert depuis son index local (.nfo)
→ play Inception
→ GET https://media.example.com/virtuallib/proxy/emby-b/lib456/12345
→ ProxyController
→ EmbyConnector.GetStreamUrlAsync("12345")
← "http://B:8096/Videos/12345/stream?api_key=TOKEN"
→ HttpClient.GetAsync(streamUrl, Range: bytes=0-)
→ pipe Response.Body ← upstream.Content.ReadAsStreamAsync()
← stream (Emby A peut transcoder avant d'envoyer au client)
public class PluginConfiguration : BasePluginConfiguration
{
public List<ConnectorConfig> Connectors { get; set; } = new();
public string VirtualLibraryRootPath { get; set; } = "";
public int SyncIntervalHours { get; set; } = 6;
public int ProxyTimeoutSeconds { get; set; } = 30;
// Override de l'URL de base pour les .strm (utile derrière un reverse proxy)
public string ProxyBaseUrl { get; set; } = string.Empty;
// Préfixe/suffixe du nom des médiathèques Emby partagées (mode SharedByType)
public string SharedLibraryPrefix { get; set; } = string.Empty;
public string SharedLibrarySuffix { get; set; } = string.Empty;
}
public class ConnectorConfig
{
public string Id { get; set; } // GUID unique
public string DisplayName { get; set; }
public string ServerType { get; set; } // "Emby" | "Plex" | "PlexTV"
public string ServerUrl { get; set; }
public AuthMode AuthMode { get; set; } // ApiKey | UserCredentials
public string ApiKey { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public MetadataMode MetadataMode { get; set; } // RemoteSync | RemoteSyncFull | LocalScraping
public List<string> LibraryIds { get; set; } = new();
public List<KnownLibrary> KnownLibraries { get; set; } = new();
public bool Enabled { get; set; } = true;
public int MaxParallelLibraries { get; set; } = 4;
// Isolated : médiathèque Emby dédiée par bibliothèque distante
// SharedByType : médiathèque Emby partagée par type (Movies, TvShows…)
// Dans les deux cas, fichiers physiques dans virtualLibRoot/ConnectorName/LibraryName/
public LibraryOrganization LibraryOrganization { get; set; } = LibraryOrganization.Isolated;
// User local dont les données personnelles (vu, favori, position) sont synchronisées
// avec ce connecteur. Les autres users ont leurs sessions forwardées mais leurs
// données personnelles restent locales (issue #41).
public string LocalUserId { get; set; } = string.Empty;
}- Les API keys des serveurs sources sont stockées dans la config Emby (chiffrée sur disque)
- Les tokens ne sont jamais exposés dans les URLs
.strm(tout passe par le proxy) - Le ProxyController valide connector + libraryId avant de proxifier
- Accès navigateur sans token bloqué par filtre User-Agent
- Accès utilisateur avec token → vérification droits sur la bibliothèque virtuelle Emby
- Le vrai point de contrôle pour les requêtes internes (ffprobe) est Emby via
PlaybackInfo
Détails complets → PROXY.md.
Pour ajouter un nouveau connecteur (ex: Jellyfin — issue #15) :
- Créer
JellyfinConnector : IMediaServerConnector(API très proche d'Emby — voir connectors/EMBY.md) - Implémenter la traduction API → modèles normalisés (dont états utilisateur)
- Enregistrer dans
ConnectorFactory(connectors/SPEC.md) - Ajouter
"Jellyfin"dans la listeServerTypede l'UI
Le core (StrmGenerator, NfoGenerator, ProxyController, SyncService) ne change pas.