mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-07-30 03:28:28 -07:00
Added the ability to select certain libraries in jellyfin
This commit is contained in:
parent
132c935c28
commit
fc7df0e11b
11 changed files with 199 additions and 68 deletions
|
@ -13,13 +13,12 @@ namespace Ombi.Api.Jellyfin
|
|||
Task<List<JellyfinUser>> GetUsers(string baseUri, string apiKey);
|
||||
Task<JellyfinUser> LogIn(string username, string password, string apiKey, string baseUri);
|
||||
|
||||
Task<JellyfinItemContainer<JellyfinMovie>> GetAllMovies(string apiKey, int startIndex, int count, string userId,
|
||||
Task<JellyfinItemContainer<JellyfinMovie>> GetAllMovies(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri);
|
||||
|
||||
Task<JellyfinItemContainer<JellyfinEpisodes>> GetAllEpisodes(string apiKey, string parentIdFilder, int startIndex, int count, string userId,
|
||||
string baseUri);
|
||||
|
||||
Task<JellyfinItemContainer<JellyfinEpisodes>> GetAllEpisodes(string apiKey, int startIndex, int count, string userId,
|
||||
string baseUri);
|
||||
|
||||
Task<JellyfinItemContainer<JellyfinSeries>> GetAllShows(string apiKey, int startIndex, int count, string userId,
|
||||
Task<JellyfinItemContainer<JellyfinSeries>> GetAllShows(string apiKey, string parentIdFilder, int startIndex, int count, string userId,
|
||||
string baseUri);
|
||||
|
||||
Task<JellyfinItemContainer<JellyfinMovie>> GetCollection(string mediaId,
|
||||
|
|
|
@ -97,19 +97,19 @@ namespace Ombi.Api.Jellyfin
|
|||
return response;
|
||||
}
|
||||
|
||||
public async Task<JellyfinItemContainer<JellyfinMovie>> GetAllMovies(string apiKey, int startIndex, int count, string userId, string baseUri)
|
||||
public async Task<JellyfinItemContainer<JellyfinMovie>> GetAllMovies(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri)
|
||||
{
|
||||
return await GetAll<JellyfinMovie>("Movie", apiKey, userId, baseUri, true, startIndex, count);
|
||||
return await GetAll<JellyfinMovie>("Movie", apiKey, userId, baseUri, true, startIndex, count, parentIdFilder);
|
||||
}
|
||||
|
||||
public async Task<JellyfinItemContainer<JellyfinEpisodes>> GetAllEpisodes(string apiKey, int startIndex, int count, string userId, string baseUri)
|
||||
public async Task<JellyfinItemContainer<JellyfinEpisodes>> GetAllEpisodes(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri)
|
||||
{
|
||||
return await GetAll<JellyfinEpisodes>("Episode", apiKey, userId, baseUri, false, startIndex, count);
|
||||
return await GetAll<JellyfinEpisodes>("Episode", apiKey, userId, baseUri, false, startIndex, count, parentIdFilder);
|
||||
}
|
||||
|
||||
public async Task<JellyfinItemContainer<JellyfinSeries>> GetAllShows(string apiKey, int startIndex, int count, string userId, string baseUri)
|
||||
public async Task<JellyfinItemContainer<JellyfinSeries>> GetAllShows(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri)
|
||||
{
|
||||
return await GetAll<JellyfinSeries>("Series", apiKey, userId, baseUri, false, startIndex, count);
|
||||
return await GetAll<JellyfinSeries>("Series", apiKey, userId, baseUri, false, startIndex, count, parentIdFilder);
|
||||
}
|
||||
|
||||
public async Task<SeriesInformation> GetSeriesInformation(string mediaId, string apiKey, string userId, string baseUrl)
|
||||
|
@ -152,15 +152,19 @@ namespace Ombi.Api.Jellyfin
|
|||
var obj = await Api.Request<JellyfinItemContainer<T>>(request);
|
||||
return obj;
|
||||
}
|
||||
private async Task<JellyfinItemContainer<T>> GetAll<T>(string type, string apiKey, string userId, string baseUri, bool includeOverview, int startIndex, int count)
|
||||
private async Task<JellyfinItemContainer<T>> GetAll<T>(string type, string apiKey, string userId, string baseUri, bool includeOverview, int startIndex, int count, string parentIdFilder = default)
|
||||
{
|
||||
var request = new Request($"users/{userId}/items", baseUri, HttpMethod.Get);
|
||||
|
||||
request.AddQueryString("Recursive", true.ToString());
|
||||
request.AddQueryString("IncludeItemTypes", type);
|
||||
request.AddQueryString("Fields", includeOverview ? "ProviderIds,Overview" : "ProviderIds");
|
||||
request.AddQueryString("Fields", includeOverview ? "ProviderIds,Overview,ParentId" : "ProviderIds,ParentId");
|
||||
request.AddQueryString("startIndex", startIndex.ToString());
|
||||
request.AddQueryString("limit", count.ToString());
|
||||
if(!string.IsNullOrEmpty(parentIdFilder))
|
||||
{
|
||||
request.AddQueryString("ParentId", parentIdFilder);
|
||||
}
|
||||
|
||||
request.AddQueryString("IsVirtualItem", "False");
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ namespace Ombi.Schedule.Jobs.Jellyfin
|
|||
{
|
||||
try
|
||||
{
|
||||
await StartServerCache(server, jellyfinSettings);
|
||||
await StartServerCache(server);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -70,55 +70,47 @@ namespace Ombi.Schedule.Jobs.Jellyfin
|
|||
}
|
||||
|
||||
|
||||
private async Task StartServerCache(JellyfinServers server, JellyfinSettings settings)
|
||||
private async Task StartServerCache(JellyfinServers server)
|
||||
{
|
||||
if (!ValidateSettings(server))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//await _repo.ExecuteSql("DELETE FROM JellyfinEpisode");
|
||||
//await _repo.ExecuteSql("DELETE FROM JellyfinContent");
|
||||
|
||||
var movies = await Api.GetAllMovies(server.ApiKey, 0, 200, server.AdministratorId, server.FullUri);
|
||||
var totalCount = movies.TotalRecordCount;
|
||||
var processed = 1;
|
||||
if (server.JellyfinSelectedLibraries.Any())
|
||||
{
|
||||
var movieLibsToFilter = server.JellyfinSelectedLibraries.Where(x => x.Enabled && x.CollectionType == "movies");
|
||||
|
||||
var mediaToAdd = new HashSet<JellyfinContent>();
|
||||
|
||||
while (processed < totalCount)
|
||||
foreach (var movieParentIdFilder in movieLibsToFilter)
|
||||
{
|
||||
foreach (var movie in movies.Items)
|
||||
{
|
||||
if (movie.Type.Equals("boxset", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
var movieInfo =
|
||||
await Api.GetCollection(movie.Id, server.ApiKey, server.AdministratorId, server.FullUri);
|
||||
foreach (var item in movieInfo.Items)
|
||||
{
|
||||
await ProcessMovies(item, mediaToAdd, server);
|
||||
_logger.LogInformation($"Scanning Lib '{movieParentIdFilder.Title}'");
|
||||
await ProcessMovies(server, movieParentIdFilder.Key);
|
||||
}
|
||||
|
||||
processed++;
|
||||
var tvLibsToFilter = server.JellyfinSelectedLibraries.Where(x => x.Enabled && x.CollectionType == "tvshows");
|
||||
foreach (var tvParentIdFilter in tvLibsToFilter)
|
||||
{
|
||||
_logger.LogInformation($"Scanning Lib '{tvParentIdFilter.Title}'");
|
||||
await ProcessTv(server, tvParentIdFilter.Key);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
processed++;
|
||||
// Regular movie
|
||||
await ProcessMovies(movie, mediaToAdd, server);
|
||||
await ProcessMovies(server);
|
||||
await ProcessTv(server);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the next batch
|
||||
movies = await Api.GetAllMovies(server.ApiKey, processed, 200, server.AdministratorId, server.FullUri);
|
||||
await _repo.AddRange(mediaToAdd);
|
||||
mediaToAdd.Clear();
|
||||
|
||||
}
|
||||
|
||||
|
||||
private async Task ProcessTv(JellyfinServers server, string parentId = default)
|
||||
{
|
||||
// TV Time
|
||||
var tv = await Api.GetAllShows(server.ApiKey, 0, 200, server.AdministratorId, server.FullUri);
|
||||
var mediaToAdd = new HashSet<JellyfinContent>();
|
||||
var tv = await Api.GetAllShows(server.ApiKey, parentId, 0, 200, server.AdministratorId, server.FullUri);
|
||||
var totalTv = tv.TotalRecordCount;
|
||||
processed = 1;
|
||||
var processed = 1;
|
||||
while (processed < totalTv)
|
||||
{
|
||||
foreach (var tvShow in tv.Items)
|
||||
|
@ -162,14 +154,53 @@ namespace Ombi.Schedule.Jobs.Jellyfin
|
|||
}
|
||||
}
|
||||
// Get the next batch
|
||||
tv = await Api.GetAllShows(server.ApiKey, processed, 200, server.AdministratorId, server.FullUri);
|
||||
tv = await Api.GetAllShows(server.ApiKey, parentId, processed, 200, server.AdministratorId, server.FullUri);
|
||||
await _repo.AddRange(mediaToAdd);
|
||||
mediaToAdd.Clear();
|
||||
}
|
||||
|
||||
if (mediaToAdd.Any())
|
||||
{
|
||||
await _repo.AddRange(mediaToAdd);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessMovies(JellyfinServers server, string parentId = default)
|
||||
{
|
||||
var movies = await Api.GetAllMovies(server.ApiKey, parentId, 0, 200, server.AdministratorId, server.FullUri);
|
||||
var totalCount = movies.TotalRecordCount;
|
||||
var processed = 1;
|
||||
var mediaToAdd = new HashSet<JellyfinContent>();
|
||||
while (processed < totalCount)
|
||||
{
|
||||
foreach (var movie in movies.Items)
|
||||
{
|
||||
if (movie.Type.Equals("boxset", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
var movieInfo =
|
||||
await Api.GetCollection(movie.Id, server.ApiKey, server.AdministratorId, server.FullUri);
|
||||
foreach (var item in movieInfo.Items)
|
||||
{
|
||||
await ProcessMovies(item, mediaToAdd, server);
|
||||
}
|
||||
|
||||
processed++;
|
||||
}
|
||||
else
|
||||
{
|
||||
processed++;
|
||||
// Regular movie
|
||||
await ProcessMovies(movie, mediaToAdd, server);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the next batch
|
||||
movies = await Api.GetAllMovies(server.ApiKey, parentId, processed, 200, server.AdministratorId, server.FullUri);
|
||||
await _repo.AddRange(mediaToAdd);
|
||||
mediaToAdd.Clear();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessMovies(JellyfinMovie movieInfo, ICollection<JellyfinContent> content, JellyfinServers server)
|
||||
{
|
||||
|
|
|
@ -72,7 +72,20 @@ namespace Ombi.Schedule.Jobs.Jellyfin
|
|||
.SendAsync(NotificationHub.NotificationEvent, "Jellyfin Episode Sync Started");
|
||||
foreach (var server in settings.Servers)
|
||||
{
|
||||
await CacheEpisodes(server);
|
||||
|
||||
if (server.JellyfinSelectedLibraries.Any())
|
||||
{
|
||||
var tvLibsToFilter = server.JellyfinSelectedLibraries.Where(x => x.Enabled && x.CollectionType == "tvshows");
|
||||
foreach (var tvParentIdFilter in tvLibsToFilter)
|
||||
{
|
||||
_logger.LogInformation($"Scanning Lib for episodes '{tvParentIdFilter.Title}'");
|
||||
await CacheEpisodes(server, tvParentIdFilter.Key);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await CacheEpisodes(server, string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
|
||||
|
@ -81,9 +94,9 @@ namespace Ombi.Schedule.Jobs.Jellyfin
|
|||
await OmbiQuartz.TriggerJob(nameof(IRefreshMetadata), "System");
|
||||
}
|
||||
|
||||
private async Task CacheEpisodes(JellyfinServers server)
|
||||
private async Task CacheEpisodes(JellyfinServers server, string parentIdFilter)
|
||||
{
|
||||
var allEpisodes = await Api.GetAllEpisodes(server.ApiKey, 0, 200, server.AdministratorId, server.FullUri);
|
||||
var allEpisodes = await Api.GetAllEpisodes(server.ApiKey, parentIdFilter, 0, 200, server.AdministratorId, server.FullUri);
|
||||
var total = allEpisodes.TotalRecordCount;
|
||||
var processed = 1;
|
||||
var epToAdd = new HashSet<JellyfinEpisode>();
|
||||
|
@ -150,7 +163,7 @@ namespace Ombi.Schedule.Jobs.Jellyfin
|
|||
|
||||
await _repo.AddRange(epToAdd);
|
||||
epToAdd.Clear();
|
||||
allEpisodes = await Api.GetAllEpisodes(server.ApiKey, processed, 200, server.AdministratorId, server.FullUri);
|
||||
allEpisodes = await Api.GetAllEpisodes(server.ApiKey, parentIdFilter, processed, 200, server.AdministratorId, server.FullUri);
|
||||
}
|
||||
|
||||
if (epToAdd.Any())
|
||||
|
|
|
@ -17,5 +17,13 @@ namespace Ombi.Core.Settings.Models.External
|
|||
public string AdministratorId { get; set; }
|
||||
public string ServerHostname { get; set; }
|
||||
public bool EnableEpisodeSearching { get; set; }
|
||||
public List<EmbySelectedLibraries> EmbySelectedLibraries { get; set; } = new List<EmbySelectedLibraries>();
|
||||
}
|
||||
|
||||
public class EmbySelectedLibraries
|
||||
{
|
||||
public int Key { get; set; }
|
||||
public string Title { get; set; } // Name is for display purposes
|
||||
public bool Enabled { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,5 +17,14 @@ namespace Ombi.Core.Settings.Models.External
|
|||
public string AdministratorId { get; set; }
|
||||
public string ServerHostname { get; set; }
|
||||
public bool EnableEpisodeSearching { get; set; }
|
||||
public List<JellyfinSelectedLibraries> JellyfinSelectedLibraries { get; set; } = new List<JellyfinSelectedLibraries>();
|
||||
}
|
||||
|
||||
public class JellyfinSelectedLibraries
|
||||
{
|
||||
public string Key { get; set; }
|
||||
public string Title { get; set; } // Name is for display purposes
|
||||
public string CollectionType { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,6 +64,25 @@ export interface IJellyfinServer extends IExternalSettings {
|
|||
administratorId: string;
|
||||
enableEpisodeSearching: boolean;
|
||||
serverHostname: string;
|
||||
jellyfinSelectedLibraries: IJellyfinLibrariesSettings[];
|
||||
}
|
||||
export interface IJellyfinLibrariesSettings {
|
||||
key: string;
|
||||
title: string;
|
||||
enabled: boolean;
|
||||
collectionType: string;
|
||||
}
|
||||
|
||||
export interface IJellyfinContainer<T> {
|
||||
items: T[];
|
||||
totalRecordCount: number;
|
||||
}
|
||||
|
||||
export interface IJellyfinLibrary {
|
||||
name: string;
|
||||
serverId: string;
|
||||
id: string;
|
||||
collectionType: string;
|
||||
}
|
||||
|
||||
export interface IPublicInfo {
|
||||
|
|
|
@ -5,7 +5,7 @@ import { Observable } from "rxjs";
|
|||
|
||||
import { ServiceHelpers } from "../service.helpers";
|
||||
|
||||
import { IJellyfinServer, IJellyfinSettings, IPublicInfo, IUsersModel } from "../../interfaces";
|
||||
import { IEmbyServer, IJellyfinContainer, IJellyfinLibrary, IJellyfinServer, IJellyfinSettings, IPublicInfo, IUsersModel } from "../../interfaces";
|
||||
|
||||
@Injectable()
|
||||
export class JellyfinService extends ServiceHelpers {
|
||||
|
@ -25,4 +25,7 @@ export class JellyfinService extends ServiceHelpers {
|
|||
return this.http.post<IPublicInfo>(`${this.url}info`, JSON.stringify(server), {headers: this.headers});
|
||||
}
|
||||
|
||||
public getLibraries(settings: IJellyfinServer): Observable<IJellyfinContainer<IJellyfinLibrary>> {
|
||||
return this.http.post<IJellyfinContainer<IJellyfinLibrary>>(`${this.url}Library`, JSON.stringify(settings), {headers: this.headers});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,11 +9,11 @@
|
|||
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-6 col-sm-6">
|
||||
<div style="float:right;text-align:left;">
|
||||
<div class="md-form-field">
|
||||
<mat-slide-toggle [(ngModel)]="settings.enable" (change)="toggle()" [checked]="settings.enable">Enable</mat-slide-toggle>
|
||||
</div>
|
||||
<mat-slide-toggle [(ngModel)]="settings.enable" [checked]="settings.enable">Enable
|
||||
</mat-slide-toggle>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<mat-tab-group #tabGroup [selectedIndex]="selected.value" (selectedTabChange)="addTab($event)" (selectedIndexChange)="selected.setValue($event)" animationDuration="0ms" style="display:block;">
|
||||
|
@ -75,7 +75,28 @@
|
|||
</small>
|
||||
</div>
|
||||
|
||||
|
||||
<label>Please select the libraries you want Ombi to look in for content</label>
|
||||
<br />
|
||||
<small>Note: if nothing is selected, we will monitor all libraries</small>
|
||||
<div class="md-form-field">
|
||||
<div>
|
||||
<button mat-raised-button (click)="loadLibraries(server)"
|
||||
class="mat-focus-indicator mat-stroked-button mat-button-base">Load Libraries
|
||||
<i class="fas fa-film"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<div *ngIf="server.jellyfinSelectedLibraries">
|
||||
<div *ngFor="let lib of server.jellyfinSelectedLibraries">
|
||||
<div class="md-form-field">
|
||||
<div class="checkbox">
|
||||
<mat-slide-toggle [(ngModel)]="lib.enabled" [checked]="lib.enabled"
|
||||
for="{{lib.title}}">{{lib.title}}</mat-slide-toggle>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button mat-raised-button id="testJellyfin" type="button" (click)="test(server)" class="mat-focus-indicator mat-stroked-button mat-button-base">Test Connectivity <div id="spinner"></div></button>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { Component, OnInit } from "@angular/core";
|
||||
|
||||
import { IJellyfinServer, IJellyfinSettings } from "../../interfaces";
|
||||
import { IEmbyServer, IJellyfinLibrariesSettings, IJellyfinServer, IJellyfinSettings } from "../../interfaces";
|
||||
import { JellyfinService, JobService, NotificationService, SettingsService, TesterService } from "../../services";
|
||||
import { MatTabChangeEvent } from "@angular/material/tabs";
|
||||
|
||||
import {FormControl} from '@angular/forms';
|
||||
import { MatTabChangeEvent } from "@angular/material/tabs";
|
||||
|
||||
@Component({
|
||||
templateUrl: "./jellyfin.component.html",
|
||||
|
@ -101,4 +101,28 @@ export class JellyfinComponent implements OnInit {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
public loadLibraries(server: IJellyfinServer) {
|
||||
if (server.ip == null) {
|
||||
this.notificationService.error("Jellyfin is not yet configured correctly");
|
||||
return;
|
||||
}
|
||||
this.jellyfinService.getLibraries(server).subscribe(x => {
|
||||
server.jellyfinSelectedLibraries = [];
|
||||
if (x.totalRecordCount > 0) {
|
||||
x.items.forEach((item) => {
|
||||
const lib: IJellyfinLibrariesSettings = {
|
||||
key: item.id,
|
||||
title: item.name,
|
||||
enabled: false,
|
||||
collectionType: item.collectionType
|
||||
};
|
||||
server.jellyfinSelectedLibraries.push(lib);
|
||||
});
|
||||
} else {
|
||||
this.notificationService.error("Couldn't find any libraries");
|
||||
}
|
||||
},
|
||||
err => { this.notificationService.error(err); });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { Component, OnInit } from "@angular/core";
|
||||
|
||||
import { IJellyfinSettings } from "../../interfaces";
|
||||
import { JellyfinService } from "../../services";
|
||||
import { NotificationService } from "../../services";
|
||||
|
||||
import { IJellyfinSettings } from "../../interfaces";
|
||||
|
||||
@Component({
|
||||
selector: "wizard-jellyfin",
|
||||
templateUrl: "./jellyfin.component.html",
|
||||
|
@ -35,7 +34,8 @@ export class JellyfinComponent implements OnInit {
|
|||
ssl: false,
|
||||
subDir: "",
|
||||
serverHostname: "",
|
||||
serverId: undefined
|
||||
serverId: undefined,
|
||||
jellyfinSelectedLibraries: []
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue