#nullable disable #pragma warning disable CA1002, CS1591 using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; namespace MediaBrowser.Providers.MediaInfo { /// /// Uses ffmpeg to create video images. /// public class AudioImageProvider : IDynamicImageProvider { private readonly IMediaEncoder _mediaEncoder; private readonly IServerConfigurationManager _config; private readonly IFileSystem _fileSystem; public AudioImageProvider(IMediaEncoder mediaEncoder, IServerConfigurationManager config, IFileSystem fileSystem) { _mediaEncoder = mediaEncoder; _config = config; _fileSystem = fileSystem; } public string AudioImagesPath => Path.Combine(_config.ApplicationPaths.CachePath, "extracted-audio-images"); public string Name => "Image Extractor"; public IEnumerable GetSupportedImages(BaseItem item) { return new List { ImageType.Primary }; } public Task GetImage(BaseItem item, ImageType type, CancellationToken cancellationToken) { var audio = (Audio)item; var imageStreams = audio.GetMediaStreams(MediaStreamType.EmbeddedImage); // Can't extract if we didn't find a video stream in the file if (imageStreams.Count == 0) { return Task.FromResult(new DynamicImageResponse { HasImage = false }); } return GetImage((Audio)item, imageStreams, cancellationToken); } public async Task GetImage(Audio item, List imageStreams, CancellationToken cancellationToken) { var path = GetAudioImagePath(item); if (!File.Exists(path)) { Directory.CreateDirectory(Path.GetDirectoryName(path)); var imageStream = imageStreams.FirstOrDefault(i => (i.Comment ?? string.Empty).IndexOf("front", StringComparison.OrdinalIgnoreCase) != -1) ?? imageStreams.FirstOrDefault(i => (i.Comment ?? string.Empty).IndexOf("cover", StringComparison.OrdinalIgnoreCase) != -1) ?? imageStreams.FirstOrDefault(); var imageStreamIndex = imageStream == null ? (int?)null : imageStream.Index; var tempFile = await _mediaEncoder.ExtractAudioImage(item.Path, imageStreamIndex, cancellationToken).ConfigureAwait(false); File.Copy(tempFile, path, true); try { _fileSystem.DeleteFile(tempFile); } catch { } } return new DynamicImageResponse { HasImage = true, Path = path }; } private string GetAudioImagePath(Audio item) { string filename; if (item.GetType() == typeof(Audio)) { if (item.AlbumArtists.Count > 0 && !string.IsNullOrWhiteSpace(item.Album) && !string.IsNullOrWhiteSpace(item.AlbumArtists[0])) { filename = (item.Album + "-" + item.AlbumArtists[0]).GetMD5().ToString("N", CultureInfo.InvariantCulture); } else { filename = item.Id.ToString("N", CultureInfo.InvariantCulture); } filename += ".jpg"; } else { // If it's an audio book or audio podcast, allow unique image per item filename = item.Id.ToString("N", CultureInfo.InvariantCulture) + ".jpg"; } var prefix = filename.AsSpan().Slice(0, 1); return Path.Join(AudioImagesPath, prefix, filename); } public bool Supports(BaseItem item) { if (item.IsShortcut) { return false; } if (!item.IsFileProtocol) { return false; } return item is Audio; } } }