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??