aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/ISSUE_TEMPLATE/issue report.yml49
-rw-r--r--.github/workflows/ci-codeql-analysis.yml6
-rw-r--r--CONTRIBUTORS.md2
-rw-r--r--Directory.Packages.props4
-rw-r--r--Emby.Server.Implementations/Localization/Core/et.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/ga.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/mk.json3
-rw-r--r--Jellyfin.Api/Controllers/VideosController.cs6
-rw-r--r--Jellyfin.Api/Helpers/DynamicHlsHelper.cs15
-rw-r--r--Jellyfin.Api/Helpers/StreamingHelpers.cs21
-rw-r--r--Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs3
-rw-r--r--Jellyfin.Server/Jellyfin.Server.csproj5
-rw-r--r--Jellyfin.Server/Jellyfin.Server.icobin0 -> 40883 bytes
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs5
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs46
-rw-r--r--MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs19
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs4
-rw-r--r--MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs15
-rw-r--r--MediaBrowser.Model/Net/MimeTypes.cs13
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs17
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_webm.json106
-rw-r--r--tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs10
-rw-r--r--tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs13
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-av1-aac-srt-2600k.json2
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-vp9-aac-srt-2600k.json2
-rw-r--r--tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-vp9-ac3-srt-2600k.json2
26 files changed, 287 insertions, 86 deletions
diff --git a/.github/ISSUE_TEMPLATE/issue report.yml b/.github/ISSUE_TEMPLATE/issue report.yml
index 587802833..b690b82c2 100644
--- a/.github/ISSUE_TEMPLATE/issue report.yml
+++ b/.github/ISSUE_TEMPLATE/issue report.yml
@@ -6,7 +6,11 @@ body:
- type: markdown
attributes:
value: |
- Thanks for taking the time to fill out this bug report! Please provide as much detail as necessary, most questions may not be applicable to you. If you need real-time help, join us on [Matrix](https://matrix.to/#/#jellyfin-troubleshooting:matrix.org) or [Discord](https://discord.gg/zHBxVSXdBV).
+ Thanks for taking the time to report an issue. Before submitting a report, please do the following:
+ 1. Please head to our forum or chat rooms and troubleshoot with volunteers if you haven't already. Links can be found here: https://jellyfin.org/contact/
+ 2. Please search the bug tracker for similar issues. If you do find one, please comment there instead of opening a new bug report.
+ 3. If you decide to open a new report, please provide as much detail as possible.
+ 4. Please **ONLY** report **ONE** issue per report. If you are experiencing multiple issues, please open multiple reports.
- type: textarea
id: what-happened
attributes:
@@ -14,14 +18,18 @@ body:
description: Also tell us, what did you expect to happen?
placeholder: |
The more information that you are able to provide, the better. Did you do anything before this happened? Did you upgrade or change anything? Any screenshots or logs you can provide will be helpful.
-
- This is my issue.
-
- Steps to Reproduce
- 1. In this environment...
- 2. With this config...
- 3. Run '...'
- 4. See error...
+ If you are using an old release of Jellyfin, please also explain why.
+ validations:
+ required: true
+ - type: textarea
+ id: repro-steps
+ attributes:
+ label: Reproduction Steps
+ placeholder: |
+ 1. In this environment...
+ 2. With this config...
+ 3. Run '...'
+ 4. See error...
validations:
required: true
- type: dropdown
@@ -30,11 +38,10 @@ body:
label: Jellyfin Version
description: What version of Jellyfin are you running?
options:
- - 10.8.z
- - 10.8.9
- - 10.7.7
- - 10.6.4
- - Other
+ - 10.8.13
+ - 10.8.12
+ - 10.8.11 or older (please specify)
+ - Unstable (master branch)
validations:
required: true
- type: input
@@ -77,6 +84,18 @@ body:
- Networking:
- Storage:
render: markdown
+ validations:
+ required: true
+ - type: markdown
+ attributes:
+ value: |
+ When providing logs, please keep the following things in mind.
+ 1. **DO NOT** use external paste services.
+ 2. Please provide complete logs.
+ - For server logs, include everything you think is important plus *10 lines before and after*
+ - For ffmpeg logs, please provide the entire file unmodified.
+ 3. Please do not run logs through any translation program. Especially beware if your browser translates pages by default.
+ 4. Please do not include logs as screenshots, with the only exception being client logs in browsers.
- type: textarea
id: logs
attributes:
@@ -84,6 +103,8 @@ body:
description: Please copy and paste any relevant log output. This can be found in Dashboard > Logs.
placeholder: For playback issues, browser/client and FFmpeg logs may be more useful.
render: shell
+ validations:
+ required: true
- type: textarea
id: ffmpeg-logs
attributes:
diff --git a/.github/workflows/ci-codeql-analysis.yml b/.github/workflows/ci-codeql-analysis.yml
index 839bebb96..f8012e90e 100644
--- a/.github/workflows/ci-codeql-analysis.yml
+++ b/.github/workflows/ci-codeql-analysis.yml
@@ -27,11 +27,11 @@ jobs:
dotnet-version: '8.0.x'
- name: Initialize CodeQL
- uses: github/codeql-action/init@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0
+ uses: github/codeql-action/init@379614612a29c9e28f31f39a59013eb8012a51f0 # v3.24.3
with:
languages: ${{ matrix.language }}
queries: +security-extended
- name: Autobuild
- uses: github/codeql-action/autobuild@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0
+ uses: github/codeql-action/autobuild@379614612a29c9e28f31f39a59013eb8012a51f0 # v3.24.3
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0
+ uses: github/codeql-action/analyze@379614612a29c9e28f31f39a59013eb8012a51f0 # v3.24.3
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 3be6331c8..55642e4e2 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -179,6 +179,7 @@
- [Çağrı Sakaoğlu](https://github.com/ilovepilav)
_ [Barasingha](https://github.com/MaVdbussche)
- [Gauvino](https://github.com/Gauvino)
+ - [felix920506](https://github.com/felix920506)
# Emby Contributors
@@ -250,3 +251,4 @@
- [Utku Özdemir](https://github.com/utkuozdemir)
- [JPUC1143](https://github.com/Jpuc1143/)
- [0x25CBFC4F](https://github.com/0x25CBFC4F)
+ - [Robert Lützner](https://github.com/rluetzner)
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 4a236e5b6..0370ed055 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -16,10 +16,10 @@
<PackageVersion Include="Diacritics" Version="3.3.27" />
<PackageVersion Include="DiscUtils.Udf" Version="0.16.13" />
<PackageVersion Include="DotNet.Glob" Version="3.1.3" />
- <PackageVersion Include="EFCoreSecondLevelCacheInterceptor" Version="4.2.0" />
+ <PackageVersion Include="EFCoreSecondLevelCacheInterceptor" Version="4.2.1" />
<PackageVersion Include="FsCheck.Xunit" Version="2.16.6" />
<PackageVersion Include="HarfBuzzSharp.NativeAssets.Linux" Version="7.3.0.1" />
- <PackageVersion Include="IDisposableAnalyzers" Version="4.0.4" />
+ <PackageVersion Include="IDisposableAnalyzers" Version="4.0.7" />
<PackageVersion Include="Jellyfin.XmlTv" Version="10.8.0" />
<PackageVersion Include="libse" Version="3.6.13" />
<PackageVersion Include="LrcParser" Version="2023.524.0" />
diff --git a/Emby.Server.Implementations/Localization/Core/et.json b/Emby.Server.Implementations/Localization/Core/et.json
index c78ffa28c..977307b06 100644
--- a/Emby.Server.Implementations/Localization/Core/et.json
+++ b/Emby.Server.Implementations/Localization/Core/et.json
@@ -31,7 +31,7 @@
"VersionNumber": "Versioon {0}",
"ValueSpecialEpisodeName": "Eriepisood - {0}",
"ValueHasBeenAddedToLibrary": "{0} lisati meediakogusse",
- "UserStartedPlayingItemWithValues": "{0} taasesitab {1} serveris {2}",
+ "UserStartedPlayingItemWithValues": "{0} taasesitab {1} seadmes {2}",
"UserPasswordChangedWithName": "Kasutaja {0} parool muudeti",
"UserLockedOutWithName": "Kasutaja {0} lukustati",
"UserDeletedWithName": "Kasutaja {0} kustutati",
diff --git a/Emby.Server.Implementations/Localization/Core/ga.json b/Emby.Server.Implementations/Localization/Core/ga.json
new file mode 100644
index 000000000..28e54bff5
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Core/ga.json
@@ -0,0 +1,3 @@
+{
+ "Albums": "Albaim"
+}
diff --git a/Emby.Server.Implementations/Localization/Core/mk.json b/Emby.Server.Implementations/Localization/Core/mk.json
index cbccad87f..7ef907918 100644
--- a/Emby.Server.Implementations/Localization/Core/mk.json
+++ b/Emby.Server.Implementations/Localization/Core/mk.json
@@ -122,5 +122,6 @@
"TaskRefreshChapterImagesDescription": "Создава тамбнеил за видеата шти имаат поглавја.",
"TaskCleanActivityLogDescription": "Избришува логови на активности постари од определеното време.",
"TaskCleanActivityLog": "Избриши Лог на Активности",
- "External": "Надворешен"
+ "External": "Надворешен",
+ "HearingImpaired": "Оштетен слух"
}
diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs
index e6c319869..b3029d6fa 100644
--- a/Jellyfin.Api/Controllers/VideosController.cs
+++ b/Jellyfin.Api/Controllers/VideosController.cs
@@ -458,10 +458,8 @@ public class VideosController : BaseJellyfinApiController
return BadRequest($"Input protocol {state.InputProtocol} cannot be streamed statically");
}
- var outputPath = state.OutputFilePath;
-
// Static stream
- if (@static.HasValue && @static.Value)
+ if (@static.HasValue && @static.Value && !(state.MediaSource.VideoType == VideoType.BluRay || state.MediaSource.VideoType == VideoType.Dvd))
{
var contentType = state.GetMimeType("." + state.OutputContainer, false) ?? state.GetMimeType(state.MediaPath);
@@ -478,7 +476,7 @@ public class VideosController : BaseJellyfinApiController
// Need to start ffmpeg (because media can't be returned directly)
var encodingOptions = _serverConfigurationManager.GetEncodingOptions();
- var ffmpegCommandLineArguments = _encodingHelper.GetProgressiveVideoFullCommandLine(state, encodingOptions, outputPath, "superfast");
+ var ffmpegCommandLineArguments = _encodingHelper.GetProgressiveVideoFullCommandLine(state, encodingOptions, "superfast");
return await FileStreamResponseHelpers.GetTranscodedFile(
state,
isHeadRequest,
diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
index b0c17c835..f8d89119a 100644
--- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
+++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
@@ -211,19 +211,8 @@ public class DynamicHlsHelper
var sdrVideoUrl = ReplaceProfile(playlistUrl, "hevc", string.Join(',', requestedVideoProfiles), "main");
sdrVideoUrl += "&AllowVideoStreamCopy=false";
- var sdrOutputVideoBitrate = _encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec);
- var sdrOutputAudioBitrate = 0;
- if (EncodingHelper.LosslessAudioCodecs.Contains(state.VideoRequest.AudioCodec, StringComparison.OrdinalIgnoreCase))
- {
- sdrOutputAudioBitrate = state.AudioStream.BitRate ?? 0;
- }
- else
- {
- sdrOutputAudioBitrate = _encodingHelper.GetAudioBitrateParam(state.VideoRequest, state.AudioStream, state.OutputAudioChannels) ?? 0;
- }
-
- var sdrTotalBitrate = sdrOutputAudioBitrate + sdrOutputVideoBitrate;
- AppendPlaylist(builder, state, sdrVideoUrl, sdrTotalBitrate, subtitleGroup);
+ // HACK: Use the same bitrate so that the client can choose by other attributes, such as color range.
+ AppendPlaylist(builder, state, sdrVideoUrl, totalBitrate, subtitleGroup);
// Restore the video codec
state.OutputVideoCodec = "copy";
diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs
index 7a3842a9f..bfe71fd87 100644
--- a/Jellyfin.Api/Helpers/StreamingHelpers.cs
+++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs
@@ -225,7 +225,7 @@ public static class StreamingHelpers
var ext = string.IsNullOrWhiteSpace(state.OutputContainer)
? GetOutputFileExtension(state, mediaSource)
- : ("." + state.OutputContainer);
+ : ("." + GetContainerFileExtension(state.OutputContainer));
state.OutputFilePath = GetOutputFilePath(state, ext, serverConfigurationManager, streamingRequest.DeviceId, streamingRequest.PlaySessionId);
@@ -559,4 +559,23 @@ public static class StreamingHelpers
}
}
}
+
+ /// <summary>
+ /// Parses the container into its file extension.
+ /// </summary>
+ /// <param name="container">The container.</param>
+ private static string? GetContainerFileExtension(string? container)
+ {
+ if (string.Equals(container, "mpegts", StringComparison.OrdinalIgnoreCase))
+ {
+ return "ts";
+ }
+
+ if (string.Equals(container, "matroska", StringComparison.OrdinalIgnoreCase))
+ {
+ return "mkv";
+ }
+
+ return container;
+ }
}
diff --git a/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs b/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs
index f6854157a..095bc9ed3 100644
--- a/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs
+++ b/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs
@@ -382,7 +382,7 @@ public class TrickplayManager : ITrickplayManager
if (trickplayInfo.ThumbnailCount > 0)
{
- const string urlFormat = "Trickplay/{0}/{1}.jpg?MediaSourceId={2}&api_key={3}";
+ const string urlFormat = "{0}.jpg?MediaSourceId={1}&api_key={2}";
const string decimalFormat = "{0:0.###}";
var resolution = $"{trickplayInfo.Width}x{trickplayInfo.Height}";
@@ -431,7 +431,6 @@ public class TrickplayManager : ITrickplayManager
.AppendFormat(
CultureInfo.InvariantCulture,
urlFormat,
- width.ToString(CultureInfo.InvariantCulture),
i.ToString(CultureInfo.InvariantCulture),
itemId.ToString("N"),
apiKey)
diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj
index 21c6e6f01..e18212908 100644
--- a/Jellyfin.Server/Jellyfin.Server.csproj
+++ b/Jellyfin.Server/Jellyfin.Server.csproj
@@ -12,6 +12,7 @@
<ServerGarbageCollection>false</ServerGarbageCollection>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
+ <ApplicationIcon>Jellyfin.Server.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
@@ -19,6 +20,10 @@
</ItemGroup>
<ItemGroup>
+ <Content Include="Jellyfin.Server.ico" />
+ </ItemGroup>
+
+ <ItemGroup>
<EmbeddedResource Include="Resources/Configuration/*" />
</ItemGroup>
diff --git a/Jellyfin.Server/Jellyfin.Server.ico b/Jellyfin.Server/Jellyfin.Server.ico
new file mode 100644
index 000000000..0872b956a
--- /dev/null
+++ b/Jellyfin.Server/Jellyfin.Server.ico
Binary files differ
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 1c95192f1..b6738e7cc 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -2988,7 +2988,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
return string.Format(
CultureInfo.InvariantCulture,
- @"scale=-1:{1}:fast_bilinear,crop,pad=max({0}\,iw):max({1}\,ih):(ow-iw)/2:(oh-ih)/2:black@0,crop={0}:{1}",
+ @"scale=-1:{1}:fast_bilinear,scale,crop,pad=max({0}\,iw):max({1}\,ih):(ow-iw)/2:(oh-ih)/2:black@0,crop={0}:{1}",
outWidth.Value,
outHeight.Value);
}
@@ -6541,13 +6541,14 @@ namespace MediaBrowser.Controller.MediaEncoding
return " -codec:s:0 " + codec + " -disposition:s:0 default";
}
- public string GetProgressiveVideoFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, string outputPath, string defaultPreset)
+ public string GetProgressiveVideoFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, string defaultPreset)
{
// Get the output codec name
var videoCodec = GetVideoEncoder(state, encodingOptions);
var format = string.Empty;
var keyFrame = string.Empty;
+ var outputPath = state.OutputFilePath;
if (Path.GetExtension(outputPath.AsSpan()).Equals(".mp4", StringComparison.OrdinalIgnoreCase)
&& state.BaseRequest.Context == EncodingContext.Streaming)
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index f86d14fc8..cc6971c1b 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -1111,6 +1111,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
return allVobs
.Where(vob => titles.Contains(_fileSystem.GetFileNameWithoutExtension(vob).AsSpan().RightPart('_').ToString()))
.Select(i => i.FullName)
+ .Order()
.ToList();
}
@@ -1127,6 +1128,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
return directoryFiles
.Where(f => validPlaybackFiles.Contains(f.Name, StringComparer.OrdinalIgnoreCase))
.Select(f => f.FullName)
+ .Order()
.ToList();
}
@@ -1150,31 +1152,29 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
// Generate concat configuration entries for each file and write to file
- using (StreamWriter sw = new StreamWriter(concatFilePath))
+ using StreamWriter sw = new StreamWriter(concatFilePath);
+ foreach (var path in files)
{
- foreach (var path in files)
- {
- var mediaInfoResult = GetMediaInfo(
- new MediaInfoRequest
+ var mediaInfoResult = GetMediaInfo(
+ new MediaInfoRequest
+ {
+ MediaType = DlnaProfileType.Video,
+ MediaSource = new MediaSourceInfo
{
- MediaType = DlnaProfileType.Video,
- MediaSource = new MediaSourceInfo
- {
- Path = path,
- Protocol = MediaProtocol.File,
- VideoType = videoType
- }
- },
- CancellationToken.None).GetAwaiter().GetResult();
-
- var duration = TimeSpan.FromTicks(mediaInfoResult.RunTimeTicks.Value).TotalSeconds;
-
- // Add file path stanza to concat configuration
- sw.WriteLine("file '{0}'", path);
-
- // Add duration stanza to concat configuration
- sw.WriteLine("duration {0}", duration);
- }
+ Path = path,
+ Protocol = MediaProtocol.File,
+ VideoType = videoType
+ }
+ },
+ CancellationToken.None).GetAwaiter().GetResult();
+
+ var duration = TimeSpan.FromTicks(mediaInfoResult.RunTimeTicks.Value).TotalSeconds;
+
+ // Add file path stanza to concat configuration
+ sw.WriteLine("file '{0}'", path);
+
+ // Add duration stanza to concat configuration
+ sw.WriteLine("duration {0}", duration);
}
}
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index b532f9a7e..317aba418 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -30,6 +30,8 @@ namespace MediaBrowser.MediaEncoding.Probing
private const string ArtistReplaceValue = " | ";
private readonly char[] _nameDelimiters = { '/', '|', ';', '\\' };
+ private readonly string[] _webmVideoCodecs = { "av1", "vp8", "vp9" };
+ private readonly string[] _webmAudioCodecs = { "opus", "vorbis" };
private readonly ILogger _logger;
private readonly ILocalizationManager _localization;
@@ -114,7 +116,7 @@ namespace MediaBrowser.MediaEncoding.Probing
if (data.Format is not null)
{
- info.Container = NormalizeFormat(data.Format.FormatName);
+ info.Container = NormalizeFormat(data.Format.FormatName, info.MediaStreams);
if (int.TryParse(data.Format.BitRate, CultureInfo.InvariantCulture, out var value))
{
@@ -260,7 +262,7 @@ namespace MediaBrowser.MediaEncoding.Probing
return info;
}
- private string NormalizeFormat(string format)
+ private string NormalizeFormat(string format, IReadOnlyList<MediaStream> mediaStreams)
{
if (string.IsNullOrWhiteSpace(format))
{
@@ -288,9 +290,20 @@ namespace MediaBrowser.MediaEncoding.Probing
{
splitFormat[i] = "mkv";
}
+
+ // Handle WebM
+ else if (string.Equals(splitFormat[i], "webm", StringComparison.OrdinalIgnoreCase))
+ {
+ // Limit WebM to supported codecs
+ if (mediaStreams.Any(stream => (stream.Type == MediaStreamType.Video && !_webmVideoCodecs.Contains(stream.Codec, StringComparison.OrdinalIgnoreCase))
+ || (stream.Type == MediaStreamType.Audio && !_webmAudioCodecs.Contains(stream.Codec, StringComparison.OrdinalIgnoreCase))))
+ {
+ splitFormat[i] = string.Empty;
+ }
+ }
}
- return string.Join(',', splitFormat);
+ return string.Join(',', splitFormat.Where(s => !string.IsNullOrEmpty(s)));
}
private int? GetEstimatedAudioBitrate(string codec, int? channels)
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
index 8f1cc3f64..4b1b1bbc6 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
@@ -509,7 +509,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
var outputPaths = new List<string>();
var args = string.Format(
CultureInfo.InvariantCulture,
- "-i {0} -copyts",
+ "-i \"{0}\" -copyts",
inputPath);
foreach (var subtitleStream in subtitleStreams)
@@ -680,7 +680,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
var processArgs = string.Format(
CultureInfo.InvariantCulture,
- "-i {0} -copyts -map 0:{1} -an -vn -c:s {2} \"{3}\"",
+ "-i \"{0}\" -copyts -map 0:{1} -an -vn -c:s {2} \"{3}\"",
inputPath,
subtitleStreamIndex,
outputCodec,
diff --git a/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs b/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs
index ab3eb3298..146b30643 100644
--- a/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs
+++ b/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs
@@ -405,7 +405,7 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable
var user = userId.IsEmpty() ? null : _userManager.GetUserById(userId);
if (user is not null && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding))
{
- this.OnTranscodeFailedToStart(outputPath, transcodingJobType, state);
+ OnTranscodeFailedToStart(outputPath, transcodingJobType, state);
throw new ArgumentException("User does not have access to video transcoding.");
}
@@ -417,7 +417,12 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable
if (state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
{
var attachmentPath = Path.Combine(_appPaths.CachePath, "attachments", state.MediaSource.Id);
- if (state.VideoType != VideoType.Dvd)
+ if (state.MediaSource.VideoType == VideoType.Dvd || state.MediaSource.VideoType == VideoType.BluRay)
+ {
+ var concatPath = Path.Join(_serverConfigurationManager.GetTranscodePath(), state.MediaSource.Id + ".concat");
+ await _attachmentExtractor.ExtractAllAttachments(concatPath, state.MediaSource, attachmentPath, cancellationTokenSource.Token).ConfigureAwait(false);
+ }
+ else
{
await _attachmentExtractor.ExtractAllAttachments(state.MediaPath, state.MediaSource, attachmentPath, cancellationTokenSource.Token).ConfigureAwait(false);
}
@@ -432,7 +437,7 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable
}
}
- var process = new Process
+ using var process = new Process
{
StartInfo = new ProcessStartInfo
{
@@ -452,7 +457,7 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable
EnableRaisingEvents = true
};
- var transcodingJob = this.OnTranscodeBeginning(
+ var transcodingJob = OnTranscodeBeginning(
outputPath,
state.Request.PlaySessionId,
state.MediaSource.LiveStreamId,
@@ -507,7 +512,7 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable
catch (Exception ex)
{
_logger.LogError(ex, "Error starting FFmpeg");
- this.OnTranscodeFailedToStart(outputPath, transcodingJobType, state);
+ OnTranscodeFailedToStart(outputPath, transcodingJobType, state);
throw;
}
diff --git a/MediaBrowser.Model/Net/MimeTypes.cs b/MediaBrowser.Model/Net/MimeTypes.cs
index 5a1871070..90035f18f 100644
--- a/MediaBrowser.Model/Net/MimeTypes.cs
+++ b/MediaBrowser.Model/Net/MimeTypes.cs
@@ -66,6 +66,11 @@ namespace MediaBrowser.Model.Net
{
// Type application
{ ".azw3", "application/vnd.amazon.ebook" },
+ { ".cb7", "application/x-cb7" },
+ { ".cba", "application/x-cba" },
+ { ".cbr", "application/vnd.comicbook-rar" },
+ { ".cbt", "application/x-cbt" },
+ { ".cbz", "application/vnd.comicbook+zip" },
// Type image
{ ".tbn", "image/jpeg" },
@@ -87,7 +92,7 @@ namespace MediaBrowser.Model.Net
{ ".dsf", "audio/dsf" },
{ ".dsp", "audio/dsp" },
{ ".flac", "audio/flac" },
- { ".m4b", "audio/m4b" },
+ { ".m4b", "audio/mp4" },
{ ".mp3", "audio/mpeg" },
{ ".vorbis", "audio/vorbis" },
{ ".webma", "audio/webm" },
@@ -98,6 +103,12 @@ namespace MediaBrowser.Model.Net
private static readonly Dictionary<string, string> _extensionLookup = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
// Type application
+ { "application/vnd.comicbook-rar", ".cbr" },
+ { "application/vnd.comicbook+zip", ".cbz" },
+ { "application/x-cb7", ".cb7" },
+ { "application/x-cba", ".cba" },
+ { "application/x-cbr", ".cbr" },
+ { "application/x-cbt", ".cbt" },
{ "application/x-cbz", ".cbz" },
{ "application/x-javascript", ".js" },
{ "application/xml", ".xml" },
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs
index 344ac8971..020e20fb8 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs
+++ b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs
@@ -46,6 +46,8 @@ namespace Jellyfin.MediaEncoding.Tests.Probing
var internalMediaInfoResult = JsonSerializer.Deserialize<InternalMediaInfoResult>(bytes, _jsonOptions);
MediaInfo res = _probeResultNormalizer.GetMediaInfo(internalMediaInfoResult, VideoType.VideoFile, false, "Test Data/Probing/video_metadata.mkv", MediaProtocol.File);
+ Assert.Equal("mkv", res.Container);
+
Assert.Equal(3, res.MediaStreams.Count);
Assert.NotNull(res.VideoStream);
@@ -178,6 +180,21 @@ namespace Jellyfin.MediaEncoding.Tests.Probing
}
[Fact]
+ public void GetMediaInfo_WebM_Success()
+ {
+ var bytes = File.ReadAllBytes("Test Data/Probing/video_webm.json");
+ var internalMediaInfoResult = JsonSerializer.Deserialize<InternalMediaInfoResult>(bytes, _jsonOptions);
+
+ MediaInfo res = _probeResultNormalizer.GetMediaInfo(internalMediaInfoResult, VideoType.VideoFile, false, "Test Data/Probing/video_metadata.webm", MediaProtocol.File);
+
+ Assert.Equal("mkv,webm", res.Container);
+
+ Assert.Equal(2, res.MediaStreams.Count);
+
+ Assert.False(res.MediaStreams[0].IsAVC);
+ }
+
+ [Fact]
public void GetMediaInfo_ProgressiveVideoNoFieldOrder_Success()
{
var bytes = File.ReadAllBytes("Test Data/Probing/video_progressive_no_field_order.json");
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_webm.json b/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_webm.json
new file mode 100644
index 000000000..4cc7db80d
--- /dev/null
+++ b/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_webm.json
@@ -0,0 +1,106 @@
+{
+ "streams": [
+ {
+ "index": 0,
+ "codec_name": "vp8",
+ "codec_long_name": "On2 VP8",
+ "profile": "1",
+ "codec_type": "video",
+ "codec_tag_string": "[0][0][0][0]",
+ "codec_tag": "0x0000",
+ "width": 540,
+ "height": 360,
+ "coded_width": 540,
+ "coded_height": 360,
+ "closed_captions": 0,
+ "film_grain": 0,
+ "has_b_frames": 0,
+ "sample_aspect_ratio": "1:1",
+ "display_aspect_ratio": "3:2",
+ "pix_fmt": "yuv420p",
+ "level": -99,
+ "field_order": "progressive",
+ "refs": 1,
+ "r_frame_rate": "2997/125",
+ "avg_frame_rate": "2997/125",
+ "time_base": "1/1000",
+ "start_pts": 0,
+ "start_time": "0.000000",
+ "disposition": {
+ "default": 1,
+ "dub": 0,
+ "original": 0,
+ "comment": 0,
+ "lyrics": 0,
+ "karaoke": 0,
+ "forced": 0,
+ "hearing_impaired": 0,
+ "visual_impaired": 0,
+ "clean_effects": 0,
+ "attached_pic": 0,
+ "timed_thumbnails": 0,
+ "captions": 0,
+ "descriptions": 0,
+ "metadata": 0,
+ "dependent": 0,
+ "still_image": 0
+ },
+ "tags": {
+ "language": "eng"
+ }
+ },
+ {
+ "index": 1,
+ "codec_name": "vorbis",
+ "codec_long_name": "Vorbis",
+ "codec_type": "audio",
+ "codec_tag_string": "[0][0][0][0]",
+ "codec_tag": "0x0000",
+ "sample_fmt": "fltp",
+ "sample_rate": "44100",
+ "channels": 1,
+ "channel_layout": "mono",
+ "bits_per_sample": 0,
+ "r_frame_rate": "0/0",
+ "avg_frame_rate": "0/0",
+ "time_base": "1/1000",
+ "start_pts": 0,
+ "start_time": "0.000000",
+ "extradata_size": 3097,
+ "disposition": {
+ "default": 1,
+ "dub": 0,
+ "original": 0,
+ "comment": 0,
+ "lyrics": 0,
+ "karaoke": 0,
+ "forced": 0,
+ "hearing_impaired": 0,
+ "visual_impaired": 0,
+ "clean_effects": 0,
+ "attached_pic": 0,
+ "timed_thumbnails": 0,
+ "captions": 0,
+ "descriptions": 0,
+ "metadata": 0,
+ "dependent": 0,
+ "still_image": 0
+ },
+ "tags": {
+ "language": "eng"
+ }
+ }
+ ],
+ "format": {
+ "filename": "sample.webm",
+ "nb_streams": 2,
+ "nb_programs": 0,
+ "format_name": "matroska,webm",
+ "format_long_name": "Matroska / WebM",
+ "start_time": "0.000000",
+ "duration": "117.700914",
+ "size": "8566268",
+ "bit_rate": "582239",
+ "probe_score": 100
+ }
+}
diff --git a/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs b/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs
index 2f84fa544..d9dceee55 100644
--- a/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs
+++ b/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs
@@ -28,7 +28,7 @@ namespace Jellyfin.Model.Tests
[InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
[InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported | TranscodeReason.SecondaryAudioNotSupported, "Transcode")]
[InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported, "Remux")] // #6450
- [InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+ [InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported | TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
// Firefox
[InlineData("Firefox", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450
@@ -39,7 +39,7 @@ namespace Jellyfin.Model.Tests
[InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
[InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported | TranscodeReason.SecondaryAudioNotSupported, "Transcode")]
[InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported, "Remux")] // #6450
- [InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+ [InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported | TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
// Safari
[InlineData("SafariNext", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450
@@ -90,7 +90,7 @@ namespace Jellyfin.Model.Tests
[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 | TranscodeReason.SecondaryAudioNotSupported, "Transcode", "http")]
[InlineData("Chrome-NoHLS", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported, "Remux")] // #6450
- [InlineData("Chrome-NoHLS", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+ [InlineData("Chrome-NoHLS", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported | TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Chrome-NoHLS", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
// TranscodeMedia
[InlineData("TranscodeMedia", "mp4-h264-aac-vtt-2600k", PlayMethod.Transcode, TranscodeReason.DirectPlayError, "Remux", "HLS.mp4")]
@@ -178,7 +178,7 @@ namespace Jellyfin.Model.Tests
[InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
[InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")]
[InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported, "Remux")] // #6450
- [InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+ [InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported | TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
// Firefox
[InlineData("Firefox", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450
@@ -188,7 +188,7 @@ namespace Jellyfin.Model.Tests
[InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
[InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")]
[InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported, "Remux")] // #6450
- [InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
+ [InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported | TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
// Safari
[InlineData("SafariNext", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450
diff --git a/tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs b/tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs
index 371c3811a..a18a85ec0 100644
--- a/tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs
+++ b/tests/Jellyfin.Model.Tests/Net/MimeTypesTests.cs
@@ -6,6 +6,11 @@ namespace Jellyfin.Model.Tests.Net
public class MimeTypesTests
{
[Theory]
+ [InlineData(".cb7", "application/x-cb7")]
+ [InlineData(".cba", "application/x-cba")]
+ [InlineData(".cbr", "application/vnd.comicbook-rar")]
+ [InlineData(".cbt", "application/x-cbt")]
+ [InlineData(".cbz", "application/vnd.comicbook+zip")]
[InlineData(".dll", "application/octet-stream")]
[InlineData(".log", "text/plain")]
[InlineData(".srt", "application/x-subrip")]
@@ -69,7 +74,7 @@ namespace Jellyfin.Model.Tests.Net
[InlineData(".dsp", "audio/dsp")]
[InlineData(".flac", "audio/flac")]
[InlineData(".m4a", "audio/mp4")]
- [InlineData(".m4b", "audio/m4b")]
+ [InlineData(".m4b", "audio/mp4")]
[InlineData(".mid", "audio/midi")]
[InlineData(".midi", "audio/midi")]
[InlineData(".mp3", "audio/mpeg")]
@@ -94,10 +99,16 @@ namespace Jellyfin.Model.Tests.Net
[InlineData("application/pdf", ".pdf")]
[InlineData("application/ttml+xml", ".ttml")]
[InlineData("application/vnd.amazon.ebook", ".azw")]
+ [InlineData("application/vnd.comicbook-rar", ".cbr")]
+ [InlineData("application/vnd.comicbook+zip", ".cbz")]
[InlineData("application/vnd.ms-fontobject", ".eot")]
[InlineData("application/vnd.rar", ".rar")]
[InlineData("application/wasm", ".wasm")]
[InlineData("application/x-7z-compressed", ".7z")]
+ [InlineData("application/x-cb7", ".cb7")]
+ [InlineData("application/x-cba", ".cba")]
+ [InlineData("application/x-cbr", ".cbr")]
+ [InlineData("application/x-cbt", ".cbt")]
[InlineData("application/x-cbz", ".cbz")]
[InlineData("application/x-javascript", ".js")]
[InlineData("application/x-mobipocket-ebook", ".mobi")]
diff --git a/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-av1-aac-srt-2600k.json b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-av1-aac-srt-2600k.json
index da185aacf..e528281bd 100644
--- a/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-av1-aac-srt-2600k.json
+++ b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-av1-aac-srt-2600k.json
@@ -1,7 +1,7 @@
{
"Id": "a766d122b58e45d9492d17af66748bf5",
"Path": "/Media/MyVideo-720p.mkv",
- "Container": "mkv,webm",
+ "Container": "mkv",
"Size": 835317696,
"Name": "MyVideo-1080p",
"ETag": "579a34c6d5dfb23f61539a51220b6a23",
diff --git a/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-vp9-aac-srt-2600k.json b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-vp9-aac-srt-2600k.json
index 0a85a1353..8ef10ae87 100644
--- a/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-vp9-aac-srt-2600k.json
+++ b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-vp9-aac-srt-2600k.json
@@ -1,7 +1,7 @@
{
"Id": "a766d122b58e45d9492d17af66748bf5",
"Path": "/Media/MyVideo-720p.mkv",
- "Container": "mkv,webm",
+ "Container": "mkv",
"Size": 835317696,
"Name": "MyVideo-1080p",
"ETag": "579a34c6d5dfb23f61539a51220b6a23",
diff --git a/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-vp9-ac3-srt-2600k.json b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-vp9-ac3-srt-2600k.json
index 2b932ff52..80a9f4103 100644
--- a/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-vp9-ac3-srt-2600k.json
+++ b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mkv-vp9-ac3-srt-2600k.json
@@ -1,7 +1,7 @@
{
"Id": "a766d122b58e45d9492d17af66748bf5",
"Path": "/Media/MyVideo-720p.mkv",
- "Container": "mkv,webm",
+ "Container": "mkv",
"Size": 835317696,
"Name": "MyVideo-1080p",
"ETag": "579a34c6d5dfb23f61539a51220b6a23",