11using System . Globalization ;
2+ using System . IO ;
23using Jellyfin . Data ;
34using Jellyfin . Data . Enums ;
45using Jellyfin . Database . Implementations . Entities ;
1011using MediaBrowser . Controller . LiveTv ;
1112using MediaBrowser . Controller . Persistence ;
1213using MediaBrowser . Controller . Providers ;
14+ using MediaBrowser . Controller . Subtitles ;
1315using MediaBrowser . Model . Dlna ;
1416using MediaBrowser . Model . Dto ;
1517using MediaBrowser . Model . Entities ;
1618using MediaBrowser . Model . MediaInfo ;
19+ using MediaBrowser . Model . Providers ;
1720using Microsoft . AspNetCore . Http ;
1821using Microsoft . Extensions . Logging ;
1922using MediaBrowser . Controller . MediaSegments ;
2023using MediaBrowser . Controller . Chapters ;
24+ using MediaBrowser . Model . Configuration ;
25+ using MediaBrowser . Common . Configuration ;
26+ using MediaBrowser . Controller . Configuration ;
27+ using Gelato . Services ;
28+
2129
2230namespace Gelato . Decorators ;
2331
@@ -28,21 +36,30 @@ public sealed class MediaSourceManagerDecorator(
2836 IHttpContextAccessor http ,
2937 GelatoItemRepository repo ,
3038 IDirectoryService directoryService ,
39+ IServerConfigurationManager config ,
40+ //Lazy<ISubtitleManager> subtitleManager,
3141 Lazy < GelatoManager > manager ,
32- IMediaSegmentManager mediaSegmentManager )
42+ IMediaSegmentManager mediaSegmentManager ,
43+ IEnumerable < ICustomMetadataProvider < Video > > videoProbeProviders )
3344 : IMediaSourceManager {
3445 private readonly IMediaSourceManager _inner = inner ?? throw new ArgumentNullException ( nameof ( inner ) ) ;
3546 private readonly ILogger < MediaSourceManagerDecorator > _log = log ?? throw new ArgumentNullException ( nameof ( log ) ) ;
3647 private readonly IHttpContextAccessor _http = http ?? throw new ArgumentNullException ( nameof ( http ) ) ;
3748 private readonly KeyLock _lock = new ( ) ;
3849 private readonly IMediaSegmentManager _mediaSegmentManager = mediaSegmentManager ?? throw new ArgumentNullException ( nameof ( mediaSegmentManager ) ) ;
3950 private readonly ILibraryManager _libraryManager = libraryManager ?? throw new ArgumentNullException ( nameof ( libraryManager ) ) ;
51+ private readonly IServerConfigurationManager _config = config ?? throw new ArgumentNullException ( nameof ( config ) ) ;
52+ private readonly Lazy < GelatoManager > _manager = manager ;
53+ // private readonly Lazy<ISubtitleManager> _subtitleManager = subtitleManager ?? throw new ArgumentNullException(nameof(subtitleManager));
54+ private readonly ICustomMetadataProvider < Video > ? _probeProvider =
55+ videoProbeProviders . FirstOrDefault ( p => p . Name == "Probe Provider" ) ;
56+
4057 public IReadOnlyList < MediaSourceInfo > GetStaticMediaSources (
4158 BaseItem item ,
4259 bool enablePathSubstitution ,
4360 User ? user = null
4461 ) {
45- var manager1 = manager . Value ;
62+ var manager = _manager . Value ;
4663 _log . LogDebug (
4764 "GetStaticMediaSources {Id}" ,
4865 item . Id
@@ -57,7 +74,7 @@ public IReadOnlyList<MediaSourceInfo> GetStaticMediaSources(
5774
5875 var cfg = GelatoPlugin . Instance ! . GetConfig ( userId ) ;
5976 if (
60- ( ! cfg . EnableMixed && ! manager1 . IsGelato ( item ) )
77+ ( ! cfg . EnableMixed && ! item . IsGelato ( ) )
6178 || item . GetBaseItemKind ( ) is not ( BaseItemKind . Movie or BaseItemKind . Episode )
6279 ) {
6380 return _inner . GetStaticMediaSources ( item , enablePathSubstitution , user ) ;
@@ -84,7 +101,7 @@ public IReadOnlyList<MediaSourceInfo> GetStaticMediaSources(
84101 uri ? . ToString ( )
85102 ) ;
86103 }
87- else if ( uri is not null && ! manager1 . HasStreamSync ( cacheKey ) ) {
104+ else if ( uri is not null && ! manager . HasStreamSync ( cacheKey ) ) {
88105 // Bug in web UI that calls the detail page twice. So that's why there's a lock.
89106 _lock
90107 . RunSingleFlightAsync (
@@ -95,9 +112,9 @@ public IReadOnlyList<MediaSourceInfo> GetStaticMediaSources(
95112 item . Id
96113 ) ;
97114 try {
98- var count = await manager1 . SyncStreams ( item , userId , ct ) . ConfigureAwait ( false ) ;
115+ var count = await manager . SyncStreams ( item , userId , ct ) . ConfigureAwait ( false ) ;
99116 if ( count > 0 ) {
100- manager1 . SetStreamSync ( cacheKey ) ;
117+ manager . SetStreamSync ( cacheKey ) ;
101118 }
102119 }
103120 catch ( Exception ex ) {
@@ -150,7 +167,7 @@ public IReadOnlyList<MediaSourceInfo> GetStaticMediaSources(
150167 . GetItemList ( query )
151168 . OfType < Video > ( )
152169 . Where ( x =>
153- manager1 . IsGelato ( x ) &&
170+ x . IsGelato ( ) &&
154171 (
155172 userId == Guid . Empty ||
156173 ( x . GelatoData < List < Guid > > ( "userIds" ) ? . Contains ( userId ) ?? false )
@@ -212,80 +229,9 @@ public IReadOnlyList<MediaStream> GetMediaStreams(Guid itemId) {
212229 return _inner . GetMediaStreams ( itemId ) ;
213230 }
214231
215- public async Task < List < MediaStream > > GetSubtitleStreams (
216- BaseItem item ,
217- MediaSourceInfo source
218- ) {
219- var manager1 = manager . Value ;
220-
221- var subtitles = manager1 . GetStremioSubtitlesCache ( item . Id ) ;
222- if ( subtitles is null ) {
223- var uri = StremioUri . FromBaseItem ( item ) ;
224- if ( uri is null ) {
225- _log . LogError ( $ "unable to build stremio uri for { item . Name } ") ;
226- return new List < MediaStream > ( ) ; // Return empty list instead of void
227- }
228-
229- Uri u = new Uri ( source . Path ) ;
230- string filename = System . IO . Path . GetFileName ( u . LocalPath ) ;
231-
232- var cfg = GelatoPlugin . Instance ! . GetConfig ( Guid . Empty ) ;
233- subtitles = await cfg
234- . Stremio . GetSubtitlesAsync ( uri , filename )
235- . ConfigureAwait ( false ) ;
236- manager1 . SetStremioSubtitlesCache ( item . Id , subtitles ) ;
237- }
238-
239- var streams = new List < MediaStream > ( ) ;
240-
241- if ( subtitles == null || ! subtitles . Any ( ) ) {
242- _log . LogDebug ( $ "GetSubtitleStreams: no subtitles found") ;
243- return streams ;
244- }
245-
246- var index = 0 ; // Start from 0 since this is a new list
247- var limitedSubtitles = subtitles . GroupBy ( s => s . Lang ) . SelectMany ( g => g . Take ( 2 ) ) ;
248- foreach ( var s in limitedSubtitles ) {
249- streams . Add (
250- new MediaStream {
251- Type = MediaStreamType . Subtitle ,
252- Index = index ,
253- Language = s . Lang ,
254- Codec = GuessSubtitleCodec ( s . Url ) ,
255- IsExternal = true ,
256- // subtitle urls usually dont end with an extension. Breaking some clients cause they fucking check the extension instead of thr codec field.
257- SupportsExternalStream = false ,
258- Path = s . Url ,
259- DeliveryMethod = SubtitleDeliveryMethod . External ,
260- }
261- ) ;
262- index ++ ;
263- }
232+
264233
265- _log . LogDebug ( $ "GetSubtitleStreams: loaded { streams . Count } subtitles") ;
266- return streams ;
267- }
268-
269- public string GuessSubtitleCodec ( string ? urlOrPath ) {
270- if ( string . IsNullOrWhiteSpace ( urlOrPath ) )
271- return "subrip" ;
272-
273- var s = urlOrPath . ToLowerInvariant ( ) ;
274-
275- if ( s . Contains ( ".vtt" ) )
276- return "vtt" ;
277- if ( s . Contains ( ".srt" ) )
278- return "srt" ;
279- if ( s . Contains ( ".ass" ) || s . Contains ( ".ssa" ) )
280- return "ass" ;
281- if ( s . Contains ( ".subf2m" ) )
282- return "subrip" ;
283- if ( s . Contains ( "subs" ) && s . Contains ( ".strem.io" ) )
284- return "srt" ; // Stremio proxies are always normalized to .srt
285-
286- _log . LogWarning ( $ "unkown subtitle format for { s } , defaulting to srt") ;
287- return "srt" ;
288- }
234+
289235
290236 public IReadOnlyList < MediaStream > GetMediaStreams ( MediaStreamQuery query ) {
291237 return _inner . GetMediaStreams ( query ) . ToList ( ) ;
@@ -316,7 +262,7 @@ CancellationToken ct
316262 . ConfigureAwait ( false ) ;
317263 }
318264
319- var manager1 = manager . Value ;
265+ var manager = _manager . Value ;
320266 var ctx = _http . HttpContext ;
321267
322268 var sources = GetStaticMediaSources ( item , enablePathSubstitution , user ) ;
@@ -327,7 +273,7 @@ CancellationToken ct
327273 && Guid . TryParse ( idStr , out var fromCtx )
328274 ? fromCtx
329275 : (
330- manager1 . IsPrimaryVersion ( item as Video )
276+ item . IsPrimaryVersion ( )
331277 && sources . Count > 0
332278 && Guid . TryParse ( sources [ 0 ] . Id , out var fromSource )
333279 ? fromSource
@@ -345,7 +291,7 @@ CancellationToken ct
345291 return sources ;
346292
347293 var owner = ResolveOwnerFor ( selected , item ) ;
348- if ( manager1 . IsPrimaryVersion ( owner as Video ) && owner . Id != item . Id ) {
294+ if ( owner . IsPrimaryVersion ( ) && owner . Id != item . Id ) {
349295 sources = GetStaticMediaSources ( owner , enablePathSubstitution , user ) ;
350296 selected = SelectByIdOrFirst ( sources , mediaSourceId ) ;
351297 if ( selected is null )
@@ -355,18 +301,11 @@ CancellationToken ct
355301 if ( NeedsProbe ( selected ) ) {
356302 var libraryOptions = _libraryManager . GetLibraryOptions ( owner ) ;
357303
358- // Run segment providers and metadata refresh in parallel
359304 var segmentTask = _mediaSegmentManager . RunSegmentPluginProviders ( owner , libraryOptions , false , ct ) ;
360- var metadataTask = owner . RefreshMetadata (
361- new MetadataRefreshOptions ( directoryService ) {
362- EnableRemoteContentProbe = true ,
363- MetadataRefreshMode = MetadataRefreshMode . FullRefresh ,
364- } ,
365- ct
366- ) ;
305+ var metadataTask = ProbeStreamAsync ( ( Video ) owner , selected . Path , ct ) ;
306+ // var subtitleTask = DownloadSubtitles((Video)owner, ct);
367307
368- // Wait for both operations to complete
369- await Task . WhenAll ( segmentTask , metadataTask ) . ConfigureAwait ( false ) ;
308+ await Task . WhenAll ( metadataTask , segmentTask ) . ConfigureAwait ( false ) ;
370309
371310 await owner
372311 . UpdateToRepositoryAsync ( ItemUpdateType . MetadataEdit , ct )
@@ -379,22 +318,6 @@ await owner
379318 return refreshed ;
380319 }
381320
382- if ( GelatoPlugin . Instance ! . Configuration . EnableSubs ) {
383- var subtitleStreams = await GetSubtitleStreams ( item , selected )
384- . ConfigureAwait ( false ) ;
385-
386- var streams = selected . MediaStreams ? . ToList ( ) ?? new List < MediaStream > ( ) ;
387-
388- var index = streams . LastOrDefault ( ) ? . Index ?? - 1 ;
389- foreach ( var s in subtitleStreams ) {
390- index ++ ;
391- s . Index = index ;
392- streams . Add ( s ) ;
393- }
394-
395- selected . MediaStreams = streams ;
396- }
397-
398321 if ( item . RunTimeTicks is null && selected . RunTimeTicks is not null ) {
399322 item . RunTimeTicks = selected . RunTimeTicks ;
400323 await item . UpdateToRepositoryAsync ( ItemUpdateType . MetadataEdit , ct )
@@ -581,4 +504,44 @@ private MediaSourceInfo GetVersionInfo(
581504
582505 return info ;
583506 }
507+
508+ private async Task ProbeStreamAsync ( Video owner , string streamUrl , CancellationToken ct )
509+ {
510+ var gelatoFilename = owner . GelatoData < string > ( "filename" ) ;
511+ var strmBaseName = ! string . IsNullOrEmpty ( gelatoFilename )
512+ ? Path . GetFileNameWithoutExtension ( gelatoFilename )
513+ : $ "{ owner . Id : N} ";
514+ var tmpStrm = Path . Combine ( Path . GetTempPath ( ) , $ "{ strmBaseName } .strm") ;
515+ await File . WriteAllTextAsync ( tmpStrm , streamUrl , ct ) . ConfigureAwait ( false ) ;
516+
517+ var origPath = owner . Path ;
518+ var origShortcut = owner . IsShortcut ;
519+ owner . Path = tmpStrm ;
520+ owner . IsShortcut = true ;
521+ owner . DateModified = new FileInfo ( tmpStrm ) . LastWriteTimeUtc ;
522+
523+ try
524+ {
525+ _log . LogInformation ( "Probing stream for {Id} via {Url}" , owner . Id , streamUrl ) ;
526+ await owner . RefreshMetadata (
527+ new MetadataRefreshOptions ( directoryService ) {
528+ EnableRemoteContentProbe = true ,
529+ MetadataRefreshMode = MetadataRefreshMode . FullRefresh ,
530+ } ,
531+ ct
532+ ) ;
533+ }
534+ catch ( Exception ex )
535+ {
536+ _log . LogError ( ex , "Stream probe failed for {Id}" , owner . Id ) ;
537+ }
538+ finally
539+ {
540+ owner . Path = origPath ;
541+ owner . IsShortcut = origShortcut ;
542+ try { File . Delete ( tmpStrm ) ; } catch { /* best effort */ }
543+ }
544+ }
545+
546+
584547}
0 commit comments