#nullable disable 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.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; namespace MediaBrowser.Providers.MediaInfo { /// /// Uses to extract embedded images. /// public class AudioImageProvider : IDynamicImageProvider { private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaEncoder _mediaEncoder; private readonly IServerConfigurationManager _config; private readonly IFileSystem _fileSystem; /// /// Initializes a new instance of the class. /// /// The media source manager for fetching item streams. /// The media encoder for extracting embedded images. /// The server configuration manager for getting image paths. /// The filesystem. public AudioImageProvider(IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder, IServerConfigurationManager config, IFileSystem fileSystem) { _mediaSourceManager = mediaSourceManager; _mediaEncoder = mediaEncoder; _config = config; _fileSystem = fileSystem; } private string AudioImagesPath => Path.Combine(_config.ApplicationPaths.CachePath, "extracted-audio-images"); /// public string Name => "Image Extractor"; /// public IEnumerable GetSupportedImages(BaseItem item) { return new[] { ImageType.Primary }; } /// public Task GetImage(BaseItem item, ImageType type, CancellationToken cancellationToken) { var imageStreams = _mediaSourceManager.GetMediaStreams(new MediaStreamQuery { ItemId = item.Id, Type = 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); } private 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).Contains("front", StringComparison.OrdinalIgnoreCase)) ?? imageStreams.FirstOrDefault(i => (i.Comment ?? string.Empty).Contains("cover", StringComparison.OrdinalIgnoreCase)) ?? imageStreams.FirstOrDefault(); var imageStreamIndex = 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; } } }