Add configuration options
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
|
using System.Linq;
|
||||||
using Jellyfin.Plugin.TubeArchivistMetadata.Utilities;
|
using Jellyfin.Plugin.TubeArchivistMetadata.Utilities;
|
||||||
using MediaBrowser.Model.Plugins;
|
using MediaBrowser.Model.Plugins;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -14,6 +16,8 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Configuration
|
|||||||
private ILogger _logger;
|
private ILogger _logger;
|
||||||
private string _tubeArchivistUrl;
|
private string _tubeArchivistUrl;
|
||||||
private string _tubeArchivistApiKey;
|
private string _tubeArchivistApiKey;
|
||||||
|
private HashSet<string> _jfUsernamesTo;
|
||||||
|
private HashSet<string> _jfUsernamesFrom;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="PluginConfiguration"/> class.
|
/// Initializes a new instance of the <see cref="PluginConfiguration"/> class.
|
||||||
@@ -33,6 +37,10 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Configuration
|
|||||||
_tubeArchivistUrl = string.Empty;
|
_tubeArchivistUrl = string.Empty;
|
||||||
_tubeArchivistApiKey = string.Empty;
|
_tubeArchivistApiKey = string.Empty;
|
||||||
MaxDescriptionLength = 500;
|
MaxDescriptionLength = 500;
|
||||||
|
TAJFSync = false;
|
||||||
|
_jfUsernamesTo = new HashSet<string>();
|
||||||
|
JFTASync = false;
|
||||||
|
_jfUsernamesFrom = new HashSet<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -87,5 +95,69 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Configuration
|
|||||||
/// Gets or sets maximum series and episodes overviews length.
|
/// Gets or sets maximum series and episodes overviews length.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int MaxDescriptionLength { get; set; }
|
public int MaxDescriptionLength { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether to enable TA->JF playback progress synchronization.
|
||||||
|
/// </summary>
|
||||||
|
public bool TAJFSync { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether to enable JF->TA playback progress synchronization.
|
||||||
|
/// </summary>
|
||||||
|
public bool JFTASync { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the playback progress owner Jellyfin usernames to synchronize data to TubeArchivist.
|
||||||
|
/// </summary>
|
||||||
|
public string JFUsernamesFrom
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
_logger.LogInformation("JFUsernamesFrom configured: {Message}", string.Join(", ", _jfUsernamesFrom));
|
||||||
|
return string.Join(", ", _jfUsernamesFrom);
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
value.Replace(" ", string.Empty, StringComparison.CurrentCulture).Split(',').ToList().ForEach(u => _jfUsernamesFrom.Add(u));
|
||||||
|
_logger.LogInformation("Set JFUsernamesFrom to: {Message}", value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the playback progress owners Jellyfin usernames to synchronize data from TubeArchivist.
|
||||||
|
/// </summary>
|
||||||
|
public string JFUsernamesTo
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
_logger.LogInformation("JFUsernamesTo configured: {Message}", string.Join(", ", _jfUsernamesTo));
|
||||||
|
return string.Join(", ", _jfUsernamesTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
value.Replace(" ", string.Empty, StringComparison.CurrentCulture).Split(',').ToList().ForEach(u => _jfUsernamesTo.Add(u));
|
||||||
|
_logger.LogInformation("Set JFUsernamesTo to: {Message}", value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the playback progress owner Jellyfin usernames to synchronize data to TubeArchivist.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An array of usernames.</returns>
|
||||||
|
public HashSet<string> GetJFUsernamesFromArray()
|
||||||
|
{
|
||||||
|
return _jfUsernamesFrom;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the playback progress owners Jellyfin usernames to synchronize data from TubeArchivist.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An array of usernames.</returns>
|
||||||
|
public HashSet<string> GetJFUsernamesToArray()
|
||||||
|
{
|
||||||
|
return _jfUsernamesTo;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Template</title>
|
<title>TubeArchivistMetadata</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@@ -12,25 +12,65 @@
|
|||||||
<div data-role="content">
|
<div data-role="content">
|
||||||
<div class="content-primary">
|
<div class="content-primary">
|
||||||
<form id="TemplateConfigForm">
|
<form id="TemplateConfigForm">
|
||||||
|
<div>
|
||||||
|
<h1>TubeArchivistMetadata</h1>
|
||||||
|
</div>
|
||||||
<div class="inputContainer">
|
<div class="inputContainer">
|
||||||
<label class="inputLabel inputLabelUnfocused" for="CollectionTitle">Collection Title</label>
|
<label class="inputLabel inputLabelUnfocused" for="CollectionTitle">Collection Title</label>
|
||||||
<input id="CollectionTitle" name="CollectionTitle" type="text" is="emby-input" placeholder="eg. YouTube" />
|
<input id="CollectionTitle" name="CollectionTitle" type="text" is="emby-input"
|
||||||
|
placeholder="eg. YouTube" />
|
||||||
<div class="fieldDescription">TubeArchivist collection display name</div>
|
<div class="fieldDescription">TubeArchivist collection display name</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="inputContainer">
|
<div class="inputContainer">
|
||||||
<label class="inputLabel inputLabelUnfocused" for="TubeArchivistUrl">TubeArchivist URL</label>
|
<label class="inputLabel inputLabelUnfocused" for="TubeArchivistUrl">TubeArchivist URL</label>
|
||||||
<input id="TubeArchivistUrl" name="TubeArchivistUrl" type="text" is="emby-input" placeholder="http://..." />
|
<input id="TubeArchivistUrl" name="TubeArchivistUrl" type="text" is="emby-input"
|
||||||
|
placeholder="http://..." />
|
||||||
<div class="fieldDescription">TubeArchivist URL</div>
|
<div class="fieldDescription">TubeArchivist URL</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="inputContainer">
|
<div class="inputContainer">
|
||||||
<label class="inputLabel inputLabelUnfocused" for="TubeArchivistApiKey">TubeArchivist API key</label>
|
<label class="inputLabel inputLabelUnfocused" for="TubeArchivistApiKey">TubeArchivist API
|
||||||
|
key</label>
|
||||||
<input id="TubeArchivistApiKey" name="TubeArchivistApiKey" type="text" is="emby-input" />
|
<input id="TubeArchivistApiKey" name="TubeArchivistApiKey" type="text" is="emby-input" />
|
||||||
<div class="fieldDescription">TubeArchivist API key</div>
|
<div class="fieldDescription">TubeArchivist API key</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="inputContainer">
|
<div class="inputContainer">
|
||||||
<label class="inputLabel inputLabelUnfocused" for="MaxDescriptionLength">Maximum overviews length</label>
|
<label class="inputLabel inputLabelUnfocused" for="MaxDescriptionLength">Maximum overviews
|
||||||
<input id="MaxDescriptionLength" name="MaxDescriptionLength" type="number" is="emby-input" min="0" />
|
length</label>
|
||||||
<div class="fieldDescription">This is the maximum length of the descriptions showed in series and episodes</div>
|
<input id="MaxDescriptionLength" name="MaxDescriptionLength" type="number" is="emby-input"
|
||||||
|
min="0" />
|
||||||
|
<div class="fieldDescription">This is the maximum length of the descriptions showed in series
|
||||||
|
and episodes</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Playback synchronization</h2>
|
||||||
|
</div>
|
||||||
|
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||||
|
<label class="emby-checkbox-label" for="TAJFSync">
|
||||||
|
<input id="TAJFSync" name="TAJFSync" type="checkbox" is="emby-checkbox" />
|
||||||
|
<span>Synchronize TubeArchivist playback progress to Jellyfin</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="inputContainer">
|
||||||
|
<label class="inputLabel inputLabelUnfocused" for="JFUsernamesTo">Jellyfin usernames to sync
|
||||||
|
playback status to</label>
|
||||||
|
<input id="JFUsernamesTo" name="JFUsernamesTo" type="text" is="emby-input"
|
||||||
|
placeholder="Username1, username2, ..." />
|
||||||
|
<div class="fieldDescription">These are the (comma separated) Jellyfin usernames to synchronize
|
||||||
|
data from TubeArchivist</div>
|
||||||
|
</div>
|
||||||
|
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||||
|
<label class="emby-checkbox-label" for="JFTASync">
|
||||||
|
<input id="JFTASync" name="JFTASync" type="checkbox" is="emby-checkbox" />
|
||||||
|
<span>Synchronize Jellyfin playback progress to TubeArchivist</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="inputContainer">
|
||||||
|
<label class="inputLabel inputLabelUnfocused" for="JFUsernamesFrom">Jellyfin usernames to sync
|
||||||
|
playback status from</label>
|
||||||
|
<input id="JFUsernamesFrom" name="JFUsernamesFrom" type="text" is="emby-input"
|
||||||
|
placeholder="Username1, username2, ..." />
|
||||||
|
<div class="fieldDescription">These are the (comma separated) Jellyfin usernames to synchronize data from on
|
||||||
|
TubeArchivist</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button is="emby-button" type="submit" class="raised button-submit block emby-button">
|
<button is="emby-button" type="submit" class="raised button-submit block emby-button">
|
||||||
@@ -53,6 +93,10 @@
|
|||||||
document.querySelector('#TubeArchivistUrl').value = config.TubeArchivistUrl;
|
document.querySelector('#TubeArchivistUrl').value = config.TubeArchivistUrl;
|
||||||
document.querySelector('#TubeArchivistApiKey').value = config.TubeArchivistApiKey;
|
document.querySelector('#TubeArchivistApiKey').value = config.TubeArchivistApiKey;
|
||||||
document.querySelector('#MaxDescriptionLength').value = config.MaxDescriptionLength;
|
document.querySelector('#MaxDescriptionLength').value = config.MaxDescriptionLength;
|
||||||
|
document.querySelector('#JFTASync').checked = config.JFTASync;
|
||||||
|
document.querySelector('#JFUsernamesTo').value = config.JFUsernamesTo;
|
||||||
|
document.querySelector('#TAJFSync').checked = config.TAJFSync;
|
||||||
|
document.querySelector('#JFUsernamesFrom').value = config.JFUsernamesFrom;
|
||||||
Dashboard.hideLoadingMsg();
|
Dashboard.hideLoadingMsg();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -65,6 +109,10 @@
|
|||||||
config.TubeArchivistUrl = document.querySelector('#TubeArchivistUrl').value;
|
config.TubeArchivistUrl = document.querySelector('#TubeArchivistUrl').value;
|
||||||
config.TubeArchivistApiKey = document.querySelector('#TubeArchivistApiKey').value;
|
config.TubeArchivistApiKey = document.querySelector('#TubeArchivistApiKey').value;
|
||||||
config.MaxDescriptionLength = document.querySelector('#MaxDescriptionLength').value;
|
config.MaxDescriptionLength = document.querySelector('#MaxDescriptionLength').value;
|
||||||
|
config.JFTASync = document.querySelector('#JFTASync').checked;
|
||||||
|
config.JFUsernamesTo = document.querySelector('#JFUsernamesTo').value;
|
||||||
|
config.TAJFSync = document.querySelector('#TAJFSync').checked;
|
||||||
|
config.JFUsernamesFrom = document.querySelector('#JFUsernamesFrom').value;
|
||||||
ApiClient.updatePluginConfiguration(TAMetadataConfig.pluginUniqueId, config).then(function (result) {
|
ApiClient.updatePluginConfiguration(TAMetadataConfig.pluginUniqueId, config).then(function (result) {
|
||||||
Dashboard.processPluginConfigurationUpdateResult(result);
|
Dashboard.processPluginConfigurationUpdateResult(result);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -132,15 +132,19 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata
|
|||||||
|
|
||||||
private async void OnPlaybackProgress(object? sender, PlaybackProgressEventArgs eventArgs)
|
private async void OnPlaybackProgress(object? sender, PlaybackProgressEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
BaseItem? channel = LibraryManager.GetItemById(eventArgs.Item.ParentId);
|
if (Instance!.Configuration.JFTASync && eventArgs.Users.Any(u => Instance!.Configuration.GetJFUsernamesFromArray().Contains(u.Username)))
|
||||||
BaseItem? collection = LibraryManager.GetItemById(channel!.ParentId);
|
|
||||||
if (collection?.Name.ToLower(CultureInfo.CurrentCulture) == Instance?.Configuration.CollectionTitle.ToLower(CultureInfo.CurrentCulture) && eventArgs.PlaybackPositionTicks != null)
|
|
||||||
{
|
{
|
||||||
long progress = (long)eventArgs.PlaybackPositionTicks / TimeSpan.TicksPerSecond;
|
BaseItem? channel = LibraryManager.GetItemById(eventArgs.Item.ParentId);
|
||||||
var statusCode = await TubeArchivistApi.GetInstance().SetProgress(Utils.GetVideoNameFromPath(eventArgs.Item.Path), progress).ConfigureAwait(true);
|
BaseItem? collection = LibraryManager.GetItemById(channel!.ParentId);
|
||||||
if (statusCode != System.Net.HttpStatusCode.OK)
|
if (collection?.Name.ToLower(CultureInfo.CurrentCulture) == Instance?.Configuration.CollectionTitle.ToLower(CultureInfo.CurrentCulture) && eventArgs.PlaybackPositionTicks != null)
|
||||||
{
|
{
|
||||||
Logger.LogInformation("{Message}", $"POST /progress returned {statusCode} for video {eventArgs.Item.Name} with progress {progress} seconds");
|
long progress = (long)eventArgs.PlaybackPositionTicks / TimeSpan.TicksPerSecond;
|
||||||
|
var videoId = Utils.GetVideoNameFromPath(eventArgs.Item.Path);
|
||||||
|
var statusCode = await TubeArchivistApi.GetInstance().SetProgress(videoId, progress).ConfigureAwait(true);
|
||||||
|
if (statusCode != System.Net.HttpStatusCode.OK)
|
||||||
|
{
|
||||||
|
Logger.LogInformation("{Message}", $"POST /video/{videoId}/progress returned {statusCode} for video {eventArgs.Item.Name} with progress {progress} seconds");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,79 +55,82 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
|
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// TODO: Check if the TA->JF synchronization is enabled from the configuration before proceeding
|
if (Plugin.Instance!.Configuration.TAJFSync)
|
||||||
_logger.LogInformation("Starting TubeArchivist playback progresses synchronization.");
|
|
||||||
// TODO: Replace with the lists from configuration
|
|
||||||
var jfUsernames = new[] { "c", "c" };
|
|
||||||
var taUsernames = new[] { "c", "c" };
|
|
||||||
var taApi = TubeArchivistApi.GetInstance();
|
|
||||||
foreach (var jfUsername in jfUsernames)
|
|
||||||
{
|
{
|
||||||
var user = _userManager.GetUserByName(jfUsername);
|
_logger.LogInformation("Starting TubeArchivist playback progresses synchronization.");
|
||||||
if (user == null)
|
var taApi = TubeArchivistApi.GetInstance();
|
||||||
|
foreach (var jfUsername in Plugin.Instance!.Configuration.GetJFUsernamesToArray())
|
||||||
{
|
{
|
||||||
_logger.LogInformation("{Message}", $"Jellyfin user with username {jfUsername} not found");
|
var user = _userManager.GetUserByName(jfUsername);
|
||||||
continue;
|
if (user == null)
|
||||||
}
|
|
||||||
|
|
||||||
var collectionItem = _libraryManager.GetItemList(new InternalItemsQuery
|
|
||||||
{
|
|
||||||
Name = Plugin.Instance?.Configuration.CollectionTitle,
|
|
||||||
IncludeItemTypes = new[] { BaseItemKind.CollectionFolder }
|
|
||||||
}).FirstOrDefault();
|
|
||||||
|
|
||||||
if (collectionItem == null)
|
|
||||||
{
|
|
||||||
var message = $"Collection '{Plugin.Instance?.Configuration.CollectionTitle}' not found.";
|
|
||||||
_logger.LogCritical("{Message}", message);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var channels = _libraryManager.GetItemList(new InternalItemsQuery
|
|
||||||
{
|
{
|
||||||
ParentId = collectionItem.Id,
|
_logger.LogInformation("{Message}", $"Jellyfin user with username {jfUsername} not found");
|
||||||
IncludeItemTypes = new[] { BaseItemKind.Series }
|
continue;
|
||||||
});
|
}
|
||||||
|
|
||||||
foreach (var channel in channels)
|
var collectionItem = _libraryManager.GetItemList(new InternalItemsQuery
|
||||||
{
|
{
|
||||||
var years = _libraryManager.GetItemList(new InternalItemsQuery
|
Name = Plugin.Instance?.Configuration.CollectionTitle,
|
||||||
|
IncludeItemTypes = new[] { BaseItemKind.CollectionFolder }
|
||||||
|
}).FirstOrDefault();
|
||||||
|
|
||||||
|
if (collectionItem == null)
|
||||||
|
{
|
||||||
|
var message = $"Collection '{Plugin.Instance?.Configuration.CollectionTitle}' not found.";
|
||||||
|
_logger.LogCritical("{Message}", message);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var channels = _libraryManager.GetItemList(new InternalItemsQuery
|
||||||
{
|
{
|
||||||
ParentId = channel.Id,
|
ParentId = collectionItem.Id,
|
||||||
IncludeItemTypes = new[] { BaseItemKind.Season }
|
IncludeItemTypes = new[] { BaseItemKind.Series }
|
||||||
});
|
});
|
||||||
|
_logger.LogInformation("Analyzing collection {Id} with name {Name}", collectionItem.Id, collectionItem.Name);
|
||||||
|
_logger.LogInformation("Found {Message} channels", channels.Count);
|
||||||
|
|
||||||
foreach (var year in years)
|
foreach (var channel in channels)
|
||||||
{
|
{
|
||||||
var videos = _libraryManager.GetItemList(new InternalItemsQuery
|
var years = _libraryManager.GetItemList(new InternalItemsQuery
|
||||||
{
|
{
|
||||||
ParentId = year.Id,
|
ParentId = channel.Id,
|
||||||
IncludeItemTypes = new[] { BaseItemKind.Episode }
|
IncludeItemTypes = new[] { BaseItemKind.Season }
|
||||||
});
|
});
|
||||||
|
_logger.LogInformation("Found {Message} years", years.Count);
|
||||||
|
|
||||||
foreach (var video in videos)
|
foreach (var year in years)
|
||||||
{
|
{
|
||||||
var playbackProgress = await taApi.GetProgress(Utils.GetVideoNameFromPath(video.Path)).ConfigureAwait(true);
|
var videos = _libraryManager.GetItemList(new InternalItemsQuery
|
||||||
if (playbackProgress != null)
|
|
||||||
{
|
{
|
||||||
var userItemData = _userDataManager.GetUserData(user, video);
|
ParentId = year.Id,
|
||||||
var userUpdateData = new UpdateUserItemDataDto
|
IncludeItemTypes = new[] { BaseItemKind.Episode }
|
||||||
{
|
});
|
||||||
PlaybackPositionTicks = playbackProgress.Position * TimeSpan.TicksPerSecond
|
_logger.LogInformation("Found {Message} videos", videos.Count);
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: Also last played datetime should be updated once TA will return it
|
foreach (var video in videos)
|
||||||
if (userItemData.PlaybackPositionTicks >= video.RunTimeTicks)
|
{
|
||||||
|
var playbackProgress = await taApi.GetProgress(Utils.GetVideoNameFromPath(video.Path)).ConfigureAwait(true);
|
||||||
|
if (playbackProgress != null)
|
||||||
{
|
{
|
||||||
userUpdateData.Played = true;
|
var userItemData = _userDataManager.GetUserData(user, video);
|
||||||
}
|
var userUpdateData = new UpdateUserItemDataDto
|
||||||
else
|
{
|
||||||
{
|
PlaybackPositionTicks = playbackProgress.Position * TimeSpan.TicksPerSecond
|
||||||
userUpdateData.Played = false;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
_userDataManager.SaveUserData(user, video, userUpdateData, UserDataSaveReason.UpdateUserData);
|
// TODO: Also last played datetime should be updated once TA will return it
|
||||||
_logger.LogInformation("{Message}", $"Playback progress for video {video.Name} set to {userItemData.PlaybackPositionTicks / TimeSpan.TicksPerSecond} seconds for user {jfUsername}.");
|
if (userItemData.PlaybackPositionTicks >= video.RunTimeTicks)
|
||||||
|
{
|
||||||
|
userUpdateData.Played = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
userUpdateData.Played = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_userDataManager.SaveUserData(user, video, userUpdateData, UserDataSaveReason.UpdateUserData);
|
||||||
|
_logger.LogInformation("{Message}", $"Playback progress for video {video.Name} set to {userItemData.PlaybackPositionTicks / TimeSpan.TicksPerSecond} seconds for user {jfUsername}.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user