Compare commits

...

18 Commits

Author SHA1 Message Date
Joshua M. Boniface
3566d21ad1 Bump version to 10.7.3 2021-05-04 20:01:50 -04:00
Bond-009
4c8df4c5bb Merge pull request #5943 from Maxr1998/device-profile-defaults
(cherry picked from commit 9d3f614527)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-05-04 19:33:08 -04:00
Bond-009
9798bf29f3 Merge pull request #5937 from Maxr1998/videoscontroller-api-fix
Remove extraneous 'stream' parameter

(cherry picked from commit 266913c5d8)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-05-02 17:02:10 -04:00
Joshua M. Boniface
93ce087fc9 Merge pull request from GHSA-rgjw-4fwc-9v96
Remove /Images/Remote API endpoint

(cherry picked from commit e71cd8274a)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-05-02 17:01:33 -04:00
Bond-009
e39495354b Merge pull request #5904 from cvium/fix-updatepeople-questionmark
add UpdatePeopleAsync and add people to both tables

(cherry picked from commit 5df87b3e0d)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-29 14:56:58 -04:00
Bond-009
ee94fad8f7 Merge pull request #5903 from iwalton3/auto-leave-syncplay
Leave SyncPlay group on session disconnect.

(cherry picked from commit dcc2df75ec)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-29 14:56:58 -04:00
Claus Vium
53239b0529 Merge pull request #5861 from BaronGreenback/ProfileMatch
Change profile matching to match what the web interface says.

(cherry picked from commit 12496677bd)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-29 14:56:57 -04:00
Bond-009
cf0da1de86 Merge pull request #5826 from BaronGreenback/ssdpFix
PlayTo Fix: Use external ip not internal interface

(cherry picked from commit f4a59c92e6)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-29 14:56:30 -04:00
Bond-009
093510ae58 Merge pull request #5881 from cvium/tmdb-episode-externalids
Add tvrage and imdb ids for episodes

(cherry picked from commit e19d89bb4f)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-21 21:25:41 -04:00
Bond-009
c3fafe9289 Merge pull request #5878 from Artiume/patch-2
(cherry picked from commit 95ab603a40)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-21 21:25:41 -04:00
Bond-009
bd914acd16 Merge pull request #5873 from cvium/fix-displaypref-migration
(cherry picked from commit 233900401e)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-21 21:25:41 -04:00
Bond-009
81f9bec101 Merge pull request #5870 from cvium/fix-tmdbpersonprovider
(cherry picked from commit 6b103f7ab2)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-21 21:25:40 -04:00
Bond-009
7db8601fbc Merge pull request #5863 from cvium/fix-index-migration
use IF NOT EXISTS in migration

(cherry picked from commit 5a4cfe11cf)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-21 21:25:40 -04:00
Bond-009
1ec247f5d8 Merge pull request #5860 from cvium/fix-notification-user-list
Fix notification disabled users list

(cherry picked from commit ebe8301404)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-21 21:25:39 -04:00
Bond-009
11e9173fbc Merge pull request #5859 from cvium/fix-streambuilder-permissions
Respect user settings for transcode and remux

(cherry picked from commit 5a6e60b414)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-21 21:24:27 -04:00
Bond-009
34508286a8 Merge pull request #5852 from cvium/fix-person-creation
Add Person to TypedBaseItems if it's new

(cherry picked from commit 4eeb69233d)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-21 21:24:27 -04:00
Claus Vium
a82eded845 Merge pull request #5848 from sgmoore/IndexError
Fix ArgumentOutOfRangeException scanning AudioBooks

(cherry picked from commit 665220c4fb)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-21 21:24:27 -04:00
Bond-009
fcb729ff6b Merge pull request #5808 from cvium/semi-fix-collection-perf
(cherry picked from commit 48ed4b016c)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-21 21:24:27 -04:00
43 changed files with 517 additions and 509 deletions

View File

@@ -974,15 +974,28 @@ namespace Emby.Dlna.Didl
return;
}
var albumartUrlInfo = GetImageUrl(imageInfo, _profile.MaxAlbumArtWidth, _profile.MaxAlbumArtHeight, "jpg");
// TODO: Remove these default values
var albumArtUrlInfo = GetImageUrl(
imageInfo,
_profile.MaxAlbumArtWidth ?? 10000,
_profile.MaxAlbumArtHeight ?? 10000,
"jpg");
writer.WriteStartElement("upnp", "albumArtURI", NsUpnp);
writer.WriteAttributeString("dlna", "profileID", NsDlna, _profile.AlbumArtPn);
writer.WriteString(albumartUrlInfo.url);
if (!string.IsNullOrEmpty(_profile.AlbumArtPn))
{
writer.WriteAttributeString("dlna", "profileID", NsDlna, _profile.AlbumArtPn);
}
writer.WriteString(albumArtUrlInfo.url);
writer.WriteFullEndElement();
// TOOD: Remove these default values
var iconUrlInfo = GetImageUrl(imageInfo, _profile.MaxIconWidth ?? 48, _profile.MaxIconHeight ?? 48, "jpg");
// TODO: Remove these default values
var iconUrlInfo = GetImageUrl(
imageInfo,
_profile.MaxIconWidth ?? 48,
_profile.MaxIconHeight ?? 48,
"jpg");
writer.WriteElementString("upnp", "icon", NsUpnp, iconUrlInfo.url);
if (!_profile.EnableAlbumArtInDidl)
@@ -1206,8 +1219,7 @@ namespace Emby.Dlna.Didl
if (width.HasValue && height.HasValue)
{
var newSize = DrawingUtils.Resize(
new ImageDimensions(width.Value, height.Value), 0, 0, maxWidth, maxHeight);
var newSize = DrawingUtils.Resize(new ImageDimensions(width.Value, height.Value), 0, 0, maxWidth, maxHeight);
width = newSize.Width;
height = newSize.Height;

View File

@@ -111,7 +111,7 @@ namespace Emby.Dlna
if (profile != null)
{
_logger.LogDebug("Found matching device profile: {0}", profile.Name);
_logger.LogDebug("Found matching device profile: {ProfileName}", profile.Name);
}
else
{
@@ -138,80 +138,45 @@ namespace Emby.Dlna
_logger.LogInformation(builder.ToString());
}
private bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo)
/// <summary>
/// Attempts to match a device with a profile.
/// Rules:
/// - If the profile field has no value, the field matches irregardless of its contents.
/// - the profile field can be an exact match, or a reg exp.
/// </summary>
/// <param name="deviceInfo">The <see cref="DeviceIdentification"/> of the device.</param>
/// <param name="profileInfo">The <see cref="DeviceIdentification"/> of the profile.</param>
/// <returns><b>True</b> if they match.</returns>
public bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo)
{
if (!string.IsNullOrEmpty(profileInfo.FriendlyName))
{
if (deviceInfo.FriendlyName == null || !IsRegexOrSubstringMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName))
{
return false;
}
}
if (!string.IsNullOrEmpty(profileInfo.Manufacturer))
{
if (deviceInfo.Manufacturer == null || !IsRegexOrSubstringMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer))
{
return false;
}
}
if (!string.IsNullOrEmpty(profileInfo.ManufacturerUrl))
{
if (deviceInfo.ManufacturerUrl == null || !IsRegexOrSubstringMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl))
{
return false;
}
}
if (!string.IsNullOrEmpty(profileInfo.ModelDescription))
{
if (deviceInfo.ModelDescription == null || !IsRegexOrSubstringMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription))
{
return false;
}
}
if (!string.IsNullOrEmpty(profileInfo.ModelName))
{
if (deviceInfo.ModelName == null || !IsRegexOrSubstringMatch(deviceInfo.ModelName, profileInfo.ModelName))
{
return false;
}
}
if (!string.IsNullOrEmpty(profileInfo.ModelNumber))
{
if (deviceInfo.ModelNumber == null || !IsRegexOrSubstringMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber))
{
return false;
}
}
if (!string.IsNullOrEmpty(profileInfo.ModelUrl))
{
if (deviceInfo.ModelUrl == null || !IsRegexOrSubstringMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl))
{
return false;
}
}
if (!string.IsNullOrEmpty(profileInfo.SerialNumber))
{
if (deviceInfo.SerialNumber == null || !IsRegexOrSubstringMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber))
{
return false;
}
}
return true;
return IsRegexOrSubstringMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName)
&& IsRegexOrSubstringMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer)
&& IsRegexOrSubstringMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl)
&& IsRegexOrSubstringMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription)
&& IsRegexOrSubstringMatch(deviceInfo.ModelName, profileInfo.ModelName)
&& IsRegexOrSubstringMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber)
&& IsRegexOrSubstringMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl)
&& IsRegexOrSubstringMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber);
}
private bool IsRegexOrSubstringMatch(string input, string pattern)
{
if (string.IsNullOrEmpty(pattern))
{
// In profile identification: An empty pattern matches anything.
return true;
}
if (string.IsNullOrEmpty(input))
{
// The profile contains a value, and the device doesn't.
return false;
}
try
{
return input.Contains(pattern, StringComparison.OrdinalIgnoreCase) || Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
return input.Equals(pattern, StringComparison.OrdinalIgnoreCase)
|| Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
}
catch (ArgumentException ex)
{

View File

@@ -188,7 +188,7 @@ namespace Emby.Dlna.PlayTo
_sessionManager.UpdateDeviceName(sessionInfo.Id, deviceName);
string serverAddress = _appHost.GetSmartApiUrl(info.LocalIpAddress);
string serverAddress = _appHost.GetSmartApiUrl(info.RemoteIpAddress);
controller = new PlayToController(
sessionInfo,

View File

@@ -104,7 +104,7 @@ namespace Emby.Dlna.Ssdp
{
Location = e.DiscoveredDevice.DescriptionLocation,
Headers = headers,
LocalIpAddress = e.LocalIpAddress
RemoteIpAddress = e.RemoteIpAddress
});
DeviceDiscoveredInternal?.Invoke(this, args);

View File

@@ -33,7 +33,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Naming</PackageId>
<VersionPrefix>10.7.2</VersionPrefix>
<VersionPrefix>10.7.3</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>

View File

@@ -124,7 +124,7 @@ namespace Emby.Server.Implementations.Collections
private IEnumerable<BoxSet> GetCollections(User user)
{
var folder = GetCollectionsFolder(false).Result;
var folder = GetCollectionsFolder(false).GetAwaiter().GetResult();
return folder == null
? Enumerable.Empty<BoxSet>()
@@ -319,11 +319,11 @@ namespace Emby.Server.Implementations.Collections
{
var results = new Dictionary<Guid, BaseItem>();
var allBoxsets = GetCollections(user).ToList();
var allBoxSets = GetCollections(user).ToList();
foreach (var item in items)
{
if (!(item is ISupportsBoxSetGrouping))
if (item is not ISupportsBoxSetGrouping)
{
results[item.Id] = item;
}
@@ -331,33 +331,44 @@ namespace Emby.Server.Implementations.Collections
{
var itemId = item.Id;
var currentBoxSets = allBoxsets
.Where(i => i.ContainsLinkedChildByItemId(itemId))
.ToList();
if (currentBoxSets.Count > 0)
var itemIsInBoxSet = false;
foreach (var boxSet in allBoxSets)
{
foreach (var boxset in currentBoxSets)
if (!boxSet.ContainsLinkedChildByItemId(itemId))
{
results[boxset.Id] = boxset;
continue;
}
itemIsInBoxSet = true;
results.TryAdd(boxSet.Id, boxSet);
}
// skip any item that is in a box set
if (itemIsInBoxSet)
{
continue;
}
var alreadyInResults = false;
// this is kind of a performance hack because only Video has alternate versions that should be in a box set?
if (item is Video video)
{
foreach (var childId in video.GetLocalAlternateVersionIds())
{
if (!results.ContainsKey(childId))
{
continue;
}
alreadyInResults = true;
break;
}
}
else
{
var alreadyInResults = false;
foreach (var child in item.GetMediaSources(true))
{
if (Guid.TryParse(child.Id, out var id) && results.ContainsKey(id))
{
alreadyInResults = true;
break;
}
}
if (!alreadyInResults)
{
results[item.Id] = item;
}
if (!alreadyInResults)
{
results[itemId] = item;
}
}
}

View File

@@ -2880,6 +2880,12 @@ namespace Emby.Server.Implementations.Library
}
public void UpdatePeople(BaseItem item, List<PersonInfo> people)
{
UpdatePeopleAsync(item, people, CancellationToken.None).GetAwaiter().GetResult();
}
/// <inheritdoc />
public async Task UpdatePeopleAsync(BaseItem item, List<PersonInfo> people, CancellationToken cancellationToken)
{
if (!item.SupportsPeople)
{
@@ -2887,6 +2893,8 @@ namespace Emby.Server.Implementations.Library
}
_itemRepository.UpdatePeople(item.Id, people);
await SavePeopleMetadataAsync(people, cancellationToken).ConfigureAwait(false);
}
public async Task<ItemImageInfo> ConvertImageToLocal(BaseItem item, ItemImageInfo image, int imageIndex)
@@ -2990,6 +2998,58 @@ namespace Emby.Server.Implementations.Library
}
}
private async Task SavePeopleMetadataAsync(IEnumerable<PersonInfo> people, CancellationToken cancellationToken)
{
var personsToSave = new List<BaseItem>();
foreach (var person in people)
{
cancellationToken.ThrowIfCancellationRequested();
var itemUpdateType = ItemUpdateType.MetadataDownload;
var saveEntity = false;
var personEntity = GetPerson(person.Name);
// if PresentationUniqueKey is empty it's likely a new item.
if (string.IsNullOrEmpty(personEntity.PresentationUniqueKey))
{
personEntity.PresentationUniqueKey = personEntity.CreatePresentationUniqueKey();
saveEntity = true;
}
foreach (var id in person.ProviderIds)
{
if (!string.Equals(personEntity.GetProviderId(id.Key), id.Value, StringComparison.OrdinalIgnoreCase))
{
personEntity.SetProviderId(id.Key, id.Value);
saveEntity = true;
}
}
if (!string.IsNullOrWhiteSpace(person.ImageUrl) && !personEntity.HasImage(ImageType.Primary))
{
personEntity.SetImage(
new ItemImageInfo
{
Path = person.ImageUrl,
Type = ImageType.Primary
},
0);
saveEntity = true;
itemUpdateType = ItemUpdateType.ImageUpdate;
}
if (saveEntity)
{
personsToSave.Add(personEntity);
await RunMetadataSavers(personEntity, itemUpdateType).ConfigureAwait(false);
}
}
CreateItems(personsToSave, null, CancellationToken.None);
}
private void StartScanInBackground()
{
Task.Run(() =>

View File

@@ -200,10 +200,15 @@ namespace Emby.Server.Implementations.Library
{
source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding);
}
else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
{
source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding);
source.SupportsDirectStream = user.HasPermission(PermissionKind.EnablePlaybackRemuxing);
}
}
}
return SortMediaSources(list).Where(i => i.Type != MediaSourceType.Placeholder).ToList();
return SortMediaSources(list);
}
public MediaProtocol GetPathProtocol(string path)
@@ -437,7 +442,7 @@ namespace Emby.Server.Implementations.Library
}
}
private static IEnumerable<MediaSourceInfo> SortMediaSources(IEnumerable<MediaSourceInfo> sources)
private static List<MediaSourceInfo> SortMediaSources(IEnumerable<MediaSourceInfo> sources)
{
return sources.OrderBy(i =>
{
@@ -452,8 +457,9 @@ namespace Emby.Server.Implementations.Library
{
var stream = i.VideoStream;
return stream == null || stream.Width == null ? 0 : stream.Width.Value;
return stream?.Width ?? 0;
})
.Where(i => i.Type != MediaSourceType.Placeholder)
.ToList();
}

View File

@@ -201,6 +201,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
continue;
}
if (resolvedItem.Files.Count == 0)
{
continue;
}
var firstMedia = resolvedItem.Files[0];
var libraryItem = new T

View File

@@ -248,15 +248,15 @@ namespace Emby.Server.Implementations.Library
}
else if (positionTicks > 0 && hasRuntime && item is AudioBook)
{
var minIn = TimeSpan.FromTicks(positionTicks).TotalMinutes;
var minOut = TimeSpan.FromTicks(runtimeTicks - positionTicks).TotalMinutes;
var playbackPositionInMinutes = TimeSpan.FromTicks(positionTicks).TotalMinutes;
var remainingTimeInMinutes = TimeSpan.FromTicks(runtimeTicks - positionTicks).TotalMinutes;
if (minIn > _config.Configuration.MinAudiobookResume)
if (playbackPositionInMinutes < _config.Configuration.MinAudiobookResume)
{
// ignore progress during the beginning
positionTicks = 0;
}
else if (minOut < _config.Configuration.MaxAudiobookResume || positionTicks >= runtimeTicks)
else if (remainingTimeInMinutes < _config.Configuration.MaxAudiobookResume || positionTicks >= runtimeTicks)
{
// mark as completed close to the end
positionTicks = 0;

View File

@@ -87,7 +87,7 @@ namespace Emby.Server.Implementations.SyncPlay
_sessionManager = sessionManager;
_libraryManager = libraryManager;
_logger = loggerFactory.CreateLogger<SyncPlayManager>();
_sessionManager.SessionControllerConnected += OnSessionControllerConnected;
_sessionManager.SessionEnded += OnSessionEnded;
}
/// <inheritdoc />
@@ -352,18 +352,18 @@ namespace Emby.Server.Implementations.SyncPlay
return;
}
_sessionManager.SessionControllerConnected -= OnSessionControllerConnected;
_sessionManager.SessionEnded -= OnSessionEnded;
_disposed = true;
}
private void OnSessionControllerConnected(object sender, SessionEventArgs e)
private void OnSessionEnded(object sender, SessionEventArgs e)
{
var session = e.SessionInfo;
if (_sessionToGroupMap.TryGetValue(session.Id, out var group))
{
var request = new JoinGroupRequest(group.GroupId);
JoinGroup(session, request, CancellationToken.None);
var leaveGroupRequest = new LeaveGroupRequest();
LeaveGroup(session, leaveGroupRequest, CancellationToken.None);
}
}

View File

@@ -239,48 +239,6 @@ namespace Jellyfin.Api.Controllers
return Ok(results);
}
/// <summary>
/// Gets a remote image.
/// </summary>
/// <param name="imageUrl">The image url.</param>
/// <param name="providerName">The provider name.</param>
/// <response code="200">Remote image retrieved.</response>
/// <returns>
/// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.
/// The task result contains an <see cref="FileStreamResult"/> containing the images file stream.
/// </returns>
[HttpGet("Items/RemoteSearch/Image")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesImageFile]
public async Task<ActionResult> GetRemoteSearchImage(
[FromQuery, Required] string imageUrl,
[FromQuery, Required] string providerName)
{
var urlHash = imageUrl.GetMD5();
var pointerCachePath = GetFullCachePath(urlHash.ToString());
try
{
var contentPath = await System.IO.File.ReadAllTextAsync(pointerCachePath).ConfigureAwait(false);
if (System.IO.File.Exists(contentPath))
{
return PhysicalFile(contentPath, MimeTypes.GetMimeType(contentPath));
}
}
catch (FileNotFoundException)
{
// Means the file isn't cached yet
}
catch (IOException)
{
// Means the file isn't cached yet
}
await DownloadImage(providerName, imageUrl, urlHash, pointerCachePath).ConfigureAwait(false);
var updatedContentPath = await System.IO.File.ReadAllTextAsync(pointerCachePath).ConfigureAwait(false);
return PhysicalFile(updatedContentPath, MimeTypes.GetMimeType(updatedContentPath));
}
/// <summary>
/// Applies search criteria to an item and refreshes metadata.
/// </summary>
@@ -322,54 +280,5 @@ namespace Jellyfin.Api.Controllers
return NoContent();
}
/// <summary>
/// Downloads the image.
/// </summary>
/// <param name="providerName">Name of the provider.</param>
/// <param name="url">The URL.</param>
/// <param name="urlHash">The URL hash.</param>
/// <param name="pointerCachePath">The pointer cache path.</param>
/// <returns>Task.</returns>
private async Task DownloadImage(string providerName, string url, Guid urlHash, string pointerCachePath)
{
using var result = await _providerManager.GetSearchImage(providerName, url, CancellationToken.None).ConfigureAwait(false);
if (result.Content.Headers.ContentType?.MediaType == null)
{
throw new ResourceNotFoundException(nameof(result.Content.Headers.ContentType));
}
var ext = result.Content.Headers.ContentType.MediaType.Split('/')[^1];
var fullCachePath = GetFullCachePath(urlHash + "." + ext);
var directory = Path.GetDirectoryName(fullCachePath) ?? throw new ResourceNotFoundException($"Provided path ({fullCachePath}) is not valid.");
Directory.CreateDirectory(directory);
using (var stream = result.Content)
{
// use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
await using var fileStream = new FileStream(
fullCachePath,
FileMode.Create,
FileAccess.Write,
FileShare.None,
IODefaults.FileStreamBufferSize,
true);
await stream.CopyToAsync(fileStream).ConfigureAwait(false);
}
var pointerCacheDirectory = Path.GetDirectoryName(pointerCachePath) ?? throw new ArgumentException($"Provided path ({pointerCachePath}) is not valid.", nameof(pointerCachePath));
Directory.CreateDirectory(pointerCacheDirectory);
await System.IO.File.WriteAllTextAsync(pointerCachePath, fullCachePath).ConfigureAwait(false);
}
/// <summary>
/// Gets the full cache path.
/// </summary>
/// <param name="filename">The filename.</param>
/// <returns>System.String.</returns>
private string GetFullCachePath(string filename)
=> Path.Combine(_appPaths.CachePath, "remote-images", filename.Substring(0, 1), filename);
}
}

View File

@@ -145,58 +145,6 @@ namespace Jellyfin.Api.Controllers
return Ok(_providerManager.GetRemoteImageProviderInfo(item));
}
/// <summary>
/// Gets a remote image.
/// </summary>
/// <param name="imageUrl">The image url.</param>
/// <response code="200">Remote image returned.</response>
/// <response code="404">Remote image not found.</response>
/// <returns>Image Stream.</returns>
[HttpGet("Images/Remote")]
[Produces(MediaTypeNames.Application.Octet)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesImageFile]
public async Task<ActionResult> GetRemoteImage([FromQuery, Required] Uri imageUrl)
{
var urlHash = imageUrl.ToString().GetMD5();
var pointerCachePath = GetFullCachePath(urlHash.ToString());
string? contentPath = null;
var hasFile = false;
try
{
contentPath = await System.IO.File.ReadAllTextAsync(pointerCachePath).ConfigureAwait(false);
if (System.IO.File.Exists(contentPath))
{
hasFile = true;
}
}
catch (FileNotFoundException)
{
// The file isn't cached yet
}
catch (IOException)
{
// The file isn't cached yet
}
if (!hasFile)
{
await DownloadImage(imageUrl, urlHash, pointerCachePath).ConfigureAwait(false);
contentPath = await System.IO.File.ReadAllTextAsync(pointerCachePath).ConfigureAwait(false);
}
if (string.IsNullOrEmpty(contentPath))
{
return NotFound();
}
var contentType = MimeTypes.GetMimeType(contentPath);
return PhysicalFile(contentPath, contentType);
}
/// <summary>
/// Downloads a remote image for an item.
/// </summary>

View File

@@ -298,9 +298,9 @@ namespace Jellyfin.Api.Controllers
{
Type = DlnaProfileType.Audio,
Context = EncodingContext.Streaming,
Container = transcodingContainer,
AudioCodec = audioCodec,
Protocol = transcodingProtocol,
Container = transcodingContainer ?? "mp3",
AudioCodec = audioCodec ?? "mp3",
Protocol = transcodingProtocol ?? "http",
BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false,
MaxAudioChannels = transcodingAudioChannels?.ToString(CultureInfo.InvariantCulture)
}

View File

@@ -538,7 +538,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param>
/// <param name="playSessionId">The play session id.</param>
/// <param name="segmentContainer">The segment container.</param>
/// <param name="segmentLength">The segment lenght.</param>
/// <param name="segmentLength">The segment length.</param>
/// <param name="minSegments">The minimum number of segments.</param>
/// <param name="mediaSourceId">The media version id, if playing an alternate version.</param>
/// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param>
@@ -567,7 +567,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param>
/// <param name="requireAvc">Optional. Whether to require avc.</param>
/// <param name="deInterlace">Optional. Whether to deinterlace the video.</param>
/// <param name="requireNonAnamorphic">Optional. Whether to require a non anamporphic stream.</param>
/// <param name="requireNonAnamorphic">Optional. Whether to require a non anamorphic stream.</param>
/// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param>
/// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param>
/// <param name="liveStreamId">The live stream id.</param>
@@ -581,8 +581,8 @@ namespace Jellyfin.Api.Controllers
/// <param name="streamOptions">Optional. The streaming options.</param>
/// <response code="200">Video stream returned.</response>
/// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
[HttpGet("{itemId}/{stream=stream}.{container}")]
[HttpHead("{itemId}/{stream=stream}.{container}", Name = "HeadVideoStreamByContainer")]
[HttpGet("{itemId}/stream.{container}")]
[HttpHead("{itemId}/stream.{container}", Name = "HeadVideoStreamByContainer")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesVideoFile]
public Task<ActionResult> GetVideoStreamByContainer(

View File

@@ -308,7 +308,7 @@ namespace Jellyfin.Api.Helpers
{
if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)
&& !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding)
&& !user.HasPermission(PermissionKind.EnablePlaybackRemuxing))
&& user.HasPermission(PermissionKind.EnablePlaybackRemuxing))
{
options.ForceDirectStream = true;
}

View File

@@ -19,7 +19,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Data</PackageId>
<VersionPrefix>10.7.2</VersionPrefix>
<VersionPrefix>10.7.3</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>

View File

@@ -41,9 +41,9 @@ namespace Jellyfin.Server.Migrations.Routines
var databasePath = Path.Join(_serverApplicationPaths.DataPath, DbFilename);
using var connection = SQLite3.Open(databasePath, ConnectionFlags.ReadWrite, null);
_logger.LogInformation("Creating index idx_TypedBaseItemsUserDataKeyType");
connection.Execute("CREATE INDEX idx_TypedBaseItemsUserDataKeyType ON TypedBaseItems(UserDataKey, Type);");
connection.Execute("CREATE INDEX IF NOT EXISTS idx_TypedBaseItemsUserDataKeyType ON TypedBaseItems(UserDataKey, Type);");
_logger.LogInformation("Creating index idx_PeopleNameListOrder");
connection.Execute("CREATE INDEX idx_PeopleNameListOrder ON People(Name, ListOrder);");
connection.Execute("CREATE INDEX IF NOT EXISTS idx_PeopleNameListOrder ON People(Name, ListOrder);");
}
}
}
}

View File

@@ -126,13 +126,13 @@ namespace Jellyfin.Server.Migrations.Routines
ShowSidebar = dto.ShowSidebar,
ScrollDirection = dto.ScrollDirection,
ChromecastVersion = chromecastVersion,
SkipForwardLength = dto.CustomPrefs.TryGetValue("skipForwardLength", out var length)
? int.Parse(length, CultureInfo.InvariantCulture)
SkipForwardLength = dto.CustomPrefs.TryGetValue("skipForwardLength", out var length) && int.TryParse(length, out var skipForwardLength)
? skipForwardLength
: 30000,
SkipBackwardLength = dto.CustomPrefs.TryGetValue("skipBackLength", out length)
? int.Parse(length, CultureInfo.InvariantCulture)
SkipBackwardLength = dto.CustomPrefs.TryGetValue("skipBackLength", out length) && !string.IsNullOrEmpty(length) && int.TryParse(length, out var skipBackwardLength)
? skipBackwardLength
: 10000,
EnableNextVideoInfoOverlay = dto.CustomPrefs.TryGetValue("enableNextVideoInfoOverlay", out var enabled)
EnableNextVideoInfoOverlay = dto.CustomPrefs.TryGetValue("enableNextVideoInfoOverlay", out var enabled) && !string.IsNullOrEmpty(enabled)
? bool.Parse(enabled)
: true,
DashboardTheme = dto.CustomPrefs.TryGetValue("dashboardtheme", out var theme) ? theme : string.Empty,

View File

@@ -8,7 +8,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Common</PackageId>
<VersionPrefix>10.7.2</VersionPrefix>
<VersionPrefix>10.7.3</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>

View File

@@ -1434,9 +1434,14 @@ namespace MediaBrowser.Controller.Entities
var linkedChildren = LinkedChildren;
foreach (var i in linkedChildren)
{
if (i.ItemId.HasValue && i.ItemId.Value == itemId)
if (i.ItemId.HasValue)
{
return true;
if (i.ItemId.Value == itemId)
{
return true;
}
continue;
}
var child = GetLinkedChild(i);

View File

@@ -466,6 +466,15 @@ namespace MediaBrowser.Controller.Library
/// <param name="people">The people.</param>
void UpdatePeople(BaseItem item, List<PersonInfo> people);
/// <summary>
/// Asynchronously updates the people.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="people">The people.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The async task.</returns>
Task UpdatePeopleAsync(BaseItem item, List<PersonInfo> people, CancellationToken cancellationToken);
/// <summary>
/// Gets the item ids.
/// </summary>

View File

@@ -8,7 +8,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Controller</PackageId>
<VersionPrefix>10.7.2</VersionPrefix>
<VersionPrefix>10.7.3</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>

View File

@@ -1,7 +1,7 @@
#nullable disable
#pragma warning disable CS1591
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Xml.Serialization;
@@ -9,25 +9,17 @@ namespace MediaBrowser.Model.Dlna
{
public class ContainerProfile
{
[Required]
[XmlAttribute("type")]
public DlnaProfileType Type { get; set; }
public ProfileCondition[] Conditions { get; set; }
public ProfileCondition[]? Conditions { get; set; } = Array.Empty<ProfileCondition>();
[Required]
[XmlAttribute("container")]
public string Container { get; set; }
public string Container { get; set; } = string.Empty;
public ContainerProfile()
{
Conditions = Array.Empty<ProfileCondition>();
}
public string[] GetContainers()
{
return SplitValue(Container);
}
public static string[] SplitValue(string value)
public static string[] SplitValue(string? value)
{
if (string.IsNullOrEmpty(value))
{
@@ -37,14 +29,14 @@ namespace MediaBrowser.Model.Dlna
return value.Split(',', StringSplitOptions.RemoveEmptyEntries);
}
public bool ContainsContainer(string container)
public bool ContainsContainer(string? container)
{
var containers = GetContainers();
var containers = SplitValue(Container);
return ContainsContainer(containers, container);
}
public static bool ContainsContainer(string profileContainers, string inputContainer)
public static bool ContainsContainer(string? profileContainers, string? inputContainer)
{
var isNegativeList = false;
if (profileContainers != null && profileContainers.StartsWith('-'))
@@ -56,46 +48,29 @@ namespace MediaBrowser.Model.Dlna
return ContainsContainer(SplitValue(profileContainers), isNegativeList, inputContainer);
}
public static bool ContainsContainer(string[] profileContainers, string inputContainer)
public static bool ContainsContainer(string[]? profileContainers, string? inputContainer)
{
return ContainsContainer(profileContainers, false, inputContainer);
}
public static bool ContainsContainer(string[] profileContainers, bool isNegativeList, string inputContainer)
public static bool ContainsContainer(string[]? profileContainers, bool isNegativeList, string? inputContainer)
{
if (profileContainers.Length == 0)
if (profileContainers == null || profileContainers.Length == 0)
{
return true;
return isNegativeList;
}
if (isNegativeList)
{
var allInputContainers = SplitValue(inputContainer);
var allInputContainers = SplitValue(inputContainer);
foreach (var container in allInputContainers)
foreach (var container in allInputContainers)
{
if (profileContainers.Contains(container, StringComparer.OrdinalIgnoreCase))
{
if (profileContainers.Contains(container, StringComparer.OrdinalIgnoreCase))
{
return false;
}
return !isNegativeList;
}
return true;
}
else
{
var allInputContainers = SplitValue(inputContainer);
foreach (var container in allInputContainers)
{
if (profileContainers.Contains(container, StringComparer.OrdinalIgnoreCase))
{
return true;
}
}
return false;
}
return isNegativeList;
}
}
}

View File

@@ -1,6 +1,6 @@
#nullable disable
#pragma warning disable CA1819 // Properties should not return arrays
using System;
using System.ComponentModel;
using System.Linq;
using System.Xml.Serialization;
using MediaBrowser.Model.MediaInfo;
@@ -13,121 +13,104 @@ namespace MediaBrowser.Model.Dlna
[XmlRoot("Profile")]
public class DeviceProfile
{
/// <summary>
/// Initializes a new instance of the <see cref="DeviceProfile"/> class.
/// </summary>
public DeviceProfile()
{
DirectPlayProfiles = Array.Empty<DirectPlayProfile>();
TranscodingProfiles = Array.Empty<TranscodingProfile>();
ResponseProfiles = Array.Empty<ResponseProfile>();
CodecProfiles = Array.Empty<CodecProfile>();
ContainerProfiles = Array.Empty<ContainerProfile>();
SubtitleProfiles = Array.Empty<SubtitleProfile>();
XmlRootAttributes = Array.Empty<XmlAttribute>();
SupportedMediaTypes = "Audio,Photo,Video";
MaxStreamingBitrate = 8000000;
MaxStaticBitrate = 8000000;
MusicStreamingTranscodingBitrate = 128000;
}
/// <summary>
/// Gets or sets the Name.
/// </summary>
public string Name { get; set; }
public string? Name { get; set; }
/// <summary>
/// Gets or sets the Id.
/// </summary>
[XmlIgnore]
public string Id { get; set; }
public string? Id { get; set; }
/// <summary>
/// Gets or sets the Identification.
/// </summary>
public DeviceIdentification Identification { get; set; }
public DeviceIdentification? Identification { get; set; }
/// <summary>
/// Gets or sets the FriendlyName.
/// </summary>
public string FriendlyName { get; set; }
public string? FriendlyName { get; set; }
/// <summary>
/// Gets or sets the Manufacturer.
/// </summary>
public string Manufacturer { get; set; }
public string? Manufacturer { get; set; }
/// <summary>
/// Gets or sets the ManufacturerUrl.
/// </summary>
public string ManufacturerUrl { get; set; }
public string? ManufacturerUrl { get; set; }
/// <summary>
/// Gets or sets the ModelName.
/// </summary>
public string ModelName { get; set; }
public string? ModelName { get; set; }
/// <summary>
/// Gets or sets the ModelDescription.
/// </summary>
public string ModelDescription { get; set; }
public string? ModelDescription { get; set; }
/// <summary>
/// Gets or sets the ModelNumber.
/// </summary>
public string ModelNumber { get; set; }
public string? ModelNumber { get; set; }
/// <summary>
/// Gets or sets the ModelUrl.
/// </summary>
public string ModelUrl { get; set; }
public string? ModelUrl { get; set; }
/// <summary>
/// Gets or sets the SerialNumber.
/// </summary>
public string SerialNumber { get; set; }
public string? SerialNumber { get; set; }
/// <summary>
/// Gets or sets a value indicating whether EnableAlbumArtInDidl.
/// </summary>
[DefaultValue(false)]
public bool EnableAlbumArtInDidl { get; set; }
/// <summary>
/// Gets or sets a value indicating whether EnableSingleAlbumArtLimit.
/// </summary>
[DefaultValue(false)]
public bool EnableSingleAlbumArtLimit { get; set; }
/// <summary>
/// Gets or sets a value indicating whether EnableSingleSubtitleLimit.
/// </summary>
[DefaultValue(false)]
public bool EnableSingleSubtitleLimit { get; set; }
/// <summary>
/// Gets or sets the SupportedMediaTypes.
/// </summary>
public string SupportedMediaTypes { get; set; }
public string SupportedMediaTypes { get; set; } = "Audio,Photo,Video";
/// <summary>
/// Gets or sets the UserId.
/// </summary>
public string UserId { get; set; }
public string? UserId { get; set; }
/// <summary>
/// Gets or sets the AlbumArtPn.
/// </summary>
public string AlbumArtPn { get; set; }
public string? AlbumArtPn { get; set; }
/// <summary>
/// Gets or sets the MaxAlbumArtWidth.
/// </summary>
public int MaxAlbumArtWidth { get; set; }
public int? MaxAlbumArtWidth { get; set; }
/// <summary>
/// Gets or sets the MaxAlbumArtHeight.
/// </summary>
public int MaxAlbumArtHeight { get; set; }
public int? MaxAlbumArtHeight { get; set; }
/// <summary>
/// Gets or sets the MaxIconWidth.
@@ -142,92 +125,97 @@ namespace MediaBrowser.Model.Dlna
/// <summary>
/// Gets or sets the MaxStreamingBitrate.
/// </summary>
public int? MaxStreamingBitrate { get; set; }
public int? MaxStreamingBitrate { get; set; } = 8000000;
/// <summary>
/// Gets or sets the MaxStaticBitrate.
/// </summary>
public int? MaxStaticBitrate { get; set; }
public int? MaxStaticBitrate { get; set; } = 8000000;
/// <summary>
/// Gets or sets the MusicStreamingTranscodingBitrate.
/// </summary>
public int? MusicStreamingTranscodingBitrate { get; set; }
public int? MusicStreamingTranscodingBitrate { get; set; } = 128000;
/// <summary>
/// Gets or sets the MaxStaticMusicBitrate.
/// </summary>
public int? MaxStaticMusicBitrate { get; set; }
public int? MaxStaticMusicBitrate { get; set; } = 8000000;
/// <summary>
/// Gets or sets the content of the aggregationFlags element in the urn:schemas-sonycom:av namespace.
/// </summary>
public string SonyAggregationFlags { get; set; }
public string? SonyAggregationFlags { get; set; }
/// <summary>
/// Gets or sets the ProtocolInfo.
/// </summary>
public string ProtocolInfo { get; set; }
public string? ProtocolInfo { get; set; }
/// <summary>
/// Gets or sets the TimelineOffsetSeconds.
/// </summary>
[DefaultValue(0)]
public int TimelineOffsetSeconds { get; set; }
/// <summary>
/// Gets or sets a value indicating whether RequiresPlainVideoItems.
/// </summary>
[DefaultValue(false)]
public bool RequiresPlainVideoItems { get; set; }
/// <summary>
/// Gets or sets a value indicating whether RequiresPlainFolders.
/// </summary>
[DefaultValue(false)]
public bool RequiresPlainFolders { get; set; }
/// <summary>
/// Gets or sets a value indicating whether EnableMSMediaReceiverRegistrar.
/// </summary>
[DefaultValue(false)]
public bool EnableMSMediaReceiverRegistrar { get; set; }
/// <summary>
/// Gets or sets a value indicating whether IgnoreTranscodeByteRangeRequests.
/// </summary>
[DefaultValue(false)]
public bool IgnoreTranscodeByteRangeRequests { get; set; }
/// <summary>
/// Gets or sets the XmlRootAttributes.
/// </summary>
public XmlAttribute[] XmlRootAttributes { get; set; }
public XmlAttribute[] XmlRootAttributes { get; set; } = Array.Empty<XmlAttribute>();
/// <summary>
/// Gets or sets the direct play profiles.
/// </summary>
public DirectPlayProfile[] DirectPlayProfiles { get; set; }
public DirectPlayProfile[] DirectPlayProfiles { get; set; } = Array.Empty<DirectPlayProfile>();
/// <summary>
/// Gets or sets the transcoding profiles.
/// </summary>
public TranscodingProfile[] TranscodingProfiles { get; set; }
public TranscodingProfile[] TranscodingProfiles { get; set; } = Array.Empty<TranscodingProfile>();
/// <summary>
/// Gets or sets the ContainerProfiles.
/// </summary>
public ContainerProfile[] ContainerProfiles { get; set; }
public ContainerProfile[] ContainerProfiles { get; set; } = Array.Empty<ContainerProfile>();
/// <summary>
/// Gets or sets the CodecProfiles.
/// </summary>
public CodecProfile[] CodecProfiles { get; set; }
public CodecProfile[] CodecProfiles { get; set; } = Array.Empty<CodecProfile>();
/// <summary>
/// Gets or sets the ResponseProfiles.
/// </summary>
public ResponseProfile[] ResponseProfiles { get; set; }
public ResponseProfile[] ResponseProfiles { get; set; } = Array.Empty<ResponseProfile>();
/// <summary>
/// Gets or sets the SubtitleProfiles.
/// </summary>
public SubtitleProfile[] SubtitleProfiles { get; set; }
public SubtitleProfile[] SubtitleProfiles { get; set; } = Array.Empty<SubtitleProfile>();
/// <summary>
/// The GetSupportedMediaTypes.
@@ -244,13 +232,13 @@ namespace MediaBrowser.Model.Dlna
/// <param name="container">The container.</param>
/// <param name="audioCodec">The audio Codec.</param>
/// <returns>A <see cref="TranscodingProfile"/>.</returns>
public TranscodingProfile GetAudioTranscodingProfile(string container, string audioCodec)
public TranscodingProfile? GetAudioTranscodingProfile(string? container, string? audioCodec)
{
container = (container ?? string.Empty).TrimStart('.');
foreach (var i in TranscodingProfiles)
{
if (i.Type != MediaBrowser.Model.Dlna.DlnaProfileType.Audio)
if (i.Type != DlnaProfileType.Audio)
{
continue;
}
@@ -278,13 +266,13 @@ namespace MediaBrowser.Model.Dlna
/// <param name="audioCodec">The audio Codec.</param>
/// <param name="videoCodec">The video Codec.</param>
/// <returns>The <see cref="TranscodingProfile"/>.</returns>
public TranscodingProfile GetVideoTranscodingProfile(string container, string audioCodec, string videoCodec)
public TranscodingProfile? GetVideoTranscodingProfile(string? container, string? audioCodec, string? videoCodec)
{
container = (container ?? string.Empty).TrimStart('.');
foreach (var i in TranscodingProfiles)
{
if (i.Type != MediaBrowser.Model.Dlna.DlnaProfileType.Video)
if (i.Type != DlnaProfileType.Video)
{
continue;
}
@@ -299,7 +287,7 @@ namespace MediaBrowser.Model.Dlna
continue;
}
if (!string.Equals(videoCodec, i.VideoCodec ?? string.Empty, StringComparison.OrdinalIgnoreCase))
if (!string.Equals(videoCodec, i.VideoCodec, StringComparison.OrdinalIgnoreCase))
{
continue;
}
@@ -320,7 +308,7 @@ namespace MediaBrowser.Model.Dlna
/// <param name="audioSampleRate">The audio sample rate.</param>
/// <param name="audioBitDepth">The audio bit depth.</param>
/// <returns>The <see cref="ResponseProfile"/>.</returns>
public ResponseProfile GetAudioMediaProfile(string container, string audioCodec, int? audioChannels, int? audioBitrate, int? audioSampleRate, int? audioBitDepth)
public ResponseProfile? GetAudioMediaProfile(string container, string? audioCodec, int? audioChannels, int? audioBitrate, int? audioSampleRate, int? audioBitDepth)
{
foreach (var i in ResponseProfiles)
{
@@ -384,7 +372,7 @@ namespace MediaBrowser.Model.Dlna
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <returns>The <see cref="ResponseProfile"/>.</returns>
public ResponseProfile GetImageMediaProfile(string container, int? width, int? height)
public ResponseProfile? GetImageMediaProfile(string container, int? width, int? height)
{
foreach (var i in ResponseProfiles)
{
@@ -442,10 +430,10 @@ namespace MediaBrowser.Model.Dlna
/// <param name="videoCodecTag">The video Codec tag.</param>
/// <param name="isAvc">True if Avc.</param>
/// <returns>The <see cref="ResponseProfile"/>.</returns>
public ResponseProfile GetVideoMediaProfile(
public ResponseProfile? GetVideoMediaProfile(
string container,
string audioCodec,
string videoCodec,
string? audioCodec,
string? videoCodec,
int? width,
int? height,
int? bitDepth,

View File

@@ -1,6 +1,6 @@
#nullable disable
#pragma warning disable CS1591
using System.ComponentModel.DataAnnotations;
using System.Xml.Serialization;
namespace MediaBrowser.Model.Dlna
@@ -8,14 +8,15 @@ namespace MediaBrowser.Model.Dlna
public class DirectPlayProfile
{
[XmlAttribute("container")]
public string Container { get; set; }
public string? Container { get; set; }
[XmlAttribute("audioCodec")]
public string AudioCodec { get; set; }
public string? AudioCodec { get; set; }
[XmlAttribute("videoCodec")]
public string VideoCodec { get; set; }
public string? VideoCodec { get; set; }
[Required]
[XmlAttribute("type")]
public DlnaProfileType Type { get; set; }

View File

@@ -1,54 +1,69 @@
#nullable disable
#pragma warning disable CS1591
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Xml.Serialization;
namespace MediaBrowser.Model.Dlna
{
public class TranscodingProfile
{
[Required]
[XmlAttribute("container")]
public string Container { get; set; }
public string Container { get; set; } = string.Empty;
[Required]
[XmlAttribute("type")]
public DlnaProfileType Type { get; set; }
[Required]
[XmlAttribute("videoCodec")]
public string VideoCodec { get; set; }
public string VideoCodec { get; set; } = string.Empty;
[Required]
[XmlAttribute("audioCodec")]
public string AudioCodec { get; set; }
public string AudioCodec { get; set; } = string.Empty;
[Required]
[XmlAttribute("protocol")]
public string Protocol { get; set; }
public string Protocol { get; set; } = string.Empty;
[DefaultValue(false)]
[XmlAttribute("estimateContentLength")]
public bool EstimateContentLength { get; set; }
[DefaultValue(false)]
[XmlAttribute("enableMpegtsM2TsMode")]
public bool EnableMpegtsM2TsMode { get; set; }
[DefaultValue(TranscodeSeekInfo.Auto)]
[XmlAttribute("transcodeSeekInfo")]
public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
[DefaultValue(false)]
[XmlAttribute("copyTimestamps")]
public bool CopyTimestamps { get; set; }
[DefaultValue(EncodingContext.Streaming)]
[XmlAttribute("context")]
public EncodingContext Context { get; set; }
[DefaultValue(false)]
[XmlAttribute("enableSubtitlesInManifest")]
public bool EnableSubtitlesInManifest { get; set; }
[XmlAttribute("maxAudioChannels")]
public string MaxAudioChannels { get; set; }
public string? MaxAudioChannels { get; set; }
[DefaultValue(0)]
[XmlAttribute("minSegments")]
public int MinSegments { get; set; }
[DefaultValue(0)]
[XmlAttribute("segmentLength")]
public int SegmentLength { get; set; }
[DefaultValue(false)]
[XmlAttribute("breakOnNonKeyFrames")]
public bool BreakOnNonKeyFrames { get; set; }

View File

@@ -16,5 +16,7 @@ namespace MediaBrowser.Model.Dlna
public IPAddress LocalIpAddress { get; set; }
public int LocalPort { get; set; }
public IPAddress RemoteIpAddress { get; set; }
}
}

View File

@@ -8,7 +8,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Model</PackageId>
<VersionPrefix>10.7.2</VersionPrefix>
<VersionPrefix>10.7.3</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>

View File

@@ -95,16 +95,17 @@ namespace MediaBrowser.Model.Notifications
{
NotificationOption opt = GetOptions(notificationType);
return opt == null ||
!opt.DisabledServices.Contains(service, StringComparer.OrdinalIgnoreCase);
return opt == null
|| !opt.DisabledServices.Contains(service, StringComparer.OrdinalIgnoreCase);
}
public bool IsEnabledToMonitorUser(string type, Guid userId)
{
NotificationOption opt = GetOptions(type);
return opt != null && opt.Enabled &&
!opt.DisabledMonitorUsers.Contains(userId.ToString(""), StringComparer.OrdinalIgnoreCase);
return opt != null
&& opt.Enabled
&& !opt.DisabledMonitorUsers.Contains(userId.ToString("N"), StringComparer.OrdinalIgnoreCase);
}
public bool IsEnabledToSendToUser(string type, string userId, User user)

View File

@@ -201,7 +201,7 @@ namespace MediaBrowser.Providers.Manager
}
// Save to database
await SaveItemAsync(metadataResult, libraryOptions, updateType, cancellationToken).ConfigureAwait(false);
await SaveItemAsync(metadataResult, updateType, cancellationToken).ConfigureAwait(false);
}
await AfterMetadataRefresh(itemOfType, refreshOptions, cancellationToken).ConfigureAwait(false);
@@ -216,66 +216,18 @@ namespace MediaBrowser.Providers.Manager
lookupInfo.Year = result.ProductionYear;
}
protected async Task SaveItemAsync(MetadataResult<TItemType> result, LibraryOptions libraryOptions, ItemUpdateType reason, CancellationToken cancellationToken)
protected async Task SaveItemAsync(MetadataResult<TItemType> result, ItemUpdateType reason, CancellationToken cancellationToken)
{
if (result.Item.SupportsPeople && result.People != null)
{
var baseItem = result.Item;
LibraryManager.UpdatePeople(baseItem, result.People);
await SavePeopleMetadataAsync(result.People, libraryOptions, cancellationToken).ConfigureAwait(false);
await LibraryManager.UpdatePeopleAsync(baseItem, result.People, cancellationToken).ConfigureAwait(false);
}
await result.Item.UpdateToRepositoryAsync(reason, cancellationToken).ConfigureAwait(false);
}
private async Task SavePeopleMetadataAsync(List<PersonInfo> people, LibraryOptions libraryOptions, CancellationToken cancellationToken)
{
var personsToSave = new List<BaseItem>();
foreach (var person in people)
{
cancellationToken.ThrowIfCancellationRequested();
if (person.ProviderIds.Count > 0 || !string.IsNullOrWhiteSpace(person.ImageUrl))
{
var itemUpdateType = ItemUpdateType.MetadataDownload;
var saveEntity = false;
var personEntity = LibraryManager.GetPerson(person.Name);
foreach (var id in person.ProviderIds)
{
if (!string.Equals(personEntity.GetProviderId(id.Key), id.Value, StringComparison.OrdinalIgnoreCase))
{
personEntity.SetProviderId(id.Key, id.Value);
saveEntity = true;
}
}
if (!string.IsNullOrWhiteSpace(person.ImageUrl) && !personEntity.HasImage(ImageType.Primary))
{
personEntity.SetImage(
new ItemImageInfo
{
Path = person.ImageUrl,
Type = ImageType.Primary
},
0);
saveEntity = true;
itemUpdateType = ItemUpdateType.ImageUpdate;
}
if (saveEntity)
{
personsToSave.Add(personEntity);
await LibraryManager.RunMetadataSavers(personEntity, itemUpdateType).ConfigureAwait(false);
}
}
}
LibraryManager.CreateItems(personsToSave, null, CancellationToken.None);
}
protected virtual Task AfterMetadataRefresh(TItemType item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
{
item.AfterMetadataRefresh();

View File

@@ -49,37 +49,36 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
{
var person = (Person)item;
var personTmdbId = Convert.ToInt32(person.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
if (personTmdbId > 0)
if (!person.TryGetProviderId(MetadataProvider.Tmdb, out var personTmdbId))
{
var personResult = await _tmdbClientManager.GetPersonAsync(personTmdbId, cancellationToken).ConfigureAwait(false);
if (personResult?.Images?.Profiles == null)
{
return Enumerable.Empty<RemoteImageInfo>();
}
var remoteImages = new List<RemoteImageInfo>();
var language = item.GetPreferredMetadataLanguage();
for (var i = 0; i < personResult.Images.Profiles.Count; i++)
{
var image = personResult.Images.Profiles[i];
remoteImages.Add(new RemoteImageInfo
{
ProviderName = Name,
Type = ImageType.Primary,
Width = image.Width,
Height = image.Height,
Language = TmdbUtils.AdjustImageLanguage(image.Iso_639_1, language),
Url = _tmdbClientManager.GetProfileUrl(image.FilePath)
});
}
return remoteImages.OrderByLanguageDescending(language);
return Enumerable.Empty<RemoteImageInfo>();
}
return Enumerable.Empty<RemoteImageInfo>();
var personResult = await _tmdbClientManager.GetPersonAsync(int.Parse(personTmdbId, CultureInfo.InvariantCulture), cancellationToken).ConfigureAwait(false);
if (personResult?.Images?.Profiles == null)
{
return Enumerable.Empty<RemoteImageInfo>();
}
var remoteImages = new RemoteImageInfo[personResult.Images.Profiles.Count];
var language = item.GetPreferredMetadataLanguage();
for (var i = 0; i < personResult.Images.Profiles.Count; i++)
{
var image = personResult.Images.Profiles[i];
remoteImages[i] = new RemoteImageInfo
{
ProviderName = Name,
Type = ImageType.Primary,
Width = image.Width,
Height = image.Height,
Language = TmdbUtils.AdjustImageLanguage(image.Iso_639_1, language),
Url = _tmdbClientManager.GetProfileUrl(image.FilePath)
};
}
return remoteImages.OrderByLanguageDescending(language);
}
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)

View File

@@ -30,11 +30,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(PersonLookupInfo searchInfo, CancellationToken cancellationToken)
{
var personTmdbId = Convert.ToInt32(searchInfo.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
if (personTmdbId <= 0)
if (searchInfo.TryGetProviderId(MetadataProvider.Tmdb, out var personTmdbId))
{
var personResult = await _tmdbClientManager.GetPersonAsync(personTmdbId, cancellationToken).ConfigureAwait(false);
var personResult = await _tmdbClientManager.GetPersonAsync(int.Parse(personTmdbId, CultureInfo.InvariantCulture), cancellationToken).ConfigureAwait(false);
if (personResult != null)
{
@@ -51,19 +49,15 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
}
result.SetProviderId(MetadataProvider.Tmdb, personResult.Id.ToString(CultureInfo.InvariantCulture));
result.SetProviderId(MetadataProvider.Imdb, personResult.ExternalIds.ImdbId);
if (!string.IsNullOrEmpty(personResult.ExternalIds.ImdbId))
{
result.SetProviderId(MetadataProvider.Imdb, personResult.ExternalIds.ImdbId);
}
return new[] { result };
}
}
// TODO why? Because of the old rate limit?
if (searchInfo.IsAutomated)
{
// Don't hammer moviedb searching by name
return Enumerable.Empty<RemoteSearchResult>();
}
var personSearchResult = await _tmdbClientManager.SearchPersonAsync(searchInfo.Name, cancellationToken).ConfigureAwait(false);
var remoteSearchResults = new List<RemoteSearchResult>();

View File

@@ -121,9 +121,20 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
CommunityRating = Convert.ToSingle(episodeResult.VoteAverage)
};
if (!string.IsNullOrEmpty(episodeResult.ExternalIds?.TvdbId))
var externalIds = episodeResult.ExternalIds;
if (!string.IsNullOrEmpty(externalIds?.TvdbId))
{
item.SetProviderId(MetadataProvider.Tvdb, episodeResult.ExternalIds.TvdbId);
item.SetProviderId(MetadataProvider.Tvdb, externalIds.TvdbId);
}
if (!string.IsNullOrEmpty(externalIds?.ImdbId))
{
item.SetProviderId(MetadataProvider.Imdb, externalIds.ImdbId);
}
if (!string.IsNullOrEmpty(externalIds?.TvrageId))
{
item.SetProviderId(MetadataProvider.TvRage, externalIds.TvrageId);
}
if (episodeResult.Videos?.Results != null)

View File

@@ -8,7 +8,7 @@ namespace Rssdp
/// </summary>
public sealed class DeviceAvailableEventArgs : EventArgs
{
public IPAddress LocalIpAddress { get; set; }
public IPAddress RemoteIpAddress { get; set; }
private readonly DiscoveredSsdpDevice _DiscoveredDevice;

View File

@@ -208,7 +208,7 @@ namespace Rssdp.Infrastructure
/// Raises the <see cref="DeviceAvailable"/> event.
/// </summary>
/// <seealso cref="DeviceAvailable"/>
protected virtual void OnDeviceAvailable(DiscoveredSsdpDevice device, bool isNewDevice, IPAddress localIpAddress)
protected virtual void OnDeviceAvailable(DiscoveredSsdpDevice device, bool isNewDevice, IPAddress IpAddress)
{
if (this.IsDisposed)
{
@@ -220,7 +220,7 @@ namespace Rssdp.Infrastructure
{
handlers(this, new DeviceAvailableEventArgs(device, isNewDevice)
{
LocalIpAddress = localIpAddress
RemoteIpAddress = IpAddress
});
}
}
@@ -286,7 +286,7 @@ namespace Rssdp.Infrastructure
}
}
private void AddOrUpdateDiscoveredDevice(DiscoveredSsdpDevice device, IPAddress localIpAddress)
private void AddOrUpdateDiscoveredDevice(DiscoveredSsdpDevice device, IPAddress IpAddress)
{
bool isNewDevice = false;
lock (_Devices)
@@ -304,17 +304,17 @@ namespace Rssdp.Infrastructure
}
}
DeviceFound(device, isNewDevice, localIpAddress);
DeviceFound(device, isNewDevice, IpAddress);
}
private void DeviceFound(DiscoveredSsdpDevice device, bool isNewDevice, IPAddress localIpAddress)
private void DeviceFound(DiscoveredSsdpDevice device, bool isNewDevice, IPAddress IpAddress)
{
if (!NotificationTypeMatchesFilter(device))
{
return;
}
OnDeviceAvailable(device, isNewDevice, localIpAddress);
OnDeviceAvailable(device, isNewDevice, IpAddress);
}
private bool NotificationTypeMatchesFilter(DiscoveredSsdpDevice device)
@@ -347,7 +347,7 @@ namespace Rssdp.Infrastructure
return _CommunicationsServer.SendMulticastMessage(message, null, cancellationToken);
}
private void ProcessSearchResponseMessage(HttpResponseMessage message, IPAddress localIpAddress)
private void ProcessSearchResponseMessage(HttpResponseMessage message, IPAddress IpAddress)
{
if (!message.IsSuccessStatusCode)
{
@@ -367,11 +367,11 @@ namespace Rssdp.Infrastructure
ResponseHeaders = message.Headers
};
AddOrUpdateDiscoveredDevice(device, localIpAddress);
AddOrUpdateDiscoveredDevice(device, IpAddress);
}
}
private void ProcessNotificationMessage(HttpRequestMessage message, IPAddress localIpAddress)
private void ProcessNotificationMessage(HttpRequestMessage message, IPAddress IpAddress)
{
if (String.Compare(message.Method.Method, "Notify", StringComparison.OrdinalIgnoreCase) != 0)
{
@@ -381,7 +381,7 @@ namespace Rssdp.Infrastructure
var notificationType = GetFirstHeaderStringValue("NTS", message);
if (String.Compare(notificationType, SsdpConstants.SsdpKeepAliveNotification, StringComparison.OrdinalIgnoreCase) == 0)
{
ProcessAliveNotification(message, localIpAddress);
ProcessAliveNotification(message, IpAddress);
}
else if (String.Compare(notificationType, SsdpConstants.SsdpByeByeNotification, StringComparison.OrdinalIgnoreCase) == 0)
{
@@ -389,7 +389,7 @@ namespace Rssdp.Infrastructure
}
}
private void ProcessAliveNotification(HttpRequestMessage message, IPAddress localIpAddress)
private void ProcessAliveNotification(HttpRequestMessage message, IPAddress IpAddress)
{
var location = GetFirstHeaderUriValue("Location", message);
if (location != null)
@@ -404,7 +404,7 @@ namespace Rssdp.Infrastructure
ResponseHeaders = message.Headers
};
AddOrUpdateDiscoveredDevice(device, localIpAddress);
AddOrUpdateDiscoveredDevice(device, IpAddress);
}
}
@@ -630,7 +630,7 @@ namespace Rssdp.Infrastructure
private void CommsServer_RequestReceived(object sender, RequestReceivedEventArgs e)
{
ProcessNotificationMessage(e.Message, e.LocalIpAddress);
ProcessNotificationMessage(e.Message, e.ReceivedFrom.Address);
}
}
}

View File

@@ -1,4 +1,4 @@
using System.Reflection;
[assembly: AssemblyVersion("10.7.2")]
[assembly: AssemblyFileVersion("10.7.2")]
[assembly: AssemblyVersion("10.7.3")]
[assembly: AssemblyFileVersion("10.7.3")]

View File

@@ -1,7 +1,7 @@
---
# We just wrap `build` so this is really it
name: "jellyfin"
version: "10.7.2"
version: "10.7.3"
packages:
- debian.amd64
- debian.arm64

6
debian/changelog vendored
View File

@@ -1,3 +1,9 @@
jellyfin-server (10.7.3-1) unstable; urgency=medium
* New upstream version 10.7.3; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.7.3
-- Jellyfin Packaging Team <packaging@jellyfin.org> Tue, 04 May 2021 20:01:46 -0400
jellyfin-server (10.7.2-1) unstable; urgency=medium
* New upstream version 10.7.2; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.7.2

View File

@@ -5,7 +5,7 @@ Homepage: https://jellyfin.org
Standards-Version: 3.9.2
Package: jellyfin
Version: 10.7.2
Version: 10.7.3
Maintainer: Jellyfin Packaging Team <packaging@jellyfin.org>
Depends: jellyfin-server, jellyfin-web
Description: Provides the Jellyfin Free Software Media System

View File

@@ -7,7 +7,7 @@
%endif
Name: jellyfin
Version: 10.7.2
Version: 10.7.3
Release: 1%{?dist}
Summary: The Free Software Media System
License: GPLv3
@@ -137,6 +137,8 @@ fi
%systemd_postun_with_restart jellyfin.service
%changelog
* Tue May 04 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.7.3; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.7.3
* Sun Apr 11 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.7.2; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.7.2
* Sun Mar 21 2021 Jellyfin Packaging Team <packaging@jellyfin.org>

View File

@@ -0,0 +1,131 @@
using Emby.Dlna;
using Emby.Dlna.PlayTo;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;
namespace Jellyfin.Dlna.Tests
{
public class DlnaManagerTests
{
private DlnaManager GetManager()
{
var xmlSerializer = new Mock<IXmlSerializer>();
var fileSystem = new Mock<IFileSystem>();
var appPaths = new Mock<IApplicationPaths>();
var loggerFactory = new Mock<ILoggerFactory>();
var appHost = new Mock<IServerApplicationHost>();
return new DlnaManager(xmlSerializer.Object, fileSystem.Object, appPaths.Object, loggerFactory.Object, appHost.Object);
}
[Fact]
public void IsMatch_GivenMatchingName_ReturnsTrue()
{
var device = new DeviceInfo()
{
Name = "My Device",
Manufacturer = "LG Electronics",
ManufacturerUrl = "http://www.lge.com",
ModelDescription = "LG WebOSTV DMRplus",
ModelName = "LG TV",
ModelNumber = "1.0",
};
var profile = new DeviceProfile()
{
Name = "Test Profile",
FriendlyName = "My Device",
Manufacturer = "LG Electronics",
ManufacturerUrl = "http://www.lge.com",
ModelDescription = "LG WebOSTV DMRplus",
ModelName = "LG TV",
ModelNumber = "1.0",
Identification = new ()
{
FriendlyName = "My Device",
Manufacturer = "LG Electronics",
ManufacturerUrl = "http://www.lge.com",
ModelDescription = "LG WebOSTV DMRplus",
ModelName = "LG TV",
ModelNumber = "1.0",
}
};
var profile2 = new DeviceProfile()
{
Name = "Test Profile",
FriendlyName = "My Device",
Identification = new DeviceIdentification()
{
FriendlyName = "My Device",
}
};
var deviceMatch = GetManager().IsMatch(device.ToDeviceIdentification(), profile2.Identification);
var deviceMatch2 = GetManager().IsMatch(device.ToDeviceIdentification(), profile.Identification);
Assert.True(deviceMatch);
Assert.True(deviceMatch2);
}
[Fact]
public void IsMatch_GivenNamesAndManufacturersDoNotMatch_ReturnsFalse()
{
var device = new DeviceInfo()
{
Name = "My Device",
Manufacturer = "JVC"
};
var profile = new DeviceProfile()
{
Name = "Test Profile",
FriendlyName = "My Device",
Manufacturer = "LG Electronics",
ManufacturerUrl = "http://www.lge.com",
ModelDescription = "LG WebOSTV DMRplus",
ModelName = "LG TV",
ModelNumber = "1.0",
Identification = new ()
{
FriendlyName = "My Device",
Manufacturer = "LG Electronics",
ManufacturerUrl = "http://www.lge.com",
ModelDescription = "LG WebOSTV DMRplus",
ModelName = "LG TV",
ModelNumber = "1.0",
}
};
var deviceMatch = GetManager().IsMatch(device.ToDeviceIdentification(), profile.Identification);
Assert.False(deviceMatch);
}
[Fact]
public void IsMatch_GivenNamesAndRegExMatch_ReturnsTrue()
{
var device = new DeviceInfo()
{
Name = "My Device"
};
var profile = new DeviceProfile()
{
Name = "Test Profile",
FriendlyName = "My .*",
Identification = new ()
};
var deviceMatch = GetManager().IsMatch(device.ToDeviceIdentification(), profile.Identification);
Assert.True(deviceMatch);
}
}
}

View File

@@ -9,6 +9,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="coverlet.collector" Version="1.3.0" />