Use RegexGenerator where possible

pull/9799/head
Bond_009 11 months ago
parent f954dc5c96
commit b5f0760db8

@ -31,6 +31,9 @@ namespace Emby.Dlna.PlayTo
_httpClientFactory = httpClientFactory;
}
[GeneratedRegex("(&(?![a-z]*;))")]
private static partial Regex EscapeAmpersandRegex();
private static string NormalizeServiceUrl(string baseUrl, string serviceUrl)
{
// If it's already a complete url, don't stick anything onto the front of it
@ -128,12 +131,5 @@ namespace Emby.Dlna.PlayTo
// Have to await here instead of returning the Task directly, otherwise request would be disposed too soon
return await SendRequestAsync(request, cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// Compile-time generated regular expression for escaping ampersands.
/// </summary>
/// <returns>Compiled regular expression.</returns>
[GeneratedRegex("(&(?![a-z]*;))")]
private static partial Regex EscapeAmpersandRegex();
}
}

@ -10,7 +10,7 @@ namespace Emby.Naming.Audio
/// <summary>
/// Helper class to determine if Album is multipart.
/// </summary>
public class AlbumParser
public partial class AlbumParser
{
private readonly NamingOptions _options;
@ -23,6 +23,9 @@ namespace Emby.Naming.Audio
_options = options;
}
[GeneratedRegex(@"([-\.\(\)]|\s+)")]
private static partial Regex CleanRegex();
/// <summary>
/// Function that determines if album is multipart.
/// </summary>
@ -42,13 +45,9 @@ namespace Emby.Naming.Audio
// Normalize
// Remove whitespace
filename = filename.Replace('-', ' ');
filename = filename.Replace('.', ' ');
filename = filename.Replace('(', ' ');
filename = filename.Replace(')', ' ');
filename = Regex.Replace(filename, @"\s+", " ");
filename = CleanRegex().Replace(filename, " ");
ReadOnlySpan<char> trimmedFilename = filename.TrimStart();
ReadOnlySpan<char> trimmedFilename = filename.AsSpan().TrimStart();
foreach (var prefix in _options.AlbumStackingPrefixes)
{

@ -7,14 +7,15 @@ namespace Emby.Naming.TV
/// <summary>
/// Used to resolve information about series from path.
/// </summary>
public static class SeriesResolver
public static partial class SeriesResolver
{
/// <summary>
/// Regex that matches strings of at least 2 characters separated by a dot or underscore.
/// Used for removing separators between words, i.e turns "The_show" into "The show" while
/// preserving namings like "S.H.O.W".
/// </summary>
private static readonly Regex _seriesNameRegex = new Regex(@"((?<a>[^\._]{2,})[\._]*)|([\._](?<b>[^\._]{2,}))", RegexOptions.Compiled);
[GeneratedRegex(@"((?<a>[^\._]{2,})[\._]*)|([\._](?<b>[^\._]{2,}))")]
private static partial Regex SeriesNameRegex();
/// <summary>
/// Resolve information about series from path.
@ -37,7 +38,7 @@ namespace Emby.Naming.TV
if (!string.IsNullOrEmpty(seriesName))
{
seriesName = _seriesNameRegex.Replace(seriesName, "${a} ${b}").Trim();
seriesName = SeriesNameRegex().Replace(seriesName, "${a} ${b}").Trim();
}
return new SeriesInfo(path)

@ -12,9 +12,13 @@ namespace Emby.Naming.Video
/// <summary>
/// Resolves alternative versions and extras from list of video files.
/// </summary>
public static class VideoListResolver
public static partial class VideoListResolver
{
private static readonly Regex _resolutionRegex = new Regex("[0-9]{2}[0-9]+[ip]", RegexOptions.IgnoreCase | RegexOptions.Compiled);
[GeneratedRegex("[0-9]{2}[0-9]+[ip]", RegexOptions.IgnoreCase)]
private static partial Regex ResolutionRegex();
[GeneratedRegex(@"^\[([^]]*)\]")]
private static partial Regex CheckMultiVersionRegex();
/// <summary>
/// Resolves alternative versions and extras from list of video files.
@ -131,7 +135,7 @@ namespace Emby.Naming.Video
if (videos.Count > 1)
{
var groups = videos.GroupBy(x => _resolutionRegex.IsMatch(x.Files[0].FileNameWithoutExtension)).ToList();
var groups = videos.GroupBy(x => ResolutionRegex().IsMatch(x.Files[0].FileNameWithoutExtension)).ToList();
videos.Clear();
foreach (var group in groups)
{
@ -201,7 +205,7 @@ namespace Emby.Naming.Video
// The CleanStringParser should have removed common keywords etc.
return testFilename.IsEmpty
|| testFilename[0] == '-'
|| Regex.IsMatch(testFilename, @"^\[([^]]*)\]", RegexOptions.Compiled);
|| CheckMultiVersionRegex().IsMatch(testFilename);
}
}
}

@ -24,7 +24,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
/// <summary>
/// Class MovieResolver.
/// </summary>
public class MovieResolver : BaseVideoResolver<Video>, IMultiItemResolver
public partial class MovieResolver : BaseVideoResolver<Video>, IMultiItemResolver
{
private readonly IImageProcessor _imageProcessor;
@ -56,6 +56,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
/// <value>The priority.</value>
public override ResolverPriority Priority => ResolverPriority.Fourth;
[GeneratedRegex(@"\bsample\b", RegexOptions.IgnoreCase)]
private static partial Regex IsIgnoredRegex();
/// <inheritdoc />
public MultiItemResolverResult ResolveMultiple(
Folder parent,
@ -261,7 +264,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
{
leftOver.Add(child);
}
else if (!IsIgnored(child.Name))
else if (!IsIgnoredRegex().IsMatch(child.Name))
{
files.Add(child);
}
@ -314,9 +317,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return result;
}
private static bool IsIgnored(ReadOnlySpan<char> filename)
=> Regex.IsMatch(filename, @"\bsample\b", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static bool ContainsFile(IReadOnlyList<VideoInfo> result, FileSystemMetadata file)
{
for (var i = 0; i < result.Count; i++)

@ -5,7 +5,7 @@ using System.Text.RegularExpressions;
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
public class LegacyHdHomerunChannelCommands : IHdHomerunChannelCommands
public partial class LegacyHdHomerunChannelCommands : IHdHomerunChannelCommands
{
private string? _channel;
private string? _program;
@ -13,7 +13,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
public LegacyHdHomerunChannelCommands(string url)
{
// parse url for channel and program
var match = Regex.Match(url, @"\/ch([0-9]+)-?([0-9]*)");
var match = ChannelAndProgramRegex().Match(url);
if (match.Success)
{
_channel = match.Groups[1].Value;
@ -21,6 +21,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
}
[GeneratedRegex(@"\/ch([0-9]+)-?([0-9]*)")]
private static partial Regex ChannelAndProgramRegex();
public IEnumerable<(string CommandName, string CommandValue)> GetCommands()
{
if (!string.IsNullOrEmpty(_channel))

@ -20,7 +20,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
public class M3uParser
public partial class M3uParser
{
private const string ExtInfPrefix = "#EXTINF:";
@ -33,6 +33,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
_httpClientFactory = httpClientFactory;
}
[GeneratedRegex(@"([a-z0-9\-_]+)=\""([^""]+)\""", RegexOptions.IgnoreCase, "en-US")]
private static partial Regex KeyValueRegex();
public async Task<List<ChannelInfo>> Parse(TunerHostInfo info, string channelIdPrefix, CancellationToken cancellationToken)
{
// Read the file and display it line by line.
@ -311,7 +314,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
var matches = Regex.Matches(line, @"([a-z0-9\-_]+)=\""([^""]+)\""", RegexOptions.IgnoreCase);
var matches = KeyValueRegex().Matches(line);
remaining = line;
@ -320,7 +323,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
var key = match.Groups[1].Value;
var value = match.Groups[2].Value;
dict[match.Groups[1].Value] = match.Groups[2].Value;
dict[key] = value;
remaining = remaining.Replace(key + "=\"" + value + "\"", string.Empty, StringComparison.OrdinalIgnoreCase);
}

@ -31,7 +31,7 @@ namespace Jellyfin.Server.Implementations.Users
/// <summary>
/// Manages the creation and retrieval of <see cref="User"/> instances.
/// </summary>
public class UserManager : IUserManager
public partial class UserManager : IUserManager
{
private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
private readonly IEventManager _eventManager;
@ -105,6 +105,12 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc/>
public IEnumerable<Guid> UsersIds => _users.Keys;
// This is some regex that matches only on unicode "word" characters, as well as -, _ and @
// In theory this will cut out most if not all 'control' characters which should help minimize any weirdness
// Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), periods (.) and spaces ( )
[GeneratedRegex("^[\\w\\ \\-'._@]+$")]
private static partial Regex ValidUsernameRegex();
/// <inheritdoc/>
public User? GetUserById(Guid id)
{
@ -527,7 +533,7 @@ namespace Jellyfin.Server.Implementations.Users
}
var defaultName = Environment.UserName;
if (string.IsNullOrWhiteSpace(defaultName) || !IsValidUsername(defaultName))
if (string.IsNullOrWhiteSpace(defaultName) || !ValidUsernameRegex().IsMatch(defaultName))
{
defaultName = "MyJellyfinUser";
}
@ -710,7 +716,7 @@ namespace Jellyfin.Server.Implementations.Users
internal static void ThrowIfInvalidUsername(string name)
{
if (!string.IsNullOrWhiteSpace(name) && IsValidUsername(name))
if (!string.IsNullOrWhiteSpace(name) && ValidUsernameRegex().IsMatch(name))
{
return;
}
@ -718,14 +724,6 @@ namespace Jellyfin.Server.Implementations.Users
throw new ArgumentException("Usernames can contain unicode symbols, numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)", nameof(name));
}
private static bool IsValidUsername(ReadOnlySpan<char> name)
{
// This is some regex that matches only on unicode "word" characters, as well as -, _ and @
// In theory this will cut out most if not all 'control' characters which should help minimize any weirdness
// Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), periods (.) and spaces ( )
return Regex.IsMatch(name, @"^[\w\ \-'._@]+$");
}
private IAuthenticationProvider GetAuthenticationProvider(User user)
{
return GetAuthenticationProviders(user)[0];

@ -8,20 +8,19 @@ namespace MediaBrowser.Common.Extensions
/// <summary>
/// Class BaseExtensions.
/// </summary>
public static class BaseExtensions
public static partial class BaseExtensions
{
// http://stackoverflow.com/questions/1349023/how-can-i-strip-html-from-text-in-net
[GeneratedRegex(@"<(.|\n)*?>")]
private static partial Regex StripHtmlRegex();
/// <summary>
/// Strips the HTML.
/// </summary>
/// <param name="htmlString">The HTML string.</param>
/// <returns><see cref="string" />.</returns>
public static string StripHtml(this string htmlString)
{
// http://stackoverflow.com/questions/1349023/how-can-i-strip-html-from-text-in-net
const string Pattern = @"<(.|\n)*?>";
return Regex.Replace(htmlString, Pattern, string.Empty).Trim();
}
=> StripHtmlRegex().Replace(htmlString, string.Empty).Trim();
/// <summary>
/// Gets the Md5.

@ -23,7 +23,7 @@ using Microsoft.Extensions.Configuration;
namespace MediaBrowser.Controller.MediaEncoding
{
public class EncodingHelper
public partial class EncodingHelper
{
private const string QsvAlias = "qs";
private const string VaapiAlias = "va";
@ -112,6 +112,9 @@ namespace MediaBrowser.Controller.MediaEncoding
_config = config;
}
[GeneratedRegex(@"\s+")]
private static partial Regex WhiteSpaceRegex();
public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
=> GetH264OrH265Encoder("libx264", "h264", state, encodingOptions);
@ -1733,7 +1736,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
var profile = state.GetRequestedProfiles(targetVideoCodec).FirstOrDefault() ?? string.Empty;
profile = Regex.Replace(profile, @"\s+", string.Empty);
profile = WhiteSpaceRegex().Replace(profile, string.Empty);
// We only transcode to HEVC 8-bit for now, force Main Profile.
if (profile.Contains("main10", StringComparison.OrdinalIgnoreCase)

@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.MediaEncoding.Encoder
{
public class EncoderValidator
public partial class EncoderValidator
{
private static readonly string[] _requiredDecoders = new[]
{
@ -160,6 +160,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
public static Version? MaxVersion { get; } = null;
[GeneratedRegex(@"^ffmpeg version n?((?:[0-9]+\.?)+)")]
private static partial Regex FfmpegVersionRegex();
[GeneratedRegex(@"((?<name>lib\w+)\s+(?<major>[0-9]+)\.\s*(?<minor>[0-9]+))", RegexOptions.Multiline)]
private static partial Regex LibraryRegex();
public bool ValidateVersion()
{
string output;
@ -278,7 +284,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
internal Version? GetFFmpegVersionInternal(string output)
{
// For pre-built binaries the FFmpeg version should be mentioned at the very start of the output
var match = Regex.Match(output, @"^ffmpeg version n?((?:[0-9]+\.?)+)");
var match = FfmpegVersionRegex().Match(output);
if (match.Success)
{
@ -326,10 +332,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
var map = new Dictionary<string, Version>();
foreach (Match match in Regex.Matches(
output,
@"((?<name>lib\w+)\s+(?<major>[0-9]+)\.\s*(?<minor>[0-9]+))",
RegexOptions.Multiline))
foreach (Match match in LibraryRegex().Matches(output))
{
var version = new Version(
int.Parse(match.Groups["major"].ValueSpan, CultureInfo.InvariantCulture),

@ -36,7 +36,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <summary>
/// Class MediaEncoder.
/// </summary>
public class MediaEncoder : IMediaEncoder, IDisposable
public partial class MediaEncoder : IMediaEncoder, IDisposable
{
/// <summary>
/// The default SDR image extraction timeout in milliseconds.
@ -142,6 +142,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <inheritdoc />
public bool IsVaapiDeviceSupportVulkanFmtModifier => _isVaapiDeviceSupportVulkanFmtModifier;
[GeneratedRegex(@"[^\/\\]+?(\.[^\/\\\n.]+)?$")]
private static partial Regex FfprobePathRegex();
/// <summary>
/// Run at startup or if the user removes a Custom path from transcode page.
/// Sets global variables FFmpegPath.
@ -176,7 +179,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (_ffmpegPath is not null)
{
// Determine a probe path from the mpeg path
_ffprobePath = Regex.Replace(_ffmpegPath, @"[^\/\\]+?(\.[^\/\\\n.]+)?$", @"ffprobe$1");
_ffprobePath = FfprobePathRegex().Replace(_ffmpegPath, @"ffprobe$1");
// Interrogate to understand what coders are supported
var validator = new EncoderValidator(_logger, _ffmpegPath);
@ -416,8 +419,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
public Task<MediaInfo> GetMediaInfo(MediaInfoRequest request, CancellationToken cancellationToken)
{
var extractChapters = request.MediaType == DlnaProfileType.Video && request.ExtractChapters;
string analyzeDuration = string.Empty;
string ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty;
var analyzeDuration = string.Empty;
var ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty;
if (request.MediaSource.AnalyzeDurationMs > 0)
{

@ -11,8 +11,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// <summary>
/// ASS subtitle writer.
/// </summary>
public class AssWriter : ISubtitleWriter
public partial class AssWriter : ISubtitleWriter
{
[GeneratedRegex(@"\n", RegexOptions.IgnoreCase)]
private static partial Regex NewLineRegex();
/// <inheritdoc />
public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken)
{
@ -40,7 +43,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
var trackEvent = trackEvents[i];
var startTime = TimeSpan.FromTicks(trackEvent.StartPositionTicks).ToString(timeFormat, CultureInfo.InvariantCulture);
var endTime = TimeSpan.FromTicks(trackEvent.EndPositionTicks).ToString(timeFormat, CultureInfo.InvariantCulture);
var text = Regex.Replace(trackEvent.Text, @"\n", "\\n", RegexOptions.IgnoreCase);
var text = NewLineRegex().Replace(trackEvent.Text, "\\n");
writer.WriteLine(
"Dialogue: 0,{0},{1},Default,{2}",

@ -11,8 +11,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// <summary>
/// SRT subtitle writer.
/// </summary>
public class SrtWriter : ISubtitleWriter
public partial class SrtWriter : ISubtitleWriter
{
[GeneratedRegex(@"\\n", RegexOptions.IgnoreCase)]
private static partial Regex NewLineEscapedRegex();
/// <inheritdoc />
public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken)
{
@ -35,7 +38,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
var text = trackEvent.Text;
// TODO: Not sure how to handle these
text = Regex.Replace(text, @"\\n", " ", RegexOptions.IgnoreCase);
text = NewLineEscapedRegex().Replace(text, " ");
writer.WriteLine(text);
writer.WriteLine();

@ -11,8 +11,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// <summary>
/// SSA subtitle writer.
/// </summary>
public class SsaWriter : ISubtitleWriter
public partial class SsaWriter : ISubtitleWriter
{
[GeneratedRegex(@"\n", RegexOptions.IgnoreCase)]
private static partial Regex NewLineRegex();
/// <inheritdoc />
public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken)
{
@ -40,7 +43,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
var trackEvent = trackEvents[i];
var startTime = TimeSpan.FromTicks(trackEvent.StartPositionTicks).ToString(timeFormat, CultureInfo.InvariantCulture);
var endTime = TimeSpan.FromTicks(trackEvent.EndPositionTicks).ToString(timeFormat, CultureInfo.InvariantCulture);
var text = Regex.Replace(trackEvent.Text, @"\n", "\\n", RegexOptions.IgnoreCase);
var text = NewLineRegex().Replace(trackEvent.Text, "\\n");
writer.WriteLine(
"Dialogue: 0,{0},{1},Default,{2}",

@ -9,8 +9,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// <summary>
/// TTML subtitle writer.
/// </summary>
public class TtmlWriter : ISubtitleWriter
public partial class TtmlWriter : ISubtitleWriter
{
[GeneratedRegex(@"\\n", RegexOptions.IgnoreCase)]
private static partial Regex NewLineEscapeRegex();
/// <inheritdoc />
public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken)
{
@ -38,7 +41,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
var text = trackEvent.Text;
text = Regex.Replace(text, @"\\n", "<br/>", RegexOptions.IgnoreCase);
text = NewLineEscapeRegex().Replace(text, "<br/>");
writer.WriteLine(
"<p begin=\"{0}\" dur=\"{1}\">{2}</p>",

@ -10,8 +10,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// <summary>
/// Subtitle writer for the WebVTT format.
/// </summary>
public class VttWriter : ISubtitleWriter
public partial class VttWriter : ISubtitleWriter
{
[GeneratedRegex(@"\\n", RegexOptions.IgnoreCase)]
private static partial Regex NewlineEscapeRegex();
/// <inheritdoc />
public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken)
{
@ -39,7 +42,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
var text = trackEvent.Text;
// TODO: Not sure how to handle these
text = Regex.Replace(text, @"\\n", " ", RegexOptions.IgnoreCase);
text = NewlineEscapeRegex().Replace(text, " ");
writer.WriteLine(text);
writer.WriteLine();

@ -5,7 +5,7 @@ using System.Text.RegularExpressions;
namespace MediaBrowser.Model.Dlna
{
public class SearchCriteria
public partial class SearchCriteria
{
public SearchCriteria(string search)
{
@ -13,10 +13,10 @@ namespace MediaBrowser.Model.Dlna
SearchType = SearchType.Unknown;
string[] factors = RegexSplit(search, "(and|or)");
string[] factors = AndOrRegex().Split(search);
foreach (string factor in factors)
{
string[] subFactors = RegexSplit(factor.Trim().Trim('(').Trim(')').Trim(), "\\s", 3);
string[] subFactors = WhiteSpaceRegex().Split(factor.Trim().Trim('(').Trim(')').Trim(), 3);
if (subFactors.Length == 3)
{
@ -46,27 +46,10 @@ namespace MediaBrowser.Model.Dlna
public SearchType SearchType { get; set; }
/// <summary>
/// Splits the specified string.
/// </summary>
/// <param name="str">The string.</param>
/// <param name="term">The term.</param>
/// <param name="limit">The limit.</param>
/// <returns>System.String[].</returns>
private static string[] RegexSplit(string str, string term, int limit)
{
return new Regex(term).Split(str, limit);
}
[GeneratedRegex("\\s")]
private static partial Regex WhiteSpaceRegex();
/// <summary>
/// Splits the specified string.
/// </summary>
/// <param name="str">The string.</param>
/// <param name="term">The term.</param>
/// <returns>System.String[].</returns>
private static string[] RegexSplit(string str, string term)
{
return Regex.Split(str, term, RegexOptions.IgnoreCase);
}
[GeneratedRegex("(and|or)", RegexOptions.IgnoreCase)]
private static partial Regex AndOrRegex();
}
}

@ -25,7 +25,7 @@ namespace MediaBrowser.Providers.MediaInfo
/// <summary>
/// Probes audio files for metadata.
/// </summary>
public class AudioFileProber
public partial class AudioFileProber
{
// Default LUFS value for use with the web interface, at -18db gain will be 1(no db gain).
private const float DefaultLUFSValue = -18;
@ -58,6 +58,9 @@ namespace MediaBrowser.Providers.MediaInfo
_mediaSourceManager = mediaSourceManager;
}
[GeneratedRegex("I:\\s+(.*?)\\s+LUFS")]
private static partial Regex LUFSRegex();
/// <summary>
/// Probes the specified item for metadata.
/// </summary>
@ -129,7 +132,7 @@ namespace MediaBrowser.Providers.MediaInfo
output = await process.StandardError.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
MatchCollection split = Regex.Matches(output, @"I:\s+(.*?)\s+LUFS");
MatchCollection split = LUFSRegex().Matches(output);
if (split.Count != 0)
{

@ -11,10 +11,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// <summary>
/// Utilities for the TMDb provider.
/// </summary>
public static class TmdbUtils
public static partial class TmdbUtils
{
private static readonly Regex _nonWords = new(@"[\W_]+", RegexOptions.Compiled);
/// <summary>
/// URL of the TMDb instance to use.
/// </summary>
@ -50,6 +48,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
PersonKind.Producer
};
[GeneratedRegex(@"[\W_]+")]
private static partial Regex NonWordRegex();
/// <summary>
/// Cleans the name according to TMDb requirements.
/// </summary>
@ -58,7 +59,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
public static string CleanName(string name)
{
// TMDb expects a space separated list of words make sure that is the case
return _nonWords.Replace(name, " ");
return NonWordRegex().Replace(name, " ");
}
/// <summary>

@ -27,15 +27,12 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.XbmcMetadata.Savers
{
public abstract class BaseNfoSaver : IMetadataFileSaver
public abstract partial class BaseNfoSaver : IMetadataFileSaver
{
public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss";
public const string YouTubeWatchUrl = "https://www.youtube.com/watch?v=";
// filters control characters but allows only properly-formed surrogate sequences
private const string _invalidXMLCharsRegex = @"(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F\uFEFF\uFFFE\uFFFF]";
private static readonly HashSet<string> _commonTags = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"plot",
@ -148,6 +145,12 @@ namespace MediaBrowser.XbmcMetadata.Savers
public static string SaverName => "Nfo";
// filters control characters but allows only properly-formed surrogate sequences
// http://web.archive.org/web/20181230211547/https://emby.media/community/index.php?/topic/49071-nfo-not-generated-on-actualize-or-rescan-or-identify
// Web Archive version of link since it's not really explained in the thread.
[GeneratedRegex(@"(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F\uFEFF\uFFFE\uFFFF]")]
private static partial Regex InvalidXMLCharsRegexRegex();
/// <inheritdoc />
public string GetSavePath(BaseItem item)
=> GetLocalSavePath(item);
@ -354,9 +357,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
if (!string.IsNullOrEmpty(stream.Language))
{
// http://web.archive.org/web/20181230211547/https://emby.media/community/index.php?/topic/49071-nfo-not-generated-on-actualize-or-rescan-or-identify
// Web Archive version of link since it's not really explained in the thread.
writer.WriteElementString("language", Regex.Replace(stream.Language, _invalidXMLCharsRegex, string.Empty));
writer.WriteElementString("language", InvalidXMLCharsRegexRegex().Replace(stream.Language, string.Empty));
}
var scanType = stream.IsInterlaced ? "interlaced" : "progressive";

@ -60,6 +60,8 @@
<Rule Id="SA1515" Action="None" />
<!-- disable warning SA1600: Elements should be documented -->
<Rule Id="SA1600" Action="None" />
<!-- disable warning SA1601: Partial elements should be documented -->
<Rule Id="SA1601" Action="None" />
<!-- disable warning SA1602: Enumeration items should be documented -->
<Rule Id="SA1602" Action="None" />
<!-- disable warning SA1633: The file header is missing or not located at the top of the file -->

@ -123,8 +123,7 @@ public partial class StripCollageBuilder
var typeFace = SKTypeface.FromFamilyName("sans-serif", SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright);
// use the system fallback to find a typeface for the given CJK character
var nonCjkPattern = @"[^\p{IsCJKUnifiedIdeographs}\p{IsCJKUnifiedIdeographsExtensionA}\p{IsKatakana}\p{IsHiragana}\p{IsHangulSyllables}\p{IsHangulJamo}]";
var filteredName = Regex.Replace(libraryName ?? string.Empty, nonCjkPattern, string.Empty);
var filteredName = NonCjkPatternRegex().Replace(libraryName ?? string.Empty, string.Empty);
if (!string.IsNullOrEmpty(filteredName))
{
typeFace = SKFontManager.Default.MatchCharacter(null, SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright, null, filteredName[0]);

@ -6,11 +6,13 @@ namespace Jellyfin.Extensions
/// <summary>
/// Provides extensions methods for <see cref="string" />.
/// </summary>
public static class StringExtensions
public static partial 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)", RegexOptions.Compiled);
[GeneratedRegex("([\ud800-\udbff](?![\udc00-\udfff]))|((?<![\ud800-\udbff])[\udc00-\udfff])|(<EFBFBD>)")]
private static partial Regex NonConformingUnicodeRegex();
/// <summary>
/// Removes the diacritics character from the strings.
@ -19,7 +21,7 @@ namespace Jellyfin.Extensions
/// <returns>The string without diacritics character.</returns>
public static string RemoveDiacritics(this string text)
=> Diacritics.Extensions.StringExtensions.RemoveDiacritics(
_nonConformingUnicode.Replace(text, string.Empty));
NonConformingUnicodeRegex().Replace(text, string.Empty));
/// <summary>
/// Checks whether or not the specified string has diacritics in it.
@ -28,7 +30,7 @@ namespace Jellyfin.Extensions
/// <returns>True if the string has diacritics, false otherwise.</returns>
public static bool HasDiacritics(this string text)
=> Diacritics.Extensions.StringExtensions.HasDiacritics(text)
|| _nonConformingUnicode.IsMatch(text);
|| NonConformingUnicodeRegex().IsMatch(text);
/// <summary>
/// Counts the number of occurrences of [needle] in the string.

@ -25,10 +25,13 @@ using Xunit;
namespace Jellyfin.Providers.Tests.Manager
{
public class ItemImageProviderTests
public partial class ItemImageProviderTests
{
private const string TestDataImagePath = "Test Data/Images/blank{0}.jpg";
[GeneratedRegex("[0-9]+")]
private static partial Regex NumbersRegex();
[Fact]
public void ValidateImages_PhotoEmptyProviders_NoChange()
{
@ -463,7 +466,7 @@ namespace Jellyfin.Providers.Tests.Manager
// images from the provider manager are sorted by preference (earlier images are higher priority) so we can verify that low url numbers are chosen
foreach (var image in actualImages)
{
var index = int.Parse(Regex.Match(image.Path, @"[0-9]+").Value, NumberStyles.Integer, CultureInfo.InvariantCulture);
var index = int.Parse(NumbersRegex().Match(image.Path).ValueSpan, NumberStyles.Integer, CultureInfo.InvariantCulture);
Assert.True(index < imageCount);
}
}

Loading…
Cancel
Save