diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
index 29cb3d2f9f..cb73ad7657 100644
--- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
+++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
@@ -323,6 +323,11 @@ public class TranscodingJobHelper : IDisposable
if (delete(job.Path!))
{
await DeletePartialStreamFiles(job.Path!, job.Type, 0, 1500).ConfigureAwait(false);
+ if (job.MediaSource?.VideoType == VideoType.Dvd || job.MediaSource?.VideoType == VideoType.BluRay)
+ {
+ var path = Path.GetDirectoryName(job.Path) + "/" + job.MediaSource.Id + ".concat";
+ File.Delete(path);
+ }
}
if (closeLiveStream && !string.IsNullOrWhiteSpace(job.LiveStreamId))
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index b3a1b2a991..7c4892ea50 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -941,8 +941,18 @@ namespace MediaBrowser.Controller.MediaEncoding
arg.Append(canvasArgs);
}
- arg.Append(" -i ")
- .Append(GetInputPathArgument(state));
+ if (state.MediaSource.VideoType == VideoType.Dvd || state.MediaSource.VideoType == VideoType.BluRay)
+ {
+ var tmpConcatPath = options.TranscodingTempPath + "/" + state.MediaSource.Id + ".concat";
+ _mediaEncoder.GenerateConcatConfig(state.MediaSource, tmpConcatPath);
+ arg.Append(" -f concat -safe 0 ");
+ arg.Append(" -i " + tmpConcatPath + " ");
+ }
+ else
+ {
+ arg.Append(" -i ")
+ .Append(GetInputPathArgument(state));
+ }
// sub2video for external graphical subtitles
if (state.SubtitleStream is not null
diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
index c34ce5d29b..716adc8f0b 100644
--- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
+++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
@@ -211,5 +211,12 @@ namespace MediaBrowser.Controller.MediaEncoding
/// The title number to start with.
/// A playlist.
IEnumerable GetPrimaryPlaylistM2TsFiles(string path, uint? titleNumber);
+
+ ///
+ /// Generates a FFmpeg concat config for the source.
+ ///
+ /// The .
+ /// The path the config should be written to.
+ void GenerateConcatConfig(MediaSourceInfo source, string concatFilePath);
}
}
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index df31ba83e5..f4e6ea4285 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -918,6 +918,46 @@ namespace MediaBrowser.MediaEncoding.Encoder
.Select(f => f.FullName);
}
+ public void GenerateConcatConfig(MediaSourceInfo source, string concatFilePath)
+ {
+ var files = new List();
+ var videoType = source.VideoType;
+ if (videoType == VideoType.Dvd)
+ {
+ files = GetPrimaryPlaylistVobFiles(source.Path, null).ToList();
+ }
+ else if (videoType == VideoType.BluRay)
+ {
+ files = GetPrimaryPlaylistM2TsFiles(source.Path, null).ToList();
+ }
+
+ var lines = new List();
+
+ foreach (var path in files)
+ {
+ var fileinfo = _fileSystem.GetFileInfo(path);
+ var mediaInfoResult = GetMediaInfo(
+ new MediaInfoRequest
+ {
+ 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;
+
+ lines.Add("file " + "'" + path + "'");
+ lines.Add("duration " + duration);
+ }
+
+ File.WriteAllLinesAsync(concatFilePath, lines, CancellationToken.None).GetAwaiter().GetResult();
+ }
+
public bool CanExtractSubtitles(string codec)
{
// TODO is there ever a case when a subtitle can't be extracted??