Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions TheIntroDB/Configuration/PluginConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,5 +80,10 @@ public PluginConfiguration()
/// instead of the main Emby log.
/// </summary>
public bool EnableFileLogging { get; set; }

/// <summary>
/// Gets or sets a value indicating whether to automatically fetch segments for newly added items and on playback start.
/// </summary>
public bool EnableOnDemandFetch { get; set; } = true;
}
}
8 changes: 8 additions & 0 deletions TheIntroDB/Configuration/configPage.html
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ <h2 class="sectionTitle">TheIntroDB</h2>
<div class="fieldDescription">When enabled, items that already have segments are skipped to avoid refetching.</div>
</div>

<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableOnDemandFetch" name="EnableOnDemandFetch" type="checkbox" is="emby-checkbox" />
<span>Automatically fetch segments on item addition</span>
</label>
<div class="fieldDescription">When enabled, segments will be fetched for newly added items, so long as no segments exist yet.</div>
</div>

<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableAnonymousUsageReporting" name="EnableAnonymousUsageReporting" type="checkbox" is="emby-checkbox" />
Expand Down
2 changes: 2 additions & 0 deletions TheIntroDB/Configuration/configPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -955,6 +955,7 @@ define(["emby-input", "emby-button", "emby-checkbox"], function () {
page.querySelector('#EnableCredits').checked = config.EnableCredits !== false;
page.querySelector('#EnablePreview').checked = config.EnablePreview !== false;
page.querySelector('#IgnoreMediaWithExistingSegments').checked = config.IgnoreMediaWithExistingSegments !== false;
page.querySelector('#EnableOnDemandFetch').checked = config.EnableOnDemandFetch !== false;
page.querySelector('#EnableAnonymousUsageReporting').checked = config.EnableAnonymousUsageReporting !== false;
page.querySelector('#EnableFileLogging').checked = config.EnableFileLogging === true;
if (apiKeyInput.value) {
Expand Down Expand Up @@ -1083,6 +1084,7 @@ define(["emby-input", "emby-button", "emby-checkbox"], function () {
config.EnableCredits = page.querySelector('#EnableCredits').checked;
config.EnablePreview = page.querySelector('#EnablePreview').checked;
config.IgnoreMediaWithExistingSegments = page.querySelector('#IgnoreMediaWithExistingSegments').checked;
config.EnableOnDemandFetch = page.querySelector('#EnableOnDemandFetch').checked;
config.EnableAnonymousUsageReporting = page.querySelector('#EnableAnonymousUsageReporting').checked;
config.EnableFileLogging = page.querySelector('#EnableFileLogging').checked;
return ApiClient.updatePluginConfiguration(pluginUniqueId, config).then(function (result) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Entities;
Expand All @@ -13,6 +15,8 @@
using MediaBrowser.Model.Logging;
using TheIntroDB.Configuration;
using TheIntroDB.Data;
using TheIntroDB.Models;
using TheIntroDB.Providers;
using TheIntroDB.Services;

namespace TheIntroDB.EntryPoints
Expand All @@ -23,6 +27,7 @@ public sealed class TheIntroDbChapterMarkerPersistenceEntryPoint : IServerEntryP
private readonly IItemRepository _itemRepository;
private readonly TheIntroDbSegmentRepository _segmentRepository;
private readonly TheIntroDbChapterMarkerWriter _chapterWriter;
private readonly TheIntroDbSegmentProvider _segmentProvider;
private readonly ILogger _logger;

private readonly ConcurrentDictionary<long, byte> _writesInProgress = new ConcurrentDictionary<long, byte>();
Expand All @@ -38,19 +43,33 @@ public TheIntroDbChapterMarkerPersistenceEntryPoint(
_logger = Plugin.Instance?.FileLogger ?? logManager.GetLogger("TheIntroDB");
_segmentRepository = new TheIntroDbSegmentRepository(_logger, applicationPaths);
_chapterWriter = new TheIntroDbChapterMarkerWriter(_itemRepository, _logger);
_segmentProvider = new TheIntroDbSegmentProvider(libraryManager, _logger);
}

public void Run()
{
_libraryManager.ItemUpdated += LibraryManager_ItemUpdated;
_libraryManager.ItemAdded += LibraryManager_ItemAdded;
}

public void Dispose()
{
_libraryManager.ItemUpdated -= LibraryManager_ItemUpdated;
_libraryManager.ItemAdded -= LibraryManager_ItemAdded;
_segmentRepository.Dispose();
}

private void LibraryManager_ItemAdded(object sender, ItemChangeEventArgs e)
{
var item = e?.Item;
if (item is null)
{
return;
}

_ = OnDemandFetchAsync(item, "item_added");
}

private void LibraryManager_ItemUpdated(object sender, ItemChangeEventArgs e)
{
try
Expand Down Expand Up @@ -78,6 +97,9 @@ private void LibraryManager_ItemUpdated(object sender, ItemChangeEventArgs e)
return;
}

// Also trigger on-demand fetch for updated items that have no segments yet
_ = OnDemandFetchAsync(item, "item_updated");

_ = Task.Run(() => EnsureMarkersApplied(item, config));
}
catch (Exception ex)
Expand Down Expand Up @@ -179,5 +201,54 @@ private static bool NeedsMarkerApply(IReadOnlyList<ChapterInfo> chapters, Plugin

return !(hasIntro && hasRecap && hasCredits && hasPreview);
}

private async Task OnDemandFetchAsync(BaseItem item, string trigger)
{
try
{
var config = Plugin.Instance?.Configuration;
if (config == null || !config.EnableOnDemandFetch)
{
return;
}

var internalId = item.InternalId;

// Check if segments already exist
var existing = _segmentRepository.GetSegments(internalId);
if (existing != null && existing.Count > 0)
{
_logger.Debug("On-demand fetch skipped ({0}): segments already exist for {1}", trigger, item.Name);
return;
}

_logger.Info("On-demand segment fetch triggered ({0}) for {1} ({2})", trigger, item.Name, item.GetType().Name);

var result = await _segmentProvider.GetMediaSegmentsAsync(item.Id, CancellationToken.None).ConfigureAwait(false);

if (result.Segments == null || result.Segments.Count == 0)
{
_logger.Info("On-demand segment fetch ({0}): no segments returned for {1}", trigger, item.Name);
return;
}

var storedSegments = result.Segments.Select(s => new StoredMediaSegment
{
ItemInternalId = internalId,
Type = s.Type,
StartTicks = s.StartTicks,
EndTicks = s.EndTicks
}).ToList();

_segmentRepository.ReplaceSegments(internalId, storedSegments, DateTime.UtcNow);
_chapterWriter.ApplyMarkers(item, storedSegments, config);

_logger.Info("On-demand segment fetch completed ({0}) for {1}: {2} segments, markers applied", trigger, item.Name, storedSegments.Count);
}
catch (Exception ex)
{
_logger.Error("On-demand segment fetch failed ({0}) for {1}: {2}", trigger, item?.Name ?? "null", ex.Message);
}
}
}
}
71 changes: 71 additions & 0 deletions TheIntroDB/EntryPoints/TheIntroDbUsageReportingEntryPoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Logging;
using TheIntroDB.Data;
using TheIntroDB.Models;
using TheIntroDB.Providers;

namespace TheIntroDB.EntryPoints
{
Expand All @@ -21,15 +25,18 @@ public class TheIntroDbUsageReportingEntryPoint : IServerEntryPoint

private readonly ISessionManager _sessionManager;
private readonly TheIntroDbSegmentRepository _repository;
private readonly ILibraryManager _libraryManager;
private readonly ILogger _logger;
private readonly ConcurrentDictionary<string, PlaybackState> _states = new ConcurrentDictionary<string, PlaybackState>();

public TheIntroDbUsageReportingEntryPoint(
ISessionManager sessionManager,
IApplicationPaths applicationPaths,
ILibraryManager libraryManager,
ILogManager logManager)
{
_sessionManager = sessionManager;
_libraryManager = libraryManager;
_logger = Plugin.Instance?.FileLogger ?? logManager.GetLogger("TheIntroDB");
_repository = new TheIntroDbSegmentRepository(_logger, applicationPaths);
}
Expand All @@ -54,12 +61,14 @@ public void Run()
});
_sessionManager.PlaybackProgress += SessionManager_PlaybackProgress;
_sessionManager.PlaybackStopped += SessionManager_PlaybackStopped;
_sessionManager.PlaybackStart += SessionManager_PlaybackStart;
}

public void Dispose()
{
_sessionManager.PlaybackProgress -= SessionManager_PlaybackProgress;
_sessionManager.PlaybackStopped -= SessionManager_PlaybackStopped;
_sessionManager.PlaybackStart -= SessionManager_PlaybackStart;
_repository.Dispose();
}

Expand Down Expand Up @@ -162,6 +171,68 @@ private void SessionManager_PlaybackProgress(object sender, PlaybackProgressEven
}
}

private void SessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e)
{
try
{
if (e?.Item == null)
{
return;
}

var item = e.Item;
var config = Plugin.Instance?.Configuration;
if (config == null || !config.EnableOnDemandFetch)
{
return;
}

var internalId = item.InternalId;
var existingSegments = _repository.GetSegments(internalId);
if (existingSegments != null && existingSegments.Count > 0)
{
return;
}

_logger.Info("On-demand segment fetch triggered (playback_start) for {0} ({1})", item.Name, item.GetType().Name);

_ = Task.Run(async () =>
{
try
{
var provider = new TheIntroDbSegmentProvider(_libraryManager, _logger);
var result = await provider.GetMediaSegmentsAsync(item.Id, CancellationToken.None).ConfigureAwait(false);

if (result.Segments == null || result.Segments.Count == 0)
{
_logger.Info("On-demand segment fetch (playback_start): no segments returned for {0}", item.Name);
return;
}

var storedSegments = result.Segments.Select(s => new StoredMediaSegment
{
ItemInternalId = internalId,
Type = s.Type,
StartTicks = s.StartTicks,
EndTicks = s.EndTicks
}).ToList();

_repository.ReplaceSegments(internalId, storedSegments, DateTime.UtcNow);

_logger.Info("On-demand segment fetch completed (playback_start) for {0}: {1} segments", item.Name, storedSegments.Count);
}
catch (Exception ex)
{
_logger.Error("On-demand segment fetch failed (playback_start) for {0}: {1}", item?.Name ?? "null", ex.Message);
}
});
}
catch (Exception ex)
{
_logger.Error("PlaybackStart handler exception: " + ex.Message);
}
}

private sealed class PlaybackState
{
public long ItemInternalId;
Expand Down
12 changes: 11 additions & 1 deletion TheIntroDB/Providers/TheIntroDbSegmentProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,16 @@ public async Task<SegmentFetchResult> GetMediaSegmentsAsync(
_logger.Info("Episode: Name={0}, Series={1}, S{2}E{3}, TmdbId={4}, TvdbId={5}, ImdbId={6}",
item.Name, ep.SeriesName, season, episode, tmdbId, tvdbId, imdbId ?? "(none)");
}
else if (item is Video video)
{
isMovie = false;
tmdbId = GetTmdbId(video);
tvdbId = GetTvdbId(video);
imdbId = GetImdbId(video);
season = video.ParentIndexNumber;
episode = video.IndexNumber;
_logger.Info("Video: Name={0}, TmdbId={1}, TvdbId={2}, ImdbId={3}, Season={4}, Episode={5}", item.Name, tmdbId, tvdbId, imdbId ?? "(none)", season, episode);
}

if ((!tmdbId.HasValue || tmdbId.Value <= 0) && (!tvdbId.HasValue || tvdbId.Value <= 0) && string.IsNullOrWhiteSpace(imdbId))
{
Expand Down Expand Up @@ -209,7 +219,7 @@ public async Task<SegmentFetchResult> GetMediaSegmentsAsync(
/// <returns>True if the item is supported (Movie or Episode).</returns>
public bool Supports(BaseItem item)
{
var supported = item is Episode || item is Movie;
var supported = item is Episode || item is Movie || item is Video;
_logger.Debug("Supports({0}, {1}): {2}", item?.Name ?? "null", item?.GetType().Name ?? "null", supported);
return supported;
}
Expand Down
Loading