mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-05-01 06:16:30 +01:00
Merge branch 'master' into trickplay
This commit is contained in:
@@ -112,7 +112,8 @@ namespace Emby.Server.Implementations.Collections
|
||||
return Path.Combine(_appPaths.DataPath, "collections");
|
||||
}
|
||||
|
||||
private Task<Folder?> GetCollectionsFolder(bool createIfNeeded)
|
||||
/// <inheritdoc />
|
||||
public Task<Folder?> GetCollectionsFolder(bool createIfNeeded)
|
||||
{
|
||||
return EnsureLibraryFolder(GetCollectionsFolderPath(), createIfNeeded);
|
||||
}
|
||||
|
||||
@@ -30,12 +30,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
{
|
||||
public class M3UTunerHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost
|
||||
{
|
||||
private static readonly string[] _disallowedSharedStreamExtensions =
|
||||
private static readonly string[] _disallowedMimeTypes =
|
||||
{
|
||||
".mkv",
|
||||
".mp4",
|
||||
".m3u8",
|
||||
".mpd"
|
||||
"video/x-matroska",
|
||||
"video/mp4",
|
||||
"application/vnd.apple.mpegurl",
|
||||
"application/mpegurl",
|
||||
"application/x-mpegurl",
|
||||
"video/vnd.mpeg.dash.mpd"
|
||||
};
|
||||
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
@@ -118,9 +120,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
|
||||
if (mediaSource.Protocol == MediaProtocol.Http && !mediaSource.RequiresLooping)
|
||||
{
|
||||
var extension = Path.GetExtension(mediaSource.Path) ?? string.Empty;
|
||||
using var message = new HttpRequestMessage(HttpMethod.Head, mediaSource.Path);
|
||||
using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
|
||||
.SendAsync(message, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
if (!_disallowedMimeTypes.Contains(response.Content.Headers.ContentType?.ToString(), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new SharedHttpStream(mediaSource, tunerHost, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper);
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_appHost = appHost;
|
||||
OriginalStreamId = originalStreamId;
|
||||
EnableStreamSharing = true;
|
||||
}
|
||||
|
||||
public override async Task Open(CancellationToken openCancellationToken)
|
||||
@@ -59,39 +58,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, CancellationToken.None)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var contentType = response.Content.Headers.ContentType?.ToString() ?? string.Empty;
|
||||
if (contentType.Contains("matroska", StringComparison.OrdinalIgnoreCase)
|
||||
|| contentType.Contains("mp4", StringComparison.OrdinalIgnoreCase)
|
||||
|| contentType.Contains("dash", StringComparison.OrdinalIgnoreCase)
|
||||
|| contentType.Contains("mpegURL", StringComparison.OrdinalIgnoreCase)
|
||||
|| contentType.Contains("text/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Close the stream without any sharing features
|
||||
response.Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
SetTempFilePath("ts");
|
||||
|
||||
var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
_ = StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token);
|
||||
|
||||
// OpenedMediaSource.Protocol = MediaProtocol.File;
|
||||
// OpenedMediaSource.Path = tempFile;
|
||||
// OpenedMediaSource.ReadAtNativeFramerate = true;
|
||||
|
||||
MediaSource.Path = _appHost.GetApiUrlForLocalAccess() + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts";
|
||||
MediaSource.Protocol = MediaProtocol.Http;
|
||||
|
||||
// OpenedMediaSource.Path = TempFilePath;
|
||||
// OpenedMediaSource.Protocol = MediaProtocol.File;
|
||||
|
||||
// OpenedMediaSource.Path = _tempFilePath;
|
||||
// OpenedMediaSource.Protocol = MediaProtocol.File;
|
||||
// OpenedMediaSource.SupportsDirectPlay = false;
|
||||
// OpenedMediaSource.SupportsDirectStream = true;
|
||||
// OpenedMediaSource.SupportsTranscoding = true;
|
||||
var res = await taskCompletionSource.Task.ConfigureAwait(false);
|
||||
if (!res)
|
||||
{
|
||||
@@ -108,15 +81,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
try
|
||||
{
|
||||
Logger.LogInformation("Beginning {StreamType} stream to {FilePath}", GetType().Name, TempFilePath);
|
||||
using var message = response;
|
||||
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||
await using var fileStream = new FileStream(TempFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
|
||||
await StreamHelper.CopyToAsync(
|
||||
stream,
|
||||
fileStream,
|
||||
IODefaults.CopyToBufferSize,
|
||||
() => Resolve(openTaskCompletionSource),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
using (response)
|
||||
{
|
||||
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||
await using var fileStream = new FileStream(TempFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
|
||||
await StreamHelper.CopyToAsync(
|
||||
stream,
|
||||
fileStream,
|
||||
IODefaults.CopyToBufferSize,
|
||||
() => Resolve(openTaskCompletionSource),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
|
||||
@@ -109,5 +109,19 @@
|
||||
"Sync": "समकालीन",
|
||||
"SubtitleDownloadFailureFromForItem": "उपशीर्षकहरू {0} बाट {1} को लागि डाउनलोड गर्न असफल",
|
||||
"PluginUpdatedWithName": "{0} अद्यावधिक गरिएको थियो",
|
||||
"PluginUninstalledWithName": "{0} को स्थापना रद्द गरिएको थियो"
|
||||
"PluginUninstalledWithName": "{0} को स्थापना रद्द गरिएको थियो",
|
||||
"HearingImpaired": "सुन्न नसक्ने",
|
||||
"TaskUpdatePluginsDescription": "स्वचालित रूपमा अद्यावधिक गर्न कन्फिगर गरिएका प्लगइनहरूका लागि अद्यावधिकहरू डाउनलोड र स्थापना गर्दछ।",
|
||||
"TaskCleanTranscode": "सफा ट्रान्सकोड निर्देशिका",
|
||||
"TaskCleanTranscodeDescription": "एक दिन भन्दा पुराना ट्रान्सकोड फाइलहरू मेटाउँछ।",
|
||||
"TaskRefreshChannels": "च्यानलहरू ताजा गर्नुहोस्",
|
||||
"TaskDownloadMissingSubtitlesDescription": "मेटाडेटा कन्फिगरेसनमा आधारित हराइरहेको उपशीर्षकहरूको लागि इन्टरनेट खोज्छ।",
|
||||
"TaskOptimizeDatabase": "डेटाबेस अप्टिमाइज गर्नुहोस्",
|
||||
"TaskOptimizeDatabaseDescription": "डाटाबेस कम्प्याक्ट र खाली ठाउँ काट्छ। पुस्तकालय स्क्यान गरेपछि वा डाटाबेस परिमार्जनलाई संकेत गर्ने अन्य परिवर्तनहरू गरेपछि यो कार्य चलाउँदा कार्यसम्पादनमा सुधार हुन सक्छ।",
|
||||
"TaskKeyframeExtractorDescription": "थप सटीक एचएलएस प्लेलिस्टहरू सिर्जना गर्न भिडियो फाइलहरूबाट कीफ्रेमहरू निकाल्छ। यो कार्य लामो समय सम्म चल्न सक्छ।",
|
||||
"TaskUpdatePlugins": "प्लगइनहरू अपडेट गर्नुहोस्",
|
||||
"TaskRefreshPeopleDescription": "तपाईंको मिडिया लाइब्रेरीमा अभिनेता र निर्देशकहरूको लागि मेटाडेटा अपडेट गर्दछ।",
|
||||
"TaskRefreshChannelsDescription": "इन्टरनेट च्यानल जानकारी ताजा गर्दछ।",
|
||||
"TaskDownloadMissingSubtitles": "छुटेका उपशीर्षकहरू डाउनलोड गर्नुहोस्",
|
||||
"TaskKeyframeExtractor": "कीफ्रेम एक्स्ट्रक्टर"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Collections;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.ScheduledTasks.Tasks;
|
||||
|
||||
/// <summary>
|
||||
/// Deletes Path references from collections that no longer exists.
|
||||
/// </summary>
|
||||
public class CleanupCollectionPathsTask : IScheduledTask
|
||||
{
|
||||
private readonly ILocalizationManager _localization;
|
||||
private readonly ICollectionManager _collectionManager;
|
||||
private readonly ILogger<CleanupCollectionPathsTask> _logger;
|
||||
private readonly IProviderManager _providerManager;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CleanupCollectionPathsTask"/> class.
|
||||
/// </summary>
|
||||
/// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
|
||||
/// <param name="collectionManager">Instance of the <see cref="ICollectionManager"/> interface.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="providerManager">The provider manager.</param>
|
||||
/// <param name="fileSystem">The filesystem.</param>
|
||||
public CleanupCollectionPathsTask(
|
||||
ILocalizationManager localization,
|
||||
ICollectionManager collectionManager,
|
||||
ILogger<CleanupCollectionPathsTask> logger,
|
||||
IProviderManager providerManager,
|
||||
IFileSystem fileSystem)
|
||||
{
|
||||
_localization = localization;
|
||||
_collectionManager = collectionManager;
|
||||
_logger = logger;
|
||||
_providerManager = providerManager;
|
||||
_fileSystem = fileSystem;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => _localization.GetLocalizedString("TaskCleanCollections");
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => "CleanCollections";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Description => _localization.GetLocalizedString("TaskCleanCollectionsDescription");
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
var collectionsFolder = await _collectionManager.GetCollectionsFolder(false).ConfigureAwait(false);
|
||||
if (collectionsFolder is null)
|
||||
{
|
||||
_logger.LogDebug("There is no collection folder to be found");
|
||||
return;
|
||||
}
|
||||
|
||||
var collections = collectionsFolder.Children.OfType<BoxSet>().ToArray();
|
||||
_logger.LogDebug("Found {CollectionLength} Boxsets", collections.Length);
|
||||
|
||||
var itemsToRemove = new List<LinkedChild>();
|
||||
for (var index = 0; index < collections.Length; index++)
|
||||
{
|
||||
var collection = collections[index];
|
||||
_logger.LogDebug("Check Boxset {CollectionName}", collection.Name);
|
||||
|
||||
foreach (var collectionLinkedChild in collection.LinkedChildren)
|
||||
{
|
||||
if (!File.Exists(collectionLinkedChild.Path))
|
||||
{
|
||||
_logger.LogInformation("Item in boxset {CollectionName} cannot be found at {ItemPath}", collection.Name, collectionLinkedChild.Path);
|
||||
itemsToRemove.Add(collectionLinkedChild);
|
||||
}
|
||||
}
|
||||
|
||||
if (itemsToRemove.Count != 0)
|
||||
{
|
||||
_logger.LogDebug("Update Boxset {CollectionName}", collection.Name);
|
||||
collection.LinkedChildren = collection.LinkedChildren.Except(itemsToRemove).ToArray();
|
||||
await collection.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
_providerManager.QueueRefresh(
|
||||
collection.Id,
|
||||
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||
{
|
||||
ForceSave = true
|
||||
},
|
||||
RefreshPriority.High);
|
||||
|
||||
itemsToRemove.Clear();
|
||||
}
|
||||
|
||||
progress.Report(100D / collections.Length * (index + 1));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
||||
{
|
||||
return new[] { new TaskTriggerInfo() { Type = TaskTriggerInfo.TriggerStartup } };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user