diff options
Diffstat (limited to 'MediaBrowser.MediaEncoding')
10 files changed, 256 insertions, 62 deletions
diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index a82f2108f..f02999370 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -269,7 +269,6 @@ namespace MediaBrowser.MediaEncoding.Attachments if (disposing) { - } _disposed = true; diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs index 3260f3051..ccfae2fa5 100644 --- a/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs +++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs @@ -9,7 +9,7 @@ using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.MediaEncoding.BdInfo { /// <summary> - /// Class BdInfoExaminer + /// Class BdInfoExaminer. /// </summary> public class BdInfoExaminer : IBlurayExaminer { @@ -41,7 +41,7 @@ namespace MediaBrowser.MediaEncoding.BdInfo var outputStream = new BlurayDiscInfo { - MediaStreams = new MediaStream[] { } + MediaStreams = Array.Empty<MediaStream>() }; if (playlist == null) diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index 6e036d24c..0fd0239b4 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -14,23 +14,45 @@ namespace MediaBrowser.MediaEncoding.Encoder private static readonly string[] requiredDecoders = new[] { + "h264", + "hevc", "mpeg2video", + "mpeg4", + "msmpeg4", + "dts", + "ac3", + "aac", + "mp3", "h264_qsv", "hevc_qsv", "mpeg2_qsv", - "mpeg2_mmal", - "mpeg4_mmal", "vc1_qsv", - "vc1_mmal", + "vp8_qsv", + "vp9_qsv", "h264_cuvid", "hevc_cuvid", - "dts", - "ac3", - "aac", - "mp3", - "h264", + "mpeg2_cuvid", + "vc1_cuvid", + "mpeg4_cuvid", + "vp8_cuvid", + "vp9_cuvid", "h264_mmal", - "hevc" + "mpeg2_mmal", + "mpeg4_mmal", + "vc1_mmal", + "h264_mediacodec", + "hevc_mediacodec", + "mpeg2_mediacodec", + "mpeg4_mediacodec", + "vp8_mediacodec", + "vp9_mediacodec", + "h264_opencl", + "hevc_opencl", + "mpeg2_opencl", + "mpeg4_opencl", + "vp8_opencl", + "vp9_opencl", + "vc1_opencl" }; private static readonly string[] requiredEncoders = new[] @@ -43,22 +65,24 @@ namespace MediaBrowser.MediaEncoding.Encoder "libvpx-vp9", "aac", "libfdk_aac", + "ac3", "libmp3lame", "libopus", "libvorbis", "srt", - "h264_nvenc", - "hevc_nvenc", + "h264_amf", + "hevc_amf", "h264_qsv", "hevc_qsv", - "h264_omx", - "hevc_omx", + "h264_nvenc", + "hevc_nvenc", "h264_vaapi", "hevc_vaapi", + "h264_omx", + "hevc_omx", "h264_v4l2m2m", - "ac3", - "h264_amf", - "hevc_amf" + "h264_videotoolbox", + "hevc_videotoolbox" }; // Try and use the individual library versions to determine a FFmpeg version @@ -66,6 +90,7 @@ namespace MediaBrowser.MediaEncoding.Encoder // $ ffmpeg -version | perl -ne ' print "$1=$2.$3," if /^(lib\w+)\s+(\d+)\.\s*(\d+)/' private static readonly IReadOnlyDictionary<string, Version> _ffmpegVersionMap = new Dictionary<string, Version> { + { "libavutil=56.51,libavcodec=58.91,libavformat=58.45,libavdevice=58.10,libavfilter=7.85,libswscale=5.7,libswresample=3.7,libpostproc=55.7,", new Version(4, 3) }, { "libavutil=56.31,libavcodec=58.54,libavformat=58.29,libavdevice=58.8,libavfilter=7.57,libswscale=5.5,libswresample=3.5,libpostproc=55.5,", new Version(4, 2) }, { "libavutil=56.22,libavcodec=58.35,libavformat=58.20,libavdevice=58.5,libavfilter=7.40,libswscale=5.3,libswresample=3.3,libpostproc=55.3,", new Version(4, 1) }, { "libavutil=56.14,libavcodec=58.18,libavformat=58.12,libavdevice=58.3,libavfilter=7.16,libswscale=5.1,libswresample=3.1,libpostproc=55.1,", new Version(4, 0) }, @@ -159,6 +184,8 @@ namespace MediaBrowser.MediaEncoding.Encoder public IEnumerable<string> GetEncoders() => GetCodecs(Codec.Encoder); + public IEnumerable<string> GetHwaccels() => GetHwaccelTypes(); + /// <summary> /// Using the output from "ffmpeg -version" work out the FFmpeg version. /// For pre-built binaries the first line should contain a string like "ffmpeg version x.y", which is easy @@ -218,6 +245,29 @@ namespace MediaBrowser.MediaEncoding.Encoder Decoder } + private IEnumerable<string> GetHwaccelTypes() + { + string output = null; + try + { + output = GetProcessOutput(_encoderPath, "-hwaccels"); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error detecting available hwaccel types"); + } + + if (string.IsNullOrWhiteSpace(output)) + { + return Enumerable.Empty<string>(); + } + + var found = output.Split(new char[] {'\r','\n'}, StringSplitOptions.RemoveEmptyEntries).Skip(1).Distinct().ToList(); + _logger.LogInformation("Available hwaccel types: {Types}", found); + + return found; + } + private IEnumerable<string> GetCodecs(Codec codec) { string codecstr = codec == Codec.Encoder ? "encoders" : "decoders"; diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 26bd202ba..62fdbc618 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -21,11 +21,12 @@ using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; using System.Diagnostics; +using Microsoft.Extensions.Configuration; namespace MediaBrowser.MediaEncoding.Encoder { /// <summary> - /// Class MediaEncoder + /// Class MediaEncoder. /// </summary> public class MediaEncoder : IMediaEncoder, IDisposable { @@ -46,7 +47,7 @@ namespace MediaBrowser.MediaEncoding.Encoder private readonly object _runningProcessesLock = new object(); private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>(); - private string _ffmpegPath; + private string _ffmpegPath = string.Empty; private string _ffprobePath; public MediaEncoder( @@ -55,14 +56,14 @@ namespace MediaBrowser.MediaEncoding.Encoder IFileSystem fileSystem, ILocalizationManager localization, Lazy<EncodingHelper> encodingHelperFactory, - string startupOptionsFFmpegPath) + IConfiguration config) { _logger = logger; _configurationManager = configurationManager; _fileSystem = fileSystem; _localization = localization; _encodingHelperFactory = encodingHelperFactory; - _startupOptionFFmpegPath = startupOptionsFFmpegPath; + _startupOptionFFmpegPath = config.GetValue<string>(Controller.Extensions.ConfigurationExtensions.FfmpegPathKey) ?? string.Empty; } private EncodingHelper EncodingHelper => _encodingHelperFactory.Value; @@ -111,6 +112,7 @@ namespace MediaBrowser.MediaEncoding.Encoder SetAvailableDecoders(validator.GetDecoders()); SetAvailableEncoders(validator.GetEncoders()); + SetAvailableHwaccels(validator.GetHwaccels()); } _logger.LogInformation("FFmpeg: {EncoderLocation}: {FfmpegPath}", EncoderLocation, _ffmpegPath ?? string.Empty); @@ -165,7 +167,7 @@ namespace MediaBrowser.MediaEncoding.Encoder /// Validates the supplied FQPN to ensure it is a ffmpeg utility. /// If checks pass, global variable FFmpegPath and EncoderLocation are updated. /// </summary> - /// <param name="path">FQPN to test</param> + /// <param name="path">FQPN to test.</param> /// <param name="location">Location (External, Custom, System) of tool</param> /// <returns></returns> private bool ValidatePath(string path, FFmpegLocation location) @@ -228,6 +230,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { return inJellyfinPath; } + var values = Environment.GetEnvironmentVariable("PATH"); foreach (var path in values.Split(Path.PathSeparator)) @@ -247,14 +250,21 @@ namespace MediaBrowser.MediaEncoding.Encoder public void SetAvailableEncoders(IEnumerable<string> list) { _encoders = list.ToList(); - //_logger.Info("Supported encoders: {0}", string.Join(",", list.ToArray())); + // _logger.Info("Supported encoders: {0}", string.Join(",", list.ToArray())); } private List<string> _decoders = new List<string>(); public void SetAvailableDecoders(IEnumerable<string> list) { _decoders = list.ToList(); - //_logger.Info("Supported decoders: {0}", string.Join(",", list.ToArray())); + // _logger.Info("Supported decoders: {0}", string.Join(",", list.ToArray())); + } + + private List<string> _hwaccels = new List<string>(); + public void SetAvailableHwaccels(IEnumerable<string> list) + { + _hwaccels = list.ToList(); + //_logger.Info("Supported hwaccels: {0}", string.Join(",", list.ToArray())); } public bool SupportsEncoder(string encoder) @@ -267,6 +277,11 @@ namespace MediaBrowser.MediaEncoding.Encoder return _decoders.Contains(decoder, StringComparer.OrdinalIgnoreCase); } + public bool SupportsHwaccel(string hwaccel) + { + return _hwaccels.Contains(hwaccel, StringComparer.OrdinalIgnoreCase); + } + public bool CanEncodeToAudioCodec(string codec) { if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase)) @@ -425,7 +440,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } /// <summary> - /// The us culture + /// The us culture. /// </summary> protected readonly CultureInfo UsCulture = new CultureInfo("en-US"); @@ -500,11 +515,11 @@ namespace MediaBrowser.MediaEncoding.Encoder break; case Video3DFormat.FullSideBySide: vf = "crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale=600:trunc(600/dar/2)*2"; - //fsbs crop width in half,set the display aspect,crop out any black bars we may have made the scale width to 600. + // fsbs crop width in half,set the display aspect,crop out any black bars we may have made the scale width to 600. break; case Video3DFormat.HalfTopAndBottom: vf = "crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale=600:trunc(600/dar/2)*2"; - //htab crop heigh in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to 600 + // htab crop heigh in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to 600 break; case Video3DFormat.FullTopAndBottom: vf = "crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale=600:trunc(600/dar/2)*2"; @@ -920,7 +935,6 @@ namespace MediaBrowser.MediaEncoding.Encoder var fileParts = _fileSystem.GetFileNameWithoutExtension(f).Split('_'); return fileParts.Length == 3 && string.Equals(title, fileParts[1], StringComparison.OrdinalIgnoreCase); - }).ToList(); // If this resulted in not getting any vobs, just take them all diff --git a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs index 78dc7b607..3aa296f7f 100644 --- a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs +++ b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs @@ -35,7 +35,7 @@ namespace MediaBrowser.MediaEncoding.Probing } /// <summary> - /// Gets a string from an FFProbeResult tags dictionary + /// Gets a string from an FFProbeResult tags dictionary. /// </summary> /// <param name="tags">The tags.</param> /// <param name="key">The key.</param> @@ -52,7 +52,7 @@ namespace MediaBrowser.MediaEncoding.Probing } /// <summary> - /// Gets an int from an FFProbeResult tags dictionary + /// Gets an int from an FFProbeResult tags dictionary. /// </summary> /// <param name="tags">The tags.</param> /// <param name="key">The key.</param> @@ -73,7 +73,7 @@ namespace MediaBrowser.MediaEncoding.Probing } /// <summary> - /// Gets a DateTime from an FFProbeResult tags dictionary + /// Gets a DateTime from an FFProbeResult tags dictionary. /// </summary> /// <param name="tags">The tags.</param> /// <param name="key">The key.</param> @@ -94,7 +94,7 @@ namespace MediaBrowser.MediaEncoding.Probing } /// <summary> - /// Converts a dictionary to case insensitive + /// Converts a dictionary to case insensitive. /// </summary> /// <param name="dict">The dict.</param> /// <returns>Dictionary{System.StringSystem.String}.</returns> diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 9386d6524..c85d963ed 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -93,6 +93,7 @@ namespace MediaBrowser.MediaEncoding.Probing { overview = FFProbeHelpers.GetDictionaryValue(tags, "description"); } + if (string.IsNullOrWhiteSpace(overview)) { overview = FFProbeHelpers.GetDictionaryValue(tags, "desc"); @@ -274,10 +275,12 @@ namespace MediaBrowser.MediaEncoding.Probing reader.Read(); continue; } + using (var subtree = reader.ReadSubtree()) { ReadFromDictNode(subtree, info); } + break; default: reader.Skip(); @@ -319,6 +322,7 @@ namespace MediaBrowser.MediaEncoding.Probing { ProcessPairs(currentKey, pairs, info); } + currentKey = reader.ReadElementContentAsString(); pairs = new List<NameValuePair>(); break; @@ -332,6 +336,7 @@ namespace MediaBrowser.MediaEncoding.Probing Value = value }); } + break; case "array": if (reader.IsEmptyElement) @@ -339,6 +344,7 @@ namespace MediaBrowser.MediaEncoding.Probing reader.Read(); continue; } + using (var subtree = reader.ReadSubtree()) { if (!string.IsNullOrWhiteSpace(currentKey)) @@ -346,6 +352,7 @@ namespace MediaBrowser.MediaEncoding.Probing pairs.AddRange(ReadValueArray(subtree)); } } + break; default: reader.Skip(); @@ -381,6 +388,7 @@ namespace MediaBrowser.MediaEncoding.Probing reader.Read(); continue; } + using (var subtree = reader.ReadSubtree()) { var dict = GetNameValuePair(subtree); @@ -389,6 +397,7 @@ namespace MediaBrowser.MediaEncoding.Probing pairs.Add(dict); } } + break; default: reader.Skip(); @@ -413,7 +422,6 @@ namespace MediaBrowser.MediaEncoding.Probing .Where(i => !string.IsNullOrWhiteSpace(i)) .Distinct(StringComparer.OrdinalIgnoreCase) .ToArray(); - } else if (string.Equals(key, "screenwriters", StringComparison.OrdinalIgnoreCase)) { @@ -425,7 +433,6 @@ namespace MediaBrowser.MediaEncoding.Probing Type = PersonType.Writer }); } - } else if (string.Equals(key, "producers", StringComparison.OrdinalIgnoreCase)) { @@ -517,7 +524,7 @@ namespace MediaBrowser.MediaEncoding.Probing } /// <summary> - /// Converts ffprobe stream info to our MediaAttachment class + /// Converts ffprobe stream info to our MediaAttachment class. /// </summary> /// <param name="streamInfo">The stream info.</param> /// <returns>MediaAttachments.</returns> @@ -550,7 +557,7 @@ namespace MediaBrowser.MediaEncoding.Probing } /// <summary> - /// Converts ffprobe stream info to our MediaStream class + /// Converts ffprobe stream info to our MediaStream class. /// </summary> /// <param name="isAudio">if set to <c>true</c> [is info].</param> /// <param name="streamInfo">The stream info.</param> @@ -562,7 +569,7 @@ namespace MediaBrowser.MediaEncoding.Probing if (string.Equals(streamInfo.CodecName, "mov_text", StringComparison.OrdinalIgnoreCase)) { // Edit: but these are also sometimes subtitles? - //return null; + // return null; } var stream = new MediaStream @@ -684,7 +691,7 @@ namespace MediaBrowser.MediaEncoding.Probing stream.BitDepth = streamInfo.BitsPerRawSample; } - //stream.IsAnamorphic = string.Equals(streamInfo.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase) || + // stream.IsAnamorphic = string.Equals(streamInfo.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase) || // string.Equals(stream.AspectRatio, "2.35:1", StringComparison.OrdinalIgnoreCase) || // string.Equals(stream.AspectRatio, "2.40:1", StringComparison.OrdinalIgnoreCase); @@ -769,7 +776,7 @@ namespace MediaBrowser.MediaEncoding.Probing } /// <summary> - /// Gets a string from an FFProbeResult tags dictionary + /// Gets a string from an FFProbeResult tags dictionary. /// </summary> /// <param name="tags">The tags.</param> /// <param name="key">The key.</param> @@ -950,11 +957,12 @@ namespace MediaBrowser.MediaEncoding.Probing { peoples.Add(new BaseItemPerson { Name = person, Type = PersonType.Composer }); } + audio.People = peoples.ToArray(); } - //var conductor = FFProbeHelpers.GetDictionaryValue(tags, "conductor"); - //if (!string.IsNullOrWhiteSpace(conductor)) + // var conductor = FFProbeHelpers.GetDictionaryValue(tags, "conductor"); + // if (!string.IsNullOrWhiteSpace(conductor)) //{ // foreach (var person in Split(conductor, false)) // { @@ -962,8 +970,8 @@ namespace MediaBrowser.MediaEncoding.Probing // } //} - //var lyricist = FFProbeHelpers.GetDictionaryValue(tags, "lyricist"); - //if (!string.IsNullOrWhiteSpace(lyricist)) + // var lyricist = FFProbeHelpers.GetDictionaryValue(tags, "lyricist"); + // if (!string.IsNullOrWhiteSpace(lyricist)) //{ // foreach (var person in Split(lyricist, false)) // { @@ -981,6 +989,7 @@ namespace MediaBrowser.MediaEncoding.Probing { peoples.Add(new BaseItemPerson { Name = person, Type = PersonType.Writer }); } + audio.People = peoples.ToArray(); } @@ -999,7 +1008,7 @@ namespace MediaBrowser.MediaEncoding.Probing var artist = FFProbeHelpers.GetDictionaryValue(tags, "artist"); if (string.IsNullOrWhiteSpace(artist)) { - audio.Artists = new string[] { }; + audio.Artists = Array.Empty<string>(); } else { @@ -1014,6 +1023,7 @@ namespace MediaBrowser.MediaEncoding.Probing { albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "album artist"); } + if (string.IsNullOrWhiteSpace(albumArtist)) { albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "album_artist"); @@ -1021,14 +1031,13 @@ namespace MediaBrowser.MediaEncoding.Probing if (string.IsNullOrWhiteSpace(albumArtist)) { - audio.AlbumArtists = new string[] { }; + audio.AlbumArtists = Array.Empty<string>(); } else { audio.AlbumArtists = SplitArtists(albumArtist, _nameDelimiters, true) .DistinctNames() .ToArray(); - } if (audio.AlbumArtists.Length == 0) @@ -1056,23 +1065,43 @@ namespace MediaBrowser.MediaEncoding.Probing // These support mulitple values, but for now we only store the first. var mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Artist Id")); - if (mb == null) mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ALBUMARTISTID")); + if (mb == null) + { + mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ALBUMARTISTID")); + } + audio.SetProviderId(MetadataProvider.MusicBrainzAlbumArtist, mb); mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Artist Id")); - if (mb == null) mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ARTISTID")); + if (mb == null) + { + mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ARTISTID")); + } + audio.SetProviderId(MetadataProvider.MusicBrainzArtist, mb); mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Id")); - if (mb == null) mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ALBUMID")); + if (mb == null) + { + mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ALBUMID")); + } + audio.SetProviderId(MetadataProvider.MusicBrainzAlbum, mb); mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Group Id")); - if (mb == null) mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_RELEASEGROUPID")); + if (mb == null) + { + mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_RELEASEGROUPID")); + } + audio.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, mb); mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Track Id")); - if (mb == null) mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_RELEASETRACKID")); + if (mb == null) + { + mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_RELEASETRACKID")); + } + audio.SetProviderId(MetadataProvider.MusicBrainzTrack, mb); } @@ -1157,7 +1186,7 @@ namespace MediaBrowser.MediaEncoding.Probing } /// <summary> - /// Gets the studios from the tags collection + /// Gets the studios from the tags collection. /// </summary> /// <param name="info">The info.</param> /// <param name="tags">The tags.</param> @@ -1178,6 +1207,7 @@ namespace MediaBrowser.MediaEncoding.Probing { continue; } + if (info.AlbumArtists.Contains(studio, StringComparer.OrdinalIgnoreCase)) { continue; @@ -1194,7 +1224,7 @@ namespace MediaBrowser.MediaEncoding.Probing } /// <summary> - /// Gets the genres from the tags collection + /// Gets the genres from the tags collection. /// </summary> /// <param name="info">The information.</param> /// <param name="tags">The tags.</param> @@ -1354,14 +1384,18 @@ namespace MediaBrowser.MediaEncoding.Probing description = string.Join(" ", numbers, 1, numbers.Length - 1).Trim(); // Skip the first, concatenate the rest, clean up spaces and save it } else + { throw new Exception(); // Switch to default parsing + } } catch // Default parsing { if (subtitle.Contains(".")) // skip the comment, keep the subtitle description = string.Join(".", subtitle.Split('.'), 1, subtitle.Split('.').Length - 1).Trim(); // skip the first else + { description = subtitle.Trim(); // Clean up whitespaces and save it + } } } } diff --git a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs b/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs index f44cf1452..0e2d70017 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs @@ -23,6 +23,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles string line; while (reader.ReadLine() != "[Events]") { } + var headers = ParseFieldHeaders(reader.ReadLine()); while ((line = reader.ReadLine()) != null) @@ -56,6 +57,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles trackEvents.Add(subEvent); } } + trackInfo.TrackEvents = trackEvents.ToArray(); return trackInfo; } @@ -112,11 +114,13 @@ namespace MediaBrowser.MediaEncoding.Subtitles { pre = s.Substring(0, 5) + "}"; } + int indexOfEnd = p.Text.IndexOf('}'); p.Text = p.Text.Remove(indexOfBegin, (indexOfEnd - indexOfBegin) + 1); indexOfBegin = p.Text.IndexOf('{'); } + p.Text = pre + p.Text; } } diff --git a/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs index c98dd1502..728efa788 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs @@ -35,6 +35,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles { continue; } + var subEvent = new SubtitleTrackEvent { Id = line }; line = reader.ReadLine(); @@ -52,11 +53,15 @@ namespace MediaBrowser.MediaEncoding.Subtitles _logger.LogWarning("Unrecognized line in srt: {0}", line); continue; } + subEvent.StartPositionTicks = GetTicks(time[0]); var endTime = time[1]; var idx = endTime.IndexOf(" ", StringComparison.Ordinal); if (idx > 0) + { endTime = endTime.Substring(0, idx); + } + subEvent.EndPositionTicks = GetTicks(endTime); var multiline = new List<string>(); while ((line = reader.ReadLine()) != null) @@ -65,8 +70,10 @@ namespace MediaBrowser.MediaEncoding.Subtitles { break; } + multiline.Add(line); } + subEvent.Text = string.Join(ParserValues.NewLine, multiline); subEvent.Text = subEvent.Text.Replace(@"\N", ParserValues.NewLine, StringComparison.OrdinalIgnoreCase); subEvent.Text = Regex.Replace(subEvent.Text, @"\{(?:\\\d?[\w.-]+(?:\([^\)]*\)|&H?[0-9A-Fa-f]+&|))+\}", string.Empty, RegexOptions.IgnoreCase); @@ -76,6 +83,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles trackEvents.Add(subEvent); } } + trackInfo.TrackEvents = trackEvents.ToArray(); return trackInfo; } diff --git a/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs index b94d45165..77033c6b4 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs @@ -41,7 +41,9 @@ namespace MediaBrowser.MediaEncoding.Subtitles lineNumber++; if (!eventsStarted) + { header.AppendLine(line); + } if (line.Trim().ToLowerInvariant() == "[events]") { @@ -62,17 +64,29 @@ namespace MediaBrowser.MediaEncoding.Subtitles for (int i = 0; i < format.Length; i++) { if (format[i].Trim().ToLowerInvariant() == "layer") + { indexLayer = i; + } else if (format[i].Trim().ToLowerInvariant() == "start") + { indexStart = i; + } else if (format[i].Trim().ToLowerInvariant() == "end") + { indexEnd = i; + } else if (format[i].Trim().ToLowerInvariant() == "text") + { indexText = i; + } else if (format[i].Trim().ToLowerInvariant() == "effect") + { indexEffect = i; + } else if (format[i].Trim().ToLowerInvariant() == "style") + { indexStyle = i; + } } } } @@ -89,28 +103,48 @@ namespace MediaBrowser.MediaEncoding.Subtitles string[] splittedLine; if (s.StartsWith("dialogue:")) + { splittedLine = line.Substring(10).Split(','); + } else + { splittedLine = line.Split(','); + } for (int i = 0; i < splittedLine.Length; i++) { if (i == indexStart) + { start = splittedLine[i].Trim(); + } else if (i == indexEnd) + { end = splittedLine[i].Trim(); + } else if (i == indexLayer) + { layer = splittedLine[i]; + } else if (i == indexEffect) + { effect = splittedLine[i]; + } else if (i == indexText) + { text = splittedLine[i]; + } else if (i == indexStyle) + { style = splittedLine[i]; + } else if (i == indexName) + { name = splittedLine[i]; + } else if (i > indexText) + { text += "," + splittedLine[i]; + } } try @@ -130,11 +164,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles } } - //if (header.Length > 0) - //subtitle.Header = header.ToString(); + // if (header.Length > 0) + // subtitle.Header = header.ToString(); - //subtitle.Renumber(1); + // subtitle.Renumber(1); } + trackInfo.TrackEvents = trackEvents.ToArray(); return trackInfo; } @@ -168,15 +203,23 @@ namespace MediaBrowser.MediaEncoding.Subtitles CheckAndAddSubTags(ref fontName, ref extraTags, out italic); text = text.Remove(start, end - start + 1); if (italic) + { text = text.Insert(start, "<font face=\"" + fontName + "\"" + extraTags + "><i>"); + } else + { text = text.Insert(start, "<font face=\"" + fontName + "\"" + extraTags + ">"); + } int indexOfEndTag = text.IndexOf("{\\fn}", start); if (indexOfEndTag > 0) + { text = text.Remove(indexOfEndTag, "{\\fn}".Length).Insert(indexOfEndTag, "</font>"); + } else + { text += "</font>"; + } } } @@ -193,15 +236,23 @@ namespace MediaBrowser.MediaEncoding.Subtitles { text = text.Remove(start, end - start + 1); if (italic) + { text = text.Insert(start, "<font size=\"" + fontSize + "\"" + extraTags + "><i>"); + } else + { text = text.Insert(start, "<font size=\"" + fontSize + "\"" + extraTags + ">"); + } int indexOfEndTag = text.IndexOf("{\\fs}", start); if (indexOfEndTag > 0) + { text = text.Remove(indexOfEndTag, "{\\fs}".Length).Insert(indexOfEndTag, "</font>"); + } else + { text += "</font>"; + } } } } @@ -225,14 +276,22 @@ namespace MediaBrowser.MediaEncoding.Subtitles text = text.Remove(start, end - start + 1); if (italic) + { text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + "><i>"); + } else + { text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + ">"); + } int indexOfEndTag = text.IndexOf("{\\c}", start); if (indexOfEndTag > 0) + { text = text.Remove(indexOfEndTag, "{\\c}".Length).Insert(indexOfEndTag, "</font>"); + } else + { text += "</font>"; + } } } @@ -255,32 +314,41 @@ namespace MediaBrowser.MediaEncoding.Subtitles text = text.Remove(start, end - start + 1); if (italic) + { text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + "><i>"); + } else + { text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + ">"); + } text += "</font>"; } } - } text = text.Replace(@"{\i1}", "<i>"); text = text.Replace(@"{\i0}", "</i>"); text = text.Replace(@"{\i}", "</i>"); if (CountTagInText(text, "<i>") > CountTagInText(text, "</i>")) + { text += "</i>"; + } text = text.Replace(@"{\u1}", "<u>"); text = text.Replace(@"{\u0}", "</u>"); text = text.Replace(@"{\u}", "</u>"); if (CountTagInText(text, "<u>") > CountTagInText(text, "</u>")) + { text += "</u>"; + } text = text.Replace(@"{\b1}", "<b>"); text = text.Replace(@"{\b0}", "</b>"); text = text.Replace(@"{\b}", "</b>"); if (CountTagInText(text, "<b>") > CountTagInText(text, "</b>")) + { text += "</b>"; + } return text; } @@ -288,7 +356,10 @@ namespace MediaBrowser.MediaEncoding.Subtitles private static bool IsInteger(string s) { if (int.TryParse(s, out var i)) + { return true; + } + return false; } @@ -300,9 +371,13 @@ namespace MediaBrowser.MediaEncoding.Subtitles { count++; if (index == text.Length) + { return count; + } + index = text.IndexOf(tag, index + 1); } + return count; } @@ -330,6 +405,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles { rest = string.Empty; } + extraTags += " size=\"" + fontSize.Substring(2) + "\""; } else if (rest.StartsWith("fn") && rest.Length > 2) @@ -345,6 +421,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles { rest = string.Empty; } + extraTags += " face=\"" + fontName.Substring(2) + "\""; } else if (rest.StartsWith("c") && rest.Length > 2) diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index f08af6045..f1aa8ea5f 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -115,6 +115,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles { throw new ArgumentNullException(nameof(item)); } + if (string.IsNullOrWhiteSpace(mediaSourceId)) { throw new ArgumentNullException(nameof(mediaSourceId)); @@ -271,8 +272,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles } public string Path { get; set; } + public MediaProtocol Protocol { get; set; } + public string Format { get; set; } + public bool IsExternal { get; set; } } @@ -287,10 +291,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles { return new SrtParser(_logger); } + if (string.Equals(format, SubtitleFormat.SSA, StringComparison.OrdinalIgnoreCase)) { return new SsaParser(); } + if (string.Equals(format, SubtitleFormat.ASS, StringComparison.OrdinalIgnoreCase)) { return new AssParser(); @@ -315,14 +321,17 @@ namespace MediaBrowser.MediaEncoding.Subtitles { return new JsonWriter(); } + if (string.Equals(format, SubtitleFormat.SRT, StringComparison.OrdinalIgnoreCase)) { return new SrtWriter(); } + if (string.Equals(format, SubtitleFormat.VTT, StringComparison.OrdinalIgnoreCase)) { return new VttWriter(); } + if (string.Equals(format, SubtitleFormat.TTML, StringComparison.OrdinalIgnoreCase)) { return new TtmlWriter(); @@ -344,7 +353,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles } /// <summary> - /// The _semaphoreLocks + /// The _semaphoreLocks. /// </summary> private readonly ConcurrentDictionary<string, SemaphoreSlim> _semaphoreLocks = new ConcurrentDictionary<string, SemaphoreSlim>(); @@ -640,7 +649,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles } catch (FileNotFoundException) { - } catch (IOException ex) { @@ -737,7 +745,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles var charset = CharsetDetector.DetectFromStream(stream).Detected?.EncodingName; // UTF16 is automatically converted to UTF8 by FFmpeg, do not specify a character encoding - if ((path.EndsWith(".ass") || path.EndsWith(".ssa")) + if ((path.EndsWith(".ass") || path.EndsWith(".ssa") || path.EndsWith(".srt")) && (string.Equals(charset, "utf-16le", StringComparison.OrdinalIgnoreCase) || string.Equals(charset, "utf-16be", StringComparison.OrdinalIgnoreCase))) { |
