Compare commits

...

51 Commits

Author SHA1 Message Date
Joshua M. Boniface
884a59da07 Merge pull request #7724 from jtcasper/perms 2022-05-15 20:26:00 -04:00
Joshua M. Boniface
5a9e5e0d5d Merge pull request #7712 from jellyfin/fix-hevc-disable-option 2022-05-15 20:25:01 -04:00
Joshua M. Boniface
85cfea4c50 Merge branch 'release-10.8.z' into fix-hevc-disable-option 2022-05-15 20:24:52 -04:00
Joshua M. Boniface
3ea67374ae Merge pull request #7741 from LewkyB/fix-improperly-labeled-four-digit-episode-numbering
Fix to allow for episode numbering over 999 in certain scenarios
2022-05-15 20:23:59 -04:00
Joshua M. Boniface
5da4bcc782 Merge pull request #7736 from jellyfin/fix-swscale-pgs 2022-05-15 20:22:24 -04:00
Joshua M. Boniface
b46d61dfdf Merge pull request #7699 from Shadowghost/streambuilder-fix 2022-05-15 20:22:13 -04:00
Joshua M. Boniface
8119e4a573 Merge pull request #7749 from cvium/disable_auto_add_collection 2022-05-15 20:21:40 -04:00
Joshua M. Boniface
de3c68d474 Bump version to 10.8.0-beta3 2022-05-15 20:16:25 -04:00
cvium
7aa0db24d8 fix: disable "Automatically add to collection" by default 2022-05-15 14:53:40 +02:00
Luke Brown
be832e82cc change capture groups to take up to 4 digits and adding inline data to test 2022-05-11 22:22:52 -05:00
nyanmisaka
368d10d042 Fix the mismatched resolution in sw PGS burn-in 2022-05-11 19:18:31 +08:00
Nyanmisaka
9523a1682b Apply suggestions from code review
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2022-05-11 16:38:30 +08:00
Jacob Casper
e67d8ce077 Don't let permission denied kill library scans
I have a single `./Movies` directory that contains symlinks to many
other directories, some of which are in shared paths. Other daemons
sometimes overwrite permissions on these paths and prevent Jellyfin from
being able to scan. Because this exception is unhandled within a LINQ
statement it can randomly kill the metadata refresh for my entire Movies
library.

Running with this patch has allowed me to at least add + scan for
movies in directories that Jellyfin can currently access and gives me
some notification of the symlinks failing due to permissions errors.

(cherry picked from commit 193cc8690a60bc2a432df14d256f00371e055c90)
2022-05-09 10:49:52 -05:00
Cody Robibero
39196bb5e2 Merge pull request #7723 from crobibero/tmdb-lib 2022-05-09 07:32:36 -06:00
Nyanmisaka
5386f06095 Apply suggestions from code review
Co-authored-by: Cody Robibero <cody@robibe.ro>
2022-05-09 17:09:03 +08:00
Cody Robibero
5a9afb0874 Merge pull request #7716 from Shadowghost/opus-fix
Respect limited opus sampling rates when building trancoding command
2022-05-08 15:23:58 -06:00
Shadowghost
029be321d1 Respect limited opus sampling rates when building trancoding command 2022-05-08 22:28:45 +02:00
Cody Robibero
96b7c46df4 Update TMDbLib to 1.9.2 2022-05-08 13:28:57 -06:00
Bond-009
f7ef7d9eda Merge pull request #7718 from jellyfin/dovi-hevc-remux 2022-05-08 21:06:03 +02:00
Nyanmisaka
c69e79f64d Fix the disordered color in Dolby Vision remuxing on Safari 2022-05-07 22:43:32 +08:00
nyanmisaka
4b1256e67b Fix the issue that HEVC transcoding can't be disabled 2022-05-06 02:27:16 +08:00
Bond-009
8d1d973438 Merge pull request #7604 from Jellifi007/fixes-diactritics
Co-authored-by: Cody Robibero <cody@robibe.ro>
2022-05-05 19:59:17 +02:00
Cody Robibero
60affd0965 Merge pull request #7529 from Shadowghost/strm-ffprobe-external-fix 2022-05-04 08:20:48 -06:00
Cody Robibero
8a1eca0913 Merge pull request #7544 from jaantaponen/long-filename-fix 2022-05-04 08:19:43 -06:00
Shadowghost
a4e4b761d5 Apply review suggestions 2022-05-04 16:13:06 +02:00
Shadowghost
2be9a34b26 Fix tests and profiles 2022-05-03 22:22:46 +02:00
Shadowghost
0c8b9091a5 Fix streambuilder reasons for direct playback checks 2022-05-03 15:48:46 +02:00
Shadowghost
128d54622a Fix stream index and subtitle container handling, preserve attachments and nonexternal streams between scans 2022-05-03 11:10:58 +02:00
Shadowghost
21ce0e58c6 Properly handle stream addition and removal for strm use cases 2022-05-03 11:10:58 +02:00
Cody Robibero
3229ba4918 Merge pull request #7693 from crobibero/auth-migrate 2022-05-02 07:41:13 -06:00
Cody Robibero
105f057512 Don't migrate auth token if user doesn't exist 2022-05-01 17:55:20 -06:00
Jaan Taponen
b6a2640f22 Update Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
Co-authored-by: Joe Rogers <1337joe@users.noreply.github.com>
2022-04-30 22:24:11 +03:00
Cody Robibero
a2abae3014 Merge pull request #7654 from Shadowghost/nfo-provider-fix
Prefer MetadataProvider enum as provider id key over arbitrary strings
2022-04-27 18:16:31 -06:00
Shadowghost
7084541508 Prefer MetadataProvider enum as provider id key over arbitrary strings 2022-04-24 21:27:17 +02:00
Joshua M. Boniface
e87240b374 Merge pull request #7648 from jellyfin/libssl3-jammy
Add libssl3 as an alternative dependency for Ubuntu 22.04 LTS
2022-04-23 10:53:25 -04:00
Nyanmisaka
057d5dfc25 Add libssl3 as an alternative dependency for Ubuntu 22.04 LTS 2022-04-23 18:53:59 +08:00
Cody Robibero
12f9132975 Merge pull request #7643 from jellyfin/h264-level 2022-04-22 16:40:53 -06:00
Nyanmisaka
4a1aa619d2 Fix H264 level on safari fmp4 2022-04-22 17:36:36 +08:00
Jellifi007
1002d1d193 Update src/Jellyfin.Extensions/StringExtensions.cs
Co-authored-by: Cody Robibero <cody@robibe.ro>
2022-04-22 09:01:19 +02:00
Cody Robibero
7c91543694 Merge pull request #7638 from 1337joe/fix-quick-connect-tests 2022-04-21 17:48:29 -06:00
Joe Rogers
b04211a707 Fix quick connect tests 2022-04-21 23:24:45 +02:00
Cody Robibero
fcb65ac38d Merge pull request #7634 from neilsb/patch-1
Correct LocalTrailerCount in API
2022-04-21 14:26:17 -06:00
Jellifi007
727402f7f7 Adding Korean language tests Addresses #6393 2022-04-21 22:17:25 +02:00
Neil Burrows
2a1ca4badc Use already defined variable
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2022-04-21 11:11:25 +01:00
Neil Burrows
7f52f77ef5 Correct LocalTrailerCount in API
Revert a change which resulted in the `LocalTrailerCount` including the count of Local and Remote trailers which had been introduced in 10.8.
2022-04-21 11:03:33 +01:00
Cody Robibero
1d5961126e Merge pull request #7624 from nielsvanvelzen/quickconnect-enable-d 2022-04-20 17:37:53 -06:00
Cody Robibero
ec6f7bdcff Merge pull request #7625 from crobibero/xml-cache-delete 2022-04-20 17:37:44 -06:00
Cody Robibero
65c47c79da Only delete cache file if exist 2022-04-20 17:37:26 -06:00
Niels van Velzen
5d9df10e27 Enable Quick Connect by default 2022-04-19 21:48:11 +02:00
JaanTaponen
2b7d139b5b Fix XMLTV edge case where title & sub-title causes a too long filename error
Limit the series title to 241 bytes (so file extensions can be added).
If the end result is longer, the title will be dropped and only series name
with timestamp is used.
2022-04-17 16:46:01 +03:00
Jellifi007
56573f14b0 Fixes diactritics regressions 2022-04-15 20:14:13 +02:00
44 changed files with 428 additions and 139 deletions

View File

@@ -6,8 +6,8 @@ using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using Diacritics.Extensions;
using Emby.Dlna.Didl;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Configuration;
using Microsoft.Extensions.Logging;

View File

@@ -314,7 +314,7 @@ namespace Emby.Naming.Common
// This isn't a Kodi naming rule, but the expression below causes false positives,
// so we make sure this one gets tested first.
// "Foo Bar 889"
new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?<seriesname>[\w\s]+?)\s(?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*[^\\\/x]*$")
new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?<seriesname>[\w\s]+?)\s(?<epnumber>[0-9]{1,4})(-(?<endingepnumber>[0-9]{2,4}))*[^\\\/x]*$")
{
IsNamed = true
},

View File

@@ -11,7 +11,6 @@ using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading;
using Diacritics.Extensions;
using Emby.Server.Implementations.Playlists;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
@@ -5763,7 +5762,7 @@ AND Type = @InternalPersonType)");
{
var itemIdBlob = id.ToByteArray();
// First delete chapters
// Delete existing mediastreams
db.Execute("delete from mediastreams where ItemId=@ItemId", itemIdBlob);
InsertMediaStreams(itemIdBlob, streams, db);
@@ -5867,10 +5866,10 @@ AND Type = @InternalPersonType)");
}
/// <summary>
/// Gets the chapter.
/// Gets the media stream.
/// </summary>
/// <param name="reader">The reader.</param>
/// <returns>ChapterInfo.</returns>
/// <returns>MediaStream.</returns>
private MediaStream GetMediaStream(IReadOnlyList<ResultSetValue> reader)
{
var item = new MediaStream

View File

@@ -1094,7 +1094,7 @@ namespace Emby.Server.Implementations.Dto
{
if (item is IHasTrailers hasTrailers)
{
dto.LocalTrailerCount = hasTrailers.GetTrailerCount();
dto.LocalTrailerCount = hasTrailers.LocalTrailers.Count;
}
else
{

View File

@@ -262,6 +262,10 @@ namespace Emby.Server.Implementations.IO
_logger.LogError(ex, "Reading the file size of the symlink at {Path} failed. Marking the file as not existing.", fileInfo.FullName);
result.Exists = false;
}
catch (UnauthorizedAccessException ex)
{
_logger.LogError(ex, "Reading the file at {Path} failed due to a permissions exception.", fileInfo.FullName);
}
}
}

View File

@@ -151,7 +151,11 @@ namespace Emby.Server.Implementations.Library
{
var mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user);
if (allowMediaProbe && mediaSources[0].Type != MediaSourceType.Placeholder && !mediaSources[0].MediaStreams.Any(i => i.Type == MediaStreamType.Audio || i.Type == MediaStreamType.Video))
// If file is strm or main media stream is missing, force a metadata refresh with remote probing
if (allowMediaProbe && mediaSources[0].Type != MediaSourceType.Placeholder
&& (item.Path.EndsWith(".strm", StringComparison.OrdinalIgnoreCase)
|| (item.MediaType == MediaType.Video && !mediaSources[0].MediaStreams.Any(i => i.Type == MediaStreamType.Video))
|| (item.MediaType == MediaType.Audio && !mediaSources[0].MediaStreams.Any(i => i.Type == MediaStreamType.Audio))))
{
await item.RefreshMetadata(
new MetadataRefreshOptions(_directoryService)

View File

@@ -5,9 +5,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Diacritics.Extensions;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;

View File

@@ -3,6 +3,7 @@
using System;
using System.Globalization;
using MediaBrowser.Controller.LiveTv;
using System.Text;
namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
@@ -48,12 +49,18 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (!string.IsNullOrWhiteSpace(info.EpisodeTitle))
{
var tmpName = name;
if (addHyphen)
{
name += " -";
tmpName += " -";
}
name += " " + info.EpisodeTitle;
tmpName += " " + info.EpisodeTitle;
// Since the filename will be used with file ext. (.mp4, .ts, etc)
if (Encoding.UTF8.GetByteCount(tmpName) < 250)
{
name = tmpName;
}
}
}
else if (info.IsMovie && info.ProductionYear != null)

View File

@@ -79,7 +79,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return UnzipIfNeeded(info.Path, cacheFile);
}
File.Delete(cacheFile);
// Must check if file exists as parent directory may not exist.
if (File.Exists(cacheFile))
{
File.Delete(cacheFile);
}
_logger.LogInformation("Downloading xmltv listings from {Path}", info.Path);
Directory.CreateDirectory(Path.GetDirectoryName(cacheFile));

View File

@@ -1773,13 +1773,23 @@ namespace Jellyfin.Api.Controllers
var args = "-codec:v:0 " + codec;
// Prefer hvc1 to hev1.
if (string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
{
args += " -tag:v:0 hvc1";
if (EncodingHelper.IsCopyCodec(codec)
&& (string.Equals(state.VideoStream.CodecTag, "dvh1", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.VideoStream.CodecTag, "dvhe", StringComparison.OrdinalIgnoreCase)))
{
// Prefer dvh1 to dvhe
args += " -tag:v:0 dvh1";
}
else
{
// Prefer hvc1 to hev1
args += " -tag:v:0 hvc1";
}
}
// if (state.EnableMpegtsM2TsMode)

View File

@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using BlurHashSharp.SkiaSharp;
using Diacritics.Extensions;
using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Drawing;

View File

@@ -5,6 +5,7 @@ using Emby.Server.Implementations.Data;
using Jellyfin.Data.Entities.Security;
using Jellyfin.Server.Implementations;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Library;
using Microsoft.Extensions.Logging;
using SQLitePCL.pretty;
@@ -20,6 +21,7 @@ namespace Jellyfin.Server.Migrations.Routines
private readonly ILogger<MigrateAuthenticationDb> _logger;
private readonly JellyfinDbProvider _dbProvider;
private readonly IServerApplicationPaths _appPaths;
private readonly IUserManager _userManager;
/// <summary>
/// Initializes a new instance of the <see cref="MigrateAuthenticationDb"/> class.
@@ -27,11 +29,17 @@ namespace Jellyfin.Server.Migrations.Routines
/// <param name="logger">The logger.</param>
/// <param name="dbProvider">The database provider.</param>
/// <param name="appPaths">The server application paths.</param>
public MigrateAuthenticationDb(ILogger<MigrateAuthenticationDb> logger, JellyfinDbProvider dbProvider, IServerApplicationPaths appPaths)
/// <param name="userManager">The user manager.</param>
public MigrateAuthenticationDb(
ILogger<MigrateAuthenticationDb> logger,
JellyfinDbProvider dbProvider,
IServerApplicationPaths appPaths,
IUserManager userManager)
{
_logger = logger;
_dbProvider = dbProvider;
_appPaths = appPaths;
_userManager = userManager;
}
/// <inheritdoc />
@@ -74,6 +82,14 @@ namespace Jellyfin.Server.Migrations.Routines
}
else
{
var userId = new Guid(row[6].ToString());
var user = _userManager.GetUserById(userId);
if (user is null)
{
// User doesn't exist, don't bring over the device.
continue;
}
dbContext.Devices.Add(new Device(
new Guid(row[6].ToString()),
row[3].ToString(),

View File

@@ -8,9 +8,9 @@ using System.Linq;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Diacritics.Extensions;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using Microsoft.Extensions.Logging;

View File

@@ -5,8 +5,8 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Diacritics.Extensions;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Entities.Audio

View File

@@ -11,7 +11,6 @@ using System.Text;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Diacritics.Extensions;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;

View File

@@ -5,8 +5,8 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Diacritics.Extensions;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Entities

View File

@@ -5,7 +5,7 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Diacritics.Extensions;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Providers;
using Microsoft.Extensions.Logging;

View File

@@ -5,7 +5,7 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Diacritics.Extensions;
using Jellyfin.Extensions;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Entities

View File

@@ -3,7 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Diacritics.Extensions;
using Jellyfin.Extensions;
namespace MediaBrowser.Controller.Library
{

View File

@@ -18,7 +18,6 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Diacritics" Version="3.3.10" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />

View File

@@ -1062,10 +1062,12 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (string.Equals(state.ActualOutputVideoCodec, "h264", StringComparison.OrdinalIgnoreCase))
{
// Clients may direct play higher than level 41, but there's no reason to transcode higher.
if (requestLevel >= 41)
// Transcode to level 5.1 and lower for maximum compatibility.
// h264 4k 30fps requires at least level 5.1 otherwise it will break on safari fmp4.
// https://en.wikipedia.org/wiki/Advanced_Video_Coding#Levels
if (requestLevel >= 51)
{
return "41";
return "51";
}
}
}
@@ -2213,13 +2215,13 @@ namespace MediaBrowser.Controller.MediaEncoding
return state.IsInputVideo ? "-sn" : string.Empty;
}
// We have media info, but we don't know the stream indexes
// We have media info, but we don't know the stream index
if (state.VideoStream != null && state.VideoStream.Index == -1)
{
return "-sn";
}
// We have media info, but we don't know the stream indexes
// We have media info, but we don't know the stream index
if (state.AudioStream != null && state.AudioStream.Index == -1)
{
return state.IsInputVideo ? "-sn" : string.Empty;
@@ -2229,10 +2231,12 @@ namespace MediaBrowser.Controller.MediaEncoding
if (state.VideoStream != null)
{
int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
args += string.Format(
CultureInfo.InvariantCulture,
"-map 0:{0}",
state.VideoStream.Index);
videoStreamIndex);
}
else
{
@@ -2242,24 +2246,24 @@ namespace MediaBrowser.Controller.MediaEncoding
if (state.AudioStream != null)
{
int audioStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.AudioStream);
if (state.AudioStream.IsExternal)
{
bool hasExternalGraphicsSubs = state.SubtitleStream != null && state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream;
int externalAudioMapIndex = hasExternalGraphicsSubs ? 2 : 1;
int externalAudioStream = state.MediaSource.MediaStreams.Where(i => i.Path == state.AudioStream.Path).ToList().IndexOf(state.AudioStream);
args += string.Format(
CultureInfo.InvariantCulture,
" -map {0}:{1}",
externalAudioMapIndex,
externalAudioStream);
audioStreamIndex);
}
else
{
args += string.Format(
CultureInfo.InvariantCulture,
" -map 0:{0}",
state.AudioStream.Index);
audioStreamIndex);
}
}
else
@@ -2274,14 +2278,21 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (subtitleMethod == SubtitleDeliveryMethod.Embed)
{
int subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
args += string.Format(
CultureInfo.InvariantCulture,
" -map 0:{0}",
state.SubtitleStream.Index);
subtitleStreamIndex);
}
else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
{
args += " -map 1:0 -sn";
int externalSubtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
args += string.Format(
CultureInfo.InvariantCulture,
" -map 1:{0} -sn",
externalSubtitleStreamIndex);
}
return args;
@@ -2510,7 +2521,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Format(
CultureInfo.InvariantCulture,
"scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/{2})*{2}:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2",
"scale=trunc(min(max(iw\\,ih*a)\\,min({0}\\,{1}*a))/{2})*{2}:trunc(min(max(iw/a\\,ih)\\,min({0}/a\\,{1}))/2)*2",
maxWidthParam,
maxHeightParam,
scaleVal);
@@ -2554,7 +2565,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Format(
CultureInfo.InvariantCulture,
"scale=trunc(min(max(iw\\,ih*dar)\\,{0})/{1})*{1}:trunc(ow/dar/2)*2",
"scale=trunc(min(max(iw\\,ih*a)\\,{0})/{1})*{1}:trunc(ow/a/2)*2",
maxWidthParam,
scaleVal);
}
@@ -2566,7 +2577,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Format(
CultureInfo.InvariantCulture,
"scale=trunc(oh*a/{1})*{1}:min(max(iw/dar\\,ih)\\,{0})",
"scale=trunc(oh*a/{1})*{1}:min(max(iw/a\\,ih)\\,{0})",
maxHeightParam,
scaleVal);
}
@@ -2615,7 +2626,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else
{
filter = "scale={0}:trunc({0}/dar/2)*2";
filter = "scale={0}:trunc({0}/a/2)*2";
}
}
@@ -2769,8 +2780,8 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (hasGraphicalSubs)
{
// [0:s]scale=s=1280x720
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
// [0:s]scale=expr
var subSwScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
}
@@ -2956,7 +2967,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
var subSwScaleFilter = isSwDecoder
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
}
@@ -3154,7 +3167,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
var subSwScaleFilter = isSwDecoder
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
}
@@ -3400,7 +3415,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
var subSwScaleFilter = isSwDecoder
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
}
@@ -3609,7 +3626,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
var subSwScaleFilter = isSwDecoder
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
}
@@ -3856,7 +3875,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
var subSwScaleFilter = isSwDecoder
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
@@ -4031,7 +4052,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
var subSwScaleFilter = isSwDecoder
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
@@ -4127,9 +4150,8 @@ namespace MediaBrowser.Controller.MediaEncoding
string.Join(',', overlayFilters));
var mapPrefix = Convert.ToInt32(state.SubtitleStream.IsExternal);
var subtitleStreamIndex = state.SubtitleStream.IsExternal
? 0
: state.SubtitleStream.Index;
var subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
var videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
if (hasSubs)
{
@@ -4150,7 +4172,7 @@ namespace MediaBrowser.Controller.MediaEncoding
filterStr,
mapPrefix,
subtitleStreamIndex,
state.VideoStream.Index,
videoStreamIndex,
mainStr,
subStr,
overlayStr);
@@ -5360,12 +5382,22 @@ namespace MediaBrowser.Controller.MediaEncoding
audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture));
}
// opus will fail on 44100
if (!string.Equals(state.OutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase))
{
if (state.OutputAudioSampleRate.HasValue)
// opus only supports specific sampling rates
var sampleRate = state.OutputAudioSampleRate;
if (sampleRate.HasValue)
{
audioTranscodeParams.Add("-ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture));
var sampleRateValue = sampleRate.Value switch
{
<= 8000 => 8000,
<= 12000 => 12000,
<= 16000 => 16000,
<= 24000 => 24000,
_ => 48000
};
audioTranscodeParams.Add("-ar " + sampleRateValue.ToString(CultureInfo.InvariantCulture));
}
}
@@ -5387,6 +5419,28 @@ namespace MediaBrowser.Controller.MediaEncoding
string.Empty).Trim();
}
public static int FindIndex(IReadOnlyList<MediaStream> mediaStreams, MediaStream streamToFind)
{
var index = 0;
var length = mediaStreams.Count;
for (var i = 0; i < length; i++)
{
var currentMediaStream = mediaStreams[i];
if (currentMediaStream == streamToFind)
{
return index;
}
if (string.Equals(currentMediaStream.Path, streamToFind.Path, StringComparison.Ordinal))
{
index++;
}
}
return -1;
}
public static bool IsCopyCodec(string codec)
{
return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase);

View File

@@ -141,6 +141,13 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <returns>System.String.</returns>
string GetInputArgument(string inputFile, MediaSourceInfo mediaSource);
/// <summary>
/// Gets the input argument for an external subtitle file.
/// </summary>
/// <param name="inputFile">The input file.</param>
/// <returns>System.String.</returns>
string GetExternalSubtitleInputArgument(string inputFile);
/// <summary>
/// Gets the time parameter.
/// </summary>

View File

@@ -411,6 +411,19 @@ namespace MediaBrowser.MediaEncoding.Encoder
return EncodingUtils.GetInputArgument(prefix, inputFile, mediaSource.Protocol);
}
/// <summary>
/// Gets the input argument for an external subtitle file.
/// </summary>
/// <param name="inputFile">The input file.</param>
/// <returns>System.String.</returns>
/// <exception cref="ArgumentException">Unrecognized InputType.</exception>
public string GetExternalSubtitleInputArgument(string inputFile)
{
const string Prefix = "file";
return EncodingUtils.GetInputArgument(Prefix, inputFile, MediaProtocol.File);
}
/// <summary>
/// Gets the media info internal.
/// </summary>

View File

@@ -195,7 +195,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
MediaStream subtitleStream,
CancellationToken cancellationToken)
{
if (!subtitleStream.IsExternal)
if (!subtitleStream.IsExternal || subtitleStream.Path.EndsWith(".mks", StringComparison.OrdinalIgnoreCase))
{
string outputFormat;
string outputCodec;
@@ -224,7 +224,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
// Extract
var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, "." + outputFormat);
await ExtractTextSubtitle(mediaSource, subtitleStream.Index, outputCodec, outputPath, cancellationToken)
await ExtractTextSubtitle(mediaSource, subtitleStream, outputCodec, outputPath, cancellationToken)
.ConfigureAwait(false);
return new SubtitleInfo(outputPath, MediaProtocol.File, outputFormat, false);
@@ -494,7 +494,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// Extracts the text subtitle.
/// </summary>
/// <param name="mediaSource">The mediaSource.</param>
/// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
/// <param name="subtitleStream">The subtitle stream.</param>
/// <param name="outputCodec">The output codec.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="cancellationToken">The cancellation token.</param>
@@ -502,7 +502,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// <exception cref="ArgumentException">Must use inputPath list overload.</exception>
private async Task ExtractTextSubtitle(
MediaSourceInfo mediaSource,
int subtitleStreamIndex,
MediaStream subtitleStream,
string outputCodec,
string outputPath,
CancellationToken cancellationToken)
@@ -511,12 +511,21 @@ namespace MediaBrowser.MediaEncoding.Subtitles
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
var subtitleStreamIndex = EncodingHelper.FindIndex(mediaSource.MediaStreams, subtitleStream);
try
{
if (!File.Exists(outputPath))
{
var args = _mediaEncoder.GetInputArgument(mediaSource.Path, mediaSource);
if (subtitleStream.IsExternal)
{
args = _mediaEncoder.GetExternalSubtitleInputArgument(subtitleStream.Path);
}
await ExtractTextSubtitleInternal(
_mediaEncoder.GetInputArgument(mediaSource.Path, mediaSource),
args,
subtitleStreamIndex,
outputCodec,
outputPath,

View File

@@ -17,7 +17,7 @@ namespace MediaBrowser.Model.Configuration
RequirePerfectSubtitleMatch = true;
AllowEmbeddedSubtitles = EmbeddedSubtitleOptions.AllowAll;
AutomaticallyAddToCollection = true;
AutomaticallyAddToCollection = false;
EnablePhotos = true;
SaveSubtitlesWithMedia = true;
EnableRealtimeMonitor = true;

View File

@@ -79,7 +79,7 @@ namespace MediaBrowser.Model.Configuration
/// <summary>
/// Gets or sets a value indicating whether quick connect is available for use on this server.
/// </summary>
public bool QuickConnectAvailable { get; set; } = false;
public bool QuickConnectAvailable { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether [enable case sensitive item ids].

View File

@@ -385,7 +385,7 @@ namespace MediaBrowser.Model.Dlna
// If device requirements are satisfied then allow both direct stream and direct play
if (item.SupportsDirectPlay)
{
if (IsItemBitrateEligibleForDirectPlay(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectPlay))
if (IsItemBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectPlay))
{
if (options.EnableDirectPlay)
{
@@ -401,7 +401,7 @@ namespace MediaBrowser.Model.Dlna
// While options takes the network and other factors into account. Only applies to direct stream
if (item.SupportsDirectStream)
{
if (IsItemBitrateEligibleForDirectPlay(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectStream))
if (IsItemBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectStream))
{
if (options.EnableDirectStream)
{
@@ -604,11 +604,11 @@ namespace MediaBrowser.Model.Dlna
var videoStream = item.VideoStream;
var directPlayEligibilityResult = IsEligibleForDirectPlay(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectPlay);
var directStreamEligibilityResult = IsEligibleForDirectPlay(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectStream);
bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayEligibilityResult == 0);
bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directPlayEligibilityResult == 0);
var transcodeReasons = directPlayEligibilityResult | directStreamEligibilityResult;
var directPlayBitrateEligibility = IsBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectPlay);
var directStreamBitrateEligibility = IsBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectStream);
bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayBitrateEligibility == 0);
bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directStreamBitrateEligibility == 0);
var transcodeReasons = directPlayBitrateEligibility | directStreamBitrateEligibility;
_logger.LogDebug(
"Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}",
@@ -625,7 +625,7 @@ namespace MediaBrowser.Model.Dlna
var directPlay = directPlayInfo.PlayMethod;
transcodeReasons |= directPlayInfo.TranscodeReasons;
if (directPlay != null)
if (directPlay.HasValue)
{
directPlayProfile = directPlayInfo.Profile;
playlistItem.PlayMethod = directPlay.Value;
@@ -676,7 +676,7 @@ namespace MediaBrowser.Model.Dlna
playlistItem.TranscodeReasons = transcodeReasons;
if (playlistItem.PlayMethod != PlayMethod.DirectStream || !options.EnableDirectStream)
if (playlistItem.PlayMethod != PlayMethod.DirectStream && playlistItem.PlayMethod != PlayMethod.DirectPlay)
{
// Can't direct play, find the transcoding profile
// If we do this for direct-stream we will overwrite the info
@@ -687,6 +687,8 @@ namespace MediaBrowser.Model.Dlna
BuildStreamVideoItem(playlistItem, options, item, videoStream, audioStream, candidateAudioStreams, transcodingProfile.Container, transcodingProfile.VideoCodec, transcodingProfile.AudioCodec);
playlistItem.PlayMethod = PlayMethod.Transcode;
if (subtitleStream != null)
{
var subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles, PlayMethod.Transcode, _transcoderSupport, transcodingProfile.Container, transcodingProfile.Protocol);
@@ -696,14 +698,9 @@ namespace MediaBrowser.Model.Dlna
playlistItem.SubtitleCodecs = new[] { subtitleProfile.Format };
}
if (playlistItem.PlayMethod != PlayMethod.DirectPlay)
if ((playlistItem.TranscodeReasons & (VideoReasons | TranscodeReason.ContainerBitrateExceedsLimit)) != 0)
{
playlistItem.PlayMethod = PlayMethod.Transcode;
if ((playlistItem.TranscodeReasons & (VideoReasons | TranscodeReason.ContainerBitrateExceedsLimit)) != 0)
{
ApplyTranscodingConditions(playlistItem, transcodingProfile.Conditions, null, true, true);
}
ApplyTranscodingConditions(playlistItem, transcodingProfile.Conditions, null, true, true);
}
}
}
@@ -771,12 +768,19 @@ namespace MediaBrowser.Model.Dlna
private void BuildStreamVideoItem(StreamInfo playlistItem, VideoOptions options, MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream, IEnumerable<MediaStream> candidateAudioStreams, string container, string videoCodec, string audioCodec)
{
// prefer matching video codecs
// Prefer matching video codecs
var videoCodecs = ContainerProfile.SplitValue(videoCodec);
var directVideoCodec = ContainerProfile.ContainsContainer(videoCodecs, videoStream?.Codec) ? videoStream?.Codec : null;
playlistItem.VideoCodecs = directVideoCodec != null ? new[] { directVideoCodec } : videoCodecs;
if (directVideoCodec != null)
{
// merge directVideoCodec to videoCodecs
Array.Resize(ref videoCodecs, videoCodecs.Length + 1);
videoCodecs[^1] = directVideoCodec;
}
// copy video codec options as a starting point, this applies to transcode and direct-stream
playlistItem.VideoCodecs = videoCodecs;
// Copy video codec options as a starting point, this applies to transcode and direct-stream
playlistItem.MaxFramerate = videoStream?.AverageFrameRate;
var qualifier = videoStream?.Codec;
if (videoStream?.Level != null)
@@ -799,7 +803,7 @@ namespace MediaBrowser.Model.Dlna
playlistItem.SetOption(qualifier, "level", videoStream.Level.ToString());
}
// prefer matching audio codecs, could do better here
// Prefer matching audio codecs, could do better here
var audioCodecs = ContainerProfile.SplitValue(audioCodec);
var directAudioStream = candidateAudioStreams.FirstOrDefault(stream => ContainerProfile.ContainsContainer(audioCodecs, stream.Codec));
playlistItem.AudioCodecs = audioCodecs;
@@ -809,7 +813,7 @@ namespace MediaBrowser.Model.Dlna
playlistItem.AudioStreamIndex = audioStream.Index;
playlistItem.AudioCodecs = new[] { audioStream.Codec };
// copy matching audio codec options
// Copy matching audio codec options
playlistItem.AudioSampleRate = audioStream.SampleRate;
playlistItem.SetOption(qualifier, "audiochannels", audioStream.Channels.ToString());
@@ -1070,7 +1074,7 @@ namespace MediaBrowser.Model.Dlna
DeviceProfile profile = options.Profile;
string container = mediaSource.Container;
// video
// Video
int? width = videoStream?.Width;
int? height = videoStream?.Height;
int? bitDepth = videoStream?.BitDepth;
@@ -1082,7 +1086,7 @@ namespace MediaBrowser.Model.Dlna
bool? isInterlaced = videoStream?.IsInterlaced;
string videoCodecTag = videoStream?.CodecTag;
bool? isAvc = videoStream?.IsAVC;
// audio
// Audio
var defaultLanguage = audioStream?.Language ?? string.Empty;
var defaultMarked = audioStream?.IsDefault ?? false;
@@ -1211,6 +1215,7 @@ namespace MediaBrowser.Model.Dlna
return (Result: (Profile: directPlayProfile, PlayMethod: playMethod, AudioStreamIndex: selectedAudioStream?.Index, TranscodeReason: failureReasons), Order: order, Rank: ranked);
})
.OrderByDescending(analysis => analysis.Result.PlayMethod)
.ThenByDescending(analysis => analysis.Rank)
.ThenBy(analysis => analysis.Order)
.ToArray()
.ToLookup(analysis => analysis.Result.PlayMethod != null);
@@ -1223,7 +1228,7 @@ namespace MediaBrowser.Model.Dlna
return profileMatch;
}
var failureReasons = analyzedProfiles[false].OrderBy(a => a.Result.TranscodeReason).ThenBy(analysis => analysis.Order).FirstOrDefault().Result.TranscodeReason;
var failureReasons = analyzedProfiles[false].Select(analysis => analysis.Result).FirstOrDefault().TranscodeReason;
if (failureReasons == 0)
{
failureReasons = TranscodeReason.DirectPlayError;
@@ -1269,13 +1274,13 @@ namespace MediaBrowser.Model.Dlna
mediaSource.Path ?? "Unknown path");
}
private TranscodeReason IsEligibleForDirectPlay(
private TranscodeReason IsBitrateEligibleForDirectPlayback(
MediaSourceInfo item,
long maxBitrate,
VideoOptions options,
PlayMethod playMethod)
{
bool result = IsItemBitrateEligibleForDirectPlay(item, maxBitrate, playMethod);
bool result = IsItemBitrateEligibleForDirectPlayback(item, maxBitrate, playMethod);
if (!result)
{
return TranscodeReason.ContainerBitrateExceedsLimit;
@@ -1443,7 +1448,7 @@ namespace MediaBrowser.Model.Dlna
return null;
}
private bool IsItemBitrateEligibleForDirectPlay(MediaSourceInfo item, long maxBitrate, PlayMethod playMethod)
private bool IsItemBitrateEligibleForDirectPlayback(MediaSourceInfo item, long maxBitrate, PlayMethod playMethod)
{
// Don't restrict by bitrate if coming from an external domain
if (item.IsRemote)

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace MediaBrowser.Model.Entities
{
@@ -9,6 +10,16 @@ namespace MediaBrowser.Model.Entities
/// </summary>
public static class ProviderIdsExtensions
{
/// <summary>
/// Case insensitive dictionary of <see cref="MetadataProvider"/> string representation.
/// </summary>
private static readonly Dictionary<string, string> _metadataProviderEnumDictionary =
Enum.GetValues<MetadataProvider>()
.ToDictionary(
enumValue => enumValue.ToString(),
enumValue => enumValue.ToString(),
StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Checks if this instance has an id for the given provider.
/// </summary>
@@ -108,7 +119,7 @@ namespace MediaBrowser.Model.Entities
/// <param name="instance">The instance.</param>
/// <param name="name">The name.</param>
/// <param name="value">The value.</param>
public static void SetProviderId(this IHasProviderIds instance, string name, string value)
public static void SetProviderId(this IHasProviderIds instance, string name, string? value)
{
if (instance == null)
{
@@ -125,7 +136,15 @@ namespace MediaBrowser.Model.Entities
// Ensure it exists
instance.ProviderIds ??= new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
instance.ProviderIds[name] = value;
// Match on internal MetadataProvider enum string values before adding arbitrary providers
if (_metadataProviderEnumDictionary.TryGetValue(name, out var enumValue))
{
instance.ProviderIds[enumValue] = value;
}
else
{
instance.ProviderIds[name] = value;
}
}
}

View File

@@ -8,7 +8,7 @@ using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Diacritics.Extensions;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;

View File

@@ -22,7 +22,7 @@
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="OptimizedPriorityQueue" Version="5.0.0" />
<PackageReference Include="PlaylistsNET" Version="1.1.3" />
<PackageReference Include="TMDbLib" Version="1.9.1" />
<PackageReference Include="TMDbLib" Version="1.9.2" />
</ItemGroup>
<PropertyGroup>

View File

@@ -173,16 +173,30 @@ namespace MediaBrowser.Providers.MediaInfo
IReadOnlyList<MediaAttachment> mediaAttachments;
ChapterInfo[] chapters;
mediaStreams = new List<MediaStream>();
// Add external streams before adding the streams from the file to preserve stream IDs on remote videos
await AddExternalSubtitlesAsync(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
await AddExternalAudioAsync(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
var startIndex = mediaStreams.Count == 0 ? 0 : (mediaStreams.Max(i => i.Index) + 1);
if (mediaInfo != null)
{
mediaStreams = mediaInfo.MediaStreams.ToList();
foreach (var mediaStream in mediaInfo.MediaStreams)
{
mediaStream.Index = startIndex++;
mediaStreams.Add(mediaStream);
}
mediaAttachments = mediaInfo.MediaAttachments;
video.TotalBitrate = mediaInfo.Bitrate;
// video.FormatName = (mediaInfo.Container ?? string.Empty)
// .Replace("matroska", "mkv", StringComparison.OrdinalIgnoreCase);
// For dvd's this may not always be accurate, so don't set the runtime if the item already has one
// For DVDs this may not always be accurate, so don't set the runtime if the item already has one
var needToSetRuntime = video.VideoType != VideoType.Dvd || video.RunTimeTicks == null || video.RunTimeTicks.Value == 0;
if (needToSetRuntime)
@@ -213,15 +227,20 @@ namespace MediaBrowser.Providers.MediaInfo
}
else
{
mediaStreams = new List<MediaStream>();
var currentMediaStreams = video.GetMediaStreams();
foreach (var mediaStream in currentMediaStreams)
{
if (!mediaStream.IsExternal)
{
mediaStream.Index = startIndex++;
mediaStreams.Add(mediaStream);
}
}
mediaAttachments = Array.Empty<MediaAttachment>();
chapters = Array.Empty<ChapterInfo>();
}
await AddExternalSubtitlesAsync(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
await AddExternalAudioAsync(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
var libraryOptions = _libraryManager.GetLibraryOptions(video);
if (mediaInfo != null)
@@ -254,7 +273,11 @@ namespace MediaBrowser.Providers.MediaInfo
video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle);
_itemRepo.SaveMediaStreams(video.Id, mediaStreams, cancellationToken);
_itemRepo.SaveMediaAttachments(video.Id, mediaAttachments, cancellationToken);
if (mediaAttachments.Any())
{
_itemRepo.SaveMediaAttachments(video.Id, mediaAttachments, cancellationToken);
}
if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh ||
options.MetadataRefreshMode == MetadataRefreshMode.Default)

View File

@@ -13,7 +13,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using Diacritics.Extensions;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;

6
debian/changelog vendored
View File

@@ -1,3 +1,9 @@
jellyfin-server (10.8.0~beta3) unstable; urgency=medium
* New upstream version 10.8.0-beta3; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.0-beta3
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 15 May 2022 20:15:43 -0400
jellyfin-server (10.8.0~beta2) unstable; urgency=medium
* New upstream version 10.8.0-beta2; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.0-beta2

2
debian/control vendored
View File

@@ -22,7 +22,7 @@ Depends: at,
libsqlite3-0,
libfontconfig1,
libfreetype6,
libssl1.1
libssl1.1 | libssl3
Recommends: jellyfin-web, sudo
Description: Jellyfin is the Free Software Media System.
This package provides the Jellyfin server backend and API.

View File

@@ -5,7 +5,7 @@ Homepage: https://jellyfin.org
Standards-Version: 3.9.2
Package: jellyfin
Version: 10.8.0~beta2
Version: 10.8.0~beta3
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.8.0~beta2
Version: 10.8.0~beta3
Release: 1%{?dist}
Summary: The Free Software Media System
License: GPLv3
@@ -153,6 +153,8 @@ fi
%systemd_postun_with_restart jellyfin.service
%changelog
* Sun May 15 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.8.0-beta3; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.0-beta3
* Sun Apr 17 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.8.0-beta2; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.0-beta2
* Fri Mar 25 2022 Jellyfin Packaging Team <packaging@jellyfin.org>

View File

@@ -1,4 +1,8 @@
using System;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
namespace Jellyfin.Extensions
{
@@ -7,6 +11,44 @@ namespace Jellyfin.Extensions
/// </summary>
public static class StringExtensions
{
// Matches non-conforming unicode chars
// https://mnaoumov.wordpress.com/2014/06/14/stripping-invalid-characters-from-utf-16-strings/
private static readonly Regex _nonConformingUnicode = new Regex("([\ud800-\udbff](?![\udc00-\udfff]))|((?<![\ud800-\udbff])[\udc00-\udfff])|(\ufffd)");
/// <summary>
/// Removes the diacritics character from the strings.
/// </summary>
/// <param name="text">The string to act on.</param>
/// <returns>The string without diacritics character.</returns>
public static string RemoveDiacritics(this string text)
{
string withDiactritics = _nonConformingUnicode
.Replace(text, string.Empty)
.Normalize(NormalizationForm.FormD);
var withoutDiactritics = new StringBuilder();
foreach (char c in withDiactritics)
{
UnicodeCategory uc = CharUnicodeInfo.GetUnicodeCategory(c);
if (uc != UnicodeCategory.NonSpacingMark)
{
withoutDiactritics.Append(c);
}
}
return withoutDiactritics.ToString().Normalize(NormalizationForm.FormC);
}
/// <summary>
/// Checks wether or not the specified string has diacritics in it.
/// </summary>
/// <param name="text">The string to check.</param>
/// <returns>True if the string has diacritics, false otherwise.</returns>
public static bool HasDiacritics(this string text)
{
return !string.Equals(text, text.RemoveDiacritics(), StringComparison.Ordinal);
}
/// <summary>
/// Counts the number of occurrences of [needle] in the string.
/// </summary>

View File

@@ -5,6 +5,38 @@ namespace Jellyfin.Extensions.Tests
{
public class StringExtensionsTests
{
[Theory]
[InlineData("", "")] // Identity edge-case (no diactritics)
[InlineData("Indiana Jones", "Indiana Jones")] // Identity (no diactritics)
[InlineData("a\ud800b", "ab")] // Invalid UTF-16 char stripping
[InlineData("Jön", "Jon")] // Issue #7484
[InlineData("Jönssonligan", "Jonssonligan")] // Issue #7484
[InlineData("Kieślowski", "Kieslowski")] // Issue #7450
[InlineData("Cidadão Kane", "Cidadao Kane")] // Issue #7560
[InlineData("운명처럼 널 사랑해", "운명처럼 널 사랑해")] // Issue #6393 (Korean language support)
[InlineData("애타는 로맨스", "애타는 로맨스")] // Issue #6393
public void RemoveDiacritics_ValidInput_Corrects(string input, string expectedResult)
{
string result = input.RemoveDiacritics();
Assert.Equal(expectedResult, result);
}
[Theory]
[InlineData("", false)] // Identity edge-case (no diactritics)
[InlineData("Indiana Jones", false)] // Identity (no diactritics)
[InlineData("a\ud800b", true)] // Invalid UTF-16 char stripping
[InlineData("Jön", true)] // Issue #7484
[InlineData("Jönssonligan", true)] // Issue #7484
[InlineData("Kieślowski", true)] // Issue #7450
[InlineData("Cidadão Kane", true)] // Issue #7560
[InlineData("운명처럼 널 사랑해", false)] // Issue #6393 (Korean language support)
[InlineData("애타는 로맨스", false)] // Issue #6393
public void HasDiacritics_ValidInput_Corrects(string input, bool expectedResult)
{
bool result = input.HasDiacritics();
Assert.Equal(expectedResult, result);
}
[Theory]
[InlineData("", '_', 0)]
[InlineData("___", '_', 3)]

View File

@@ -27,7 +27,7 @@ namespace Jellyfin.Model.Tests
[InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
[InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
[InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450
[InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
// Firefox
@@ -38,7 +38,7 @@ namespace Jellyfin.Model.Tests
[InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
[InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
[InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450
[InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
// Safari
@@ -89,7 +89,7 @@ namespace Jellyfin.Model.Tests
[InlineData("Chrome-NoHLS", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Chrome-NoHLS", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode", "http")]
[InlineData("Chrome-NoHLS", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode", "http")]
[InlineData("Chrome-NoHLS", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Chrome-NoHLS", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450
[InlineData("Chrome-NoHLS", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Chrome-NoHLS", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
// TranscodeMedia
@@ -177,7 +177,7 @@ namespace Jellyfin.Model.Tests
[InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
[InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
[InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450
[InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
// Firefox
@@ -187,7 +187,7 @@ namespace Jellyfin.Model.Tests
[InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
[InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
[InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450
[InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
// Safari
@@ -338,23 +338,23 @@ namespace Jellyfin.Model.Tests
Assert.NotNull(mediaSource);
var videoStreams = mediaSource.MediaStreams.Where(stream => stream.Type == MediaStreamType.Video);
var audioStreams = mediaSource.MediaStreams.Where(stream => stream.Type == MediaStreamType.Audio);
// TODO: check AudioStreamIndex vs options.AudioStreamIndex
// TODO: Check AudioStreamIndex vs options.AudioStreamIndex
var inputAudioStream = mediaSource.GetDefaultAudioStream(audioStreamIndexInput ?? mediaSource.DefaultAudioStreamIndex);
var uri = ParseUri(val);
if (playMethod == PlayMethod.DirectPlay)
{
// check expected container
// Check expected container
var containers = ContainerProfile.SplitValue(mediaSource.Container);
// TODO: test transcode too
// TODO: Test transcode too
// Assert.Contains(uri.Extension, containers);
// check expected video codec (1)
// Check expected video codec (1)
Assert.Contains(targetVideoStream.Codec, val.TargetVideoCodec);
Assert.Single(val.TargetVideoCodec);
// check expected audio codecs (1)
// Check expected audio codecs (1)
Assert.Contains(targetAudioStream.Codec, val.TargetAudioCodec);
Assert.Single(val.TargetAudioCodec);
// Assert.Single(val.AudioCodecs);
@@ -370,7 +370,7 @@ namespace Jellyfin.Model.Tests
Assert.NotEmpty(val.VideoCodecs);
Assert.NotEmpty(val.AudioCodecs);
// check expected container (todo: this could be a test param)
// Check expected container (todo: this could be a test param)
if (transcodeProtocol == "http")
{
// Assert.Equal("webm", val.Container);
@@ -403,32 +403,39 @@ namespace Jellyfin.Model.Tests
stream => Assert.DoesNotContain(stream.Codec, val.VideoCodecs));
}
// todo: fill out tests here
// TODO: Fill out tests here
}
// DirectStream and Remux
else
{
// check expected video codec (1)
// Check expected video codec (1)
Assert.Contains(targetVideoStream.Codec, val.TargetVideoCodec);
Assert.Single(val.TargetVideoCodec);
if (transcodeMode == "DirectStream")
{
// Check expected audio codecs (1)
if (!targetAudioStream.IsExternal)
{
// check expected audio codecs (1)
Assert.DoesNotContain(targetAudioStream.Codec, val.AudioCodecs);
if (val.TranscodeReasons.HasFlag(TranscodeReason.ContainerNotSupported))
{
Assert.Contains(targetAudioStream.Codec, val.AudioCodecs);
}
else
{
Assert.DoesNotContain(targetAudioStream.Codec, val.AudioCodecs);
}
}
}
else if (transcodeMode == "Remux")
{
// check expected audio codecs (1)
// Check expected audio codecs (1)
Assert.Contains(targetAudioStream.Codec, val.AudioCodecs);
Assert.Single(val.AudioCodecs);
}
// video details
// Video details
var videoStream = targetVideoStream;
Assert.False(val.EstimateContentLength);
Assert.Equal(TranscodeSeekInfo.Auto, val.TranscodeSeekInfo);
@@ -437,10 +444,10 @@ namespace Jellyfin.Model.Tests
Assert.Equal(videoStream.BitDepth, val.TargetVideoBitDepth);
Assert.InRange(val.VideoBitrate.GetValueOrDefault(), videoStream.BitRate.GetValueOrDefault(), int.MaxValue);
// audio codec not supported
// Audio codec not supported
if ((why & TranscodeReason.AudioCodecNotSupported) != 0)
{
// audio stream specified
// Audio stream specified
if (options.AudioStreamIndex >= 0)
{
// TODO:fixme
@@ -450,10 +457,10 @@ namespace Jellyfin.Model.Tests
}
}
// audio stream not specified
// Audio stream not specified
else
{
// TODO:fixme
// TODO: Fixme
Assert.All(audioStreams, stream =>
{
if (!stream.IsExternal)

View File

@@ -45,8 +45,8 @@
},
{
"Container": "wmv",
"AudioCodec": "",
"VideoCodec": "",
"AudioCodec": "wma",
"VideoCodec": "wmv,vc1",
"Type": "Video",
"$type": "DirectPlayProfile"
},
@@ -59,8 +59,8 @@
},
{
"Container": "asf",
"AudioCodec": "",
"VideoCodec": "",
"AudioCodec": "wma",
"VideoCodec": "wmv,vc1",
"Type": "Video",
"$type": "DirectPlayProfile"
},

View File

@@ -45,8 +45,8 @@
},
{
"Container": "wmv",
"AudioCodec": "",
"VideoCodec": "",
"AudioCodec": "wma",
"VideoCodec": "wmv,vc1",
"Type": "Video",
"$type": "DirectPlayProfile"
},
@@ -59,8 +59,8 @@
},
{
"Container": "asf",
"AudioCodec": "",
"VideoCodec": "",
"AudioCodec": "wma",
"VideoCodec": "wmv,vc1",
"Type": "Video",
"$type": "DirectPlayProfile"
},

View File

@@ -1,4 +1,4 @@
using Emby.Naming.Common;
using Emby.Naming.Common;
using Emby.Naming.TV;
using Xunit;
@@ -9,6 +9,7 @@ namespace Jellyfin.Naming.Tests.TV
private readonly NamingOptions _namingOptions = new NamingOptions();
[Theory]
[InlineData("Season 21/One Piece 1001", 1001)]
[InlineData("Watchmen (2019)/Watchmen 1x03 [WEBDL-720p][EAC3 5.1][h264][-TBS] - She Was Killed by Space Junk.mkv", 3)]
[InlineData("The Daily Show/The Daily Show 25x22 - [WEBDL-720p][AAC 2.0][x264] Noah Baumbach-TBS.mkv", 22)]
[InlineData("Castle Rock 2x01 Que el rio siga su curso [WEB-DL HULU 1080p h264 Dual DD5.1 Subs].mkv", 1)]

View File

@@ -85,6 +85,17 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv
EpisodeTitle = "The VCR Illumination"
});
data.Add(
"Lorem ipsum dolor sit amet: consect 2018_12_06_21_06_00",
new TimerInfo
{
Name = "Lorem ipsum dolor sit amet: consect",
IsProgramSeries = true,
StartDate = new DateTime(2018, 12, 6, 21, 6, 0, DateTimeKind.Local),
OriginalAirDate = new DateTime(2018, 12, 6),
EpisodeTitle = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor"
});
return data;
}

View File

@@ -50,7 +50,10 @@ namespace Jellyfin.Server.Implementations.Tests.QuickConnect
[Fact]
public void IsEnabled_QuickConnectUnavailable_False()
=> Assert.False(_quickConnectManager.IsEnabled);
{
_config.QuickConnectAvailable = false;
Assert.False(_quickConnectManager.IsEnabled);
}
[Theory]
[InlineData("", "DeviceId", "Client", "1.0.0")]
@@ -69,19 +72,31 @@ namespace Jellyfin.Server.Implementations.Tests.QuickConnect
[Fact]
public void TryConnect_QuickConnectUnavailable_ThrowsAuthenticationException()
=> Assert.Throws<AuthenticationException>(() => _quickConnectManager.TryConnect(_quickConnectAuthInfo));
{
_config.QuickConnectAvailable = false;
Assert.Throws<AuthenticationException>(() => _quickConnectManager.TryConnect(_quickConnectAuthInfo));
}
[Fact]
public void CheckRequestStatus_QuickConnectUnavailable_ThrowsAuthenticationException()
=> Assert.Throws<AuthenticationException>(() => _quickConnectManager.CheckRequestStatus(string.Empty));
{
_config.QuickConnectAvailable = false;
Assert.Throws<AuthenticationException>(() => _quickConnectManager.CheckRequestStatus(string.Empty));
}
[Fact]
public void AuthorizeRequest_QuickConnectUnavailable_ThrowsAuthenticationException()
=> Assert.ThrowsAsync<AuthenticationException>(() => _quickConnectManager.AuthorizeRequest(Guid.Empty, string.Empty));
{
_config.QuickConnectAvailable = false;
Assert.ThrowsAsync<AuthenticationException>(() => _quickConnectManager.AuthorizeRequest(Guid.Empty, string.Empty));
}
[Fact]
public void GetAuthorizedRequest_QuickConnectUnavailable_ThrowsAuthenticationException()
=> Assert.Throws<AuthenticationException>(() => _quickConnectManager.GetAuthorizedRequest(string.Empty));
{
_config.QuickConnectAvailable = false;
Assert.Throws<AuthenticationException>(() => _quickConnectManager.GetAuthorizedRequest(string.Empty));
}
[Fact]
public void IsEnabled_QuickConnectAvailable_True()