public async Task <ItemUpdateType> ProbeVideo <T>(T item, MetadataRefreshOptions options, CancellationToken cancellationToken) where T : Video { var isoMount = await MountIsoIfNeeded(item, cancellationToken).ConfigureAwait(false); BlurayDiscInfo blurayDiscInfo = null; try { if (item.VideoType == VideoType.BluRay || (item.IsoType.HasValue && item.IsoType == IsoType.BluRay)) { var inputPath = isoMount != null ? isoMount.MountedPath : item.Path; blurayDiscInfo = GetBDInfo(inputPath); } OnPreFetch(item, isoMount, blurayDiscInfo); // If we didn't find any satisfying the min length, just take them all if (item.VideoType == VideoType.Dvd || (item.IsoType.HasValue && item.IsoType == IsoType.Dvd)) { if (item.PlayableStreamFileNames.Count == 0) { _logger.Error("No playable vobs found in dvd structure, skipping ffprobe."); return(ItemUpdateType.MetadataImport); } } if (item.VideoType == VideoType.BluRay || (item.IsoType.HasValue && item.IsoType == IsoType.BluRay)) { if (item.PlayableStreamFileNames.Count == 0) { _logger.Error("No playable vobs found in bluray structure, skipping ffprobe."); return(ItemUpdateType.MetadataImport); } } var result = await GetMediaInfo(item, isoMount, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); FFProbeHelpers.NormalizeFFProbeResult(result); cancellationToken.ThrowIfCancellationRequested(); await Fetch(item, cancellationToken, result, isoMount, blurayDiscInfo, options).ConfigureAwait(false); } finally { if (isoMount != null) { isoMount.Dispose(); } } return(ItemUpdateType.MetadataImport); }
/// <summary> /// Gets the genres from the tags collection /// </summary> /// <param name="audio">The audio.</param> /// <param name="tags">The tags.</param> private void FetchGenres(Audio audio, Dictionary <string, string> tags) { var val = FFProbeHelpers.GetDictionaryValue(tags, "genre"); if (!string.IsNullOrEmpty(val)) { audio.Genres.Clear(); foreach (var genre in Split(val, true)) { audio.AddGenre(genre); } } }
public async Task <ItemUpdateType> Probe <T>(T item, CancellationToken cancellationToken) where T : Audio { var result = await GetMediaInfo(item, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); FFProbeHelpers.NormalizeFFProbeResult(result); cancellationToken.ThrowIfCancellationRequested(); await Fetch(item, cancellationToken, result).ConfigureAwait(false); return(ItemUpdateType.MetadataImport); }
/// <summary> /// Gets the studios from the tags collection /// </summary> /// <param name="audio">The audio.</param> /// <param name="tags">The tags.</param> /// <param name="tagName">Name of the tag.</param> private void FetchStudios(Audio audio, Dictionary <string, string> tags, string tagName) { var val = FFProbeHelpers.GetDictionaryValue(tags, tagName); if (!string.IsNullOrEmpty(val)) { // Sometimes the artist name is listed here, account for that var studios = Split(val, true).Where(i => !audio.HasArtist(i)); foreach (var studio in studios) { audio.AddStudio(studio); } } }
/// <summary> /// Gets the disc number, which is sometimes can be in the form of '1', or '1/3' /// </summary> /// <param name="tags">The tags.</param> /// <param name="tagName">Name of the tag.</param> /// <returns>System.Nullable{System.Int32}.</returns> private int?GetDictionaryDiscValue(Dictionary <string, string> tags, string tagName) { var disc = FFProbeHelpers.GetDictionaryValue(tags, tagName); if (!string.IsNullOrEmpty(disc)) { disc = disc.Split('/')[0]; int num; if (int.TryParse(disc, out num)) { return(num); } } return(null); }
public async Task <ItemUpdateType> Probe <T>(T item, CancellationToken cancellationToken) where T : Audio { if (item.IsArchive) { var ext = Path.GetExtension(item.Path) ?? string.Empty; item.Container = ext.TrimStart('.'); return(ItemUpdateType.MetadataImport); } var result = await GetMediaInfo(item, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); FFProbeHelpers.NormalizeFFProbeResult(result); cancellationToken.ThrowIfCancellationRequested(); await Fetch(item, cancellationToken, result).ConfigureAwait(false); return(ItemUpdateType.MetadataImport); }
public const int MaxSubtitleDescriptionExtractionLength = 100; // When extracting subtitles, the maximum length to consider (to avoid invalid filenames) private void FetchWtvInfo(Video video, InternalMediaInfoResult data) { if (data.format == null || data.format.tags == null) { return; } if (!video.LockedFields.Contains(MetadataFields.Genres)) { var genres = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/Genre"); if (!string.IsNullOrWhiteSpace(genres)) { //genres = FFProbeHelpers.GetDictionaryValue(data.format.tags, "genre"); } if (!string.IsNullOrWhiteSpace(genres)) { video.Genres = genres.Split(new[] { ';', '/', ',' }, StringSplitOptions.RemoveEmptyEntries) .Where(i => !string.IsNullOrWhiteSpace(i)) .Select(i => i.Trim()) .ToList(); } } if (!video.LockedFields.Contains(MetadataFields.OfficialRating)) { var officialRating = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/ParentalRating"); if (!string.IsNullOrWhiteSpace(officialRating)) { video.OfficialRating = officialRating; } } if (!video.LockedFields.Contains(MetadataFields.Cast)) { var people = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/MediaCredits"); if (!string.IsNullOrEmpty(people)) { video.People = people.Split(new[] { ';', '/' }, StringSplitOptions.RemoveEmptyEntries) .Where(i => !string.IsNullOrWhiteSpace(i)) .Select(i => new PersonInfo { Name = i.Trim(), Type = PersonType.Actor }) .ToList(); } } var year = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/OriginalReleaseTime"); if (!string.IsNullOrWhiteSpace(year)) { int val; if (int.TryParse(year, NumberStyles.Integer, _usCulture, out val)) { video.ProductionYear = val; } } var premiereDateString = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/MediaOriginalBroadcastDateTime"); if (!string.IsNullOrWhiteSpace(premiereDateString)) { DateTime val; // Credit to MCEBuddy: https://mcebuddy2x.codeplex.com/ // DateTime is reported along with timezone info (typically Z i.e. UTC hence assume None) if (DateTime.TryParse(year, null, DateTimeStyles.None, out val)) { video.PremiereDate = val.ToUniversalTime(); } } var description = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitleDescription"); var episode = video as Episode; if (episode != null) { var subTitle = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitle"); // For below code, credit to MCEBuddy: https://mcebuddy2x.codeplex.com/ // Sometimes for TV Shows the Subtitle field is empty and the subtitle description contains the subtitle, extract if possible. See ticket https://mcebuddy2x.codeplex.com/workitem/1910 // The format is -> EPISODE/TOTAL_EPISODES_IN_SEASON. SUBTITLE: DESCRIPTION // OR -> COMMENT. SUBTITLE: DESCRIPTION // e.g. -> 4/13. The Doctor's Wife: Science fiction drama. When he follows a Time Lord distress signal, the Doctor puts Amy, Rory and his beloved TARDIS in grave danger. Also in HD. [AD,S] // e.g. -> CBeebies Bedtime Hour. The Mystery: Animated adventures of two friends who live on an island in the middle of the big city. Some of Abney and Teal's favourite objects are missing. [S] if (String.IsNullOrWhiteSpace(subTitle) && !String.IsNullOrWhiteSpace(description) && description.Substring(0, Math.Min(description.Length, MaxSubtitleDescriptionExtractionLength)).Contains(":")) // Check within the Subtitle size limit, otherwise from description it can get too long creating an invalid filename { string[] parts = description.Split(':'); if (parts.Length > 0) { string subtitle = parts[0]; try { if (subtitle.Contains("/")) // It contains a episode number and season number { string[] numbers = subtitle.Split(' '); episode.IndexNumber = int.Parse(numbers[0].Replace(".", "").Split('/')[0]); int totalEpisodesInSeason = int.Parse(numbers[0].Replace(".", "").Split('/')[1]); 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 } } } } } if (!video.LockedFields.Contains(MetadataFields.Overview)) { if (!string.IsNullOrWhiteSpace(description)) { video.Overview = description; } } }
private void FetchWtvInfo(Video video, InternalMediaInfoResult data) { if (data.format == null || data.format.tags == null) { return; } if (video.Genres.Count == 0) { if (!video.LockedFields.Contains(MetadataFields.Genres)) { var genres = FFProbeHelpers.GetDictionaryValue(data.format.tags, "genre"); if (!string.IsNullOrEmpty(genres)) { video.Genres = genres.Split(new[] { ';', '/', ',' }, StringSplitOptions.RemoveEmptyEntries) .Where(i => !string.IsNullOrWhiteSpace(i)) .Select(i => i.Trim()) .ToList(); } } } if (string.IsNullOrEmpty(video.Overview)) { if (!video.LockedFields.Contains(MetadataFields.Overview)) { var overview = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitleDescription"); if (!string.IsNullOrWhiteSpace(overview)) { video.Overview = overview; } } } if (string.IsNullOrEmpty(video.OfficialRating)) { var officialRating = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/ParentalRating"); if (!string.IsNullOrWhiteSpace(officialRating)) { if (!video.LockedFields.Contains(MetadataFields.OfficialRating)) { video.OfficialRating = officialRating; } } } if (video.People.Count == 0) { if (!video.LockedFields.Contains(MetadataFields.Cast)) { var people = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/MediaCredits"); if (!string.IsNullOrEmpty(people)) { video.People = people.Split(new[] { ';', '/' }, StringSplitOptions.RemoveEmptyEntries) .Where(i => !string.IsNullOrWhiteSpace(i)) .Select(i => new PersonInfo { Name = i.Trim(), Type = PersonType.Actor }) .ToList(); } } } if (!video.ProductionYear.HasValue) { var year = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/OriginalReleaseTime"); if (!string.IsNullOrWhiteSpace(year)) { int val; if (int.TryParse(year, NumberStyles.Integer, _usCulture, out val)) { video.ProductionYear = val; } } } }
/// <summary> /// Fetches data from the tags dictionary /// </summary> /// <param name="audio">The audio.</param> /// <param name="tags">The tags.</param> private void FetchDataFromTags(Audio audio, Dictionary <string, string> tags) { var title = FFProbeHelpers.GetDictionaryValue(tags, "title"); // Only set Name if title was found in the dictionary if (!string.IsNullOrEmpty(title)) { audio.Name = title; } if (!audio.LockedFields.Contains(MetadataFields.Cast)) { audio.People.Clear(); var composer = FFProbeHelpers.GetDictionaryValue(tags, "composer"); if (!string.IsNullOrWhiteSpace(composer)) { foreach (var person in Split(composer, false)) { audio.AddPerson(new PersonInfo { Name = person, Type = PersonType.Composer }); } } } audio.Album = FFProbeHelpers.GetDictionaryValue(tags, "album"); var artist = FFProbeHelpers.GetDictionaryValue(tags, "artist"); if (string.IsNullOrWhiteSpace(artist)) { audio.Artists.Clear(); } else { audio.Artists = SplitArtists(artist) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); } var albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "albumartist") ?? FFProbeHelpers.GetDictionaryValue(tags, "album artist") ?? FFProbeHelpers.GetDictionaryValue(tags, "album_artist"); if (string.IsNullOrWhiteSpace(albumArtist)) { audio.AlbumArtists = new List <string>(); } else { audio.AlbumArtists = SplitArtists(albumArtist) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); } // Track number audio.IndexNumber = GetDictionaryDiscValue(tags, "track"); // Disc number audio.ParentIndexNumber = GetDictionaryDiscValue(tags, "disc"); audio.ProductionYear = FFProbeHelpers.GetDictionaryNumericValue(tags, "date"); // Several different forms of retaildate audio.PremiereDate = FFProbeHelpers.GetDictionaryDateTime(tags, "retaildate") ?? FFProbeHelpers.GetDictionaryDateTime(tags, "retail date") ?? FFProbeHelpers.GetDictionaryDateTime(tags, "retail_date") ?? FFProbeHelpers.GetDictionaryDateTime(tags, "date"); // If we don't have a ProductionYear try and get it from PremiereDate if (audio.PremiereDate.HasValue && !audio.ProductionYear.HasValue) { audio.ProductionYear = audio.PremiereDate.Value.ToLocalTime().Year; } if (!audio.LockedFields.Contains(MetadataFields.Genres)) { FetchGenres(audio, tags); } if (!audio.LockedFields.Contains(MetadataFields.Studios)) { audio.Studios.Clear(); // There's several values in tags may or may not be present FetchStudios(audio, tags, "organization"); FetchStudios(audio, tags, "ensemble"); FetchStudios(audio, tags, "publisher"); } audio.SetProviderId(MetadataProviders.MusicBrainzAlbumArtist, FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Artist Id")); audio.SetProviderId(MetadataProviders.MusicBrainzArtist, FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Artist Id")); audio.SetProviderId(MetadataProviders.MusicBrainzAlbum, FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Id")); audio.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Group Id")); }