src/Cli/Program.cs
+3
-9
diff --git a/src/Cli/Program.cs b/src/Cli/Program.cs
index 9a1df89..751ac5a 100644
@@ -16,15 +16,9 @@ builder.Services.AddJellyfinDatabase().AddFfmpegServices();
using var app = builder.Build();
var subtitleReader = app.Services.GetRequiredService<ISubtitleReader>();
var mediaRepository = app.Services.GetRequiredService<IMediaRepository>();
var lines = await subtitleReader.ReadSubtitles(
new("media", args[0], new Subtitles.Embedded(0)),
CancellationToken.None
);
var media = await mediaRepository.GetRandomMediaItem(CancellationToken.None);
var logger = app.Services.GetRequiredService<ILogger<Program>>();
foreach (var line in lines)
{
logger.LogInformation("{Start} ({Duration}): {Lines}", line.Start, line.Duration, line.Lines);
}
logger.LogInformation("{Name} ({Path}): {Subs}", media.Name, media.Path, media.Subtitles);
src/Domain/IMediaRepository.cs
+9
-0
diff --git a/src/Domain/IMediaRepository.cs b/src/Domain/IMediaRepository.cs
new file mode 100644
index 0000000..14316e3
@@ -0,0 +1,9 @@
using System.Threading;
using System.Threading.Tasks;
namespace Slopper.Domain;
public interface IMediaRepository
{
Task<MediaItem> GetRandomMediaItem(CancellationToken cancellationToken);
}
src/Infrastructure/Database/MediaRepository.cs
+59
-0
diff --git a/src/Infrastructure/Database/MediaRepository.cs b/src/Infrastructure/Database/MediaRepository.cs
new file mode 100644
index 0000000..91ba028
@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Database.Implementations;
using Jellyfin.Database.Implementations.Entities;
using Microsoft.EntityFrameworkCore;
using Slopper.Domain;
namespace Slopper.Infrastructure.Database;
internal sealed class MediaRepository(JellyfinDbContext jellyfinDbContext, Random random) : IMediaRepository
{
public async Task<MediaItem> GetRandomMediaItem(CancellationToken cancellationToken)
{
var query = jellyfinDbContext
.MediaStreamInfos.AsNoTracking()
.Where(s => s.StreamType == MediaStreamTypeEntity.Subtitle && s.Language == "eng" && s.Codec != "PGSSUB");
var count = await query.CountAsync(cancellationToken);
var index = random.Next(count);
var mediaStreamInfo =
await query
.Include(s => s.Item)
.OrderBy(s => s.ItemId)
.ThenBy(s => s.StreamIndex)
.Skip(index)
.FirstOrDefaultAsync(cancellationToken)
?? throw new Exception("No media available");
return Map(mediaStreamInfo);
}
private static MediaItem Map(MediaStreamInfo mediaStreamInfo) =>
new(MapName(mediaStreamInfo.Item), MapVideoPath(mediaStreamInfo.Item), MapSubtitle(mediaStreamInfo));
private static string MapName(BaseItemEntity item) =>
item switch
{
{ SeriesName: string seriesName, SeasonName: string seasonName, EpisodeTitle: string episodeTitle } =>
$"{seriesName} {seasonName} {episodeTitle}",
{ SeriesName: string seriesName, SeasonName: string seasonName, Name: string name } =>
$"{seriesName} {seasonName} {name}",
{ Name: string name } => name,
{ SortName: string sortName } => sortName,
{ CleanName: string cleanName } => cleanName,
_ => throw new Exception("No name for media item"),
};
private static string MapVideoPath(BaseItemEntity item) =>
item.Path ?? throw new Exception("No video stream for media item");
private static Subtitles MapSubtitle(MediaStreamInfo mediaStreamInfo) =>
mediaStreamInfo.IsExternal
? new Subtitles.External(mediaStreamInfo.Path ?? throw new Exception("External subtitles without path"))
: new Subtitles.Embedded(mediaStreamInfo.StreamIndex);
}
src/Infrastructure/Database/ServiceCollectionExtensions.cs
+14
-8
diff --git a/src/Infrastructure/Database/ServiceCollectionExtensions.cs b/src/Infrastructure/Database/ServiceCollectionExtensions.cs
index 5c2f8db..76665cb 100644
@@ -1,19 +1,25 @@
using System;
using Jellyfin.Database.Implementations;
using Jellyfin.Database.Implementations.Locking;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Slopper.Domain;
namespace Slopper.Infrastructure.Database;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddJellyfinDatabase(this IServiceCollection services) =>
services
.AddSingleton<IJellyfinDatabaseProvider, JellyfinDatabaseProvider>()
.AddSingleton<IEntityFrameworkCoreLockingBehavior, EntityFrameworkCoreLockingBehavior>()
.AddDbContext<JellyfinDbContext>(
(sp, options) =>
options.UseSqlite(sp.GetRequiredService<IConfiguration>().GetConnectionString("database"))
);
public static IServiceCollection AddJellyfinDatabase(this IServiceCollection services)
{
services.AddSingleton<IJellyfinDatabaseProvider, JellyfinDatabaseProvider>();
services.AddSingleton<IEntityFrameworkCoreLockingBehavior, EntityFrameworkCoreLockingBehavior>();
services.AddDbContext<JellyfinDbContext>(
(sp, options) => options.UseSqlite(sp.GetRequiredService<IConfiguration>().GetConnectionString("jellyfin"))
);
services.TryAddSingleton(Random.Shared);
services.AddTransient<IMediaRepository, MediaRepository>();
return services;
}
}